"""RUN-013 - Round 12 (v0.12): the mpmath.iv replay of Round 11's certificate - the special enclosures, the boundary
balls, interval Newton on the root boxes, the boundary sides.

Round 12 finds no Arb in its environment and replays Round 11 in mpmath's interval arithmetic: s270 and s120 - s270
by a composite midpoint rule on 65,536 parts in intervals, with the analytic error added; the event control's first
branch and the gain; the 19 boundary formulas as balls; an interval Newton step on each of the 12 root boxes; and the
17 switches' sides, of which it resolves 5 directly, calling the other 12 dependency inflation, not counterexamples.
It leaves the 579 leaf boxes unreplayed.

Checks (each returns a list of failures; empty = pass)
  W1 special      the s270, s120 - s270 and event-branch intervals contain the exact values; the gain interval is the
                  s270 interval less the event interval; this run's own interval replay of the method (65,536 parts,
                  75 digits) gives the stated intervals, to 1e-70
  W2 balls        19 balls with Round 11's formulas, each containing the formula's exact value and narrower than 1e-69
                  (the round's 70-digit balls); the containment flags
                  for Round 11's floats are what the balls hold, and the summary's all-contain is their conjunction
  W3 roots        each root box holds one exact stationary point of its kind; its Newton image lies inside it and holds
                  the root; its second-derivative interval contains s'' at four points inside it (60 digits, to
                  1e-10) and excludes 0; the derivative's signs at its ends are its kind's; its scale interval holds s
                  at the root; the margins; the boxes are all the stationary points (fixed-text failure)
  W4 sides        17 boundaries with Round 11's formulas; the direct passes are the five switches between corners;
                  Round 11's safety flags; at the other twelve, wherever Round 11 states a sign (eleven of them), the
                  derivative keeps it on each side (60 digits) - so those failures are not counterexamples to the signs
  W5 copies       the summary holds the data files' records and counts; the leaf replay is not completed
  W6 documents    the report's numbers and tables (sections 2, 4, 5), its statuses, the README, the audit note and the
                  manifest; their account of the boundary balls against the data (fixed-text failure)
  W7 sources      the replay scripts' constants are Round 09's decimals and their methods are those stated

Drill: each plant names the checks that must trip - trip meaning a failure the unplanted package does not already
have; controls must trip nothing.

Usage:
  python code/run013_round12_iv_replay.py [--drill [--log PATH] | --blind W#]
"""
import argparse
import copy
import json
import math
import pathlib
import re

import mpmath as mp

from corpus import TREE, sha256, source_bytes
import gate
from run001_corpus_identity import restore_escapes
from run004_round03_symmetric_links import load
import run010_round09_precision_cusp as r10
import run011_round10_phase_map as r11
import run012_round11_box_certificate as r12
from run010_round09_precision_cusp import dec, hi, iv, lo

RID = "round-v0.12"
LOG = TREE / "data" / "gate-logs" / "run013-round12-iv-replay.json"


# ---------------------------------------------------------------- the world

def build_world():
    f, f11 = load(RID), load("round-v0.11")
    md = lambda n: restore_escapes(f[n].decode("utf-8"))
    js = lambda n: json.loads(f[n])
    return {
        "readme": md("README.md"), "report": md("reports/ROUND_12_REPORT.md"), "audit_note": md("reports/ROUND_12_IV_REPLAY_AUDIT.md"),
        "handoff": md("reports/ROUND_13_SMOOTH_KKT_HANDOFF.md"),
        "special_src": f["src/special_branch_iv_replay.py"].decode("utf-8"), "root_src": f["src/root_interval_newton_replay.py"].decode("utf-8"),
        "manifest": js("MANIFEST.json"), "members": sorted(f),
        "summary": js("data/round12_summary.json"), "special": js("data/special_branch_iv_certificate.json"),
        "balls": js("data/exact_boundary_balls.json"), "roots": js("data/root_interval_newton_replay.json"),
        "sides": js("data/boundary_iv_replay.json"),
        "round11_boundaries": json.loads(f11["data/exact_contact_boundaries.json"]),
        "round11_neighbourhoods": json.loads(f11["data/boundary_neighborhood_audit.json"]),
        "round11_roots": json.loads(f11["data/stationary_root_boxes.json"]),
        "round10": json.loads(load("round-v0.10")["data/round10_summary.json"]),
        "round09": json.loads(load("round-v0.9")["data/round09_summary.json"]),
        "round05": json.loads(load("round-v0.5")["data/round05_summary.json"])["event_solution"]["parameters"],
        "legs": ("1", "1"),
    }


