Feat/player tracker - #6
Conversation
Replaces the server-side penalty timer PRs (closed). Client's overlay workflow starts its own timer using wall-clock arithmetic: elapsed_ms = Date.now() - box_entered_at_ms remaining_s = Math.max(0, 30 - elapsed_ms / 1000) - SkaterPosition.box_entered_at_ms: Optional[int] — Unix epoch ms when the skater entered the box; None when not in box. - ScoreboardClient._update_box_entry_times() sets the timestamp on the False->True transition and clears it on True->False. Re-sending PenaltyBox=True does not reset the timestamp. - _box_entry_times is cleared on each reconnect so stale timestamps do not carry over. - 4 new tests: None when not in box, timestamp set on entry within expected window, cleared on exit, not reset on repeated True.
There was a problem hiding this comment.
Pull request overview
Adds penalty-box timing metadata to the live state model so downstream consumers (e.g., overlays) can display when a skater entered the box and how many seconds remain.
Changes:
- Add
box_entered_at_msandbox_time_remaining_stoSkaterPosition. - Track penalty-box entry times in
ScoreboardClientand compute remaining time on demand. - Add client tests covering entry timestamp behavior and remaining-time countdown/expiry.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
client.py |
Tracks per-skater box entry times and derives box_entered_at_ms + box_time_remaining_s in get_live_state(). |
models.py |
Extends SkaterPosition schema with new penalty-box timing fields. |
tests/test_client.py |
Adds async tests validating timestamp set/clear semantics and remaining-time behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| def _update_box_entry_times(self) -> None: | ||
| """Record wall-clock entry time when a skater transitions into the box. | ||
|
|
||
| Called after every state update. Entry time is set once on the | ||
| false→True transition and cleared on True→False. | ||
| """ | ||
| for team_n in (1, 2): | ||
| for crg_pos in POSITION_MAP.values(): | ||
| key = f"{team_n}.{crg_pos}" | ||
| in_box = ( | ||
| self._get(f"Team({team_n}).Position({crg_pos}).PenaltyBox", bool) | ||
| or False | ||
| ) | ||
| if in_box and key not in self._box_entry_times: | ||
| self._box_entry_times[key] = int(time.time() * 1000) | ||
| elif not in_box: | ||
| self._box_entry_times.pop(key, None) |
There was a problem hiding this comment.
_update_box_entry_times() sets an entry timestamp any time it sees PenaltyBox=True and the key isn't in _box_entry_times. On initial connect (after _box_entry_times.clear()), this will assign a fresh “entered_at” for skaters who were already in the box before the client connected, even though no false→true transition was observed. If consumers interpret box_entered_at_ms/box_time_remaining_s as real penalty timing, this can be misleading. Consider representing “already in box on snapshot/unknown entry time” as box_entered_at_ms=None (and remaining None) until an observed exit+re-entry, or track previous in_box values separately to only timestamp true transitions after the first snapshot.
…, Field descriptions, mock time in test
No description provided.