# X-Check Frontend Integration Guide ## Overview X-Check defensive plays are now fully integrated into the WebSocket `play_resolved` event. When a play results in an X-Check (defensive position check), the event will include detailed resolution data that the UI should display to players. **Phase**: 3E-Final - WebSocket Integration **Date**: 2025-11-03 **Status**: ✅ Complete (Backend), ⏳ Pending (Frontend) --- ## What is X-Check? X-Check is a defensive play resolution system where: 1. Ball is hit to a specific defensive position (SS, LF, 3B, etc.) 2. Server rolls 1d20 (defense range table) + 3d6 (error chart) 3. Defender's range and error ratings determine the outcome 4. Result can be an out, hit, or error with detailed breakdown **Example**: Grounder to shortstop → SS range 4 → d20=12 → "Routine groundout" → Out recorded --- ## WebSocket Event Structure ### Event: `play_resolved` This existing event now includes **optional** X-Check details when applicable. **Full Event Structure**: ```javascript { // Standard fields (always present) "game_id": "123e4567-e89b-12d3-a456-426614174000", "play_number": 15, "outcome": "x_check", // PlayOutcome enum value "hit_location": "SS", // Position where ball was hit "description": "X-Check SS: G2 → G2 + NO = groundball_b", "outs_recorded": 1, "runs_scored": 0, "batter_result": null, // null = out, 1-4 = base reached "runners_advanced": [], "is_hit": false, "is_out": true, "is_walk": false, "roll_id": "abc123def456", // X-Check details (OPTIONAL - only present for defensive plays) "x_check_details": { // Defensive player info "position": "SS", // SS, LF, 3B, etc. "defender_id": 42, // Lineup ID of defender "defender_range": 4, // 1-5 (adjusted for playing in) "defender_error_rating": 12, // 0-25 (lower is better) // Dice rolls "d20_roll": 12, // 1-20 (defense table lookup) "d6_roll": 10, // 3-18 (error chart lookup) // Resolution steps "base_result": "G2", // Initial result from defense table "converted_result": "G2", // After SPD test and G2#/G3# conversion "error_result": "NO", // Error type: NO, E1, E2, E3, RP "final_outcome": "groundball_b", // Final PlayOutcome enum value "hit_type": "g2_no_error", // Combined result string // Optional: SPD test (if base_result was 'SPD') "spd_test_roll": null, // 1-20 if SPD test was needed "spd_test_target": null, // Target number for SPD test "spd_test_passed": null // true/false if test was needed } } ``` --- ## Frontend Implementation ### 1. Detecting X-Check Plays ```javascript socket.on('play_resolved', (data) => { // Check if this play was an X-Check if (data.outcome === 'x_check' && data.x_check_details) { // This is a defensive play - show X-Check UI displayXCheckResult(data); } else { // Normal play - show standard result displayStandardPlayResult(data); } }); ``` ### 2. Displaying X-Check Details **Basic Display** (minimum viable): ```javascript function displayXCheckResult(data) { const xcheck = data.x_check_details; console.log(`⚾ X-Check to ${xcheck.position}`); console.log(` Defender Range: ${xcheck.defender_range}`); console.log(` Rolls: d20=${xcheck.d20_roll}, 3d6=${xcheck.d6_roll}`); console.log(` Result: ${xcheck.base_result} → ${xcheck.converted_result}`); console.log(` Error: ${xcheck.error_result}`); console.log(` Outcome: ${data.description}`); // Update game UI updateGameState({ outs: data.outs_recorded, runs: data.runs_scored, description: data.description }); } ``` **Enhanced Display** (with animations): ```javascript function displayXCheckResult(data) { const xcheck = data.x_check_details; // 1. Show fielder animation animateFielder(xcheck.position, xcheck.defender_id); // 2. Show dice rolls with delay setTimeout(() => { showDiceRoll('Defense Roll', xcheck.d20_roll); showDiceRoll('Error Roll', xcheck.d6_roll); }, 500); // 3. Show defensive rating overlay setTimeout(() => { showDefenderStats({ position: xcheck.position, range: xcheck.defender_range, error_rating: xcheck.defender_error_rating, defender_id: xcheck.defender_id }); }, 1500); // 4. Show result progression setTimeout(() => { showResultFlow({ base: xcheck.base_result, converted: xcheck.converted_result, error: xcheck.error_result, final: data.description }); }, 2500); // 5. Update game state setTimeout(() => { updateGameState({ outs: data.outs_recorded, runs: data.runs_scored, description: data.description }); }, 3500); } ``` ### 3. Defender Stats Display Show defender's ratings when X-Check is triggered: ```javascript function showDefenderStats(stats) { const defender = getPlayerById(stats.defender_id); return `
${defender.name}
${stats.position}
${stats.range}/5 ${renderRatingBars(stats.range, 5)}
${stats.error_rating}/25 ${renderRatingBars(25 - stats.error_rating, 25)}
`; } ``` ### 4. Result Flow Visualization Show the resolution steps: ```javascript function showResultFlow(result) { return `
Defense Table
${result.base}
${result.converted !== result.base ? `
Converted
${result.converted}
` : ''}
+
Error Check
${result.error}
=
Result
${result.final}
`; } ``` --- ## Error Result Types The `error_result` field can be one of: | Code | Meaning | Effect | |------|---------|--------| | `NO` | No error | Clean play | | `E1` | Minor error | +1 base advancement | | `E2` | Moderate error | +2 base advancement | | `E3` | Major error | +3 base advancement | | `RP` | Rare play | Treat as E3 | **Important**: If `base_result` was an out (FO, PO) and `error_result` is E1-E3/RP, the error **overrides the out** and batter reaches base safely. --- ## Base Result Codes Common result codes from the defense table: ### Groundball Results - `G1`, `G2`, `G3`: Groundball outcomes (1=easy, 3=hard) - `G2#`, `G3#`: Hash results (convert to SI2 if defender holding runner or playing in) ### Flyball Results - `F1`, `F2`, `F3`: Flyball outcomes (1=deep, 3=shallow) - `FO`: Flyout - `PO`: Popout ### Hit Results - `SI1`, `SI2`: Singles (1=standard, 2=enhanced advancement) - `DO2`, `DO3`: Doubles (2=to second, 3=to third) - `TR3`: Triple ### Special Results - `SPD`: Speed test (requires batter's speed rating) --- ## Example Scenarios ### Scenario 1: Clean Out ```json { "outcome": "x_check", "description": "X-Check SS: G2 → G2 + NO = groundball_b", "x_check_details": { "position": "SS", "d20_roll": 8, "d6_roll": 10, "defender_range": 4, "defender_error_rating": 8, "base_result": "G2", "converted_result": "G2", "error_result": "NO", "final_outcome": "groundball_b" } } ``` **UI Display**: "Grounder to shortstop. Clean play. Batter out." --- ### Scenario 2: Error on Out ```json { "outcome": "error", "description": "X-Check 3B: PO → PO + E2 = error", "outs_recorded": 0, "batter_result": 2, // Reached 2nd base "x_check_details": { "position": "3B", "d20_roll": 15, "d6_roll": 16, "defender_range": 3, "defender_error_rating": 18, "base_result": "PO", "converted_result": "PO", "error_result": "E2", "final_outcome": "error" } } ``` **UI Display**: "Pop up to third base. Error! Ball dropped. Batter advances to second." --- ### Scenario 3: Hit with Error ```json { "outcome": "single_2", "description": "X-Check RF: SI2 → SI2 + E1 = single_2_plus_error_1", "outs_recorded": 0, "runs_scored": 1, "batter_result": 2, // Batter to 2nd (single + error) "x_check_details": { "position": "RF", "d20_roll": 18, "d6_roll": 15, "defender_range": 2, "defender_error_rating": 12, "base_result": "SI2", "converted_result": "SI2", "error_result": "E1", "final_outcome": "single_2" } } ``` **UI Display**: "Single to right field. Fielding error! Batter advances to second. Runner scores from 2nd." --- ### Scenario 4: Hash Conversion ```json { "outcome": "single_2", "description": "X-Check 2B: G3# → SI2 + NO = single_2_no_error", "x_check_details": { "position": "2B", "d20_roll": 19, "d6_roll": 8, "defender_range": 4, // Was 3, but playing in (+1) "defender_error_rating": 5, "base_result": "G3#", "converted_result": "SI2", // Converted because defender holding runner "error_result": "NO", "final_outcome": "single_2" } } ``` **UI Display**: "Grounder to second base. Defender holding runner. Batter beats the throw. Single." --- ## Testing X-Check UI ### Manual Testing Steps 1. **Start a game** and get to a play with runners on base 2. **Submit X-Check outcome**: `outcome: "x_check"`, `hit_location: "SS"` 3. **Verify event received** with `x_check_details` field 4. **Check UI displays**: - Defender's name and ratings - Dice roll values - Resolution steps (base → converted → + error → final) - Final game state update ### Test Cases ```javascript // Test 1: Clean out testXCheck({ outcome: "x_check", hit_location: "SS", expected: { hasXCheckDetails: true, errorResult: "NO", outsRecorded: 1, runsScored: 0 } }); // Test 2: Error on out testXCheck({ outcome: "x_check", hit_location: "3B", expected: { hasXCheckDetails: true, errorResult: "E2", outsRecorded: 0, batter_result: 2 // Batter reached 2nd on error } }); // Test 3: Hit with error testXCheck({ outcome: "x_check", hit_location: "RF", expected: { hasXCheckDetails: true, errorResult: "E1", batter_result: 2, // Single + error = 2nd base runsScored: 1 // Runner scored from 2nd } }); ``` --- ## League Differences ### SBA League - Uses **default ratings** (range=3, error=15) for all defenders - X-Check still works, just with generic ratings - UI should still display X-Check details ### PD League - Uses **actual player ratings** from PD API - Range: 1-5 (varies by player and position) - Error: 0-25 (varies by player and position) - Ratings loaded at game start and cached - UI should highlight superior/poor defensive ratings --- ## UI Components Checklist ### Minimum Viable (Phase 1) - [ ] Detect X-Check plays in `play_resolved` event - [ ] Display defender's name and position - [ ] Show dice roll results (d20, 3d6) - [ ] Display final outcome description - [ ] Update game state (outs, runs, bases) ### Enhanced (Phase 2) - [ ] Animated fielder sprites - [ ] Defender rating bars (range, error) - [ ] Result flow visualization (base → converted → + error → final) - [ ] Error type indicators (E1/E2/E3/RP with color coding) - [ ] SPD test display (when applicable) ### Polish (Phase 3) - [ ] Sound effects for different outcomes - [ ] Particle effects for errors - [ ] Detailed play-by-play log with X-Check breakdowns - [ ] Hover tooltips explaining ratings - [ ] Mobile-optimized compact view --- ## Common Pitfalls ### ❌ Don't Assume X-Check Details Exist ```javascript // BAD: Will crash if not an X-Check const position = data.x_check_details.position; // GOOD: Check first if (data.x_check_details) { const position = data.x_check_details.position; } ``` ### ❌ Don't Ignore Error Overrides ```javascript // BAD: Assumes outcome matches base_result if (xcheck.base_result === 'PO') { showOut(); // Wrong if error_result is E1-E3! } // GOOD: Use final_outcome or check error_result if (data.is_out) { showOut(); } ``` ### ❌ Don't Hardcode Position Labels ```javascript // BAD: Doesn't handle all positions const positionName = xcheck.position === 'SS' ? 'Shortstop' : 'Unknown'; // GOOD: Use position map const POSITION_NAMES = { 'P': 'Pitcher', 'C': 'Catcher', '1B': 'First Base', '2B': 'Second Base', '3B': 'Third Base', 'SS': 'Shortstop', 'LF': 'Left Field', 'CF': 'Center Field', 'RF': 'Right Field' }; const positionName = POSITION_NAMES[xcheck.position]; ``` --- ## Questions? **Backend Developer**: See `app/websocket/handlers.py` lines 369-388 **X-Check Logic**: See `app/core/play_resolver.py` lines 590-785 **Data Models**: See `app/models/game_models.py` lines 242-289 **Phase 3E-Final Docs**: See `.claude/implementation/NEXT_SESSION.md` --- **Last Updated**: 2025-11-03 **Author**: Claude (Phase 3E-Final) **Status**: ✅ Backend Complete, ⏳ Frontend Pending