Lap detection
Algorithm lap-detect-1.0.0+laps-1.0.0+centerline-1.0.0+projection-1.0.0 · source
engine/src/dtta/engine/laps/{gate,crossings,classify}.py.
Dirt ovals are short, GPS is noisy at 10–18 Hz, and the car also drives pit roads and the infield. Lap detection therefore uses a gate with several guards and a full-loop progress check, never a single "distance to a point" rule.
Steps
- Working set — valid GPS samples in local metres, split into runs at gaps > 1 s. Heading is taken
from a
lap_heading_smooth_samples(5) moving average of positions and is undefined belowlap_heading_min_speed_ms(2 m/s). - Racing surface — samples faster than
lap_race_min_speed_ms(8 m/s ≈ 18 mph) define the loop; a preliminary centerline gives its length and direction (sign of the net heading change: counter-clockwise = left turns). - Start/finish gate — placed automatically at the centre of the longest straight (lowest curvature
over the longest arc), perpendicular to the centerline, half-width
(
lap_gate_min_halfwidth_m,lap_gate_spread_factor). Lap times of complete laps do not depend on where the gate sits. - Crossings — consecutive samples whose segment intersects the gate, with speed ≥
lap_cross_min_speed_ms(3 m/s), heading withinlap_gate_heading_max_deg(45°) of the gate direction, and at leastlap_min_lap_s(6 s) after the previous accepted crossing. The crossing time is interpolated: , where is the intersection parameter along the sample segment. Every rejected crossing keeps its reason. Crossing confidence: from the lateral offset at the gate and the heading error . - Lap assembly — intervals between consecutive crossings in one run. Before the first crossing:
partial_first; after the last:partial_last. - Progress check — each candidate lap is projected onto the centerline (see Track progress). A
genuine lap covers ≥
lap_progress_min_fraction(95 %) of , never steps backward more thanlap_backward_jump_m(10 m), has an integrated distance withinlap_distance_tolerance(±10 %) of , and does not lose projection for more thanproj_lost_limit(5) consecutive samples or 5 % of its samples.
Classification
Evaluated in this order; the first matching rule wins and the reason is stored on the lap.
| result | rule | parameters |
|---|---|---|
pit |
more than lap_pit_min_s (2 s) below lap_pit_speed_ms (2 m/s) |
stopped on track / pit road / red flag |
invalid short_cut / reversed / distance_mismatch / projection_lost |
progress check failed | see step 6 |
invalid gps_quality |
valid fraction < lap_gps_valid_fraction_min (0.9) or a gap > lap_max_gap_s (1 s) |
|
invalid implausibly_fast |
lap time < lap_implausibly_fast_factor (0.95) × |
physical bound |
caution |
lap time > lap_caution_factor (1.35) × fastest candidate, or mean speed < lap_caution_speed_fraction (0.7) × best mean speed |
|
racing |
everything else |
Observed on real data: racing laps 18.8–22.9 s, caution laps 39–52 s, first partial 47–52 s, so the 1.35 factor separates cleanly. The fastest candidate is used (not a percentile) because sessions with a single green lap exist.
Only racing laps can be chosen as reference or comparison; caution laps are shown with their delta to
the fastest lap for context.
Resolution of a lap time
The crossing interpolation resolves the crossing to a fraction of a sample interval; with ~0.2 m positional noise at 35 m/s the crossing time noise is of the order of 5–10 ms. Because the reference and the comparison lap cross the same gate with the same receiver, GPS latency cancels in the delta.
Verified
- Hamilton County HERO9 heat: 8 racing laps 20.70–21.49 s, one caution, two partials; I-35 HERO11 heat: pace laps classified caution, green laps racing. Lap times match the independent heading-based reconnaissance within 0.3 s.
- Synthetic oval: exact lap count and times; an infield excursion produces no lap; a stop on track →
pit; a slow lap →caution. Tests:engine/tests/unit/test_laps_geometry.py,engine/tests/synthetic, regression goldens (lap count, classes, times ±5 ms).