makepad/skills/makepad-game-director/references/architecture.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

140 lines
7.8 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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:
```rust
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 — never `std` rand)
- **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:
```rust
println!("{}", makepad_game_script::api_text());
```
Two rules that cost real debugging time:
1. **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.
2. **The script budget is one cumulative 500k-instruction pool per tick**, shared by `on_tick` + timers + touch events, target ≤ 2 ms. A per-entity `on_tick` loop 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:
```rust
// 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:
```rust
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 seeded `rng`, and `makepad-game-math` transcendentals, not `std` float 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_DT` step; 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.GameMeshVertex` is 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_color` off-instance cut the cube instance 27%.
- Quest is bandwidth-bound before it is ALU-bound. Optimise bytes-per-frame first, shader complexity second.