This commit fixes two critical bugs in the game engine and updates tests
to match current webhook behavior:
1. State Recovery - Batter Advancement (operations.py:545-546)
- Added missing batting_order and outs_recorded fields to plays dictionary
- These fields exist in database but weren't loaded by load_game_state()
- Root cause: Batter index was correctly recovered but current_batter remained
at placeholder (batting_order=1) because recovery logic couldn't find the
actual batting_order from last play
- Fix enables proper batter advancement after backend restarts
2. Flyball Descriptions - 2 Outs Logic (runner_advancement.py)
- Made flyball descriptions dynamic based on outs and actual base runners
- FLYOUT_A (Deep): Lines 1352-1363
- FLYOUT_B (Medium): Lines 1438-1449
- FLYOUT_BQ (Medium-shallow): Lines 1521-1530
- With 2 outs: "3rd out, inning over" (no advancement possible)
- With 0-1 outs: Dynamic based on runners ("R3 scores" only if R3 exists)
- Game logic was already correct (runs_scored=0), only descriptions were wrong
- Fixed method signatures to include hit_location parameter for all flyball methods
- Updated X-check function calls to pass hit_location parameter
3. Test Updates
- Fixed test expectation in test_flyball_advancement.py to match corrected behavior
- Descriptions now only show runners that actually exist (no phantom "R2 DECIDE")
- Auto-fixed import ordering with Ruff
- Updated websocket tests to match current webhook behavior:
* test_submit_manual_outcome_success now expects 2 broadcasts (play_resolved + game_state_update)
* test_submit_manual_outcome_missing_required_location updated to reflect hit_location now optional
Testing:
- All 739 unit tests passing (100%)
- Verified batter advances correctly after state recovery
- Verified flyball with 2 outs shows correct description
- Verified dynamic descriptions only mention actual runners on base
- Verified websocket handler broadcasts work correctly
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Updated runner_advancement.py and its tests to use the new GameState
structure where current_batter is a LineupPlayerState object instead of
an integer ID.
Changes:
- runner_advancement.py: Replaced all 17 references to
state.current_batter_lineup_id with state.current_batter.lineup_id
- tests/unit/core/test_runner_advancement.py: Updated test fixtures
- Mock fixture: Added mock_batter object with lineup_id attribute
- GameState constructor calls: Create LineupPlayerState objects instead
of using current_batter_lineup_id parameter (9 tests fixed)
All 34 runner advancement tests now passing. This fixes the AttributeError
that was preventing X-Check resolution from completing in the terminal
client ("'GameState' object has no attribute 'current_batter_lineup_id'").
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Updated X-Check runner advancement functions to properly delegate to
existing result handlers for non-error cases.
Changes:
- Updated x_check_g1/g2/g3 signatures to accept GameState, hit_location,
and defensive_decision parameters
- Updated x_check_f1/f2/f3 signatures to accept GameState and hit_location
- Implemented delegation logic: error cases use simple tables, non-error
cases delegate to existing tested result handlers (_execute_result,
_fb_result_*)
- Updated PlayResolver._get_x_check_advancement() to pass new parameters
- Updated all tests to provide required GameState fixtures
Benefits:
- Reuses 13 existing groundball + 4 flyball result handlers (DRY)
- No DP probability needed - X-Check d20 already tested defender
- Full game context: real lineup IDs, outs count, conditional logic
- Error cases remain simple and efficient
Test Results: 264/265 core tests passing (1 pre-existing dice failure)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed incorrect double play logic that was rolling for probability
twice - once for the chart result and again for execution.
Changes:
- Removed _calculate_double_play_probability() method entirely
- Updated _gb_result_2() to execute DP deterministically
- Updated _gb_result_10() to execute DP deterministically
- Updated _gb_result_13() to execute DP deterministically
- Removed TestDoublePlayProbability test class (5 tests)
- Updated DP tests to reflect deterministic behavior
Logic: Chart already determines outcome via dice roll. When chart
says "Result 2: Double Play", the DP happens (if <2 outs and runner
on 1st exists). No additional probability roll needed.
Tests: 55/55 runner advancement tests passing
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Major Phase 2 refactoring to consolidate runner advancement logic:
**Flyball System Enhancement**:
- Add FLYOUT_BQ variant (medium-shallow depth)
- 4 flyball types with clear semantics: A (deep), B (medium), BQ (medium-shallow), C (shallow)
- Updated helper methods to include FLYOUT_BQ
**RunnerAdvancement Integration**:
- Extend runner_advancement.py to handle both groundballs AND flyballs
- advance_runners() routes to _advance_runners_groundball() or _advance_runners_flyball()
- Comprehensive flyball logic with proper DECIDE mechanics per flyball type
- No-op movements recorded for state recovery consistency
**PlayResolver Refactoring**:
- Consolidate all 4 flyball outcomes to delegate to RunnerAdvancement (DRY)
- Eliminate duplicate flyball resolution code
- Rename helpers for clarity: _advance_on_single_1/_advance_on_single_2 (was _advance_on_single)
- Fix single/double advancement logic for different hit types
**State Recovery Fix**:
- Fix state_manager.py game recovery to build LineupPlayerState objects properly
- Use get_lineup_player() helper to construct from lineup data
- Correctly track runners in on_first/on_second/on_third fields (matches Phase 2 model)
**Database Support**:
- Add runner tracking fields to play data for accurate recovery
- Include batter_id, on_first_id, on_second_id, on_third_id, and *_final fields
**Type Safety Improvements**:
- Fix lineup_id access throughout runner_advancement.py (was accessing on_first directly, now on_first.lineup_id)
- Make current_batter_lineup_id non-optional (always set by _prepare_next_play)
- Add type: ignore for known SQLAlchemy false positives
**Documentation**:
- Update CLAUDE.md with comprehensive flyball documentation
- Add flyball types table, usage examples, and test coverage notes
- Document differences between groundball and flyball mechanics
**Testing**:
- Add test_flyball_advancement.py with 21 flyball tests
- Coverage: all 4 types, DECIDE scenarios, no-op movements, edge cases
🚀 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Created runner_advancement.py with complete groundball advancement system
- Implemented GroundballResultType IntEnum with 13 rulebook-aligned results
- Built RunnerAdvancement class with chart lookup logic (Infield Back/In)
- Implemented all 13 result handlers (gb_result_1 through gb_result_13)
- Added DECIDE mechanic support for interactive runner advancement decisions
- Implemented double play probability calculation with positioning modifiers
- Created 30 comprehensive unit tests covering all scenarios (100% passing)
Key Features:
- Supports Infield Back and Infield In defensive positioning
- Handles Corners In hybrid positioning (applies In rules to corner fielders)
- Conditional results based on hit location (middle IF, right side, corners)
- Force play detection and advancement logic
- Double play mechanics with probability-based success (45% base rate)
- Result types match official rulebook exactly (1-13)
Architecture:
- IntEnum for result types (type-safe, self-documenting)
- Comprehensive hit location tracking (1B, 2B, SS, 3B, P, C, LF, CF, RF)
- Dataclasses for movements (RunnerMovement, AdvancementResult)
- Probability modifiers: Infield In (-15%), hit location (±10%)
Testing:
- 30 unit tests covering chart lookup, all result types, and edge cases
- Double play probability validation
- All on-base codes (0-7) tested
- All groundball types (A, B, C) verified
Status: Week 7 Tasks 4 & 5 complete (~87% of Week 7 finished)
Next: Task 6 (PlayResolver Integration)
Related: #task4 #task5 #runner-advancement #double-play #week7