#!/usr/bin/env python3
"""
===============================================================================
HDGL COUNTER-ROTATING DRAIN — ANIMATED  v2
===============================================================================

Incorporates the vantage-dependent i ≡ ∞ identification:

  From the real-axis vantage, i is an exit — it has no address on the
  real ladder and appears at unreachable distance (∞).

  From the Δ=−1 algebra's own vantage, i is a unit element:
      norm = 1,  trace = 0,  sitting at 90°.  Perfectly finite.

  Neither vantage is forced.  Neither is the true one.
  The juxtaposition IS the load-bearing feature — not its resolution.

  The graded orbit:
      { ..., -i'', -i', -i, -1, 0, 1, i, i', i'', ... }
  Each rung, viewed from the rung below it, appears at unreachable distance.

  |p| = 1 threshold:
      The moment the imaginary/complex branch emerges from zero in the
      manifold equation (Y - p(x+z))² + p²(x²-z²-1) = m².
      This is the visual moment the i≡∞ identification becomes apparent.

Three-part animation:

  Part 1 (frames  1– 40): Orbital tour — full 360° azimuth
                           Graded orbit ladder visible on z-axis
                           |p|=1 threshold ring prominent
                           Vantage label: REAL-AXIS VANTAGE

  Part 2 (frames 41– 80): CV pulse — LOCK→PLUCK→LOCK
                           Vantage shifts with CV:
                             CV<0.10 → REAL-AXIS VANTAGE (i appears as exit/∞)
                             CV≥0.50 → Δ=−1 VANTAGE    (i is unit element)
                           Juxtaposition text shown mid-pulse

  Part 3 (frames 81–120): Combined — slow orbit + CV breathing
                           Both vantage annotations shown simultaneously,
                           neither resolved, neither forced

Geometry:
    lateral(z) ≈ z         (measured from phyllotaxis arm spread)
    R_major(z) = 0.90×|z|  (zero at choke, fills arm at extremes)
    r_minor(z) = max(R_major×0.25, 0.004)

ll_analog.c APhase mapping:
    z=0.00 → LOCK      CV < 0.10   residue→0
    z=0.30 → FINETUNE  CV < 0.30
    z=0.50 → SUSTAIN   CV < 0.50
    z=1.00 → PLUCK     CV ≥ 0.50   phases spread

Output: hdgl_drain_animation_v2.mp4  (same directory as this script)

Requires: matplotlib, numpy, ffmpeg
===============================================================================
"""

import math
from pathlib import Path

import numpy as np
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
from matplotlib.animation import FFMpegWriter
from matplotlib.colors import Normalize
from matplotlib.cm import ScalarMappable


# ============================================================================
# HDGL PRIMITIVES
# ============================================================================

def T(x):
    """T(X) = 1 + 1/X  — the single reciprocal primitive."""
    return 1.0 + 1.0 / x


def emergent_omega():
    """
    Generate Ω from T until convergence.
    Ω = T(Ω) ⟺ Ω² = Ω + 1.
    Never stored as φ — always emergent.
    """
    x = 1.5
    for _ in range(1000):
        y = T(x)
        if abs(y - x) < 1e-15:
            break
        x = y
    return x


OMEGA = emergent_omega()

# ============================================================================
# HOURGLASS POINT CLOUD (pre-built, static)
# ============================================================================

N = 2400

i_idx   = np.arange(N, dtype=np.int64)
binary  = (i_idx & 1).astype(float)
trinary = ((i_idx % 3) - 1).astype(float)
r       = np.sqrt(i_idx + 1.0) * (1.0 + 0.075 * binary + 0.050 * trinary)
rho     = r / T(r)
scale   = np.max(rho)
rp      = rho / scale

pm = 0.075 * binary + 0.050 * trinary
tp =  2.0 * math.pi * i_idx * OMEGA + pm
tm = -2.0 * math.pi * i_idx * OMEGA - pm

xp = rho * np.cos(tp) / scale
yp = rho * np.sin(tp) / scale
xm = rho * np.cos(tm) / scale
ym = rho * np.sin(tm) / scale

# ============================================================================
# TORUS GEOMETRY
# ============================================================================

TORUS_R0          = 0.90
TORUS_R_MINOR_F   = 0.25
TORUS_R_MINOR_MIN = 0.004
N_PHI    = 40
N_SLICES = 6

