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>