APIs and integrationsEdit on GitHubSource: docs/ARCHITECTURE.md

Kshana — Architecture

Kshana is one engine organised in layers over a shared core: the sensor packs (clock, inertial, time-transfer, hybrid); an astrodynamics / numerical layer (analytic SGP4/SDP4 (SGP4: Simplified General Perturbations 4; SDP4: Simplified Deep-space Perturbations 4), a numerical Cowell propagator with its seven-perturbation force model, maneuver design, orbit determination from ground-station ranges, and the force-model fit against agency precise ephemerides); a time & reference-frame layer (IERS (International Earth Rotation and Reference Systems Service) time scales, IAU (International Astronomical Union) 2006/2000A precession–nutation, and the CIO (Celestial Intermediate Origin) GCRS↔ITRS (GCRS: Geocentric Celestial Reference System; ITRS: International Terrestrial Reference System) reduction); a fusion layer (the GNSS/INS (GNSS: global navigation satellite system; INS: inertial navigation system) estimators); and the integrity, resilience, alt-PNT (PNT: positioning, navigation and timing) and lunar layers (RAIM/ARAIM/SBAS (RAIM: receiver autonomous integrity monitoring; ARAIM: advanced receiver autonomous integrity monitoring; SBAS: satellite-based augmentation system), jamming and multi-layer spoof detection, gravity/terrain/magnetic map-matching, and cislunar PNT); and the newer LEO-PNT (low Earth orbit PNT), spectrum, solar-system and constellation-design, network-timing and campaign layers, with the report, animation and interoperability writers that every run can use (§1d). Across all of them the engine knows nothing about "quantum" vs "classical": it drives sensor error models through a GNSS-outage scenario, runs an estimator, and scores the outcome. A quantum and a classical device are therefore compared on the same scenario, differing only in their (published, cited) error parameters and their independent noise seeds.

This document collects the structural and behavioural diagrams. §1 is the sensor-pack core; §1a maps the astrodynamics, fusion, and alt-PNT layers added since; §1b–§1d the study, domain and output layers. For usage see the README; for what is and isn't validated see VALIDATION; for the per-capability maturity table see CAPABILITY.

Abbreviations used in the diagrams and not spelled out where they appear: ADEV, Allan deviation; BW, bandwidth; CAI, cold-atom interferometer; CZML, the Cesium Language; Δ-DOR, delta differential one-way ranging; DO-229E, the RTCA (Radio Technical Commission for Aeronautics) SBAS receiver standard; EKF, extended Kalman filter; FM, frequency modulation; GSE, ground support equipment; HPL/VPL, horizontal/vertical protection level; IMU, inertial measurement unit; IOAG, Interagency Operations Advisory Group; IQ, in-phase/quadrature samples; ISL, inter-satellite link; ITU-T, the Telecommunication Standardization Sector of the International Telecommunication Union; J2–J6, the Earth's zonal gravity harmonics of degree 2 to 6; KIF, the Kshana Interchange Format; KML, Keyhole Markup Language; KPI, key performance indicator; L1/L5, GNSS carrier bands (the L2 of the CR3BP halo orbits is instead the Earth–Moon Lagrange point 2); LEO, low Earth orbit; LOO, leave-one-out; LTC, Coordinated Lunar Time; MEO, medium Earth orbit; MTIE, maximum time interval error; N2, the order-2 Hill diversity number; NED, north-east-down; NTN, non-terrestrial network; OD, orbit determination; OPS-SAT, the European Space Agency's in-orbit software laboratory satellite; PPP, precise point positioning; PSD, power spectral density; PVT, position, velocity and time; RINEX, Receiver Independent Exchange Format; RK4 / RK5(4), fourth-order and embedded fifth(fourth)-order Runge–Kutta integrators; SH, spherical harmonic; SigMF, Signal Metadata Format; SIR, sampling importance resampling; SRIF, square-root information filter; SRP, solar radiation pressure; SSC, spectral separation coefficient; STK, Systems Tool Kit; SVG, Scalable Vector Graphics; TDEV, time deviation; TDM, the CCSDS (Consultative Committee for Space Data Systems) Tracking Data Message; TLE, two-line element set; TPL, Timing Protection Level; UBX, the u-blox binary protocol; UHF, ultra high frequency; VLBI, very-long-baseline interferometry; VRW / ARW, velocity / angle random walk; WASM, WebAssembly.


1. Module structure#

