fix(server): winner_id on completed games + stats idempotency latch
Two issues in the GAME_OVER broadcast path: 1. log_game_end called update_game_completed with winner_id=None default, so games_v2.winner_id was NULL on all 17 completed staging rows. The denormalized column existed but carried no information. Compute winner (lowest total; None on tie) in broadcast_game_state and thread through. 2. _process_stats_safe had no idempotency guard. log_game_end was already self-guarding via game_log_id=None after first fire, but nothing stopped repeated GAME_OVER broadcasts from re-firing stats and double-counting games_played/games_won. Add Room.stats_processed latch; reset it in handle_start_game so a re-used room still records. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -73,6 +73,10 @@ class Room:
|
||||
game_lock: asyncio.Lock = field(default_factory=asyncio.Lock)
|
||||
cpu_turn_task: Optional[asyncio.Task] = None
|
||||
last_activity: float = field(default_factory=time.time)
|
||||
# Latched True after _process_stats_safe fires for this game; prevents
|
||||
# double-counting if broadcast_game_state is invoked multiple times
|
||||
# with phase=GAME_OVER (double-click on next-round, reconnect flush).
|
||||
stats_processed: bool = False
|
||||
|
||||
def touch(self) -> None:
|
||||
"""Update last_activity timestamp to mark room as active."""
|
||||
|
||||
Reference in New Issue
Block a user