id: 537f25c14af14c419deeda2af4174e92
parent_id: 
item_type: 1
item_id: aed9f3be040943048273a16e05a8100f
item_updated_time: 1780732801664
title_diff: "[{\"diffs\":[[1,\"Assetto Corsa — UDP Telemetry Protocol Specification\"]],\"start1\":0,\"start2\":0,\"length1\":0,\"length2\":52}]"
body_diff: "[{\"diffs\":[[1,\"# Assetto Corsa — UDP Telemetry Protocol Specification\\\n\\\n> **Game**: Assetto Corsa (2014) by Kunos Simulazioni\\\n> **Engine**: Kunos in-house engine\\\n> **Last verified**: 2026-06-03 (Abarth 500, Vallelunga, Soft tyres)\\\n> **Parser status**: ✅ KSUDP (`parser_ksudp.rs`), ✅ Telemetry Tool (`parser_telemetry_tool.rs`), ❌ Lua Plugin (not yet working)\\\n\\\n---\\\n\\\n## Overview — Three Independent Feeds\\\n\\\nAC exposes telemetry through **three completely independent UDP feeds**, each with different protocols, data depths, and reliability:\\\n\\\n| Feed | Port | Protocol | Source | Rate | Data Depth | Status |\\\n|---|---|---|---|---|---|---|\\\n| **KSUDP** | 9996 | Binary (handshaked) | AC built-in engine | ~20 Hz (broken) | Low (~40%) | ⚠️ Sparse |\\\n| **Telemetry Tool** | 10101 | Binary (no handshake) | 3rd-party Python plugin | ~60 Hz | Very High (~95%) | ✅ Working |\\\n| **Lua Plugin** | 5005 | JSON | Our CSP Lua app | Configurable | High (~90%) + CSP unique | ❌ LAZY=FULL bug |\\\n\\\n**Primary feed: Telemetry Tool (10101)** — reliable, high-frequency, data-rich.\\\n**Secondary: Lua Plugin (5005)** — complementary CSP data (damage, fuel, tyre wear, weather).\\\n**Deprioritized: KSUDP (9996)** — sparse data, unreliable handshake.\\\n\\\n---\\\n\\\n## Cross-References\\\n\\\n- **Session analysis**: [Telemetry Feed Analysis — Session 2026-06-03](joplin://395bc805650d44b6bfec423ca095ed64)\\\n- **Main project plan**: [Sim Racing Telemetry Analysis Platform — Project Plan](joplin://6c0dcb2a567348fd9796f50c790082e4)\\\n- **ACC protocol**: [ACC — UDP Telemetry Protocol Specification](joplin://6ae7005d9810437093d63470cff98b59)\\\n- **AC EVO status**: [AC EVO — Telemetry Status & Research](joplin://2171d34ab9c1431ea3a979d30d206e23)\\\n- **AC Rally status**: [AC Rally — Telemetry Status & Research](joplin://b7b331aa87544b6ebe5db5b8d7bcd2a0)\\\n- **PCARS protocol**: [Project CARS 1 & 2 — UDP Telemetry Protocol Specification](joplin://c6bd2c45938246fa9d61776deae9874b)\\\n\\\n---\\\n\\\n## Feed A: KSUDP (Port 9996) — Built-in Engine Feed\\\n\\\n### Connection Protocol (3-Phase Handshake)\\\n\\\n1. AC listens on port 9996 for registration packets\\\n2. Client sends a registration packet: `1u32.to_le_bytes()` (4 bytes: `[1, 0, 0, 0]`) to AC's port 9996\\\n3. AC responds by streaming RTCarInfo packets to the client's return address\\\n4. Client must send periodic keep-alive registration packets (every ~10 seconds)\\\n5. AC sends RTCarInfo structs at ~20 Hz\\\n\\\n**Important**: Only one client can bind to :9996 at a time. If rusty-telemetry binds first, AC cannot bind its listener → zero packets. The handshake model requires AC to bind first, then the client registers.\\\n\\\n### RTCarInfo Binary Struct (Naturally Aligned)\\\n\\\nThe C struct uses **natural alignment** (NOT packed). This means padding bytes exist:\\\n- 3 bytes padding after `char identifier` (offset 0) before `int size` (offset 4)\\\n- 2 bytes padding after the 6 bools (offsets 20–25) before the next float (offset 28)\\\n\\\n**Total struct size: 328 bytes** (not 323).\\\n\\\n### Field-by-Field Byte Map\\\n\\\n| Offset | Size | Type | Field | Notes |\\\n|---|---|---|---|---|\\\n| 0 | 1 | char | identifier | Must be `0x61` = `'a'` |\\\n| 1 | 3 | — | padding | (natural alignment) |\\\n| 4 | 4 | int32_le | size | Self-reported struct size (328) |\\\n| 8 | 4 | float32_le | speedKmh | |\\\n| 12 | 4 | float32_le | speedMph | |\\\n| 16 | 4 | float32_le | speedMs | |\\\n| 20 | 1 | bool | isAbsEnabled | |\\\n| 21 | 1 | bool | isAbsInAction | |\\\n| 22 | 1 | bool | isTcInAction | |\\\n| 23 | 1 | bool | isTcEnabled | |\\\n| 24 | 1 | bool | isInPit | |\\\n| 25 | 1 | bool | isEngineLimiterOn | |\\\n| 26 | 2 | — | padding | (natural alignment) |\\\n| 28 | 4 | float32_le | accG_vertical | G-force vertical |\\\n| 32 | 4 | float32_le | accG_horizontal | G-force horizontal |\\\n| 36 | 4 | float32_le | accG_frontal | G-force frontal |\\\n| 40 | 4 | int32_le | lapTime | Current lap time (ms) |\\\n| 44 | 4 | int32_le | lastLap | Last lap time (ms) |\\\n| 48 | 4 | int32_le | bestLap | Best lap time (ms) |\\\n| 52 | 4 | int32_le | lapCount | Current lap number |\\\n| 56 | 4 | float32_le | gas | Throttle 0.0–1.0 |\\\n| 60 | 4 | float32_le | brake | Brake 0.0–1.0 |\\\n| 64 | 4 | float32_le | fuel | Fuel level 0.0–1.0 (NOT clutch) |\\\n| 68 | 4 | float32_le | engineRPM | |\\\n| 72 | 4 | float32_le | steer | Steering -1.0 to 1.0 |\\\n| 76 | 4 | int32_le | gear | Current gear |\\\n| 80 | 4 | float32_le | cgHeight | Centre of gravity height |\\\n| 84 | 16 | float32_le × 4 | wheelAngularSpeed | Per-wheel |\\\n| 100 | 16 | float32_le × 4 | slipAngle | Per-wheel |\\\n| 116 | 16 | float32_le × 4 | slipAngleContactPatch | Per-wheel |\\\n| 132 | 16 | float32_le × 4 | slipRatio | Per-wheel |\\\n| 148 | 16 | float32_le × 4 | tyreSlip | Per-wheel |\\\n| 164 | 16 | float32_le × 4 | ndSlip | Per-wheel normalized slip |\\\n| 180 | 16 | float32_le × 4 | wheelLoad | Per-wheel (N) |\\\n| 196 | 16 | float32_le × 4 | wheelDy | Per-wheel lateral displacement |\\\n| 212 | 16 | float32_le × 4 | wheelMz | Per-wheel self-aligning torque |\\\n| 228 | 16 | float32_le × 4 | tyreDirtyLevel | Per-wheel marble buildup |\\\n| 244 | 16 | float32_le × 4 | camberRAD | Per-wheel camber in radians |\\\n| 260 | 16 | float32_le × 4 | tyreRadius | Per-wheel |\\\n| 276 | 16 | float32_le × 4 | tyreLoadedRadius | Per-wheel |\\\n| 292 | 16 | float32_le × 4 | suspensionHeight | Per-wheel |\\\n| 308 | 4 | float32_le | carPositionNormalized | 0.0–1.0 = spline position |\\\n| 312 | 4 | float32_le | carSlope | |\\\n| 316 | 4 | float32_le | worldX | World position X |\\\n| 320 | 4 | float32_le | worldY | World position Y |\\\n| 324 | 4 | float32_le | worldZ | World position Z |\\\n\\\n### KSUDP — Known Issues\\\n\\\n1. **Extremely sparse**: Only 13 packets in ~5 min (expected ~6,000 at 20 Hz)\\\n2. **Handshake unreliability**: Registration packet format may be incomplete\\\n3. **No clutch input**: Offset 64 is fuel level, not clutch\\\n4. **No tyre temperatures**: No tyre_core_temps or tread temps\\\n5. **No multi-car data**: Only player car\\\n6. **No velocity vectors**: No world_speed or local_speed\\\n7. **KSUDP is considered a legacy/abandoned feature** by Kunos\\\n\\\n### KSUDP — Rust Parser\\\n\\\n- File: `src/parser_ksudp.rs` (267 lines)\\\n- Function: `parse_rt_car_info(data: &[u8]) -> Result<TelemetryFrame, KsudpParseError>`\\\n- Identifier check: byte 0 must be `b'a'`\\\n- Validation: reported size at offset 4 vs actual packet length\\\n- All per-wheel fields read as `read_f32_4(data, offset)` → `[f32; 4]` (FL, FR, RL, RR)\\\n\\\n---\\\n\\\n## Feed B: Telemetry Tool (Port 10101) — PRIMARY FEED\\\n\\\n### Source\\\n\\\nThird-party Python plugin: `<AC>/apps/python/Telemetry_Tool_plugin/Telemetry_Tool_plugin.py` by IkoRein v1.3.\\\nSends binary telemetry for **all cars** in the session. No handshake needed — just bind to port 10101 and receive.\\\n\\\n### Connection Model\\\n\\\n- rusty-telemetry binds UDP socket to `0.0.0.0:10101`\\\n- AC Python plugin sends packets to `127.0.0.1:10101`\\\n- No registration, no handshake, no keep-alive needed\\\n\\\n### Performance (Verified 2026-06-03)\\\n\\\n| Metric | Value |\\\n|---|---|\\\n| Packets in 4.9 min session | 17,496 data + 17 heartbeats |\\\n| Rate | ~60 Hz (one per rendered frame) |\\\n| Bytes per data packet | 357 per driver |\\\n| Total session data | ~10.6 MB across 2 sessions |\\\n| Packet loss | Zero observed |\\\n| Validation | All markers intact (`test_float=666.666`, `test_int=666`) |\\\n\\\n### Packet Structure\\\n\\\n**Packet byte 0 = packet_type:**\\\n- `22` = Telemetry data (contains N × 357-byte driver structs)\\\n- `23` = Version info (sent every ~100 frames, typically ignored)\\\n- Anything else = unknown\\\n\\\n**Heartbeat packets**: 5 bytes, sent periodically (type byte + 4 bytes).\\\n\\\n### Driver Struct (357 bytes per driver)\\\n\\\n| Offset | Size | Type | Field | Notes |\\\n|---|---|---|---|---|\\\n| 0 | 4 | uint32_le | driver_id | Unique per car |\\\n| 4 | 4 | uint32_le | lap_time | Current lap time (ms) |\\\n| 8 | 4 | uint32_le | last_lap | Last lap time (ms) |\\\n| 12 | 4 | uint32_le | best_lap | Best lap time (ms) |\\\n| 16 | 12 | int32_le × 3 | sector_times | Current sector times |\\\n| 28 | 12 | int32_le × 3 | last_sector_times | Previous lap sector times |\\\n| 40 | 4 | float32_le | steer | -1.0 to 1.0 |\\\n| 45 | 4 | float32_le | throttle | 0.0–1.0 |\\\n| 49 | 4 | float32_le | brake | 0.0–1.0 |\\\n| 53 | 4 | float32_le | clutch | 0.0–1.0 |\\\n| 57 | 2 | int16_le | gear | Current gear |\\\n| 59 | 4 | float32_le | engine_rpm | |\\\n| 63 | 4 | float32_le | speed_kmh | |\\\n| 67 | 2 | int16_le | track_position | Position in track order |\\\n| 69 | 2 | int16_le | leaderboard_position | Race position |\\\n| 71 | 2 | int16_le | lap_count | Current lap |\\\n| 73 | 4 | float32_le | world_x | World position X |\\\n| 77 | 4 | float32_le | world_y | World position Y |\\\n| 81 | 4 | float32_le | world_z | World position Z |\\\n| 85 | 8 | float64_le | spline_position | 0.0–1.0 track progress |\\\n| 93 | 4 | float32_le | test_float | Validation: 666.666 |\\\n| 97 | 33 | char[33] | driver_name | Null-terminated string |\\\n| 130 | 4 | char[4] | nationality | e.g. \\\"SM\\\", \\\"IT\\\" |\\\n| 134 | 33 | char[33] | car_name | e.g. \\\"abarth500\\\" |\\\n| 167 | 10 | char[10] | tyre_compound | e.g. \\\"SM\\\" |\\\n| 177 | 4 | uint32_le | test_int | Validation: 666 |\\\n| 181 | 1 | bool | is_connected | |\\\n| 182 | 1 | bool | is_in_pit | |\\\n| 183 | 1 | bool | is_car_in_pitlane | |\\\n| 184 | 1 | bool | lap_invalidated | |\\\n| 185 | 4 | float32_le | acc_g_vertical | |\\\n| 189 | 4 | float32_le | acc_g_horizontal | |\\\n| 193 | 4 | float32_le | acc_g_frontal | |\\\n| 197 | 1 | bool | is_drs_available | |\\\n| 198 | 1 | bool | is_drs_enabled | |\\\n| 199 | 1 | bool | is_race_finished | |\\\n| 205 | 4 | float32_le | ers_max_kj | ERS capacity |\\\n| 209 | 4 | float32_le | ers_current_kj | ERS current charge |\\\n| 213 | 16 | float32_le × 4 | tyre_dirty_level | Per-wheel marbles |\\\n| 229 | 4 | float32_le | turbo_boost | Turbo boost pressure |\\\n| 233 | 8 | float32_le × 2 | ride_height_front_rear | Front, rear ride height |\\\n| 241 | 16 | float32_le × 4 | tyre_core_temp | Per-wheel °C |\\\n| 257 | 16 | float32_le × 4 | tyre_slip_ratio | Per-wheel slip ratio |\\\n| 273 | 16 | float32_le × 4 | tyre_slip_angle | Per-wheel slip angle |\\\n| 289 | 16 | float32_le × 4 | nd_slip | Per-wheel normalized slip |\\\n| 305 | 16 | float32_le × 4 | wheel_slip | Per-wheel slip indicator |\\\n| 321 | 12 | float32_le × 3 | world_velocity | 3D world velocity vector |\\\n| 333 | 12 | float32_le × 3 | local_velocity | 3D local velocity vector |\\\n\\\n### Telemetry Tool — Coaching Coverage\\\n\\\n| Use Case | Supported? | Data Source |\\\n|---|---|---|\\\n| Braking point analysis | ✅ | speed, spline_position, brake input |\\\n| Racing line tracking | ✅ | world_pos (x,y,z), spline_pos |\\\n| Throttle/traction control | ✅ | tyre_slip_ratio, nd_slip, wheel_slip |\\\n| Understeer/oversteer detection | ✅ (~95%) | slip_angle + nd_slip per wheel |\\\n| Tyre management | ✅ | tyre_core_temp, tyre_dirty_level |\\\n| Lap time comparison | ✅ | lap_time, best_lap, sector_times |\\\n| G-force analysis | ✅ | 3-axis accelerations |\\\n| Ride height / setup validation | ✅ | ride_height F/R |\\\n| Multi-car tracking | ✅ | All drivers in packet |\\\n| ERS/DRS/P2P | ✅ | ers_*, drs_* fields |\\\n| Turbo monitoring | ✅ | turbo_boost |\\\n\\\n### Telemetry Tool — Rust Parser\\\n\\\n- File: `src/parser_telemetry_tool.rs` (407 lines)\\\n- Fast path: `parse_first_driver_no_strings(data)` — skips strings, returns `(TelemetryFrame, driver_id)`\\\n- Full path: `parse_first_driver(data)` — includes driver_name, car_name, nationality, tyre_compound\\\n- Multi-driver: `parse_packet(data)` — returns `Vec<TelemetryFrame>` for all drivers\\\n- String offsets are exported as constants for cache-based string loading:\\\n  - `STRING_OFFSET_DRIVER_NAME = 98` (1 + 97)\\\n  - `STRING_OFFSET_CAR_NAME = 135` (1 + 134)\\\n  - `STRING_OFFSET_TYRE_COMPOUND = 168` (1 + 167)\\\n\\\n---\\\n\\\n## Feed C: Lua Plugin (Port 5005) — CSP Extension Feed\\\n\\\n### Source\\\n\\\nOur custom CSP (Custom Shaders Patch) Lua application, deployed to `<AC>/apps/lua/ac-telemetry-plugin/`.\\\nSends JSON datagrams via LuaSocket. We control the code — can add any CSP API field.\\\n\\\n### Status: NOT WORKING (Root Cause Identified)\\\n\\\n**Root cause**: `LAZY = FULL` in `manifest.ini` line 13.\\\nCSP's lazy loading means the Lua script never executes until the user clicks the app icon in the in-game taskbar. The user never clicked it during sessions → 0 bytes received.\\\n\\\n**Fix**: Change `LAZY = FULL` to `LAZY = NONE` in `manifest.ini`, or remove the `[CORE]` section's `LAZY` line entirely (NONE is default).\\\n\\\n### Expected Data (After Fix)\\\n\\\nJSON datagrams at configurable Hz (target 60 Hz). Based on CSP Lua API:\\\n\\\n```json\\\n{\\\n  \\\"speed_ms\\\": 42.5,\\\n  \\\"rpm\\\": 6500,\\\n  \\\"gear\\\": 4,\\\n  \\\"throttle\\\": 0.85,\\\n  \\\"brake\\\": 0.0,\\\n  \\\"clutch\\\": 0.0,\\\n  \\\"steer\\\": 0.1,\\\n  \\\"fuel\\\": 0.72,\\\n  \\\"lap_time_ms\\\": 78123,\\\n  \\\"position\\\": {\\\"x\\\": 100.5, \\\"y\\\": 2.3, \\\"z\\\": -50.7},\\\n  \\\"suspension_travel\\\": [0.05, 0.04, 0.06, 0.05],\\\n  \\\"tyre_wear\\\": [0.98, 0.97, 0.96, 0.95],\\\n  \\\"damage\\\": {\\\n    \\\"body\\\": 0.0,\\\n    \\\"suspension\\\": 0.0,\\\n    \\\"engine\\\": 0.0,\\\n    \\\"transmission\\\": 0.0,\\\n    \\\"aero\\\": 0.0\\\n  },\\\n  \\\"weather\\\": {\\\n    \\\"track_temp\\\": 28,\\\n    \\\"ambient_temp\\\": 22,\\\n    \\\"rain\\\": 0.0\\\n  },\\\n  \\\"tyre_temps\\\": [85.2, 84.8, 88.1, 87.5]\\\n}\\\n```\\\n\\\n### Lua Plugin — Unique Fields (Not in 10101)\\\n\\\n| Field | CSP API | Value |\\\n|---|---|---|\\\n| Per-wheel suspension travel | `car.wheels[i].suspensionTravel` | Detailed damper analysis |\\\n| 5-zone damage model | `car.damage.*` | Body/aero/suspension/engine/transmission |\\\n| Fuel level | `car.fuel` | Strategy coaching |\\\n| Tyre wear | CSP tyre API | Long-run degradation |\\\n| Weather/track conditions | `ac.*` weather calls | Ambient temp, track temp, rain |\\\n\\\n### Lua Plugin — Secondary Concern\\\n\\\nAfter fixing LAZY, `require(\\\"socket\\\")` may fail if LuaSocket is unavailable in CSP's sandboxed Lua environment. Diagnostic `ac.log()` checkpoints are in place to confirm this on the next AC run.\\\n\\\n---\\\n\\\n## Data Comparison Matrix\\\n\\\n| Capability | 9996 KSUDP | 10101 Telemetry Tool | 5005 Lua Plugin |\\\n|---|---|---|---|\\\n| **Status** | ⚠️ Sparse (13 pkts) | ✅ Working (17K pkts) | ❌ LAZY=FULL bug |\\\n| **Rate** | ~0.04 Hz (broken) | ~60 Hz ✅ | Configurable |\\\n| **Format** | Binary | Binary | JSON |\\\n| **Basic inputs** | ✅ | ✅ | ✅ |\\\n| **G-forces** | ⚠️ Basic 3-axis | ✅ 3-axis | ✅ 3-axis |\\\n| **Wheel slip** | ✅ Ratios + angles | ✅ Ratios + angles + nd_slip | ✅ Per-wheel |\\\n| **Tyre temps** | ❌ | ✅ Core temps | ⚠️ Can add via CSP |\\\n| **Tyre wear** | ❌ | ❌ | ✅ CSP API |\\\n| **Suspension** | ✅ Height per wheel | ✅ Ride height F/R | ✅ Per-wheel travel |\\\n| **Damage** | ❌ | ❌ | ✅ 5-zone CSP |\\\n| **Fuel** | ✅ (offset 64) | ❌ | ✅ CSP API |\\\n| **World position** | ✅ | ✅ | ⚠️ Trivial to add |\\\n| **Spline position** | ✅ | ✅ (float64!) | ⚠️ Trivial to add |\\\n| **Multi-car** | ❌ | ✅ All drivers | ❌ Player only |\\\n| **Velocity vectors** | ❌ | ✅ 3D world + local | ❌ |\\\n| **Weather** | ❌ | ❌ | ✅ CSP API |\\\n| **ERS/DRS/Turbo** | ❌ | ✅ | ❌ |\\\n| **Coaching quality** | Low (~40%) | Very High (~95%) | High (~90%) + CSP |\\\n\\\n---\\\n\\\n## Configuration Summary\\\n\\\n| Setting | Value |\\\n|---|---|\\\n| AC setting | `SETUDP_REMOTE_TELEMETRY=1` in `race.ini` or Content Manager |\\\n| KSUDP port | 9996 (hardcoded by AC) |\\\n| Telemetry Tool port | 10101 (configured in Python plugin) |\\\n| Lua Plugin port | 5005 (configured in our `config.lua`) |\\\n| Bind address | `0.0.0.0` (listen on all interfaces) |\\\n| Target address | `127.0.0.1` (localhost only) |\\\n\\\n---\\\n\\\n*Document extracted from: Telemetry Feed Analysis Session 2026-06-03 (note 395bc805650d44b6bfec423ca095ed64) and rusty-telemetry source code*\\\n*Last updated: 2026-06-06*\"]],\"start1\":0,\"start2\":0,\"length1\":0,\"length2\":15177}]"
metadata_diff: {"new":{"id":"aed9f3be040943048273a16e05a8100f","parent_id":"0e8e00b432a840628faa4df5bc2068bc","latitude":"0.00000000","longitude":"0.00000000","altitude":"0.0000","author":"","source_url":"","is_todo":0,"todo_due":0,"todo_completed":0,"source":"joplin-desktop","source_application":"net.cozic.joplin-desktop","application_data":"","order":1780732408032,"markup_language":1,"is_shared":0,"share_id":"","conflict_original_id":"","master_key_id":"","user_data":"","deleted_time":0},"deleted":[]}
encryption_cipher_text: 
encryption_applied: 0
updated_time: 2026-06-06T08:04:09.647Z
created_time: 2026-06-06T08:04:09.647Z
type_: 13