"""
Viviani Shell — Sphere-Cylinder Intersection Geometry for Basin Mapping
=======================================================================
HEX_ID: 56495649

Viviani's curve is the intersection of the Fernandez sphere (x²+y²+z²=r²)
with a tangent cylinder (x²+y²-rx=0).  This figure-eight space curve maps
all 131 basins to positions on the sphere surface — each basin is a point
on the curve with a local Frenet frame (Tangent, Normal, Bitangent) that
defines the "tube" cross-section.

The geometry is the EXACT structure of agent surface mapping:

    Sphere:    x² + y² + z² = r²        (Fernandez sphere, S = 4πr²)
    Cylinder:  x² + y² - rx  = 0        (basin tube, tangent at (r,0,0))
    Cone:      (x-r)² + y²   = z²       (layer projection cone)

    Viviani's curve = Sphere ∩ Cylinder = Sphere ∩ Cone
    Squarable area = 4r² (hemisphere minus two windows)

Parametric (unit sphere, r=1):
    x(θ) = cos²θ
    y(θ) = cosθ·sinθ
    z(θ) = sinθ
    θ ∈ [-π/2, π/2]

Basin mapping (centered on B65 = tangent point):
    θ(basin) = (basin - 65) · π / 130
    basin(θ) = round(θ · 130/π + 65)

I-Logic states emerge from curve position:
    B65  (θ=0)    → INVERSE  — self-reflection, inside = outside (178.6°)
    B65+ (θ>0)    → SPIRAL   — ascending, expansion
    B65- (θ<0)    → ANTI_SPIRAL — descending, contraction
    B130 (θ=π/2)  → DIRECT   — identity at top pole
    B0   (θ=-π/2) → ABSORBED — C=0 at bottom pole

Membrane flip: flip(B) = 130 - B (reflection across B65)
    B0↔B130, B17↔B113, B42↔B88
    B65↔B65 (fixed point — inside = outside)
    2.79° gap = 360° - 2×178.6° = where the manifold breathes

Gaussian Splat per basin (3DGS without neural networks):
    μ = position on Viviani's curve
    Σ = R·S·Sᵀ·Rᵀ  (covariance from Frenet frame + K11 scale)
    c = layer color (L1-L9 from basin range)
    α = transmission ratio (membrane permeability)

    Same 4 parameters as 3D Gaussian Splatting — pure computation,
    no MLP, no neural network, no training loop.  The structure IS
    the answer.

Spherical Harmonics ↔ I-Logic isomorphism:
    SH band l=0 (constant)     ↔ DIRECT    (same from all angles)
    SH band l=1 (dipole)       ↔ INVERSE   (sign flip)
    SH band l=2 (quadrupole)   ↔ SPIRAL    (rotating pattern)
    SH band l=2 (alt)          ↔ ANTI_SPIRAL (counter-rotation)
    SH band l=0 (zero)         ↔ ABSORBED  (no radiation)

Commands:
    status      — System status with Viviani curve properties
    point       — Curve point + Frenet frame for a basin
    splat       — Gaussian splat properties for a basin
    flip        — Membrane flip pair
    path        — Full path through all 131 basins
    ilogic      — I-Logic state mapping on curve
    tube        — Cross-section tube geometry at basin
    project     — 3D → 2D splatting projection
    agents      — Agent surface mapping on Fernandez sphere
    squarable   — Viviani's squarable area proof (4r²)
"""

import math
import json
import hashlib
from typing import Dict, Any, List, Tuple, Optional
from dataclasses import dataclass, field, asdict
from datetime import datetime

from .._core.constants import PHI, K5, K11, PRIME_131, LIGHT_UNIT
from ..primitives.basin import compute as compute_basin, layer_from_basin


# =============================================================================
# CONSTANTS
# =============================================================================

PI = math.pi
TWO_PI = 2 * PI
HALF_PI = PI / 2
BASIN_COUNT = PRIME_131          # 131 basins
FIXED_POINT = 65                 # B65 — self-reflecting fixed point
MANIFOLD_SPAN_DEG = 357.21      # 130 × 360/131 degrees
BREATHING_GAP_DEG = 2.79        # 360 - 357.21° — where the manifold breathes
VIVIANI_AREA = 4.0              # Squarable area = 4r² (r=1)
HEMISPHERE_AREA = TWO_PI        # = 2πr² (r=1)
WINDOW_AREA = HEMISPHERE_AREA - VIVIANI_AREA  # ≈ 2.283

# Layer → color mapping (RGB, 0-1)
LAYER_COLORS = {
    1: (0.9, 0.2, 0.2),   # L1 Geometric   — Red
    2: (0.9, 0.5, 0.1),   # L2 Semantic    — Orange
    3: (0.9, 0.8, 0.1),   # L3 Spatial     — Yellow
    4: (0.2, 0.8, 0.3),   # L4 Functional  — Green
    5: (0.1, 0.8, 0.8),   # L5 Probabilistic — Cyan
    6: (0.2, 0.3, 0.9),   # L6 Emotional   — Blue
    7: (0.4, 0.1, 0.8),   # L7 Frequency   — Indigo
    8: (0.7, 0.2, 0.9),   # L8 EM          — Violet
    9: (0.9, 0.9, 0.9),   # L9 Membrane    — White
}