def w12(w):
    """the world RUN-012's tools read"""
    return {"round10": w["round10"], "round09": w["round09"], "round05": w["round05"], "legs": w["legs"],
            "boundaries": w["round11_boundaries"]}


def exact(w):
    """s270, s120 and the event's first branch, exactly (200 digits)"""
    def go():
        H = r11.hp(r12.w11(w12(w)))
        s = r10.s270(H)
        with mp.workdps(100):
            s120 = r11.cand(r12.w11(w12(w)), 100).s(2 * mp.pi / 3)
        return {"s270": s, "s120": s120, "B1": r10.event_branches(H)[0]}
    return r10.memo("exact13", r11.hp(r12.w11(w12(w))))(go)


def within(txt_pair, x, digits=120):
    with mp.workdps(digits):
        return mp.mpf(txt_pair[0]) <= x <= mp.mpf(txt_pair[1])


# ---------------------------------------------------------------- the checks

def replay(w, N=65536, dps=75):
    """this run's own interval replay of the round's method: midpoint sums of sin, cos and q(theta) on N parts in
    intervals, plus the analytic midpoint error M/(24 N^2), M = |theta'|^2 + |theta''| bounds"""
    def go():
        iv.dps = dps
        D = w["round09"]["definitions"]["candidate"]
        eps, wl, b, d, c = (iv.mpf(D[k]) for k in ("epsilon", "wing_length", "beta", "delta", "transition_center"))
        r3 = iv.sqrt(iv.mpf(3))
        fa, fb = (iv.mpf(x) for x in w["legs"])
        A = (3 + 4 * r3) / 18 / iv.mpf("1.0048") * fa
        B = (4 + r3) / 6 / iv.mpf("1.0048") * fb
        l0 = 1 - 2 * wl
        tanh = lambda x: 1 - 2 / (iv.exp(2 * x) + 1)
        raw = lambda u: iv.mpf("0.5") * (1 - tanh((u - c) / eps))
        r0, r1 = raw(iv.mpf(0)), raw(iv.mpf(1))
        qa, qb = 2 / r3 - 1, 1 / r3
        s1, s2, sq = iv.mpf(0), iv.mpf(0), iv.mpf(0)
        for i in range(N):
            th = b + d * (raw(iv.mpf(2 * i + 1) / iv.mpf(2 * N)) - r1) / (r0 - r1)
            ss, cc = iv.sin(th), iv.cos(th)
            s1 += ss
            s2 += cc
            sq += qa * ss - qb * cc
        Cc = d * iv.mpf("0.5") / eps / (r0 - r1)
        M = Cc * Cc + 2 * Cc / eps * 2 / (3 * r3)
        Ec = M / (24 * N * N)
        Eq = iv.sqrt((2 / r3 - 1) ** 2 + (1 / r3) ** 2) * M / (24 * N * N)
        pm = iv.mpf([-1, 1])
        I1 = s1 / N + pm * Ec
        I2 = s2 / N + pm * Ec
        Iq = sq / N + pm * Eq
        s270 = wl * I1 / A + (wl * I2 + l0) / B
        diff = l0 * (1 / (2 * A) - 1 / B) + (wl / A) * Iq
        # the ends at full precision (the 40-digit lo/hi would cut them)
        with mp.workdps(dps + 15):
            ends = lambda x: (mp.mpf(x._mpi_[0]), mp.mpf(x._mpi_[1]))
            return {"s270": ends(s270), "diff": ends(diff)}
    return r10.memo("replay13", r11.hp(r12.w11(w12(w))), N, dps)(go)


