"""p26_pnp — the ledger file of 26-pnp (16 October 2026).

"P versus NP: Computation as Counting over Finite Relational Substrate" (Akhtman & Voether, 2026, preprint
10.20944/preprints202606.1781.v1). The paper's predicate ledger is docs/26-pnp/26-pnp-ledger.json. This file binds its
predicates to the framework: the table of keys (generated from the ledger by ci/make_ledgers.py) and the checks of eight
blocks. The blocks follow the paper's sections; their checks compute what the paper's validation scripts computed
(validation/closure_pnp.py, chart_localization.py, longitudinal_turn.py, exact; dlog_asymmetry.py and
transform_charts.py, which timed the computer, are replaced by operation counts) and decide every theorem row that
admits a finite check. Each check names the predicate(s) it witnesses (LEDGER), under a `# 26:XN (<key>)` marker on the
check that decides it (PREDICATES). The Lean side is the horizon theme's Theme/Ladder.lean with 20-rh's
Theme/Horizon.lean, the foundation theme's Theme/Drive.lean and the frame theme's Transform.lean, the key files
Keys/Horizon.lean, Keys/Foundation.lean and Keys/Frame.lean (Mathlib: Keys/Chart.lean), and the certificates
lean/FrcCore/Ledgers/P26Pnp.lean and lean/FrcLedger/Ledgers/P26Pnp.lean.

The checks call the horizon theme (frc/ladder.py: the ascent and the descent with their counts, the index dynamics, the
recentring, the Gauss sum in the cyclotomic ring, the number-theoretic transform, the affine systems over F_2;
frc/horizon.py: the scale shift), the foundation theme (frc/foundation.py), the quantum theme's cyclotomic ring
(frc/quantum.py) and the chart theme's certified bracket of Ω (frc/chart.py). This file itself is exact (gate G09): the
chart row is decided on the bracket's integer ends by bit length and integer square roots, no float, no random number,
no numeral above Ω.

    python3 -m frc.ledgers.p26_pnp           every block, the census; exit 1 if a check fails (≈ 3 s)
    python3 -m frc.ledgers.p26_pnp D         one block
    from frc.ledgers.p26_pnp import R; R.predicate("26:F3")    one predicate: its blocks run once per session

Blocks, with the paper's section and the kind:
    L  the longitudinal turn        the characters as eigenvectors of the scale shift, the Gauss sum     EXACT, L2 CHART (26:B3, B4, B5)
    H  the horizon arithmetic       the family's bits and the table beyond Ω^{1/4} on the bracket      CHART, H2 EXACT (26:C2)
    C  the localising chart         the recentring, the index as counting distance, the bijection       EXACT   (26:E8, E10, F10)
    A  the ascent                   square-and-multiply against 2ℓ                                      EXACT   (26:F2)
    D  the descent                  baby-step–giant-step: the index recovered, the counts               EXACT   (26:F3, G4)
    R  the roles and the closure    the tower reduction, the phase formula, the counts, the image sizes EXACT   (26:G2, G3, G4, G5)
    T  the transforms               the exact round trip over F_17, F_257, F_65537; the convolution counts EXACT (26:H3)
    W  the affine witness sets      Gaussian elimination over F_2 and the recentred chart              EXACT   (26:H4)

A check is CHART when its verdict is a continuum reading: the dilation generator's eigenfunction (L2, the power rule
applied symbolically) and the family's bit length against the certified bracket of Ω = 4S + 1 (H1). The notebook
frc/ledgers/p26_pnp.ipynb runs one predicate per cell, the cell's id its key (gate G13).
"""
import sys
from fractions import Fraction as Fr
from math import isqrt

from frc import arith
from frc import chart as CH
from frc import horizon as HZ
from frc import ladder as LD
from frc.quantum import Cyclotomic
from frc.registry import Registry

# ---- generated by ci/make_ledgers.py from docs/26-pnp/26-pnp-ledger.json; edit the ledger, not this table ----
PAPER = "26-pnp"
KEYS = {"A1": "p26001", "A2": "p26002", "A3": "p26003", "A4": "p26004", "A5": "p26005", "A6": "p26006", "B1": "p26007", "B2": "p26008", "B3": "p26009", "B4": "p26010", "B5": "p26011", "C1": "p26012", "C2": "p26013", "D1": "p26014", "D2": "p26015", "D3": "p26016", "D4": "p26017", "E1": "p26018", "E2": "p26019", "E3": "p26020", "E4": "p26021", "E5": "p26022", "E6": "p26023", "E7": "p26024", "E8": "p26025", "E9": "p26026", "E10": "p26027", "E11": "p26028", "F1": "p26029", "F2": "p26030", "F3": "p26031", "F4": "p26032", "F5": "p26033", "F6": "p26034", "F7": "p26035", "F8": "p26036", "F9": "p26037", "F10": "p26038", "F11": "p26039", "G1": "p26040", "G2": "p26041", "G3": "p26042", "G4": "p26043", "G5": "p26044", "G6": "p26045", "H1": "p26046", "H2": "p26047", "H3": "p26048", "H4": "p26049", "H5": "p26050", "J1": "p26051", "J2": "p26052", "K1": "p26053", "K2": "p26054", "K3": "p26055", "K4": "p26056", "K5": "p26057", "K6": "p26058"}       # every row of the ledger: its label and its accession key
LEAN = {"B3": "core", "B4": "ml", "B5": "core, ml", "C2": "ml", "E8": "core", "E10": "core", "F2": "core", "F3": "core", "F10": "core", "G2": "core", "G3": "core", "G4": "core", "G5": "core", "H3": "core"}       # the rows with a Lean declaration: the libraries whose key files prove them
# ---- end of the generated table ----

LEAN_ONLY = ()                  # the predicates with a Lean declaration and no python check (gate G12): none

# The paper's predicate ledger (predicates cited as 26:XN): the predicate(s) each check witnesses.
LEDGER = {
    "L1": "26:B3", "L2": "26:B4", "L3": "26:B5", "L4": "26:B5", "L5": "26:E10, 26:F10",
    "H1": "26:C2", "H2": "26:C2",
    "C1": "26:E8", "C2": "26:E10", "C3": "26:F10, 26:E10",
    "A1": "26:F2",
    "D1": "26:F3", "D2": "26:F3", "D3": "26:G4",
    "R1": "26:G2", "R2": "26:G3", "R3": "26:G4, 26:F2", "R4": "26:G5", "R5": "26:G2",
    "T1": "26:H3", "T2": "26:H3", "T3": "26:H3, 26:B3",
    "W1": "26:H4",
}

BLOCK = {"L": "the longitudinal turn: the characters as eigenvectors of the scale shift, the Gauss sum",
         "H": "the horizon arithmetic: the family's bits and the table beyond the quarter root, on the bracket",
         "C": "the localising chart: the recentring, the index as counting distance, the bijection",
         "A": "the ascent: square-and-multiply against twice the bit length",
         "D": "the descent: baby-step-giant-step, the index recovered, the counts",
         "R": "the roles and the closure: the tower reduction, the phase formula, the counts, the image sizes",
         "T": "the transforms: the exact round trip and the convolution counts",
         "W": "the affine witness sets: Gaussian elimination over F_2 and the recentred chart"}

# the deciding check of each witnessed predicate (the other checks that touch it are corroboration)
PREDICATES = {
    "26:B3": "L1", "26:B4": "L2", "26:B5": "L4", "26:C2": "H1", "26:E8": "C1", "26:E10": "C2", "26:F10": "C3",
    "26:F2": "A1", "26:F3": "D1", "26:G2": "R1", "26:G3": "R2", "26:G4": "R3", "26:G5": "R4", "26:H3": "T1",
    "26:H4": "W1",
}

R = Registry("26", "pnp", LEDGER, PREDICATES, sources=[__file__])

TURN = (13, 17, 53)                                   # the suite's shells for the turn
CARRIERS = (10007, 1000003, 100000007)                # the suite's carriers for the counts
LOCAL = (101, 1009, 10007, 100003)                    # the suite's shells for the localisation

def g_of(p): return arith.primitive_root(p)           # the least primitive root, the suite's generator


# ---- L: the longitudinal turn --------------------------------------------------------------------------------------------
@R.block("L", BLOCK["L"])
def block_L():
    """Block L — longitudinal_turn.py (EXACT): the characters as eigenvectors of the scale shift (26:B3), the dilation
    generator (26:B4, CHART), the Gauss-sum telescoping and modulus (26:B5), the exponential bijection (26:E10, F10)."""
    # L1: the scale shift x ↦ g x on the units, the characters χ_ν(g^t) = g^{νt} its eigenvectors, eigenvalue g^ν, distinct
    ok, det = True, []
    for p in TURN:
        g, n = g_of(p), p - 1
        lams = set()
        for nu in range(n):
            v = [pow(g, nu * t % n, p) for t in range(n)]
            lam = pow(g, nu, p)
            ok &= all(v[(t + 1) % n] == lam * v[t] % p for t in range(n))       # (S v)_t = v_{t+1} = λ v_t
            lams.add(lam)
        ok &= len(lams) == n
        fixed = [len(HZ.fixed_points(p, g, r)) for r in (0, 1, n // 2, n, n + 1)]
        ok &= fixed == [n, 0, 0, n, 0]
        det.append(f"F_{p}: {n} eigenvectors, {len(lams)} distinct eigenvalues, fixed points {fixed}")
    # 26:B3 (p26009)
    R.check("L1", "the characters are eigenvectors of the scale shift x -> g x with the (P-1)-th roots of unity as distinct eigenvalues; the shift fixes every unit when (P-1) | r and none otherwise",
            ok, "; ".join(det))

    # L2: Ĥ = -i(x d/dx + 1/2) on x^s with s = -1/2 + i ν: by the power rule x d/dx x^s = s x^s, so Ĥ x^s = -i(s + 1/2) x^s,
    # and -i(s + 1/2) = -i(i ν) = ν. Computed in Q(i)[ν]: a number is (re, im) with coefficients polynomials in ν (lists).
    def padd(a, b): return [x + y for x, y in zip(a + [0] * (len(b) - len(a)), b + [0] * (len(a) - len(b)))]
    def pmul(a, b):
        out = [Fr(0)] * (len(a) + len(b) - 1)
        for i, x in enumerate(a):
            for j, y in enumerate(b): out[i + j] += x * y
        return out
    def cmul(z, w):                                    # (re, im) × (re, im)
        return padd(pmul(z[0], w[0]), [-c for c in pmul(z[1], w[1])]), padd(pmul(z[0], w[1]), pmul(z[1], w[0]))
    def strip(a):
        while a and a[-1] == 0: a = a[:-1]
        return a
    s = ([Fr(-1, 2)], [Fr(0), Fr(1)])                  # s = -1/2 + i ν
    coef = cmul(([Fr(0)], [Fr(-1)]), (padd(s[0], [Fr(1, 2)]), s[1]))   # -i (s + 1/2)
    nu = ([Fr(0), Fr(1)], [])
    ok = strip(coef[0]) == strip(nu[0]) and strip(coef[1]) == strip(nu[1])
    # 26:B4 (p26010)
    R.check("L2", "the dilation generator H = -i(x d/dx + 1/2) has x^(-1/2 + i nu) as eigenfunction of eigenvalue nu: the eigenvalue's coefficient -i(s + 1/2) with s = -1/2 + i nu is nu, computed in Q(i)[nu] (the power rule x d/dx x^s = s x^s is the chart's)",
            ok, "-i(s + 1/2) = nu exactly; the power rule x d/dx x^s = s x^s is the chart's", kind="CHART")

    # L3: the telescoping: t ↦ tc permutes the units; Σ_{t≠0} ψ(tc) = -1 (c ≠ 0) or P-1 (c = 0) in Z[ζ_P]; Σ_u χ(u) = 0 for χ ≠ 1
    ok, det = True, []
    for p in TURN:
        g, n = g_of(p), p - 1
        ok &= all(LD.mul_perm(c, p) for c in range(1, p))
        Cp = Cyclotomic(p)
        ok &= all(LD.ramanujan(p, c, Cp) == Cp.const(-1) for c in range(1, p)) and LD.ramanujan(p, 0, Cp) == Cp.const(n)
        Cn = Cyclotomic(n)
        ok &= all(Cn.sum(Cn.zeta(nu * t % n) for t in range(n)) == Cn.zero for nu in range(1, n))
        ok &= Cn.sum(Cn.zeta(0) for t in range(n)) == Cn.const(n)
        det.append(f"F_{p}: every c permutes the units; the additive sums -1 and {n}; the {n - 1} nontrivial character sums vanish")
    R.check("L3", "the telescoping of the Gauss sum: multiplication by a unit permutes the units, the additive character sum over the units is -1 for c != 0 and P-1 for c = 0, every nontrivial multiplicative character sums to 0",
            ok, "; ".join(det))

    # L4: |g(χ_ν)|² = P exactly in Z[ζ_{P(P-1)}]
    ok, det = True, []
    for p, nus in ((13, (1, 2, 5, 11)), (17, (1, 3, 8))):
        g, n = g_of(p), p - 1
        C = Cyclotomic(p * n)
        res = [LD.gauss_norm(p, g, nu, C) == C.const(p) for nu in nus]
        ok &= all(res)
        det.append(f"F_{p}: g(chi)·conj g(chi) = {p} in Z[zeta_{p * n}] for nu in {nus}")
    # 26:B5 (p26011)
    R.check("L4", "the Gauss sum of a nontrivial character against the additive character has modulus squared P, computed exactly in the cyclotomic ring Z[zeta_{P(P-1)}]",
            ok, "; ".join(det))

    # L5: exp t ↦ g^t is a bijection C_{P-1} → the units
    ok, det = True, []
    for p in TURN:
        g, n = g_of(p), p - 1
        img = [pow(g, t, p) for t in range(n)]
        ok &= sorted(img) == list(range(1, p))
        det.append(f"F_{p}: the {n} powers of {g} are the units")
    R.check("L5", "the exponential t -> g^t is a bijection of the index cycle onto the units", ok, "; ".join(det))


# ---- H: the horizon arithmetic on the bracket --------------------------------------------------------------------------
@R.block("H", BLOCK["H"])
def block_H():
    """Block H (CHART on the bracket): the family P^2 < Ω has at most 205 bits, a 204-bit decision table exceeds the
    absolute horizon Ω^{1/4} (26:C2); the three scales on the worked carriers (EXACT)."""
    lo, hi = CH.omega_bracket()
    flo, fhi = lo.numerator // lo.denominator, hi.numerator // hi.denominator     # the integer floors of the ends
    s_lo, s_hi = isqrt(flo), isqrt(fhi)                                            # ⌊√Ω⌋ at the two ends
    q_lo, q_hi = isqrt(s_lo), isqrt(s_hi)                                          # ⌊Ω^{1/4}⌋
    ok = (flo.bit_length() == 410 == fhi.bit_length() and 2 ** 204 < s_lo and s_hi < 2 ** 205
          and 2 ** 102 < q_lo and q_hi < 2 ** 103 and 2 ** 204 > q_hi and s_lo.bit_length() == 205 == s_hi.bit_length()
          and q_lo.bit_length() == 103 == q_hi.bit_length())
    # 26:C2 (p26013)
    R.check("H1", "on the certified bracket of Omega = 4S + 1: 2^204 < sqrt(Omega) < 2^205, so the family P^2 < Omega has shells of at most 205 bits; 2^102 < Omega^(1/4) < 2^103, so a 204-bit decision table (2^204 entries) lies beyond the absolute horizon and below sqrt(Omega)",
            ok, f"the bracket's ends have bit length {flo.bit_length()}; sqrt(Omega) has {s_lo.bit_length()} bits ({len(str(s_lo))} digits), Omega^(1/4) has {q_lo.bit_length()} bits ({len(str(q_lo))} digits); no numeral beyond the bracket's ends formed", kind="CHART")

    # H2: the three scales on the worked carriers: P^2 < Omega (the family), the descent's count 2(isqrt(P-1)+1) < sqrt(P)·2 + 2 ≤ Omega^{1/4}, P < sqrt(Omega)
    ok, det = True, []
    for p in (13, 101, 1009, 10007, 1000003, 100000007):
        m = isqrt(p - 1) + 1
        ok &= p * p < flo and m * m >= p - 1 and (m - 1) ** 2 <= p - 1 and 2 * m < q_lo and p < s_lo
        det.append(f"P={p}: m={m}, 2m={2 * m}")
    R.check("H2", "the three scales at the worked carriers: P^2 < Omega (the family), the giant-step bound 2(isqrt(P-1)+1) is far below Omega^(1/4), and P itself below sqrt(Omega)",
            ok, "; ".join(det))


# ---- C: the localising chart -----------------------------------------------------------------------------------------------
@R.block("C", BLOCK["C"])
def block_C():
    """Block C — chart_localization.py (EXACT): the recentring chart (26:E8), the index as counting distance and the
    bijection (26:E10), the ascent as the coordinate map and the descent as its inverse (26:F10)."""
    # C1: for every index m the translation recentre(n, m, ·) carries m to 1, one step from the origin 0, and permutes the indices
    ok, det = True, []
    for p in (13, 101, 1009):
        g, n = g_of(p), p - 1
        for m in range(n):
            ok &= pow(g, LD.recentre(n, m, m), p) == g == pow(g, 0, p) * g % p
            ok &= sorted(LD.recentre(n, m, j) for j in range(n)) == list(range(n))
        det.append(f"F_{p}: every m carried to 1 by a permutation of the {n} indices")
    # 26:E8 (p26025)
    R.check("C1", "existence is free: for every witness index m the translated chart recentre(n, m, .) places m one counting step from the origin and is a bijection of the index cycle",
            ok, "; ".join(det))

    # C2: the counting distance of a unit is its index 1..P-1 (the suite's convention: g^k reached after k steps, 1 = g^(P-1) after P-1); a bijection, mean P/2, maximum P-1 = 2^ell - O(1)
    ok, det = True, []
    for p in LOCAL:
        g, n = g_of(p), p - 1
        dist, x = {}, 1
        for k in range(1, p):
            x = x * g % p
            dist.setdefault(x, k)
        vals = sorted(dist.values())
        ell = (p - 1).bit_length()                        # ⌈log₂ P⌉ for P not a power of two
        mean = Fr(sum(vals), n)
        ok &= vals == list(range(1, p)) and mean == Fr(p, 2) and max(vals) == n and 2 ** (ell - 1) < n < 2 ** ell
        det.append(f"F_{p}: bijection onto 1..{n}, mean {mean}, max {n}, ell={ell} bits")
    # 26:E10 (p26027)
    R.check("C2", "the counting distance of a unit along the orbit of g is its index: a bijection onto 1..P-1 with mean P/2 and maximum P-1, between 2^(ell-1) and 2^ell for ell the bit length",
            ok, "; ".join(det))

    # C3: a_P(j) = g^j is the coordinate map C_{P-1} -> units and d_P = dlog its inverse, agreeing with the index table
    ok, det = True, []
    for p in (13, 17, 53, 101):
        g, n = g_of(p), p - 1
        table = LD.index_table(g, p)
        ok &= sorted(table) == list(range(1, p)) and sorted(table.values()) == list(range(n))
        ok &= all(pow(g, m, p) == x for x, m in table.items())                  # each index reproduces its unit
        ok &= all(LD.dlog(g, pow(g, j, p), p) == j for j in range(n)) and all(pow(g, LD.dlog(g, x, p), p) == x for x in range(1, p))
        ok &= all(LD.dlog(g, pow(g, j, p), p) == j % n for j in (n, n + 1, 2 * n + 3))   # an exponent beyond the cycle reads modulo n
        det.append(f"F_{p}: a_P a bijection, dlog its two-sided inverse on all {n} units")
    # 26:F10 (p26038)
    R.check("C3", "the ascent a_P: j -> g^j is a bijection of the index cycle onto the units and the descent dlog is its inverse on every unit",
            ok, "; ".join(det))


# ---- A: the ascent -----------------------------------------------------------------------------------------------------------
@R.block("A", BLOCK["A"])
def block_A():
    """Block A — closure_pnp.py T2 (EXACT): square-and-multiply within 2ℓ multiplications (26:F2)."""
    ok, det = True, []
    def exact_counts(e):                                 # the counts the two scans must report, from the bit pattern of e alone
        b, ones = len(bin(e)) - 2 if e > 0 else 0, bin(e).count("1")
        return b - 1 + ones if e > 0 else 0, b + ones  # right to left (the last squaring skipped), left to right
    for p in CARRIERS:
        g, ell = g_of(p), p.bit_length()
        for e in (p - 2, p // 3):
            v, ops = LD.sqmul(g, e, p); v2, ops2 = LD.sqmul_ltr(g, e, p)
            ok &= v == pow(g, e, p) == v2 and (ops, ops2) == exact_counts(e) and ops <= ops2 <= 2 * ell and ops2 <= 2 * LD.bits(e)
        det.append(f"P={p}: ell={ell}, {LD.sqmul(g, p - 2, p)[1]} and {LD.sqmul(g, p // 3, p)[1]} multiplications at e=P-2, P/3 (bound {2 * ell})")
    for e in range(1 << 10):
        v, ops = LD.sqmul(2, e, 13); v2, ops2 = LD.sqmul_ltr(2, e, 13)
        ok &= v == pow(2, e, 13) == v2 and (ops, ops2) == exact_counts(e) and ops2 <= 2 * LD.bits(e)
        ok &= (LD.bits(e) == 0 if e == 0 else (1 << (LD.bits(e) - 1)) <= e < (1 << LD.bits(e)))   # bits(e) against the powers of two
    # 26:F2 (p26030)
    R.check("A1", "square-and-multiply computes g^j in at most 2·bits(j) modular multiplications, hence at most 2·ell for j < P, the counts those of the bit pattern (right to left with the last squaring skipped: bits(j) - 1 + popcount(j); left to right: bits(j) + popcount(j)): on the three carriers at j = P-2 and P/3, and on F_13 for every j < 2^10",
            ok, "; ".join(det) + "; F_13: every e < 1024 within 2·bits(e)")


# ---- D: the descent ------------------------------------------------------------------------------------------------------
@R.block("D", BLOCK["D"])
def block_D():
    """Block D — closure_pnp.py T3 (EXACT): baby-step–giant-step recovers the index with the stated counts (26:F3); the
    bound (26:F3); the ratio table (26:G4)."""
    ok, det = True, []
    counts = {}
    for p in CARRIERS:
        g, n = g_of(p), p - 1
        m = isqrt(n) + 1
        x = p // 3
        res = LD.bsgs(g, pow(g, x, p), p)
        i, j, giant = res
        ok &= pow(g, j + m * i, p) == pow(g, x, p) and (j + m * i) % n == x and giant <= m and j < m
        counts[p] = (m, giant, m + giant)
        det.append(f"P={p}: m={m}, {giant} giant steps, {m + giant} operations")
    ok &= [counts[p][2] for p in CARRIERS] == [134, 1334, 13334]
    for p, m in ((13, 4), (101, 10)):
        g = g_of(p)
        ok &= all(LD.descends(g, pow(g, x, p), p, m) for x in range(p - 1))
        res = [LD.bsgs(g, pow(g, x, p), p, m) for x in range(p - 1)]
        ok &= all(r is not None and r[1] < m and (r[1] + m * r[0]) % (p - 1) == x for x, r in enumerate(res))   # the pair itself
    ok &= not LD.descends(g_of(101), pow(g_of(101), 50, 101), 101, 3) and LD.descends(g_of(101), pow(g_of(101), 8, 101), 101, 3)   # m = 3 reaches 8, not 50
    # 26:F3 (p26031)
    R.check("D1", "baby-step-giant-step with m = isqrt(P-1)+1 recovers the index of g^x: on the three carriers at x = P/3 with 134, 1334, 13334 operations (the m baby steps and the giant steps), and on F_13 (m = 4) and F_101 (m = 10) for every target",
            ok, "; ".join(det) + "; F_13 and F_101: every index recovered")

    # D2: the bound: m^2 >= P-1 and the operation count m + i <= 2m = 2(isqrt(P-1)+1); every target on F_101 within the bound
    ok, det = True, []
    for p in (101, 1009, 10007):
        g, n = g_of(p), p - 1
        m = isqrt(n) + 1
        worst = 0
        for x in range(n):
            i, j, giant = LD.bsgs(g, pow(g, x, p), p)
            ok &= giant < m and (j + m * i) % n == x
            worst = max(worst, m + giant)
        ok &= m * m >= n and worst <= 2 * m
        det.append(f"F_{p}: m={m}, worst {worst} <= {2 * m}")
    R.check("D2", "the descent's bound: with m^2 >= P-1 every index is recovered within 2m = 2(isqrt(P-1)+1) operations, checked on every target of F_101, F_1009 and F_10007",
            ok, "; ".join(det))

    # D3: the ratio table of prop:invlast, descent over ascent at the three carriers (the data the paper quotes)
    pairs = []
    for p in CARRIERS:
        g = g_of(p)
        asc = LD.sqmul(g, p // 3, p)[1]
        i, j, giant = LD.bsgs(g, pow(g, p // 3, p), p)
        pairs.append((isqrt(p - 1) + 1 + giant, asc))
    ratios = [Fr(d, a) for d, a in pairs]
    ok = pairs == [(134, 17), (1334, 26), (13334, 38)] and ratios[0] < ratios[1] < ratios[2] and ratios[-1] > 5 * ratios[0]
    R.check("D3", "the descent/ascent operation ratio at the three carriers: 134/17, 1334/26, 13334/38 (7.9, 51.3, 350.9), growing with the carrier",
            ok, "; ".join(f"P={p}: {d}/{a}" for p, (d, a) in zip(CARRIERS, pairs)))


# ---- R: the roles and the closure ----------------------------------------------------------------------------------------
@R.block("R", BLOCK["R"])
def block_R():
    """Block R — closure_pnp.py T1, T2, T4 (EXACT): the tower reduction (26:G2), the phase formula (26:G3), the four
    roles' counts (26:G4), the bijective and the compressive power maps (26:G5)."""
    # R1: a tower exponent is read modulo P-1; the index map m -> k m; the orbit of iterated exponentiation closes
    ok, det = True, []
    for p in (13, 101, 1009):
        g, n = g_of(p), p - 1
        ok &= all(LD.tower(g, j, p) == (pow(g, pow(g, j, p), p), pow(g, pow(g, j, p) % n, p)) for j in range(n))   # against pow
        ok &= all(pow(g, pow(g, j, p), p) == pow(g, pow(g, j, p) % n, p) for j in range(n))                         # the identity itself
        ok &= any(pow(g, j, p) >= n for j in range(n))                                                               # the reduction acts
        ok &= all(pow(pow(g, m, p), k, p) == pow(g, k * m % n, p) for m in range(n) for k in (2, 3, 5, 7))
        seen, j, steps = {}, 1, 0                      # j ↦ (g^j mod P) mod n, the tower read as an index
        while j not in seen:
            seen[j] = steps; j = pow(g, j, p) % n; steps += 1
        ok &= steps <= n
        det.append(f"F_{p}: g^(g^j) = g^((g^j) mod {n}) for every j; the iterated exponential closes in {steps} steps")
    # 26:G2 (p26041)
    R.check("R1", "the two cyclic structures host every iterate: a tower exponent g^j is read modulo P-1, the power map acts on the index as multiplication, and iterated exponentiation closes on the index cycle",
            ok, "; ".join(det))

    # R2: the phase formula P_k^r(g^m) = g^(k^r m mod n) over the suite's cases, and the ascent step
    ok, cases = True, 0
    for p in (13, 101, 1009, 10009):
        g, n = g_of(p), p - 1
        for k in (2, 3, 5, 7, 10):
            for m in (1, 2, 7):
                for r in (0, 1, 2, 3, 5):
                    it, closed = LD.phase_index(g, m, k, r, p)
                    x = pow(g, m, p)
                    for _ in range(r): x = pow(x, k, p)                                     # the iterate, by pow
                    ok &= it == x == closed == pow(g, pow(k, r, n) * m % n, p); cases += 1  # the closed form, by pow
        ok &= all(pow(g, j + 1, p) == g * pow(g, j, p) % p for j in range(n))
    # 26:G3 (p26042)
    R.check("R2", "the phase-index dynamics: P_k^r(g^m) = g^(k^r m mod (P-1)) over the suite's cases on four carriers, and the ascent g^(j+1) = g · g^j is one multiplication per counted step",
            ok, f"{cases} (P, k, m, r) cases, no violation")

    # R3: the four roles' counts on an instrumented residue (every modular operation counted): succession one, addition one,
    # multiplication one, the ascent at most 2ℓ; the descent recovers the index (the G18 round of 16 October 2026: the first
    # version compared the three lower roles' results with themselves)
    class Counted:
        """A residue mod p whose operations are counted: one per +, one per *, as the cost model (26:G1) reads them."""
        ops = 0
        def __init__(self, v, p): self.v, self.p = v % p, p
        def __add__(self, o): Counted.ops += 1; return Counted(self.v + (o.v if isinstance(o, Counted) else o), self.p)
        def __mul__(self, o): Counted.ops += 1; return Counted(self.v * (o.v if isinstance(o, Counted) else o), self.p)
        def __pow__(self, e):                                            # square-and-multiply, right to left, through * only
            r, b = Counted(1, self.p), self
            while e > 0:
                if e % 2 == 1: r = r * b
                e //= 2
                if e > 0: b = b * b
            return r
    ok, det = True, []
    for p in CARRIERS:
        g, n, ell = g_of(p), p - 1, p.bit_length()
        a = Counted(p // 7, p)
        Counted.ops = 0; succ = a + 1; c1 = Counted.ops                     # succession: one operation
        Counted.ops = 0; add = a + a; c2 = Counted.ops                      # addition: one
        Counted.ops = 0; mult = a * a; c3 = Counted.ops                     # multiplication: one
        Counted.ops = 0; asc = Counted(g, p) ** (p // 3); c4 = Counted.ops  # the ascent: at most 2ℓ
        ok &= (succ.v, add.v, mult.v, asc.v) == ((p // 7 + 1) % p, 2 * (p // 7) % p, (p // 7) ** 2 % p, pow(g, p // 3, p))
        ok &= (c1, c2, c3) == (1, 1, 1) and c4 <= 2 * ell and c4 == LD.sqmul(g, p // 3, p)[1]
        i, j, giant = LD.bsgs(g, asc.v, p)
        ok &= (j + (isqrt(n) + 1) * i) % n == p // 3 and giant <= isqrt(n) + 1
        det.append(f"P={p}: succession, addition, multiplication {c1}, {c2}, {c3} operations; ascent {c4} <= {2 * ell}; descent {isqrt(n) + 1 + giant} operations")
    # 26:G4 (p26043)
    R.check("R3", "on a residue whose modular operations are counted, succession, addition and multiplication cost one operation each and the ascent at most 2·ell multiplications; the descent recovers the index from its orbit position, the inverse of the last role",
            ok, "; ".join(det))

    # R4: P_k is a bijection iff k is invertible mod P-1 (inverse exponent a with a k ≡ 1), else the image is the cycle of g^d, d = gcd(k, P-1), of size (P-1)/d
    ok, det = True, []
    for p, ks in ((1009, (2, 3, 4, 6, 7)), (10009, (2, 3, 4, 6, 7)), (13, tuple(range(1, 12)))):
        g, n = g_of(p), p - 1
        for k in ks:
            d, a, b = LD.bezout(k, n)
            ok &= a * k == b * n + d
            img = LD.power_image(k, p)
            ok &= len(img) == n // d
            if d == 1:
                ok &= all(pow(pow(x, k, p), a, p) == x for x in range(1, p))
            else:
                gd = pow(g, d, p)
                cyc = sorted(pow(gd, i, p) for i in range(n // d))
                ok &= img == cyc and all(pow(pow(pow(g, a, p), i, p), k, p) == pow(gd, i, p) for i in range(n // d))
        det.append(f"F_{p}: k in {ks}: image sizes {[n // LD.bezout(k, n)[0] for k in ks]}")
    # 26:G5 (p26044)
    R.check("R4", "the power map x -> x^k is a bijection of the units iff k is invertible modulo P-1, inverted by the exponent a with a k = 1; otherwise its image is the cycle of g^d, d = gcd(k, P-1), of (P-1)/d elements",
            ok, "; ".join(det))

    # R5: the index-cycle orbit r -> k^r m mod n is eventually periodic (the suite's T4 period)
    ok, det = True, []
    for p in (1009, 10009):
        n = p - 1
        for k in (2, 3, 4, 6, 7):
            seen, m, r = {}, 1, 0
            while m not in seen:
                seen[m] = r; m = k * m % n; r += 1
            period = r - seen[m]
            ok &= 1 <= period <= n
            det.append(f"F_{p}, k={k}: period {period}")
    R.check("R5", "the iteration of the power map on the index, r -> k^r m mod (P-1), is eventually periodic: a finite counted orbit, not a new primitive role",
            ok, "; ".join(det))


# ---- T: the transforms ---------------------------------------------------------------------------------------------------
@R.block("T", BLOCK["T"])
def block_T():
    """Block T — transform_charts.py replaced by exact counts (EXACT): the additive transform's exact round trip on the
    shell and the convolution counts in the two charts (26:H3)."""
    ok, det = True, []
    for q, n, w in ((17, 8, 2), (257, 256, 3), (65537, 1 << 16, 3)):
        ok &= pow(w, n, q) == 1 and pow(w, n // 2, q) == q - 1
        a = [(i * i + 1) % q for i in range(n)]
        A, o1 = LD.ntt(a, w, q)
        b, o2 = LD.intt(A, w, q)
        ok &= b == a and o1 == o2 == (n // 2) * n.bit_length() - n // 2
        if n <= 256:                                                      # the transform is the matrix w^(jk): n^2 products
            ok &= A == [sum(a[j] * pow(w, j * k, q) for j in range(n)) % q for k in range(n)]
        else:                                                             # at n = 2^16: eight rows of the matrix
            ok &= all(A[k] == sum(a[j] * pow(w, j * k, q) for j in range(n)) % q for k in (0, 1, 2, 3, 255, 4096, 32768, n - 1))
        det.append(f"F_{q}, n={n}: T^-1 T a = a exactly, {o1} butterflies, T the matrix w^(jk)")
    # 26:H3 (p26048)
    R.check("T1", "the additive transform on the shell is exact and self-inverse: the number-theoretic transform over F_17 (n = 8), F_257 (n = 256) and F_65537 (n = 2^16) returns the vector exactly, with (n/2) log2 n butterflies",
            ok, "; ".join(det))

    # T2: the convolution counts at n = 2^10 over F_65537: n^2 in the position chart against three transforms and n products
    q, n = 65537, 1 << 10
    w = pow(3, (q - 1) // n, q)
    a = [(i * i + 1) % q for i in range(n)]; b = [(3 * i + 7) % q for i in range(n)]
    c1, o1 = LD.convolve_direct(a, b, q)
    c2, o2 = LD.convolve_ntt(a, b, w, q)
    ok = c1 == c2 and o1 == n * n == 1048576 and o2 == 3 * ((n // 2) * 10) + n == 16384
    R.check("T2", "the same convolution target far in the position chart and near through the transform: n^2 = 1,048,576 multiplications against 16,384 at n = 2^10 over F_65537, the two results equal",
            ok, f"direct {o1}, through the transform {o2}")

    # T3: the transform is the shell DFT of the frame (p, g): over F_17 with the full cycle n = 16 and w = g = 3, ntt agrees with the matrix g^(jk)
    p, g = 17, 3
    n = p - 1
    a = [(i * i + 1) % p for i in range(n)]
    A, _ = LD.ntt(a, g, p)
    W = [[pow(g, j * k, p) for j in range(n)] for k in range(n)]
    B = [sum(W[k][j] * a[j] for j in range(n)) % p for k in range(n)]
    ok = A == B
    R.check("T3", "the number-theoretic transform on the full cycle of F_17 (w = g = 3) is the shell's Fourier matrix g^(jk) applied to the vector", ok)


# ---- W: the affine witness sets -------------------------------------------------------------------------------------------
@R.block("W", BLOCK["W"])
def block_W():
    """Block W (EXACT): Gaussian elimination over F_2 returns a particular solution and a basis; every solution is
    reached from the recentred origin within the basis's size (26:H4)."""
    systems = [
        ([[1, 0, 1, 1, 1], [0, 1, 1, 0, 0], [1, 1, 0, 1, 1]], 4),
        ([[1, 1, 0, 0, 1, 0, 0], [0, 1, 1, 0, 0, 1, 1], [1, 0, 1, 1, 0, 0, 0], [0, 0, 0, 1, 1, 1, 1]], 6),
        ([[1, 1, 0, 0, 1, 0, 1], [0, 1, 1, 0, 0, 1, 0], [1, 0, 1, 1, 0, 0, 1], [0, 0, 0, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 0]], 6),
        ([[(i * j + i + j) % 2 for j in range(8)] + [i % 2] for i in range(5)], 8),
    ]
    ok, det = True, []
    for rows, ncols in systems:
        res = LD.affine_f2(rows, ncols)
        sols = LD.f2_solutions(rows, ncols)
        if res is None:
            ok &= sols == []; det.append(f"{len(rows)}x{ncols}: inconsistent, no solution"); continue
        w0, basis = res
        ok &= w0 in sols and len(sols) == 2 ** len(basis) and len(basis) <= ncols
        span, steps = set(), {}
        for mask in range(1 << len(basis)):
            v = list(w0)
            for t, bvec in enumerate(basis):
                if mask >> t & 1: v = [x ^ y for x, y in zip(v, bvec)]
            span.add(tuple(v)); steps[tuple(v)] = min(steps.get(tuple(v), ncols + 1), bin(mask).count("1"))
        ok &= span == {tuple(s) for s in sols}
        # the recentred chart w ↦ w - w0: the origin (mask 0) is a witness, every solution within len(basis) basis steps
        ok &= steps.get(tuple(w0)) == 0 and all(steps.get(tuple(s), ncols + 1) <= len(basis) for s in sols) and max(steps.values()) == len(basis)
        det.append(f"{len(rows)}x{ncols}: {len(sols)} solutions = w0 + span of {len(basis)}")
    # 26:H4 (p26049)
    R.check("W1", "an affine F_2-system's solutions are w0 + the span of a basis returned by Gaussian elimination; in the recentred chart the origin is a witness and every solution lies within the basis's size in counting steps",
            ok, "; ".join(det))


if __name__ == "__main__":
    want = [a.upper() for a in sys.argv[1:]] or sorted(BLOCK)
    bad = [b for b in want if b not in BLOCK]
    if bad: sys.exit(f"no block {', '.join(bad)}: the blocks are {', '.join(sorted(BLOCK))}")
    for b in want: R.run_block(b)
    sys.exit(0 if R.summary(write=False, expect=list(LEDGER) if want == sorted(BLOCK) else None) else 1)
