Free-roam time-trial racing on the real Laguna Seca circuit — in your browser. Drive a Ferrari around the actual layout (yes, over the Corkscrew), chase your best lap, and watch the live +/- delta tell you whether you're up or down on it. GTA-style arcade handling: grippy, weighty, forgiving, with deliberate handbrake drifts.
Built with React + three.js (R3F) + Rapier physics. The car model, track, HDRI sky, engine sounds and tyre samples are real assets; everything else — grass, smoke, sparks, billboards, UI audio — is generated in code.
Inspired by APEX by @mustache_dev.
Live: https://redline-racing.vercel.app
npm install
npm run devOpen the printed localhost URL, type a name, click DRIVE (or press Enter). The name is required — it's what shows on the leaderboard.
| Key | Action |
|---|---|
W / ↑ |
Throttle |
S / ↓ |
Brake / reverse |
A D / ← → |
Steer |
Space |
Handbrake (drift) |
R |
Reset to start line |
L |
Record a lap → rebuilds the minimap + timing line |
P |
Car setup panel (live tuning sliders) |
M |
Mute |
Esc |
Back to menu |
Timing runs on ordered "gates" dropped along a baked centreline of Laguna Seca. Cross them in order; the start/finish gate completes the lap. While driving, the HUD shows a live delta against your best lap at each gate (−0.31 green = up on the pace, +0.42 red = down bad). Best-lap splits persist in localStorage.
Press L to trace a fresh centreline yourself: drive one clean lap between presses and the timing/minimap re-target to your line.
Global fastest-lap board backed by Supabase (anonymous reads/writes, RLS-protected). If no Supabase backend is configured or reachable, the game falls back to a built-in seed board so it's never empty. To wire your own backend, set VITE_SUPABASE_URL + VITE_SUPABASE_PUBLISHABLE_KEY and create the table (SQL in HANDOFF.md).
The car is a tuned Rapier raycast vehicle with a GTA-style traction curve (rear grip falls as it slides → catchable drifts, yaw-damped so they hold an angle instead of spinning). Every knob is live-tunable:
- In-game SETUP panel (
P) — power, brakes, grip, steering, drift. - Leva panels (top-right) exist in
npm run devonly — production builds swap leva for a stub, so shipped builds carry zero dev tooling.
src/
state/ zustand stores (phase/laps/best/name) + per-frame telemetry
game/
config.js all handling defaults + car geometry
Car.jsx drive/brake/steer/drift forces, surface grip, lap wiring
useVehicleController.js Rapier DynamicRayCastVehicleController wiring
LagunaTrack.jsx real-track GLB: visual + collider bake + barrier colliders
colliderSmooth.js ground-only Y smoothing for trimesh twitch (unit-tested)
lapTimer.js gate-based lap timing, splits + best-lap delta, centreline recorder
CarModel.jsx GLB car, wheel spin/steer, auto-fit
ChaseCamera.jsx lagged chase cam, speed FOV, impact shake
Smoke.jsx / Sparks.jsx GPU tyre-smoke + collision-spark particles
Billboards.jsx canvas-text trackside signs
audio.js sampled Ferrari engine loops + tyre samples + wind roar,
master compressor; procedural impacts/UI blips
scene/ Scene assembly (lights, physics, environment) + postprocessing
ui/ Menu, HUD, Speedo, Minimap, Countdown, Leaderboard, Setup, LoadingScreen
No browser needed for the core checks:
node test-physics.mjs # Rapier sim: accel / brake / cornering / drift assertions
node test-smooth.mjs # collider smoothing unit test
node test-laptimer.mjs # simulated laps: gates, splits, PB deltas
node test-leva-stub.mjs # prod stub returns every panel default correctly
npm run build && npm run lintFeel/looks still need human eyes: run npm run dev, drive, tune.
Zero-config Vite preset — build npm run build, output dist. Env vars (all optional): only VITE_SUPABASE_* reach the browser. Secrets like API keys must never be VITE_-prefixed.
Deep dive: HANDOFF.md has the full architecture history, tuning tables and gotchas.