#!/usr/bin/env python3
r"""sunrise-row09-evaluate.py -- two-loop sunrise, unequal squared masses (1,1,2), eps^0:
STANDALONE evaluator (row 9 of the paper's table of integrals).

Two-loop sunrise S_111(2-2eps, t), one external scale t = p^2 (Euclidean t < 0; physical t > 0
by the continued layer, see DOMAIN), three massive propagators with squared masses
(m1^2, m2^2, m3^2) = (1, 1, 2), mu = 1.
Object: the eps^0 coefficient J[1,1,1]^(0)(t) of the (1,1,1,0,0) master (d = 2 - 2 eps,
AMFlow normalization), and its period-stripped form E^(0) = J^(0)/psihat1.

CLOSED FORM (uniform per-puncture Kronecker-eMPL formula, zero free parameters):

    J^(0)/psihat1 = -C_{4,2}(t)
                    - (6/(2 pi)^3) * [ (2W(z1,1)+W(z3,1)) - 8 (2W(z1,2)+W(z3,2)) ] / 3

  * psihat1 = |psi1_F|/pi: the BMSW Feynman-curve holomorphic period (elliptic K).
  * z1 = z2 (the m1 = m2 degeneracy of this row), z3: Abel-Jacobi images of the
    three punctures (incomplete elliptic integrals); z1+z2+z3 = 1 enforced
    (branch repair, in the shared layer's fcurve).
  * W(z,N) = I(1, g^(3)(z, N tau_C); q_C): depth-two Kronecker-eMPL word,
    convergent q-series with the PROVEN tail bound |b_n| <= C n^2,
    C = (2 pi)^3 zeta(2) = 408.03, certified + ENFORCED at runtime by
    sunrise_empl.Nq_for (fail-closed at the depth cap); the achieved bound is
    propagated to value level and printed (BOUND, not estimate).
  * C_{4,2} = sum_j (1/2i)[Li2(w_j) - Li2(1/w_j)]: elliptic-dilogarithm boundary.
  * Coefficients [-1, -1/3, +8/3]: PSLQ-exact rationals, identified on the 4 fit
    points t = -1/2, -1, -2, -5 (an overdetermined fit: 4 points for three
    coefficients) and verified at 159-160-digit closure there.  ZERO free
    parameters at runtime.

TWO KINDS OF REFERENCE POINT (the data file flags each one):
  * in_fit: the 4 PSLQ fit points above (in-sample agreement; the fit saw them);
  * held_out_in_pslq_fit: 2 points, t = -3 and t = -7, NEVER in the fit -- AMFlow
    values (goal 150 digits) computed after the closed form and its coefficients
    were fixed; each entry carries the sha256 of its AMFlow input configuration and
    output file, its ball radius and the digits that radius certifies.
  The two kinds are gated SEPARATELY below and both must clear the bar.

All machinery lives in the shared module sunrise_empl.py (same directory): pure
mpmath, self-contained q-series from the printed kernel definitions, no IBP
reduction, no differential-equation solver, no network at runtime.  This script
adds no arithmetic to the value: the held-out gate calls the same predict_J.  For
t > 0 the value is the SAME closed form continued to t + i0 by sunrise_empl_cont.py +
frame_ode.py (same directory; pure mpmath): this script compares it with the
references and prints a bound.

WHAT THE DEFAULT RUN CHECKS (exit 0 only if every leg passes):
  1. positive control: the same marked-point machinery at equal mass (1,1,1) vs
     the independent classical Gamma_1(6) route (Eisenstein series -> newform f3
     -> I(1,f3;q_C), boundary (3/2) sqrt3 L(chi_{-3},2) via Hurwitz zeta);
  2. fit-point gate: J^(0) at the 4 fit points vs 159-290-digit AMFlow reference
     values (comparison only, never in the evaluation; in-sample: they were the
     PSLQ fit points); bar: worst agreement STRICTLY > 30 digits and relative
     error < 10^-30/2;
  3. held-out gate: J^(0) at t = -3 and t = -7 vs the 160- and 159-digit AMFlow
     values that were held out of the fit; same bar, gated and reported on its
     own line ('held-out: ...'), with the fit points on their own ('fit-point: ...');
  4. the digits the paper prints: E^(0)(-1) = -2.3471191... and
     E^(0)(-5) = -3.2711938... (8 digits each, trailing digits truncated) must be
     matched to within one unit in the last printed place;
  5. the digit count the paper states ("held out to 159 digits at two fresh points,
     t=-3 and t=-7, ..."): measured here as the worst agreement over the 2 held-out
     points; tested when the run's precision reaches it (the default does).

FILES (beside this script; sha256-pinned below and REFUSED on any mismatch):
  sunrise_empl.py             shared Kronecker-eMPL layer (curve, marked points, words, gates)
  sunrise_empl_cont.py        the continued layer for t > 0: the same frame continued to t + i0, the
                              words resummed as dilogarithms (loads sunrise_empl.py from this directory)
  frame_ode.py                the frame transported by its own differential equations (used by the
                              layer above)
  sunrise-row09-data.json     reference values (4 fit points + 2 held-out points + 3 physical-region
                              points t = 16, 20, 12 with their twins) + the paper's printed digits

DOMAIN: Euclidean t < 0 (the default run and --point t < 0).  |q_C| -> 0 at the soft
point t -> 0^-; |q_C| >= 0.8995 (astronomically deep |t|; keeps 1/(1-|q_C|) < 10, one
budgeted guard digit) is refused explicitly.  Gate-verified window -7 <= t <= -1/2;
singular locus of the row is t in {0, 2, 6 +- 4 sqrt2} (all outside the Euclidean
domain).
PHYSICAL t > 0 (--point t with a rational t > 0; t = 0 is refused): the SAME closed
form continued analytically to t + i0 by the continued layer (sunrise_empl_cont.py +
frame_ode.py).  Convention (the Feynman prescription): J^(0)(t + i0) is the limit onto
the real axis from the UPPER half t-plane; the references were computed with the point
entered as the exact rational s = t on the real axis and the engine's per-system
prescription pinning its auxiliary-mass contour to the +i0 propagators, so both sides
carry the same i0; with J[1,1,1] = -S_111, Im J^(0) < 0 above the normal threshold
t = (m1+m2+m3)^2 = 6 + 4 sqrt2 = 11.657... and vanishes below it.  Path of record: the
arc t(u) = t0 + (t - t0) u + i h sin(pi u) from the Euclidean anchor t0 = -1 with
h = 4, then a geometric descent to t + i delta, delta = 10^-(dps+5); the frame (the
periods, the marked points, the dressing period) is transported along it by its own
differential equations in 1200 RK4 steps at 30 digits, matched at the endpoint to
integer combinations of the principal-branch integrals (exact at any precision; the
lattice relation z1+z2+z3 = 1 is checked there), and the depth-two words are resummed
as dilogarithms with their cut crossings tracked along the same path; the endpoint is
then evaluated at the working precision and at 40 more digits (the two-precision pair,
printed).  Check of the convention: --schwarz evaluates the lower detour (Im t < 0,
the -i0 side) and prints its agreement with the CONJUGATE of the +i0 value (J is real
on the Euclidean axis, so J(t - i0) = conj J(t + i0)).  The physical reference points
t = 16, 20 (goal 60 with goal-40 twins) and t = 12 (goal 150 with its goal-120 twin)
are gated when given as --point; any other rational t > 0 is evaluated and printed
without a gate.  The eps^1 script (sunrise-row12-evaluate.py) is not continued.

PHYSICAL GATE (--point 16, --point 20, --point 12; each point on its own): the members
printed as the reference prints them -- this run's Re and Im of J^(0)(t + i0) vs the
goal-60 (t = 12: goal-150) midpoint, capped at min(the string's digits, the working
precision); the ball radius per component; this run's two-precision pair per
component; the reference's own two-precision pair (goal 60 vs goal 40; t = 12: 150 vs
120) per component; this run vs the twin per component -- then the FLOOR = the minimum
over the members with its name, the reportable digits per component = min(this run vs
the midpoint, the ball), and the verdict: PASS iff the FLOOR is STRICTLY > 30 digits
AND the worst relative error (|Delta Re|, |Delta Im| over |J|) < 10^-30/2; a FAIL
exits 1.  Digits are -log10(|difference| / |J|), |J| the reference midpoint's modulus.

EXIT CODES: 0 every gate passes; 1 a gate fails (this is what --mutate,
--mutate-heldout and --mutate-physical must produce); 2 usage error or --point
domain refusal (t = 0, or a t > 0 the continued layer refuses); 3 a
pinned file's sha256 does not match (refused before any computation); 4 a
required file or mpmath is missing.

USAGE
  python3 sunrise-row09-evaluate.py                    # default: dps 150, all checks (seconds)
  python3 sunrise-row09-evaluate.py --dps 60           # lower precision (paper claim then SKIPs)
  python3 sunrise-row09-evaluate.py --point=-7/2       # any Euclidean t < 0 (use --point= for negatives)
  python3 sunrise-row09-evaluate.py --point 16         # physical t > 0: continued to t + i0 and GATED (t = 16, 20, 12)
  python3 sunrise-row09-evaluate.py --point 16 --dps 60      # the physical arm at a lower precision
  python3 sunrise-row09-evaluate.py --point 16 --schwarz     # + the lower detour (the -i0 side) vs the conjugate
  python3 sunrise-row09-evaluate.py --point 14 --dps 60      # any rational t > 0: evaluated and printed, no gate
  python3 sunrise-row09-evaluate.py --point 16 --dps 60 --mutate-physical   # control: the continued layer's
                                                       #   coefficient +8 -> 8(1+1e-12) in the physical evaluation
                                                       #   only; the physical gate MUST fail, exit nonzero
  python3 sunrise-row09-evaluate.py --check            # two-precision rule: rerun all gate points at dps+60, diff
  python3 sunrise-row09-evaluate.py --mutate           # control: +8 -> 8(1+1e-12) everywhere; MUST exit nonzero
  python3 sunrise-row09-evaluate.py --mutate-heldout   # control: the same perturbation in the HELD-OUT
                                                       #   evaluation only (fit table untouched); MUST exit nonzero

WALLS (this build's pilots: one process in a fenced scope on a shared host under load; contended walls,
not a price): the default run 1.20 s (no physical point evaluated); --point 16 --dps 60 (the arm at dps 70 /
110) 16.4 s for the arm, 17.0 s for the run; --point 16 at the default precision (the arm at dps 160 / 200)
13.8 s for the arm, 15.2 s for the run -- the arm is transport-dominated (the 1200-step RK4 transport at 30
digits), the two endpoints are seconds at either precision; t = 12 sits closest to the threshold (the largest
|q_C|, the most dilogarithm terms) and is the slowest of the three reference points (see CHANGES.md).

mp.dps is set inside main() after argparse (module-level mpf footgun avoided).
Dependency: python3 + mpmath (pip install mpmath).
"""
import argparse
import hashlib
import json
import os
import sys