flowchart TD
    main["main.rs<br/>CLI (thin wrapper)"]
    py["python.rs<br/>PyO3 (feature)"]
    wasm["wasm.rs<br/>wasm-bindgen (feature)"]
    api["api.rs<br/>run_toml: parse · dispatch by kind · json+svg+summary"]

    subgraph shared["Shared core"]
      types["types.rs<br/>Seconds · TimeGrid · ModelSpec"]
      scenario["scenario.rs<br/>GnssState · GnssTimeline · ClockCfg · Scenario"]
      allan["allan.rs<br/>overlapping Allan deviation"]
    end

    subgraph pack1["Pack 1 · Clock holdover"]
      models["models.rs<br/>ErrorModel · ClockModel (incl. flicker FM)"]
      estimator["estimator.rs<br/>HoldoverEstimator"]
      kalman["kalman.rs<br/>KalmanClock → Integrity bound"]
      security["security.rs<br/>clock-aided spoof detection → Security"]
      fom["fom.rs<br/>Sample · FoMScores · score · worst_case_holdover"]
      report["report.rs<br/>RunResult · hash · to_svg"]
      run["run.rs<br/>run / run_clock / run_orbit_clock"]
    end

    inertial["inertial/<br/>Pack 2 · AccelModel (accel + gyro + bias instability/RW) · run_inertial · quantum_imu CAI + QuantumNavBudget dead-reckoning-over-holdover"]
    timetransfer["timetransfer.rs<br/>Pack 3 · TimeTransferLink · run_timetransfer"]
    hybrid["hybrid.rs<br/>Pack 4 · run_suite · score_hybrid · run_hybrid (+ integrity/security)"]
    fusion["fusion/<br/>joint Kalman PNT estimator · run_fusion"]
    orbit["orbit.rs<br/>Propagator (Kepler | SGP4) · Walker / TLE / multi-constellation · visibility · DOP"]
    tle["tle.rs<br/>two-line element parsing (line 2 → Kepler, full TLE → SGP4)"]
    sgp4mod["sgp4.rs<br/>SGP4 / SDP4 propagator (deep-space + resonance)"]
    ensemble["ensemble.rs<br/>Monte Carlo confidence bands"]
    sweep["sweep.rs<br/>trade-study parameter sweeps"]
    spoof["spoof.rs<br/>active spoofing-attack demonstrator"]
    jamming["jamming.rs<br/>link-budget anti-jam C/N₀ (J/S · processing gain · Q)"]
    navsignal["navsignal.rs<br/>signal level: BPSK-R / BOC PSD · SSC κ · Gabor BW · DLL jitter · multipath · ranging-code (m-seq/Gold) design-trade"]
    holdover["holdover.rs<br/>GNSS-denied clock holdover: van-Loan coast → holdover-to-threshold · quantum-clock classes"]
    verification["verification.rs<br/>machine-checked requirement→module→test→oracle→status matrix"]

    main --> api
    py --> api
    wasm --> api
    api --> run
    api --> inertial
    api --> timetransfer
    api --> hybrid
    api --> ensemble
    api --> sweep
    api --> fusion
    api --> spoof
    spoof --> security
    api --> jamming
    api --> holdover
    api --> verification
    navsignal -. derives anti-jam Q (κ → Q=1/(R_c·κ)) .-> jamming
    jamming -. uses PSD / SSC .-> navsignal
    holdover -. coast-variance vs van-Loan recursion .-> kalman
    ensemble --> run
    sweep --> run
    orbit --> tle
    tle --> sgp4mod
    orbit -. SGP4 propagator .-> sgp4mod
    fusion -. composes .-> models
    fusion -. composes .-> inertial
    fusion --> kalman

    run --> models
    run --> estimator
    run --> kalman
    run --> security
    run --> fom
    run --> report
    run --> orbit
    models --> types
    scenario --> types
    inertial --> scenario
    timetransfer --> types
    orbit --> scenario
    hybrid -. composes .-> models
    hybrid -. composes .-> estimator
    hybrid -. composes .-> inertial
    hybrid -. composes .-> timetransfer
    pack1 --> shared
    inertial --> allan

The CLI (command-line interface) and both bindings funnel through one api::run_toml entry point, so they never drift. The packs reuse the shared core (types, scenario, allan); Pack 4 (hybrid) composes the models and estimators of Packs 1–3 rather than reimplementing them; orbit derives a GNSS timeline from geometry that then feeds the Pack 1 run. The navsignal module sits at the signal level between the link budget and the measurement domain: it derives the spectral-separation coefficient κ from the actual signal and jammer power spectra, from which jamming now computes its anti-jam Q = 1/(R_c·κ) rather than taking a representative constant (cross-checked in CI (continuous integration)); it also carries the ranging-code (m-sequence/Gold) design-trade.

Three further modules form the GNSS-denied resilience spine. holdover answers the operational question directly — how long can this clock free-run before its timing error exceeds budget? — by exposing the van-Loan coast-error closed form as a holdover-to- threshold inversion, cross-checked against the multi-step clock_state covariance recursion. Its inertial twin lives in inertial::quantum_imu (QuantumNavBudget), which composes the cold-atom-interferometer white-noise drift with bias and scale-factor error into a position-drift-over-holdover budget (the bias term cross-checked against the independent AccelModel integrator). verification renders the whole engine's requirement to module to test to oracle to status matrix, with unit-tested invariants that forbid a validated label without an independent external oracle — so the assurance claim cannot drift from the code.

1a. Astrodynamics, fusion & alt-PNT layers#

Beyond the sensor-pack core, three subsystems share the same shared core and feed (or are fed by) orbit. The astrodynamics / numerical layer adds a non-analytic Cowell propagator alongside the analytic SGP4/SDP4 path; the fusion layer carries the GNSS/INS estimators; and the alt-PNT layer is GPS-denied (GPS: Global Positioning System) gravity-map matching. These are library/scenario capabilities (see CAPABILITY for which are wired to a scenario kind vs reachable as a Rust API (application programming interface)).

