"""
2026-09-14 -- Mechanical Relativity Step 1, twenty-ninth calculation.
The explicit equation-of-state (EOS) route to the gravitational frequency
shift, run on Matthew's YES of 2026-09-14 (checklist Usage Log, same date).
The eighteenth and nineteenth calculations (2026-09-05) held this route open
and correctly did not attempt it without that yes.

QUESTION. If the light-carrying medium has an explicit barotropic EOS P(rho)
and sits in hydrostatic equilibrium in Earth's / the Sun's external Newtonian
potential Phi(r) = -GM/r (PBT's own already-"holds" potential), does the
standing-wave clock's tick rate f ~ v_s(r) reproduce the measured gravitational
frequency shift, and what does the SAME v_s(r) profile then predict for light
bending and Shapiro delay, which the same medium must also carry?

POSTULATED (new, honestly flagged, Matthew 2026-09-14):
  P1  the light carrier is a barotropic fluid, P = P0 + K*rho^Gamma, at rest in
      the Earth-centred (or Sun-centred) frame, with negligible self-gravity.
      STANDING CONFLICT, stated up front (second review, item 1): the carrier
      pinned at c is medium 2, and the checklist's Ruled Out list ALREADY closed
      medium 2 under its only supplied EOS (2026-09-04, eleventh calculation:
      kinetic form -> zero shear modulus, cannot carry transverse light; plus a
      5.7-10.4-order photon-energy ceiling failure). P1 therefore does not fill
      a gap, it REPLACES a ruled-out EOS with a different one -- and a barotropic
      fluid also has zero shear modulus, so the transverse-light objection is
      untouched by the replacement. P1 is a postulate contradicting an existing
      closure, not a postulate filling an open slot.
      The polytropic FAMILY is itself a choice: a general barotrope permits any
      v_s^2(h), so "the profile is forced" holds inside this family only.
  P2  it responds to Phi(r) hydrostatically, (1/rho) dP/dr = -dPhi/dr
      (the nineteenth calculation's OPEN gate, assumed here, not derived);
  P3  the clock is a standing wave of FIXED length whose tick rate is f ~ v_s
      (the eighteenth calculation's "frequency-vs-geometry" choice, branch (a);
      branches (b) and (c) are computed in row 8 rather than silently dropped).
DERIVED (mechanics, no further input):
  Bernoulli at rest: h(r) + Phi(r) = const, with dh = dP/rho and v_s^2 = dP/drho.
  For the polytrope: v_s^2(r) = v_inf^2 - (Gamma-1)*Phi(r).
  Hence f/f_inf = sqrt(1 - (Gamma-1)*Phi/v_inf^2), and the height PROFILE is
  Phi(r) ~ 1/r for every Gamma: the shape is forced, only the amplitude is free.
IMPORTED (already accepted for fact 9): nothing new; E=hf / E=mc^2 are NOT used
  here -- that is the point of this route.
PINNED (one fit): Gamma is solved from the clock benchmark, then everything else
  is predicted with zero further freedom.
FIXED-BY-FACTS: v_inf = c, because facts 8-10 already pin the clock's carrier at
  c_eff ~ c (Ruled Out #3 excludes SAP's own 2.5e5 c by 11 orders).

FALSIFICATION CONDITIONS, stated before the first run (checklist vocabulary):
  Row 1  Gamma admissibility: v_s^2 <= 0 anywhere outside r_s -> CONTRADICTION.
  Row 2  Pound-Rebka/Pound-Snider height ratio outside Pound-Snider 3 sigma
         (0.9990 +/- 0.0228) -> CONTRADICTION.
  Row 3  GPS gravity term vs the shipped fact-9 function (gravity_shift, model
         package v1). MODEL-INTERNAL IDENTITY, NOT A TEST: both sides are
         Delta Phi / c^2 by construction once Gamma is fitted, so this row cannot
         fail short of an algebra error; it is printed for continuity with the
         v1 package's row 5 and labelled as such (second review, item 2). The
         +45.65 us/day figure is a formula's split, not a measurement (Retracted
         #5); GPS measures only the net.
  Row 4  Solar-limb deflection vs Dyson-Eddington-Davidson 1920 Sobral 4-inch
         1.98 +/- 0.12 arcsec PROBABLE ERROR = +/- 0.18 arcsec ONE standard
         deviation (0.12 x 1.4826); band used is 1 s.d. -> outside = CONTRADICTION.
  Row 5  PPN gamma from that deflection vs Cassini (Bertotti, Iess & Tortora,
         Nature 425, 374 (2003)): gamma-1 = (2.1 +/- 2.3)e-5; |gamma-1| > 3 sigma
         -> CONTRADICTION.  VLBI cross-check (Lambert & Le Poncin-Lafitte, A&A
         529, A70 (2011)): gamma = 0.99992 +/- 0.00012.
  Row 6  Shapiro round-trip, Cassini-like geometry: r1 = 1 AU (Earth), r2 = 7.4 AU
         (Earth-spacecraft distance at the June 2002 conjunction, Bertotti 2003;
         Cassini was still en route, arriving at Saturn July 2004), b = 1.6 R_sun
         (RECALLED, not confirmed from the primary paper -- treat as a
         representative impact parameter). Illustrative magnitude only; the
         verdict rides on row 5 (same measurement, gamma).
  Row 7  Gamma required under v_inf = c, 2.5e5 c, 6.1e24 c: spread > 1 order
         -> UNDERDETERMINED (the postulate absorbs whatever the carrier misses).
  Row 8  Delta f/f under the three atom-geometry branches of SAP point 5:
         (a) fixed length, (b) radius set by medium wavelength with fixed source
         frequency, (c) source frequency itself ~ v_s^2. Branch (c) is NOT
         derivable from the spec: it is f_source = m c^2 / h with c -> v_s, which
         re-imports E=hf and E=mc^2 (excluded by this route's own charter above)
         PLUS a third unstated postulate that the c in the Compton relation
         tracks the local carrier. Labelled a postulate with two imports; the
         row's conclusion (branches disagree -> UNDERDETERMINED) already follows
         from (a) vs (b) alone, 1x vs 0x.
  Row 9  Chromaticity: n(r) independent of frequency for a non-dispersive
         barotrope -> any dispersion = CONTRADICTION with SAP point 13.

NOT NEW PHYSICS, said plainly. v_s^2 = c^2 + 2 Phi is Einstein's 1911 variable-c
gravity (Ann. Phys. 35, 898; confirmed), which predicted 0.83 arcsec and was
doubled in 1915-16 by spatial curvature. The refractive-medium reading is
Eddington 1920 and Dicke (Rev. Mod. Phys. 29, 363 (1957); confirmed); de Felice
1971 and the polarizable-vacuum line (Puthoff 2002) are RECALLED citations,
verified by neither review. THIS VAULT ALREADY HAS IT: Theory Working Notes
2026-07-31 writes n(r) ~ 1 + 2GM/(c^2 r) and states "half the deflection it
reproduces comes from spatial curvature, so 'it's time, not space' is right for
a falling apple and wrong for a passing photon." The read-this-first gate
(Retracted #8) exists for exactly this; the entry is cited, not rediscovered.

WHAT THE MISSING HALF IS (first review, item 6; dropped from rev 2, restored).
The factor of 2 is structural, not a coincidence. Clock rates come from g_00;
light bending and Shapiro delay take half from g_00 and half from the SPATIAL
part of the metric (gravitational ruler contraction). A single scalar function
of Phi -- which is all a barotropic v_s(r) can ever be -- supplies one half
only. Reaching n = 1 + 2GM/(r c^2) needs lengths to shrink by (1 + Phi/c^2)
alongside the clock; SAP point 5 would supply exactly that factor, but only in
branch (b), where the clock shift goes to zero. That is the tensor requirement,
and this program has hit the same wall before: GW170817 polarization (Takeda et
al. 2021, log Bayes factor 44.5 against pure scalar; checklist Ruled Out, the
seventeenth calculation). PBT's own absolute space has no slot for a
position-dependent ruler.

CIRCULARITY CHECK. Not Ruled Out #1 class: Gamma could have come out
inadmissible (imaginary v_s) and did not, and the 1/r profile is a real
mechanical consequence (the delta P/P route died at 38 % precisely by using
local g ~ 1/r^2 instead). But every barotrope gives f = F(Phi), so the
Pound-Rebka : GPS ratio is passed by the whole family and cannot separate this
route from fact 9. No discriminating power is claimed.

Process: one independent read-only design review (Opus) re-derived A-F from the
primary files before this script existed; it and this author's own derivation
agreed on every formula. A second review of the executed output follows the run.
"""
import argparse, importlib.util, math, os, sys