EXIT_PASS, EXIT_FAIL, EXIT_USAGE, EXIT_PIN, EXIT_MISSING = 0, 1, 2, 3, 4

ROW = 9
HERE = os.path.dirname(os.path.abspath(__file__))
EMPL_FILE = os.path.join(HERE, "sunrise_empl.py")
DATA_FILE = os.path.join(HERE, "sunrise-row09-data.json")
EMPL_SHA256 = "1928e35f0edd8197fc10550ea439acaa21e5c4ae4136396b0493fe49331f62bf"
DATA_SHA256 = "564a71425c7404b4f06b3d4681546cf864a85fa15bf3e13194167a69f59b3d40"
CONT_FILE = os.path.join(HERE, "sunrise_empl_cont.py")
ODE_FILE = os.path.join(HERE, "frame_ode.py")
CONT_SHA256 = "caeceb936d74f37a30031e26f75dd5def8adcefa276548a7cbbf2356868d6691"
ODE_SHA256 = "c6209e14cd014ec99c457ef08250acfc32933228819beeb47847deb473fdf0d0"
# the continued layer's path of record for --point t > 0: the Euclidean anchor t0, the arc height h, the RK4 step count
# of the transport and its tracking precision; the two-precision pair is [dps, dps + PHYS_PAIR_STEP]; the gate bar
PHYS_T0 = -1
PHYS_H = 4
PHYS_NSTEPS = 1200
PHYS_DPS_TRACK = 30
PHYS_PAIR_STEP = 40
PHYS_BAR = 30