flowchart TD
    subgraph astro["Astrodynamics & numerical"]
      orbit2["orbit · sgp4 · tle · walker<br/>analytic SGP4/SDP4 · Walker design"]
      prop["propagator<br/>Cowell driver (accel_at / accel_rv)"]
      forces["forces<br/>two-body · J2–J6 · EGM2008 d/o-70 · 3rd-body · SRP · drag · Schwarzschild + Lense–Thirring · tides"]
      integ["integrator<br/>RK4 step-doubling · Dormand–Prince RK5(4)"]
      ephem["ephem<br/>low-precision Sun & Moon"]
      man["maneuver<br/>impulsive/finite burns · Izzo Lambert · porkchop"]
      od["orbit_determination<br/>Gauss–Newton batch (batch_ls) · sequential UKF"]
    end
    subgraph fusion["Fusion (GNSS/INS)"]
      ekf["fusion/gnss_ins_ekf · closed_loop · pack<br/>15-state loosely-coupled EKF"]
      tc["fusion/tightly_coupled (8-state)<br/>fusion/tightly_coupled17 (17-state, quantum-CAI)"]
      ukf["fusion/ukf — sigma-point core"]
      coup["fusion/coupled — clock+position cross-covariance"]
    end
    subgraph alt["Alt-PNT (GPS-denied)"]
      grav["gravimeter<br/>cold-atom model + SH anomaly field + mascons"]
      pf["particle_filter — SIR"]
      mm["mapmatch — field-match likelihood"]
    end
    prop --> forces
    prop --> integ
    forces --> ephem
    man --> integ
    od --> forces
    od --> integ
    od --> ukf
    tc --> ukf
    tc -. coasts on .-> forces
    grav --> pf
    mm --> pf
    grav --> mm
    ekf -. drives .-> strap["inertial/ strapdown (quaternion · NED · IMU errors)"]
    tc -. drives .-> strap
    grav -. CAI floor .-> strap

The numerical propagator's force terms are off by default, so enabling them never perturbs the released goldens. The 17-state tightly-coupled UKF (unscented Kalman filter) coasts a GNSS outage on the quantum-CAI accelerometer's derived velocity-random-walk; orbit determination reuses the same forces/integrator to propagate a candidate state across the tracking arc.

The same shared core also carries four further subsystems not drawn above (their packs appear in the dispatch of §4): a time & reference-frame layer (timescales, jd2, precession, nutation, cio, frames, eop) reducing TEME↔GCRS↔ITRS (TEME: true equator, mean equinox) and feeding the ephemeris/ground-track pack; an integrity layer (raim, sbas) for RAIM/ARAIM/SBAS; a resilience layer (jamming, spoof, spoof_detect, spoof_monitors, detection) for jamming and multi-layer spoof detection, with a nav-signal layer (navsignal) at the signal level — BPSK-R (BPSK: binary phase-shift keying) / sine-BOC (BOC: binary offset carrier) power spectral densities, the spectral-separation coefficient κ that now derives jamming's anti-jam Q, the RMS (Gabor) ranging bandwidth, the coherent early–late DLL (delay-locked loop) code-tracking jitter, and the multipath error envelope (signal-performance analysis, not RF-payload (RF: radio-frequency) / antenna hardware design — a payload partner's role); and a lunar / cislunar layer (lunar, lunar_frame, lunar_od, cr3bp) for CR3BP (circular restricted three-body problem) dynamics — including the 6×6 state-transition matrix and a single-shooting differential corrector (cr3bp_jacobian, propagate_state_stm, differential_correct_halo) that produces genuinely periodic halo/NRHO (NRHO: near-rectilinear halo orbit) orbits, reproducing the published L2 southern 9:2 NRHO (the Gateway orbit) at period ≈ 6.57 d / perilune ≈ 3,250 km (published ≈ 6.56 d / ≈ 3,370 km); the selenocentric MCI/MCMF (MCI: Moon-centred inertial; MCMF: Moon-centred, Moon-fixed) transform of a corrected orbit and family-continuation remain follow-ons, and the NRHO is a CR3BP (circular, Sun-free) solution, not validated against a real LANS/Gateway (LANS: Lunar Augmented Navigation Service) ephemeris — and LunaNet integrity. The agency-ephemeris force-model fit (precise_od, lunar_od, tides, gravity_sh) and the full alt-PNT field set (igrf, altpnt/terrain, gravimeter) round out the module list; see CAPABILITY for the per-module maturity.

1b. Resilience studies, machine-learning evaluation & reproducible artifacts#

Five further module groups form the open, reproducible-study layer behind the project's research papers. Unlike the scenario kinds, these are reached as cargo run --example / --bin generators that write byte-deterministic artifacts (fixed seeds, recorded engine version + config hash). All are MODELLED (synthetic or public-dataset calibration) and carry honest validation labels — none is a certification.