HERE = os.path.dirname(os.path.abspath(__file__))
# Published copy (solvetheuniverse.com /models/, 2026-09-23): the vault's dated
# derivation record is unchanged; this copy differs only in resolving the
# shipped package by its published filename when the dated one is absent.
_PKG_CANDIDATES = [
    os.path.join(HERE, "2026-09-11 Mechanical Relativity Step 1 - How It Could Work Model Package v1.py"),
    os.path.join(HERE, "how-it-could-work-v1.py"),
]
PKG = next((p for p in _PKG_CANDIDATES if os.path.exists(p)), _PKG_CANDIDATES[-1])

def load_package():
    """Call the SHIPPED fact-9 function (item 11: never a pasted copy)."""
    spec = importlib.util.spec_from_file_location("hicw_v1", PKG)
    mod = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(mod)
    return mod

# ---- constants (sourced) -----------------------------------------------------
c      = 299_792_458.0                 # m/s, exact
G      = 6.674_30e-11                  # CODATA 2018
GM_sun = 1.327_124_400_18e20           # m^3/s^2, IAU 2015 nominal
R_sun  = 6.957e8                       # m, IAU 2015 nominal
AU     = 1.495_978_707e11              # m, exact
SEC_PER_DAY = 86400.0
ARCSEC = math.pi / (180 * 3600)

