# Manual vs Auto Mode X-Check Workflows ## Overview Paper Dynasty supports two play resolution modes that affect how X-Check defensive plays are resolved: - **Manual Mode** (Primary): Players read physical cards and submit outcomes - **Auto Mode** (Rare, PD only): System auto-generates outcomes from digitized ratings This document explains how X-Check resolution differs between these modes and provides implementation guidance. **Author**: Claude **Date**: 2025-11-03 **Phase**: 3E-Final --- ## Game Modes Comparison | Feature | Manual Mode | Auto Mode | |---------|-------------|-----------| | **Availability** | All leagues (SBA, PD) | PD only | | **Card Reading** | Required (physical cards) | Not required | | **Outcome Source** | Player submission | Auto-generated | | **Position Ratings** | Used for X-Check resolution | Used for all outcomes | | **Dice Rolls** | Server-rolled, broadcast to all | Server-rolled internally | | **Player Interaction** | High (read card, submit) | Low (watch results) | | **Speed** | Slower (human input) | Faster (instant) | | **Use Case** | Most games, casual play | Simulations, AI opponents | --- ## Manual Mode Workflow **Most common** - Players own physical cards and read results. ### X-Check Flow (Manual Mode) ``` 1. Server broadcasts dice rolled ↓ 2. Players read their cards ↓ 3. Player sees "X-Check SS" on card ↓ 4. Player submits outcome="x_check", hit_location="SS" ↓ 5. Server resolves X-Check - Rolls d20 (defense table) - Rolls 3d6 (error chart) - Gets defender's range/error from Redis cache - Looks up result: G2 + NO = Groundball B ↓ 6. Server broadcasts play_resolved with x_check_details ↓ 7. UI shows defender ratings, dice rolls, resolution steps ``` ### Player Experience **Step 1: Dice Rolled** ```javascript // Player receives: { "event": "dice_rolled", "d6_one": 4, // Card column: 4-6 = pitcher card "d6_two_total": 8, // Card row: 8 "chaos_d20": 15, // For splits "message": "Dice rolled - read your card and submit outcome" } ``` **Player Action**: 1. Check pitcher card (d6_one = 4) 2. Look at row 8 (d6_two_total = 8) 3. Read result: "X-Check SS" **Step 2: Submit Outcome** ```javascript socket.emit('submit_manual_outcome', { game_id: "123e4567-...", outcome: "x_check", hit_location: "SS" // Important: must specify position }); ``` **Step 3: Receive Result** ```javascript socket.on('play_resolved', (data) => { // Standard fields console.log(data.outcome); // "groundball_b" console.log(data.description); // "X-Check SS: G2 → G2 + NO = groundball_b" // X-Check details (show in UI) if (data.x_check_details) { const xcheck = data.x_check_details; // Show defender console.log(`Defender at ${xcheck.position}`); console.log(`Range: ${xcheck.defender_range}/5`); console.log(`Error: ${xcheck.defender_error_rating}/25`); // Show resolution console.log(`Rolls: d20=${xcheck.d20_roll}, 3d6=${xcheck.d6_roll}`); console.log(`Result: ${xcheck.base_result} → ${xcheck.converted_result} + ${xcheck.error_result}`); console.log(`Final: ${xcheck.final_outcome}`); } }); ``` ### UI Recommendations (Manual Mode) **Show X-Check Details After Resolution**: - Display defender's name, position, and ratings - Show dice roll results with icons/animations - Visualize resolution flow: base → converted → + error → final - Use color coding for error types (NO=green, E1-E3=yellow-red, RP=purple) **Example UI Components**: ```jsx function XCheckResult({ xcheck, defender }) { return (
); } ``` --- ## Auto Mode Workflow **Rare** - Used for simulations, AI opponents, or when players don't have physical cards. ### X-Check Flow (Auto Mode) ``` 1. Server auto-generates outcome from ratings ↓ 2. System determines outcome="x_check" from probabilities ↓ 3. Server resolves X-Check - Rolls d20 (defense table) - Rolls 3d6 (error chart) - Gets defender's range/error from Redis cache - Looks up result: F2 + E1 = Error ↓ 4. Server broadcasts play_resolved with x_check_details ↓ 5. UI shows complete resolution (no player input needed) ``` ### System Experience **No Player Input Required**: - System rolls dice internally - Generates outcome from `PdPitchingRating.xcheck_ss` probability - Resolves X-Check automatically - Broadcasts final result **Backend Flow**: ```python # Auto mode resolution result = play_resolver.resolve_auto_play( state=state, batter=pd_batter, # PdPlayer with ratings pitcher=pd_pitcher, # PdPlayer with ratings defensive_decision=def_decision, offensive_decision=off_decision ) # If outcome is X_CHECK: # - X-Check already resolved internally # - result.x_check_details populated # - Broadcast includes full details ``` ### Enabling Auto Mode **Game Creation**: ```python # Set auto_mode on game creation state = GameState( game_id=game_id, league_id="pd", # Must be PD league auto_mode=True, # Enable auto resolution home_team_id=1, away_team_id=2 ) ``` **League Support**: ```python from app.config import get_league_config config = get_league_config("pd") # Check if auto mode supported if config.supports_auto_mode(): # Can use auto mode pass else: # Must use manual mode raise ValueError("Auto mode not supported for this league") ``` **Requirements for Auto Mode**: 1. League must be PD (has digitized card data) 2. All players must have batting/pitching ratings loaded 3. `supports_auto_mode()` returns True ### UI Recommendations (Auto Mode) **Show Results Immediately**: - No waiting for player input - Animate resolution quickly - Focus on visual clarity over interactivity **Example UI Flow**: ```jsx function AutoModePlay({ playResult }) { // Show play animation immediately useEffect(() => { if (playResult.x_check_details) { // Animate X-Check resolution animateXCheck(playResult.x_check_details); } else { // Animate standard play animatePlay(playResult); } }, [playResult]); return (
{playResult.x_check_details && ( )}
); } ``` --- ## Implementation Differences ### Backend **PlayResolver Initialization**: ```python # Manual mode (default) manual_resolver = PlayResolver(league_id="pd", auto_mode=False) # Auto mode (rare) auto_resolver = PlayResolver(league_id="pd", auto_mode=True) # Raises error for SBA sba_auto = PlayResolver(league_id="sba", auto_mode=True) # ❌ ValueError ``` **Resolution Methods**: ```python # Manual mode result = resolver.resolve_manual_play( submission=ManualOutcomeSubmission( outcome="x_check", hit_location="SS" ), state=state, defensive_decision=def_decision, offensive_decision=off_decision, ab_roll=ab_roll ) # Auto mode result = resolver.resolve_auto_play( state=state, batter=pd_player, # Needs PdPlayer with ratings pitcher=pd_pitcher, # Needs PdPlayer with ratings defensive_decision=def_decision, offensive_decision=off_decision ) # Note: ab_roll generated internally for auto mode ``` ### Frontend **Detect Mode**: ```javascript // Check game mode const isAutoMode = gameState.auto_mode; if (isAutoMode) { // Don't show "submit outcome" UI // Just listen for play_resolved events } else { // Show "roll dice" and "submit outcome" buttons // Show card reading instructions } ``` **Event Handling**: ```javascript // Manual mode: Wait for dice, then submit socket.on('dice_rolled', (data) => { if (!isAutoMode) { showCardReaderUI(data); } }); // Both modes: Handle play results socket.on('play_resolved', (data) => { if (data.x_check_details) { displayXCheckResult(data); } else { displayStandardResult(data); } }); ``` --- ## X-Check Resolution Details **Both modes use identical X-Check resolution**: 1. **Defense Table Lookup**: d20 + defender range → base result 2. **SPD Test** (if needed): Batter speed vs target 3. **Hash Conversion**: G2#/G3# → SI2 if conditions met 4. **Error Chart Lookup**: 3d6 + defender error → error result 5. **Final Outcome**: Combine base + error → final outcome **Key Point**: The X-Check resolution logic is **mode-agnostic**. The only difference is: - Manual: Triggered by player submitting "x_check" - Auto: Triggered by system generating "x_check" from probabilities --- ## Position Ratings Usage ### Manual Mode **When Used**: Only for X-Check resolution ```python # Normal play (strikeout, walk, single): # - No position ratings needed # - Outcome from player submission # X-Check play: # - Get defender at position # - Use defender.position_rating.range # - Use defender.position_rating.error # - Resolve with tables ``` ### Auto Mode **When Used**: For ALL play generation + X-Check resolution ```python # All plays: # - Use PdBattingRating to generate outcome probabilities # - Use PdPitchingRating.xcheck_* for defensive play chances # - Roll weighted random based on probabilities # If X-Check generated: # - Same X-Check resolution as manual mode # - Use defender.position_rating.range/error ``` --- ## Testing Considerations ### Manual Mode Tests **What to Test**: - Player submits "x_check" with valid hit_location - Server resolves X-Check correctly - x_check_details included in broadcast - Defender ratings retrieved from Redis - Error handling for missing ratings **Mock Data**: ```python # Simulate manual submission submission = ManualOutcomeSubmission( outcome="x_check", hit_location="SS" ) # Mock pending dice roll state.pending_manual_roll = ab_roll # Resolve result = await game_engine.resolve_manual_play( game_id=game_id, ab_roll=ab_roll, outcome=PlayOutcome.X_CHECK, hit_location="SS" ) # Verify X-Check details present assert result.x_check_details is not None assert result.x_check_details.position == "SS" ``` ### Auto Mode Tests **What to Test**: - Auto resolver generates X-Check outcomes - X-Check probability matches PdPitchingRating - Resolution includes x_check_details - Works without player input **Mock Data**: ```python # Create auto resolver resolver = PlayResolver(league_id="pd", auto_mode=True) # Create players with ratings batter = PdPlayer(...) pitcher = PdPlayer(...) # Resolve automatically result = resolver.resolve_auto_play( state=state, batter=batter, pitcher=pitcher, defensive_decision=def_decision, offensive_decision=off_decision ) # Verify auto-generated assert result.ab_roll is not None # Generated internally ``` --- ## Common Scenarios ### Scenario 1: Manual Game, X-Check to SS **Flow**: 1. Player rolls dice → d6_one=5, d6_two_total=11 2. Reads pitcher card row 11 → "X-Check SS" 3. Submits outcome="x_check", hit_location="SS" 4. Server: - Rolls d20=14, 3d6=9 - Gets SS defender (range=4, error=8) - Defense table[14][4] = "G2" - Error chart[8][9] = "NO" - Final: G2 + NO = Groundball B 5. Broadcasts result with x_check_details 6. UI shows: "Grounder to short. Routine play. Out recorded." ### Scenario 2: Manual Game, X-Check with Error **Flow**: 1. Player submits outcome="x_check", hit_location="3B" 2. Server: - Rolls d20=15, 3d6=17 - Gets 3B defender (range=3, error=18) - Defense table[15][3] = "PO" - Error chart[18][17] = "E2" - Final: PO + E2 = Error (error overrides out) 3. Batter reaches 2nd base safely 4. UI shows: "Pop up to third. Error! Ball dropped. Batter to second." ### Scenario 3: Auto Game, X-Check Generated **Flow**: 1. System generates outcome from PdPitchingRating 2. xcheck_2b = 8% → Randomly selects X-Check 2B 3. Server auto-resolves: - Rolls d20=10, 3d6=7 - Gets 2B defender (range=5, error=3) - Defense table[10][5] = "G1" - Error chart[3][7] = "NO" - Final: G1 + NO = Groundball A 4. Broadcasts with x_check_details 5. UI animates: "Grounder to second. Easy play. Out." ### Scenario 4: Mixed Game (Manual Batters, Auto Pitchers) **Not Currently Supported** - A game is either fully manual or fully auto. **Future Enhancement**: Could support per-team mode selection: - Away team (manual): Reads cards, submits outcomes - Home team (auto): System generates outcomes --- ## Configuration Reference ### Game Settings ```python class GameState: league_id: str # 'sba' or 'pd' auto_mode: bool # True = auto, False = manual (default) home_team_is_ai: bool # AI teams use auto mode internally away_team_is_ai: bool ``` ### League Config ```python class PdConfig(BaseLeagueConfig): def supports_auto_mode(self) -> bool: return True # PD has digitized ratings def supports_position_ratings(self) -> bool: return True # PD has position ratings class SbaConfig(BaseLeagueConfig): def supports_auto_mode(self) -> bool: return False # SBA uses physical cards only def supports_position_ratings(self) -> bool: return False # SBA uses defaults ``` --- ## Summary **Manual Mode** (Primary): - Players read physical cards - Submit outcomes via WebSocket - X-Check resolved server-side - Shows defender ratings in UI - **Best for**: Normal gameplay **Auto Mode** (Rare): - System generates outcomes from ratings - No player input needed - X-Check auto-resolved - Faster, less interactive - **Best for**: Simulations, AI opponents **X-Check Resolution**: Identical in both modes - Uses same defense/error tables - Uses same Redis-cached position ratings - Returns same x_check_details structure - UI displays same information **Key Difference**: How the X-Check is **triggered** - Manual: Player submits after reading card - Auto: System generates from probabilities --- ## Related Documentation - **WebSocket Events**: `app/websocket/handlers.py` - **Frontend Guide**: `app/websocket/X_CHECK_FRONTEND_GUIDE.md` - **Play Resolution**: `app/core/play_resolver.py` - **Position Ratings**: `app/services/position_rating_service.py` - **League Configs**: `app/config/league_configs.py` --- **Last Updated**: 2025-11-03 **Phase**: 3E-Final **Status**: ✅ Complete