def _pinned_bytes(path, pin):
    """Read a companion file and refuse it unless its sha256 matches the pin."""
    name = os.path.basename(path)
    if not os.path.exists(path):
        print(f"MISSING: {name} must sit beside this script (download it from the same page); "
              f"nothing computed")
        sys.exit(EXIT_MISSING)
    raw = open(path, "rb").read()
    sha = hashlib.sha256(raw).hexdigest()
    if sha != pin:
        print(f"REFUSED: {name} sha256 {sha} ({len(raw)} bytes) does not match the pin "
              f"{pin} -- the file was altered or is not the released version; nothing computed")
        sys.exit(EXIT_PIN)
    return raw


def _t_label(gp):
    """'-3' or '-1/2': the rational t of a reference point as printed in the summaries."""
    return f"{gp['t_num']}" if gp["t_den"] == 1 else f"{gp['t_num']}/{gp['t_den']}"


def _agree_digits(mp, J_pred, oracle):
    """Agreement digits and relative error of a computed value against a reference midpoint
    (the same measurement the shared layer's gate table prints)."""
    rel = abs(J_pred - oracle) / abs(oracle)
    d = int(-mp.log10(rel)) if rel > 0 else mp.mp.dps
    return d, rel


def heldout_gate(mp, R, data, held, args, masses):
    """Gate the held-out reference points SEPARATELY from the fit points.

    held: the data file's gate points flagged held_out_in_pslq_fit (t = -3, -7).  Each is
    evaluated with the SAME predict_J the fit table used (no new arithmetic on the value;
    args.mutate or args.mutate_heldout threads the shared layer's coefficient perturbation
    into these evaluations only through predict_J's own mutate hook), compared with its
    AMFlow midpoint, and printed per point with its agreement digits, cap and provenance.
    Bar: worst agreement STRICTLY > 30 digits AND relative error < 10^-30/2 (the fit
    table's bar).  Then the paper's stated held-out digit count is measured as the worst
    agreement over these points (SKIP, not FAIL, when the precision cannot reach it), and
    --check reruns them at dps+60.  Returns (worst_digits, wall, failures) where failures
    is a list of messages (empty when every leg passes)."""
    import time
    t0 = time.time()
    mutate = bool(getattr(args, "mutate", False) or getattr(args, "mutate_heldout", False))
    failures = []
    if getattr(args, "mutate_heldout", False):
        print("\n[mutate-heldout] CONTROL RUN: per-puncture coefficient +8 -> 8 (1 + 1e-12) in the "
              "HELD-OUT evaluation only (the fit table above is untouched); the held-out gate "
              "MUST fail and this run MUST exit nonzero")
    names = ", ".join(_t_label(gp) for gp in held)
    print(f"\nheld-out reference points (t = {names}; held out of the PSLQ fit -- the coefficients "
          f"never saw them):")
    worst = None
    worst_rel = mp.mpf(0)
    results = []
    for gp in held:
        t = mp.mpf(gp["t_num"]) / mp.mpf(gp["t_den"])
        diag = {}
        J_pred = R.predict_J(t, masses, diag=diag, mutate=mutate)
        oracle = mp.mpf(gp["oracle_mid"])
        d, rel = _agree_digits(mp, J_pred, oracle)
        cap = min(gp["oracle_digits"], mp.mp.dps)
        results.append((t, J_pred, d, cap, gp))
        worst = d if worst is None else min(worst, d)
        worst_rel = max(worst_rel, rel)
        print(f"  t = {mp.nstr(t, 8):>10}  J_pred = {mp.nstr(J_pred, 24)}")
        print(f"    vs {gp['source']}: agree {d} digits "
              f"(cap: {cap} = min(oracle {gp['oracle_digits']}d, dps))  "
              f"|q_C| = {mp.nstr(abs(diag['qC']), 5)}  Nq = {diag['Nq']}")
        print(f"    provenance: AMFlow goal {gp['amflow_goal_digits']} digits, eps order "
              f"{gp['amflow_eps_order']}, ball radius {gp['amflow_ball_radius']} "
              f"({gp['amflow_ball_digits']} digits in the ball); input configuration sha256 "
              f"{gp['amflow_config_sha256'][:16]}..., output sha256 {gp['amflow_output_sha256'][:16]}... "
              f"(full values in the data file)")
    bar_rel = mp.mpf(10) ** (-30) / 2
    ok = (worst > 30) and (worst_rel < bar_rel)
    if not ok:
        failures.append(f"held-out gate: worst agree {worst}d not STRICTLY > 30d, or rel error "
                        f"{mp.nstr(worst_rel, 3)} not < 10^-30/2 = {mp.nstr(bar_rel, 3)}")
    per_point = "; ".join(f"t = {_t_label(r[4])}: {r[2]} digits (cap {r[3]})" for r in results)
    print(f"\n  held-out: t = {names} (held out of the PSLQ fit): {per_point}; worst {worst} digits  "
          f"[{'PASS' if ok else 'FAIL'}: strict bar > 30 d AND worst-rel {mp.nstr(worst_rel, 3)} "
          f"< 10^-30/2, directed-rounding slack]")

    # the digit count the paper's own sentence STATES (quoted in cl["text"]), measured as the
    # worst agreement over the held-out points (cl["measured_over"] names the subset; only the
    # held-out subset is wired -- anything else is refused as a data error, never silently passed)
    for cl in data.get("paper_claims", []):
        if cl.get("measured_over") != "held_out_points":
            failures.append(f"paper claim '{cl['text']}': measured_over = {cl.get('measured_over')!r} "
                            f"is not 'held_out_points' (the only subset this script measures)")
            continue
        need = int(cl["held_out_digits"])
        cap = min(min(gp["oracle_digits"] for gp in held), mp.mp.dps)
        if cap >= need:
            cl_ok = worst >= need - 1   # one digit slack: the reference's own last digit
            if not cl_ok:
                failures.append(f"paper claim '{cl['text']}': worst held-out agreement {worst}d < {need - 1}d")
            print(f"  paper claim '{cl['text']}' [{cl['where']}]: measured here as this run's worst "
                  f"agreement over the {len(held)} held-out points above (t = {names}; never in the fit) = "
                  f"{worst} digits (cap {cap} = min(held-out reference digits, dps); bar {need} - 1) "
                  f"[{'PASS' if cl_ok else 'FAIL'}]")
        else:
            print(f"  paper claim '{cl['text']}' [{cl['where']}]: not testable at this precision "
                  f"(cap {cap} = min(held-out reference digits, dps) < {need}; rerun with --dps {need}) [SKIP]")

    if args.check:
        print(f"\n--check (held-out points): rerunning at dps {mp.mp.dps + 60} ...")
        old_dps = mp.mp.dps
        mp.mp.dps = old_dps + 60
        masses_hi = R.masses_from_sq(data["masses_sq"])   # re-sqrt at the new dps
        okall = True
        for t, J_lo, _, _, gp in results:
            J_hi = R.predict_J(t, masses_hi, mutate=mutate)
            dd = abs(J_hi - J_lo) / abs(J_hi)
            stab = int(-mp.log10(dd)) if dd > 0 else mp.mp.dps
            okk = stab >= old_dps - 12
            okall = okall and okk
            print(f"  t = {mp.nstr(t, 8):>10}: low-dps value stable to {stab} digits "
                  f"(target ~{old_dps})  [{'ok' if okk else 'DRIFT'}]")
        mp.mp.dps = old_dps
        print(f"--check (held-out) verdict: {'PASS' if okall else 'FAIL'}")
        if not okall:
            failures.append("--check two-precision DRIFT on a held-out point (see table)")

    wall = time.time() - t0
    print(f"\nheld-out gate wall time: {wall:.2f} s")
    return worst, wall, failures