# real measured benchmarks (see docstring for sources)
SOBRAL_DEFL, SOBRAL_ERR = 1.98, 0.18          # arcsec; 0.12 probable error = 0.18 one s.d.
GR_DEFL_LIMB = (4 * GM_sun / (R_sun * c**2)) / ARCSEC   # computed, not hardcoded (1.75119...)
CASSINI_GM1, CASSINI_SIG = 2.1e-5, 2.3e-5      # gamma - 1
VLBI_GAMMA, VLBI_SIG = 0.99992, 0.00012
# Pound-Snider is read from the shipped package's SOURCES at run time (single
# source of truth; the v1 package carries it as secondary-sourced). Fallback
# values are only used if the key is absent, and that is reported.
PS_RATIO_FALLBACK, PS_SIG_FALLBACK = 0.9990, 0.0076

# ---- the derived model -------------------------------------------------------
def gamma_for_clock(v_inf):
    """Gamma that makes f/f_inf = 1 + Phi/c^2 to first order, for carrier v_inf."""
    return 1.0 - 2.0 * v_inf**2 / c**2

def vs2(r, GM, Gamma, v_inf):
    """v_s^2(r) = v_inf^2 - (Gamma-1) Phi(r), Phi -> 0 at infinity."""
    return v_inf**2 - (Gamma - 1.0) * (-GM / r)

def clock_ratio(r, GM, Gamma, v_inf):
    return math.sqrt(vs2(r, GM, Gamma, v_inf)) / v_inf

def clock_shift(r1, r2, GM, Gamma, v_inf):
    """f(r2)/f(r1) - 1, computed without catastrophic cancellation.
    Delta Phi = GM (r2 - r1)/(r1 r2) is formed directly; then
    f2/f1 = sqrt(v_s^2(r2)/v_s^2(r1)) = sqrt(1 + [-(Gamma-1) Delta Phi]/v_s^2(r1)).
    First run took the ratio of two numbers within 1e-9 of 1.0 and lost 8 % of a
    2.5e-15 shift to float64 rounding (row 2 read 1.086, a false FAIL); this
    form keeps full precision at 22.5 m."""
    dphi = GM * (r2 - r1) / (r1 * r2)
    return math.expm1(0.5 * math.log1p(-(Gamma - 1.0) * dphi / vs2(r1, GM, Gamma, v_inf)))

def index(r, GM, Gamma, v_inf):
    """acoustic refractive index n = v_inf / v_s(r)"""
    return v_inf / math.sqrt(vs2(r, GM, Gamma, v_inf))

