# Essence Wars - Core Game Engine

High-performance, deterministic card game engine designed for AI/RL research and competitive play.

## Overview

This crate provides:

- **Complete game engine** with state management, turn resolution, and action processing
- **Deterministic execution** via seeded RNG for reproducible gameplay
- **Perfect information** design suitable for adversarial AI training
- **Multiple AI bots** from random baselines to competitive Alpha-Beta search
- **ML-ready interfaces** with 328-float state tensors and 422 discrete actions
- **CLI tools** for analysis, tuning, tournaments, and replay

## Installation

```bash
# Build the crate
cargo build --release -p cardgame

# Run tests
cargo nextest run -p cardgame --status-level=fail

# Run benchmarks
cargo bench -p cardgame
```

## Quick Start

```rust
use cardgame::{GameEngine, CardDatabase, DeckRegistry, Action};
use cardgame::bots::{create_bot, BotType, MctsConfig, AlphaBetaConfig};

// Load game data
let db = CardDatabase::load_with_commanders(
    "data/cards/core_set",
    "data/commanders",
)?;
let decks = DeckRegistry::load_from_directory("data/decks")?;

// Create engine and start game
let mut engine = GameEngine::new(&db);
engine.start_game(
    decks.get("iron_wall").unwrap(),
    decks.get("swarm_aggro").unwrap(),
    12345,  // seed
)?;

// Create an MCTS bot
let mut bot = create_bot(
    &db, &BotType::Mcts, None,
    &MctsConfig::default(), &AlphaBetaConfig::default(), 42,
);

// Game loop
while !engine.is_game_over() {
    let action = bot.select_action_with_engine(&engine);
    engine.apply_action(action)?;
}

println!("Winner: {:?}", engine.get_result());
```

## Game Concepts

### Core Mechanics

| Concept | Description |
|---------|-------------|
| **Players** | 2 opponents (P1 plays first, P2 draws 2 bonus cards) |
| **Life** | 30 HP per player (0 HP = defeat) |
| **Essence** | Mana pool (starts at 1, +1/turn, max 10) |
| **Action Points** | 3 per turn for plays, attacks, abilities |
| **Board** | 5 creature slots + 2 support slots per player |
| **Hand** | Max 10 cards (excess burned) |
| **Deck** | 30 cards standard (29 + 1 commander) |
| **Turn Limit** | 30 turns (draw if no winner) |

### Card Types

- **Creatures** - Deploy to board, attack/defend, have stats and keywords
- **Spells** - One-time effects (instant resolution)
- **Supports** - Passive auras in support slots
- **Tokens** - Creatures spawned by effects (no card ID)

### Keywords (16)

| Keyword | Effect |
|---------|--------|
| Rush | Can attack turn played |
| Ranged | Takes no counter-damage |
| Piercing | Excess damage hits face |
| Guard | Must be attacked first |
| Lifesteal | Attacker heals for damage dealt |
| Lethal | Any non-zero damage kills |
| Shield | First damage absorbed |
| Quick | Damage dealt before counter-attack |
| Ephemeral | Dies at end of turn |
| Regenerate | Heals 2 HP at turn start |
| Stealth | Untargetable by attacks |
| Charge | +2 attack when attacking |
| Frenzy | +1 attack per attack this turn |
| Volatile | Deal 2 damage to all enemies on death |
| Fortify | Takes 1 less damage (min 1) |
| Ward | First targeting spell/ability has no effect |

### Factions

| Faction | Identity | Card IDs |
|---------|----------|----------|
| **Argentum Combine** | Defense & durability (Guard, Shield, Fortify) | 1000-1074 |
| **Symbiote Circles** | Aggressive tempo (Rush, Lethal, Volatile) | 2000-2074 |
| **Obsidion Syndicate** | Burst damage (Lifesteal, Stealth, Quick) | 3000-3074 |
| **Neutral** | Flexible utility (Ranged, various) | 4000-4074 |
| **Commanders** | Unique passive abilities (one per deck) | 5000-5011 |

