# tavolo-platform The game-independent half of tavolo: lobby, live play, match history and leaderboards. Everything here works for any game that implements the [`GameEngine`](src/tavolo/platform/engine.py) contract; the package itself ships no game. ## Contents - `engine.py` — the platform↔game contract: `GameEngine` (ABC), `GameSession` (the platform-owned envelope with an opaque `state` blob), `Seat`, `Deadline` (kind + due time + revalidation token) and the `MatchResult`/`PlayerResult` outcome types. - `registry.py` — `GameRegistry`: game id → engine lookup, single source of truth for which games exist. - `mixin.py` — `Platform` (registry + store + scheduler + OIDC, the collaborators every endpoint needs) and `PlatformMixin`, the kaya mixin that registers all routes and the websocket endpoint on an app. - `routes/` — `health` (`GET /api/health`), `me` (`GET /api/me`), `games` (lobby: `GET /api/game-types`, `POST /api/games`, `POST /api/games/join`, `GET /api/games/{id}`) and `stats` (`GET /api/me/matches`, `GET /api/leaderboard`, `GET /api/me/ratings`). - `ws.py` — the live-play websocket (`/ws/games/{id}`): connection lifecycle, the message envelope and the publish/subscribe fan-out. Game-specific actions are dispatched to the session's engine. - `store.py` — live-session persistence: `RedisGameStore` (production) and `InMemoryGameStore` (tests/dev) over a JSON envelope plus the engine's opaque state blob, with per-game locks, change signals and the shared deadline queue. - `deadlines.py` — `DeadlineScheduler`: enqueue the deadline an engine declares after each mutation; a per-loop consumer fires due entries through `engine.fire_deadline` under the per-game lock. Engines revalidate the token, so stale or duplicate deliveries are harmless. - `models.py` — `Match` (`game_type`, timestamps, game-specific JSON `result`), `MatchPlayer` (seat, team, won, points, Elo delta, JSON `details`) and `PlayerRating` (Elo per `(user_sub, game_type)`). - `stats.py` — `save_match_result` (engine `MatchResult` → Postgres, transactionally, with Elo) and `apply_elo`. - `elo.py` — chess-style Elo math generalized to two-team matches. - `backfill_elo.py` — rebuild every rating from the match history: `python -m tavolo.platform.backfill_elo --database-url postgres://…`. - `auth.py`, `http.py`, `pagination.py`, `openapi.py`, `tortoise_mixin.py` — OIDC helpers, JSON helpers, keyset pagination, shared OpenAPI fragments, the TortoiseORM lifecycle mixin. ## Adding a game Implement `GameEngine` (see `engine.py` for the full contract), register it in a `GameRegistry`, and mount `PlatformMixin(Platform(...))` on a `KayaApp` — see the composition root in `tavolo.app`. The canonical example is [`tavolo-scopone`](../tavolo-scopone/README.md). ## Development (from `server/`) ```sh .venv/bin/python -m unittest discover -s packages/tavolo-platform/tests .venv/bin/python -m mypy -p tavolo.platform ``` Tests run fully in-process against a `DummyEngine` (a two-player toy game in `tests/helpers.py`): in-memory sqlite, in-memory stores, a patched OIDC user and `httpx` / `httpx-ws` ASGI transports. No test in this package may import a real game.