# 3DRace

![3DRace icon](pwa-192x192.png "3DRace")

## Part 1 — Player manual

Race a sixteen-car field around six circuits, from pine forests to the Las Vegas Strip at night. Up to eight people can drive together using phones, tablets, computers or gamepads. Empty places are driven by AI, and you can watch from six camera angles whenever you want a break. Use a current browser with WebGL 2 support.

### Start driving or invite friends

1. [Open 3DRace](./). The game starts racing automatically in Trackside view.
2. Select the **steering-wheel button** to take the wheel. The view switches to Chase. During an existing race, you take over the lowest-placed available AI car without resetting its progress.
3. Open **Settings** with the cog button to enter your driver name. Choose **Done** to return to the race.
4. To invite someone, select the **chain-link button** and have them scan the QR code or open its link. Everyone must join the same room. When sharing from the server computer, use its network address rather than a localhost address.
5. Select the steering-wheel button again to let AI drive your car while you watch. The shared race continues.

Human drivers start behind the AI at the next new grid. If eight human places are occupied, additional devices can still watch. A reserved or finished car cannot be taken over.

![Chase-camera driving, race information, track map and speed display](screenshot-driving.jpg "Driving and race information")

### Controls

- **Keyboard acceleration:** hold **W** or **Up arrow**.
- **Keyboard braking and reverse:** hold **S** or **Down arrow**. The car brakes to a stop before reversing.
- **Keyboard steering:** **A/D** or **Left/Right arrows**.
- **Keyboard shortcuts:** **C** cycles cameras; **R** recovers a stranded car; **P** or **Escape** hands driving to AI. Escape also closes an open settings or leave dialog.
- **Touch:** hold a steering arrow on the left and a pedal on the right. Green accelerates; red brakes and then reverses. You can hold steering and a pedal together. After more than two continuous seconds off track, a **Recover** button appears above the steering arrows.
- **Gamepad:** the left stick steers; the right trigger or **A** accelerates; the left trigger or **B** brakes and reverses.

Pressing both pedals stops the car. Reverse is limited to about **18 mph**. The speed display uses **mph**, and track lengths use **miles**.

On supported phones, buttons give a short vibration and collisions involving your own car give a stronger double pulse. Vibration works independently of sound. Devices without vibration support play normally.

![Landscape phone layout with left and right steering buttons, green accelerator and red brake pedal](screenshot-controls.jpg "Mobile controls")

### Understand the screen

The top-left corner shows the track name, a mini-map, track length and connection status. The top-right panel shows the selected driver's name, position, current lap, lap time and best lap. The human-player count is highlighted in green. Position and lap numbers flash green three times when they change.

The icon row, from left to right, contains:

- **Steering wheel:** drive or watch.
- **Camera:** change view.
- **Medal:** show the standings and choose a car to follow.
- **Cog:** open settings.
- **Chain link:** invite another device to the room.
- **Fullscreen corners:** toggle fullscreen where the browser supports it.
- **Back arrow:** return to the app list after confirmation, on this device only.

Speed appears at the bottom right. Messages identify contact, reverse, run-off surfaces, recovery and finishing. On a phone in landscape, the panel becomes compact to leave more of the road visible.

### Settings and difficulty

![Settings with driver name, track, laps, opponents, sound controls](screenshot-settings.jpg?v=instant-recovery-1 "Settings")

Every player can change their own **Driver name** and **Sound** preference. The room host controls **Track**, **Laps**, **Opponents** and **Restart race**. If another player tries to change a shared selector, a tooltip points to it and says **“Only the host can change this setting.”**

Choose **3, 4 or 5 laps**. Changing laps or track starts a fresh shared countdown and keeps the returning drivers. Changing difficulty takes effect during the race and carries into the next one.

- **Relaxed:** the most forgiving opposition, but still a competitive field.
- **Standard:** quicker cornering and a stronger challenge.
- **Fast:** demanding opponents with faster front runners.
- **Expert:** the quickest opposition.

Front-grid AI drivers are more capable than those further back. AI uses the same acceleration, braking and top-speed limits as your car; its advantage comes from pace, cornering and traffic handling.

Your name, sound choice, laps, difficulty and current track are saved on this device. Automatic track progression updates the saved track too. Refreshing a solo game starts a new race on that track; joining an existing room uses the room's current settings. Opening settings focuses **Done**, so the on-screen keyboard does not appear until you select the name field.

### The six tracks

These Whole track screenshots show each circuit's layout and surrounding scenery. The order below is also the automatic race rotation.

#### Pine Circuit — 0.97 miles

