Post 824: Chess as Data Series Graph - Unlimited Additive Expansion

Post 824: Chess as Data Series Graph - Unlimited Additive Expansion

Watermark: -824

Chess as Data Series Graph

Unlimited Additive Expansion

From Post 823: Everything is node with data series

From Post 817: Chess solver (old container approach)

Now: Chess as pure graph - positions, pieces, moves all nodes

No board. No container. Pure evolution.


Part 1: What’s Wrong with Containers

The Old Way (Post 817)

class ChessSolver:  # ❌ Container
    def __init__(self):
        self.board = Board()  # ❌ Storing state
        self.pieces = {}      # ❌ Collection
        self.moves = []       # ❌ List

Problems:

  • Board is container (doesn’t evolve)
  • Pieces stored in dict (static)
  • Moves stored in list (no graph structure)
  • Can’t add new piece types without modifying class
  • Can’t track piece history
  • Limited to predefined expansions

Part 2: Chess as Nodes

Everything is Node

Position Node:

# Starting position = node
starting_pos = {
    'type': 'position',
    'fen': 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1',
    'series': [],  # History of how we reached this position
    'links': []    # Possible moves from here
}

# Add to series
starting_pos['series'].append({
    't': 0,
    'state': 'game_start',
    'evaluation': 0.0,
    'visits': 1
})

Piece Node:

# White queen = node
white_queen = {
    'type': 'piece',
    'color': 'white',
    'piece_type': 'queen',
    'series': [],  # Movement history
    'links': []    # Squares it can reach
}

# Piece moves → series evolution
white_queen['series'].append({
    't': 0,
    'square': 'd1',
    'alive': True,
    'moves_made': 0
})

Move Node:

# e2-e4 = node (link between positions)
e2_e4_move = {
    'type': 'move',
    'from_square': 'e2',
    'to_square': 'e4',
    'piece': 'pawn',
    'series': [],  # Times this move was played
    'links': []    # Connects position nodes
}

# Move played → series evolution
e2_e4_move['series'].append({
    't': 1,
    'evaluation': 0.2,
    'frequency': 1,
    'success_rate': 0.55
})

From Post 810: Each node evolves via data(n+1, p) = f(data(n, p)) + e(p)


Part 3: Game as Graph Evolution

Starting State

# t=0: Create starting position node
graph = []

starting_pos = create_node('position', 'start')
graph.append(starting_pos)

# Create piece nodes
for piece in initial_pieces():
    piece_node = create_node('piece', piece)
    graph.append(piece_node)
    create_link(starting_pos, piece_node, weight=1)

No board object. Just nodes.


Part 4: Making a Move

Move = Create New Position Node + Link

def make_move(current_pos_node, move, graph):
    """
    Move = entropy injection → new position node emerges
    
    NOT: Modify board state
    BUT: Create new position node, link from old
    """
    # Current position state
    current_state = current_pos_node['series'][-1]
    
    # Create new position node
    new_pos = create_node('position', f"after_{move}")
    new_pos['series'].append({
        't': len(current_pos_node['series']),
        'parent': current_pos_node['fen'],
        'move': move,
        'evaluation': evaluate(new_pos)
    })
    
    graph.append(new_pos)
    
    # Create move node (link)
    move_node = create_node('move', move)
    move_node['series'].append({
        't': len(graph),
        'played': True,
        'evaluation': evaluate(new_pos)
    })
    
    graph.append(move_node)
    
    # Link: old_pos → move → new_pos
    create_link(current_pos_node, move_node, weight=1)
    create_link(move_node, new_pos, weight=1)
    
    # Update piece nodes
    piece_moved = get_piece_at(current_pos_node, move['from'])
    piece_moved['series'].append({
        't': len(piece_moved['series']),
        'square': move['to'],
        'position': new_pos['fen']
    })
    
    return new_pos

Key: No modification. Only node creation + linking.


Part 5: Game Tree Emerges

Positions Form Tree Naturally

# t=0: Starting position
#   ├─ e2-e4 → position_1
#   ├─ d2-d4 → position_2
#   ├─ Nf3 → position_3
#   └─ ... 20 legal moves