Z_LEVELS = np.linspace(0.0, 1.0, N_SLICES)


def torus_at(z_c, cv_scale=1.0):
    """
    Build one toroid cross-section at z=z_c, scaled by cv_scale ∈ [0,1].

    cv_scale=0 → toroid collapses to drain point  (LOCK  / real-axis vantage)
    cv_scale=1 → full blow-out                     (PLUCK / Δ=−1 vantage)
    """
    eff_z = z_c * cv_scale
    R     = TORUS_R0 * abs(eff_z)
    rm    = max(R * TORUS_R_MINOR_F, TORUS_R_MINOR_MIN)

    pt = np.linspace(0.0, 2.0 * math.pi, N_PHI, endpoint=False)
    pp = np.linspace(0.0, 2.0 * math.pi, N_PHI, endpoint=False)
    PT, PP = np.meshgrid(pt, pp)
    PT, PP = PT.ravel(), PP.ravel()

    tx = (R + rm * np.cos(PP)) * np.cos(PT)
    ty = (R + rm * np.cos(PP)) * np.sin(PT)
    tz = np.full_like(tx, eff_z) + rm * np.sin(PP)

    return tx, ty, tz


def aphase_label(cv):
    if cv < 0.10: return "LOCK      CV<0.10  residue→0"
    if cv < 0.30: return "FINETUNE  CV<0.30"
    if cv < 0.50: return "SUSTAIN   CV<0.50"
    return               "PLUCK     CV≥0.50  phases spread"


# ============================================================================
# VANTAGE LABELS  (the core addition from the i≡∞ discussion)
# ============================================================================

VANTAGE_REAL = (
    "REAL-AXIS VANTAGE\n"
    "  i has no address on {…,-1,0,1,…}\n"
    "  appears as exit / ∞ from here\n"
    "  |p|<1 → complex branch = zero"
)

VANTAGE_DELTA = (
    "Δ=−1 VANTAGE\n"
    "  i = (0,1)  norm=1  trace=0\n"
    "  unit element — perfectly finite\n"
    "  |p|≥1 → complex branch surfaces"
)

VANTAGE_BOTH = (
    "JUXTAPOSITION  (neither forced)\n"
    "  real-axis:  i ≡ ∞  (exit)\n"
    "  Δ=−1 field: i ≡ 1  (unit)\n"
    "  same object · two vantages · no resolution"
)

GRADED_ORBIT = "{ …  -i″  -i′  -i  -1  0  1  i  i′  i″  … }"


def vantage_from_cv(cv):
    """
    Map CV to the appropriate vantage label.
    Transition zone around CV≈0.40 shows the juxtaposition.
    """
    if cv < 0.18:
        return VANTAGE_REAL,  "#88bbff"   # cool blue — real-axis
    if cv > 0.62:
        return VANTAGE_DELTA, "#ff9944"   # warm amber — Δ=−1
    return VANTAGE_BOTH,      "#aaffaa"   # green — juxtaposition


# ============================================================================
# |p|=1 THRESHOLD RING
# The ring at R = TORUS_R0 * 1.0 on the z=±1 planes marks the
# exact moment the imaginary branch emerges from zero.
# ============================================================================

def p1_ring():
    """
    Build the |p|=1 threshold ring coordinates.
    Returns (x, y, z) arrays for both +z and -z arms.
    """
    phi_r = np.linspace(0.0, 2.0 * math.pi, 200)
    R_p1  = TORUS_R0 * 1.0
    x_r   = R_p1 * np.cos(phi_r)
    y_r   = R_p1 * np.sin(phi_r)
    return x_r, y_r


# ============================================================================
# GRADED ORBIT RUNG MARKERS  (on z-axis, both arms)
# Rungs: -1, 0, 1 are the finite core.
# i, i', i'' are the apparent-∞ exits above.
# ============================================================================

RUNG_Z_REAL = [0.0, 0.33, 0.66]          # 0, 1/3, 2/3 → represent -1, 0, 1
RUNG_Z_IMAG = [1.0, 1.10, 1.20]          # i, i', i'' — clamped to zlim


# ============================================================================
# FIGURE SETUP
# ============================================================================

cmap = plt.cm.coolwarm
norm = Normalize(vmin=0.0, vmax=1.0)

