strat-gameplay-webapp/.claude/implementation/NEXT_SESSION.md
Cal Corum 5ebbd9ebda CLAUDE: Update project plan for next session - Substitution system completion
Generated comprehensive NEXT_SESSION.md with:
- Complete analysis of Phase 3E-Final and Substitution core logic
- 4 detailed tasks with code examples and acceptance criteria
- Outstanding questions and architecture decisions documented
- 6-7 hour estimate to complete remaining 40%

Next session will complete:
- WebSocket substitution events (2-3 hrs)
- Validation unit tests (2 hrs)
- Manager integration tests (2 hrs)
- API documentation (1 hr)

Phase 3 will be ~99% complete after next session.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-04 00:08:53 -06:00

27 KiB

Next Session Plan - Phase 3: Substitution System Completion

Current Status: Phase 3 - 60% Substitution System Complete Last Commit: d1619b4 - "CLAUDE: Phase 3 - Substitution System Core Logic" Date: 2025-11-03 Remaining Work: 40% (WebSocket events, tests, documentation)


Quick Start for Next AI Agent

🎯 Where to Begin

  1. Read this entire document first
  2. Review .claude/implementation/SUBSTITUTION_SYSTEM_SUMMARY.md for architecture overview
  3. Review core logic files in "Files to Review Before Starting" section
  4. Start with Task 1: WebSocket Events (highest user impact)
  5. Run test commands after each task

📍 Current Context

We just completed the core business logic for the substitution system (1,027 lines). The validation rules, database operations, and state management are fully implemented and follow the established DB-first pattern. What remains is integration (WebSocket events for real-time gameplay) and verification (comprehensive testing).

Phase 3E (X-Check system with position ratings and Redis caching) is 100% complete. Phase 3 overall is at ~98% for X-Check work, with substitutions now being the active focus.


What We Just Completed

1. Phase 3E-Final: Redis Caching & X-Check WebSocket Integration (adf7c76)

  • app/services/redis_client.py - Async Redis client with connection pooling
  • app/services/position_rating_service.py - Migrated from in-memory to Redis (760x speedup)
  • app/main.py - Redis startup/shutdown lifecycle
  • app/config.py - Added redis_url setting
  • app/websocket/handlers.py - Enhanced submit_manual_outcome with X-Check details
  • app/websocket/X_CHECK_FRONTEND_GUIDE.md - 517 lines of frontend integration docs
  • app/websocket/MANUAL_VS_AUTO_MODE.md - 588 lines of workflow documentation
  • tests/integration/test_xcheck_websocket.py - WebSocket integration tests (2 tests)
  • test_redis_cache.py - Live Redis integration test (10 verification steps)
  • Performance: 760x speedup (0.274s API → 0.000361s Redis)
  • Tests: 2/2 WebSocket tests passing, Redis live test validated

2. Substitution System Core Logic (d1619b4) - Just Completed

SubstitutionRules - Validation Logic (345 lines)

  • backend/app/core/substitution_rules.py - Complete validation for all substitution types
  • 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 (2 simultaneous subs)
  • 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 unless injury)
    • Position validation (valid baseball positions)
    • Timing checks (warnings for mid-inning changes)

SubstitutionManager - Orchestration Logic (552 lines)

  • backend/app/core/substitution_manager.py - Orchestrates DB-first pattern
  • Methods:
    • pinch_hit() - Execute pinch hitter substitution
    • defensive_replace() - Execute defensive replacement
    • change_pitcher() - Execute pitching change
  • Pattern: Validate (in-memory) → Update DATABASE FIRST → Update state SECOND → Return result
  • Features:
    • Comprehensive error handling with error codes
    • Automatic lineup cache updates
    • Current player references updated (batter/pitcher/catcher in GameState)
    • Detailed logging at every step

Database Operations (+115 lines)

  • backend/app/database/operations.py - Added substitution-specific operations
  • Methods:
    • create_substitution() - Marks old player inactive, creates new lineup entry
    • 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

Model Enhancements (+15 lines)

  • backend/app/models/game_models.py - Added helper method
  • get_player_by_card_id() in TeamLineupState - Find player by card/player ID

