LOGOS-SPCW-Matroska / logos /fractal_engine.py
GitHub Copilot
Fix: Reverse bit order in Fractal Navigation (MSB First) to match Heat Code
26a1bbb
"""
LOGOS Fractal-Galois Engine (Advanced Implementation)
Quadtree-based interpreter with GF(4) arithmetic core
For hierarchical 16K → 512B decomposition
"""
import numpy as np
from enum import Enum
import logging
class GF4:
"""Galois Field GF(4) operations on 2-bit pairs"""
# GF(4) addition table (XOR-like)
ADD_TABLE = [
[0, 1, 2, 3], # 00 + {00,01,10,11}
[1, 0, 3, 2], # 01 + {00,01,10,11}
[2, 3, 0, 1], # 10 + {00,01,10,11}
[3, 2, 1, 0] # 11 + {00,01,10,11}
]
# GF(4) multiplication table
MUL_TABLE = [
[0, 0, 0, 0], # 00 * {00,01,10,11}
[0, 1, 2, 3], # 01 * {00,01,10,11}
[0, 2, 3, 1], # 10 * {00,01,10,11}
[0, 3, 1, 2] # 11 * {00,01,10,11}
]
@staticmethod
def add(a, b):
"""GF(4) addition (XOR for change deltas)"""
return GF4.ADD_TABLE[a][b]
@staticmethod
def multiply(a, b):
"""GF(4) multiplication (for dissolution/scaling)"""
return GF4.MUL_TABLE[a][b]
@staticmethod
def dissolve_atom(current_state_vector, input_vector, heat_coefficient):
"""
Dissolve atom using GF(4) vector math
Simulates "Heat" modifying "Structure"
Args:
current_state_vector: List of 2-bit values (current state)
input_vector: List of 2-bit values (input delta)
heat_coefficient: 2-bit value (heat intensity)
Returns:
New state vector after dissolution
"""
result = []
for cs, iv in zip(current_state_vector, input_vector):
# Multiply input by heat coefficient (scaling)
scaled_input = GF4.multiply(iv, heat_coefficient)
# Add to current state (change delta)
new_state = GF4.add(cs, scaled_input)
result.append(new_state)
return result
class Quadrant(Enum):
"""Quadtree quadrants"""
TL = 0 # Top-Left
TR = 1 # Top-Right
BL = 2 # Bottom-Left
BR = 3 # Bottom-Right
class ContextAction(Enum):
"""Binary context control bits"""
PERSIST = 0 # 00: Update heat_state only (reinforce structure)
CHANGE_1 = 1 # 01: Trigger dissolution
CHANGE_2 = 2 # 10: Trigger dissolution
RESET = 3 # 11: Clear node (dissolve to null)
class FractalQuadTreeNode:
"""Node in the Fractal Quadtree"""
def __init__(self, depth, parent=None):
self.depth = depth # 0=Root/16K, 1=4K, 2=1K, 3=256B, 4=Atom/512B
self.parent = parent
# Node state
self.matrix_key = None # Hex RGB Matrix signature (Structure)
self.heat_state = 0 # Current entropy/dissolution level (Delta) [0-3]
# Children (for non-leaf nodes)
self.children = [None, None, None, None] # TL, TR, BL, BR
# Leaf node data (for depth 4 / Atom level)
self.atom_data = None
def get_quadrant(self, quadrant):
"""Get or create child node for quadrant"""
if self.children[quadrant.value] is None:
self.children[quadrant.value] = FractalQuadTreeNode(
self.depth + 1,
parent=self
)
return self.children[quadrant.value]
def is_leaf(self):
"""Check if node is at atom level (depth 4)"""
return self.depth >= 4
class LogosFractalEngine:
"""
Fractal-Galois Interpreter
Processes 512-byte atoms into hierarchical Quadtree
Resolves visuals via GF(4) vector math
"""
def __init__(self, min_bucket_size=64):
"""
Initialize Fractal Engine
Args:
min_bucket_size: Minimum bucket size in pixels (termination condition)
"""
self.root = FractalQuadTreeNode(depth=0)
self.min_bucket_size = min_bucket_size
self.logger = logging.getLogger('LogosFractalEngine')
def resolve_fractal_address(self, heat_code_int, canvas_size):
"""
Decode 32-bit heat code to spatial ZoneRect via quadtree descent
Logic:
- Read 32 bits as 16 levels of 2-bit pairs (MSB to LSB)
- Each pair selects a quadrant (00=TL, 01=TR, 10=BL, 11=BR)
- Traverse quadtree by subdividing canvas recursively
- Stop when bucket_size reached or stop sequence detected
Args:
heat_code_int: Integer representation of 4-byte heat code
canvas_size: (width, height) tuple of canvas dimensions
Returns:
ZoneRect: (x, y, width, height) defining target region for 512B atom
"""
canvas_width, canvas_height = canvas_size
# Initialize current rect to full canvas
x = 0.0
y = 0.0
w = float(canvas_width)
h = float(canvas_height)
# Process 32 bits as 16 levels of 2-bit pairs (MSB first)
# Bits 31-30 = Level 1, Bits 29-28 = Level 2, ..., Bits 1-0 = Level 16
for level in range(16):
# Extract 2-bit pair for this level (MSB to LSB)
bit_offset = 31 - (level * 2)
quadrant_bits = (heat_code_int >> bit_offset) & 0b11
# Check for stop sequence (0000 at end - all zeros means stop early)
if quadrant_bits == 0 and level > 8: # Only stop if we've descended reasonably
# Check if next few bits are also zero (stop sequence)
if level < 15:
next_bits = (heat_code_int >> (bit_offset - 2)) & 0b11
if next_bits == 0:
break # Stop sequence detected
# Branch based on quadrant selection
# 00 = Top-Left, 01 = Top-Right, 10 = Bottom-Left, 11 = Bottom-Right
if quadrant_bits == 0b00: # Top-Left
# No translation, just subdivide
w /= 2.0
h /= 2.0
elif quadrant_bits == 0b01: # Top-Right
x += w / 2.0
w /= 2.0
h /= 2.0
elif quadrant_bits == 0b10: # Bottom-Left
y += h / 2.0
w /= 2.0
h /= 2.0
elif quadrant_bits == 0b11: # Bottom-Right
x += w / 2.0
y += h / 2.0
w /= 2.0
h /= 2.0
# Termination: Stop when region is small enough for bucket
if w <= self.min_bucket_size or h <= self.min_bucket_size:
break
# Ensure we have at least minimum bucket size
w = max(w, self.min_bucket_size)
h = max(h, self.min_bucket_size)
# Clamp to canvas bounds
x = max(0, min(x, canvas_width - 1))
y = max(0, min(y, canvas_height - 1))
w = min(w, canvas_width - x)
h = min(h, canvas_height - y)
# Convert to integers for pixel coordinates
zone_rect = (int(x), int(y), int(w), int(h))
return zone_rect
def fractal_to_bucket_coords(self, heat_code_int, num_buckets_x, num_buckets_y):
"""
Convert fractal address to discrete bucket coordinates
This is a helper that uses fractal addressing but returns bucket indices
Useful for integration with bucket-based display interpreter
Args:
heat_code_int: Integer representation of 4-byte heat code
num_buckets_x: Number of buckets in X direction
num_buckets_y: Number of buckets in Y direction
Returns:
(bucket_x, bucket_y): Bucket indices
"""
# Use fractal addressing to get zone rect
# Assume a normalized canvas (1.0 x 1.0) for bucket space
zone_rect = self.resolve_fractal_address(heat_code_int, (1.0, 1.0))
# Convert zone center to bucket coordinates
zone_x, zone_y, zone_w, zone_h = zone_rect
center_x = zone_x + (zone_w / 2.0)
center_y = zone_y + (zone_h / 2.0)
# Map to bucket indices
bucket_x = int(center_x * num_buckets_x) % num_buckets_x
bucket_y = int(center_y * num_buckets_y) % num_buckets_y
return (bucket_x, bucket_y)
def navigate_quadtree_path(self, hex_header):
"""
Navigate quadtree path from hex header
Algorithm: Convert Hex to Binary, group into 2-bit pairs
Each pair (00, 01, 10, 11) selects a Quadrant (TL, TR, BL, BR)
Args:
hex_header: 8-character hex string (4 bytes = 32 bits = 16 quadrants)
Returns:
List of quadrants (path from root to target node)
"""
# Convert hex to integer
header_int = int(hex_header, 16)
# Extract 2-bit pairs (each pair = one level of quadtree)
# Extract 2-bit pairs (each pair = one level of quadtree)
# MUST iterate MSB to LSB to match calculate_heat_code
path = []
for i in range(16): # 32 bits / 2 = 16 levels
shift = 30 - (i * 2)
pair = (header_int >> shift) & 0b11
path.append(Quadrant(pair))
return path
def process_atom(self, hex_header, wave_payload):
"""
Process 512-byte atom through fractal quadtree
Args:
hex_header: 8-character hex string (4 bytes)
wave_payload: 508 bytes
"""
# Step A: Navigational Parse
path = self.navigate_quadtree_path(hex_header)
# Navigate to target node
node = self.root
for quadrant in path[:4]: # Use first 4 levels (depth 0-3)
if not node.is_leaf():
node = node.get_quadrant(quadrant)
# Step B: Context Action (Binary Context)
# Extract control bits from first 2 bits of payload
if len(wave_payload) > 0:
control_bits = (wave_payload[0] >> 6) & 0b11
action = ContextAction(control_bits)
# Remaining payload (after control bits)
data_payload = wave_payload[1:] if len(wave_payload) > 1 else b''
if action == ContextAction.PERSIST:
# Update heat_state only (reinforce structure)
node.heat_state = (node.heat_state + 1) % 4
elif action in [ContextAction.CHANGE_1, ContextAction.CHANGE_2]:
# Trigger GF(4) dissolution
current_vector = self._extract_state_vector(node)
input_vector = self._payload_to_vector(data_payload)
heat_coeff = node.heat_state
new_vector = GF4.dissolve_atom(current_vector, input_vector, heat_coeff)
node.matrix_key = self._vector_to_matrix(new_vector)
node.heat_state = (node.heat_state + 1) % 4
elif action == ContextAction.RESET:
# Clear node
node.matrix_key = None
node.heat_state = 0
node.atom_data = None
# Store atom data at leaf
if node.is_leaf():
node.atom_data = data_payload
def _extract_state_vector(self, node):
"""Extract 2-bit state vector from node"""
if node.matrix_key is None:
return [0] * 16 # Default zero vector
# Convert matrix key to vector (simplified)
return [(node.matrix_key >> (i * 2)) & 0b11 for i in range(16)]
def _payload_to_vector(self, payload):
"""Convert payload bytes to 2-bit vector"""
if not payload:
return [0] * 16
# Extract 2-bit pairs from first bytes
vector = []
for byte in payload[:8]: # 8 bytes = 16 pairs
vector.append((byte >> 6) & 0b11)
vector.append((byte >> 4) & 0b11)
vector.append((byte >> 2) & 0b11)
vector.append(byte & 0b11)
return vector[:16]
def _vector_to_matrix(self, vector):
"""Convert 2-bit vector to matrix key (integer)"""
key = 0
for i, val in enumerate(vector[:16]):
key |= (val << (i * 2))
return key
def draw_viewport(self, viewport_size):
"""
Render viewport from quadtree
Args:
viewport_size: (width, height) tuple
Returns:
numpy array (H, W, 3) RGB image
"""
width, height = viewport_size
image = np.zeros((height, width, 3), dtype=np.uint8)
self._render_node(self.root, image, 0, 0, width, height)
return image
def _render_node(self, node, image, x, y, w, h):
"""Recursively render quadtree node"""
if node is None:
return
if node.is_leaf():
# Leaf node: render based on matrix_key and heat_state
if node.matrix_key is not None:
color = self._matrix_key_to_color(node.matrix_key, node.heat_state)
image[y:y+h, x:x+w] = color
else:
# Non-leaf: recurse into children or render block
if any(child is not None for child in node.children):
# Has children: recurse
hw, hh = w // 2, h // 2
self._render_node(node.children[0], image, x, y, hw, hh) # TL
self._render_node(node.children[1], image, x + hw, y, w - hw, hh) # TR
self._render_node(node.children[2], image, x, y + hh, hw, h - hh) # BL
self._render_node(node.children[3], image, x + hw, y + hh, w - hw, h - hh) # BR
else:
# No children: render geometric block
color = self._matrix_key_to_color(node.matrix_key, node.heat_state) if node.matrix_key else [0, 0, 0]
image[y:y+h, x:x+w] = color
def _matrix_key_to_color(self, matrix_key, heat_state):
"""Convert matrix key and heat state to RGB color"""
# Extract RGB from matrix key
r = ((matrix_key >> 0) & 0xFF) % 256
g = ((matrix_key >> 8) & 0xFF) % 256
b = ((matrix_key >> 16) & 0xFF) % 256
# Apply heat_state intensity scaling
intensity = (heat_state + 1) / 4.0
r = int(r * intensity)
g = int(g * intensity)
b = int(b * intensity)
return [r, g, b]