The question nobody answers
Most race analysis asks "where is the car fastest?". That is the wrong question for 2026. Under the new regulations the Overtake system replaces DRS, and roughly 350 kW of electrical deployment becomes a strategic resource rather than a lap- time tool. The question that decides races is:
Is spending energy NOW worth more than saving it for the next
Overtake opportunity — or for the defence that follows?
E-Delta answers that. Its hero concept is the ENERGY SHADOW PRICE: the marginal value of one more kilojoule, measured in race position rather than seconds, and priced differently at every point on the lap. A joule spent 300 m before an Activation Line against a rival who is conserving can win track position for the rest of the race. The same joule spent where no overtake is legal buys a tenth and gives it straight back.
What it does
• Detects attacker-defender BATTLES in real telemetry — 33,649 episodes — and resolves every overtaking opportunity to the specific battle it belongs to.
• Predicts the probability a pass completes, at THREE separate decision checkpoints: the Detection Line, the Activation Line, and the braking point.
• Runs a rule engine that generates only LEGAL candidate actions before anything is scored, so an illegal deployment can never win a plan.
• Renders the whole thing in a 3D race simulator: 22 cars replayed from real telemetry, with the Detection and Activation lines drawn on the circuit, live pass probability for whichever battle you click, and an evidence reel that replays the moments the model called correctly against what the cars actually did.
A pass is three decisions, not one
This is the piece of engineering we would defend hardest.
DETECTION "Can we be within the required gap at the Detection Line?"
ACTIVATION "It is legal now. Is it worth the energy to convert it?"
BRAKING "Having committed, does this still improve race position?"
A DETECTION model must never see the activation speed. A model that does scores beautifully in validation and is worthless at the moment the decision is actually made, because that number does not exist yet. We enforce the boundary three times over: in the feature builder (features are built from telemetry TRUNCATED at the checkpoint distance, never built in full and blanked afterwards), in the model's own feature schema, and again in the serving layer, which refuses the request with CHECKPOINT_VIOLATION if a caller supplies a future-scoped field.
Feature counts rise 15 -> 16 -> 17 across the three checkpoints, and measured performance rises with them.
How we built it
Public F1 telemetry (FastF1) + FIA 2026 regulation documents
-> resample onto a fixed 20 m distance grid 15,427,924 rows
-> segment the track by geometry 1,124,954 segment rows
-> overlay weather, tyre pace, fuel, energy twin
-> detect battle episodes 33,649 episodes
-> emit opportunities at 3 checkpoints 4,970 opportunities
Coverage: 32 drivers, 11 teams, 14 events, 5 session types.
WHY 20 METRES. Time-sampled telemetry is not comparable between cars — two drivers on the same lap time are in different places at the same clock reading. Resampling onto a fixed distance grid makes "the same point on track" a join key, which is what turns two independent telemetry streams into a battle.
The backend is Python (FastAPI) serving trained gradient-boosted models from pinned artifacts. The frontend is a statically-exported Next.js site; the simulator is an imperative three.js renderer with a Web Worker owning the race clock, so the 60 Hz pose stream never enters React.
What is real, and what is estimated
Public telemetry does not contain battery state of charge, fuel load, or rival intent. We refuse to pretend otherwise. Every quantity carries a provenance tag and the UI renders it:
Gap, speed, position, throttle OBSERVED directly in the feed
Tyre degradation DERIVED computed from pace
Fuel load INFERRED from the twin's ICE work
Battery SOC / ERS energy SIMULATED physics twin estimate
Overtake legality RULE versioned FIA rule config
Energy is labelled "estimated", never "battery" or "SOC". Where a value is missing it is null with a stated reason — never a plausible-looking default. We think showing the estimate with its uncertainty is a stronger position than implying we hold private team data.
We also stop the OLD regulation leaking into the new one: historical DRS fields are admitted only for named 2022-2025 audit consumers and are refused on every 2026 model path, enforced in code rather than by convention.
Measured results
On a frozen British Grand Prix the model never trained on — 412 opportunities, 43 passes, 10.4% base rate, LightGBM:
Checkpoint ROC-AUC Brier ECE
DETECTION 0.725 0.0866 0.026
ACTIVATION 0.745 0.0828 0.024
BRAKING 0.699 0.0915 0.059
Three claims we will stand behind:
1. CALIBRATION TRANSFERRED. ECE 0.024 on an event the model never saw — better than in cross-validation. For anything reading these as probabilities, that is the number that matters.
2. A 6.2x LIFT on the positive class. At ACTIVATION's F1-optimal threshold the model is right 65% of the time it calls a pass, against a 10.4% prior.
3. IT BEATS A GAP-ONLY REFERENCE at all three checkpoints, so the other fourteen features earn their place on unseen data.
ROC-AUC leads the table because it is prevalence-independent: it shows the model is genuinely ordering opportunities rather than riding the base rate. Brier and ECE sit beside it because a planner reads these as probabilities, not as labels, and a well-ordered model that is badly calibrated is no use to one.
Challenges
THE LAP BOUNDARY ATE 9 OF 11 CIRCUITS. The Detection Line sits near the lap end, so 90% of opportunities wrap into the following lap — and the activation distance is then numerically SMALLER than the detection distance. Ordering by raw lap distance silently rejected every wrapping opportunity. Rows now carry distance travelled since detection, which is strictly increasing.
THE POSITION FEED IS ONE-DIMENSIONAL. Across ~1,000 clean laps per circuit the median lateral spread between cars at a fixed point is 0.33-0.61 m, against a 2 m car. Three independent probes confirmed it. So side-by-side racing cannot be read off the feed, and anything the renderer does about it is a labelled presentation choice, not a measurement.
THE THRESHOLD IS THE BASE RATE, NOT 0.5. Passes are about a tenth of opportunities. A 0.5 cut calls almost nothing a pass and scores ~89% by doing nothing useful. What the model is actually for is RANKING.
CAMERA SHAKE THAT WAS NOT THE CAMERA. Every car appeared to tremble. The cause was heading being taken as the forward difference across a single 1 m ring segment — documented jitter of 0.36-0.52 degrees per metre, crossed 70 times a second at racing speed — and because the chase camera aims along the focused car's heading, it swung the whole view and made every other car look like it was wobbling too.
What we learned
That the honest version is usually also the more defensible one. Every time we were tempted to fill a gap with a plausible default — a battery level, a lateral offset, a missing zone end — the version that said "this is absent, and here is why" was the one that survived scrutiny. It also made the failures findable: a value that is null when it is unknown fails loudly, and a value that is 0 when it is unknown fails silently and is believed.
What's next
• M22's dynamic programme, which turns the shadow price from a single marginal value into a full per-segment profile painted along the circuit. • The physics twin's calibration has no accepted rung yet, and its energy sensitivity is inverted by segment type — so the planner routes that depend on it stay deliberately labelled and unused rather than quietly wired in. • The full era split across the historical seasons, widening the pass model's evaluation beyond the single frozen event it is measured on today.
Built With
- catboost
- docker
- duckdb
- fastapi
- fastf1
- lightgbm
- next.js
- numpy
- pandas
- parquet
- pyarrow
- pydantic
- pytest
- python
- react
- scikit-learn
- scipy
- three.js
- typescript
- uvicorn
- vitest
- web-workers
- webgl
- xgboost
- yaml
Log in or sign up for Devpost to join the conversation.