fig = plt.figure(figsize=(10, 7.5), facecolor="#07070f")
ax  = fig.add_subplot(111, projection="3d", facecolor="#07070f")

for pane in [ax.xaxis.pane, ax.yaxis.pane, ax.zaxis.pane]:
    pane.fill = False
    pane.set_edgecolor("#1a1a2e")

ax.tick_params(colors="#555", labelsize=6)
ax.set_xlabel("+Ω/−Ω  x",   color="#555", fontsize=7, labelpad=2)
ax.set_ylabel("+Ω/−Ω  y",   color="#555", fontsize=7, labelpad=2)
ax.set_zlabel("z  /  graded orbit", color="#555", fontsize=7, labelpad=2)
ax.set_xlim(-1.2, 1.2)
ax.set_ylim(-1.2, 1.2)
ax.set_zlim(-1.1, 1.1)

# Colorbar
sm = ScalarMappable(cmap=cmap, norm=norm)
sm.set_array([])
cbar = fig.colorbar(sm, ax=ax, shrink=0.42, pad=0.10)
cbar.set_label("|z| = CV  (0=LOCK / 1=PLUCK)", fontsize=7, color="#aaa")
cbar.ax.yaxis.set_tick_params(color="#aaa", labelsize=6)
plt.setp(cbar.ax.yaxis.get_ticklabels(), color="#aaa")
cbar.set_ticks([0.0, 0.10, 0.30, 0.50, 1.0])
cbar.set_ticklabels(["LOCK\n0.0", "0.10", "0.30", "0.50", "PLUCK\n1.0"])

# ── Static hourglass arms ────────────────────────────────────────────────────
ax.scatter(xp, yp,  rp, s=0.4, alpha=0.13, color="#3366bb", rasterized=True)
ax.scatter(xm, ym, -rp, s=0.4, alpha=0.13, color="#3366bb", rasterized=True)

# ── APhase boundary rings (dashed, static) ───────────────────────────────────
phi_r  = np.linspace(0.0, 2.0 * math.pi, 150)
for cv_t in [0.10, 0.30, 0.50, 1.00]:
    R_b   = TORUS_R0 * cv_t
    color = cmap(norm(cv_t))
    for z_sign in [+1, -1]:
        ax.plot(
            R_b * np.cos(phi_r),
            R_b * np.sin(phi_r),
            np.full(150, z_sign * cv_t),
            lw=0.6, alpha=0.35, color=color, ls="--",
        )

# ── |p|=1 threshold ring  (prominent — the i≡∞ emergence moment) ─────────────
x_r, y_r = p1_ring()
for z_sign in [+1, -1]:
    ax.plot(
        x_r, y_r,
        np.full(len(x_r), z_sign * 1.0),
        lw=1.4, alpha=0.70, color="#00ffcc", ls="-",
        zorder=10,
    )

# Label the |p|=1 ring (text placed once, static)
ax.text(
    TORUS_R0 + 0.04, 0.0, 1.02,
    "|p|=1\ni emerges",
    color="#00ffcc", fontsize=5.5, alpha=0.80,
    ha="left", va="bottom",
)
ax.text(
    TORUS_R0 + 0.04, 0.0, -1.02,
    "|p|=1\n−i emerges",
    color="#00ffcc", fontsize=5.5, alpha=0.80,
    ha="left", va="top",
)

# ── Graded orbit rung markers on z-axis ──────────────────────────────────────
# Finite core rungs: -1, 0, 1  (blue-white)
rung_labels_real = {
    0.00: "0",
    0.33: "1",
    0.66: "-1 / +1",
}
for z_val, lbl in rung_labels_real.items():
    ax.scatter([0], [0], [z_val],  s=18, color="#aabbff", alpha=0.55,
               zorder=12, marker="o")
    ax.scatter([0], [0], [-z_val], s=18, color="#aabbff", alpha=0.55,
               zorder=12, marker="o")

