Driving alongside a road accrued no heat at all, so the dominant strategy was to never actually use one. Heat is now credited to any road within 12m of the centreline, tarmac or verge. The cutoff is hard rather than tapered: a taper leaves a gradient to optimise along, where sitting at 80% of the catchment costs 20% of the heat. A cliff edge means you either use the route or genuinely leave it. 12m is a balance, not a guess. It puts ~7.5m of verge past the kerb — two car widths, so hugging the shoulder gains nothing — while leaving 57% of the map off-route. At 20m two thirds of the map counts as on-route, heat becomes unavoidable everywhere, and choosing a different route stops meaning anything. Barricades now span the full corridor for the same reason. Stopping at the kerb left them free to round on the verge at speed. Junction heat is still deferred; a point near a junction is credited to the nearest segment. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
6.9 KiB
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
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 |
npm test # sim + headless physics
npm run build # typecheck + production bundle
Road heat
Every metre driven on or alongside 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.
The route corridor
Heat is credited to a road for anything within ROUTE_CATCHMENT (12 m) of its
centreline, not just the 9 m of tarmac. Otherwise the dominant strategy is to
drive next to every road and never accrue heat at all.
Two consequences follow from that, and both are deliberate:
- The cutoff is hard, not a taper. A taper leaves a gradient to optimise along — sit at 80% of the catchment, take 20% of the heat. A cliff edge means you either use the route or you actually leave it.
- Barricades span the full corridor, well past the kerb. A checkpoint that stopped at the tarmac would be free to round on the verge at speed.
12 m is chosen, not arbitrary: it puts ~7.5 m of verge beyond the kerb (about two car widths, so hugging the shoulder gains nothing) while leaving 57% of the map genuinely off-route. Widening it backfires — at 20 m two thirds of the map counts as on-route, heat becomes unavoidable, and "take a different route" stops being a choice at all.
Other deliberate choices
- 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.tsandsim/car.tsare 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
applyWearonce 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.
- Junctions have no heat of their own. A point near a junction is credited to whichever segment is nearest, so a heavily used crossroads never fortifies as a crossroads. Known gap, deferred on purpose.
- Barricades are visually crude. Blocking the whole 24 m corridor means two long concrete slabs, which reads more like a wall than a checkpoint. The gameplay shape is right; the presentation wants berms, wire, or wreckage on the outer sections. They can also clip scenery placed on the verge — both are static bodies, so physics is unaffected, but it looks wrong up close.
- 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-compatinlines as base64. Switching to the non-compat@dimforge/rapier3dpackage 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.