## Bot System

### Available Bots

| Bot | Strategy | Speed | Use Case |
|-----|----------|-------|----------|
| `RandomBot` | Uniform random selection | ~23.7k games/sec | Baseline, stress testing |
| `GreedyBot` | Heuristic evaluation (28 weights) | ~1.5k games/sec | Fast validation, tuning |
| `MctsBot` | UCB1 tree search + rollouts | ~18.6ms/move (100 sims) | Training data, interactive play |
| `AlphaBetaBot` | Minimax with pruning | ~1.4ms/move @ depth 6 | Strong opponent, balance testing |
| `NeuralBot` | ONNX model inference | Varies by model | Trained policy (requires `neural` feature) |

### Bot Trait

```rust
pub trait Bot: Send {
    fn select_action(
        &mut self,
        state_tensor: &[f32; 328],
        legal_mask: &[f32; 422],
        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>;
}
```

### Creating Bots

Bots are created via `create_bot()`, which takes the bot type, configuration objects, and a seed:

```rust
use cardgame::bots::{create_bot, BotType, MctsConfig, AlphaBetaConfig};

// create_bot(card_db, bot_type, weights, mcts_config, ab_config, seed)
let random = create_bot(&db, &BotType::Random, None, &MctsConfig::default(), &AlphaBetaConfig::default(), 42);
let greedy = create_bot(&db, &BotType::Greedy, Some(&weights), &MctsConfig::default(), &AlphaBetaConfig::default(), 42);
let mcts = create_bot(&db, &BotType::Mcts, None, &MctsConfig::strong(), &AlphaBetaConfig::default(), 42);
let ab = create_bot(&db, &BotType::AlphaBeta, None, &MctsConfig::default(), &AlphaBetaConfig { depth: 8, ..Default::default() }, 42);
```

MCTS configurations: `MctsConfig::fast()` (100 sims), `MctsConfig::default()` (1000 sims), `MctsConfig::strong()` (5000 sims), `MctsConfig::interactive(sims)` (parallel), `MctsConfig::batch(sims)` (sequential).

### GreedyBot Weights (28 Parameters)

The heuristic evaluator uses tunable weights for:

- **Life**: own_life, enemy_life_damage
- **Creatures**: attack, health, count, board advantage
- **Resources**: cards in hand, action points
- **Keywords**: guard, lethal, lifesteal, rush, ranged, piercing, shield, quick, ephemeral, regenerate, stealth, charge, frenzy, volatile, fortify, ward
- **Terminal**: win bonus (~+999), lose penalty (~-1000)

Load custom weights: `data/weights/` or `data/weights/archetypes/`

## ML Interface

### State Tensor (328 floats)

```rust
pub 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, 1.0
    fn fork(&self) -> Self;  // Clone for MCTS
}
```

| Section | Indices | Size | Content |
|---------|---------|------|---------|
| Global | 0-5 | 6 | Turn, current player, game state |
| Player 1 | 6-80 | 75 | Life, essence, AP, hand, board |
| Player 2 | 81-155 | 75 | Same as P1 |
| Card IDs | 156-325 | 170 | Normalized card IDs |
| Commanders | 326-327 | 2 | Commander IDs (÷6000) |

### Action Space (422 indices, u16)

| Range | Action |
|-------|--------|
| 0-119 | PlayCard (hand × 12 + target_slot) |
| 120-144 | Attack (attacker × 5 + defender) |
| 145-419 | UseAbility (slot × 55 + ability × 11 + target) |
| 420 | CommanderInsight (draw for 4 essence) |
| 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).

## CLI Tools

### Arena - Head-to-Head Matches

```bash
cargo run --release --bin arena -- \
    --bot1 mcts --bot2 greedy \
    --games 100 --seed 42
```

