Deterministic telemetry analysis for sim racing. Reads iRacing .ibt files,
computes precise per-corner performance facts, and reports where a lap loses time
against a reference — entirely from your own data.
This repo implements Phase 1 of apex-coach-technical-design.md:
the deterministic layer plus self-reference (personal best + theoretical best).
No LLM, no external data. (The MCP server and the LLM coach are Phase 2+.)
.ibt ─► ingest ─► laps ─► corners ─► references ─► per-corner fact set
(Parquet) (segment + (detect + (personal + (time loss, Δspeed,
validity) label) theoretical) Δbrake, Δthrottle)
Everything is computed in plain, deterministic code and emitted as a structured fact set — the boundary the design is built around (design doc Section 4).
pyirsdk, numpy, pandas, pyarrow, pyyaml, scipy (and pytest for tests):
python -m pip install pyirsdk numpy pandas pyarrow pyyaml scipy pytestPut your session file in data/raw/*.ibt (gitignored — it's large).
# Build the Parquet table from the .ibt (run once per session)
python -m apex.ingest.ibt_reader
# Analyze the personal-best lap vs your theoretical best
python -m apex.cli
# Compare a specific lap against your personal best
python -m apex.cli --lap 108 --reference pb
# Raw structured fact set (what an LLM coach will consume in Phase 2)
python -m apex.cli --jsonExample (Porsche 992 GT3 R, Watkins Glen full course, 28 valid laps):
personal best: lap 106 107.404s
theoretical best: 104.397s (+3.007s vs PB)
biggest opportunities (by time lost vs reference):
1. Turn 4: +0.424s — 10 km/h less apex speed; brakes 54m early; throttle 5m late
2. Turn 7: +0.275s — 2 km/h less apex speed; brakes 16m early
3. Turn 5: +0.269s — 8 km/h less apex speed; throttle 16m late
apex/
ingest/ibt_reader.py .ibt -> normalized Parquet (units to g / km/h)
analysis/
laps.py lap segmentation, interpolated lap times, validity
corners.py corner detection (lateral-G hysteresis) + track YAML
delta.py align two laps on a common LapDistPct grid; delta time
reference.py personal best + theoretical best (micro-sector stitch)
facts.py the structured per-corner fact set (design doc 9.8)
cli.py end-to-end driver / report
tracks/watkinsglen.yaml detected corner LapDistPct ranges (hand-editable names)
tests/test_pipeline.py invariant sanity tests
A broken global pytest plugin in this environment requires disabling autoload:
PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python -m pytest tests/ -qAll deltas are lap minus reference:
| field | meaning when negative |
|---|---|
time_loss |
(positive = slower through the corner) |
min_speed_delta_kph |
carried less speed |
brake_point_delta_m |
braked earlier |
throttle_delta_m |
back to power earlier |
- Phase 1 — deterministic layer + self-reference (this repo)
- Phase 2 — physics envelope (empirical traction circle / unused grip)
- Phase 2 — MCP server + LLM coach + eval harness
- Phase 3 — external "alien" reference laps