strat-gameplay-webapp/backend/terminal_client/CLAUDE.md
Cal Corum c7b376df4f CLAUDE: Update documentation for GameState refactoring and X-Check testing
Updated CLAUDE.md files to document recent changes including the GameState
refactoring and new X-Check testing capabilities in the terminal client.

Changes to app/models/CLAUDE.md:
- Updated GameState field documentation
  - Replaced current_batter_lineup_id references with current_batter
  - Documented LineupPlayerState requirement for current players
  - Added comprehensive usage examples

- Added "Recent Updates" section documenting GameState refactoring
  - Before/after code examples showing migration path
  - Explanation of why the change was made
  - Migration notes for developers
  - List of all affected files (7 files updated)

Changes to terminal_client/CLAUDE.md:
- Added "2025-11-04: X-Check Testing & GameState Refactoring" section
  - New feature: resolve_with x-check <position> command
    - Complete X-Check resolution with defense tables and error charts
    - Shows all resolution steps with audit trail
    - Works with actual player ratings from PD API

  - Documented 8 X-Check commands now in help system
    - roll_jump / test_jump, roll_fielding / test_fielding
    - test_location, rollback, force_wild_pitch, force_passed_ball

  - Bug fixes documented
    - GameState structure updates (display.py, repl.py)
    - Game recovery fix (state_manager.py)
    - DO3 advancement fix (play_resolver.py)

  - Complete testing workflow examples
  - List of 7 files updated
  - Test coverage status (all passing)

- Updated footer: Last Updated 2025-11-04

These documentation updates provide clear migration guides for the GameState
refactoring and comprehensive examples for the new X-Check testing features.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-04 16:09:58 -06:00

23 KiB
Raw Blame History

Terminal Client - Game Engine Testing Tool

Overview

Interactive REPL (Read-Eval-Print Loop) terminal UI for testing the game engine directly without WebSockets or frontend dependencies. Built with Python's cmd module, Click, and Rich for a polished CLI experience.

Purpose: Rapid iteration on game engine development without needing to build/maintain a web frontend during core logic development.

Key Feature: Persistent in-memory state - the game engine state_manager stays loaded throughout your REPL session, allowing you to test gameplay flow without losing state between commands.

Usage Modes

Start the interactive shell for persistent in-memory state:

uv run python -m terminal_client
# Or: source .venv/bin/activate && python -m terminal_client

# Then type commands interactively:
⚾ > new_game
⚾ > defensive
⚾ > offensive
⚾ > resolve
⚾ > quick_play 10
⚾ > status
⚾ > quit

Advantages:

  • Game state stays in memory across commands
  • Fast iteration - no process startup overhead
  • Natural workflow like psql or redis-cli
  • Persistent event loop prevents database connection issues

Standalone Commands (Alternative)

Run individual commands with persistent config file:

uv run python -m terminal_client new-game
uv run python -m terminal_client defensive --alignment normal
uv run python -m terminal_client offensive --approach power
uv run python -m terminal_client resolve

Note: Config file (~/.terminal_client_config.json) remembers current game between commands.

Architecture

Design Decisions

Why Terminal UI?

  • Zero frontend overhead - test game logic immediately
  • No WebSocket complexity - direct function calls to GameEngine
  • Fast iteration - change code, test instantly
  • Easy debugging - logs and state visible in same terminal
  • CI/CD ready - can be scripted for automated testing

Why Click over Typer?

  • Already installed (FastAPI dependency)
  • Battle-tested and stable (v8.1.8)
  • Python 3.13 compatible
  • No version compatibility issues

Why Rich?

  • Beautiful formatted output (colors, panels, tables)
  • Clear game state visualization
  • Better testing UX than plain print statements

Why REPL (cmd module)?

  • Single persistent process - state manager stays in memory
  • Persistent event loop - no database connection conflicts
  • Command history and readline support
  • Tab completion (future enhancement)

Technology Stack

click==8.1.8      # CLI framework (already installed via FastAPI)
rich==13.9.4      # Terminal formatting and colors
cmd (stdlib)      # REPL framework (built into Python)

Project Structure

terminal_client/
├── __init__.py         # Package marker
├── __main__.py         # Entry point - routes to REPL or CLI
├── repl.py             # Interactive REPL with cmd.Cmd
├── main.py             # Click CLI standalone commands
├── display.py          # Rich formatting for game state
├── config.py           # Persistent configuration file manager
└── CLAUDE.md           # This file

REPL Commands Reference

All commands in interactive REPL mode. Use underscores (e.g., new_game not new-game).

new_game

Create a new game with lineups and start it (all-in-one).

