strat-gameplay-webapp/backend/app/websocket/MANUAL_VS_AUTO_MODE.md
Cal Corum adf7c7646d CLAUDE: Phase 3E-Final - Redis Caching & X-Check WebSocket Integration
Completed Phase 3E-Final with Redis caching upgrade and WebSocket X-Check
integration for real-time defensive play resolution.

## Redis Caching System

### New Files
- app/services/redis_client.py - Async Redis client with connection pooling
  * 10 connection pool size
  * Automatic connect/disconnect lifecycle
  * Ping health checks
  * Environment-configurable via REDIS_URL

### Modified Files
- app/services/position_rating_service.py - Migrated from in-memory to Redis
  * Redis key pattern: "position_ratings:{card_id}"
  * TTL: 86400 seconds (24 hours)
  * Graceful fallback if Redis unavailable
  * Individual and bulk cache clearing (scan_iter)
  * 760x performance improvement (0.274s API → 0.000361s Redis)

- app/main.py - Added Redis startup/shutdown events
  * Connect on app startup with settings.redis_url
  * Disconnect on shutdown
  * Warning logged if Redis connection fails

- app/config.py - Added redis_url setting
  * Default: "redis://localhost:6379/0"
  * Override via REDIS_URL environment variable

- app/services/__init__.py - Export redis_client

### Testing
- test_redis_cache.py - Live integration test
  * 10-step validation: connect, cache miss, cache hit, performance, etc.
  * Verified 760x speedup with player 8807 (7 positions)
  * Data integrity checks pass

## X-Check WebSocket Integration

### Modified Files
- app/websocket/handlers.py - Enhanced submit_manual_outcome handler
  * Serialize XCheckResult to JSON when present
  * Include x_check_details in play_resolved broadcast
  * Fixed bug: Use result.outcome instead of submitted outcome
  * Includes defender ratings, dice rolls, resolution steps

### New Files
- app/websocket/X_CHECK_FRONTEND_GUIDE.md - Comprehensive frontend documentation
  * Event structure and field definitions
  * Implementation examples (basic, enhanced, polished)
  * Error handling and common pitfalls
  * Test scenarios with expected data
  * League differences (SBA vs PD)
  * 500+ lines of frontend integration guide

- app/websocket/MANUAL_VS_AUTO_MODE.md - Workflow documentation
  * Manual mode: Players read cards, submit outcomes
  * Auto mode: System generates from ratings (PD only)
  * X-Check resolution comparison
  * UI recommendations for each mode
  * Configuration reference
  * Testing considerations

### Testing
- tests/integration/test_xcheck_websocket.py - WebSocket integration tests
  * Test X-Check play includes x_check_details 
  * Test non-X-Check plays don't include details 
  * Full event structure validation

## Performance Impact

- Redis caching: 760x speedup for position ratings
- WebSocket: No performance impact (optional field)
- Graceful degradation: System works without Redis

## Phase 3E-Final Progress

-  WebSocket event handlers for X-Check UI
-  Frontend integration documentation
-  Redis caching upgrade (from in-memory)
-  Redis connection pool in app lifecycle
-  Integration tests (2 WebSocket, 1 Redis)
-  Manual vs Auto mode workflow documentation

Phase 3E-Final: 100% Complete
Phase 3 Overall: ~98% Complete

## Testing Results

All tests passing:
- X-Check table tests: 36/36 
- WebSocket integration: 2/2 
- Redis live test: 10/10 steps 

## Configuration

Development:
  REDIS_URL=redis://localhost:6379/0  (Docker Compose)

Production options:
  REDIS_URL=redis://10.10.0.42:6379/0  (DB server)
  REDIS_URL=redis://your-redis-cloud.com:6379/0  (Managed)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-03 22:46:59 -06:00

14 KiB

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

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

socket.emit('submit_manual_outcome', {
  game_id: "123e4567-...",
  outcome: "x_check",
  hit_location: "SS"  // Important: must specify position
});

Step 3: Receive Result

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:

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:

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

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

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:

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:

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

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

// 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:

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

# 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

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

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

# 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

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

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

  • 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