flowchart TD
    gen["study generators<br/>examples/ · bin/ (cargo run) → byte-deterministic artifacts"]

    subgraph timing["Timing integrity"]
      tplm["tpl<br/>conditional Timing Protection Level: k-σ floor · van-Loan coast · CUSUM"]
    end
    subgraph resilm["Resilience scoring & decision-instability"]
      arch["resilience/arch · score<br/>architecture model + RPCF-aligned scoring"]
      stats["resilience/stats<br/>Dirichlet · Kendall-τ · flip-rate · bootstrap"]
      div["resilience/diversity · timeline<br/>Hill-N2 common-mode collapse · KPIs"]
      study["resilience/study · report · panel<br/>instability study + integrity-hashed assurance report"]
    end
    subgraph aiml["RF-impairment optimism-gap (Machine learning)"]
      ie["impairment_eval<br/>labelled synthetic corpus · ROC/AUC harness"]
      is["impairment_study<br/>13-detector panel · scaling laws · LOO predictor"]
      ml["impairment_ml<br/>logistic-regression + one-hidden-layer MLP"]
      es["eval_stats<br/>bootstrap CI · DeLong · Spearman · ridge"]
      sdrm["sdr · realdata<br/>IQ/IF → E/P/L taps → SQM + ingest adapters"]
    end
    subgraph trade["Quantum-vs-classical"]
      cross["crossover<br/>resilience crossover map under parameter uncertainty"]
      qt["quantum_trade<br/>measured-ADEV holdover benefit vs classical"]
    end

    gen --> timing
    gen --> resilm
    gen --> aiml
    gen --> trade
    tplm -. cross-checked vs .-> recursion["holdover · clock_state · allan"]
    is --> ie
    is --> ml
    is --> es
    ie -. real recordings via .-> sdrm
    cross -. composes .-> packs2["clock · inertial packs"]
  • tpl — the conditional Timing Protection Level: a holdover-limited bound on the undetected time error under spoofing, composing a k-σ monitor floor, the van-Loan coast variance over the detection latency, and a CUSUM (cumulative sum) time-to-alarm; calibrated on a real recorded spoof (JammerTest 2024). There is no finite unconditional bound — the TPL is conditional on an independent cross-check detecting the attack.
  • resilience/ — a framework-aligned PNT-resilience scoring engine (DHS (Department of Homeland Security) RPCF (Resilient PNT Conformance Framework) categories) plus a decision-instability study: a Dirichlet weighting simplex, Kendall-τ rank instability, top-1 winner flip rate, and common-mode diversity collapse (Hill-N2), with an integrity-hashed assurance report and 35 hand-derived oracle tests. See RESILIENCE-CROSSWALK. Synthetic architectures aligned to RPCF v2.0 — a self-assessment, not a certification.
  • impairment_eval / impairment_study / impairment_ml / eval_stats — the RF-impairment optimism-gap study: a labelled synthetic corpus and detector-agnostic ROC/AUC (ROC: receiver operating characteristic; AUC: area under the curve) harness, a 13-detector panel (energy/AGC/SQM/parity (AGC: automatic gain control; SQM: signal-quality monitoring) plus seeded logistic-regression and one-hidden-layer-MLP (MLP: multi-layer perceptron) detectors), in- vs out-of-distribution scaling laws with a permutation null, and a leave-one-out predictor of out-of-distribution degradation. The eval metrics are validated bit-for-bit against scikit-learn.
  • sdr / realdata — a software-defined-receiver front end (raw IQ/IF (IQ: in-phase/quadrature; IF: intermediate frequency) → correlator early/prompt/late taps → SQM) and ingest adapters (RINEX, u-blox UBX, GnssLogger, JammerTest, Yunnan, SatGrid) that let the same detectors run over recordings supplied locally; no datasets are committed to the repo.
  • crossover / quantum_trade — the quantum-vs-classical resilience crossover map under parameter uncertainty, and the measured-ADEV holdover-benefit trade; the trade numerical kernels are validated against scipy.

1c. Integrity, GNSS, deep-space, lunar & mission layers#

The remaining domains plug into the same api dispatch and reuse the shared core, frames and geometry. Everything here is MODELLED unless a verification-matrix row cites an external oracle (RAIM kernel vs SciPy, SBAS vs the RTKLIB (an open-source real-time kinematic positioning library) fork, the gnss_lib_py DOP (dilution of precision) kernel, the OPS-SAT eval, the lunar ARAIM protection-level kernel, the cross-provider lunar ephemeris consistency) — the rest of the lunar suite and the quantum demonstrator are modelled, illustrative, public-source, and carry no TRL (technology readiness level) / heritage / agency-endorsement claim.

