docs: update handoff document - Phase 2 complete, Phase 3 architecture planning
This commit is contained in:
+703
@@ -0,0 +1,703 @@
|
||||
# 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).
|
||||
Reference in New Issue
Block a user