"""RUN-060 - LESR Paper 09B: the analytic primary blow-up equations.

LESR Paper 09B (2026-09-21) derives Paper 09A's numerical blow-up velocity from the contact geometry. At each of the
three triple walls (the top pi/2 and the left/right pi -/+ theta*) the three bodies' first-order support lines
a_i + b_i tau must meet at one common transition speed, or a third-body sliver opens at O(rho): a3 b5 - a5 b3 = 0. With
six wall constants (beta3T, beta5T, gammaT, Ltheta, Lalpha, gammaL) the three equations are linear in (u, v, w);
E_R - E_L = 2 Ltheta cos(theta*) w forces w* = 0, and the top and left equations give u* = 5.157126245268359,
v* = -0.1892281899066407 in closed form. It gives the primary transition speeds (tau_T = tau_bottom = -0.14689...,
tau_L = tau_R = 0.08175..., tau_alpha = 2.57033...), a primary 15-piece structure, and the primary chart's first area
variation Phi1_primary = gamma_w w, gamma_w = 0.18389963923727759, the u and v terms cancelling identically. Full
multi-chart Phi1 nonnegativity is left open. It ships a result record, a certificate schema, a verifier, a state
record and VALIDATION. It is evidence, not a canonical document. Here the constants and the solution are rebuilt from
the barrier's geometry at 70 digits, the speeds against the true hull, Phi1_primary from the area of the forced
15-piece chain, and the full Phi1 against the true hull around the critical velocity.

Checks
  A0 anchors      the statements below occur in the document as written
  W1 constants    sec. 4-5: the six wall constants from the barrier (B3 and B5's corners and arcs, x5*, theta*)
  W2 velocity     sec. 3, 6-9, 16: the collapse system - its rank, its solution, the closed forms, w* = 0 - against the
                  text, the result record and RUN-059's numerical critical point
  W3 speeds       sec. 10: the transition speeds, and the true hull's hand-overs along the critical velocity
  W4 structure    sec. 11: the primary 15-piece signature along the critical velocity and at RUN-059's minima
  W5 primary      sec. 12-14: the forced 15-piece chain's first area variation - its affine coefficients
  W6 full Phi1    sec. 15-16, 20: the true hull's one-sided rates at the critical velocity; what the slivers add
  W7 records      the result record, the schema and the state against the text; Paper 09C, named next, not archived
  W8 verifier     the verifier, read after this rebuild and never run
  P1 package      the VALIDATION record against the bytes and the archive

Usage: python code/run060_lesr_paper09b.py [--drill] [--blind CID]
"""
import argparse
import copy
import hashlib
import inspect
import json
import math
import re

import mpmath as mp

import gate
import run046_uesfcm_cycles_07_09 as r46
import run059_lesr_paper09a as r59
from corpus import SOURCES, TREE, source_bytes, walk_sources

FOLDER = "main.zip!/LESR_Skew_Field_Variational/"
PKG = FOLDER + "LESR_Paper09B_Package_v0.1.zip!/"
DOC = PKG + "LESR_Paper09B_Analytic_Primary_Blowup_Equations_v0.1.md"
RESULT = PKG + "LESR_Paper09B_Analytic_Primary_Blowup_Result_v0.1.json"
SCHEMA = PKG + "LESR_Paper09B_Certificate_Schema_v0.1.json"
VERIFIER = PKG + "LESR_Paper09B_Verify_Primary_Blowup.py"
STATE = PKG + "LESR_State_Paper09B_v0.1.json"
VALIDATION = PKG + "LESR_Paper09B_VALIDATION.json"
LOG = TREE / "data" / "gate-logs" / "run060-lesr-paper09b.json"
LOG59 = TREE / "data" / "gate-logs" / "run059-lesr-paper09a.json"
MANIFEST = TREE / "data" / "SOURCE_MANIFEST.v1.json"
CACHE = TREE / "data" / "cache" / "run060-searches.json"     # data/cache is not committed
DPS = 70


def load_inputs():
    blobs = {n: source_bytes(n) for n in SOURCES}
    want = (DOC, RESULT, SCHEMA, VERIFIER, STATE, VALIDATION)
    got = {leaf.id: d for leaf, d in walk_sources(blobs, keep=lambda l: l.id in want) if d is not None}
    rows = json.loads(MANIFEST.read_text(encoding="utf-8"))["leaves"]
    return {"text": got[DOC].decode("utf-8"), "result": json.loads(got[RESULT]), "schema": json.loads(got[SCHEMA]), "state": json.loads(got[STATE]),
            "verifier": got[VERIFIER].decode("utf-8"), "validation": json.loads(got[VALIDATION]), "bytes": {k: got[k] for k in want},
            "manifest_rows": {r[0]: r for r in rows if r[0] in want}, "archived": sorted(r[0].split("!/")[-1] for r in rows if "Paper09" in r[0]),
            "log59": json.loads(LOG59.read_text(encoding="utf-8"))}


# ---------------------------------------------------------------- the barrier, at 70 digits