3. Documentation

  • .claude/implementation/SUBSTITUTION_SYSTEM_SUMMARY.md - Complete architecture and status doc
  • Detailed implementation notes, integration points, testing strategy

Key Architecture Decisions Made

1. DB-First Pattern for Substitutions

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 survives server restarts; in-memory state is just cache

2. Immutable Lineup History

  • Never delete lineup entries
  • Use is_active flag for current lineup
  • replacing_id creates complete audit trail

Benefits: Can reconstruct game at any point; rollback support; complete substitution history

3. Position Flexibility (MVP)

  • Don't enforce strict position eligibility in MVP
  • Any player can play any position
  • Can add validation post-MVP

Rationale: Simplifies MVP; users know their rosters; reduces complexity

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

5. Error Codes for All Validation Failures

Every validation failure returns both human-readable message and machine-readable code:

  • NOT_CURRENT_BATTER, PLAYER_ALREADY_OUT, NOT_IN_ROSTER, ALREADY_ACTIVE, etc.

Rationale: Frontend can show appropriate UI based on error type


Blockers Encountered 🚧

None - development proceeded smoothly.

Core logic implementation was straightforward following established patterns. All validation rules are clear and testable. Database operations follow proven async session pattern.


Outstanding Questions

1. Should we implement double switch in MVP?

Context: Double switch is complex (2 simultaneous subs with batting order swap). Core logic exists but no WebSocket events yet. Decision Needed: MVP or post-MVP? Recommendation: Post-MVP - rarely used, complex UX, single subs cover 95% of use cases

2. Position eligibility validation - when to add?

Context: Currently any player can play any position (MVP simplification). Decision Needed: Add position eligibility check post-MVP? Implication: Would require position lists on player models, more complex validation

3. Automatic pitcher fatigue system - scope?

Context: Database has is_fatigued flag on Lineup model. Decision Needed: Implement automatic fatigue detection or manual only? Recommendation: Manual only for MVP - managers know their staff better than algorithm


Tasks for Next Session

Task 1: WebSocket Substitution Events (2-3 hours)

File(s): backend/app/websocket/handlers.py

Goal: Add WebSocket event handlers for pinch hitting, defensive replacements, and pitching changes. Enable real-time substitution notifications to all game clients.

Changes:

  1. Import SubstitutionManager at top of file:
from app.core.substitution_manager import SubstitutionManager
  1. Initialize in GameEngine (if not already there):
# In GameEngine.__init__()
self.substitution_manager = SubstitutionManager(self.db_ops)
  1. Add event handler for pinch hitter:
@sio.event
async def request_pinch_hitter(sid, data):
    """
    Request pinch hitter substitution.

    Data:
        - game_id: UUID
        - player_out_lineup_id: int
        - player_in_card_id: int
        - team_id: int
    """
    try:
        game_id = UUID(data['game_id'])

        # Verify user has access to game and team
        # TODO: Add authorization check

        result = await game_engine.substitution_manager.pinch_hit(
            game_id=game_id,
            player_out_lineup_id=data['player_out_lineup_id'],
            player_in_card_id=data['player_in_card_id'],
            team_id=data['team_id']
        )

        if result.success:
            # Broadcast to all clients in game
            await manager.emit_to_game(
                game_id,
                'player_substituted',
                {
                    'type': 'pinch_hitter',
                    'player_out_lineup_id': result.player_out_lineup_id,
                    'player_in_card_id': result.player_in_card_id,
                    'new_lineup_id': result.new_lineup_id,
                    'position': result.new_position,
                    'batting_order': result.new_batting_order,
                    'team_id': data['team_id']
                }
            )

            # Send success to requester
            await sio.emit('substitution_confirmed', {
                'new_lineup_id': result.new_lineup_id
            }, room=sid)
        else:
            # Send error to requester
            await sio.emit('substitution_error', {
                'error': result.error_message,
                'code': result.error_code
            }, room=sid)

    except Exception as e:
        logger.error(f"Error in pinch hitter request: {e}", exc_info=True)
        await sio.emit('error', {'message': str(e)}, room=sid)
  1. Add similar handlers for:

    • request_defensive_replacement (similar structure)
    • request_pitching_change (similar structure)
  2. Add get_lineup event for UI to request current lineup:

