* fix(core): motion blur preview no longer slows down later in the timeline
With data-motion-blur-events="on", which Studio's Subtle and Strong write,
both blur tiers ran timeline callbacks at every per-frame pose read. Each
frame's reads step back from half a shutter ahead to the frame, so a
callback that keeps state and replays from zero on a backward seek (a
ripple simulation, for example) redid its whole history most frames, and
the cost grew with the playhead.
The mover scan still runs callbacks, so motion set from a render(t)
callback is still found. With events on, it also scans once without them,
and the per-frame reads run callbacks only when the two scans disagree.
The eventful scan runs first, so tweens take their start values after the
callbacks before them and a one-off call fires as often as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(core): keep the motion blur comments to the invariant
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(core): motion blur reads run callbacks when a callback moves any box
Comparing which elements each scan finds missed a callback that pans a
mover's ancestor while a tween also changes that ancestor: both scans
found the same movers and the same changing elements, so the reads left
the pan out. The scans now compare every changing element's box at each
step, and the reads run callbacks when any step differs.
Raises @hyperframes/core's unpacked size budget to the measured size plus
the usual 5%: main already sat 0.98% over the old budget, and this change
adds 1,232 bytes, which tipped it past the 1% tolerance.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(core): silent motion blur reads land back on the frame as its own seek left it
After silent pose reads, the restore seek back to the frame left GSAP in a
different state than the frame's own eventful seek did:
- A step exactly on the frame (a .call() or set) was re-armed, so the next
frame fired it again. Calls fired 2-5 times per crossing, and a toggle
could end inverted.
- In renders, which keep no GSAP structure, landing back on the frame from
later skipped same-time steps' authored order, so the frame showed the
first of two sets instead of the last.
Events-off blur always read silently and had both. This PR moves events-on
roots without callback-driven motion (the Studio default) onto that path,
so both are fixed here.
The host's new holdFrame snapshots every landing mark on the root and scene
timelines before the reads, and its restore seeks back silently, redraws in
authored order where there is no structure, and puts the marks back. Each
call now fires once per frame crossing, as without blur, and same-time sets
land as authored.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(core): a held render frame draws its scenes first, so the root lands last
holdFrame redrew the root and then the scenes, the reverse of the frame's
own seek. When a scene and the root tween one property, the frame landed
on the scene's value instead of the root's.
The hold and landing move into the GSAP adapter as holdFrameMarks, which
takes the scenes and the root separately, so the order lives in one place
a real-GSAP test can pin.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>