def deflection_arcsec(b, GM, Gamma, v_inf, zmax_factor=2000.0, N=400_001):
    """Ray-optics deflection through n(r): alpha = -int (d n / d b) dz along the
    unperturbed straight line r = sqrt(b^2 + z^2). Integrated numerically so the
    result is not assumed from any closed form; the closed form 2GM/(b c^2) is
    printed alongside as the check."""
    zmax = zmax_factor * b
    h = 2 * zmax / (N - 1)
    total = 0.0
    for i in range(N):
        z = -zmax + i * h
        r = math.hypot(b, z)
        # d n / d b at fixed z  = (dn/dr)(b/r), with
        # d(v_s^2)/dr = -(Gamma-1) GM / r^2  and  n = v_inf / sqrt(v_s^2)
        # -> dn/dr = +0.5 n^3 (Gamma-1) GM / (r^2 v_inf^2)   (< 0 for Gamma < 1)
        # First run had the sign of d(v_s^2)/dr dropped; the --selftest fixture
        # (integrator vs closed form) caught it before any output was logged.
        n_eff = index(r, GM, Gamma, v_inf)
        dn_dr = 0.5 * n_eff**3 * ((Gamma - 1.0) * GM / r**2) / v_inf**2
        w = 1.0 if i in (0, N - 1) else (4.0 if i % 2 else 2.0)
        total += w * dn_dr * (b / r)
    alpha = -(h / 3.0) * total
    return alpha / ARCSEC

def shapiro_roundtrip_us(b, r1, r2, GM, Gamma, v_inf):
    """Extra round-trip time through n(r) relative to n=1, straight-line path."""
    def one_leg(rr):
        L = math.sqrt(rr**2 - b**2)
        N = 200_001
        h = L / (N - 1)
        tot = 0.0
        for i in range(N):
            z = i * h
            r = math.hypot(b, z)
            w = 1.0 if i in (0, N - 1) else (4.0 if i % 2 else 2.0)
            tot += w * (index(r, GM, Gamma, v_inf) - 1.0)
        return (h / 3.0) * tot / v_inf
    return 2.0 * (one_leg(r1) + one_leg(r2)) * 1e6

def gr_shapiro_roundtrip_us(b, r1, r2, GM, gamma_ppn=1.0):
    """Round-trip Shapiro excess, PPN form: 2 (1+gamma) (GM/c^3) ln(4 r1 r2 / b^2)
    (Will, Living Rev. Relativ. 17, 4 (2014), eq. for the round-trip delay; the
    one-way delay is half of this). Check: Viking geometry, b = R_sun, r2 = 1.52 AU
    gives ~247 us, the textbook ~250 us. First run omitted the leading 2 and so
    compared a one-way GR number against the model's round-trip; row 6 read
    ratio 1.000, a false agreement."""
    return 2 * (1 + gamma_ppn) * (GM / c**3) * math.log(4 * r1 * r2 / b**2) * 1e6

def verdict(ok, kind):
    return f"PASS" if ok else f"FAIL -> {kind}"

