# Core - Game Engine & Logic ## Purpose The `core` directory contains the baseball simulation engine - game orchestration, dice rolling, play resolution, and state management for real-time performance (<500ms target). ## Architecture Overview ``` WebSocket → GameEngine → PlayResolver → RunnerAdvancement ↓ ↓ StateManager DiceSystem ↓ Database ``` ## Core Modules | Module | Purpose | Singleton | |--------|---------|-----------| | `state_manager.py` | In-memory game state with O(1) lookups | Yes | | `game_engine.py` | Main orchestrator, workflow coordination | Yes | | `play_resolver.py` | Outcome resolution (manual/auto modes) | No | | `runner_advancement.py` | Groundball/flyball runner logic | No | | `dice.py` | Cryptographic dice rolling | Yes | | `validators.py` | Baseball rule enforcement | Static | | `ai_opponent.py` | AI decision generation | Yes | ## GameEngine Key Features ### Storage (per-game tracking) ```python _rolls_this_inning: dict[UUID, List] # Batch saved at inning boundary _game_locks: dict[UUID, asyncio.Lock] # Prevents concurrent decision race conditions ``` ### Thread Safety Decision submissions (`submit_defensive_decision`, `submit_offensive_decision`) use per-game locks: ```python async with self._get_game_lock(game_id): # decision logic ``` ### Resource Cleanup When games complete (natural or manual), `_cleanup_game_resources(game_id)` releases: - `_rolls_this_inning[game_id]` - `_game_locks[game_id]` ### Core Workflow (6 steps) 1. Resolve play with dice rolls 2. Save play to DB (uses snapshot) 3. Apply result to state 4. Update game state in DB (conditional) 5. Check for inning change, batch save rolls 6. Prepare next play OR cleanup if completed ## StateManager Storage ```python _states: Dict[UUID, GameState] # O(1) state lookup _lineups: Dict[UUID, Dict[int, TeamLineupState]] # Cached lineups _last_access: Dict[UUID, pendulum.DateTime] # For idle eviction _pending_decisions: Dict[tuple, asyncio.Future] # Async decision futures ``` ## Common Patterns ### Two Resolution Modes - **Manual** (primary): Player submits outcome from physical card - **Auto** (rare, PD only): System generates from digitized ratings ### Lineup Caching ```python # First access - fetch from DB lineup_state = await lineup_service.load_team_lineup_with_player_data(...) state_manager.set_lineup(game_id, team_id, lineup_state) # Subsequent - cache hit (no DB query) lineup_state = state_manager.get_lineup(game_id, team_id) ``` ### Error Handling - "Raise or Return" pattern - exceptions propagate, no silent failures - `_batch_save_inning_rolls` re-raises on failure (audit data is critical) ## Integration Points ### With WebSocket Handlers ```python from app.core.game_engine import game_engine # Start game state = await game_engine.start_game(game_id) # Submit decisions await game_engine.submit_defensive_decision(game_id, decision) await game_engine.submit_offensive_decision(game_id, decision) # Resolve play (manual mode) result = await game_engine.resolve_manual_play(game_id, ab_roll, outcome, hit_location) ``` ### With Models ```python from app.models.game_models import GameState, DefensiveDecision, OffensiveDecision from app.config import PlayOutcome ``` ## Testing ```bash # All core tests uv run pytest tests/unit/core/ -v # Terminal client (interactive testing) uv run python -m terminal_client ``` ## Performance - State access: O(1) (~1μs) - Play resolution: 50-100ms - Query reduction: 60% vs naive implementation ## Troubleshooting | Issue | Cause | Solution | |-------|-------|----------| | Game not found | Evicted or never created | `await state_manager.recover_game(game_id)` | | Batter/pitcher None | `_prepare_next_play` not called | Ensure proper orchestration sequence | | Lineup out of sync | DB changed but cache stale | `state_manager.set_lineup()` after changes | ## References - **Code Review**: `../.claude/CODE_REVIEW_GAME_ENGINE.md` - Detailed issues and fixes - **Terminal Client**: `../terminal_client/CLAUDE.md` - Interactive testing guide - **Database Operations**: `../database/operations.py` - Persistence layer - **Main CLAUDE**: `../../CLAUDE.md` - Backend overview --- **Tests**: 2481/2481 passing | **Last Updated**: 2026-02-11