The turn numbers on F1Peak's Monaco map were wrong. Not off-by-one wrong, where a smarter offset fixes it. Totally wrong: numbers missing through whole sections of the lap, and the ones that existed not matching anything a commentator would say. I had assumed corner numbering was a geometry problem, because everything else about the map is. It is not, and Monaco is the circuit that proves it.

How the map numbers its own corners
Some context on why the map even tries. F1Peak draws its track outlines from data, not from hand-drawn SVG assets: it collects clean closed laps from real position samples, rejects laps with dropped-sample discontinuities, resamples each by arc length, and takes a per-point median so an outlier lap gets outvoted. The line on screen is where the cars actually drove. That principle has survived every rewrite of the outline code, and I like it.
Corners followed the same philosophy. The free timing feed carries no corner metadata at all, so the map detected corners itself: find clusters of high curvature along the derived outline, number them in order from the start/finish seam. I had already recorded the honest caveat in my build records: these were derived and approximate, not official FIA numbers. On Monaco the detector resolved roughly 13 of the real 19; on Barcelona-Catalunya, about 13 of 14. Close enough that I believed a tuning pass would finish the job.
The diagnosis: wrong in both directions
I wrote a diagnostic script (diag-monaco.ts) to run the detector against the cached Monaco data and actually look at what the curvature said, instead of tweaking a threshold and eyeballing the result.
A curvature detector has essentially one knob: how much curvature counts as a corner. Monaco defeats every setting of it.
Set the threshold high enough to reject noise and you get a dead zone covering about 25% of the lap. The gentle corners through the tunnel and chicane section simply do not bend hard enough to clear any threshold that also rejects noise. They are real, numbered, named corners that every F1 fan knows, and to the curvature signal they barely exist.
Set the threshold low enough to catch them and the detector reported 39 corner candidates on a circuit that has 19. The extra 20 were phantoms: noise in the derived outline crossing the lowered bar. The median-of-many-laps outline is smooth, but it is still built from real GPS-grade position samples, and at low thresholds its small wobbles are indistinguishable from Monaco's gentlest real corners.
My next idea was to sidestep the threshold entirely: I know Monaco has 19 corners, so take the 19 strongest apexes and number those. That fails too, and the failure is the most instructive part. The tunnel corners are so gentle that they do not make the top 19 by curvature strength. Noise elsewhere on the lap outranks them. The set of official corners is not the set of the lap's 19 strongest curvature peaks, at any count, under any ranking.
That is the actual conclusion, and it is worth stating plainly: official FIA turn numbering is not a property of the track's geometry. It is a human catalogue. Some entries in it are violent hairpins, some are kinks a curvature signal cannot separate from measurement noise, and no function from the position data alone can reproduce the list. The detector was not badly tuned. It was answering a different question.
Curated counts, real apexes
Once the problem is framed that way, the fix is obvious and slightly humbling: put the human catalogue in the code. I weighed the options (keep the auto-derived approximation, turn the numbers off entirely, or curate) and picked curated numbering.
The result is circuit-corners.ts: a per-circuit record of the official corner data, checked against public sources. Monaco 19, Barcelona-Catalunya 14, plus official counts for around 20 more circuits. A circuit entry can carry two levels of detail:
// circuit-corners.ts - shape of a curated entry
{
count: 19,
// official corners as lap fractions from start/finish, racing order
positions: [/* 19 fractions */],
}
The interesting part is how the curated data meets the geometry, because I did not want to throw away the data-derived apexes. They are the truth about where the corners physically are; the curation only supplies which ones officially count and what they are called.
For a circuit with curated positions, each official corner number snaps to the strongest real curvature apex within plus or minus 3% of its curated lap fraction. The snap is monotonic: corner N+1 can never land before corner N along the lap, so one noisy apex cannot reorder the sequence. And if a gentle corner has no detectable apex near its fraction (hello, tunnel), it just sits at the curated fraction itself. Official number, official order, real position where a real position exists, and tolerant of small errors in the curated fractions.
For a circuit with only a curated count, the map keeps the N strongest apexes: right total, geometric positions. Uncatalogued circuits fall back to the old threshold detection, still labelled approximate. Making any circuit's numbering official is now a data entry task: add its positions to the file.
Did it work
The outline algorithm had changed, so I cleared the cached canonical outlines, re-derived them, and checked Monaco in the browser: 19 numbered corners spanning the whole lap, no dead zone, and turn 6 landing at the Fairmont hairpin, which is exactly where a Monaco T6 should be. The turn numbers (and the pit lane, which had its own glitch that session) are layer toggles on the map, so anyone who prefers a clean outline can switch them off.
What stays with me from this one is the boundary it drew. Deriving everything from data is still the right default for this project: the outline, the pit lane, the mini-sectors all come from what the cars actually did, and none of them need a curated file. But a label is not a measurement. The moment the thing you are rendering is a convention (a name, a number, an official count) rather than a physical quantity, no detector tuning will save you, because the convention was never in the signal. Nineteen lap fractions in a source file was the honest answer, and it took 39 phantom corners to convince me.