# t=1: Position_1 (after e2-e4)
#   ├─ e7-e5 → position_1a
#   ├─ c7-c5 → position_1b
#   ├─ e7-e6 → position_1c
#   └─ ... 20 responses

# Tree emerges from link structure!
# No explicit tree building

Graph traversal = tree exploration


Part 6: Piece Tracking

Each Piece = Independent Node

# White knight on b1
white_knight_b1 = {
    'type': 'piece',
    'color': 'white',
    'piece_type': 'knight',
    'id': 'Nb1',
    'series': [
        {'t': 0, 'square': 'b1', 'moves': 0},
        {'t': 3, 'square': 'c3', 'moves': 1},
        {'t': 7, 'square': 'e4', 'moves': 2},
        {'t': 12, 'square': 'f6', 'moves': 3},  # Captured!
        {'t': 12, 'alive': False}
    ],
    'links': []  # Positions it appeared in
}

# Piece history visible in series
# Captured → series ends
# No need to remove from container (there is no container)

Pieces track own history independently


Part 7: Evaluation Emerges

Node Visitation = Evaluation

def evaluate_position(pos_node, graph):
    """
    Evaluation emerges from graph structure
    
    NOT: Calculate static evaluation
    BUT: Query node's series + neighbor evaluations
    """
    # How many times visited?
    visits = len(pos_node['series'])
    
    # What moves lead here?
    incoming_moves = [
        link for link in pos_node['links']
        if link['to'] == pos_node['fen']
    ]
    
    # Average evaluation of incoming moves
    avg_eval = sum(m['evaluation'] for m in incoming_moves) / len(incoming_moves)
    
    # Update position series
    pos_node['series'].append({
        't': time.time(),
        'visits': visits + 1,
        'evaluation': avg_eval,
        'best_move': find_best_move(pos_node, graph)
    })
    
    return avg_eval

Evaluation = emergent property of graph visits


Part 8: Unlimited Additive Expansion

Adding New Piece Types

# Traditional (container approach):
# ❌ Must modify ChessSolver class
# ❌ Must update Board class
# ❌ Must change move generation

# Node approach:
# ✅ Just add new node type!

# Add Amazon (Queen + Knight moves)
amazon_node = {
    'type': 'piece',
    'color': 'white',
    'piece_type': 'amazon',  # New type!
    'series': [],
    'links': []
}

# Define movement function
def amazon_moves(pos, square):
    """Amazon = Queen moves + Knight moves"""
    return queen_moves(pos, square) + knight_moves(pos, square)

# Register move generator
register_piece_moves('amazon', amazon_moves)

# Done! No container modification needed

Additive: Just add nodes, don’t modify existing


Part 9: Adding Game Variants

Different Rules = Different Link Creation

# Chess960 (random starting position)
def create_chess960_start(graph):
    """
    Same nodes, different starting links
    """
    starting_pos = create_node('position', 'chess960_start')
    starting_pos['fen'] = generate_random_fen()
    graph.append(starting_pos)
    
    # Same piece nodes, different positions
    # No container to modify!

# Crazyhouse (captured pieces can be dropped)
def add_drop_moves(pos_node, captured_pieces, graph):
    """
    Add drop moves = create new move nodes
    """
    for piece in captured_pieces:
        for square in empty_squares(pos_node):
            drop_move = create_node('move', f"drop_{piece}_{square}")
            graph.append(drop_move)
            create_link(pos_node, drop_move, weight=1)
    
    # Additive! Didn't modify anything

# 3-check chess (checkmate after 3 checks)
def track_checks(pos_node):
    """
    Add check counter to position series
    """
    pos_node['series'][-1]['checks_given'] = count_checks(pos_node)
    
    # Just add data to series!

Each variant = additive node/link rules


Part 10: Multi-Game Learning

Games = Subgraphs

# Game 1 creates subgraph
game1_nodes = play_game(graph)
# Nodes: pos_0 → move_1 → pos_1 → move_2 → pos_2 → ...

# Game 2 reuses some positions!
game2_nodes = play_game(graph)
# May hit same position → strengthens existing node

