Go to file
dejvino a66032674d Phase 0: drivable car with persistent wear
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>
2026-08-07 07:39:05 +02:00
.claude Phase 0: drivable car with persistent wear 2026-08-07 07:39:05 +02:00
src Phase 0: drivable car with persistent wear 2026-08-07 07:39:05 +02:00
.gitignore Phase 0: drivable car with persistent wear 2026-08-07 07:39:05 +02:00
index.html Phase 0: drivable car with persistent wear 2026-08-07 07:39:05 +02:00
package-lock.json Phase 0: drivable car with persistent wear 2026-08-07 07:39:05 +02:00
package.json Phase 0: drivable car with persistent wear 2026-08-07 07:39:05 +02:00
README.md Phase 0: drivable car with persistent wear 2026-08-07 07:39:05 +02:00
tsconfig.json Phase 0: drivable car with persistent wear 2026-08-07 07:39:05 +02:00

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 14 (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: simphysicsrender. 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 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-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 0's gate: drive for 1520 minutes, feel the condition changing how the car handles, and want to keep driving anyway. Everything else waits on that.