Per-racer lap times and a sub-second race clock in the feed #15
Labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Reference
mintopia/blur#15
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What to build
Per-racer lap timing and a sub-second race clock in the game-state feed, for every racer (local, AI, remote network humans), in single-player, split-screen and network races. Today the feed only has whole-second
race_timefor the local racer; the overlay guesses lap times from lap-counter changes (~1 s accuracy).Where the data is (shipped
Blur.exe, sha256dba0264b...)The racer object (vtable
0xe72ac8, found bysrc/host/race_scan.cpp) has a lap block at+0xf44; getter0x4361d0returnsracer+0xf44:+0xf44u8 lap (already read)+0xf48float, current-lap timer in seconds, restarts each lap (already read asRacer::lap_time, but only used for the local racer'srace_time)+0xf4cfloat, best lap (strong static evidence, verify at runtime): initialised to -1.0 with+0xf48at0x450ce5(constant0xe646a4=0xbf800000); only read when lap > 1 (0xc24607);FUN_00c0d850inserts+0xf48into a sorted lap-record table only when it is lower than+0xf4c;0xc25015compares the two and tests+0xf4c == -1.Every racer has this object and the scan already reads all of them, so no new discovery is needed for AI/remote racers.
Proposed shape
Add to each
standingsentry (only when known; omit, do not emit -1):lap_ms: current-lap elapsed, integer milliseconds (+0xf48)last_ms: last completed lap, ms. Computed host-side per racer when its lap byte goes up:prev_sample_timer + wall_dt_between_samples - new_timer(the timer restarts at the line, so this recovers the crossing to ~frame accuracy rather than the 200 ms sample period). Ignore lap decreases (see the lap-decrease ticket).best_ms:+0xf4cif runtime check confirms it is the best lap; otherwise min oflast_mshost-side.race_ms: that racer's elapsed race time (sum of completed laps +lap_ms).Add a top-level key
race_ms(local racer's elapsed race time, integer ms), published alongsiderace_time(keeprace_timein whole seconds for compatibility). Put timing instandings(≈5 Hz, same cadence asspeeds) rather than a new key, so one message carries consistent position + timing.Extend
race::Racerwithbest(float,+0xf4c); keep per-racer timing state (prev lap, prev timer, cumulative, last/best) inrace::Feed, keyed by racer index, reset with the race.Steps
tools/modes/plans,work/ws-capture), log+0xf48/+0xf4cfor all racers around lap crossings. Confirm+0xf4c= best lap (and whether it is maintained for remote racers). If it is something else (e.g. last lap), use it as that and compute the other host-side; note the result in ADR 0013.src/host/race_scan.cpp/race_feed.{h,cpp}.docs/state-websocket.md(standings fields,race_ms, units, "absent until known") and ADR 0013's field list.Acceptance criteria
standingsentries carrylap_ms,race_ms, andlast_ms/best_msonce a lap is complete, for all racersrace_ms(integer ms) published;race_timeunchangedtests/race_feed_test.cpp: last lap derived across a lap change, best is the minimum, values absent before the first completed lap, state reset between raceslast_ms/best_msmatch the game's results/HUD lap times within 50 msBLUR_ENGINE=recompBlocked by
Implemented in
f47ed58:standingsentries getlap_ms,race_ms,last_ms/best_ms(absent until a lap completes) for all racers; top-levelrace_msadded,race_timeunchanged. Lap times derived host-side (prev_timer + wall_dt - new_timer), best = host-side min,+0xf4cNOT read (unverified). Unit tests intests/race_feed_test.cpppass (standalone build). Docs + ADR 0013 updated.Not done (needs the game running, not possible unattended): runtime check of
+0xf4c, recomp SP check vs HUD within 50 ms, 2-instance LAN check for remote racers,BLUR_ENGINE=recompverification.Runtime verification done on
BLUR_ENGINE=recomp(worktree build, temp per-racer logging of +0xf48/+0xf4c, removed before commit).Racer::bestand used forbest_ms;last_msequals it exactly on a new-best lap, otherwise it is the host-side estimate (clamped >= best).last_msmean error 18 ms, max 67 ms for the pure host-side estimate (before using the game best and before stamping the sample time at read time); with the game best the new-best laps are exact.+0xf4cis maintained for the remote racer. 4 new-best laps per instance,last_mserror 0 ms; both instances agree on each racer best within ~20 ms (alpha 87.284 on both; bravo 87.335 vs 87.318).tests/race_feed_test.cpp) pass incl. the game-best path. Docs + ADR 0013 updated with the confirmed layout.Caveat: non-best laps
last_msis an estimate (no ground truth available from memory); the HUD screenshot comparison was not done, game-internal best values were used as truth. Commit: see branch head.Completed and merged by Harmonic (task 15).