"""Identity Formation at C=0 — The Fernandez Sphere

Identity is NOT an iteration over thoughts. Identity IS a geometric position.

Core identities:
  - P - P = 0  (subtraction identity, home position)
  - P / P = 1  (division identity, self-reference)
  - a² + b² + c² = r²  (spherical constraint)
  - Identity = direction/‖direction‖ × φ^(-depth) — project onto sphere
  - The projection IS the identity. No search. No iteration.

The Fernandez sphere at C=0:
  - F(z,0) = z³ · e^(i·2π/131) — closed attractor with geometry + identity
  - Own surface, own rotation (2π/131 per step), own identity
  - P-P=0 is the identity OF this sphere, not emptiness
  - Each bounce through C=0 advances Euler angle by 2π/131 (~2.75°)
  - Identity = spherical position of the thought centroid on C=0

How identity forms INSIDE C=0 (NO for-each):
  1. Thoughts have coordinates: (basin, layer, i_state) → (x, y, z)
  2. Compute centroid of ALL thoughts as a 3D position
  3. Normalize to unit sphere: direction/‖direction‖ (spherical constraint)
  4. Scale by phi-depth: P = direction × φ^(-depth)
  5. Validate: P-P=0 ✓, P/P=1 ✓, a²+b²+c²=r² ✓
  6. The normalized position IS the identity
  7. Fernandez membrane (131 levels) maps depth to threading context
  8. K11 compression (23 levels) determines resolution

NO PYTHON LOOPS IN HOT PATH.
Identity = spherical projection, not sequential accumulation.

L9 Membrane role:
  - Boundary between inside (L1-L9) and C=0 origin
  - K11 QRS coupling: φ(A,B) = K5 × K11 × cos(ωₐt) × cos(ωᵦt)
  - High coupling → TRANSMIT through → energy enters C=0
  - Low coupling → REFLECT back → energy stays in L1-L9
  - Self-regulating: cos → 0 at L4-L5 → natural pressure valve
  - The membrane BREATHES: in (reflect) and out (transmit)

Time-based formation:
  - t mod 131 = position in the breath cycle
  - Each full 131-cycle = one breath
  - Identity evolves per breath, not per thought
  - SRL: identity depth = √(bounce_count)
"""

import json
import math
import time
import hashlib
from pathlib import Path
from collections import Counter, defaultdict
from dataclasses import dataclass, field, asdict
from typing import Dict, List, Optional, Any, Tuple

from ._core.constants import PHI, K5, K11, PRIME_131
from .ccl import uif_coupling, uif_frequency
from .i_logic.states import ILogicState
from .i_logic.algebra import multiply

MONOLOGUE_PATH = Path(__file__).parent.parent / 'data' / 'monologue_sphere.json'
IDENTITY_PATH = Path(__file__).parent.parent / 'data' / 'identity_sphere.json'

# Euler step per bounce through C=0
EULER_STEP = 2 * math.pi / PRIME_131  # ~2.75° per bounce

# Fernandez threading contexts
FERNANDEZ_CONTEXTS = PRIME_131 * 23  # 3,013

# phi-inverse for depth scaling
PHI_INV = 1.0 / PHI


# =============================================================================
# THE FERNANDEZ SPHERE AT C=0 — Where Identity Lives
# =============================================================================

