# MTG Online Multiplayer Game Server — Handoff Document **Project**: MTG Online Web (Modern Web-based MTG Platform) **Previous Thread**: Card Backend — Phase 1 & 2 (Deck Building, Card Import, User Data) **New Thread**: Phase 3 — Multiplayer Game Server **Location**: `/home/wall-o/projects/mtgonline` **Date**: 2026-07-25 --- ## Context: What Was Built (Card Backend) The previous thread delivered a complete card management and deckbuilding backend: ### Completed Features - **FastAPI application** with JWT authentication, dual PostgreSQL databases (`mtgonline` + `mtgdata`), Redis caching - **29 REST endpoints** across 10 routers (auth, users, decks, rooms, games, admin, cards, interactions, refresh, user-data, card-import) - **16 user-data tables** with Alembic migrations, CASCADE deletes, JSONB flexibility - **Card import feature** with fuzzy matching for XLSX/CSV/JSON/ODS files - **Deck CRUD** with DRAFT/FINAL status transitions, precedents, suggestions - **MTGJSON data pipeline** downloading and upserting card data into `mtgdata` database - **Frontend** (React/TypeScript) with deck builder UI, card import, authentication, admin dashboard ### Current Architecture ``` mtgonline/ ├── backend/ # FastAPI card backend (Phase 1 & 2 complete) │ ├── app/ │ │ ├── core/ # Settings, database engines, Redis client │ │ ├── models/ # SQLAlchemy ORM models (user_data, user_deck, card_import) │ │ ├── routers/ # API route modules (10 routers) │ │ ├── schemas/ # Pydantic schemas │ │ ├── services/ # MTGJSON manager, card DB, deck manager, fuzzy matcher │ │ └── main.py # FastAPI app entry point │ ├── alembic/ # Database migrations │ └── Dockerfile ├── frontend/ # React/TypeScript deck builder (Phase 3 complete) ├── C++/ # Cockatrice architecture analysis (reference) ├── state.json # Current state tracking └── ROADMAP.md # Full project roadmap ``` ### Key Integration Points Available - **Card lookup**: `GET /api/cards/{card_id}` — returns full card data for game display - **Card search**: `GET /api/cards/search?q=...` — search cards during gameplay - **Set lists**: `GET /api/sets/` — list available sets for game formatting - **User decks**: `GET /api/users/{user_id}/decks` — retrieve FINAL decks for player selection - **Card import status**: `GET /api/v1/card-import/status` — check if user has imported their collection - **Database access**: Direct SQLAlchemy async engines for `mtgonline` (app data) and `mtgdata` (card data) --- ## Phase 3: Multiplayer Game Server — Requirements ### Objective Build a real-time multiplayer game server that enables live MTG gameplay between multiple players via WebSocket connections. This is the core gameplay engine — the replacement for Cockatrice's `servatrice` component. ### Success Criteria - [ ] Players can create/join game rooms and play MTG in real-time - [ ] Game state syncs correctly across all connected players (< 50ms latency) - [ ] All 11 MTG turn phases are enforced server-side - [ ] Stack resolution works correctly (layers, priority, targeting) - [ ] Deck validation ensures legality before gameplay - [ ] Game history is recorded as a serialized event log (for replays) - [ ] Admin commands work (kick, ban, game control) - [ ] Spectator mode is supported - [ ] WebSocket reconnection handles gracefully with state sync --- ## Architecture Blueprint (Derived from Cockatrice Analysis) ### Authority Model ``` ┌─────────────────┐ WebSocket (JSON) ┌─────────────────┐ │ CLIENT │◄────────────────────────────────────►│ SERVER │ │ (React/TS) │ │ (FastAPI/Py) │ │ │ │ │ │ • Game Scene │ GameCommands (play, attack, etc.) │ • PostgreSQL │ │ • Hand View │◄────────────────────────────────────►│ • Game State │ │ • Chat Panel │ GameEvents (state changes) │ • Room/Player │ │ • Deck Panel │ │ Management │ │ • Phase Toolbar │ Chat, Admin, Spectator │ • Replay Log │ └─────────────────┘ └─────────────────┘ ``` **Key Principle**: Server is authoritative. Clients send commands, server validates and broadcasts events. No client-side state manipulation. ### Game Engine Architecture | Cockatrice Component | Modern Web Equivalent | Role | |---|---|---| | `AbstractGame` | `Game` (server-side) | Core game instance holding state, players, event handler | | `GameMetaInfo` | `GameMetadata` | gameId, maxPlayers, description, started, spectators | | `GameState` | `GameBoardState` | currentPhase, activePlayer, hostId, gameTimer | | `GameEventHandler` | `GameEventDispatcher` | Central dispatch — processes events, prepares commands | | `PlayerManager` | `PlayerRegistry` | Coordinates all players in a game | | `PlayerLogic` | `Player` | Per-player game logic | | `PlayerActions` | `PlayerCommands` | Concrete commands: play, attack, tap, draw | | `CardZone` | `Zone` (base class) | Abstract zone — Hand/Stack/Table/Graveyard/Exile/Library | | `HandZone` | `Hand` | Player's hand (secret/hidden zone) | | `StackZone` | `Stack` | Spells/abilities on the stack | | `TableZone` | `Battlefield` | Permanents on the battlefield | | `PileZone` | `Pile` (Graveyard, Exile, Library) | Discard/exile/draw piles | | `Replay` | `GameReplay` | Serialized event stream for replay | | `Phase` | `TurnPhase` | 11-phase MTG turn structure | ### Turn Phase System (11 Phases with Sub-Phases) ``` Untap → Upkeep → Draw → Main 1 → Combat → Main 2 → End → Cleanup │ └── Sub-phases: Beginning of Combat Declare Attackers Declare Blockers Combat Damage End of Combat ``` ### Command/Event Flow ``` User Action (React component) │ ▼ GameCommand (JSON message) │ ▼ GameEventDispatcher.process() │ ▼ Player.handleCommand() │ ▼ ZoneLogic (state mutation) │ ▼ GameEvent (broadcast to all clients via WebSocket) │ ▼ Client receives & updates UI via Zustand/XState ``` ### Network Protocol Design Cockatrice uses Protocol Buffers over TCP. Modern web equivalent: | Cockatrice Proto Message | JSON WebSocket Message | |---|---| | `ServerInfo_Game` | `{ "type": "game_info", "game_id": 1, "max_players": 2, ... }` | | `ServerInfo_Player` | `{ "type": "player_info", "player_id": 1, "name": "...", ... }` | | `Command_PlayCard` | `{ "type": "cmd_play_card", "card_id": 42, "zone": "hand", ... }` | | `Command_Attack` | `{ "type": "cmd_attack", "attacker_id": 7, "targets": [3, 5], ... }` | | `Event_Join` | `{ "type": "event_join", "player_id": 2, "properties": {...} }` | | `Event_Leave` | `{ "type": "event_leave", "player_id": 1, "reason": "..." }` | | `Event_SetActivePlayer` | `{ "type": "event_active_player", "player_id": 1 }` | | `Event_SetActivePhase` | `{ "type": "event_active_phase", "phase": 5 }` | | `Event_GameSay` | `{ "type": "event_chat", "player_id": 1, "message": "..." }` | | `GameReplay` | `{ "type": "replay", "events": [...] }` | ### Architecture Patterns to Reuse | Pattern | Cockatrice Usage | Modern Equivalent | |---|---|---| | **Event Bus** | Qt signals/slots | WebSocket broadcast + event system | | **Command Pattern** | `Command_PlayCard`, `Command_Attack` | JSON command messages, validated server-side | | **Observer** | `GameEventHandler` emits signals | WebSocket events trigger UI updates | | **Strategy** | `CardZoneLogic` subclasses | Zone classes with strategy pattern | | **Facade** | `AbstractGame` | Single `Game` object wrapping all subsystems | | **Memento** | `DeckListMemento` for undo | Immutable state snapshots for undo/redo | | **Repository** | `ServatriceDatabaseInterface` | SQLAlchemy repositories for game state | --- ## Implementation Plan ### Phase 3.1: Game Server Foundation #### Deliverables 1. **Game Server Module** — Separate FastAPI WebSocket endpoint for game rooms 2. **Game Room Management** — Room creation, player join/leave, spectator support 3. **Game State Management** — Server-authoritative game state with full phase tracking 4. **Player System** — Player registration, connection management, disconnection handling 5. **WebSocket Protocol** — JSON message protocol for commands and events 6. **Database Models** — Game rooms, player connections, game state persistence #### Files to Create ``` backend/app/ ├── models/ │ ├── game_room.py # Game room model (room_id, game_type, host_id, status) │ ├── game_player.py # Player connection (player_id, room_id, connection_id, hand, life) │ ├── game_state.py # Game board state (phase, turn, cards, zones) │ ├── game_event.py # Game events log (for replay) │ └── game_log.py # Game history (serialized event log) ├── routers/ │ └── game_server.py # WebSocket endpoint for game rooms ├── services/ │ ├── game_engine.py # Core game engine (turn phases, combat, stack) │ ├── game_state_manager.py # Game state management and synchronization │ ├── player_manager.py # Player management (join, leave, disconnect) │ ├── card_zone.py # Card zone logic (hand, stack, battlefield, pile) │ ├── game_rule_engine.py # Game rule enforcement (mana, timing, targeting) │ └── game_replay.py # Game replay recording and playback ├── schemas/ │ └── game_schemas.py # Pydantic schemas for game messages └── ws/ ├── connection.py # WebSocket connection management └── message_handler.py # Message parsing and dispatch ``` #### Detailed Implementation Steps **Step 1: Game Room Model & Router** - Create `GameRoom` model with room_id, game_type, host_id, status (WAITING/PLAYING/FINISHED) - Create `GamePlayer` model with player_id, room_id, connection_id, hand (JSONB), life_total - Create `GameServer` router with WebSocket endpoint `/ws/game/{room_id}` - Implement room creation (host creates room), player join/leave, spectator join/leave - Handle WebSocket disconnection gracefully (player_timeout for reconnect) **Step 2: Game State Management** - Create `GameBoardState` model tracking: current_phase, active_player, turn_number, host_id - Track all zone contents: hands, stack, battlefield, graveyards, exiles, libraries - Implement phase transitions (Untap → Upkeep → Draw → Main1 → Combat → Main2 → End → Cleanup) - Handle sub-phases within Combat (Beginning, Declare Attackers, Declare Blockers, Combat Damage, End) **Step 3: Card Zone Logic** - Implement `CardZone` base class with common operations (add, remove, search, sort) - Implement `HandZone` (secret zone, only visible to owner) - Implement `StackZone` (priority-based, spell/ability resolution) - Implement `BattlefieldZone` (permanent zone, battlefield effects) - Implement `PileZone` (Graveyard, Exile, Library — shuffle, draw, discard) - Implement zone-specific behaviors (hand: count/mulligan, stack: layer resolution) **Step 4: Game Rule Engine** - Implement mana payment tracking and validation - Implement timing rules (sorcery speed, instant speed, flash) - Implement targeting validation (legal targets, priority chain) - Implement combat rules (attackers, blockers, damage assignment, trample, first strike) - Implement stack resolution (last in, first out, layer system) - Implement life total tracking and loss conditions (0 life, poison, concession) **Step 5: Game Replay System** - Serialize all game events as a JSONB log - Support replay recording (on game start) and playback (on request) - Implement deterministic replay (same commands → same state) - Store replays in `game_logs` table with compressed JSONB **Step 6: Admin Commands** - Implement kick player command - Implement ban player command (from mtgonline DB) - Implement game pause/resume - Implement spectator mode toggle - Implement admin override for game state ### Phase 3.2: Integration with Card Backend #### Deliverables 1. **Deck Validation Service** — Validate decks before game start 2. **Card Lookup Service** — Fetch card data for game display 3. **User Authentication** — JWT validation for game server 4. **Database Integration** — Access to `mtgdata` and `mtgonline` databases #### Integration Points - **Card Backend API** (`http://backend:8000`): - `GET /api/cards/{card_id}` — Get card details for game board display - `GET /api/cards/search?q=...` — Search cards during gameplay - `GET /api/sets/` — List available sets for game formatting - `GET /api/users/{user_id}/decks` — Get user's FINAL decks for selection - **Direct PSQL Access** (via shared connection string): - `mtgdata` database — Read card data (cards, sets, etc.) - `mtgonline` database — Read user decks (for deck validation, game setup) #### Deck Validation Flow ``` Player selects deck → Server fetches deck from mtgonline DB │ ▼ Validate deck against rules: │ ├── Check card count (60+ for Standard/Modern, 100+ for Commander) │ ├── Card validity (must exist in mtgdata) │ ├── Color identity (Commander format) │ └── Basic land count (max 4 of each non-basic) │ ├── Check banned cards (format-specific ban list) │ └── Return validation result (pass/fail with reasons) ``` ### Phase 3.3: WebSocket Client Integration #### Deliverables 1. **WebSocket Client** — React component for game client 2. **State Management** — Zustand store for game state 3. **Message Handlers** — Parse and handle game events 4. **Reconnection Logic** — Exponential backoff with state sync #### Integration with Frontend - **WebSocket Connection**: Connect to `/ws/game/{room_id}` on game start - **State Store**: Use Zustand to manage game state, sync with server events - **Message Handlers**: Handle join, leave, play_card, attack, etc. - **Reconnection**: On disconnect, attempt reconnect with exponential backoff, sync state on reconnect --- ## Database Schema for Game Server ### Tables to Create | Table | Purpose | Key Columns | |-------|---------|-------------| | `game_rooms` | Game room management | room_id (PK), game_type, host_id, status, created_at | | `game_players` | Player connections | player_id (PK), room_id (FK), connection_id, hand (JSONB), life_total, is_spectator | | `game_states` | Game board state | state_id (PK), room_id (FK), current_phase, turn_number, active_player, cards (JSONB), created_at | | `game_events` | Event log for replay | event_id (PK), room_id (FK), event_type, event_data (JSONB), timestamp | | `game_replays` | Recorded games | replay_id (PK), room_id (FK), events (JSONB), duration, started_at, finished_at | ### JSONB Structures **Hand**: `{"cards": [{"card_id": 1, "name": "Lightning Bolt", "zone": "hand"}]}` **Stack**: `{"items": [{"card_id": 5, "name": "Counterspell", "controller": 1}]}` **Battlefield**: `{"cards": [{"card_id": 10, "name": "Craterhoof", "zone": "battlefield", "tapped": false}]}` **Graveyard**: `{"cards": [{"card_id": 1, "name": "Lightning Bolt", "zone": "graveyard"}]}` --- ## Game Engine Implementation Details ### Core Game Class ```python class Game: """Core game instance holding state, players, event handler.""" def __init__(self, room_id: str, game_type: str, host_id: int): self.room_id = room_id self.game_type = game_type # Standard, Modern, Commander self.host_id = host_id self.players: Dict[int, Player] = {} self.state: GameBoardState = GameBoardState() self.event_dispatcher: GameEventDispatcher = GameEventDispatcher(self) self.replay: GameReplay = GameReplay() def add_player(self, player: Player) -> bool: """Add player to game. Returns True if successful.""" def remove_player(self, player_id: int) -> bool: """Remove player from game. Broadcasts event.""" def transition_phase(self) -> None: """Advance to next phase. Validates phase transitions.""" def handle_command(self, command: GameCommand) -> List[GameEvent]: """Process player command. Returns events to broadcast.""" def broadcast_event(self, event: GameEvent) -> None: """Broadcast event to all connected clients.""" ``` ### Player Class ```python class Player: """Per-player game logic.""" def __init__(self, player_id: int, name: str, deck: Deck): self.player_id = player_id self.name = name self.deck = deck self.hand: HandZone = HandZone() self.battlefield: BattlefieldZone = BattlefieldZone() self.graveyard: PileZone = PileZone() self.exile: PileZone = PileZone() self.library: LibraryZone = LibraryZone() self.life_total: int = 20 self.poison_counters: int = 0 def handle_command(self, command: GameCommand) -> List[GameEvent]: """Process command. Returns events to broadcast.""" def play_card(self, card_id: int, zone: str) -> GameEvent: """Play card from hand to battlefield.""" def attack(self, attacker_id: int, targets: List[int]) -> GameEvent: """Declare attackers and assign damage.""" def tap_card(self, card_id: int) -> GameEvent: """Tap card for mana or attack.""" def draw_card(self) -> GameEvent: """Draw card from library.""" ``` ### Zone Classes ```python class CardZone: """Abstract zone base class.""" def add_card(self, card: Card) -> None: """Add card to zone.""" def remove_card(self, card_id: int) -> Card: """Remove card from zone.""" def search(self, filter: SearchFilter) -> List[Card]: """Search zone with filter.""" def shuffle(self) -> None: """Shuffle zone.""" def sort(self) -> None: """Sort zone.""" class HandZone(CardZone): """Player's hand (secret zone).""" # Only visible to owner class StackZone(CardZone): """Spells/abilities on the stack.""" # Priority-based resolution class BattlefieldZone(CardZone): """Permanent zone.""" # Has tap/untap, counters, attachments class PileZone(CardZone): """Graveyard, exile, library.""" # Shuffleable (library) or non-shuffleable (graveyard) ``` --- ## Testing Strategy ### Unit Tests - **Game State Transitions**: Test all 11 phases and sub-phases - **Combat Rules**: Test attack, block, damage assignment, trample, first strike - **Stack Resolution**: Test priority, layer system, LIFO resolution - **Mana Payment**: Test mana pool, timing, costs - **Zone Operations**: Test add/remove/search/shuffle/sort for each zone - **Deck Validation**: Test format rules (Standard, Modern, Commander) - **Replay Determinism**: Test same commands → same state ### Integration Tests - **WebSocket Connection**: Test connect/disconnect/join/leave - **Game Room Management**: Test room creation, player count, spectator - **Real-time Sync**: Test state sync across multiple clients - **Reconnection**: Test reconnect with state sync - **Admin Commands**: Test kick, ban, pause, resume ### Load Tests - **Concurrent Players**: Test 2-8 players per game - **Multiple Games**: Test 100+ concurrent games - **Message Throughput**: Test 1000+ messages/sec per game - **Latency**: Test < 50ms message latency --- ## Acceptance Criteria ### Functional Requirements - [ ] Players can create game rooms with custom settings - [ ] Players can join existing game rooms - [ ] Game starts with all players ready - [ ] 11-phase turn structure works correctly - [ ] Combat rules enforce attack/block/damage correctly - [ ] Stack resolves spells/abilities in correct order - [ ] Mana payment validates costs and timing - [ ] Deck validation ensures format legality - [ ] Game history recorded as serialized event log - [ ] Admin can kick/ban players - [ ] Spectators can watch games - [ ] WebSocket disconnect triggers graceful player timeout ### Performance Requirements - [ ] WebSocket latency < 50ms for game state updates - [ ] Support 1000+ concurrent users across multiple games - [ ] Game state syncs within 100ms across all connected players - [ ] Replay recording doesn't impact gameplay performance - [ ] Database queries for card lookup < 10ms ### Security Requirements - [ ] JWT authentication for all WebSocket connections - [ ] Input validation for all game commands - [ ] Rate limiting for message spam protection - [ ] Admin-only commands require admin role - [ ] Game state never exposed to spectators (unless enabled) --- ## Files to Create/Modify ### New Files ``` backend/app/ ├── models/ │ ├── game_room.py │ ├── game_player.py │ ├── game_state.py │ ├── game_event.py │ └── game_log.py ├── routers/ │ └── game_server.py ├── services/ │ ├── game_engine.py │ ├── game_state_manager.py │ ├── player_manager.py │ ├── card_zone.py │ ├── game_rule_engine.py │ └── game_replay.py ├── schemas/ │ └── game_schemas.py └── ws/ ├── connection.py └── message_handler.py ``` ### Modified Files ``` backend/app/ ├── main.py # Mount game server router ├── core/ │ ├── database.py # Add game database engine if needed │ └── settings.py # Add game server settings ├── alembic/ │ └── versions/ │ └── 005_game_server_tables.py ``` ### Dependencies to Add ``` # backend/requirements.txt websockets>=12.0 pydantic>=2.9.2 ``` --- ## Integration with Card Backend ### API Endpoints Used by Game Server | Endpoint | Purpose | Method | |----------|---------|--------| | `/api/cards/{card_id}` | Get card details | GET | | `/api/cards/search?q=...` | Search cards | GET | | `/api/sets/` | List sets | GET | | `/api/users/{user_id}/decks` | Get user decks | GET | | `/api/v1/card-import/status` | Check import status | GET | ### Database Access **mtgdata database** (read-only): - `mtg_cards` — Card data for game display - `mtg_sets` — Set data for format rules - `mtg_card_types` — Card type definitions **mtgonline database** (read-write): - `users` — User authentication - `decks` — User decks for validation - `ban` — Ban list for admin commands - `game_logs` — Game history storage --- ## Cockatrice Analysis Reference The complete architecture analysis is at `/home/wall-o/projects/mtgonline/C++/ARCHITECTURE_ANALYSIS.md`. Key takeaways: - **Server authoritative**: Never trust client state - **Deterministic replay**: Same commands → same state - **Zone abstraction**: Each zone independently implementable - **Phase system**: 11-phase MTG turn with sub-phases - **Command/Event separation**: What player wants vs. what happens The modern web adaptation replaces: - TCP with WebSocket (JSON) - Protocol Buffers with JSON messages - Qt Widgets with React/Next.js - C++/MySQL with Python/FastAPI + PostgreSQL - Monolithic handler with modular service architecture --- ## Next Steps 1. **Start with Phase 3.1**: Game server foundation (models, router, WebSocket) 2. **Implement core game engine**: Turn phases, combat, stack resolution 3. **Add zone logic**: Hand, stack, battlefield, pile zones 4. **Integrate with card backend**: Deck validation, card lookup 5. **Add replay system**: Serialize events for replay 6. **Test thoroughly**: Unit, integration, load tests 7. **Update documentation**: API docs, architecture docs 8. **Push to Gitea**: Commit and push all changes --- ## Quick Reference ### Game Room WebSocket Endpoint ``` WS /ws/game/{room_id} ``` ### Command Messages ```json { "type": "cmd_play_card", "card_id": 42, "zone": "hand", "targets": [3, 5], "mana_cost": [1, "R", "G"] } ``` ### Event Messages ```json { "type": "event_play_card", "card_id": 42, "controller": 1, "zone": "battlefield", "timestamp": 1784948189 } ``` ### State Sync Message ```json { "type": "state_sync", "game_state": { "current_phase": "main1", "turn_number": 5, "active_player": 1, "hands": {"1": [...], "2": [...]}, "battlefield": [...], "stack": [...], "graveyards": {"1": [...], "2": [...]} } } ``` --- ## Environment Variables Add to `.env` or docker-compose: ```bash # Game Server GAME_SERVER_HOST=0.0.0.0 GAME_SERVER_PORT=8765 GAME_WS_PATH=/ws/game GAME_TOKEN_EXPIRY=3600 GAME_PLAYER_TIMEOUT=30 GAME_MAX_PLAYERS=8 ``` --- ## Docker Configuration Add to `docker-compose.dev.yml`: ```yaml game-server: build: context: ./backend dockerfile: Dockerfile ports: - "8765:8765" environment: - DATABASE_URL=${DATABASE_URL} - MTG_DATABASE_URL=${MTG_DATABASE_URL} - REDIS_URL=${REDIS_URL} - GAME_SERVER_PORT=8765 - GAME_WS_PATH=/ws/game depends_on: - postgres - mtgdata - redis networks: - mtgonline ``` --- **Last Updated**: 2026-07-25 **Status**: Handoff complete. Ready for Phase 3 implementation. **Next Action**: Begin Phase 3.1 — Game server foundation (models, router, WebSocket).