# Substitution System Implementation - Phase 3 Week 8 **Date**: 2025-11-04 (Updated) **Status**: WebSocket Integration Complete (4/5 phases done - 80% complete) **Estimated Time**: 8-9 hours completed, 2-3 hours remaining (tests only) ## Overview Implemented core substitution system for baseball gameplay following established DB-first pattern. ## Components Implemented ✅ ### 1. **SubstitutionRules** (validation logic) **File**: `backend/app/core/substitution_rules.py` (345 lines) Comprehensive baseball substitution validation: **Classes**: - `ValidationResult`: Data class for validation results - `SubstitutionRules`: Static validation methods **Methods**: - `validate_pinch_hitter()`: Validates pinch hitter substitutions - `validate_defensive_replacement()`: Validates defensive replacements - `validate_pitching_change()`: Validates pitching changes - `validate_double_switch()`: Validates complex double switches **Rules Enforced**: - ✅ No re-entry (once removed, cannot return) - ✅ Roster eligibility (must be on roster) - ✅ Active status (substitute must be inactive) - ✅ Current batter check (pinch hitter only for current batter) - ✅ Minimum batters faced (pitcher must face 1 batter) - ✅ Position validation (valid baseball positions) - ✅ Timing checks (with warnings for mid-inning changes) ### 2. **SubstitutionManager** (orchestration logic) **File**: `backend/app/core/substitution_manager.py` (552 lines) Orchestrates substitutions with DB-first pattern: **Classes**: - `SubstitutionResult`: Data class for operation results - `SubstitutionManager`: Main orchestration class **Methods**: - `pinch_hit()`: Execute pinch hitter substitution - `defensive_replace()`: Execute defensive replacement - `change_pitcher()`: Execute pitching change **Pattern** (DB-First): 1. Validate using in-memory state 2. Update DATABASE FIRST 3. Update in-memory state SECOND 4. Return result (WebSocket broadcast in handler) **Key Features**: - ✅ Comprehensive error handling - ✅ State + DB sync guaranteed - ✅ Automatic lineup cache updates - ✅ Current player references updated (batter/pitcher/catcher) - ✅ Logging at every step ### 3. **Database Operations** (persistence) **File**: `backend/app/database/operations.py` (extended) Added substitution-specific operations: **Methods Added**: - `create_substitution()`: Creates substitution in database - Marks old player inactive - Creates new lineup entry with metadata - Returns new lineup_id - `get_eligible_substitutes()`: Gets inactive players (potential subs) **Database Fields Used**: - `is_active`: Tracks current vs benched - `is_starter`: Original vs substitute - `entered_inning`: When player entered - `replacing_id`: Links to replaced player - `after_play`: Exact play number of substitution ### 4. **Model Enhancements** (helper methods) **File**: `backend/app/models/game_models.py` (modified) Added helper method to `TeamLineupState`: - `get_player_by_card_id()`: Find player by card/player ID ## Architecture Decisions ### 1. DB-First Pattern Following established game engine pattern: ``` 1. Validate (in-memory, fast) 2. Update DATABASE (source of truth) 3. Update STATE (cache) 4. Broadcast (WebSocket in handler) ``` **Rationale**: Database is source of truth, survives server restarts ### 2. Immutable Lineup History - Never delete lineup entries - Use `is_active` flag for current lineup - `replacing_id` creates complete audit trail **Benefits**: - Can reconstruct any moment in game history - Rollback support - Complete substitution tracking ### 3. Position Flexibility (MVP) - Don't enforce strict position eligibility in MVP - Any player can play any position - Can add position validation post-MVP **Rationale**: Simplifies MVP, users know their rosters ### 4. Batting Order Inheritance - Pinch hitter takes batting order of replaced player - Defensive replacement keeps batting order if in lineup - Double switch allows batting order changes **Rationale**: Matches real baseball rules ## Components Implemented ✅ (cont'd) ### 5. **WebSocket Events** (2025-11-04) - ✅ **COMPLETED** **File**: `backend/app/websocket/handlers.py` (+600 lines) Real-time substitution events for multiplayer gameplay: **Event Handlers**: - `request_pinch_hitter(sid, data)` - Pinch hitter substitution event - `request_defensive_replacement(sid, data)` - Defensive replacement event - `request_pitching_change(sid, data)` - Pitching change event - `get_lineup(sid, data)` - Get active lineup for team (UI refresh) **Event Pattern**: 1. Validate inputs (game_id, player_out_lineup_id, player_in_card_id, team_id) 2. Create SubstitutionManager instance 3. Execute substitution (validate → DB → state) 4. Broadcast `player_substituted` to all clients in game room 5. Send `substitution_confirmed` to requester 6. Error handling with specific error codes **Events Emitted**: - `player_substituted` - Broadcast to game room with substitution details - `substitution_confirmed` - Confirmation to requester with new lineup_id - `substitution_error` - Validation error to requester with error code - `lineup_data` - Active lineup data response - `error` - Generic error to requester **Error Codes**: - `MISSING_FIELD` - Required field not provided - `INVALID_FORMAT` - Invalid game_id UUID format - `NOT_CURRENT_BATTER` - Can only pinch hit for current batter - `PLAYER_ALREADY_OUT` - Player has already been removed - `NOT_IN_ROSTER` - Substitute not on team roster - `ALREADY_ACTIVE` - Substitute already in game **Documentation**: Added 350+ lines to `backend/app/websocket/CLAUDE.md` with: - Complete handler documentation - Event data structures - Client integration examples - Error code reference - Workflow diagrams ## What's NOT Implemented Yet ### 6. Testing (2-3 hours) - **REMAINING WORK** Need to write: - Unit tests for SubstitutionRules (~300 lines) - Test all validation paths (15+ pinch hitter, 12+ defensive, 10+ pitcher) - Edge cases (already out, not in roster, already active) - Integration tests for SubstitutionManager (~400 lines) - Test full DB + state sync flow - Verify state recovery after substitution - Test error handling and rollback - WebSocket event tests (optional, can defer) - Mock event testing - End-to-end flow testing ### 7. API Documentation (optional, 1 hour) Optional (WebSocket docs already complete): - Python API usage examples (already in code) - Additional flow diagrams - Troubleshooting guide ## Files Created/Modified ### Created: ``` backend/app/core/substitution_rules.py (345 lines) backend/app/core/substitution_manager.py (552 lines) .claude/implementation/SUBSTITUTION_SYSTEM_SUMMARY.md ``` ### Modified: ``` backend/app/models/game_models.py (+15 lines - helper method) backend/app/database/operations.py (+115 lines - DB operations) backend/app/websocket/handlers.py (+600 lines - WebSocket events) 2025-11-04 backend/app/websocket/CLAUDE.md (+350 lines - event documentation) 2025-11-04 ``` **Total**: ~1,977 lines of new code (core logic + WebSocket integration) ## Integration Points ### With Game Engine ```python from app.core.substitution_manager import SubstitutionManager from app.database.operations import DatabaseOperations # In GameEngine.__init__() self.substitution_manager = SubstitutionManager(self.db_ops) # Usage result = await self.substitution_manager.pinch_hit( game_id=game_id, player_out_lineup_id=current_batter_lineup_id, player_in_card_id=123, team_id=1 ) ``` ### With WebSocket (To be implemented) ```python # In handlers.py @sio.event async def request_pinch_hitter(sid, data): result = await game_engine.substitution_manager.pinch_hit(...) if result.success: # Broadcast to all clients await manager.broadcast_to_game( game_id, 'player_substituted', { 'type': 'pinch_hitter', 'player_out': result.player_out_lineup_id, 'player_in': result.player_in_card_id, 'new_lineup_id': result.new_lineup_id, 'updated_lineup': result.updated_lineup.model_dump() } ) else: await sio.emit('substitution_error', { 'error': result.error_message, 'code': result.error_code }, room=sid) ``` ## Success Criteria ### Completed ✅: - [x] Validation rules enforced - [x] DB-first pattern followed - [x] Database + state stay in sync - [x] Comprehensive error handling - [x] Audit trail (replacing_id, entered_inning, after_play) - [x] Logging at every step - [x] WebSocket events implemented ✅ (2025-11-04) - [x] Real-time lineup updates broadcast ✅ (2025-11-04) - [x] WebSocket event documentation complete ✅ (2025-11-04) ### Remaining ⏳ (20%): - [ ] Unit tests written (~300 lines needed) - [ ] Integration tests written (~400 lines needed) - [ ] Substitution history visible in UI (frontend work, deferred) ## Testing Strategy ### Unit Tests (SubstitutionRules) ```python def test_validate_pinch_hitter_not_current_batter() def test_validate_pinch_hitter_player_already_out() def test_validate_pinch_hitter_substitute_not_in_roster() def test_validate_pinch_hitter_substitute_already_active() def test_validate_pinch_hitter_success() # Similar for defensive_replacement and pitching_change ``` ### Integration Tests (SubstitutionManager) ```python async def test_pinch_hit_full_flow() async def test_defensive_replace_full_flow() async def test_change_pitcher_full_flow() async def test_substitution_updates_state_correctly() async def test_substitution_survives_recovery() ``` ### WebSocket Tests ```python async def test_pinch_hitter_event() async def test_substitution_broadcast() async def test_substitution_error_handling() ``` ## Performance Notes ### Expected Latency: - Validation: < 1ms (in-memory) - Database update: < 20ms (INSERT + UPDATE) - State update: < 1ms (in-memory) - **Total**: < 25ms for complete substitution ### Memory Impact: - Minimal (one additional LineupPlayerState object) - Old player remains in memory (marked inactive) ## Next Steps 1. **Add WebSocket Events** (2-3 hours) - Event handlers in `app/websocket/handlers.py` - Integrate SubstitutionManager - Broadcast to all clients 2. **Write Tests** (2-3 hours) - Unit tests for validation rules - Integration tests for manager - WebSocket event tests 3. **Documentation** (1 hour) - API usage guide - Event format reference - Error codes list - Example workflows ## Commit Message ``` CLAUDE: Phase 3 - Substitution System Core Logic Implemented comprehensive substitution system with DB-first pattern: ## Core Components (897 lines) 1. SubstitutionRules (345 lines) - Validates pinch hitter, defensive replacement, pitching change - Enforces no re-entry, roster eligibility, active status - Comprehensive error messages with error codes 2. SubstitutionManager (552 lines) - Orchestrates DB-first pattern: validate → DB → state - Handles pinch_hit, defensive_replace, change_pitcher - Automatic state sync and lineup cache updates 3. Database Operations (+115 lines) - create_substitution(): Creates sub with full metadata - get_eligible_substitutes(): Lists inactive players 4. Model Enhancements (+15 lines) - Added get_player_by_card_id() to TeamLineupState ## Key Features - ✅ DB-first pattern (database is source of truth) - ✅ Immutable lineup history (audit trail) - ✅ Comprehensive validation (8+ rule checks) - ✅ State + DB sync guaranteed - ✅ Error handling at every step - ✅ Detailed logging for debugging ## Architecture Decisions - Position flexibility (MVP - no eligibility check) - Batting order inheritance (pinch hitter takes spot) - No re-entry (matches real baseball rules) - Validation uses in-memory state (fast) ## Remaining Work - WebSocket event handlers (2-3 hours) - Comprehensive testing (2-3 hours) - API documentation (1 hour) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude ``` --- **Status**: 60% complete - Core logic done, WebSocket + tests remaining **Next Session**: Implement WebSocket events and testing