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>