@sio.event
async def get_lineup(sid, data):
    """Get current active lineup for a team."""
    try:
        game_id = UUID(data['game_id'])
        team_id = data['team_id']

        lineup = state_manager.get_lineup(game_id, team_id)

        if lineup:
            await sio.emit('lineup_data', {
                'team_id': team_id,
                'players': [p.model_dump() for p in lineup.players if p.is_active]
            }, room=sid)
        else:
            await sio.emit('error', {
                'message': f'Lineup not found for team {team_id}'
            }, room=sid)

    except Exception as e:
        logger.error(f"Error getting lineup: {e}", exc_info=True)
        await sio.emit('error', {'message': str(e)}, room=sid)

Files to Update:

  • backend/app/websocket/handlers.py - Add 4 event handlers

Test Command:

# Manual test with terminal client
python -m terminal_client
# Then in REPL, test substitution flow

Acceptance Criteria:

  • request_pinch_hitter event handler implemented
  • request_defensive_replacement event handler implemented
  • request_pitching_change event handler implemented
  • get_lineup event handler implemented
  • Successful substitutions broadcast to all clients
  • Errors sent only to requester
  • No crashes on invalid data

Task 2: Substitution Validation Tests (2 hours)

File(s): backend/tests/unit/core/test_substitution_rules.py (NEW)

Goal: Comprehensive unit tests for all validation rules in SubstitutionRules class.

Changes:

Create new test file with pytest structure:

"""
Unit tests for SubstitutionRules validation logic.
"""
import pytest
from uuid import uuid4
from app.core.substitution_rules import SubstitutionRules, ValidationResult
from app.models.game_models import GameState, LineupPlayerState, TeamLineupState

@pytest.fixture
def game_state():
    """Create test game state."""
    state = GameState(
        game_id=uuid4(),
        league_id='sba',
        home_team_id=1,
        away_team_id=2,
        current_batter_lineup_id=10
    )
    return state

@pytest.fixture
def roster():
    """Create test roster."""
    players = [
        LineupPlayerState(lineup_id=10, card_id=101, position='CF', batting_order=1, is_active=True),
        LineupPlayerState(lineup_id=11, card_id=102, position='SS', batting_order=2, is_active=True),
        LineupPlayerState(lineup_id=12, card_id=103, position='P', batting_order=9, is_active=True),
        # Bench players
        LineupPlayerState(lineup_id=20, card_id=201, position='CF', batting_order=None, is_active=False),
        LineupPlayerState(lineup_id=21, card_id=202, position='SS', batting_order=None, is_active=False),
    ]
    return TeamLineupState(team_id=1, players=players)

class TestPinchHitterValidation:
    def test_success(self, game_state, roster):
        """Test successful pinch hitter validation."""
        player_out = roster.get_player_by_lineup_id(10)

        result = SubstitutionRules.validate_pinch_hitter(
            state=game_state,
            player_out=player_out,
            player_in_card_id=201,
            roster=roster
        )

        assert result.is_valid
        assert result.error_message is None

    def test_not_current_batter(self, game_state, roster):
        """Test pinch hit fails when not current batter."""
        player_out = roster.get_player_by_lineup_id(11)  # Not current batter

        result = SubstitutionRules.validate_pinch_hitter(
            state=game_state,
            player_out=player_out,
            player_in_card_id=201,
            roster=roster
        )

        assert not result.is_valid
        assert result.error_code == "NOT_CURRENT_BATTER"

    def test_substitute_not_in_roster(self, game_state, roster):
        """Test pinch hit fails when substitute not in roster."""
        player_out = roster.get_player_by_lineup_id(10)

        result = SubstitutionRules.validate_pinch_hitter(
            state=game_state,
            player_out=player_out,
            player_in_card_id=999,  # Not in roster
            roster=roster
        )

        assert not result.is_valid
        assert result.error_code == "NOT_IN_ROSTER"

    def test_substitute_already_active(self, game_state, roster):
        """Test pinch hit fails when substitute already in game."""
        player_out = roster.get_player_by_lineup_id(10)

        result = SubstitutionRules.validate_pinch_hitter(
            state=game_state,
            player_out=player_out,
            player_in_card_id=102,  # Already active
            roster=roster
        )

        assert not result.is_valid
        assert result.error_code == "ALREADY_ACTIVE"