flowchart TD
    api["api.rs — run_toml / run_scenario · 75 kinds"]

    subgraph gnss["Integrity & GNSS measurement"]
      raim["raim — RAIM/ARAIM · HPL/VPL (kernel vs SciPy)"]
      sbas["sbas — DO-229E PL · L1/L5 (vs RTKLIB fork)"]
      meas["gnss_sim · ionex · pvt · glonass — pseudorange · iono maps · single-point PVT"]
      imp["integrity_impact · frugal — miss→integrity · cost-per-coverage"]
    end

    subgraph deep["Deep-space & Mars"]
      core2["body · ephem_provider · clock_state — multi-body · ephemeris seam · onboard clock"]
      radio["radiometric · ccsds_tdm — light-time · Doppler/range · Δ-DOR · TDM"]
      mars["deepspace_od · mars_frame · mars_atmos · mars_pnt · linkbudget · gse_sim — SRIF OD · Mars frame · relay-PNT · GSE sim"]
    end

    subgraph lunar["Lunar PNT suite (MODELLED · illustrative public-source)"]
      cis["cr3bp · lunar · lunar_frame · lunar_od — CR3BP · halo/NRHO · cislunar ARAIM · MCI↔MCMF"]
      suite["lunar_time · lunar_vlbi · lunar_combination · lunar_frame_realise — LTC time · VLBI · joint OD+clock · frame realisation"]
      suite2["lunar_service · lunar_dpnt · lunar_interop — Moonlight service-volume · differential PNT · LunaNet/IOAG interop"]
    end

    subgraph quantum["Quantum-Enabled PNT demonstrator (MODELLED)"]
      qd["quantum_devices · quantum_faults · quantum_nav_od — device error models · fault catalogue · GNSS-free OD"]
      qt["qtrade · timetransfer_chain · representativeness — unified trade · time-transfer chain · gaps-to-flight ledger"]
    end

    subgraph mission["Mission analysis, environment & interop"]
      ma["launch · reentry · eo_payload · attitude_budget · passes · linkbudget — first-order budgets"]
      env["space_weather — Jacchia-71 density"]
      iop["rinex · rinex_obs · sp3 · oem · omm · ccsds_tdm · space_packet · interchange · permalink — interop formats + KIF"]
      ver["verification — requirement→module→test→oracle→status matrix"]
    end

    api --> gnss & deep & lunar & quantum & mission
    raim -. reuses .-> meas
    suite -. reuses validated DOP / SBAS kernels .-> gnss
    qt -. rides validated kernels (scipy / sklearn) .-> quantum

1d. LEO PNT, spectrum, solar system, network timing, campaigns & outputs#

The newest layers follow the same pattern: each is one or more scenario kinds dispatched from api, built on the shared core, and MODELLED unless a matrix row names an external oracle.

flowchart TD
    api["api.rs — run_toml / run_scenario"]

    subgraph leo["LEO PNT (low Earth orbit positioning, navigation and timing)"]
      lsig["leo_signal — signal design: acquisition, code tracking, GNSS compatibility, band trade"]
      lpass["leo_pass · leo_link (geometry · antenna · itu · iono · energy · spoof · presets) — pass and per-band link budget"]
      lnav["leo_navmsg (elements · fit · codec · sisre · services · text) — broadcast ephemeris and clock message"]
      lfus["leo_fusion (doppler · joint_pvt · ppp · ntn · timing · polar · presets) — fused MEO+LEO PVT · PPP · 5G NTN"]
      lchain["leo_pnt_chain — one system end to end, each stage handing its output to the next"]
    end

    subgraph rf["Spectrum"]
      spec["spectrum · navsignal — L-band (and UHF/S/C) PSD waterfall under a jammer timeline"]
      sig["sigmf — SigMF recording read/write · Welch PSD of complex IQ"]
    end

    subgraph sky["Solar system & constellations"]
      ss["solar_system · body · ephem · ephem_provider — bodies, constants, light time at one epoch"]
      bp["body_pnt — positioning around any body with a local constellation"]
      cd["constellation · walker — Walker delta/star, multi-shell, published GNSS slots, coverage/DOP maps"]
    end

    subgraph net["Network timing"]
      tel["telecom_timing — time error, MTIE, TDEV vs ITU-T masks"]
      slot["slot_timing — seconds until a free-running clock leaves a slot guard"]
    end

    subgraph comp["Composition & outputs"]
      camp["campaign · study · suite — chained phases, sweeps, Monte Carlo, shared conditions"]
      rep["advanced_report — report.html + report.json with a reproducibility record"]
      anim["animation — SVG / HTML / frame animations"]
      iop["interop (czml · kml · geojson · stk) · sigmf — exchange files for --export"]
    end

    api --> leo & rf & sky & net & comp
    lchain -. composes .-> lsig & lpass & lnav & lfus
    lsig -. SSC chain .-> spec
    lfus -. GNSS presets .-> cd
    bp -. reuses .-> ss
    camp -. runs other kinds through .-> api

The per-kind detail is in LEO-PNT, LEO-SIGNAL, LEO-PASS, LEO-NAVMSG, LEO-PNT-FUSION, SPECTRUM, CONSTELLATION-DESIGN, TELECOM-TIMING, SLOT-TIMING, CAMPAIGNS, REPORTS, ANIMATION and INTEROP.

2. Engine pipeline (per run)#

Each run steps a single sensor model through the time grid, disciplining it whenever GNSS is nominal and letting it free-run (holdover / dead-reckoning) during the outage.

flowchart TD
    A["for each time step t"] --> B{"i &gt; 0 ?"}
    B -- yes --> C["model.step(dt, rng)<br/>evolve noise state"]
    B -- no --> D
    C --> D{"GNSS state at t ?"}
    D -- "Nominal" --> E["discipline to truth<br/>(estimator sync / dead-reckoning reset)<br/>error = 0"]
    D -- "Denied/Degraded" --> F["estimator predicts;<br/>error = truth − prediction"]
    E --> G["record Sample(t, error, gnss)"]
    F --> G
    G --> A
    A -. after loop .-> H["score(series, spec)<br/>→ figures of merit"]
    H --> I["assemble Result<br/>(specs · series · FoM · scenario hash)"]

A scenario runs this pipeline twice — once for the quantum sensor, once for the classical sensor — with independent seeds (classical_seed = seed + 0x9e3779b97f4a7c15) so the two noise realizations are uncorrelated.

3. The error-model interface (the extension point)#

