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.
3.7 KiB
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.
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
#[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.