Implemented complete WebSocket integration for real-time player substitutions.
System is now 80% complete - only tests remain.
## WebSocket Events Implemented (600 lines)
### Event Handlers (backend/app/websocket/handlers.py):
1. request_pinch_hitter - Pinch hitter substitution
- Validates: game_id, player_out_lineup_id, player_in_card_id, team_id
- Executes: SubstitutionManager.pinch_hit()
- Broadcasts: player_substituted (all clients), substitution_confirmed (requester)
- Error codes: MISSING_FIELD, INVALID_FORMAT, NOT_CURRENT_BATTER, etc.
2. request_defensive_replacement - Defensive replacement
- Additional field: new_position (P, C, 1B, 2B, 3B, SS, LF, CF, RF)
- Executes: SubstitutionManager.defensive_replace()
- Same broadcast pattern as pinch hitter
3. request_pitching_change - Pitching change
- Validates minimum batters faced (handled in SubstitutionManager)
- Executes: SubstitutionManager.change_pitcher()
- Broadcasts new pitcher to all clients
4. get_lineup - Get active lineup for team
- Returns: lineup_data with all active players
- Uses: StateManager cache (O(1)) or database fallback
- Purpose: UI refresh after substitutions
### Event Pattern (follows existing handlers):
- Validate inputs (UUID format, required fields, game exists)
- Create SubstitutionManager instance with DatabaseOperations
- Execute substitution (validate → DB → state)
- Broadcast player_substituted to game room
- Send substitution_confirmed to requester
- Error handling with specific error codes
### Events Emitted:
- player_substituted (broadcast) - Includes: type, lineup IDs, position, batting_order
- substitution_confirmed (requester) - Success confirmation with new_lineup_id
- substitution_error (requester) - Validation error with error code
- lineup_data (requester) - Active lineup response
- error (requester) - Generic error
## Documentation Updates (350 lines)
### backend/app/websocket/CLAUDE.md:
- Complete handler documentation with examples
- Event data structures and response formats
- Error code reference (MISSING_FIELD, INVALID_FORMAT, NOT_CURRENT_BATTER, etc.)
- Client integration examples (JavaScript)
- Complete workflow diagrams
- Updated event summary table (+8 events)
- Updated Common Imports section
### .claude/implementation/ updates:
- NEXT_SESSION.md: Marked Task 1 complete, updated to 80% done
- SUBSTITUTION_SYSTEM_SUMMARY.md: Added WebSocket section, updated status
- GAMESTATE_REFACTOR_PLAN.md: Marked complete
- PHASE_3_OVERVIEW.md: Updated all phases to reflect completion
- phase-3e-COMPLETED.md: Created comprehensive completion doc
## Architecture
### DB-First Pattern (maintained):
```
Client Request → WebSocket Handler
↓
SubstitutionManager
├─ SubstitutionRules.validate_*()
├─ DatabaseOperations.create_substitution() (DB first!)
├─ StateManager.update_lineup_cache()
└─ Update GameState if applicable
↓
Success Responses
├─ player_substituted (broadcast to room)
└─ substitution_confirmed (to requester)
```
### Error Handling:
- Three-tier: ValidationError, GameValidationError, Exception
- Specific error codes for each failure type
- User-friendly error messages
- Comprehensive logging at each step
## Status Update
**Phase 3F Substitution System**: 80% Complete
- ✅ Core logic (SubstitutionRules, SubstitutionManager) - 1,027 lines
- ✅ Database operations (create_substitution, get_eligible_substitutes)
- ✅ WebSocket events (4 handlers) - 600 lines
- ✅ Documentation (350 lines)
- ⏳ Unit tests (20% remaining) - ~300 lines needed
- ⏳ Integration tests - ~400 lines needed
**Phase 3 Overall**: ~97% Complete
- Phase 3A-D (X-Check Core): 100%
- Phase 3E (GameState, Ratings, Redis, Testing): 100%
- Phase 3F (Substitutions): 80%
## Files Modified
backend/app/websocket/handlers.py (+600 lines)
backend/app/websocket/CLAUDE.md (+350 lines)
.claude/implementation/NEXT_SESSION.md (updated progress)
.claude/implementation/SUBSTITUTION_SYSTEM_SUMMARY.md (added WebSocket section)
.claude/implementation/GAMESTATE_REFACTOR_PLAN.md (marked complete)
.claude/implementation/PHASE_3_OVERVIEW.md (updated all phases)
.claude/implementation/phase-3e-COMPLETED.md (new file, 400+ lines)
## Next Steps
Remaining work (2-3 hours):
1. Unit tests for SubstitutionRules (~300 lines)
- 15+ pinch hitter tests
- 12+ defensive replacement tests
- 10+ pitching change tests
2. Integration tests for SubstitutionManager (~400 lines)
- Full DB + state sync flow
- State recovery verification
- Error handling and rollback
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
388 lines
12 KiB
Markdown
388 lines
12 KiB
Markdown
# 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
|