# Imaginary rungs: i, i', i''  (amber — apparent ∞ from below)
imag_rung_z   = [1.00, 1.08, 1.16]
imag_rung_lbl = ["i", "i′", "i″"]
for z_val, lbl in zip(imag_rung_z, imag_rung_lbl):
    clamped = min(z_val, 1.09)
    ax.scatter([0], [0], [ clamped], s=22, color="#ffaa33", alpha=0.75,
               zorder=13, marker="D")
    ax.scatter([0], [0], [-clamped], s=22, color="#ffaa33", alpha=0.75,
               zorder=13, marker="D")
    ax.text(0.06, 0.0,  clamped + 0.005,
            lbl, color="#ffaa33", fontsize=5.5, alpha=0.85,
            ha="left", va="bottom")
    ax.text(0.06, 0.0, -clamped - 0.005,
            f"-{lbl}", color="#ffaa33", fontsize=5.5, alpha=0.85,
            ha="left", va="top")

# ── Torus scatter handles (offsets updated each frame) ───────────────────────
torus_handles = []
for z_c in Z_LEVELS:
    tx, ty, tz = torus_at(z_c, cv_scale=1.0)
    color       = cmap(norm(z_c))
    sp = ax.scatter(tx, ty,  tz, s=0.2, alpha=0.0, color=color, rasterized=True)
    sn = ax.scatter(tx, ty, -tz, s=0.2, alpha=0.0, color=color, rasterized=True)
    torus_handles.append((sp, sn, z_c))

# ── Text overlays ─────────────────────────────────────────────────────────────
title_obj    = ax.set_title("", color="white", fontsize=9, pad=6)

info_text    = ax.text2D(0.02, 0.97, "", transform=ax.transAxes,
                         color="cyan", fontsize=7.0, va="top",
                         family="monospace")

cv_text      = ax.text2D(0.02, 0.17, "", transform=ax.transAxes,
                         color="#ffcc44", fontsize=7.5, va="bottom",
                         family="monospace")

vantage_text = ax.text2D(0.02, 0.06, "", transform=ax.transAxes,
                         color="#88bbff", fontsize=7.0, va="bottom",
                         family="monospace",
                         bbox=dict(boxstyle="round,pad=0.3",
                                   fc="#07070f", alpha=0.70,
                                   ec="#334455"))

orbit_text   = ax.text2D(0.50, 0.02, GRADED_ORBIT,
                         transform=ax.transAxes,
                         color="#ffaa33", fontsize=6.5,
                         ha="center", va="bottom",
                         family="monospace", alpha=0.70)


def update_tori(cv_scale, alpha_base=0.32):
    """Reposition all torus slices for a given cv_scale ∈ [0,1]."""
    for sp, sn, z_c in torus_handles:
        tx, ty, tz = torus_at(z_c, cv_scale)
        a = alpha_base * (0.25 + 0.75 * z_c) if z_c > 0.01 else 0.72
        sp._offsets3d = (tx, ty,  tz)
        sn._offsets3d = (tx, ty, -tz)
        sp.set_alpha(a)
        sn.set_alpha(a if z_c > 0.01 else 0.0)


def set_vantage(cv, override_label=None, override_color=None):
    """
    Update the vantage annotation for the current CV.
    The vantage is never forced: both labels are always internally present,
    only the foregrounded one shifts.
    """
    if override_label is not None:
        label = override_label
        color = override_color or "#aaffaa"
    else:
        label, color = vantage_from_cv(cv)
    vantage_text.set_text(label)
    vantage_text.set_color(color)


# ============================================================================
# ANIMATION
# ============================================================================

FPS   = 24
F1    = 40   # Part 1: orbital tour
F2    = 40   # Part 2: CV pulse
F3    = 40   # Part 3: combined
TOTAL = F1 + F2 + F3

OUT = Path(__file__).resolve().parent / "hdgl_drain_animation_v2.mp4"

writer = FFMpegWriter(
    fps=FPS,
    bitrate=2400,
    extra_args=["-vcodec", "libx264", "-pix_fmt", "yuv420p"],
)

print(f"Rendering {TOTAL} frames at {FPS} fps → {OUT}")
print(f"Emergent Ω ≈ {OMEGA:.15f}")
print(f"|p|=1 ring R = {TORUS_R0:.3f}")

