#!/usr/bin/env python3
"""
substrate_gravity.py — reproduce the gravity-sector results
===========================================================

Companion reproduction script for the standalone science intro,
``arxiv/bridge-paper.qmd``, §sec-gravity ("Gravity as a leak, and the acoustic
metric").  Where ``substrate_atomic.py`` reproduces the particle-physics
backbone and ``substrate_galactic.py`` the DESI/Hubble cosmology, this script
reproduces the three gravitational results:

  1. Painlevé–Gullstrand / Schwarzschild metric  (§sec-pg)
     The substrate ebb v_ebb(r) = √(2GM/r), fed into the Unruh acoustic metric,
     is *exactly* the Painlevé–Gullstrand form of Schwarzschild.  We verify the
     metric identity, the horizon r_s = 2GM/c², the "pressure gravitates"
     factor ρ_eff = 4ρ, and the four classical tests against solar-system data.

  2. The small cosmological constant  (§sec-lambda)
     Volovik self-tuning: the dark-energy scale is the substrate scale,
     ρ_Λ^(1/4) ≈ m₁c² ≈ 2 meV, and the notorious tiny Λ is one residual
     disequilibrium read against two rulers — 1.6 against ρ_DM, 3.4e-62 against
     the Planck density — the two differing by exactly (m₁/M_Pl)², the same
     hierarchy that makes G weak.

  3. MOND from the superfluid  (§sec-mond)
     With ρ_DM fixed by the bridge equation, the MOND acceleration scale is
     a₀ = c√(Gρ_DM) = 1.16e-10 m/s², within ~3% of the measured g†; the
     "cosmic coincidence" a₀/cH₀ = √(3Ω_DM/8π) = 0.178 follows identically;
     and the outer-rim Landau velocity is predicted from constants by the
     clean-units law v_L = c(4π/ν)^(1/3) ≈ 740 km/s (~1.3% from the ω₀ξ target,
     matching the fast solar wind), superseding the raw-SI cubic (4πν c²G)^(1/3)
     that undershoots to ~398 km/s for want of the 2D→3D projection.

Pure Python standard library only — no third-party dependencies:
    python3 scripts/substrate_gravity.py
"""

import math

# ══════════════════════════════════════════════════════════════════════════
#  Measured input constants
# ══════════════════════════════════════════════════════════════════════════
# Fundamental constants — CODATA 2018.
HBAR = 1.054571817e-34      # reduced Planck constant   [J·s]
C    = 2.99792458e8         # speed of light            [m/s]
G    = 6.67430e-11          # gravitational constant    [m³ kg⁻¹ s⁻²]
KB   = 1.380649e-23         # Boltzmann constant        [J/K]
EV   = 1.602176634e-19      # 1 eV in joules            [J]
ARCSEC = 206264.806         # radians → arcseconds
AU   = 1.495978707e11       # astronomical unit         [m]
YR   = 365.25 * 86400.0     # Julian year               [s]
MAS_PER_RAD = 1.0e3 * ARCSEC                             # radians → milliarcsec

# Cosmology — Planck 2018.
OMEGA_C_H2 = 0.1200         # cold dark matter density parameter  Ω_c h²
OMEGA_L    = 0.6847         # dark-energy density parameter        Ω_Λ
H_LITTLE   = 0.6736         # reduced Hubble constant  h = H₀/(100 km/s/Mpc)
MPC        = 3.0856775814913673e22   # 1 megaparsec  [m]

# Solar-system data (IAU / JPL), for the classical tests of §sec-pg.
M_SUN = 1.98892e30          # solar mass                [kg]
R_SUN = 6.9634e8            # solar radius              [m]
# Mercury orbit.
A_MERC = 5.7909050e10       # semi-major axis           [m]
E_MERC = 0.20563            # eccentricity
T_MERC = 87.9691 * 86400.0  # orbital period            [s]
JULIAN_CENTURY = 36525.0 * 86400.0                       # [s]

# Observed reference values (for the discrepancy columns).
MERC_PREC_OBS = 42.98       # GR perihelion precession  [arcsec/century]
DEFLECT_OBS   = 1.75        # light deflection at solar limb  [arcsec]
G_DAGGER_OBS  = 1.20e-10    # McGaugh radial-acceleration scale g†  [m/s²]
V_ULYSSES     = 751.5e3     # Ulysses mean fast solar-wind speed  [m/s]