class TestDefensiveReplacementValidation:
    # Similar structure, test all validation paths
    pass

class TestPitchingChangeValidation:
    # Similar structure, test minimum batters faced, etc.
    pass

Files to Create:

  • backend/tests/unit/core/test_substitution_rules.py (new file, ~300 lines)

Test Command:

cd backend
pytest tests/unit/core/test_substitution_rules.py -v

Acceptance Criteria:

  • 15+ tests for pinch hitter validation (all validation paths)
  • 12+ tests for defensive replacement validation
  • 10+ tests for pitching change validation
  • 5+ tests for double switch validation (optional - can defer)
  • All tests passing
  • Edge cases covered (player already out, not in roster, already active, etc.)

Task 3: Substitution Manager Integration Tests (2 hours)

File(s): backend/tests/integration/test_substitution_manager.py (NEW)

Goal: Integration tests for SubstitutionManager that verify full DB + state sync flow.

Changes:

Create new integration test file:

"""
Integration tests for SubstitutionManager.

Tests full flow: validation → DB → state sync
"""
import pytest
from uuid import uuid4
from app.core.substitution_manager import SubstitutionManager
from app.core.state_manager import state_manager
from app.database.operations import DatabaseOperations
from app.models.game_models import GameState

@pytest.fixture
async def setup_game():
    """Create test game with lineups in database."""
    db_ops = DatabaseOperations()
    game_id = uuid4()

    # Create game in DB
    await db_ops.create_game(
        game_id=game_id,
        league_id='sba',
        home_team_id=1,
        away_team_id=2,
        game_mode='friendly',
        visibility='public'
    )

    # Create lineup entries in DB
    lineup_ids = {}
    for i in range(1, 10):
        lineup_id = await db_ops.add_sba_lineup_player(
            game_id=game_id,
            team_id=1,
            player_id=100 + i,
            position='P' if i == 1 else f'{i}B',
            batting_order=i,
            is_starter=True
        )
        lineup_ids[i] = lineup_id

    # Add bench players
    bench_ids = {}
    for i in range(1, 4):
        bench_id = await db_ops.add_sba_lineup_player(
            game_id=game_id,
            team_id=1,
            player_id=200 + i,
            position='OF',
            batting_order=None,
            is_starter=False
        )
        bench_ids[i] = bench_id

    # Create game state
    state = await state_manager.create_game(
        game_id=game_id,
        league_id='sba',
        home_team_id=1,
        away_team_id=2
    )
    state.current_batter_lineup_id = lineup_ids[1]
    state_manager.update_state(game_id, state)

    yield game_id, lineup_ids, bench_ids

    # Cleanup
    state_manager.remove_game(game_id)

@pytest.mark.asyncio
async def test_pinch_hit_full_flow(setup_game):
    """Test complete pinch hit flow: validation → DB → state."""
    game_id, lineup_ids, bench_ids = await setup_game

    db_ops = DatabaseOperations()
    manager = SubstitutionManager(db_ops)

    # Execute substitution
    result = await manager.pinch_hit(
        game_id=game_id,
        player_out_lineup_id=lineup_ids[1],
        player_in_card_id=201,
        team_id=1
    )

    # Verify result
    assert result.success
    assert result.new_lineup_id is not None
    assert result.player_out_lineup_id == lineup_ids[1]

    # Verify database updated
    lineup = await db_ops.get_active_lineup(game_id, team_id=1)
    active_ids = [p.id for p in lineup]
    assert result.new_lineup_id in active_ids
    assert lineup_ids[1] not in active_ids

    # Verify state updated
    state = state_manager.get_state(game_id)
    assert state.current_batter_lineup_id == result.new_lineup_id

    # Verify lineup cache updated
    roster = state_manager.get_lineup(game_id, team_id=1)
    old_player = roster.get_player_by_lineup_id(lineup_ids[1])
    assert not old_player.is_active

    new_player = roster.get_player_by_lineup_id(result.new_lineup_id)
    assert new_player.is_active
    assert new_player.card_id == 201

