All work GAME / 3D SIMULATION prototype

07  /  GAME / 3D SIMULATION

APEX GP

Twelve circuits.
Nothing downloaded.

A Formula 1 simulator that runs in a browser tab. Every car, circuit, livery and engine note is generated in code — there is no asset pipeline.

August 2026Three.jsViteNode.js 22+Express 5ws / binary WebSocketnode:sqliteWeb Audio APIES modules
85% → 55%grip a fully sliding tyre keeps, before and after the fix

The challenge

Filling a browser racing game with content is the easy half. My first playable builds failed on feel. The car snapped at the smallest steering input, reverse would not engage, and every car on the grid slid the same way and never came back. AI drivers got stuck on track and sat there, and the race engineer's voice was, in my words, "very bad".

What I built

I built a Three.js client and a Node game server, roughly 80,000 lines across 125 ES modules, sharing one physics implementation. Driving runs on a 120 Hz fixed timestep against a Pacejka combined-slip tyre model with load sensitivity, camber, a two-layer thermal model, wear, flat-spotting and aquaplaning; ground-effect aero with a ride-height stall, DRS, dirty air and slipstream sit on top. Race weekends run practice, qualifying, sprint and race, with flags, safety car, penalties, pit stops, dynamic weather and championship progression. I keep the server authoritative for timing and race control over a binary WebSocket protocol, with scrypt accounts, SQLite persistence and packet-level anti-cheat, and nothing is downloaded: cars, circuits, sky, liveries and the engine note are all generated in code.

How it works

HOW THE BRAKE POINT IS FOUND Scan aheadalong the racing line Measure curvaturegeometrically, from the line Solve corner speedfrom grip and downforce Backward passan allowed-speed profile The brake point wherever the profile forces it below its speed No brake-point table exists anywhere the profile is solved from the line, the grip and the downforce THE GUARANTEE Same physics as the player's car difficulty moves pace, consistency and error rate — never the simulation
The mechanism, drawn from the build.

What it looks like

APEX GP — screenshot
Running locallyGAME / 3D SIMULATION

The detail

01

One coefficient

I traced every car sliding and never recovering to the lateral shape factor in the magic formula. At 1.35 the sliding tail sat at 85% of peak grip, so a tyre dragged fully sideways still returned about 1.5 g and a slide never scrubbed off speed. Moving it to 1.63 drops that tail to 55% of peak; peak grip is set by D, not C, so the change moved only the far side of the curve — how a tyre behaves once it has let go.

02

Testing feel

simtest.js drives the real physics, AI, track sampler and timing system with no browser, so its lap times are the numbers the game shows, and a full twelve-circuit sweep takes a few seconds. A valid lap under the circuit's real record is reported as SUPERHUMAN and fails the run. drivetest.js is the stranger harness: 21 gates measuring steering build-up, speed-sensitive lock, reverse engagement and snap resistance, which I run on an infinite flat plane so a result can never be contaminated by running off a real circuit.

03

Drivers, not cheats

Every AI car runs the identical vehicle physics the player does — same grip, power and mass. Difficulty moves only a pace ceiling, a consistency band and an error rate, never the simulation. I wrote no brake-point table anywhere: the racing line is scanned ahead, curvature measured geometrically, cornering speed solved from estimated grip and current downforce, and a backward pass turns that into an allowed-speed profile, so the brake point is simply wherever that profile forces the car below its current speed.

04

Sound from mechanics

I synthesise the engine from the behaviour of a V6 rather than pitch-shifting a recording: a four-stroke V6 fires three times per crank revolution, so I build the note from oscillators locked to engine orders, each with its own load exponent, plus exhaust resonators, turbo whine that lags throttle, and overrun crackle. Each manufacturer reshapes the same mechanics instead of being an EQ preset. Browsers will not route speechSynthesis through WebAudio, so I built the race engineer's radio effect around the speech instead — mic click in, band-limited static bed, squelch out, a duck on the engine bus — with a formant vocoder as fallback when no voices are installed.

05

Trusting the client

The client simulates its own car and ships state at 30 Hz, which is what makes the driving feel good and is also the obvious way to cheat. Every packet is checked against what the physics allows between two ticks: speed and acceleration ceilings, travel per tick against claimed speed, centreline progress against distance actually covered, lateral distance off the road, and lap times against the circuit record. Violations carry weights that accumulate into a time-decaying score and are written to an audit table; serve the client from an origin the CORS allowlist does not cover and it drops straight into offline mode, guest profile intact across a reload.

The outcome

Playable end to end on a local machine. The game server boots with no configuration, creating its own SQLite database and signing key, and the client loads into a live race with a working timing tower, race control flags and penalties. drivetest.js passes all 21 drivability gates — 0 to 100 km/h in 2.88 s, 314 km/h top speed, 3.32 g peak lateral, full steering lock 0.63 s from a keyboard tap, authority down to 29% of parking lock at speed. simtest.js posts three clean valid laps on all twelve circuits; Spa is the one that fails the pace gate, at 23.5 s off the record. A realistic eight-car sweep is harder on it than the single-car run those headline figures come from: five of twelve circuits fail, Spa worst at five valid laps out of sixteen. My README documents its own remaining failures rather than hiding them. I have verified single-player end to end; the multiplayer path — rooms, accounts, binary protocol, persistence — is built and reads correctly but has not been run with two clients in the same race. This is a fan prototype, not deployed, and not affiliated with or endorsed by any racing series.