# cardgame - Core Engine CLAUDE.md

<!-- Last verified: 2026-03-08 -->

## Overview

The core Essence Wars game engine. Handles game state, combat resolution, effect processing, and AI bots.

## Key Directories

| Path | Purpose |
|------|---------|
| `src/core/` | Game types, state, actions, combat |
| `src/dto.rs` | Shared DTOs for UI and MCP serialization |
| `src/bots/` | RandomBot, GreedyBot, MctsBot, AlphaBetaBot, NeuralBot |
| `src/tensor.rs` | AI state tensor encoding |
| `src/bin/` | CLI tools: arena, swiss, tune, validate, validate_decks, benchmark, diagnose, replay, generate_dataset, card_stats, profile_mcts |
| `tests/unit/` | Unit tests (separate from src) |

## Bot System

| Bot | Description |
|-----|-------------|
| `random` | Uniform random selection |
| `greedy` | Heuristic evaluation (28 tunable weights) |
| `mcts` | UCB1 tree search with greedy rollouts |
| `alphabeta` | Alpha-Beta minimax search (depth 6-8 recommended) |
| `neural` | ONNX model inference (requires `neural` feature) |

### Bot Trait

```rust
pub trait Bot: Send {
    fn name(&self) -> &str;
    fn select_action(&mut self, state_tensor: &[f32; STATE_TENSOR_SIZE],
                     legal_mask: &[f32; Action::ACTION_SPACE_SIZE],
                     legal_actions: &[Action]) -> Action;
    fn select_action_with_engine(&mut self, engine: &GameEngine) -> Action; // default impl
    fn requires_engine(&self) -> bool; // default: false
    fn reset(&mut self);
    fn clone_box(&self) -> Box<dyn Bot>;
}
```

### Alpha-Beta Bot

Minimax with alpha-beta pruning. 60-70% win rate vs MCTS-1000 at depth 8.

```bash
cargo run --release --bin arena -- --bot1 alphabeta --bot2 mcts --ab-depth 8 --games 50
```

Performance: Depth 6 ~1.4ms/move, Depth 8 ~50s/game, Depth 10+ too slow.

### MCTS Bot

Monte Carlo Tree Search with UCB1 selection and greedy rollouts.

```rust
MctsConfig::default()           // 1000 sims, sequential
MctsConfig::fast()              // 100 sims, for testing
MctsConfig::strong()            // 5000 sims, serious play
MctsConfig::interactive(sims)   // Parallel trees (MCP/UI)
MctsConfig::batch(sims)         // Sequential, for arena
```

**Optimizations (enabled by default):**
- Early termination: Stop rollouts at ±300 evaluation
- Transposition table: Cache position evaluations
- `tt_min_visits: 3`: Minimum visits before trusting cache

### GreedyBot Weights (28 params)

Life, creature stats, board state, resources, keywords (guard/lethal/lifesteal/rush/ranged/piercing/shield/quick), terminal bonuses. See `src/bots/weights.rs`.

## State Tensor (328 floats)

| Section | Indices | Size | Description |
|---------|---------|------|-------------|
| Global state | 0-5 | 6 | Turn number, current player, game state |
| Player 1 state | 6-80 | 75 | Life, essence, AP, deck/hand info, board |
| Player 2 state | 81-155 | 75 | Same as P1 |
| Card embeddings | 156-325 | 170 | Card IDs from hands/boards (normalized) |
| Commander IDs | 326-327 | 2 | P1 commander (326), P2 commander (327) |

**Player state breakdown (75 floats each):**
- Base stats: 5 (life, essence, AP, deck size, hand size)
- Hand cards: 10 (normalized card IDs)
- Creatures: 50 (5 slots × 10 floats)
- Supports: 10 (2 slots × 5 floats)

**Normalization:** Card IDs divided by 6000.0 (handles cards up to 4074, commanders up to 5011)

## Action Space (422 indices, u16)