def _t_fraction(ps):
    """The exact rational of a --point string ('16', '33/2', '16.5'); None when it is not a rational literal."""
    from fractions import Fraction
    try:
        return Fraction(ps)
    except (ValueError, ZeroDivisionError):
        return None


def _sig_digits(s):
    """Significant digits of a decimal reference string (sign, point and leading zeros dropped)."""
    return len(s.replace("-", "").replace("+", "").replace(".", "").lstrip("0"))


def _digits_rel(mp, delta, scale):
    """Agreement digits -log10(|delta| / scale) as a float (the references' convention: every difference is measured
    relative to |J| of the reference midpoint); the working precision when delta is exactly zero."""
    delta = abs(delta)
    if delta == 0:
        return float(mp.mp.dps)
    return float(-mp.log10(delta / scale))


def physical_arm(mp, R, data, phys, args):
    """--point t > 0: the (1,1,2) closed form continued to t + i0 by the continued layer (sunrise_empl_cont.py +
    frame_ode.py, imported from this directory; that layer loads the served sunrise_empl.py from the same directory and
    never edits it).  For every rational t > 0 given: ONE transport at PHYS_DPS_TRACK digits along the path of record
    (PHYS_T0, PHYS_H, PHYS_NSTEPS) fixes the branch data, the endpoint is evaluated at the working precision and at
    PHYS_PAIR_STEP more digits (the two-precision pair), Re and Im of J^(0)(t + i0) and of E^(0) are printed with the
    certified truncation bound of the resummed words; when t is one of the data file's physical reference points the gate
    block is applied: the members as the reference prints them (this run vs the goal-60 / goal-150 midpoint per component,
    capped at min(the string's digits, the working precision); the ball; this run's two-precision pair; the reference's
    own two-precision pair (goal 60 vs 40; t = 12: 150 vs 120); this run vs the twin), the FLOOR = the minimum over the
    members with its name, the reportable digits per component = min(this run vs the midpoint, the ball), and the verdict
    against the bar (the FLOOR STRICTLY > PHYS_BAR digits AND the worst relative error < 10^-PHYS_BAR/2).  --schwarz adds
    the lower detour (Im t < 0: the -i0 side) against the conjugate of the +i0 value.  mutate (--mutate-physical or
    --mutate) threads the continued layer's own coefficient perturbation (+8 -> 8(1+1e-12)) into the evaluation.
    Returns (failures, gated): failures a list of messages (empty when every leg passes), gated the gated t labels."""
    import time
    import sunrise_empl_cont as C  # noqa: E402  (the pinned continued layer, beside this script)
    failures = []
    gated = []
    mutate = bool(getattr(args, "mutate", False) or getattr(args, "mutate_physical", False))
    masses_sq = data["masses_sq"]
    refs = {_t_fraction(gp["t"]): gp for gp in data.get("physical_gate_points", [])}
    ref_names = ", ".join(gp["t"] for gp in data.get("physical_gate_points", []))
    dps = args.dps
    dps_pair = dps + PHYS_PAIR_STEP
    thr = 6 + 4 * mp.sqrt(2)
    if getattr(args, "mutate_physical", False):
        print("\n[mutate-physical] CONTROL RUN: the continued layer's per-puncture coefficient +8 -> 8 (1 + 1e-12) in the "
              "physical evaluation (one digit at the twelfth place, in memory; the data file untouched); the physical "
              "gate MUST fail and this run MUST exit nonzero")
    for ps, tq in phys:
        t_start = time.time()
        with mp.workdps(dps_pair + 20):
            T = mp.mpf(tq.numerator) / mp.mpf(tq.denominator)
        try:
            res = C.evaluate_ode_hp(T, masses_sq, [dps, dps_pair], t0=PHYS_T0, h=PHYS_H, nsteps=PHYS_NSTEPS,
                                    dps_track=PHYS_DPS_TRACK, mutate=mutate)
            if getattr(args, "schwarz", False):
                res_low = C.evaluate_ode_hp(T, masses_sq, [dps], t0=PHYS_T0, h=PHYS_H, nsteps=PHYS_NSTEPS,
                                            dps_track=PHYS_DPS_TRACK, mutate=mutate, side=-1)
        except (ValueError, AssertionError) as e:
            print(f"\n--point t = {ps}: REFUSED ({e})")
            raise SystemExit(EXIT_USAGE)  # domain refusal exits 2 (never a silent rc=0)
        E, J, d = res[dps]
        E2, J2, d2 = res[dps_pair]
        absJ_run = abs(J2)
        print(f"\n--point t = {ps} (physical region; continued to t + i0 along the path of record: t0 = {PHYS_T0}, "
              f"h = {PHYS_H}, {PHYS_NSTEPS} RK4 steps at {PHYS_DPS_TRACK} digits; the endpoint at dps {dps + 10} and "
              f"{dps_pair + 10}):")
        print(f"  J^(0)(t + i0) = {mp.nstr(mp.re(J), mp.mp.dps)} + ({mp.nstr(mp.im(J), mp.mp.dps)}) i")
        print(f"  E^(0)(t + i0) = J/psihat1 = {mp.nstr(mp.re(E), mp.mp.dps)} + ({mp.nstr(mp.im(E), mp.mp.dps)}) i")
        print(f"  psihat1 = {mp.nstr(d['psihat1'], 30)}")
        m_ = d["match"]
        print(f"  branch data (the transported frame matched to the principal-branch integrals at the endpoint): "
              f"K {m_['K']}, iK' {m_['iKc']}, K(m') {m_['Kp']}, iK'(m') {m_['iKpc']}, z1 {m_['z1']}, z2 {m_['z2']}, "
              f"z3 {m_['z3']}, sqrt(Z3) sign {int(m_['sqZ3_sign'])}; lattice relation z1+z2+z3 = 1 residual "
              f"{mp.nstr(d['exact_lattice_residual'], 2)}; |q_C| = {mp.nstr(d['abs_qC'], 5)}")
        # the certified bound of the dilogarithm tails: the resummed word W(z,N) = (pref/N^2) sum_k [Li2(w q^(Nk)) -
        # Li2(q^(Nk)/w)] is summed until both arguments are below tol = 10^-(dps_hi+10) (every summed argument has
        # |x| < 1/2, where |Li2(x)| <= 2|x|); the dropped terms are geometric in |q_C|^N below that, so
        # |Delta W_N| <= 4 (pref/N^2) tol |q_C|^N / (1 - |q_C|^N) per marked point, |Delta E| = a_norm/3 * sum over the
        # marked points (with multiplicity) of |Delta W_1| + 8 |Delta W_2|, |Delta J| = |psihat1| |Delta E|
        # (BOUND, not estimate; the boundary term C_{4,2} has no truncation).
        tol = mp.mpf(10) ** (-(d["dps_hi"] + 10))
        q = d["abs_qC"]
        pref = abs(R.g3_pref())
        mults = {"z1": (2 if len(d["zkeys"]) == 2 else 1), "z2": 1, "z3": 1}
        dW = {N: 4 * (pref / (N * N)) * tol * q ** N / (1 - q ** N) for N in (1, 2)}
        dE = abs(R.a_norm()) / 3 * sum(mults[zk] * (dW[1] + 8 * dW[2]) for zk in d["zkeys"])
        dJ = abs(d["psihat1"]) * dE
        print(f"  certified bound: |Delta E| <= {mp.nstr(dE, 3)}, |Delta J| <= {mp.nstr(dJ, 3)}  (dilogarithm-tail "
              f"truncation of the resummed words at tol = 10^-(dps+20); BOUND, not estimate)")
        with mp.workdps(dps_pair + 20):   # the pair measured at the higher leg's precision (no rounding of the legs)
            pair_re = _digits_rel(mp, mp.re(J) - mp.re(J2), absJ_run)
            pair_im = _digits_rel(mp, mp.im(J) - mp.im(J2), absJ_run)
        print(f"  two-precision pair (dps {dps} vs {dps_pair}): Re {pair_re:.1f} digits, Im {pair_im:.1f} digits "
              f"(relative to |J|)")
        above = T > thr
        if above:
            print(f"  above the normal threshold 6 + 4 sqrt2 = {mp.nstr(thr, 6)}: Im J^(0)(t + i0) < 0: "
                  f"{'yes' if mp.im(J) < 0 else 'NO'}")
        else:
            print(f"  below the normal threshold 6 + 4 sqrt2 = {mp.nstr(thr, 6)}: |Im J^(0)| = {mp.nstr(abs(mp.im(J)), 2)} "
                  f"(vanishes there; the real part is the value)")
        if getattr(args, "schwarz", False):
            El, Jl, dl = res_low[dps]
            s_all = _digits_rel(mp, Jl - mp.conj(J), absJ_run)
            s_re = _digits_rel(mp, mp.re(Jl) - mp.re(J), absJ_run)
            s_im = _digits_rel(mp, mp.im(Jl) + mp.im(J), absJ_run)
            s_ok = s_all > PHYS_BAR
            print(f"  Schwarz control (the lower detour, Im t < 0: the -i0 side): J^(0)(t - i0) = "
                  f"{mp.nstr(mp.re(Jl), 30)} + ({mp.nstr(mp.im(Jl), 30)}) i; vs the conjugate of J^(0)(t + i0): "
                  f"{s_all:.1f} digits (Re {s_re:.1f}, Im {s_im:.1f})  [{'PASS' if s_ok else 'FAIL'}: > {PHYS_BAR} d]")
            if not s_ok:
                failures.append(f"Schwarz control at t = {ps}: the lower detour vs the conjugate agree {s_all:.1f}d, "
                                f"not > {PHYS_BAR}d")
        gp = refs.get(tq)
        if gp is None:
            print(f"  t = {ps} is not one of the physical reference points (t = {ref_names}): evaluated, no gate")
            print(f"  physical arm wall time (t = {ps}): {time.time() - t_start:.2f} s")
            continue
        g, tg = gp["goal_digits"], gp["twin_goal_digits"]
        wdps = mp.mp.dps
        cap_re = min(_sig_digits(gp["J111_eps0"]["re"]), wdps)
        cap_im = min(_sig_digits(gp["J111_eps0"]["im"]), wdps)
        cap_tre = min(_sig_digits(gp["twin_J111_eps0"]["re"]), wdps)
        cap_tim = min(_sig_digits(gp["twin_J111_eps0"]["im"]), wdps)
        # the reference strings are parsed and every member measured at the strings' own precision (never rounded to
        # the working precision: a string rounded to dps digits would put a false floor at dps on the string-vs-string
        # members); the caps above carry the working precision into the 'vs the midpoint' members
        hi = max(cap_re, cap_im, cap_tre, cap_tim, _sig_digits(gp["J111_eps0"]["re"]), _sig_digits(gp["J111_eps0"]["im"]),
                 _sig_digits(gp["twin_J111_eps0"]["re"]), _sig_digits(gp["twin_J111_eps0"]["im"]), wdps) + 20
        with mp.workdps(hi):
            re_m, im_m = mp.mpf(gp["J111_eps0"]["re"]), mp.mpf(gp["J111_eps0"]["im"])
            re_r, im_r = mp.mpf(gp["J111_eps0"]["re_radius"]), mp.mpf(gp["J111_eps0"]["im_radius"])
            tre, tim = mp.mpf(gp["twin_J111_eps0"]["re"]), mp.mpf(gp["twin_J111_eps0"]["im"])
            absJ = abs(mp.mpc(re_m, im_m))
            d_re = _digits_rel(mp, mp.re(J) - re_m, absJ)
            d_im = _digits_rel(mp, mp.im(J) - im_m, absJ)
            d_tre = _digits_rel(mp, mp.re(J) - tre, absJ)
            d_tim = _digits_rel(mp, mp.im(J) - tim, absJ)
            worst_rel = max(abs(mp.re(J) - re_m), abs(mp.im(J) - im_m)) / absJ
            members = [
                (f"continued evaluator (dps {dps}) Re vs the AMFlow goal-{g} Re midpoint", min(d_re, cap_re)),
                (f"continued evaluator (dps {dps}) Im vs the AMFlow goal-{g} Im midpoint", min(d_im, cap_im)),
                (f"AMFlow goal-{g} Re ball (radius {gp['J111_eps0']['re_radius']})", _digits_rel(mp, re_r, absJ)),
                (f"continued evaluator two-precision pair Re (dps {dps} vs {dps_pair})", pair_re),
                (f"continued evaluator two-precision pair Im (dps {dps} vs {dps_pair})", pair_im),
                (f"AMFlow goal-{g} Im ball (radius {gp['J111_eps0']['im_radius']})", _digits_rel(mp, im_r, absJ)),
                (f"two-precision AMFlow pair Re (goal {g} vs goal {tg})", _digits_rel(mp, re_m - tre, absJ)),
                (f"two-precision AMFlow pair Im (goal {g} vs goal {tg})", _digits_rel(mp, im_m - tim, absJ)),
                (f"continued evaluator (dps {dps}) Re vs the AMFlow goal-{tg} twin Re midpoint", min(d_tre, cap_tre)),
                (f"continued evaluator (dps {dps}) Im vs the AMFlow goal-{tg} twin Im midpoint", min(d_tim, cap_tim)),
            ]
            # the four 'vs a midpoint' members carry their cap (the string's digits / the working precision) when it binds
            capped = {members[0][0]: (d_re, cap_re), members[1][0]: (d_im, cap_im), members[8][0]: (d_tre, cap_tre),
                      members[9][0]: (d_tim, cap_tim)}
            absJ_str = mp.nstr(absJ, 6)
            worst_rel_str = mp.nstr(worst_rel, 3)
            im_ref_neg = im_m < 0
        floor_name, floor = min(members, key=lambda kv: kv[1])
        rep_re = int(min(members[0][1], members[2][1]))
        rep_im = int(min(members[1][1], members[5][1]))
        bar_rel = mp.mpf(10) ** (-PHYS_BAR) / 2
        ok = (floor > PHYS_BAR) and (worst_rel < bar_rel)
        print(f"  [gate] t = {ps} is a physical reference point ({gp['source']}):")
        for name, val in members:
            raw, cap = capped.get(name, (val, None))
            note = f" (capped at {cap}; uncapped {raw:.1f})" if cap is not None and raw > cap else ""
            print(f"    {name} = {val:.1f}{note}")
        print(f"    caps on 'vs the midpoint': min(the string's digits, the working precision {mp.mp.dps}) = {cap_re} (Re), "
              f"{cap_im} (Im); twin {cap_tre} (Re), {cap_tim} (Im); digits = -log10(|difference| / |J|), |J| = "
              f"{absJ_str} the reference midpoint's modulus; the reference's Im < 0: "
              f"{'yes' if im_ref_neg else 'no'}, this run's: {'yes' if mp.im(J) < 0 else 'no'}")
        print(f"    provenance: AMFlow goal {g} digits, eps order {gp['eps_order']}, {gp['n_thread']} threads, run "
              f"direction {gp['run_direction_knob']}; input configuration sha256 {gp['config_sha256'][:16]}..., output "
              f"sha256 {gp['out_sha256'][:16]}..., wall {gp['wall']['elapsed']}; twin (goal {tg}): configuration "
              f"{gp['twin_config_sha256'][:16]}..., output {gp['twin_out_sha256'][:16]}..., wall "
              f"{gp['twin_wall']['elapsed']} (full values in the data file)")
        print(f"    FLOOR {floor:.1f} digits = {floor_name}; reportable Re {rep_re} / Im {rep_im} digits (= min(this run "
              f"vs the midpoint, the ball), truncated)")
        print(f"  physical gate t = {ps}: [{'PASS' if ok else 'FAIL'}: FLOOR {floor:.1f} d strictly > {PHYS_BAR} d AND "
              f"worst-rel {worst_rel_str} < 10^-{PHYS_BAR}/2, directed-rounding slack]")
        if not ok:
            failures.append(f"physical gate t = {ps}: FLOOR {floor:.1f}d ({floor_name}) not STRICTLY > {PHYS_BAR}d, or "
                            f"rel error {worst_rel_str} not < 10^-{PHYS_BAR}/2 = {mp.nstr(bar_rel, 3)}")
        gated.append(f"t = {ps} (floor {floor:.1f} digits)")
        print(f"  physical arm wall time (t = {ps}): {time.time() - t_start:.2f} s")
    return failures, gated