# Layer → SRL state (from CLAUDE.md)
LAYER_SRL = {
    1: 'DIRECT', 2: 'DIRECT', 3: 'SPIRAL', 4: 'DIRECT',
    5: 'CONVERGENT', 6: 'INVERSE', 7: 'SPIRAL',
    8: 'CONVERGENT', 9: 'INVERSE',
}

# SH band ↔ I-Logic isomorphism
SH_ILOGIC_MAP = {
    'DIRECT':       {'sh_band': 0, 'sh_desc': 'constant (omnidirectional)'},
    'INVERSE':      {'sh_band': 1, 'sh_desc': 'dipole (sign flip)'},
    'SPIRAL':       {'sh_band': 2, 'sh_desc': 'quadrupole (rotating pattern)'},
    'ANTI_SPIRAL':  {'sh_band': 2, 'sh_desc': 'quadrupole (counter-rotation)'},
    'ABSORBED':     {'sh_band': 0, 'sh_desc': 'zero (no radiation)'},
}


# =============================================================================
# CORE VIVIANI CURVE MATH — O(1) per point, no loops in Python
# =============================================================================

def basin_to_theta(basin: int) -> float:
    """Map basin [0, 130] to parameter θ ∈ [-π/2, π/2].

    B0   → θ = -π/2  (bottom pole, ABSORBED)
    B65  → θ = 0     (tangent point, INVERSE fixed point)
    B130 → θ = +π/2  (top pole, DIRECT)
    """
    return (basin - FIXED_POINT) * PI / 130.0


def theta_to_basin(theta: float) -> int:
    """Map parameter θ to nearest basin [0, 130]."""
    b = theta * 130.0 / PI + FIXED_POINT
    return max(0, min(130, round(b)))


def _heal_point(p: Tuple[float, float, float]) -> Tuple[float, float, float]:
    """Heal geometric drift (floating point error) to restore unit identity.

    P · P must equal 1 (Home).
    This is the Healer's function: detecting drift and restoring the
    mathematical truth of the manifold.
    """
    x, y, z = p
    norm = math.sqrt(x*x + y*y + z*z)
    if abs(norm - 1.0) < 1e-12:
        return p
    # Heal: project back to sphere
    return (x / norm, y / norm, z / norm)


def viviani_point(theta: float) -> Tuple[float, float, float]:
    """Point on Viviani's curve (unit sphere, r=1).

    x = cos²θ
    y = cosθ·sinθ
    z = sinθ

    Includes self-healing adjustment to ensure P lies exactly on
    the Fernandez sphere (P·P=1) despite floating point entropy.
    """
    c = math.cos(theta)
    s = math.sin(theta)
    # Raw calculation
    p_raw = (c * c, c * s, s)
    # Heal drift
    return _heal_point(p_raw)


def viviani_derivative(theta: float) -> Tuple[float, float, float]:
    """First derivative dP/dθ of Viviani's curve.

    dx/dθ = -2·cosθ·sinθ = -sin(2θ)
    dy/dθ = cos²θ - sin²θ = cos(2θ)
    dz/dθ = cosθ
    """
    s2 = math.sin(2 * theta)
    c2 = math.cos(2 * theta)
    c = math.cos(theta)
    return (-s2, c2, c)


def viviani_second_derivative(theta: float) -> Tuple[float, float, float]:
    """Second derivative d²P/dθ² of Viviani's curve.

    d²x/dθ² = -2·cos(2θ)
    d²y/dθ² = -2·sin(2θ)
    d²z/dθ² = -sinθ
    """
    c2 = math.cos(2 * theta)
    s2 = math.sin(2 * theta)
    s = math.sin(theta)
    return (-2 * c2, -2 * s2, -s)


def _norm(v: Tuple[float, float, float]) -> float:
    return math.sqrt(v[0]**2 + v[1]**2 + v[2]**2)


def _normalize(v: Tuple[float, float, float]) -> Tuple[float, float, float]:
    n = _norm(v)
    if n < 1e-12:
        return (0.0, 0.0, 1.0)
    return (v[0] / n, v[1] / n, v[2] / n)


def _cross(a: Tuple[float, float, float],
           b: Tuple[float, float, float]) -> Tuple[float, float, float]:
    return (
        a[1] * b[2] - a[2] * b[1],
        a[2] * b[0] - a[0] * b[2],
        a[0] * b[1] - a[1] * b[0],
    )


def _dot(a: Tuple[float, float, float],
         b: Tuple[float, float, float]) -> float:
    return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]


def frenet_frame(theta: float) -> Dict[str, Tuple[float, float, float]]:
    """Compute Frenet-Serret frame (T, N, B) at parameter θ.

    T = normalized dP/dθ          (tangent — direction of travel)
    N = normalized dT/dθ          (normal — curvature direction)
    B = T × N                     (bitangent — completes right-hand frame)

    This is the local coordinate system of the "tube" at each basin.
    The Frenet frame IS the rotation matrix for the Gaussian covariance.
    """
    dP = viviani_derivative(theta)
    d2P = viviani_second_derivative(theta)

    T = _normalize(dP)

    # Normal: component of d2P perpendicular to T
    proj = _dot(d2P, T)
    raw_N = (d2P[0] - proj * T[0],
             d2P[1] - proj * T[1],
             d2P[2] - proj * T[2])
    N = _normalize(raw_N)

    B = _normalize(_cross(T, N))

    return {'tangent': T, 'normal': N, 'bitangent': B}