| Range | Action |
|-------|--------|
| 0-119 | PlayCard (hand_idx × 12 + target_slot) |
| 120-144 | Attack (attacker × 5 + defender) |
| 145-419 | UseAbility (slot × 55 + ability × 11 + target) |
| 420 | CommanderInsight |
| 421 | EndTurn |

PlayCard target_slot: 0-4 enemy creature, 5-9 ally creature, 10 enemy player, 11 self player.
Target encoding: 0=NoTarget, 1-5=EnemySlot(0-4), 6-10=AllySlot(0-4).

## AI Interface (GameEnvironment trait)

```rust
trait GameEnvironment {
    fn get_state_tensor(&self) -> [f32; 328];
    fn get_legal_action_mask(&self) -> [f32; 422];
    fn apply_action_by_index(&mut self, index: u16);
    fn get_reward(&self, player: PlayerId) -> f32;  // -1.0, 0.0, or 1.0
    fn fork(&self) -> Self;  // Clone for MCTS
}
```

## Weight Tuning

CMA-ES optimizer with parallel evaluation. Outputs to `experiments/mcts/YYYY-MM-DD_HHMM_tag/`.

| Mode | Use Case |
|------|----------|
| `generalist` | Cross-deck optimization (all archetypes) |
| `archetype` | Playstyle-specific (aggro, control, tempo, midrange) |
| `specialist` | Specific deck matchup |

```bash
# Tune all archetypes
./scripts/tune-archetypes.sh

# Tune specific ones
./scripts/tune-archetypes.sh aggro tempo

# Preview commands
./scripts/tune-archetypes.sh --dry-run
```

## Balance Tools

### Quick Validation (`validate`)

Fast balance check with Greedy bot. ~1 sec for default 10 games/matchup.

```bash
cargo run --release --bin validate -- --progress
cargo run --release --bin validate -- -n 50 --progress
```

### Thorough Benchmark (`benchmark`)

Alpha-Beta (default) or MCTS. 50 games/matchup default.

```bash
cargo run --release --bin benchmark -- --progress              # Alpha-Beta depth 6
cargo run --release --bin benchmark -- --ab-depth 8            # Deeper search
cargo run --release --bin benchmark -- --bot mcts --mcts-sims 200
```

**Output:** Per-deck win rates with 95% CI, best/worst matchups, visual indicators (▲ >60%, ▼ <40%).

### Swiss Tournaments

Full tournaments with score-based pairing.

```bash
cargo run --release --bin swiss -- --games 20 --progress
cargo run --release --bin swiss -- --faction argentum --games 30
```

### Game Replay

```bash
# Generate and summarize
cargo run --release --bin replay -- --seed 12345 --deck1 X --deck2 Y

# Interactive step-through
cargo run --release --bin replay -- --seed 12345 --deck1 X --deck2 Y -i

# Export transcript
cargo run --release --bin replay -- --seed 12345 --deck1 X --deck2 Y --export transcript -o game.txt
```

## Performance Benchmarks

| Benchmark | Result | Notes |
|-----------|--------|-------|
| Random game | ~23.7k/sec | ~42 µs/game |
| Greedy game | ~1.5k/sec | ~670 µs/game |
| MCTS 100 sims | ~18.6 ms | Per move (sequential) |
| MCTS 200 sims (8 trees) | ~9.3 ms | Per move (parallel) |
| Alpha-Beta depth 4 | ~827 µs | Per move |
| Alpha-Beta depth 6 | ~1.4 ms | Per move |
| Engine fork | ~404 ns | State cloning |
| State tensor | ~157 ns | 328-float encoding |
| Legal actions | ~20 ns | Action enumeration |
| Legal action mask | ~184 ns | [f32; 422] for ML |
| Greedy evaluate | ~5.6 ns | Single state evaluation |
| Zobrist hash | ~6.8 ns | Position hashing |
| Apply action (end turn) | ~142 ns | Cheapest action type |
| Apply action (play card) | ~154 ns | Card from hand to board |
| Apply action (attack) | ~190 ns | Combat resolution |

```bash
cargo bench -p cardgame
```