# Add similar tests for defensive_replace and change_pitcher

Files to Create:

  • backend/tests/integration/test_substitution_manager.py (new file, ~400 lines)

Test Command:

cd backend
pytest tests/integration/test_substitution_manager.py -v

Acceptance Criteria:

  • Test pinch_hit full flow (DB + state sync verified)
  • Test defensive_replace full flow
  • Test change_pitcher full flow
  • Test validation failures (error handling)
  • Test state recovery after substitution
  • All tests passing

Task 4: Documentation - API Reference (1 hour)

File(s): .claude/implementation/SUBSTITUTION_API_REFERENCE.md (NEW)

Goal: Create comprehensive API documentation for substitution system usage.

Changes:

Create new documentation file with:

  1. WebSocket Events Reference:

    • Event names
    • Request data format
    • Response data format
    • Error codes
    • Example payloads
  2. Python API Reference:

    • SubstitutionManager methods
    • SubstitutionRules methods
    • Database operations
    • Example code snippets
  3. Workflows:

    • Pinch hitter flow diagram
    • Defensive replacement flow
    • Pitching change flow
    • Error handling patterns
  4. Integration Guide:

    • How to integrate with GameEngine
    • How to handle substitutions in UI
    • State management considerations

Files to Create:

  • .claude/implementation/SUBSTITUTION_API_REFERENCE.md (~300 lines)

Acceptance Criteria:

  • All WebSocket events documented with examples
  • All Python APIs documented
  • Error codes reference table included
  • Flow diagrams for each substitution type
  • Integration examples provided

Files to Review Before Starting

Critical files (understand before coding):

  1. backend/app/core/substitution_rules.py - All validation logic

    • Understand each validation method and error codes
  2. backend/app/core/substitution_manager.py - Orchestration logic

    • Understand DB-first pattern implementation
    • Note how state is updated after DB
  3. backend/app/database/operations.py:290-403 - DB operations

    • Review create_substitution() method
    • Note transaction handling
  4. backend/app/models/game_models.py:128-141 - Model helper

    • Review get_player_by_card_id() method
  5. backend/app/websocket/handlers.py - Existing WebSocket patterns

    • Understand error handling pattern
    • Note broadcast vs. emit_to_user patterns
  6. .claude/implementation/SUBSTITUTION_SYSTEM_SUMMARY.md - Architecture overview

    • Complete context on design decisions
  7. backend/app/core/game_engine.py:33-42 - GameEngine initialization

    • See where SubstitutionManager will be integrated

Verification Steps

After completing all tasks:

1. Run all tests:

cd backend
source venv/bin/activate

# Unit tests
pytest tests/unit/core/test_substitution_rules.py -v

# Integration tests
pytest tests/integration/test_substitution_manager.py -v

# All tests
pytest -v

2. Manual testing with terminal client:

python -m terminal_client

# In REPL:
⚾ > new_game
⚾ > status  # Note current batter lineup_id
⚾ > # Manual WebSocket test would go here (needs WebSocket client)

3. Verify database state:

-- Check lineup entries
SELECT id, card_id, position, batting_order, is_active, is_starter, entered_inning, replacing_id
FROM lineups
WHERE game_id = '<test_game_id>'
ORDER BY id;

-- Verify inactive players
SELECT * FROM lineups WHERE is_active = false;

4. Commit changes:

git add backend/app/websocket/handlers.py
git add backend/tests/unit/core/test_substitution_rules.py
git add backend/tests/integration/test_substitution_manager.py
git add .claude/implementation/SUBSTITUTION_API_REFERENCE.md
git commit -m "CLAUDE: Phase 3 - Complete Substitution System with WebSocket & Tests

- Add WebSocket event handlers for substitutions
- Comprehensive unit tests for validation rules (37+ tests)
- Integration tests for full DB + state flow (8+ tests)
- Complete API documentation with examples

Substitution system now 100% complete and production-ready.

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

Co-Authored-By: Claude <noreply@anthropic.com>"

Success Criteria

