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.
7.8 KiB
Makepad game architecture
The crate map an agent needs before touching anything. Paths are relative to repo root.
Crate map
| Crate | Path | Owns | Needs a Cx? |
|---|---|---|---|
makepad-game-sim |
libs/sim |
GameWorld, Entity, movers, rigid dynamics, terrain, water, voxels, nav, particles state, HUD state |
no |
makepad-game-math |
libs/sim/math |
deterministic transcendentals — the reason two devices generate byte-identical meshes | no |
makepad-game-render |
libs/game/render |
forward renderer, shaders, shadows, skinning, models, particles, fireworks, sun, stage/quality | yes |
makepad-game-gen |
libs/game/gen |
procedural mesh/terrain/texture/tree/L-system/interior/level generation, seeded RNG | no |
makepad-game-blocks |
libs/game/blocks |
Car, Character, Plane, Npc, Brain, RaceKit, controllers |
no |
makepad-game-audio |
libs/game/audio |
WAV/Vorbis decode, SampleBank, Mixer, AudioDirector, material impacts |
no |
makepad-game-script |
libs/game/script |
the game.* verb table, ScriptHost, sandbox/trust, compose, interact |
partly |
makepad-game-net |
libs/game/net |
host-authoritative LAN transport, lobby auth, protocol | no |
makepad-game-session |
libs/game/session |
HostSession / ClientSession, replication, per-player input |
no |
makepad-game-coedit |
libs/game/coedit |
host-serialized collaborative edits, leases, diff3 merge | no |
makepad-game-pkg |
libs/game/pkg |
package manifest, library, registry, sha256 verification | no |
makepad-game-assets |
libs/game/assets |
the searchable CC0 index, aliases, casts, packs, palettes | no |
makepad-arcade |
apps/arcade |
the host app: viewport widget, AI chat, pairing, settings, XR input | yes |
The Cx column is the single most useful fact in this table. Everything without a Cx is unit-testable headlessly, which is why sim/gen/audio/net logic must never be written inside a widget. If you find yourself needing a Cx to test a rule, the rule is in the wrong crate.
Dependency direction
math ──► sim ──► blocks ──► script ──► arcade
│ │ │ ▲ ▲
├──► gen ┘ │ │ │
├──► render ────────┘ │ │
├──► audio ────────────┘ │
└──► net ──► session ───────────┘
coedit, pkg, assets ┘
Never introduce an edge back up this graph. sim must not depend on render; if a rule needs a mesh, it needs bounds, and bounds come from render::model_bounds passed in by the caller.
Route A — script (game.* verbs)
The game is a script the ScriptHost evaluates. Roughly 180 verbs, dispatched through:
pub type VerbFn = fn(&mut ScriptVm, &Ctx, ScriptObject) -> ScriptValue;
pub static VERBS: &[(&str, VerbFn)] = &[ /* ... */ ];
Verb families (from libs/game/script/src/dispatch.rs):
- World:
box,block,mover,spawn,terrain,sky,gravity,remove - Transform / physics:
pos,vel,set_pos,teleport,set_vel,push,walk,jump,on_floor,face,yaw,ground_y,ground_normal - Query:
tag,find,distance - Look:
set_color,glow,scale,camera,sun - HUD:
text,bar,crosshair,label,label_text - Events / time:
on_tick,on_touch,on_join,on_leave,after,every,cancel,time - Random:
rand,rand_range(seeded — neverstdrand) - Audio:
sfx,sfx_at,beep,jingle - FX:
particles,burst,particles_stop - Blocks:
car,character,player_character,plane,drive,autodrive,speed,interactable,interact_prompt - AI:
wander,chase,patrol,caught - Race:
spawnpoint,checkpoint,place,race,standings,lap,rank,finished - Score / players:
score,score_of,players,player_name
Get the authoritative list at any time — do not trust this summary if it disagrees:
println!("{}", makepad_game_script::api_text());
Two rules that cost real debugging time:
- Options keys are typo-guarded. Every options-taking verb validates its keys, because a silently ignored misspelled option used to burn whole test cycles. If a verb rejects a key, the key is wrong — do not work around it.
- The script budget is one cumulative 500k-instruction pool per tick, shared by
on_tick+ timers + touch events, target ≤ 2 ms. A per-entityon_tickloop over thousands of entities will exhaust it; batch instead.
Sandboxing: Trust and strip_capabilities decide what a script may touch. Untrusted (AI- or peer-authored) scripts must be stripped before evaluation. EvalReport carries the result.
Route B — Rust app crate
Model it on apps/arcade/src/arcade_view.rs. The shape of a frame:
// 1. sim, fixed step
world.step(TICK_DT); // makepad-game-sim
// 2. camera + scene state
let scene = render_scene_state(&world, &camera_rig, /* ... */);
set_pass_camera(cx, &self.pass, &scene);
// 3. feed the renderer
renderer.set_models(model_instances);
renderer.set_particles(particle_instances);
renderer.set_fireworks(firework_instances);
// 4. draw
renderer.draw_scene_full(cx, &mut self.draws, &world, &scene /* ... */);
draw_hud_overlay(cx, /* ... */);
GameRenderer also owns adaptive quality: report_frame_ms() returns whether quality changed, and quality(), quality_level(), quality_reason(), frame_p90_ms(), set_shadow_budget(), set_refresh_hz() expose the controls. Do not build a second frame-pacing system beside it.
Register the crate in the root Cargo.toml workspace.members list, in the arcade block.
Styling: script_mod!, not live_design!
This fork's widgets use the splash DSL through script_mod!. Shader and style overrides are inheritance-based:
script_mod! {
use mod.prelude.widgets_internal.*
use mod.widgets.*
mod.widgets.MyGameViewBase = #(MyGameView::register_widget(vm))
mod.widgets.MyGameView = set_type_default() do mod.widgets.MyGameViewBase {
width: Fill
height: Fill
draw_cube += { light_dir: vec3(0.35, 0.8, 0.45) }
draw_firework += { /* fn override for the look only */ }
}
}
This seam is deliberate: the engine shader owns the structure, the DSL owns the look. A generated game can restyle the sky without being able to break the simulation. Keep it that way — do not move gameplay-relevant constants into the DSL, and do not hardcode look constants in Rust.
Determinism
Networked and replayable play both depend on it:
- Use
makepad-game-gen's seededrng, andmakepad-game-mathtranscendentals, notstdfloat math, anywhere a result crosses the wire or is regenerated on another device. - Content replicates as
(preset, seed, position), not vertex data. A forest is a seed. - Fixed
TICK_DTstep; never advance the sim by a variable frame delta.
Budgets
apps/arcade/BUDGETS.md is the authority. The headline numbers:
- 100 movers + 50 rigid bodies + 65×65 terrain = 0.038 ms/tick; the 60 Hz budget is 16.6 ms. Simulation is never your problem.
- Draw calls and CPU skinning are. Skinned vertices are re-uploaded every frame.
- Packed vertex layout
geom.GameMeshVertexis 6 floats / 24 B (position exact, octahedral normal, f16 uv, unorm8 colour). Use it; do not invent a fatter vertex. - Per-instance data that is constant across a batch belongs in a uniform, not the instance stream. Moving
sun_color/sun_sky/sun_ground/fog_coloroff-instance cut the cube instance 27%. - Quest is bandwidth-bound before it is ALU-bound. Optimise bytes-per-frame first, shader complexity second.