fix(server): mark games abandoned on room teardown + staging leaderboard

When handle_player_leave emptied a room or handle_end_game was invoked,
the room was removed from memory without touching games_v2. Periodic
cleanup only scans in-memory rooms, so those rows were stranded as
status='active' forever — staging had 42 orphans accumulated over 5h.

- event_store.update_game_abandoned: guarded UPDATE (status='active' only)
- GameLogger.log_game_abandoned{,_async}: fire-and-forget wrapper
- handle_end_game + handle_player_leave: flip status before remove_room
- LEADERBOARD_INCLUDE_TEST_DEFAULT: env override so staging can show
  soak-harness accounts by default; prod keeps them hidden

Verified on staging: 42 orphans swept on restart, soak accounts now
visible on /api/stats/leaderboard (rank 1-4).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
adlee-was-taken
2026-04-18 00:37:49 -04:00
parent 70498b1c33
commit d5f8eef6b3
7 changed files with 230 additions and 3 deletions

View File

@@ -508,6 +508,14 @@ async def handle_end_game(data: dict, ctx: ConnectionContext, *, room_manager, c
pass
ctx.current_room.cpu_turn_task = None
# Mark the DB row abandoned before we lose the room (and its game_log_id)
# from memory — otherwise games_v2 would be stranded as 'active' forever.
if ctx.current_room.game_log_id:
game_logger = get_logger()
if game_logger:
game_logger.log_game_abandoned(ctx.current_room.game_log_id)
ctx.current_room.game_log_id = None
await ctx.current_room.broadcast({
"type": "game_ended",
"reason": "Host ended the game",