Gap to leader and interval to car ahead in the feed #16

Closed
opened 2026-10-19 13:05:07 +00:00 by mintopia · 4 comments
Owner

What to build

Time gaps for an F1-style timing tower: per racer, gap_ms (behind the leader) and int_ms (behind the car directly ahead), in single-player, split-screen and network races.

Where the data is (shipped Blur.exe, needs runtime confirmation)

The racer object (vtable 0xe72ac8) holds track-progress floats next to the position byte (+0xefc; getter 0x436170 returns racer+0xefc), all zeroed at race init (0x450d05):

  • +0xf08 float: looks like cumulative race distance. 0xc26c24 loads +0xf08 of two racers, scales both and compares the difference with a threshold; 0x463b4d finds the racer whose position byte is 2 and subtracts its +0xf08 from another racer's (lead distance).
  • +0xf00 float: looks like progress within the current lap. 0x468e00: on the last lap (lap == laps) compares +0xf00 against a global threshold (0x112a674), i.e. "near the end of the final lap"; 0x44dc28 passes +0xf00 of two racers to a helper at 0x45ee90.

Units (metres vs. normalised) are unknown.

Approach

  1. Runtime check first: log +0xf00, +0xf08, lap and position for all racers over a few laps (SP and 2-instance LAN). Confirm +0xf08 grows monotonically with distance driven across laps (and what happens on a lap decrease / reset), find its units and whether it is updated for remote racers. If neither field is a usable progress measure, stop and comment the findings on this issue instead of implementing.
  2. Host-side gap, F1 style: keep the leader's history of (+0xf08 distance, race clock in ms from the lap-times ticket), sampled at the scan rate (5 Hz). A racer's gap_ms = now - the time the leader was at that racer's current distance (linear interpolation between samples). int_ms = same against the car one position ahead (keep a short history per racer, or difference of gap_ms values). Leader has no gap. Lapped racers: gap as time (can exceed a lap); optionally add laps_down.
  3. Also emit raw dist (rounded +0xf08) in standings so clients can do their own maths.

Proposed shape

Add to each standings entry: gap_ms, int_ms (integers, absent for the leader and before the leader has history), dist.

Acceptance criteria

  • Runtime check result recorded in ADR 0013 (field meaning, units, remote-racer behaviour)
  • standings entries carry gap_ms, int_ms, dist
  • Unit tests in tests/race_feed_test.cpp: interpolated gap from a synthetic leader history, interval to car ahead, leader omitted, history reset between races
  • On recomp, gaps in a 2-instance LAN race agree between the two machines within ~0.3 s
  • docs/state-websocket.md updated
  • Verified on BLUR_ENGINE=recomp

Blocked by

## What to build Time gaps for an F1-style timing tower: per racer, `gap_ms` (behind the leader) and `int_ms` (behind the car directly ahead), in single-player, split-screen and network races. ## Where the data is (shipped `Blur.exe`, needs runtime confirmation) The racer object (vtable `0xe72ac8`) holds track-progress floats next to the position byte (`+0xefc`; getter `0x436170` returns `racer+0xefc`), all zeroed at race init (`0x450d05`): - `+0xf08` float: looks like cumulative race distance. `0xc26c24` loads `+0xf08` of two racers, scales both and compares the difference with a threshold; `0x463b4d` finds the racer whose position byte is 2 and subtracts its `+0xf08` from another racer's (lead distance). - `+0xf00` float: looks like progress within the current lap. `0x468e00`: on the last lap (`lap == laps`) compares `+0xf00` against a global threshold (`0x112a674`), i.e. "near the end of the final lap"; `0x44dc28` passes `+0xf00` of two racers to a helper at `0x45ee90`. Units (metres vs. normalised) are unknown. ## Approach 1. Runtime check first: log `+0xf00`, `+0xf08`, lap and position for all racers over a few laps (SP and 2-instance LAN). Confirm `+0xf08` grows monotonically with distance driven across laps (and what happens on a lap decrease / reset), find its units and whether it is updated for remote racers. If neither field is a usable progress measure, stop and comment the findings on this issue instead of implementing. 2. Host-side gap, F1 style: keep the leader's history of (`+0xf08` distance, race clock in ms from the lap-times ticket), sampled at the scan rate (5 Hz). A racer's `gap_ms` = now - the time the leader was at that racer's current distance (linear interpolation between samples). `int_ms` = same against the car one position ahead (keep a short history per racer, or difference of `gap_ms` values). Leader has no gap. Lapped racers: gap as time (can exceed a lap); optionally add `laps_down`. 3. Also emit raw `dist` (rounded `+0xf08`) in `standings` so clients can do their own maths. ## Proposed shape Add to each `standings` entry: `gap_ms`, `int_ms` (integers, absent for the leader and before the leader has history), `dist`. ## Acceptance criteria - [ ] Runtime check result recorded in ADR 0013 (field meaning, units, remote-racer behaviour) - [ ] `standings` entries carry `gap_ms`, `int_ms`, `dist` - [ ] Unit tests in `tests/race_feed_test.cpp`: interpolated gap from a synthetic leader history, interval to car ahead, leader omitted, history reset between races - [ ] On recomp, gaps in a 2-instance LAN race agree between the two machines within ~0.3 s - [ ] `docs/state-websocket.md` updated - [ ] Verified on `BLUR_ENGINE=recomp` ## Blocked by - #15
Author
Owner