# Earth + Gravity Probe B orbit (IERS / GP-B), for frame-dragging (^eq-fd).
GM_EARTH   = 3.986004418e14   # Earth standard gravitational parameter [m³/s²]
M_EARTH    = 5.9722e24        # Earth mass                             [kg]
R_EARTH    = 6.371e6          # Earth mean radius                      [m]
I_EARTH_C  = 0.3307           # Earth moment-of-inertia coefficient I/MR²
OMEGA_EARTH = 7.292115e-5     # Earth sidereal spin rate               [rad/s]
R_GPB      = 7027.4e3         # GP-B orbit semi-major axis             [m]
GEODETIC_GPB_OBS  = 6601.8    # GP-B measured geodetic drift  [mas/yr]
FRAMEDRAG_GPB_OBS = 37.2      # GP-B measured frame-drag      [mas/yr]


# ══════════════════════════════════════════════════════════════════════════
#  Shared derived quantities
# ══════════════════════════════════════════════════════════════════════════

def rho_DM() -> float:
    """Dark-matter mass density from Planck Ω_c h²  [kg/m³]  (^rhodm)."""
    H100 = 100.0e3 / MPC                         # 100 km/s/Mpc in s⁻¹
    rho_crit_over_h2 = 3.0 * H100**2 / (8.0 * math.pi * G)
    return OMEGA_C_H2 * rho_crit_over_h2          # = 2.254e-27 kg/m³


def rho_crit() -> float:
    """Critical density at the Planck-2018 H₀  [kg/m³]."""
    H0 = H_LITTLE * 100.0e3 / MPC
    return 3.0 * H0**2 / (8.0 * math.pi * G)


def rho_Lambda() -> float:
    """Dark-energy mass density  ρ_Λ = Ω_Λ ρ_crit  [kg/m³]."""
    return OMEGA_L * rho_crit()


def m1_closepacked() -> float:
    """dc1 mass from close-packing, ρ_DM = m₁⁴c³/ℏ³  ⇒  m₁ = (ρ_DM ℏ³/c³)^¼  [kg].

    bridge-paper.qmd §sec-lambda, footnote ^eq-lambda.  Route-independent form
    of the Volovik + close-packing relation; m₁c² ≈ 1.77 meV ≈ "2 meV".
    """
    return (rho_DM() * HBAR**3 / C**3) ** 0.25


def M_planck() -> float:
    """Planck mass  M_Pl = √(ℏc/G)  [kg]."""
    return math.sqrt(HBAR * C / G)


# ══════════════════════════════════════════════════════════════════════════
#  Result 1 — Painlevé–Gullstrand / Schwarzschild metric  (§sec-pg)
# ══════════════════════════════════════════════════════════════════════════

def v_ebb(r: float, M: float) -> float:
    """Free-fall substrate inflow  v_ebb(r) = √(2GM/r)  [m/s]  (@eq-schwarzschild).

    From the substrate's Euler + continuity equations: convective acceleration
    v dv/dr = −GM/r² with no pressure gradient (the equivalence principle).
    """
    return math.sqrt(2.0 * G * M / r)


def schwarzschild_radius(M: float) -> float:
    """Horizon radius r_s = 2GM/c², where v_ebb(r_s) = c."""
    return 2.0 * G * M / C**2


def verify_pg_metric(M: float) -> dict:
    """Check the acoustic metric with v_ebb = √(2GM/r) is the PG–Schwarzschild form.

    Acoustic metric (@eq-acoustic):  g_tt = −(c² − v_ebb²),  g_tr = −v_ebb,
    g_rr = 1.  Substituting v_ebb² = 2GM/r must give the Schwarzschild
    g_tt = −c²(1 − r_s/r) exactly — the Painlevé–Gullstrand "rain" form.
    """
    r_s = schwarzschild_radius(M)
    r = 3.0 * r_s                                # sample point outside horizon
    g_tt_acoustic = -(C**2 - v_ebb(r, M)**2)     # from the flowing-fluid metric
    g_tt_schwarz  = -C**2 * (1.0 - r_s / r)      # textbook Schwarzschild g_tt
    return {
        'r_s': r_s,
        'g_tt_acoustic': g_tt_acoustic,
        'g_tt_schwarz':  g_tt_schwarz,
        'match': abs(g_tt_acoustic - g_tt_schwarz) < 1e-6 * C**2,
        'v_at_horizon_over_c': v_ebb(r_s, M) / C,   # must equal 1
    }


