makepad/skills/makepad-game-ui-designer/references/menus-and-flow.md
Arena Agent fe21c07d84 skills: add Makepad game skills pack ported from threejs-game-skills
Nine agent skills for building Makepad/Rust games, ported from
majidmanzarpour/threejs-game-skills. Same director-routed workflow and
premium bar; runtime rewritten for this fork's game crates.

- makepad-game-director        entrypoint, routing, continuity, asset probe
- makepad-gameplay-systems     loop, movers vs rigid bodies, input, camera, netplay
- makepad-aaa-graphics-builder lighting, shaders, budget, visual scorecard
- makepad-game-ui-designer     HUD, menus, touch and XR UI
- makepad-debug-profiler       defect bisection and profiling
- makepad-qa-release           verification ladder, evidence, packaging
- makepad-3d-generator         CC0 model search, casts, procedural geometry
- makepad-image-generator      texgen textures, palettes, sky, icons
- makepad-audio-generator      sample bank, mixer, material impacts, 3D audio

Written against the real APIs in libs/game/*, libs/sim and apps/arcade:
the game.* verb table, GameRenderer adaptive quality, the packed 6-float
GameMeshVertex layout, script_mod! splash styling, makepad-test driving,
and the BUDGETS.md numbers. No paid generation API is required - the CC0
library plus seeded makepad-game-gen replaces them.

Includes install.sh (Codex/Claude), validate-skills.sh and a repo-aware
probe_assets.sh; both scripts verified against this checkout.
2026-09-05 21:18:58 +00:00

70 lines
3.7 KiB
Markdown

# 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<GameState>) }
```
`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.