# Echo: five laps where your past laps are the traffic

> A racing mode where every lap you finish comes back as a hologram of your own car that you must pass without touching. How it is built and what it costs.

Canonical page: https://andersonmancini.dev/blog/endurance-racing-echo-mode

Published: 2026-10-18 · 5 min read · Tags: game-dev, three.js, webgpu, racing, performance

Echo is a mode of [Endurance Racing](https://andersonmancini.dev/blog/endurance-racing.md) with a simple rule: you race five laps, and every lap you finish becomes traffic. It comes back as a hologram of your own car, replaying that lap on the circuit, and you have to pass it without touching it. A touch costs 3 seconds, and the score is the total time of the five laps plus the penalties. It is desktop only.

Lap 1 is the recording lap, with nothing in front of you. From lap 2, the last valid laps, at most four, replay as echoes.

## Racing yourself does not work by default

The first problem is not code, it is geometry. If an echo replays your lap at the speed you drove it, in the same direction, it never meets you. You would see holograms up the road and never reach one.

So an echo replays its lap at 0.75 times the speed it was driven. It is slower than you, so you catch it. I modelled how many echoes you meet per lap with a constant speed, where you pass the echo that starts `off` meters ahead if `off ≤ (1 − pace) · lap length`:

| Pace | Echoes met on laps 2 to 5 |
|---|---|
| 0.60 | 1, 2, 3, 4 |
| **0.75** | **1, 2, 3, 3** |
| 0.90 | 1, 1, 1, 1 |
| 1.00 | 0, 0, 0, 0 |

I then measured it in the browser with the scripted pilot I use for filming, which follows the same line every lap and never avoids anything. At 0.75 it met 0, 2, 3, 4 and 4 echoes per lap and touched 0, 1, 2, 3 and 3 of them. When the pilot alternated 3 meters to one side lap by lap, so that an echo was never on its line, the touches fell to 0, 0, 1, 1, 1. Passing 6 meters apart is never a touch.

Those are a robot's numbers. A person is the only real playtest of the pace, and I have left it as a knob: `?echopace=0.9` changes it for a session.

## Recording a lap

A lap is a list of poses. The recorder is the one that Photo Run uses: one pose per 1/60 s of simulation, written into an array allocated once. It records the car's root position and rotation and the pivots of the wheels, so the hologram has its suspension and its spinning wheels. It samples by simulation time, not by frame, so a 60 Hz and a 144 Hz screen record the same lap. A lap is about 0.9 MB.

A lap is not kept if it had a respawn (Backspace, or a fall) or lasted under 20 seconds.

## Putting echoes ahead of you

At every crossing of the line, the echoes are placed ahead of you, the newest nearest:

```javascript
startOffsets: [ 300, 700, 1100, 1500 ], // meters ahead of the line
pace: 0.75,
penalty: 3,
grace: 2.5,    // no touches in the first seconds of a lap: they are fading in
lockout: 2,    // one contact is one penalty
```

Each lap start gives the echoes a new id, and the hologram pool fades a new id in over 700 ms and hides an old one at once. That one rule hides the fact that the whole set was moved.

An echo that reaches the line starts its lap again. The end of a lap and its start differ by a few meters sideways and a little in heading, so I carry that difference as an offset that fades out over 0.4 seconds. There is no teleport.

## What counts as a touch

Two oriented boxes overlapping: the chassis box of your car, shrunk by 12 cm, against the echo's, tested with the separating axis theorem on the ground plane. A hologram's edge is not a wall, hence the shrink. Only echoes within 30 meters are tested at all. After a touch that echo cannot be touched again for 2 seconds, and the first 2.5 seconds of a lap are free.

Holograms have no physics. Nothing pushes you, the penalty is the whole consequence.

## One pool of holograms

This is the part I like most. The echoes are drawn by the same pool of ghost cars that the public rooms use for other drivers. It holds at most four cars, each a baked copy of the real car of about 30,000 triangles, all sharing one additive material.

The pool is built and compiled during the normal startup, and the real car's hologram is baked during lap 1, three milliseconds per frame. So when the first echo appears there is no new program to compile and no new mesh to make. I measured it by wrapping the device's `createShaderModule`, `createRenderPipeline` and `createComputePipeline`: zero, zero and zero from the first lap to the result screen. On a page where building a shader at the wrong moment is a hitch, and on an older iPhone can end the page, that was the requirement that shaped everything else.

Echo and the online rooms never run together, because they would use the same pool. Entering a room turns Echo off.

## What it costs

| While | What runs | Cost |
|---|---|---|
| Driving | one pose per step, at most 4 pose interpolations, at most 4 box tests (only within 30 m) | the recorder is Photo Run's: 0.09 µs a pose, about 5.5 KB per second |
| Drawing | at most 4 meshes from the shared pool | 4 echoes drawn against hidden, lap 5, 960×540: median 16.7 ms both, p95 18.5 against 18.6 ms |
| GPU objects while playing | none made | 0 modules, 0 render pipelines, 0 compute pipelines from the first lap to the result |

The frame times are vsync-bound, so the difference is smaller than what the measurement can show.

## What is missing

- A real playtest of the pace and of the 3 second penalty.
- The echoes are always your current car. If you swap cars between runs, laps from the other car are not kept.
- A set of echoes is replaced at every lap start, so an echo you were passing vanishes at the line.
- I thought about an echo coming back toward you. It would be more dramatic and much harsher on blind corners, so I left it out.
- There are no online records. The best time is saved in your browser only, because the room server bills by message.

Echo is in [Endurance Racing](https://endurance-racing.vercel.app/), and the project page is [here](https://andersonmancini.dev/projects/endurance-racing.md).

## Related projects

- [Endurance Racing](https://andersonmancini.dev/projects/endurance-racing.md) — Rain-soaked GT3 racing for the browser
