Let free videos wait while a run waits for its driven videos (#437)
A run that waits for its driven videos does not tick its world, so the clock of every free video sprite stands. Its player played on anyway. The picture ran ahead of the clock by the length of the wait, so On video finished came late and a Pause video sent the picture back. - VideoLibrary.setHeld holds the free players next to the toolbar pause. PlaySession and Player switch it with the wait at the start, after a Load scene and while a recording sets up its encoders. - The hold starts in waitForVideos, not on the next tick. Otherwise a free video started at Play would be a frame ahead. - A free player without its first picture plays on until it has one. Held at once, it would never get one. - startRun and endRun clear the hold, so a run that ends while it waits leaves no held preview behind. Found on the way: - Resuming called play() on a clip without Loop that already stood at its end, and play() starts such a video over. This happened after the toolbar pause already and would have happened after every waiting Load scene. Such a clip now stays at its end. Tests: - Unit video-library 14/14, 2 of them new. 7 deliberate breaks, all red. - e2e 66 (atx): ALL PASSED, 40 checks. New 66t: the driven video came 1.5 s late, the wait took 1686 ms. The free video stayed red all along, blue came at 0.983 s, white at 1.483 s and On video finished at 2.017 s. Without the hold the free video was white in the first frame and only 66t was red. - Gate (atx): CHECK EXIT 0, 339 files, 5253 tests. The guard for the clip end came in while the gate ran. It is checked with tsc, lint and its test file with 3 deliberate breaks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
5c0d52da66
commit
beb95c17a7
5 files changed
+236
-15
No files matched your search
+88
-2
@@ -29,6 +29,7 @@ const STEER = 'vd66-steer';
|
||||
const STEERED = 'vd66-steered';
|
||||
const STEER_LOGIC = 'vd66-steer-logic';
|
||||
const STEER_BRAIN = 'vd66-steer-brain';
|
||||
const SLOW = 'vd66-slow';
|
||||
const LANE = LANES.video;
|
||||
const COLORS = { red: '#fe0000', green: '#00ff01', blue: '#0000ff', white: '#ffffff' };
|
||||
|
||||
@@ -710,7 +711,7 @@ export async function video(t) {
|
||||
// video sends it to 0.5 s (green) and it stays held there, Play video plays on from there:
|
||||
// blue, then white held at the end of a video without Loop, and On video finished is heard
|
||||
// once. Read from the Game tab, the frames after Play in the page. No driven sprite is left
|
||||
// in the scene: the run would wait for its picture, free videos playing meanwhile, keys lost.
|
||||
// in the scene: the run would wait for its picture, and keys pressed meanwhile are lost.
|
||||
await mutate(`
|
||||
const p = __gm.state.project;
|
||||
const scene = __gm.state.activeScene;
|
||||
@@ -824,9 +825,94 @@ export async function video(t) {
|
||||
JSON.stringify({ finished, frames: playedFrames.join(' ') }),
|
||||
);
|
||||
|
||||
// 66t. A driven sprite whose video comes 1.5 s late holds the run at its start (#437). The free
|
||||
// video beside it waits too, at its first picture: red all through the wait, then its colours
|
||||
// at their game times, and On video finished (it logs its Time) with the end of its last
|
||||
// picture, not 1.5 s after it. The video elements opened at Play get their file 1.5 s late.
|
||||
await mutate(`
|
||||
const p = __gm.state.project;
|
||||
const gone = ['${OBJECT}', '${EARLY}', '${LATE}', '${STEERED}', '${STEER_BRAIN}'];
|
||||
p.assets.find((a) => a.id === '${STEER_LOGIC}').graph = ${JSON.stringify({
|
||||
nodes: [
|
||||
node('vd66t-end', 'OnVideoFinished', 0, 0),
|
||||
node('vd66t-time', 'Time', 0, 160),
|
||||
node('vd66t-log', 'Log', 260, 0),
|
||||
],
|
||||
edges: [
|
||||
edge('vd66t-e1', 'vd66t-end', 'exec', 'vd66t-log', 'exec'),
|
||||
edge('vd66t-e2', 'vd66t-time', 'time', 'vd66t-log', 'message'),
|
||||
],
|
||||
})};
|
||||
${MAKE_OBJECT}
|
||||
const scene = __gm.state.activeScene;
|
||||
const cam = scene.objects.find((o) => o.id === 'o3').position;
|
||||
const slow = make('${SLOW}', '${DRIVEN}', '', 0, '${SLOW}');
|
||||
Object.assign(slow.position, { x: cam.x - 200, y: cam.y - 100, z: 900 });
|
||||
scene.objects.push(slow);`);
|
||||
await t.evaluate(`(() => {
|
||||
const make = document.createElement;
|
||||
const src = Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, 'src');
|
||||
document.createElement = function (tag, ...rest) {
|
||||
const element = make.call(document, tag, ...rest);
|
||||
if (String(tag).toLowerCase() === 'video')
|
||||
Object.defineProperty(element, 'src', {
|
||||
configurable: true,
|
||||
get: () => src.get.call(element),
|
||||
set: (url) => setTimeout(() => src.set.call(element, url), 1500),
|
||||
});
|
||||
return element;
|
||||
};
|
||||
setTimeout(() => (document.createElement = make), 1000);
|
||||
window.__vd66t = [];
|
||||
(function read() {
|
||||
const info = __gm.playInfo();
|
||||
const r = info.playing && __gm.gameRect('${STEERED}');
|
||||
if (r) window.__vd66t.push({ color: __gm.pixelAt(Math.round(r.x), Math.round(r.y)), waiting: info.waiting, time: info.time, at: performance.now() });
|
||||
if (window.__vd66t.length < 600 && !(info.time > 2.4)) requestAnimationFrame(read);
|
||||
})();
|
||||
})()`);
|
||||
const loggedFrom = t.consoleLog.length;
|
||||
await play();
|
||||
await waitFor(
|
||||
() => json('__gm.playInfo().time'),
|
||||
(time) => time > 2.4,
|
||||
40,
|
||||
);
|
||||
await sleep(200);
|
||||
const slowFrames = await json('window.__vd66t.splice(0)');
|
||||
const logged = t.consoleLog
|
||||
.slice(loggedFrom)
|
||||
.filter((line) => line.includes(`[behavior ${STEER_BRAIN}]`))
|
||||
.map((line) => Number(line.split(' ').at(-1)));
|
||||
await stop();
|
||||
const slowWait = slowFrames.filter((f) => f.waiting);
|
||||
const slowRun = slowFrames.filter((f) => !f.waiting && f.time > 0);
|
||||
const firstAt = (color) => slowRun.find((f) => f.color === color)?.time ?? null;
|
||||
const [blueAt, whiteAt] = [firstAt(COLORS.blue), firstAt(COLORS.white)];
|
||||
check(
|
||||
'a free video waits with a run that waits for a driven one, and On video finished comes with its last picture',
|
||||
slowWait.length > 0 &&
|
||||
slowWait.at(-1).at - slowWait[0].at > 1000 &&
|
||||
slowWait.every((f) => f.color === COLORS.red) &&
|
||||
slowRun[0]?.color === COLORS.red &&
|
||||
blueAt !== null &&
|
||||
Math.abs(blueAt - 1) < 0.15 &&
|
||||
whiteAt !== null &&
|
||||
Math.abs(whiteAt - 1.5) < 0.15 &&
|
||||
logged.length === 1 &&
|
||||
Math.abs(logged[0] - 2) < 0.15,
|
||||
JSON.stringify({
|
||||
waited: slowWait.length && Math.round(slowWait.at(-1).at - slowWait[0].at),
|
||||
waitColors: [...new Set(slowWait.map((f) => f.color))],
|
||||
blueAt,
|
||||
whiteAt,
|
||||
logged,
|
||||
frames: slowRun.map((f) => `${f.color}@${f.time.toFixed(2)}`).join(' '),
|
||||
}),
|
||||
);
|
||||
|
||||
await mutate(`
|
||||
const p = __gm.state.project;
|
||||
const gone = ['${OBJECT}', '${EARLY}', '${LATE}', '${STEERED}', '${STEER_BRAIN}', '${SLOW}'];
|
||||
for (const holder of p.assets) if (holder.objects) holder.objects = holder.objects.filter((o) => !gone.includes(o.id));
|
||||
p.assets = p.assets.filter((a) => ![ '${SPRITE}', '${DRIVEN}', '${CUT}', '${BUS}', '${STEER}', '${STEER_LOGIC}', ${JSON.stringify(assetId)} ].includes(a.id));
|
||||
__gm.state.select(null);
|
||||
|
||||
Reference in new issue
Block a user