def geometry():
    """B3 (corners at 2 pi k / 3, radius 1/sqrt3) at (x3*, 0), x3* = x3^0 - s; the disc of radius 1/2; theta*, where B3's
    corner v0 meets the disc; x5*, where B5's arc about v3 supports at 1/2 at theta*; B5 (corners at 2 pi k / 5, radius
    1 / (2 cos(pi/10))) at (x5*, 0); alpha*, where that arc crosses the disc again (RUN-057). At the caller's precision"""
    pi = mp.pi
    s, x30 = mp.mpf("0.00882"), mp.mpf("-0.01215883209899")
    r3, r5 = 1 / mp.sqrt(3), 1 / (2 * mp.cos(pi / 10))
    x3 = x30 - s
    th = mp.acos(1 / (2 * (r3 + x3)))
    x5 = (-mp.mpf(1) / 2 - r5 * mp.cos(6 * pi / 5 - th)) / mp.cos(th)
    V = [(x3 + r3 * mp.cos(2 * pi * k / 3), r3 * mp.sin(2 * pi * k / 3)) for k in range(3)]
    W = [(x5 + r5 * mp.cos(2 * pi * k / 5), r5 * mp.sin(2 * pi * k / 5)) for k in range(5)]
    al = mp.findroot(lambda a: W[3][0] * mp.cos(a) + W[3][1] * mp.sin(a) + mp.mpf(1) / 2, mp.mpf("0.848180369775584"))
    return {"s": s, "x30": x30, "r3": r3, "r5": r5, "th": th, "x5": x5, "V": V, "W": W, "al": al}


def wall_constants():
    """sec. 4-5. A piece with centre p has support p . u(theta) + r; at a wall normal theta0 its first-order line is
    a + b tau with a = pdot . u(theta0) and b = p . u'(theta0) (the disc's are 0). B3 moves at (0, s u) on its circle;
    B5's points move at (w, v) + J (p - C5) as it turns. Top wall (pi/2): B3's arc about v2, B5's corner v1. Left wall
    (pi - theta*): B3's arc about v0, B5's corner v2"""
    mp.mp.dps = DPS
    g = geometry()
    pi, th, V, W, x5 = mp.pi, g["th"], g["V"], g["W"], g["x5"]
    du = lambda t: (-mp.sin(t), mp.cos(t))
    dot = lambda p, q: p[0] * q[0] + p[1] * q[1]
    return {"beta3T": dot(V[2], du(pi / 2)), "beta5T": dot(W[1], du(pi / 2)), "gammaT": W[1][0] - x5,
            "Ltheta": -dot(V[0], du(pi - th)), "Lalpha": -dot(W[2], du(pi - th)), "gammaL": mp.cos(th) * W[2][1] + mp.sin(th) * (W[2][0] - x5),
            "theta": th, "alpha": g["al"], "sin": mp.sin(th), "cos": mp.cos(th), "s": g["s"]}


def collapse():
    """sec. 4-8: E_T, E_L, E_R as a linear system in (u, v, w): its determinant, its solution, the closed forms, and
    E_R - E_L as a multiple of w; sec. 10: the transition speeds at the solution"""
    c = wall_constants()                                             # at DPS digits
    s, sn, cs = c["s"], c["sin"], c["cos"]
    A = mp.matrix([[s * c["beta5T"], -c["beta3T"], 0], [-s * sn * c["Lalpha"], c["Ltheta"] * sn, -c["Ltheta"] * cs],
                   [-s * sn * c["Lalpha"], c["Ltheta"] * sn, c["Ltheta"] * cs]])
    rhs = mp.matrix([c["beta3T"] * c["gammaT"], -c["Ltheta"] * c["gammaL"], -c["Ltheta"] * c["gammaL"]])
    sol = mp.lu_solve(A, rhs)
    u, v, w = sol[0], sol[1], sol[2]
    uf = c["Ltheta"] * (c["gammaL"] - sn * c["gammaT"]) / (s * sn * (c["Lalpha"] - c["Ltheta"] * c["beta5T"] / c["beta3T"]))
    vf = s * uf * c["beta5T"] / c["beta3T"] - c["gammaT"]
    g = geometry()
    W3, al = g["W"][3], g["al"]
    pd = (-W3[1], v + W3[0] - g["x5"])                                   # B5's v3 at w = 0
    tau_al = -(pd[0] * mp.cos(al) + pd[1] * mp.sin(al)) / (-W3[0] * mp.sin(al) + W3[1] * mp.cos(al))
    diff = (A[2, 0] - A[1, 0], A[2, 1] - A[1, 1], A[2, 2] - A[1, 2])     # E_R - E_L, coefficient by coefficient
    return {"det": mp.det(A), "u": u, "v": v, "w": w, "uf": uf, "vf": vf, "diff": diff, "two_Lcos": 2 * c["Ltheta"] * cs,
            "tauT": -s * u / c["beta3T"], "tauL": s * sn * u / c["Ltheta"], "tau_alpha": tau_al}


# ---------------------------------------------------------------- the primary chart: the forced 15-piece chain