Every sensor implements the same idea: a stateful object whose step() advances its internal stochastic error and whose accumulated state is read out each tick. Clocks expose accumulated phase; accelerometers expose doubly-integrated position; links expose per-measurement jitter.

classDiagram
    class ErrorModel {
      <<trait>>
      +step(dt, rng)
      +spec() ModelSpec
    }
    class ClockModel {
      +y0, q_wf, q_rw, drift, flicker
      +phase() s
      +det_freq()  +drift_rate()
    }
    class AccelModel {
      +bias, q_va, gyro_bias, q_arw
      +pos() m  +theta() rad
      +reset()
    }
    class TimeTransferLink {
      +sigma_j
      +sample(rng) s
    }
    ErrorModel <|.. ClockModel
    ClockModel : white FM + random-walk FM + flicker FM + aging
    AccelModel : accel bias + VRW + gyro bias + ARW (gravity-tilt)
    TimeTransferLink : white timing jitter

ModelSpec { id, kind, provenance, params } travels into the result so every figure in the output is traceable to the published source named in provenance.

Alongside the analytic HoldoverEstimator, the clock pack runs a two-state (phase, frequency) Kalman filter (KalmanClock) whose process noise matches the truth model. Coasting through an outage, its phase-error variance grows to exactly q_wf·T + q_rw·T³/3 — the analytic holdover relation — and its online 1-σ bound is used to populate the Integrity figure of merit (fraction of outage samples whose error stays inside the k-σ bound).

4. Dispatch (CLI and bindings)#

api::run_toml(src) is the single entry point: it peeks the top-level kind, deserializes the matching scenario, runs the pack, and returns { json, svg, summary, csv } (csv is present only for the kinds that publish a reproducibility table). The CLI writes those to files, plus the report (report.html, report.json) and any --export / --animate output; the Python and WebAssembly bindings return them to the host. One dispatch, no drift.

flowchart TD
    F["api::run_toml(src) · run_scenario(src)"] --> K{"ScenarioKind::classify<br/>typed · exhaustive · 75 kinds<br/>(absent kind → clock; unknown kind → InvalidInput)"}
    K --> G1["Timing<br/>clock · timetransfer · quantum-time-transfer · telecom-timing · slot-timing"]
    K --> G2["Inertial & fusion<br/>inertial · hybrid · hybrid-ukf · fusion · gnss-ins · quantum-gnss-free-nav · ins-trn-coast · hybrid-optical-rf"]
    K --> G3["Orbit, geometry & positioning<br/>orbit · ephemeris · constellation-design · gnss-sim · pvt"]
    K --> G4["Integrity<br/>integrity · lunar-integrity · araim-reference-check"]
    K --> G5["Resilience & spectrum<br/>jamming · spoof · spoof-detect · quantum-anomaly-detect · tracking-loop · spectrum · conflict-resilience"]
    K --> G6["Alt-PNT (GPS-denied)<br/>gravity-map · terrain-nav · terrain-slam · combined-altpnt"]
    K --> G7["LEO PNT<br/>leo-signal · leo-pass · leo-navmsg · leo-pvt · leo-ppp · ntn-positioning · leo-pnt-chain"]
    K --> G8["Lunar / cislunar suite (MODELLED)<br/>lunar-time-offset · lunar-time-budget · lunar-vlbi · lunar-vlbi-fim · lunar-joint-od-clock<br/>lunar-frame-realisation · lunar-frame-campaign · lunar-llr-datum · realtime-frame-eop<br/>moonlight-service-volume · lunar-differential-pnt · lunar-interop-export · lunar-beacon<br/>lunar-jamming · lunar-attack-surface · earth-gnss-lunar · cislunar-observability · cislunar-arc-recovery"]
    K --> G9["Deep space & solar system<br/>mars-pnt · solar-system · body-pnt"]
    K --> G10["Machine learning & trade<br/>impairment-eval · quantum-trade"]
    K --> G11["Mission analysis & environment<br/>launch-window · reentry · eo-coverage · attitude-budget · aperture-duty-cycle<br/>passes · link-budget · space-packet · space-weather"]
    K --> G12["Trade studies, campaigns & interop<br/>sweep · sweep-nd · campaign · oem-interop"]
    G1 & G2 & G3 & G4 & G5 & G6 & G7 & G8 & G9 & G10 & G11 & G12 --> W["RunOutput { json, svg, summary, csv }<br/>+ SHA-256 scenario_hash"]

Dispatch is on a typed ScenarioKind enum, matched exhaustively (see the next subsection), so adding a pack is a compile-checked change. An absent kind falls back to the clock pack, so existing single-kind scenarios deserialize unchanged (serde ignores the kind field on each scenario struct). An unrecognised kind is refused with KshanaError::InvalidInput, which names the nearest built-in kind when the name looks like a typo.

Typed dispatch and the structured API#

Dispatch is on a typed ScenarioKind enum, not a raw string match: ScenarioKind::classify(src) resolves the kind field to a variant, and the dispatcher matches on it exhaustively — adding a pack is a compile-checked change, not a string typo. Two typed surfaces sit alongside the string-returning run_toml (kept for the CLI and existing bindings):

  • run_scenario(src) -> Result<RunOutput, KshanaError> — the typed entry, with a structured error taxonomy (InvalidInput, NonConvergence, Unsupported, IoError). Each error carries a stable kind_tag() so a caller can branch on the failure category instead of parsing the message. The bindings expose this as error_kind(toml).
  • list_scenario_kinds() -> Vec<ScenarioMeta> (and list_scenario_kinds_json(), exposed in the bindings as list_kinds()) — programmatic introspection: each kind's name, description, and required/optional fields, for UI (user interface) and notebook auto-complete.

