From b45fb70d80781e01aaa124641e36140d69566443 Mon Sep 17 00:00:00 2001 From: a1ly404 Date: Fri, 3 Apr 2026 13:46:15 -0700 Subject: [PATCH 1/2] Detect intermission clock and return Intermission state When the intermission clock is running, override the game_state field to return "Intermission" instead of showing the raw CRG state. This ensures the display correctly shows halftime status. --- client.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/client.py b/client.py index 68ebdad..1f644c7 100644 --- a/client.py +++ b/client.py @@ -252,6 +252,10 @@ def get_live_state(self) -> LiveState: for field, (suffix, typ) in GAME_FIELD_MAP.items() } clock_timeout_running = self._get("Clock(Timeout).Running", bool) + # Check if intermission clock is running — if so, override game_state to show intermission + intermission_clock_running = self._get("Clock(Intermission).Running", bool) + if intermission_clock_running: + game_fields["game_state"] = "Intermission" # Pop timeout_clock_ms so we can conditionally suppress it below # without it conflicting with the **game_fields unpack. raw_timeout_clock_ms = game_fields.pop("timeout_clock_ms", None) From 1d0c5cecd06d2e99a79cf9c1117354f08d7bfcbd Mon Sep 17 00:00:00 2001 From: a1ly404 Date: Fri, 3 Apr 2026 14:11:32 -0700 Subject: [PATCH 2/2] Add intermission regression coverage and docs --- README.md | 4 ++++ models.py | 10 +++++++++- tests/test_client.py | 25 +++++++++++++++++++++++++ 3 files changed, 38 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 635b909..5542de1 100644 --- a/README.md +++ b/README.md @@ -241,6 +241,10 @@ Clean, mapped live game state. Poll this at whatever rate suits your overlay (20 > `null` means no update has been received yet (proxy just connected). If this grows above a few > seconds while `connected` is `true`, the scoreboard may be frozen. +> **`game_state`:** Usually mirrors CRG's raw `State` field, but may be normalized for display use. +> For example, if `Clock(Intermission).Running` is `true`, `/live` returns `"Intermission"` +> even if the raw scoreboard `State` still says `"Running"`. + > **`timeout_type`:** Normalized timeout/review state derived from `game_state`. > Values: `team_timeout`, `official_timeout`, `official_review`, `timeout`, or `null`. > It is forced to `null` when `jam_running` is `true` (play resumed). diff --git a/models.py b/models.py index 1ea441b..2538089 100644 --- a/models.py +++ b/models.py @@ -64,7 +64,15 @@ class LiveState(BaseModel): period_clock: Optional[str] = None jam_running: Optional[bool] = None in_jam: Optional[bool] = None - game_state: Optional[str] = None + game_state: Optional[str] = Field( + default=None, + description=( + "Display-oriented game state. Usually mirrors CRG's State field, " + "but may be synthesized from clock activity for clearer downstream use. " + 'For example, when Clock(Intermission).Running is true this is returned as "Intermission" ' + 'even if the raw CRG State still reads "Running".' + ), + ) timeout_type: Optional[str] = Field( default=None, description=( diff --git a/tests/test_client.py b/tests/test_client.py index d690a0f..6057278 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -78,6 +78,31 @@ async def test_get_live_state_maps_clocks(mock_server): await asyncio.sleep(0.05) +@pytest.mark.asyncio +async def test_get_live_state_intermission_clock_overrides_game_state(mock_server): + client, task = await _connected_client(mock_server) + try: + await mock_server.push_update({ + "ScoreBoard.CurrentGame.State": "Running", + "ScoreBoard.CurrentGame.Clock(Intermission).Running": True, + }) + await asyncio.sleep(0.1) + + state = client.get_live_state() + assert state.game_state == "Intermission" + + await mock_server.push_update({ + "ScoreBoard.CurrentGame.Clock(Intermission).Running": False, + }) + await asyncio.sleep(0.1) + + state = client.get_live_state() + assert state.game_state == "Running" + finally: + client.stop() + await asyncio.sleep(0.05) + + @pytest.mark.asyncio async def test_get_live_state_maps_jammer_info(mock_server): client, task = await _connected_client(mock_server)