A test that waits for a deadline somebody guessed is waiting for the wrong thing, so it waits for the editor (#93)
game-maker / suite (push) Successful in 6m48s

The suite spent 88% of its step time asleep: 564 s of steps, 496 s of
them in sleep(n) - fixed spans estimated by hand and then copied from
step to step. That is why it was slow, and why it flickered.

The signal is afterFrames(count, timeout) in ui/render.ts, exposed as
__gm.afterFrames(). It wakes the loop itself, because an editor that
draws nothing would never redeem a promise of a frame, and it gives up
after a timeout instead of hanging the run. settle() in the harness
waits two drawn frames (~32 ms) instead of 150-300 ms and falls back to
sleep(150) when the hook is missing (an old build): silently not
waiting would make every later check a coin toss.

Every action goes through it now - click, doubleClick, wheel, drag,
dragAsset, press, typeText, select, mutate, undo, redo - and with that
about 550 sleep() calls behind those actions became redundant, helpers
that are nothing but a mutate in a coat included. What a frame cannot
answer got its own condition instead: waitUntil(expr), play()/stop()
waiting on __gm.state.mode rather than on 300 ms, saved() for the
autosave (the two hand-written polling loops sampled at 300 ms for
something that arrives after a second), and showTab() as a real tool -
47 places called evaluate("__gm.showTab(...)") raw, and 24 of them
waited for nothing at all and read the old panel.

564 s -> 243 s of step time, the waiting share 88% -> 76%. Three kinds
of wait were kept, and each one bit when it was taken away: loading
bytes (textures, fonts), game time that a repaint cannot stand in for
(time scale, shader and particle clocks, the live values a running
graph carries), and the inertia the scroll step measures on purpose.

Two things the new speed uncovered, both real: 25-animation read the
frame menu unfiltered and had frozen an incomplete list (the CI failure
on c990dbc), and 33-render-on-change measured idleness while the
autosave of the step before was still painting - it waits for
saveStatus to leave dirty/saving now, so it measures that nothing
happens any more instead of that it waited long enough.
This commit is contained in:
admin committed 2026-09-16 08:08:10 +02:00
1 parent c990dbcb26
commit 7e5003b590
41 files changed
+362 -732

No files matched your search

+2 -14
View File
@@ -33,6 +33,7 @@ export async function transform(t) {
entity,
stop,
shot,
showTab,
} = t;
const field = (label) => json(`__gm.inspectorField(${JSON.stringify(label)})`);
const rect = (id) => json(`__gm.objectRect(${JSON.stringify(id)})`);
@@ -47,7 +48,6 @@ export async function transform(t) {
);
const selectObject = async (id) => {
await select({ kind: 'object', holderId: 'scene1', objectIds: [id], objectId: id });
await sleep(300);
};
const remove = (ids) =>
mutate(`
@@ -57,8 +57,7 @@ export async function transform(t) {
await press('Escape');
await evaluate('__gm.state.select(null)');
await evaluate("__gm.showTab('scene')");
await sleep(200);
await showTab('scene');
// 29a. A plain red 100×40 sprite in its lane, and a second camera to read the inspector off;
// a sprite object has the transform rows, a camera object has not.
@@ -118,7 +117,6 @@ export async function transform(t) {
// 29c. Rotation 90: the box stands up – its frame swaps sides, the pixels move with it.
await override(BOX, { rotation: 90 });
await sleep(300);
const turned = await rect(BOX);
const turnedPx = { right: await px(40, 0), below: await px(0, 40) };
check(
@@ -132,7 +130,6 @@ export async function transform(t) {
// 29d. Scale 2 on top: twice the frame, red where the upright box would have ended long ago.
await override(BOX, { scale: 2 });
await sleep(300);
const grown = await rect(BOX);
const far = await px(0, 90);
check(
@@ -159,7 +156,6 @@ export async function transform(t) {
JSON.stringify(plain),
);
await override(BOX, { mirrorX: true });
await sleep(300);
const mirrored = { left: await px(-30, 0), right: await px(30, 0) };
check(
'Mirror X swaps the halves: green left, red right',
@@ -167,7 +163,6 @@ export async function transform(t) {
JSON.stringify(mirrored),
);
await override(BOX, { mirrorY: true });
await sleep(300);
const both = { left: await px(-30, 0), right: await px(30, 0) };
check(
'Mirror Y on top leaves the halves where Mirror X put them',
@@ -176,17 +171,14 @@ export async function transform(t) {
);
await override(BOX, { mirrorX: false, mirrorY: false });
await spriteProps({ texture: '', color: 0xff0000 });
await sleep(300);
// 29f. A child 100 to the right of its parent: once the parent turns a quarter, the child hangs
// 100 below it – and is drawn turned itself.
await mutate(`
const scene = __gm.state.activeScene;
scene.objects.push({ id: ${JSON.stringify(CHILD)}, assetId: ${JSON.stringify(SPRITE)}, name: '', position: { x: 100, y: 0, z: 1 }, editorOffset: { x: 0, y: 0 }, overrides: {}, parentId: ${JSON.stringify(BOX)}, instanceEdits: {} });`);
await sleep(300);
const flat = { parent: await screen(BOX), child: await screen(CHILD) };
await override(BOX, { rotation: 90 });
await sleep(300);
const hung = { parent: await screen(BOX), child: await screen(CHILD), childRect: await rect(CHILD) };
check(
'a child follows its parent’s turn: 100 to the right becomes 100 below, drawn turned',
@@ -202,7 +194,6 @@ export async function transform(t) {
// 29g. Dragging the child 30 screen pixels down moves it along the parent's x axis – which now
// points down – so its local x grows by 30 / scale and y stays.
await drag(hung.child.x, hung.child.y, hung.child.x, hung.child.y + 30, { steps: 8 });
await sleep(300);
const dragged = await object(CHILD);
check(
'a drag under a turned parent lands in the parent’s frame: local x + 30 / scale, y unchanged',
@@ -210,7 +201,6 @@ export async function transform(t) {
JSON.stringify({ position: dragged.position, expected: 100 + 30 / s }),
);
await undo();
await sleep(200);
check('undo puts the child back', (await object(CHILD)).position.x === 100);
await selectObject(CHILD);
const gizmos = await json('__gm.gizmos()');
@@ -236,7 +226,6 @@ export async function transform(t) {
);
__gm.state.select(null)`);
await press('Enter', { ctrl: true });
await sleep(300);
await holdKey('ArrowRight', 1000);
const body = await entity('otf-body');
await stop();
@@ -255,7 +244,6 @@ export async function transform(t) {
project.assets = project.assets.filter((a) => !gone.has(a.id));
__gm.lookAt(0, 0)`);
await evaluate('__gm.state.select(null)');
await sleep(200);
check(
'the step leaves nothing behind',
(await json(`__gm.state.project.assets.some((a) => a.id === ${JSON.stringify(SPRITE)})`)) === false &&