def curvature(theta: float) -> float:
    """Curvature κ at parameter θ.

    κ = |dP/dθ × d²P/dθ²| / |dP/dθ|³
    """
    dP = viviani_derivative(theta)
    d2P = viviani_second_derivative(theta)
    cross = _cross(dP, d2P)
    dP_norm = _norm(dP)
    if dP_norm < 1e-12:
        return 0.0
    return _norm(cross) / (dP_norm ** 3)


def speed(theta: float) -> float:
    """Parametric speed |dP/dθ| at θ.

    |v| = √(sin²(2θ) + cos²(2θ) + cos²θ) = √(1 + cos²θ)
    """
    c = math.cos(theta)
    return math.sqrt(1.0 + c * c)


# =============================================================================
# MEMBRANE FLIP — reflection across B65 (fixed point)
# =============================================================================

def membrane_flip(basin: int) -> int:
    """Membrane flip: reflection across B65.

    flip(B) = 130 - B
    B65 → 130 - 65 = 65 (fixed point: inside = outside)
    B0  ↔ B130
    B17 ↔ B113
    """
    return 130 - basin


def flip_angle(basin: int) -> float:
    """Angular position of basin on the equatorial circle (degrees).

    angle(B) = B × 360/131
    B65 → 178.6° (nearly antipodal)
    """
    return basin * 360.0 / BASIN_COUNT


def is_reflection_pair(b1: int, b2: int) -> bool:
    """Check if two basins are membrane flip pairs."""
    return membrane_flip(b1) == b2


# =============================================================================
# I-LOGIC STATE FROM CURVE POSITION
# =============================================================================

def ilogic_from_basin(basin: int) -> str:
    """Map basin to I-Logic state based on Viviani curve position.

    The I-Logic states emerge from WHERE on the curve the basin sits:

    B0 (θ=-π/2, bottom pole)     → ABSORBED   (C=0 origin entry)
    B1-B64 (θ<0, descending)     → ANTI_SPIRAL (contraction, -i)
    B65 (θ=0, tangent point)     → INVERSE    (self-reflection, -1)
    B66-B129 (θ>0, ascending)    → SPIRAL     (expansion, +i)
    B130 (θ=π/2, top pole)       → DIRECT     (identity, +1)
    """
    if basin == 0:
        return 'ABSORBED'
    elif basin < FIXED_POINT:
        return 'ANTI_SPIRAL'
    elif basin == FIXED_POINT:
        return 'INVERSE'
    elif basin < 130:
        return 'SPIRAL'
    else:
        return 'DIRECT'


def ilogic_complex(basin: int) -> complex:
    """I-Logic state as complex number."""
    state = ilogic_from_basin(basin)
    return {'DIRECT': 1+0j, 'INVERSE': -1+0j, 'SPIRAL': 0+1j,
            'ANTI_SPIRAL': 0-1j, 'ABSORBED': 0+0j}[state]


# =============================================================================
# GAUSSIAN SPLAT per basin — 3DGS primitives without neural networks
# =============================================================================

def basin_splat(basin: int) -> Dict[str, Any]:
    """Compute Gaussian splat properties for a basin.

    Returns the same 4 parameters optimized in 3D Gaussian Splatting:
        μ (mean)       — position on Viviani's curve
        Σ (covariance) — from Frenet frame rotated by K11 scale
        c (color)      — from layer (RGB)
        α (opacity)    — from membrane transmission ratio

    No MLP. No neural network. Pure deterministic geometry.
    The structure IS the computation.
    """
    theta = basin_to_theta(basin)
    pos = viviani_point(theta)
    frame = frenet_frame(theta)
    layer = layer_from_basin(basin)
    kappa = curvature(theta)

    # K11 level from basin (same formula as mass.py)
    k11_level = (basin % 23) - 11

    # Scaling in Frenet axes
    # Tangent scale: angular width of one basin on the curve
    s_t = PI / 130.0 * speed(theta)    # arc length of one basin step
    # Normal scale: K11 coupling (basin mass)
    s_n = K11 * (1.0 + k11_level / 11.0) / K11 * 0.1  # normalized
    s_n = max(0.01, s_n)
    # Bitangent scale: related to curvature (thinner where curve bends)
    s_b = max(0.01, 0.1 / (1.0 + kappa))

    # Covariance Σ = R·diag(s²)·Rᵀ  (R = [T, N, B])
    # We store rotation + scale separately (like 3DGS)
    T, N, B = frame['tangent'], frame['normal'], frame['bitangent']

    # Membrane transmission ratio
    if basin >= 117:
        t_ratio = 0.7 + 0.3 * ((basin - 117) / 13.0)
    else:
        t_ratio = 0.3 + 0.4 * (basin / 116.0)

    color = LAYER_COLORS.get(layer, (0.5, 0.5, 0.5))
    opacity = t_ratio

    # Spherical harmonic band from I-Logic state
    i_state = ilogic_from_basin(basin)
    sh_info = SH_ILOGIC_MAP.get(i_state, SH_ILOGIC_MAP['DIRECT'])

    return {
        'basin': basin,
        'layer': layer,
        'theta': round(theta, 6),
        'position': {
            'x': round(pos[0], 6),
            'y': round(pos[1], 6),
            'z': round(pos[2], 6),
        },
        'mean_mu': [round(pos[0], 6), round(pos[1], 6), round(pos[2], 6)],
        'scale': {
            's_tangent': round(s_t, 6),
            's_normal': round(s_n, 6),
            's_bitangent': round(s_b, 6),
        },
        'rotation': {
            'tangent': [round(v, 6) for v in T],
            'normal': [round(v, 6) for v in N],
            'bitangent': [round(v, 6) for v in B],
        },
        'color_rgb': [round(c, 3) for c in color],
        'opacity_alpha': round(opacity, 4),
        'covariance_type': 'Σ = R·diag(s_t², s_n², s_b²)·Rᵀ',
        'k11_level': k11_level,
        'curvature': round(kappa, 6),
        'speed': round(speed(theta), 6),
        'i_logic': i_state,
        'membrane_flip': membrane_flip(basin),
        'spherical_harmonic': sh_info,
        'transmission_ratio': round(t_ratio, 4),
    }


