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:
@@ -164,6 +164,24 @@ class GameLogger:
|
||||
# Not in async context - skip (simulations don't need this)
|
||||
pass
|
||||
|
||||
async def log_game_abandoned_async(self, game_id: str) -> None:
|
||||
"""Mark game as abandoned (room emptied before GAME_OVER)."""
|
||||
try:
|
||||
await self.event_store.update_game_abandoned(game_id)
|
||||
log.debug(f"Logged game abandoned: {game_id}")
|
||||
except Exception as e:
|
||||
log.error(f"Failed to log game abandoned: {e}")
|
||||
|
||||
def log_game_abandoned(self, game_id: str) -> None:
|
||||
"""Sync wrapper: fires async task in async context, no-op otherwise."""
|
||||
if not game_id:
|
||||
return
|
||||
try:
|
||||
asyncio.get_running_loop()
|
||||
asyncio.create_task(self.log_game_abandoned_async(game_id))
|
||||
except RuntimeError:
|
||||
pass
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Move Logging
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user