⚾ > new_game [--league sba|pd] [--home-team N] [--away-team N]

Examples:
  new_game
  new_game --league pd
  new_game --home-team 5 --away-team 3

Automatically:

  1. Creates game in database
  2. Generates 9-player lineups for both teams
  3. Starts the game
  4. Sets as current game

defensive

Submit defensive decision.

⚾ > defensive [--alignment TYPE] [--infield DEPTH] [--outfield DEPTH] [--hold BASES]

Options:
  --alignment    normal, shifted_left, shifted_right, extreme_shift
  --infield      in, normal, back, double_play
  --outfield     in, normal, back
  --hold         Comma-separated bases (e.g., 1,3)

Examples:
  defensive
  defensive --alignment shifted_left
  defensive --infield double_play --hold 1,3

offensive

Submit offensive decision.

⚾ > offensive [--approach TYPE] [--steal BASES] [--hit-run] [--bunt]

Options:
  --approach     normal, contact, power, patient
  --steal        Comma-separated bases (e.g., 2,3)
  --hit-run      Enable hit-and-run
  --bunt         Attempt bunt

Examples:
  offensive
  offensive --approach power
  offensive --steal 2 --hit-run

resolve

Resolve the current play.

⚾ > resolve

Both defensive and offensive decisions must be submitted first. Displays play result with dice roll, runner advancement, and updated state.

status

Display current game state.

⚾ > status

Shows score, inning, outs, runners, and pending decisions.

quick_play

Auto-play multiple plays with default decisions.

⚾ > quick_play [COUNT]

Examples:
  quick_play        # Play 1 play
  quick_play 10     # Play 10 plays
  quick_play 27     # Play ~3 innings

Perfect for rapidly advancing game state for testing.

box_score

Display box score.

⚾ > box_score

list_games

List all games in state manager (in memory).

⚾ > list_games

Shows active games with current game marked.

use_game

Switch to a different game.

⚾ > use_game <game_id>

Example:
  use_game a1b2c3d4-e5f6-7890-abcd-ef1234567890

config

Show configuration.

⚾ > config

Displays config file path and current game.

clear

Clear the screen.

⚾ > clear

quit / exit

Exit the REPL.

⚾ > quit
⚾ > exit

Or press Ctrl+D.


Standalone Commands Reference

For running individual commands outside REPL mode.

new-game

uv run python -m terminal_client new-game [OPTIONS]

Options:
  --league TEXT        League (sba or pd) [default: sba]
  --game-id TEXT       Game UUID (auto-generated if not provided)
  --home-team INTEGER  Home team ID [default: 1]
  --away-team INTEGER  Away team ID [default: 2]

Example:
  uv run python -m terminal_client start-game --league sba
  uv run python -m terminal_client start-game --game-id <uuid> --home-team 5 --away-team 3

Defensive Decision

uv run python -m terminal_client defensive [OPTIONS]

Options:
  --game-id TEXT    Game UUID (uses current if not provided)
  --alignment TEXT  Defensive alignment [default: normal]
                    Values: normal, shifted_left, shifted_right, extreme_shift
  --infield TEXT    Infield depth [default: normal]
                    Values: in, normal, back, double_play
  --outfield TEXT   Outfield depth [default: normal]
                    Values: in, normal, back
  --hold TEXT       Comma-separated bases to hold (e.g., 1,3)

Example:
  uv run python -m terminal_client defensive --alignment shifted_left
  uv run python -m terminal_client defensive --infield double_play --hold 1,3

Offensive Decision

uv run python -m terminal_client offensive [OPTIONS]

Options:
  --game-id TEXT  Game UUID (uses current if not provided)
  --approach TEXT Batting approach [default: normal]
                  Values: normal, contact, power, patient
  --steal TEXT    Comma-separated bases to steal (e.g., 2,3)
  --hit-run       Hit-and-run play (flag)
  --bunt          Bunt attempt (flag)

Example:
  uv run python -m terminal_client offensive --approach power
  uv run python -m terminal_client offensive --steal 2 --hit-run

Resolve Play

uv run python -m terminal_client resolve [OPTIONS]

Options:
  --game-id TEXT  Game UUID (uses current if not provided)

Example:
  uv run python -m terminal_client resolve

Resolves the current play using submitted defensive and offensive decisions. Displays full play result with dice rolls, runner advancement, and updated game state.

Game Status

uv run python -m terminal_client status [OPTIONS]

Options:
  --game-id TEXT  Game UUID (uses current if not provided)

Example:
  uv run python -m terminal_client status

Box Score

uv run python -m terminal_client box-score [OPTIONS]

