How ChessGate's AI Works

A complete guide to the technology that makes each chess personality feel like the real player.

← Back to ChessGate

How ChessGate's AI Personalities Work

A complete guide to how ChessGate selects moves that feel like real historical chess players.


The Big Picture (For Everyone)

When you play against Karpov, Tal, or Fischer in ChessGate, you're not playing against a standard chess engine. You're playing against a system that:

  1. Asks a strong chess engine for the 10 best moves in the position
  2. Scores each move based on how likely the real player would have chosen it
  3. Picks the most "in character" move that doesn't lose the game

Think of it like an actor playing a role: the engine provides the chess knowledge (what moves are good), and the personality system provides the character (which good move this player would prefer).

A Simple Example

Imagine it's Karpov's turn. The engine finds 10 good moves:

Move Engine Rank Engine Eval Style Score What It Is
Nd5 #1 (best) +0.45 0.82 Quiet knight outpost
Bxf7 #2 +0.40 0.35 Aggressive sacrifice
Qh5 #3 +0.38 0.28 Attacking queen move
c4 #4 +0.35 0.91 Quiet pawn advance
Rd1 #5 +0.30 0.78 Rook to open file

Karpov's style system scores c4 highest (0.91) because Karpov loved quiet positional moves. Even though Nd5 is the engine's #1, the system picks c4 because: - It's only 0.10 pawns worse than the best move (within the safety threshold) - It matches Karpov's known style perfectly - It's the kind of move the real Karpov played in thousands of games

If Bxf7 were the only good move (all others losing), the safety filters would force the engine to play it regardless of style.


The Key Question: "But Every Game Is Different!"

This is the most common question about chess personality engines: chess has more possible positions than atoms in the universe. How can a database of past games help in a position the player has never seen?

The answer: we don't match positions. We match characteristics.

The Principle: Characteristics, Not Positions

Imagine you're an art expert trying to identify a painting as a Monet. You don't compare it pixel-by-pixel to every known Monet painting. Instead you look for characteristics: soft brush strokes, light effects, water themes, pastel colors. Even if it's a scene Monet never painted, you'd still recognize his style.

ChessGate works the same way. We extract characteristics from the position and match them against characteristics from thousands of games. The system never asks "has Karpov seen this exact position?" — it asks "does this position have features that triggered specific behavior in Karpov's games?"

What We Actually Match: 6 Levels of Abstraction

Each level is progressively more abstract, matching in positions that are increasingly different from anything in the database.

Level 1: Exact Position (Rare — Opening Book Only)

The board is identical to a position from a real game. This only happens in the opening (moves 1-15). The opening book stores these exactly.

Database position:  rnbqkb1r/pppppppp/5n2/8/4P3/8/PPPP1PPP/RNBQKBNR
Current position:   rnbqkb1r/pppppppp/5n2/8/4P3/8/PPPP1PPP/RNBQKBNR
Match: 100% identical → Play the book move

Level 2: Pawn Structure Matching (Very Common)

Pawns change slowly. Two positions with the same pawn structure face similar strategic problems even if the pieces are on different squares.

Database game:  Karpov had pawns on c4-d5-e4 vs opponent e6-d5
                → He played a minority attack on the queenside

Current game:   We have pawns on c4-d5-e4 vs opponent e6-d5
                → Different piece positions, but SAME pawn structure
                → The minority attack plan APPLIES

Why this works: Pawn structure determines long-term strategy.
                Karpov's plan worked because of the PAWNS, not
                because his knight happened to be on f3.

The plan matcher uses structure hashing — it computes a hash of the pawn positions and opponent king location, then looks up plans from games with the same hash. This is the primary fuzzy matching mechanism:

Tier 1: Exact structure hash   Pawns + king side identical
Tier 2: Pawn-only hash         Same pawns, king moved
Tier 3: Structure category     Same pawn type (isolated d-pawn, etc.)

From 60,000+ stored plans, typically 5-50 match any given middlegame position.

Level 3: Theme Detection (Always Available)

Strategic themes are abstract patterns that apply regardless of specific piece placement. The system detects themes live from the current position:

Theme How It's Detected What It Means
file_control Rook on open file (no pawns) Pressure down the file
outpost_occupation Knight on rank 5+ (or 4- for Black) Strong piece placement
king_attack 2+ pieces attacking enemy king Mating attack possible
passed_pawn_advance Pawn on rank 5+ with no opposing pawn Promotion threat
centralization Piece on d4/e4/d5/e5 Central dominance

