# 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