def forced_area(rho, u, v, w):
    """the area of the 15-piece chain of RUN-059's minima - B3v0, D, B5a3, B5v1, B3a2, B3v1, B3a0, D, B3a0, B3v2, B5a1,
    B5v4, B5a2, D (B3v0 across normal 0) - at (rho, u, v, w): crossings where two bodies' pieces have equal support,
    the bodies' own breakpoints elsewhere; half the integral of h^2 - h'^2 piece by piece"""
    g = geometry()
    pi, s, r3, r5, th = mp.pi, g["s"], g["r3"], g["r5"], g["th"]
    rho, u, v, w = (mp.mpf(x) for x in (rho, u, v, w))
    c3 = (g["x30"] + s * mp.cos(pi - rho * u), s * mp.sin(pi - rho * u))
    c5 = (g["x5"] + rho * w, rho * v)
    V = [(c3[0] + r3 * mp.cos(2 * pi * k / 3), c3[1] + r3 * mp.sin(2 * pi * k / 3)) for k in range(3)]
    W = [(c5[0] + r5 * mp.cos(2 * pi * k / 5 + rho), c5[1] + r5 * mp.sin(2 * pi * k / 5 + rho)) for k in range(5)]
    O, half = (mp.mpf(0), mp.mpf(0)), mp.mpf(1) / 2

    def cross(p, q, c, near):
        d = (p[0] - q[0], p[1] - q[1])
        n, phi = mp.sqrt(d[0] ** 2 + d[1] ** 2), mp.atan2(d[1], d[0])
        a = mp.acos(c / n)
        return min((phi + sg * a + 2 * pi * k for sg in (1, -1) for k in (-1, 0, 1)), key=lambda r: abs(r - near))

    def part(a, b, c, r):
        cn = mp.sqrt(c[0] ** 2 + c[1] ** 2)
        ang = mp.atan2(c[1], c[0]) if cn > 0 else mp.mpf(0)
        F = lambda t: cn ** 2 / 2 * mp.sin(2 * (t - ang)) + 2 * r * cn * mp.sin(t - ang) + r ** 2 * t
        return (F(b) - F(a)) / 2
    al = g["al"]
    t = [cross(V[0], O, half, th), cross(W[3], O, -half, al), 3 * pi / 10 + rho, cross(W[1], V[2], 1, pi / 2), pi / 2, 5 * pi / 6,
         cross(V[0], O, -half, pi - th), cross(V[0], O, -half, pi + th), 7 * pi / 6, cross(V[2], W[1], 1, 3 * pi / 2), 3 * pi / 2 + rho,
         17 * pi / 10 + rho, cross(W[2], O, -half, 2 * pi - al), cross(V[0], O, half, 2 * pi - th)]
    owners = [(O, half), (W[3], 1), (W[1], 0), (V[2], 1), (V[1], 0), (V[0], 1), (O, half), (V[0], 1), (V[2], 0), (W[1], 1), (W[4], 0), (W[2], 1),
              (O, half), (V[0], 0)]
    ends = t + [t[0] + 2 * pi]
    return sum(part(ends[i], ends[i + 1], *owners[i]) for i in range(14))


def primary_variation():
    """sec. 12-13: Phi1_primary = d/d rho of the forced chain's area at rho = 0, at rho = 1e-40 (80 digits): its affine
    coefficients in (1, u, v, w), checked at two more points; and the chain's area at rho = 0 (the barrier's m(0))"""
    mp.mp.dps = 90
    a0 = forced_area(0, 0, 0, 0)
    rho = mp.mpf("1e-40")
    phi = lambda u, v, w: (forced_area(rho, u, v, w) - a0) / rho
    c0 = phi(0, 0, 0)
    co = [c0, phi(1, 0, 0) - c0, phi(0, 1, 0) - c0, phi(0, 0, 1) - c0]
    checks = [phi(*z) - (co[0] + co[1] * z[0] + co[2] * z[1] + co[3] * z[2]) for z in ((3, 1, -2), (-7, 0.5, 0.25))]
    return {"a0": a0, "coefficients": co, "affine_residual": max(abs(x) for x in checks)}


# ---------------------------------------------------------------- the true hull along the critical velocity

def handovers(z):
    """the true hull at (rho, u*, v*, 0) for rho = 1e-4 and 1e-5: its signature, and (theta - theta0) / rho for the hand-overs
    near pi/2 (B5v1 -> B3a2), pi - theta* (B3a0 -> D), alpha* (D -> B5a3), theta* (B3v0 -> D) and 3 pi/2 (B3v2 -> B5a1)"""
    g = geometry()
    th, al = float(g["th"]), float(g["al"])
    out = {}
    for rho in (1e-4, 1e-5):
        m = r59.merged(r59.point(rho, *z))
        start = lambda piece, near: min((a for a, wd, k in m if r46.PIECE[k] == piece and wd > 0), key=lambda a: abs(a - near))
        end = lambda piece, near: min(((a + wd) % (2 * math.pi) for a, wd, k in m if r46.PIECE[k] == piece and wd > 0), key=lambda b: abs(b - near))
        # every interval narrower than rho / 100 that is not a tie: the secondary slivers, width / rho^2
        thin = [[r46.PIECE[k], round(a, 4), wd / rho ** 2] for a, wd, k in m if 1e-11 < wd < rho / 100]
        out[str(rho)] = {"pieces": [p for _, _, p in r59.signature(r59.point(rho, *z))], "thin": thin,
                         "top": (start("B3a2", math.pi / 2) - math.pi / 2) / rho, "left": (end("B3a0", math.pi - th) - (math.pi - th)) / rho,
                         "alpha": (start("B5a3", al) - al) / rho, "theta": (end("B3v0", th) - th) / rho,
                         "bottom": (start("B5a1", 3 * math.pi / 2) - 3 * math.pi / 2) / rho}
    return out


