Start paused, step one frame and break from a behavior
Finding out what a game does in its first frames, or right at one spot of a flow, needed a quick hand on the pause button. Now the editor can start a play paused, step it frame by frame and let a behavior pause it. - The pause button can be pressed in edit mode. Then Play starts paused, and every further Play does too until it is released. A reload of the page clears it. - A third button, Next frame, right of Pause, or Ctrl+Shift+Enter. It works only while paused and runs exactly one frame of 1/60 s, whatever the frame took on screen. Hierarchy and inspector follow. - The Break node pauses the play like the button, at the end of the frame it ran in. The status line names the behavior's object. It is the editor's pause, not Pause game. - Break reaches the editor through a service only the editor hands to a play. In play.html and Run in editor the node does nothing. - Restart game keeps the running play's state. Start paused only counts when Play is pressed. - Wiki Concepts, Behaviors, Keyboard: the buttons, the node and the shortcut. Not tried: sound, free-running videos and a running recording while stepping. Sound and videos stay held during the pause. Tests: - tsc, eslint and Prettier are clean. - Unit pause, node-catalog, tooltip: 465 green. svg-icons now expects the step icon too. - e2e 18 and 46 on stein2 (melektaus-atx was off): ALL PASSED, 63 checks, 7 new in 18j. A step moves the box by 1.667 px at 100 px/s, Break stops the play with "Break in Breaker". - Counter-checks: a session that ignores the step turns 18j red. A Break that does not report its object turns the unit test red. - Gate on stein2: tsc, eslint and Prettier clean, 305 files, 4722 tests, 6 more than before. One was red at first, the icon list of the top bar did not know step yet. Fixed in svg-icons.test.ts and run again. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
64d0d472e5
commit
7a952cb20d
23 files changed
+378
-55
No files matched your search
@@ -415,6 +415,109 @@ export async function pause(t) {
|
||||
JSON.stringify(menuLeft),
|
||||
);
|
||||
|
||||
// 18j. Pause before Play: the run starts held, Next frame moves it by exactly one fixed step, and
|
||||
// a Break node holds a running game the way the pause button does.
|
||||
const buttons = () => json('__gm.runButtons()');
|
||||
const notice = () => json('__gm.state.notice');
|
||||
const editButtons = await buttons();
|
||||
await clickToolbar('pause');
|
||||
const armed = await buttons();
|
||||
check(
|
||||
'before Play the pause button is a switch, and Next frame waits for a run',
|
||||
editButtons.pauseEnabled === true &&
|
||||
editButtons.pauseToggled === false &&
|
||||
editButtons.stepEnabled === false &&
|
||||
armed.pauseToggled === true &&
|
||||
(await playInfo()).playing === false,
|
||||
JSON.stringify({ editButtons, armed }),
|
||||
);
|
||||
await t.play();
|
||||
const startX = await boxX();
|
||||
const startClock = await clock();
|
||||
await sleep(400);
|
||||
const startHeld = { info: await playInfo(), buttons: await buttons(), notice: await notice() };
|
||||
check(
|
||||
'Play starts paused: the box and the clock stand still, and Next frame is offered',
|
||||
startHeld.info.paused === true &&
|
||||
Math.abs((await boxX()) - startX) < 0.001 &&
|
||||
Math.abs((await clock()).now - startClock.now) < 0.001 &&
|
||||
startHeld.buttons.stepEnabled === true &&
|
||||
startHeld.notice.startsWith('Paused · Ctrl+Enter resumes · Ctrl+Shift+Enter steps'),
|
||||
JSON.stringify(startHeld),
|
||||
);
|
||||
const fixedDrift = DRIFT_PER_SECOND / 60;
|
||||
await clickToolbar('step');
|
||||
await sleep(200);
|
||||
const stepped = { x: (await boxX()) - startX, t: (await clock()).now - startClock.now };
|
||||
await press('Enter', { ctrl: true, shift: true });
|
||||
await sleep(200);
|
||||
const steppedTwice = { x: (await boxX()) - startX, info: await playInfo() };
|
||||
check(
|
||||
'Next frame and Ctrl+Shift+Enter each move the run by one 1/60 s frame, and it stays paused',
|
||||
Math.abs(stepped.x - fixedDrift) < 0.01 &&
|
||||
Math.abs(stepped.t - 1 / 60) < 0.0001 &&
|
||||
Math.abs(steppedTwice.x - 2 * fixedDrift) < 0.01 &&
|
||||
steppedTwice.info.paused === true,
|
||||
JSON.stringify({ stepped, steppedTwice, fixedDrift }),
|
||||
);
|
||||
await stop();
|
||||
await t.play();
|
||||
const again = await playInfo();
|
||||
await stop();
|
||||
await clickToolbar('pause');
|
||||
const disarmed = await buttons();
|
||||
await t.play();
|
||||
const free = await playInfo();
|
||||
await stop();
|
||||
check(
|
||||
'the switch holds for every Play until it is switched off again',
|
||||
again.paused === true && disarmed.pauseToggled === false && free.paused === false,
|
||||
JSON.stringify({ again, disarmed, free }),
|
||||
);
|
||||
|
||||
const breaker = await scaffoldBehavior(t, {
|
||||
assetId: 'breaker',
|
||||
name: 'Breaker',
|
||||
graph: {
|
||||
nodes: [node('b-key', 'OnKeyDown', 0, 0, { key: 'p' }), node('b-break', 'Break', 300, 0)],
|
||||
edges: [edge('b-e1', 'b-key', 'exec', 'b-break', 'exec')],
|
||||
},
|
||||
boxId: 'b-box',
|
||||
boxName: 'Break box',
|
||||
logicId: 'b-logic',
|
||||
y: LANES.pause - 75,
|
||||
});
|
||||
await t.play();
|
||||
await sleep(300);
|
||||
const beforeBreak = await playInfo();
|
||||
await press('p');
|
||||
await sleep(100);
|
||||
const broke = { info: await playInfo(), notice: await notice(), x: await boxX() };
|
||||
await sleep(400);
|
||||
check(
|
||||
"a Break node pauses Play like the pause button, not the game's own pause, and names its object",
|
||||
beforeBreak.paused === false &&
|
||||
broke.info.paused === true &&
|
||||
broke.info.gamePaused === false &&
|
||||
broke.notice.startsWith('Break in Breaker · Ctrl+Enter resumes') &&
|
||||
Math.abs((await boxX()) - broke.x) < 0.001,
|
||||
JSON.stringify({ beforeBreak, broke }),
|
||||
);
|
||||
await press('Enter', { ctrl: true });
|
||||
const afterBreak = { info: await playInfo(), notice: await notice() };
|
||||
await stop();
|
||||
check(
|
||||
'Ctrl+Enter resumes from a break',
|
||||
afterBreak.info.paused === false && afterBreak.notice.startsWith('Playing'),
|
||||
JSON.stringify(afterBreak),
|
||||
);
|
||||
const breakerLeft = await breaker.remove();
|
||||
check(
|
||||
'the break rig leaves nothing of its own behind',
|
||||
breakerLeft.objects === 0 && breakerLeft.assets === 0,
|
||||
JSON.stringify(breakerLeft),
|
||||
);
|
||||
|
||||
// 18h. Play discarded its copy, so the box is back at the position the model holds.
|
||||
const left = await rig.remove();
|
||||
check(
|
||||
|
||||
+11
-9
@@ -27,23 +27,25 @@ export async function topbar(t) {
|
||||
);
|
||||
const buttons = await json('__gm.runButtons()');
|
||||
check(
|
||||
'in edit mode the run buttons offer play and nothing to pause',
|
||||
'in edit mode the run buttons offer play, pause as a switch for the start, and no step',
|
||||
buttons.play === 'play' &&
|
||||
buttons.pause === 'pause' &&
|
||||
buttons.playEnabled === true &&
|
||||
buttons.pauseEnabled === false,
|
||||
buttons.pauseEnabled === true &&
|
||||
buttons.pauseToggled === false &&
|
||||
buttons.stepEnabled === false,
|
||||
JSON.stringify(buttons),
|
||||
);
|
||||
// The pair is centred on the bar itself. The row cannot do that on its own: the spacers around
|
||||
// the two buttons share the free space with the flexible notice and so get a third of it each,
|
||||
// which used to leave the pair well left of the middle.
|
||||
// The group is centred on the bar itself. The row cannot do that on its own: the spacers around
|
||||
// the buttons share the free space with the flexible notice and so get a third of it each, which
|
||||
// used to leave the group well left of the middle.
|
||||
const play = await json('__gm.toolbarAt("play")');
|
||||
const pause = await json('__gm.toolbarAt("pause")');
|
||||
const step = await json('__gm.toolbarAt("step")');
|
||||
const barCentre = await json('window.innerWidth / 2');
|
||||
check(
|
||||
'play and pause sit on the middle of the bar, not on the middle of what is left over',
|
||||
Math.abs((play.x + pause.x) / 2 - barCentre) <= 2,
|
||||
JSON.stringify({ middle: (play.x + pause.x) / 2, barCentre }),
|
||||
'play, pause and next frame sit on the middle of the bar, not on the middle of what is left over',
|
||||
Math.abs((play.x + step.x) / 2 - barCentre) <= 2,
|
||||
JSON.stringify({ middle: (play.x + step.x) / 2, barCentre }),
|
||||
);
|
||||
await shot('46-topbar');
|
||||
|
||||
|
||||
Reference in new issue
Block a user