Skip to content

fix(plugin-trees): consume dirty marks so scene-ready fires for plant scenes#476

Merged
wass08 merged 1 commit into
mainfrom
fix/plugin-instanced-dirty-clear
Jul 9, 2026
Merged

fix(plugin-trees): consume dirty marks so scene-ready fires for plant scenes#476
wass08 merged 1 commit into
mainfrom
fix/plugin-instanced-dirty-clear

Conversation

@wass08

@wass08 wass08 commented Jul 8, 2026

Copy link
Copy Markdown
Collaborator

What does this PR do?

Instanced plant kinds (trees:tree, trees:grass, trees:flower) never cleared their scene-store dirty marks. FloorElevationSystem deliberately leaves the mark for any kind with a def.system ("the downstream consumer will clear it"), but InstancedKindSystem never participated in the dirty protocol — so the marks lived forever, hasPendingSceneBuildWork() never went false, and the Viewer's scene-ready signal stalled at SCENE_READY_MAX_WAIT_FRAMES (180 frames) on every scene containing a plant.

The visible cost: on the headless bake worker (SwiftShader, ~1s frames) every plant-containing scene paid ~190s of pure cap-wait per bake — measured as pageSettleMs ≈ 193s on prod bakes of a plant-heavy scene, regardless of warm caches.

The fix adds a priority-2 useFrame pass to InstancedKindSystem that clears this kind's dirty marks:

  • priority 2 — runs after FloorElevationSystem's priority-1 lift pass in the same frame, so the mark survives long enough for the lift to apply (same contract GeometrySystem/ItemSystem rely on);
  • only for registered nodes — a node whose selection proxy isn't in sceneRegistry yet keeps its mark for a later frame;
  • instances rebuild synchronously from the store (the useMemo chain), so a rendered node is already "built" — there is no async work the mark needs to outlive.

How to test

  1. bun dev, open a scene containing trees/grass — verify plants render, hover/selection outlines work, and moving a plant on an elevated slab still sits at the right height.
  2. Bake a plant-heavy scene headlessly (bun apps/community/scripts/bake.ts <id> from private-editor) and check the [bake:timing] line: pageSettleMs should be actual load time (~seconds), not ~180 × frame-time.
  3. Export correctness: baking the same scene with and without this change produces a byte-identical GLB (verified on a 35 MB plant-heavy prod scene — 35,193,812 bytes both ways; settle dropped 22.5s → 11.6s locally).

Screenshots / screen recording

N/A — non-visual change (render output and exported GLB verified byte-identical; only the readiness signal timing changes).

Checklist

  • I've tested this locally with bun dev
  • My code follows the existing code style (run bun check to verify)
  • I've updated relevant documentation (if applicable)
  • This PR targets the main branch

🤖 Generated with Claude Code


Note

Low Risk
Small, localized change to dirty-mark lifecycle timing; no auth/data paths and render output is intended to stay identical.

Overview
Instanced plant kinds (trees:tree, grass, flowers) now participate in the scene dirty-node protocol by clearing their own marks after each frame, instead of leaving them set indefinitely.

InstancedKindSystem gains a priority-2 useFrame pass that calls clearDirty for dirty nodes of this kind that are already in sceneRegistry. That runs after FloorElevationSystem’s priority-1 lift (same ordering other def.system consumers use). Nodes without a mounted proxy keep their mark until registration catches up.

Effect: hasPendingSceneBuildWork() can go false when only plants were dirty, so the viewer scene-ready signal and headless bakes no longer sit at the ~180-frame cap; rendering is unchanged because instances already rebuild synchronously from the store.

Reviewed by Cursor Bugbot for commit 93bcdae. Bugbot is set up for automated code reviews on this repo. Configure here.

… scenes

Instanced plant kinds (trees/grass/flowers) never cleared their dirty
marks: FloorElevationSystem deliberately leaves the mark for kinds with
a def.system, expecting that system to clear it after its own work, but
InstancedKindSystem never participated in the dirty protocol. The marks
lived forever, hasPendingSceneBuildWork() never went false, and the
Viewer's scene-ready signal stalled at SCENE_READY_MAX_WAIT_FRAMES on
every plant-containing scene — measured on the headless bake worker as
~190s (180 frames x ~1s SwiftShader frames) of pure cap-wait per bake.

Clear the marks in a priority-2 useFrame pass: after the priority-1
floor-elevation lift in the same frame, and only for nodes whose proxy
is registered (instances rebuild synchronously from the store, so a
rendered node is already built). Validated by baking Wawa House
locally: settle 22.5s -> 11.6s (the remainder is genuine asset
loading), exported GLB byte-identical with and without the fix.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@wass08 wass08 merged commit 58339c6 into main Jul 9, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant