# Debug playbook ## Rendering | Symptom | Check | Likely cause | | --- | --- | --- | | Black viewport | camera pos/target, near/far | camera inside geometry; far plane too near; pass not composited | | HUD visible, world not | `scene_state` output | camera never updated from sim; rig left at origin | | Object missing entirely | `model_is_loaded(id)` | wrong id — **silently degrades to a primitive** | | Object at world origin | the `transform` passed to `ModelInstance::new` | identity matrix passed | | Object wrong size | `model_bounds(id)` vs player capsule | kit scale mismatch | | Z-fighting | coplanar surfaces | offset decals/ground overlays slightly | | Object visible through walls | draw order / depth | drawn in the alpha pass when it should be opaque | | Flat lighting | `light_dir` on every draw type | set on some, missed on others | | Shadows disappeared | `quality_reason()` | adaptive quality cut the shadow budget under load | | Props look pasted on | AO | no contact darkening; bake it | | Distant geometry on a different backdrop | fog vs sky colour | fog colour does not match the horizon | | Sparks look like points on a sphere | firework override | missing per-spark uniform radial angle + swirl | | Fine in `cargo check`, broken on launch | — | **the splash DSL resolves at runtime** | ## Assets | Symptom | Cause | | --- | --- | | Everything is primitives | assets never downloaded — run `download_assets.sh` | | One thing is a primitive | wrong id; ids look like `kenney/racing/vehicle-truck-yellow` | | Tests "pass" but prove nothing | asset-dependent tests **skip** without assets; check for skips | | Crowd looks like clones | one cast member reused; vary members, tint, scale, yaw | | Animation plays on one character, not another | members from two different casts — pick one cast per scene | | Download fails loudly | sha256 mismatch — upstream moved or was tampered with; that is the check working | ## Input | Symptom | Cause | | --- | --- | | Nothing responds | event never reaches the mapping layer; log the action stream | | Diagonal movement faster | keyboard vector not normalised | | Stick drift / cross-shaped dead spot | per-axis deadzone instead of radial | | Jump never fires | ground raycast hits the player's own capsule; filter it | | Double jump on one press | input buffer not cleared on use | | Works on desktop, dead on phone | touch handled only as mouse; no hover on touch | | XR controller does nothing | pose read as gamepad axes; see `apps/arcade/src/xr_input.rs` | ## Gameplay | Symptom | Cause | | --- | --- | | Player floats / snags on seams | player on a rigid body; use a kinematic mover | | Crate feels weightless | crate on a mover; use a rigid body | | Bullet passes through walls | discrete stepping; use `projectile_ccd` | | Character climbs walls | `climb` step height too large; walkable slope unclamped | | Lap counts twice | no latch on the checkpoint crossing | | Win fires repeatedly | unlatched win condition checked every tick | | Second run behaves oddly | `reset()` incomplete — score, timers, emitters, audio voices | | Pause does not pause | sim still stepping behind the overlay | | Crowd pathing tanks perf | N individual paths to one goal; use a `FlowField` | ## Netplay | Symptom | Cause | | --- | --- | | Clients diverge | variable `dt`; `std` transcendentals; unseeded RNG; `HashMap` order in `step` | | Cheating possible | client sending state instead of input — host must be authoritative | | Huge bandwidth | replicating vertex data instead of `(preset, seed, position)` | | Desync after N ticks | log a per-tick state hash both sides, find the first differing tick | ## Script route | Symptom | Cause | | --- | --- | | Verb rejects an option key | keys are **typo-guarded** — the key is wrong | | Handlers stop firing late in a tick | the shared **500k-instruction pool** is exhausted; batch instead of per-entity `on_tick` | | Capability denied | `strip_capabilities` / `Trust` — the sandbox working as designed | | Verb not found | check `makepad_game_script::api_text()`, the authoritative list | ## Bisection recipe When the layer is unclear, binary-search the pipeline rather than reading code: 1. Assert the value at the midpoint (instance data handed to the renderer). 2. Right there? The fault is downstream (draw/shader/pass). 3. Wrong there? Move up (scene state, then sim). 4. Repeat. Three or four steps localises almost anything in this engine. ## After every fix Add the test that would have caught it, in the lowest layer that can express it — a Cx-free unit test if possible, a `makepad-test` driven test if it genuinely needs the app. A fix without a regression test is a fix with a return date.