Live race telemetry in the game-state feed #5

Closed
opened 2026-10-17 02:09:14 +00:00 by mintopia · 4 comments
Owner

What to build

During a race, the game-state feed (and so websocket clients) carries live race data: lap/total laps, the player's position, race time, per-racer standings, and race finish with final results. External apps can follow a race as it happens.

Acceptance criteria

  • A websocket client sees lap, position and race time update during a race
  • Per-racer standings are present and match the in-game HUD
  • Race finish produces a final-results message
  • Works in single-player and in multiplayer races
  • No measurable frame-time cost with no client connected
  • Verified on BLUR_ENGINE=recomp

Blocked by

## What to build During a race, the game-state feed (and so websocket clients) carries live race data: lap/total laps, the player's position, race time, per-racer standings, and race finish with final results. External apps can follow a race as it happens. ## Acceptance criteria - [ ] A websocket client sees lap, position and race time update during a race - [ ] Per-racer standings are present and match the in-game HUD - [ ] Race finish produces a final-results message - [ ] Works in single-player and in multiplayer races - [ ] No measurable frame-time cost with no client connected - [ ] Verified on `BLUR_ENGINE=recomp` ## Blocked by - #2 - #4
Author
Owner

Harmonic Task 6: blocked, no code changes made. Findings from probing a live career race on recomp (probe scripts only in /tmp):

  • Lua exposes no live SP lap / race time / position / standings. HUD text is native-bound: Hud.lua only calls Amax.ChangeText(id, "HUD_0_LAP" / "HUD_0_POS_NUM" / "HUD_0_POS_NUM_OF" ...); values are filled by the engine.
  • Static only: Amax.GetLevelData() -> Laps, NumberOfCars, RaceMode, Route. Amax.GetNumVehicles()=20, Amax.GetPlayerVehicleIndex(0)=0.
  • Multiplayer.GetVehiclePosition/GetVehicleIndexByPosition/GetRaceResults/VehiclesFinished, NetRace.RaceInProgress/HasRaceFinished exist but are inert in an SP race (position 0, empty standings). They likely work in network races (untested; needs 2 instances).
  • UIEnums.GameMessage has no lap/position/finish messages (only RaceStartsIn, Sp* events).
  • Native route: strings HUD_CURRENT_LAP (0xeea6b0, xref 0xc394f7), CMN_HUD_LAP_FORMAT, BEST_LAP in Blur.exe; xref site is not in a Ghidra-defined function. Needs reversing the race manager (lap, time, per-racer position/standings), then a recomp hook or guest-memory reader (gen.sh regen), and MP verification with host+join.

Needs a human decision/direction: accept native RE of the race manager (and a hook/reader in the host), or narrow scope (e.g. MP-only via the Lua getters above, total laps from GetLevelData).

Harmonic Task 6: blocked, no code changes made. Findings from probing a live career race on recomp (probe scripts only in /tmp): - Lua exposes **no live SP lap / race time / position / standings**. HUD text is native-bound: Hud.lua only calls `Amax.ChangeText(id, "HUD_0_LAP" / "HUD_0_POS_NUM" / "HUD_0_POS_NUM_OF" ...)`; values are filled by the engine. - Static only: `Amax.GetLevelData()` -> `Laps`, `NumberOfCars`, `RaceMode`, `Route`. `Amax.GetNumVehicles()`=20, `Amax.GetPlayerVehicleIndex(0)`=0. - `Multiplayer.GetVehiclePosition/GetVehicleIndexByPosition/GetRaceResults/VehiclesFinished`, `NetRace.RaceInProgress/HasRaceFinished` exist but are inert in an SP race (position 0, empty standings). They likely work in network races (untested; needs 2 instances). - `UIEnums.GameMessage` has no lap/position/finish messages (only RaceStartsIn, Sp* events). - Native route: strings `HUD_CURRENT_LAP` (0xeea6b0, xref 0xc394f7), `CMN_HUD_LAP_FORMAT`, `BEST_LAP` in Blur.exe; xref site is not in a Ghidra-defined function. Needs reversing the race manager (lap, time, per-racer position/standings), then a recomp hook or guest-memory reader (`gen.sh` regen), and MP verification with host+join. Needs a human decision/direction: accept native RE of the race manager (and a hook/reader in the host), or narrow scope (e.g. MP-only via the Lua getters above, total laps from GetLevelData).
mintopia removed their assignment 2026-10-17 20:16:41 +00:00
Author
Owner