Options:
  --game-id TEXT  Game UUID (uses current if not provided)

Example:
  uv run python -m terminal_client box-score

Quick Play

uv run python -m terminal_client quick-play [OPTIONS]

Options:
  --count INTEGER   Number of plays to execute [default: 1]
  --game-id TEXT    Game UUID (uses current if not provided)

Example:
  uv run python -m terminal_client quick-play --count 10
  uv run python -m terminal_client quick-play --count 27  # Full inning

Use Case: Rapidly advance game state for testing specific scenarios (e.g., test 9th inning logic).

Submits default decisions (normal alignment, normal approach) and auto-resolves plays.

List Games

uv run python -m terminal_client list-games

Example:
  uv run python -m terminal_client list-games

Shows all active games in the state manager.

Use Game

uv run python -m terminal_client use-game <game_id>

Example:
  uv run python -m terminal_client use-game a1b2c3d4-e5f6-7890-abcd-ef1234567890

Switch to a different game (sets as "current" game for subsequent commands).

Usage Patterns

Typical REPL Testing Workflow

# Start REPL
uv run python -m terminal_client
# Or: source .venv/bin/activate && python -m terminal_client

# Create and play a game
⚾ > new_game
⚾ > defensive
⚾ > offensive
⚾ > resolve
⚾ > status

# Continue playing
⚾ > defensive --alignment shifted_left
⚾ > offensive --approach power
⚾ > resolve

# Or use quick-play to auto-advance
⚾ > quick_play 10

# Check final state
⚾ > status
⚾ > quit

Advantages: Game state stays in memory throughout the session!

Testing Specific Scenarios

# Test defensive shifts
uv run python -m terminal_client start-game
uv run python -m terminal_client defensive --alignment shifted_left --infield double_play
uv run python -m terminal_client offensive
uv run python -m terminal_client resolve

# Test stealing
uv run python -m terminal_client start-game
uv run python -m terminal_client defensive
uv run python -m terminal_client offensive --steal 2,3  # Double steal
uv run python -m terminal_client resolve

# Test hit-and-run
uv run python -m terminal_client defensive
uv run python -m terminal_client offensive --hit-run
uv run python -m terminal_client resolve

# Advance to late game quickly
uv run python -m terminal_client start-game
uv run python -m terminal_client quick-play --count 50  # ~6 innings
uv run python -m terminal_client status

Multiple Games

# Start game 1
uv run python -m terminal_client start-game
# ... play some ...

# Start game 2
uv run python -m terminal_client start-game
# ... play some ...

# List all games
uv run python -m terminal_client list-games

# Switch back to game 1
uv run python -m terminal_client use-game <game-1-uuid>
uv run python -m terminal_client status

Display Features

Game State Panel

  • Game Info: UUID, league, status
  • Score: Away vs Home
  • Inning: Current inning and half
  • Outs: Current out count
  • Runners: Bases occupied with lineup IDs
  • Current Players: Batter, pitcher (by lineup ID)
  • Pending Decision: Enhanced display showing what action is needed next (Added 2025-10-28)
    • Shows "⚠️ WAITING FOR ACTION" header when play is pending
    • Displays specific message: "The defense needs to submit their decision" or "Ready to resolve play"
    • Shows exact command to run next: defensive [OPTIONS], offensive [OPTIONS], or resolve
    • Color-coded for clarity (yellow warning + cyan command hints)
  • Last Play: Result description

Play Result Panel

  • Outcome: Hit type (GB, FB, LD, etc.)
  • Result Description: Human-readable play result
  • Dice Roll: Actual d20 roll with context
  • Outs/Runs: Changes from play
  • Runner Movement: Base-by-base advancement
  • Updated Score: Current score after play

Color Coding

  • Green: Success messages, runs scored
  • Red: Error messages, outs recorded
  • Blue: Info messages
  • ⚠️ Yellow: Warning messages
  • Cyan: Game state highlights

Implementation Details

Persistent Event Loop (REPL Mode)

The REPL uses a single persistent event loop for the entire session:

def __init__(self):
    # Create persistent event loop
    self.loop = asyncio.new_event_loop()
    asyncio.set_event_loop(self.loop)

def _run_async(self, coro):
    # Reuse same loop for all commands
    return self.loop.run_until_complete(coro)

def do_quit(self, arg):
    # Clean up on exit
    self.loop.close()

Why: Prevents database connection pool conflicts that occur when creating new event loops for each command.

Game State Management

REPL Mode: Game state manager stays in memory throughout the session.

  • In-memory state persists across all commands
  • O(1) state lookups
  • Perfect for testing gameplay flow

