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>
This commit is contained in:
dejvino
2026-08-07 07:39:05 +02:00
co-authored by Claude Opus 5
commit a66032674d
20 changed files with 2695 additions and 0 deletions
+31
View File
@@ -0,0 +1,31 @@
import type { CarCondition } from '../sim/car';
const BAR_WIDTH = 12;
function bar(value: number): string {
const filled = Math.round(value * BAR_WIDTH);
return '█'.repeat(filled) + '·'.repeat(BAR_WIDTH - filled);
}
export function createHud(seed: number) {
const el = document.getElementById('hud')!;
let last = 0;
return {
update(speedMs: number, condition: CarCondition, now: number) {
// The HUD is debug scaffolding, not the real interface — 10 Hz is plenty.
if (now - last < 0.1) return;
last = now;
el.textContent = [
`${Math.abs(speedMs * 3.6).toFixed(0).padStart(3)} km/h`,
'',
`engine ${bar(condition.engine)} ${(condition.engine * 100).toFixed(0)}%`,
`tires ${bar(condition.tires)} ${(condition.tires * 100).toFixed(0)}%`,
`chassis ${bar(condition.chassis)} ${(condition.chassis * 100).toFixed(0)}%`,
'',
`seed ${seed}`,
'WASD drive · space handbrake · R respawn',
].join('\n');
},
};
}