fix(server): game completion pipeline — stats recording + dict iteration safety

Three bugs prevented game stats from recording:

1. broadcast_game_state had game_over processing (log_game_end + stats)
   inside the per-player loop — if all players disconnected before the
   loop ran, stats never processed. Moved to run once before the loop.

2. room.broadcast and broadcast_game_state iterated players.items()
   without snapshotting, causing RuntimeError when concurrent player
   disconnects mutated the dict. Fixed with list().

3. stats_service.process_game_from_state passed avg_round_score to a
   CASE expression without a type hint, causing asyncpg to fail with
   "could not determine data type of parameter $6". Added ::integer
   casts.

Also wrapped per-player send_json calls in try/except so a single
disconnected player doesn't abort the broadcast to remaining players.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
adlee-was-taken
2026-04-17 20:37:08 -04:00
parent d5194f43ba
commit ccc2f3b559
3 changed files with 49 additions and 42 deletions

View File

@@ -232,7 +232,7 @@ class Room:
message: JSON-serializable message dict.
exclude: Optional player ID to skip.
"""
for player_id, player in self.players.items():
for player_id, player in list(self.players.items()):
if player_id != exclude and player.websocket and not player.is_cpu:
try:
await player.websocket.send_json(message)