ui_battlefield: a queued battleground's time waited is the time since the status plus the wire's waited ms, computed forwards so no Instant precedes the clock's origin (#628)

A queued battleground's time waited was computed backwards: `*at - Duration::from_millis(waited)`, with `waited` the server's u32 milliseconds. On Windows, std's `Instant` is a `Duration` from the performance counter's origin, near boot, so a wait longer than the machine's uptime panics there. On macOS and Linux, `Instant` is a signed timespec and the subtraction cannot panic; the issue's platform note overstated that.

The time waited is now taken forwards, as `now.saturating_duration_since(*at) + waited`. That is the same value the reference's `[slot+0x1c] = now − Δ`, read back as `now − stamp`, gives, and the old formula's value everywhere it did not panic. `a_waited_span_longer_than_any_uptime_reads_forwards` pins a 49.7-day wait reading forwards and saturating at the getter's u32. `the_slot_view_reduces_the_clocks` still gives 7 000 ms for 5 s waited plus 2 s since. Built and gated on macOS; the Windows target was not built.

Fixes #615

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Sam Sung
2026-09-29 19:17:58 +07:00
committed by GitHub
co-authored by Claude Opus 5.5
parent 61f17d46c3
commit 45825b0609
+24 -3
View File
@@ -153,11 +153,12 @@ fn slot_view(
let deadline = *at + std::time::Duration::from_millis(u64::from(delta));
view.port_expiration_ms = ms(deadline.saturating_duration_since(now));
}
// Status 1: the raw estimate, and `[slot+0x1c] = now − Δ` read back as `now − stamp`.
// Status 1: the raw estimate, and `[slot+0x1c] = now − Δ` read back as `now − stamp`, which is
// the time since the status plus Δ, taken forwards so no `Instant` precedes the clock's origin.
if let Some((estimate, waited)) = status.queued {
view.estimated_wait_ms = estimate;
let stamp = *at - std::time::Duration::from_millis(u64::from(waited));
view.time_waited_ms = ms(now.saturating_duration_since(stamp));
let since = now.saturating_duration_since(*at);
view.time_waited_ms = ms(since + std::time::Duration::from_millis(u64::from(waited)));
}
view
}
@@ -389,6 +390,26 @@ mod tests {
}
}
#[test]
fn a_waited_span_longer_than_any_uptime_reads_forwards() {
let at = Instant::now();
let mut queued = status(0, 489, 1);
queued.queued = Some((0, u32::MAX));
let v = slot_view(Some(&(queued.clone(), at)), None, at);
assert_eq!(
v.time_waited_ms,
u32::MAX,
"49.7 days waited, no stamp needed"
);
let later = at + std::time::Duration::from_secs(9);
let v = slot_view(Some(&(queued, at)), None, later);
assert_eq!(
v.time_waited_ms,
u32::MAX,
"saturates as the getter's u32 does"
);
}
#[test]
fn the_slot_view_reduces_the_clocks() {
let at = Instant::now();