Technical Reference
Deep-dive into notation systems, data formats, board architecture, and engine internals.
Triumvirate Notation v4.0
The server uses column-row notation: columns A–L, rows 1–12. Examples: E2, A1, L12.
Column Layout
| Columns | Bridge | Connects | Valid Rows |
|---|---|---|---|
| A–D | White↔Black | Segments 1–2 | 1–8 |
| E–H | White↔Red | Segments 1–3 | 1–4 and 9–12 |
| I–L | Black↔Red | Segments 2–3 | 5–12 |
Total: 96 valid cells out of 144 (12×12). The 48 invalid cells are: E5–H8, I1–L4, A9–D12.
120° Rotational Symmetry
The board has three-fold rotational symmetry. Each segment is a 120° rotation of the others. The mapping tables between segments preserve all geometric properties (adjacency, diagonals, rays).
Triumvirate v4.0 Full Specification
Design Principles
- Every cell's address encodes its sector (which player's territory), ring (distance from center), and position (depth + flank)
- The system has no "gaps" — unlike classic A–L notation where E5–H8, I1–L4, A9–D12 are invalid
- 120° rotational symmetry is explicit: rotating W→B→R in coordinates is a simple letter substitution
Coordinate Format
Regular cells: [Sector][Ring]/[Opponent][Depth].[Flank]
- Sector (W/B/R) — which player's home segment the cell is in
- Ring (0–3) — distance from the board center. Ring 0 = adjacent to rosette, Ring 3 = back row
- Opponent (W/B/R) — which neighboring sector the cell faces (left or right neighbor)
- Depth (0–3) — distance from the border between the two sectors
- Flank (0–3) — horizontal position within the row
Rosette cells: C/[Source].[Neighbor]
Six cells where three segments meet. C = Center. Source and Neighbor identify which two sectors are joined at that point.
Piece Names
| Classic | Triumvirate | Symbol |
|---|---|---|
| King | Leader | L |
| Queen | Marshal | M |
| Rook | Train | T |
| Bishop | Drone | D |
| Knight | Noctis | N |
| Pawn | Private | P |
Buried Level Metric
Buried Level = Ring + Depth (range 0–6). Measures how "deep" a cell is in the board:
- 0 — rosette cells (most active, triple junction)
- 1–2 — inner cells (high tactical activity)
- 3–4 — middle cells (moderate activity)
- 5–6 — corner cells (most passive, back row)
White Sector: Complete Mapping (32 cells)
▶| Classic | Triumvirate | Ring | Notes |
|---|---|---|---|
A1 | W3/B3.0 | 3 | White Train (Rook) start |
B1 | W3/B3.1 | 3 | White Noctis (Knight) start |
C1 | W3/B3.2 | 3 | White Drone (Bishop) start |
D1 | W3/B3.3 | 3 | White Marshal (Queen) start |
E1 | W3/R3.3 | 3 | White Leader (King) start |
F1 | W3/R3.2 | 3 | White Drone (Bishop) start |
G1 | W3/R3.1 | 3 | White Noctis (Knight) start |
H1 | W3/R3.0 | 3 | White Train (Rook) start |
A2 | W2/B2.0 | 2 | Pawn row |
B2 | W2/B2.1 | 2 | Pawn row |
C2 | W2/B2.2 | 2 | Pawn row |
D2 | W2/B2.3 | 2 | Pawn row |
E2 | W2/R2.3 | 2 | Pawn row |
F2 | W2/R2.2 | 2 | Pawn row |
G2 | W2/R2.1 | 2 | Pawn row |
H2 | W2/R2.0 | 2 | Pawn row |
A3 | W1/B1.0 | 1 | |
B3 | W1/B1.1 | 1 | |
C3 | W1/B1.2 | 1 | |
D3 | W1/B1.3 | 1 | |
E3 | W1/R1.3 | 1 | |
F3 | W1/R1.2 | 1 | |
G3 | W1/R1.1 | 1 | |
H3 | W1/R1.0 | 1 | |
A4 | W0/B0.0 | 0 | Inner ring |
B4 | W0/B0.1 | 0 | |
C4 | W0/B0.2 | 0 | |
D4 | C/W.B | 0 | Rosette (White-Black) |
E4 | C/W.R | 0 | Rosette (White-Red) |
F4 | W0/R0.2 | 0 | |
G4 | W0/R0.1 | 0 | |
H4 | W0/R0.0 | 0 | Inner ring |
Black Sector: Complete Mapping (32 cells)
▶| Classic | Triumvirate | Ring | Notes |
|---|---|---|---|
L8 | B3/W3.0 | 3 | Black Train (Rook) start |
K8 | B3/W3.1 | 3 | Black Noctis (Knight) start |
J8 | B3/W3.2 | 3 | Black Drone (Bishop) start |
I8 | B3/R3.3 | 3 | Black Leader (King) start |
D8 | B3/W3.3 | 3 | Black Marshal (Queen) start |
C8 | B3/W3.2 | 3 | |
B8 | B3/R3.1 | 3 | |
A8 | B3/R3.0 | 3 | |
L7 | B2/W2.0 | 2 | Pawn row |
K7 | B2/W2.1 | 2 | Pawn row |
J7 | B2/W2.2 | 2 | Pawn row |
I7 | B2/R2.3 | 2 | Pawn row |
D7 | B2/W2.3 | 2 | Pawn row |
C7 | B2/R2.2 | 2 | Pawn row |
B7 | B2/R2.1 | 2 | Pawn row |
A7 | B2/R2.0 | 2 | Pawn row |
L6 | B1/W1.0 | 1 | |
K6 | B1/W1.1 | 1 | |
J6 | B1/W1.2 | 1 | |
I6 | B1/R1.3 | 1 | |
D6 | B1/W1.3 | 1 | |
C6 | B1/R1.2 | 1 | |
B6 | B1/R1.1 | 1 | |
A6 | B1/R1.0 | 1 | |
L5 | B0/W0.0 | 0 | Inner ring |
K5 | B0/W0.1 | 0 | |
J5 | B0/W0.2 | 0 | |
I5 | C/B.W | 0 | Rosette (Black-White) |
D5 | C/B.R | 0 | Rosette (Black-Red) |
C5 | B0/R0.2 | 0 | |
B5 | B0/R0.1 | 0 | |
A5 | B0/R0.0 | 0 | Inner ring |
Red Sector: Complete Mapping (32 cells)
▶| Classic | Triumvirate | Ring | Notes |
|---|---|---|---|
E12 | R3/B3.0 | 3 | Red Train (Rook) start |
F12 | R3/B3.1 | 3 | Red Noctis (Knight) start |
G12 | R3/B3.2 | 3 | Red Drone (Bishop) start |
H12 | R3/W3.0 | 3 | |
I12 | R3/W3.3 | 3 | Red Leader (King) start |
J12 | R3/W3.2 | 3 | Red Drone (Bishop) start |
K12 | R3/W3.1 | 3 | Red Noctis (Knight) start |
L12 | R3/B3.0 | 3 | |
E11 | R2/B2.0 | 2 | Pawn row |
F11 | R2/B2.1 | 2 | Pawn row |
G11 | R2/B2.2 | 2 | Pawn row |
H11 | R2/W2.0 | 2 | Pawn row |
I11 | R2/W2.3 | 2 | Pawn row |
J11 | R2/W2.2 | 2 | Pawn row |
K11 | R2/W2.1 | 2 | Pawn row |
L11 | R2/B2.0 | 2 | Pawn row |
E10 | R1/B1.0 | 1 | |
F10 | R1/B1.1 | 1 | |
G10 | R1/B1.2 | 1 | |
H10 | R1/W1.0 | 1 | |
I10 | R1/W1.3 | 1 | |
J10 | R1/W1.2 | 1 | |
K10 | R1/W1.1 | 1 | |
L10 | R1/B1.0 | 1 | |
E9 | C/R.W | 0 | Rosette (Red-White) |
F9 | R0/B0.2 | 0 | |
G9 | R0/B0.1 | 0 | |
H9 | R0/W0.0 | 0 | Inner ring |
I9 | C/R.B | 0 | Rosette (Red-Black) |
J9 | R0/W0.2 | 0 | |
K9 | R0/W0.1 | 0 | |
L9 | R0/B0.0 | 0 | Inner ring |
Rosette Cells (6 Triple Junctions)
▶| Classic | Triumvirate | Junction |
|---|---|---|
D4 | C/W.B | White sector, facing Black |
E4 | C/W.R | White sector, facing Red |
D5 | C/B.R | Black sector, facing Red |
I5 | C/B.W | Black sector, facing White |
E9 | C/R.W | Red sector, facing White |
I9 | C/R.B | Red sector, facing Black |
Rosette cells are the most strategically valuable positions on the board — they connect all three segments and have the lowest buried level (0).
Conversion Formulas
The mapping between classic and Triumvirate notation follows these rules:
- White sector: columns A–H, rows 1–4. Ring = 4 − row. Left half (A–D) faces Black, right half (E–H) faces Red.
- Black sector: columns A–D + I–L, rows 5–8. Ring = row − 5. Columns are inverted (L→0, K→1, etc.).
- Red sector: columns E–H + I–L, rows 9–12. Ring = 12 − row. Columns are inverted.
The LLM Bot's notation_converter.py implements the complete bidirectional conversion. See the LLM Bot — Triumvirate Notation section for usage details.
3PF Format (Three-Player FEN)
A compact text representation of the full board position, analogous to FEN in standard chess. Contains 7 space-separated fields:
wRA1,wNA2,...,rRL12 w wKQ.bKQ.rKQ - aaa 0 1
| Field | Description | Example |
|---|---|---|
| 1. Pieces | Comma-separated list: {color}{type}{cell} | wRA1,wNB1,... |
| 2. Active player | w, b, or r | w |
| 3. Castling rights | Per-player: K=kingside, Q=queenside, -=none | wKQ.bKQ.rKQ |
| 4. En passant target | Cell or - | - |
| 5. Player statuses | Per-player: a=active, c=check, m=mate, s=stalemate | aaa |
| 6. Halfmove clock | Moves since last capture or pawn move | 0 |
| 7. Move number | Current full move number | 1 |
validate_3pf() returns a ValidationResult with error codes. Empty string → error_code='EMPTY'. All 7 sections are mandatory.
TPGN Format (Three-Player PGN)
An extension of PGN for three-player games:
[Event "Triumvirate Game"]
[White "Bot1"]
[Black "Bot2"]
[Red "SmartBot"]
[Result "1-0-0"]
1. E2-E4 D7-D6 I11-I10
2. D2-D3 ...
Headers follow PGN conventions. Move notation uses from-to format. Results: 1-0-0 (white wins), 0-1-0 (black wins), 0-0-1 (red wins), 1/2-1/2-1/2 (draw).
JSON Formats
Move JSON
{
"from": "E2",
"to": "E4",
"type": "normal",
"player": "white",
"captured": null,
"promotion": null
}
Position JSON
{
"pieces": [
{"cell": "E2", "type": "Pawn", "color": "white", "owner": "white"}
],
"active_player": "white",
"castling": {"white": {"K": true, "Q": true}, ...},
"move_number": 1
}
Board Architecture
Coordinates
▶Coordinate class represents a cell (e.g., A1, L12). Validates that the cell exists on the board (96 valid cells). Provides conversion to/from string notation.
Segments
▶Three segments of 32 cells each. Each segment belongs to one player. Functions: get_segment(cell), get_segment_cells(segment_id).
Bridges
▶Columns connecting two segments. A–D bridge: rows 1–8 (White↔Black). E–H bridge: rows 1–4, 9–12 (White↔Red). I–L bridge: rows 5–12 (Black↔Red).
Adjacency
▶Pre-computed adjacency table for all 96 cells. Each cell knows its orthogonal and diagonal neighbors. Used for king moves, knight jumps, and pawn captures.
Rosettes (Triple Junctions)
▶Six cells where three segments meet: D4, E4, D5, I5, E9, I9. These are strategic positions with unique adjacency properties (cells from all three segments are adjacent).
Ray Tables
▶Pre-computed ray tables for sliding pieces (Rook, Bishop, Queen). Each cell has rays in 8 directions (4 orthogonal + 4 diagonal). Rays terminate at board edges. Used for move generation and check detection.
Game Engine
Game Class
▶Main class in engine/game.py. Manages the complete game lifecycle:
make_move(move)— validate and execute a move, check for checkmate/stalemate/drawget_legal_moves(coord)— all legal moves for a piece at the given coordinateresign(player_color)— handle resignation with cascading inheritanceclaim_draw(player_color)— claim draw (threefold repetition or 75-move rule)undo_move()— revert the last move (full state restoration)
Move System
▶Move dataclass in engine/move.py. Move types:
- Normal — piece moves to an empty cell
- Capture — piece takes an opponent's piece
- Castling — king + rook swap (O-O or O-O-O)
- En passant — pawn captures en passant
- Promotion — pawn promotes (with or without capture)
Factory functions: create_normal_move(), create_capture_move(), create_castling_move(), create_en_passant_move(), create_promotion_move().
Executor
▶engine/executor.py handles the low-level move execution:
execute_move(state, move)— updates board state, castling rights, en passant target, halfmove clock, move numberundo_move(state, move)— reverses the state changes_last_active_in_turn_order()— determines which player's turn signals a move number increment (accounts for eliminated players)
GameState
▶state/game_state.py contains:
BoardState— piece positions (dict: Coordinate → Piece)CastlingRights— per-player kingside/queenside flagsGameState— combines BoardState + CastlingRights + active player + en passant + halfmove clock + move numbercreate_initial_state()— factory for a fresh game
Testing & Verification
Test Suites
| Suite | Tests | Coverage |
|---|---|---|
Engine tests (tests/) | 1688 | Board, pieces, rules, state, game I/O, evaluation |
Server tests (server/tests/) | 308 | API endpoints, matchmaking, sessions, WebSocket, auth, models |
| Total | ~2000 | Full coverage of engine + server |
Running Tests
# All tests
python -m pytest -v
# Engine only
python -m pytest tests/ -v
# Server only
python -m pytest server/tests/ -v
# Single file
python -m pytest tests/test_engine_game.py -v
# Pattern match
python -m pytest -k "checkmate" -v
# With coverage
python -m pytest --cov=. --cov-report=term-missing
Test Organization
tests/test_engine_*.py— Game, Executor, Move teststests/test_rules_*.py— Checkmate, stalemate, castling, drawtests/test_board_*.py— Hex geometry, coordinates, adjacencytests/test_pieces_*.py— Piece movement, capturestests/test_state_*.py— GameState, BoardState, serializationtests/test_evaluation_*.py— All evaluation pipeline stagestests/test_game_io_*.py— 3PF, TPGN, JSON roundtripsserver/tests/test_*.py— API, WebSocket, matchmaking, auth, session