def full_rates(z, gamma_w):
    """sec. 15-16: the true hull's one-sided rate Phi1(z* + h d) / h (Richardson at rho = 1e-6, 2e-6, h = 1e-3) along the six
    axes and the eight diagonals (+-1, +-0.1, +-0.1); what the slivers add over the primary chart (rate - gamma_w d_w)"""
    rows = []
    dirs = [(1, 0, 0), (-1, 0, 0), (0, 1, 0), (0, -1, 0), (0, 0, 1), (0, 0, -1)] + [(a, 0.1 * b, 0.1 * c) for a in (1, -1) for b in (1, -1) for c in (1, -1)]
    h = 1e-3
    for d in dirs:
        rate = r59.phi1(z[0] + h * d[0], z[1] + h * d[1], z[2] + h * d[2]) / h
        rows.append([list(d), rate, rate - gamma_w * d[2]])
    return {"rows": rows, "at_critical": r59.phi1(*z)}


# ---------------------------------------------------------------- the searches

def _key(name, fn, extra=""):
    import run008_round05_saturation_gate as r8
    import run009_round06_witness_exchange as r9
    digest = hashlib.sha256(inspect.getsource(fn).encode())
    for helper in HELPERS[name]:
        digest.update(inspect.getsource(helper).encode())
    for path in (r8.__file__, r9.__file__, r46.__file__, r59.__file__):
        digest.update(open(path, "rb").read())
    digest.update(extra.encode())
    return digest.hexdigest()


HELPERS = {"exact": (geometry, wall_constants, collapse, forced_area), "hull": (handovers, full_rates, geometry)}


def exact_scan():
    s = lambda x: mp.nstr(x, 40, strip_zeros=False)
    c, k, p = wall_constants(), collapse(), primary_variation()
    return {"constants": {n: s(x) for n, x in c.items()},
            "collapse": {"det": s(k["det"]), "u": s(k["u"]), "v": s(k["v"]), "w": s(k["w"]), "uf": s(k["uf"]), "vf": s(k["vf"]),
                         "diff": [s(x) for x in k["diff"]], "two_Lcos": s(k["two_Lcos"]), "tauT": s(k["tauT"]), "tauL": s(k["tauL"]),
                         "tau_alpha": s(k["tau_alpha"])},
            "primary": {"a0": s(p["a0"]), "coefficients": [s(x) for x in p["coefficients"]], "affine_residual": s(p["affine_residual"])}}


def hull_scan(exact):
    z = [float(exact["collapse"]["u"]), float(exact["collapse"]["v"]), 0.0]
    return {"z": z, "handovers": handovers(z), "rates": full_rates(z, float(exact["primary"]["coefficients"][3]))}


def searches():
    cached = json.loads(CACHE.read_text(encoding="utf-8")) if CACHE.exists() else {}
    out = {}
    jobs = (("exact", exact_scan, lambda: exact_scan(), lambda: ""),
            ("hull", hull_scan, lambda: hull_scan(out["exact"]), lambda: json.dumps(out["exact"]["collapse"], sort_keys=True)))
    for name, fn, call, extra in jobs:
        key = _key(name, fn, extra())
        if cached.get(name, {}).get("key") != key:
            cached[name] = {"key": key, "value": json.loads(json.dumps(call()))}
            CACHE.parent.mkdir(parents=True, exist_ok=True)
            CACHE.write_text(json.dumps(cached, indent=1) + "\n", encoding="utf-8")
        out[name] = cached[name]["value"]
    return out


# ---------------------------------------------------------------- checks

ANCHORS = {"A0": [r"a_3b_5-a_5b_3=0.", r"\beta_{3T}=0.30965396669380318,", r"\beta_{5T}=-0.18223079446440071,", r"\gamma_T=0.16245984811645317.",
                  r"L_\theta=0.24402699852741569,", r"L_\alpha=0.09982945996422124,", r"\gamma_L=0.091157834546918698.",
                  r"E_T=su\beta_{5T}-\beta_{3T}(\gamma_T+v)=0.", r"2L_\theta\cos\theta_\star\,w=0.", r"u_\star=5.157126245268359.",
                  r"v_\star=-0.1892281899066407.", r"=-0.14689252641883627.", r"=0.081754472733396338.", r"\tau_\alpha=2.5703350860764402.",
                  "thepositivebranchhasaprimary15-piecestructure.", r"\gamma_w=0.18389963923727759.", r"\Phi_1^{\rmprimary}(u,v,w)=\gamma_w\,w.",
                  "w*=0:PROVEDFROMLEFT/RIGHTCOLLAPSE", "FULLMULTI-CHARTPhi1NONNEGATIVITY:OPEN"]}
