strat-gameplay-webapp/backend/app/core/CLAUDE.md
Cal Corum cbdd8cf903 CLAUDE: Fix critical game engine issues and refactor CLAUDE.md docs
Critical fixes in game_engine.py:
- Fix silent error swallowing in _batch_save_inning_rolls (re-raise)
- Add per-game asyncio.Lock for race condition prevention
- Add _cleanup_game_resources() for memory leak prevention
- All 739 tests passing

Documentation refactoring:
- Created CODE_REVIEW_GAME_ENGINE.md documenting 24 identified issues
- Trimmed backend/app/core/CLAUDE.md from 1371 to 143 lines
- Trimmed frontend-sba/CLAUDE.md from 696 to 110 lines
- Created focused subdirectory CLAUDE.md files:
  - frontend-sba/components/CLAUDE.md (105 lines)
  - frontend-sba/composables/CLAUDE.md (79 lines)
  - frontend-sba/store/CLAUDE.md (116 lines)
  - frontend-sba/types/CLAUDE.md (95 lines)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-19 16:05:26 -06:00

4.3 KiB

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)

_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:

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

_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

# 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

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

from app.models.game_models import GameState, DefensiveDecision, OffensiveDecision
from app.config import PlayOutcome

Testing

# 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