Vite + TypeScript + three.js + Rapier raycast vehicle, fixed 60 Hz step. Flat plate, seeded obstacle scatter, chase camera, debug HUD. Car condition (engine/tires/chassis) degrades permanently and is derived into handling numbers, so decline is felt through the wheel rather than read off a meter. src/sim/ is kept free of three.js and Rapier imports — the later heat, region and front-line systems all live there, and staying engine-free is what makes them unit-testable without a browser. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2.7 KiB
Drive Between the Lines — Phase 0 prototype
Endless driving survival game. This is the Phase 0 skeleton from the roadmap: a drivable car on a flat plate with scattered obstacles, plus the persistent wear system that makes condition felt through the wheel.
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
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 — world generation, car condition → handling
physics/ Rapier world, raycast vehicle, input → wheel forces
render/ three.js scene, chase camera
core/ fixed-timestep loop, seeded RNG, keyboard
ui/ debug HUD
carSpec.ts shared car dimensions, so body and mesh cannot drift apart
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 is not yet persisted across reloads. IndexedDB comes with the campaign layer.
- 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 0's gate: drive for 15–20 minutes, feel the condition changing how the car handles, and want to keep driving anyway. Everything else waits on that.