diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..4432d77 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,54 @@ +# AGENTS.md — rusty-telemetry Project Context + +## Mandatory Reference + +**Before starting any work on this project, always read the Joplin note that contains the full project plan and architecture.** + +- **Joplin Note ID**: `6c0dcb2a567348fd9796f50c790082e4` +- **Title**: "Sim Racing Telemetry Analysis Platform — Project Plan" +- **Tags**: sim-racing, lua, telemetry, rust, project-plan, assetto-corsa + +Use the `joplin_get_note` tool with note ID `6c0dcb2a567348fd9796f50c790082e4` to retrieve the full project plan. + +## Project Overview + +`rusty-telemetry` is a Rust-based multi-port UDP telemetry listener and analysis platform for sim racing. It receives telemetry from Assetto Corsa (KSUDP + Telemetry Tool) and Project CARS 1 & 2, provides a terminal dashboard (ratatui), and exposes a REST API for session management, analysis, and car characteristics aggregation. + +## Companion Repository + +| Field | Value | +|-------|-------| +| **Repo** | `racecraft` | +| **Path** | `~/WebstormProjects/racecraft` | +| **Language** | TypeScript (Vue 3) | +| **Purpose** | Web client — live telemetry, session UI, charts, track maps | + +## Tech Stack + +- Rust (edition 2021) +- Tokio (async runtime) +- Axum 0.8 (REST API) +- Ratatui (terminal UI) +- Serde (JSON serialization) + +## Key Ports + +| Port | Feed Name | Source | Format | +|-------|-------------------|-------------------------------|---------| +| 9996 | ksudp | AC built-in engine | Binary (3-phase handshake, RTCarInfo) | +| 10101 | telemetry_tool | Telemetry Tool Python plugin | Binary (per-driver structs, ~60 Hz) | +| 5606 | pcars | Project CARS 1 & 2 | Binary (V1 1367B / V2 559B, auto-detected) | +| 8080 | REST API | Axum HTTP server | JSON | + +## Data Persistence + +- **Recordings**: `./data//` — manifest.json + per-feed JSONL files (written on shutdown) +- **Sessions**: `./data/sessions/.json` (metadata + analysis) + `.frames.json` (frames, lazy-loaded) +- **Track Models**: stored in session analysis results, served via `/api/tracks` + +## Commands + +```bash +cargo run --release # Start server + TUI +cargo test # Run 33 unit tests +``` diff --git a/README.md b/README.md index 3bb5850..bd28952 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Multi-port UDP telemetry listener and analysis platform for sim racing games. -Receives real-time telemetry from **Assetto Corsa** and **Project CARS 1 & 2** via UDP, parses binary packet formats, and provides live data via a terminal dashboard and REST API. Supports use-case driven analysis sessions for shift point optimization and track mapping. +Receives real-time telemetry from **Assetto Corsa** and **Project CARS 1 & 2** via UDP, parses binary packet formats, and provides live data via a terminal dashboard and REST API. Supports use-case driven analysis sessions for shift point optimization and track mapping, with persistent session storage and cross-session car characteristics aggregation. ## Supported Games @@ -147,14 +147,22 @@ Each recording has a `manifest.json` with metadata (game, track, car, use case, Use-case sessions allow targeted data capture and analysis: -1. **Create a session** with a use case (`shift_points` or `track_map`) +1. **Create a session** with a use case (`shift_points` or `track_mapping`) 2. **Start runs** — each run captures frames from the ring buffer between start and stop 3. **Complete the session** — auto-analyzes captured data and stores results 4. **Retrieve analysis** — shift point RPM ranges or track boundary coordinates 5. **Re-open if needed** — re-open a completed session to add more runs, then complete again 6. **Delete runs** — remove unwanted runs from a session (must be stopped first) -Sessions are in-memory and independent of persistent recordings. +Sessions are persistent: session metadata, runs, and analysis results survive a restart. On startup, all sessions are loaded from disk and incomplete runs are normalized to stopped state. Session data is stored separately from recordings: + +``` +./data/sessions/ +├── .json # Metadata (runs, analysis results, car_name) +└── .frames.json # Telemetry frames (lazy-loaded on demand) +``` + +Session metadata is loaded at startup for fast listing; frame data is lazy-loaded only when analysis or frame extraction is requested. Writes are atomic (temp file + rename) and frame serialization is offloaded to a background thread to avoid blocking the API. #### Feed Merging @@ -206,6 +214,11 @@ A built-in HTTP API server runs on port 8080 for recording management and live t | POST | `/api/sessions/{id}/complete` | Complete session + auto-analyze | | GET | `/api/sessions/{id}/analysis` | Get analysis results | | POST | `/api/sessions/{id}/analyze` | Re-run analysis on completed session | +| GET | `/api/characteristics/cars` | List all cars with aggregated shift-point data | +| GET | `/api/characteristics/cars/{car}` | Get detailed shift-point profile for a car | +| GET | `/api/tracks` | List all track models | +| GET | `/api/tracks/{track}` | Get a track model by key | +| POST | `/api/sessions/{id}/build-track-model` | Build a track model from a track mapping session | ### Start Recording Body @@ -289,7 +302,7 @@ Returns the updated session on success. ```json [ { "id": "shift_points", "name": "ShiftPoints", "guidance": "1. Drive to a long straight..." }, - { "id": "track_map", "name": "TrackMap", "guidance": "1. Drive a complete lap..." } + { "id": "track_mapping", "name": "TrackMapping", "guidance": "1. Drive a complete lap..." } ] ``` @@ -298,6 +311,41 @@ Each object contains: - `name` — human-readable name - `guidance` — step-by-step instructions for the use case +### Car Characteristics + +The characteristics endpoints aggregate shift-point analysis data across all completed `shift_points` sessions for each car. This provides a growing database of optimal shift RPMs per gear, useful for comparing setups and tracking improvements over time. + +**`GET /api/characteristics/cars`** — returns a summary list: + +```json +[ + { + "car_name": "Ferrari 488 GT3", + "session_count": 3, + "recommended_shift_rpm": 7500.0 + } +] +``` + +**`GET /api/characteristics/cars/{car}`** — returns detailed per-gear data: + +```json +{ + "car_name": "Ferrari 488 GT3", + "gears": [ + { + "gear": 2, + "min_rpm": 3200, + "max_rpm": 8500, + "min_speed_kmh": 45.0, + "max_speed_kmh": 120.0 + } + ], + "recommended_shift_rpm": 7500.0, + "session_count": 3 +} +``` + ### Shift Point Analysis Response ```json @@ -353,7 +401,7 @@ Then start the web client on the same machine — it proxies `/api/*` to `localh cargo test ``` -32 unit tests covering: +33 unit tests covering: - Binary parsers for all three feed formats (KSUDP, Telemetry Tool, PCARS V1/V2) - Ring buffer frequency calculation and edge cases - Feed health state machine transitions (Waiting → Alive → Stale → Alive) @@ -364,10 +412,12 @@ cargo test ``` src/ ├── main.rs Multi-port UDP listener, TUI event loop, KSUDP handshake -├── api.rs REST API server (axum) — recordings, sessions, live telemetry +├── api.rs REST API server (axum) — recordings, sessions, tracks, characteristics, live telemetry ├── recording.rs Recording manager — ring buffers, manifest CRUD, flush to JSONL -├── session.rs Session manager — use-case sessions, runs, frame extraction +├── session.rs Session manager — persistent sessions, runs, frame extraction, lazy-loading ├── analysis.rs Analysis algorithms — shift points, track mapping +├── characteristics.rs Car characteristics aggregation — cross-session shift-point profiles +├── track_model.rs Track model geometry — center line computation, boundary data ├── binary_utils.rs Shared safe binary read helpers (bounds-checked, zero fallback) ├── telemetry_frame.rs Unified telemetry data model (TelemetryFrame) ├── feed_state.rs Per-feed health/frequency state machine with ring buffer