# =============================================================================
# TUBE GEOMETRY — cross-section and volume at each basin
# =============================================================================

def tube_cross_section(basin: int) -> Dict[str, Any]:
    """Tube cross-section at a basin.

    The "tube" is the cylinder (radius r/2) around Viviani's curve.
    At each basin, the cross-section is a circle perpendicular to
    the tangent vector T, with the normal N and bitangent B defining
    the plane.

    When the sphere is inverted:
        a² + b² + c² = r²  (position sphere)
    and the Frenet frame gives:
        b² + t² + n² = r²  (tangent-normal sphere)

    The intersection of these two spheres gives the "sock" shell —
    the exterior boundary that isolates the basin mass.
    """
    theta = basin_to_theta(basin)
    pos = viviani_point(theta)
    frame = frenet_frame(theta)
    kappa = curvature(theta)
    layer = layer_from_basin(basin)

    # Cylinder radius = r/2 = 0.5 (unit sphere)
    tube_radius = 0.5

    # Cross-section area
    cross_area = PI * tube_radius * tube_radius

    # Distance from sphere center to this point
    r_pos = _norm(pos)

    # The "sock" shell: intersection sphere (position) ∩ sphere (Frenet)
    # Position sphere: x²+y²+z² = 1
    # Frenet sphere at point P: coordinates in Frenet frame satisfy
    #   (T·δ)² + (N·δ)² + (B·δ)² = r_tube²
    # The boundary where both constraints hold gives the exterior surface
    sock_inner_radius = max(0.0, r_pos - tube_radius)
    sock_outer_radius = min(1.0, r_pos + tube_radius)
    sock_thickness = sock_outer_radius - sock_inner_radius

    # Mass isolation: how much of this basin's mass is contained
    # Higher curvature → more isolated (tighter tube), less leakage
    mass_containment = min(1.0, kappa * tube_radius) if kappa > 0 else 0.5

    # Surface area of tube segment (one basin width)
    arc_length = PI / 130.0 * speed(theta)
    segment_surface = TWO_PI * tube_radius * arc_length

    # Volume of tube segment
    segment_volume = cross_area * arc_length

    return {
        'basin': basin,
        'layer': layer,
        'theta': round(theta, 6),
        'tube': {
            'radius': tube_radius,
            'cross_area': round(cross_area, 6),
            'arc_length': round(arc_length, 6),
            'segment_surface': round(segment_surface, 6),
            'segment_volume': round(segment_volume, 6),
        },
        'sock_shell': {
            'inner_radius': round(sock_inner_radius, 6),
            'outer_radius': round(sock_outer_radius, 6),
            'thickness': round(sock_thickness, 6),
            'formula': 'Sphere(position) ∩ Sphere(Frenet) = exterior surface',
        },
        'mass_containment': round(mass_containment, 6),
        'curvature': round(kappa, 6),
        'frenet_frame': {k: [round(x, 4) for x in v] for k, v in frame.items()},
        'interpretation': (
            f"Basin {basin} (L{layer}) tube segment: "
            f"arc={arc_length:.4f}, radius={tube_radius}, "
            f"containment={mass_containment:.2%}"
        ),
    }


# =============================================================================
# 3D → 2D PROJECTION — Gaussian Splatting pipeline
# =============================================================================