def w1_special(w):
    failures = []
    X = exact(w)
    SP = w["special"]
    with mp.workdps(120):
        for label, pair, true in (("s270", SP["s270_interval"], X["s270"]), ("s120 - s270", SP["s120_minus_s270_interval"], X["s120"] - X["s270"]),
                                  ("event branch", SP["event_B1_interval"], X["B1"])):
            if not within(pair, true):
                failures.append(f"the {label} interval does not contain the exact value")
        g = SP["smooth_minus_event_interval"]
        want = (mp.mpf(SP["s270_interval"][0]) - mp.mpf(SP["event_B1_interval"][1]), mp.mpf(SP["s270_interval"][1]) - mp.mpf(SP["event_B1_interval"][0]))
        if abs(mp.mpf(g[0]) - want[0]) > mp.mpf(10) ** -75 or abs(mp.mpf(g[1]) - want[1]) > mp.mpf(10) ** -75:
            failures.append("the gain interval is not the s270 interval less the event interval")
        if not (SP["s120_above_s270"] == (mp.mpf(SP["s120_minus_s270_interval"][0]) > 0) and SP["smooth_above_event"] == (mp.mpf(g[0]) > 0)):
            failures.append("the flags are not the intervals' signs")
        R = replay(w)
        for label, mine, pair in (("s270", R["s270"], SP["s270_interval"]), ("s120 - s270", R["diff"], SP["s120_minus_s270_interval"])):
            # the same method in 75-digit interval arithmetic gives the same ends, up to its rounding
            tol = mp.mpf("1e-70")
            if abs(mine[0] - mp.mpf(pair[0])) > tol or abs(mine[1] - mp.mpf(pair[1])) > tol:
                failures.append(f"this run's replay does not give the {label} interval")
    return failures


def w2_balls(w):
    failures = []
    B = w["balls"]
    F11 = w["round11_boundaries"]
    if len(B) != 19:
        failures.append("there are not 19 balls")
    with mp.workdps(90):
        al, be = r12.alpha_beta(w12(w))
        for b, f in zip(B, F11):
            v = r12.formula_value(b["formula"], al, be)
            if b["formula"] != f["formula"]:
                failures.append(f"ball {b['index']}: the formula is not Round 11's")
            if not within(b["ball"], v, 90) or mp.mpf(b["ball"][1]) - mp.mpf(b["ball"][0]) > mp.mpf(10) ** -69:
                failures.append(f"ball {b['index']}: the ball does not hold the formula's value")
            if b["contains_round11_float"] != within(b["ball"], mp.mpf(f["phase"]), 90):
                failures.append(f"ball {b['index']}: the containment flag is not the ball's")
    if w["summary"]["exact_boundaries"]["all_contain"] != all(b["contains_round11_float"] for b in B) \
            or w["summary"]["exact_boundaries"]["count"] != len(B):
        failures.append("the summary's all-contain is not the balls' flags")
    return failures


def w3_roots(w):
    failures = []
    W = w12(w)
    stat = r12.stationary(W)
    C = r11.cand(r12.w11(W), 60)
    s270_hi = w["special"]["s270_interval"][1]
    for j, r in enumerate(w["roots"]):
        with mp.workdps(60):
            a, z = mp.mpf(r["box"][0]), mp.mpf(r["box"][1])
            inside = [e for e in stat if a <= e["phase"] <= z]
            if len(inside) != 1 or inside[0]["kind"] != r["kind"] or inside[0]["interval"] != r["interval_index"]:
                failures.append(f"root {j}: the box does not hold one exact stationary point of its kind")
                continue
            q = mp.mpf(inside[0]["phase"])
            N0, N1 = mp.mpf(r["newton_image"][0]), mp.mpf(r["newton_image"][1])
            if not (a < N0 <= q <= N1 < z) or not r["newton_subset"]:
                failures.append(f"root {j}: the Newton image is not inside the box around the root")
            # the root box is Round 11's
            r11box = w["round11_roots"][j]["uniqueness_box"]
            if [float(x) for x in r["box"]] != r11box:
                failures.append(f"root {j}: the box is not Round 11's")
            h = mp.mpf("1e-25")
            d2 = []
            for x in (a + (z - a) * mp.mpf("0.02"), (a + z) / 2, a + (z - a) * mp.mpf("0.98"), q):
                d2.append((r11.deriv(C, r["signature"], x + h) - r11.deriv(C, r["signature"], x - h)) / (2 * h))
            L, H = mp.mpf(r["second_derivative"][0]), mp.mpf(r["second_derivative"][1])
            if min(d2) < L - mp.mpf("1e-10") or max(d2) > H + mp.mpf("1e-10") or L <= 0 <= H:
                failures.append(f"root {j}: the second-derivative interval does not hold s'' or holds 0")
            dl, dr = r11.deriv(C, r["signature"], a), r11.deriv(C, r["signature"], z)
            want = (dl < 0 < dr) if r["kind"] == "smooth_minimum" else (dl > 0 > dr)
            if not want or not r["endpoint_signs"]:
                failures.append(f"root {j}: the end signs are not its kind's")
            if not within(r["scale_interval"], C.s(q), 60):
                failures.append(f"root {j}: the scale interval does not hold s at the root")
        with mp.workdps(120):
            if abs(mp.mpf(r["scale_lower_minus_s270_upper"]) - (mp.mpf(r["scale_interval"][0]) - mp.mpf(s270_hi))) > mp.mpf(10) ** -75:
                failures.append(f"root {j}: the margin")
    if len(stat) != len(w["roots"]):
        failures.append("the root boxes are not all the stationary points")
    return failures


