# 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 <noreply@anthropic.com>
```

---

**Status**: 60% complete - Core logic done, WebSocket + tests remaining
**Next Session**: Implement WebSocket events and testing
