# 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 (
    <div className="xcheck-result">
      <DefenderCard
        name={defender.name}
        position={xcheck.position}
        range={xcheck.defender_range}
        error={xcheck.defender_error_rating}
      />

      <DiceRolls
        d20={xcheck.d20_roll}
        d6={xcheck.d6_roll}
      />

      <ResolutionFlow
        base={xcheck.base_result}
        converted={xcheck.converted_result}
        error={xcheck.error_result}
        final={xcheck.final_outcome}
      />

      <OutcomeDescription text={data.description} />
    </div>
  );
}
```

---

## 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 (
    <div className="auto-mode-result">
      <PlayAnimation result={playResult} />
      {playResult.x_check_details && (
        <XCheckBreakdown details={playResult.x_check_details} />
      )}
    </div>
  );
}
```

---

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