Harmonic Task 6: implemented (commits 356f58a + follow-up on harmonic/task-6).

Feed keys (set while a race runs; need a connected BLUR_STATE_WS client): lap, laps, position, racers, race_time (s), standings (JSON, best first), speeds (JSON mph per racer, ~5/s), race_result (on finish; final:true once all finished, completed:false if the race ends first), plus racer, car, mods. In network races each human's standings entry has name, car (icon name) and mods; local:true marks this machine's player. Docs: docs/state-websocket.md, ADR 0013.

How: host thread reads racer objects (position +0xefc, the HUD field; lap, lap timer, speed) and the network player list from guest memory (src/host/race_scan.cpp); pure feed logic in race_feed.cpp (unit test race_feed_test). BLUR_PLAYER_NAME sets the player name (default blur).

Verified on recomp: scripted full 3-lap SP race; two game instances on one Mac (LAN host + join, scripted input, distinct names alpha/bravo): both clients saw correct lap/position/standings/speeds, names/cars/mods resolved per client (local racer index differs per client and matches each HUD: host 1/2, client 2/2), race_result on host finish. fps median 120 (vsync cap), min 114-116, with the feed enabled and no client vs disabled. ctest 15/15.

Limits: names/cars/mods only for humans in network races (AI and SP: local car only, no mods in SP); both test clients shared one save so cars/mods were identical (names differed); 3+ instances on one Mac can't be tested (docs/multiplayer-lan.md); a client connecting mid-race gets race_time from the current lap only; offsets are specific to the shipped Blur.exe.

Harmonic Task 6: implemented (commits 356f58a + follow-up on harmonic/task-6). **Feed keys** (set while a race runs; need a connected `BLUR_STATE_WS` client): `lap`, `laps`, `position`, `racers`, `race_time` (s), `standings` (JSON, best first), `speeds` (JSON mph per racer, ~5/s), `race_result` (on finish; `final:true` once all finished, `completed:false` if the race ends first), plus `racer`, `car`, `mods`. In network races each human's `standings` entry has `name`, `car` (icon name) and `mods`; `local:true` marks this machine's player. Docs: `docs/state-websocket.md`, ADR 0013. **How**: host thread reads racer objects (position `+0xefc`, the HUD field; lap, lap timer, speed) and the network player list from guest memory (`src/host/race_scan.cpp`); pure feed logic in `race_feed.cpp` (unit test `race_feed_test`). `BLUR_PLAYER_NAME` sets the player name (default `blur`). **Verified on recomp**: scripted full 3-lap SP race; two game instances on one Mac (LAN host + join, scripted input, distinct names alpha/bravo): both clients saw correct lap/position/standings/speeds, names/cars/mods resolved per client (local racer index differs per client and matches each HUD: host 1/2, client 2/2), race_result on host finish. fps median 120 (vsync cap), min 114-116, with the feed enabled and no client vs disabled. ctest 15/15. **Limits**: names/cars/mods only for humans in network races (AI and SP: local car only, no mods in SP); both test clients shared one save so cars/mods were identical (names differed); 3+ instances on one Mac can't be tested (docs/multiplayer-lan.md); a client connecting mid-race gets `race_time` from the current lap only; offsets are specific to the shipped Blur.exe.
Author
Owner

Comment-policy fixes: moved update() param comment to update(), replaced trailing comment with named condition (racers_reset_at_teardown), removed stale key list from game_state.h header. No behaviour change.

Comment-policy fixes: moved update() param comment to update(), replaced trailing comment with named condition (racers_reset_at_teardown), removed stale key list from game_state.h header. No behaviour change.
Author
Owner

Completed and merged by Harmonic (task 6).

Completed and merged by Harmonic (task 6).
mintopia referenced this issue from a commit 2026-10-19 09:56:15 +00:00
Sign in to join this conversation.
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Depends on
Reference
mintopia/blur#5
No description provided.