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.9 KiB
Profiling
Rules
- Measure first. Optimising an unmeasured guess usually makes something else slower.
--releaseonly. Debug frame times are meaningless for shipping decisions.- Report p90, not mean. A 16 ms mean with a 40 ms p90 stutters, and the mean hides it.
- Measure on the target device class. The BUDGETS Quest column is an estimate until it runs on device.
Instruments
renderer.report_frame_ms(ms); // feed every frame; returns whether quality changed
renderer.frame_p90_ms(); // Option<f32>
renderer.quality_level();
renderer.quality_reason(); // why quality dropped - read this before debugging "missing" effects
renderer.shadow_budget();
stats.instance_floats; // RenderStats, from the compiled shader
renderer.model_triangles(id);
cargo run -p makepad-arcade --example bigworld_probe --release
Where time goes
From apps/arcade/BUDGETS.md:
- Sim: 100 movers + 50 rigid bodies + 65×65 terrain = 0.038 ms/tick against 16.6 ms. Gameplay code is essentially never the problem.
- Script: ≤ 2 ms, one cumulative 500k-instruction pool per tick.
- The real costs are draw calls and CPU skinning.
Skinning is CPU-side, so a skinned character's entire vertex buffer re-uploads every frame. The Knight: 3716 verts × 64 B = 238 KB/frame unpacked, 89 KB packed. Skinned character count is the highest-leverage number in most scenes.
Bandwidth first
Quest is bandwidth-bound before ALU-bound.
| stream | packed |
|---|---|
| cube instance | 32 floats / 128 B (was 44 / 176 B) |
| skinned vertex | 6 floats / 24 B (was 16 / 64 B) |
| shadow mesh vertex | 6 floats / 24 B |
geom.GameMeshVertex: position 3 exact f32, octahedral normal 1 lane, uv 2×f16 1 lane, colour 4×unorm8 1 lane. Attributes are f32-only in this engine; compression is bit-packing plus unpack2f16 / unpack4u8 in-shader.
Anything constant across a batch belongs in a uniform. Moving sun_color, sun_sky, sun_ground, fog_color off the instance stream saved 27% per cube.
Cut order
- Instance/vertex bytes (packed layout, batch constants → uniforms)
- Draw calls (batch by material and mesh)
- Skinned character count (cap; degrade distant ones)
- Shadow casters (
set_shadow_budget; bake AO for the rest) - Particle pool cap
- Terrain resolution (uploaded per revision, not per frame)
- Fragment work — last, and only with evidence you are ALU-bound
Stutter
Different problem from low framerate. Causes, in order of likelihood: allocation inside the frame loop, model loading during play, terrain regeneration on a gameplay event, container rehash in step, unbounded Vec growth (particles, BulletDecal, logs, the gen cache), and GC-like bulk frees.
Fix by preloading and pooling. Anything created per frame should be reused per frame.
Memory
Watch: model cache (makepad-game-gen::cache), decal buffers (cap and recycle — DecalBuffer has clear, push, len), particle pools, audio voices (Mixer has Priority for a reason — cap voices and let priority evict), and log buffers.
The asset index itself is cheap: ~2.1 MB heap and ~120 ms build for 4,999 entries in release. It is not your leak.
Two-controller oscillation
GameRenderer already implements adaptive quality. A second frame pacer or a settings slider that overrides it will oscillate and produce worse frame times than either alone. Surface quality_level / set_shadow_budget / set_refresh_hz as a ceiling and let the adaptive controller work beneath it.
Reporting a performance result
Always give before and after, in release, on a named device, with p90:
device M1 Air, release
before p90 24.3 ms, q1 (reason: frame_time), 41 skinned, 96 casters
after p90 11.8 ms, q3, 12 skinned (distant LOD'd), 24 casters
change packed skinned vertex 64B->24B; shadow budget 96->24; batched crates
budget 16.6 ms @ 60 Hz - 29% headroom
A performance claim without a before number is not a claim.