CORNER_SWITCHES = {5, 8, 11, 16, 17}


def w4_sides(w):
    failures = []
    S = w["sides"]
    NB = {n["boundary_index"]: n for n in w["round11_neighbourhoods"]}
    if len(S) != 17:
        failures.append("there are not 17 boundaries")
    for s in S:
        k = s["boundary_index"]
        nb = NB[k]
        if s["formula"] != nb["formula"] or s["round11_global_safe"] != nb["global_exclusion_safe"]:
            failures.append(f"boundary {k}: the formula or Round 11's flag is not Round 11's")
        if s["direct_iv_side_sign_pass"] != (k in CORNER_SWITCHES) or s["status"] != ("direct_pass" if s["direct_iv_side_sign_pass"] else "dependency_inflation"):
            failures.append(f"boundary {k}: the direct pass is not where both supports are corners")
        if not s["direct_iv_side_sign_pass"] and nb["classification"] != "unresolved_derivative":
            want = {"decreasing_through_switch": (-1, -1), "increasing_through_switch": (1, 1), "cusp_minimum": (-1, 1)}[nb["classification"]]
            for side, sg in zip(("left", "right"), want):
                d = [v for _, v in r12.side_hp(w12(w), nb, side)]
                if not all(x * sg > 0 for x in d):
                    failures.append(f"boundary {k}: the derivative does not keep Round 11's sign on the {side}")
    return failures


def w5_copies(w):
    failures = []
    S = w["summary"]
    if S["special_branch"] != w["special"] or S["exact_boundaries"]["items"] != w["balls"] or S["root_interval_newton"]["items"] != w["roots"] \
            or S["boundary_side_replay"]["items"] != w["sides"]:
        failures.append("the summary's records are not the data files")
    R, B = S["root_interval_newton"], S["boundary_side_replay"]
    if (R["count"], R["newton_pass"], R["endpoint_sign_pass"]) != (len(w["roots"]), sum(r["newton_subset"] for r in w["roots"]), sum(r["endpoint_signs"] for r in w["roots"])):
        failures.append("the summary's root counts are not the data's")
    if (B["count"], B["direct_pass_count"], B["dependency_inflation_count"], B["round11_safe_count"]) != (
            len(w["sides"]), sum(s["direct_iv_side_sign_pass"] for s in w["sides"]), sum(s["status"] == "dependency_inflation" for s in w["sides"]),
            sum(s["round11_global_safe"] for s in w["sides"])):
        failures.append("the summary's boundary counts are not the data's")
    if S["leaf_derivative_replay"]["status"] != "not_completed" or S["environment"] != {"python_flint": False, "arb": False, "fallback": "mpmath.iv"}:
        failures.append("the leaf replay or the environment is not as stated")
    return failures


