From 44c628a3bacaf560977f004ee0994291ce1b12e8 Mon Sep 17 00:00:00 2001 From: Abdulwaarith Date: Thu, 13 Aug 2026 19:59:02 +0300 Subject: [PATCH] fix(chess): flag flag-fall on a running clock in PlayerClock::time_out() time_out() checked the raw remaining_time field, which is only decremented on stop(). While a clock is_running, elapsed time is not yet reflected there, so a player who sits on a running clock past zero on their own turn was never reported as flagged. Route time_out() through get_real_time_remaining(), which already accounts for the running clock, so on-the-move flag-fall is detected. Adds unit tests for the stopped, running-past-zero, and running-with-time-left cases. Closes #941 --- backend/modules/chess/src/time_control.rs | 37 ++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/backend/modules/chess/src/time_control.rs b/backend/modules/chess/src/time_control.rs index 0b5feff4..5099547d 100644 --- a/backend/modules/chess/src/time_control.rs +++ b/backend/modules/chess/src/time_control.rs @@ -68,6 +68,41 @@ impl PlayerClock { } pub fn time_out(&self) -> bool { - self.remaining_time.is_zero() + self.get_real_time_remaining().is_zero() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn stopped_clock_reports_timeout_when_remaining_is_zero() { + let mut clock = PlayerClock::new(Duration::from_secs(0)); + assert!(clock.time_out()); + + clock.set_remaining_time(Duration::from_secs(10)); + assert!(!clock.time_out()); + } + + #[test] + fn running_clock_flags_when_it_runs_past_zero_on_the_mover() { + // Player on the move with almost no time left, clock running. + let mut clock = PlayerClock::new(Duration::from_millis(1)); + clock.start(); + // The mover sits on the running clock past zero without moving. + std::thread::sleep(Duration::from_millis(5)); + + // remaining_time is untouched until stop(), so the raw field is still + // non-zero; flag-fall must be detected via the real remaining time. + assert!(!clock.remaining_time.is_zero()); + assert!(clock.time_out()); + } + + #[test] + fn running_clock_with_time_left_is_not_flagged() { + let mut clock = PlayerClock::new(Duration::from_secs(60)); + clock.start(); + assert!(!clock.time_out()); } }