# Core loop, levels, difficulty ## The brief Before code. Keep it in `artifacts/game-progress.md` and re-read it after any interruption. ``` Loop: Player drives a route, hits checkpoints, beats a lap time. Escalate: Each lap adds traffic; the target time tightens. Win: 3 laps under par. log "race: won" Lose: Timer expires. log "race: lost" Input: kb WASD+Space | pad LS+RT/LB | touch left stick + pedals Camera: chase, 6 m back, 2.4 m up, 60 deg fov, player car 4.2 m long Device: desktop + Quest, 72 Hz target Art: dusk desert, warm key, long shadows, Kenney racing kit ``` Emit those exact log lines from the game. They make win/lose machine-checkable in a `makepad-test` run, which is the difference between a claim and evidence. ## Loop archetypes | Archetype | Escalation | Ends when | Blocks to use | | --- | --- | --- | --- | | Race | opponents, tighter par | laps complete / time out | `Car`, `RaceKit`, `Checkpoint` | | Arena survival | wave size and mix | player dies / wave N cleared | `Character`, `Brain`, `Npc` | | Chase / evade | pursuer speed, spawn rate | caught / escaped | `Brain::chase`, `caught` | | Collect | scatter distance, hazards | quota met / timer | `interactable`, `score` | | Traversal | gap width, hazard density | goal reached / fall | mover + `deck_floor_under` | | Flight | wind, corridor width | landed / crashed | `Plane` | Pick one and do it well. A game that is two archetypes stapled together is usually neither. ## Level structure Three stages, always in this order: 1. **Teach** — the mechanic in isolation, no failure possible. One idea. 2. **Test** — the same mechanic under pressure. This is most of the game. 3. **Twist** — the mechanic combined with a second one, or inverted. Build one complete vertical slice through all three before making level 2. A slice that is wrong is cheap to fix; five levels of the same wrongness are not. ## Generating levels `makepad-game-gen` gives `levelgen`, `kit`, `interior`, `scatter`, `terrain`, `spline`, `lsystem`, `tree`, `implicit`, `mesh`, `texgen`, all from a seeded `rng`. `makepad-game-script::compose` adds `kit_from_index`, `level_placements`, `PlacedTile`. `libs/rtsmap` is the tiled-RTS map generator shared by the importer and the sandbox. Rules: - A level is a **seed plus a preset**, not a vertex dump. That is what makes it replicable across the wire and cheap to store. - Generate, then **validate**: is the goal reachable, is the spawn clear, is there a navigable path? An unvalidated generator produces unwinnable levels roughly as often as it produces good ones. - Hand-place the things the player must notice. Scatter the rest. ## Difficulty Tune three axes independently so a change is diagnosable: **density** (how many), **speed** (how fast), **tolerance** (how much error is forgiven — hitbox, coyote time, par time). Widen tolerance early and narrow it late; players read tolerance as "I got better", and read density as "the game got harder". Never scale all three at once. ## State machine Every game needs these states, and every one needs a UI surface (see `makepad-game-ui-designer`): ``` Boot -> Menu -> Playing -> Paused -> Playing |-> Win -> Menu | Retry |-> Lose -> Menu | Retry ``` Failure modes worth naming: pausing that does not stop the sim step; a retry that leaves score, timers, particle emitters or audio voices from the previous run alive; a win that fires twice because the condition is checked without a latch. Reset through one `reset()` that rebuilds world state, and test it by winning twice in a row. ## Scoring Keep score in the sim (`score`, `score_of` verbs, or a sim field) — never in the widget, or it dies with the view and cannot be replicated to clients. Make it observable: log it on every change so tests can assert on it.