def main():
    ap = argparse.ArgumentParser(
        description="Row 9: unequal-mass sunrise (1,1,2) eps^0, "
                    "uniform per-puncture Kronecker-eMPL closed form")
    ap.add_argument("--dps", type=int, default=150,
                    help="reported decimal digits (default 150; +10 guard digits internally)")
    ap.add_argument("--point", action="append", default=[],
                    help="extra t: Euclidean t < 0 (rational -7/2 or decimal; use --point=-7/2 "
                         "syntax for negatives) or physical t > 0 (a rational; continued to t + i0 "
                         "and gated at t = 16, 20, 12); repeatable")
    ap.add_argument("--schwarz", action="store_true",
                    help="with --point t > 0: also evaluate the lower detour (the -i0 side) and print its "
                         "agreement with the conjugate of the +i0 value")
    ap.add_argument("--mutate-physical", action="store_true",
                    help="control: the continued layer's coefficient +8 -> 8(1+1e-12) in the physical "
                         "evaluation only; the physical gate must fail and the run exit nonzero")
    ap.add_argument("--check", action="store_true",
                    help="two-precision rule: rerun all gate points (fit and held-out) at dps+60 and diff")
    ap.add_argument("--mutate", action="store_true",
                    help="control: perturb the exact coefficient +8 to 8(1+1e-12) in every gate "
                         "evaluation; run must exit nonzero")
    ap.add_argument("--mutate-heldout", action="store_true",
                    help="control: the same perturbation in the held-out evaluation ONLY (fit table "
                         "untouched); the held-out gate must fail and the run exit nonzero")
    args = ap.parse_args()
    if args.dps < 30:
        ap.error("--dps must be >= 30 (the gate bar is 30 digits)")   # argparse exits 2

    try:
        import mpmath as mp
    except ImportError:
        print("MISSING: python3 module mpmath (pip install mpmath); nothing computed")
        return EXIT_MISSING

    raw_data = _pinned_bytes(DATA_FILE, DATA_SHA256)   # refused before any computation
    _pinned_bytes(EMPL_FILE, EMPL_SHA256)
    _pinned_bytes(CONT_FILE, CONT_SHA256)
    _pinned_bytes(ODE_FILE, ODE_SHA256)
    sys.path.insert(0, HERE)
    import sunrise_empl as R  # noqa: E402
    print(f"[pins] {os.path.basename(DATA_FILE)} sha256 {DATA_SHA256[:16]}..., "
          f"sunrise_empl.py sha256 {EMPL_SHA256[:16]}..., sunrise_empl_cont.py sha256 {CONT_SHA256[:16]}... "
          f"and frame_ode.py sha256 {ODE_SHA256[:16]}... match")

    mp.mp.dps = args.dps + 10  # guard digits; gate digits reported vs the references
    data = json.loads(raw_data)
    if data["row"] != ROW:
        print(f"REFUSED: data file is for row {data['row']}, not row {ROW}")
        return EXIT_PIN
    # split the reference points by the data file's own flags: the shared layer's gate table
    # sees ONLY the fit points (in_fit; its printed values are unchanged), the held-out points
    # are gated separately below; the paper claim is measured over the held-out points only
    fit = [gp for gp in data["gate_points"] if gp["in_fit"] and not gp["held_out_in_pslq_fit"]]
    held = [gp for gp in data["gate_points"] if gp["held_out_in_pslq_fit"] and not gp["in_fit"]]
    if len(fit) + len(held) != len(data["gate_points"]) or not fit or not held:
        print(f"REFUSED: data file flags are inconsistent ({len(fit)} in_fit, {len(held)} held out, "
              f"{len(data['gate_points'])} points)")
        return EXIT_PIN
    data_fit = dict(data, gate_points=fit, paper_claims=[])
    print(f"reference points: {len(fit)} fit points (t = {', '.join(_t_label(g) for g in fit)}; in-sample) "
          f"+ {len(held)} held-out points (t = {', '.join(_t_label(g) for g in held)}; never in the fit)")
    print(f"physical-region reference points (t > 0; gated on --point only, the default run does not evaluate "
          f"them): t = {', '.join(g['t'] for g in data.get('physical_gate_points', []))}")
    # --point t > 0 goes to the continued layer (physical_arm); t <= 0 stays on the served Euclidean path, which
    # sees a Namespace copy carrying only those points (t = 0 is refused there, as before)
    pts = [(ps, _t_fraction(ps)) for ps in args.point]
    phys = [(ps, tq) for ps, tq in pts if tq is not None and tq > 0]
    args_e = argparse.Namespace(**vars(args))
    args_e.point = [ps for ps, tq in pts if not (tq is not None and tq > 0)]
    failures = []
    worst_fit = None
    try:
        worst_fit, wall = R.run_row(ROW, data_fit, args_e)   # SystemExit(2) on a --point refusal
    except RuntimeError as e:
        failures.append(str(e))
        print(f"\n[gate] fit-point table FAIL: {e}")
    masses = R.masses_from_sq(data["masses_sq"])   # same call, same dps as the table above
    worst_held, wall_held, held_failures = heldout_gate(mp, R, data, held, args_e, masses)
    failures += held_failures
    phys_gated = []
    if phys:
        phys_failures, phys_gated = physical_arm(mp, R, data, phys, args)
        failures += phys_failures
    fit_names = ", ".join(_t_label(g) for g in fit)
    if worst_fit is None:
        print(f"  fit-point: t = {fit_names} (the PSLQ fit points; in-sample): FAIL (see the table above)")
    else:
        print(f"  fit-point: t = {fit_names} (the PSLQ fit points; in-sample): worst agreement "
              f"{worst_fit} digits  [PASS: strict bar > 30 d]")
    if failures:
        print(f"\n[gate] FAIL (exit {EXIT_FAIL}): " + "; ".join(failures))
        return EXIT_FAIL
    print(f"\n[gate] PASS: positive control, fit-point table (worst {worst_fit} digits over {len(fit)} points, "
          f"strict bar > 30), held-out points t = {', '.join(_t_label(g) for g in held)} (worst {worst_held} "
          f"digits, strict bar > 30), the paper's printed digits and its stated held-out digit count all clear"
          + (" (two-precision --check stable on both sets)" if args.check else "")
          + (f"; physical-region gate {', '.join(phys_gated)}: clear" if phys_gated else ""))
    return EXIT_PASS


if __name__ == "__main__":
    sys.exit(main())
