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.
class ChessSolver: # ❌ Container
def __init__(self):
self.board = Board() # ❌ Storing state
self.pieces = {} # ❌ Collection
self.moves = [] # ❌ List
Problems:
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)
# 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.
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.
# 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
# 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
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
# 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
# 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
# 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
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.
# 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
Not:
But:
The process:
Unlimited additive expansion:
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:
Created: 2026-02-14
Status: ♟️ CHESS AS GRAPH
∞