# 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, graphics and sound controls](screenshot-settings.jpg?v=graphics-1 "Settings")

Every player can change their own **Driver name**, **Graphics** 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.”**

**Settings** has Basic, Graphics and Advanced tabs. Basic contains driver name, race options and sound. Advanced lets the host choose **5, 10 or 20 snapshots per second** for the room (default 20). Lower rates save bandwidth but delay confirmed updates. **Prioritise humans** offers **20 or 50 updates per second** using smaller movement updates between snapshots. The 50 option reduces human playback buffering but uses more bandwidth. AI updates retain the selected snapshot rate; while driving, their displayed movement uses the same local simulation as your car. These settings apply without restarting the race, survive host migration and are saved for new rooms.

In Graphics, **Resolution** defaults to **Automatic**, which reduces resolution after sustained performance below 30 FPS. **Full** keeps the original resolution; **Reduced** and **Low** use progressively smaller rendering budgets. **Antialiasing** independently offers **Automatic**, **On** and **Off**. Automatic enables smoothing at full resolution and disables it when resolution is reduced; On requests smoothing when supported by the device. Text and controls remain sharp. The Graphics tab shows measured FPS, render resolution and actual antialiasing status. Automatic resolution stays reduced for the session; select Full and then Automatic to retry. Both preferences are saved locally and changes leave the race running.

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:** gentle opponent pace with plenty of room to learn the tracks.
- **Standard:** a forgiving race with moderately quicker opponents.
- **Fast:** a step up in pace once you are comfortable with the corners.
- **Expert:** the quickest opposition, with a brisk but forgiving cornering pace.

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, graphics preferences, 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 **30 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. The highlighted timer shows the remaining seconds and next track. The host can select **Next race** at the top right of the results to start the next circuit immediately for everyone.

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 or windows clears held controls but keeps your car in manual mode while connected. If inputs stop, the host uses neutral controls instead of engaging AI; return and continue driving without another take-control countdown. Disconnecting hands the car to AI. Reloading the same tab can reclaim your car. If the hosting browser leaves or becomes hidden, another foreground device can take over race authority after a short synchronisation pause, without releasing a still-connected driver's car.

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 to install the game. The PWA requests fullscreen when launched, where supported. 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.
- [quality.js](quality.js), `RaceQuality`: device-local frame-rate sampling, saved quality modes and bounded render-resolution reductions.
- [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 renderer with sRGB output. Full resolution caps device pixel ratio at **1.75**. Antialiasing can independently be Automatic, On or Off. Automatic quality measures uncapped animation-frame intervals after a **5-second** warmup; two consecutive **2-second** windows below **30 FPS** lower quality. Ignore hidden tabs and interruptions over one second. With Automatic antialiasing, level 1 disables smoothing and uses at most 75% of the native pixel ratio with a **1920×1080 pixel budget**; level 2 uses at most 50% with a **1280×720 pixel budget**. Preserve aspect ratio and CSS canvas size. Recreate and dispose the graphics renderer to change WebGL antialiasing, leaving simulation/network state untouched. Preserve the lowered level across track changes; reset sampling after rebuilds and visibility changes. Save resolution as `3drace-graphics` and antialiasing as `3drace-antialiasing`; never send them as room settings. 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 **30-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 and deduct the actual recovery distance from progress. Explicit player recovery applies **180-tick** ghost protection without a driving delay. Automatic AI recovery keeps collisions active and is blocked by traffic within 20 m ahead or behind, including on the shoulder. Otherwise queued cars can teleport backwards and drive through each other. Client prediction must not invent a recovery because future remote inputs are missing.

### Opponent pace and traffic

Use base paces **0.945, 1.071, 1.197 and 1.323** 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.

Passing a stopped human, retired car, stranded off-road car or AI stationary for over 45 ticks is a separate low-speed manoeuvre. Below 40 m/s and within 65 m, consider offsets of **±4.8 m**, since a centre-road car blocks both normal ±3.2 m lanes. Check nearby cars and their reserved lanes, use a shorter steering lookahead of **3 + 0.15 × speed metres**, and permit a crawl up to **5 m/s** as physical clearance opens. Do not switch across the obstacle at close range when the nearer side has room. A car already on the shoulder may use available space on that side, capped at 10.2 m from the centre and 2 m inside the barrier. Limit off-road AI speed to 12 m/s. Return to the normal lane only when clear and after the cooldown.

Within 12 m of stopped traffic and below 5 m/s, test steering paths using a scratch engine and the actual car collision shapes. Sample fifteen 0.1-second steps at 3 m/s against nearby cars and barriers. Keep a clearance margin and crawl forward when possible. If tightly blocked for over 45 ticks, briefly reverse with opposite steering only when the checked rear path is clear; resume forward travel once room opens. Followers can pass a stationary AI when a lane becomes available. Scratch simulations never modify the race cars or their checkpoints.

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 state updates at **5, 10 or 20 per second**, selected in Advanced settings (default 20). Each update reconstructs a full recovery checkpoint containing host/term/serial/race ID, `snapshotTicks` (12, 6 or 3), `networkSettings`, 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. Network settings change without restarting the race and survive host migration.

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.

Peers additionally advertising `hello.checkpointDelta: 1` can receive **state-delta**, format 2: lossless changed-field masks against the preceding checkpoint serial. Send full compact checkpoints at least once per second and whenever the host, term, race or recipient generations change. Use a full checkpoint whenever it is smaller than the delta. Receivers validate the base identity before applying a patch to a fresh copy. A missing or invalid base produces a visible notice; the next full checkpoint restores playback. Older compact clients continue receiving format 1 and unknown clients retain legacy objects.

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 for physics rather than alternating neutral guesses and AI. Display the local car and unreserved AI cars from the same prediction, so visible AI positions match their collision bodies. Reserved remote human slots, including temporary AI cover, use confirmed buffered interpolation so short throttle taps cannot become invented acceleration followed by backward corrections. Keep delayed display poses out of physics. Use a three-tick (50 ms) lead margin plus the connection's highest observed RTT, increasing headroom gradually with bounded replay.
3. **Spectator:** interpolate cars on a monotonic buffered clock, with roughly **100, 200 or 400 ms** buffering at 20, 10 or 5 snapshots per second, in addition to network transit. Retain **200 ms** buffering for older hosts without an advertised cadence. Do not extrapolate through missing snapshots.

Optional **human priority**, off by default, fills gaps between full-world snapshots with `human-motion` packets for a combined 20 or 50 updates per second. Store the selection in `networkSettings.humanRate`, defaulting older settings to 20. Use a fractional tick deadline for 50 Hz on the 60 Hz simulation, counting snapshots towards the total and skipping missed deadlines after stalls. Send authoritative pose and visual contact/damage fields for all reserved human slots, including temporary AI cover, only to peers advertising `hello.humanMotion: 1`. Validate host, term, race, tick and every reserved slot. Buffer these poses on a separate timeline of approximately 100 ms at 20 Hz or 40 ms at 50 Hz, and overlay remote human presentation only; never modify local prediction, collision physics or recovery checkpoints. When snapshot and human rates match, use the normal shared timeline without extra motion packets. Refresh all browsers to use the new rate and shorter buffer.

Reconcile at the same command tick. Adopt corrected physics immediately; a small local 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. Local-versus-AI contact shares the predicted timeline. Contacts involving remote human cars can still look out of sync or overlap because those cars use confirmed playback. Spectators share confirmed playback time except when human priority runs faster than snapshots. Physics always uses its coherent predicted world, never delayed display poses. When received human history runs out, hold their displayed motion rather than extrapolating through obstacles.

Use 300 ms RTT only as an initial estimate. Replace it with the first measured round trip, then retain measured spikes for jitter protection until reconnect. This avoids imposing an artificial 300 ms minimum on local connections. The faster checkpoint cadence uses more bandwidth and replay work; it does not remove network transit delay.

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`, `blocked-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 races to complete without recovery, meaningful pace differences, and rare contacts. Trailing AI cars can receive DNF when the 30-second finish window expires.
- 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.
