Telemetry

Drawing an F1 circuit from nothing but where the cars drove

F1Peak ships no hand-drawn track maps. Every circuit outline is computed from the cars' own position data, as a median over many resampled laps, with dirty laps rejected and the start/finish seam parked next to the pit lane.

F1Peak's track map has no artwork behind it. No per-circuit SVG, no traced satellite image, no coordinates file for me to maintain. The outline on screen is computed from the same feed that moves the cars: their position samples. I made that call on day one of the project and recorded it as a decision, because it has three properties I wanted. Zero per-circuit assets. It works for any track F1 ever visits, including future ones. And it honours the project's no-fake rule, because the line is where the cars actually drove.

The first version was simple. Find a time window roughly the length of the best lap in which one car's path closes on itself, smooth it, done. It produced a recognisable Catalunya within hours of the project starting, and then it spent the next day teaching me everything that is wrong with trusting a single lap.

A parked car drives a perfect lap

The first failure showed up while verifying the very first build. The derivation locked onto a parked car. A car sitting in the garage closes on itself flawlessly: its end position equals its start position with zero error, which made it the best "lap" by the closure metric. It had also driven zero metres. The fix was to require at least 1.5 km of driven arc before a window counts as a lap. Obvious in hindsight, and a good early warning that this data would reward paranoia.

One dropped sample draws a chord

The deeper flaw was structural: one lap from one driver means any glitch becomes canon. The position feed runs at roughly 3.7 Hz with something like half a metre to two metres of noise, and it occasionally drops samples. When the chosen lap had a gap, the outline joined the points on either side with a straight chord, cutting across whatever corner lived in between. Barcelona's Turn 4 came out chopped flat in the FP2 replay. Monaco's Race outline had what I logged at the time as a corner "broken with a white line". One dropped sample, one wrong track, for every viewer of that session.

So I rewrote the outline derivation around consensus instead of a single witness:

  1. Collect every clean closed lap across the top drivers, not one lap from one car.
  2. Reject any lap containing a teleport or dropped-sample discontinuity. Don't patch the gap; drop the lap. There are plenty more.
  3. Resample each surviving lap by normalized arc length to 720 points, so point 200 on one lap and point 200 on another are the same place on the circuit regardless of pace.
  4. Phase-align every lap to a reference lap.
  5. Take the per-point median. An outlier in one lap is simply outvoted.

The closure seam and the start/finish notch were parked on the longest straight, so neither could ever chord a corner again. And because "looks fine to me" is not verification, a test script recomputes the outline from every cached session bundle and renders it as ASCII, alongside a numeric tripwire: the ratio of the longest segment to the median segment sits at 2.0 to 3.0 on a healthy outline, and a corner-cutting chord spikes it to 5 to 10 times or more. After the rewrite, Barcelona FP2 matched FP1 and the Monaco seam sat on a straight.

Same circuit, different track

That should have been the end, but the median had a subtler leak: I was still deriving the outline per session. Qualifying and the Race each computed their own shape, slightly different, with the seam in a different place and different corner numbers. I noticed it as a single wrong pixel-cluster: the white start/finish notch sat on Monaco's Turn 6 in the Race, but not in Qualifying.

Race laps are dirtier than qualifying laps. Cars run off line, trundle behind a safety car, defend into corners. Enough of that and even a median drifts. Two changes fixed it. First, outlier-lap rejection before the median: measure each lap's deviation from the consensus and drop the laps that sit beyond median plus twice the MAD.

const keep = laps.filter((lap) => deviation(lap) <= median + 2 * mad);

That strips the dirty race laps, so a Race derivation converges back onto the clean qualifying line.

Second, a canonical per-circuit cache, keyed by the race-weekend folder. Every session's derivation gets scored by a quality metric (many tightly-agreeing laps score high), the best one is promoted, and it is reused for every session of that circuit. The client fetches it from a small outline endpoint before rendering, and the endpoint derives from the cached bundle on demand, so the best derivation wins no matter which session you happen to load first. Verified: Monaco Qualifying and Race now return the identical outline, quality score 109, same start/finish point.

A derived circuit outline rendered in the F1Peak app

The seam belongs next to the pit lane

"Longest straight" turned out to be a geometric guess, and on street circuits it wandered onto corners. The real start/finish line is next to the pit lane, so the honest fix was to find the pit lane. Except the feed has no pit geometry at all: every single position sample is flagged "OnTrack", including cars parked in the garage. I checked; there were zero non-OnTrack samples.

What the feed does know is when a car is in the pit lane, from its timing data. So the coordinates a car emits during its InPit windows trace the pit corridor. Fit a principal axis through that point cloud, take a per-bin median along it, and you get a clean centreline, which F1Peak draws as a cyan dashed line with a PIT tag. The seam then moves to the straightest outline point alongside that lane, which is where the actual start/finish line is. On Monaco the chosen seam point bends by 0.9 degrees, on the pit straight. One refinement came out of Barcelona, where the angled pit entry and exit roads dragged the fitted axis diagonal: a density pre-filter keeps the dense corridor and drops the sparse approach roads, and clipping to the longest contiguous populated run trimmed the drawn lane from 366 m to 324 m, along the main straight where it belongs.

What geometry cannot know

The corner numbers were always the honest weak spot. They are derived from curvature clusters, because the feed carries no corner metadata, and curvature only resolves about 13 of Monaco's 19 official corners and about 13 of Catalunya's 14. The gentle corners through Monaco's tunnel section fall below any usable threshold, and lowering the threshold produced 39 phantom corners from noise. Numbering eventually moved to a small curated table per circuit, with each official corner snapped to the strongest real curvature apex near its expected position. The outline itself needs no such help. It stays fully derived.

The pattern I kept from this: one sample is an anecdote, a median over many resampled witnesses is a measurement, and the witnesses that teleport get ejected before the vote. When the authoritative geometry simply is not published, consensus across noisy observers gets you a circuit accurate enough that the remaining errors are single pixels, and every one of those pixels so far has pointed at a real bug.

← All devlog stories