Done on harmonic/task-14 (95812a0). standings entries now carry dist (race progress in laps, 3 decimals), and gap_ms/int_ms for non-leaders. Runtime check (recomp, SP 20 racers + 2-instance LAN): +0xf08 = lap byte + +0xf00 (fraction of lap), normalised not metres, continuous across the line, keeps rising after finish; grid starts ~0.8; stale 1.8->0.8 reset at race init (history clears on a >0.25 lap drop). Remote racers update; cross-machine dist agrees median 0.02 s, p95 0.07 s. Gaps use the front-runner (max dist) history with linear interpolation; int_ms = gap difference to car ahead. Unit tests added (interpolated gap, interval, leader omitted, reset between races); race_feed/game_state/state_ws pass. ADR 0013 and docs/state-websocket.md updated. Not verified: LAN race where both cars drive from the start (second car idled in the captured run, so gap-vs-gap comparison on both sides not done); menu driving of 2 instances was flaky.

Done on harmonic/task-14 (95812a0). standings entries now carry dist (race progress in laps, 3 decimals), and gap_ms/int_ms for non-leaders. Runtime check (recomp, SP 20 racers + 2-instance LAN): +0xf08 = lap byte + +0xf00 (fraction of lap), normalised not metres, continuous across the line, keeps rising after finish; grid starts ~0.8; stale 1.8->0.8 reset at race init (history clears on a >0.25 lap drop). Remote racers update; cross-machine dist agrees median 0.02 s, p95 0.07 s. Gaps use the front-runner (max dist) history with linear interpolation; int_ms = gap difference to car ahead. Unit tests added (interpolated gap, interval, leader omitted, reset between races); race_feed/game_state/state_ws pass. ADR 0013 and docs/state-websocket.md updated. Not verified: LAN race where both cars drive from the start (second car idled in the captured run, so gap-vs-gap comparison on both sides not done); menu driving of 2 instances was flaky.
Author
Owner

Done: standings now carry dist, gap_ms, int_ms (gaps interpolated from front-runner progress history, cleared between races). Unit tests, ADR 0013, ws docs updated. Last fix: removed inaccurate reset comment in race_feed.cpp.

Done: standings now carry dist, gap_ms, int_ms (gaps interpolated from front-runner progress history, cleared between races). Unit tests, ADR 0013, ws docs updated. Last fix: removed inaccurate reset comment in race_feed.cpp.
mintopia removed their assignment 2026-10-19 15:35:04 +00:00
Author
Owner

Follow-up on harmonic/task-14 (48f2f4c, 987d155). This closes the unverified LAN criterion and fixes the defects the LAN run exposed.

LAN verification (recomp, 2 instances, both cars driving 3 laps, lead swapped several times): the two machines' gap_ms for the same racer at matched times agree to a median of 0.010 s and a p95 of 0.055 s (799 samples). 5 single samples were 0.3–0.9 s apart, all at wall collisions where the leader stalled near the trailer's distance. Frozen finish gaps: 3.31 s and 3.26 s, against a 3.38 s difference in race_ms. An earlier run gave 0.011 s / 0.087 s over 857 samples. Results are in ADR 0013.

Why the previous run had one-sided gaps: the leader history only started once the local racer crossed the line. Also, the unfocused instance lost throttle because windowDidResignKey clears key_state. That is a test-harness issue; I drove throttle via the virtual pad trigger instead.

Fixes:

  • Leader history starts once any racer has crossed the start line, whatever the local player is doing.
  • Each distance keeps the time the leader first reached it. The game holds a finisher's +0xf08 at exactly the lap count for about 0.4 s.
  • gap_ms freezes at the finish, as on an F1 timing tower. Before this, it drifted because +0xf08 keeps rising after the line.
  • Gaps use a race clock that only advances while lap timers run, so pausing does not grow gaps. The spec asked for the race clock.
  • A racer with no gap no longer hides gap_ms for the cars behind it. int_ms still needs the car ahead to have a gap.
  • Unit tests added for each of these. All 22 ctest tests pass. docs/state-websocket.md and ADR 0013 are updated.

Not runtime-tested: split-screen (same code path) and an SP pause (covered by a unit test). laps_down (optional) is not done.

Follow-up on harmonic/task-14 (48f2f4c, 987d155). This closes the unverified LAN criterion and fixes the defects the LAN run exposed. **LAN verification (recomp, 2 instances, both cars driving 3 laps, lead swapped several times):** the two machines' `gap_ms` for the same racer at matched times agree to a median of 0.010 s and a p95 of 0.055 s (799 samples). 5 single samples were 0.3–0.9 s apart, all at wall collisions where the leader stalled near the trailer's distance. Frozen finish gaps: 3.31 s and 3.26 s, against a 3.38 s difference in race_ms. An earlier run gave 0.011 s / 0.087 s over 857 samples. Results are in ADR 0013. **Why the previous run had one-sided gaps:** the leader history only started once the *local* racer crossed the line. Also, the unfocused instance lost throttle because `windowDidResignKey` clears `key_state`. That is a test-harness issue; I drove throttle via the virtual pad trigger instead. **Fixes:** - Leader history starts once any racer has crossed the start line, whatever the local player is doing. - Each distance keeps the time the leader *first* reached it. The game holds a finisher's `+0xf08` at exactly the lap count for about 0.4 s. - `gap_ms` freezes at the finish, as on an F1 timing tower. Before this, it drifted because `+0xf08` keeps rising after the line. - Gaps use a race clock that only advances while lap timers run, so pausing does not grow gaps. The spec asked for the race clock. - A racer with no gap no longer hides `gap_ms` for the cars behind it. `int_ms` still needs the car ahead to have a gap. - Unit tests added for each of these. All 22 ctest tests pass. `docs/state-websocket.md` and ADR 0013 are updated. Not runtime-tested: split-screen (same code path) and an SP pause (covered by a unit test). `laps_down` (optional) is not done.
Author
Owner

Completed and merged by Harmonic (task 14).

Completed and merged by Harmonic (task 14).
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.

Reference
mintopia/blur#16
No description provided.