### Validate - Quick Balance Check

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

### Benchmark - Thorough Analysis

```bash
# Alpha-Beta (default)
cargo run --release --bin benchmark -- --ab-depth 6 --progress

# MCTS alternative
cargo run --release --bin benchmark -- --bot mcts --mcts-sims 200
```

### Swiss - Tournament Mode

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

### Tune - Weight Optimization

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

# Specific archetypes
./scripts/tune-archetypes.sh aggro tempo

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

### Replay - Game Analysis

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

# Interactive step-through
cargo run --release --bin replay -- --seed 12345 --deck1 iron_wall --deck2 swarm_aggro -i

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

## Data Formats

### Card Definitions (YAML)

Location: `data/cards/core_set/{faction}/creatures.yaml`

```yaml
- id: 1000
  name: Brass Sentinel
  cost: 2
  card_type: creature
  attack: 2
  health: 5
  keywords: [Guard]
  rarity: Common
  abilities:
    - trigger: OnPlay
      targeting: TargetAllyCreature
      effects:
        - type: buff_stats
          attack: 1
```

### Deck Definitions (TOML)

Location: `data/decks/{faction}/{deck_name}.toml`

```toml
id = "iron_wall"
name = "Iron Wall"
description = "Impenetrable fortress defense"
playstyle = "Control"
commander = 5003

cards = [
    1000, 1000, 1000,  # Brass Sentinel x3
    1001, 1001, 1002,  # ...
    # 30 cards total (excluding commander)
]
```

### Weight Files (TOML)

Location: `data/weights/{name}.toml`

```toml
[default.greedy]
own_life = 2.74
enemy_life_damage = 1.81
own_creature_attack = 1.64
keyword_guard = 2.63
keyword_lethal = 3.11
# ... 28 total weights
```

## Performance

| Operation | Throughput |
|-----------|-----------|
| Random game | ~23.7k/sec |
| Greedy game | ~1.5k/sec |
| MCTS (100 sims) | ~18.6ms/move |
| Alpha-Beta (depth 6) | ~1.4ms/move |
| Engine fork | ~404 ns |
| State tensor | ~157 ns |
| Legal actions | ~20 ns |

Run benchmarks: `cargo bench -p cardgame`

## Architecture

```
src/
├── core/           # Game engine core
│   ├── types.rs    # CardId, PlayerId, Slot, etc.
│   ├── keywords.rs # 16 keywords (u16 bitfield)
│   ├── state.rs    # GameState, PlayerState
│   ├── actions.rs  # Action types
│   ├── combat/     # Combat resolution (creature, face, death, triggers)
│   ├── engine/     # GameEngine + handlers
│   └── legal.rs    # Legal action generation
├── bots/           # AI implementations
│   ├── random.rs
│   ├── greedy.rs
│   ├── mcts.rs
│   ├── alphabeta.rs
│   ├── neural.rs   # ONNX inference (neural feature)
│   └── weights.rs
├── tensor.rs       # ML state encoding
├── decks.rs        # Deck registry
├── replay/         # Game recording
├── arena/          # Match execution
├── validation/     # Balance tools
├── client_api/     # High-level interface with events
└── bin/            # CLI tools
```

## Testing

Tests are **separate from source** (not inline `#[cfg(test)]`):

- **Unit tests**: `tests/unit/<module>_tests.rs`
- **Integration tests**: `tests/*.rs`
- **Shared utilities**: `tests/common/mod.rs`

```bash
cargo nextest run -p cardgame --status-level=fail
```

## Features

```toml
[features]
default = ["parallel", "cli"]
parallel = ["dep:rayon"]           # Parallel game execution
cli = [...]                        # CLI tools
python = ["dep:pyo3", "dep:numpy"] # Python bindings
neural = ["dep:ort"]               # ONNX neural network inference
```

## License

MIT License - see [LICENSE](../../LICENSE) for details.