PRINTED = {"beta3T": "0.30965396669380318", "beta5T": "-0.18223079446440071", "gammaT": "0.16245984811645317", "Ltheta": "0.24402699852741569",
           "Lalpha": "0.09982945996422124", "gammaL": "0.091157834546918698"}
CLAIMS = {
    # relative differences: the printed digits are binary64's (beyond ~1e-15 they are rounding)
    "constants_claim": {"text": {"beta3T": 1e-15, "beta5T": 1e-17, "gammaT": 4e-17, "Ltheta": 6e-16, "Lalpha": 3e-16, "gammaL": 2e-15},
                        "result": {"beta3T": 1e-15, "beta5T": 7e-17, "gammaT": 4e-17, "Ltheta": 6e-16, "Lalpha": 3e-16, "gammaL": 2e-15},
                        "angles": [4e-16, 2e-16, 4e-16, 1e-16],
                        "values": {"beta3T": "0.30965396669380288225", "beta5T": "-0.1822307944644007122", "gammaT": "0.16245984811645316307",
                                   "Ltheta": "0.24402699852741554812", "Lalpha": "0.09982945996422126522", "gammaL": "0.09115783454691888673"}},
    # the system has full rank (det -1.28e-4: an error of 1e-16 in a constant moves u* by ~1e-14); the printed u* is 5e-14 off
    "velocity_claim": {"det": -0.000128, "solution": ["5.15712624526841370759", "-0.1892281899066409745", 0.0], "closed_form": [0.0, 0.0],
                       "right_minus_left": [0.0, 0.0, 0.0], "text": [5e-14, 3e-16], "result": [5e-14, 3e-16], "run059": [7e-05, 2e-06, [5.157, -0.1892, 0.0]]},
    "speeds_claim": {"text": [2e-15, 9e-16, 2e-15], "result": [2e-15, 9e-16, 9e-16, 2e-15, 2e-15],
                     "hull": {"0.0001": [-0.1469, -0.147, 0.0817, 0.0817, 2.5701], "1e-05": [-0.1469, -0.1469, 0.0818, 0.0817, 2.5703]},
                     "exact": [-0.1469, 0.0818, 2.5703]},
    "structure_claim": {"along_critical": {"0.0001": 18, "1e-05": 18}, "same": True,
                        "slivers": {"0.0001": [["B5a3", 0.4541, 2.72], ["D", 1.5708, 1.79], ["B5a2", 5.8291, 2.72]],
                                    "1e-05": [["B5a3", 0.454, 2.72], ["D", 1.5708, 1.79], ["B5a2", 5.8291, 2.72]]},
                        "corners_at_pi_pm_theta": False, "run059_minima": [15, 15, 15, 15]},
    # the forced chain's area at rho = 0 is RUN-057's m(0); its first variation is gamma_w w exactly
    "primary_claim": {"a0": "0.8349861105592240998506", "zero_terms": [True, True, True], "gamma_w": "0.18389963923727719444",
                      "affine_residual_below": True, "text": 4e-16,
                      "result": [4e-16, [5.551115123125783e-17, 8.673617379884035e-19, -4.440892098500626e-16]]},
    # one-sided rates per unit and what the slivers add: along +-w gamma_w +- 0.2193, so -w keeps 0.0354
    "full_claim": {"at_critical": True, "positive": True, "penalty_positive": True,
                   "axes": [[[1, 0, 0], 0.0012, 0.0012], [[-1, 0, 0], 0.0012, 0.0012], [[0, 1, 0], 0.2619, 0.2619], [[0, -1, 0], 0.2619, 0.2619],
                            [[0, 0, 1], 0.4032, 0.2193], [[0, 0, -1], 0.0354, 0.2193]], "least": [[1, 0, 0], 0.0012]},
    "records_claim": {"status": "ANALYTIC_PRIMARY_BLOWUP_DERIVATION; INTERVAL_AUDIT_OPEN",
                      "state": ["PRIMARY_BLOWUP_ANALYTIC_REDUCTION", [5.157126245268359, -0.18922818990664067, 0.0], "0.18389963923727759 * w",
                                "Paper 09C — Secondary Sliver Asymptotics and Explicit Phi2"],
                      "obligations": ["unique root of collapse system in velocity box", "gamma_w > 0", "enumeration of all neighboring first-order charts",
                                      "full multi-chart Phi1 nonnegativity"],
                      "schema_next": "Paper 09C — Secondary Sliver Asymptotics and Explicit Phi2", "next_archived": False,
                      "replay": ["PASS", "u* = 5.15712624526836", "v* = -0.189228189906641", "w* = 0", "gamma_w = 0.183899639237278", "Phi1 critical = 0"]},
    "verifier_facts": {"lines": 55, "constants_carried": 7, "derives_geometry": False, "solve": True, "checks_against_own": True, "gamma_w_supplied": True},
    "package_claim": {"digests": [True, True, True, True, True], "utf8": True, "delimiters": ["PASS", []], "manifest": True},
}


def sig(x, n=2):
    return float(f"{float(x):.{n - 1}e}")