# ---- rows --------------------------------------------------------------------
def run(args):
    pkg = load_package()
    P = pkg.PARAMS
    GM_e, R_e, a_gps = P["GM_earth"], P["R_earth"], P["a_gps"]
    v_inf = c
    Gamma = gamma_for_clock(v_inf)
    out = []
    pr = out.append

    pr("=" * 78)
    pr("Barotropic-medium EOS gravity route -- clock, bending, Shapiro (2026-09-14)")
    pr("=" * 78)
    pr(f"carrier speed at infinity v_inf = c (facts 8-10);  fitted Gamma = {Gamma:+.6f}")
    pr(f"EOS: P = P0 + K rho^Gamma  ->  v_s^2(r) = c^2 - (Gamma-1) Phi(r) = c^2 + 2 Phi(r)")
    pr("")

    # Row 1: admissibility
    r_s = 2 * GM_sun / c**2
    ok1 = all(vs2(r, GM_sun, Gamma, v_inf) > 0 for r in (1.0001 * r_s, R_sun, AU)) and Gamma != 1.0
    pr(f"[1] admissibility: Gamma={Gamma:+.3f}; dP/drho = K*Gamma*rho^(Gamma-1) > 0 needs K*Gamma > 0,")
    pr(f"    i.e. K < 0 with P0 > 0 offset: P = P0 - |K|/rho. v_s^2 > 0 for all r > r_s = {r_s:.1f} m: {ok1}")
    pr(f"    verdict: {verdict(ok1, 'CONTRADICTION')}  (admissible, exotic: no laboratory fluid has Gamma < 1)")
    pr("")

    # Row 2: Pound-Rebka height ratio vs fact-9 (shipped function), and vs Pound-Snider band
    h_pr = P["h_pound_rebka"]
    psn = getattr(pkg, "SOURCES", {}).get("pound_snider1965", {})
    PS_RATIO = psn.get("ratio", PS_RATIO_FALLBACK); PS_3SIG = 3 * psn.get("sigma", PS_SIG_FALLBACK)
    ps_src = "shipped pkg.SOURCES (" + psn.get("verified_by", "?") + ")" if psn else "FALLBACK constants (key missing)"
    model_pr = clock_shift(R_e, R_e + h_pr, GM_e, Gamma, v_inf)
    fact9_pr = pkg.gravity_shift(R_e, R_e + h_pr, GM_e, c)
    ratio_pr = model_pr / fact9_pr
    ok2 = abs(ratio_pr - PS_RATIO) <= PS_3SIG
    pr(f"[2] Pound-Rebka 22.5 m: model {model_pr:.4e}, shipped fact-9 gravity_shift {fact9_pr:.4e},")
    pr(f"    ratio {ratio_pr:.6f} vs Pound-Snider {PS_RATIO} +/- {PS_3SIG:.4f} (3 sigma; source: {ps_src})")
    pr(f"    verdict: {verdict(ok2, 'CONTRADICTION')}  (fitted: Gamma sets this amplitude; not evidence)")
    pr("")

    # Row 3: GPS gravity term
    model_gps = clock_shift(R_e, a_gps, GM_e, Gamma, v_inf)
    fact9_gps = pkg.gravity_shift(R_e, a_gps, GM_e, c)
    ok3 = abs(model_gps / fact9_gps - 1.0) < 1e-6
    pr(f"[3] GPS gravity term (ground R_e -> a_gps): model {model_gps*SEC_PER_DAY*1e6:+.3f} us/day,")
    pr(f"    shipped fact-9 {fact9_gps*SEC_PER_DAY*1e6:+.3f} us/day, rel. diff {model_gps/fact9_gps-1:+.2e} (second-order term)")
    pr(f"    verdict: {'IDENTITY' if ok3 else 'ALGEBRA ERROR'} (MODEL-INTERNAL consistency; NOT evidence; cannot fail once Gamma is fitted)")
    pr(f"             1/r PROFILE forced by Bernoulli inside the polytropic family; amplitude fitted; NET-ONLY (Retracted #5)")
    pr("")

    # Row 4: solar limb deflection
    defl = deflection_arcsec(R_sun, GM_sun, Gamma, v_inf)
    closed = (2 * GM_sun / (R_sun * c**2)) / ARCSEC
    ok4 = abs(defl - SOBRAL_DEFL) <= SOBRAL_ERR
    pr(f"[4] solar-limb deflection through n(r)=c/v_s(r): numerical {defl:.4f} arcsec, closed form 2GM/(bc^2) {closed:.4f}")
    pr(f"    real: Sobral 4-inch 1920, {SOBRAL_DEFL} +/- {SOBRAL_ERR} (1 s.d.; 0.12 probable error);  GR 4GM/(bc^2) = {GR_DEFL_LIMB:.4f}")
    pr(f"    verdict: {verdict(ok4, 'CONTRADICTION')}  (model/GR = {defl/GR_DEFL_LIMB:.4f}; Einstein 1911's factor of 2)")
    pr("")

    # Row 5: PPN gamma
    gamma_ppn = 2 * defl / GR_DEFL_LIMB - 1.0
    ok5 = abs((gamma_ppn - 1.0) - CASSINI_GM1) <= 3 * CASSINI_SIG
    pr(f"[5] PPN gamma implied: {gamma_ppn:+.4f};  Cassini gamma-1 = {CASSINI_GM1:.1e} +/- {CASSINI_SIG:.1e}")
    pr(f"    ({abs(gamma_ppn-1-CASSINI_GM1)/CASSINI_SIG:.0f} sigma off);  VLBI gamma = {VLBI_GAMMA} +/- {VLBI_SIG} ({abs(gamma_ppn-VLBI_GAMMA)/VLBI_SIG:.0f} sigma off)")
    pr(f"    verdict: {verdict(ok5, 'CONTRADICTION')}")
    pr("")

    # Row 6: Shapiro, Cassini-like geometry. r2 = Earth-spacecraft distance at the
    # June 2002 conjunction (~7.4 AU; Cassini reached Saturn July 2004, so "Saturn's
    # distance" was the wrong quantity in rev 2). b = 1.6 R_sun is RECALLED, unverified.
    b = 1.6 * R_sun; r2 = 7.4 * AU
    sh_model = shapiro_roundtrip_us(b, AU, r2, GM_sun, Gamma, v_inf)
    sh_gr = gr_shapiro_roundtrip_us(b, AU, r2, GM_sun, 1.0)
    pr(f"[6] Shapiro round-trip, Cassini-like geometry (b=1.6 R_sun recalled, r2=7.4 AU): model {sh_model:.1f} us, GR {sh_gr:.1f} us, ratio {sh_model/sh_gr:.3f}")
    pr(f"    verdict: illustrative; rides on row 5 (same gamma). Half of GR, as the (1+gamma)/2 factor requires.")
    pr("")

    # Row 7: Gamma under three carrier speeds
    pr("[7] Gamma required for the clock benchmark under candidate carrier speeds:")
    gs = []
    for label, v in (("c (facts 8-10)", c), ("2.5e5 c (SAP point 5)", 2.5e5 * c), ("6.1e24 c (heat floor)", 6.1e24 * c)):
        g = gamma_for_clock(v); gs.append(abs(g - 1))
        pr(f"    v_inf = {label:>22}: Gamma = {g:+.4e}")
    spread = math.log10(max(gs) / min(gs))
    pr(f"    spread |Gamma-1|: {spread:.0f} orders  -> {'UNDERDETERMINED' if spread > 1 else 'determinate'}")
    pr("    (the postulate absorbs whatever fraction of Phi the carrier fails to feel; nineteenth-calc gate quantified)")
    pr("")

    # Row 8: geometry branches
    phi_over_c2 = (-GM_e / R_e) / c**2
    br = {"(a) fixed length, f ~ v_s": 1.0,
          "(b) r ~ v_s / f_source, f_source fixed": 0.0,
          "(c) f_source ~ v_s^2 [POSTULATE: re-imports E=hf, E=mc^2]": 2.0}
    pr("[8] Delta f/f at Earth's surface under SAP point 5's atom-geometry branches (multiples of Phi/c^2):")
    for k, m in br.items():
        pr(f"    {k:<56} {m:.0f} x Phi/c^2 = {m*phi_over_c2:+.3e}")
    pr("    branches disagree (0x, 1x, 2x) -> UNDERDETERMINED: the postulate does not pick the branch, the fit does")
    pr("")

    # Row 9: chromaticity
    pr("[9] chromaticity: n(r) = c/v_s(r) has no frequency dependence for a non-dispersive barotrope -> PASS")
    pr("    (consistent with SAP point 13 and Fermi-LAT; the one clean pass, shared with GR)")
    pr("")

    # Summary
    pr("-" * 78)
    pr("SUMMARY (three-state rule)")
    pr("  HOLDS       : row 2 (Pound-Snider band) by one fitted Gamma; row 9. Row 3 is a model-internal identity, not evidence.")
    pr("                The 1/r profile is forced by hydrostatics INSIDE the polytropic family; the family is a choice.")
    pr("  RULED OUT   : the one-postulate route as a complete gravity model -- rows 4, 5 fail by a factor of 2")
    pr(f"                ({abs(gamma_ppn-1-CASSINI_GM1)/CASSINI_SIG:.0f} sigma at Cassini). Same medium, same v_s(r): B and C are one postulate.")
    pr("  UNDERDETERM.: rows 7, 8 -- which medium, and which atom-geometry branch, are not fixed by the spec.")
    pr("  NOT NEW     : Einstein 1911 / Dicke 1957 in PBT vocabulary; already in this vault 2026-07-31.")
    pr("  CONFLICT    : P1 replaces medium 2's already-ruled-out EOS (eleventh calc) with another zero-shear-modulus fluid;")
    pr("                the transverse-light objection is untouched. The missing half is ruler contraction (tensor), GW170817.")
    print("\n".join(out))
    return dict(Gamma=Gamma, defl=defl, gamma_ppn=gamma_ppn, ok=[ok1, ok2, ok3, ok4, ok5])

