# Menus and flow ## The state machine ``` Boot -> Menu -> Playing -> Paused -> Playing |-> Win -> Menu | Retry |-> Lose -> Menu | Retry Menu -> Settings -> Menu Paused -> Settings -> Paused ``` Model it as one enum with one transition function. Scattering `is_paused` / `is_over` booleans across widgets produces states that are reachable but not designed — paused *and* won, for instance. ```rust enum GameState { Boot, Menu, Playing, Paused, Win, Lose, Settings(Box) } ``` `Settings` carries its return state so one screen serves both entry points. ## Transition rules - **Pause must stop the sim step.** Not just hide the HUD. Also pause audio voices and particle emitters, or the player returns to a changed world. - **Retry must fully reset.** Score, timers, spawns, particle pools, audio voices, camera. One `reset()` that rebuilds world state. Test it by winning twice in a row and losing after a win — stale state is nearly always found this way, not by inspection. - **Win and lose need a latch.** An unlatched condition fires every tick, which stacks overlays and replays the jingle. - **Never trap the player.** Every screen has a way back, including with a controller. ## Loading Under ~1 s: nothing. Over: show progress. Asset loading here is `load_model` plus library indexing (~120 ms for 5000 entries), so the usual wait is model loading and terrain generation. Log a ready line — `log "game: ready"` — so `wait_for_log_contains` can synchronise tests instead of sleeping. ## Focus and controller navigation If the game supports a gamepad, menus must be fully navigable with it. That means an explicit focus model: a default focused item on every screen, a defined neighbour in each direction, a visible focus indicator that survives against every background, and consistent confirm/back buttons. Mouse-only menus in a controller game are a completeness failure, and they are usually noticed by the user rather than the agent. ## Settings Minimum viable set: master / SFX / music volume, quality (the renderer already exposes `quality_level`, `set_shadow_budget`, `set_refresh_hz` — surface those rather than inventing parallel settings), invert-Y and sensitivity, reduce shake / reduce flashes, and a control reference showing the active device's glyphs. Apply immediately, do not require confirmation, and persist. A quality slider that fights `GameRenderer`'s adaptive controller will oscillate — set the *ceiling*, and let adaptive quality work below it. ## Results screens Show, in this order: **outcome**, the number that matters, how it compares (best, par, previous), and the next action already focused. Retry is the primary action after a loss; next-level or menu after a win. Do not show a table of statistics nobody asked for. One headline number plus a comparison is what gets read. ## Text Write it as UI, not as prose. `Retry`, not `Would you like to try again?`. Second person, present tense, no exclamation marks. Say what a control does, not that it exists: `Hold Shift to drift`, not `Shift: Drift`. Keep every string in one place so it can be checked, changed, and eventually localised without hunting through widget definitions. ## Verification ```rust #[makepad_test] fn pause_stops_and_resumes(app: TestApp) { app.wait_for_log_contains("game: ready"); app.press_key(KeyCode::Escape); app.wait_for_log_contains("state: paused"); let shot = app.screenshot(); // inspect it app.press_key(KeyCode::Escape); app.wait_for_log_contains("state: playing"); } ``` Capture every state at the target aspect ratio. Emit a state log line on each transition — it is the cheapest thing that makes flow machine-testable, and it costs one line per transition.