def w1_constants(w):
    """sec. 4-5: the six constants from the geometry, against the text and the result record (relative differences)"""
    c, r = w["search"]["exact"]["constants"], w["result"]["constants"]
    keys = {"beta3T": "beta3_top", "beta5T": "beta5_top", "gammaT": "gamma_top", "Ltheta": "L_theta", "Lalpha": "L_alpha", "gammaL": "gamma_left"}
    mp.mp.dps = 50
    rel = lambda a, b: sig(abs(mp.mpf(a) - mp.mpf(b)) / abs(mp.mpf(a)), 1)
    got = {"text": {k: rel(c[k], PRINTED[k]) for k in keys}, "result": {k: rel(c[k], repr(r[v])) for k, v in keys.items()},
           "angles": [rel(c["theta"], repr(r["theta_star"])), rel(c["alpha"], repr(r["alpha_star"])), rel(c["sin"], repr(r["sin_theta"])),
                      rel(c["cos"], repr(r["cos_theta"]))],
           "values": {k: c[k][:22] for k in keys}}
    w["w1"] = got
    return [] if got == w["constants_claim"] else [f"the constants: {got}"]


def w2_velocity(w):
    """sec. 3, 6-9, 16: the system's determinant (full rank), its solution and the closed forms, E_R - E_L = 2 Ltheta
    cos(theta*) w, against the text, the result record and RUN-059's critical point and branch limit"""
    k, r = w["search"]["exact"]["collapse"], w["result"]["critical_velocity"]
    l59 = w["log59"]
    mp.mp.dps = 50
    d = lambda a, b: sig(abs(mp.mpf(a) - mp.mpf(b)), 1)
    got = {"det": sig(k["det"], 3), "solution": [k["u"][:22], k["v"][:22], sig(k["w"], 1) + 0.0], "closed_form": [d(k["u"], k["uf"]), d(k["v"], k["vf"])],
           "right_minus_left": [d(k["diff"][0], 0), d(k["diff"][1], 0), d(k["diff"][2], k["two_Lcos"])],
           "text": [d(k["u"], "5.157126245268359"), d(k["v"], "-0.1892281899066407")], "result": [d(k["u"], repr(r["u_star"])), d(k["v"], repr(r["v_star"]))],
           "run059": [d(k["u"], repr(l59["w4"]["minimizer"][0])), d(k["v"], repr(l59["w4"]["minimizer"][1])), l59["w1"]["limit"]]}
    w["w2"] = got
    return [] if got == w["velocity_claim"] else [f"the velocity: {got}"]


def w3_speeds(w):
    """sec. 10: the speeds at the solution against the text and the result record; the true hull's hand-overs along the
    critical velocity give them back to O(rho)"""
    k, r, h = w["search"]["exact"]["collapse"], w["result"]["primary_transition_speeds"], w["search"]["hull"]["handovers"]
    mp.mp.dps = 50
    d = lambda a, b: sig(abs(mp.mpf(a) - mp.mpf(b)), 1)
    got = {"text": [d(k["tauT"], "-0.14689252641883627"), d(k["tauL"], "0.081754472733396338"), d(k["tau_alpha"], "2.5703350860764402")],
           "result": [d(k["tauT"], repr(r["tau_top"])), d(k["tauL"], repr(r["tau_left"])), d(k["tauL"], repr(r["tau_right"])), d(k["tauT"], repr(r["tau_bottom"])),
                      d(k["tau_alpha"], repr(r["tau_alpha"]))],
           "hull": {rho: [round(x[n], 4) for n in ("top", "bottom", "left", "theta", "alpha")] for rho, x in h.items()},
           "exact": [round(float(k["tauT"]), 4), round(float(k["tauL"]), 4), round(float(k["tau_alpha"]), 4)]}
    w["w3"] = got
    return [] if got == w["speeds_claim"] else [f"the speeds: {got}"]


def w4_structure(w):
    """sec. 11, 18: along the critical velocity the true hull has the 15 primary pieces of RUN-059's minima and three
    O(rho^2) slivers - the disc at pi/2, and B5's arcs about v3 and v2 at theta* and 2 pi - theta*, the antipodes of the
    corners at pi +- theta* that sec. 18 lists (each body has constant width 1, so a wall and its antipode give one
    equation); at the minima there are none"""
    h, l59 = w["search"]["hull"]["handovers"], w["log59"]["w3"]
    primary = [p for p in l59["pieces_at_minimum"]]
    got = {"along_critical": {rho: len(x["pieces"]) for rho, x in h.items()}, "same": len({tuple(x["pieces"]) for x in h.values()}) == 1,
           "slivers": {rho: [[p, a, round(c, 2)] for p, a, c in x["thin"]] for rho, x in h.items()},
           "corners_at_pi_pm_theta": any(p in ("B5v2", "B5v3") for x in h.values() for p, _, _ in x["thin"]),
           "run059_minima": primary}
    w["w4"] = got
    return [] if got == w["structure_claim"] else [f"the structure: {got}"]


