fix(sim): kill the reverse-landing frame jump (D3) #30

Merged
benstull merged 1 commits from fix/d3-reverse-landing-jump into main 2026-06-30 14:17:56 +00:00
Owner

Ascending the altitude dial and landing on the higher-altitude clip jumped ~3s. A baked morph transitions/{h}__{l}.mp4 spans src@0 (frac 0) -> dst@~LOOP_TAIL_S (frac 1), scrubbed bidirectionally, but the steady loop always seeked LOOP_TAIL_S (3.0s) on landing — correct for a descending landing (lands on the dst tail), a ~3s jump for an ascending one (lands on the src head).

Fix (app-side, no 154-morph re-bake): pure HEFScrub.loopLandFrame(dir, loopTailS) returns 0 when ascending; loadLoop stores it as dataset.loopStart and the timeupdate/ended wrap handlers honor it (bases are already seamless crossfade-loops, so [0,dur] wraps cleanly). Settle lands explicitly on the directional frame so a fast ascent never jumps.

Verify: node unit (loopLandFrame) + a new reverse-landing e2e (loop anchors at head, loopStart 0); existing forward loops-from-tail e2e still green (altitude-lock 12/12). pytest 299 pass (3 pre-existing env-only failures, untouched).

Session 0028.

🤖 Generated with Claude Code

Ascending the altitude dial and landing on the higher-altitude clip jumped ~3s. A baked morph `transitions/{h}__{l}.mp4` spans `src@0` (frac 0) -> `dst@~LOOP_TAIL_S` (frac 1), scrubbed bidirectionally, but the steady loop always seeked `LOOP_TAIL_S` (3.0s) on landing — correct for a descending landing (lands on the dst tail), a ~3s jump for an ascending one (lands on the src head). **Fix (app-side, no 154-morph re-bake):** pure `HEFScrub.loopLandFrame(dir, loopTailS)` returns 0 when ascending; `loadLoop` stores it as `dataset.loopStart` and the timeupdate/ended wrap handlers honor it (bases are already seamless crossfade-loops, so `[0,dur]` wraps cleanly). Settle lands explicitly on the directional frame so a fast ascent never jumps. **Verify:** node unit (`loopLandFrame`) + a new reverse-landing e2e (loop anchors at head, `loopStart` `0`); existing forward `loops-from-tail` e2e still green (altitude-lock 12/12). pytest 299 pass (3 pre-existing env-only failures, untouched). Session 0028. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
benstull added 1 commit 2026-06-30 14:17:44 +00:00
Ascending the altitude dial and landing on the higher-altitude clip jumped ~3
seconds. A baked morph `transitions/{h}__{l}.mp4` spans src@0 (frac 0) ->
dst@~LOOP_TAIL_S (frac 1) and is scrubbed bidirectionally, but the steady loop
element always seeked LOOP_TAIL_S (3.0s) on landing. Descending lands on dst at
its tail (matches the seek — the s0026 loop-from-tail fix); ascending lands on
src at the morph's HEAD (src@0), so seeking the tail was a 3s jump.

Make the loop landing frame direction-aware: pure `HEFScrub.loopLandFrame(dir,
loopTailS)` returns 0 when ascending, loopTailS otherwise. `loadLoop` takes a
`landFrame`, stores it as `dataset.loopStart`, and the timeupdate/ended wrap
handlers honor it (the base clips are already seamless crossfade-loops, so a
reverse landing wraps [0,dur] cleanly). The settle path lands explicitly on the
direction's frame so a fast ascent never jumps even if the heading clip wasn't
preloaded in time. App-side only — no 154-morph LFS re-bake.

Verify: node unit (loopLandFrame) + a new reverse-landing e2e asserting the loop
anchors at the head (loopStart "0"), and the existing forward "loops from tail"
e2e still green (12/12 altitude-lock).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
benstull merged commit 582183db2f into main 2026-06-30 14:17:56 +00:00
Sign in to join this conversation.