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

851 lines
23 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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:
```bash
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:
```bash
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
```python
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
```bash
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
```bash
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
```bash
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
```bash
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
```bash
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
```bash
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
```bash
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
```bash
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
```bash
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
```bash
# 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
```bash
# 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
```bash
# 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:
```python
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:
```python
@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
```bash
# 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
```bash
# 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
```python
# 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
```python
# 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:
```python
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
```bash
# Wrong
python terminal_client/main.py # ❌
# Correct
uv run python -m terminal_client start-game # ✅
```
### Import Errors
```bash
# 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
```bash
# 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:
```bash
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`
```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)
## 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`
- 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**:
```bash
⚾ > 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
```bash
> 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**:
```bash
# 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