def w5_primary(w):
    """sec. 12-14: the forced chain at rho = 0 has the barrier's area; its first variation is affine in (1, u, v, w) with
    coefficients (0, 0, 0, gamma_w) - the u and v terms cancel exactly - against the text and the result record"""
    p, r = w["search"]["exact"]["primary"], w["result"]["primary_area_first_variation"]
    mp.mp.dps = 50
    co = [mp.mpf(x) for x in p["coefficients"]]
    got = {"a0": p["a0"][:24], "zero_terms": [abs(x) < mp.mpf("1e-35") for x in co[:3]], "gamma_w": p["coefficients"][3][:22],
           "affine_residual_below": abs(mp.mpf(p["affine_residual"])) < mp.mpf("1e-35"),
           "text": sig(abs(co[3] - mp.mpf("0.18389963923727759")), 1), "result": [sig(abs(co[3] - mp.mpf(repr(r["gamma_w"]))), 1), r["affine_coefficients_const_u_v_w"][:3]]}
    w["w5"] = got
    return [] if got == w["primary_claim"] else [f"the primary variation: {got}"]


def w6_full(w):
    """sec. 15-16, 20: at the critical velocity the true hull's rate is zero to the resolution; along every direction tried
    it is positive, and what the slivers add over gamma_w d_w is positive; along -w the margin is the smallest"""
    f = w["search"]["hull"]["rates"]
    rows = f["rows"]
    got = {"at_critical": sig(abs(f["at_critical"]), 1) < 1e-6, "positive": all(r > 0 for _, r, _ in rows), "penalty_positive": all(p > 0 for _, _, p in rows),
           "axes": [[d, round(r, 4), round(p, 4)] for d, r, p in rows[:6]], "least": [min(rows, key=lambda x: x[1])[0], round(min(x[1] for x in rows), 4)]}
    w["w6"] = got
    return [] if got == w["full_claim"] else [f"the full Phi1: {got}"]


def w7_records(w):
    r, sc, st = w["result"], w["schema"], w["state"]
    got = {"status": r["status"], "state": [st["status"], st["critical_velocity"], st["Phi1_primary"], st["next"]],
           "obligations": sc["interval_obligations"], "schema_next": sc["next"], "next_archived": any("Paper09C" in a for a in w["archived"]),
           "replay": w["validation"]["fresh_replay_stdout"].split("\n")}
    w["w7"] = got
    return [] if got == w["records_claim"] else [f"the records: {got}"]


def w8_verifier(w):
    """the verifier, read after the rebuild and never run: it carries the six constants and gamma_w as numbers, solves the
    3 x 3 system in floating point, checks u and v against values it also carries, and takes Phi1 = gamma_w w on trust"""
    code = w["verifier"]
    got = {"lines": code.count("\n"), "constants_carried": len(re.findall(r"^(?:Ltheta|Lalpha|beta3T|beta5T|gammaT|gammaL|gamma_w) = ", code, re.M)),
           "derives_geometry": bool(re.search(r"acos|findroot|Reuleaux|sqrt\(3\)", code)), "solve": "np.linalg.solve(M,rhs)" in code,
           "checks_against_own": "abs(u-5.157126245268359) < 1e-10" in code, "gamma_w_supplied": "supplied by the analytic 15-piece summation" in code}
    w["w8"] = got
    return [] if got == w["verifier_facts"] else [f"the verifier: {got}"]


def p1_package(w):
    v, B = w["validation"], w["bytes"]
    sha = lambda b: hashlib.sha256(b).hexdigest()
    body = B[DOC].decode("utf-8")
    got = {"digests": [v["paper_sha256"] == sha(B[DOC]), v["result_sha256"] == sha(B[RESULT]), v["verifier_sha256"] == sha(B[VERIFIER]),
                       v["schema_sha256"] == sha(B[SCHEMA]), v["state_sha256"] == sha(B[STATE])],
           "utf8": v["utf8"] is True, "delimiters": [v["math_delimiters"], sorted(set(re.findall(r"\\\(|\\\)|\\\[|\\\]", body)))],
           "manifest": all(w["manifest_rows"][k][2] == sha(b) for k, b in B.items())}
    w["p1"] = got
    return [] if got == w["package_claim"] else [f"the package: {got}"]


def a0_anchors(w):
    text = gate.squash(w["text"])
    return [f"anchor not found: {a[:60]}" for a in ANCHORS["A0"] if a not in text]


CHECKS = {"A0": a0_anchors, "W1": w1_constants, "W2": w2_velocity, "W3": w3_speeds, "W4": w4_structure, "W5": w5_primary, "W6": w6_full, "W7": w7_records,
          "W8": w8_verifier, "P1": p1_package}
DEPENDS = {"A0": {"text"}, "W1": {"search", "result", "constants_claim"}, "W2": {"search", "result", "log59", "velocity_claim"},
           "W3": {"search", "result", "speeds_claim"}, "W4": {"search", "log59", "structure_claim"}, "W5": {"search", "result", "primary_claim"},
           "W6": {"search", "full_claim"}, "W7": {"result", "schema", "state", "archived", "validation", "records_claim"}, "W8": {"verifier", "verifier_facts"},
           "P1": {"validation", "bytes", "manifest_rows", "package_claim"}}