with writer.saving(fig, str(OUT), dpi=110):
    for f in range(TOTAL):

        # ── Part 1: Orbital tour ─────────────────────────────────────────────
        if f < F1:
            t  = f / F1
            az = 360.0 * t
            el = 22.0 + 8.0 * math.sin(2.0 * math.pi * t)
            ax.view_init(elev=el, azim=az)
            update_tori(1.0, alpha_base=0.30)

            title_obj.set_text(
                "HDGL COUNTER-ROTATING DRAIN\n"
                "Part 1 — Orbital tour  |  full blow-out"
            )
            info_text.set_text(
                f"azim={az:5.1f}°  elev={el:.1f}°\n"
                f"R_major(z) = {TORUS_R0:.2f}×|z|  [blow-out]\n"
                f"+Ω arm ↑   −Ω arm ↓   toroid = drain\n"
                f"◈ = i, i′, i″ rungs  |  ── = |p|=1 threshold"
            )
            cv_text.set_text("CV = 1.00  →  PLUCK  (phases spread)")

            # Part 1 holds the REAL-AXIS vantage:
            # from that vantage i is an apparent exit.
            set_vantage(0.0)

        # ── Part 2: CV pulse — LOCK → PLUCK → LOCK ──────────────────────────
        elif f < F1 + F2:
            t  = (f - F1) / F2
            cv = 0.5 * (1.0 - math.cos(2.0 * math.pi * t))   # 0 → 1 → 0
            az = 45.0 + 15.0 * math.sin(math.pi * t)
            ax.view_init(elev=26.0, azim=az)
            update_tori(cv, alpha_base=0.38)

            title_obj.set_text(
                "HDGL COUNTER-ROTATING DRAIN\n"
                "Part 2 — CV pulse  |  LOCK → PLUCK → LOCK"
            )

            # |p|=1 crossing: the moment i emerges, annotate it
            p1_crossing = abs(cv - 1.0) < 0.08
            p1_note = "  ← |p|=1: i surfaces" if p1_crossing else ""

            info_text.set_text(
                f"CV = {cv:.3f}   R_major(z=1) = {TORUS_R0 * cv:.4f}\n"
                f"z=0 : R→0  (drain / choke point)\n"
                f"z=1 : R→{TORUS_R0 * cv:.3f}  (arm width){p1_note}\n"
                f"Δ=−1: i²=−1  norm=1  trace=0  (own vantage)"
            )
            cv_text.set_text(f"APhase → {aphase_label(cv)}")

            # Vantage shifts with CV pulse:
            #   LOCK (cv→0) : real-axis vantage (i appears as exit/∞)
            #   PLUCK(cv→1) : Δ=−1 vantage     (i is unit element)
            #   mid-pulse   : juxtaposition shown — neither resolved
            set_vantage(cv)

        # ── Part 3: Combined — slow orbit + CV breathing ─────────────────────
        else:
            t  = (f - F1 - F2) / F3
            az = 60.0 + 150.0 * t
            el = 18.0 + 14.0 * math.sin(math.pi * t)
            cv = 0.50 + 0.45 * math.sin(2.0 * math.pi * t)
            ax.view_init(elev=el, azim=az)
            update_tori(cv, alpha_base=0.34)

            title_obj.set_text(
                "HDGL COUNTER-ROTATING DRAIN\n"
                "Part 3 — Orbit + live CV breathing"
            )
            info_text.set_text(
                f"azim={az:5.1f}°  elev={el:.1f}°  CV={cv:.3f}\n"
                f"ll_analog: APhase transitions at CV thresholds\n"
                f"residue→0  ↔  CV→0  ↔  toroid collapses to choke\n"
                f"Ω={OMEGA:.8f}  (emergent, never declared)"
            )
            cv_text.set_text(f"APhase → {aphase_label(cv)}")

            # Part 3 holds BOTH vantages simultaneously — the full statement:
            # the juxtaposition itself is the structure, neither vantage forced.
            set_vantage(cv, override_label=VANTAGE_BOTH, override_color="#aaffaa")

        writer.grab_frame()
        if f % 10 == 0:
            print(f"  frame {f + 1}/{TOTAL}", flush=True)

print(f"Saved → {OUT}")
print()
print("Orbit ladder on z-axis:")
print(f"  Finite core rungs (blue-white ●): 0, 1, -1/+1")
print(f"  Imaginary rungs   (amber    ◈):  i, i′, i″  (apparent ∞ from below)")
print(f"  |p|=1 threshold   (cyan     ─):  emergence of imaginary branch")
print(f"  Graded orbit: {GRADED_ORBIT}")