The system then finds plans from the database that involved the same themes:

Current position has: outpost_occupation + file_control
Database plan #4712: "Karpov occupied outpost, then doubled rooks on the file"
   Themes match! This plan scores high regardless of exact position.

Level 4: Opponent Weakness Matching (Context-Aware)

Plans don't just match OUR position — they match what's wrong with the OPPONENT's position. The system detects:

If the opponent has an isolated d-pawn and the database has 200 plans where Karpov exploited isolated d-pawns, those plans activate — even in a position Karpov never saw.

Level 5: Harmonics Profile (Always Available)

The harmonics engine computes 10 numerical metrics for any position. These are compared against what the player historically valued:

Current position metrics:
  mobility: 0.65, initiative: 0.40, space: 0.72, pawn_structure: 0.81

Karpov's harmonics weights (from decision formula):
  mobility: 0.12, initiative: 0.08, space: 0.16, pawn_structure: 0.18

Move A improves space by +0.15    → Karpov boost: 0.15 × 0.16 = 0.024
Move B improves mobility by +0.20 → Karpov boost: 0.20 × 0.12 = 0.024
Move C improves initiative by +0.30 → Karpov boost: 0.30 × 0.08 = 0.024

For Tal (who weights initiative at 0.22):
Move C initiative boost: 0.30 × 0.22 = 0.066  ← Tal prefers this move!

This works in ANY position because it measures abstract properties, not specific squares.

Level 6: Causality Chains (Strategic Thinking)

The most abstract level: cause-and-effect relationships between themes. These are statistical patterns discovered across thousands of games:

In Karpov's games:
  pawn_storm → minority_attack    (3.2x more likely after pawn storm)
  file_control → king_attack      (2.8x more likely after file control)
  outpost → passed_pawn_advance   (2.1x more likely after outpost)

These apply to ANY position. If a pawn storm is happening, the system knows Karpov typically followed up with a minority attack — even in a position that's completely novel.

Why This Works: The 60,000-Plan Database

A single game generates 10-30 plans. Across 3,000+ games per player, we extract 60,000+ plans. With that volume:

It's like how a doctor doesn't need to have seen your exact illness before — they recognize symptoms, patterns, and typical progressions from thousands of cases.

The 11-Dimension Scoring System

When the system finds candidate plans, it scores each plan's relevance to the current position across 11 dimensions:

Dimension Weight What It Measures
Structure hash match 20% How similar are the pawn structures?
Theme overlap 15% How many themes match?
Trigger conditions 10% What triggered this plan originally?
Success rating 10% Did this plan lead to a win?
Plan outcome 10% Win/draw/loss in the original game
Piece count proximity 8% Similar number of pieces on board?
Weakness overlap 7% Same opponent weaknesses?
Eval gain 7% How much did the plan improve eval?
King-side match 5% Is the opponent's king on the same side?
Bishop pair/fianchetto 5% Similar piece characteristics?
Context eval 3% Is current eval similar to plan start?

The top 5 plans by combined score are used for move scoring. This multi-dimensional matching ensures plans are relevant even when no single dimension is a perfect match.


The 10-Tier Decision Pipeline

Every time a personality needs to make a move, it goes through 10 tiers in strict order. Once a tier makes a decision, all later tiers are skipped.

┌─────────────────────────────────────────────────┐
│  POSITION: It's the AI's turn to move           │
└─────────────┬───────────────────────────────────┘
              │
              ▼
┌─────────────────────────────────────────────────┐
│  TIER 1: OPENING BOOK                           │
│  "Has this player played this exact position    │
│   before in their career? Play their move."     │
│                                                 │
│  Data: karpov_opening_book.json (3500+ games)   │
│  Speed: Instant (dictionary lookup)             │
│  Only active: Moves 1-15                        │
└─────────────┬───────────────────────────────────┘
              │ No book move found
              ▼
┌─────────────────────────────────────────────────┐
│  TIER 2: ASK THE ENGINE                         │
│  Send position to the C engine via UCI protocol │
│  Get back 10 candidate moves with evaluations   │
│                                                 │
│  Engine: ultimate_engine_v3_drawfix.exe          │
│  Protocol: UCI (Universal Chess Interface)      │
│  Output: 10 moves ranked by evaluation          │
└─────────────┬───────────────────────────────────┘
              │ Candidates ready
              ▼
