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>
23 KiB
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
Interactive REPL (Recommended)
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
psqlorredis-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:
- Creates game in database
- Generates 9-player lineups for both teams
- Starts the game
- 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], orresolve - 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-gameoruse-game - Used as default if
--game-idnot 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)
- No Lineup Management: Must set lineups via database directly
- No Player Data: Shows lineup IDs only (no names/stats)
- Simple Box Score: Only shows final scores (no detailed stats)
- No Substitutions: Cannot make mid-game substitutions
- 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_gamecommand (sets current)use_gamecommand (changes current)config --clearcommand (clears current)
Related Documentation
- 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
- "The defense needs to submit their decision" → Run
- 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:
-
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 fieldWhat 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
-
X-Check Help Documentation (8 commands documented)
roll_jump/test_jump- Stolen base jump dice testingroll_fielding/test_fielding- Fielding dice testingtest_location- Hit location distribution testingrollback- Undo playsforce_wild_pitch/force_passed_ball- Interrupt play testing- All commands now appear in
helpoutput
Bug Fixes:
-
GameState Structure - Updated for LineupPlayerState refactoring
display.py: Now accessesstate.current_batter.lineup_idinstead ofstate.current_batter_lineup_idrepl.py: Fixed typos (self.current_game → self.current_game_id)- All 105 terminal client tests passing
-
Game Recovery - Fixed validation error when loading saved games
- State manager now creates placeholder
current_batterduring recovery - Placeholder is corrected by
_prepare_next_play()after recovery
- State manager now creates placeholder
-
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_withterminal_client/commands.py- Added xcheck_position parameterterminal_client/help_text.py- Documented 8 X-Check commandsterminal_client/display.py- Fixed GameState referencesapp/core/game_engine.py- Added xcheck_position supportapp/core/play_resolver.py- Fixed DO3 batter advancementapp/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