Standalone Mode: Uses persistent config file (~/.terminal_client_config.json).

  • Stores current game UUID between command invocations
  • Set automatically by new-game or use-game
  • Used as default if --game-id not specified

Async Command Pattern

All commands use the same async pattern:

@cli.command()
@click.option('--game-id', default=None)
def some_command(game_id):
    async def _some_command():
        gid = UUID(game_id) if game_id else get_current_game()
        # ... async operations ...
        state = await game_engine.some_method(gid)
        display.show_something(state)

    asyncio.run(_some_command())

This allows direct async calls to GameEngine without WebSocket overhead.

Error Handling

  • Click Abort: Raises click.Abort() on errors (clean exit)
  • Rich Display: All errors shown with colored formatting
  • Logger: Exceptions logged with full traceback
  • User-Friendly: Clear error messages (not stack traces)

Testing with Terminal Client

Unit Test Scenarios

# Test game startup validation
uv run python -m terminal_client start-game
# Should fail: No lineups set

# Test invalid decisions
uv run python -m terminal_client defensive --alignment invalid_value
# Should fail: ValidationError

# Test resolve without decisions
uv run python -m terminal_client resolve
# Should fail: No decisions submitted

Integration Test Scenarios

# Full game flow
uv run python -m terminal_client start-game
# ... set lineups via database directly ...
uv run python -m terminal_client start-game  # Retry
uv run python -m terminal_client quick-play --count 100
uv run python -m terminal_client status
# Verify: Game status = "completed"

# State persistence
uv run python -m terminal_client start-game
# Note the game UUID
uv run python -m terminal_client quick-play --count 10
# Kill terminal
# Restart and use same UUID
uv run python -m terminal_client use-game <uuid>
uv run python -m terminal_client status
# Verify: State recovered from database

Limitations

Current Limitations (Phase 2)

  1. No Lineup Management: Must set lineups via database directly
  2. No Player Data: Shows lineup IDs only (no names/stats)
  3. Simple Box Score: Only shows final scores (no detailed stats)
  4. No Substitutions: Cannot make mid-game substitutions
  5. No AI Decisions: All decisions manual (no AI opponent)

Future Enhancements (Post-MVP)

  • Interactive lineup builder
  • Player name/stat display from league API
  • Full box score with batting stats
  • Substitution commands
  • AI decision simulation
  • Play-by-play export
  • Replay mode for completed games

Development Notes

Adding New Commands

# In main.py
@cli.command('new-command')
@click.option('--some-option', default='value', help='Description')
def new_command(some_option):
    """Command description for help text."""
    async def _new_command():
        # Implementation
        pass

    asyncio.run(_new_command())

Adding New Display Functions

# In display.py
def display_new_thing(data: SomeModel) -> None:
    """Display new thing with Rich formatting."""
    panel = Panel(
        Text("Content here"),
        title="[bold]Title[/bold]",
        border_style="color",
        box=box.ROUNDED
    )
    console.print(panel)

Logging

All commands use the module logger:

logger = logging.getLogger(f'{__name__}.main')

logger.info("Game started")
logger.error("Failed to resolve play", exc_info=True)

Logs appear in both terminal and backend/logs/app_YYYYMMDD.log.

Troubleshooting

Command Not Found

# Wrong
python terminal_client/main.py  # ❌

# Correct
uv run python -m terminal_client start-game  # ✅

Import Errors

# Ensure you're in backend directory
cd backend

# Recommended: Use UV (auto-activates venv)
uv run python -m terminal_client start-game

# Alternative: Manually activate venv
source .venv/bin/activate
python -m terminal_client start-game

Game Not Found

# Check active games
uv run python -m terminal_client list-games

# Use specific game ID
uv run python -m terminal_client status --game-id <uuid>

Validation Errors

Check logs for detailed error messages:

tail -f logs/app_$(date +%Y%m%d).log

Performance

REPL Mode

  • Startup Time: ~500ms (one-time on launch)
  • Command Execution: <50ms (in-memory state lookups)
  • State Display: <50ms (Rich rendering)
  • Quick Play (10 plays): ~4-5 seconds (includes 0.3s sleeps per play)
  • Memory: ~10MB for active game state

Standalone Mode

  • Per-Command Startup: ~500ms (process + imports)
  • Command Execution: <100ms (direct function calls)
  • Database Queries: 50-100ms (config file + state recovery)

REPL is significantly faster for iterative testing!

Configuration File

Location: ~/.terminal_client_config.json

