# 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**: 739/739 passing | **Last Updated**: 2025-01-19
