v0.10 - Game Pane
Adds a game pane type for small 2D games, Snake first, so waiting on an agent can be a break instead of a context switch. The release is deliberately small and self-contained: built-in games only, no files, network or third-party code (a native pane SDK stays out of scope per the architecture boundary), and no part of the control surface or the daemon depends on it. It builds on the v0.8.0 theme tokens and palette and the v0.9.x notification signals.
v0.10.0 - Game Pane
| Done | Area | Item |
|---|---|---|
| ⬜ | CORE | A game pane type, registered on both sides of the vocabulary seam - game joins PaneTypes.ALL and the PaneType enum in gpty-core, spelled by PaneType::as_str() like every other type, so new-pane, the plugin-manifest validator’s closed set and the did-you-mean list pick it up from the enumeration, and the key-for-key test between the Rust enum and pane_types.gd fails on a one-sided rename. sanitize_tile accepts a game setting only from a closed set of built-in game ids (the layout-trust rule: a file never names code), and an unknown id restores as the game picker with a notice. Tests: the vocabulary test covers game; a tile with an unknown or path-shaped id restores as the picker; the chosen game round-trips through a saved layout. |
| ⬜ | GUI | A game host contract every game implements - A small GameBody interface behind PaneBody (start, pause, resume, reset, score, state) with a fixed-timestep tick that is independent of the frame rate, so a game runs at the same speed on a 60 Hz and a 144 Hz display and under a busy GUI. The game draws through the pane’s own control and has no access to terminals, the IPC layer or the file system; per the pane request contract it never spawns a pane. Tests: with an injected clock a game advances the same number of ticks over the same wall time at 30 and at 144 frames per second; a game script has no reference to TerminalManager. |
| ⬜ | GUI | Snake - The first game, and the one that proves the host: a grid sized from the pane’s own dimensions (clamped to a minimum and maximum), arrow keys and WASD, growth, wall and self collision, a speed that rises with the score, a pause key, game over with restart, and an optional wrap-around mode. Tests: collision rules, growth, food never spawns on the snake, and a resize mid-game keeps the game valid (it rescales or restarts with a notice) instead of leaving the snake off the grid. |
| ⬜ | GUI | Keyboard focus is explicit and does not leak - A game takes keys only while its pane is focused; a key a game uses never reaches a workspace shortcut, and Esc hands focus back to the workspace (the same consume-the-event discipline as the terminal’s copy chord). Losing focus pauses the game. Tests: a key consumed by a focused game does not trigger a workspace shortcut; an unfocused game ignores keys; focus loss pauses. |
| ⬜ | GUI | A game that is not visible does not run - A hidden workspace, a minimised window and an obscured pane all stop the tick (processing is disabled, not merely ignored), so a game costs nothing while the agent work beside it is what the machine should be doing. Measure and record the CPU use of a paused pane and of a running Snake at the default size. Tests: switching workspace pauses the game and its tick count does not move while away; no process callbacks fire while the pane is hidden. |
| ⬜ | CORE | High scores live in a user-owned store - A GameScoreStore on the BasePersistenceManager pattern (typed reads, size caps, owner-only mode) keyed by game id. A layout, profile or IPC call can carry the chosen game id but never a score or a saved game state, so a shared file cannot plant a record. Tests: a corrupt or oversized entry costs only itself; a profile carrying a score field has it stripped on restore; an unknown game id in the store is ignored. |
| ⬜ | GUI | Second game: Minesweeper - Pointer-driven (click, flag, chord) so the host is shown to handle the mouse as well as keys, sized from the pane, with a difficulty setting; the first click is never a mine. Tests: first-click safety, flood reveal, flag and chord counts, and win and loss detection on a seeded board. |
| ⬜ | GUI | Third game: falling blocks - A timer- and rotation-driven game (hard drop, line clears, next-piece preview), named generically, so the tick contract is exercised at a different rhythm from Snake. Tests: seeded piece order, rotation against walls, line-clear scoring, and game over when a spawn is blocked. |
| ⬜ | GUI | Game picker and per-game settings - The new-pane menu and the command palette offer each game by name, and a fresh game pane shows the picker. Pane Settings change the game and its options (speed, wrap, difficulty), validated like every other pane setting; switching games mid-run asks first. Games use the shared theme and colour tokens, follow UI scale, and never carry information by colour alone (Minesweeper numbers and flags have glyphs). Tests: the picker lists exactly the registered games; an invalid option falls back to its default only; switching games with a game in progress asks before resetting. |
| ⬜ | GUI | A running game steps aside when something needs attention - When another pane’s agent state becomes needs-attention or a command-finished alert fires, a running game pauses and shows a banner naming that pane, so playing while an agent works never means missing the prompt. It listens to the workspace’s existing state signal and does not read or poll other panes (observe and display, never orchestrate). A setting turns it off. Tests: a state change on another pane pauses the running game and names the pane; with the setting off nothing happens; a paused or finished game is untouched. |
| ⬜ | IPC | The control surface treats a game pane honestly - list-panes reports type game and its game id; pane-status reports running, paused or over and the score as primitives; inject, pane-run, pane-read and pane-wait answer with a clear “not supported for game panes” error instead of silently succeeding; new-pane --type game --game <id> works. Tests: each verb’s response on a game pane, including the error text, and new-pane with an unknown game id. |
| ⬜ | CORE | A game pane holds no daemon state - With the v0.9.0 daemon a game is GUI-local: it owns no PTY, a reattached window restores the pane at the game’s start screen (not mid-game), and only scores persist. State it in AGENTS.md beside the pane-type rules. Tests: a game tile restores through layout restore and through reattach without a PTY and without error. |
| ⬜ | SEC | The trust boundary stays closed - Games load no files, no network resources and no scripts, and take nothing from a layout beyond their own id, the same stance as the media pane’s allow-listed decoders. Games are built in: third-party games are out of scope until a native pane SDK has its own trust design (the architecture boundary’s non-goal stands). Tests: a source check in ci-check fails when a game script uses FileAccess, HTTPRequest, OS.execute or load of a path built from input. |
| ⬜ | DOCS | Docs, soak and a release gate - A docs page for the game pane (games, keys, settings, control surface), the host contract recorded in AGENTS.md, and a 10-minute headless soak per game with flat memory and no frame stalls recorded here, with GUT coverage for all of the above in the gate. |