Keep a driven video ready while its sprite is off (#404)

A cut between two videos flashed white: the sprite switched on had
no picture yet, because its player only opened then or had been
dropped meanwhile. A driven video now waits muted at its time while
its sprite is off, in the editor and in a run.

- A disabled sprite with Video clock Driven asks for its player
  without drawing it. A Free video still opens when shown.
- A run's draw list keeps disabled driven sprites, marked as off.
  Before, collectWorldItems dropped every disabled object, so the
  game view never held a player for them.
- VideoLibrary.wear() counts as asking. The sweeper dropped a held
  player after 2 to 4 s and it was loading again at the cut.
- The speed of the asked time is read over a window that grows to
  500 ms. Over 100 ms one 1/60 s step more or less read as 17 % off,
  the playback rate jumped and the sound dropped out.
- A stream feeds every packet it has loaded to the decoder at once
  and waits once. One round trip per 20 ms packet let the decoder
  fall behind on a busy main thread, which made e2e 34 flaky in CI.

Tests:
- Unit streaming, video-library, video-follow: green, 4 new
- e2e 34 + 66 (atx): ALL PASSED, stream skipped 0, 66m new
- e2e 66 (atx), two runs: ALL PASSED, 25 checks, 66n new
- Gate (atx): CHECK EXIT 0, 297 files, 4557 tests
- Counter-checks: without the draw list fix 66n finds no player,
  without the wear() fix and with the old speed reading the new
  unit tests turn red

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
adminandClaude Opus 5.5 committed 2026-10-11 15:37:16 +02:00
1 parent 774207208c
commit 43fd22a763
11 files changed
+202 -40

No files matched your search

+43
View File
@@ -383,6 +383,49 @@ export async function video(t) {
);
await showTab('scene');
// 66m. Switched off, a driven sprite keeps its player ready at its Video time, longer than a player
// nobody asks for lives: switched on, the first frame shows 1.2 s blue, not its plain colour.
// 4.5 s, as the sweeper looks only every 2 s and lets go of what is 2 s idle.
const switchLate = (on) =>
mutate(`
const late = __gm.state.activeScene.objects.find((o) => o.id === '${LATE}');
late.overrides = { ...late.overrides, videoTime: 1.2, enabled: ${on} };`);
await switchLate(false);
await sleep(4500);
const readied = (await json('__gm.videos()')).find((v) => v.key.includes(LATE));
await switchLate(true);
await t.evaluate('__gm.afterFrames(1)');
const firstShown = await colorAt(await json(`__gm.objectRect('${LATE}')`));
check(
'a driven sprite switched off keeps its video ready and shows its picture the frame it is switched on',
readied?.ready === true &&
readied.paused &&
readied.muted &&
Math.abs(readied.time - 1.2) < 0.05 &&
firstShown === COLORS.blue,
JSON.stringify({ readied, firstShown }),
);
// 66n. The same in a run: the world used to leave a switched-off object out of what the Game tab
// draws, so a cut to it loaded the video only then and showed white meanwhile. Nor did wearing
// the player count as asking for it: let go after 2 to 4 s, it was opened again.
await switchLate(false);
const sceneKeys = new Set((await json('__gm.videos()')).map((v) => v.key));
await play();
await sleep(4500);
const inRun = (await json('__gm.videos()')).filter((v) => v.key.includes(LATE) && !sceneKeys.has(v.key));
await stop();
await switchLate(true);
check(
'in a run, a driven sprite switched off keeps its video ready too',
inRun.length === 1 &&
inRun[0].ready &&
inRun[0].paused &&
inRun[0].muted &&
Math.abs(inRun[0].time - 1.2) < 0.05,
JSON.stringify(inRun),
);
await mutate(`
const p = __gm.state.project;
const gone = ['${OBJECT}', '${EARLY}', '${LATE}'];