File size: 14,538 Bytes
ac73ca8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26a1bbb
 
ac73ca8
 
26a1bbb
 
ac73ca8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37e2fa3
 
 
ac73ca8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
"""
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]