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>
Leaderboard and rank queries take an optional include_test param
(default false). Real users never see soak-harness traffic unless
they explicitly opt in via ?include_test=true.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replaces the previous MIT license with GPL-3.0-or-later. Adds the full
GPL-3.0 license text at LICENSE, updates pyproject.toml metadata and
classifier, updates the README, and adds SPDX-License-Identifier headers
to all first-party server Python and client JavaScript sources.
Third-party anime.min.js is left untouched.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Enforce invite codes on registration (INVITE_ONLY=true by default)
- Bootstrap admin account for first-time setup
- Require authentication for WebSocket connections and room creation
- Add Glicko-2 rating system with multiplayer pairwise comparisons
- Add Redis-backed matchmaking queue with expanding rating window
- Auto-start matched games with standard rules after countdown
- Add "Find Game" button and matchmaking UI to client
- Add rating column to leaderboard
- Scale down docker-compose.prod.yml for 512MB droplet
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Only standard-rules games now count toward leaderboard stats. Games
with any house rule variant are marked "Unranked" in the active rules
bar, and a notice appears in the lobby when house rules are selected.
Also fixes game_logger duplicate options dicts (now uses dataclasses.asdict,
capturing all options including previously missing ones) and refactors
duplicated achievement-checking logic into shared helpers.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>