Game
Runs a game: world builds the first World, then every display frame the loop advances it step by step through World.update and draws World.root on screen.
The loop is semi-fixed-timestep. Each display frame's wall-clock seconds — clamped to maxFrameTime — are spent as whole 1/tickRate steps plus one remainder step of whatever is left; nothing carries over between frames and nothing is discarded, so the scene drawn is the exact wall-clock-time state: rendering needs no interpolation, and World.root stays a pure projection. World.update is the only place state advances, by a variable dt in [0, 1/tickRate] seconds — the base step for every whole step of a frame, the frame's remainder once per frame. A step of dt = 0 occurs when a frame lands exactly on a step boundary and must be harmless. Because steps vary in size, game logic must be a function of dt — a timer counts seconds, decay is a per-second rate — never counts of steps.
Each step receives sampled Input, never events: pointers in CGA model units — converted against the current scene's bounds — and the keyboard on Compose Key codes. Press and release edges are latched so none is lost between frames and none fires twice within one frame's burst of steps; the latching contract lives on Input. The composable requests focus on launch, so key input arrives without any game-side focus code.
world runs before the first step — the single creation site for every named node the state and scene share, AssetScope loads and programmatic shapes alike — and again whenever a step ends in restart: the world that called it is discarded, the rest of its frame with it, and world builds the replacement, which draws from the next frame on and whose clock starts there — the seconds the rebuild took are never simulated as catch-up. Input outlives a world: a key held through a restart is still down in the replacement, with no fresh press edge. The world world returns is fully formed: no step runs and no scene is read before it exists, so games carry no nullable-asset tolerance; until the first returns, nothing draws, and until a replacement returns, the discarded world's last frame stays on screen. The scene renders through Kanvas: uniformly fitted to the surface, clipped to its bounds, letterboxed on aspect mismatch. An exception thrown by world or World.update propagates unchanged — Restart is the one the loop consumes.
A build made with the gameDebug Gradle property carries a pause/step facility owned by the loop: P toggles pause — no step runs while paused, the scene keeps drawing — and O, while paused, advances exactly one 1/tickRate step. Unpausing resumes from the current wall clock; paused time is never simulated. Without the property the facility does not exist in the binary and both keys reach the game untouched.
Parameters
The host's modifier chain; sizing it imposes overrides the scene's intrinsic size.
The base simulation frequency in Hz; 120 by default. Its step 1/tickRate is the largest dt World.update ever receives — the bound to size fast movers against.
The most wall-clock seconds one frame may simulate; 0.25 by default. A longer stall — a debugger pause, a hidden tab — loses the excess time instead of spiraling into a catch-up storm.
Builds a world — the first at launch, a replacement on each restart; suspends freely — asset loading awaits inside.
Type Parameters
The game's world type.
Throws
when a scene holds a shape that is neither a CGA primitive nor a composite — the unsealed Shape marker makes a foreign implementation constructible.