Phase 3 Substitution System will be 100% complete when:

  • 4 WebSocket event handlers implemented and tested
  • 37+ unit tests passing for SubstitutionRules
  • 8+ integration tests passing for SubstitutionManager
  • API documentation complete with examples
  • Manual testing successful via terminal client
  • Database verification confirms audit trail working
  • Git commit created

Overall Phase 3 Progress will be:

  • Phase 3E (X-Check): 100% complete
  • Phase 3F (Substitutions): 100% complete (after this session)
  • Phase 3 Overall: ~99% complete (only minor TODOs deferred to Phase 4+)

Quick Reference

Current Test Count: 327 tests (base), +45 expected after this session Last Test Run: All passing (2025-11-03) Branch: implement-phase-3 Python: 3.13.3 Virtual Env: backend/venv/

Key Imports for Next Session:

# WebSocket integration
from app.core.substitution_manager import SubstitutionManager
from app.core.state_manager import state_manager
from uuid import UUID

# Testing
import pytest
from app.core.substitution_rules import SubstitutionRules, ValidationResult
from app.models.game_models import GameState, LineupPlayerState, TeamLineupState

Recent Commit History (Last 10):

d1619b4 - CLAUDE: Phase 3 - Substitution System Core Logic (2 minutes ago)
adf7c76 - CLAUDE: Phase 3E-Final - Redis Caching & X-Check WebSocket Integration (66 minutes ago)
7d15018 - CLAUDE: Update documentation for Phase 3E-Main completion (2 hours ago)
02e816a - CLAUDE: Phase 3E-Main - Position Ratings Integration for X-Check Resolution (3 hours ago)
a55b31d - CLAUDE: Update documentation for Phase 3E-Prep completion (10 hours ago)
d560844 - CLAUDE: Phase 3E-Prep - Refactor GameState to use full LineupPlayerState objects (10 hours ago)
7417a3f - Offline catchup (11 hours ago)
683954f - CLAUDE: Update implementation notes to reflect Phase 2 completion (17 hours ago)
fc0e2f1 - CLAUDE: Integrate X-Check advancement with full GameState support (24 hours ago)
5f42576 - CLAUDE: Remove double-dipping on double play probability (24 hours ago)

Context for AI Agent Resume

If the next agent needs to understand the bigger picture:

  • Overall project: See @prd-web-scorecard-1.1.md and @CLAUDE.md
  • Architecture: See @.claude/implementation/00-index.md
  • Current phase details: See @.claude/implementation/03-gameplay-features.md
  • Substitution system: See @.claude/implementation/SUBSTITUTION_SYSTEM_SUMMARY.md

Critical files in current focus area:

  1. backend/app/core/substitution_rules.py - All validation logic (345 lines)
  2. backend/app/core/substitution_manager.py - Orchestration (552 lines)
  3. backend/app/database/operations.py - DB operations (with substitution methods)
  4. backend/app/websocket/handlers.py - Will add event handlers here
  5. backend/app/core/game_engine.py - GameEngine class
  6. backend/app/core/state_manager.py - StateManager singleton
  7. backend/app/models/game_models.py - Game state models
  8. backend/app/models/db_models.py - Lineup model with substitution fields
  9. .claude/implementation/SUBSTITUTION_SYSTEM_SUMMARY.md - Architecture doc
  10. backend/tests/unit/core/ - Where to add unit tests

What NOT to do:

  • Don't modify core validation logic - it's complete and follows baseball rules correctly
  • Don't change DB-first pattern - it's critical for data integrity
  • Don't add position eligibility validation yet - deferred to post-MVP
  • Don't implement double switch WebSocket events - deferred to post-MVP
  • Don't change database schema - all fields needed are already there

Patterns we've established:

  • DB-first: validate → DB → state → broadcast
  • Error codes for all validation failures
  • Comprehensive logging at every step
  • Immutable lineup history (never delete, use is_active flag)
  • Batting order inheritance for substitutions

Estimated Time for Next Session: 6-7 hours (2-3 WebSocket + 2 unit tests + 2 integration + 1 docs) Priority: Medium (completes substitution system, not blocking other work) Blocking Other Work: No (X-Check and core game engine are complete) Next Milestone After This: Phase 4 - Spectator Mode & Polish OR Phase 3 - AI Opponent (choose based on priority)