fix(reduce-motion): re-roll the clip on an instant-cut altitude change

With reduce motion on, selecting a new altitude jumps (autoScrub's reduced
branch → setPos) and skips the segment that would re-roll the destination clip,
so activeClipId stayed on the previous altitude's clip and the video didn't
change. Self-heal at the landing point: if the landed altitude's pool doesn't
contain activeClipId, pickPoolClip a fresh one. TDD: new altitude-lock e2e.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
BenStullsBets
2026-06-30 14:18:16 -07:00
parent fafb498d6f
commit ecd444b53b
2 changed files with 20 additions and 0 deletions
+14
View File
@@ -108,6 +108,20 @@ test("dragging the dial scrubs morph currentTime and audio gains", async ({ page
await page.mouse.up();
});
test("reduce motion: selecting a new altitude still swaps to a fresh clip (instant cut)", async ({ page }) => {
await boot(page);
// #scale-name reads "<scale> · <clip> (i/n)"; extract the clip id.
const clipId = (s: string) => (s.split("·")[1] || "").split("(")[0].trim();
await page.locator('label[for="reduce-motion"]').click(); // engage reduce motion
await expect(page.locator("#reduce-motion")).toBeChecked();
const before = (await page.locator("#scale-name").textContent())!;
await wheelOnStage(page, 60); // descend one altitude (instant cut)
await page.waitForFunction(() => /orbit/.test(document.querySelector("#scale-name")?.textContent || ""), null, { timeout: 20000 });
const after = (await page.locator("#scale-name").textContent())!;
expect(after).toContain("orbit");
expect(clipId(after)).not.toBe(clipId(before)); // the clip actually changed (not stuck on the old scale's clip)
});
test("wheel auto-scrubs one altitude and locks", async ({ page }) => {
await boot(page);
const start = (await page.locator("#scale-name").textContent())!;
+6
View File
@@ -1015,6 +1015,12 @@ function setPos(next) {
const { lo, frac } = HEFScrub.segmentOf(pos);
if (frac === 0) { // settled exactly on an altitude → lock + base loop + single soundtrack
busy = false;
// If we JUMPED straight here (a reduced-motion instant cut skips the segment that
// would re-roll the destination), activeClipId still points at the previous
// altitude's clip. Re-roll from this altitude's pool so the video actually changes.
if (!poolIds(ring.scales[ringIndex]).includes(activeClipId)) {
activeClipId = pickPoolClip(ringIndex, activeClipId);
}
delete vid.dataset.morph; // clear so re-entering the same segment reloads the morph (not the base)
currentClipId = null; // force ensureClipMedia to reload + loop the locked clip
// Land the loop on the frame the morph last showed for THIS travel direction: