#!/usr/bin/env python3
"""
How It Could Work -- Model Package v1
Mechanical Relativity Program (Step 1), consolidated.
solvetheuniverse.com / Pressure-Based Theory, 2026-09-11
Revision 2, same day, after one independent adversarial review (14 findings,
all folded in; the review record is in the vault's Theory Working Notes).

WHAT THIS IS. One re-runnable package that puts the already-validated
Step 1 clock model on the table: parameters at the top, eight benchmark
rows with PASS / FAIL / UNVERIFIED verdicts, each row saying what it does
and does not test, and a caveats block that prints every run. It
consolidates math that already exists in six dated scripts in this folder
(2026-09-03 and 2026-09-04); it adds no new physics. Function bodies are
reused from those scripts (an independent line-by-line comparison found no
math change); the originals remain the derivation record.

THE MODEL, IN FOUR POSTULATES (universal time and space, a real medium):
  P1  Time is absolute. One background time t, the same everywhere.
  P2  Signals propagate at one fixed speed c_eff, isotropic in the rest
      frame of a real physical medium. Not a relativistic invariant.
  P3  A "clock" is a standing wave: a signal bouncing between reflectors
      (a rod) or circulating a closed loop (a ring, the atomic case). One
      round trip is one tick, counted in absolute time.
  P4  Matter moving through the medium at v has its length along the
      motion multiplied by 1/gamma(v/c_eff); lengths across the motion are
      unchanged. The 1/gamma factor was SOLVED FOR from the Michelson-Morley
      null (2026-09-03, third calculation). The perpendicular-unchanged part
      is an ASSUMPTION: the MM null alone leaves a one-parameter family
      (validated fact 2), and exactly one row below (row 3) tests it.
  plus, for gravity only:
  G1  A standing wave of energy E=h*f behaves as a mass h*f/c^2 and does
      work climbing PBT's own already-holds Newtonian potential
      Phi(r) = -GM/r, with energy conserved in absolute time.

POSTULATED vs DERIVED vs IMPORTED vs PINNED -- stated before anything runs:
  Postulated:  P1, P2, P3, P4's perpendicular-unchanged assumption, G1's
               energy-conservation step.
  Derived:     P4's 1/gamma factor (from the MM null); the clock rate
               T(v)/T(0)=gamma for rod and ring at every orientation;
               the Einstein-synchronization offset -v*L0/c_eff^2.
  Read off:    the Mansouri-Sexl clock coefficient alpha = -1/2 is the
               beta^2 coefficient of 1/gamma. It follows from the rate law
               above; it is not new and is not claimed as a derivation.
  Imported:    E = h*f and E = m*c^2 (G1). E=mc^2 is historically a
               relativistic result. Flagged, not hidden. Row 8 also imports
               v <= c for storage-ring muons (a kinematic regularity, not
               the p=gamma*m*v relation this program has not derived).
  Pinned:      c_eff = c. NOT derived. Pinned by measured dilation: SAP's
               own characteristic speed (~2.5e5 c) underpredicts real
               dilation by ~11 orders (Ruled Out, 2026-09-03). "Plausible
               parameter" here means "the one value the data allows."
  Convention:  the medium rest frame is taken as Earth-centered and
               non-rotating (ECI) for every terrestrial/orbital benchmark.
               Under Einstein synchronization the medium's frame is
               unobservable in this model (validated fact 12), which is
               the real reason any choice is safe; ECI is used because the
               ground clock then carries Earth's rotation, which row 5
               needs at the 0.1 % level and row 6 needs for its sign.
               Where the medium is actually at rest is open (checklist
               Open Question 1). Stellar aberration neither supports nor
               excludes this choice.

WHAT THE MODEL IS, NAMED PLAINLY. P1-P4 are Lorentz Ether Theory (Lorentz
1892/1904, Larmor 1897, Poincare 1905; Bell 1976), formalized as the
Robertson (1949) / Mansouri-Sexl (1977) test theory, empirically equivalent
to Special Relativity under Einstein synchronization. G1 is NOT part of
LET; it is Einstein's 1911 pre-GR heuristic on PBT's own potential.

WHAT EACH ROW IS, so nothing is counted as evidence twice:
  Rows that test the model against a MEASUREMENT and can fail:
     3  Ives-Stilwell (Botermann 2014) -- the only test of P4's
        perpendicular-unchanged assumption
     4  Pound-Rebka 1960 / Pound-Snider 1965
     5  GPS net rate (Ashby 2003)
  Rows that are MODEL-INTERNAL consistency, not evidence:
     1  rod clock rate = gamma (follows from P2+P4 by construction)
     2  ring clock has no orientation dependence (the same identity that
        makes MM null; the checklist's twelfth calculation ruled it out as
        a test of the medium)
     7  one-way isotropy under Einstein sync (validated fact 12)
  Rows that are a SIGN or a CONSTRAINT, not a reproduced number:
     6  Hafele-Keating sign (frame-composition arithmetic; any theory with
        SR/GR-form terms gives it)
     8  weak-force carrier bound from muon lifetimes (satisfied, one-sided)

FALSIFICATION CONDITION, STATED BEFORE RUNNING:
  (a) DETERMINATE, holds   -- rows 3, 4, 5 pass their sourced values within
                              tolerances set from the sources' own error
                              bars, at the default parameters.
  (b) DETERMINATE, fails   -- any of rows 3, 4, 5 misses at the defaults.
  (c) UNVERIFIED           -- a constant not checked against a primary or a
                              secondary source in this package's own
                              preparation prints UNVERIFIED, never PASS.
  Perturbation switches, to prove (b) is reachable (CLAUDE.md item 17).
  Marginal thresholds were computed from the shipped functions first, then
  each perturbation was chosen beyond them; the numbers are stated so
  nobody has to trust the word "comfortably":
    --perp-scale 1e-6   multiplies the perpendicular length by
                        (1 + eps*beta^2). Rows 1 and 3 MUST fail (row 3's
                        marginal eps is 2.0e-8 against Botermann's 2.3e-9).
    --no-contraction    sets P4's parallel factor to 1. Rows 2 and 7 MUST
                        fail: orientation dependence appears in the ring,
                        and both one-way speeds come out c_eff/gamma with
                        the sync offset departing from -v*L0/c_eff^2 (they
                        stay isotropic; the value is wrong).
    --phi-scale 1.25    multiplies Phi by 1.25. Rows 4 and 5 MUST fail
                        (row 4's marginal scale is 1.0228 on Pound-Snider's
                        3 sigma; row 5's is ~1.001 on a 0.1 % band).
    --frame ecef        puts the ground clock at rest in the medium. Rows 5
                        and 6 MUST fail: GPS drifts 0.3 % and the eastward
                        Hafele-Keating sign flips.
    --selftest          runs every configuration and asserts exactly those
                        outcomes, calling the shipped functions (a
                        regression fixture, not a pasted copy of the logic).

Run:  python3 "<this file>"            (needs numpy, scipy)
      python3 "<this file>" --selftest
"""

import argparse
import sys

import numpy as np
from scipy.integrate import quad

# =========================================================================
# PARAMETERS -- the "plausible and repeatable parameters"
# =========================================================================

PARAMS = dict(
    c_eff=299792458.0,          # m/s. PINNED to c by measured dilation, not derived.
    contraction_parallel=True,  # P4. Solved for from the MM null. --no-contraction sets False.
    perp_scale=0.0,             # P4's perpendicular assumption: factor (1 + perp_scale*beta^2). 0 = unchanged.
    phi_scale=1.0,              # G1. Multiplies Phi(r). --phi-scale X perturbs.
    frame="eci",                # medium rest frame convention: "eci" (ground clock carries omega*R) or "ecef"
    # Real constants (WGS84 / IERS), used as inputs, not fitted:
    GM_earth=3.986004418e14,    # m^3/s^2, WGS84
    R_earth=6378137.0,          # m, WGS84 equatorial radius
    J2_earth=1.08263e-3,        # WGS84 oblateness; applied to the ground clock's potential only
    omega_earth=7.2921159e-5,   # rad/s, sidereal rotation rate
    a_gps=26561.75e3,           # m, GPS semi-major axis (Ashby 2003)
    h_pound_rebka=22.5,         # m, Jefferson tower height (Pound-Rebka 1960; Pound-Snider used 22.86 m, ratio unaffected at 1e-7)
)

SEC_PER_DAY = 86400.0

# =========================================================================
# SOURCES -- every benchmark constant, with HOW it was checked:
#   primary      the paper's own text was read (abstract or full text)
#   secondary    a source quoting the paper (Wikipedia, ADS abstract via a
#                search snippet) because the primary was paywalled/blocked
#   vault-record the value is on record in this vault with its primary
#                cited there; not re-fetched for this package
# A row whose constant is "secondary" or "vault-record" prints that in its
# verdict. A constant with none of the three prints UNVERIFIED, never PASS.
# =========================================================================

SOURCES = {
    "botermann2014": dict(
        cite="Botermann et al., Phys. Rev. Lett. 113, 120405 (2014); arXiv:1409.7951",
        value=2.3e-9, unit="fractional",
        what="the SR relation between gamma and beta verified to within 2.3 ppb at beta=0.338 (7Li+, ESR Darmstadt); used here as the tolerance on gamma, not as the paper's Mansouri-Sexl headline bound",
        verified_by="primary", how="arXiv abstract fetched 2026-09-11"),
    "pound_rebka1960": dict(
        cite="Pound & Rebka, Phys. Rev. Lett. 4, 337 (1960)",
        ratio=1.05, sigma=0.10, unit="ratio to prediction",
        what="two-way shift difference (5.13+/-0.51)e-15 vs predicted 4.92e-15 over 22.5 m; the often-quoted 2.56e-15 is half of that, rounded",
        verified_by="secondary", how="Wikipedia quoting the paper, 2026-09-11 (APS fetch blocked)"),
    "pound_snider1965": dict(
        cite="Pound & Snider, Phys. Rev. 140, B788 (1965)",
        ratio=0.9990, sigma=0.0076, unit="ratio to prediction",
        what="improved gamma-ray gravitational shift, 75-ft path",
        verified_by="secondary", how="ADS abstract text via search snippet, 2026-09-11 (APS/ADS direct fetch blocked)"),
    "ashby2003": dict(
        cite="Ashby, Living Rev. Relativity 6, 1 (2003), 'Relativistic Effects on Satellite Clocks', Eq. 35",
        value=4.4647e-10 * SEC_PER_DAY * 1e6, unit="microseconds/day net (from the fractional offset -4.4647e-10)",
        what="net rate offset applied to GPS satellite clocks (proper frequency set to 10.22999999543 MHz)",
        verified_by="primary", how="full text via the PMC mirror, 2026-09-11; us/day converted here (x 86400 s)"),
    "bailey1977": dict(
        cite="Bailey et al., Nature 268, 301 (1977), CERN Muon Storage Ring",
        tau_lab_us=64.419, tau_rest_us=2.19703, unit="microseconds",
        what="lab-frame and rest-frame muon lifetimes, two direct measurements",
        verified_by="vault-record", how="2026-09-03/04 scripts in this folder cite the primary"),
    "hafele_keating1972": dict(
        cite="Hafele & Keating, Science 177, 166 & 168 (1972)",
        east_pred_ns=-40.0, east_meas_ns=-59.0, west_pred_ns=275.0, west_meas_ns=273.0,
        east_hr=41.2, east_alt_m=8900.0, west_hr=48.6, west_alt_m=9400.0,
        what="flying atomic clocks, eastward and westward",
        verified_by="vault-record", how="checklist Benchmarks section cites the primary"),
    "nagel2015": dict(
        cite="Nagel et al., Nature Communications 6, 8174 (2015)",
        value=9.2e-19, sigma=10.7e-19, unit="fractional anisotropy",
        what="modern Michelson-Morley cavity bound (caveats only; no row)",
        verified_by="vault-record", how="checklist Open Question 1 cites the primary"),
}


def src_tag(*keys):
    kinds = {SOURCES[k]["verified_by"] for k in keys}
    if None in kinds or not kinds:
        return None
    if kinds == {"primary"}:
        return ""
    return " (" + ", ".join(sorted(k for k in kinds if k != "primary")) + " source)"


# =========================================================================
# MODEL FUNCTIONS -- reused from the dated derivation scripts, credited.
# =========================================================================

def gamma_of(v, c_eff):
    return 1.0 / np.sqrt(1.0 - (v / c_eff) ** 2)


def rod_round_trip(L0, theta, v, c_eff, contract_parallel, perp_scale=0.0):
    """From 2026-09-03 'Standing-Wave Clock Tick Rate.py'
    (general_angle_round_trip), plus the perp_scale knob. Rod of rest
    length L0 at angle theta to v (0 = parallel, pi/2 = perpendicular),
    moving at v along +x through a medium in which the signal moves at
    c_eff. Round-trip time from the two intercept quadratics; no
    approximation."""
    beta = v / c_eff
    g = gamma_of(v, c_eff)
    ell_par = L0 * np.cos(theta)
    ell_perp = L0 * np.sin(theta) * (1.0 + perp_scale * beta ** 2)
    if contract_parallel:
        ell_par = ell_par / g
    Lsq = ell_par ** 2 + ell_perp ** 2
    a1 = c_eff ** 2 - v ** 2
    b1 = -2 * ell_par * v
    c1 = -Lsq
    t1 = (-b1 + np.sqrt(b1 ** 2 - 4 * a1 * c1)) / (2 * a1)
    Ax0, Ay0 = v * t1, 0.0
    Sx0, Sy0 = ell_par + v * t1, ell_perp
    dx0, dy0 = Ax0 - Sx0, Ay0 - Sy0
    a2 = c_eff ** 2 - v ** 2
    b2 = -2 * dx0 * v
    c2 = -(dx0 ** 2 + dy0 ** 2)
    t2 = (-b2 + np.sqrt(b2 ** 2 - 4 * a2 * c2)) / (2 * a2)
    return t1 + t2


def ring3d_lap_time(v, c_eff, r0, theta, contract_parallel, direction="ccw"):
    """From 2026-09-04 'Atomic Frequency Shift General 3D Orientation.py'.
    Ring of rest radius r0, plane tilted so its normal makes angle theta
    with the velocity axis, translating at v along x. Only the x-projection
    is contracted (P4). Lap time by quadrature of 1/|dphi/dt|. With
    contraction on, the integrand is r0*(1+beta*sin(theta)*sin(phi)) /
    (c*sqrt(1-beta^2)), a trig polynomial on which Gauss-Kronrod is exact,
    so a residual of exactly 0.0 is expected, not a self-comparison."""
    g = gamma_of(v, c_eff)
    k = r0 * np.sin(theta) / (g if contract_parallel else 1.0)

    def integrand(phi):
        A = (k ** 2 * np.sin(phi) ** 2 + r0 ** 2 * np.cos(phi) ** 2
             + r0 ** 2 * np.cos(theta) ** 2 * np.sin(phi) ** 2)
        B = 2 * v * k * np.sin(phi)
        C = v ** 2 - c_eff ** 2
        sq = np.sqrt(B ** 2 - 4 * A * C)
        rate = (-B + sq) / (2 * A) if direction == "ccw" else (-B - sq) / (2 * A)
        return 1.0 / abs(rate)

    val, _ = quad(integrand, 0.0, 2 * np.pi, limit=200)
    return val


def phi_newton(r, GM, phi_scale=1.0):
    """PBT's own Newtonian-equivalent potential ('exact by construction'
    caveat carried). phi_scale is the perturbation knob only."""
    return -phi_scale * GM / r


def gravity_shift(r1, r2, GM, c_eff, phi_scale=1.0, extra1=0.0, extra2=0.0):
    """G1: Delta f / f = Delta Phi / c^2, from 2026-09-04 'Atomic Frequency
    Shift (Velocity + Gravity Terms).py'. extra1/extra2 add any further
    potential terms at the two positions (used for the ground clock's J2
    term in row 5)."""
    return ((phi_newton(r2, GM, phi_scale) + extra2) - (phi_newton(r1, GM, phi_scale) + extra1)) / c_eff ** 2


def one_way_measurement(v, c_eff, L0, contract_parallel):
    """From 2026-09-04 'One-Way Simultaneity.py' (verify_isotropy_numeric),
    contraction made switchable. Real absolute-time one-way times across
    the rod, the Einstein/radar synchronization PROCEDURE, and the one-way
    speeds the moving observer then measures."""
    g = gamma_of(v, c_eff)
    L = L0 / g if contract_parallel else L0
    dt_fwd = L / (c_eff - v)
    dt_bwd = L / (c_eff + v)
    round_trip = dt_fwd + dt_bwd
    tau_B_natural_fwd = dt_fwd / g
    tau_B_required = (round_trip / g) / 2.0
    offset = tau_B_required - tau_B_natural_fwd
    measured_speed_fwd = L0 / (tau_B_natural_fwd + offset)
    tau_A_arrival = dt_bwd / g
    measured_speed_bwd = L0 / (tau_A_arrival - offset)
    return dict(offset=offset, fwd=measured_speed_fwd, bwd=measured_speed_bwd,
                lorentz_offset=-v * L0 / c_eff ** 2)


def muon_carrier_bound(tau_lab_us, tau_rest_us):
    """From 2026-09-04 'Force-Type Generality.py' Part A. Momentum-free:
    D = tau_lab/tau_rest, solve D = gamma(v/c_B), then v <= c bounds c_B."""
    D = tau_lab_us / tau_rest_us
    v_over_cB = np.sqrt(1 - 1 / D ** 2)
    return D, v_over_cB, 1.0 / v_over_cB


def mansouri_sexl_alpha(c_eff, contract_parallel, perp_scale=0.0):
    """Reads off the Mansouri-Sexl clock coefficient alpha (rate = 1 +
    alpha*beta^2 + ...) from the model's perpendicular rod clock by
    Richardson extrapolation. With the perpendicular length unchanged this
    is the beta^2 coefficient of 1/gamma, i.e. -1/2 by construction; with
    perp_scale = eps it becomes -1/2 - eps. Not a derivation."""
    L0 = 1.0
    out = []
    for beta in (1e-3, 5e-4):
        v = beta * c_eff
        T0 = 2 * L0 / c_eff
        T = rod_round_trip(L0, np.pi / 2, v, c_eff, contract_parallel, perp_scale)
        out.append((T0 / T - 1.0) / beta ** 2)
    return (4 * out[1] - out[0]) / 3.0


# =========================================================================
# BENCHMARK ROWS
# =========================================================================

class Row:
    def __init__(self, n, name, real, model, tol, verdict, source, note=""):
        self.n, self.name, self.real, self.model = n, name, real, model
        self.tol, self.verdict, self.source, self.note = tol, verdict, source, note

    @property
    def failed(self):
        return self.verdict.startswith("FAIL")


def verdict(ok, tag, label=""):
    """tag: '' for primary sources, ' (secondary source)' etc., or None for
    unverified. label: extra qualifier for PASS."""
    if tag is None:
        return "UNVERIFIED"
    if not ok:
        return "FAIL" + tag
    return "PASS" + (f" ({label})" if label else "") + tag


def run_rows(P):
    c, cp, eps, ps = P["c_eff"], P["contraction_parallel"], P["perp_scale"], P["phi_scale"]
    R, GM, om = P["R_earth"], P["GM_earth"], P["omega_earth"]
    eci = (P["frame"] == "eci")
    rows = []

    # --- Row 1: rod clock rate vs gamma (model-internal) ------------------
    worst = max(abs(rod_round_trip(1.0, np.pi / 2, b * c, c, cp, eps) / (2.0 / c) / gamma_of(b * c, c) - 1)
                for b in (0.001, 0.1, 0.3, 0.5, 0.7, 0.9, 0.99, 0.999))
    rows.append(Row(1, "Rod clock T(v)/T(0) vs gamma, 8 speeds to 0.999c",
                    "gamma(v/c_eff)", f"max dev {worst:.1e}", "1e-9",
                    verdict(worst < 1e-9, "", "follows from P2+P4 by construction; not evidence"),
                    "2026-09-03 Standing-Wave Clock Tick Rate.py",
                    "Perpendicular rod. Fails only if the perpendicular assumption is perturbed (--perp-scale)."))

    # --- Row 2: ring clock, orientation independence (model-internal) ------
    b = 0.6
    v = b * c
    r0 = 1.0
    T0 = 2 * np.pi * r0 / c
    g = gamma_of(v, c)
    res = {th: abs(ring3d_lap_time(v, c, r0, np.deg2rad(th), cp) / (g * T0) - 1) for th in (0, 20, 45, 70, 90)}
    resid = max(res.values())
    rows.append(Row(2, "Ring (atomic) clock: T(v)/T(0)=gamma at every tilt, beta=0.6",
                    "0 residual (the identity that makes MM null)",
                    "per tilt " + ", ".join(f"{th}:{r:.1e}" for th, r in res.items()),
                    "1e-8",
                    verdict(resid < 1e-8, "", "model-internal consistency; not evidence"),
                    "2026-09-04 Atomic Frequency Shift General 3D Orientation.py",
                    "0.0 expected: quadrature is exact for this integrand. Fails under --no-contraction (orientation dependence of order 0.1 appears)."))

    # --- Row 3: Ives-Stilwell (Botermann 2014): the perpendicular test ------
    s = SOURCES["botermann2014"]
    beta_li = 0.338
    v = beta_li * c
    dev = abs(rod_round_trip(1.0, np.pi / 2, v, c, cp, eps) / (2.0 / c) / gamma_of(v, c) - 1)
    alpha = mansouri_sexl_alpha(c, cp, eps)
    rows.append(Row(3, "Ives-Stilwell: gamma(beta) at beta=0.338; Mansouri-Sexl alpha",
                    f"SR gamma(beta) to {s['value']:.1e}; alpha=-1/2",
                    f"dev {dev:.1e}; alpha={alpha:+.7f}", f"{s['value']:.1e}",
                    verdict(dev < s["value"], src_tag("botermann2014"), "the one row testing P4's perpendicular assumption"),
                    s["cite"],
                    "MM fixes only beta_MS - delta = 1/2; this row bounds delta (perpendicular change) at ~2e-8. Identity in c_eff, NOT in P4. Fails at --perp-scale 1e-6."))

    # --- Row 4: Pound-Rebka / Pound-Snider --------------------------------
    pr, psn = SOURCES["pound_rebka1960"], SOURCES["pound_snider1965"]
    h = P["h_pound_rebka"]
    model_pr = gravity_shift(R, R + h, GM, c, ps)
    ratio = model_pr / ((GM / R ** 2) * h / c ** 2)
    ok_pr = abs(ratio - pr["ratio"]) <= 1.0 * pr["sigma"]
    tol_sn = 3 * psn["sigma"]
    ok_sn = abs(ratio - psn["ratio"]) <= tol_sn
    rows.append(Row(4, "Pound-Rebka 1960 and Pound-Snider 1965: ratio of shift to prediction",
                    f"{pr['ratio']}+/-{pr['sigma']} (PR, 1 sigma); {psn['ratio']}+/-{psn['sigma']} (PS, 3 sigma = {tol_sn:.4f})",
                    f"{model_pr:.3e} over {h} m; ratio {ratio:.4f}", "1 sigma PR and 3 sigma PS",
                    verdict(ok_pr and ok_sn, src_tag("pound_rebka1960", "pound_snider1965")),
                    f"{pr['cite']}; {psn['cite']}",
                    "Marginal phi_scale: 1.0228 (PS), 1.15 (PR). Fails at --phi-scale 1.25."))

    # --- Row 5: GPS net (net only; the split is Retracted #5) ----------------
    a = SOURCES["ashby2003"]
    a_gps = P["a_gps"]
    v_sat = np.sqrt(GM / a_gps)                       # circular orbit speed in ECI
    v_ground = om * R if eci else 0.0                 # ground clock's ECI speed (equator)
    j2_ground = -GM * P["J2_earth"] / (2 * R)         # oblateness term at the equator, ground only
    grav = gravity_shift(R, a_gps, GM, c, ps, extra1=j2_ground) * SEC_PER_DAY * 1e6
    vel = ((1.0 / gamma_of(v_sat, c)) - (1.0 / gamma_of(v_ground, c))) * SEC_PER_DAY * 1e6
    net = grav + vel
    tol_gps = 0.001
    ok = abs(net - a["value"]) / abs(a["value"]) <= tol_gps
    rows.append(Row(5, "GPS satellite clock, net rate vs an equatorial ground clock",
                    f"{a['value']:+.3f} us/day", f"{net:+.3f} us/day (model split {vel:+.3f} / {grav:+.3f}, printed, never compared)",
                    "0.1 %",
                    verdict(ok, src_tag("ashby2003")),
                    a["cite"],
                    "NET ONLY (Retracted #5). Both clocks in ECI: ground carries omega*R (+0.104 us/day) and sits in the J2 geoid term (+0.033). Fails at --phi-scale 1.25 and under --frame ecef."))

    # --- Row 6: Hafele-Keating sign check (frame composition) --------------
    hk = SOURCES["hafele_keating1972"]
    v_air = 271.0  # m/s, representative 707 cruise -- illustrative, not the logged speed
    out = {}
    for name, sgn, hrs, alt in (("east", +1, hk["east_hr"], hk["east_alt_m"]), ("west", -1, hk["west_hr"], hk["west_alt_m"])):
        dur = hrs * 3600.0
        r_pl = R + alt
        for lat in (0.0, 40.0):
            cl = np.cos(np.deg2rad(lat)) if eci else 0.0
            vg = om * R * cl
            vp = om * r_pl * cl + sgn * v_air
            kin = -(vp ** 2 - vg ** 2) / (2 * c ** 2) * dur * 1e9
            grv = gravity_shift(R, r_pl, GM, c, ps) * dur * 1e9
            out[(name, lat)] = kin + grv
    e0, w0, e40, w40 = out[("east", 0.0)], out[("west", 0.0)], out[("east", 40.0)], out[("west", 40.0)]
    rows.append(Row(6, "Hafele-Keating: sign of east/west net shifts",
                    f"east {hk['east_meas_ns']:+.0f} ns, west {hk['west_meas_ns']:+.0f} ns (measured)",
                    f"east {e0:+.0f}, west {w0:+.0f} ns (equator); {e40:+.0f}/{w40:+.0f} (40N)",
                    "sign only",
                    verdict(e0 < 0 and w0 > 0, src_tag("hafele_keating1972"), "frame-composition sign check; not evidence"),
                    hk["cite"],
                    "Any theory with SR/GR-form terms gives these signs once velocities are composed in ECI. Magnitude NOT claimed. Fails under --frame ecef (east sign flips)."))

    # --- Row 7: one-way isotropy under Einstein sync (model-internal) -------
    worst_iso, worst_off = 0.0, 0.0
    for b in (0.1, 0.5, 0.9):
        m = one_way_measurement(b * c, c, 1.0, cp)
        worst_iso = max(worst_iso, abs(m["fwd"] / c - 1), abs(m["bwd"] / c - 1))
        worst_off = max(worst_off, abs(m["offset"] / m["lorentz_offset"] - 1))
    rows.append(Row(7, "One-way light speed measured by a moving observer (Einstein sync)",
                    "c_eff both ways; offset -vL0/c^2", f"max dev {worst_iso:.1e}; offset dev {worst_off:.1e}",
                    "1e-9",
                    verdict(worst_iso < 1e-9 and worst_off < 1e-9, "", "model-internal consistency; not evidence"),
                    "2026-09-04 One-Way Simultaneity.py",
                    "Real one-way times ARE anisotropic; the sync procedure masks it exactly (validated fact 12). Under --no-contraction both speeds come out c_eff/gamma (still isotropic) and the offset is wrong."))

    # --- Row 8: weak-force carrier bound (constraint) -------------------------
    bm = SOURCES["bailey1977"]
    D, vcb, cB_max = muon_carrier_bound(bm["tau_lab_us"], bm["tau_rest_us"])
    rows.append(Row(8, "Weak-force carrier speed bound from muon lifetimes (momentum-free)",
                    f"c_B <= {cB_max:.6f} c (D={D:.4f}, v<=c imported)", "c_B = c_eff = c (single carrier)",
                    "one-sided",
                    verdict(1.0 <= cB_max, src_tag("bailey1977"), "constraint satisfied; not evidence"),
                    bm["cite"],
                    "Never uses p=gamma*m*v (Retracted #4). Only row whose clock is not an electromagnetic transition (Retracted #6)."))
    return rows


CAVEATS = """\
WHAT THIS MODEL DOES NOT DO (printed every run, deliberately):
  * Michelson-Morley's null and the modern cavity bound (Nagel 2015,
    (9.2 +/- 10.7)e-19) are identities: P4's parallel factor was solved for
    to make them zero. Rows 1, 2 and 7 are the same identity in three
    costumes. None of them is evidence for a medium.
  * P4's perpendicular-unchanged assumption is tested by exactly one row
    (row 3, Ives-Stilwell), to about 2 parts in 1e8. Nothing else here
    touches it.
  * c_eff = c is pinned to measured dilation, not derived. E=hf and E=mc^2
    are imported. PBT does not derive c or electromagnetism here.
  * The medium's rest frame is unobservable in this model under Einstein
    synchronization; ECI is a convention that the GPS and flying-clock
    rows need for the ground clock's own motion, not a claim about where
    the medium is. The frame problem is open (Open Question 1): two test
    designs were killed as circular (twelfth, thirteenth calculations) and
    one on a structural barrier (sixteenth).
  * No point of divergence from Special Relativity has been found (Open
    Question 6). Under Einstein synchronization this model and SR make the
    same predictions everywhere either has been tested. Underdetermination
    cuts both ways: these rows cannot prove this model right any more than
    they can prove SR's geometry right.
  * Every clock in rows 1-7 is an electromagnetic process (Retracted #6).
    Only row 8 (weak decay) is a different process type, and it is a
    constraint, not a reproduced number.
  * P1-P4 describe a local, realist medium. Bell's theorem experimentally
    excludes local realist mechanisms for entanglement (Aspect, Clauser,
    Zeilinger; Nobel 2022). Nothing here reaches that wall, and the
    checklist marks it explicitly out of reach.
  * Gravity here is Einstein's 1911 heuristic on PBT's potential (G1), not
    a mechanism. PBT's own gravity mechanism (Class A shadowing) is on
    record as of 2026-09-11 as "narrowed, not cleared" on the heating wall.
  * GW170817's tensor polarization is NOT reproduced: the vault's own
    candidate wave carrier failed that test (seventeenth calculation).
    GW speed and the EHT shadow are untouched here.
  * Hafele-Keating: sign only, from frame composition; magnitude not claimed.
  * The GPS and Hafele-Keating velocity/gravity SPLITS are printed as model
    output only and never compared to anything (Retracted #5).
"""


