# 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 `
    <div class="xcheck-defender">
      <div class="defender-name">${defender.name}</div>
      <div class="defender-position">${stats.position}</div>
      <div class="defender-ratings">
        <div class="rating-range">
          <label>Range:</label>
          <span class="rating-value">${stats.range}/5</span>
          ${renderRatingBars(stats.range, 5)}
        </div>
        <div class="rating-error">
          <label>Error:</label>
          <span class="rating-value">${stats.error_rating}/25</span>
          ${renderRatingBars(25 - stats.error_rating, 25)}
        </div>
      </div>
    </div>
  `;
}
```

### 4. Result Flow Visualization

Show the resolution steps:

```javascript
function showResultFlow(result) {
  return `
    <div class="xcheck-flow">
      <div class="flow-step">
        <div class="step-label">Defense Table</div>
        <div class="step-value">${result.base}</div>
      </div>
      ${result.converted !== result.base ? `
        <div class="flow-arrow">→</div>
        <div class="flow-step">
          <div class="step-label">Converted</div>
          <div class="step-value">${result.converted}</div>
        </div>
      ` : ''}
      <div class="flow-arrow">+</div>
      <div class="flow-step">
        <div class="step-label">Error Check</div>
        <div class="step-value">${result.error}</div>
      </div>
      <div class="flow-arrow">=</div>
      <div class="flow-step final">
        <div class="step-label">Result</div>
        <div class="step-value">${result.final}</div>
      </div>
    </div>
  `;
}
```

---

## 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
