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
Render budget
apps/arcade/BUDGETS.md is the authority. This is the working summary and the method.
What the numbers say
Measured on an M-class Mac, release. The Quest column in BUDGETS.md is a conservative estimate until run on device — do not quote it as measured.
| thing | cost |
|---|---|
| 100 movers + 50 rigid bodies + 65×65 terrain | 0.038 ms/tick |
| 60 Hz frame budget | 16.6 ms |
| script per tick | ≤ 2 ms, one cumulative 500k-instruction pool |
| entity lookup | O(log n) |
Simulation is ~0.2% of the frame. It is never your performance problem. Draw calls and CPU skinning are.
Bandwidth
| stream | before | after |
|---|---|---|
| cube instance | 44 floats / 176 B | 32 floats / 128 B (−27%) |
| skinned character vertex | 16 floats / 64 B | 6 floats / 24 B (−62%) |
| shadow mesh vertex | 16 floats / 64 B | 6 floats / 24 B (−62%) |
| terrain vertex | 16 floats / 64 B | unchanged (uploaded once per revision) |
The skinned vertex matters most because it re-uploads every frame: the Knight at 3716 verts went from 238 KB/frame to 89 KB/frame. On a bandwidth-bound tiler that is the largest single saving available.
The instance saving came from moving sun_color, sun_sky, sun_ground, fog_color off the instance stream into uniforms — 12 floats of pure per-cube duplication.
Quest is bandwidth-bound before it is ALU-bound. Optimise bytes per frame first.
Measuring
Never estimate. Read it:
renderer.report_frame_ms(ms); // -> bool: did adaptive quality change?
renderer.frame_p90_ms(); // Option<f32> - report p90, not mean
renderer.quality_level();
renderer.quality_reason();
stats.instance_floats; // RenderStats: from the compiled shader
renderer.model_triangles(id);
Report p90, not average. A 16 ms average with a 40 ms p90 stutters, and the average hides it.
Headless evidence:
cargo run -p makepad-arcade --example bigworld_probe --release
Always measure in --release. A debug-build frame time tells you nothing about shipping.
Cutting, in order
- Instance/vertex bytes — packed layout everywhere, batch-constant values as uniforms.
- Draw calls — batch by material and mesh. Many small draws beat nothing.
- Skinned character count — the most expensive category per unit. Cap it; degrade distant characters.
- Shadow casters —
set_shadow_budget(n); bake AO for the rest. - Terrain resolution — uploaded once per revision, so this is a memory and vertex-count question more than a per-frame one.
- Particle count — cheap individually, unbounded collectively. Cap the pool.
- Fragment work — last, and only with a measurement showing you are ALU-bound.
Adaptive quality
GameRenderer owns it: report_frame_ms each frame, set_refresh_hz for the device (72 Hz on Quest, not 60), set_shadow_budget, Stage / StageMode, and quality_reason() to explain a drop.
Do not build a second frame pacer. Two controllers oscillate against each other and produce worse frame times than either alone. If quality is dropping unexpectedly, read quality_reason() — that is what it is for.
Budget template
Fill this in for the target device before optimising, so you know when to stop:
target device Quest 2, 72 Hz -> 13.9 ms
sim 0.1 ms (measured)
skinned chars 8 x ? ms
shadow casters 24 x ? ms
opaque draws ? calls ? ms
particles ? max ? ms
HUD ? ms
headroom >= 20%
Twenty percent headroom is not padding: it is what absorbs the worst wave, the busiest camera angle, and thermal throttling on a mobile part after ten minutes of play.