def rho_eff_factor() -> float:
    """"Pressure gravitates": ρ_eff = ρ + 3P/c² = 4ρ for the stiff EOS P = ρc².

    bridge-paper.qmd footnote ^eq-factor4.  This factor of 4 turns the naïve
    fluid-Poisson 4πG into the general-relativistic 16πG.
    """
    P_over_rho_c2 = 1.0                           # stiff EOS: P = ρc²
    return 1.0 + 3.0 * P_over_rho_c2              # = 4


def perihelion_precession(M: float, a: float, e: float,
                          period: float) -> float:
    """GR perihelion precession 6πGM/(a c²(1−e²)), in arcsec/century (^eq-tests)."""
    per_orbit = 6.0 * math.pi * G * M / (a * C**2 * (1.0 - e**2))   # rad/orbit
    orbits_per_century = JULIAN_CENTURY / period
    return per_orbit * orbits_per_century * ARCSEC


def light_deflection(M: float, b: float) -> float:
    """GR light deflection Δθ = 4GM/(b c²), in arcsec (^eq-tests).

    Twice the Newtonian-corpuscular value: the modon is both refracted by the
    graded signal speed and dragged by the inflow — two equal contributions.
    """
    return 4.0 * G * M / (b * C**2) * ARCSEC


def gravitational_redshift(M: float, r: float) -> float:
    """Full nonlinear gravitational redshift z = 1/√(1−r_s/r) − 1  (^eq-tests)."""
    return 1.0 / math.sqrt(1.0 - schwarzschild_radius(M) / r) - 1.0


def shapiro_delay(M: float, b: float, r1: float, r2: float) -> float:
    """GR Shapiro delay Δt = (2GM/c³) ln(4 r₁ r₂/b²), one-way  [s]  (^eq-tests).

    bridge-paper.qmd §sec-pg, footnote ^eq-tests.  The same reduced signal speed
    c(r)=c(1−2GM/rc²) that bends light also delays it, integrated along the path;
    it is a geodesic of @eq-schwarzschild, not an add-on.  For a ray grazing the
    Sun between two points at r₁, r₂ ≈ 1 AU the one-way delay is ~120 μs.
    """
    return 2.0 * G * M / C**3 * math.log(4.0 * r1 * r2 / b**2)


# ══════════════════════════════════════════════════════════════════════════
#  Result 2 — the small cosmological constant  (§sec-lambda)
# ══════════════════════════════════════════════════════════════════════════

def cosmological_constant() -> dict:
    """Dark-energy scale and the one-residual-two-rulers reading of tiny Λ.

    bridge-paper.qmd §sec-lambda + footnote ^eq-lambda:
      * the dark-energy scale is the substrate scale, ρ_Λ^(1/4) ≈ m₁c² ≈ 2 meV;
      * the disequilibrium δT/T_c = √(ρ_Λ/ρ_ref) is order-unity (≈1.6) against
        the substrate density ρ_DM but 3.4e-62 against the Planck density
        ρ_Pl = c⁵/(ℏG²);
      * the two readings differ by exactly √(ρ_DM/ρ_Pl) = (m₁/M_Pl)², the same
        hierarchy (M_Pl² = ℏc/G) that makes G weak — small Λ and weak gravity
        are one number, not two.
    """
    rho_L  = rho_Lambda()
    rho_dm = rho_DM()
    rho_Pl = C**5 / (HBAR * G**2)                 # Planck density  [kg/m³]

    # Dark-energy scale in energy units: (ρ_Λ c² · (ℏc)³)^(1/4).
    rho_L14_eV = (rho_L * C**2 * (HBAR * C)**3) ** 0.25 / EV
    m1c2_eV    = m1_closepacked() * C**2 / EV

    dT_vs_DM = math.sqrt(rho_L / rho_dm)          # ≈ 1.6
    dT_vs_Pl = math.sqrt(rho_L / rho_Pl)          # ≈ 3.4e-62
    ratio    = (m1_closepacked() / M_planck())**2  # = √(ρ_DM/ρ_Pl)

    return {
        'rho_L14_meV':      rho_L14_eV * 1e3,
        'm1c2_meV':         m1c2_eV * 1e3,
        'dT_vs_DM':         dT_vs_DM,
        'dT_vs_Pl':         dT_vs_Pl,
        'ratio_m1_MPl_sq':  ratio,
        'ratio_from_rho':   math.sqrt(rho_dm / rho_Pl),   # identity check
    }


# ══════════════════════════════════════════════════════════════════════════
#  Result 3 — MOND from the superfluid  (§sec-mond)
# ══════════════════════════════════════════════════════════════════════════