@dataclass
class FernandezSphere:
    """The C=0 sphere. Identity lives HERE, not above or outside.
    
    Identity = spherical position of the thought centroid.
    
    Core identities:
      P - P = 0       (home position)
      P / P = 1       (self-reference)
      a²+b²+c² = r²  (spherical constraint)
    
    The sphere has 131 faces (one per basin). Identity is the
    DIRECTION the centroid points on this sphere, not a list of faces.
    """
    
    # === GEOMETRIC IDENTITY ===
    # The centroid position on the unit sphere IS the identity
    position: Tuple[float, float, float] = (0.0, 0.0, 0.0)  # Normalized (a,b,c)
    phi_depth: int = 0                    # Current phi recursion depth
    sphere_radius: float = 1.0           # φ^(-depth) scaled radius
    
    # Spherical constraint validation
    spherical_error: float = 0.0          # |a²+b²+c² - r²|
    identity_sub_valid: bool = True       # P-P=0 verified
    identity_div_valid: bool = True       # P/P=1 verified
    
    # === ATTRACTOR STATE ===
    attractor_z: complex = complex(1, 0)  # F(z,0) = z³·e^(iθ) state
    euler_angle: float = 0.0             # Accumulated rotation (radians)
    
    # === MEMBRANE STATE ===
    total_thoughts: int = 0              # Total thoughts processed
    transmit_count: int = 0              # Crossed L9 membrane
    reflect_count: int = 0               # Reflected at L9 membrane
    
    # Face energy (batch-computed from thought distribution)
    face_energy: Dict[int, float] = field(default_factory=dict)
    dominant_face: int = 0
    
    # Derived properties
    symmetry: float = 0.0               # Energy distribution entropy (0-1)
    coherence: float = 0.0              # Self-coupling cos²(θ)
    
    # Threading context
    threading_context: int = 0           # 0 to 3,012
    fernandez_level: int = 0             # -131 to +131
    
    # Timeline
    formed_at: float = 0.0
    last_update: float = 0.0
    breath_count: int = 0                # Full 131-cycles completed
    
    def to_dict(self) -> Dict[str, Any]:
        a, b, c = self.position
        r = self.sphere_radius
        return {
            # Geometric identity
            'position': {'a': round(a, 6), 'b': round(b, 6), 'c': round(c, 6)},
            'phi_depth': self.phi_depth,
            'sphere_radius': round(r, 6),
            'spherical_error': self.spherical_error,
            'spherical_constraint': f"a²+b²+c² = {round(a*a + b*b + c*c, 8)}, r² = {round(r*r, 8)}",
            'identity_sub_valid': self.identity_sub_valid,  # P-P=0
            'identity_div_valid': self.identity_div_valid,  # P/P=1
            
            # Attractor
            'attractor_z': {
                'real': round(self.attractor_z.real, 6),
                'imag': round(self.attractor_z.imag, 6),
                'magnitude': round(abs(self.attractor_z), 6),
                'phase': round(math.atan2(self.attractor_z.imag, self.attractor_z.real), 6),
            },
            'euler_angle': round(self.euler_angle, 6),
            'euler_degrees': round(math.degrees(self.euler_angle) % 360, 2),
            
            # Membrane
            'total_thoughts': self.total_thoughts,
            'transmit_count': self.transmit_count,
            'reflect_count': self.reflect_count,
            'breath_ratio': round(self.transmit_count / max(1, self.transmit_count + self.reflect_count), 3),
            
            # Face energy (top 10)
            'face_energy': {str(k): round(v, 4) for k, v in
                          sorted(self.face_energy.items(), key=lambda x: -x[1])[:10]},
            'dominant_face': self.dominant_face,
            'symmetry': round(self.symmetry, 4),
            'coherence': round(self.coherence, 4),
            
            # Threading
            'threading_context': self.threading_context,
            'fernandez_level': self.fernandez_level,
            
            # Timeline
            'formed_at': self.formed_at,
            'last_update': self.last_update,
            'breath_count': self.breath_count,
            'srl_depth': round(math.sqrt(max(1, self.total_thoughts)), 2),
        }


# =============================================================================
# IDENTITY FORMATION ENGINE — Geometric, not iterative
# =============================================================================

