# 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
