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.
70 lines
3.7 KiB
Markdown
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.
|