def selftest():
    """Regression fixture. Calls the shipped functions above, never a copy."""
    pkg = load_package(); P = pkg.PARAMS
    # (i) Gamma = 1 reproduces the 2026-09-05 Step 0 null exactly
    for r in (P["R_earth"], P["R_earth"] + 22.5, P["a_gps"]):
        assert abs(clock_ratio(r, P["GM_earth"], 1.0, c) - 1.0) < 1e-15, "Step 0 null not reproduced"
    # (ii) fitted Gamma reproduces fact 9 to first order via the SHIPPED gravity_shift
    Gm = gamma_for_clock(c)
    m = clock_ratio(P["a_gps"], P["GM_earth"], Gm, c) / clock_ratio(P["R_earth"], P["GM_earth"], Gm, c) - 1
    assert abs(m / pkg.gravity_shift(P["R_earth"], P["a_gps"], P["GM_earth"], c) - 1) < 1e-6
    # (ii-b) the 22.5 m row must agree with the shipped function to 1e-6 relative
    m2 = clock_shift(P["R_earth"], P["R_earth"] + 22.5, P["GM_earth"], Gm, c)
    assert abs(m2 / pkg.gravity_shift(P["R_earth"], P["R_earth"] + 22.5, P["GM_earth"], c) - 1) < 1e-6, "22.5 m precision regression"
    # (ii-c) GR Shapiro helper reproduces Viking's ~250 us round trip (b = R_sun, r_p = 1.52 AU)
    vk = gr_shapiro_roundtrip_us(R_sun, AU, 1.52 * AU, GM_sun, 1.0)
    assert 240 < vk < 260, f"Viking check {vk} us"
    # (ii-d) model-side Shapiro normalisation: at Gamma = -3, n - 1 = 2GM/(r c^2) to first
    #        order, so the MODEL round trip must equal GR's gamma=1 round trip within 1 %
    m_sh = shapiro_roundtrip_us(1.6 * R_sun, AU, 7.4 * AU, GM_sun, -3.0, c)
    g_sh = gr_shapiro_roundtrip_us(1.6 * R_sun, AU, 7.4 * AU, GM_sun, 1.0)
    assert abs(m_sh / g_sh - 1) < 1e-2, f"model Shapiro normalisation {m_sh} vs {g_sh}"
    # (ii-e) PPN gamma extraction: GR's own deflection must map to gamma = 1, half of it to 0
    assert abs((2 * GR_DEFL_LIMB / GR_DEFL_LIMB - 1) - 1.0) < 1e-12
    assert abs((2 * (GR_DEFL_LIMB / 2) / GR_DEFL_LIMB - 1) - 0.0) < 1e-12
    # (iii) deflection integrator reproduces the closed form to 0.1 %
    d = deflection_arcsec(R_sun, GM_sun, Gm, c); cf = (2 * GM_sun / (R_sun * c**2)) / ARCSEC
    assert abs(d / cf - 1) < 1e-3, f"integrator {d} vs closed {cf}"
    # (iv) failure path: a Gamma giving imaginary v_s at the limb must be caught, not silently sqrt'd
    try:
        clock_ratio(1.0001 * 2 * GM_sun / c**2, GM_sun, 1.0 - 2.0 * 1.5, c)  # Gamma=-2 -> v_s^2 = c^2 + 3Phi < 0 near r_s
        caught = False
    except ValueError:
        caught = True
    assert caught, "imaginary sound speed not caught"
    print("selftest: 9/9 passed (Step-0 null; fact-9 match via shipped function at GPS and at 22.5 m; Viking GR Shapiro; model Shapiro normalisation at Gamma=-3; PPN gamma extraction x2; integrator vs closed form; failure path)")

if __name__ == "__main__":
    ap = argparse.ArgumentParser()
    ap.add_argument("--selftest", action="store_true")
    a = ap.parse_args()
    if a.selftest:
        selftest()
    else:
        run(a)
