fct_cliff_prediction_features is the single mart the five models consume, and every feature in the table was computed by the transform layer before ML ever runs.
33 features across 6 physics groups
Every feature has a physics interpretation. The grouping below matchesFEATURE_GROUPS in ml/src/schema.py and the features.groups section of the model card the exact same ordering used during training.
Phase 9 (2026-09-05): pruned from 42 to 24.
powertrain, telemetry_cliff, weather_air,
track and context (18 columns) were dropped on a noise-floor group ablation across all
three ablation-bearing model families each dropped group’s contribution to headline accuracy
fell inside the seed-refit noise floor in every family scored, so it costs ONNX size and
browser payload without a measured accuracy return. powertrain and telemetry_cliff were
the mart’s entire consumption of int_lap_telemetry_aggregates; the ingestion and the
telemetry data itself are unaffected, only their use as model features. See the execution
plan’s Phase 9 section for the full ablation table.Phase 10a (2026-09-05): 24 → 33, and the nine come from a different sensor. Every
traffic feature before this one descends from FastF1’s
DistanceToDriverAhead, which
int_lap_air_state divides by point speed to get a gap in seconds an approximation that
assumes the car ahead is travelling at the same speed as the car behind, which is exactly
false in the situation the feature exists to describe. The proximity group instead
measures a time: each lap is cut into 100 fractions of relative_distance, the session
clock at which a driver first enters a fraction is a crossing time of a fixed point on
track, and the gap is the difference between two crossings of the same point. The source
is the telemetry stream’s position channel, 58.8M rows that had no consumer at all
before this phase.Admitted on an add-ablation, not on the argument: p50 pinball 1.034661 → 1.012128 (1.54× its
seed-refit noise floor), cliff macro-F1 0.372960 → 0.380950 (2.17×), stint-life AFT NLL
1.956152 → 1.952005 (0.59×, inside noise). The dirty_air group stays a swap arm that
drops it and keeps proximity lands inside noise on all three families, so the two are
carrying different things. Validated independently of the telemetry it is built from,
against the FIA’s own 4,909 waved blue flags
(assert_proximity_agrees_with_blue_flags).A permutation-null arm separates information from capacity nine more columns is nine more
split candidates, which can flatter an eval fold on its own. With the proximity columns
row-shuffled (capacity preserved, signal destroyed), the classifier’s gain is +0.008406,
2.28× its floor, entirely information, with a capacity effect of 0.11× floor. The p50
regressor’s total gain clears its floor but splits roughly evenly between information and
capacity, with neither half clearing on its own so the quantile trio’s improvement is
real on this fold but is not cleanly attributable to traffic.stint_position (4)
Lap number, stint position, fuel mass. The core temporal signal where in the stint this lap falls.
compound (7)
Compound identity, grip peak, wear gradient, optimal temp window, cliff-onset and severity priors. The compound-level physics prior baked into the Reference family.
cliff_prior (5)
Expected pace, degradation rate, whether the onset lap has passed, laps past the cliff, and a cliff candidate flag. The Kaplan-Meier survival prior from the physics layer ML’s starting point, not its conclusion.
thermal (4)
Push residual, cumulative surface and bulk thermal load, and the surface-to-bulk ratio (added under Route C, C3). Encodes how hard the tyre has been working at each of the two thermal layers.
dirty_air (4)
Dirty-air share per lap, surface and bulk thermal load attributed to following traffic, and the dominant air-state label. Encodes the aerodynamic heat penalty from following another car, derived from FastF1’s
DistanceToDriverAhead.proximity (9)
True pairwise track gaps from the position channel: share of the lap within 1 s / 2 s of the car ahead, share spent in a train rather than a single tow, share with a car within 1 s behind, seconds within 1 s, closest and median gap ahead, how stable the car ahead’s identity was, and how many distinct cars came within 3 s. The same physical hypothesis as
dirty_air measured from a different sensor and both are kept, because the swap arm says they carry different things.The read-only contract in practice
Two target spans. The quantile trio and cliff classifier train on 114,270 laps that carry the degradation and cliff targets; the stint-life regressor trains on 120,934 laps the spans differ because a lap can have a valid stint-life count even where its next-lap jump is undefined.
−1 (the MISSING_ORDINAL sentinel). Continuous features keep NaN as NaN XGBoost learns the best split direction for missing values natively.
Excluded columns
Excluded (22 columns causal leakage / identifiers / targets / training gate):circuit_key, drift_s_per_lap, driver_id, driver_skill_field_s, driver_skill_loro_mean_s, driver_skill_loro_s, driver_skill_proxy_s, driver_skill_residual_proxy_s, driver_skill_residual_s, is_training_eligible, lap_id, laps_until_cliff_class, next_3_lap_cumulative_jump_s, next_5_lap_cumulative_jump_s, next_lap_degradation_jump_detrended_s, next_lap_degradation_jump_s, race_id, race_year, remaining_stint_life_laps, stint_id, stint_length_laps, survival_weight
Relationships
fct_cliff_prediction_features
The mart this contract reads its full column list, lineage, and the tests that guard its grain.
Features and targets
How a mart row becomes a row of the
X matrix encoding, fingerprinting, the forward-window audit, and the three targets.Leakage spine
The 12 CI tests that assert these exclusions hold every build one idea per guard.
Model overview
Back to the ML front door.