┌─────────────────────────────────────────────────┐
│  TIERS 3-8: EMERGENCY SHORTCUTS                 │
│  Skip all personality if the position demands   │
│  pure engine play:                              │
│                                                 │
│  3. Mate found → play it immediately            │
│  4. Forced win (few pieces) → engine best       │
│  5. Tablebase position → perfect play           │
│  6. Overwhelming material → just convert        │
│  7. Big material lead → don't get fancy         │
│  8. Winning endgame → technique, not style      │
└─────────────┬───────────────────────────────────┘
              │ Position is balanced/complex
              ▼
┌─────────────────────────────────────────────────┐
│  TIER 9: EXACT POSITION MATCH                   │
│  "Has this player faced this EXACT position?"   │
│  If yes AND their move is safe → play it        │
│                                                 │
│  Safety: Move must be within 50cp of best       │
│  Data: Player's game database                   │
└─────────────┬───────────────────────────────────┘
              │ No exact match
              ▼
┌─────────────────────────────────────────────────┐
│  TIER 10: FULL PERSONALITY SCORING              │
│  This is where the magic happens.               │
│  12 independent scoring layers analyze each     │
│  candidate move for style compatibility.        │
│                                                 │
│  Then: Select best move within safety bounds    │
│  Then: Verify it doesn't allow forced mate      │
└─────────────────────────────────────────────────┘

How the Engine Communicates (UCI Protocol)

ChessGate talks to the chess engine using UCI (Universal Chess Interface), a standard text protocol. It works like a conversation:

