Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
1aad273
docs: specify the fix of the analysis engine's artifacts
AnaLauraFFerraz Oct 25, 2026
efe2f2c
docs: record the clarifications of spec 005
AnaLauraFFerraz Oct 25, 2026
a9b0e2f
docs: plan the fix of the analysis engine's artifacts
AnaLauraFFerraz Oct 25, 2026
4d44fd4
docs: list the tasks of spec 005
AnaLauraFFerraz Oct 25, 2026
6a090df
docs: align the artifacts of spec 005 after the analysis
AnaLauraFFerraz Oct 25, 2026
6339afe
docs: align the sample-gap edge case of spec 005 with the design
AnaLauraFFerraz Oct 25, 2026
5d5df1a
feat: bring insightsMatch to main for the cache staleness rule
AnaLauraFFerraz Oct 25, 2026
40a6d88
test: version a cut of the published recording for the engine's prope…
AnaLauraFFerraz Oct 25, 2026
3a918af
feat: count each lap from its extrapolated start in the delta series
AnaLauraFFerraz Oct 25, 2026
1912888
feat: detect corners from the track's geometry, the same in both dire…
AnaLauraFFerraz Oct 25, 2026
914369b
feat: measure each lap's braking and throttle inside the corner's own…
AnaLauraFFerraz Oct 25, 2026
8f27563
feat: describe the corrected insights to the coach
AnaLauraFFerraz Oct 25, 2026
b731024
feat: treat a cached analysis with outdated insights as stale
AnaLauraFFerraz Oct 25, 2026
6fa5d58
docs: record the validation of spec 005
AnaLauraFFerraz Oct 25, 2026
1cf9dd6
feat: read the full-throttle phrase in the order of the corner
AnaLauraFFerraz Oct 25, 2026
afa0119
docs: record the apex limitation and the reference role of computeDelta
AnaLauraFFerraz Oct 25, 2026
c293300
docs: record the measured token count of the coach's prompt and the p…
AnaLauraFFerraz Oct 25, 2026
eac4ebc
docs: mark spec 005 implemented and record the CI of the last push
AnaLauraFFerraz Oct 25, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 10 additions & 6 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,12 @@ Flow: `network/udpListener` → `telemetry/dispatcher` (parse) → `persistence/
| `src/telemetry/` | Binary parsing of the F1 24 protocol, one folder per packet type (`header`, `motion`, `lapData`, `carTelemetry`, `session`), shared `BufferCursor`, sizes and `assertBytesRead` in `shared/` |
| `src/ws/` | WebSocket message envelope |
| `src/persistence/` | SQLite schema and its migrations (`migrations.ts`), per-session in-memory context, decimation, lap completion detection, repositories (one per table); sample tables deduplicate on `(session_id, overall_frame_identifier)` — that field never resets or regresses within a session, including across a flashback, unlike session time or lap distance |
| `src/analysis/` | Pure functions, no I/O: corner detection, braking/throttle, delta, line deviation, tyre windows → `buildInsights` |
| `src/analysis/` | Pure functions, no I/O: track geometry and corner detection, braking/throttle, delta, line deviation, tyre windows → `buildInsights` |
| `src/coaching/` | Static system prompt, user-message builder, the code's reading of the compared laps and the opportunities (opportunity rule and labels, direction phrases), prompt version, analysis resolver (staleness rule, one call per pair), Anthropic call with typed errors |
| `src/http/` | Express app and routers (`sessionsRouter`, `analysisRouter`) |
| `src/replay/` | Pure replay pacing (`computeDelayMs`) and orchestration (`runReplay`): speed, loop, absolute-deadline scheduling |
| `src/tooling/` | Tests of the project's own tooling: the test of the empty-discovery guard |
| `tools/` | CLI scripts: recorder, analyze (insights, coach message, prompt version), a thin replayer CLI over `src/replay/`, and `assertTestFiles.ts`, the guard that runs as the `pretest` of `npm test` |
| `tools/` | CLI scripts: recorder, analyze (insights, coach message, prompt version), `exportRecordingCut.ts` (regenerates `src/analysis/recordingCut.json` from the published recording, in a temporary database), a thin replayer CLI over `src/replay/`, and `assertTestFiles.ts`, the guard that runs as the `pretest` of `npm test` |

The REST and WebSocket payloads are a contract consumed by the frontend, which mirrors the types by hand. This repo owns that contract: any change to `src/ws/messageFormat.ts`, the routers or the repository row types must be flagged so the frontend's `src/types/` can be updated.

Expand All @@ -55,14 +55,14 @@ The REST and WebSocket payloads are a contract consumed by the frontend, which m
- `analysis/` stays pure and deterministic. The AI coach only ever receives the derived `LapInsights` JSON, never raw samples, plus the compared laps' official number, time and validity and the code's reading of the opportunities.
- Repositories use prepared statements created at module load and map snake_case rows to camelCase objects.
- Unknown values are stored as `NULL`, never invented (see `lapCompletionDetector`).
- Tests: flat `test()` calls, synthetic data from a `testFixtures.ts` per folder (`src/analysis/`, `src/replay/`), mostly pure functions. The handful of tests that need a real SQLite connection (sample repositories, the replay integration test) use `src/persistence/testDb.ts` to open an isolated, disposable database via `DB_PATH` — never `data/telemetry.db`. A new test file under `src/` is picked up by the `test` script with no edit.
- Tests: flat `test()` calls, synthetic data from a `testFixtures.ts` per folder (`src/analysis/`, `src/replay/`), mostly pure functions; `src/analysis/` also checks properties on real laps from the versioned cut `recordingCut.json`, with no database. The handful of tests that need a real SQLite connection (sample repositories, the replay integration test) use `src/persistence/testDb.ts` to open an isolated, disposable database via `DB_PATH` — never `data/telemetry.db`. A new test file under `src/` is picked up by the `test` script with no edit.
- Commits: small, Conventional Commits (`feat:`, `fix:`, `docs:`, `test:`, `chore:`, `ci:`), English subject, **no `Co-Authored-By` trailer**.
- **Check names are a contract:** renaming a CI job (`typecheck`, `test`) requires updating the required checks of the ruleset on `main` in the same piece of work, and renaming `ci.yml` requires updating the README badge in the same piece of work.
- **Actions:** first-party `actions/*` by major tag; any third-party action by full commit SHA with a trailing version comment.

## Rules

- Never commit secrets: `.env`, API keys, tokens. `.env`, `data/` and `recordings/` are gitignored.
- Never commit secrets: `.env`, API keys, tokens. `.env`, `data/` and `recordings/` are gitignored. One exception, on purpose: `src/analysis/recordingCut.json` is versioned. It is a cut of the published sample recording (laps 6–8, only the fields the engine reads), so it holds none of the packets the engine does not use, it is small (ceiling 660,000 bytes, enforced by a test) and it comes from data already public as a release asset. Regenerate it with `tools/exportRecordingCut.ts`, never by hand.
- Run `npm test` and `npm run typecheck` (the same scripts the CI calls) before every commit; report any failure instead of committing around it.
- Do not change packet layouts, field order or sizes in `src/telemetry/` without checking the official spec, *Data Output from F1 24* (v27.2x).
- Documentation must describe only what exists in the code. Partial features are described as partial; no invented metrics or results.
Expand All @@ -88,12 +88,16 @@ These are also listed under "Known limitations" in the README. When a limitation
- The `max` replay speed overflows the UDP receive buffer by design: laps, times and sectors arrive intact, but a large share of the per-distance samples is lost. `1x` and `2x` do not lose samples.
- `--speed=max` combined with `--loop` delivers practically no samples (measured: effectively zero) — the flood never lets up between passes. Use `1x` or `2x` for a continuous demo.
- A looped replay of a recording whose own `overallFrameIdentifier` span is at or below `PASS_RESTART_THRESHOLD_FRAMES` (100, in `sessionContext.ts`) does not reset the in-memory lap/decimation state at the loop boundary — the next pass carries over stale state instead of starting fresh. Not expected to matter for any realistic recording (the published Interlagos example spans about 4,500 frames).
- The corner geometry thresholds (`src/analysis/trackGeometry.ts`: radius up to 100 m, at least 20° of turning) were validated only on Interlagos; on a track whose fast corners have a larger radius those corners may not appear. Revalidate them when a recording of another track exists.
- A corner's apex depends on the pair of laps compared: the geometric apex comes from the mean path of the two laps, so the same corner reads a few metres differently from pair to pair (in (8,6) and (8,7), the pairs the demo shows: 390/385, 1430/1440, 2130/2110, 2785/2780 and 3275/3290 m). Future work: a fixed corner map per track, together with revalidating the geometry thresholds on other tracks.
- A lap without motion samples gets no corners (`corners: []`); the total delta and the tyre windows are still computed.
- `corner.brakingStartDistance` and `corner.exitDistance` hold the start and end of the corner's stretch, not a braking point and a throttle pick-up. The names are kept for the frontend's contract; renaming them is debt for the frontend's spec.
- Switching the Node major without running `npm ci` again makes `better-sqlite3` fail to load with `ERR_DLOPEN_FAILED` (its native binary is built for one major's ABI).
- `sessionType` 18 is Time Trial in F1 24 (the numbering differs from F1 23).
- `.env` is read only at startup; `ts-node-dev` does not restart on `.env` changes.
- The first start of a version with a schema change migrates `data/telemetry.db` in place (this version adds `lap_analyses.prompt_version`); back it up first to a folder outside the repository, with the `VACUUM INTO` command in the README ("Upgrading an existing database").
- Without `ANTHROPIC_API_KEY`, `POST /sessions/:id/analysis` returns 503; analyses cached with the current prompt version and model are still served, older ones answer 503.
- The coach's system prompt is cacheable for `claude-opus-4-8`: the documented minimum is 1,024 tokens and the current prompt measures 2,172 (an estimate from the free token-counting endpoint). `cache_control` works with this model: the previous prompt showed `cache_write=1380` and then `cache_read=1380` on 2026-10-23; the current prompt has not been sent yet. Each call logs its token `usage` (`[coachService] usage ...`). Re-measure if the prompt changes.
- Without `ANTHROPIC_API_KEY`, `POST /sessions/:id/analysis` returns 503; analyses cached with the current prompt version, model and insights are still served (stored insights are compared with freshly computed ones by `insightsMatch`), older ones answer 503, and a pair whose samples are gone is not served from the cache and answers 400 (no samples), with or without the key.
- The coach's system prompt is cacheable for `claude-opus-4-8`: the documented minimum is 1,024 tokens and the current prompt (`PROMPT_VERSION` `0cb0df9eaefa147e`) measured 2,600 tokens, above it (an estimate from the free token-counting endpoint: the prompt plus a one-character message, 2,607, minus the message alone, 7). `cache_control` works with this model: the prompt that existed before `PROMPT_VERSION` was introduced (spec 004; it has no version) showed `cache_write=1380` and then `cache_read=1380` on 2026-10-23; the current prompt (`0cb0df9eaefa147e`) has not been sent yet. Each call logs its token `usage` (`[coachService] usage ...`). Re-measure if the prompt changes.

## How to work in this repo

Expand Down
Loading