# Drive Between the Lines — prototype Endless driving survival game. - **Phase 0** — a drivable car with persistent wear that is felt through the wheel. - **Phase 1** — a road network whose roads remember being driven, and escalate. ## Running ```bash npm run dev ``` Then open http://localhost:5173. Append `?seed=anything` to regenerate the world — seeds may be numbers or words. | | | |---|---| | `W` / `S` | throttle / brake-reverse | | `A` / `D` | steer | | `Space` | handbrake (rear wheels only) | | `R` | respawn the car — **does not** repair it | ```bash npm test # sim + headless physics npm run build # typecheck + production bundle ``` ## Road heat Every metre driven on a road adds heat to that segment; every road everywhere sheds it slowly. Cross a threshold and the road escalates: `Clear → Patrol → Barricade → Turret` - **Patrol** parks a vehicle on the verge. The road narrows. - **Barricade** puts concrete across it, leaving a gap you have to slow for. - **Turret** adds a tower overlooking the checkpoint. Roughly four traversals take a road from clear to turret; an untouched road cools off in about four minutes. Both numbers are in `sim/heat.ts` and both are guesses meant to be tuned. Three things are deliberate: - **No AI yet.** The props are stationary hazards. Escalation currently means the road gets slower and more expensive to get wrong, which is enough to test the phase's real question. - **Changes never happen on the road you are on.** They are deferred until you leave. Otherwise a barricade would spawn a static collider inside your car — and you are supposed to discover escalation by *coming back*, not by watching it assemble behind you. - **A checkpoint's position is seeded from its segment**, so a given stretch of road always fortifies in the same place. Recognising it is part of learning the map. The road network is a jittered grid with roughly a quarter of its edges removed, keeping the whole thing connected. The loops are the point: "take a different road this time" is not a decision unless alternative routes exist. ## Layout The one rule worth keeping: **`src/sim/` imports neither three.js nor Rapier.** Phases 1–4 (heat, regions, front lines, quests) are all simulation, and keeping them engine-free is what makes them unit-testable and fast-forwardable — you can run a hundred simulated days in milliseconds to tune an escalation curve without ever opening a browser. ``` src/ sim/ pure model — roads, heat, world generation, condition → handling physics/ Rapier world, raycast vehicle, input → wheel forces render/ three.js scene, roads, chase camera core/ fixed-timestep loop, seeded RNG, keyboard ui/ debug HUD carSpec.ts shared car dimensions, so body and mesh cannot drift apart heatProps.ts integration layer: heat levels → colliders + meshes ``` `heatProps.ts` sits at the top level on purpose — it is the one module allowed to touch both Rapier and three.js, because it owns objects that must exist in both or neither. Its tests run headless: three.js scene graphs work fine in Node, so "the barricade's collider was removed along with its mesh" is a unit test. Data flows one way: `sim` → `physics` → `render`. Condition reaches the physics layer already digested into a `Handling` by `deriveHandling`, so there is exactly one place where "how broken the car is" turns into "how it drives". ## Notes on the state of it - **The car handles like a placeholder.** Suspension, grip, and engine numbers in `physics/physics.ts` and `sim/car.ts` are first guesses that pass a smoke test, not something tuned by feel. That tuning *is* Phase 0's verification gate. - **Wear rates are deliberately aggressive** so decline is visible in minutes rather than hours. Turn them down in `applyWear` once the curve reads right. - **No interpolation** between physics steps. Fine at 60 Hz; revisit if the step rate changes. - **Condition and heat are not yet persisted** across reloads. IndexedDB comes with the campaign layer. - **Heat has no diegetic signal at a distance.** You learn a road is hot by arriving at the checkpoint. The brief wants it readable from patrol density and wreckage before you commit — that needs the enemy presence Phase 5 brings. - **The heat HUD lines are debug scaffolding.** The brief is explicit that no numeric heat meter ships; they exist to tune the curve and should come out. - **Bundle:** ~2.7 MB raw / ~945 KB gzipped, dominated by Rapier's WASM, which `rapier3d-compat` inlines as base64. Switching to the non-compat `@dimforge/rapier3d` package serves the WASM as a separate file (~570 KB gzipped, compiled in parallel with the JS) at the cost of extra Vite plugin config. Worth doing before anyone but you plays it; not worth doing now. ## Next Phase 1's gate: **you catch yourself avoiding a road because of its history, not its distance.** That cannot be checked by a test — it needs you driving the same routes for a while and noticing what you start doing. If it fails, the likely culprits, in order: escalation is too slow to matter within a session (`METRES_PER_HEAT`), decay is so fast that nothing accumulates (`DECAY_PER_SECOND`), or the props are not actually inconvenient enough to route around. Tune before building Phase 2 on top.