A game can pause itself and set its own time scale (#167)
game-maker / gate (push) Successful in 2m11s
game-maker / suite (push) Successful in 7m48s

Until now only the editor toolbar could pause a running game. A pause
menu inside the game had no way to stop the world while its own graph
kept running, and there was no slow motion.

- Five new nodes: Pause game, Resume game, Is game paused, Set time
  scale and Time scale. Pause and resume take effect on the next frame.
- A game pause stops world time, physics, animations, particles and
  shader time. Only behaviors with the new "Run while paused" switch
  keep running.
- Set time scale 0 freezes the world but graphs keep running, as
  agreed in the issue.
- Play and Stop reset the time scale to 1, Load scene keeps it. The
  pause belongs to the scene, so Load scene and Restart unpause.
- The toolbar pause stays separate and also freezes the menu. When the
  game pauses itself the status line reads "The game paused itself".
- While paused, collision events are not reported, sounds keep
  playing, and the editor run ignores both nodes with a log line.

Findings:
- The first status line was longer than any before and got cut with
  an ellipsis. It is shorter now.
- The e2e harness did not know the key "p". It does now.

Tests:
- ./check.sh: 133 files, 1968 tests, exit 0 (before 132 / 1952)
- npm run build: exit 0
- GM_STEPS=18 fresh: ALL PASSED, 33 checks (before 26)
- 16 new unit tests: 8 catalog cases, game-pause.test.ts, editor run
- 7 new e2e checks in block 18i, driven by real keys in the game
- Six counter-checks all bite: behavior filter, collision lock, world
  time, editor run, clock reset and the game clock in the browser
- Check total 1217 in README is computed (1210 + 7), CI measures it

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
adminandClaude Opus 5 committed 2026-09-21 10:27:17 +02:00
1 parent 7063c8d074
commit 3427f84a55
22 files changed
+570 -26

No files matched your search

+22 -2
View File
@@ -30,7 +30,7 @@ import { createEmptyProject, findScene, type Project } from '../src/core/model/P
import type { SceneObject } from '../src/core/model/SceneObject';
import { deserializeProject, serializeProject } from '../src/core/store/serialize';
import type { AudioSink } from '../src/runtime/systems/Audio';
import { BehaviorSystem } from '../src/runtime/systems/BehaviorSystem';
import { BehaviorSystem, type TimeScaled } from '../src/runtime/systems/BehaviorSystem';
import { readyPhysics } from '../src/runtime/physics/engine';
import { PhysicsSystem } from '../src/runtime/physics/PhysicsSystem';
import { CollisionSystem } from '../src/runtime/systems/CollisionSystem';
@@ -202,6 +202,9 @@ export type FakeContext = NodeContext & {
silenced: string[];
/** The scenes the context was asked to load, in order. */
loaded: string[];
/** The time scale and the pause the context holds, as the game nodes left them. */
scale: number;
paused: boolean;
};
/** A NodeContext double: self/owner handles, `speed = 100`, dt 0.5, moves applied directly, `log` and `stop` recorded. */
@@ -245,6 +248,16 @@ export function fakeContext(overrides: Partial<NodeContext> = {}): FakeContext {
ctx.stopped = true;
},
restart: () => {},
scale: 1,
paused: false,
timeScale: () => ctx.scale,
setTimeScale: (scale) => {
ctx.scale = scale;
},
isPaused: () => ctx.paused,
setPaused: (paused) => {
ctx.paused = paused;
},
...overrides,
};
return ctx;
@@ -284,6 +297,8 @@ export interface WorldOptions {
behaviors?: boolean;
/** Where `Play sound` ends up; without one the run plays to nobody (see `SILENT_AUDIO`). */
audio?: AudioSink;
/** The clock `Set time scale` turns; without one the run is treated like the editor run. */
clock?: TimeScaled;
}
export interface StartedWorld {
@@ -314,7 +329,12 @@ export function startWorld(fixture: SceneFixture, options: WorldOptions = {}): S
if (collisions) world.addSystem(collisions);
if (animations) world.addSystem(animations);
const behaviors = options.behaviors
? new BehaviorSystem(fixture.project, input, { collisions, animations, audio: options.audio })
? new BehaviorSystem(fixture.project, input, {
collisions,
animations,
audio: options.audio,
clock: options.clock,
})
: null;
if (behaviors) world.addSystem(behaviors);
return {