A flowing forest circuit surrounding a lake. Look for the little dock, rowing boats, sailboats and several shapes of pine tree. Selected corners have wider sand and gravel run-off areas.

![Trackside view of cars racing past Pine Circuit's lake, sailboats and pine trees](screenshot-pine.jpg?v=trackside-1 "Pine Circuit — Trackside")

#### Lakeside Fair — 1.35 miles

A longer circuit with linked bends, two blue lakes, orange suspension bridges and rocky shores. The fairground includes a large moving Ferris wheel, carousel and striped tents, with houses along the waterfront.

![Trackside view across Lakeside Fair's suspension bridge with the Ferris wheel beyond](screenshot-lakeside.jpg?v=trackside-1 "Lakeside Fair — Trackside")

#### Riviera City — 1.50 miles

A technical street circuit with apartment blocks, a fountain square, tight returns and a curved tunnel. Following cameras lower and pull closer before the tunnel, then return to the selected view after the exit. Whole track view opens the roof so you can still see the cars.

![Trackside view of the field rounding a Riviera City bend between apartment buildings](screenshot-city.jpg?v=trackside-1 "Riviera City — Trackside")

#### Amsterdam — 1.80 miles

A winding route between striped tulip fields, rivers, a canal and Dutch drawbridges. Narrow canal houses face the road, windmill sails turn above the fields, and low hills form the backdrop.

![Trackside view of Amsterdam's canal houses and drawbridge with a windmill in the distance](screenshot-amsterdam.jpg?v=trackside-1 "Amsterdam — Trackside")

#### London — 1.85 miles

Race across Tower Bridge and Westminster Bridge beside the Thames. Approaches lead towards Buckingham Palace, the Houses of Parliament, Big Ben and the London Eye. The Shard, Gherkin and O2 dome join the skyline, with red buses, black taxis, riverside boats and terraces around the circuit.

![Trackside view of cars racing through London's landmark-lined circuit](screenshot-london.jpg?v=trackside-1 "London — Trackside")

#### Las Vegas — 1.73 miles

A night race along the Strip and through a winding casino section. Pass Bellagio's fountains, the Paris Eiffel Tower, Luxor pyramid and sky beam, Venetian, New York–New York, Sphere, High Roller, STRAT and Welcome sign. Warm yellow street lamps cast translucent polygon beams onto pools of light below. Casino facades, plazas and fountains glow against the dark surroundings, rooftop searchlights reach into the sky, and glossy car paint catches coloured highlights.

![Trackside view of the Las Vegas night race beneath warm street lamps and illuminated casinos](screenshot-vegas.jpg?v=trackside-1 "Las Vegas — Trackside")

### Racing, run-off and recovery

Brake before tighter corners and leave room for other cars. Grass, gravel and sand slow you down; paved run-off preserves more speed. The barriers move further away from the road at some corners, and stacked tyres mark parts of the outer boundary. Bridge and tunnel barriers stay close to the road. Cutting across the infield will not complete a valid lap.

Collisions cause impact sounds, sparks and cosmetic damage. Damage does not reduce performance and resets for the next race. Heavy braking can leave fading tyre marks. The flashing rear light represents braking or energy recovery; there is no battery control to manage.

If you are stopped or stranded, press **R** or tap the mobile **Recover** button after spending more than two seconds off track. Returning to the road resets that button's delay. Recovery moves you a short distance back onto the track, lets you drive immediately and gives temporary contact protection. It is unavailable while driving normally at speed or after finishing. Sand and gravel still slow the car, but holding either pedal lets you drive or reverse out of a trap.

### Cameras and spectating

The camera button cycles through **Chase**, **High chase**, **Whole track**, **Nose**, **Trackside** and **Side**. Your camera choice is local to your device.

Open **Standings** with the medal button, then select a driver's camera button to follow them. Following another car hands yours to AI. Your row is highlighted, and AI-controlled entries have an **AI** badge.

If Trackside view is following an AI car when a human joins, it switches to that human. A view already following a human keeps its selection. You can manually follow an AI again afterward.

### Sound and accessibility

Tap the page or press a key to enable audio. Gamepad-only users may need to tap first. **Settings → Sound** mutes engines, tyres, impacts, countdowns and celebrations. Hidden tabs are silent.

You hear nearby engines passing in stereo, tyre squeal under heavy braking, a quieter scrub during sustained hard cornering, and rumble on loose surfaces. Brief steering corrections and light braking stay quieter.

The game respects the device's reduced-motion preference: confetti and sparks are suppressed, the finish number is static, the recovery light is steady, and decorative rides, windmills and Vegas animations stop.

### Finishing and the next race

Finish in the top three while driving to see a large 3D podium number, confetti and a short applause effect. Muting sound also mutes applause. After you cross the line, your result is fixed even while the car continues moving.

Once the winner finishes, the remaining field has **45 seconds** to finish. Unfinished cars then receive **DNF**. Results show finishing order, total time and AI-assisted entries. Cars continue a slow victory lap during a **15-second** countdown to the next circuit.

Races repeat automatically: **Pine Circuit → Lakeside Fair → Riviera City → Amsterdam → London → Las Vegas → Pine Circuit**. Track, lap and difficulty changes apply to everyone in the room.

### Leaving, reconnecting and installation

Changing tabs releases your controls and hands the car to AI. Return and toggle driving on to resume. Reloading the same tab can reclaim your car. If the hosting browser leaves or becomes hidden, another foreground device can take over after a short synchronisation pause.

Keep at least one device connected to preserve the current race. If every device leaves, the live race is lost; saved preferences remain. A host change can resume from a slightly earlier checkpoint.

Use your browser's **Install app** or **Add to Home Screen** command for a separate game window. Installation requires HTTPS, or localhost on the server computer, where supported. The game still needs a network connection; an offline launch provides a retry link.

## Part 2 — Implementation details for recreation

This section specifies the current implementation, including behaviour that must survive a rebuild. The source files are the reference for exact procedural meshes, tuning constants and packet field order. [NETWORKING.md](NETWORKING.md) explains the prediction design and its failure cases. [PLAN.md](PLAN.md) is historical planning material, not a substitute for the behaviour below.

### Runtime, files and responsibilities

Use vanilla HTML, conservative CSS and ES5-style application JavaScript organised as global object modules. Load RCWeb's `/assets/core/comms.js` before application scripts. Expose `race3d.receive` globally and install RCWeb callbacks before `rc.connect()`. Room and client identity become usable only after `rc.onConnected()`.

The existing renderer is the locally vendored **Three.js r186** module, loaded by the small module bootstrap in `index.html` and passed to `race3d.start(THREE)`. The application modules themselves remain ES5-style. WebGL 2 is required. Reuse the bundled QR library and existing icon set; do not add a framework, CDN dependency, remote models, fonts or audio files.

- [index.html](index.html) and [style.css](style.css): canvas container, HUD, controls, dialogs, responsive layout and finish-number styling.
- [script.js](script.js), global `race3d`: startup, input, settings, storage, UI, camera selection and connecting the other modules.
- [engine.js](engine.js), `RaceEngine`: track definitions and queries, one-car state, steering, surfaces, barriers, lap gates and AI controls.
- [race.js](race.js), `RaceSession`: sixteen-car authority, grid, race phases, traffic avoidance, collisions, recovery, classification and snapshots.
- [network.js](network.js), `RaceRoom`: authority election, driver identity, fixed-tick commands, checkpoints, replay, interpolation, reconnect and migration.
- [car-model.js](car-model.js), `RaceCarModel`: procedural open-wheel car, independent paint materials, driver and articulated components. [car.html](car.html) is a separate model study.
- [graphics.js](graphics.js), `RaceGraphics`: Three.js scene, track surface, cars, cameras, tyre marks, impact particles and disposal.
- [scenery.js](scenery.js), `RaceScenery`: terrain, water, bridges, buildings, vegetation and animated props for the five daytime circuits.
- [vegas.js](vegas.js), `RaceVegas`: night-track scenery, landmark meshes and bounded decorative animation.
- [audio.js](audio.js), `RaceAudio`: local Web Audio engine, tyre, impact, countdown and applause synthesis.
- [feedback.js](feedback.js), `RaceFeedback`, and [haptics.js](haptics.js), `RaceHaptics`: local HUD flashes, confirmed podium effects and touch vibration.
- [pwa.js](pwa.js), [sw.js](sw.js) and [webmanifest.json](webmanifest.json): optional installation and offline reconnect page. The service worker fetches current code and does not cache race state or room setup.

### Track geometry and reproducible layouts

Use metres, a flat X/Z driving plane and +Y upwards. Heading zero points along +Z. Interpolate a closed, uniform Catmull–Rom curve through each control-point array below, taking **32 samples per control-point span**. Join samples with straight segments; accumulate distance `s`, tangent `(tx,tz)` and right normal `(tz,-tx)` for every segment. Road half-width is **8 m** and the default barrier offset is **14 m**.

For four consecutive scalar coordinates `a,b,c,d`, evaluate a span from `b` to `c` with this function. Wrap control indices.

```javascript
function cat(a, b, c, d, t) {
    var q = 2*a - 5*b + 4*c - d;
    var r = -a + 3*b - 3*c + d;
    return 0.5 * (2*b + (-a+c)*t
        + q*t*t + r*t*t*t);
}
```

These arrays contain `[x,z]` pairs:

```javascript
var layouts = {
    pine: [
        [-180,200],[-180,60],
        [-170,-130],[-110,-215],
        [60,-220],[180,-150],
        [200,-20],[125,50],
        [90,140],[130,240],
        [0,285],[-120,275]
    ],
    lakeside: [
        [-220,220],[-220,100],
        [-220,-60],[-220,-200],
        [-140,-260],[-20,-225],
        [95,-255],[225,-200],
        [255,-100],[200,-30],
        [110,-25],[90,35],
        [130,85],[235,85],
        [270,155],[235,265],
        [125,295],[40,250],
        [-70,275],[-170,290]
    ],
    city: [
        [-245,180],[-245,65],
        [-260,-20],[-230,-90],
        [-260,-160],[-220,-235],
        [-135,-255],[-70,-200],
        [0,-195],[55,-265],
        [150,-265],[220,-200],
        [220,-80],[195,0],
        [250,55],[250,130],
        [190,150],[150,105],
        [95,95],[65,155],
        [100,220],[190,240],
        [190,330],[105,355],
        [10,300],[-70,330],
        [-180,290],[-245,255]
    ],
    amsterdam: [
        [-270,180],[-270,80],
        [-270,-50],[-265,-180],
        [-210,-270],[-110,-290],
        [-35,-260],[40,-270],
        [130,-310],[225,-285],
        [265,-240],[235,-200],
        [155,-185],[45,-195],
        [-70,-195],[-125,-180],
        [-150,-135],[-125,-85],
        [-70,-65],[40,-65],
        [140,-115],[220,-115],
        [260,-65],[265,0],
        [270,95],[220,175],
        [165,195],[140,245],
        [145,295],[80,340],
        [-10,305],[-105,325],
        [-195,285],[-260,255]
    ],
    london: [
        [-330,-170],[-350,-270],
        [-340,-380],[-260,-440],
        [-180,-410],[-125,-365],
        [0,-350],[90,-340],
        [130,-275],[95,-220],
        [55,-170],[85,-125],
        [175,-120],[265,-170],
        [320,-150],[320,-80],
        [320,80],[320,170],
        [390,240],[400,330],
        [310,395],[200,375],
        [50,350],[-120,345],
        [-145,250],[-220,210],
        [-315,215],[-345,155],
        [-310,100],[-310,-70]
    ],
    vegas: [
        [-280,250],[-280,100],
        [-280,-80],[-280,-260],
        [-260,-365],[-175,-415],
        [-60,-405],[5,-325],
        [100,-285],[230,-310],
        [320,-260],[365,-150],
        [320,-45],[225,0],
        [240,100],[345,155],
        [360,260],[285,350],
        [170,380],[90,330],
        [-10,355],[-100,405],
        [-210,380],[-275,325]
    ]
};
```

`sample(s)` wraps around the circuit and returns its position, heading and normal. `nearest(x,z)` projects onto the sampled segments and returns `s`, distance and signed offset. Index segments in **32 m spatial cells**, with deterministic tie-breaking and a complete fallback search. Rendering, AI, physics, scenery clearance and the mini-map must all use this geometry.

At selected outside corners, expand the barrier smoothly using `blend=(1+cos(PI*distance/reach))/2` and `width=14+(maximumWidth-14)*blend`. Store the corner index, reach, maximum width and surface in `runoffs`; use the same profile for the mesh and collisions. Draw sand, gravel or paving, then stack tyres along the widened outer boundary. Keep river-crossing and tunnel geometry clear of the driving corridor. Scale and centre the mini-map from actual track bounds with padding, rather than assuming every track fits Pine's dimensions.

### Scenery and visual construction

Build scenery from flat-coloured boxes, cones, low-sided cylinders, triangle fans and custom quad meshes. Merge static vertices by material or use instancing for repeated objects. Keep architectural silhouettes recognisable without textures or high-poly ornament. Use separate meshes only where animation, cutaways or material behaviour require them. Do not add the extra decorative gantries that were rejected; retain the original start structure.

- **Pine:** lake centre `(-25,15)`, radii `(91,136)`, water below road level. Cut its irregular shore out of the ground. Add a timber dock, small hulls and sails, reeds and rocks. Mix slender, broad and layered pines, with varied greens, height and rotation.
- **Lakeside:** lake centres `(-220,-60)` and `(247,180)`, radii `(112,126)` and `(83,82)`. Use deep-blue faceted water, angular rock banks and tall orange suspension-bridge towers. Main cables sag between towers with vertical hangers outside the road. Include the large Ferris wheel with upright cabins, carousel, tents and waterfront houses.
- **City:** lay out warm stone apartment blocks on a street grid, with repeated recessed-looking windows, cornices and roof terraces. Reserve a fountain square and open road corridors. The tunnel follows control spans 10 through 13 and is **8.5 m** high. Construct continuous wall/roof rings from averaged adjacent road normals, with lamps and an overview cutaway.
- **Amsterdam:** use a main river centred at `(0,0)` with size `(1800,44)`, a canal at `(-60,184)` sized `(28,280)`, and another river at `(0,-420)` sized `(1800,36)`. Add broad coloured strips for tulips, never individual flowers. Put fields and houses outside as well as inside the circuit. Rotate each narrow, steep-roofed or stepped-gable house towards its nearest road point so the windowed facade faces drivers. Add white Dutch drawbridges, barges, three well-spaced windmills, and low faceted hills. Sails rotate; their towers stay fixed.
- **London:** the Thames is a long water strip centred on `z=0`, size `(2200,140)`. Reserve landmark approach sightlines before placing terraces, trees or props. Build Parliament from repeated bays and pinnacles; give Big Ben four clock faces. Add Buckingham Palace's facade, balcony, gates and Victoria Memorial. Tower Bridge needs paired stone towers, blue suspension elements and elevated walkways, with an unobstructed road deck. Include Westminster Bridge, the London Eye's upright capsules, the O2 dome, faceted Shard and Gherkin. Place buses, taxis, phone boxes, lamps and boats outside the full road/run-off footprint. Keep the grandstand close enough to read from the road without its roof or seating projecting over it, and keep buses out of its sightline.
- **Vegas:** use a dark blue ground and sky, desert silhouettes, palms, lit windows and neon strips. Bellagio's fountain lake is centred at `(-165,40)` with radii `(64,90)`. Construct the Paris Eiffel lattice silhouette, Luxor pyramid and fading sky beam, Venetian canal and campanile, New York skyline/Statue of Liberty/coaster, coloured polygonal Sphere, High Roller with upright cabins, STRAT and Welcome sign. Animate fountain jets, the Sphere and observation wheel with bounded work per frame.

Prevent z-fighting structurally: a pitched roof has two exposed sloping planes, not a flat lid poking through; a bridge deck/top face must not be duplicated. Offset genuinely layered window, trim and road-marking faces slightly. Use terrain holes under water instead of covering one coplanar surface with another. Test building, bridge and stand footprints against the **outer run-off barrier**, not only the centreline or asphalt edge.

### Renderer, car and cameras

Use a full-window antialiased renderer with device pixel ratio capped at **1.75** and sRGB output. Daylight uses a hemisphere light and one directional light; London has a softer blue sky. Vegas uses sky `0x070b1d`, fog `0x10152c`, subdued hemisphere lighting at **0.34** intensity and a warm `0xffdfa3` directional light at **0.65** intensity. Its dark road alternates pre-coloured amber light pools at roughly **55 m** intervals; emissive windows and neon provide the night appearance without a real light or shadow map for each lamp. Whole track view disables distance fog while drawing.

Vegas street lamps also have eight-sided translucent shafts, angled slightly towards the road, and matching flat octagonal ground pools. Batch all shafts into one mesh and all pools into another. Fade vertex brightness down each shaft; use additive blending, depth testing, no depth writing and a single transparency pass. A small material shader hook fades their opacity between **70 m and 260 m** from the camera, preventing distant beams from adding up into a white horizon. Disable scene fog on these additive meshes. They are decorative light, not collision geometry, and add no real-time lights or shadows.

Use warm yellow vertex colours for street beams. Three additional batches provide triangular facade uplights, twelve-sided plaza/fountain glows and four eight-sided rooftop searchlights. Fade the searchlights between **300 m and 850 m**, with a per-material uniform so they share the shader program without sharing the street lamps' shorter fade range. All effects remain static, low-polygon decorative meshes.

Model the open-wheel car procedurally: narrow nose, wings, cockpit/helmet, sidepods, visible suspension, four tyres, hubs and rear light. Use two independent livery colours per car. Reflective physical paint uses metalness **0.72**, roughness **0.15**, clearcoat **1**, clearcoat roughness **0.065** and environment intensity **1.65**; preserve flat faceting. Generate the reflection environment once with PMREM. Vegas uses a dark backdrop, a narrow warm overhead panel and gold, aqua and pink light strips; these are stylised reflections rather than live captures of the buildings. Steering articulates the front wheels. Wheel rotation uses frame-relative facet motion capped below half a facet to avoid the backwards-spinning wagon-wheel effect. Cosmetic impact compression, paint damage, sparks and tyre marks must not become an extra movement simulation.

Use a **57-degree perspective camera** for following views and an orthographic, tilted camera for Whole track. Chase sits approximately **15 m behind and 6.5 m high**; High chase uses **29 m and 15 m**. Nose sits **3.2 m ahead and 0.85 m high**. Side is about **11 m sideways and 3.5 m high**. Trackside chooses successive road anchors at 180 m intervals. Frame the whole circuit from its actual bounds and draw numbered car markers. Raise Chase's look target for London and Vegas to reveal the skyline.

Anchor camera position to the displayed car. Smooth only the chase orbit heading with bounded angular lag; do not spring the whole camera behind a second car simulation. For the City tunnel, smoothly lower following views towards **3.5 m**, cap following distance at **15 m**, and keep orientation tied to the car. Blend before entering and after leaving. Raycast from the car towards the intended camera against the tunnel walls and cover; pull in before the first obstruction with a small margin. Never steer the camera by changing segment normals, alter car physics inside the tunnel, or introduce a camera collision impulse. Nose keeps its own placement. Hide the cover in Whole track mode.

Dispose renderer resources, geometry, materials, animated scenery and DOM car markers on track changes. Honour reduced motion in each animation owner. Reuse bounded pools for sparks and tyre marks instead of allocating indefinitely.

### Vehicle simulation and race rules

Keep simulation independent of render rate: **60 fixed ticks per second**, each with **two collision substeps**. Snapshot/restore must retain every variable that influences the next tick. Rendering and audio only read state.

- Hold forward speed to **78 m/s**, reverse to **8 m/s**. Forward power is `18*(1-speed/180)`, baseline resistance is `0.9+0.0016*speed*speed`, and braking contributes up to `32 m/s²`. Braking stops before reverse acceleration begins. Opposing pedals hold the car stopped.
- Smooth steering towards the requested `[-1,1]` input with `min(1,dt*7)`. Positive input means driver-right. Signed path curvature is `-steering*1.15/(abs(speed)+10)`, multiplied by `0.65` off road. Integrate the rear-axle arc and reconstruct the centre from the **1.58 m** rear offset; the wheelbase is **2.63 m**. There is no lateral rear-axle slip.
- Beyond the 8 m road half-width, apply surface resistance in addition to normal drag: sand `8+0.4*speed`, gravel `7+0.4*speed`, paving `2+0.06*speed`, grass `10+0.22*speed`. Lower resistance at low speed allows forward and reverse escape from traps. Match the visible run-off profile; City shoulders are paved.
- Barrier contact clamps the centre inside `runoffWidth-1.3`, damps speed with `exp(-5*dt)` and records the normal/impact speed. Approximate each car with three overlapping circles centred along its heading at `-1.3,0,+1.3 m`; the combined circle collision radius is **2.35 m**. Separate overlaps equally. For closing contacts, apply the existing asymmetric speed damping and emit impact IDs with a cooldown. Cosmetic damage never changes pace.
- Validate laps through ordered quarter-lap gates. Only valid forward movement inside the legal drivable corridor advances gates; start-line oscillation, reverse crossings and infield cuts cannot manufacture laps. Legal wider run-off can still cross gates and the finish line.

`RaceSession` owns **16 fixed car slots**, with at most **8 human drivers**. Start humans behind AI, sort by the established grid order, place rows 8 m apart and use lanes at `-3.2` and `+3.2 m`, beginning 12 m behind the line. Use phases **LOBBY → COUNTDOWN → RACING → FINISHING → RESULTS**. Countdown lasts **360 ticks**. Interpolate the finish time within the substep and freeze each finisher's progress/classification immediately.

The first finish starts a **45-second** deadline. Complete the results when everyone finishes or the deadline expires; mark remaining entries DNF. Finished cars drive slowly, and results run a **900-tick** victory-lap countdown before cycling track. Preserve race timing and classification during that motion. A new race clears best times, damage and finish feedback while retaining entrants and shared settings.

Recovery is authority-owned, allowed only for an unfinished, unprotected car that is stopped or stranded. Move back up to **6 m**, never wrap backwards across the start to award progress. Reset steering/speed, deduct the actual recovery distance from progress, apply **180-tick** ghost protection without a driving delay. Auto-recover a stuck AI only on the authority. Client prediction must not invent a recovery because future remote inputs are missing.

### Opponent pace and traffic

Use base paces **1.32, 1.44, 1.56 and 1.69** for Relaxed, Standard, Fast and Expert. For zero-based AI grid position `g`, apply `basePace*(1-g*0.014)`. Preserve these values in checkpoints, recompute them on a difficulty change and use restored difficulty when starting a restored lobby. Do not increase AI engine power, grip, braking force or maximum speed.

Steering aims down the selected lane with lookahead `6+abs(speed)*0.28` and a **0.12-second predicted pose** to compensate for steering response. Inspect fifteen 10 m stretches ahead. Convert centreline bend to lane curvature with `bend/(1-bend*lane)` using the existing denominator guard. Compute corner speed from `sqrt(24/max(abs(curve),0.002))*pace`, constrained by the tuned steering envelope `max(12,1.6/max(abs(curve),0.002)-10)`. Work backwards through the bends with `sqrt(cornerSpeed²+2*29*remainingDistance)`. Pace affects corner commitment, not braking distance. Modulate throttle and brake continuously around the target speed.

Traffic checks use **each other car's own road normal** and wrapped longitudinal gap; projecting every neighbour onto the follower's normal fails on bends. Search about 100 m ahead and 80 m behind. Consider both actual lateral position and the intended lane. Only initiate an overtake when the alternative lane is clear, rear closing speed permits it, the road ahead is sufficiently straight and the lane-change cooldown has expired. Reserve another AI's target lane as well as its current physical lane.

After choosing a passing lane, keep braking behind the lead car until actual lateral clearance exists. Following speed is bounded by both a closing-distance calculation and a speed-dependent gap, approximately `7+0.18*speed` metres. Exclude ghosted cars from traffic and contact checks. Keep collision detection active; reducing reported collisions by suppressing contact is not an acceptable substitute for better driving.

### Multiplayer and checkpoint contract

The Java server routes RCWeb messages; it does not run race physics. One browser is authoritative. A locally generated session token identifies a returning driver, while a per-connection generation separates reconnects from old messages. Exchange validated envelopes with version, sender, generation, kind and data through `rc.sendFunctionCall`, passing the target, global function name `race3d.receive` and message object.

The host runs the only official race and publishes full recovery checkpoints approximately every **100 ms**. Packets include host/term/serial/race ID, the complete `RaceSession.save()` result, roster, held controls and queued future inputs. Driver commands target the host and carry tick/sequence information. Reject stale epochs, invalid settings, malformed inputs and non-host shared-setting requests.

Avoid sending the host its own checkpoint. For legacy/unknown capability, publish once to `3drace,!<hostClientId>`. When membership and capability are confirmed, send one compact message to a **comma-separated list of confirmed client IDs excluding the host**. This is one multicast call, not one copy per recipient; an unknown late join must never be sent a format it has not announced.

Compact format 1 replaces known car/state objects with arrays in the immutable `carFields` and `stateFields` order in `network.js`. It is **lossless**: preserve all numeric precision, flags, timing, damage, AI lane/pace/cooldown and recovery state. Objects with unknown or missing fields retain object form. Decode into fresh objects, validate array lengths and packet shape, and visibly report malformed checkpoints. Negotiate support through `hello.checkpointFormat`; retain the legacy object path. Do not silently reorder fields or add quantisation under the same format version.

Preserve three distinct presentation paths:

1. **Host:** interpolate all cars between the same two official ticks. Apply local input on the next real tick; do not run a second speculative host car.
2. **Non-host driver:** restore the last checkpoint, overlay pending local commands and replay the complete sixteen-car collision world to a common tick. Include held and queued human commands; retain last-known future controls rather than alternating neutral guesses and AI. Use the six-tick lead margin plus measured RTT, with bounded replay.
3. **Spectator:** interpolate all cars on one monotonic buffered clock, roughly **200 ms** behind. Do not extrapolate through missing snapshots.

Reconcile at the same command tick. Adopt corrected physics immediately; a small free-flight presentation error may fade over **100 ms**. Contacts, recovery, large corrections and race changes bypass the fade. Rendering must not push cars apart, clamp walls or mutate correction state. Otherwise a future local car collides with a past opponent, producing artificial overlap and shaking.

Use presence, visibility, host terms and latest valid checkpoints for foreground-peer election and host migration. Preserve the race ID, roster, settings, inputs and results countdown through migration. Disconnects hand a car to AI; reclaim uses the session token. If every browser leaves, there is no durable race server. The game assumes trusted room participants, not hostile-client anti-cheat.

### Interface, persistence and local feedback

Use white text, dark translucent green panels, Arial/Helvetica and accent **`#d9fd60`**. Keep the canvas central, logo/map at top left, compact icon/timing panel top right and a smaller transparent speed display with text shadow at bottom right. Touch controls sit at the lower corners with rounded edges and press shadows. Use short-landscape media queries to reduce panel, map, typography and controls without covering the road centre. Test both portrait and landscape phones. Avoid heavy paint effects on weak devices.

Use labelled name/settings fields, button titles/ARIA labels, visible focus and live status messages. Name input is whitespace-normalised and limited to **20 characters**. Focus Done when settings opens. Guard Track, Laps and Opponents on pointer and keyboard interaction; show the exact player-manual tooltip with its arrow aimed at the attempted selector. Do not use `alert()`. The canonical `rc.buildQRCodePanel` uses the real room URL and standard QR styling. Back navigation targets `/v-c/?r=<room>#apps` in this tab after confirmation.

Local storage keys are **`rcwebName`**, **`3drace-laps`**, **`3drace-difficulty`**, **`3drace-track`** and **`3drace-muted`**. Sound stores the strings `true`/`false`. Validate loaded values against allowed laps, difficulty IDs and track IDs; report unavailable storage visibly. Save confirmed room settings and automatic track progression. Preferences seed a new room; they must not overwrite an existing authority's state. Session storage carries identity/reclaim information, not a durable saved race.

Pointer capture supports simultaneous steering/pedal touches and releases cancelled or lost pointers. Clear controls on blur, visibility changes, leaving drive mode and disconnect. Map keyboard/gamepad input to the same steer/throttle/brake structure. Drive/watch state, selected camera, sound and haptics are local; shared race settings remain host-owned.

Synthesize audio with Web Audio after a user gesture. Reuse oscillator/noise/filter nodes and buffers, mix sixteen engines by distance and stereo direction, and apply Doppler from camera/car relative motion. Reset Doppler history on camera cuts or large corrections. Heavy braking drives squeal; corner scrub starts only after sustained load, with loose-surface rumble separate. Use short filtered noise and a low thud for impacts, plus locally generated countdown and applause. Audio cannot mutate simulation state. Mute/hidden states silence playback.

Deduplicate effects with confirmed race, car and impact/finish identities. Haptics uses a **12 ms** button pulse and **[100,40,140] ms** collision pattern, with a **500 ms** collision cooldown; vibrate only for fresh impacts on the locally driven car. HUD metric changes make three quick green flashes over **900 ms**, without flashing on initial state or follow changes. Top-three human finishes show a **4.5-second** CSS-extruded numeral, a bounded 90-piece canvas confetti burst and short applause. Delay the results panel while celebrating. Do not replay a celebration on reconnect, repeated checkpoints, rollback, an already-finished initial snapshot or spectator handoff.

### Verification before accepting a recreation

App files are served directly; no Java build or server restart is needed for these changes. Use a local running RCWeb server and valid short room IDs. Keep the tests deterministic and exercise real movement, not just flags or implementation-shaped assertions.

- Run `engine.test.cjs`, `race.test.cjs`, `finish.test.cjs`, `runoff.test.cjs` and `track-query.test.cjs` for physics, barriers, lap validation, final classification and exact geometry queries.
- Run `ai-ability.test.cjs`, `ai-traffic.test.cjs` and `ai-races.test.cjs` for difficulty progression, physical overtaking, stopped traffic, lane reservations and front-to-back ability. The extended suite checks 72 solo four-lap runs and 48 full-field three/five-lap races across every track and level. Require the whole AI field to finish without recovery, meaningful pace differences, and rare contacts.
- Run `checkpoint.test.cjs`, `network.test.cjs`, `coherent-prediction.test.cjs`, `contact-prediction.test.cjs`, `recovery-prediction.test.cjs`, `prediction-correction.test.cjs` and `wall-network.test.cjs`. Check exact restored state and same-tick replay, no host echo, compact/legacy compatibility, eight drivers, delay/jitter, reconnect and migration. Start fixtures after the actual countdown rather than assuming an obsolete tick count.
- Use the track-specific browser tests for scenery/road clearance, bridge and roof overlap, animated landmarks, minimap margins, renderer disposal and night lighting. Check tunnel camera continuity and car-relative steering feedback separately.
- Use browser tests for saved settings, HUD, controls, sound, haptics and finish feedback; exercise real keyboard, touch and gamepad paths where available. Check 1280×800 and 1920×1080 desktop, 960×540 compact landscape, 450×960 phone portrait and approximately 844×390 phone landscape.
- Verify performance in a real browser. Full-field prediction must remain affordable; distant scenery should be merged or instanced, particle pools bounded, and Vegas should not add per-lamp shadow rendering.
- Open the rendered `/3drace/appinfo` page. Require exactly these two main documentation sections, no Markdown tables, working source links and a loaded screenshot for every track, driving, mobile controls and settings. Keep screenshot captions truthful to the current game.