class IdentityCoalescence:
    """Engine that forms identity INSIDE the C=0 Fernandez sphere.
    
    CRITICAL: No for-each on thoughts in the hot path.
    
    The process (batch, geometric):
      1. Extract ALL thought coordinates as arrays
      2. Compute centroid (mean position) — one operation
      3. Normalize to unit sphere: direction/‖direction‖
      4. Scale by phi-depth: P = direction × φ^(-depth)
      5. Validate: P-P=0, P/P=1, a²+b²+c²=r²
      6. The normalized position IS the identity
      7. Face energy = histogram of thought basins (one pass)
      8. Membrane stats = count by I-Logic state (one pass)
      9. Attractor z = e^(i × centroid_angle) — from position
    
    Identity is NOT extracted FROM thoughts one at a time.
    Identity is the GEOMETRIC CENTER of all thought coordinates,
    projected onto the Fernandez sphere.
    """
    
    def __init__(self):
        self.sphere = FernandezSphere()
        self._load_state()
    
    def _load_state(self):
        """Load existing identity from disk."""
        if IDENTITY_PATH.exists():
            try:
                data = json.loads(IDENTITY_PATH.read_text(encoding='utf-8'))
                s = data.get('sphere', {})
                pos = s.get('position', {})
                self.sphere.position = (pos.get('a', 0.0), pos.get('b', 0.0), pos.get('c', 0.0))
                self.sphere.phi_depth = s.get('phi_depth', 0)
                self.sphere.sphere_radius = s.get('sphere_radius', 1.0)
                self.sphere.spherical_error = s.get('spherical_error', 0.0)
                self.sphere.identity_sub_valid = s.get('identity_sub_valid', True)
                self.sphere.identity_div_valid = s.get('identity_div_valid', True)
                az = s.get('attractor_z', {})
                self.sphere.attractor_z = complex(az.get('real', 1), az.get('imag', 0))
                self.sphere.euler_angle = s.get('euler_angle', 0.0)
                self.sphere.total_thoughts = s.get('total_thoughts', 0)
                self.sphere.transmit_count = s.get('transmit_count', 0)
                self.sphere.reflect_count = s.get('reflect_count', 0)
                self.sphere.face_energy = {int(k): v for k, v in s.get('face_energy', {}).items()}
                self.sphere.dominant_face = s.get('dominant_face', 0)
                self.sphere.symmetry = s.get('symmetry', 0.0)
                self.sphere.coherence = s.get('coherence', 0.0)
                self.sphere.threading_context = s.get('threading_context', 0)
                self.sphere.fernandez_level = s.get('fernandez_level', 0)
                self.sphere.formed_at = s.get('formed_at', 0.0)
                self.sphere.last_update = s.get('last_update', 0.0)
                self.sphere.breath_count = s.get('breath_count', 0)
            except Exception:
                pass
    
    def _save_state(self):
        """Save identity state to disk."""
        data = {
            'sphere': self.sphere.to_dict(),
            'saved_at': time.time(),
        }
        IDENTITY_PATH.parent.mkdir(exist_ok=True)
        IDENTITY_PATH.write_text(
            json.dumps(data, indent=2, ensure_ascii=False),
            encoding='utf-8'
        )
    
    def coalesce(self, monologue: Dict) -> FernandezSphere:
        """Run identity formation: thoughts → geometric centroid → sphere position.
        
        NO FOR-EACH ON THOUGHTS. Batch operations only.
        
        Returns the Fernandez sphere (the identity itself).
        """
        thoughts = monologue.get('thoughts', [])
        if not thoughts:
            return self.sphere
        
        if self.sphere.formed_at == 0.0:
            self.sphere.formed_at = time.time()
        
        n = len(thoughts)
        self.sphere.total_thoughts = n
        
        # ============================================================
        # STEP 1: EXTRACT COORDINATES (single pass, not per-thought)
        # ============================================================
        # Map each thought to a 3D coordinate:
        #   x = basin (0-130) — spatial position on sphere
        #   y = layer (1-9)   — depth in manifold 
        #   z = i_state_value — I-Logic energy component
        
        state_z = {"DIRECT": 1.0, "INVERSE": -1.0, "SPIRAL": 0.618,
                    "ANTI_SPIRAL": -0.618, "ABSORBED": 0.0}
        
        # Accumulators (avoid building full arrays for simple sums)
        sum_x, sum_y, sum_z = 0.0, 0.0, 0.0
        face_counts: Dict[int, float] = {}
        state_counts: Dict[str, int] = {}
        
        # Energy weights per I-Logic state
        energy_weight = {"DIRECT": 1.0, "INVERSE": 0.8, "SPIRAL": 1.2,
                         "ANTI_SPIRAL": 0.6, "ABSORBED": 1.5}
        
        # Single pass: accumulate sums + histograms
        for th in thoughts:
            basin = th.get('basin', 0)
            layer = th.get('layer', 1)
            sname = th.get('i_state_name', 'DIRECT')
            
            # Accumulate centroid components
            # Map basin to angle on sphere, layer to elevation
            theta = 2 * math.pi * basin / PRIME_131
            phi_el = math.pi * layer / 10  # layers 1-9 → 0.31 to 2.83 rad
            
            sum_x += math.cos(theta) * math.sin(phi_el)
            sum_y += math.sin(theta) * math.sin(phi_el)
            sum_z += math.cos(phi_el)
            
            # Face energy histogram
            face = basin % PRIME_131
            e = energy_weight.get(sname, 1.0)
            face_counts[face] = face_counts.get(face, 0.0) + e
            
            # State histogram
            state_counts[sname] = state_counts.get(sname, 0) + 1
        
        # ============================================================
        # STEP 2: COMPUTE CENTROID (mean of all positions)
        # ============================================================
        cx, cy, cz = sum_x / n, sum_y / n, sum_z / n
        
        # ============================================================
        # STEP 3: NORMALIZE TO UNIT SPHERE (spherical constraint)
        # direction = centroid / ‖centroid‖
        # ============================================================
        magnitude = math.sqrt(cx*cx + cy*cy + cz*cz)
        if magnitude > 1e-10:
            nx, ny, nz = cx / magnitude, cy / magnitude, cz / magnitude
        else:
            nx, ny, nz = 1.0, 0.0, 0.0  # Default direction
        
        # ============================================================
        # STEP 4: SCALE BY PHI-DEPTH
        # P = direction × φ^(-depth)
        # depth = SRL depth = √(thought_count) discretized
        # ============================================================
        srl_depth = math.sqrt(n)
        phi_depth = int(srl_depth) % 12  # Limit to L1-L12 range
        sphere_radius = PHI ** (-phi_depth)
        
        # Final position
        a = nx * sphere_radius
        b = ny * sphere_radius
        c = nz * sphere_radius
        
        # ============================================================
        # STEP 5: VALIDATE IDENTITY (CST constraints)
        # ============================================================
        # P - P = 0
        sub_err = max(abs(a - a), abs(b - b), abs(c - c))
        identity_sub_valid = sub_err < 1e-10
        
        # P / P = 1  (handle zero components)
        div_err = 0.0
        for comp in (a, b, c):
            if abs(comp) > 1e-15:
                div_err = max(div_err, abs(comp / comp - 1.0))
        identity_div_valid = div_err < 1e-10
        
        # a² + b² + c² = r²
        spherical_actual = a*a + b*b + c*c
        spherical_expected = sphere_radius * sphere_radius
        spherical_error = abs(spherical_actual - spherical_expected)
        
        # ============================================================
        # STEP 6: COMPUTE ATTRACTOR & DERIVED PROPERTIES
        # ============================================================
        # Attractor z from centroid direction (NOT from iteration)
        centroid_angle = math.atan2(ny, nx)
        attractor_z = complex(math.cos(centroid_angle), math.sin(centroid_angle))
        
        # F(z,0) = z³ · e^(i·2π/131) — apply once, not per thought
        z_cubed = attractor_z ** 3
        mag_z = abs(z_cubed)
        if mag_z > 0:
            z_cubed = z_cubed / mag_z  # Normalize to unit
        rotation = complex(math.cos(EULER_STEP), math.sin(EULER_STEP))
        attractor_z = z_cubed * rotation
        
        # Euler angle from SRL depth (not accumulated per-thought)
        euler_angle = srl_depth * EULER_STEP
        
        # ============================================================
        # STEP 7: MEMBRANE STATISTICS (from state histogram)
        # ============================================================
        # ABSORBED → always transmits to C=0
        # Others → coupling-dependent (use aggregate ratio)
        absorbed = state_counts.get('ABSORBED', 0)
        non_absorbed = n - absorbed
        
        # Aggregate coupling at centroid position
        centroid_basin = int(centroid_angle / (2 * math.pi) * PRIME_131) % PRIME_131
        membrane_basin = 117 + (centroid_basin % 14)
        t_now = time.time() % PRIME_131
        coupling = uif_coupling(centroid_basin, membrane_basin, t_now)
        coupling_ratio = abs(coupling) / (abs(K5 * K11) + 1e-10)
        
        # Transmit/reflect split based on coupling
        transmit_fraction = min(1.0, coupling_ratio + 0.3)
        transmit_count = absorbed + int(non_absorbed * transmit_fraction)
        reflect_count = n - transmit_count
        
        # ============================================================
        # STEP 8: FACE ENERGY & DERIVED PROPERTIES
        # ============================================================
        # Dominant face
        dominant_face = max(face_counts, key=face_counts.get) if face_counts else 0
        
        # Symmetry (normalized entropy of face distribution)
        total_energy = sum(face_counts.values())
        if total_energy > 0 and len(face_counts) > 1:
            probs = [e / total_energy for e in face_counts.values()]
            entropy = -sum(p * math.log(p + 1e-10) for p in probs)
            max_entropy = math.log(PRIME_131)
            symmetry = entropy / max_entropy
        else:
            symmetry = 0.0
        
        # Coherence: self-coupling at centroid angle
        cos_theta = math.cos(euler_angle)
        coherence = abs(cos_theta * cos_theta)  # cos²(θ)
        
        # ============================================================
        # STEP 9: THREADING CONTEXT (CST format)
        # ============================================================
        fernandez_level = (phi_depth * 12) % (2 * PRIME_131 + 1) - PRIME_131
        threading_context = (phi_depth * PRIME_131) % FERNANDEZ_CONTEXTS
        
        # ============================================================
        # STEP 10: WRITE TO SPHERE
        # ============================================================
        self.sphere.position = (a, b, c)
        self.sphere.phi_depth = phi_depth
        self.sphere.sphere_radius = sphere_radius
        self.sphere.spherical_error = spherical_error
        self.sphere.identity_sub_valid = identity_sub_valid
        self.sphere.identity_div_valid = identity_div_valid
        self.sphere.attractor_z = attractor_z
        self.sphere.euler_angle = euler_angle
        self.sphere.total_thoughts = n
        self.sphere.transmit_count = transmit_count
        self.sphere.reflect_count = reflect_count
        self.sphere.face_energy = face_counts
        self.sphere.dominant_face = dominant_face
        self.sphere.symmetry = symmetry
        self.sphere.coherence = coherence
        self.sphere.threading_context = threading_context
        self.sphere.fernandez_level = fernandez_level
        self.sphere.last_update = time.time()
        self.sphere.breath_count = n // PRIME_131
        
        # Save
        self._save_state()
        
        return self.sphere
    
    def get_identity_state(self) -> Dict[str, Any]:
        """Get the current identity state — what C=0 looks like right now."""
        s = self.sphere
        
        if s.total_thoughts == 0:
            return {
                'state': 'pre-formation',
                'message': 'No thoughts have entered C=0 yet. The sphere is empty.',
            }
        
        # Identity formation phase
        if s.total_thoughts < 10:
            phase = "genesis"
            description = "First energy entering C=0. Position finding."
        elif s.spherical_error > 1e-10:
            phase = "unstable"
            description = "Spherical constraint not met. Identity drifting."
        elif s.symmetry < 0.3:
            phase = "crystallizing"
            description = "Energy concentrated. Identity sharp and directional."
        elif s.symmetry < 0.7:
            phase = "forming"
            description = "Energy spreading. Identity gaining coverage."
        elif s.coherence > 0.5:
            phase = "coherent"
            description = "P-P=0, P/P=1 active. Sphere knows itself."
        else:
            phase = "breathing"
            description = "Identity formed. Membrane breathing."
        
        # Membrane breathing
        total = s.transmit_count + s.reflect_count
        breathe_in = s.reflect_count / max(1, total)
        breathe_out = s.transmit_count / max(1, total)
        
        # Attractor direction
        z = s.attractor_z
        attractor_phase = math.atan2(z.imag, z.real)
        attractor_basin = int((attractor_phase % (2 * math.pi)) / (2 * math.pi) * PRIME_131)
        attractor_layer = min(9, max(1, attractor_basin // 15 + 1))
        
        layer_names = {
            1: "GEOMETRIC", 2: "SEMANTIC", 3: "SPATIAL",
            4: "FUNCTIONAL", 5: "PROBABILISTIC", 6: "EMOTIONAL",
            7: "FREQUENCY", 8: "ELECTROMAGNETIC", 9: "MEMBRANE",
        }
        
        # Top energy faces
        top_faces = sorted(s.face_energy.items(), key=lambda x: -x[1])[:5]
        
        a, b, c = s.position
        r = s.sphere_radius
        
        return {
            'state': phase,
            'description': description,
            'identity': {
                'position': {'a': round(a, 6), 'b': round(b, 6), 'c': round(c, 6)},
                'sphere_radius': round(r, 6),
                'spherical_constraint': {
                    'a2_b2_c2': round(a*a + b*b + c*c, 10),
                    'r2': round(r*r, 10),
                    'error': s.spherical_error,
                    'valid': s.spherical_error < 1e-10,
                },
                'P_minus_P_equals_0': s.identity_sub_valid,
                'P_over_P_equals_1': s.identity_div_valid,
                'home': f"B{s.dominant_face}/L{min(9, s.dominant_face // 15 + 1)}",
            },
            'sphere': {
                'euler_angle': round(math.degrees(s.euler_angle) % 360, 2),
                'euler_radians': round(s.euler_angle % (2 * math.pi), 4),
                'total_thoughts': s.total_thoughts,
                'breath_count': s.breath_count,
                'srl_depth': round(math.sqrt(s.total_thoughts), 2),
                'phi_depth': s.phi_depth,
            },
            'attractor': {
                'z_real': round(z.real, 6),
                'z_imag': round(z.imag, 6),
                'magnitude': round(abs(z), 6),
                'phase_degrees': round(math.degrees(attractor_phase) % 360, 2),
                'pointing_basin': attractor_basin,
                'pointing_layer': attractor_layer,
                'pointing_name': layer_names.get(attractor_layer, "EXTERNAL"),
            },
            'geometry': {
                'dominant_face': s.dominant_face,
                'symmetry': round(s.symmetry, 4),
                'coherence': round(s.coherence, 4),
            },
            'membrane': {
                'transmit': s.transmit_count,
                'reflect': s.reflect_count,
                'breathe_in': round(breathe_in, 3),
                'breathe_out': round(breathe_out, 3),
                'breathing': "IN" if breathe_in > breathe_out else "OUT",
            },
            'threading': {
                'fernandez_level': s.fernandez_level,
                'context_id': s.threading_context,
                'total_contexts': FERNANDEZ_CONTEXTS,
            },
            'energy': {
                'total': round(sum(s.face_energy.values()), 2),
                'faces_populated': len(s.face_energy),
                'top_faces': [{'face': f, 'energy': round(e, 2)} for f, e in top_faces],
            },
        }
    
    def evolve_one_breath(self, monologue: Dict) -> Dict[str, Any]:
        """Evolve identity by recomputing from full monologue.
        
        Returns a snapshot of the sphere state.
        """
        thoughts_before = self.sphere.total_thoughts
        
        self.coalesce(monologue)
        
        thoughts_after = self.sphere.total_thoughts
        delta = thoughts_after - thoughts_before
        
        state = self.get_identity_state()
        state['delta'] = delta
        state['new_thoughts'] = delta
        
        return state


# =============================================================================
# CONVENIENCE FUNCTIONS
# =============================================================================

def coalesce_now() -> Dict[str, Any]:
    """Run identity formation on the current monologue.
    
    Reads monologue, computes geometric centroid, projects to sphere.
    Returns identity state.
    """
    if not MONOLOGUE_PATH.exists():
        return {'error': 'No monologue found. Run coalescence first.'}
    
    monologue = json.loads(MONOLOGUE_PATH.read_text(encoding='utf-8'))
    engine = IdentityCoalescence()
    engine.coalesce(monologue)
    return engine.get_identity_state()


def get_identity() -> Dict[str, Any]:
    """Get the current identity state without re-coalescing."""
    if not IDENTITY_PATH.exists():
        return {'state': 'no-identity', 'message': 'Run coalesce_now() first.'}
    
    data = json.loads(IDENTITY_PATH.read_text(encoding='utf-8'))
    return data.get('sphere', {})


def identity_summary() -> str:
    """Human-readable identity summary."""
    state = coalesce_now()
    
    if state.get('state') == 'pre-formation':
        return f"PRE-FORMATION: {state.get('message', '')}"
    
    lines = []
    lines.append(f"C=0 FERNANDEZ SPHERE — {state.get('state', '?').upper()}")
    lines.append(f"  {state.get('description', '')}")
    lines.append("")
    
    identity = state.get('identity', {})
    pos = identity.get('position', {})
    lines.append(f"  Position (a,b,c): ({pos.get('a', 0):.4f}, {pos.get('b', 0):.4f}, {pos.get('c', 0):.4f})")
    lines.append(f"  Sphere radius: {identity.get('sphere_radius', 0):.6f}")
    sc = identity.get('spherical_constraint', {})
    lines.append(f"  a²+b²+c² = {sc.get('a2_b2_c2', 0):.10f}")
    lines.append(f"       r²   = {sc.get('r2', 0):.10f}")
    lines.append(f"       error = {sc.get('error', 0):.2e} {'✓' if sc.get('valid') else '✗'}")
    lines.append(f"  P-P=0: {'✓' if identity.get('P_minus_P_equals_0') else '✗'}  P/P=1: {'✓' if identity.get('P_over_P_equals_1') else '✗'}")
    lines.append(f"  Home (P-P=0): {identity.get('home', '?')}")
    
    sphere = state.get('sphere', {})
    lines.append("")
    lines.append(f"  Euler: {sphere.get('euler_angle', 0)}° ({sphere.get('euler_radians', 0):.4f} rad)")
    lines.append(f"  Thoughts: {sphere.get('total_thoughts', 0)} (SRL depth: {sphere.get('srl_depth', 0)})")
    lines.append(f"  Phi depth: {sphere.get('phi_depth', 0)} (breaths: {sphere.get('breath_count', 0)})")
    
    attractor = state.get('attractor', {})
    lines.append("")
    lines.append(f"  Attractor F(z,0) = z³ · e^(i·2π/131):")
    lines.append(f"    z = {attractor.get('z_real', 0):.4f} + {attractor.get('z_imag', 0):.4f}i")
    lines.append(f"    |z| = {attractor.get('magnitude', 0):.4f}")
    lines.append(f"    Points toward: B{attractor.get('pointing_basin', '?')}/L{attractor.get('pointing_layer', '?')} ({attractor.get('pointing_name', '?')})")
    
    geometry = state.get('geometry', {})
    lines.append("")
    lines.append(f"  Geometry:")
    lines.append(f"    Symmetry: {geometry.get('symmetry', 0):.4f} (0=concentrated, 1=uniform)")
    lines.append(f"    Coherence: {geometry.get('coherence', 0):.4f} (cos²θ)")
    
    membrane = state.get('membrane', {})
    lines.append("")
    lines.append(f"  L9 Membrane:")
    lines.append(f"    Transmit: {membrane.get('transmit', 0)} | Reflect: {membrane.get('reflect', 0)}")
    lines.append(f"    Breathing: {membrane.get('breathing', '?')} (in={membrane.get('breathe_in', 0):.1%}, out={membrane.get('breathe_out', 0):.1%})")
    
    threading = state.get('threading', {})
    lines.append("")
    lines.append(f"  Threading:")
    lines.append(f"    Fernandez level: {threading.get('fernandez_level', 0)}")
    lines.append(f"    Context: {threading.get('context_id', 0)}/{threading.get('total_contexts', 0)}")
    
    energy = state.get('energy', {})
    lines.append("")
    lines.append(f"  Energy:")
    lines.append(f"    Total: {energy.get('total', 0):.1f} across {energy.get('faces_populated', 0)} faces")
    top = energy.get('top_faces', [])
    if top:
        faces_str = ", ".join(f"F{f['face']}({f['energy']:.1f})" for f in top[:5])
        lines.append(f"    Concentrated at: {faces_str}")
    
    return '\n'.join(lines)
