Repository navigation
fix(core): exported scenes keep their frames when later scenes are added - #6035
Conversation
jrusso1020
left a comment
There was a problem hiding this comment.
Reviewed 0f3373fefee203b3fad4c9d0a95f4005aa364f3a; no earlier reviews or findings. No blocker found.
init.ts:4641-4650 remeasures after siblings are armed and before the root seek, fixing the cached-duration boundary itself. shiftChildren(0) invalidates GSAP's cached length without moving start times or labels. The no-sibling path remains unchanged and the methods remain optional for partial timeline implementations. Existing seekTimelineAndAdapters cleanup at init.ts:4728-4735 still pauses every borrowed sibling in finally, including a sibling whose play throws.
Audited: the full three-file delta and new regression; targeted surrounding root nesting/duration padding, renderSeek, duration clamp, standalone scene clock/seek and cleanup paths. This is not an end-to-end reread of the 5,600-line init module. All changes are confined to the activation seam and its structural type.
Executed: actual GSAP control reproduced duration 10 → cached 0 while children rest paused → still 0 after play → restored 10 after the added invalidation/read. Separate checks covered four scene counts across seven forward/backward/repeated seeks, preserving start times and labels. A fresh adversarial pass checked the actual activation helper, repeat/yoyo cases and cleanup, with no introduced failure found.
Limits: did not run the complete runtime Vitest regression or capture the reported 575-second project/flash frame; those remain author evidence. No full Chromium, HDR, distributed render or pixel proof. The added two-scene/three-scene tests correctly target each onUpdate time rather than merely asserting a total duration.
Verdict: APPROVE
Reasoning: Remeasuring the root after rearming repairs the stale-length seek clamp while preserving placement and borrowed-sibling cleanup. Focused GSAP behavior checks and an independent adversarial pass found no additional blocker.
— hf-review
Edit accuracy: accurate 271 (base branch 271), smooth 152 of thoseThe gate passes. Quarantined, measured but not gated (0) Unstable (1)
|
jrusso1020
left a comment
There was a problem hiding this comment.
Re-reviewed exact head 54ed97e2015cdeff34f1c6a1797fcbdebfbfe127 against my approved 0f3373fe (review 5601851719). The single follow-up commit changes only test formatting and the explanatory root-duration comment.
packages/core/src/runtime/init.ts:4642 retains the same invalidation/remeasurement calls after sibling activation. The two regression cases and their exact scene-time assertions remain intact; types.ts is byte-identical. I read the complete updated test and surrounding activation helper, and compared TypeScript-transformed JavaScript for all three PR files: each is identical to the approved revision after whitespace/comment removal. No runtime or test-assertion change, and no new blocker.
The prior focused GSAP checks and independent adversarial pass still apply to the unchanged implementation; I did not rerun the full runtime Vitest suite or the author's long-project capture. This supersedes my approval's commit association, preserving its stated validation limits.
Verdict: APPROVE
Reasoning: The follow-up is mechanical and preserves the previously reviewed root-length fix and regression assertions.
— hf-review
What
Exporting a scene gives the same frames no matter how many scenes come after it. Before, adding a scene to a long project could change the export of untouched scenes: single frames from the wrong moment (a visible flash), one-frame shifts, and animations drawn at the wrong phase.
Why
The runtime keeps scene timelines paused at rest and unpauses them only across a root seek. GSAP counts only unpaused children in a timeline's length, and
play()on a child does not re-measure the parent. When a scene after the playhead first renders during a seek (for example anoverwrite: "auto"tween), it marks the root's length stale; the next length read happens while the scenes rest paused, so the root caches a length of 0. Every later root seek is then clamped to 0, so each scene is drawn once at its local time 0 before its own redraw. Stateful scenes keep that wrong state, which is the flash.How
activateSiblingTimelinesinpackages/core/src/runtime/init.tsnow re-measures the root right after it unpauses the scenes (shiftChildren(0)marks the length dirty,totalDuration()recomputes it with the scenes counted). It is the one place a render seek arms the scenes, so every capture path that driveswindow.__hf.seek(local render, chunked/distributed render, HDR compositing) gets it. It runs once per seek and only when there are scenes to arm.Test plan
packages/core/src/runtime/init.rootLength.test.ts: three back-to-back scenes, one with an overwriting tween; renders frames 210 to 214 and checks the second scene is drawn only at its own times. Red without the fix, and red with only theshiftChildrenline removed. Green 3 runs in a row.