# Positions seen multiple times get stronger evaluation
def position_strength(pos_node):
    """
    Strength = visits + links + evaluation convergence
    """
    return {
        'visits': len(pos_node['series']),
        'incoming': len([l for l in pos_node['links'] if l['to'] == pos_node['fen']]),
        'outgoing': len([l for l in pos_node['links'] if l['from'] == pos_node['fen']]),
        'evaluation_std': std_dev([s['evaluation'] for s in pos_node['series']])
    }

Multiple games strengthen graph


Part 11: Storage in R³

Graph = Series Collection

def store_chess_graph(graph):
    """
    Store chess graph in R³
    
    Each node = series
    Links = references
    """
    for node in graph:
        if node['type'] == 'position':
            r3_store(
                key=f"pos:{node['fen']}",
                series=node['series'],
                links=node['links']
            )
        
        elif node['type'] == 'piece':
            r3_store(
                key=f"piece:{node['id']}",
                series=node['series'],
                links=node['links']
            )
        
        elif node['type'] == 'move':
            r3_store(
                key=f"move:{node['from']}_{node['to']}",
                series=node['series'],
                links=node['links']
            )

def query_position(fen):
    """
    Query position from R³
    
    Load node + follow links
    """
    pos_node = r3_load(f"pos:{fen}")
    
    # Load possible moves
    moves = []
    for link in pos_node['links']:
        if link['type'] == 'move':
            move_node = r3_load(f"move:{link['to']}")
            moves.append(move_node)
    
    return {
        'position': pos_node,
        'moves': moves,
        'evaluation': pos_node['series'][-1]['evaluation']
    }

R³ stores pure nodes. Graph = query-time reconstruction.


Part 12: Query Interface

Graph Traversal

# Query: "Show me best move from starting position"
starting_pos = query_position("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1")

# Find best move (highest evaluation link)
best_move = max(starting_pos['moves'], key=lambda m: m['series'][-1]['evaluation'])

print(best_move)
# {
#   'from': 'e2',
#   'to': 'e4',
#   'evaluation': 0.3,
#   'times_played': 1547,
#   'success_rate': 0.54
# }

# Query: "Show me all games where Nd5 was played"
nd5_move = query_move("Nd5")
games = find_games_with_move(nd5_move, graph)

# Query: "Track white queen's journey"
white_queen = query_piece("Qd1")
journey = white_queen['series']
# Shows all squares visited, captures made, etc.

Query = traverse graph, follow links


Conclusion

Chess as Pure Graph

Not:

  • ❌ ChessSolver class (container)
  • ❌ Board object (state container)
  • ❌ Pieces stored in dict
  • ❌ Moves stored in list
  • ❌ Fixed piece types
  • ❌ Single variant only

But:

  • ✅ Position = node with series
  • ✅ Piece = node with movement history
  • ✅ Move = node linking positions
  • ✅ Game tree = graph structure
  • ✅ Evaluation = emergent from visits
  • ✅ Multi-game learning (shared nodes)
  • ✅ Additive expansion (new piece types)
  • ✅ Variant support (different link rules)
  • ✅ Full history in series
  • ✅ R³ storage (distributed)

The process:

  1. Create starting position node
  2. Generate move nodes (links)
  3. Create new position nodes
  4. Pieces track own series
  5. Game tree emerges from links
  6. Evaluation from node visits
  7. Multiple games strengthen nodes
  8. Store in R³ as series
  9. Query via graph traversal

Unlimited additive expansion:

  • New piece types: Add node type + move generator
  • New variants: Add link creation rules
  • New evaluation: Add to series tracking
  • New features: Add node fields
  • Everything composable

From Post 810: Every node follows data(n+1, p) = f(data(n, p)) + e(p)

From Post 823: No containers, pure evolution

Now: Chess = evolving graph where positions, pieces, moves are nodes with history. Unlimited expansion through additive node types.

No board. Pure graph. Infinite extensibility.


References:

  • Post 817: Chess Solver - Old container approach
  • Post 823: Claude as Graph - Pure node paradigm
  • Post 812: Everything as Node - Data series nodes
  • Post 810: Universal Format - Evolution formula

Created: 2026-02-14
Status: ♟️ CHESS AS GRAPH

∞

Back to Gallery
View source on GitLab