Nothing here is hidden. A quasi-static point-mass solver walks the racing line under a load-sensitive tyre and a friction-ellipse limit, deploys the hybrid, integrates the lap, and commits the result to a hash before the session runs. Every step is below, with the maths that goes with it.
Two inputs, a track and one lap, run through the frozen model and come out as a single number you can check. Colour marks where each step comes from: most of the lap is first-principles physics, and only the tyre (2 params) and the offset are anchored to data. Click any step to jump to its maths.
The car is a point mass tracing the line, parametrised by distance s, with local curvature κ(s) (radius R = 1/κ). Cornering needs lateral acceleration alat = v²κ, and the tyre can only supply alat ≤ μ·N/m.
The catch is that normal load N isn’t constant. Downforce grows with speed, so grip grows with speed, so the maximum corner speed ends up depending on itself. It gets solved as a fixed-point iteration: guess v, compute load and grip, get a new v, repeat until it settles. In practice that takes a few passes.
With every corner’s limit known, two sweeps stitch the profile together. A backward pass walks upstream from each apex applying the maximum braking the tyre allows; a forward pass walks downstream applying the maximum acceleration (engine + hybrid, grip-limited). The realised speed at every point is the pointwise minimum of the three: corner limit, brake limit, drive limit.
Integrating gives the lap: sum the time to cross each segment at the local speed.
Longitudinal and lateral grip share one budget. A tyre at the limit of cornering has nothing left for braking, and the other way round, so the usable combinations trace out an ellipse. That is what couples the two passes: at corner entry the model trades braking away for the lateral grip the corner is already asking for, so brake pressure has to bleed off as steering goes on and the two trade smoothly rather than fighting.
Peak grip is D = μy·Fz, but the coefficient itself falls with vertical load: μy = pdy1 + pdy2·dfz, with dfz the normalised load and pdy2 < 0. That negative slope is why piling on downforce buys you less than linear grip, and a lap simulator lives or dies on it.
Exactly two parameters, pdy1 and pdy2, are fit, to one real qualifying lap. Everything else is first principles. Then they get frozen for the season.
Lap time only pins down the product μ·load. Downforce CL can swing 3.5 → 8.0 and, as long as μ moves the other way, the lap comes out the same. So the product is what gets reported. Quoting a downforce number here would be false precision.
Given the track’s usable width, the line is whichever lateral offset minimises total squared curvature ∫κ²ds, which is the geometrically smoothest path available. Since vcorner ∝ κ−1/2, less curvature means more speed everywhere.
Since the summer re-freeze this generated line is history: calls from Zandvoort onward build the line from the venue’s real 2025 pole lap GPS, filtered, with no generated geometry and no survey map. This section stays as written because it documents the method the early calls, Spa included, actually ran on. The full why is in the repo’s re-freeze document.
A minimum-curvature line can be smoother than any human drives. At Spa the generated line was 36.6% flatter than Antonelli’s and 29 m longer, so the model called the lap 2.4 s too fast. On his real line the same physics is −0.36%. See the debrief →
There is no claim here about predicting terminal velocity from aero and power maps. It gets anchored to last year instead, using a measured 1.0106 top-speed ratio (2026 over 2025, from the seven pairs that ran after the mid-season power change; the earlier 1.0037 mixed pre- and post-change sessions and was replaced at the re-freeze for exactly that reason), and the solver’s straight-line speed is capped at V_CAP = 92.2 m/s (≈ 331.9 km/h). On a grip circuit like Budapest the entire top-speed term is worth about 3.5% of the lap, so the cap barely moves anything. It is a carry-over, and it is labelled as one everywhere it appears.
The battery deploys a fixed budget per lap, and where it goes matters. A Bellman dynamic program over the state (position, speed, energy) finds the true optimum. The frozen config uses the simpler greedy deploy anyway, because it is canonical, its offset is near zero, and it is easier to defend to someone who doesn’t trust you. DP stays in as a secondary result with tighter scatter.
Since the summer re-freeze the fixed tank is gone too: the prediction path now walks a state-of-charge ledger in track order, draining under deployment and recovering under braking at up to 350 kW, all constants from the published 2026 spec and not one of them fitted. Deployment per lap comes out at 7–9 MJ rather than 4, which is the world the technical directive actually describes (the re-freeze →).
The integrator is semi-implicit (symplectic) Euler at a 1 ms substep, in float32 with floating-point contraction off. Same input gives the same bits, which gives the same lap, which gives the same SHA-256. So the prediction string gets hashed and the hash gets published before every qualifying session. After that the number can’t be moved by anyone, us included. That is the difference between a falsifiable call and an opinion.
The blind model time gets corrected by a single frozen offset, μ = +0.102%, and the scatter around it is σ = 1.414%. Both come from a seven-circuit leave-one-out: fit the tyre on six, predict the seventh, repeat seven times, so every error in the average was measured on a circuit its own fit had never seen. That is what makes these numbers out-of-sample rather than a description of the training set.
Bands are Student-t predictive intervals (ν = 6) rather than a flat ±σ, because with seven points σ is itself an estimate: the bands have to pay for where the next error lands and for how well the scatter is even known. The 68% band works out near ±1.639% of lap time and the 95% near ±3.699%, wider than ±σ and ±2σ, and honestly so. The earlier four-circuit figure (μ −0.39%, σ 1.11%) was in-sample and is superseded; the Spa and Budapest calls were made under it and are scored under it, permanently (the full re-freeze →).
Everything a v4 call depends on is fixed before the session and quoted inside the string that gets hashed. So this table is less a summary of the model than the model itself: change any row and the published SHA-256 stops matching.
| Frozen | Value | How it was obtained |
|---|---|---|
| tyre pair (pdy1, pdy2) | 1.36, −0.12 | fitted on seven circuits, in-sample |
| offset μ | +0.102% | out-of-fold, seven-fold leave-one-out |
| scatter σ | 1.414% | out-of-fold, seven-fold leave-one-out; rose from 1.32% to 1.46% at the v3 re-freeze when a cancelling defect was removed, then fell to 1.414% at v4 when a leak in the calibration itself was closed |
| top-speed ratio | 1.0106 | measured, seven 2026/2025 pairs |
| band multipliers | T₆₈ 1.159 · T₉₅ 2.616 | derived: Student-t, ν = 6, ×√(1+1/7) |
| line filter | Savitzky-Golay, window 9 + box half-width 3 | chosen; the spread across both filters (windows 7/11, half-width ±1) is declared in every call as s1 |
| MGU-K | 350 kW · 4 MJ store · 7 MJ/lap harvest | 2026 regulations |
The first row and the two after it are doing opposite jobs, and keeping them apart is the reason the bands mean anything. The tyre pair saw all seven circuits, so it is the best available description of what already happened. μ and σ never saw the circuit they were scored on, so they are the estimate of what happens next. Only the second kind belongs anywhere near a band, and a model that quotes its in-sample residual as its accuracy has quietly swapped one for the other.
Two engines sit behind this: a high-fidelity 3-DOF C++ core for telemetry and line optimisation, and the QSS point-mass layer above, which is the one that makes the published call. Run it on a circuit →