def print_report(P, rows, title):
    print("=" * 78)
    print(title)
    print("=" * 78)
    print("PARAMETERS")
    for k, val in P.items():
        print(f"  {k:<22} {val}")
    print()
    print("BENCHMARKS  (real value | model value | tolerance | verdict)")
    for r in rows:
        print(f"[{r.n}] {r.name}")
        print(f"     real:    {r.real}")
        print(f"     model:   {r.model}")
        print(f"     tol:     {r.tol}")
        print(f"     verdict: {r.verdict}")
        print(f"     source:  {r.source}")
        if r.note:
            print(f"     note:    {r.note}")
    print()
    print(CAVEATS)
    print("SOURCE CHECK: " + "; ".join(f"{k}={s['verified_by']}" for k, s in SOURCES.items()))
    fails = [r.n for r in rows if r.failed]
    unv = [r.n for r in rows if r.verdict == "UNVERIFIED"]
    print(f"SUMMARY: FAIL rows {fails or 'none'}; UNVERIFIED rows {unv or 'none'}")
    return fails, unv


def selftest():
    """Regression fixture: calls run_rows() itself and asserts each
    perturbation fails exactly the rows it is designed to fail."""
    def fails(**kw):
        return {r.n for r in run_rows(dict(PARAMS, **kw)) if r.failed}
    checks = [
        ("baseline has no FAIL rows", fails(), set()),
        ("--perp-scale 1e-6 fails exactly rows {1, 3}", fails(perp_scale=1e-6), {1, 3}),
        ("--no-contraction fails exactly rows {2, 7}", fails(contraction_parallel=False), {2, 7}),
        ("--phi-scale 1.25 fails exactly rows {4, 5}", fails(phi_scale=1.25), {4, 5}),
        ("--frame ecef fails exactly rows {5, 6}", fails(frame="ecef"), {5, 6}),
    ]
    ok_all = True
    print("SELFTEST")
    for name, got, want in checks:
        ok = (got == want)
        print(f"  [{'ok' if ok else 'FAILED'}] {name}   got {sorted(got)}")
        ok_all &= ok
    return ok_all


def main():
    ap = argparse.ArgumentParser(description="How It Could Work -- Model Package v1")
    ap.add_argument("--no-contraction", action="store_true", help="switch P4's parallel contraction off")
    ap.add_argument("--perp-scale", type=float, default=0.0, help="perpendicular length factor (1 + X*beta^2)")
    ap.add_argument("--phi-scale", type=float, default=1.0, help="multiply Phi(r) by this factor")
    ap.add_argument("--frame", choices=["eci", "ecef"], default="eci", help="medium rest-frame convention")
    ap.add_argument("--selftest", action="store_true", help="run the perturbation regression fixture")
    args = ap.parse_args()
    if args.selftest:
        sys.exit(0 if selftest() else 1)
    P = dict(PARAMS, contraction_parallel=not args.no_contraction, perp_scale=args.perp_scale,
             phi_scale=args.phi_scale, frame=args.frame)
    tag = [t for t, on in (("--no-contraction", args.no_contraction), (f"--perp-scale {args.perp_scale}", args.perp_scale != 0.0),
                           (f"--phi-scale {args.phi_scale}", args.phi_scale != 1.0), (f"--frame {args.frame}", args.frame != "eci")) if on]
    title = "How It Could Work -- Model Package v1 (Mechanical Relativity Step 1)" + (f"   [PERTURBED: {' '.join(tag)}]" if tag else "")
    fails, unv = print_report(P, run_rows(P), title)
    sys.exit(1 if fails else 0)


if __name__ == "__main__":
    main()
