F1Peak replays Formula 1 sessions with every car moving live on a track map. The position channel that drives it arrives at roughly 3.7 Hz, and each sample carries somewhere between half a metre and two metres of noise. The screen renders at 60 fps. Do the division: one real data point about every 16 frames. Everything in between is interpolation, and the way you interpolate is the whole difference between watching cars race and watching markers twitch around a circuit.
Before this pass, the twitch was visible. Cars cut across apexes on straight chords between samples and their heading snapped at every sample boundary. The live view had a worse habit: a car would freeze while the engine waited for data, then teleport when the next batch landed. My build plan for this phase called killing the jumpy motion the single biggest perceived-quality jump available, with no new dependencies. It was right.
The rule: smooth the path, not the samples
Two constraints were settled before any code, and they shaped everything after.
First: smooth the path between samples, never invent resolution. The feed is 3.7 Hz and noisy; no amount of curve-fitting changes what was actually measured. The smoothing is only allowed to decide how the car travels between real points, not where the real points are.
Second: cars render at their true per-car X and Y. F1Peak already drew side-by-side battles and off-track excursions at real coordinates, and the track centerline exists for analytics math only. It never moves a car. Plenty of viewers quietly snap cars to a racing line because it looks tidy; that throws away the most honest thing in the data. The trade is that the exact lateral gap shimmers a little when two cars run wheel-to-wheel, because two metres of noise is about a car's width. Ordering between cars stays reliable, so I accepted the shimmer rather than smoothing it into fiction.
Centripetal Catmull-Rom through four real samples
The interpolation is a centripetal Catmull-Rom spline (alpha 0.5) through the four real GPS samples surrounding the playback cursor. Catmull-Rom because it is an interpolating spline: the curve passes through every control point, so the car still visits every true measured position. A fitted or approximating curve would drift off the data, which breaks the first rule.
Centripetal, specifically, because the parameterisation matters at hairpins. With uniform knot spacing, Catmull-Rom segments can overshoot and loop when consecutive points sit at tight angles, exactly the geometry a hairpin produces at 3.7 Hz. The centripetal variant spaces knots by the square root of the distance between points, and it does not overshoot there. The car sweeps through the corner on a curve instead of clipping it on a chord, and heading changes continuously instead of snapping 16 frames apart.
Dead reckoning at the live edge
Splines need samples on both sides of the cursor. In a replay you always have them. Live, the cursor rides the edge of the data, and the old freeze-then-teleport behaviour was the result of having nothing better to do while waiting.
The replacement is dead reckoning: when the engine runs out of samples ahead, it projects the car forward along its last heading at its real reported speed. Two clamps keep it honest. Extrapolation is capped at 1.5 seconds, so a long dropout cannot send a ghost car sailing off into the run-off; and slow or parked cars hold position instead of creeping, because a stopped car predicted forward is just wrong in a more animated way. The same mechanism covers mid-stream dropouts, not only the live edge.
One piece of the original sketch did not ship: an eased snap-back (around 150 ms) to reconcile the predicted position with reality when fresh data arrives. It is noted in the plan as minor polish and it is still on the list. I would rather record that plainly than pretend the reconciliation is graceful today.
The cache I decided not to build
The plan sketched a pre-resampled per-car spline cache, so that reading a car's position became a cheap array lookup and the motion trail a contiguous slice. Before building it I looked at the actual shape of the work: about 170 position evaluations per frame, each doing a binary search over arrays of around twenty thousand samples followed by a constant-time spline evaluation. That is nothing. The cache would have added memory and a rebuild obligation every time a live session appended data, for no measurable gain. I kept the binary search and moved on. Optimisations that exist to feel thorough are how engines get slower.
Proving it with a number
"Looks smoother" is not evidence, and motion is exactly the kind of thing the eye will vouch for incorrectly. So I measured the property that distinguishes a chord path from a curve. Along a chord, heading is constant within a segment and jumps at every sample boundary: per-frame heading change looks like near-zero readings punctuated by spikes. Along a genuine curve, the car turns a little every frame.
Measured in the browser during a Barcelona qualifying replay at 1536x864, per-frame heading change came out:
median 0.30 deg
mean 0.77 deg
p95 2.35 deg
spikes above 5 deg over a 3 s cornering window: 0
A median of 0.30 degrees with a p95 still only 2.35 means the cars are turning continuously, in small increments, every frame. Zero spikes above 5 degrees through a full cornering window means the chord-snap signature is gone, not merely rarer. The same verification pass confirmed cars still sat on track at their real positions with no regressions; the only console noise was a missing favicon.

One function, whole app
The satisfying part is where the change lives: one rewritten method, the engine's carPos. The motion trail, the 2D track map and the 3D track view all read positions through it, so every surface inherited smooth motion with zero changes of their own. That was a deliberate payoff of routing all rendering through a single position query, and it is the reason a phase this visible needed no new dependencies.
The smoothing never lies about the data. The spline still passes through every measured point; dead reckoning is clamped and only fills gaps; the noisy wheel-to-wheel gap stays noisy. All the pass really decided is how a car travels between the moments we actually know about, chosen so that it turns the way a car turns. And the heading-change check went straight into my build records as the standard way to verify this kind of work: one distribution, not a feeling.