{
  "current_game_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}

Automatically managed by:

  • new_game command (sets current)
  • use_game command (changes current)
  • config --clear command (clears current)
  • Game Engine: ../app/core/game_engine.py
  • State Manager: ../app/core/state_manager.py
  • Game Models: ../app/models/game_models.py
  • Backend Guide: ../CLAUDE.md

Recent Updates

2025-10-28: Enhanced Status Display

Improvement: Added user-friendly pending action guidance to status command.

Changes:

  • Status display now shows prominent "⚠️ WAITING FOR ACTION" section when play is pending
  • Provides specific guidance based on game state:
    • "The defense needs to submit their decision" → Run defensive [OPTIONS]
    • "The offense needs to submit their decision" → Run offensive [OPTIONS]
    • "Ready to resolve play - both teams have decided" → Run resolve
  • Command hints color-coded (yellow warnings + cyan command text + green command names)
  • Makes testing workflow clearer by showing exactly what to do next

Location: terminal_client/display.py:75-97

Usage Example:

⚾ > status

╭─────────────────────────── Game State ───────────────────────────╮
│ ...                                                               │
│                                                                   │
│ ⚠️  WAITING FOR ACTION                                            │
│ ────────────────────────────────────────                          │
│ The defense needs to submit their decision.                       │
│ Run: defensive [OPTIONS]                                          │
│                                                                   │
╰───────────────────────────────────────────────────────────────────╯

2025-11-04: X-Check Testing & GameState Refactoring

Major Update: Added complete X-Check defensive play testing capabilities and fixed GameState structure.

New Features:

  1. X-Check Resolution Testing - resolve_with x-check <position>

    • Test complete X-Check defensive plays with actual player ratings
    • Includes defense range tables, error charts, SPD tests, G2#/G3# conversions
    • Shows full XCheckResult audit trail with all resolution steps
    ⚾ > defensive
    ⚾ > offensive
    ⚾ > resolve_with x-check SS    # Test X-Check to shortstop
    ⚾ > resolve_with x-check LF    # Test X-Check to left field
    

    What It Shows:

    • Dice rolls (1d20 range + 3d6 error)
    • Defender ratings (range/error from PD API or defaults)
    • Defense table lookup (G1, G2, F3, SI2, DO3, etc.)
    • SPD test results (if applicable)
    • G2#/G3# conversion (playing in or holding runners)
    • Error chart lookup (NO, E1, E2, E3, RP)
    • Final outcome with error modifiers
    • Complete runner advancement
  2. X-Check Help Documentation (8 commands documented)

    • roll_jump / test_jump - Stolen base jump dice testing
    • roll_fielding / test_fielding - Fielding dice testing
    • test_location - Hit location distribution testing
    • rollback - Undo plays
    • force_wild_pitch / force_passed_ball - Interrupt play testing
    • All commands now appear in help output

Bug Fixes:

  1. GameState Structure - Updated for LineupPlayerState refactoring

    • display.py: Now accesses state.current_batter.lineup_id instead of state.current_batter_lineup_id
    • repl.py: Fixed typos (self.current_game → self.current_game_id)
    • All 105 terminal client tests passing
  2. Game Recovery - Fixed validation error when loading saved games

    • State manager now creates placeholder current_batter during recovery
    • Placeholder is corrected by _prepare_next_play() after recovery
  3. DO3 Advancement - Fixed incorrect batter advancement

    • DO3 now correctly: Batter reaches 2B (it's a double), runners advance 3 bases
    • Was incorrectly sending batter to 3B

Testing Workflow:

# Complete X-Check testing workflow
⚾ > new_game
⚾ > defensive --infield in          # Set defensive positioning
⚾ > offensive --approach power      # Set offensive approach
⚾ > resolve_with x-check 2B         # Test X-Check to second base

# See complete resolution:
# - Defense table: d20=15 + range=4 → G2
# - Error chart: 3d6=8 vs error=12 → NO
# - Final: G2 + NO = groundout to 2B
# - Runner advancement based on G2 tables

Files Updated:

  • terminal_client/repl.py - Added x-check parsing to resolve_with
  • terminal_client/commands.py - Added xcheck_position parameter
  • terminal_client/help_text.py - Documented 8 X-Check commands
  • terminal_client/display.py - Fixed GameState references
  • app/core/game_engine.py - Added xcheck_position support
  • app/core/play_resolver.py - Fixed DO3 batter advancement
  • app/core/runner_advancement.py - Updated all 17 GameState references

Test Coverage:

  • All 34 runner_advancement tests passing
  • All 105 terminal_client tests passing
  • All 26 state_manager tests passing

Created: 2025-10-26 Author: Claude Purpose: Testing tool for Phase 2 game engine development Status: Complete - Interactive REPL with persistent state and full X-Check testing! Last Updated: 2025-11-04