Extending Kshana with an external pack#

A third-party pack implements two small, semver-stable traits from api:

use kshana::api::{ExternalPack, KshanaError, RunOutput, Scenario, ScenarioMeta};

struct MyPack {
    threshold: f64, // the deserialized scenario fields
}

impl Scenario for MyPack {
    fn run(&self) -> Result<RunOutput, KshanaError> {
        // Run the model, then build the unified output envelope.
        Ok(RunOutput {
            json: format!("{{\"threshold\": {}}}", self.threshold),
            svg: String::from("<svg xmlns=\"http://www.w3.org/2000/svg\"/>"),
            summary: format!("my-pack | threshold {}", self.threshold),
            csv: None,
        })
    }
}

impl ExternalPack for MyPack {
    fn kind_name(&self) -> &'static str {
        "my-pack"
    }
    fn meta(&self) -> ScenarioMeta {
        ScenarioMeta {
            name: "my-pack",
            description: "An out-of-tree example pack.",
            required_fields: &["threshold"],
            optional_fields: &[],
        }
    }
}

ExternalPack also has a default register_into(&mut PackRegistry) method, a no-op unless a pack opts into the registry dispatch seam in src/registry.rs. The built-in jamming pack is wired through Scenario as the worked example; out-of-tree packs follow the same contract without forking core (mirroring the ErrorModel extension point in §3, which the private resilience overlay uses).

Kshana therefore has two stable extension seams — add a sensor by implementing ErrorModel (§3), or add a whole scenario kind by implementing Scenario + ExternalPack — both semver-stable, neither requiring a core fork:

classDiagram
    class ErrorModel {
      <<trait — sensor seam>>
      +step(dt, rng)
      +spec() ModelSpec
    }
    class Scenario {
      <<trait — pack seam>>
      +run() RunOutput
    }
    class ExternalPack {
      <<trait : Scenario>>
      +kind_name() str
      +meta() ScenarioMeta
    }
    ErrorModel <|.. ClockModel
    ErrorModel <|.. AccelModel
    ErrorModel <|.. TimeTransferLink
    Scenario <|.. ExternalPack
    Scenario <|.. JammingPack : worked example
    ExternalPack <|.. ThirdPartyPack : out-of-tree, no core fork

5. The hybrid capstone#

The hybrid pack runs a suite (one clock + one inertial sensor) and requires both timing and position to stay in spec; pnt_holdover is the time until either breaches. Optionally an optical inter-satellite link re-syncs the clock during the outage — time aiding only; position is not re-synced, because time transfer gives time, not position. This is what isolates the inertial sensor as the limiting factor.

flowchart LR
    subgraph suite["PNT suite (per technology)"]
      clk["clock → timing error"]
      acc["inertial sensor → position error"]
      isl["optical ISL<br/>re-sync clock at interval"] -. aids .-> clk
    end
    clk --> J["both within spec ?"]
    acc --> J
    J --> P["pnt_holdover = first breach<br/>(timing OR position)"]

6. Geometry-derived GNSS availability#

orbit.rs is a deterministic, dependency-free geometry layer. A Propagator is either the analytic Keplerian Orbit (two-body, optionally secular J2) or a full Sgp4 propagator built from a complete two-line element set; a Walker-delta generator produces synthetic constellations, and line-of-sight visibility = Earth occultation + elevation mask. The visible-satellite count maps to a GNSS state (≥4 = nominal, 1–3 = degraded, 0 = denied), and build_timeline turns that into the availability timeline that drives the standard clock-holdover run. Availability is therefore derived from geometry rather than hand-authored, while the run, estimator, and scoring stay unchanged.

A constellation supplied as full TLEs is propagated with the SGP4/SDP4 model in sgp4.rs (validated against the AIAA (American Institute of Aeronautics and Astronautics) 2006-6753 vectors); line-2-only elements keep the analytic two-body path. The two can be mixed within one constellation block.

flowchart LR
    U["user orbit"] --> V
    C["Walker constellation"] --> V["visible_count(t)<br/>occultation + mask"]
    V --> S["gnss_state: ≥4 / 1–3 / 0"]
    S --> T["build_timeline → GnssTimeline"]
    T --> R["run_orbit_clock → clock holdover"]

7. Bindings#