def mond_acceleration() -> float:
    """MOND acceleration scale a₀ = c√(Gρ_DM)  [m/s²]  (@eq-a0).

    Parameter-free: every factor is a substrate quantity, with ρ_DM fixed by
    the bridge equation.  Predicts 1.16e-10, within ~3% of g†.
    """
    return C * math.sqrt(G * rho_DM())


def cosmic_coincidence() -> float:
    """a₀/cH₀ = √(3Ω_DM/8π)  (footnote ^eq-a0).

    Applying Friedmann H₀² = (8πG/3)ρ_crit to a₀ = c√(Gρ_DM) makes the old
    "a₀ ≈ cH₀/6" coincidence the exact √(3Ω_DM/8π) — the 1/6 is Gauss's 8π,
    geometry's 3, and Ω_DM, nothing tuned.
    """
    Omega_DM = OMEGA_C_H2 / H_LITTLE**2
    return math.sqrt(3.0 * Omega_DM / (8.0 * math.pi))


NU_CONDENSATION = 8.35e8         # condensation number ν (electroweak / SC2 leg)


def landau_velocity_clean() -> float:
    """Clean-units prediction v_L = c·(4π/ν)^(1/3)  [m/s].

    outer-rim-onset.qmd §"The Selector Is External, and Gravity Is Cubic": the
    framework's forward, dimensionally-consistent law for the outer-rim velocity
    from c and the condensation number ν *alone* — no G, no projection.  With
    the electroweak ν ≈ 8.35e8 it gives ~740 km/s, matching the ω₀ξ target
    (749.5 km/s) to ~1.3% and the Ulysses fast-wind mean (751.5 km/s) to ~1.5%.
    In vortex variables it reads (ω₀/ω₁)³ ν = 4π — the outer-rotation phase-space
    volume equals one solid angle per condensed quantum.  This is the current
    from-constants formula (see substrate_atomic.py for ν).
    """
    return C * (4.0 * math.pi / NU_CONDENSATION) ** (1.0 / 3.0)


def landau_velocity_cubic() -> float:
    """The gravity-sector cubic v_L = (4π ν c² G)^(1/3)  [m/s], in raw SI.

    outer-rim-onset.qmd §"What is still owed": the *same* relation as the
    clean-units law above, but with the standing 2D→3D projection P = Gν²/c not
    yet applied, so in raw MKS it carries broken units and lands at ~398 km/s.
    The clean-units and cubic values differ by exactly (c/Gν²)^(1/3) ≈ 1.86 —
    the cube root of the projection residual c/Gν² ≈ 6.4 the chapter tracks —
    so the "~2× undershoot" is that missing projection, not a wrong formula.
    Reported for transparency only; the clean-units law is the prediction.
    """
    return (4.0 * math.pi * NU_CONDENSATION * C**2 * G) ** (1.0 / 3.0)


# ══════════════════════════════════════════════════════════════════════════
#  Result 4 — horizon thermodynamics and frame-dragging  (§sec-tier2)
# ══════════════════════════════════════════════════════════════════════════

def hawking_temperature(M: float) -> dict:
    """Hawking temperature  T_H = ℏc³/(8π G M k_B), exact  (^t2-hawking).

    bridge-paper.qmd §sec-tier2.  The sonic horizon of the same Painlevé–Gullstrand
    inflow: the surface gravity is κ = ½|dv_ebb²/dr|_{r_s} = c⁴/4GM, so
    k_B T_H = ℏκ/2πc = ℏc³/8πGM.  Because the acoustic metric is *exactly*
    Schwarzschild, Unruh's 1981 result follows exactly rather than by analogy.
    The prefactor 8π = 2×4π_SC2 is the framework's gravitational normalization —
    the same 8π as the Higgs VEV and the Friedmann equation.
    """
    kappa = C**4 / (4.0 * G * M)                       # surface gravity [1/s·m? -> m/s²/m]
    T_H = HBAR * C**3 / (8.0 * math.pi * G * M * KB)
    return {
        'kappa':     kappa,
        'T_H':       T_H,
        'r_s_km':    schwarzschild_radius(M) / 1e3,
    }


