fix(sim): double-buffer video so morph->loop handoff doesn't stall

Operator eyeball: a jolt/pause when the morph ends and the next altitude's loop
loads. Root cause: swapping vid.src to the base + seeking to the tail reloaded the
ON-SCREEN element (decode + seek stall). Now a dedicated #vid-loop element carries
the steady-state base loop; during a scrub it is PRELOADED to the clip we're heading
toward, seeked to the tail and paused on that exact frame. The paint shader reads the
active source (busy ? morph : loop), so landing is an instant source swap — no reload,
no seek. E2E asserts the loop element is armed + playing from ~3s after landing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
BenStullsBets
2026-06-27 16:58:08 -07:00
parent 0752746f7b
commit ea530b3df6
4 changed files with 78 additions and 38 deletions
+7 -6
View File
@@ -93,19 +93,20 @@ test("a landed clip loops from the morph-tail offset (no jump-back to frame 0)",
const start = (await page.locator("#scale-name").textContent())!;
await wheelOnStage(page, 60);
await page.waitForFunction((s) => document.querySelector("#scale-name")?.textContent !== s, start, { timeout: 20000 });
// After landing, the base loop is armed (loopTail="1") and seeked to the tail
// (~3s) so it continues the morph's last frame instead of restarting at 0.
// After landing, the dedicated loop element (#vid-loop) is armed (loopTail="1") and
// playing from the tail (~3s) — preloaded during the scrub, so the landing was a
// source swap, not a reload that restarts at 0.
await page.waitForFunction(
() => { const v = document.querySelector("video") as HTMLVideoElement; return v.dataset.loopTail === "1" && v.currentTime >= 2.5; },
() => { const v = document.querySelector("#vid-loop") as HTMLVideoElement; return v.dataset.loopTail === "1" && v.currentTime >= 2.5; },
null,
{ timeout: 10000 },
);
const v = await page.evaluate(() => {
const el = document.querySelector("video") as HTMLVideoElement;
return { t: el.currentTime, loopTail: el.dataset.loopTail, morph: el.dataset.morph || "" };
const el = document.querySelector("#vid-loop") as HTMLVideoElement;
return { t: el.currentTime, loopTail: el.dataset.loopTail, paused: el.paused };
});
expect(v.morph).toBe(""); // on the base loop, not a morph
expect(v.loopTail).toBe("1"); // tail-loop armed
expect(v.paused).toBe(false); // the loop is actually playing
expect(v.t).toBeGreaterThanOrEqual(2.5); // looping from ~3s, not 0
});