def w6_documents(w):
    failures = []
    rp = w["report"].replace("\r\n", "\n")
    rs = gate.squash(rp)
    SP = w["special"]
    for pat, pair in ((r"s_\{270\}\\in\[([\d.]+),([\d.]+)\]", SP["s270_interval"]),
                      (r"s_\{120\}-s_\{270\}\\in\[([\d.e-]+),([\d.e-]+)\]", SP["s120_minus_s270_interval"]),
                      (r"s_\{270\}-s_\{\\mathrm\{event\},B1\}\\in\[([\d.]+),([\d.]+)\]", SP["smooth_minus_event_interval"])):
        m = re.search(pat, rs)
        ok = m and all(r10.holds(m.group(i + 1), pair[i]) if "e" not in m.group(i + 1) else r10.rounds_to(m.group(i + 1), float(pair[i]))
                       for i in (0, 1))
        if not ok:
            failures.append(f"section 2's interval {pat[:12]} is not the data's")
    rows = re.findall(r"^\|\s*(\d+)\s*\|\s*`([^`]+)`\s*\|\s*(smooth_\w+)\s*\|\s*(True|False)\s*\|\s*(True|False)\s*\|\s*([\d.e-]+)\s*\|", rp, re.M)
    if len(rows) != len(w["roots"]):
        failures.append(f"section 4: {len(rows)} rows")
    for (k, sg, kd, nt, es, dist), r in zip(rows, w["roots"]):
        if sg != r["signature"] or kd != r["kind"] or (nt == "True") != r["newton_subset"] or (es == "True") != r["endpoint_signs"] \
                or not r10.rounds_to(dist, float(r["scale_lower_minus_s270_upper"])):
            failures.append(f"section 4 row {k} is not the data's")
    rows = re.findall(r"^\|\s*(\d+)\s*\|\s*`([^`]+)`\s*\|\s*(True|False)\s*\|\s*(True|False)\s*\|\s*(\w+)\s*\|", rp, re.M)
    if len(rows) != len(w["sides"]):
        failures.append(f"section 5: {len(rows)} rows")
    for (k, fm, dp, sf, st), s in zip(rows, w["sides"]):
        if fm != s["formula"] or (dp == "True") != s["direct_iv_side_sign_pass"] or (sf == "True") != s["round11_global_safe"] or st != s["status"]:
            failures.append(f"section 5 row {k} is not the data's")
    m = re.search(r"\\boxed\{(\d+)/17\}", rs)
    if not m or int(m.group(1)) != sum(s["direct_iv_side_sign_pass"] for s in w["sides"]):
        failures.append("section 5's count is not the data's")
    m = re.search(r"\\boxed\{(\d+)/12\}", rs)
    if not m or int(m.group(1)) != sum(r["newton_subset"] for r in w["roots"]):
        failures.append("section 4's count is not the data's")
    # the boundary balls, as the documents report them against the data
    n_in = sum(b["contains_round11_float"] for b in w["balls"])
    says = re.search(r"\\boxed\{(\d+)/19\}", rs)
    if not says or int(says.group(1)) != n_in or "全部包含第 11 輪保存的浮點值" in rp or "解析邊界：19/19" in w["audit_note"] \
            or w["summary"]["certificate_status"]["exact_boundary_balls"] == "passed":
        failures.append("the documents report the boundary balls as all containing Round 11's floats; the data say otherwise")
    cs = w["summary"]["certificate_status"]
    if w["manifest"]["certificate_status"] != cs or w["manifest"]["version"] != "0.12" \
            or sorted(w["manifest"]["files"]) != sorted(set(w["members"]) - {"MANIFEST.json"}):
        failures.append("the manifest is not the package's")
    rd = w["readme"]
    if "12 個 interval-Newton 根盒通過" not in rd or f"邊界直接重播 {sum(s['direct_iv_side_sign_pass'] for s in w['sides'])}/17" not in rd:
        failures.append("the README's counts are not the data's")
    an = w["audit_note"]
    if f"interval Newton：{sum(r['newton_subset'] for r in w['roots'])}/12" not in an or "579 葉盒：未完成" not in an:
        failures.append("the audit note's counts are not the data's")
    if "p=(w,\\beta,\\delta,c,\\varepsilon)" not in w["handoff"]:
        failures.append("the handoff does not state its unknowns")
    return failures