def frame_dragging() -> dict:
    """Geodetic + Lense–Thirring rates for Gravity Probe B  [mas/yr]  (^eq-fd).

    bridge-paper.qmd §sec-feedback, footnote ^eq-fd.  A rotating mass entrains the
    azimuthal part of the gravitational inflow (Step 3) through the same mutual
    friction that couples rotating superfluid helium, so for a slow rotator the
    framework reproduces GR exactly.  Geodetic (de Sitter) precession is
    (3/2)(GM/c²r) ω_orb with ω_orb = √(GM/r³); the orbit-averaged frame-drag
    (Lense–Thirring) of the gyroscope is G J/(2c²r³) with J = I_c M R² Ω_⊕.
    Predicts ~6604 and ~41 mas/yr against GP-B's 6601.8 and 37.2 mas/yr.
    """
    r = R_GPB
    omega_orb = math.sqrt(GM_EARTH / r**3)
    geodetic = 1.5 * GM_EARTH / (C**2 * r) * omega_orb          # rad/s
    J = I_EARTH_C * M_EARTH * R_EARTH**2 * OMEGA_EARTH          # Earth spin ang. mom.
    frame_drag = (GM_EARTH / M_EARTH) * J / (2.0 * C**2 * r**3) # G J /(2c²r³)  [rad/s]
    return {
        'J_earth':        J,
        'geodetic':       geodetic * MAS_PER_RAD * YR,
        'frame_drag':     frame_drag * MAS_PER_RAD * YR,
        'geodetic_obs':   GEODETIC_GPB_OBS,
        'frame_drag_obs': FRAMEDRAG_GPB_OBS,
    }


# ══════════════════════════════════════════════════════════════════════════
#  Report
# ══════════════════════════════════════════════════════════════════════════

def _disc(pred: float, obs: float) -> str:
    return f'{100.0 * (pred - obs) / obs:+.1f}%'