The core compiles unchanged to native, to a Python extension, and to WebAssembly. The Python (python.rs, PyO3 abi3) and WebAssembly (wasm.rs, wasm-bindgen) modules are optional, feature-gated dependencies (--features python / --features wasm): the default build and the test suite never compile them, while the dependency-audit gate (cargo deny --all-features) does inspect their dependency trees, because those crates ship inside the wheel and the npm package. Both call api::run_toml, so every surface returns identical results. The WebAssembly module backs the browser playground in web/ and exports thirteen functions: run, run_all (one engine run returning result, chart, summary and table together), chart_svg, summary, table_csv, list_kinds, error_kind, version, the encode_permalink / decode_permalink shareable-URL (URL: web address) codec, and the three exporters export_sp3 / export_omm / export_oem that back the playground's export menu. Two further front doors reach the same api: the MCP (Model Context Protocol) server (mcp/kshana-mcp, a workspace-excluded rmcp crate exposing fourteen tools — run_scenario, list_scenario_kinds, validate_scenario, list_example_scenarios, get_example_scenario, report_scenario, animate_scenario, list_export_formats, export_interop, import_route, export_sp3, export_omm, export_oem, export_table_csv; the two example tools read the bundled scenarios through the library's off-by-default bundled-scenarios feature, which only this server turns on) and the JetBrains IDE (integrated development environment) plugin (ide/jetbrains, a Kotlin project that shells out to the kshana CLI rather than linking the library).

flowchart LR
    cli["CLI · main.rs<br/>native binary"] --> api
    py["Python · python.rs (PyO3 abi3)<br/>RunOutput class + run · run_full · run_typed · scenario_kinds · list_kinds · validate_toml · error_kind · version"] --> api
    wasm["WebAssembly · wasm.rs (wasm-bindgen)<br/>run · run_all · chart_svg · summary · table_csv · list_kinds · error_kind · version · encode/decode_permalink<br/>export_sp3 · export_omm · export_oem"] --> api
    mcp["MCP server · mcp/kshana-mcp (rmcp)<br/>tools: run_scenario · list_scenario_kinds · validate_scenario<br/>list_example_scenarios · get_example_scenario<br/>report_scenario · animate_scenario<br/>list_export_formats · export_interop · import_route<br/>export_sp3 · export_omm · export_oem · export_table_csv"] --> api
    ide["JetBrains plugin · ide/jetbrains (Kotlin)"] -- spawns process --> cli
    api["api::run_toml / run_scenario / list_scenario_kinds"] --> out["identical { json, svg, summary } on every surface"]

Feature-gating: Python and WASM are --features python / --features wasm; the bundled scenario table is --features bundled-scenarios (the MCP server's only use of it); the MCP server and the xval/* cross-checks are workspace-EXCLUDED crates; the IDE plugin links nothing — it runs the CLI.

8. Determinism & reproducibility#

  • All randomness flows through a single seeded ChaCha8Rng per run; the step order is fixed, so (scenario, seed, engine version) → identical bits.
  • The result carries a SHA-256 (SHA: Secure Hash Algorithm) scenario_hash; scripts/check-reproducible.sh runs a reference scenario twice and asserts byte-identical output.
  • The same engine compiles to native, to a Python extension, and to wasm32-unknown-unknown for in-browser runs producing the same numbers.

9. Deferred / future structure#

The astrodynamics, fusion, and alt-PNT layers in §1a — the full SGP4/SDP4 propagator, the numerical Cowell propagator with its seven-perturbation force model and two adaptive integrators, maneuver/trajectory design, orbit determination, the 15-/8-/17-state GNSS/INS estimators, the coupled clock+position filter, and gravity-map matching — have all shipped, alongside the Security FoM with an active spoof demonstrator, real TLE/multi-constellation geometry, Monte-Carlo bands, trade-study sweeps, the printable HTML (HyperText Markup Language) report with its machine-readable twin, and the release/publish/wheels/pages workflows.

Several capabilities once listed here as future work have since shipped and are covered above or in VALIDATION: the full IAU 2000A nutation and the equinox-free CIO GCRS↔ITRS / ITRF (International Terrestrial Reference Frame) reduction (validated bit-for-bit against SOFA/ERFA (SOFA: Standards of Fundamental Astronomy; ERFA: Essential Routines for Fundamental Astronomy)), the EGM2008 (Earth Gravitational Model 2008) geopotential to degree/order 70, the Lense–Thirring frame-dragging term, solid/ocean/atmospheric tides, a DE-grade (DE440/ANISE (DE440: Development Ephemeris 440; ANISE: Attitude, Navigation, Instrument, Spacecraft, Ephemeris — a pure-Rust planetary-geometry toolkit)) ephemeris cross-validation, the external Orekit 12.2 cross-validation of the numerical Cowell propagator (0.08 m over 24 h) and of batch and sequential orbit determination, and the 17-state tightly-coupled navigator surfaced as the hybrid-ukf scenario kind.

The remaining follow-ons are tracked in CHANGELOG [Unreleased] and the per-capability roadmap in CAPABILITY: a higher-degree (e.g. 200×200) EGM (Earth Gravitational Model) tesseral field and loader beyond the shipped degree/order-70 path, the NRLMSISE-00 (NRLMSISE: Naval Research Laboratory Mass Spectrometer and Incoherent Scatter Radar Extended atmosphere model) thermospheric density (the propagator's drag uses a static exponential density, and the space-weather kind's Jacchia-71 density is characterised against NRLMSISE-00 but not replaced by it), solar limb darkening / the oblate-Earth shadow, carrier-phase GNSS/INS tight coupling, and a real EGM2008/EIGEN (EIGEN: European Improved Gravity model of the Earth by New techniques) gravity map for the alt-PNT matcher.

A private overlay repo holds export-sensitive resilience depth; it plugs in via the same ErrorModel interface (and the ExternalPack contract in §4) without changing the public engine.