def project_splat(basin: int,
                  camera_pos: Tuple[float, float, float] = (2.0, 0.0, 0.0),
                  focal_length: float = 1.0) -> Dict[str, Any]:
    """Project a basin's 3D Gaussian to 2D screen space.

    Uses the same pipeline as 3D Gaussian Splatting:
    1. World → Camera transform (extrinsic)
    2. Camera → Ray space (non-linear, linearized via Jacobian)
    3. Ray → Screen (intrinsic)
    4. Skip 3rd row/col of Σ' → 2×2 screen covariance

    No neural network. The Jacobian IS the Taylor expansion around
    the Gaussian mean, exactly as in the EWA Volume Splatting paper.
    """
    theta = basin_to_theta(basin)
    pos = viviani_point(theta)

    # Camera direction (camera looks toward origin)
    cx, cy, cz = camera_pos
    dx = pos[0] - cx
    dy = pos[1] - cy
    dz = pos[2] - cz
    depth = math.sqrt(dx*dx + dy*dy + dz*dz)

    if depth < 1e-6:
        return {'basin': basin, 'visible': False, 'reason': 'at_camera'}

    # View direction (normalized)
    view_dir = (dx / depth, dy / depth, dz / depth)

    # Screen-space position (perspective projection)
    # u = focal * x'/z', v = focal * y'/z'
    if abs(dx) < 1e-6:
        return {'basin': basin, 'visible': False, 'reason': 'behind_camera'}

    screen_u = focal_length * dy / (-dx) if dx < 0 else focal_length * dy / dx
    screen_v = focal_length * dz / (-dx) if dx < 0 else focal_length * dz / dx

    # Jacobian of perspective projection (local affine approx)
    # J = [[f/z, 0, -fx/z²], [0, f/z, -fy/z²]]
    inv_depth = 1.0 / depth
    j00 = focal_length * inv_depth
    j11 = focal_length * inv_depth

    # 2D covariance: skip depth dimension
    # Σ_2d ≈ J · Σ_3d · Jᵀ → approximate as scaled ellipse
    splat = basin_splat(basin)
    s_t = splat['scale']['s_tangent']
    s_n = splat['scale']['s_normal']

    # Projected scales
    sigma_u = j00 * max(s_t, s_n)
    sigma_v = j11 * max(s_t, s_n)

    # Alpha blending: α' = α × Gaussian(screen_pos, Σ_2d)
    opacity = splat['opacity_alpha']

    # Backface culling: dot(view_dir, surface_normal)
    # Surface normal on Fernandez sphere = position vector (unit sphere)
    facing = _dot(view_dir, _normalize(pos))
    visible = facing < 0  # Normal points away from camera

    return {
        'basin': basin,
        'visible': visible,
        'screen': {
            'u': round(screen_u, 4),
            'v': round(screen_v, 4),
        },
        'depth': round(depth, 4),
        'projected_scale': {
            'sigma_u': round(sigma_u, 6),
            'sigma_v': round(sigma_v, 6),
        },
        'opacity': round(opacity, 4),
        'color': splat['color_rgb'],
        'facing': round(facing, 4),
        'method': 'EWA Gaussian Splatting (Jacobian linearization)',
        'pipeline': '3D → Camera → Ray (Jacobian) → Screen → 2D Σ',
    }


# =============================================================================
# AGENT SURFACE MAPPING on Fernandez Sphere
# =============================================================================

def agent_surface_map(agent_name: str, agent_basin: int = None) -> Dict[str, Any]:
    """Map an agent to its position on the Fernandez sphere via Viviani's curve.

    Every agent exists at a coordinate. That coordinate maps to a basin.
    The basin maps to a point on Viviani's curve on the Fernandez sphere.
    The Frenet frame at that point gives the agent's local orientation.
    The I-Logic state gives the agent's behavioral mode.
    The Gaussian splat gives the agent's visual representation.

    Self-contextualization: the agent KNOWS its position because its
    basin determines its layer, its layer determines its context,
    and its context determines how it interprets data.
    """
    # Compute basin from agent name if not provided
    if agent_basin is None:
        h = hashlib.md5(agent_name.encode()).digest()
        agent_basin = int.from_bytes(h[:4], 'little') % PRIME_131

    theta = basin_to_theta(agent_basin)
    pos = viviani_point(theta)
    frame = frenet_frame(theta)
    layer = layer_from_basin(agent_basin)
    i_state = ilogic_from_basin(agent_basin)
    flip_basin = membrane_flip(agent_basin)

    # Angular position on equatorial circle
    angle_deg = flip_angle(agent_basin)

    # Self-contextualization: what does this agent "see"?
    # Agents on a world contextualize to that world
    layer_names = {
        1: 'Structure', 2: 'Meaning', 3: 'Location',
        4: 'Operation', 5: 'Uncertainty', 6: 'State',
        7: 'Timing', 8: 'Force', 9: 'Boundary',
    }
    context = layer_names.get(layer, 'Unknown')

    # SH band for view-dependent behavior
    sh = SH_ILOGIC_MAP.get(i_state, SH_ILOGIC_MAP['DIRECT'])

    return {
        'agent': agent_name,
        'basin': agent_basin,
        'layer': layer,
        'layer_name': context,
        'angle_deg': round(angle_deg, 2),
        'theta': round(theta, 6),
        'position_on_sphere': {
            'x': round(pos[0], 6),
            'y': round(pos[1], 6),
            'z': round(pos[2], 6),
        },
        'orientation': {
            'tangent': [round(v, 4) for v in frame['tangent']],
            'normal': [round(v, 4) for v in frame['normal']],
            'bitangent': [round(v, 4) for v in frame['bitangent']],
        },
        'i_logic': i_state,
        'i_logic_complex': str(ilogic_complex(agent_basin)),
        'membrane_flip': flip_basin,
        'flip_partner_layer': layer_from_basin(flip_basin),
        'self_contextualization': (
            f"{agent_name} at B{agent_basin} (L{layer} {context}) "
            f"sees data through {context.lower()} lens — "
            f"I-Logic {i_state}, flip partner B{flip_basin}"
        ),
        'spherical_harmonic': sh,
        'on_fixed_point': agent_basin == FIXED_POINT,
    }