def drill_cases(base):
    def edit(obj, fn):
        x = copy.deepcopy(obj)
        fn(x)
        return x

    text, s = base["text"], base["search"]
    return [
        ("K0-unmodified", "control", {}, set()),
        ("K1-crlf", "control", {"text": text.replace("\n", "\r\n"), "verifier": base["verifier"].replace("\n", "\r\n")}, set()),
        ("K2-validation-keys-sorted", "control", {"validation": json.loads(json.dumps(base["validation"], sort_keys=True))}, set()),
        ("M1-text-u", "mutation", {"text": text.replace("5.157126245268359.", "5.157126245268358.")}, {"A0"}),
        ("M2-constant", "mutation", {"search": edit(s, lambda x: x["exact"]["constants"].update(beta3T="0.3096539666938100000000"))}, {"W1"}),
        ("M3-result-constant", "mutation", {"result": edit(base["result"], lambda x: x["constants"].update(L_theta=0.2440269985275))}, {"W1"}),
        ("M4-solution", "mutation", {"search": edit(s, lambda x: x["exact"]["collapse"].update(u="5.1572"))}, {"W2"}),
        ("M5-run059", "mutation", {"log59": edit(base["log59"], lambda x: x["w4"]["minimizer"].__setitem__(0, 5.2))}, {"W2"}),
        ("M6-handover", "mutation", {"search": edit(s, lambda x: x["hull"]["handovers"]["1e-05"].update(top=-0.2))}, {"W3"}),
        ("M7-result-speed", "mutation", {"result": edit(base["result"], lambda x: x["primary_transition_speeds"].update(tau_alpha=2.57))}, {"W3"}),
        ("M8-corner-sliver", "mutation", {"search": edit(s, lambda x: x["hull"]["handovers"]["1e-05"]["thin"].append(["B5v2", 2.6876, 0.5]))}, {"W4"}),
        ("M9-run059-pieces", "mutation", {"log59": edit(base["log59"], lambda x: x["w3"].update(pieces_at_minimum=[18, 15, 15, 15]))}, {"W4"}),
        ("M10-primary-u", "mutation", {"search": edit(s, lambda x: x["exact"]["primary"]["coefficients"].__setitem__(1, "0.01"))}, {"W5"}),
        ("M11-result-gamma", "mutation", {"result": edit(base["result"], lambda x: x["primary_area_first_variation"].update(gamma_w=0.1839))}, {"W5"}),
        ("M12-rate-negative", "mutation", {"search": edit(s, lambda x: x["hull"]["rates"]["rows"][5].__setitem__(1, -0.01))}, {"W6"}),
        ("M13-state-next", "mutation", {"state": edit(base["state"], lambda x: x.update(next="Paper 10"))}, {"W7"}),
        ("M14-09c-archived", "mutation", {"archived": base["archived"] + ["LESR_Paper09C_Secondary_Sliver_Asymptotics_v0.1.md"]}, {"W7"}),
        ("M15-verifier-solve", "mutation", {"verifier": base["verifier"].replace("np.linalg.solve(M,rhs)", "solve_it(M,rhs)")}, {"W8"}),
        ("M16-validation-digest", "mutation", {"validation": {**base["validation"], "result_sha256": "0" * 64}}, {"P1"}),
        ("M17-verifier-bytes", "mutation", {"bytes": {**base["bytes"], VERIFIER: base["bytes"][VERIFIER] + b" "}}, {"P1"}),
    ]


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--drill", action="store_true")
    ap.add_argument("--blind", metavar="CID")
    args = ap.parse_args()
    inputs = load_inputs()
    base = {**inputs, "search": searches()}

    def build():
        return {**copy.deepcopy(base), **copy.deepcopy(CLAIMS)}

    if args.blind:
        CHECKS[args.blind] = lambda w: []
        args.drill = True
    w = build()
    results = {}
    for c, fn in CHECKS.items():
        try:
            results[c] = fn(w)
        except KeyError as e:
            results[c] = [f"claim missing: {e}"]
    for c, f in results.items():
        print(f"{c} {'PASS' if not f else 'FAIL'}", *(f"\n     {x}" for x in f[:6]))
    for key in ("w1", "w2", "w3", "w4", "w5", "w6", "w7", "w8", "p1"):
        print(key, "=", json.dumps(w.get(key), default=str, ensure_ascii=False))
    if not args.drill:
        return
    cases = drill_cases(base)
    if args.blind:
        cases = [c for c in cases if args.blind in c[3]]
    d = gate.run_drill(build, CHECKS, DEPENDS, cases)
    summary = gate.summarize(d, CHECKS)
    print(gate.report_drill(summary))
    for x in d:
        if not x["ok"]:
            print("   DRILL FAIL", x)
    if args.blind:
        n = sum(1 for x in d if args.blind in x["expected"] and args.blind not in x["tripped"])
        print(f"blind {args.blind}: drill reported it blind in {n} cases (nothing written)")
        return
    log = {"run": "RUN-060", "subject": "LESR Paper 09B: the analytic primary blow-up equations", "document": DOC,
           "checks": {c: {"pass": not f, "failures": f} for c, f in results.items()},
           **{k: w.get(k) for k in ("w1", "w2", "w3", "w4", "w5", "w6", "w7", "w8", "p1")}, "drill": summary}
    LOG.write_text(json.dumps(log, ensure_ascii=False, indent=1, default=float) + "\n", encoding="utf-8")


if __name__ == "__main__":
    main()
