id: e381bd027c89457ca59995179cfcea65
parent_id: 2977cec2004b4a79aad66805d2e63c79
item_type: 1
item_id: 0c837f4e6b7e462a997cbc19e47c864a
item_updated_time: 1782289390084
title_diff: "[]"
body_diff: "[{\"diffs\":[[0,\"** v0.7.\"],[-1,\"0\"],[1,\"1\"],[0,\" (28 end\"]],\"start1\":326,\"start2\":326,\"length1\":17,\"length2\":17},{\"diffs\":[[0,\"_points,\"],[1,\" car_grip,\"],[0,\" track_m\"]],\"start1\":2702,\"start2\":2702,\"length1\":16,\"length2\":26},{\"diffs\":[[0,\"ry frames.\\\n\\\n\"],[1,\"### Car Grip Analysis Response (v0.7.1)\\\n\\\nProduced by the `car_grip` use case. Uses **envelope extraction**: for each 10 km/h speed bin, finds the peak |lateral G|, peak acceleration G, and peak braking G. The harder you drive, the more complete the profile.\\\n\\\n```json\\\n{\\\n  \\\"summary\\\": {\\\n    \\\"max_lateral_g\\\": 1.42,\\\n    \\\"max_accel_g\\\": 0.85,\\\n    \\\"max_brake_g\\\": 1.35,\\\n    \\\"max_combined_g\\\": 1.58,\\\n    \\\"peak_lateral_speed_kmh\\\": 120.0\\\n  },\\\n  \\\"grip_envelope\\\": [\\\n    { \\\"speed_kmh\\\": 45.0, \\\"max_lateral_g\\\": 1.2, \\\"max_accel_g\\\": 0.8, \\\"max_brake_g\\\": 1.1, \\\"sample_count\\\": 340 }\\\n  ],\\\n  \\\"grip_circle\\\": [\\\n    { \\\"longitudinal_g\\\": 0.5, \\\"lateral_g\\\": -1.2 }\\\n  ],\\\n  \\\"steering_response\\\": [\\\n    { \\\"steer\\\": 0.15, \\\"lateral_g\\\": -1.1, \\\"speed_kmh\\\": 95.0 }\\\n  ],\\\n  \\\"runs_analyzed\\\": 3,\\\n  \\\"total_frames_analyzed\\\": 8400\\\n}\\\n```\\\n\\\n\"],[0,\"### Track Mo\"]],\"start1\":5609,\"start2\":5609,\"length1\":24,\"length2\":813},{\"diffs\":[[0,\"\\\": [\"],[-1,\"\\\n    {\\\n      \\\"index\\\": 234,\\\n      \\\"number\\\": 1,\\\n      \\\"entry_s\\\": 0.117,\\\n      \\\"apex_s\\\": 0.125,\\\n      \\\"exit_s\\\": 0.133,\\\n      \\\"curvature_peak\\\": 0.018\\\n    }\\\n  ],\\\n  \\\"sectors\\\": [\\\n    { \\\"index\\\": 0, \\\"start_s\\\": 0.0, \\\"end_s\\\": 0.333 },\\\n    { \\\"index\\\": 1, \\\"start_s\\\": 0.333, \\\"end_s\\\": 0.667 },\\\n    { \\\"index\\\": 2, \\\"start_s\\\": 0.667, \\\"end_s\\\": 1.0 }\\\n  \"],[1,\"...],\\\n  \\\"sectors\\\": [...\"],[0,\"],\\\n \"]],\"start1\":6751,\"start2\":6751,\"length1\":339,\"length2\":31},{\"diffs\":[[0,\"ks\\\n[\\\n  {\"],[-1,\"\\\n   \"],[0,\" \\\"game\\\":\"]],\"start1\":6916,\"start2\":6916,\"length1\":20,\"length2\":16},{\"diffs\":[[0,\"_corsa\\\",\"],[-1,\"\\\n   \"],[0,\" \\\"track_\"]],\"start1\":6941,\"start2\":6941,\"length1\":20,\"length2\":16},{\"diffs\":[[0,\"\\\"monza\\\",\"],[-1,\"\\\n   \"],[0,\" \\\"source\"]],\"start1\":6964,\"start2\":6964,\"length1\":20,\"length2\":16},{\"diffs\":[[0,\"ording\\\",\"],[-1,\"\\\n   \"],[0,\" \\\"create\"]],\"start1\":6996,\"start2\":6996,\"length1\":20,\"length2\":16},{\"diffs\":[[0,\"\\\": \\\"\"],[-1,\"2026-06-08T14:17:39+02:00\\\",\\\n   \"],[1,\"...\\\",\"],[0,\" \\\"co\"]],\"start1\":7016,\"start2\":7016,\"length1\":39,\"length2\":13},{\"diffs\":[[0,\"unt\\\": 11\"],[-1,\"\\\n \"],[0,\" }\\\n]\\\n```\"]],\"start1\":7036,\"start2\":7036,\"length1\":18,\"length2\":16},{\"diffs\":[[0,\"rs\\\n[\\\n  {\"],[-1,\"\\\n   \"],[0,\" \\\"car_na\"]],\"start1\":7131,\"start2\":7131,\"length1\":20,\"length2\":16},{\"diffs\":[[0,\"88 GT3\\\",\"],[-1,\"\\\n   \"],[0,\" \\\"sessio\"]],\"start1\":7162,\"start2\":7162,\"length1\":20,\"length2\":16},{\"diffs\":[[0,\"unt\\\": 3,\"],[-1,\"\\\n   \"],[0,\" \\\"recomm\"]],\"start1\":7182,\"start2\":7182,\"length1\":20,\"length2\":16},{\"diffs\":[[0,\": 7500.0\"],[-1,\"\\\n \"],[0,\" }\\\n]\\\n```\"]],\"start1\":7214,\"start2\":7214,\"length1\":18,\"length2\":16},{\"diffs\":[[0,\"\\\": [\"],[-1,\"\\\n    {\\\n     \"],[1,\"{\"],[0,\" \\\"ge\"]],\"start1\":7363,\"start2\":7363,\"length1\":20,\"length2\":9},{\"diffs\":[[0,\"ear\\\": 2,\"],[-1,\"\\\n     \"],[0,\" \\\"min_rp\"]],\"start1\":7371,\"start2\":7371,\"length1\":22,\"length2\":16},{\"diffs\":[[0,\"\\\": 3200,\"],[-1,\"\\\n     \"],[0,\" \\\"max_rp\"]],\"start1\":7388,\"start2\":7388,\"length1\":22,\"length2\":16},{\"diffs\":[[0,\"\\\": 8500,\"],[-1,\"\\\n     \"],[0,\" \\\"min_sp\"]],\"start1\":7405,\"start2\":7405,\"length1\":22,\"length2\":16},{\"diffs\":[[0,\"5.0,\"],[-1,\"\\\n     \"],[0,\" \\\"ma\"]],\"start1\":7432,\"start2\":7432,\"length1\":14,\"length2\":8},{\"diffs\":[[0,\"20.0\"],[-1,\"\\\n    }\\\n \"],[0,\" \"],[1,\"}\"],[0,\"],\\\n \"]],\"start1\":7455,\"start2\":7455,\"length1\":17,\"length2\":10},{\"diffs\":[[0,\"les \"],[-1,\"for all sessions \"],[0,\"— fa\"]],\"start1\":8149,\"start2\":8149,\"length1\":25,\"length2\":8},{\"diffs\":[[0,\"cess\"],[-1,\" (analysis, frame extraction)\"],[0,\"\\\n- *\"]],\"start1\":8271,\"start2\":8271,\"length1\":37,\"length2\":8},{\"diffs\":[[0,\"topped (\"],[1,\"stale \"],[0,\"ring-buf\"]],\"start1\":8339,\"start2\":8339,\"length1\":16,\"length2\":22},{\"diffs\":[[0,\"kers\"],[-1,\" are stale after restart\"],[0,\")\\\n\\\n#\"]],\"start1\":8368,\"start2\":8368,\"length1\":32,\"length2\":8},{\"diffs\":[[0,\"awn`\"],[-1,\" to avoid blocking API responses\"],[0,\"\\\n\\\n--\"]],\"start1\":8639,\"start2\":8639,\"length1\":40,\"length2\":8},{\"diffs\":[[0,\"tion\"],[-1,\"\\\n- **Optional** — if rusty-telemetry can't bind :10101, continues without it\"],[0,\"\\\n\\\n##\"]],\"start1\":9502,\"start2\":9502,\"length1\":84,\"length2\":8},{\"diffs\":[[0,\"s\\\n- \"],[-1,\"Configure in PCARS: Options → System → UDP Protocol → set target IP/port\\\n- PCARS 1: set format to \\\"Project CARS\\\"\\\n- PCARS 2: set format to \\\"2\\\" for native format, or \\\"1\\\" for PCARS1-compatible\\\n- **Optional** — if bind fails, shows as \\\"not available\\\" and continues\\\n- **V1 position data** (added 2026-06-08): reads `world_position` (3×i16), `current_lap_distance`, derives `spline_position`; enables track mapping for PCARS\\\n\\\n---\\\n\\\n## Track Model Storage\\\n\\\nTrack models are persisted as JSON at `data/tracks/<game>/<track_name>.json`.\\\n\\\n| Method | Description |\\\n|--------|-------------|\\\n| `TrackModelStore::new(data_dir)` | Create store rooted at `data_dir/tracks/` |\\\n| `list()` | List all track model summaries |\\\n| `load(game, track_name)` | Load full TrackModel |\\\n| `save(&model)` | Save model (creates directories) |\\\n| `delete(game, track_name)` | Delete model file |\\\n| `exists(game, track_name)` | Check existence |\\\n\\\nPath traversal protection: `game` and `track_name` are validated to prevent `..`, `/`, `\\\\` components.\\\n\\\n---\\\n\\\n## Version History\"],[1,\"**V1 position data** (added 2026-06-08): reads `world_position` (3×i16), `current_lap_distance`, derives `spline_position`\\\n\\\n---\\\n\\\n## Version History\\\n\\\n### v0.7.1 (2026-06-24) — Car Grip Analysis\\\n- **New use case: `car_grip`** — envelope extraction grip analysis from race-pace laps\\\n  - `analyze_car_grip()` in `analysis.rs` — speed-binned peak G (lateral/accel/brake)\\\n  - Grip circle scatter (longitudinal vs lateral G), steering response scatter\\\n  - Downsampled to ~800 scatter points per dataset\\\n  - Guidance: \\\"Drive 3–5 laps at race pace. Brake hard, carry speed through corners...\\\"\\\n- Added to `UseCase` enum, `all()`, `guidance()`, and analysis dispatch\\\n- 33 tests (unchanged)\"],[0,\"\\\n\\\n##\"]],\"start1\":9656,\"start2\":9656,\"length1\":1047,\"length2\":686},{\"diffs\":[[0,\"ion\\\n\"],[-1,\"  - Metadata-only `load_all` on startup; `ensure_frames_loaded` lazy-loads on demand\\\n  - Recording runs normalized to Stopped on reload\\\n\"],[0,\"  - \"]],\"start1\":10643,\"start2\":10643,\"length1\":144,\"length2\":8},{\"diffs\":[[0,\".rs`\"],[-1,\" — aggregates shift-point data across sessions\\\n  - Typed `Deserialize` view structs (`ShiftPointAnalysisView`, `GearCurveView`)\"],[0,\"\\\n- *\"]],\"start1\":10836,\"start2\":10836,\"length1\":135,\"length2\":8},{\"diffs\":[[0,\"case\"],[-1,\" (superseded by `track_mapping`)\"],[0,\"\\\n- `\"]],\"start1\":10880,\"start2\":10880,\"length1\":40,\"length2\":8},{\"diffs\":[[0,\"rce`\"],[-1,\" (for session frame round-trip)\\\n- 33 unit tests (unchanged from v0.6.0)\\\n\\\n### Post-v0.6.0 (2026-06-08) — Track Mapping Implementation\\\n- `GET /api/tracks` — list all stored track models\\\n- `GET /api/tracks/{game}/{track_name}` — get full track model geometry\\\n- `DELETE /api/tracks/{game}/{track_name}` — delete track model\\\n- `GET /api/tracks/{game}/{track_name}/corners` — get auto-detected corners\\\n- `POST /api/tracks/build` — build TrackModel from completed boundary recording session\"],[1,\"\\\n- 33 unit tests\\\n\\\n### Post-v0.6.0 (2026-06-08) — Track Mapping Implementation\\\n- Track API endpoints (`GET /api/tracks`, `GET/DELETE /api/tracks/{game}/{track_name}`, `POST /api/tracks/build`)\"],[0,\"\\\n- N\"]],\"start1\":10939,\"start2\":10939,\"length1\":491,\"length2\":199},{\"diffs\":[[0,\"g` —\"],[-1,\" deliberate\"],[0,\" bou\"]],\"start1\":11164,\"start2\":11164,\"length1\":19,\"length2\":8},{\"diffs\":[[0,\"flow\"],[-1,\" (left + right laps)\"],[0,\"\\\n- P\"]],\"start1\":11192,\"start2\":11192,\"length1\":28,\"length2\":8},{\"diffs\":[[0,\"data\"],[-1,\": world_position (3×i16), current_lap_distance, derived spline_position\\\n- Dual position mode: auto-detect spline vs. coordinate, cumulative distance as pseudo-spline\\\n- Track model builder: resample → center line → heading → curvature → corner detection → sectors → normaliz\"],[1,\", dual position mod\"],[0,\"e\\\n- \"]],\"start1\":11217,\"start2\":11217,\"length1\":281,\"length2\":27},{\"diffs\":[[0,\"w module\"],[1,\"s\"],[0,\": `track\"]],\"start1\":11246,\"start2\":11246,\"length1\":16,\"length2\":17},{\"diffs\":[[0,\"del.rs` \"],[-1,\"— \"],[1,\"(\"],[0,\"TrackMod\"]],\"start1\":11266,\"start2\":11266,\"length1\":18,\"length2\":17},{\"diffs\":[[0,\"tore\"],[-1,\"\\\n- New field: `TelemetryFrame::current_lap_distance` (Option<u16>)\\\n- 33 unit tests total (6 ksudp + 7 pcars + 7 telemetry_tool + 14 feed_state\"],[0,\")\\\n\\\n#\"]],\"start1\":11314,\"start2\":11314,\"length1\":150,\"length2\":8},{\"diffs\":[[0,\"id}`\"],[-1,\" — delete a run\\\n-\"],[1,\",\"],[0,\" `PO\"]],\"start1\":11414,\"start2\":11414,\"length1\":25,\"length2\":9},{\"diffs\":[[0,\"pen`\"],[-1,\" — reopen completed session\\\n-\"],[1,\"\\\n- Extended\"],[0,\" `PO\"]],\"start1\":11448,\"start2\":11448,\"length1\":37,\"length2\":19},{\"diffs\":[[0,\"uns`\"],[-1,\" — extended\"],[0,\" wit\"]],\"start1\":11490,\"start2\":11490,\"length1\":19,\"length2\":8},{\"diffs\":[[0,\"e_feeds`\"],[-1,\" field\"],[0,\"\\\n\\\n### v0\"]],\"start1\":11505,\"start2\":11505,\"length1\":22,\"length2\":16},{\"diffs\":[[0,\"sis\\\n\"],[-1,\"- Full REST API with recording CRUD, session CRUD, run management, analysis endpoints\\\n\\\n### v0.4.1 (2026-06-04) — Remote Operation Support\\\n- Split BIND into GAME_HOST + LISTEN env vars\\\n\\\n### v0.4.0 (2026-06-04) — PCARS Support\\\n- Port 5606 parser (V1/V2 auto-detect)\\\n\\\n### v0.3.0 (2026-06-04) — TUI + Binary Parsers\\\n- KSUDP handshake fix, Telemetry Tool parser, ring buffer frequency, feed health state machine\"],[1,\"### v0.4.1 (2026-06-04) — Remote Operation Support\\\n### v0.4.0 (2026-06-04) — PCARS Support\\\n### v0.3.0 (2026-06-04) — TUI + Binary Parsers\"],[0,\"\\\n\\\n--\"]],\"start1\":11583,\"start2\":11583,\"length1\":414,\"length2\":145},{\"diffs\":[[0,\"0.7.\"],[-1,\"0: session persistence (split metadata/frames, lazy load, atomic writes), car characteristics DB (2 endpoints), removed track_map use case,\"],[1,\"1: car_grip use case with envelope extraction grip analysis. v0.7.0: session persistence, car characteristics DB.\"],[0,\" 28 \"]],\"start1\":12355,\"start2\":12355,\"length1\":147,\"length2\":121},{\"diffs\":[[0,\"ndpoints\"],[1,\".\"],[0,\"*\"]],\"start1\":12483,\"start2\":12483,\"length1\":9,\"length2\":10}]"
metadata_diff: {"new":{},"deleted":[]}
encryption_cipher_text: 
encryption_applied: 0
updated_time: 2026-06-24T08:27:22.461Z
created_time: 2026-06-24T08:27:22.461Z
type_: 13