fix(extract): incremental delta() misses active-child changes (Switch.whichChoice, LOD level)#32
Merged
Merged
Conversation
classifyDirty mapped a Switch.whichChoice change to DirtyField, but a Switch is in neither geomDeps_ nor materialDeps_, so SceneExtractor::delta() ignored it — no subtree re-walk. Incremental consumers (the OpenGL PoC) never saw the active-child swap; only full-snapshot consumers (cpuraster, which re-extracts every frame) did. The extractor already reads whichChoice on a full walk, so only the INCREMENTAL channel was affected — the prior 'Switch audited clean' note covered fullSnapshot. Fix: classifyDirty maps whichChoice -> DirtyChildren, so delta() re-walks the Switch subtree and emits removed(old child)+added(new child). Found via a Switch + IntegerSequencer demo rendered through the PoC's delta() path. - runtime/events/X3DExecutionContext.hpp: whichChoice -> DirtyChildren - scene_extractor_t8_test.cpp: case 5 (flip 0->1->-1) - findings.yaml: SW-DELTA-1 (fixed); TIME-ORIGIN-1 documents the app-relative tick(now) time-origin contract (= Castle's timeOriginAtLoad by default) - system-time.md: 'Time origin' section
…ra motion) Sibling of SW-DELTA-1 for VIEW-DEPENDENT (vs settable-field) active-child selection, found by differential testing the OpenGL PoC (incremental delta()) against cpuraster (full snapshot): a moving viewer over a static LOD swapped the level in cpuraster but stayed frozen in the PoC. The rendered LOD level is computed from the camera, not a settable field, so it never reaches classifyDirty. Fix: ViewDependentSystem calls the new X3DExecutionContext::markActiveChildChanged when an LOD's announced level flips, so delta() re-walks the LOD subtree and swaps the active child. - X3DExecutionContext.hpp: markActiveChildChanged(node) -> DirtyChildren|DirtyBounds - ViewDependentSystem.hpp: mark on level flip - scene_extractor_t8_test.cpp: case 6 (move Viewpoint d=10 -> d=1 -> swap) - findings.yaml: LOD-DELTA-1 (camera-motion fixed; LOD under an animated parent transform / multi-path USE remains per-path-deferred, same root as Billboard)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A bug class in the incremental
SceneExtractor::delta()channel: a scene change updates correctly for full-snapshot consumers (cpuraster re-extracts every frame) but is invisible to incrementaldelta()consumers (the OpenGL PoC), which render stale state.delta()only re-walks a subtree onDirtyChildren; a change is missed when it alters rendering but sits on a grouping node in neithergeomDeps_normaterialDeps_and isn't classifiedDirtyChildren.Found by differential testing: render the same animated scene through
poc --animate(incremental delta) andcpuraster --animate(full-snapshot oracle); where the oracle animates but the PoC is frozen → bug.Two members fixed
DirtyFieldclassifyDirtymapswhichChoice→DirtyChildrenclassifyDirty(the cascade field-observer only fires for fields with a setter;level_changedis outputOnly)ViewDependentSystemcalls newX3DExecutionContext::markActiveChildChanged()on a level flipRegression tests:
scene_extractor_t8_test.cppcases 5 (Switch flip 0→1→−1) and 6 (LOD Viewpoint d=10→d=1 swap).Also
system-time.md+ findingTIME-ORIGIN-1): the SDK never bakes a time origin —tick(now)lets the consumer choose. App-relative feeding (both renderers) gives Castle Engine'stimeOriginAtLoadbehaviour by default; strict epoch feeding gives literalSFTimesemantics.SW-DELTA-1,LOD-DELTA-1(+ the stale 'Switch audited clean' note corrected to scope it to full-snapshot).Known residual (deferred, documented in LOD-DELTA-1)
LOD under an animated parent transform or multi-path USE stays stale in
delta()—ViewDependentSystem's per-node level uses the first-path/identity transform (M2C-1); the accurate level is per-path in the extractor walk. Same per-path view-dependent gap as Billboard orientation (already deferred).Cleared by the harness (not bugs): Material color/transparency, light intensity, Coordinate morph, nested Switch.
Split out of #31 (OpenGL renderer work); these are pure SDK fixes with no dependency on the poc changes.