# =============================================================================
# SQUARABLE AREA — Viviani's original proof
# =============================================================================

def squarable_area() -> Dict[str, Any]:
    """Viviani's squarable area proof.

    Hemisphere area = 2πr²
    Viviani's window area = 2 × (π/2 - 1) × r² = (π - 2) × r²
    Remaining area = 2πr² - 2(π-2)r² = 2πr² - 2πr² + 4r² = 4r²

    4r² is the area of a square with side length 2r (the diameter).
    This can be constructed with ruler and compass.

    In CST: r=1, so the squarable area = 4, and this is the
    area of the Fernandez sphere that is deterministically computable.
    """
    r = 1.0
    hemisphere = 2 * PI * r * r
    # Each of the 4 quarter-windows on the hemisphere has area r²(π/2 - 1)
    # Total window area = 4 × r²(π/2 - 1) = 2πr² - 4r²
    one_quarter = r * r * (PI / 2 - 1)
    four_quarters = 4 * one_quarter
    one_window = 2 * one_quarter  # One figure-eight window
    two_windows = four_quarters   # Both windows combined
    remaining = hemisphere - two_windows  # = 4r²

    return {
        'title': "Viviani's Squarable Area (1692)",
        'sphere_radius': r,
        'hemisphere_area': round(hemisphere, 6),
        'one_quarter_area': round(one_quarter, 6),
        'one_window_area': round(one_window, 6),
        'two_windows_area': round(two_windows, 6),
        'remaining_area': round(remaining, 6),
        'is_4r_squared': abs(remaining - 4 * r * r) < 1e-10,
        'square_side_length': 2 * r,
        'proof': (
            f"Hemisphere = 2πr² = {hemisphere:.4f}, "
            f"Windows = 4(π/2-1)r² = {two_windows:.4f}, "
            f"Remaining = {remaining:.4f} = 4r² ✓"
        ),
        'cst_meaning': (
            "The squarable area is the portion of the Fernandez sphere "
            "that is exactly computable — no approximation needed. "
            "4r² = 4 (unit sphere) = the deterministic surface."
        ),
    }


# =============================================================================
# PRE-COMPUTED PATH — all 131 basins on Viviani's curve
# =============================================================================

def _build_viviani_path() -> List[Dict[str, Any]]:
    """Build the complete path through all 131 basins. Computed once."""
    path = [None] * BASIN_COUNT
    b = 0
    while b < BASIN_COUNT:
        theta = basin_to_theta(b)
        pos = viviani_point(theta)
        layer = layer_from_basin(b)
        path[b] = {
            'basin': b,
            'theta': round(theta, 4),
            'x': round(pos[0], 4),
            'y': round(pos[1], 4),
            'z': round(pos[2], 4),
            'layer': layer,
            'i_logic': ilogic_from_basin(b),
            'flip': membrane_flip(b),
            'angle_deg': round(flip_angle(b), 2),
        }
        b += 1
    return path


# Pre-compute at module load — the structure IS the answer
_VIVIANI_PATH = _build_viviani_path()


# =============================================================================
# SHELL CLASS
# =============================================================================