def main() -> None:
    line = '═' * 78
    print(line)
    print('  SUBSTRATE — GRAVITY-SECTOR RESULTS')
    print('  reproducing arxiv/bridge-paper.qmd  §sec-gravity')
    print(line)

    # ── 1. Painlevé–Gullstrand / Schwarzschild metric ──────────────────────
    print('\n  1. Painlevé–Gullstrand / Schwarzschild metric   (§sec-pg)')
    print('  ' + '-' * 74)
    pg = verify_pg_metric(M_SUN)
    print(f'    v_ebb = √(2GM/r) fed into the acoustic metric (@eq-acoustic):')
    print(f'       g_tt (acoustic)      = {pg["g_tt_acoustic"]:.6e}')
    print(f'       g_tt (Schwarzschild) = {pg["g_tt_schwarz"]:.6e}')
    print(f'       identical PG form?     {"YES" if pg["match"] else "NO"}'
          f'   (horizon at v_ebb=c: v/c = {pg["v_at_horizon_over_c"]:.3f})')
    print(f'    Schwarzschild radius r_s(Sun) = 2GM/c² = {pg["r_s"]/1e3:.3f} km')
    print(f'    "pressure gravitates": ρ_eff = ρ+3P/c² = {rho_eff_factor():.0f}ρ'
          f'   ⇒  4πG → 16πG   (^eq-factor4)')

    print(f'\n    Classical tests (geodesics of @eq-schwarzschild):')
    prec = perihelion_precession(M_SUN, A_MERC, E_MERC, T_MERC)
    defl = light_deflection(M_SUN, R_SUN)
    zsun = gravitational_redshift(M_SUN, R_SUN)
    print(f'      {"Test":<34}{"Predicted":>14}{"Observed":>14}{"Disc.":>9}')
    print('      ' + '-' * 68)
    print(f'      {"Mercury perihelion (″/century)":<34}{prec:>14.2f}'
          f'{MERC_PREC_OBS:>14.2f}{_disc(prec, MERC_PREC_OBS):>9}')
    print(f'      {"Light deflection, solar limb (″)":<34}{defl:>14.3f}'
          f'{DEFLECT_OBS:>14.3f}{_disc(defl, DEFLECT_OBS):>9}')
    print(f'      {"Solar surface redshift (z)":<34}{zsun:>14.3e}'
          f'{2.12e-6:>14.3e}{_disc(zsun, 2.12e-6):>9}')
    shap = shapiro_delay(M_SUN, R_SUN, AU, AU) * 1e6            # one-way, μs
    print(f'      {"Shapiro delay, grazing (μs)":<34}{shap:>14.1f}'
          f'{"~119":>14}{"exact":>9}')

    # ── 2. The small cosmological constant ─────────────────────────────────
    print('\n  2. The small cosmological constant   (§sec-lambda)')
    print('  ' + '-' * 74)
    cc = cosmological_constant()
    print(f'    Dark-energy scale  ρ_Λ^(1/4)   = {cc["rho_L14_meV"]:.2f} meV'
          f'   (observed dark-energy scale)')
    print(f'    Substrate scale    m₁c²        = {cc["m1c2_meV"]:.2f} meV'
          f'   (close-packing, ^eq-lambda)')
    print(f'    ⇒ same substrate scale — dissolves the ρ_Λ ~ ρ_DM coincidence')
    print(f'\n    One residual disequilibrium, two rulers:')
    print(f'      δT/T_c  vs ρ_DM (substrate) = {cc["dT_vs_DM"]:.2f}'
          f'          (order unity — nothing tuned)')
    print(f'      δT/T_c  vs ρ_Pl (gravity)   = {cc["dT_vs_Pl"]:.2e}'
          f'   (the notorious tiny Λ)')
    print(f'      ratio = (m₁/M_Pl)²          = {cc["ratio_m1_MPl_sq"]:.2e}')
    print(f'            = √(ρ_DM/ρ_Pl)         = {cc["ratio_from_rho"]:.2e}'
          f'   (identical ⇒ small Λ & weak G are one number)')

    # ── 3. MOND from the superfluid ────────────────────────────────────────
    print('\n  3. MOND from the superfluid   (§sec-mond)')
    print('  ' + '-' * 74)
    a0 = mond_acceleration()
    coinc = cosmic_coincidence()
    print(f'      {"Quantity":<30}{"Predicted":>14}{"Observed":>14}{"Disc.":>9}')
    print('      ' + '-' * 68)
    print(f'      {"MOND scale a₀ = c√(Gρ_DM)":<30}{a0:>14.3e}'
          f'{G_DAGGER_OBS:>14.3e}{_disc(a0, G_DAGGER_OBS):>9}')
    print(f'      {"Coincidence √(3Ω_DM/8π)":<30}{coinc:>14.4f}'
          f'{0.179:>14.4f}{_disc(coinc, 0.179):>9}')
    print(f'\n    Outer-rim velocity (v_L = ω₀ξ, now predicted from constants):')
    vL = 0.0025 * C                               # ω₀ξ ≈ 0.0025 c, target
    vL_clean = landau_velocity_clean()
    print(f'      target       v_L = ω₀ξ ≈ 0.0025c   = {vL/1e3:.1f} km/s')
    print(f'      prediction   v_L = c(4π/ν)^⅓       = {vL_clean/1e3:.1f} km/s'
          f'   ({_disc(vL_clean, vL)} vs ω₀ξ)')
    print(f'      Ulysses fast solar wind             = {V_ULYSSES/1e3:.1f} km/s'
          f'   ({_disc(vL_clean, V_ULYSSES)})')
    print(f'      [note] the raw-SI cubic (4πν c²G)^⅓ = '
          f'{landau_velocity_cubic()/1e3:.0f} km/s is the *same* relation')
    print(f'             missing the 2D→3D projection P=Gν²/c; the two differ by'
          f' exactly')
    print(f'             (c/Gν²)^⅓ ≈ 1.86 — see docstring / outer-rim-onset.qmd.')

    # ── 4. Horizon thermodynamics and frame-dragging ───────────────────────
    print('\n  4. Horizon thermodynamics and frame-dragging   (§sec-tier2)')
    print('  ' + '-' * 74)
    hw = hawking_temperature(M_SUN)
    print(f'    Hawking T_H = ℏc³/(8π G M k_B)  (solar mass) = {hw["T_H"]:.2e} K'
          f'   (^t2-hawking)')
    print(f'      surface gravity κ = c⁴/4GM, horizon r_s = {hw["r_s_km"]:.3f} km;'
          f' 8π = 2×4π_SC2')
    fd = frame_dragging()
    print(f'\n    Gravity Probe B (Lense–Thirring), Earth J = {fd["J_earth"]:.3e} kg·m²/s'
          f'   (^eq-fd):')
    print(f'      {"Rate":<28}{"Predicted":>14}{"Observed":>14}{"Disc.":>9}')
    print('      ' + '-' * 62)
    print(f'      {"Geodetic (mas/yr)":<28}{fd["geodetic"]:>14.1f}'
          f'{fd["geodetic_obs"]:>14.1f}{_disc(fd["geodetic"], fd["geodetic_obs"]):>9}')
    print(f'      {"Frame-drag (mas/yr)":<28}{fd["frame_drag"]:>14.1f}'
          f'{fd["frame_drag_obs"]:>14.1f}{_disc(fd["frame_drag"], fd["frame_drag_obs"]):>9}')
    print(line)


if __name__ == '__main__':
    main()