def w7_sources(w):
    failures = []
    D = w["round09"]["definitions"]["candidate"]
    S1, S2 = w["special_src"], w["root_src"]
    for name, k in (("EPS", "epsilon"), ("W", "wing_length"), ("BETA", "beta"), ("DELTA", "delta"), ("CENTER", "transition_center")):
        if f"{name}=iv.mpf('{D[k]}')" not in S1 or f"{name}=mp.mpf('{D[k]}')" not in S2:
            failures.append(f"a replay script's {name} is not Round 09's")
    for frag in ("N=65536", "u=iv.mpf(2*i+1)/iv.mpf(2*N)", "M=M1*M1+M2", "Ec=M/(24*N*N)", "iv.dps=75"):
        if frag not in S1:
            failures.append(f"the special replay lacks {frag}")
    for frag in ("N=ib(m)-fm/fp", "'inside':lo(N)>a and hi(N)<b", "ex=abs(x1-x2)+mp.mpf('1e-55')"):
        if frag not in S2:
            failures.append(f"the root replay lacks {frag}")
    return failures


CHECKS = {"W1": w1_special, "W2": w2_balls, "W3": w3_roots, "W4": w4_sides, "W5": w5_copies, "W6": w6_documents, "W7": w7_sources}
CURVE = {"round10", "round09", "round05", "legs"}
DEPENDS = {
    "W1": CURVE | {"special"},
    "W2": CURVE | {"balls", "round11_boundaries", "summary"},
    "W3": CURVE | {"roots", "round11_roots", "special"},
    "W4": CURVE | {"sides", "round11_neighbourhoods", "round11_boundaries"},
    "W5": {"summary", "special", "balls", "roots", "sides"},
    "W6": {"report", "readme", "audit_note", "handoff", "manifest", "members", "summary", "special", "roots", "sides", "balls"},
    "W7": {"special_src", "root_src", "round09"},
}


# ---------------------------------------------------------------- findings

def findings(w):
    out = {}
    X = exact(w)
    SP = w["special"]
    R = replay(w)
    with mp.workdps(120):
        out["s270_inside_interval_by"] = [mp.nstr(X["s270"] - mp.mpf(SP["s270_interval"][0]), 5), mp.nstr(mp.mpf(SP["s270_interval"][1]) - X["s270"], 5)]
        d = X["s120"] - X["s270"]
        out["difference_inside_interval_by"] = [mp.nstr(d - mp.mpf(SP["s120_minus_s270_interval"][0]), 5), mp.nstr(mp.mpf(SP["s120_minus_s270_interval"][1]) - d, 5)]
        out["replay_minus_stated"] = {"s270": [mp.nstr(R["s270"][k] - mp.mpf(SP["s270_interval"][k]), 3) for k in (0, 1)],
                                      "difference": [mp.nstr(R["diff"][k] - mp.mpf(SP["s120_minus_s270_interval"][k]), 3) for k in (0, 1)]}
        out["balls_containing_round11_float"] = sum(b["contains_round11_float"] for b in w["balls"])
        out["round11_float_minus_exact_largest"] = mp.nstr(max(abs(mp.mpf(f["phase"]) - mp.mpf(b["ball"][0])) for f, b in zip(w["round11_boundaries"], w["balls"])), 3)
        out["ball_width_largest"] = mp.nstr(max(mp.mpf(b["ball"][1]) - mp.mpf(b["ball"][0]) for b in w["balls"]), 3)
    stat = r12.stationary(w12(w))
    out["stationary_count"] = len(stat)
    # how wide the root replay's position boxes are: |quad - quadgl| + 1e-55 at the wing's end
    C = r11.cand(r12.w11(w12(w)), 80)
    with mp.workdps(80):
        x_q, y_q = C.pos(mp.mpf(1), "quad")
        x_g, y_g = C.pos(mp.mpf(1), "quadgl")
        out["quad_minus_quadgl_at_wing_end"] = [mp.nstr(abs(x_q - x_g), 3), mp.nstr(abs(y_q - y_g), 3)]
    # Round 11's intervals at the twelve boundaries Round 12 calls dependency inflation, from RUN-012's 60-digit sides
    rows = []
    NB = {n["boundary_index"]: n for n in w["round11_neighbourhoods"]}
    for s in w["sides"]:
        if s["direct_iv_side_sign_pass"]:
            continue
        nb = NB[s["boundary_index"]]
        miss = 0.0
        for side in ("left", "right"):
            d = [float(v) for _, v in r12.side_hp(w12(w), nb, side)]
            DL, DH = nb[f"{side}_derivative_interval"]
            miss = max(miss, DL - min(d), max(d) - DH)
        rows.append({"boundary": s["boundary_index"], "round11_interval_shortfall": miss})
    out["inflation_boundaries_round11_shortfall"] = rows
    return out