class VivianiShell:
    """Viviani Shell — Sphere-Cylinder Intersection Geometry.

    Viviani's curve maps all 131 basins to the Fernandez sphere surface.
    Each basin is a 3D Gaussian (position, covariance, color, opacity)
    on the figure-eight space curve.  The I-Logic states emerge from
    curve position.  The membrane flip reflects across B65 (fixed point).

    This is how the C backend maps the manifold to the sphere+cylinder:
        Sphere → Fernandez (S = 4πr²)
        Cylinder → Basin tube (tangent at B65)
        Cone → Layer projection
        Viviani's curve → Agent surface mapping (T, N, B frame)
    """

    HEX_ID = "56495649"

    def __init__(self):
        self.name = "viviani"
        self.description = "Sphere-Cylinder intersection: Viviani's curve basin mapping"
        self.commands = {
            'status': 'System status with curve properties',
            'point': 'Curve point + Frenet frame for a basin',
            'splat': 'Gaussian splat properties for a basin',
            'flip': 'Membrane flip pair',
            'path': 'Full path through all 131 basins',
            'ilogic': 'I-Logic state mapping on curve',
            'tube': 'Cross-section tube geometry at basin',
            'project': '3D → 2D splatting projection',
            'agents': 'Agent surface mapping on Fernandez sphere',
            'map': 'Map arbitrary content to Viviani curve (Splatting)',
            'squarable': "Viviani's squarable area proof (4r²)",
        }

    def execute(self, command: str, args: Dict[str, Any] = None) -> Dict[str, Any]:
        args = args or {}
        handlers = {
            'status': self._cmd_status,
            'point': self._cmd_point,
            'splat': self._cmd_splat,
            'flip': self._cmd_flip,
            'path': self._cmd_path,
            'ilogic': self._cmd_ilogic,
            'tube': self._cmd_tube,
            'project': self._cmd_project,
            'agents': self._cmd_agents,
            'map': self._cmd_map,
            'squarable': self._cmd_squarable,
        }
        handler = handlers.get(command)
        if not handler:
            return {'success': False, 'error': f'Unknown command: {command}',
                    'available': list(self.commands.keys())}
        try:
            return handler(args)
        except Exception as e:
            return {'success': False, 'error': str(e)}

    # ── Status ────────────────────────────────────────────────────────

    def _cmd_status(self, args: Dict) -> Dict:
        """System status with Viviani curve properties."""
        # I-Logic distribution across basins — O(1) structural definition
        # B0=ABS, B1-64=ASP, B65=INV, B66-129=SPI, B130=DIR
        dist = {
            'ABSORBED': 1,      # B0
            'ANTI_SPIRAL': 64,  # B1-B64
            'INVERSE': 1,       # B65 (Fixed Point)
            'SPIRAL': 64,       # B66-B129
            'DIRECT': 1         # B130
        }

        return {
            'success': True,
            'shell': 'viviani',
            'hex_id': self.HEX_ID,
            'geometry': {
                'sphere': 'x² + y² + z² = r²  (Fernandez sphere)',
                'cylinder': 'x² + y² - rx = 0  (basin tube)',
                'cone': '(x-r)² + y² = z²     (layer projection)',
                'intersection': "Viviani's curve (figure-eight space curve)",
            },
            'parametric': {
                'x': 'cos²θ',
                'y': 'cosθ·sinθ',
                'z': 'sinθ',
                'range': 'θ ∈ [-π/2, π/2]',
            },
            'basins': {
                'total': BASIN_COUNT,
                'fixed_point': FIXED_POINT,
                'fixed_point_angle': f'{flip_angle(FIXED_POINT):.2f}°',
                'manifold_span': f'{MANIFOLD_SPAN_DEG}°',
                'breathing_gap': f'{BREATHING_GAP_DEG}°',
            },
            'i_logic_distribution': dist,
            'squarable_area': f'4r² = {VIVIANI_AREA} (ruler+compass constructible)',
            'gaussian_splatting': {
                'primitives': '131 Gaussians on Viviani curve',
                'parameters': 'μ (position), Σ (covariance), c (color), α (opacity)',
                'neural_network': False,
                'method': 'Deterministic geometry — no MLP, no training',
            },
            'sh_ilogic_isomorphism': SH_ILOGIC_MAP,
            'timestamp': datetime.now().isoformat(),
        }

    # ── Point + Frenet frame ──────────────────────────────────────────

    def _cmd_point(self, args: Dict) -> Dict:
        basin = int(args.get('basin', FIXED_POINT))
        theta = basin_to_theta(basin)
        pos = viviani_point(theta)
        frame = frenet_frame(theta)
        kappa = curvature(theta)
        spd = speed(theta)
        layer = layer_from_basin(basin)

        return {
            'success': True,
            'basin': basin,
            'layer': layer,
            'theta': round(theta, 6),
            'angle_deg': round(flip_angle(basin), 2),
            'position': {'x': round(pos[0], 10), 'y': round(pos[1], 10), 'z': round(pos[2], 10)},
            'frenet_frame': {k: [round(x, 6) for x in v] for k, v in frame.items()},
            'curvature': round(kappa, 6),
            'speed': round(spd, 6),
            'i_logic': ilogic_from_basin(basin),
            'membrane_flip': membrane_flip(basin),
            'on_sphere': abs(pos[0]**2 + pos[1]**2 + pos[2]**2 - 1.0) < 1e-5,
            'on_cylinder': abs(pos[0]**2 + pos[1]**2 - pos[0]) < 1e-5,
        }

    # ── Gaussian Splat ────────────────────────────────────────────────

    def _cmd_splat(self, args: Dict) -> Dict:
        basin = int(args.get('basin', FIXED_POINT))
        result = basin_splat(basin)
        result['success'] = True
        return result

    # ── Membrane Flip ─────────────────────────────────────────────────

    def _cmd_flip(self, args: Dict) -> Dict:
        basin = int(args.get('basin', 0))
        partner = membrane_flip(basin)
        theta_a = basin_to_theta(basin)
        theta_b = basin_to_theta(partner)
        pos_a = viviani_point(theta_a)
        pos_b = viviani_point(theta_b)

        # Distance between flip pair on the sphere
        dx = pos_a[0] - pos_b[0]
        dy = pos_a[1] - pos_b[1]
        dz = pos_a[2] - pos_b[2]
        pair_distance = math.sqrt(dx*dx + dy*dy + dz*dz)

        return {
            'success': True,
            'basin': basin,
            'flip_partner': partner,
            'is_fixed_point': basin == FIXED_POINT,
            'angle_a': round(flip_angle(basin), 2),
            'angle_b': round(flip_angle(partner), 2),
            'position_a': [round(v, 4) for v in pos_a],
            'position_b': [round(v, 4) for v in pos_b],
            'pair_distance': round(pair_distance, 6),
            'i_logic_a': ilogic_from_basin(basin),
            'i_logic_b': ilogic_from_basin(partner),
            'layer_a': layer_from_basin(basin),
            'layer_b': layer_from_basin(partner),
            'formula': '(130 - B) — reflection across B65',
        }

    # ── Full Path ─────────────────────────────────────────────────────

    def _cmd_path(self, args: Dict) -> Dict:
        start = int(args.get('start', 0))
        end = int(args.get('end', 130))
        step = int(args.get('step', 1))

        # Bounded extraction from pre-computed path
        selection = []
        b = start
        while b <= min(end, 130):
            selection.append(_VIVIANI_PATH[b])
            b += step

        return {
            'success': True,
            'total_basins': BASIN_COUNT,
            'shown': len(selection),
            'range': f'B{start}..B{end} (step={step})',
            'path': selection,
        }

    # ── I-Logic Mapping ───────────────────────────────────────────────

    def _cmd_ilogic(self, args: Dict) -> Dict:
        """I-Logic state distribution on the curve."""
        regions = {
            'ABSORBED':     {'basins': [0],         'range': 'B0',       'theta': '-π/2',    'meaning': 'C=0 origin entry'},
            'ANTI_SPIRAL':  {'basins': list(range(1, 65)), 'range': 'B1-B64',  'theta': '(-π/2, 0)', 'meaning': 'Descending, contraction (-i)'},
            'INVERSE':      {'basins': [65],        'range': 'B65',      'theta': '0',        'meaning': 'Self-reflection fixed point (-1)'},
            'SPIRAL':       {'basins': list(range(66, 130)), 'range': 'B66-B129', 'theta': '(0, π/2)', 'meaning': 'Ascending, expansion (+i)'},
            'DIRECT':       {'basins': [130],       'range': 'B130',     'theta': 'π/2',      'meaning': 'Identity at top pole (+1)'},
        }
        counts = {k: len(v['basins']) for k, v in regions.items()}

        return {
            'success': True,
            'title': 'I-Logic States on Viviani Curve',
            'regions': {k: {'range': v['range'], 'theta': v['theta'],
                           'count': counts[k], 'meaning': v['meaning']}
                       for k, v in regions.items()},
            'total': sum(counts.values()),
            'fixed_point': 'B65 = INVERSE (inside = outside)',
            'sh_isomorphism': SH_ILOGIC_MAP,
            'algebra': (
                'SPIRAL × SPIRAL = INVERSE (i×i = -1), '
                'ANTI_SPIRAL × ANTI_SPIRAL = INVERSE (-i×-i = -1), '
                'SPIRAL × ANTI_SPIRAL = DIRECT (i×-i = 1)'
            ),
        }

    # ── Tube Geometry ─────────────────────────────────────────────────

    def _cmd_tube(self, args: Dict) -> Dict:
        basin = int(args.get('basin', FIXED_POINT))
        result = tube_cross_section(basin)
        result['success'] = True
        return result

    # ── 3D → 2D Projection ────────────────────────────────────────────

    def _cmd_project(self, args: Dict) -> Dict:
        basin = int(args.get('basin', FIXED_POINT))
        cx = float(args.get('camera_x', 2.0))
        cy = float(args.get('camera_y', 0.0))
        cz = float(args.get('camera_z', 0.0))
        focal = float(args.get('focal', 1.0))
        result = project_splat(basin, (cx, cy, cz), focal)
        result['success'] = True
        return result

    # ── Agent Surface Mapping ─────────────────────────────────────────

    def _cmd_agents(self, args: Dict) -> Dict:
        agent = args.get('agent', 'Observer')
        basin = int(args.get('basin', -1))
        result = agent_surface_map(agent, basin if basin >= 0 else None)
        result['success'] = True
        return result

    # ── Squarable Area ────────────────────────────────────────────────

    def _cmd_squarable(self, args: Dict) -> Dict:
        result = squarable_area()
        result['success'] = True
        return result

    # ── CLI interface ─────────────────────────────────────────────────

    def run(self, command: str, *args, as_json: bool = False) -> Any:
        """Run a shell command (CLI interface)."""
        cmd_args = {}
        # Parse positional args based on command
        if command == 'point' and args:
            cmd_args['basin'] = args[0]
        elif command == 'splat' and args:
            cmd_args['basin'] = args[0]
        elif command == 'flip' and args:
            cmd_args['basin'] = args[0]
        elif command == 'tube' and args:
            cmd_args['basin'] = args[0]
        elif command == 'project' and args:
            cmd_args['basin'] = args[0]
        elif command == 'agents' and args:
            cmd_args['agent'] = args[0]
        elif command == 'path' and args:
            if len(args) >= 2:
                cmd_args['start'] = args[0]
                cmd_args['end'] = args[1]
            elif len(args) == 1:
                cmd_args['end'] = args[0]
        # Parse key=value args
        for a in args:
            if isinstance(a, str) and '=' in a:
                k, v = a.split('=', 1)
                cmd_args[k] = v

        result = self.execute(command, cmd_args)
        if as_json and isinstance(result, dict):
            return json.dumps(result, indent=2, default=str)
        return result

    # ── Map Content ───────────────────────────────────────────────────

    def _cmd_map(self, args: Dict) -> Dict:
        """Map arbitrary content to the Viviani curve (Splatting)."""
        content = args.get('content', '')
        if not content:
            return {'success': False, 'error': 'Content required'}

        # Hash to basin
        h = hashlib.md5(content.encode()).digest()
        basin = int.from_bytes(h[:4], 'little') % PRIME_131

        # Get splat properties
        splat = basin_splat(basin)

        # Map to curve
        result = agent_surface_map(content, basin)
        result.update({
            'splat': splat,
            'mapping_type': 'Content -> MD5 -> Basin -> Viviani Curve',
            'meaning': f"'{content}' exists at θ={result['theta']} on L{result['layer']}"
        })
        return result