ChessGate  Engine:  "position fen rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq - 0 1"
                      (Here's the board position after 1.e4)

ChessGate  Engine:  "go depth 8"
                      (Search 8 moves deep, give me your best moves)

Engine  ChessGate:  "info depth 8 multipv 1 score cp 45 pv c7c5"
                      (Best move: c5, evaluation +0.45 pawns)
Engine  ChessGate:  "info depth 8 multipv 2 score cp 38 pv e7e5"
                      (2nd best: e5, evaluation +0.38 pawns)
Engine  ChessGate:  "info depth 8 multipv 3 score cp 30 pv d7d5"
                      (3rd best: d5, evaluation +0.30 pawns)
...
Engine  ChessGate:  "bestmove c7c5"
                      (Done searching)

The engine runs as a separate program (ultimate_engine_v3_drawfix.exe). ChessGate starts it as a subprocess and communicates via stdin/stdout pipes.

# Simplified version of what happens
self.engine_process = subprocess.Popen(
    [engine_path],
    stdin=subprocess.PIPE,
    stdout=subprocess.PIPE
)

# Send a command
self.engine_process.stdin.write("go depth 8\n")

# Read responses until "bestmove"
while True:
    line = self.engine_process.stdout.readline()
    if line.startswith("bestmove"):
        break
    # Parse "info" lines for candidate moves and evaluations

What "MultiPV 10" Means

Normal chess engines return only the best move. By setting MultiPV 10, we ask for the 10 best moves ranked by evaluation. This gives the personality system choices — it can pick the 3rd-best engine move if that move better matches the player's style.


The 12 Style Scoring Layers (Tier 10)

This is where each move gets scored for how well it matches the player's historical style. Each layer contributes independently to a total score between 0 and 1.

Layer 1: Harmonics Analysis (Weight: 0.30)

What it measures: How much a move improves the position's "harmony" — the coordination between your pieces.

How it works: Analyzes 10 positional metrics before and after the move:

Metric What It Measures
initiative Who's dictating play?
mobility How many squares can your pieces reach?
outposts Are your knights/bishops on strong squares?
king_safety Is your king well protected?
space_advantage Do you control more of the board?
piece_activity Are your pieces actively placed?
pawn_structure Are your pawns healthy or weak?
passed_pawns Do you have pawns close to promoting?
piece_exchanges Is this a good time to trade pieces?
weak_squares Does the opponent have exploitable weaknesses?

Each personality values these metrics differently. Tal values initiative and king_safety (of the opponent). Karpov values pawn_structure and space_advantage.

# Push the candidate move, measure the position, pop it back
board.push(candidate_move)
metrics_after = harmonics.analyze(board)
board.pop()

harmony_score = metrics_after['harmony_score']  # -1 to +1

Layer 2: Style Rules (Variable Weight)

What it measures: Does this move match known rules about the player's style?

Data file: karpov_style_rules.json

Example rules:

{
  "rule": "quiet_pawn_advance",
  "phase": "middlegame",
  "boost": 0.15,
  "description": "Karpov preferred quiet pawn advances over sharp tactics"
}

Layer 3: Comprehensive Style Scoring (Variable Weight)

What it measures: Broader style characteristics — does this move look like something this player would do?

Compares the candidate move to historical patterns of how the player differed from engine recommendations.

Layer 4: Pattern Matching (Weight: 0.20)

What it measures: Does this move match tactical or positional patterns from the player's games?

Data file: karpov_prime_patterns.json

Patterns include piece maneuvers, pawn structures, attack themes, and endgame motifs extracted from thousands of games.

Layer 5: Sequence Matching (Weight: 0.30)

What it measures: Does this move continue a known move sequence from the player's games?

Data file: karpov_prime_sequences.json

If Karpov often played Nd2-f1-e3-d5, and we're at the Nf1 stage, the system boosts Ne3.

Layer 6: Strategic Plan Matching (Weight: 0.60 — Highest!)

What it measures: Does this move advance a strategic plan this player is known for?

Data file: karpov_strategic_plans.json (60,000+ plans!)

This is the most important scoring layer. Plans are multi-move strategies extracted by analyzing games backwards — from the final result to the moves that created it.

Example plan from the data:

{
  "name": "outpost_occupation+passed_pawn_creation",
  "themes": [
    {
      "theme": "outpost_occupation",
      "primary_piece": 2,
      "squares": ["d5"],
      "description": "Knight occupies d5 outpost"
    },
    {
      "theme": "passed_pawn_creation",
      "primary_piece": 1,
      "squares": ["c5", "c6"],
      "description": "Create passed c-pawn"
    }
  ],
  "collaborations": [
    {
      "pieces": [["rook", "attacker"], ["queen", "supporter"]],
      "convergence_squares": ["d5", "d6"]
    }
  ]
}

How scoring works: - Move lands on a plan's target square → +0.10 - Moving piece matches the plan's piece type → +0.15 - Move is the plan's "achievement move" (the payoff) → +0.90 - Move advances multiple themes simultaneously → extra bonus

Layer 7: Decision Formula (Variable Weight)

What it measures: Does this move change the position in ways this player historically cared about?

Data file: karpov_decision_formula.json

{
  "harmonics_weights": {
    "initiative": 0.08,
    "mobility": 0.12,
    "pawn_structure": 0.18,
    "king_safety": 0.14,
    "space_advantage": 0.16
  },
  "avg_eval_sacrifice": 45.33,
  "quiet_move_preference": 0.90
}

If Karpov's mobility weight is high (0.12) and a move increases mobility, that move gets a boost.

Layer 8: Piece Placement Preferences (Weight: up to 0.25)

What it measures: Did this player historically prefer this piece on this square?

If Karpov put knights on d5 65% more often than average, any move placing a knight on d5 gets a +0.15 boost.

Layer 9: Move Type Preferences (Weight: up to 0.20)

What it measures: In this game phase, did the player prefer quiet moves, captures, or checks?

Phase Karpov's Preference Tal's Preference
Opening Quiet moves (1.15x) Checks (1.20x)
Middlegame Captures (1.20x) Sacrifices (1.30x)
Endgame King moves (1.15x) Passed pawns (1.25x)

Layer 10: Multi-Theme Plan Bonus (Variable Weight)

What it measures: Does this move advance multiple strategic themes at once?

If a single move advances both "pawn_storm" and "space_advantage", it gets an extra bonus. This rewards moves that serve multiple purposes — exactly what strong players do.

Layer 11: Causality Chain Scoring (Weight: 2.5x amplification!)

What it measures: Does this move start or continue a cause-and-effect chain between strategic themes?

Data file: karpov_causality.json

{
  "prerequisite": "pawn_storm",
  "enables": "minority_attack",
  "lift": 3.21,
  "occurrences": 150
}

This means: in Karpov's games, after a pawn storm was executed, a minority attack was 3.21x more likely to follow. So if a pawn storm is active and a move enables a minority attack, it gets a large boost.

This captures how grandmasters think in chains: "First I'll advance my pawns, which will create weaknesses, which I'll attack with my minor pieces."

Layer 12: Plan Memory Continuity (Weight: 2.0x amplification!)

What it measures: Does this move continue a plan we already started?

Real grandmasters commit to plans for 5-10 moves. Without plan memory: - Move 20: Nd4 (start plan) - Move 21: h3 (random) - Move 22: Qe2 (different plan)

With plan memory: - Move 20: Nd4 (start plan) - Move 21: c3 (supports plan) - Move 22: Rad1 (completes plan)

The plan memory tracks active plans across moves and boosts moves that continue them.


Safety Filters (Preventing Blunders)

Before any style scoring happens, every candidate move must pass safety filters. These ensure the personality never makes a catastrophic mistake.

Filter 1: Absolute Blunder Threshold

Any move losing more than the blunder threshold is rejected completely.

Game Phase Blunder Threshold
Opening 80 centipawns
Middlegame 120 centipawns
Endgame 100 centipawns

Filter 2: Hanging Piece Detection

Checks if a move leaves a valuable piece undefended on an attacked square. Exception: the engine's #1 move is never rejected (deep search already accounts for it).

Filter 3: Hard Penalty

Moves losing 35-80cp get a severe style score reduction (but not complete rejection).

Filter 4: Repetition Penalty

When winning, moves that allow threefold repetition (draw) are rejected.

Filter 5: Soft Penalty

Moves beyond the "acceptable loss" threshold get a gradual reduction.


Move Selection: Choosing the Final Move

After scoring, the system selects the best move using a formula that balances style and engine evaluation:

Final Score = (Style Score × 60%) + (Engine Rank Bonus × 40%)

Engine Rank Bonus: | Rank | Bonus | |------|-------| | #1 (engine best) | 0.85 | | #2 | 0.65 | | #3 | 0.50 | | #4 | 0.35 | | #5+ | 0.25 or less |

This means the engine's best move always has a strong advantage. A style move needs to score significantly higher in style to overcome the rank bonus.

The Acceptable Threshold

Moves are only considered if they're within a tight evaluation window:

Game Phase Max Loss Allowed
Opening 15-35 centipawns
Middlegame 25-45 centipawns
Endgame 20-40 centipawns

This is the core safety guarantee: the personality never plays a move that's significantly worse than the engine's best.


Post-Selection Verification (The Safety Net)

After the personality picks a move, one final check runs:

Mate Check (Always Runs)

1. Take the selected move
2. Play it on a copy of the board
3. Let the engine search from the OPPONENT's perspective
4. If opponent has forced mate in 5 or less  REJECT
5. Play the engine's best move instead

This catches quiet moves that accidentally allow forced mate — exactly the kind of tactical oversight a style system might make.

Evaluation Verification (Conditional)

If the selected move isn't the engine's best and the gap is >30cp, run a quick verification search. If the verification shows >150cp loss, fall back to engine best.


The Data Behind Each Personality

Every personality has 8-10 data files generated from thousands of their real games:

data/karpov/
├── karpov_opening_book.json         # Opening repertoire (3500+ games)
├── karpov_prime_patterns.json       # Tactical/positional patterns
├── karpov_prime_sequences.json      # Move sequences
├── karpov_strategic_plans.json      # 60,000+ strategic plans
├── karpov_causality.json            # Theme cause-and-effect chains
├── karpov_style_rules.json          # Style rules
├── karpov_db_style_rules.json       # Database-derived style rules
├── karpov_comprehensive_style.json  # Comprehensive style metrics
├── karpov_decision_formula.json     # Decision triggers and weights
├── karpov_enhanced_formula.json     # Position-adaptive thresholds
└── karpov.yaml                      # Personality definition

How the Data Was Generated

All personality data comes from the ChessMind Pipeline — a 13-stage automated analysis system:

  1. Data Ingestion — Parse PGN files of the player's games
  2. Opening Book — Extract opening repertoire with frequencies and win rates
  3. Bulk Analysis — Analyze every move with Stockfish + harmonics
  4. Pattern Extraction — Find recurring tactical and positional patterns
  5. Sequence Analysis — Discover multi-move sequences
  6. Style Discovery — Compare player moves to engine recommendations
  7. Decision Formula — Discover what makes the player deviate from the engine
  8. Enhanced Decision — Position-adaptive thresholds and rank tolerance
  9. Style Learning — Machine learning on style features
  10. Style Rules Export — Export as scoreable rules
  11. Strategic Plans — Backwards analysis discovering multi-move plans
  12. Causality Analysis — Discover theme cause-and-effect relationships
  13. YAML Generation — Create the personality definition file

How Different Players Feel Different

The same 12-layer pipeline produces very different results for different players because the data is different:

Karpov (The Boa Constrictor)

Tal (The Magician)

Fischer (The Machine)


Complete Move Flow: A Real Example

Position: Middlegame, roughly equal, Karpov to move.

Step 1: OPENING BOOK  No match (move 22, past book range)

Step 2: ASK ENGINE  10 candidates returned:
  #1: Nd5  +45cp
  #2: Rc1  +40cp
  #3: c4   +38cp
  #4: Bxf7 +35cp
  #5: Qe2  +32cp
  ... (5 more)

Step 3-8: EMERGENCY CHECKS
  - No mate found 
  - Not winning/losing heavily 
  - Not an endgame 
   Continue to personality scoring

Step 9: EXACT MATCH  No match in database

Step 10: STYLE SCORING (12 layers)

  Nd5 (rank #1, +45cp):
    Harmonics: 0.22 (decent)
    Style rules: +0.08
    Plans: +0.12 (matches outpost_occupation plan)
    Piece placement: +0.15 (Karpov loved Nd5)
    Total: 0.72

  Rc1 (rank #2, +40cp):
    Harmonics: 0.25 (good  improves piece activity)
    Style rules: +0.10
    Plans: +0.08
    Total: 0.58

  c4 (rank #3, +38cp):
    Harmonics: 0.28 (excellent  space + pawn structure)
    Style rules: +0.15 (quiet pawn advance!)
    Plans: +0.25 (matches minority_attack plan!)
    Plan continuity: +0.12 (continues active plan!)
    Causality: +0.08 (enables future theme)
    Total: 0.91   HIGHEST STYLE SCORE

  Bxf7 (rank #4, +35cp):
    Harmonics: 0.10 (poor  disrupts own structure)
    Style rules: -0.05 (Karpov avoided wild sacrifices)
    Total: 0.28   Below minimum style threshold

MOVE SELECTION:
  Acceptable threshold: 25cp (middlegame)
  c4 loses only 7cp vs best  WITHIN THRESHOLD 

  Combined scores:
    c4:  0.91 × 60% + 0.50 × 40% = 0.746
    Nd5: 0.72 × 60% + 0.85 × 40% = 0.772
    Rc1: 0.58 × 60% + 0.65 × 40% = 0.608

  Nd5 wins! Even though c4 had the highest style score,
  Nd5's rank #1 engine bonus (0.85) pushes it over the top.
  But it's close — and sometimes c4 WOULD win with slight
  random variation in rank tolerance.

VERIFICATION:
  Push Nd5  Check for mate  No mate found 
   PLAY Nd5

Technical Reference

Key Source Files

File Lines Purpose
style_engine.py ~2300 Complete personality engine
server.py ~2100 Web server, calls style_engine.get_move()
harmonics.py ~400 Position harmony analysis
plan_memory.py ~200 Active plan tracking
causality_matcher.py ~300 Theme chain detection

Key Classes

Class Method What It Does
StyleEngine get_move() Full 10-tier pipeline
StyleEngine _get_candidates() UCI engine communication
StyleEngine _score_candidates() 12-layer style scoring
StyleEngine _select_move() Formula-based selection
StyleEngine _verify_move_safety() Post-selection mate check
PlanMemory get_continuation_moves() Active plan tracking
CausalityMatcher score_move_causality() Theme chain scoring

Key Constants

# Style vs Engine balance
STYLE_WEIGHT = 0.60           # 60% style, 40% engine evaluation

# Safety thresholds (centipawns)
ACCEPTABLE_LOSS = 15-25       # Max loss before soft penalty
HARD_PENALTY    = 35-50       # Max loss before severe penalty
BLUNDER         = 80-120      # Max loss before rejection

# Post-verification
MATE_CHECK_DEPTH = 10         # Depth for mate detection
EVAL_OVERRIDE    = 150        # cp loss to trigger eval fallback

MoveCandidate Object

Every candidate move carries this data through the pipeline:

class MoveCandidate:
    move: chess.Move           # The actual move
    san: str                   # "Nd5", "c4", etc.
    eval_cp: int              # Engine evaluation in centipawns
    sf_rank: int              # Engine ranking (1 = best)
    style_score: float        # 0.0 to 1.0 (from 12 scoring layers)
    style_reason: str         # "plan_match, harmonics, outpost"

Summary

ChessGate's AI personality system is a multi-layer pipeline that:

  1. Gets strong candidate moves from a chess engine
  2. Scores them against 12 independent style metrics derived from real game data
  3. Selects the most characteristic move within strict safety bounds
  4. Verifies the selected move doesn't allow forced mate

The result: moves that are both tactically sound and stylistically authentic — each personality genuinely feels different because the underlying data comes from thousands of their real games.

The system never sacrifices safety for style. A personality will always prefer survival over character. But within the space of "good enough" moves, it consistently picks the one that the real player would have chosen.