--- name: makepad-debug-profiler description: "Diagnose and fix Makepad game defects: black or empty viewport, missing models, broken shaders, wrong lighting, input that does nothing, stutter and frame spikes, memory growth, netplay desync, script errors, and mobile/XR-specific failures. Use for 'nothing renders', 'it compiles but crashes on launch', 'assets don't appear', 'it's slow', and profiling work." --- # Makepad Debug Profiler Find the actual cause. The most expensive debugging in this codebase comes from fixing a symptom one layer above the fault. ## Method 1. **Reproduce deterministically.** Fix the seed, fix the tick count, note the platform and build profile. An intermittent bug you cannot reproduce is a bug you cannot verify fixed. 2. **Bisect the pipeline**, not the source file. The layers are: sim state → scene state → instance data → draw call → shader → pass composite. Find the last layer where the data is right. 3. **Print the data, not the code.** Assert what the values actually are at that boundary. 4. **Fix the cause, then add the test that would have caught it.** ## Layer bisection for "nothing renders" Work down; the first failing check is your bug. ```rust // 1. does the sim have anything? println!("entities: {}", world.entities.len()); // 2. is it where you think? println!("player pos: {:?}", player.pos); // 3. is the camera looking at it? println!("cam: {:?} target {:?}", rig.pos, rig.target); // 4. did instances reach the renderer? println!("models: {} particles: {}", models.len(), particles.len()); // 5. did the model actually load? println!("loaded: {}", renderer.model_is_loaded("kenney/racing/vehicle-truck-yellow")); println!("bounds: {:?}", renderer.model_bounds(id)); // 6. is quality having silently dropped things? println!("q{} because {}", renderer.quality_level(), renderer.quality_reason()); ``` Most common causes, in the order they actually occur: | Symptom | Usual cause | | --- | --- | | Black viewport | camera inside geometry, or near/far plane wrong, or the pass never composited | | Everything invisible but HUD fine | camera target not updated from the sim; rig at origin | | Model missing, no error | **wrong asset id — it degrades to a primitive silently.** Assert `model_is_loaded` | | Model present but tiny/huge | scale mismatch between the kit and the player capsule | | Model at origin | transform not applied; `ModelInstance::new(.., transform)` got identity | | Flat/wrong lighting | `light_dir` set on some draw types and not others | | Shadows vanished under load | adaptive quality dropped the budget — read `quality_reason()` | | Fine in `cargo check`, broken on launch | the splash DSL resolves **at runtime** | That last row is the most important fact in this skill: **`cargo check` cannot validate the DSL.** A malformed style, a missing widget id, or a bad inheritance path fails only when the app runs. Any UI or style change must be launched. ## Silent asset fallback The library is designed to degrade gracefully — with no assets downloaded the game runs on primitives and asset-dependent tests skip. Convenient for CI, dangerous when debugging: a typo'd id looks like a design choice. ```bash bash skills/makepad-game-director/scripts/probe_assets.sh ``` Then assert, don't eyeball: ```rust for id in scene_model_ids { assert!(renderer.model_is_loaded(id), "missing {id}"); } ``` ## Input that does nothing Bisect the same way: does the platform event arrive → does the mapping layer produce an action → does the sim consume it → does the entity respond? Specific traps: a radial deadzone applied per-axis; a ground raycast that hits the player's own capsule (so `on_floor` is always false, so jump never fires); an input buffer that is never cleared (double jumps); touch handled only as mouse (so nothing works on device); XR controller pose read as gamepad axes. ## Performance **Measure before changing anything, in `--release`.** ```rust renderer.frame_p90_ms(); // report p90, not mean renderer.quality_level(); renderer.quality_reason(); stats.instance_floats; ``` ```bash cargo run -p makepad-arcade --example bigworld_probe --release ``` Given the sim is ~0.2% of frame time, a slow frame is almost never gameplay code. Check, in order: skinned character count (CPU skinning re-uploads every vertex every frame), draw-call count, shadow casters, particle pool size, instance stream width, and only then fragment work. **Stutter is not the same as low framerate.** A 16 ms mean with a 40 ms p90 is a stutter problem: look for per-frame allocation, model loading during play, terrain regeneration, or a `HashMap` rehash in the step loop. Preload and pool. Two systems fighting is a classic here: if quality oscillates, check you have not added a frame pacer beside `GameRenderer`'s adaptive controller. ## Netplay desync Host-authoritative, so a divergence means a client simulated something it should not have, or the sim is not deterministic. Check: variable `dt` anywhere; `std` float transcendentals instead of `makepad-game-math`; unseeded RNG instead of the seeded `rng`; `HashMap` iteration order inside `step`; clients sending state instead of input; content replicated as vertex data instead of `(preset, seed, position)`. Bisect by logging a per-tick state hash on host and client and finding the first differing tick. ## Script route Errors surface in `EvalReport`. Constraints that present as bugs: the **500k-instruction cumulative pool per tick** shared by `on_tick` + timers + touch (symptom: handlers silently stop running late in a tick), and **typo-guarded option keys** (symptom: a verb rejects a key — the key is wrong, do not work around it). Untrusted scripts must pass `strip_capabilities` with a `Trust` level; a capability failure is the sandbox working. ## Memory Growth over time comes from unbounded `Vec`s: particle instances, decals (`BulletDecal` — cap and recycle), log buffers, model cache (`makepad-game-gen::cache`), or per-frame allocations never reused. Pool anything created per frame. ## Tooling `cargo check -p ` for the fast loop; `cargo test -p ` for Cx-free logic; `cargo clippy` (the repo has `clippy.toml`); `makepad-test` for driving the real app with `widget_dump()`, `widget_snapshot()`, `screenshot()`, `wait_for_log_contains()`; `RUST_BACKTRACE=1` for panics; the `ao_render` probe to judge baked AO by eye. ## When stuck Re-read the actual API rather than guessing at a signature — `makepad_game_script::api_text()` prints the authoritative verb list, and the crate sources are in the tree. State what you have ruled out. Say plainly what you could not reproduce; an unverified fix reported as fixed is worse than an open bug. ## Required reading - `references/debug-playbook.md` — symptom→cause tables and bisection recipes. - `references/profiling.md` — measuring, budgets, what to cut.