# Phase 2: Game Engine Core **Duration**: Weeks 4-6 **Status**: ✅ Weeks 4-5 Complete, Week 6 Pending **Prerequisites**: Phase 1 Complete --- ## Overview Build the core game simulation engine with in-memory state management, play resolution logic, and database persistence. Implement the polymorphic player model system and league configuration framework. ## Key Objectives (Actual Status) By end of Phase 2, you should have: - ✅ **COMPLETE**: In-memory game state management working (Week 4) - ✅ **COMPLETE**: Play resolution engine with dice rolls (Week 5, enhanced with AbRoll) - 🔲 **PENDING**: League configuration system (SBA and PD configs) - Week 6 - ✅ **COMPLETE**: Polymorphic player models (Lineup & RosterLink polymorphic, not BasePlayer hierarchy) - ✅ **COMPLETE**: Database persistence layer with async operations (Week 4) - ✅ **COMPLETE**: State recovery mechanism from database (Week 4) - ✅ **COMPLETE**: Basic game flow (start → plays → end) - Working in manual tests ## Major Components to Implement ### 1. Game Engine (`backend/app/core/game_engine.py`) - Game session initialization - Turn management (defensive → stolen base → offensive → resolution) - Action processing and validation - State update coordination - Event emission to WebSocket clients ### 2. State Manager (`backend/app/core/state_manager.py`) - In-memory game state dictionary - State CRUD operations - State lifecycle management - Cache eviction for idle games - State recovery from database ### 3. Play Resolver (`backend/app/core/play_resolver.py`) - Cryptographic dice rolling - Result chart lookup (league-specific) - Play outcome determination - Runner advancement logic - Score calculation ### 4. Dice System (`backend/app/core/dice.py`) - Secure random number generation - Roll logging and verification - Distribution testing ### 5. Polymorphic Player Models (`backend/app/models/player_models.py`) - BasePlayer abstract class - SbaPlayer implementation - PdPlayer implementation - Lineup factory method - Type guards for league-specific logic ### 6. League Configuration (`backend/app/config/`) - BaseGameConfig class - SbaConfig and PdConfig subclasses - Result charts (d20 tables) for each league - Config loader and versioning - Config validation ### 7. Database Operations (`backend/app/database/operations.py`) - Async play persistence - Game metadata updates - Lineup operations - State snapshot management - Bulk recovery queries ### 8. API Client (`backend/app/data/api_client.py`) - HTTP client for league REST APIs - Team data fetching - Roster data fetching - Player/card data fetching - Error handling and retries - Response caching (optional) ## Implementation Order (Actual Status) 1. **Week 4**: State Manager + Database Operations ✅ **COMPLETE** - ✅ In-memory state structure (GameState Pydantic models) - ✅ Basic CRUD operations (StateManager with dictionary storage) - ✅ Database persistence layer (DatabaseOperations async methods) - ✅ State recovery mechanism (Implemented and tested) 2. **Week 5**: Game Engine + Play Resolver ✅ **COMPLETE** - ✅ Game initialization flow (start_game with lineup validation) - ✅ Turn management (Forward-looking snapshot pattern, refactored 2025-10-25) - ✅ Dice rolling system (Enhanced AbRoll with batch persistence) - ✅ Basic play resolution (SimplifiedResultChart with wild pitch/passed ball) 3. **Week 6**: League Configs + Player Models 🔲 **PENDING** - ✅ Polymorphic player architecture (Done differently: Lineup & RosterLink polymorphic) - 🔲 League configuration system (Pending) - 🔲 Complete result charts (Using simplified charts for now) - 🔲 API client integration (Pending) - 🟡 End-to-end testing (Manual test script works, formal tests missing) ## Testing Strategy ### Unit Tests - State manager operations - Dice roll distribution - Play resolver outcomes - Player model instantiation - Config loading ### Integration Tests - Full game flow from start to end - State recovery from database - Multi-turn sequences - API client with mocked responses ### E2E Tests - Play a complete 9-inning game - Verify database persistence - Test state recovery mid-game ## Key Files (Actual Implementation) ``` backend/app/ ├── core/ │ ├── game_engine.py # ✅ Main game logic with forward-looking snapshots │ ├── state_manager.py # ✅ In-memory state dictionary │ ├── play_resolver.py # ✅ Play outcomes with SimplifiedResultChart │ ├── dice.py # ✅ Advanced dice system with batch persistence │ ├── roll_types.py # ✅ BONUS: AbRoll, CheckRoll, ResolutionRoll dataclasses │ └── validators.py # ✅ Rule validation with lineup position checks ├── config/ # 🔲 NOT YET CREATED (Week 6) │ ├── base_config.py # 🔲 Base configuration │ ├── league_configs.py # 🔲 SBA/PD configs │ ├── result_charts.py # 🔲 d20 tables │ └── loader.py # 🔲 Config utilities ├── models/ │ ├── db_models.py # ✅ SQLAlchemy ORM models (polymorphic Lineup & RosterLink) │ └── game_models.py # ✅ Pydantic game state models ├── database/ │ └── operations.py # ✅ DatabaseOperations with async methods └── data/ # 🔲 NOT YET CREATED (Week 6) └── api_client.py # 🔲 League API client scripts/ └── test_game_flow.py # ✅ BONUS: Manual test script (5 test scenarios) tests/ ├── unit/ │ ├── models/ │ │ └── test_game_models.py # ✅ 60 tests │ └── core/ │ ├── test_dice.py # ✅ Distribution tests │ ├── test_roll_types.py # ✅ Roll type tests │ ├── test_state_manager.py # ✅ 26 tests │ ├── test_play_resolver.py # ❌ MISSING │ └── test_validators.py # ❌ MISSING └── integration/ ├── database/ │ └── test_operations.py # ✅ 21 tests ├── test_state_persistence.py # ✅ 8 tests └── test_game_engine.py # ❌ MISSING ``` ## Actual Implementation Patterns ### Forward-Looking Snapshot Pattern (Refactored 2025-10-25) The GameEngine uses a sophisticated snapshot-before-execution pattern: #### Problem Solved Original approach had database lookbacks during play saving, causing: - Multiple redundant database queries per play - Inconsistent snapshots if lineup changes during play - Difficult to reason about "state at time of play" #### Solution: `_prepare_next_play()` Before each play execution, we prepare a complete snapshot: ```python async def _prepare_next_play(self, state: GameState) -> None: """Prepare snapshot for the next play.""" # 1. Determine and advance batting order index if state.half == "top": current_idx = state.away_team_batter_idx state.away_team_batter_idx = (current_idx + 1) % 9 batting_team = state.away_team_id fielding_team = state.home_team_id else: current_idx = state.home_team_batter_idx state.home_team_batter_idx = (current_idx + 1) % 9 batting_team = state.home_team_id fielding_team = state.away_team_id # 2. Fetch lineups from database batting_lineup = await self.db_ops.get_active_lineup(state.game_id, batting_team) fielding_lineup = await self.db_ops.get_active_lineup(state.game_id, fielding_team) # 3. Set snapshot fields state.current_batter_lineup_id = batting_order[current_idx].id state.current_pitcher_lineup_id = pitcher.id state.current_catcher_lineup_id = catcher.id # 4. Calculate on_base_code (bit field: 1=1st, 2=2nd, 4=3rd) state.current_on_base_code = 0 for runner in state.runners: if runner.on_base == 1: state.current_on_base_code |= 1 elif runner.on_base == 2: state.current_on_base_code |= 2 elif runner.on_base == 3: state.current_on_base_code |= 4 ``` #### Benefits - **Single Truth**: Snapshot captured once, used throughout play - **No Lookbacks**: `_save_play_to_db()` just reads snapshot fields - **Consistent**: State cannot change between snapshot and save - **Independent Batting Orders**: Each team tracks their own `batter_idx` - **Bit Field Optimization**: `on_base_code` enables efficient database queries ### Play Execution Sequence The `resolve_play()` method follows explicit orchestration: ```python async def resolve_play(self, game_id: UUID) -> PlayResult: # STEP 1: Resolve play (dice roll + outcome determination) result = play_resolver.resolve_play(state, defensive_decision, offensive_decision) # STEP 2: Save play to DB (uses snapshot from GameState) await self._save_play_to_db(state, result) # STEP 3: Apply result to state (outs, score, runners) self._apply_play_result(state, result) # STEP 4: Update game state in DB await self.db_ops.update_game_state(...) # STEP 5: Check for inning change if state.outs >= 3: await self._advance_inning(state, game_id) await self.db_ops.update_game_state(...) # Update again after inning change await self._batch_save_inning_rolls(game_id) # STEP 6: Prepare next play (always last step) if state.status == "active": await self._prepare_next_play(state) ``` ### Advanced Dice System (AbRoll) Instead of simple d20 rolls, we use a structured roll system: **Roll Types** (`roll_types.py`): - `CheckRoll`: Initial d20 check (1=wild pitch, 2=passed ball, 3-20=normal) - `ResolutionRoll`: Secondary d20 for confirming special events or determining specifics - `AbRoll`: Complete at-bat roll combining check + resolution + context **Context Tracking**: ```python @dataclass class AbRoll: check_roll: CheckRoll resolution_roll: Optional[ResolutionRoll] game_id: UUID inning: int half: str play_number: int # ... full audit trail ``` **Batch Persistence**: - Rolls accumulated in memory during half-inning - `_batch_save_inning_rolls()` saves all at inning boundary - Reduces database writes from N (per play) to 1 (per half-inning) ### Lineup Validation Strategy **At Game Start** (Strict): - Both teams' lineups validated (minimum 9 players) - Both teams' defensive positions validated - Exception: This is the only time we validate BOTH teams **At Inning Change** (Defensive Only): - Only defensive team's positions validated - Allows offensive substitutions without validation delay - Rationale: Offensive lineup can be incomplete mid-game (pinch hitter scenarios) **Validation Rules**: - Must have exactly one: P, C, 1B, 2B, 3B, SS, LF, CF, RF - DH is optional (not validated as required) ## Reference Documents - [Backend Architecture](./backend-architecture.md) - Complete backend structure - [Database Design](./database-design.md) - Schema and queries - [WebSocket Protocol](./websocket-protocol.md) - Event specifications - [PRD Lines 378-551](../prd-web-scorecard-1.1.md) - Polymorphic player architecture - [PRD Lines 780-846](../prd-web-scorecard-1.1.md) - League configuration system ## Deliverable A working game backend that can: - Initialize a game with teams from league APIs - Process a complete at-bat (decisions → dice roll → outcome) - Update game state in memory and persist to database - Recover game state after backend restart - Handle basic substitutions ## Notes - Focus on getting one at-bat working perfectly before expanding - Test dice roll distribution extensively - Validate all state transitions - Use simplified result charts initially, expand in Phase 3 - Don't implement UI yet - test via WebSocket events or Python scripts --- ## Implementation Approach ### Key Decisions (2025-10-22) 1. **Development Order**: SBA First → PD Second - Build each component for SBA league first - Learn lessons and apply to PD implementation - Ensures simpler case works before tackling complexity 2. **Testing Strategy**: Automated Python Tests - Unit tests for each component - Integration tests for full workflows - No WebSocket UI testing in Phase 2 - Python scripts to simulate game flows 3. **Result Selection Models**: - **SBA**: Players see dice roll, then select outcome from available results - **PD**: Flexible approach - Human players can manually select results - AI/Auto mode uses scouting model to determine results automatically - Initial implementation uses placeholder/simplified charts 4. **Build Philosophy**: One Perfect At-Bat - Focus on completing a single defensive decision → dice roll → offensive decision → resolution flow - Validate all state transitions work correctly - Expand features in Phase 3 ### Detailed Weekly Plans Detailed implementation instructions for each week: - [Week 4: State Management & Persistence](./02-week4-state-management.md) - [Week 5: Game Logic & Play Resolution](./02-week5-game-logic.md) - [Week 6: League Features & Integration](./02-week6-league-features.md) --- **Status**: ✅ Weeks 4-5 Complete (2025-10-25) | Week 6 Pending **Last Updated**: 2025-10-25 **Completed**: - Week 4: State Management & Persistence (2025-10-22) - Week 5: Game Logic with forward-looking snapshots (2025-10-24) - Week 5 Testing: All unit and integration tests (2025-10-25) **Test Coverage**: - Unit tests: 54 passing (dice, roll types, play resolver, validators) - Integration tests: 7 test classes (complete game flows with database) - Manual test script: 5 comprehensive scenarios **Pending**: Week 6 (League configs, API client) OR proceed to Phase 3 **Next Milestone**: Week 6 - League Features & Integration