Files
mtgonline/handoff.md
T

26 KiB

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

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

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

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

{
  "type": "cmd_play_card",
  "card_id": 42,
  "zone": "hand",
  "targets": [3, 5],
  "mana_cost": [1, "R", "G"]
}

Event Messages

{
  "type": "event_play_card",
  "card_id": 42,
  "controller": 1,
  "zone": "battlefield",
  "timestamp": 1784948189
}

State Sync Message

{
  "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:

# 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:

  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).