# ---------------------------------------------------------------- the drill

def drill_cases(base):
    def edit(key, fn):
        v = copy.deepcopy(base[key])
        fn(v)
        return v
    rp = base["report"]

    def rrep(text, old, new):
        assert text.count(old) == 1, old
        return text.replace(old, new)

    def bump(s, pos):
        i = s.index(".") + pos
        return s[:i] + str((int(s[i]) + 1) % 10) + s[i + 1:]

    def both(key, skey, fn):
        """a data file and the summary's copy of it, planted alike"""
        return edit(key, fn), edit("summary", lambda S: fn(S[skey]["items"] if skey != "special_branch" else S[skey]))
    b1 = both("special", "special_branch", lambda c: c["event_B1_interval"].__setitem__(0, bump(c["event_B1_interval"][0], 75)))
    ball = both("balls", "exact_boundaries", lambda B: B[5].__setitem__("ball", [bump(x, 60) for x in B[5]["ball"]]))
    flag = both("balls", "exact_boundaries", lambda B: B[0].__setitem__("contains_round11_float", False))
    newt = both("roots", "root_interval_newton", lambda R: R[4].__setitem__("newton_image", [R[4]["box"][1], bump(R[4]["box"][1], 5)]))
    sec = both("roots", "root_interval_newton", lambda R: R[3]["second_derivative"].__setitem__(1, "0.5"))
    mar = both("roots", "root_interval_newton", lambda R: R[2].__setitem__("scale_lower_minus_s270_upper", bump(R[2]["scale_lower_minus_s270_upper"], 30)))

    def unpass(S):
        S[4]["direct_iv_side_sign_pass"] = False
        S[4]["status"] = "dependency_inflation"
    sides = edit("sides", unpass)

    def unpass_s(S):
        unpass(S["boundary_side_replay"]["items"])
        S["boundary_side_replay"]["direct_pass_count"] = 4
        S["boundary_side_replay"]["dependency_inflation_count"] = 13
    sides_s = edit("summary", unpass_s)
    rep_sides = rrep(rrep(rp, "| 5 | `\\pi/2` | True | True | direct_pass |", "| 5 | `\\pi/2` | False | True | dependency_inflation |"),
                     "\\boxed{5/17}", "\\boxed{4/17}")
    readme_sides = rrep(base["readme"], "邊界直接重播 5/17", "邊界直接重播 4/17")
    form = both("sides", "boundary_side_replay", lambda S: S[2].__setitem__("formula", "\\beta-\\pi/4"))
    rep_form = rrep(rp, "| 3 | `\\beta-\\pi/3` |", "| 3 | `\\beta-\\pi/4` |")
    cnt = edit("summary", lambda S: S["root_interval_newton"].__setitem__("newton_pass", 11))
    leaf = edit("summary", lambda S: S["leaf_derivative_replay"].__setitem__("status", "completed"))
    rep4 = rrep(rp, "| 4.5455127e-8 |", "| 4.5455137e-8 |")
    readme = rrep(base["readme"], "12 個 interval-Newton 根盒通過", "11 個 interval-Newton 根盒通過")
    man = edit("manifest", lambda M: M["certificate_status"].__setitem__("root_interval_newton", "pending"))
    hand = rrep(base["handoff"], "p=(w,\\beta,\\delta,c,\\varepsilon)", "p=(w,\\beta,\\delta,c)")
    src1 = rrep(base["special_src"], "DELTA=iv.mpf('0.05204734906280986')", "DELTA=iv.mpf('0.05204734906280987')")
    src2 = rrep(base["root_src"], "ex=abs(x1-x2)+mp.mpf('1e-55')", "ex=abs(x1-x2)")
    return [
        ("K0-unmodified", "control", {}, set()),
        ("K1-report-crlf", "control", {"report": base["report"].replace("\n", "\r\n")}, set()),
        ("K2-readme-trailing-spaces", "control", {"readme": base["readme"] + "  "}, set()),
        ("K3-roots-copied", "control", {"roots": copy.deepcopy(base["roots"])}, set()),
        ("M1-triangle-leg", "mutation", {"legs": ("1.0005", "1")}, {"W1", "W3"}),
        ("M2-event-interval", "mutation", {"special": b1[0], "summary": b1[1]}, {"W1"}),
        ("M3-ball", "mutation", {"balls": ball[0], "summary": ball[1]}, {"W2"}),
        ("M4-ball-flag", "mutation", {"balls": flag[0], "summary": flag[1]}, {"W2"}),
        ("M5-newton-image", "mutation", {"roots": newt[0], "summary": newt[1]}, {"W3"}),
        ("M6-second-derivative", "mutation", {"roots": sec[0], "summary": sec[1]}, {"W3"}),
        ("M7-root-margin", "mutation", {"roots": mar[0], "summary": mar[1]}, {"W3"}),
        ("M8-side-pass", "mutation", {"sides": sides, "summary": sides_s, "report": rep_sides, "readme": readme_sides}, {"W4"}),
        ("M9-side-formula", "mutation", {"sides": form[0], "summary": form[1], "report": rep_form}, {"W4"}),
        ("M10-summary-count", "mutation", {"summary": cnt}, {"W5"}),
        ("M11-leaf-status", "mutation", {"summary": leaf}, {"W5"}),
        ("M12-report-distance", "mutation", {"report": rep4}, {"W6"}),
        ("M13-readme-count", "mutation", {"readme": readme}, {"W6"}),
        ("M14-manifest-status", "mutation", {"manifest": man}, {"W6"}),
        ("M15-handoff", "mutation", {"handoff": hand}, {"W6"}),
        ("M16-special-source", "mutation", {"special_src": src1}, {"W7"}),
        ("M17-root-source", "mutation", {"root_src": src2}, {"W7"}),
    ]


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--drill", action="store_true")
    ap.add_argument("--blind", metavar="CID")
    ap.add_argument("--log", default=None)
    args = ap.parse_args()
    if args.blind:
        CHECKS[args.blind] = lambda w: []
        args.drill = True
    w = build_world()
    results = {cid: fn(w) for cid, fn in CHECKS.items()}
    for cid, f in results.items():
        print(f"{cid} {'PASS' if not f else 'FAIL'}  ({len(f)} failures)")
        for x in f[:10]:
            print("    ", x)
    log = {"run": "RUN-013",
           "subject": "Round 12 (v0.12): the mpmath.iv replay - special enclosures, boundary balls, interval Newton, boundary sides",
           "package": {"id": RID, "sha256": sha256(source_bytes(RID))},
           "checks": {cid: {"pass": not f, "failures": f} for cid, f in results.items()},
           "findings": None if args.blind else findings(w)}
    if log["findings"]:
        print(json.dumps(log["findings"], indent=1, ensure_ascii=False, default=str)[:8000])
    if args.drill:
        drill = gate.run_drill(build_world, CHECKS, DEPENDS, drill_cases(build_world()))
        log["drill"] = gate.summarize(drill, CHECKS)
        print(gate.report_drill(log["drill"]))
        for d in drill:
            if not d["ok"]:
                print("   DRILL FAIL", d)
    if args.blind:
        seen = sum(1 for d in log["drill"]["cases"] if args.blind in d["expected"] and args.blind not in d["tripped"])
        print(f"blind {args.blind}: reported blind in {seen} cases (log not written)")
        return
    if args.drill:
        path = pathlib.Path(args.log) if args.log else LOG
        path.parent.mkdir(parents=True, exist_ok=True)
        path.write_text(json.dumps(log, ensure_ascii=False, indent=1, default=str) + "\n", encoding="utf-8", newline="\n")


if __name__ == "__main__":
    main()
