strat-gameplay-webapp/.claude/refactor_overview.md
Cal Corum 54092a8117 CLAUDE: Add refactor planning and session documentation
Added comprehensive documentation for the GameEngine refactor:
- refactor_overview.md: Detailed plan for forward-looking play tracking
- status-2025-10-24-1430.md: Session summary from Phase 2 implementation

These documents capture the architectural design decisions and
implementation roadmap that guided the refactor completed in commit 13e924a.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-25 22:19:59 -05:00

139 lines
5.5 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

 Refactor GameEngine for Forward-Looking Play Tracking
Problem
Current implementation does awkward "lookbacks" to determine runner positions. Need clean "prepare → execute → save" pattern that was error-prone in legacy implementation.
Solution Overview
Enrich GameState with current play snapshot. Use start_game() to validate lineups and prepare first play. Each resolve_play() explicitly orchestrates the sequence. Add state
recovery from last completed play.
Changes Required
1. Update GameState Model (game_models.py)
Add fields to track current play snapshot:
# Batting order tracking (per team)
away_team_batter_idx: int = 0 # 0-8, wraps for MVP (no subs yet)
home_team_batter_idx: int = 0 # 0-8
# Current play snapshot (set by _prepare_next_play)
current_batter_lineup_id: Optional[int] = None
current_pitcher_lineup_id: Optional[int] = None
current_catcher_lineup_id: Optional[int] = None
current_on_base_code: int = 0 # Bit field (1=1st, 2=2nd, 4=3rd, 7=loaded)
2. Refactor start_game() in GameEngine
- Validate BOTH lineups complete (minimum 9 players each) - HARD REQUIREMENT
- Throw ValidationError if lineups incomplete or missing positions
- After transitioning to active, call _prepare_next_play()
- Return state with first play ready to execute
3. Create _prepare_next_play() Method
# Determine current batter and advance index
if state.half == "top":
current_idx = state.away_team_batter_idx
state.away_team_batter_idx = (current_idx + 1) % 9
else:
current_idx = state.home_team_batter_idx
state.home_team_batter_idx = (current_idx + 1) % 9
# Fetch active lineups, set snapshot
state.current_batter_lineup_id = batting_lineup[current_idx].id
state.current_pitcher_lineup_id = next(p for p in fielding if p.position == "P").id
state.current_catcher_lineup_id = next(p for p in fielding if p.position == "C").id
# Calculate on_base_code from state.runners
state.current_on_base_code = 0
for runner in state.runners:
if runner.on_base == 1: state.current_on_base_code |= 1
if runner.on_base == 2: state.current_on_base_code |= 2
if runner.on_base == 3: state.current_on_base_code |= 4
4. Refactor resolve_play() Orchestration
Explicit sequence (no hidden side effects):
1. Resolve play with dice rolls
2. Save play to DB (uses snapshot from GameState)
3. Apply result to state (outs, score, runners)
4. Update game state in DB
5. If outs >= 3:
- Advance inning (clear bases, reset outs, increment, batch save rolls)
- Update game state in DB again
6. Prepare next play (always last step)
5. Update _save_play_to_db()
Use snapshot from GameState (NO lookbacks):
# From snapshot
batter_id = state.current_batter_lineup_id
pitcher_id = state.current_pitcher_lineup_id
catcher_id = state.current_catcher_lineup_id
on_base_code = state.current_on_base_code
# Runners on base BEFORE play (from state.runners)
on_first_id = next((r.lineup_id for r in state.runners if r.on_base == 1), None)
on_second_id = next((r.lineup_id for r in state.runners if r.on_base == 2), None)
on_third_id = next((r.lineup_id for r in state.runners if r.on_base == 3), None)
# Runners AFTER play (from result.runners_advanced)
# Build dict of from_base -> to_base
finals = {from_base: to_base for from_base, to_base in result.runners_advanced}
on_first_final = finals.get(1) # None if out/scored, 1-4 if moved
on_second_final = finals.get(2)
on_third_final = finals.get(3)
# Batter result
batter_final = result.batter_result # None=out, 1-4=base reached
6. Keep _apply_play_result() Focused
Only update in-memory state (NO database writes):
- Update outs, score, runners, play_count
- Database writes handled by orchestration layer
7. Keep _advance_inning() Focused
Only handle inning transition:
- Clear bases, reset outs, increment inning/half
- Check game over, batch save rolls
- NO prepare_next_play (orchestration handles)
- NO database writes (orchestration handles)
8. Add State Recovery (state_manager.py)
New method: recover_game_from_last_play(game_id)
1. Load games table → basic state
2. Query last completed play:
SELECT * FROM plays WHERE game_id=X AND complete=true
ORDER BY play_number DESC LIMIT 1
3. If play exists:
- Runners: on_first_final, on_second_final, on_third_final (use lineup_ids)
- Batting indices: derive from batting_order and team
4. If no play (just started):
- Initialize: indices=0, no runners
5. Reconstruct GameState in memory
6. Call _prepare_next_play() → ready to resume
Benefits
✅ No special case for first play
✅ No awkward lookbacks
✅ Clean validation (can't start without lineups)
✅ Single source of truth (GameState)
✅ Explicit orchestration (easy to understand)
✅ Fast state recovery (one query, no replay)
✅ Separate batter indices (18+ queries saved per game)
Testing Updates
Update test script to verify:
- start_game() fails with incomplete lineups
- on_base_code calculated correctly (bit field 1|2|4)
- Runner lineup_ids tracked in Play records
- Batting order cycles 0-8 per team independently
- State recovery from last play works