Changelog
All notable changes to Kshana are documented here.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning. While the project is pre-1.0, the public scenario/result schema may still change; breaking changes are called out explicitly.
Unreleased#
0.29.0 - 2026-10-01#
Twelve new scenario kinds and three new ways to read a run. The engine now covers the radio spectrum, the whole solar system, constellations around any body, campaigns that chain scenarios into one mission, and positioning, navigation and timing (PNT) from low Earth orbit (LEO) from the signal to the fix. Every run can also be written as an animation, a full report and a set of interoperability files for other tools.
Around the engine: a redesigned kshana.dev (a multi-page site, and a new Kshana Studio dashboard that runs every capability in the browser), a rewritten README, fourteen tools on the Model Context Protocol (MCP) server, a browser build that gives the same numbers as the native one, and a public page for Kshana Pro.
| 0.28.0 | 0.29.0 | |
|---|---|---|
| Scenario kinds | 63 | 75 |
| Scenario files (plus one suite manifest) | 77 | 138, of which 132 are bundled for kshana example |
| Verification-matrix rows | 174 | 223 |
| of which VALIDATED against an external oracle | 66 | 83 |
| of which MODELLED (stated model, checked for internal consistency) | 104 | 136 |
| of which PARTNER (needs a partner's hardware or data) | 4 | 4 |
Where the growth comes from:
| Area | Kinds | Scenario files | Matrix rows (validated + modelled) |
|---|---|---|---|
| Spectrum | 1 | 1 | 3 (1 + 2) |
| Solar system and positioning around any body | 2 | 3 | 6 (2 + 4) |
| Constellation design | 1 | 3 | 3 (2 + 1) |
| Campaigns | 1 | 5 | 3 (0 + 3) |
| Maritime, road and rail scenarios | 0 | 5 | 0 |
| Animation, reports, interoperability exports | 0 | 0 | 0 |
| LEO signal designs and the multi-band spectrum | 1 | 4 | 4 (2 + 2) |
| LEO pass and link budget | 1 | 6 | 9 (6 + 3) |
| LEO navigation message | 1 | 5 | 9 (3 + 6) |
| LEO positioning, timing and fusion | 3 | 8 | 8 (1 + 7) |
| LEO end-to-end chain | 1 | 3 | 1 (0 + 1) |
| LEO resilience, focus-area and end-user scenarios | 0 | 18 | 3 (0 + 3) |
| Total added | 12 | 61 | 49 (17 + 32) |
The LEO capability is system-agnostic: it runs on generic, stated parameters and needs no
named system. Named systems are optional presets, each in its own file with its public
source. One preset, Celeste IOD (in-orbit demonstration), uses figures presented at the ESA
NAVISP LEO-PNT workshop, 2026 (ESA is the European Space Agency; NAVISP is its Navigation
Innovation and Support Programme); it lives in one file, src/celeste_iod.rs, with its five
repository-only scenarios, so it can be withheld without a source edit, and none of its
workshop figures is repeated in this changelog.
Read "Revisions to published numbers" before upgrading. One engine output changes (the
ephemeris kind's inertial-frame columns, after a sign fix in the nutation matrix), and
the documentation audit corrected figures that earlier documents had printed.
Abbreviations used in this entry
Statuses: VALIDATED means checked against an independent external oracle (a published table, another group's software, or measured data); MODELLED means a stated model checked for internal consistency only; PARTNER means the check needs a partner's hardware or data.
| Abbreviation | Meaning |
|---|---|
| 3GPP, 5G, NTN | 3rd Generation Partnership Project; fifth-generation mobile network; non-terrestrial network |
| AD, BC | calendar eras (anno Domini, before Christ) |
| AIAA | American Institute of Aeronautics and Astronautics |
| AltBOC, BOC, BPSK, MBOC | alternative binary offset carrier; binary offset carrier; binary phase-shift keying; multiplexed binary offset carrier (signal modulations) |
| ATOMIC | Autonomous Time and Orbit Determination for Microsatellite Constellations (a published LEO ephemeris model) |
| C/A, L1, L2, L2C, L5 | coarse/acquisition code and the carrier and civil-signal names of the Global Positioning System |
| C/N0 | carrier-to-noise density ratio |
| CIO | Celestial Intermediate Origin |
| CLI | command-line interface |
| CRC-24Q | 24-bit cyclic redundancy check (Qualcomm polynomial) |
| CSAC | chip-scale atomic clock |
| CSS, HTML, SVG, XML | Cascading Style Sheets; HyperText Markup Language; Scalable Vector Graphics; Extensible Markup Language |
| CSV, JSON, PDF, TOML, URL | comma-separated values; JavaScript Object Notation; Portable Document Format; Tom's Obvious Minimal Language; uniform resource locator |
| CW | continuous wave |
| CZML, KML, GeoJSON | Cesium Language; Keyhole Markup Language; Geographic JSON (map and globe formats) |
| DE441 | Development Ephemeris 441 of the Jet Propulsion Laboratory |
| DOI | digital object identifier |
| DOP, GDOP, PDOP, HDOP, VDOP | dilution of precision: geometric, position, horizontal, vertical |
| E1, E5, E5a, E5b | Galileo signal names |
| E3F | a slot name in the Global Positioning System constellation table |
| ECEF | Earth-centred, Earth-fixed |
| EE | equation of the equinoxes |
| EGM2008 | Earth Gravitational Model 2008 |
| EIRP | equivalent isotropically radiated power |
| ERFA, SOFA | Essential Routines for Fundamental Astronomy; Standards of Fundamental Astronomy (reference astronomy libraries) |
| ESA, NAVISP | European Space Agency; its Navigation Innovation and Support Programme |
| FDMA | frequency-division multiple access |
| GCRF, GCRS | Geocentric Celestial Reference Frame; Geocentric Celestial Reference System |
| GLONASS | Russia's Global Navigation Satellite System |
| GNSS, GPS | global navigation satellite system; Global Positioning System |
| IAU | International Astronomical Union |
| ICD, OS SIS ICD, OS SDD | interface control document; Galileo Open Service Signal-In-Space Interface Control Document; Galileo Open Service Service Definition Document |
| ICRF | International Celestial Reference Frame |
| IMO | International Maritime Organization |
| INS, MEMS | inertial navigation system; micro-electro-mechanical system |
| IOD | in-orbit demonstration |
| IQ | in-phase and quadrature |
| IS-GPS-200 | the interface specification of the Global Positioning System |
| ISS | International Space Station |
| ITRF, ITRS, PEF, TEME | International Terrestrial Reference Frame; International Terrestrial Reference System; pseudo-Earth-fixed; true equator, mean equinox (reference frames) |
| ITU, ITU-R | International Telecommunication Union; its Radiocommunication Sector |
| J/S | jammer-to-signal ratio |
| J2 to J6, J2000 | zonal harmonics of the gravity field; the epoch 2000-01-01 12:00 |
| JPL | Jet Propulsion Laboratory |
| LEO, MEO | low Earth orbit; medium Earth orbit |
| MBSE | model-based systems engineering |
| NEES | normalised estimation error squared |
| NeQuick-G | the Galileo ionosphere model |
| OEM, SP3, TLE | Orbit Ephemeris Message; Standard Product 3 (precise orbits); two-line element set |
| PNT, PPP | positioning, navigation and timing; precise point positioning |
| PSD, SSC | power spectral density; spectral separation coefficient |
| RAIM, SQM | receiver autonomous integrity monitoring; signal quality monitoring |
| RINEX, RTCM | Receiver Independent Exchange Format; Radio Technical Commission for Maritime Services |
| RMS | root mean square |
| RTKLIB | an open-source GNSS positioning library, used here as an oracle |
| SGP4 | Simplified General Perturbations 4 (the orbit model of two-line element sets) |
| SHA-256 | Secure Hash Algorithm, 256-bit |
| SigMF | Signal Metadata Format |
| SISRE | signal-in-space range error |
| SPS PS | Standard Positioning Service Performance Standard |
| SRTM | Shuttle Radar Topography Mission |
| STK | Systems Tool Kit (its .e ephemeris file format) |
| STL | Satellite Time and Location |
| SVID | space vehicle identifier |
| T/P/F | a Walker constellation's total satellites, planes and phasing |
| TEC | total electron content |
| UHF | ultra high frequency |
| UTC | Coordinated Universal Time |
| WGS 84 | World Geodetic System 1984 |
| X1, X5 | signal names of the Xona Pulsar system |
Revisions to published numbers#
A changed published number is a revision and is recorded, never corrected silently. This release carries one revision to engine output and several to figures printed in the documentation.
Engine output
- The TEME→GCRS reduction turned by 2·Δψ: the nutation matrix had the sign of Δψ
reversed.
nutation::numatbuiltRx(−(ε̄+Δε))·Rz(Δψ)·Rx(ε̄)where SOFA'siauNumatbuildsRx(−(ε̄+Δε))·Rz(−Δψ)·Rx(ε̄), sonutation_matrix,nutation_matrix_2000a,teme_to_gcrsandgcrs_to_temerotated by twice the nutation in longitude, about 25 arcseconds. Every check on the chain was a property test (proper rotation, round trip, a non-zero nutation contribution) that a sign slip passes; the end-to-end Vallado test covered TEME→PEF, TEME→ITRF and GCRS→ITRS but not TEME→GCRS. On the Vallado example (AIAA 2006-6753, 2004-04-06) the chain missed the published GCRF by 1 145 m; it now lands 0.11 m from it, and the matrix equals ERFA'snumatto 1e-14.tests/frame_reference_vectors.rs::teme_to_gcrs_matches_vallado_gcrfandnutation::tests::nutation_matrix_matches_erfa_numatboth fail on the old sign. The CIO (Celestial Intermediate Origin) consistency test insrc/cio.rshad put the resulting ~130 m disagreement down to "≈ 2·EE"; the residual is now 5 cm and its bound is tightened from 250 m to 1 m. This moves published numbers, recorded here as a revision: theephemeriskind'sgcrs_r_mandgcrs_v_m_scolumns (about 0.8–1.0 km for the bundled ISS (International Space Station) scenario; its TEME, Earth-fixed, ground-track, look-angle and Doppler columns do not change), andPropagator::position_in_frame/state_gcrsfor the GCRS and ITRS frames. No bundled scenario's summary line, chart or golden hash reads these columns. The new interoperability exports use the corrected chain: the firstorbit-sgp4-gpssatellite's CZML position agrees with an ERFA reduction of the same TLE (propagated by thesgp4Python package) to 2.3 cm. - The
leo-navmsgkind, natively and in the browser build. The kind now computes every transcendental through one portable mathematics library, so its frame is the same bytes on every platform and in every build (under Fixed). Everyleo-navmsgfigure moves with it, and thenavmsgstage ofleo-pnt-chain. The kind is new in this release, so nothing from an earlier release moves, but figures printed before the fix do:docs/LEO-NAVMSG.md's check value for the encode-and-decode scenario was0x110315and is0x19105F, and its decoded-message SISRE is 0.129 cm. On kshana.dev, the Missions card forleo-navmsg-fit-interval-tradeshowed a worst range error of 1.6 mm with a 60 s fit and 40.3 mm with a 900 s fit; the site is now built from this release's engine and shows 1.8 mm and 46.6 mm. - The jamming footprint map on kshana.dev (Missions). Each of its 875 cells is one run of
maritime-strait-jammingwith only the receiver's position changed. The cells are now computed from the same three-decimal coordinates that the cell's "open this run" link gives Kshana Studio, so a cell shows exactly what its link reproduces. 30 cells' mean jammer-to-signal ratio (J/S) moves by 0.1 dB, up or down; no cell's tracking availability changes. The engine gives identical results for every cell in 0.28.0 and 0.29.0. - Seeded resampling in the WebAssembly (WASM) package. The browser build drew other
bootstrap and shuffle indices than the native build from the same seed (under Fixed).
The
quantum-anomaly-detectinterval of the area under the curve in the browser was[0.9901265, 0.9938305]and is now the native[0.99035875, 0.993861]. No native number moves.
Figures printed in the documentation
The documentation was audited word by word against runs of this release's engine. Where a printed figure did not match the run, the document was corrected, and each correction is listed here, old → new. In every case below the engine output did not change; the document had misread, mis-rounded or outlived it.
- Documentation audit of the spectrum, constellation, campaign, animation, report and
interoperability pages, and a new
docs/SOLAR-SYSTEM.md. Every figure was re-run with this engine. Two published numbers move:docs/CONSTELLATION-DESIGN.mdgave the 5 000-satellite coverage test as "about 0.13 s in a debug build"; that did not reproduce (1.24 s on a loaded laptop), so the page now gives the prefilter share the test prints (9.9 % of 19 440 000 pair tests) and the release binary's 0.14 s for the bundledleo-pnt-mega-shell. The same page gave E3F's crossing as "off by 0.050 deg"; no test prints that figure, so the page now states only the 0.06 deg bar the test enforces and the 7.30 against 7.36 deg derivation behind it. No engine output changes. - LEO-PNT documents: revised published figures (re-run on this release's engine).
docs/LEO-SIGNAL.mdband trade: the first-order ionospheric delay ofgeneric-sis 3.24 m (was printed 3.25 m) and ofgeneric-c-wide0.77 m (was 0.78 m).docs/LEO-NAVMSG.mdencode and decode: the decoded message's SISRE was printed 0.119 cm where the run gave 0.122 cm; the page now prints 0.129 cm, the figure after the platform-independence fix under Fixed below.docs/LEO-PASS.mdLEO-versus-GNSS pass: 32 dB less free-space loss at the pass peak (was "26 to 32 dB"). For the signal and pass figures the engine output did not change; the documents had misread or mis-rounded it. - Tutorials, the worked pipeline example and the
/kshana-runcommand, audited against this release's engine. Revisions to published figures, old → new: the kinds that write<scenario>.table.csvare six, not four (leo-navmsgandtelecom-timingwere missing;docs/tutorials/README.md,commands/kshana-run.md); the cold-atom ½bT² crossing in Tutorial 3 is 18,443 s, not 18,440 s; theoem-interopround-trip error indocs/examples/multi-tool-pipeline.mdis the measured 4.88e-7 km, not "~1e-7 km"; the Tutorial 2 CSAC holdover band is the 2000–3200 s the test asserts, not "~2600–4400 s"; Tutorial 1 quotes the SP3 rows and geometry block at the precision the engine writes them. No engine output changed. - Validation, quantum, SGP4, animation, claims and integrity pages.
docs/VALIDATION.md: the enforced gate on the simulated one-second Allan deviation of a white-frequency-noise clock is 20 % (was printed 25 %; the 25 % gate is the one on the curve at 1, 10 and 100 s); the velocity-random-walk row now states its enforced 20 % gate beside the about 12 % observed; and the chip-scale atomic clock's availability inorbit-gnss-challengedis about 0.73 (was printed about 0.83).docs/QUANTUM.md: the vibration-limited per-shot noise in the page's worked cold-atom example is about 46 times its shot-noise floor (was "~45×"; 46.49 recomputed), and the range "1–50 µg/√Hz" for fielded devices, which contradicted a device the page itself cites, is replaced by the two cited devices' published figures.docs/SGP4-VALIDATION.md: the worst position difference is 4.12 mm (was "≈ 4 mm"), with the satellite named.docs/ANIMATION.md: the video-encoder example read the 24 frames-per-second frames at 12; it now uses 24, the rate the frame manifest states.docs/CLAIMS-VS-REALITY.md: the jamming model has 10 tests (was 8), and the coupled filter's "2.97 m against 48.8 m" is replaced by what its test asserts (the coupled position error below 0.6 times the decoupled one, winning at least 90 of 100 trials).docs/INTEGRITY.md: the "Today" stamp read v0.22.0; it now names this release, and two capabilities built since (the constellation-wide fault mode and the integrity support message) leave its gap list.
Added#
Spectrum
spectrumscenario kind: an L-band spectrum model and waterfall (src/spectrum.rs,src/sigmf.rs,docs/SPECTRUM.md). The whole GNSS L band as one power spectral density (PSD): GPS L1 coarse/acquisition (C/A) and L2 civil (L2C) (BPSK(1)), Galileo E1 (multiplexed binary offset carrier, MBOC(6,1,1/11), or BOC(1,1)), GPS L5 and Galileo E5a (BPSK(10)), a kT noise floor with a receiver noise figure, and continuous-wave (CW), narrowband, chirp and matched-noise jammers on a scripted timeline. Each jammer is scored per band by its spectral separation coefficient (SSC), giving the jammer-to-signal ratio (J/S) and effective carrier-to-noise density (C/N0) per band per row. The chart is an SVG waterfall with C/N0 bars; result.json carries the grid block-averaged in power. Optional[iq]draws the model as IQ samples, writes and reads a Signal Metadata Format (SigMF) recording (cf32_le,ci16_le) and compares a Welch estimate with the model; optional[recording]estimates a real SigMF file. Bundled examplescenarios/l-band-waterfall-jamming.toml: a chirp takes L1 C/A and E1 at 10 s, a CW tone on the L1 carrier holds C/A at 17.98 dB-Hz after the chirp stops while E1, whose spectrum has a null there, recovers; L5 and E5a are untouched.- New VALIDATED row: the signal PSDs and SSCs, against the BPSK(n) main lobe of 2n x 1.023 MHz, the BOC(1,1) lobes centred at +/-1.023 MHz, the Parseval closed forms behind the published -61.8 / -64.8 / -67.8 dB/Hz SSCs, and the textbook Q = 1 (CW) and 1.5 (matched). The BOC(1,1) PSD maximum is at +/-0.759 MHz, not at the lobe centre; the test pins both.
- Two new MODELLED rows: the waterfall and C/N0 timeline, which reduce exactly to the
jammingkind's chain (cross-checked in every report), and the SigMF codec and Welch estimator. No third-party recording is in the repository. navsignal: an MBOC variant andspectral_separation_coeff_offset.- The report prints the C/N0 the
jammingkind's representative Q table would give beside the spectrum-derived one; for a CW tone on the C/A carrier they differ by 1.8 dB. Three new matrix rows: one validated (signal spectra and spectral separation coefficients) and two modelled.
Solar system
- Solar-system ephemeris and positioning around any body. Two new scenario kinds.
solar-system: the Sun, the eight planets, Pluto, the Moon, Phobos, Deimos, Io, Europa, Ganymede, Callisto and Titan at one epoch: heliocentric position and velocity in the International Celestial Reference Frame (ICRF), gravitational parameter, radii, J2 (the second zonal harmonic) where published, sidereal rotation, the International Astronomical Union (IAU) pole and prime meridian, an orbit track over one revolution, and the light time, one-way and two-way range, solar Shapiro delay and Sun separation from an observer body and for any extra link. Planets come from the Jet Propulsion Laboratory (JPL) Keplerian elements of Standish and Williams (Table 1, 1800 AD to 2050 AD; Tables 2a/2b, 3000 BC to 3000 AD); the moons from JPL mean elements with the IAU synchronous rotation rate, and Titan from the IAU rotation model. Example:scenarios/solar-system-tour.toml.body-pnt: an orbiter or a surface lander around any of those bodies, navigating with pseudoranges from a Walker constellation around the body and a clock-free two-way range from Earth; dilution of precision, formal uncertainty and seeded least-squares fixes with and without the Earth link. Examples:scenarios/mars-orbit-pnt.toml,scenarios/europa-surface-pnt.toml.Bodygains Mercury, Venus, Jupiter, Saturn, Uranus, Neptune, Pluto and the seven moons, a name lookup and a physical record;AnalyticSolarSystemgives any body relative to any other through the existingEphemerisProviderseam.- VALIDATED against JPL Horizons (Development Ephemeris DE441; fixtures and queries in
tests/fixtures/solar_system/): Mercury to Saturn and the Earth from Table 1 within twice the stated nominal error (worst 1.87 times), all eight planets from Tables 2a/2b (worst 1.71 times), and the Earth to Mars and Jupiter light time. MODELLED: Uranus and Neptune from Table 1, which exceed the stated error against DE441 (2.0 and 5.2 times), Pluto, the moons, the body constants and thebody-pntresults. - Finding: the Montenbruck & Gill lunar series in
src/ephem.rsis already referred to the J2000 equinox by its own precession term (0.05 degrees from Horizons), so it is used without a further precession rotation. Six new matrix rows: two validated (planet positions, light time) and four modelled.
Constellations around any body
- Constellation design at scale (
constellation-designkind,src/constellation.rs). Walker delta and Walker star patterns (T/P/F), explicit element lists and multi-shell designs, several constellations per run, around the Earth, the Moon, Mars or any other planet, Pluto or major moon from the body constants thesolar-systemkind added. Presets from published nominal elements: the Global Positioning System (GPS) baseline and expandable 24-slot constellation (Standard Positioning Service Performance Standard, SPS PS, 2020), Galileo Walker 24/3/1 (Open Service Service Definition Document issue 1.1), BeiDou medium Earth orbit Walker 24/3/1 plus geostationary and inclined geosynchronous satellites (Open Service Performance Standard 3.0) and GLONASS 24/3/1 (Interface Control Document 5.1). Coverage and dilution of precision (DOP) over a latitude/longitude grid: satellites in view, GDOP, PDOP, HDOP and VDOP (geometric, position, horizontal, vertical) and availability per cell, globally and at the worst site, with one receiver clock per constellation. A visibility prefilter (coverage half-angle plus a sub-satellite latitude band, exact on a spherical body) runs 5 000 satellites on a 10 deg grid in about 0.13 s in a debug build.- VALIDATED: the Walker generator reproduces Galileo OS SDD Table 23 and the GLONASS ICD slot formula; the GPS preset reproduces the SPS PS equatorial-crossing column (35 of 36 locations within 0.0108 deg; E3F within 0.06 deg, the table's own row being inconsistent); the GPS baseline global HDOP distribution matches SPS PS Appendix B (median 0.940 against 0.94, 95 % 1.255 against 1.25, mean 0.965 against 0.96; bar 0.03).
- MODELLED: arbitrary designs, other bodies, the BeiDou phase and inclined-geosynchronous nodes, two-body orbits with optional J2, geometry only.
- Scenarios:
constellation-multi-gnss-coverage(102 satellites, availability 100 %, median PDOP 0.95, 31.0 in view above 10 deg),leo-pnt-mega-shell(5 000 satellites, availability 99.22 % at PDOP 3 or less above 20 deg, 0 % at the polar caps, prefilter keeps 11.5 % of the pair tests),lunar-relay-constellation(14 satellites around the Moon, availability 21.33 % overall and highest over the south polar region). All three are bundled forkshana example. Notes:docs/CONSTELLATION-DESIGN.md. Three new matrix rows: two validated (Walker generator and GNSS presets, GPS global DOP) and one modelled.
- A campaign driven by the spectrum model, and constellations around any body.
scenarios/campaign-spectrum-holdover-integrity.tomlchains thespectrumkind with a clock holdover and an integrity monitor (a chirp takes L1 C/A and E1; a CW tone then keeps C/A down while E1 recovers and the receiver falls back to a Galileo-only sky), pinned against the stand-alone waterfall example intests/campaign_composition_reference.rs.constellation-designnow resolves its central body throughBody::by_name, so a constellation can be laid around any planet, Pluto or major moon with the constants thesolar-systemkind uses.
Campaigns
- Campaigns: many scenarios composed into one run. A new
campaignkind (src/campaign.rs, documented indocs/CAMPAIGNS.md) runs members of existing kinds through the same dispatch as the command line and reads numbers back out of their results. Four sections, in any combination:[[phases]]: a chained mission on one timeline. Per-kind presets read clock time error against its guard, the mean effective carrier-to-noise density ratio (C/N0) against the tracking floor, the vertical protection level against the alert limit, position error, satellites tracking and alarm flags, held onto a common grid with phase boundaries and events. State is handed on bycarry,handoffandend_at.[sweep]: one to three dotted keys of any kind, optionally with a seeded ensemble at every node.[monte_carlo]: realisation k at base seed + k, with mean, spread, percentiles and a fixed-seed bootstrap 95% confidence interval.[compose]: shared values bound into several members, with a combined best and worst summary. Every result carries a campaign hash and a digest over every member result. Four bundled scenarios:campaign-jam-spoof-holdover-integrity(nominal, jamming, spoofing ended at the clock monitor's 370 s detection, holdover carrying the 37.0 ns spoofed offset with an inertial unit coasting, an integrity alarm, recovery),campaign-sweep-jammer-power,campaign-shared-jammer-sea-roadandcampaign-monte-carlo-clock-holdover.tests/campaign_composition_reference.rspins the composition identities: a one-phase campaign reproduces the stand-alone run bit for bit on three kinds, a fixed-seed ensemble is byte-stable, and on a white-frequency-noise clock the ensemble mean lies inside the reported interval with the spread inside the chi-square interval of sqrt(q_wf * tau) = 16.459 ns. Three new modelled matrix rows.
Animation
- Animation export (
--animate svg|html|frames|all,src/animation.rs,docs/ANIMATION.md). Any run whose result carries a time series can now be written as an animated Scalable Vector Graphics (SVG) file (Cascading Style Sheets keyframes, no script: the traces draw in behind a moving time cursor), a single self-contained HyperText Markup Language (HTML) player (play and pause, scrub, speed, every panel on one synced cursor, event markers that seek on click; no external asset), or a numbered SVG frame sequence with amanifest.jsonstating frames per second, duration and frame times for a video encoder. A campaign plays as its phases with its alarms marked; a spectrum run animates its waterfall row by row. The player followsprefers-color-scheme, and underprefers-reduced-motionboth the SVG and the player show the finished picture instead of moving.--animate-fpsand--animate-durationset the playback;kshana::api::animate_tomlandkshana::animation::animate_resultare the library entry points, andresult.jsongains ananimationblock only when--animateruns. Output is a pure function of the result and the options, byte-identical on a re-run, with no timestamp. A kind with no sampled time axis is refused with "no time series to animate" and nothing is written. The exporter draws the run's own samples and adds no number, so it is MODELLED (internal consistency) and carries no verification-matrix row; no published number changes. Tests:tests/animation.rs(determinism, frame count, no external address, well-formed XML, the reduced-motion path, and every bundled scenario with a time series animating).
Reports
- Advanced run reports (
src/advanced_report.rs, docs/REPORTS.md). Every command-line interface (CLI) run now writes<scenario>.report.html, a printable HyperText Markup Language (HTML) report, and<scenario>.report.json, the same content as a machine-readable JavaScript Object Notation (JSON) document. Sections: an executive summary; every scenario input with its unit, read from the field-units schema (the result'sunitsblock), else the field-name suffix, else stated as not stated; the results with the run's chart, every scalar and a summary of every numeric column; for a campaign, the sweep node table, the Monte Carlo mean, standard deviation, 5th / 50th / 95th percentiles and bootstrap confidence interval with a histogram per metric, the chain phase table, or the composition's members and combined summary (and the grid of asweeporsweep-ndrun); an events timeline; the VALIDATED / MODELLED / PARTNER label, oracle and test evidence of every verification-matrix row the run's kinds exercise, read from the matrix through a kind-to-row crosswalk (a row that grades one input path, such as the Simplified General Perturbations 4 (SGP4) path oforbitor the measured-record path ofslot-timing, is listed only when the run took that path, and the Shuttle Radar Topography Mission (SRTM) reader row is listed for no kind, since no scenario field reads an SRTM tile); the not-modelled statements and assumptions, each quoted with its source; and a reproducibility record (engine version, the source commit when the build setsKSHANA_GIT_COMMIT, Secure Hash Algorithm 256-bit (SHA-256) digests of the scenario file and the result document, seed, platform and the exact command). The report reads no clock: same scenario, seed and engine build give a byte-identical report, and the only timestamp it can show is the one--study-namealready writes. A print stylesheet fits A4 and US Letter, repeats table headers and keeps rows and figures whole across pages; a Portable Document Format (PDF) file is made with the browser's "Save as PDF". There is no--report pdfoption, because rendering the charts into a PDF would need a new, heavy dependency. Tests:tests/advanced_report.rsreports every bundled scenario (no empty section, no placeholder text, labels equal to the matrix, the scenario digest equal to the file's, byte-identical on a re-run) andtests/advanced_report_cli.rsre-runs the recorded command in a fresh directory and requires a byte-identicalresult.json. No published number changes, and the kind, scenario-file and matrix-row counts are unchanged. - The report, the animation and the exports work together. The advanced report gains an
"Animation and exports" section: the run's animated drawing embedded as an inert image (or
the reason there is none), links to the animation and export files written beside it,
and every interoperability format with whether it applies, why not, and its
specification;
report.jsoncarries the samecompanionsblock. The HTML animation player lists the export files written beside it. The command-line interface (CLI) now renders the exports before it writes the report, so both can name the files.
Interoperability exports
- Interoperability exports and imports (
--export,src/interop/, docs/INTEROP.md).kshana <scenario.toml> --export <format>writes the scenario's geometry as CZML (the Cesium Language), KML (Keyhole Markup Language 2.2, an Open Geospatial Consortium standard, withgx:Tracktime-tagged tracks), GeoJSON (Internet Engineering Task Force Request for Comments 7946) and Ansys STK (Systems Tool Kit).eephemerides (EphemerisTimePosVel, metres, one file per moving object), and thespectrumkind's synthesised IQ (in-phase and quadrature) snapshot as a SigMF (Signal Metadata Format) pair through the existingsrc/sigmf.rs.--export allwrites every format that applies and prints why the others do not;--export listreports without running. Moving objects are written in the Geocentric Celestial Reference System (CZMLINERTIAL, STKICRF) and Earth-fixed WGS 84 (World Geodetic System 1984) longitude, latitude and height (KML, GeoJSON); fixed sites in CZML areFIXED. Thejammingkind adds two jammer footprints from its own link equations.--import-route <file.geojson>writes a GeoJSONLineStringinto the track inputs ofterrain-nav,terrain-slam,gravity-mapandcombined-altpnt. Exports are byte-deterministic and carry no timestamp.tests/interop_formats.rsvalidates every export against its published specification, compares exported satellite states with the engine's own to 1 mm, and runs every bundled scenario through every format, each either exporting or stating why not (the table in docs/INTEROP.md). No published number changes: thespectrumkind's IQ synthesis moved into one shared function and its result document is byte-identical, andPassesScenariogainspropagator()andepoch_calendar()used by its own run. Kind, scenario-file and verification-matrix counts are unchanged. An orbit or integrity scenario with noepochis dated by its satellites' own data:t = 0is the earliest TLE (two-line element set), broadcast-ephemeris or SP3 (Standard Product 3) epoch, and each such satellite is rotated into the GCRS and Earth-fixed frames at its own instant; 2000-01-01T00:00:00Z is used only when no satellite carries an epoch. The SP3 and OEM (Orbit Ephemeris Message) exports keep their 2000-01-01 label. To support this,Sgp4::epoch_jd_utc,Sp3Interpolator::jd_ut1andPropagator::own_jd_utcare added; nothing that existed reads them.
Low-Earth-orbit navigation: signal
- LEO-PNT signal designs and a multi-band spectrum. A new
leo-signalkind and aspectrumkind that reaches beyond the L band: one kind, four scenario files (three bundled; the Celeste IOD file is repository-only) and four matrix rows (two VALIDATED, two MODELLED).leo-signal: parameterised low Earth orbit (LEO) positioning, navigation and timing (PNT) signal designs for any system (band, transmit bandwidth, ITU allocation; acquisition, data and pilot components with BPSK(n), BOC(m,n), MBOC or flat spectra, power shares, FDMA sub-carriers, code lengths and data rates), from compiled-in public preset files underdata/leo-signals/(Xona Pulsar X1/X5, Iridium STL, a Starlink signal of opportunity, CentiSpace, a representative C-band design and representative UHF/L/S/C/wide-C designs, each citing its source URL) or written inline. Per signal: the band-limited PSD and in-band power fractions, Gabor bandwidth, band-limited early-late code-tracking jitter against C/N0 and spacing (Betz & Kolodziejski 2009) and the ranging accuracy, the acquisition search space, detection probability and mean serial and code-parallel acquisition time, the SSC into and from GPS L1 C/A, Galileo E1, GPS L5, Galileo E5a, E5b and AltBOC with the C/N0 loss, CW, wideband and matched J/S tolerance from the spectrum kind's SSC code, a band trade (ionospheric delay, free-space loss, ranging at equal C/N0 and equal EIRP, jammer tolerance), and optional shape checks against a described measurement. Examples:scenarios/leo-band-trade.toml,scenarios/xona-pulsar-signals.toml,scenarios/celeste-iod-classical-pilot-signals.toml(the Celeste IOD bands and signal configuration presented at the ESA NAVISP LEO-PNT workshop, 2026; withheld with the Celeste IOD preset, see "LEO-PNT end to end" below). Notes indocs/LEO-SIGNAL.md.spectrum: bands may now be a preset signal design (every component drawn, band-limited, C/N0 and J/S referred to the tracked component) or a custom carrier and modulation;[[panels]]add waterfalls over other frequency ranges on one timeline; awideband(barrage) jammer joins the four waveforms. The report addsbands[].tracked_power_dbw,bands[].designandpanels, and onenot_modelledentry. Plain bands give the same numbers as before. Example:scenarios/multi-band-jamming-waterfall.toml.navsignalgains the sine integral, the BPSK power-in-band and band-limited Gabor bandwidth closed forms, the band-limited early-late jitter for any spectrum and its Gabor bound, the offset BPSK SSC closed form, the Galileo E5 AltBOC(15,10) spectrum and a modulation-label parser;Modulation::labelprints BPSK(1/3) as a fraction.- Four matrix rows: VALIDATED band-limited closed forms (90.3 % of BPSK power in the
main lobe, the jitter reducing to Kaplan & Hegarty's coherent form, 0.0039564 chips at
45 dB-Hz, B_L 1 Hz, d 1 chip, T 20 ms, and to its Gabor bound, and the BPSK self-SSC
2/(3 R_c); the Gabor closed form and the offset SSC against its Parseval form are
stated as internal cross-checks); VALIDATED maximum LEO Doppler (Xona X1 33.2 kHz
inside the published 32 to 34 kHz, Iridium within 0.5 kHz of 36 kHz); MODELLED
leo-signal; MODELLED multi-band spectrum.
Low-Earth-orbit navigation: link
- LEO-PNT pass and per-band link budget: the
leo-passkind. LEO-PNT is positioning, navigation and timing from satellites in low Earth orbit (LEO). A user (ground, maritime, air or indoor, static or moving) and one or more LEO satellites from a designed pass, explicit elements, a two-line element set (TLE) through SGP4, or a Walker constellation from theconstellation-designcode. Per satellite, band and epoch: look angles, range, closed-form range rate and range acceleration (checked every run against central differences), Doppler and Doppler rate, free-space loss, EIRP (equivalent isotropically radiated power) with an isoflux, Gaussian-beam or flat satellite pattern, a patch user antenna, ITU-R P.676 gaseous attenuation, ITU-R P.838/P.618 rain attenuation, P.618 tropospheric scintillation, ITU-R P.2109 building entry loss for an indoor user, polarisation mismatch, system noise temperature and C/N0 (carrier-to-noise density); the first-order ionospheric delay per band from a Klobuchar or vertical-TEC (total electron content) slant TEC below the satellite, and ionosphere-free band pairs with their noise amplification. Galileo or GPS satellites are evaluated with the same receiver from their interface-document received powers, so the bell-shaped LEO pass and the flat MEO (medium Earth orbit) carriers share one plot. An[iot]section gives time to first fix, energy per fix and battery life against duty cycle (MODELLED).- System-agnostic: the engine needs no preset. Optional presets, each in its own file and
marked PUBLIC (with URL), REPRESENTATIVE or WORKSHOP: generic multi-band (UHF, L, S, C),
generic C band, Xona Pulsar X1/X5 (arXiv 2509.19551), Iridium STL, Starlink as a
Doppler-only signal of opportunity, CentiSpace, and Celeste IOD (in-orbit
demonstration), whose signal parameters were presented at the ESA NAVISP LEO-PNT
workshop, 2026, and live in
src/celeste_iod.rsso they can be withheld (see "LEO-PNT end to end" below). - Public building blocks in
src/leo_link/(geometry, antenna, itu, iono, energy, presets) for other modules to call. - Scenarios:
leo-pass-vs-gnss-cn0,leo-indoor-uhf,leo-iot-energy,leo-pass-xona-pulsar,leo-pass-iridium,leo-pass-celeste-iod-multiband. - Nine matrix rows. VALIDATED (6): ITU-R P.838-3 coefficients against its Table 5; P.618-14
rain attenuation and scintillation against the ITU-R Study Group 3 validation examples;
P.2109 building entry loss against the Study Group 3 workbook; first-order ionospheric
scaling against the IS-GPS-200 group-delay ratio, with the free-space loss; the static-user
maximum Doppler of a circular orbit against arXiv 2509.19551 Table 1. MODELLED (3): the
pass and link budget, the presets, and the low-energy fix budget. One kind and six
scenario files. Notes in
docs/LEO-PASS.md.
- System-agnostic: the engine needs no preset. Optional presets, each in its own file and
marked PUBLIC (with URL), REPRESENTATIVE or WORKSHOP: generic multi-band (UHF, L, S, C),
generic C band, Xona Pulsar X1/X5 (arXiv 2509.19551), Iridium STL, Starlink as a
Doppler-only signal of opportunity, CentiSpace, and Celeste IOD (in-orbit
demonstration), whose signal parameters were presented at the ESA NAVISP LEO-PNT
workshop, 2026, and live in
Low-Earth-orbit navigation: navigation message
- LEO navigation message (
leo-navmsgkind,src/leo_navmsg/). The broadcast ephemeris and clock message of a low Earth orbit (LEO) positioning, navigation and timing (PNT) satellite, for any orbit, carrier and model; named presets are optional data and every capability runs without them. One kind, five scenario files and nine matrix rows (three VALIDATED, six MODELLED). Seedocs/LEO-NAVMSG.md.- Message content: SVID, issue of data, band and signal health; week, time of week and a second-order clock polynomial; the ephemeris in one of four models — the Galileo OS SIS ICD 16-parameter Keplerian set, that set plus along-track, cross-track and radial correction polynomials, the Liu et al. 2025 22-parameter model (doi 10.3390/rs17162894), or the ATOMIC zero-clock ECEF polynomial — and a Klobuchar set, NeQuick-G coefficients with the effective ionisation level, and system-time-to-UTC parameters with the ICD leap-second cases.
- Fitter: a truth orbit integrated with zonal J2–J6 or EGM2008 gravity and drag, a seeded free or steered clock, a Levenberg–Marquardt fit on non-singular elements, then linear least-squares correction polynomials and a clock fitted net of the user's relativistic term.
- Four analyses and scenarios:
leo-navmsg-fit-interval-trade(signal-in-space range error (SISRE) versus fit interval and update period),leo-navmsg-model-comparison(four models, bits, and Kshana's 22-parameter fit at the Liu et al. altitudes beside the published figures, MODELLED),leo-navmsg-midpass-update(continuity at each message switch in a pass) andleo-navmsg-encode-decode(Kshana's own documented binary frame with CRC-24Q and a quantisation budget, a RINEX-4-style block labelled a Kshana extension, and a CSV table). - VALIDATED: the global-average SISRE weights against the published medium-orbit and
geostationary table of Montenbruck et al. 2018; the Galileo user algorithm against
RTKLIB on four real Galileo broadcast ephemerides (
tests/leo_navmsg_reference.rs); CRC-24Q against the catalogue check value and the RTCM 10403 1005 example frame. - Presets, one file each with sources: Xona Pulsar and Pulsar-0, Iridium, Starlink,
CentiSpace, a representative C-band system, ATOMIC, and Celeste IOD (the only preset
using material presented at the ESA NAVISP LEO-PNT workshop, 2026, kept in
src/celeste_iod.rswith its scenarios so it can be withheld).
Low-Earth-orbit navigation: fusion
- Fused MEO + LEO positioning, navigation and timing:
leo-pvt,leo-pppandntn-positioning. Three new scenario kinds insrc/leo_fusion/, system-agnostic: every constellation is Walker shells, element sets or a GNSS preset, every signal a carrier, a chip rate and a carrier-to-noise density (C/N0) envelope, every error budget an explicit one-sigma, and named low-Earth-orbit (LEO) systems are optional presets, one file each with their sources marked public, workshop or derived (Xona Pulsar X1/X5, Iridium Satellite Time and Location (STL), Starlink signals of opportunity, CentiSpace, a representative C-band system, the ATOMIC zero-clock ephemeris model, and one ESA Celeste in-orbit-demonstration preset whose workshop-derived parameters live insrc/celeste_iod.rsso they can be withheld).leo-pvthas four modes: Doppler positioning (batch least squares on range rate with clock-drift and velocity states, a Doppler-only signals-of-opportunity mode, the single-pass along-track and cross-track accuracy, and the Doppler, Doppler-rate and jerk envelope); joint GNSS + LEO weighted least squares with one clock per system (the inter-system bias) or a known offset, per-measurement sigmas as inputs, and the DOP against the number of LEO satellites; polar and Arctic coverage against latitude; and LEO time transfer to Coordinated Universal Time (UTC) against C/N0 and the receiver oscillator with the IS-GPS-200 system-time-to-UTC expression.leo-ppp: a float precise point positioning (PPP) extended Kalman filter on ionosphere-free code and phase, GNSS only and with LEO augmentation, with its convergence time and a Monte Carlo NEES (normalised estimation error squared) consistency test. The bundled scenario gives 7.4 min with four MEO systems and 4.8, 3.2, 2.7 and 2.3 min with 60, 96, 192 and 288 LEO satellites, beside the 9.6, 7.0, 3.2, 2.1 and 1.3 min of Li et al. (J. Geod. 93:749, 2019) as a MODELLED comparison of the trend.ntn-positioning: 5G non-terrestrial network (NTN) positioning in the 3GPP n256 mobile-satellite S band from the Cramér-Rao bound on time of arrival and Doppler.- Scenarios:
leo-doppler-positioning,starlink-sop-doppler-positioning,meo-leo-fused-pvt,leo-ppp-convergence,ntn-5g-positioning,polar-arctic-leo-coverage,leo-timing-utc(bundled) andceleste-iod-fused-pvt(repository-only, withheld with the Celeste IOD preset). - Eight matrix rows: the LEO Doppler envelope VALIDATED against the published Iridium
(±36 kHz: 35.9 kHz, within 5%) and Xona Pulsar X1 (32 to 34 kHz: 33.6 kHz from the
97 deg shell, with no widening) figures
(
tests/leo_doppler_reference.rs); Doppler positioning, joint pseudorange positioning, PPP convergence, NTN bounds, LEO timing, polar coverage and the presets MODELLED. Three kinds and eight scenario files. Documentation:docs/LEO-PNT-FUSION.md.
- LEO-PNT end to end: the
leo-pnt-chainkind, and the LEO stages wired together. One low Earth orbit (LEO) positioning, navigation and timing (PNT) system followed from its signal design to the user's position, each stage the engine's own kind on its own scenario table, with values handed on in code (docs/LEO-PNT.md):leo-passbands may name aleo-signaldesign (signal = "xona-x5"): the band takes the design's centre, transmit bandwidth and tracked-component chip rate, splits its EIRP (equivalent isotropically radiated power) across the design's components, and reports every epoch's tracked-component C/N0 (carrier-to-noise density) and band-limited code-tracking jitter (the ranging error; theiono_freepair noise uses it too). A band without a design gives the same output as before.leo-pnt-chainruns signal -> pass -> navigation message -> fusedleo-pvtjoint fix -> optionalleo-ppp. Every LEO system of the positioning stage that leaves them unset takes a C/N0 line in sin(elevation) fitted to the pass, the message's signal-in-space range error (SISRE: its representation error at the pass satellite's orbit, with a statedod_sisre_morbit-determination term in root-sum-square), and the design's carrier and chip rate; the precise point positioning (PPP) cases take the SISRE. Every hand-off is listed with its value and unit, andtests/leo_pnt_chain.rschecks that each equals the upstream output and that upstream changes move the downstream figures.LeoNavmsgScenario::broadcast_sisreis the new message entry point.- Scenarios:
leo-pnt-end-to-end(a generic 1080 km constellation and the representativegeneric-lsignal),xona-pulsar-end-to-end(the public X5 signal), both bundled, and the optional, repository-onlyceleste-iod-end-to-end. - The Celeste IOD (in-orbit demonstration) presets of the pass, message and fused
positioning areas now live in one file,
src/celeste_iod.rs, whichbuild.rscompiles in only when it exists (thekshana_celesteconfiguration flag). Its five scenarios (scenarios/*celeste-iod*.toml) are repository-only, so deleting that file and those scenarios withholds every number presented at the ESA NAVISP LEO-PNT workshop, 2026, with no source edit;tests/workshop_preset_isolation.rschecks which files may name the preset or carry its band-plan numbers. - Every LEO kind in the advanced report's crosswalk, with its path-specific rows listed
only when the run took that path (a rain rate, a building, an analysis or mode that ran).
leo-pvtDoppler windows gaint_sandleo-pppcases gaint_min, so both animate;leo-passandleo-pnt-chainexport CZML, KML, GeoJSON and STK ephemerides, and the other LEO kinds state why a format does not apply.
Low-Earth-orbit navigation: resilience, focus areas and end-user scenarios
- LEO-PNT resilience, one scenario per experiment focus area and one per end-user
vertical, and
docs/LEO-PNT.mdas the overview of every LEO kind and scenario. Every scenario runs without any Celeste data and is bundled.leo-passgains a[spoofer]section: a spoofer counterfeits, self-consistently in range and range rate, the signals of a claimed position that jumps and/or is pushed from the true one after an onset (per LEO band, and the MEO GNSS signal), with or without the ionosphere. Two monitors run at a stated false-alarm probability: Doppler and pass-geometry consistency (measured range rates against those predicted from the orbits and an independent prior position and velocity; a generalised least-squares chi-square over a window, on the GNSS, LEO and all channels, frequency-lock-loop noise from C/N0) and cross-band consistency (the step of each LEO band pair's model-corrected geometry-free combination). The result gives the statistics per epoch (so it animates), each pair, and when each monitor detects. Building blocks inleo_link::spoof. Absent section, unchanged output.- Every
leo-passband pair also reports the slant total electron content (TEC) its geometry-free combination recovers at the pass peak and that estimate's code-noise sigma (ionosphere sounding). This adds two fields to every multi-bandleo-passresult; no existing number changes. leo-pvttiming mode takestrace = true: every row then reports its time error and predicted sigma at every epoch, with the same draws, so the row statistics are unchanged and the run animates and chains in a campaign.- A campaign now exports each member scenario that has geometry (phase runs, the sweep
and Monte Carlo base scenarios, composed members with their shared values bound) as
its own file set, the member label in the file name; before, a campaign exported
nothing. The interoperability table in
docs/INTEROP.mdchanges accordingly. - A campaign sweep key may index an array of tables by position (
system.2.sisre_m). - Resilience scenarios:
leo-resilience-multiband-diversity(a 50 MHz L5-band barrage swept in power: every L-band signal falls, the UHF, S- and C-band LEO signals keep their C/N0),leo-resilience-js-margin(J/S margin from received power: GPS L5, Galileo E5a and Xona X5 lost at -105, -100 and -95 dBW, a -135 dBW generic LEO signal still tracking at -90 dBW),leo-resilience-spoof-doppler(a 30 m jump missed by the GNSS-only Doppler test and detected by the test on every channel 105 s after the onset),leo-resilience-spoof-monitors(four spoofers against both monitors) andleo-resilience-gnss-jammed-leo-carries(GNSS jammed, the S- and C-band LEO layer tracked, receiver autonomous integrity monitoring on the LEO layer alone alarming at 2 of 30 epochs). - Focus-area scenarios:
leo-focus-ppp-altitude,leo-focus-ntn-bandwidth,leo-focus-iot-eirp,leo-focus-science-iono-sounding,leo-focus-data-services,leo-focus-indoor-uhf,leo-focus-fused-pnt-sisre(resilience in L, S and C isleo-resilience-multiband-diversity). - Vertical campaigns, each animated and exporting its LEO passes:
leo-vertical-autonomous-vehicle,leo-vertical-rail-maritime(the bundled maritime and rail scenarios plus LEO),leo-vertical-critical-infrastructure-timing,leo-vertical-polar-arctic,leo-vertical-5g-network-timing,leo-vertical-asset-tracking-iot. One-line results indocs/LEO-PNT.md. - Three MODELLED matrix rows: the Doppler and pass-geometry spoofing monitor, the
cross-band spoofing monitor, and ionosphere sounding.
tests/leo_resilience_verticals.rschecks the monitors (silent before the onset, the Doppler statistic quadratic in a jump, the ionospheric step of a ground spoofer and none from an ionosphere-aware one), the sounding identity, the timing trace against the row statistics, and that a campaign's export equals each member's own. - The
leo-passpage moves todocs/LEO-PASS.md;docs/LEO-PNT.mdis the overview.
Maritime, road and rail scenarios
- Maritime, road and rail scenarios. Five bundled examples on existing kinds, so
ships, road vehicles and trains each have a worked case. Every figure is MODELLED; none
is validated against measured data.
scenarios/maritime-strait-jamming.toml(jamming): a 50 W broadband jammer on a 30 m mast, 30 km across open water from a ship. Mean jammer-to-signal ratio (J/S) about 48 dB, and availability falls from 1.00 to 0.00.scenarios/maritime-port-approach-coast.toml(ins-trn-coast): a navigation-grade inertial navigation system (INS) at 12 knots with GNSS lost and no aiding. It crosses 10 m after 281 s and 100 m after 833 s, the harbour-approach and ocean-waters figures of International Maritime Organization (IMO) Resolution A.1046(27), used here as budgets.scenarios/maritime-spoof-position-push.toml(spoof-detect): a +2 dB, non-carrier-aligned spoofer pushing four satellites to drag a ship 500 m. The fused monitor detects it (score 0.70 against a 0.50 threshold) on receiver autonomous integrity monitoring (RAIM) and signal quality monitoring (SQM); power monitoring alone misses it.scenarios/automotive-urban-canyon.toml(gnss-ins): a car through a 15 s underpass and a 60 s roadside-jammer outage, against a 1.5 m half-lane budget. Fused outage root-mean-square (RMS) error is 1.9 m for an automotive-class micro-electro-mechanical system (MEMS) unit and 0.7 m for a tactical-grade comparator; free-running, the MEMS unit drifts to 850.8 m.scenarios/rail-tunnel-coast.toml(ins-trn-coast): a tactical-grade INS on a train at 160 km/h in a tunnel. It crosses 2 m (track discrimination) after 35.8 s, about 1.6 km in, and 20 m after 107.3 s. This is INS only: the engine has no odometer model, so it is the pessimistic bound. The README scenario-file count moves from 77 to 82. All five are bundled forkshana exampleand listed in the browser playground.
Model Context Protocol server
The Model Context Protocol (MCP) server reaches every kind and every view of a run: seven new tools, fourteen in all.
run_scenarioalready dispatched every kind; what an agent lacked was a way to a valid scenario of a kind, and the three outputs the command line writes beside a result. New tools:list_example_scenariosandget_example_scenarioserve the bundled reference scenarios (thekshana exampletable), each with its kind and the first sentence of its own header, byte for byte the file underscenarios/. A scenario that is in the repository but not bundled is refused with the reason. Every kind butlunar-llr-datum(whose data slice ships with the repository only) has at least one example.report_scenarioreturns the run's report as JSON (JavaScript Object Notation) or as the printable HTML (HyperText Markup Language) page (docs/REPORTS.md).animate_scenarioreturns the run's time series as an animated SVG (Scalable Vector Graphics) drawing, an HTML player or numbered frames, after a JSON summary of what was drawn (docs/ANIMATION.md). A reply carries at most 120 frames; the command line writes any length.list_export_formats,export_interopandimport_routeserve the interoperability exports and the GeoJSON route import (docs/INTEROP.md): CZML (Cesium Language), KML (Keyhole Markup Language), GeoJSON, the STK (Systems Tool Kit) ephemeris.eand SigMF (Signal Metadata Format), with a JSON index of the files (suffix, size, and SHA-256, the 256-bit Secure Hash Algorithm digest) and the binary SigMF sample file as base64.
The library gains an off-by-default
bundled-scenariosfeature that exposes the reference-scenario table askshana::bundled_scenarios; only the MCP server turns it on, so the Python wheel and the WebAssembly module still do not carry the scenario text. Study suites (--study) stay command-line only, because a suite names files on disk and the server reads none. The round-trip tests run each new tool against bundled scenarios and check the engine's numbers: spectrum, the solar system, constellations around the Moon and Europa, a campaign sweep, and the low Earth orbit (LEO) signal, pass, navigation-message, fused positioning, precise point positioning, non-terrestrial-network and end-to-end kinds. Every surface that states the MCP tool count or lists the tools now says fourteen, except the playground page underweb/, which is replaced with the site.
kshana.dev, Kshana Studio and the README
- A redesigned kshana.dev. The single playground page is replaced by a multi-page
site: Home, Missions, Capabilities, Evidence, Developers, Editions and Docs, with Kshana
Studio one click away. Every chart, map and number on it is a recorded run of this
release's engine, with the scenario and a link that reopens the same run in the Studio;
the site build refuses a recording made by another engine commit. The documentation
pages are generated from the repository's
docs/, and the install options from its channels. Fonts and script libraries are served from kshana.dev itself, so no page requests anything from a third-party host. Old addresses (/#playground,/#ledger,/#s=<scenario>share links,/?embed=1&…embed links and the rest) redirect to their new place, and an unknown address gets a 404 page. - Kshana Studio, a dashboard for every capability. The Studio opens on a start screen
with domain tiles and good first runs, then takes a run through five numbered steps:
Choose a scenario, Set its parameters, Run the engine locally in the browser, Read the
results (key figures first, each with a PASS or FAIL chip where the run states a
threshold, then the panels) and Share or export. One search box finds scenarios, domains
and the fields inside them and jumps to the control; runs can be pinned and compared;
a breadcrumb says where you are; on a phone the steps become a step bar. Every scenario
kind has its view, including the spectrum waterfall, the solar system, constellation
coverage, campaigns, the animation, the run report, the interoperability exports and the
low Earth orbit chain. A deep link (
?scenario=…&tab=…) opens the same run on the named panel. - Research and citation. The Evidence page lists the five Kshana papers on arXiv (2606.22054, 2606.24210, 2607.02566, 2607.05415 and 2607.06212), each with a summary, a figure computed by this engine, the command that reproduces it, a Studio link and BibTeX, beside how to cite the software. The README gains the same Research section.
- A public page for Kshana Pro.
docs/PRO.mdand the site's Editions page say what the proprietary Pro overlay adds over the same engine (design optimiser, uncertainty and sensitivity, mission dossier, campaign watch, spectrum coexistence, on-premises job service, requirements traceability), what each produces and what it does not do. Every Pro figure the site shows is stated in that public page. Pro adds no physical model, and the open engine stays whole and free. - A rewritten README. Short sections, one image per section, the Kshana mark, two
badge rows, generated architecture, scenario-flow, low-Earth-orbit-chain and
verification images (
tools/gen_readme_assets.py, which fails on a stale image), and Studio screenshots taken from the running Studio bytools/capture_studio_shots.mjs. The crates.io, PyPI, npm and MCP server READMEs follow the same design. Long reference material is folded or moved todocs/.
Changed#
<scenario>.report.htmlis now the advanced run report described under Added, and the CLI'swrote …line names<scenario>.report.jsonas well. The one-page scorecard it replaced is unchanged asRunOutput::html_report(), which the Python and WebAssembly bindings still return.OracleKind::modelled_reason()exposes the sentencedocs/MODELLED-RATIONALE.mdprints for each MODELLED row; that document is unchanged.- The README says what Kshana Pro builds on. The Editions section and the Kshana Pro
line under "Support & professional services" now state what Pro's model-based
systems-engineering (MBSE) and programme tooling does and which of the open engine's
published outputs it reads (each run's
result.jsonandreport.json, the scenario file, the field-units schema and the verification matrix's labels), and what the clock digital twins, trade studies and evidence packs rest on in the open engine. Wording only: no engine output and no published number changes. - Compatibility. The public enum
navsignal::Modulationgains anMbocvariant, which is a breaking change for a caller that matches it exhaustively. Theephemeriskind'sgcrs_r_mandgcrs_v_m_scolumns change (see "Revisions to published numbers"). A run is still reproduced by the engine version that made it.
Fixed#
export_table_csvon the MCP server named four kinds as the only ones with a CSV (comma-separated values) table; six publish one. The tool always returned the table fortelecom-timingand forleo-navmsg(itsencode-decodeanalysis on akepler16orkepler-racmessage model), but its description, its refusal message and the documents that list the kinds left both out, so an agent was told not to ask. They are named now, and a round-trip test fetches both tables.The low Earth orbit (LEO) navigation-message frame was not the same bytes on every platform, nor in every build of one platform.
scenarios/leo-navmsg-encode-decode.tomlencoded a frame with cyclic redundancy check0x110315in the release build on aarch64 macOS,0x898BD8in a debug build of the same source on the same machine, and0x6A82D9in the WebAssembly (WASM) build;leo-navmsg-celeste-iod.tomlgave0x6EDB2Dnatively against0xC6E4F0in the browser. Three causes, each traced to the first diverging value:- Between platforms. The truth orbit was bit-identical on both; the fit's starting
point was not.
truth::state_to_elementstakes the osculating inclination from anacosand the argument of perigee from anatan2, and the two mathematics libraries round those differently in the last place (over 20 000 arguments they disagree for 781 sines, 858 cosines, 1 931 exponentials and 1 899 two-argument arctangents). - Between build profiles. An optimised build on macOS merges a sine and a cosine of
one argument into a single call to the system's combined routine, and its sine is
not the lone
sin's for 379 of 200 000 arguments; an unoptimised build makes the two calls. The kind takes a sine and a cosine together in thirteen places. - In the clock. The normal sampler of
rand_distrcalls the hostexpandlnin its two rare branches: of four million seeded draws, three differed in the last bit between the native and the WASM build.
The Levenberg–Marquardt fit, which on this arc stops at its 80-iteration limit and not at a minimum, carries one unit in the last place into different quantised fields. The kind now computes every transcendental through the new
src/portable_math.rs, which calls the pure-Rustlibmcrate (already in the dependency tree, now named inCargo.toml), compiled from one source for every target and not a library call the optimiser rewrites:leo_navmsg::{truth, fit, elements, codec, sisre, services}and the scenario code. Integer powers on that path are spelt out as square-and-multiply, becausef64::powihas unspecified precision, and the truth clock draws its normal deviates fromportable_math::standard_normal(Marsaglia's polar method on the generator's raw output). The shared routines the truth orbit integrates through are written once, generic over the mathematics library, with a crate-internal_portableentry point beside the existing one: the spherical-harmonic acceleration, the zonal and drag accelerations, the J2 secular rates and the Klobuchar delay. The existing entry points are unchanged to the last bit, so no other kind's number moves.leo_navmsg::tests::the_encoded_frame_is_the_same_bytes_on_every_platformpins the whole 171-byte frame and one check value per ephemeris model, in the ordinary (unoptimised) test profile, on every platform and with no baseline-host gate;the_kind_never_calls_the_host_mathematics_libraryreads the kind's sources from disk and fails on an inherent transcendental call, on arand_distrsampler, or on a call into a module that has not been reviewed. After the change a debug build, a release build and the WASM build produce byte-identical result documents for all fiveleo-navmsgscenarios. This moves numbers, recorded here as a revision. Theleo-navmsgkind is new in this release, so nothing published in an earlier release moves, but everyleo-navmsgfigure does, natively and in the WASM build, and thenavmsgstage ofleo-pnt-chainwith it: the orbit figures because the fit now starts from the portable elements, the clock figures because the truth clock is a different realisation of the same process.docs/LEO-NAVMSG.mdis regenerated from the new results; its published check value for the encode-and-decode scenario was0x110315and is0x19105F(position within 1.126 mm of the exact message where it was 0.665 mm, clock within 0.185 mm where it was 0.451 mm). The size of the changes in the sub-millimetre figures is the fit's sensitivity, not an error in either set: the fit is unchanged and still stops at its iteration limit.- Between platforms. The truth orbit was bit-identical on both; the fit's starting
point was not.
Seeded resampling drew different indices on a 32-bit target.
Rng::gen_rangeoverusizetakes 64 bits from the generator on a 64-bit host and 32 bits on a 32-bit one, so the same seed gave other indices, and left the stream elsewhere, in the WASM build. Three places drew that way: the percentile bootstraps ineval_stats(bootstrap_ci,bootstrap_auc_ci), the shuffle before each training pass inimpairment_ml, and the permutation test inimpairment_study. They now draw throughportable_math::uniform_index, which always samples asu64: exactly what a 64-bit host did, so no native number moves. This moves published numbers of the WASM package, recorded here as a revision: thequantum-anomaly-detectbootstrap interval of the area under the curve (quantum_auc_ci,trade.foms[0].ci95) was[0.9901265, 0.9938305]in the browser and is now the native[0.99035875, 0.993861].STK ephemeris file names from mover ids. A mover id holding a
/or a space (a constellation shell and a satellite,Pulsar inclined/S1-0163) made the CLI panic on--export stk; each id now passes through a safe file part (letters, digits,-,_).The TEME→GCRS nutation sign, described under "Revisions to published numbers" above because it moves published output.
0.28.0 - 2026-09-26#
Added#
slot-timingscenario kind (src/slot_timing.rs): seconds until a free-running clock leaves the guard of a time-indexed routing or tasking slot, every contributing term at that moment with the dominant one named, the time left since the last fix, and the largest fix interval that keeps the clock inside, net of the fix latency. The clock comes from a class default, a telecom-timing datasheet preset, an inline datasheet (Allan-deviation maxima, ageing, temperature coefficient) or a measured phase record. A breach beyond the longest averaging time the source supports is flagged as extrapolated. Bundled example:slot-timing-ocxo-leo. Seedocs/SLOT-TIMING.md.- Measured red-noise floor. A phase record's overlapping Allan deviation is fitted by
weighted least squares in the white-phase and IEEE Std 1139 frequency-modulation basis
(
slot_timing::fit_weighted), each point weighted by its equivalent degrees of freedom, so a holdover answer can rest on the clock's own floor instead of the class assumption. An unweighted fit put a spurious flicker floor on a white-noise test record and shortened a 2 000 s breach by 30 %. - Held-out validation of the holdover inversion on a real clock
(
tests/slot_timing_cs5071a_holdout.rs): fitted on the first third of the 5071A caesium-versus-hydrogen-maser record, the predicted one-sigma breaches at six thresholds from 0.5 to 2.5 ns land within 0.84 to 1.03 of the breaches measured on the other two thirds, against a bar of 1.5 fixed before the test first ran. A control (the one-second Allan deviation read as white noise) misses by a factor of about 1 000 and fails the bar. New VALIDATED ledger row; therealdata-clockworkflow now runs it. - The same prediction on a real crystal oscillator (
tests/slot_timing_ocxo_holdout.rs,scripts/fetch_ocxo.sh): on a measured oven-controlled crystal oscillator the held-out prediction is conservative, at 0.59 to 0.70 of the measured breach and never later, and mostly outside the 1.5 bar, because that oscillator's noise floor halved during the 5.5-hour record; fitted in sample it lands within 0.98 to 1.17. The crystal case is not validated and stays MODELLED. The run also exposed a missing term: a crystal's frequency wanders, so the model now carries the uncertainty of the frequency known at the fix (fix_frequency_sigma); without it the prediction was optimistic by up to 2.5×. - The same prediction on atomic clocks in orbit (
tests/slot_timing_igs_holdout.rs,scripts/fetch_igs_clocks.sh): 14 days of International GNSS Service final clocks for the GPS Block IIF satellites, with the protocol written down before the data was downloaded. Eight of ten satellites land within the 1.5 bar; G25 and G30 predict breaches up to about twice as late as measured. The protocol requires every satellite to pass, so the orbital case is not validated and stays MODELLED; the outcome is pinned. - Temperature-compensated crystal (TCXO), oven-controlled crystal (OCXO) and rubidium
atomic frequency standard (RAFS) clock classes (
ClockClass::Tcxo,Ocxo,Rafs), the parts a commercial smallsat bus or a ground gateway flies, each citing one public datasheet (ClockClass::source).ClockClass::ALL,idandfrom_idadded.quantum-trade'sbaseline_clock_classaccepts the three new ids. Adding variants to a public enum is a breaking change for a caller that matches it exhaustively. - Timing protection level for a receiver in orbit (
src/orbital_timing.rs, theslot-timingkind's optionalspoofingsection): how long a ground spoofer can reach a low-Earth-orbit satellite per pass, the pull a spoofer at a stated maximum ramp rate accumulates before the satellite leaves its reach or an independent check runs, and whether each ground-contact or crosslink check is independent of that spoofer. Reduces exactly totpl::timing_protection_level_nsfor a clock without flicker, white-phase or random-run noise. MODELLED. - Three notes:
docs/SLOT-TIMING.md(including which wander metric a slot should be accepted against: maximum absolute time error, not maximum time interval error or time deviation, and why),docs/DECEIVED-TIME.md(a signed command applied at a deceived time still misroutes) anddocs/DEPLOYMENT-TARGETS.md(where Kshana runs; nono_stdflight core is offered).
0.27.4 - 2026-09-26#
The v0.27.3 release reached crates.io (kshana and kshana-mcp) and PyPI, then stopped
at npm, so npm, the ghcr.io MCP image, the MCP registry, the JetBrains Marketplace and
kshana.dev never received it. 0.27.4 carries the same engine, with the release fix below,
to every channel.
Fixed#
- The npm publish step names the tarball by an explicit path. npm reads a bare
npm-dist/kshana-0.27.3.tgzas the GitHub shorthandowner/repo, so v0.27.3's npm job tried to clonegithub.com/npm-dist/kshana-0.27.3.tgzand failed with "Permission denied (publickey)". The tarball itself had been built and attested. The step now publishes./npm-dist/*.tgz. Everything after npm in the release order (the MCP image, the JetBrains plugin, the channel-parity check and the site) was skipped, as designed.
Changed#
- The telecom-timing and
gnss-insunits catalogs are exempt from copy-paste detection, under the same policy and justification test as the other catalogs. The SonarCloud quality gate had gone red on new-code duplication (4.0% against 3%), and every duplicated block was a units row or an ITU-T mask entry.
0.27.3 - 2026-09-25#
Added#
scenarios/small-uas-jammed-nav.toml: how long a small drone's navigation holds after GNSS is jammed. It runs a flight-controller-class micro-electro-mechanical (MEMS) inertial unit, a Bosch BMI088, against a tactical-grade unit. The GNSS (Global Navigation Satellite System) signal is nominal for 100 s, then denied for 60 s (jamming-demo.tomlshows why a nearby jammer does that). The BMI088's biases are the residuals left after start-up calibration, taken from the temperature coefficients in its datasheet (BST-BMI088-DS000-19 rev 1.9): 0.015 °/s per K for the gyro and < 0.2 mg per K for the accelerometer. Two assumptions are flagged in the file: a 10 K warm-up after calibration, and continued warming of 1 K per minute in flight. The in-flight drift is the error the filter cannot learn before the jamming starts, and it is what ends the coast. Every figure is modelled; validating it needs flight logs from a jammed environment.kshana example small-uas-jammed-navprints it.Each
gnss-inssensor can set its own filter prior ([imu_quantum.filter_prior]or[imu_classical.filter_prior], withsigma_accel_biasandsigma_gyro_bias). The default is unchanged, so every existing scenario gives the same result. A consumer-grade unit's residual gyro bias sits about 26 sigma outside the tactical-grade default, where the filter would never learn it. A prior that is not finite and positive is rejected, and the result echoes the prior when one is set.kshana example [<name>]hands a registry user a scenario to run. Acargo install kshanauser has the executable and noscenarios/directory, so the first command every quickstart gave them failed. The command-line interface (CLI) now carries the 75 reference scenarios that run on their own, byte for byte, compiled in fromscenarios/(src/bundled_scenarios.rs; the table lives in the CLI binary, so the Python wheel and the WebAssembly module do not grow).kshana examplelists them;kshana example clock-holdover > clock-holdover.tomlwrites one. The other two files are repository-only:lunar-llr-datumreads archived lunar laser-ranging data that ships with the repository only, andquantum-pnt-demonstrator.suiteis a study manifest that runs three sibling files with--study. Asking for either says why and exits 2 instead of printing something that cannot run on its own.tests/cli_first_run.rsholds the table to the directory in both directions, so a new scenario file that is neither bundled nor named repo-only fails.Every figure of merit in a clock, orbit, hybrid or fusion result now says whether it is VALIDATED or MODELLED, in the result itself. The result document gains a
figure_tiersblock, always its last key. It lists each reported figure (for examplequantum.fom.timing_p95_ns) with its tier — VALIDATED means checked against an independent external oracle, MODELLED means a first-principles model that is tested internally — and the verification-matrix row the tier is read from. The tier comes fromsrc/verification.rsthroughsrc/fom_label.rs, so there is no second table to drift. Before this, the words "validated" and "modelled" appeared zero times in a clock-holdover result, and a reader of the raw JSON (JavaScript Object Notation) had no way to tell the two apart. Thehybridandfusionposition figures have no owning matrix row, so they are listed underuntieredinstead of being given a tier. The block carries no numbers. It is proven additive, not assumed: removing it gives back the pre-change document byte for byte, for all five kinds (an engine test), and both golden harnesses (tests/registry_golden.rs,tests/cross_platform_golden.rs) now strip exactly this key, and only when it is the last one, then hash the rest against their unchanged frozen constants. A list in each harness pins which scenarios must carry the block, so the exclusion cannot quietly widen.A
telecom-timingkind answers in a timing engineer's units: time error, maximum time interval error (MTIE) and time deviation (TDEV), each with a PASS or FAIL and a margin against the masks of the International Telecommunication Union Telecommunication Standardization Sector (ITU-T). The masks are transcribed from the editions in force: G.8272 (07/2025) for the primary reference time clock (PRTC) classes A and B, G.8272.1 (2024) Amd. 1 (07/2025) for the enhanced PRTC in locked mode and in holdover (including its time-error envelope, which rises from 30 ns to 100 ns over a holdover period set by how long the clock was locked), G.8273.2 (2023) Amd. 2 (11/2025) for telecom boundary and time slave clocks, classes A to D, and G.8271.1 (2022) Amd. 3 (05/2025) for the network limits at reference point C. Every entry those Recommendations leave "for further study" is absent, not estimated, anddocs/TELECOM-TIMING.mdlists each number with its table or clause and each item that was left out. The report also gives the time to exceed each time-error budget: by default the 100 ns ePRTC holdover default, a 400 ns network holdover allocation, the 1 100 ns point-C limit and the 1.5 microsecond class 4 end-to-end requirement.The input is either a synthetic holdover or a series of your own. The synthetic one uses one of four oscillator presets (an oven-controlled crystal oscillator, a rubidium standard, a caesium standard and a chip-scale atomic clock), each carrying the stability, aging and temperature figures of a named Microchip datasheet. How those figures become white, flicker and random-walk frequency noise, aging and a temperature term is stated in every report and labelled MODELLED. A series of your own is
[time_s, time_error_ns]pairs, inline (which is how it runs in the browser) or, on native builds, from a comma-separated values file. The MTIE/TDEV table, with every selected mask's limit beside it, is written as the run's CSV artifact.scenarios/telecom-prtc-holdover-24h.tomlis a 24-hour rubidium holdover with aging, flicker and a daily temperature cycle: its largest time error is 603.2 ns, it crosses 100 ns 8 206 s after the loss and stays inside 1.5 microseconds all day, and it fails the enhanced-PRTC holdover envelope, as a clock that is not caesium-class should.scenarios/telecom-tie-ingest.tomlshows the inline input. The MTIE and TDEV the kind reports are checked against the allantools package on a committed 2 048-sample holdover series, exactly for MTIE and to 1.1e-15 for TDEV, so the matrix gains one VALIDATED row and two MODELLED ones (the mask transcription and the presets) and now stands at 171 rows — 65 VALIDATED, 102 MODELLED, 4 PARTNER. Field-units coverage goes from 60 of 61 kinds to 61 of 62, and from 1,742 to 1,788 described fields. The kind is new and additive: no existing scenario, pin or published figure moves.The playground runs the engine in a background Web Worker, so a slow scenario no longer freezes the page.
web/engine-worker.mjshosts the WebAssembly engine andweb/engine.mjsforwards every scenario execution to it — runs, parameter sweeps and the SP3 / OMM / OEM / CSV exports. A run that outlives 150 ms shows a busy state (Run disabled, "Running… N s") and a Cancel button that terminates and respawns the worker; a browser that cannot start a module worker falls back to the old main-thread path.cislunar-arc-recovery, left out of the catalogue because it froze the page, is back in it, labelled as slow: it takes about 13 s in Chrome (four engine calls of about 3 s each), during which the page stays responsive.Every reported quantity of
cislunar-observabilitynow carries a unit, a provenance class and a definition — the last kind on the crate-wide exemption list but one. All 41 numeric fields of the RELEASED document are described, so field-units coverage goes from 59 of 61 kinds to 60 of 61 and from 1,698 to 1,742 described fields, with the definitionless backlog unchanged at 239.This was blocked by the pin that freezes the released document, which named
unitsas a key the document must never gain. Adding one would have meant deleting that assertion and re-baselining three hashes in three files — a shape indistinguishable from covering up a regression. Instead the two pins that measure this document now prove the addition is additive: each strips theunitsblock back off and re-hashes against the constant frozen before the block existed, and both constants are unchanged. A mutation confirms the guarantee is real — move a released value and re-baseline the whole-document hash the way a careless fix would, and the strip-and-rehash still fails. The third pin, in the generic registry-golden harness, is re-baselined in line with the six siblings that were re-baselined for exactly this reason, and its value-bearingexpect_summaryliteral does not move.realtime-frame-eopreports which truth each Earth-orientation row was scored against, and how often the fallback fired.table3_joint_eop[].{ut1, polar_motion,combined}gaintruth_sourceandtruth_fallback_rows. Thefinalfloor is scored against the Bulletin B final and can only use epochs that publish one; everydNrow is scored againsttruth_pm()/truth_ut1(), which fall back to the Bulletin A rapid value. On the bundled 2026 extract that is 12 of 31 samples at one day, against 0 at the floor — so the floor row and the prediction rows beside it rest on measurably different truth, which the report now states instead of leaving to be inferred from a sample count that rises where nested horizons say it cannot.moonlight-service-volumeemits a size-matched Keplerian baseline.ephemeris_comparison.keplerian_matchedre-runs the illustrative constellation at the RETRIEVED set's own satellite count, present only when the counts differ. The count is part of that design's geometry — RAAN and mean anomaly are both spread as360k/n— so without it a five-satellite retrieved set was scored against an eight-satellite illustrative one and design was conflated with size. For the five-satellite LANS reference set, 8.68 of the 14.24-point coverage gap turns out to be the three missing satellites and only 5.56 points the design; for the four spacecraft really in lunar orbit, a purpose-built four-satellite set reaches 4.86 % where they reach 0 %. A test pins that the matched row is a rebuilt constellation and not the configured one relabelled, which is how it was first written and what the run then reported.Nine capability cards on the public site, covering 22 verification-matrix rows that shipped with no product-level card. The engine had grown a family of capabilities — the ARAIM check against published Working Group C reference vectors, real retrieved lunar constellation geometry, the lunar denial contour with a measured C/N₀ band, the VLBI-schedule-to-station-covariance step, optical and RF handover with cross-modality fault injection, real-time Earth-orientation prediction against archived Bulletin A, navigation-versus-communications aperture sharing, INS/TRN coasting, and unit-and-provenance coverage — each with a matrix row and a bundled scenario, and none of them named on kshana.dev. Card-to-row coverage goes from 72 of 168 rows to 94.
Eight scenarios added to the playground catalogue. A capability card's Run button only appears when
web/app.jsalso carries the file; six cards named a scenario it did not, so they rendered with no way to run them — one of those, the real-laser-ranging datum, had been in that state since it shipped.tests/scenario_examples_doc_sync.rsnow gates the invariant in both halves: a card'sruntarget must be bundled underscenarios/and offered by the catalogue. Mutation-tested against both failure modes.moonlight-service-volumeemits its per-satellite geometry as<scenario>.table.csvwhen an export site is configured (G11). It is the largest array the crate publishes — 2304 rows in the released joint communications-and-navigation table — and it reached consumers only inside JSON, the shape that truncated a sibling scenario's 57-point curve to 23 points under a column claiming all of them. One row per link, the row count on the header line, the antenna columns present only when an antenna was configured. A run with no export site is byte-unchanged and writes no file.The SBOM now describes what ships: 60 components became 66.
scripts/gen-sbom.shlisted every package of the default-feature resolve, dev-dependencies included. That putsgp4andchrono— test-only crates compiled into no artifact — in the bill of materials, and left out the--features pythonchain the PyPI wheel links (pyo3,pyo3-ffi,pyo3-macros,pyo3-macros-backend,pyo3-build-config,portable-atomic,target-lexicon,heck), which is that wheel's foreign-function boundary. The script now walks the resolve graph from the kshana package through normal and build edges only, over the union of the default,pythonandwasmfeature sets (the npm package carries this same SBOM), for every target platform. The pinned CycloneDX conformance verdict is re-baselined through its committed generator: 60 → 66 components, 53 → 60 compound licence expressions, and still zero schema errors once those are placed in the standardexpressionform.The CSV reproducibility table reaches every front door, not only the CLI and the Python wheel. The MCP server gains a seventh tool,
export_table_csv, which runs a scenario and returns the byte-stable table the CLI writes as<scenario>.table.csv— or an error naming the kinds that publish one (realtime-frame-eop,lunar-time-budget,lunar-jamming, andmoonlight-service-volumeonly whenexport_site_lat_deg+export_site_lon_degare set). The WebAssembly module gainstable_csv, returning the same text orundefined. The MCP round-trip test and the Python binding tests now pin the returned CSV byte-for-byte againsttests/golden/realtime-frame-eop.csv, and pin thatRunOutput.write_csvreturns bytes written, and 0 with no file created for a kind without a table. Every surface that states the MCP tool count or lists the tools now says seven.run_allin the WebAssembly module: one engine run for every output.run,chart_svg,summaryandtable_csveach execute the scenario from scratch, so the playground paid for four full runs per click — about 12 s instead of 3 s forcislunar-arc-recoveryin a browser.run_allreturns{json, svg, summary, csv}from one run, and the playground now uses it. It matches the four separate calls byte for byte.
Fixed#
REVISION: the GNSS/INS filter's gyro-bias coupling had the wrong sign, and the
gnss-insoutage figures change. The error-state extended Kalman filter (EKF) keeps both inertial biases as residuals (true minus the running estimate, fed back by adding). The accelerometer column drove the velocity error with+C_b^n b_a, but the gyro column drove the attitude error with−C_b^n b_g. A residual gyro bias actually rotates the computed attitude by+C_b^n b_g, so every gyro-bias estimate pushed the wrong way. The pack stayed stable only because its default gyro-bias prior (1e-4 rad/s, 1-sigma) was tight enough to hold those states still. With a wider prior the filter diverged even when the true gyro bias was zero: 93 m of fused outage error became 4.3 km. The published figures forscenarios/gnss-ins.tomlchange as follows:gnss-ins.tomlbefore (≤ 0.27.2) after cold-atom IMU, fused outage RMS 96.1 m 1.9 m cold-atom IMU, in-spec coast (50 m) 30 s 60 s (the whole outage) tactical IMU, fused outage RMS 62.5 m 3.1 m tactical IMU, in-spec coast (50 m) 37 s 60 s (the whole outage) The free-running figures (130.7 m and 314.3 m) do not change; they never used the filter. The pack's earlier reading, that the fused coast is limited by a hand-over attitude floor and so does not improve with a better sensor, was an artefact of this bug and is withdrawn. With constant, noise-free biases the filter now learns them while GNSS is up, so the fused coast is a best case. The external filterpy reference (
tests/gnss_ins_sensor_fusion_reference.rs) checks only the measurement updates, which is why it never saw the propagation. Two tests now check the sign directly and both fail on the old one. The first, inclosed_loop.rs, feeds a known residual gyro bias through the strapdown and requires the filter to predict the resulting attitude error to within 2 %. The second requires a wide prior to learn a 1e-3 rad/s gyro bias and stay inside 6 m. Thegnss-inscross-platform golden is re-pinned for this reason and no other.Every registry quickstart now runs as written on a clean machine. Measured before this change, the crates.io and PyPI (Python Package Index) examples read
scenarios/clock-holdover.toml, which a registry install does not have, and the npm example crashed under Node.js withTypeError: fetch failed, because the package'sinit()fetches its WebAssembly binary and Node'sfetchcannot read a local file. Now:README.crates.mdandREADME.pypi.mdcarry that scenario inline, asREADME.npm.mdalready did. The Rust fence is a whole program with its ownmain, since the rustdoc-hidden# Ok::<…>line it ended with is shown on crates.io and does not compile when pasted.tests/readme_code_fences_doc_sync.rsstill compiles it byte for byte, and now also refuses a hidden line in the fence.README.npm.mdseparates the browser from Node.js and gives a Node recipe that reads the binary from disk and passes it toinitSync. The doc-sync test acceptsinitSyncas the loader wasm-bindgen generates.- All three embed the scenario with its provenance strings unshortened, so the
scenario hash is
5ba83a232b94on every surface. The npm example used to shorten them, which gave it a different hash from the CLI. - Reproduced from local builds of this tree: a fresh
cargo newproject with a path dependency on the packaged crate built and ran the Rust example; a wheel built with maturin from the packaged sources, installed into a new virtual environment, ran the Python example; and thewasm-pack --target webpackage, packed and installed with npm, ran the Node recipe. All three printed the same summary line.
cargo run -- <scenario.toml>works from a clone.Cargo.tomlsetsdefault-run = "kshana". Without it cargo refused with "could not determine which binary to run", which everycargo run --line in the documentation hit.The CLI's first-minute errors say what to do next. A scenario path that does not exist now adds a hint pointing at
kshana example, and a second positional argument prints the usage text as well as naming the argument.--help,--version, the unknown-option error (exit 2) and--validate's one-lineok:on success were already on main.tests/cli_first_run.rsnow covers all of them, since no test ran the binary for any of them before.list_kinds()in Python is documented as returning one JSON string, not a list. It returns the same metadata asscenario_kinds(), serialised, so iterating it walks characters. It keeps its string return so existing callers do not break.kshana.pyi, the binding's doc comment,docs/PYTHON_API.mdandREADME.pypi.mdnow say so and point toscenario_kinds().The three registry READMEs spell out every abbreviation at first use. PNT is now expanded in the tagline, where it first appears, and CLI, TOML, JSON, SVG, CSV, CSAC, HTML, URL and API are expanded in the files that use them. The oracle-table heading no longer says "CI-gated" before CI is defined.
Corrected a published figure: dual-constellation ARAIM availability on the vendored Celestrak GPS + Galileo TLEs.
docs/CAPABILITY.mdstated that pooling Galileo lifts availability from 0.21 to 0.67 under a 12 m VAL and that the constellation-fault-robust mode is limited with only two constellations. That came fromtests/araim_dual_real_data.rspropagating every satellite from its own TLE epoch (parse_propagatorsdrops the epoch), and the epochs in those files span 70.6 h (GPS) and 335.7 h (Galileo), so the satellites sat at mutually inconsistent times. The test now keeps each epoch (parse_tle) and evaluates every satellite at one common UTC instant, the latest TLE epoch in the set (2026-06-07T07:21:04). The measured availability over the same 24 h, 289-sample grid becomes GPS-only 0.993 (was 0.208), pooled 1.000 (was 0.671) and constellation-fault-robust dual 0.990 (was 0.031); at the APV-I limit the dual mode goes from 0.180 to 1.000. The conclusion inverts: on a consistent sky two constellations carry the constellation-fault hypothesis at a 12 m VAL. The same figures result with the earliest TLE epoch as the reference. A self-check in the test pins its availability loop sample for sample toaraim_dual_constellation_availability, so the only difference is the time alignment.docs/CAPABILITY.mdanddocs/REAL_TLE_GUIDE.md§3 carry the new numbers; the 0.13.0 entry below is left as released.The public surfaces no longer advertise RINEX 4 as validated. The engine now refuses a RINEX 4 navigation file by name instead of mis-decoding it, but the standards grid still showed a "RINEX 3 / 4" card with a validated pill, bound to the navigation parser's RINEX-3 evidence. The card is now "RINEX 3", and README,
docs/CAPABILITY.md,docs/STANDARDS.md,ROADMAP.mdand the capability summary state what is true: RINEX 3.0x observation files parse, the 4.00 observation layout is expected to but no 4.00 file has been read through the code, and RINEX 4 navigation files are refused.--validatesays when "ok" means there was nothing to check. About two thirds of the catalogue publishes no required field — those packs run their reference configuration from an empty body — so the lint could only ever print "ok" for them, in the same words it used after checking six fields. The success line now states the number of required fields it found present, or that the kind requires none.scripts/gate.shrefuses a non-numericREPEATorTEST_THREADS. Under bash 3.2,REPEAT=nomade the numeric comparison error and evaluate false, so the gate announced "repeatability loop SKIPPED (REPEAT=0)" for a value that was not 0 and still wrote a receipt. Both knobs now exit 2 unless they are plain integers.The MSRV is stated as 1.85 everywhere a person reads it.
Cargo.tomlmoved to 1.85 last release; the README badge, the install and troubleshooting text,codemeta.jsonand the MCP crate's own rationale still said 1.75.RunOutput.__repr__in the Python binding reports bytes, not "chars". It printedlen()of UTF-8 strings, which is a byte count: 710 "chars" for a 708-character table.The README no longer documents
--study --study-nametogether.--study-nameis a single-scenario flag, ignored with--study(the binary warns). The two copy-paste examples in README.md and README.crates.md now show the study command alone.The site's clock-stability evidence states both observed tolerances. The 5071A caesium series is reproduced to ≤ 3e-5 and PHASE.DAT to ≤ 5e-5; the paragraph gave 3e-5 for both.
Two engine-output files are no longer tracked.
scenarios/quantum-pnt-demonstrator.study.{json,html}were stamped by engine 0.20.0 with every figure of merit empty, and the README's own study command overwrote them, dirtying the tree and blocking the push gate.*.study.json/*.study.htmlare now ignored like the other four engine outputs.A seed sweep in the playground no longer fails on every run. The sweep stepped the integer
seedknob linearly (1, 10.9, 20.8, …), and the engine, reading it asu64, rejected each fractional value. Integer knobs now sweep distinct whole numbers.Every playground download says which engine and which input made it. A few kinds (e.g.
realtime-frame-eop) emit noengine_versionorscenario_hash, so their downloads were named plainkshana-csv.csv. The name now falls back to the loaded engine's version and a 12-hex FNV-1a-64 fingerprint of the scenario text.
Changed#
The published crate is less than half its former size, and
cargo install kshanabuilds the CLI alone. The package used to include the papers, their submission packages, the paper-figure artifacts, the notebooks,docs/, the maintainer scripts and the example programs. None of them is compiled by the library or the CLI. It also included the three internal generators undersrc/bin/(crossover_study,gen_validation_artifacts,validation_report), so a registry install compiled and installed four executables, three of them useful only inside a checkout. Those trees and the three generator sources are now excluded. Cargo lists a package's binaries from the files it contains, so the published manifest nameskshanaonly. In a clone the generators are still found automatically, socargo run --bin <name>and the release job that runsvalidation_reportare unchanged. Measured withcargo package: 763 files, 16.7 MiB, 6.8 MiB compressed before; 591 files, 10.5 MiB, 3.0 MiB compressed after. Its verification build, which compiles only the packaged files, passes.cargo install --pathon the packaged crate installed one executable,kshana.Output a timing engineer can read: no more
p95 0.0ns, no moresecurity 0.000with no attack, and site counts that match the READMEs. This is a display revision. No published number changes; the JSON values are identical, which the golden hashes above prove.- The one-line summary printed the optical clock's 95th-percentile (p95) timing error
as
0.0nswhile the table beside it showed 1.20e-4 ns. A non-zero timing figure too small for one decimal place is now printed with three significant figures (p95 1.20e-4ns); an exact zero still prints as0.0. The HTML (HyperText Markup Language) report, the study comparison table and the playground's downloadable report use the same rule, so no non-zero figure prints as0.000either. securityis an analytic spoof-detectability bound that means something only against a configured attack. Theclock,orbit,hybridandfusionkinds configure none, so their summary now printssecurity n/a (no attack)instead of a number such as0.000that reads as a failed detection. The value stays infom.security, markedapplicable: falsewith the reason infigure_tiers, and the report tables show "not applicable". Thespoofkind still scores detection.- Two summary pins in
tests/registry_golden.rsmoved for exactly these two reasons and no other:golden_clock(the quantum p95 and bothsecurityvalues) andgolden_orbit(bothsecurityvalues; its p95 values are exactly zero). Their whole-document hashes did not move. The expected summaries in the README, the three tutorials and their teaching scenarios are updated to match, andtests/tutorials.rschecks the teaching copies against the engine. - The playground's guided tour no longer opens on first load. Its modal caught the first clicks and its spotlight scrolled the page away from where the visitor landed. It starts from the "Take the 60-second tour" button or the floating Tour button.
- The page no longer scrolls sideways on a phone. At 390 px the brand row and the GitHub button came to about 410 px of content, a 4 px overflow; the header now fits from 320 px up, and the figures-of-merit table scrolls inside its own box instead of widening the page.
- The capability explorer led with its own card count ("46 capability cards … 17
backed by an external oracle") beside READMEs that say 64 of 168. It now leads with
the verification matrix, read from the generated
web/data/verification-matrix.json(the same matrix the README counts are pinned to), and names the cards afterwards as the summary layer they are.web/counts.test.mjs, a new step in the continuous-integration (CI) workflow, checks the arithmetic and cross-checks README.md and the page's descriptions;tests/web_validation_counts_doc_sync.rspins the wiring from the Rust side.
- The one-line summary printed the optical clock's 95th-percentile (p95) timing error
as
The public description now leads with timing and holdover, the best-validated domain. The README opening and
docs/POSITIONING.mdpresent Kshana as an open, reproducible, provenance-labelled PNT-resilience evidence engine: critical-infrastructure timing and holdover first (the Allan, modified Allan, time-deviation and maximum time interval error estimators and the holdover coast-variance inversion are the VALIDATED rows behind it), quantum as a neutral quantum-vs-classical trade method whose results are labelled MODELLED, and lunar / cislunar and deep-space navigation as maintained capabilities rather than the headline. Both pages now say what Kshana is not — not a radio-frequency signal simulator or hardware-in-the-loop rig, not a replacement for MATLAB/Simulink, STK or Orekit — and point to the newtelecom-timingkind anddocs/TELECOM-TIMING.md. The old line "there is no good open tool" is gone: a search that finds nothing is not evidence that nothing exists.ROADMAP.mdgains an undated priority order (timing first, the quantum trade second, lunar maintained) and drops three stale items (Coriolis and light-shift systematics, the NRHO initial conditions and the lunar scenario, all shipped); the README's "Coriolis and light-shift remain roadmap" line is corrected the same way.docs/CAPABILITY.mdstopped underclaiming. The Earth-fixed frames row said "none" while the matrix grades GCRS→ITRS VALIDATED; it is now "full", and the core frames row records the ANISE cross-check as delivered. Guided mode was listed as roadmap; the playground has shipped guided sliders and a guided tour. A sweep againstsrc/verification.rscorrected five more rows that predated their validations: the Cowell force model and batch/sequential orbit determination against Orekit 12.2, ground-station passes against Orekit's elevation detector, the dilution-of-precision kernel against gnss_lib_py, and OEM import against the independentoemparser; the verification-and-validation row now names the guards that check every cited test exists.The glossary explains what a result file says.
docs/GLOSSARY.mdgains plain-language entries for the output terms (holdover, p95, integrity, the security score, PDOP,sigma_y,q_wf), the telecom timing terms (time error, max|TE|, cTE, dTE, MTIE, TDEV, PRTC, ePRTC, T-BC, T-TSC, OCXO, CSAC) and the three evidence tiers (VALIDATED, MODELLED, PARTNER).Abbreviations are spelled out at first use in every hand-written public doc. The README and 24 files under
docs/had several hundred abbreviations used before, or without, their expansion; each is now expanded where it first appears in that file. The three generated files (docs/VERIFICATION-MATRIX.md,docs/MODELLED-RATIONALE.md,docs/SCENARIOS.md) are left alone: they are written fromsrc/verification.rsandapi::list_scenario_kinds(), so their wording has to change at the source.Line coverage has a measurement of record.
docs/COVERAGE.mdrecords 95.63 % (37,697 of 39,419 lines) from the CIcoveragejob onb1d350d, andtests/coverage_figure_doc_sync.rspins the five public "~96%" surfaces to it — the one headline number that was hand-maintained, and had already moved once unnoticed.New doc-sync gates for surfaces nothing read.
tests/readme_code_fences_doc_sync.rschecks everyimport { … } from "kshana"in a JavaScript fence against the WebAssembly exports, and pins README.crates.md's Rust example byte-for-byte to a copy the test binary compiles.tests/security_md_doc_sync.rsfails ifsrc/lib.rsloses#![forbid(unsafe_code)]or SECURITY.md stops stating it.tests/no_overclaims.rsnow also scansweb/README.mdand the three registry READMEs, and every standard on the site must state itsproofexplicitly.The two real-hardware clock checks run on a runner.
realdata-clock.yml(monthly and on dispatch) fetches the 5071A and PHASE.DAT series and runs both tests withKSHANA_REQUIRE_REALDATA=1, so a missing input fails instead of skipping. Both fetch scripts are pinned to one allantools commit and verify SHA-256.The Python type stub is checked against the built wheel (
mypy.stubtestin the bindings job), which is howcsv/write_csvshipped unstubbed.The glossary defines URE, EOP, FoM, CTI and TIB, which the README and ledger used bare.
Every kind's published field contract is now true, and a test holds it there. Five of the 61 kinds changed. Kinds with a non-empty
required_fieldsgo from 21 to 22:ephemerisnow requirestle|orbit+epoch(a barekind = "ephemeris"used to pass--validateand then fail the run), andquantum-tradeaddscandidate_adev_taus+candidate_adev_values|candidate_clock_class, which its run already demanded. A required entry may use|(one of these) and+(all of these together);--validateunderstands both.lunar-joint-od-clock,pvtandearth-gnss-lunarpublish 9 real optional fields they had left out.docs/SCENARIOS.mdnow sayskindis required for every kind exceptclock.tests/required_fields_are_true.rschecks every kind against its shipped scenario: removing any required entry must make the run fail, and a document holding only the required fields must run.The JetBrains Marketplace notes are gated.
scripts/check-version-sync.shnow fails unless the newest<change-notes>entry inplugin.xmlnames the version being shipped; seven published versions had served the 0.22.0 notes unchanged.The technical report's coverage figure is the measured one.
paper/kshana-technical-report.mdsaid "near 97 %"; it now says near 96 % (95.63 % measured,docs/COVERAGE.md) and is the sixth surface the coverage test pins.The off-main-thread engine client is tested (
web/engine.test.mjs, in CI): dispatch allowlist, cancel and respawn, the three main-thread fallback routes, and crash recovery, against a fake worker.Nothing is published until the tagged commit has passed its tests, and a release is not done until every registry serves it. The order is now tag, verify, publish, parity, site, in one run of
release.yml(docs/RELEASING.md).- Publish waits for the verdict.
publish.yml,mcp-publish.ymlandjetbrains-plugin.ymlno longer start on the tag push;release.ymlcalls them from jobs thatneeds: verify. Before, they raced it: on v0.27.2 crates.io, npm and the Python Package Index (PyPI) had all published within 4.5 minutes of the tag, andverifyreturned 84 minutes after it; on v0.27.0 npm and PyPI published while the crates.io job failed. A manual retry on a tag is held by the newscripts/check-release-verdict.sh, which requires a greenverifyfor that exact commit from the Actions interface and fails closed on anything else. - Everything is built before anything is uploaded, and crates.io, whose packaging step is the one that failed mid-release, publishes first.
- A missing registry token fails the job instead of skipping the upload and reporting success.
- A parity check after publishing.
scripts/check_channel_parity.pypolls crates.io (kshanaandkshana-mcp), npm, PyPI (the source distribution and all six platform wheels) and ghcr.io until each serves the version, or fails the run; docs.rs and the Model Context Protocol (MCP) registry are reported, not required. Run against past releases it finds 0.22.0 missing from npm and PyPI, 0.23.0 from PyPI and 0.27.0 from crates.io. - Prebuilt command-line binaries for macOS (Apple silicon and Intel) and Windows
x86-64, named by target (
kshana-aarch64-apple-darwinand so on), beside the Linuxkshana, whose name is unchanged. ASHA256SUMSfile covers every asset, andverify-releasechecks it and runs the macOS and Windows binaries on their own systems. - kshana.dev shows a release, not
main. It used to redeploy on every push tomainwhile its pages named the last release.pages.ymlnow builds only a release tag: the release dispatches it once every channel serves the version, and a manual dispatch redeploys the latest release (or a named one). - One source of wheels.
publish.yml's hand-copied six-target matrix is gone; it callswheels.yml, so the auditwheel tag gate and the byte-reproducibility check now grade the wheels PyPI receives, and a failure in either stops the release. - Pinned build tools. Every workflow requests Rust 1.93.0 (the msrv job its own
version);
publish.ymlandpages.ymlhad requested@stable, and the v0.27.2 log shows rustup installing 1.98.1 and then compiling with 1.93.0 only becauserust-toolchain.tomloutranks it. wasm-pack is pinned to 0.13.1 (the version that built v0.27.2, which the unpinned installer script chose) with a checksum check, and mcp-publisher to 1.8.1 with its published SHA-256.scripts/check-toolchain.shnow fails if a workflow asks for another compiler or pipes a downloaded script into a shell. No verification job, golden pin or local gate script was removed or loosened.
- Publish waits for the verdict.
Security#
Every third-party GitHub Action is pinned by commit SHA — 93 references across 15 workflows, 92 of them newly — with the tag kept as a trailing comment for Dependabot to update. The exception is
dtolnay/rust-toolchain, whose tag is the toolchain selector.The MCP server image's base layers are pinned by multi-arch digest, and Dependabot now watches
mcp/kshana-mcp's Dockerfile, so the OS layers still receive security updates — through a reviewed PR.The tag-versus-manifest check runs in the two publish workflows that ship the tag's version.
mcp-publish.yml(ghcr image, MCP registry) andjetbrains-plugin.yml(Marketplace) racepublish.ymlrather than wait for it, so a mis-cut tag was gated only in a sibling. Both now runscripts/check-version-sync.shbefore building.Four advisories Dependabot raised the day its alerts were switched on are fixed. The MCP server moves from
rmcp1.7 to 2.2: three advisories againstrmcp< 2.1 (a Streamable-HTTP session-table leak, missing OAuth protected-resource validation, custom headers following a cross-origin redirect) — none reachable here, since the server speaks stdio only, but a published crate should not pin a vulnerable SDK.rmcp2's macros need Rust 1.88, sokshana-mcpalone now declaresrust-version = "1.88"; thekshanalibrary stays at 1.85. The one API change (Content→ContentBlock) is applied, and the round-trip tests pass. The SonarQube scan action moves from v5 to v6, which fixes an argument-injection advisory (≥ 4.0, < 6.0).
0.27.2 - 2026-09-22#
Added#
Every Rust item a verification-matrix row cites is now resolved against the crate. The existing guard checked the citations shaped like file paths; 63 of the 164 non-partner rows name their evidence as an in-crate item instead —
navsignal::code_tests,integrity::tpl_scalar::tests,api::tests::tracking_loop_kind_round_trips_through_the_dispatch— and a token with no.rssuffix was invisible to a path scanner. Those rows could have named a renamed module, an emptied test module or a function that never existed, and every gate would have stayed green.tests/verification_rows_name_a_test_that_exists.rsresolves all 339 such citations through the module tree, requires every non-partner row to name at least one test that actually carries#[test], and requires partner rows to name none. Four mutations were used to grade it: renaming a cited module, pointing a row's only test citation at a non-test function, renaming the realmod tests, and stripping the#[test]attributes from it — each goes red on the assertion that owns it.One row was already wrong: Alternative / complementary PNT cited
tests/*, a glob, which is not a citation. It now namestests/alternative_complementary_pnt_reference.rsand the three in-crate test modules behind it.The published figures are re-checked against the engine on every build (
tests/published_figures_still_reproduce.rs). The three README demo charts are the engine's ownchart.svgoutput and nothing re-ran the scenario; the paper's two crossover studies were generated once and committed; and the README states four figures of merit in prose besidescenario-fom.png. All three are now pinned to live engine output.Reconciling them turned up the useful result: the demo charts had been drawn at engine 0.22.0 and the paper's crossover JSON at 0.20.0, and at 0.27.1 every plotted value in all five artefacts is identical — the three chart SVGs differ from a fresh run by exactly two characters each, the version stamp in the footer. The charts have been re-rendered so the footer is true. The paper's JSON is deliberately left stamped 0.20.0: it is the record of the run whose figure a published paper embeds, and restamping it would claim a provenance the PDF does not have. The guard asserts the stronger property instead — that the current engine still reproduces every published value.
Fixed#
SonarCloud analyses were recorded as
VERSION=not provided, so theprevious_versionnew-code period never advanced: it was still anchored at the 2026-07-02 analysis, grading eighty-two days of work as a single delta and measuring 7877 "new" duplicated lines against a 3 % threshold. The scan now passes the crate version. The underlying duplication is real and is not hidden by this: it is concentrated in the declarativeFieldUnitregistries repeated acrossensemble,hybrid,fusion,inertialandreport, and in the 168-row verification table, and it is named here as work rather than excluded from measurement.scripts/gate.shhad no single-writer lock, and every run wrote the same log. That is not tidiness: the receipt'sintegration_binaries,tests_passedandtests_ignoredare read back OUT of that log, and the pre-push hook trusts the receipt. A gate that was killed but whose test binary was still alive kept writing into the file the next run had just truncated — so one run's counts could have been certified as another run's, authorising a push on the strength of a suite that never ran on the tree being pushed. It is now one gate per checkout (refusing with exit 75 alongside a live one, clearing a stale lock whose pid is gone) and one log per run, withtarget/gate-run.logrefreshed from the finishing run on the way out. Both directions are tested: a live pid refuses, a dead one is cleared.src/verification.rsclaimed its tests "do not prove the named test/oracle strings resolve to live code". Two guards now do exactly that, so the module doc said the opposite of the truth; it now states what is machine-checked and what is left to human judgement. A stale reference toverification::gen— the module isartifacts— is corrected in the sibling guard's own documentation.
Changed#
mainwas rewritten on 2026-09-22 and force-pushed. Ten commits became four. The rewrite folded each commit that left a gate red into the commit that cleared it, so every commit onmainnow has a tree that passes, and removed the cancelled runs that a branch-wide concurrency group had been leaving on every superseded commit.Nothing was discarded. The pre-rewrite commits remain reachable through the tags that point at them, and the final tree is byte-identical to the tree the rewrite started from —
git diffbetween the old tip and the new one is empty apart from the three workflow files this release changes.The superseded SHAs, for anyone holding a reference to one:
old subject 9733177docs(changelog): disclose the authorship rewrite and the two dangling crate sha1s 5d16ea8fix(docs): the engine-flow diagram said 6 figures of merit; the engine scores 7 148d31dfix(test): two more cross-platform pins, and two wrong assumptions of my own a3a2667feat(web): surface the lunar cluster on the site — seven capability cards 1e8bec6docs(lunar): document 42 public fields and ratchet the ceiling down to 985 7bba514chore(release): v0.27.0 3154548test(docs): guard the distribution diagram, the last unguarded one 7864142chore(release): v0.27.1 The v0.27.0 and v0.27.1 tags were deliberately NOT moved. They still point at
7bba514and7864142, the exact commits whose trees produced the artefacts now live on crates.io, npm, PyPI, ghcr.io and the MCP registry. A crate's.cargo_vcs_info.jsonrecords the sha1 it was packaged from, registries are immutable, and the SLSA build-provenance attestation on the v0.27.1 release assets is bound to that commit. Moving the tags would have left every one of those published artefacts pointing at a commit that no longer exists. Leaving them keeps the old commits reachable and every published provenance chain resolving, at the cost of two tags that are no longer ancestors ofmain— which is why this release exists: v0.27.2 givesmaina tag of its own, packaged from a commit that is on it.This is the second disclosed rewrite of this history; the first, on 2026-09-21, canonicalised authorship and is recorded in the 0.27.0 notes with its own two dangling crate sha1s. A rewrite is disclosed here every time, because a repository that sells verifiable provenance cannot quietly move the ground under a published artefact.
0.27.1 - 2026-09-22#
Fixed#
The published crate did not compile.
src/lunar_orientation.rsholds a top-levelinclude_str!oftests/fixtures/llr_geometry/de440_moon_pa.csv, and the manifest excludes/tests/fixturesfrom the package, so the tarball crates.io receives was missing a file the library itself embeds. The v0.27.0 publish failed its verification build for that reason and no 0.27.0 reached crates.io; npm and PyPI, whose jobs do not depend on it, published normally. The exclusion now carries an exception for that one catalogue.Every other
include_str!of a fixture insrc/sits inside a#[cfg(test)]module and so never reaches the packaged library. Only a top-level one does.Nothing in CI ran
cargo package, which is why a tarball that cannot compile was first discovered by the publish job, after two other registries had already shipped. Apackagejob now builds and compiles the exact tarball on every push.
Changed#
- The lunar-frame-realisation emission pins no longer move on a version bump. The SVG footer carries the engine version, so every release re-baselined three hashes — twice in one day — and a pin re-taken by routine stops being read. The version string is normalised to a placeholder before hashing, and the emission is separately asserted to state the running version, so that fact is checked more precisely than the hash ever checked it.
0.27.0 - 2026-09-22#
Added#
lunar-llr-datum— the lunar frame datum from a real observing campaign.lunar-frame-campaignreplaced an injected Helmert transform with a simulated campaign and said so: its station network, its schedule and its per-observation sigma are illustrative inputs. This scenario removes the simulation from the two places where a schedule and an error model enter the answer. The epochs are the transmit times of 337 archived ILRS lunar laser ranging normal points (2015-04-08 .. 2015-06-27, Grasse MeO 7845 and Matera MLRO 7941, all five retroreflector arrays), and every observation weight is that normal point's own archived precision,bin_rms / sqrt(n_raw)— a median of 5.13 mm of one-way range, out of the file rather than chosen here. Station coordinates come from IERS ITRF2020 and the reflector coordinates from JPL DE430 Table 7; each fixture records a URL, a retrieval date and a SHA-256 and regenerates from its source by a committed generator that verifies the source and aborts rather than emit a number. The report names which links are measured and which remain modelled, and the modelled ones are measured: the observed-minus-computed one-way range over the real data is 156 494 m RMS, which the same test set confirms against JPL Horizons over the same span (195 655 m RMS vector, worst epoch 0.054° — inside the ~0.3° the built-in analytic lunar series claims for itself). Additive: a new kind, a new reader (realdata::llr_crd) and a new module (lunar_llr); the existing lunar frame packs are pinned bit-for-bit.sigma_ure_mas a scenario parameter onlunar-integrityandmoonlight-service-volume. The signal-in-space ranging accuracy was a compile-time constant (LUNAR_SIGMA_URE_M), so a service-volume sweep could only ever answer pass/fail at one fixed value. Protection levels are exactly linear in it, so exposing it turns the sweep into a ranging-accuracy requirement over the whole volume. The default reproduces prior behaviour bit-for-bit.Per-satellite geometry export on
moonlight-service-volume:export_site_lat_deg+export_site_lon_degadd aper_sat_geometryarray giving azimuth, elevation and slant range to every satellite at every epoch for one named selenographic site, plus the visibility flag. The aggregate coverage summary deliberately collapses per-satellite geometry, but slant range is exactly what a link budget consumes, so a joint communications-and-navigation analysis could not be done from the summary alone. Off unless both coordinates are given; purely additive.lunar_service::topocentric, the public look-angle helper behind the export.A verification-matrix row for the new geometry capability.
The real antenna pattern in the geometry export. The engine has carried a uniformly-illuminated circular-aperture pattern since P1 (
antenna::pattern_gain_dbi, the Airy[2·J₁(x)/x]²form) and nothing outsideantenna.rsused it — a boresight gain paired with a gain-to-beamwidth rule of thumb was doing the work instead. An optionalexport_antennatable onmoonlight-service-volume(read only when the export site is set) now gives every exported row the off-boresight angle at the satellite and the transmit gain toward the site from that pattern, and adds anantenna_patternblock that reports the in-beam count under the real pattern beside the in-beam count under the symmetric approximation (θ₃dB[deg] = √(31000/G_lin)) with the difference as a named correction. Both numbers are emitted; neither replaces the other. Measured at the engine's own representative lunar aperture (1 m dish, 2.4 GHz, η = 0.60) over a south-polar site and the illustrative LCNS-class shell, the two disagree by more than one satellite: the real pattern puts 0 of 76 visible links inside the half-power beam, the approximation claims 28 — a correction of −2.33 satellites per epoch, worst single epoch 3. The approximation carries an aperture efficiency of its own (0.641 against the70·λ/Ddegrees rule it is normally quoted with, 0.920 against a uniform circular aperture), so on a η = 0.60 dish it returns a beam 1.238× too wide; the block emits both implied efficiencies rather than leaving them to be inferred. Purely additive: measured over the documented working point, 597 pre-existing leaves, 0 changed, 0 removed, 494 added.antenna::symmetric_beamwidth_rad,antenna::symmetric_relation_implied_efficiency,antenna::within_half_power_beam,antenna::HALF_POWER_DROP_DB,antenna::SYMMETRIC_GAIN_BEAMWIDTH_CONST_DEG2,antenna::UNIFORM_APERTURE_HPBW_COEFFandlunar_service::nadir_off_boresight_rad, the public pieces behind it. The pattern is also now anchored against the published Airy constants rather than only against itself: the half-power crossing is located by bisection and compared withx = 1.61634, which makes the exact half-power width1.02899·λ/Dand records honestly that the conventional1.02coefficient putspattern_gain_dbiat −2.955 dB, not −3.010 dB.A long-form reproducibility table for
lunar-time-budget. The scenario's array-valued outputs — the averaging-time grid, the seven per-termx(τ)curves and their root-sum-square total — reached consumers only as JSON arrays. One released table was truncated at 400 characters and published 23 of its 57 averaging times under a column that claimed all of them, and the manuscript rebuilt the per-term curves from closed forms because the engine never emitted them. A plain run now also writes<scenario>.table.csvwith one row per (grid index, averaging time, term), which cannot be truncated into something that still looks whole. The grid index is an exact join key;τis written to 13 significant figures and eachxto 7, so the bytes do not fork between builds of the same source. Purely additive: the report JSON is byte-identical.The
hybrid-optical-rfreport now describes itself. Alink_configurationblock echoes the resolved link inputs — carrier wavelength, transmit and receive aperture, range, pulse width, integration time, efficiencies and losses — with defaults applied, so a paper states the configuration it ran at instead of quoting a default read out of the source. Aunitsblock gives the unit and provenance class of every quantity a paper is likely to quote. The handoff covariance traces are the reason it exists: they were emitted as a barevarianceand a manuscript inferred square metres from an internal consistency check. The inference was right, which is exactly why it was a defect — nothing would have caught it being wrong. Purely additive: measured across four configurations, 456 pre-existing fields, 0 changed, 0 removed, 196 added.A verification-matrix row for the time-budget reproducibility table, honestly
InternalConsistency/ MODELLED — the total is checked against the root-sum-square of the terms in the same file, which shares the engine's own term definitions and is therefore not an independent oracle. Together these add three rows. The running total for this unreleased section is 168 rows — 64 VALIDATED, 100 MODELLED, 4 PARTNER.
Changed#
Commit authorship across the whole history is now a single identity, and two published crates point at a commit that no longer exists. On 2026-09-21 the git history was rewritten so that every human commit is authored and committed by
ashfordeOU <236818772+ashfordeOU@users.noreply.github.com>. It previously carried three spellings of one person — 688 commits asashfordeOU <contact@ashforde.org>, 34 asChakshu Baweja <contact@ashforde.org>and 94 already canonical. The onedependabot[bot]commit was deliberately left as its own author: relabelling a bot's dependency bump would claim authorship of work nobody here did.No content changed. The rewrite touched author and committer headers only, and that is checkable rather than asserted: the tree of every tag and every branch is bit-identical to what it was before.
v0.26.0still resolves to tree92284e9420605568f1bfabb78cd95d383b40f3e2, exactly as it did.What it cost. A commit's name is a hash over its tree, its parents and its author headers, so changing an ancestor renames every descendant. The
v0.26.0release commit wasdce2dbf7700788cf6de46260c14673c0e5347360and is nowd4921f3025569e00e425e1747d0acfc261e96e18. Two artifacts recorded the old name at publish time and cannot be corrected, because a published registry version is immutable by design:kshana0.26.0 on crates.io —.cargo_vcs_info.jsonrecordsdce2dbf…kshana-mcp0.26.0 on crates.io — the same sha1,path_in_vcs: mcp/kshana-mcp
Nothing else is affected. The npm package records no
gitHead, and the PyPI distribution records no commit at all; both were checked rather than assumed.There is no repair that makes
dce2dbf…name the rewritten commit: that string is the old ancestry, and the only way it resolves is to keep the pre-rewrite history published, which would restore the authorship the rewrite removed. Provenance for 0.26.0 therefore runs through the tag rather than through the recorded sha1 — and the tag delivers the identical bytes the crate was built from. The next release records a valid commit with no action needed.
Fixed#
realtime-frame-eopdefaulted to an EOP file with no prediction rows (G12). The scenario embedded a five-row final-onlyfinals2000Aexcerpt as its offline default, so a bare run reportedpredicted_rows.n = 0and the per-horizon table rested on 5, 4, 3 and 2 pairs. The engine could always read a prediction row — the input simply had none. The default is now the verbatim IERS 2026 extract (tools/finals2000A_2026.txt, MJD 61173–61204): 20 Bulletin B finals and 12 Bulletin A prediction-only rows, so a bare run with no file argument and no network reportspredicted_rows.n = 12and a per-horizon table at n = 20 / 31 / 30 / 29, and the operational-predictor comparison and the agreement against the product's own published predictions populate too. The long-span 45-row extract was measured as the alternative and rejected: it is also final-only, so it would have leftpredicted_rows.nat 0. This moves published numbers, under rule R4 and with founder authorisation: ten of the 22 cells of the releasedp4_frame_eop.csvand 24 of the 42 populated cells oftests/golden/realtime-frame-eop.csv. The measured rapid-minus-final pole floor becomes 0.0678 mas (from 0.0769 mas), which is the value paper P4's own polar-motion table already publishes at n = 20 — the two now agree instead of differing by 13.5 %. Every other figure P4 prints from this table is unchanged at the precision printed. The full old → new enumeration, including the fields that did not move, is indocs/revisions/G12-default-eop-cell-changes.md. The final-only excerpt remains shipped, byte-pinned and exercised: a zero prediction-row count on it is the file's property, and a test still proves it.A stale satellite clamp in
moonlight-service-volume. The scenario clampedn_satsto 12 although its own constellation builder supports 24 and a test asserts 24, so every requested count above 12 was silently reduced and a satellite-count sweep returned identical values above 12 — indistinguishable from genuine geometric saturation. Now clamped at the builder limit. This is a behaviour change forn_sats > 12only.The same stale clamp in
lunar-differential-pnt.lunar_dpntclampedn_satsto 12 with the same consequence, and this one reached a published table:dpnt_nsats_sweep.csvcarries ann = 16row byte-identical to itsn = 12row, because the engine returned a twelve-satellite answer under a sixteen-satellite label. Now clamped at the builder limit of 24, and a new test pins it — each larger constellation must both report its own count and strictly improve the protection level, so the clamp cannot return unnoticed. This moves published numbers: then = 16protection level becomes 16.92 m → 14.57 m. Swept to 24, the curve is strictly monotone (11.94 m at 24 satellites, 29.5 % better than at 12), so there is no saturation at 12 — the apparent plateau was the clamp. Behaviour change forn_sats > 12only.
0.26.0 - 2026-09-18#
A documentation, transparency and playground release. No engine behaviour changes: the verification matrix stands at 102 rows — 56 VALIDATED, 42 MODELLED, 4 PARTNER, unchanged from 0.25.0.
Added#
- Crate-level rustdoc (
//!inlib.rs) covering entry points, the honesty/tier model and reproducibility, so docs.rs opens on a real landing page rather than a bare module wall. docs/SCENARIOS.md— a per-kind reference for all 50 scenario kinds, generated fromapi::list_scenario_kinds()(the single source of truth) bygen_validation_artifactsand guarded bytests/scenarios_reference_doc_sync.rs, so it cannot drift from the dispatcher.- Module docs for the 11 previously-undocumented public modules (
allan,estimator,fom,hybrid,inertial,models,report,run,scenario,timetransfer,types). - Literature sources for five formula modules that stated an equation but
named no reference:
batch_ls(Tapley/Schutz/Born; Bjorck),cr3bp(Szebehely; Koon et al.),detection(Kay),attitude_budget(Wertz; Sidi),reentry(Allen-Eggers, NACA TR-1381). - A published
security.txton kshana.dev for coordinated disclosure. - Per-run validation tier in the playground. The single-run figure-of-merit
table now carries a per-figure VALIDATED/MODELLED pill read from the same
fomTier()lookup the downloadable report uses (mirroringsrc/fom_label.rs, itself derived from the verification matrix), so the tier shown beside a live number can never disagree with the ledger. lunar-attack-surfacein the playground menu — all 50 dispatchable kinds are now one click away (was 49/50).- Five more runnable capability cards (space-weather, RF-impairment eval, resilience, CCSDS OEM interop, KIF), lifting runnable cards from 11 to 16. API- and library-only cards keep an honest docs link rather than a fake run button.
Changed#
- Documentation coverage is now ratcheted in CI.
check-doc-coverage.shcompiles the library withmissing_docs, counts the warnings, and fails only if the count rises above a pinned ceiling (986). A hard#![warn(missing_docs)]under-D warningswould have turned ~1000 currently-undocumented public items into an overnight build break; this way new undocumented public items fail the build while the existing backlog is paid down deliberately.
Fixed#
- The MCP
list_scenario_kindstool row advertised "~17 built-in scenario kinds" whileapi::list_scenario_kinds()had exposed 50 for some time — an agent-facing doc understating real capability threefold. Pinned to the true count. - Regenerated the CycloneDX SBOM conformance oracle for the updated dependency graph (60 components). The conformance verdict is unchanged: zero normalized schema errors and every atomic licence id still in the official SPDX enumeration.
0.25.0 - 2026-07-15#
Added#
- Five capabilities promoted to externally VALIDATED, each now cross-checked
against an independent third-party implementation of the same uniquely-defined
quantity (non-circular, with regenerable-offline fixtures):
- IEEE-1139 power-law → Allan-deviation conversion against
allantoolsclosed forms for all five noise types (tests/powerlaw_oadev_reference.rs); - CRPA MVDR / minimum-norm null-steering beamformer weights against numpy/scipy
LAPACK (
tests/crpa_reference.rs); - Wahba / TRIAD / QUEST attitude determination against SciPy's SVD
Rotation.align_vectorson noiseless observations (tests/wahba_reference.rs); - CCSDS 502.0 OEM covariance-block interchange round-tripped through the
independent
oemlibrary's parser (tests/ccsds_oem_covariance_reference.rs); - the square-law acquisition detection-statistics kernel (generalized
Marcum-Q / P_d / P_fa / threshold) against SciPy
ncx2/chi2(tests/acquisition_reference.rs). The machine-checked validation matrix moves to 56 VALIDATED · 42 MODELLED · 4 PARTNER across 102 rows (was 51 · 47 · 4). Honest scope caveats are preserved — e.g. the acquisition promotion covers the detection-statistics kernel only; CFAR/straddling loss stays MODELLED.
- IEEE-1139 power-law → Allan-deviation conversion against
- Position-domain figures of merit (
fom::positioning_performance): exact CEP, SEP and 2DRMS from a navigation solution's 3-D ENU position covariance plus a horizontal protection level. CEP/SEP are the exact median radial errors (not the0.589·(σ₁+σ₂)linear rule), cross-checked againstscipy.statsrayleigh/maxwell quantiles and an independent NumPy Monte-Carlo median (tests/positioning_fom_reference.rs). Replaces the previous honest not-implemented stub — the function signature changed from()returning an error to(cov_enu, hpl_m)returning aPositioningFom. - Confidence-interval equivalent-degrees-of-freedom (EDF) completed for all
Allan-family estimators (
assurance::uncertainty::edf): the modified-Allan, Hadamard and total-variance EDF now use the Greenhall & Riley combined-EDF algorithm and the NIST SP 1065 Table-7 TOTVAR form (previously only the overlapping-Allan EDF was implemented; the others silently reused it), cross-checked againstallantoolsedf_greenhall/edf_totdev(tests/uncertainty_edf_reference.rs). - Independent external cross-checks for the lunar-PNT geometry, DOP, EOP, DRO and
RF-ranging capabilities (
tests/validate_p*.rs, with fixtures and oracle generators undertests/fixtures/{p1_footprint,lunar_service,eop_prediction,rf_ranging_precision,dro_family_jpl,p2_independent_dop}/). Each check drives an already-shipped modelled capability against an oracle produced by a different implementation, so agreement is non-circular: the orbital transmit/capture footprint againstscipy.special.j1(Cephes — a genuinely different Bessel J1); the surface-beacon geometry DOP against an independent NumPy(HᵀH)⁻¹solve; the UT1/EOP persistence prediction-error growth against a separate NumPy re-parse of the same verbatim IERSfinals2000Arows; the planar distant-retrograde-orbit family against the NASA/JPL Three-Body Periodic Orbit Database; and the RF ranging-precision budget against a first-principles reference. These are additional regression evidence for existing modules; adding these cross-check tests does not itself relabel any capability (the five promotions above are separate, oracle-backed status changes insrc/verification.rs). lunar-attack-surfacescenario (src/attack_surface.rs,kind = "lunar-attack-surface"): a composed, binary-reachable (CLI / Python / MCP / wasm viarun_toml) P1 lunar signal-security run that stitches together the link-budget power deficit and its 12–18 dB sensitivity band, the required jam/spoof transmit power vs standoff, the orbital capture footprint under a real antenna pattern, the tracking-loop spoof-capture pull-in outcome, the airless-body horizon reach, and the OSNMA/TESLA authentication budget. An empty body reproduces the P1 baseline.- Power-deficit sensitivity band (
linkbudget::deficit_sensitivity_band,deficit_power_factor): a genuine multi-axis sweep (reference × EIRP × slant range) built onsweep::SweepAxisand evaluated throughreceived_signal_power_dbw, reproducing the P1 12–18 dB band and reconciling the 32× (rounded) / 36× (unrounded) linear-factor figures. - Three-dimensional spoof-capture cube (
spoof_capture::capture_cube): adds the carrier/Doppler-offset axis to the capture map (J/S × code-offset × carrier-offset, lock time per cell); the zero-carrier slice exactly reduces to the 2-Dcapture_map.
Changed#
- Sparse-monitor-network detection (
monitor_network) now composes the physicalspoof_monitorsAGC statistic and thelunar_serviceselenographic visibility grid to derive each station's detection statistic and geometry, instead of free parameters and an ad-hoc great-circle overlap; the per-monitor and network detection probabilities remain thedetectionchi-square/Marcum-Q closed forms. TheSurfaceMonitor/SpoofEventfields changed to physical (lat/lon/alt/power) quantities.
Fixed#
- OSNMA/TESLA authentication-overhead sizing (
nma_budget): the per-page OSNMA field portions (8-bit HKROOT + 32-bit MACK per 2 s page) were mislabeled as per-subframe totals, understating the overhead 15× (1.33 bit/s). Corrected to the published SIS-ICD per-30 s-subframe totals (HKROOT 120 + MACK 480 bits ⇒ 20 bit/s). The consequence is now stated honestly: at a 50 bit/s AFS nav rate the authentication overhead is a first-order ≈40 % cost, not a negligible few percent; theauth_latencyfield is documented as the key-disclosure delay (a lower bound on the end-to-end time-to-first-authenticated-fix).
0.24.0 - 2026-07-09#
A broad capability release. It lands two fully externally-validated analysis suites — a complete multi-criteria decision-analysis (MCDA) family and the Allan wander / long-τ clock-stability estimators — and opens a new runnable lunar time-budget, real-time frame/EOP, optical/RF-hybrid, cislunar-observability and layered-PNT conflict-resilience scenario layer on top of the substrate, plus a lunar surface-beacon and signal-security capability substrate. The machine-checked validation matrix grows to 51 VALIDATED · 47 MODELLED · 4 PARTNER across 102 rows (was 40 · 47 · 4 across 91): all eleven new rows are external-oracle validations of the decision-analysis and timing estimators, checked against independent libraries (SciPy, pymcdm, pyDecision, allantools) to tight tolerance, while the new PNT capability surface is surfaced honestly as MODELLED. As ever, no numeric model claim is upgraded without an external dataset behind it.
Added#
Runnable scenarios — each a registered scenario kind, dispatched through the
engine and CI-proven end-to-end by tests/determinism.rs (which runs every bundled
scenarios/*.toml and hard-fails on any unrunnable fixture):
- Layered-PNT conflict-resilience (
conflict-resilience,src/conflict_resilience.rs) — a seeded Monte-Carlo resilience engine for a multi-layer PNT architecture, with a §4.2 per-vector graceful-degradation survival model (four named threat vectors, the usable-PNT survival closed form each per-layer Monte-Carlo converges to, emitted in the result JSON) and documented, drift-guarded threat-parameter provenance (src/conflict_threat_params.rs). MODELLED architectures; the survival Monte-Carlo → closed-form convergence is the Validated core. - Cislunar observability (
cislunar-observability,src/cislunar_observability.rs) — an observability-Gramian eigen-spectrum over an arc, a differential-corrected distant-retrograde-orbit (DRO) seeder, and an independent square-root-information-filter cross-check whose posterior covariance turns finite exactly at full observable rank. MODELLED constellation; the rank/Gramian/STM/SRIF invariants are the Validated core. - Heterogeneous optical/RF hybrid continuity & integrity (
hybrid-optical-rf,src/hybrid_integrity.rs,src/optical_availability.rs,src/optical_linkbudget.rs,src/cross_raim.rs) — cross-domain availability and integrity when optical and RF PNT are combined. MODELLED. - Endogenous lunar time-budget (
lunar-time-budget,src/lunar_time_budget.rs) and real-time frame / EOP budget (realtime-frame-eop,src/realtime_frame_eop.rs,src/frame_eop.rs) — the P3/P4 lunar-time and Earth-orientation budgets derived endogenously from the timing chain rather than asserted. MODELLED.
Externally-validated analysis suites — library capabilities, each entering the matrix as new VALIDATED rows (checked against an independent external oracle, not a self-check):
- Full multi-criteria decision-analysis (MCDA) suite (
src/mcda/) — the four method families: value aggregation (WSM, WPM, WASPAS), ratio system (MOORA) and proportional (COPRAS), distance-to-ideal (TOPSIS), compromise programming (VIKOR), and outranking (PROMETHEE II with six generalised-criterion shapes, ELECTRE I concordance/discordance) — plus AHP Perron-eigenvector priority weights with the Saaty Consistency-Ratio (CR < 0.10) gate, MAUT utility, Pareto-front extraction, and weight-sensitivity analysis. The nine aggregators reproduce pymcdm / pyDecision and the AHP eigenvector matches SciPy/LAPACK, all to < 1e-9 (tests/mcda_*_reference.rs). The sensitivity tornado is also surfaced inside the conflict-resilience scenario. - Allan wander & long-τ clock-stability estimators (
src/allan.rs) — MTIE (the ITU-T G.810/G.823/G.8261 peak-to-peak time-error wander statistic sync masks are written against), MDEV and TDEV time-domain wander, and Theo1 / TOTVAR extended-range estimators. VALIDATED against allantools on the NIST SP 1065 series; the bias-removed ThéoH hybrid built on TOTVAR stays honestly MODELLED. - Assurance / external-oracle harness (
src/assurance/) — an oracle harness wired directly to the CI honesty gate, a W3C-PROV provenance record with a deterministic Merkle root, and Greenhall equivalent-degrees-of-freedom + chi-square confidence intervals on stability estimates. - PackRegistry / ExternalPack extension seam (
src/registry.rs,src/api.rs) — a dispatch seam so capability packs route through one registry (built-in dispatch is on the CLI hot path; out-of-tree packs attach behind a defaultedExternalPack::register_into), guarded by a golden byte-identity conformance suite and standalone / wasm32 CI gates.
Modelled capability substrate — new library modules feeding the scenarios above and
future ones (not yet a standalone scenario kind):
- Lunar surface-beacon DOP (
src/lunar_beacon.rs) with realized-accuracy budget, N-satellite sweep and GDOP map, plus a perturbed lunar ephemeris (src/lunar_perturbed.rs) and airless-horizon geometry / spoof-power inverse / AFS deficit band (src/jamming.rs,src/linkbudget.rs). Reuses the gnss_lib_py-validated DOP kernel. - LOLA-format DEM ingest + terrain line-of-sight (
src/realdata/lola_dem.rs). - Signal-security three-layer defense — spoof-capture, cross-sensor integrity and
monitor-network sizing (
src/spoof_capture.rs,src/cross_sensor_integrity.rs,src/monitor_network.rs) with an orbital-beam antenna footprint and an OSNMA authentication budget (src/antenna.rs,src/nma_budget.rs). MODELLED.
Changed#
- The validation matrix and every derived artifact were regenerated from the single
source of truth (
src/verification.rs): the on-site ledger, the module-map and validation-provenance diagrams, and the README / per-surface count strings now read 51 / 47 / 4 of 102 (was 40 / 47 / 4 of 91); the count-honesty guard now scans full git history, and the README figures are unified on the dark brand palette. - One shared adaptive integration driver now backs both
integrateandintegrate_dopri(src/integrator.rs), removing the duplicated stepper. - SonarQube Cloud static analysis added to CI (Rust + imported coverage), with security / maintainability / reliability rating badges on the README.
- Release / packaging discipline: CI registers a deployment environment per package/registry publish, and the focused public-standalone job is scoped off the slow validation binaries.
- The no-attribution hygiene guard is scoped to authorship markers only, so naming an integration host or product no longer fails the build.
- Documentation: the README Citing section now lists the four published arXiv preprints, and the surface-beacon DOP capability is documented in the lunar row.
A passage naming third-party AI clients is left out here; it is in CHANGELOG.md on GitHub.
Fixed#
- Integer loop counters for float-driven loops (Sonar S2193).
- Accurate chi-square inverse at low degrees of freedom via Newton refinement in the assurance confidence-interval path.
- kshana.dev: the standards section now opens by default and the ledger count fallback is corrected; stale README / site strings around the observability layer synced.
0.23.0 - 2026-06-29#
A capability release adding a general Fisher-information / Cramér–Rao observability and optimal-experiment-design layer, and the release the lunar-PNT observability paper rests on. The machine-checked validation matrix grows to 40 VALIDATED · 47 MODELLED · 4 PARTNER across 91 rows (was 39 · 46 · 4 across 89): the new estimation engine is checked against an external oracle and is VALIDATED; the lunar application of it is honestly MODELLED. As ever, no numeric model claim is upgraded without an external dataset behind it.
Added#
src/fim.rs— Fisher information, Cramér–Rao bounds, and optimal experiment design. A general, reusable estimation-theory engine: the Fisher information matrixM = HᵀWHfrom a measurement Jacobian and weights; a symmetric eigensolver (cyclic Jacobi); the Cramér–Rao lower bound via the inverse, or the Moore–Penrose pseudo-inverse plus a null-space basis when the information is rank-deficient (the free-network / datum-defect case); and D-, A-, E- and T-optimal experiment-design metrics with abest_designselector over candidate geometries. VALIDATED against NumPy (eigh/inv) and the published closed-form bounds of Kay (1993) to 1e-9 (tests/fim_observability_reference.rs), so it enters the matrix as an external-oracle row rather than a self-check.- Lunar joint-OD observability (
lunar_observabilityinsrc/lunar_combination.rs, surfaced on the lunar-combination report): applies the FIM/CRLB engine to the joint orbit-and-clock solve to expose the absolute-station datum defect, its rank restoration as Earth-baseline stations are added, the attained station-position CRLB, and the E-optimal conditioning. Honestly MODELLED — the estimation engine is externally validated, but the lunar network it is applied to is a representative simulation, and no current dataset can turn that geometry into a measurement. - Representative elliptical-lunar-frozen-orbit (ELFO) geometry for the
lunar-combination scenario (
orbit_ecc,orbit_inc_deg,orbit_argp_deg,orbit_planes; circular placement stays the default) and a without-VLBI observability field on the report, so the datum-defect result can be shown to be geometry-general. The same 3→1→0 ladder and three-station threshold hold on a Moonlight/LCNS-family ELFO, and with three Earth VLBI baselines the absolute station position is observable at a sub-metre bound — locked byelfo_geometry_confirms_the_observability_structure. Still MODELLED.
Changed#
- The validation matrix and every generated evidence artifact were regenerated from
the single source of truth (
src/verification.rs): the ledger (web/data/verification-matrix.json),docs/VERIFICATION-MATRIX.md,docs/MODELLED-RATIONALE.md, the validation-breakdown and oracle-kind figures, and the README count strings across all distribution surfaces now read 40 / 47 / 4 of 91.
0.22.1 - 2026-06-29#
A packaging-only patch release. No engine, scenario, or result changes; the validation matrix is unchanged at 39 VALIDATED · 46 MODELLED · 4 PARTNER across 89 rows.
Fixed#
- npm publish: the package
repository.urlderived from the crate manifest used the wrong-case GitHub owner (AshfordeOU), which failed npm's sigstore provenance verification (it matches the owner case-sensitively). The manifestrepositoryis now the canonical lowercaseashfordeOU, so the generatedpackage.jsonmatches the provenance subject and the npm release publishes.
Changed#
- Wheel build pins
codegen-units = 1so the manylinux wheel is byte-reproducible across rebuilds (the rebuild-and-diff reproducibility check).
0.22.0 - 2026-06-29#
A consolidation release focused on evidence, provenance, and distribution discipline. The machine-checked validation matrix grows to 39 VALIDATED · 46 MODELLED · 4 PARTNER across its 89 rows (was 15 · 42 · 4 across 61), driven by cross-validating modelled capabilities against independent external oracles. Every distributed artifact now carries a cryptographic SLSA build-provenance attestation, ships a CycloneDX SBOM, and is published in lockstep across all surfaces. No numeric model claim is upgraded without an external dataset behind it; new capability remains honestly MODELLED.
Added#
- Attitude dynamics (
src/attitude_dynamics.rs, MODELLED) — torque-free rigid-body Euler equations + quaternion kinematics (RK4), with conservation-law self-tests (quaternion norm, kinetic energy, |Iω|, symmetric-top precession). - Cross-validation of modelled capabilities against independent external oracles,
taking the validation matrix to 39 VALIDATED rows, each backed by an external dataset
(the CI honesty gate forbids a VALIDATED status without one). The newest external-oracle
validations: the optical-clock measured stability curve (
quantum_trade's ADEV-curve fit reproduces the published ⁸⁸Sr optical-lattice-clock σ_y(τ) of Norcia et al., Science 366:93 (2019), Zenodo 10.5281/zenodo.3382347, CC-BY-4.0, vendored undertests/fixtures/optical_clock_adev/); GPS L1 C/A spreading-code generation (src/sdr.rs's G1/G2 LFSR reproduces the IS-GPS-200 Table 3-Ia code-phase octals for PRN 1–9); and the SRTM digital-elevation reader on real terrain (src/altpnt/terrain.rsreads a vendored public-domain NASA/USGS SRTM v3 tile, N36W117 / Death Valley, placing Badwater Basin within its documented survey band). - New MODELLED capabilities, each internally cross-checked against a closed form or a sibling code path: Clohessy–Wiltshire / Hill relative-motion dynamics, TDOA/FDOA passive emitter geolocation, Wahba/TRIAD/QUEST attitude determination, GNSS carrier-phase integer ambiguity resolution (LAMBDA), B-plane targeting & patched-conic gravity assist, CRPA anti-jam array beamforming, GNSS square-law acquisition statistics, quantum inertial-sensor fringe-ambiguity / dynamic range, IEEE-1139 power-law clock noise + flicker-FM floor, and CCSDS-OEM covariance-block interchange.
- Single-source evidence ledger with generated verification docs and a drift guard; a browsable validation ledger on kshana.dev with per-card evidence deep-links.
- Machine-readable citation metadata —
codemeta.json(CodeMeta 2.0) and.zenodo.json, pinned to the Cargo manifest bytests/citation_metadata_doc_sync.rs. - Supply-chain provenance — SLSA build-provenance attestation for the crate tarball, wheels, sdist and npm tarball; PyPI OIDC Trusted Publishing; CycloneDX SBOM shipped inside the wheel and npm package; reproducible-build diff and post-release verification.
- Discoverability —
robots.txt,sitemap.xml, and Open Graph / JSON-LD metadata on kshana.dev; a generatedvalidation-breakdownfigure derived from the matrix. - Per-surface READMEs for crates.io / PyPI / npm with a count honesty guard.
Changed#
- Sagnac time-transfer oracle upgraded to ExternalDataset — the equatorial- circumnavigation Sagnac correction is now checked against Ashby's published 207.4 ns (Living Reviews in Relativity 6:1, 2003, Eq. 1.29; reproduced to < 0.05 ns). The composite time-transfer capability remains honestly MODELLED.
- Documentation, diagrams and figures refreshed to the live matrix (39/46/4/89): the
validation-provenance diagram, the
oracle-kind-stackedfigure (now generated from the matrix with a CI drift-guard), VALIDATION/PROVENANCE/CAPABILITY/QUANTUM-MODELS/WHEEL_TAGS, the architecture and tutorial docs, and the kshana.dev ledger total. - Reformatted the source tree with the pinned rustfmt (toolchain 1.93.0) — pure style, no behavioural change.
- Lockstep versioning. Every distribution surface (crate, PyPI, npm, MCP crate +
OCI image, JetBrains plugin) now versions in lockstep with the engine, enforced by
scripts/check-version-sync.shin CI. - JetBrains plugin relicensed to AGPL-3.0-only (the published 0.1.1 still advertised Apache-2.0) and now derives its Marketplace version from the release tag.
- Robustness hardening — non-test
unwraps removed or justified;clippy::unwrap_usedgated on non-test code. - kshana.dev proof-first overhaul: dominant hero, architecture diagrams and result figures, i18n, collapsible/responsive ledger and Standards sections.
Fixed#
- SP3 precise ephemeris — apply the IGS Earth-rotation node correction during interpolation.
- Registry images — PyPI/crates.io/npm READMEs use absolute image URLs; the main
README's relative image paths rendered as broken images on the PyPI 0.21.0 page. A CI
guard (
tests/surface_readme_image_urls_doc_sync.rs) prevents the regression. - CI —
fmt+clippygreen; stop compiling against git-ignored real data.
Tests#
- Kalman clock covariance stays PSD and symmetric (property test,
tests/property.rs). - Doc-sync guards: every public validation-count string, citation metadata, the generated figure, and surface-README image URLs are all pinned to their source of truth.
0.21.0 - 2026-06-26#
This release adds two MODELLED application suites to the open engine — a
Quantum-Enabled PNT demonstrator (trusted quantum time transfer, GNSS-free
quantum navigation, quantum fault/anomaly detection) and a lunar / cislunar PNT
suite (lunar coordinate time, lunar VLBI, joint OD+clock, frame realisation,
service-volume, differential PNT, interoperability export) — each runnable from one
kind, each emitting an honest TradeEvidence record plus a representativeness /
gaps-to-flight ledger. It also lands six new external-oracle validations, taking
the machine-checked validation matrix to 15 VALIDATED · 42 MODELLED · 4 PARTNER
across its 61 rows. The new application numbers are MODELLED from illustrative
public-source parameters — no TRL, flight heritage, certification, or agency
endorsement — and the validated kernels they reuse are labelled as such.
Added#
- Quantum-Enabled PNT demonstrator suite (
quantum-time-transfer,quantum-gnss-free-nav,quantum-anomaly-detect). Three runnable MODELLED application areas behind the engine, each emittingTradeEvidenceplus a representativeness / gaps-to-flight record:- Trusted quantum time transfer (
src/timetransfer_chain.rs) — an optical-lattice-clock + photonic-link vs CSAC + RF two-way budget, reusing the timing protection level, with a delay/replay-attack security FoM (1 − P_md) and clock-anomaly detection + CUSUM latency. - GNSS-free quantum navigation (
src/quantum_nav_od.rs) — a cold-atom- interferometer inertial coast vs a navigation-grade INS across a GNSS outage, honest that with no external fix the accelerometer bias stays unobservable so the error still grows. - Quantum fault / anomaly detection (
src/quantum_faults.rs) — a labelled fault catalogue with a bootstrap-CI ROC AUC and a minimum-detectable-fault at a fixed false-alarm rate. - A shared quantum device error-model library (
src/quantum_devices.rs) and a unified quantum-vs-classical trade harness (src/qtrade.rs) underpin all three.
- Trusted quantum time transfer (
- Lunar / cislunar PNT suite. Seven runnable MODELLED
kinds layered on the CR3BP core: Lunar Coordinate Time (LTC/TCL − TT secular rate,src/lunar_time.rs), geodetic lunar VLBI delay observable (src/lunar_vlbi.rs), joint multi-technique OD + clock batch estimator (src/lunar_combination.rs), reference-frame realisation (7-parameter Helmert + IAU 2015 WGCCRE tie,src/lunar_frame_realise.rs), Moonlight / LCNS-class service-volume analysis (src/lunar_service.rs), lunar differential PNT (src/lunar_dpnt.rs), and a LunaNet / IOAG-aligned interoperability export (CCSDS-OEM + lunar time scale in the KIF envelope,src/lunar_interop.rs). All MODELLED from illustrative public-source parameters; not validated against real VLBI / Gateway tracking; no agency affiliation or endorsement. - Six new external-oracle validations (the validation matrix now stands at 15
validated):
- ADEV / HDEV on a real 5071A caesium clock — validated against Stable32
decade-point references (
tests/cs5071a_reference.rs). - OADEV / MDEV / TDEV on the Stable32
PHASE.DATreference — validated against Stable32's published tables (tests/phasedat_reference.rs). - Anomaly-detection ROC AUC on real ESA OPS-SAT telemetry — validated against
scikit-learn (
tests/opssat_ad_reference.rs). - ICGEM gravity-functional synthesis — validated against the GRS80
normal-gravity standard (Somigliana γ to 3.5e-12; EGM2008 disturbance map,
tests/icgem_gravity_reference.rs). - Klobuchar broadcast ionosphere model — validated against RTKLIB
(
tests/klobuchar_reference.rs). - RAIM detection kernel (171 cases,
tests/raim_reference.rs) and the SBAS DO-229E protection level on real EGNOS data (RTKLIB SBAS-PL fork,tests/sbas_reference.rs) — both validated against their external oracles.
- ADEV / HDEV on a real 5071A caesium clock — validated against Stable32
decade-point references (
- Representativeness & gaps-to-flight ledger (
src/representativeness.rs). A structured record every demonstrator emits — what is representative, what is simplified, and what stands between the model and flight — the honest companion to every MODELLED trade. - Study aggregation & CLI ergonomics. A scenario-suite manifest with one-command
study aggregation, hash-stable study metadata +
--study-name,--validatescenario linting, and per-FoM validation-tier surfacing in study reports.
Changed#
- kshana.dev redesigned around a shared domain spine — capabilities and validation folded into one domain explorer with a common vocabulary, the new validated islands surfaced, a land-accurate animated Earth instrument and a request-a-study CTA, and the Ashforde OÜ branding/links.
- README + ARCHITECTURE audited for completeness and honesty — stale validated counts corrected (now 15 external oracles), the diagrams extended, and the Conditional Timing Protection Level (arXiv:2606.24210) and RF-impairment optimism-gap (arXiv:2606.22054) preprints cited.
- The
paper/sources and the JOSS draft pipeline are kept in the public repo.
0.20.0 - 2026-06-22#
This release adds four study capabilities to the open engine — a conditional Timing Protection Level, framework-aligned PNT-resilience scoring, an RF-impairment optimism-gap evaluation, and a software-defined-receiver / real-data front end — each reproducible from one command and writing a byte-deterministic artifact. All studies are MODELLED (synthetic or public-dataset calibration), carry their honest provenance (engine version, seeds, config hash) and validation labels, and are not certifications.
Added#
- Conditional Timing Protection Level (
tpl). A holdover-limited bound on the undetected time error under GNSS spoofing, with a k-sigma monitor floor, a van Loan coast variance over the detection latency, and a CUSUM time-to-alarm. Calibrated on a real recorded attack (JammerTest 2024, scenario 2.1.1) viacargo run --release --example tpl_jammertest, which reproduces its reference table from scalars recovered from the public recording (the raw dataset is not redistributed). - Framework-aligned PNT-resilience scoring + decision-instability study
(
resilience). Architecture model, RPCF-aligned scoring, Dirichlet weighting simplex, Kendall-tau and top-1 flip-rate instability, and common-mode diversity collapse (Hill-N2), withcargo run --release --example resilience_report -- paper-artifacts/resilience-study.json. Crosswalk indocs/RESILIENCE-CROSSWALK.md. - RF-impairment optimism-gap study (
impairment_study,impairment_ml,eval_stats). A 13-detector panel over a parameter-grounded synthetic corpus, an in- vs out-of-distribution optimism gap, scaling-law trends with a permutation null, and a leave-one-out degradation predictor, viacargo run --release --example optimism_study -- paper-artifacts/optimism-study.json. - Software-defined-receiver front end (
sdr) and real-data adapters (realdata/). Raw IQ/IF to correlator taps and SQM, plus ingest adapters (RINEX, u-blox UBX, GnssLogger, JammerTest, Yunnan, SatGrid) and probe examples. These read recordings the user supplies locally; no recordings are committed. - Committed study artifacts. The byte-deterministic
optimism-study.jsonandresilience-study.jsonare now tracked underpaper-artifacts/so the study numbers can be checked without rebuilding; they remain regenerable from the generators above. Raw datasets stay out of the repo (/realdata-cache/).
Changed#
- Version bumped to 0.20.0 across
Cargo.toml,CITATION.cff, andREADME; the crossover study artifacts are regenerated so their stamp matches the released version.
0.19.0 - 2026-06-18#
Changed#
- Relicensed from Apache-2.0 to the GNU AGPL-3.0-only, with a commercial licence
available from Ashforde OÜ (dual-licensing). The open engine stays fully open and
publicly verifiable; the AGPL's network-copyleft (§13) means a closed or hosted
derivative must come back to open source — or take a commercial licence. This
defends the validated core against fork-and-close while keeping the credibility of
a public, runnable, auditable engine. See
LICENSE(AGPL) and the newLICENSING.md(what each licence covers and when it applies).LICENSEnow contains the AGPL-3.0 text; SPDX headers across all sources updated toAGPL-3.0-only;NOTICE,README,GOVERNANCE,GLOSSARY, the website, and crate/package metadata (Cargo.toml,pyproject.toml,CITATION.cff, the MCP crate + image) updated accordingly.- Contributor terms (
CONTRIBUTING.md) now license inbound under the AGPL and grant Ashforde OÜ the right to include contributions in the commercially-licensed edition, so the dual-licence keeps working. - Dependency policy unchanged but re-justified (
deny.toml,GOVERNANCE): dependencies stay permissive (Apache/MIT/BSD/ISC). AGPL is allowed only for kshana's own crate — a copyleft dependency would taint the commercial edition and break dual-licensing. - Note for downstream: this is a copyleft relicence. Users who relied on
Apache-2.0 permissive terms can continue using the last Apache-2.0 release
(
v0.18.0and earlier, as published);v0.19.0onward is AGPL-3.0 / commercial.
Security#
- Bumped
pyo30.24 → 0.29 to clear RUSTSEC-2026-0176 / RUSTSEC-2026-0177 (GHSA-36hh-v3qg-5jq4 / GHSA-chgr-c6px-7xpp) from external OSV/dependency scans. Both are function-level advisories whose affected functions (BoundList/TupleIterator::nth/nth_back,PyCFunction::new_closure) Kshana never calls, andpyo3is an optional (python-feature) dependency — so the real exposure was nil — but the bump keeps a clean scan for downstream auditors. Migratedsrc/python.rsto the pyo3 0.29 API (Bound return type forscenario_kinds; explicitskip_from_py_objecton theRunOutputpyclass). All 11 Python binding tests pass against the rebuilt extension.
0.18.0 - 2026-06-17#
Added#
Eleven new runnable scenario kinds — two-tender demonstrators, a CCSDS interop bridge, and a first-order mission-analysis / environment suite (all MODELLED, additive; existing reproducibility goldens unchanged). Each is CLI / Python / WASM / MCP dispatchable, ships a
scenarios/<kind>.toml, and carries an explicit MODELLED label in its result JSON and one-line summary (a per-kind test intests/dominance_demonstrators.rsasserts the label is present and that the output never contains the stringVALIDATED):impairment-eval(src/impairment_eval.rs) — Machine-learning RF-impairment detection evaluation testbed: a labelled synthetic corpus + a detector-agnostic ROC/AUC harness + an in/out-of-distribution optimism-gap report (operating characteristics only — never field/IQ data).quantum-trade(src/quantum_trade.rs) — quantum-vs-classical PNT trade with measured-ADEV ingestion and a GNSS-denied resilience envelope.space-weather(src/space_weather.rs) — solar/geomagnetic indices (Kp↔ap IAGA table, daily Ap, centred 81-day F10.7a), Jacchia-71 exospheric temperature, and an activity-driven thermospheric-density coupling over a static atmosphere (NOT an NRLMSISE absolute-density model).oem-interop(src/oem.rs) — CCSDS 502.0 OEM import + round-trip bridge for GMAT / Orekit / STK ephemerides (theparse_oemreader, exact inverse of the writer).launch-window(src/launch.rs) — two-body launch azimuth (sin Az = cos i/cos lat), plane-change Δv, site-rotation bonus, and daily-opportunity geometry.reentry(src/reentry.rs) — Allen-Eggers ballistic re-entry corridor: peak deceleration, peak-g and peak-heating velocities, peak-g altitude.eo-coverage(src/eo_payload.rs) — EO swath / GSD / access / revisit geometry (SMAD space-triangle).space-packet(src/space_packet.rs) — CCSDS 133.0-B Space Packet primary-header encode/decode with bit-exact round-trip.attitude-budget(src/attitude_budget.rs) — 3-DOF gravity-gradient torque + RSS pointing-error budget (scalar pre-hardware budget, not a control loop).passes(src/passes.rs) — ground-station rise/set pass prediction (AOS/TCA/LOS, max elevation, access).link-budget(src/linkbudget.rs) — one-way CCSDS-401 / DSN-810-005 link equation (C/N₀ = EIRP − FSPL − L_other + G/T − k, FSPL / Eb·N₀ / margin / closure).
17-state hybrid quantum + classical tightly-coupled UKF — surfaced as a runnable scenario (MODELLED). A new
hybrid-ukfscenario kind (src/fusion/hybrid_ukf.rs,scenarios/hybrid-ukf.toml) that turns the previously API-only 17-state tightly-coupled GNSS/INS unscented filter (src/fusion/tightly_coupled17.rs) into a scenario the CLI/Python/WASM/MCP bindings can dispatch. The 17-state error vector is the 15 INS error states (position, velocity, attitude misalignment, accel + gyro bias) augmented with the CAI-derived accelerometer-bias correction — the cold-atom interferometer (src/inertial/quantum_imu.rs) sets the velocity-random-walk floorq_va, so the long-term coast drift is the quantum-sensor-limited one — and a 2-state phase + frequency clock whose process noise comes from the q-parameter clock engine (clock_state::q_from_allan, mapping a clock's Allan-deviation profile to theq_wf/q_rwPSDs, scaled to range units). The platform is GNSS-aided for a lead-in (the filter learns the biases, velocity and clock), then coasts through a GNSS outage on the CAI IMU + clock alone, so the run demonstrates classical-IMU short-term + quantum long-term hybridisation. The figure of merit is filter self-consistency: pooled NEES (Normalised Estimation Error Squared, over the estimable position/velocity/clock subset) and innovation-whiteness NIS (Normalised Innovation Squared) over a Monte-Carlo ensemble, checked against their 95% χ² bands (Bar-Shalom §5.4). The matched filter lands inside the bands; a deliberately mistuned filter (theq_factor/r_factorknobs) is flagged — an objectively checkable, discriminating gate, not a rubber stamp. The NEES/innovation- whiteness check is the STATISTICAL ORACLE: a self-consistency statement (the filter's reported covariance honestly matches the spread of its own errors under the modelled noise), NOT a real-world accuracy guarantee. Honest scope: everything is modelled / simulation — the CAI and clock inputs are bracketed, literature-representative values, not measured hardware; the CAI hardware and its Key-Person stay partner-owned; nothing here implies TRL > 3, flight heritage, or external validation (the result JSON and one-line summary carry these labels explicitly). The single constant-velocity, level trajectory leaves attitude and IMU-bias states only weakly observable, so NEES is assessed over the 8 estimable states; a manoeuvring trajectory for full-17 observability is roadmap. Also addsUkf::update_stats(returns the per-update NIS) andTightlyCoupled17::{update_gnss_nis, nees, nees_subset}consistency instrumentation. All existing scenarios are unaffected (additive; reproducibility goldens unchanged).Sequential (recursive) terrain-referenced navigation — SITAN as a running filter. A new
terrain-slamscenario kind (src/altpnt/sequential.rs,scenarios/terrain-slam.toml) that runs the existing altimeter-vs-DEM measurement model epoch by epoch through theparticle_filterSIR engine, rather than the batch coarse-to-fine searchterrain-navuses to recover a single constant INS offset. At each waypoint the cloud is propagated by the INS-reported increment (itself corrupted by the per-step drift growth), reweighted by the terrain match, and resampled on degeneracy — so a time-varying INS drift is tracked along the track, which a constant-offset fit structurally cannot do. On the synthetic DEM the recursive estimate stays bounded and re-converges (final ≈ 70 m) while the unaided inertial solution diverges unbounded to ≈ 5 km; per-epoch error follows terrain distinctiveness (it coasts on the biased INS over flat saddles and re-locks over distinctive relief), and the effective-sample-size monitor confirms a healthy cloud. Honest scope: the map is known and fixed — recursive localization against a stored DEM (the localization half of terrain SLAM), not joint map estimation; non-circular by construction (the injected drift ramp is the independent truth). All existing scenarios are unaffected (additive; reproducibility goldens unchanged).GNSS-denied resilience spine + FutureNAV demonstrator slices (
src/holdover.rs, and resilience-envelope foundations undersrc/impairment_eval.rs,src/quantum_trade.rs,src/navsignal.rs,src/inertial/quantum_imu.rs, with FutureNAV verification slices insrc/verification.rs). Composes the alternative-PNT building blocks — clock holdover, signal tracking, inertial coast, terrain — into a single GNSS-outage resilience narrative. MODELLED; additive.Kshana Interchange Format (KIF) — a versioned, self-describing artifact envelope (
src/interchange.rs). A schema-tagged wrapper around scenario results so a stored artifact carries its kind, schema version, and MODELLED/VALIDATED labels with it, and older envelopes stay forward-compatibly readable. Additive; existing result JSON unchanged.Navigation-signal modulation / tracking + CR3BP halo/NRHO differential corrector (
src/navsignal.rs,src/cr3bp.rs). A first-order nav-signal modulation & tracking model, and a circular-restricted three-body differential corrector for halo / near-rectilinear halo orbits, surfaced on the existing deep-space capability axis. MODELLED.Distribution-shift evaluation mode + corpus severity-scale knob for
impairment-eval(src/impairment_eval.rs). Adds an explicit in/out-of-distribution split and a tunable corpus severity scale to the ROC/AUC optimism-gap harness (operating characteristics only — never field/IQ data).Cost-per-coverage ROI + detection-miss integrity-impact mapping (
src/frugal.rs,src/integrity_impact.rs). A frugal-engineering ROI lens (cost per unit coverage) and a mapping from detection-miss rate to integrity impact. MODELLED; additive.Cited cold-atom-interferometer (CAI) error-model parameter sheet (
src/inertial/quantum_imu.rs). A literature-referenced, bracketed parameter sheet for the CAI inertial model — inputs are cited, not measured hardware (no TRL / flight claim); it feeds thehybrid-ukfvelocity-random-walk floor.Project governance.
GOVERNANCE.mddocumenting the decision model and the open/closed boundary; the capability map's community/governance row movesnone → partialto reflect it.
Fixed#
ARAIM integrity protection level — nominal bias (
b_nom) and σ_URA now applied. The MHSS protection level now subtracts the one-sided nominal-bias projectionb_k = Σ_i |s_i|·b_nomper fault mode, and uses the integrity sigma σ_URA (distinct from the accuracy sigma σ_URE) carried on the Integrity Support Message. This makes the protection level more conservative and standards-correct, and therefore changes the PL values reported by existing integrity scenarios. The ISM/scenario gains#[serde(default)]sigma_ura_m/b_nom_mfields, so inputs that leave them unset retain prior behaviour. Seedocs/ARAIM_REFERENCE.mdfor the restoredb_kformula and the honest implementation note.Spoof-monitor χ² consistency.
parity_raim_testnow uses the sharedraim::chi2_quantileinverse-χ² path, removing a second divergent χ² implementation so RAIM and the parity spoof monitor agree on their thresholds.gravity-map-navwired into the CLI dispatcher. The scenario was previously reachable only through the API; it now dispatches as akind=like every other scenario.Documentation/count accuracy. Scenario-kind counts in the README are now pinned to
api::list_scenario_kinds().len()by ascenario_count_doc_synctest, so the documented count can no longer drift from the dispatcher.
0.17.0 - 2026-06-14#
Added#
- Deep-space & Mars PNT — open radiometric navigation engine + GSE simulation.
A new, fully additive capability axis on top of the Earth-validated core (every
existing Earth scenario is byte-identical — the reproducibility goldens pass
unchanged with no regeneration). Adds: a multi-body dynamics core (
Body{mu, re, zonals, gravity, rotation, IAU-pole}with Mars GMM-3 tesseral gravity, an IAU body-fixed Mars frame, a pluggableEphemerisProviderseam, sub-microsecond two-part Julian dates and TT↔TDB); radiometric observables (iterative light-time + Shapiro delay, two-/one-/three-way Doppler & range via the Moyer two-leg solve, coherent transponder turnaround, regenerative/PN ranging per CCSDS 414, Δ-DOR per CCSDS 506, solar-plasma/tropo/iono media); CCSDS-TDM (503) parse + emit; a reduced-dynamic Square-Root Information Filter with RTN empirical accelerations, a three-state onboard clock, and a Mars-drag model; a joint one-way + two-way fusion estimator; themars-pntscenario surface (relay constellation + LMO/transfer/ surface) across CLI, Python, WASM, MCP, and the playground; and an end-to-end GSE performance simulator (geometry → link budget → observables → SRIF → covariance). Validation tier — simulation-validated: synthetic closed-loop OD (Mars-LMO ≈ 0.2 m) and analytic self-consistency, with the Sun-central Mars dynamics independently cross-checked against JPL DE440 (xval/anise-mars-od, kernel-gated: 137 m @ 1-day arc, the honest unmodelled-n-body residual). Reported deep-space accuracies are simulation / covariance figures of merit, not real-mission results; real DSN/ESTRACK tracking-data validation remains on the roadmap. ANISE (MPL-2.0, edition-2024) is confined to a workspace-excluded cross-check crate, so thecargo denylicense gate and the MSRV-1.75 job are untouched. - Agency-accurate ground tracks from real IERS Earth orientation. The
ephemerisscenario takes an optionaleop_finals2000afield — the inlined body of a real IERSfinals2000Afile — and reduces the ground track through the per-epoch UT1−UTC and polar motion interpolated from it (the sameEopSeriesprecise_oduses), overriding the nominaldut1_s/xp_arcsec/yp_arcsecscalars. The data travels in the scenario, so the run stays reproducible and needs no filesystem (it works in the WASM playground). Akshana --eop <finals2000A>flag folds a real file into the scenario from the CLI. Closes the asymmetry where onlyprecise_odconsumed real Earth-orientation data.
Fixed#
- Range-rate frame consistency. The ground station is now mapped into the
inertial frame through the exact inverse of the satellite's reduction
(
frames::itrf_to_teme, undoing polar motion and the sidereal rotation) instead of a polar-motion-blind GMST rotation, so both endpoints share one frame. The effect on the reported Doppler is below the validation floor, but it removes a real frame mismatch and an "exact" overclaim in the source. carrier_hzis validated. A zero or non-finite carrier frequency now returns an error instead of silently producing zero Doppler (it had made λ = c/carrier infinite), matching the existingstep_sguard.
0.16.0 - 2026-06-11#
Added#
- Force-model validation by ephemeris fitting, cross-validated against real
agency products. A new batch least-squares estimator (
src/precise_od.rs) with a variational state-transition matrix and outlier editing, driven by a full force model (PreciseForceModel: EGM2008 geopotential, third bodies, solid + ocean + atmospheric tides, and empirical CPR/2-per-rev accelerations), fed by a real IERSfinals2000AEarth-orientation parser. Validated against published reference orbits: Galileo MEO to 13 cm post-fit, ESA Swarm-A LEO, and LRO lunar (selenocentric, GRAIL gravity, IAU-2015 body frame — reduced-dynamic 6.6 m, honestly above the 5 m target on the open path; the DE-grade ANISE/DE440 path that reaches it is a workspace-excluded crate). spoof-detectscenario — an integrated multi-layer spoofing detector combining per-epoch RAIM parity, AGC and signal-quality (SQM) monitors and a fused decision, validated against the published TEXBAT scenario parameters (Humphreys et al., ION GNSS 2012), including the carrier-aligned hard case.ephemerisscenario — state, frames, ground track and Doppler. Propagate one satellite (TLE→SGP4 or an analytic orbit) and emit, per step, the inertial TEME and GCRS state (position and velocity), the Earth-fixed ITRF/ECEF position, the WGS-84 sub-satellite ground track (latitude / longitude / altitude), and the topocentric azimuth / elevation / range with range-rate and Doppler from a ground station. Reachable from the CLI, Python, WASM and the MCP server, and shipped as the "Ground track" preset in the web playground, where the track is drawn over a real world map (Natural Earth 1:110m coastlines, embedded — no network or external dependency).- CCSDS OEM export (
--export-oem/export_oem = true) — the velocity-carrying Orbit Ephemeris Message consumed by GMAT / Orekit / STK, at parity with the existing SP3 and OMM exporters. - Solid, ocean (FES2004) and atmospheric (Ray 2001 S2) Earth tides on the geopotential (IERS Conventions Ch. 6), wired into the force model.
- ARM64 wheels — the PyPI build now also produces Linux aarch64 (manylinux_2_28), macOS arm64 and Windows arm64 wheels.
- Extended technical report (preprint) linked from the README, and the JOSS paper made submittable (author ORCID, compiled to PDF in CI on every change).
Changed#
- Frames validated to the millimetre against published Vallado vectors. The TEME→PEF/ITRF reduction, the full CIO IAU 2006/2000A GCRS→ITRS chain and the ECEF→geodetic WGS-84 conversion are now pinned to the worked example in Vallado et al. (AIAA 2006-6753) with its IERS EOP, not just to internal self-consistency.
- Independent time-scale cross-checks. ERA and the UTC/TAI/TT scales agree
with
hifitimeto < 1 µs, and the DE440 planetary ephemeris agrees with JPL Horizons (Moon/Sun geocentric positions), both as always-on CI gates. - The web playground hides the figures-of-merit tab when a result carries no figure-of-merit rows, so chart-only packs (ephemeris, RAIM, spoof) open on their chart — for ephemeris, the ground track — instead of an empty table.
- The playground's guided sliders and parameter sweep now work for the ephemeris / ground-track scenario: its knobs (station latitude / longitude, time step, duration, UT1−UTC) are tunable, and a sweep can plot pass geometry (max elevation, peak Doppler, altitude, speed) against any of them — e.g. max elevation vs station latitude. The Sweep tab is now shown only when a scenario is actually sweepable, so no pack offers a control that plots nothing.
- Documented the SGP4
DUT1 ≈ 0approximation at the GMST call site (a ≤ ~13″ rotation error, well inside SGP4's own model error) and refreshedCAPABILITY.md.
0.15.1 - 2026-06-09#
Added#
- The Kshana — PNT simulator JetBrains plugin is now published and approved on the
JetBrains Marketplace.
README,
kshana.dev, and the distribution docs link to it directly.
Fixed#
- MCP registry publish now succeeds: the
server.jsondescriptionwas over the registry's 100-character limit (HTTP 422). Shortened it, and aligned the server name (and the image's ownership label) to the canonical namespaceio.github.ashfordeOU/kshana-mcp. - JetBrains Marketplace publish now succeeds: the CI re-uploaded the same plugin
version that was listed manually, which the Marketplace rejects. The plugin version is
bumped to
0.1.1, and the idempotency guard now also treats an "already contains version" response as a no-op success. kshana.devcache-busting: the version-stampedstyle.css/app.jsquery strings track the release, so returning visitors always get the current build.
0.15.0 - 2026-06-08#
Added#
kshana-mcp— Kshana as a Model Context Protocol (MCP) server (mcp/kshana-mcp/). A standalone, workspace-excluded crate (thermcpSDK is edition 2024) that exposes the validated engine to AI agents and assistants — Cursor, JetBrains AI Assistant / Junie, and any MCP client — over stdio. Tools:run_scenario,list_scenario_kinds,validate_scenario,export_sp3,export_omm, each a thin wrapper overkshana::api.- JetBrains IDE plugin (
ide/jetbrains/). Right-click a scenario.toml→ Run Kshana Scenario; figures of merit and result JSON stream into a Kshana tool window. Pure-platform Kotlin plugin, compatible with every JetBrains IDE 2024.3+. - Public distribution + per-release auto-publish for both:
kshana-mcpto crates.io (cargo install kshana-mcp) viapublish.yml.kshana-mcpas a multi-arch OCI image onghcr.io(docker run ghcr.io/ashfordeou/kshana-mcp) via a newmcp-publish.yml.kshana-mcpto the official MCP registry via GitHub OIDC (zero secrets); the registry entry (server.json) uses the OCI package type with a label-verified owner.- the IDE plugin to the JetBrains Marketplace via
publishPlugin(token-gated, optional developer signing) on each release tag.
0.14.1 - 2026-06-08#
Added#
- Independent ANISE/SPICE reference-frame cross-validation (
xval/anise-frames/). A standalone, workspace-excluded crate cross-checkskshana's IAU 2006/2000A CIO reduction (kshana::cio::gcrs_to_itrs_matrix, GCRS→ITRS) against ANISE (the pure-Rust NAIF/SPICE reimplementation) rotating GCRF→ITRF93 from JPL'searth_latest_high_prec.bpc, with the same IERSfinals2000AEarth-orientation parameters fed to both sides, over eight quarterly epochs 2020–2023. The two independent frame realizations agree to a maximum relative rotation of 0.028″ — ≤ 0.86 m on the ground, ≤ 0.93 m at LEO, ≤ 3.6 m at GNSS orbit, meeting the long-standing ROADMAP "< 10 m" frame cross-check with large margin (it complements, and does not replace, the existing bit-for-bit SOFA/ERFA anchors). The crate is isolated becauseanise+hifitimeare MPL-2.0 / edition-2024 and must never enter the publishedkshanadependency graph, itsCargo.lock, thecargo denylicense gate, or the MSRV build; ANISE is pinneddefault-features = false. Includes aframe-xvalbinary (fetches the ~5 MB BPC, prints a table, writesreport.{json,md}), a kernel/network-self-skipping test gate, and an optionalworkflow_dispatch-only CI job (never blocksmain). Documented indocs/VALIDATION.md(CIO row) andROADMAP.md.
Fixed#
- Mobile-friendly playground. Fixed horizontal overflow of the playground
.panelon phones (a CSS-grid item defaulting tomin-width: autorendered ~100 px wider than the viewport) viamin-width: 0and width-guarded controls; verified clean at 360 / 390 / 414 / 768 px. Aligned the "Pin to compare" / "Download report" action buttons (equal margin boxes in the flex row). Enlarged run buttons, sliders, selects and nav links to the ~44 px WCAG 2.5.5 / Apple-HIG touch-target minimum on phones and touch devices, with desktop sizing unchanged.
0.14.0 - 2026-06-08#
Fixed#
- Robustness hardening from an adversarial battle-test pass. (1)
sbas_protection_levelnow rejects non-finite elevation/azimuth/variance and negative or non-finite covariance diagonals (a near-singular geometry scaled up by small σ could previously slip the absolute-pivot gate and return a NaN VPL / absurd HPL as a validSome— a silent integrity failure). (2) The numerical propagator (propagate/propagate_dopri) fails closed on a non-finite initial state instead of spinning the adaptive controller forever. (3) The DEM cell helper no longer panics on a single-sample (1×N) grid. (4)lunar_look_angleazimuth is held strictly in[0, 360). (5)SphericalHarmonicField::from_gfcrejects non-physical (NaN / non-positive)GM/radius.
Added#
validation_reportbinary + release artifact: a dependency-free generator that emits a one-page, print-ready HTML validation summary indexing every CI-enforced validation (SGP4 666/666, EGM2008, bit-for-bit frames, NIST Allan, IMU datasheets, ARAIM/SBAS, 3-OS reproducibility, coverage) to its test and external oracle. The release workflow generateskshana-validation-summary.htmland attaches it (with SLSA provenance) to each tagged release.- numpy-interop pytest + wheel hardening:
tests/python/test_numpy_interop.py(run in CI) plus a pinned manylinux container and anauditwheel showverification step in the wheel build. - Tutorials & education:
docs/tutorials/(three worked tutorials, per-domain annotated scenarios, Tier-1/2/3 exercises) withtests/tutorials.rspinning every quoted number to live engine output. - External submission artifacts (
paper/,notebooks/,submissions/): a JOSS paper draft, a quantum-vs-classical notebook, and ready-to-file kits for awesome-gnss / ESA Navipedia / NASA ASCL / ESA ESSR / ION/IAC, plusFUNDING.yml— staging the external steps for submission. - Terrain-referenced & combined alt-PNT navigation (
altpntmodule): a TERCOM/SITAN terrain-matching navigator over a DEM (.hgtloader + synthetic-fixture generator) and a combined gravity + magnetic (IGRF) + terrain GPS-denied navigator, exposed asterrain-navandcombined-altpntscenario kinds. Validated by terrain-match convergence (a known injected offset recovered) and a bounded combined-filter error over a GPS-denied window. - LunaNet LANS geometry (
lunar): named lunar surface sites (Apollo 11/15/16, Shackleton rim) with authoritative selenographic coordinates, surface look angles (az/el/range), visibility/coverage, and site DOP, validated against the Moon radius, the published site coordinates, and the radial-overhead 90° elevation identity. - Guided browser playground: guided-mode sliders, a tabbed output panel, a first-run tour
overlay, parameter-sweep and multi-run-overlay modes, a dependency-free canvas/SVG 3D orbit
view (the orbit pack now emits an additive
eci_track), an embed/iframe mode, and download-as-HTML-report — each with node unit tests in CI. - Datasheet-validated IMU error model (
tests/imu_allan_spec.rs): ADIS16465/16488/16460 ARW/VRW/bias-instability recovered from the synthesised Allan deviation and checked against the manufacturer specs (NIST SP1065 / IEEE 952 identification). - NIST SP1065 Allan-estimator validation (
tests/allan_nist_sp1065_1000point.rs): the four estimators reproduce the published 1000-point reference deviations and Table-32 confidence bounds. - SBAS / DO-229E protection levels, L1/L5 ionosphere-free, and a DO-316 compliance map
(
sbasmodule).sbas_protection_levelforms the weighted geometry matrix from each satellite's elevation/azimuth and UDRE/GIVE/airborne/tropo error budget, inverts the normal matrix (sharedorbit::invert4), and projects the variances into HPL/VPL via the DO-229E K-factors (PA 6.0/5.33, NPA 6.18).iono_free_l1l5adds the GPS L1/L5 ionosphere-free pseudorange (IS-GPS-705,γ₁₅ = 1.79327), validated to cancel the engine's independent first-order ionospheric delay.do316_compliance_maptraces DO-316/DO-229E requirements to the implementing functions. Validated against closed-form K-factor definitions, the numpyinv(GᵀG)reference geometry, and the two-route covariance identity; the published-PL RTKLIB/gLAB conformance cross-check is documented as pending review indocs/COMPLIANCE.md. - Full tesseral spherical-harmonic gravity — the EGM2008 field to degree/order 70.
A new
gravity_sh::SphericalHarmonicFieldevaluates the geopotential and its acceleration in the Earth-fixed frame from fully-normalizedC̄_nm, S̄_nmcoefficients, using the stable Holmes–Featherstone normalized Legendre recurrence (de-normalizing would overflow at this degree). The shipped coefficients are the NGA EGM2008 product (public domain, via ICGEM), bundled inegm2008_data.rsand reproduced bit-for-bit bytools/gen_egm2008.pyfrom the committedtools/egm2008_to70.gfc; any ICGEM.gfcmodel loads viafrom_gfc. Validated against three independent oracles: point-mass collapse (C̄00-only =−μr/|r|³), a zonal-only field reproducing the existingforces::zonal_accelto ~1e-9, and the analytic acceleration matching the finite-difference gradient of the directly-summed potential to <1e-6. - General-relativistic Lense–Thirring (frame-dragging) acceleration
(
forces::lense_thirring_accel, IERS 2010 Eq. 10.12), the gravitomagnetic term beyond the existing Schwarzschild correction, wired into the numerical propagator via a newForceModel::lense_thirring()flag. Validated as linear in the Earth's angular momentum and 1–2 orders of magnitude below the Schwarzschild term, the regime of the LAGEOS / Gravity Probe B measurements. - A
Propagatortrait unifying the analytic and numerical orbit propagators. The numerical Cowell force-model propagator is now a first-class peer of SGP4: a newNumericalPropagatortype (initial state +ForceModel+Tolerance+ choice of step-doubling or Dormand–PrinceIntegrator) andSgp4both implementpropagator::Propagator, whosestate_at(t_seconds) -> StateVectorreturns the inertial TEME state in SI units (m, m/s) so the two are interchangeable behind aBox<dyn Propagator>. The SGP4 impl is the exact km/min→SI conversion of the inherent method (verified by an equality test); the numerical impl clears the same sub-metre exact-Kepler gate through the trait, the two adaptive drivers agree, and aPropagatorErrorsurfaces the underlying SGP4 code.
0.13.0 - 2026-06-08#
This release closes the largest correctness gap in the engine: Earth-orientation and reference-frame reduction are now done to reference-implementation grade (validated bit-for-bit against ERFA/SOFA), the integrity stack gains dual-constellation ARAIM on real GPS+Galileo TLEs, and the propagation, quantum-sensor, lunar/cislunar, and geomagnetic layers all deepen — alongside a typed Python API, a richer browser playground, and a three-OS reproducibility matrix. Highlights:
- Reference frames, bit-for-bit. Full IAU 2000A and 2000B nutation, IAU 2006 precession, the CIO-based (X, Y, s) IAU 2006/2000A GCRS↔ITRS reduction, IERS polar motion, and TEME→GCRS/ITRS output frames — each validated bit-for-bit against ERFA/SOFA reference routines.
- Dual-constellation ARAIM (GPS + Galileo) on real TLEs, with HPL/VPL,
Stanford-diagram output, and an open
docs/ARAIM_REFERENCE.md. - Cislunar PNT: an Earth–Moon CR3BP propagator, MCI↔MCMF frames, selenographic coordinates, and a runnable LunaNet lunar-integrity scenario.
- Quantum sensing: Coriolis and AC-Stark systematics for the cold-atom interferometer, a drift sweep, and validation against the Freier (2016) budget.
- IGRF-14 geomagnetic main-field model, self-contained and validated.
- Typed Python API (PyO3
RunOutput/ScenarioMeta,.data(),scenario_kinds,validate_toml, type stubs) with a CI wheel build, plus first-class GCRS/ITRS propagator output and CCSDS OMM export. - Credibility & reproducibility: a head-to-head SGP4 accuracy comparison against
the independent
sgp4crate, a CI coverage gate (~97% line onsrc/), and a three-OS (ubuntu/macos/windows) reproducibility matrix.
Changed#
- Every playground chart now matches the site theme. All twelve SVG chart
generators — the result/holdover chart (
src/report.rs+src/chart.rs), the Allan-deviation chart (web/app.js), and every scenario chart (src/hybrid.rs,jamming.rs,timetransfer.rs,spoof.rs,raim.rsStanford + availability,lunar.rs,ensemble.rs,sweep.rs,gnss_sim.rs,fusion/pack.rs,inertial/mod.rs) — used cool navy panels (#0e131b), cool-gray axes/text, and a clashing red/blue/purple series palette. They now use the warm graphite palette throughout:--bgpanels, warm--linegrid,--fglabels, with a consistent series assignment — quantum = honey-gold (--accent-bright), classical = warm amber (--partial), spec/limit =--crit. Safety-coded views keep their meaning: the RAIM Stanford diagram stays green (available) / amber (misleading) / red (hazardous) / muted steel (unavailable), and HPL/VPL read as gold/bronze. - Charts are now self-describing when saved/downloaded. Both charts bake their
title into the SVG (the Allan chart's title + "lower is better" subtitle were
previously only HTML around the image, so a saved image had no caption), and both
carry a provenance footer —
Kshana v<version> · <scenario-hash> · kshana.dev— so a downloaded chart stands on its own and stays reproducible. - Every chart now carries the provenance footer. The footer —
Kshana v<version> · scenario <hash> · kshana.dev— is stamped centrally for all scenario kinds inapi::run_toml(it was previously only on the holdover and Allan charts), so any saved or downloaded image — from the playground, the CLI's.chart.svgexport, or the HTML scorecard — identifies its version, scenario fingerprint, and source. The hash is labelledscenariofor clarity and comes from the result'sscenario_hashwhere present, with a source-hash fallback for the integrity/lunar reports. What the fingerprint is and why it's there is documented in the README "Output" section anddocs/PROVENANCE.md.
Fixed#
raim::chi2_quantileand the RAIM Stanford-noise sampler are now panic-free on out-of-range / non-finite inputs (aread_dir-order-dependent fuzz finding from the new ARAIM scenarios).chi2_quantilenow guardsp/klikenormal_quantile(returning a boundary value instead ofassert!-panicking), and the availability Stanford-noiseNormalclamps to a strictly-positive σ — so the integrity/ARAIM stack never panics on mutated or mis-configured scenarios.
Added#
- Cross-platform reproducibility CI matrix. A new
reproducibility-matrixjob runs the reproducibility tests on ubuntu-latest, macos-latest, and windows-latest on every push. Because full result JSON is not byte-identical across OSes (last-ULP libm divergence), it asserts the platform-invariant projection exactly — the input fingerprint plus output shape, pinned per scenario as SHA-256 goldens intests/golden/by the newtests/cross_platform_golden.rs— alongside the numeric pins (golden.rs, to 1e-6), the SGP4 states (sgp4_verification.rs, to 2e-5 km), and same-process determinism (determinism.rs). Together these prove cross-platform reproducibility on three OSes without the brittleness of exact full-output byte hashing.docs/REPRODUCIBILITY.mddocuments the split. - Code-coverage gate in CI. A new
coveragejob runscargo-tarpaulinwith the LLVM source-based engine, publishes an lcov report as a build artifact, and enforces a line-coverage floor onsrc/(generated data tables, the CLI entrypoint, the tests, and web assets excluded). Measured line coverage is ~97% onsrc/(SGP4 and the clock modules ≥95%); the gate is set at 85% — above the ≥80% target and clear of the measured value, so it catches regressions without flaking. A coverage badge is published in the README. - SGP4/SDP4 head-to-head against the independent
sgp4crate. A new test (tests/sgp4_crate_comparison.rs) cross-validates Kshana's propagator against the most widely used Rust SGP4 library (neuromorphicsystems/sgp4, added as a test-only dev-dependency) over the same 666 AIAA 2006-6753 vectors. With both driven on the WGS72 gravity model the vectors use, the two independent implementations agree to sub-micron on near-earth and resonant orbits and 4.12 mm worst-case across all regimes, both reproducing the referencetcppver.outtable. The committed comparison table (tests/fixtures/sgp4_comparison.md, regenerated viaKSHANA_REGEN_FIXTURES=1) breaks the result out per regime (LEO/MEO, deep-space, ½-day and 1-day resonance) and notes the four deliberately-pathological cases the crate rejects at construction. The live assertions hold both within 2e-5 km of the reference and agree to within 4e-5 km — a regression guard, not a one-off. This is competitive pedigree: correctness against an independent codebase, not just a static table. (The crate's defaultfrom_elementsuses WGS84 and so differs from the WGS72 reference by ~km — surfaced honestly in the table prose.) - CCSDS OMM export is now reachable end-to-end. The OMM writer
(
src/omm.rs) previously had no CLI/API path; anorbitscenario's mean elements can now be published as a CCSDS 502.0-B-2 OMM catalogue — one OMM KVN message per TLE-defined satellite — viakshana <orbit.toml> --export-omm out.omm, orexport_omm = truein the scenario auto-writes<scenario>.omm(mirroring the existing--export-sp3). Each message carries the satellite's real NORAD catalogue number, COSPAR international designator (YYYY-NNNP), and epoch (CCSDS day-of-year form), parsed from the TLE line 1 by the newtle::parse_tle_identity; the name line becomesOBJECT_NAME(elseOBJECT <id>).CREATION_DATEis the scenario epoch, not wall-clock, so the output is reproducible. New API:api::export_omm/api::auto_export_omm,OmmFile::from_tle_block,OrbitClockScenario::to_omm_string. A synthetic Walker or RINEX scenario (no TLE mean elements) errors rather than emitting an empty file. Validated against the bundled 30-satellitegps-opssnapshot (tests/sp3_export_roundtrip.rs). - Interactive hover read-outs on the playground charts. Moving the cursor over
a chart snaps a crosshair to the nearest sample and shows a value tooltip
(
web/hover.mjs, wired inweb/app.js). On the Allan chart it reads τ and each clock's σ_y(τ); on the time-series scenario charts (clock holdover, dead-reckoning, time-transfer, hybrid PNT, GNSS/INS) it reads the time and each suite's value in the chart's own units (ns / m / ps / utilization), parsed from the result so the read-out matches the curve. Specialised diagrams (RAIM, spoof, sweep) get no overlay. The charts stay self-describing blob<img>s — the overlay is a transparent crosshair + tooltip on top, so download/compare/export are untouched. Coordinate math (nearest-sample, cursor→plot-fraction, polyline parsing) is unit-tested (web/hover.test.mjs, run in CI). - A/B compare mode in the playground. Pin any run as a baseline A, run a
second scenario, and the two are shown side by side with a figure-of-merit
delta table (holdover, timing RMS/p95, availability) that colours the winner
per metric (
web/compare.mjs, wired inweb/app.js; delta logic unit-tested inweb/compare.test.mjs, run in CI). All values are inserted as text and all charts via blob<img>, so nothing from a scenario string is ever injected as markup. - Chart download buttons (SVG + PNG). Each playground chart now has a
theme-matched "Download SVG / PNG" toolbar (
web/chartdl.mjs, wired inweb/app.js). SVG hands back the faithful, scalable original; PNG rasterises that same self-describing image at 2x for slides and documents. Files are named with their provenance —kshana-<chart>-v<version>-<scenario-hash>.<ext>— and the filename/size logic is unit-tested (web/chartdl.test.mjs, run in CI). - IGRF-14 geomagnetic main-field model (
src/igrf.rs). The IAGA standard spherical-harmonic field (degree/order 13, 2025.0 epoch + 2025–2030 secular variation; coefficients machine-generated from the officialigrf14coeffs.txtbytools/gen_igrf.pyintosrc/igrf_data.rs, bit-for-bit reproducible). A Schmidt-normalised synthesis returns the field vector (north/east/down) and derived elements (declination, inclination, horizontal/total intensity) at any geodetic location/date, plus the geomagnetic pole and dipole strength — the magnetic counterpart to the gravity-map matcher for alternative-PNT navigation. Validated self-contained: the synthesis matches the exact closed-form tilted dipole, the full degree-13 analytic field matches a finite-difference of the scalar potential, the dipole axis reproduces the known geomagnetic pole (~80.7°N, −72.7°E) and ~29.7 µT strength, and the global field is physical. - Typed Python bindings (
src/python.rs). Beyond the string-in/string-outrun/run_full, the module now exposes a typedRunOutputclass (.json,.svg,.summary, and a.data()accessor that returns the result parsed into a native Python dict — no JSON re-parsing, NumPy-wrappable), plusrun_typed,scenario_kinds()(parsed list of metadata dicts), andvalidate_toml()(a non-raising list of error messages). Ships a PEP 561 type stub (kshana.pyi+py.typed) for mypy/pyright/editors and adocs/PYTHON_API.mdquickstart. The existing functions are unchanged. - First-class output frames for propagators (
src/orbit.rsFrameenum +position_in_frame+state_gcrs). AnyPropagator(Kepler, SGP4, RINEX, GLONASS, SP3) can now emit its position in TEME, GCRS (≈ J2000), or ITRS (Earth-fixed) — TEME native, GCRS via the validated TEME→GCRS reduction, ITRS by chaining that into the IAU 2006/2000A CIOgcrs_to_itrsrotation — and its full GCRS state (position + velocity) viastate_gcrs. The of-date inertial output is no longer TEME-only. - CIO-based IAU 2006/2000A celestial-to-terrestrial reduction (
src/cio.rs). The modern, equinox-free GCRS↔CIRS↔ITRS chain: CIP coordinatesX, Yread off the IAU 2006/2000A bias-precession-nutation matrix (reusing the validated FW precession + 2000A nutation with theeraNut06aP03 adjustment), the 66-term CIO-locatorsseries (eraS06, machine-generated from the ERFA reference bytools/gen_s06.pyintosrc/cio_s06_data.rs, bit-for-bit reproducible), the GCRS→CIRS matrix (eraC2ixys), the Earth rotation angle (eraEra00), and the full GCRS→ITRS rotation (eraC2tcio, composed with the existing IERS polar motion). Validated bit-for-bit against the publishederaXys06a(X=0.5791308482835292617e-3, Y=0.4020580099454020310e-4, s=-0.1220032294164579896e-7 at JD_TT 2453736.5),eraC2ixys, anderaEra00test vectors. The CIO chain and the legacy equinox/GMST-1982 TEME reduction are shown to agree up to their documented ≈2·(equation of equinoxes) sidereal-time convention difference. This is the rigorous reduction the equinox/GMST path approximated. - Full IAU 2000A nutation series (
src/nutation.rsnutation_iau2000a,nutation_matrix_2000a). The complete MHB2000 model — 678 luni-solar + 687 planetary terms — accurate to < 0.1 mas, alongside the existing 77-term 2000B truncation. The tables are machine-generated from the IAU SOFA / ERFAnut00areference bytools/gen_nut00a.pyintosrc/nutation_iau2000a_data.rs(the generator reproduces the committed file bit-for-bit), and the whole series — both the IERS-2003 and MHB2000 fundamental-argument sets and the planetary longitudes — is validated bit-for-bit against the publishederaNut00atest vector (Δψ = −0.9630909107115518e-5, Δε = 0.4063239174001679e-4 at JD_TT 2453736.5, to 1e-13 rad). The default TEME→GCRS reduction keeps the 2000B series (~1 mas, below the chain's velocity-frame-rotation simplification);nutation_matrix_2000aexposes the < 0.1 mas of-date matrix for callers that need it. - Runnable lunar-integrity scenario (
kind = "lunar-integrity",scenarios/lunanet-araim.toml). Wires the lunar south-pole protection-level pass (src/lunar.rsLunarScenario→south_pole_hpl_pass) into the scenario runner with a JSONLunarReportand an SVG HPL-vs-time chart, so the cislunar integrity case is reachable straight from the CLI. It honestly surfaces the gap: with the 30 m LANS σ_URE the south-pole HPL (≈ 260–450 m) exceeds a 50 m alert limit (0 % available) — lunar PNT integrity is not yet met. - Dual-constellation ARAIM availability on real GPS+Galileo TLEs, and scenario-runner
wiring (
src/raim.rs,src/orbit.rs,scenarios/araim-gps-galileo.toml). Addsaraim_dual_constellation_availability(the advanced ARAIM engine — single-satellite and constellation-wide faults — run over a time grid) andvisible_positions_labeled, and wires it into theIntegrityScenariorunner via anaraim_dualflag so a multi-GNSS ARAIM study is reachable straight from TOML. A real-data test (tests/araim_dual_real_data.rs) on vendored 2026-06-07 Celestrak GPS+Galileo snapshots shows pooling Galileo lifts ARAIM availability from 0.21 to 0.67 under a demanding 12 m VAL (10.5→21.8 satellites in view), while the constellation-fault-robust mode is fundamentally limited with only two constellations — the quantitative reason robust dual-constellation integrity drives toward a third constellation or SBAS. Honest residual: the numerically exact EU ARAIM TN Table A-3 reproduction against a single version-locked epoch, and a Zenodo fixture record. - Circular restricted three-body problem (CR3BP) for the Earth–Moon system
(
src/cr3bp.rs). A new cislunar-dynamics core the two-body/SGP4 propagators cannot provide: rotating-frame equations of motion (cr3bp_accel), an RK4 propagator (propagate_cr3bp), the Jacobi-constant integral (jacobi_constant), and the five Lagrange points (lagrange_points). Validated against closed-form / published anchors: the Earth–Moon collinear points (L1 ≈ 0.83692, L2 ≈ 1.15568, L3 ≈ −1.00506), the exact equilateral L4/L5 = (½−μ, ±√3/2, 0), all five confirmed as field equilibria, Jacobi conserved to integrator precision under propagation, and the out-of-plane restoring force that makes halo/NRHO orbits possible. This is the foundation for representing a real NRHO. Honest residual: differential-corrected periodic 9:2 NRHO initial conditions, the eccentric/ephemeris (DE) model, and the de-normalised transform into the selenocentric frames ofsrc/lunar.rs. - IERS polar motion and the TEME→ITRF reduction (
src/frames.rs). Addspolar_motion_matrix(SOFAiauPom00:W = Rx(−y_p)·Ry(−x_p)·Rz(s′)with the TIO locators′),pef_to_itrf/itrf_to_pef, andteme_to_itrf— the GMST-based TEME→PEF rotation followed by polar motion — completing an ITRF-precise Earth-fixed position on top of the GMST-onlyteme_to_ecef(polar motion is a tens-of-metres effect at orbital radius).x_p/y_pare observed IERS quantities the caller supplies. Honest residual: a fully CIO-based (X, Y, s) chain and an ANISE/SPICE <10 m numerical cross-check remain follow-ons. - Cold-atom-interferometer systematics, drift sweep, and a published-device
validation (
src/inertial/quantum_imu.rs,docs/QUANTUM.md). Extends the first-principles CAI accelerometer with the two leading deterministic systematics: the Coriolis/rotation phaseΦ_cor = 2·k_eff·v_⊥·Ω·T²(coriolis_phase, with the equivalent acceleration bias2·Ω×vviacoriolis_accel_bias) and the AC-Stark light-shift phaseΦ_LS = (δ_LS,1 − δ_LS,3)/Ω_eff(ac_stark_phase, which cancels by π/2–π–π/2 symmetry for a constant shift). Addscai_drift_sweep(dead-reckoning position drift vs cycle time — the core of a quantum-vs-classical comparison) and a validation test against the Freier et al. 2016 mobile gravimeter (arXiv:1512.05660): the modelled quantum-projection-noise floor lies below, and within ~2 orders of, the published 96 nm/s²/√Hz short-term noise.docs/QUANTUM.mdupdated. Honest residual: wavefront/beam-pointing systematics, fringe-ambiguity resolution, the exact CARIOQA-PMP / Boeing-AOSense flight-test reproduction (needs published platform PSDs), and a JS playground preset. - Cislunar frame reduction and a lunar south-pole integrity pass (
src/lunar.rs). Extends the lunar ARAIM engine with the MCI↔MCMF (Moon-centered inertial ↔ Moon-fixed) rotation (mci_to_mcmf/mcmf_to_mci, a simplified mean-rotation model at the lunar sidereal rate), selenographic latitude/longitude/altitude (mcmf_to_selenographic/selenographic_to_mcmf), andsouth_pole_hpl_pass— a landed Artemis-region receiver against a representative LunaNet relay set over a 24 h pass, which honestly quantifies the integrity gap: with the nominal 30 m LANS σ_URE the protection level is finite but exceeds a 50 m surface-ops alert limit. Honest residual: the precise LANS NRHO ephemeris (a 3-body cislunar orbit), the physical libration / precessing lunar pole (DE421/SPICE), and a LunaNet TOML scenario remain follow-ons. - ARAIM integrity support message, Stanford-diagram SVG, and the open ARAIM
reference (
src/raim.rs,docs/ARAIM_REFERENCE.md). Adds an explicitIntegritySupportMessage(σ_URA / σ_URE / b_nom / P_sat / P_const, with the WG-C GPS+Galileo reference values and.fault_priors()/.dual_fault_priors()converters into the single-faultaraim_raimand constellation-widearaim_dual_raimengines), a standalonestanford_svgrenderer of the Stanford integrity diagram (the four zones, thePL = errorboundary, the alert-limit guides, one colour-coded marker per epoch), anddocs/ARAIM_REFERENCE.mddocumenting the algorithm, the ISM, the fault hypotheses, the protection-level contract, and the dual-constellation benefit. Tests demonstrate the geometry/redundancy gain (pooling a second constellation tightens the single-fault HPL) and constellation-fault tolerance (the dual user survives losing a whole constellation; a single-constellation user cannot). Honest residual: numerically reproducing the EU ARAIM TN Table A-3 / the 15–25 % availability figure against a version-locked real TLE snapshot, a Zenodo fixture record, and wiringaraim_dual_raiminto the scenario-file runner. - IAU 2000B nutation and the full TEME→GCRS/J2000 inertial reduction
(
src/nutation.rs). Adds the second and third pieces of a true inertial frame reduction on top of the shipped IAU 2006 precession: the 77-term luni-solar MHB2000 nutation series (nutation_iau2000b, the standard IAU 2000B truncation accurate to ~1 mas) with the Delaunay fundamental arguments and the SOFAiauNumatnutation matrix, and the Vallado AIAA-2006-6980 chain TEME→TOD (equation of the equinoxes) → TOD→MOD (nutation) → MOD→GCRS (bias-precession) exposed asteme_to_gcrs(r, v, jd_tt)/gcrs_to_teme. The series, arguments and unit constants are transcribed from the IAU SOFA / ERFAnut00breference and validated bit-for-bit against the publishederaNut00btest vector (Δψ, Δε to 1e-13 rad). Honest residual: the full IAU 2000A 678-term series (<0.1 mas), an ANISE/SPICE <10 m numerical cross-check, and polar motion remain follow-ons (seeROADMAP.md).
0.12.0 - 2026-06-06#
This release lands Kshana's first non-analytic orbit propagator — a Cowell integrator with a hierarchical six-perturbation force model (two-body + J2–J6 zonal + epoch-driven Sun/Moon third body + solar-radiation pressure with a conical umbra/penumbra shadow + atmospheric drag + the post-Newtonian Schwarzschild relativistic correction) driven by a choice of two adaptive integrators (RK4 step-doubling and the Dormand–Prince RK5(4) embedded pair) — alongside a maneuver / trajectory-design layer (impulsive and finite burns, an Izzo Lambert solver, and a porkchop sweep), a gravity-map-matching alt-PNT layer that recovers a 60-minute GPS-denied track to under 500 m, a batch + sequential orbit-determination pipeline, and a full 17-state tightly-coupled GNSS/INS UKF with quantum-CAI dead-reckoning. Every numerical capability is pinned against analytic truth or a hand-derived closed form; the off-by-default perturbations leave the released goldens untouched.
Added#
- Post-Newtonian (Schwarzschild) relativistic correction (
forces::relativistic_accel+propagator::ForceModel::relativity). Adds the dominant general-relativistic perturbation on a near-Earth orbit — the leading driver of the relativistic perigee advance — in the IERS / Montenbruck–Gillβ = γ = 1forma = (μ/c²r³)·{[4μ/r − v²]·r + 4(r·v)·v}. Like atmospheric drag it is velocity-dependent, so it rides the(r, v)integrator RHS via [accel_rv], opt-in and off by default. Validated self-contained: on a circular orbit it collapses to the closed form3μ²/(c²r³)·r̂(purely radial and outward, off-axis components exactly zero); its ratio to two-body is the textbook≈1.9·10⁻⁹at LEO (theμ/(c²r)signature); a radial-velocity case matches the hand-simplifiedμ(4μ + 3v²r)/(c²r³); and in the propagator it perturbs the orbit without dissipating it — the semi-major axis is conserved to well under a metre/day, the structural opposite of drag's monotonic decay. Because it is off by default the two-body/J2/zonal goldens are untouched. PPN-parameter (β,γ) tuning and the Lense–Thirring frame-dragging term remain follow-ons. - Conical umbra+penumbra shadow model (
forces::conical_shadow), now used by solar-radiation pressure. Upgrades the binary umbral-cylinder eclipse to a smoothν ∈ [0,1]factor: the Sun and Earth are modelled as disks of apparent angular radiia = asin(R☉/d☉),b = asin(Rₑ/|r|)with apparent centre separationc, andνis one minus the fraction of the Sun's disk occulted by the Earth's disk (the circle–circle lens-overlap area) — full sun forc ≥ a+b, total umbra forc ≤ b−a, annular forc ≤ a−b, and a continuous penumbra in between.srp_accelnow uses it, so the SRP force tapers smoothly through eclipse instead of switching on/off. Adds the IAU nominalforces::SOLAR_RADIUS. Validated self-contained:ν = 1in full sun andν = 0deep in the umbra (exact), a smooth monotonic penumbra (νrises 0 → ~½ atc = b→ 1 across the[b−a, b+a]band), and the conical penumbra extends beyond the umbral cylinder (a point the binary cylinder calls fully lit is0 < ν < 1for the cone). The simplercylindrical_shadowremains available; solar limb darkening and the oblate-Earth shadow remain follow-ons. - Dormand–Prince RK5(4) embedded integrator (
integrator::dopri54_step/integrator::integrate_dopri+propagator::propagate_dopri). Adds the standard Dormand–Prince (1980) embedded Butcher-tableau pair alongside the existing RK4 step-doubling driver: seven FSAL stages yield a 5th-order solution and a 4th-order error estimate from one set of evaluations (7 vs 11 function calls per step), a cheaper local-error estimate. The adaptive driver reuses the same RMS-error norm and0.9·(1/err)^(1/5)step controller, so it is a drop-in alternative;propagator::propagate_dopriexposes it on the orbit force model. Validated self-contained: the embedded error estimate is O(h⁵) (halving the step cuts it ~32×); DP5(4) integratesy' = ytoeand the harmonic oscillator over 50 periods conserving energy to <1e-6; it reaches the same endpoint at the same tolerance in fewer function evaluations than step doubling (without sacrificing accuracy); andpropagate_dopriclears the same analytic-truth gate as the RK4 path — sub-metre against the exact universal-variable Kepler solution over a 24 h LEO orbit — while the two drivers agree to <1 m on a J2..J6 orbit (no closed form). Higher embedded pairs (RKF7(8) / DOP853) remain a follow-on. - Atmospheric drag wired into the propagator as its first velocity-dependent force
(
forces::atmospheric_density+forces::drag_accel+propagator::ForceModel::drag). Adds the Vallado Table 8-4 piecewise-exponential atmosphereρ = ρ0·exp(−(h−h0)/H)(28 bands from sea level past 1000 km, clamped below the surface) and the quadratic draga = −½ · ρ(h) · (C_D·A/m) · |v_rel| · v_relagainst the co-rotating atmospherev_rel = v − ωₑ ẑ × r(forces::EARTH_ROTATION_RATE = 7.2921151467e-5). Because drag depends on velocity,ForceModelgains a newaccel_rv(t, r, v)and the integrator RHS now passes velocity (f(t,[r;v]) = [v; a(t,r,v)]); the position-onlyaccel_atis unchanged, so the conservative terms and goldens are untouched. Validated self-contained: the density anchors at the 1.225 kg/m³ sea-level value, clamps below the surface, decreases monotonically through LEO, sits in the solar-mean ~1e-12 kg/m³ band at 400 km, and its recovered local scale height (≈ 58 km at 400 km) is physical; drag opposes the co-rotating relative velocity at the ~2e-6 m/s² LEO magnitude forC_D·A/m = 0.02 m²/kg; and — the key signature — drag is dissipative: a 300 km orbit loses specific energy monotonically and its semi-major axis decays a bounded ~km/day, where the vacuum baseline conserves energy to <1e-9. The NRLMSISE-00 thermospheric density (the < 5 % drag-density clause) remains a follow-on. - Solar-radiation pressure wired epoch-driven into the propagator force model
(
forces::srp_accel+propagator::ForceModel::solar_radiation). Adds the cannonball SRP modela = ν · P☉ · cᵣ · (A/m) · (AU/d)² · d̂with a cylindrical-shadow eclipse factor (forces::cylindrical_shadow, ν ∈ {0,1}): the radiation pressureP☉ = Φ☉/cfrom the modern 1361 W/m² total solar irradiance (≈ 4.5398·10⁻⁶ N/m²), the inverse-square(AU/d)²flux fall-off, and the radial push away from the Sun. It rides the same epoch-driven RHS as the third body, sampling theephemSun once at the advanced epochepoch_jd_tt + t/86400shared between the Sun third body and SRP. Composable:with_zonals_j2_j6().third_body(true, true, epoch).solar_radiation(1.5, 0.02). Validated self-contained against hand-derived signatures: the 1-AU radiation pressure pins to its textbook ≈ 4.5398·10⁻⁶ N/m²; a fully-lit LEO sat's SRP is bit-identical to the cannonball formula, points away from the Sun, and sits in the ~1.36·10⁻⁷ m/s² band for cᵣ = 1.5, A/m = 0.02 m²/kg; doubling the Sun distance quarters the magnitude (inverse-square); the cylindrical shadow eclipses only the umbral cylinder (anti-sunward and within one Earth radius of the Earth–Sun line) and yields exactly zero SRP in eclipse; and in the propagator SRP perturbs a LEO orbit by a small bounded amount that scales ~linearly with A/m — while a model with no perturbations stays bit-for-bit time-independent, leaving the two-body/J2/zonal goldens untouched. The conical umbra/penumbra (smooth ν ∈ [0,1]), atmospheric drag, and external GMAT/Orekit cross-validation remain follow-ons. - Epoch-driven Sun/Moon third body wired into the time-varying propagator RHS
(
propagator::ForceModel::third_body/accel_at). The third-body perturbation is no longer a standalone force term — it is now integrated by the Cowell propagator as a genuinely time-varying force: each RHS evaluation samples theephemSun/Moon positions at the advanced epochepoch_jd_tt + t/86400(reusingprecession::julian_centuries_ttfor the day↔century conversion), so the perturbers move along their orbits during the integration rather than being frozen at the start. Composable with any gravity model (ForceModel::with_zonals_j2_j6().third_body(true, true, epoch)). Validated self-contained: the RHS Sun term is bit-identical tothird_body_accelevaluated at the ephemeris position for that instant at botht = 0andt = 1 day(proving the 86400 s ↔ 1 day ↔ 1/36525 century wiring exactly), the perturber advances ~2.6·10⁹ m/day between samples (not frozen), the instantaneous LEO tidal magnitudes hit the textbook ~5·10⁻⁷ m/s² (Sun) and ~1.1·10⁻⁶ m/s² (Moon, ≈ 2× the Sun) bands, each body measurably perturbs the day-long trajectory while staying bounded, and the same initial state propagated at epochs a quarter-year apart yields a different trajectory (the tidal axis rotates 90°) — while a model with neither body enabled is bit-for-bit time-independent, leaving the two-body/J2/zonal goldens untouched. DE-grade ephemeris accuracy and external GMAT/Orekit cross-validation remain follow-ons. - Low-precision Moon ephemeris (
ephem::moon_position), completing the Sun/Moon third-body pair. Adds the Montenbruck & Gill low-precision lunar series (§3.3.2) alongside the Sun model, so the body-agnosticforces::third_body_accelcan now be driven by either luminary with no external DE/SPK kernel. Validated self-contained against hand-derived lunar signatures: the geocentric distance stays inside the real perigee/apogee envelope (~356 500–406 700 km) over a month and its monthly mean recovers the ~384 400 km semi-major axis; the ecliptic latitude never exceeds the ~5.3° lunar-orbit inclination (checked by projecting onto the ecliptic pole in equatorial coordinates, validating the latitude series and the obliquity rotation together); the Moon's direction returns to within 1° after one sidereal month (27.3217 d) and its daily motion stays in the physical 12–15°/day band; and the lunar third-body perturbation on a LEO satellite has the textbook ~1.1·10⁻⁶ m/s² magnitude (≈ twice the Sun's). DE-grade position accuracy, atmospheric drag, and SRP remain follow-ons. - Third-body (Sun) gravity with a built-in low-precision ephemeris (
forces::third_body_accel,ephem::sun_position). Adds the third-body perturbation to the force model:a = GM₃·((s−r)/|s−r|³ − s/|s|³)(direct attraction minus the indirect term the geocentric frame must subtract), with the Sun position supplied by the newephemmodule's Montenbruck & Gill low-precision analytical series — no external DE/SPK kernel needed for a low-fidelity run. Validated self-contained: the acceleration matches the exact gradient of its own disturbing potential (third_body_potential), the perturbation vanishes at the geocentre and has the textbook ~5·10⁻⁷ m/s² magnitude on a LEO satellite, and the Sun ephemeris hits hand-derived J2000 anchors — perihelion distance ≈ 1.471·10¹¹ m, declination ≈ −23° near the December solstice, an apparent motion of ≈ 1°/day (≈ 90° per quarter-year), and a distance that stays inside the 0.983–1.017 AU Earth-orbit envelope across a full year. Delivers the third-body half of the numerical-propagator milestone's force-model step (the Moon is delivered in a companion entry above); DE-grade position accuracy, atmospheric drag, and SRP remain follow-ons. - J2–J6 zonal-harmonic force model (
forces::zonal_accel/zonal_potential). Extends the Cowell propagator's force model beyond J2 to the full Earth zonal field through degree 6 (the standard published EGM-96 unnormalisedJ2..J6), wired into the propagator asForceModel::with_zonals_j2_j6(). The acceleration is the exact analytic gradient of the zonal disturbing potentialR(r) = −(μ/r)·Σ Jₙ(Re/r)ⁿPₙ(z/r)(Legendre polynomials by upward recurrence), validated three independent ways: it reduces to the 666-vector-validatedj2_accelto machine precision when restricted to[J2]; it matches the numerical gradient of its own potential through the full J2..J6 field (the conservative-field gold-standard check); and the oddJ3vs evenJ2/J4..J6terms exhibit their characteristic north–south (anti)symmetry underz → −z— the pear-shape asymmetry. A propagated J2..J6 orbit conserves total energy (kinetic + central + zonal potential) to ~1e-8 over a day and perturbs the J2-only orbit by a small non-zero amount. This delivers step-2 ("J2–J6 zonal harmonics") of the numerical-propagator milestone; the high-degree EGM tesseral field, drag, SRP, third-body, and external GMAT/Orekit cross-validation remain follow-ons. - Numerical (Cowell) orbit propagator (
src/propagator.rs). Kshana's first non-analytic propagator (the rest of the orbit stack is analytic SGP4/SDP4): it wires the two-body + J2 force model (src/forces.rs) into the adaptive step-doubling RK4 driver (src/integrator.rs) asf(t,[r;v]) = [v; a(r)], with aForceModeltoggle. Validated against analytic truth that is stronger than a numerical cross-tool would be: the unperturbed orbit reproduces the exact universal-variable Kepler solution to sub-metre over a 24-hour LEO orbit (a tighter gate than the "vs a numerical reference < 10 m" the milestone phrases), specific energy and angular momentum conserve to ~1e-9 relative, and the J2 nodal regression reproduces the closed-formj2_secular_ratesto first-order theory (within 2 %, the O(J2²) residual). Also addssolve_kepler_checked, a Newton solver for Kepler's equation that returnsErrinstead of a silently-wrong answer when it fails to converge within a bounded iteration budget (the near-perigeee = 0.999case). Honest scope: the force model is two-body + J2 only — the high-degree EGM tesseral field (200×200 + loader), drag (NRLMSISE-00), SRP, third-body forces, and an external GMAT/Orekit cross-validation remain follow-ons. - 60-minute GPS-denied gravity-map matching to < 500 m (
run_gps_denied_gravity_nav). Deepens the alt-PNT layer to the ESA NAVISP Quantum Wayfarer validation target: a vehicle flies a ~700 km track for a full one-hour GNSS outage — its inertial solution drifting to ≈ 70 km — and a cold-atom gravimeter plus a hierarchical coarse-to-fine particle/grid matcher recovers the constant INS drift to ≈ 145 m (< 500 m), a > 480× cut. The gravimeter's real white-noise floor is injected as a deterministic seeded sequence, so the matcher is never handed noise-free truth yet the run is exactly reproducible (verified bit-identical, and stable to a few metres across noise realisations). A regression-grade test shows the refinement is necessary — a single coarse grid stalls at ~2 km, only the three-stage refinement breaks the 500 m barrier. New committed scenarioscenarios/gps-denied-gravity-nav.toml. Thedocs/CAPABILITY.mdrow stays honestly partial (still no bundled EGM2008 map) with its evidence updated to the 60-min < 500 m result. Honest scope unchanged: low-degree spherical-harmonic field + synthetic mascons; a Monte-Carlo over map-representation-error realisations is a follow-on. - Overclaim ledger + regression guard (
docs/CLAIMS-VS-REALITY.md,tests/no_overclaims.rs). Closes the honesty/de-claim track: the fourteen overclaims an earlier audit catalogued (OC-0…OC-13) are now all GREEN — the strong claims (OC-0coupled clock+position Kalman,OC-2jamming J/S→C/N₀→loss-of-lock,OC-7Mach–Zehnder CAI physics,OC-8ARAIM HPL/VPL) are superseded by shipped, tested capabilities rather than softened wording, and the remaining rows are de-claimed to match the code. A new CI test scans the live public surfaces (README,CAPABILITY,GLOSSARY,web/) and fails if any retired bare overclaim phrase reappears uncaveated, so a GREEN row cannot silently regress. The per-run "integrity" FoM stays honestly labelled filter self-consistency (not aviation integrity); the real ARAIM HPL/VPL is surfaced separately so the two are never conflated. - Gravity-map-matching navigation (GPS-denied alt-PNT). New
src/gravimeter.rsadds the alt-PNT capability layer ESA NAVISP's Quantum Wayfarer / QT-CCI gravity-map-matching studies call for: a cold-atom gravimeter measurement model whose white-noise floor is derived from the CAI accelerometer ASD (σ = ASD/√τ); a low-degree, fully-normalised spherical-harmonic gravity-anomaly field (validated against the closed-form Legendre functionsP̄₁₁=√3·cosφ,P̄₂₀=(√5/2)(3sin²φ−1),P̄₂₂=(√15/2)cos²φand a hand-derived single-term anomaly of 1.897 mGal) plus synthetic mascons for the high-degree local features; and a gravity-map-matching particle filter (composingmapmatch+particle_filter) that recovers a GPS-denied track from the anomaly sequence it flies through. A committed NAVISP benchmark (scenarios/gravity-map-nav.toml) cuts a ~73 km free-inertial drift to a few km. Honest scope: Kshana does not bundle the full EGM2008 2190° coefficient set — the field is low-degree + mascons, not a real high-resolution map; the EGM/EIGEN loader, magnetic map, terrain-aided SLAM, and scenario-enginekind=wiring with an SVG drift chart remain follow-ons.docs/CAPABILITY.md"Gravity-map / alt-PNT navigation" → partial. - Maneuver modeling and trajectory-design beachhead. New
src/maneuver.rsadds the first trajectory-design layer above SGP4: impulsive ΔV nodes that apply a velocity discontinuity and carry a 6×6 covariance forward (deterministic burn ⇒ identity state-transition; the execution-error covariance rotates from the burn frame — ECI or LVLH — into the velocity block), a finite-burn integration (constant thrust over a burn arc with mass as a state) whose achieved ΔV is checked against the closed-form Tsiolkovsky rocket equation to better than 0.01 %, an Izzo-2015 single-revolution Lambert solver (r1,r2, time-of-flight ⇒v1,v2), an exact universal-variable Kepler propagator (two-body truth), and a porkchop sweep that maps a launch-epoch × arrival-epoch grid to departure C3 and arrival V∞, emitted as a 2-D JSON array for browser contour rendering. Validation is self-contained and stronger than a tutorial read-out: every Lambert output is round-tripped through the Kepler propagator (it must land back onr2), and the porkchop minimum is checked against the analytic Hohmann-transfer C3 floor for two coplanar circular orbits. Kshana positions this as the performance-simulation layer above GMAT/Orekit, not a replacement (multi-revolution branches and a real planetary ephemeris remain out of scope). Ten tests. - Full 17-state tightly-coupled GNSS/INS UKF with quantum-CAI dead-reckoning. New
src/fusion/tightly_coupled17.rscarries the complete inertial-navigation state a tightly-coupled filter estimates —[position, velocity, attitude-error, accelerometer bias, gyro bias, clock bias, clock drift](17 states) — propagated through the strapdown mechanization driven by the measured specific force and angular rate, with the small-angleC ≈ I + [ψ×]body→inertial rotation so attitude error couples into horizontal acceleration (the standard INS tilt coupling). During a GNSS outage it coasts on the IMU alone; the velocity-random-walk process noise is the cold-atom-interferometer accelerometer's derivedq_va(crate::inertial::quantum_imu), so the dead-reckoning drift is the quantum-sensor limited one — a 120-second outage stays bounded to a few hundred metres versus the kilometres a navigation-grade free INS would reach. The pseudorange/range-rate update runs through the shared unscented filter (with α = 1 for well-conditioned weights at this state size). Five tests: measurement-model identity, perfect-IMU constant-velocity integration, GNSS aiding, accelerometer-bias estimation, and the CAI-limited 120-s outage benchmark.
0.11.0 - 2026-06-05#
Changed#
- Honest framing for the quantum positioning. The headline descriptor is now a
"PNT-resilience simulator with quantum-sensor performance models" consistently
across the README tagline, citation line,
CITATION.cff(title + abstract), and the banner artwork — replacing the looser "hybrid quantum/classical PNT simulator" marketing phrasing. The README's What it is / is not section gains an explicit "It is not (yet)" scope statement (not a first-principles atom-interferometry physics engine, not a GNSS receiver/PVT solver, not a mission-design tool), and a new top-levelROADMAP.mdmakes the Quantum physics layer a P2 item (Mach–Zehnder CAI phase, projection noise, vibration tensor) so readers know the first-principles physics is scoped-and-coming, not abandoned. No behaviour or API change.
Added#
- Constellation-design trade study: Walker design sweep with a Pareto front, revisit-time
JSON, and a sub-kilometre Walker-formula validation.
src/walker.rsgainswalker_design_sweep, which runs aplanes × sats_per_planegrid (e.g. a 3×3 trade) at a fixed inclination and tabulates, per design, the coverage fraction, worst-case PDOP, and the max/mean revisit gap;pareto_frontflags the non-dominated designs (fewer satellites, more coverage, lower PDOP, shorter revisit), andWalkerDesignReport::to_jsonserialises the cells and Pareto front — revisit-time fields included — as JSON. New validation pins the generator to the Walkeri:T/P/Fformula: same-slot satellites in adjacent planes are shown to map onto one another by an exactR_z(2π/P)rotation to under 1 km over a full 24 h of SGP4 propagation (the J2 short-period breathing is common-mode and cancels), and the in-plane slots are confirmed spaced2π/Sin the mean. Builds on the committed real Celestrakgps-ops2021-07-28 snapshot (scenarios/orbit-sgp4-gps.toml, exercised by the scenario-coverage and SP3 round-trip tests). - Advanced time-and-frequency transfer: TWSTFT, GNSS common-view, PPP, optical, IEEE-1139
power-law fit, and a clock ensemble. New
src/timetransfer_adv.rsbuilds the operational transfer methods on the shipped Sagnac/common-view closed forms and the Allan-stability tools.twstft_sagnacgives the Two-Way Satellite Time and Frequency Transfer Sagnac correction as the three-hop loop sum, equal to the BIPM closed formΔt = 2·A·ω_E/c²exactly (cross-checked by the independenttwstft_sagnac_bipm);run_twstftemits a one-dayT_A − T_Bseries and its TDEV.gnss_common_view_seriessingle-differences two synthetic ground stations so the satellite clock cancels.iono_free_combination+ppp_receiver_clockare the PPP ionosphere-free combination and receiver-clock solve against an SP3-grade (synthetic) truth, cancelling the first-order ionosphere exactly.rytov_variance,fried_parameter, and the unit-meanlognormal_fadingmodel a free-space optical link's turbulence-induced scintillation.fit_power_law_psdis a full IEEE-1139 five-coefficienth_αleast-squares fit of the Allan-variance curve (all five canonical noise processes at once) with the dominant process reported per τ-decade.ensemble_timescaleforms an inverse-variance-weighted paper timescale whose Allan deviation falls strictly below the best contributing clock. 31 unit tests; validation targets are closed forms and synthetic truth — a real BIPM Circular-T / IGS SP3 ingest remains. - IONEX ionosphere maps: file parser, time interpolation, and slant obliquity mapping.
src/ionex.rsgainsparse_ionex, which reads the IONEX file format (header grid definition +START/END OF TEC MAPblocks) into a sequence ofIonexMaps — normalising the file's north-to-south latitude ordering into a positive-stepTecGridand scaling values by10^EXPONENT.interpolate_tec_in_timeblends two successive maps to a query epoch, andobliquity_factor/slant_tecmap the vertical TEC onto a slant ray via the single-layer thin-shell factorM(z) = 1/cos z′(sin z′ = (Rₑ/(Rₑ+H))·sin z). Together with the shipped grid model these turn a measured IGS global ionosphere map into a usable slant delay. - Constellation design: streets-of-coverage sizing + multi-constellation comparison.
src/walker.rsgainsmin_satellites_streets_of_coverage, an idealised streets-of-coverage minimum-satellite solver — from the shipped coverage half-angleλand street half-widthcit sizes the near-polar constellation for continuous single global coverage asp = ⌈π/(2c)⌉planes (e.g. a GPS-altitude 4-satellite plane needs 2 planes, 8 satellites), and reportsNonewhen the satellites are too sparse to form a continuous street.compare_constellationsis the multi-constellation comparison tool: it scores each named Walker design on the same station/window viapdop_sweepand returns their coverage / PDOP / size side by side. Honest scope: the seam-exact Rider correction at the counter-rotating plane boundary and a 3-D coverage globe are follow-ons. - Multi-layer spoof detection: RAIM-consistency parity detector + layer fusion.
src/spoof_monitors.rsgains the third and final detection layer and the fusion stage:parity_raim_testleast-squares-fits the position/clock solution to a redundant pseudorange set and tests the leftover weighted residual sum-of-squares against its χ²(m−4)threshold — flagging a biased subset of satellites while correctly leaving a common-mode bias (absorbed by the receiver clock) RAIM-invisible, not papered over.fuse_spoof_layerscombines the parity, AGC and SQM layers into one weighted decision that records which layers fired. A Monte-Carlo characterises the detector: empirical P_fa ≈ 0.068 against a 0.05 design point, with missed-detection falling from 0.885 at a 2σ spoof bias to 0.16 at 8σ. Honest scope: cross-validation against specific published (Spirent / ION GNSS+) spoofing test vectors needs those external datasets and remains a follow-on. - Coupled-vs-decoupled Kalman validation ensemble. A 100-trial Monte-Carlo in
src/fusion/coupled.rsquantifies the value of carrying the position↔clock cross-covariance: a faithful inline decoupled baseline (validated bit-for-bit against the shippedCoupledPntFilter) processes the same data with the cross blocks zeroed, and after near-degenerate pseudoranges plus a clock-only fix the coupled filter recovers position to 2.97 m RMS versus the decoupled filter's 48.8 m, winning 97 of 100 trials — the clock fix sharpens position only through the correlation the decoupled pack discards. This completes the Kalman-correctness validation suite (Joseph form, PSD safety, NEES/NIS consistency, and now the coupled-filter ensemble). - Orbit determination pipeline (batch + sequential). A new
src/orbit_determination.rsrecovers a satellite's orbital state[r, v]from ground-station range tracking, composing three shipped pieces: the two-body + J2 force model (src/forces.rs) and RK4 integrator (src/integrator.rs) propagate a candidate state across the arc, a range measurement model predicts each station range, and the Gauss–Newton batch corrector (src/batch_ls.rs) drives the candidate onto the best-fit state (determine_orbit_batch). The same dynamics and range model also drive a sequential recursive determination on the shipped unscented filter (determine_orbit_sequential). Four tests validate it: range prediction across the arc; batch recovery to sub-metre / mm·s⁻¹ from noiseless ranges; batch recovery to ~2 m with a post-fit residual at the 5 m noise floor (the signature of a consistent least-squares fit); and sequential recovery to within tens of metres. Honest scope: range-rate/Doppler and angle measurements, an analytic J2 state-transition matrix, and station visibility masking are follow-ons. - Tightly-coupled GNSS/INS UKF navigator. A new
src/fusion/tightly_coupled.rswires the shipped unscented Kalman core (src/fusion/ukf.rs) into a working tightly-coupled navigator over the eight-state[px,py,pz,vx,vy,vz,b,d](ECEF position/velocity plus receiver clock bias and drift in range units). It ingests the raw satellite measurements —pseudorange(ρ = |p − sᵢ| + b) andrange_rate/Doppler (ρ̇ = (p − sᵢ)·(v − ṡᵢ)/|p − sᵢ| + d) — rather than a pre-formed position fix, soTightlyCoupled(withpropagate/propagate_orbital/update_gnss) keeps correcting with fewer than four satellites and coasts through GNSS outages on its propagated dynamics. Five tests validate it end-to-end, including the milestone acceptance scenarios: the pseudorange/Doppler geometry against hand values; noiseless convergence to sub-metre on five satellites; a three-satellite case converging from ~212 m to ~13 m where a snapshot PVT cannot even be formed; a constant-velocity 120-second outage within 50 m; and — the headline acceptance — a 30-minute curving LEO pass (real two-body + J2 orbit) with a 120-second GNSS outage, held to 0.77 m pass RMS and 2.9 m worst-case through the outage. That orbital coast composes the shipped gravity force model (src/forces.rs) and RK4 integrator (src/integrator.rs) into the UKF process model (propagate_orbital), so the filter follows the orbit's curvature — which a constant-velocity coast cannot (curvature alone is ~58 km over 120 s at LEO). Honest scope: the orbital coast uses the two-body + J2 force model rather than raw IMU specific-force (for an unpowered orbital platform these coincide); folding in a strapdown-IMU error state and in-loop iono/tropo corrections remain follow-ons. - Map-matching measurement model (terrain-/gravity-referenced navigation). A new
src/mapmatch.rssupplies the measurement model that turns the shipped sequential-importance-resampling particle filter (src/particle_filter.rs) into a working GPS-denied navigator:field_likelihood(a Gaussian field-match likelihood) andmap_match_likelihood, which samples any georeferenced reference field — terrain elevation (TRN) or a gravity anomaly — at a particle's position and weights it by agreement with the vehicle's measured value. The field is anyFn(lat, lon) -> valuesampler, so it composes with the bilinear grid insrc/ionex.rsor a closure. Two tests anchor it — the likelihood peaks (=1) at a perfect match and falls toe^(−½)at one sigma, and a particle filter over a distinctive synthetic-terrain patch recovers the true position to within 0.1. Honest scope: the real reference maps (SRTM elevation, EGM/EIGEN gravity anomaly) and their loaders are follow-ons. - Cislunar PNT integrity (lunar ARAIM). A new
src/lunar.rsapplies the Earth-side MHSS ARAIM engine to a LunaNet-style lunar navigation service with the lunar parameters (σ_URE ≈ 30 mvs GPS 0.6 m,P_sat ≈ 1e-4): lunar constants, a selenocentric East/North/Up basis and sky-geometry helper, andlunar_araim(HPL/VPL). Three tests anchor it — the orthonormal selenocentric basis, the slant-range geometry, and the exact linear protection-level scaling withσ_URE(lunar 30 m gives a 50× larger protection level than the same geometry at the GPS 0.6 m — the quantitative reason lunar PNT integrity is hard). Honest scope: the precise LANS NRHO ephemeris, the signal-in-space error budget, and the MCI↔MCMF frame reduction are follow-ons. - Two-part (high-precision) Julian dates. A new
src/jd2.rsaddsJd2, a Julian date split into an integerdayand a fractionalfracin[0,1)(the SOFA/hifitime convention), withnew/from_parts/add_seconds/diff_seconds/total. Differences of nearby epochs stay exact to thef64floor where a single-f64JD loses ~50 µs near J2000. Four tests anchor it: the round-trip, fraction normalisation, exact microsecond recovery (with the single-f64failure demonstrated alongside), and additive/reversible second arithmetic. - CCSDS OMM (Orbit Mean-Elements Message) writer. A new
src/omm.rscomplements theoemephemeris writer with the mean-elements message:OmmFile::from_tlemaps SGP4/TLE mean elements into the OMM units (mean motion in rev/day, angles in degrees, plusBSTAR), andto_omm_kvnserialises the standards-track CCSDS 502.0-B-2 KVN form — so a Kshana orbit can be consumed by any OMM-aware tool instead of as a bespoke TLE. Two tests anchor the TLE→OMM unit conversion (≈ 15.5 rev/day, 51.6° inclination, etc.) and the presence of the required KVN keywords. Honest scope: the KVN form and TLE mapping ship here; the XML (ndm/omm) rendering and a reference-parser round-trip are follow-ons. - Sequential-importance-resampling particle filter. A new
src/particle_filter.rsadds the nonlinear, non-Gaussian estimator behind map-aided, GPS-denied navigation (terrain-referenced or gravity-map matching):predict(propagate particles through the dynamics + Gaussian process noise),update(reweight by a per-particle measurement likelihood), systematicresample, theeffective_sample_sizedegeneracy monitor, and the weighted-mean estimate. Six tests anchor the deterministic core exactly — ESS spanning 1…N, systematic resampling picking indices in proportion to weight, the weighted-mean convex combination, a Gaussian likelihood pulling the estimate onto the measurement, resample-to-uniform behaviour, and seeded predict determinism. Honest scope: the engine ships here; the reference maps (SRTM elevation, EGM gravity anomaly) and the map measurement model are follow-ons (theionexgrid+bilinear sampler would serve a gravity/terrain map equally). - IONEX-style TEC ionosphere maps. A new
src/ionex.rsadds the measured-ionosphere alternative to the broadcast Klobuchar model: aTecGrid(a regular lat/lon grid of vertical TEC, an IGS global ionosphere map) with bilinear interpolation at a pierce point (vtec_at, clamped outside the grid) and the first-order delayΔ = 40.3·TEC/f²(vtec_to_delay_m,delay_at). Four tests anchor it:1 TECU ≈ 0.162 mat L1 with the1/f²scaling, node-exact interpolation, bilinear midpoints averaging the corners, and edge-clamped out-of-grid queries. Honest scope: the grid and interpolation ship here; parsing the IONEX file format, time interpolation between maps, and the slant mapping function are follow-ons. - Geometric time-transfer corrections (Sagnac + GNSS common-view). A new
src/timegeo.rsadds the two deterministic effects a real clock comparison must account for, complementing the stochastic two-way model intimetransfer:sagnac_correction(Δt = (ω_E/c²)·(x₁y₂ − x₂y₁), the rotating-Earth delay — tens of ns for continental baselines) andcommon_view_offset, the GNSS common-view single difference that cancels the satellite-clock error exactly and recovers the inter-station offset. Three tests anchor them on exact references: the ≈ 33 ns Sagnac of an equatorial quarter-turn, antisymmetry and the zero radial/polar cases, and the exact satellite-clock cancellation. Honest scope: a full TWSTFT transponder/hardware-delay budget and a PPP ionosphere-free time-transfer solution are follow-ons. - Orbital force model (two-body + J2). A new
src/forces.rsadds the acceleration model a numerical propagator integrates:two_body_accel(−μ·r/|r|³), thej2_acceloblateness perturbation (the ECI closed form), andgravity_accelsumming them — pair it withsrc/integrator.rsasf(t,[r;v]) = [v; a(r)]. It also exposes the analytic J2 secular rates (j2_secular_rates): the nodal regressionΩ̇, apsidal rotationω̇, and mean-anomaly driftṀ. Six tests anchor the physics on exact references:μ/r²for the two-body term, the J2 closed form at the equator (~10⁻³ of the two-body magnitude), the critical inclination (63.4349°) that freezes the perigee (ω̇ = 0), the ISS nodal regression (Ω̇ ≈ −5°/day), and the eastward drift of a retrograde sun-synchronous orbit. Honest scope: two-body + J2 only; J3–J6, drag, SRP, and third-body are follow-ons. - Shareable scenario permalinks. A new
src/permalink.rsadds a dependency-free RFC 4648 Base64 codec (standard+/alphabet with padding, and a URL-safe-_unpadded alphabet) andencode_scenario/decode_scenariowrappers, so a playground TOML can be encoded into a?s=query parameter and shared as a URL. Exposed to the browser asencode_permalink/decode_permalinkwasm bindings. Four tests anchor it on the canonical RFC 4648 vectors ("foobar"→"Zm9vYmFy", etc.), a URL-safe scenario round trip (no+///=to escape), invalid-symbol rejection, and an all-256-byte round trip. Honest scope: the codec and bindings ship here; the playground Share-button UI, the Plotly/D3 multi-series chart, and the A/B comparison mode are follow-ons. - Gauss–Newton batch least squares (the batch differential corrector). A new
src/batch_ls.rsadds the estimation core a batch orbit determination (or any parameter fit) rests on:gauss_newtonlinearises a user-supplied modelh(x)with a central finite-difference Jacobian, forms and solves the weighted normal equations(HᵀWH)·Δx = HᵀW·(z − h(x))(reusing the tested matrix inverse), and iterates to convergence with per-measurement weights. Four tests anchor it: a linear line fit reaching the exact weighted-least-squares solution, a nonlineara·exp(b·t)fit recovering the true parameters, a 3-D range-multilateration that recovers a known position from noise-free ranges (the orbit-determination flavour), and rejection of under-determined/mismatched inputs. Honest scope: this is the generic corrector engine; the orbit-specific range/range-rate/azimuth-elevation measurement model, the analytic J2 state-transition matrix, and the published-case validation are follow-ons. - RF-layer spoofing monitors (AGC power and SQM). A new
src/spoof_monitors.rsadds two independent receiver-front-end spoof detectors that complement the clock-aided time-spoof monitor inspoof: an AGC power monitor (combine_power_dbmincoherent power sum +AgcMonitor) that flags the excess received power a spoof transmitter adds beyond a configurable dB margin, and a signal-quality monitor (bpsk_autocorrtriangular code autocorrelation +SqmMonitor) that flags the Early-minus-Late correlator imbalance multipath/meaconing/replay introduces. Four tests anchor the exact closed forms (3.01 dB for a doubling of power, the10·log10(N)aggregate, the triangularR(τ)=1−|τ|, and the 10 % Early/Late alert threshold). Honest scope: the full RAIM-consistency parity spoof detector, the multi-layer fusion of the monitor outputs, and validation against published Spirent/ION GNSS+ spoofing vectors are follow-ons. - Adaptive numerical ODE integrator. A new
src/integrator.rsadds the first piece of a numerical propagator (Kshana's orbit propagation is otherwise analytic SGP4/SDP4): a generic fourth-order Runge–Kutta step (rk4_step) over any first-order systemy' = f(t, y), and an adaptive driver (integrate) that controls local error by step doubling (Richardson extrapolation) with the standard0.9·(tol/err)^(1/5)step controller and accept/reject logic. Six tests anchor it on exact solutions: they' = y → eexponential to< 1e-9, the ~16× error reduction per halved step that proves fourth-order convergence, energy/return conservation of the harmonic oscillator over a full period, and the adaptive driver meeting a tight tolerance with variable steps. Honest scope: this is the integrator core and its error control; the Dormand–Prince RK5(4)/RKF7(8) embedded tableaux and the hierarchical orbit force model (two-body + J2–J6 + drag + SRP + third-body) that make it aNumericalPropagatorare follow-ons. - Unscented (sigma-point) Kalman filter. A new
src/fusion/ukf.rsadds the scaled unscented Kalman filter (Julier & Uhlmann; Wan & van der Merwe) as a generaln-state estimator over user-supplied process and measurement functions — the sigma-point estimator a tightly-coupled GNSS/INS navigator uses when the pseudorange/Doppler model is strongly nonlinear and an EKF's Jacobian degrades. It includes the supporting dense linear algebra (Cholesky factor for the sigma-point spread, Gauss–Jordan inverse for the innovation covariance) and a Joseph-freeP⁺ = P⁻ − K S Kᵀupdate. Six tests pin it down, the key ones exploiting the exact property that for a linear model the unscented transform reproduces the Kalman filter to numerical precision (predict, update, and a full predict+update cycle all matched against a hand-run linear KF, plus a 1-D analytic Bayesian-posterior check and the Cholesky/inverse identities). Honest scope: this is the estimator engine; the 17-state tightly-coupled GNSS/INS navigator, pseudorange/Doppler measurement model, and outage-validation scenario remain follow-ons. - Dual-constellation ARAIM protection levels. A new
araim_dual_raimextends the single-fault Advanced RAIM (araim_raim) with the constellation-wide fault mode of EU ARAIM / DO-316: alongside the fault-free and per-satellite hypotheses, each constellation (labelled per satellite) contributes one hypothesis that removes all of its satellites at once, with priorP_const(a newDualFaultPriors { p_sat, p_const }). Every hypothesis adds a term to the same MHSS integrity sum, so VPL/HPL are the smallest bounds whose totalP_HMImeets the budget over fault-free + single-SV + per-constellation faults (the Bonferroni false-alert split is over allN + Chypotheses). WithP_const = 0the result is bit-for-bitaraim_raim; a single-constellation user returnsNoneagainst its own constellation fault (it cannot be excluded) — which is exactly why dual-constellation coverage matters. Four tests cover the equivalence, the protection-level widening, the single-constellation unavailability, and input validation, reusing the existing solution-separation sub-solution machinery. - IAU 2006 precession (Fukushima–Williams angles and bias-precession matrix). A new
src/precession.rsimplements the IAU 2006 (P03; Capitaine, Wallace & Chapront 2003) precession: the four Fukushima–Williams angles(γ̄, φ̄, ψ̄, ε̄_A)as polynomials in TT Julian centuries (fw_angles), and the GCRS→mean-of-date bias-precession rotation matrix built from them via the SOFAiauFw2mconstruction (precession_matrix, withgcrs_to_mod/mod_to_gcrshelpers). This is the first inertial-frame piece on top of the existing GMST-basedframesreduction. Eight tests validate against closed-form anchors — the J2000 mean obliquityε̄ = 84381.406″ = 23.4392794°, the published angle constant terms, theψ̄ ≈ 5039.998″general-precession accumulation over a century, matrix orthonormality anddet = +1, the near-identity (frame-bias-only) value at J2000, and the≈ 1.40°/century net rotation angle. Honest scope (ROADMAP.md): precession only — the IAU 2000A 678-term nutation, the full TEME→GCRS chain, and a SOFA/ANISE µas/<10 m numerical cross-check are follow-ons. - First-principles cold-atom-interferometer (CAI) accelerometer physics.
src/inertial/quantum_imu.rsmodels a three-pulse Mach–Zehnder atom interferometer from first principles instead of a datasheet: effective wavevectork_eff = 4π/λ, interferometer phaseΦ = k_eff·a·T², quantum projection (shot) noiseσ_Φ = 1/(C·√N), per-shot acceleration sensitivity, contrast decayC(t) = C₀·e^(−t/τ), and — the point —CaiAccelerometer::q_va(), which derives the white-acceleration PSD the classicalAccelModelalready consumes from the atom number, interrogation time, and contrast. The model now also covers vibration coupling — the dominant real-device term: the interferometer acceleration→phase transfer function|H(ω)| = (4/ω²)sin²(ωT/2)(accel_transfer_function), the white-PSD phase varianceσ_Φ² = k_eff²·S_a·T³/3(vibration_phase_variance_white, with a numeric band-integral cross-checkvibration_phase_variance_band), the rank-1 along-beambeam_axis_projection, andCaiAccelerometer::vibration_phase_noise/vibration_limited_accel(the latter reducing to thek_eff-independent√(S_a/(3T))floor). Eleven tests hand-verify the physics (Rb-87k_eff ≈ 1.61×10⁷,Φ(1 g) ≈ 1.58×10⁴ rad,σ_a ≈ 0.13 µg/shot shot-noise floor vs ≈ 5.9 µg vibration floor, the1/T²,1/√N, andT³scaling laws). Honest scope indocs/QUANTUM.md: this spans the projection-noise floor and the vibration-limited regime above it; laser-phase noise, Coriolis and light-shift systematics, and the PHARAO/CARIOQA validation scenarios remain follow-ons. - Quantum-CAI accelerometer wired into the inertial scenario. An accelerometer in an
inertial dead-reckoning scenario now resolves to a new
ImuKind—Classical(the existing datasheet-coefficient sensor) orQuantumCaiwhen it carries an optional[cai]block (CaiCfg: wavelength, pulse separation, atom number, contrast, cycle time, and an optional platformvibration_psd). Aquantum_caisensor's velocity-random-walk PSDq_vais derived from the interferometer physics — the shot-noise floor plus, when a vibration PSD is given, the vibration-limited contribution in quadrature — instead of a supplied coefficient, and the run's provenance records that the noise is physics-derived. Thecaifield isskip_serializing_if = "Option::is_none", so existing scenarios omit it and serialize byte-identically (the scenario hash is unchanged). Five tests cover the derivation, the quadrature vibration sum, theClassical/QuantumCaiselection, hash-stable serialization, and an end-to-end CAI-driven run. - Constellation-design optimiser and streets-of-coverage geometry.
src/walker.rsgainsoptimize_walker_design, a gradient-free grid optimiser that searches the{planes × sats × inclination}design space and returns the best Walker design under a chosenDesignObjective—MinSatellitesForCoverage,MaxCoverage, orMinWorstPdop— over the already-validated PDOP sweep (a test confirms it returns the brute-force winner). Plus the analytical streets-of-coverage closed formscoverage_half_angle_rad(λ = arccos(Re/r·cos ε) − ε) andstreet_half_width_rad(cos c = cos λ / cos(π/s), Rider/Beste), hand-verified against textbook geometry and detecting the under-population gap. The full Rider minimum-satellite global-coverage solver, a 3-D playground globe, and an external-tool DOP cross-check remain follow-ons. - SP3 precise-ephemeris export from the CLI. A propagated orbit/constellation
scenario can now be written to an SP3-c file:
kshana <orbit.toml> --export-sp3 out.sp3, orexport_sp3 = truein the scenario auto-writes<scenario>.sp3(api::export_sp3/auto_export_sp3,OrbitClockScenario::to_sp3_string, optionalepoch). A round-trip test (tests/sp3_export_roundtrip.rs) propagates the real Celestrakgps-opssnapshot, exports it, re-parses it, and confirms the recovered ECEF positions match the SGP4 truth over 24 h to < 0.5 m (well inside the 10 m TLE-grade tolerance). README documents the interoperability role (RINEX → RTKLIB/gLAB, SP3 → Ginan/precise-orbit products). - Coupled clock+position Kalman filter (cross-block covariance).
src/fusion/coupled.rsCoupledPntFilteris a single stacked[pos, vel, phase, freq]filter (Joseph-form updates) whose pseudorange measurementρ = g·pos + c·phase + noisegenuinely couples the position and clock blocks — unlike the legacy fusion pack's two independent two-state filters, which keep the cross-block covariance exactly zero. Validated: a shared pseudorange drivesP[pos,phase]non-zero; two distinct geometries jointly resolve injected position+clock offsets a single range cannot separate; a clock-only fix sharpens the position through the cross-covariance (the payoff decoupled filters cannot provide); and the Monte-Carlo NEES is χ²(4)-consistent. This is the 1-DOF realization (the fusion pack's dimensionality); the 3-D 8-state extension and wiring into the runnable pack are tracked as follow-ons. - Kalman filter-consistency health monitoring (NIS/NEES). The two-state clock
filter's covariance update is now in Joseph stabilised form `P⁺ = (I−KH)P(I−KH)ᵀ
- KRKᵀ
, which stays positive-semidefinite under extreme Q/R ratios (Cholesky-checked in CI atR=1e-26 / Q≈1e-30). A newsrc/filter_health.rsruns a Monte-Carlo consistency assessment (Bar-Shalom §5.4): pooled **NIS** (normalised innovation², target 1) and **NEES** (normalised estimation error², target 2) against 95% χ² bands, surfaced as afilter_health { nis_mean, nis_chi2_lower_95, nis_chi2_upper_95, nees_mean, nees_chi2_lower_95, nees_chi2_upper_95, consistent }block in the clock result JSON and as a green/amber card in the playground. A Q/R-mismatch sweep test proves the monitor flips to inconsistent when the process noise is mistuned by ×0.1–×10. Adds a general χ² quantile (detection::chi2_inv_cdf`, Wilson–Hilferty, table-checked).
- KRKᵀ
docs/PROVENANCE.md— one citable provenance table. Consolidates every sensor parameter (clocks, inertial, time-transfer), physical/algorithmic model (orbit, time systems, frames, iono/tropo, integrity, detection, jamming, Allan), and validation dataset (AIAA 2006-6753, Celestrakgps-ops) with its published source — datasheet, paper, ICD, or standard — and an honest maturity label (flight-qualified / ground-lab / space-goal-on-ground-hardware). Linked from the README intro and Documentation table; complements the per-runprovenancestrings that already travel in the result JSON.- Typed scenario API. Dispatch is now on a typed
ScenarioKindenum instead of a rawkindstring match (ScenarioKind::classify+ exhaustive dispatch), so adding a pack is compile-checked. New typed surfaces alongside the unchanged string-returningrun_toml:run_scenario(src) -> Result<RunOutput, KshanaError>with a structured error taxonomy (InvalidInput/NonConvergence/Unsupported/IoError, each with a stablekind_tag()); aScenariotrait andExternalPackextension point (thejammingpack is wired through it as the worked example); andlist_scenario_kinds()introspection (name, description, required/optional fields per kind). The Python and WebAssembly bindings gainlist_kinds()anderror_kind(). Documented indocs/ARCHITECTURE.md. - Real GPS constellation + operating-envelope coverage.
scenarios/orbit-sgp4-gps.tomlnow ships a real Celestrakgps-opssnapshot (2021-07-28, 30 satellites) instead of synthetic Walker TLEs, withstrict_checksum = trueso it only loads when every TLE checksum is valid;scripts/fetch_tles.shdocuments reproducible refresh and the README credits the open-data source. Newtests/scenario_coverage.rsexercises each pack across ≥5 envelope variants asserting finite/bounded output, confirms the flicker-FM floor measurably degrades a clock's coast when enabled (now set in three shipped scenarios), and confirms the fusion filter converges with a realistic non-zero accelerometer bias (within 3× the zeroed-bias case), closing the "fusion only works with zeroed biases" realism gap.docs/VALIDATION.mdgains an Operating Envelope table. - Measurement-domain GNSS simulation (
gnss-simkind). A pseudorange-level forward model: per visible satellite it synthesisesρ = geometric range + c·δt_rx − c·δt_sv + I + T + noise + multipathand the L1 Doppler, with the Klobuchar single-frequency ionosphere (IS-GPS-200 §20.3.3.5.2.5) and the Saastamoinen zenith troposphere projected by the Niell (1996) mapping function — exposed as[iono]and[tropo]TOML blocks. The residuals feed snapshot RAIM for per-epoch HPL/VPL, and agnss_measurements[]JSON array carries each SV's pseudorange, Doppler, C/N₀, and iono/tropo corrections. A zero-noise run reproduces geometry + corrections to sub-millimetre (CI test). Newsrc/gnss_sim.rsandscenarios/gnss-sim-raim.toml. - Stochastic time-spoof detector (
spoofkind). The spoof pack now runs a real detector instead of a deterministic ramp-vs-bound comparison: four injection shapes (linear_ramp,step_jump,meaconing,replay), a two-sided χ²₁ energy / Neyman–Pearson test on the clock-aided monitor statistic with the threshold set from a target false-alarm budgettarget_pfa, and the missed-detection probabilityP_mdreported both closed-form and by Monte-Carlo (mc_runstrials per hypothesis — the two agree to a few ×1/√N). The Security figure of merit is now1 − P_mdat the operationally-harmful (spec) magnitude. Newsrc/detection.rs(Gaussian tail functions, NP/energy test, Monte-Carlo P_fa/P_md) andscenarios/spoof-meaconing.toml. Backward compatible: a bare[attack] rate_ns_per_sis still accepted as a linear ramp.
Changed#
- Security FoM definition (
spoofkind): from the analytic detectability bound1 − min_detectable/thresholdto the stochastic detector's1 − P_md. The clock pack'ssecurityfield remains the faster analytic proxy.
Added (continued)#
- RF jamming model (
jammingkind). A link-budget interference model that turns a jammer's power and geometry into per-satellite loss of lock: the jammer-to-signal ratio from free-space path loss and the per-direction receive-antenna gain, the effective C/N₀ via the standard anti-jam equation (despreading processing gain × the spectral-separation factorQ; Kaplan & Hegarty §9.4), and a configurable tracking threshold, scored over a Walker constellation as anavailability_under_jammingfigure of merit. Newsrc/jamming.rsandscenarios/jamming-demo.toml. Honest scope (no multipath, terrain shadowing, AGC, or adaptive nulling) is documented indocs/CAPABILITY.md/docs/VALIDATION.md. - Generic N-D parameter sweep over any scenario kind (
sweep-nd). The previous N-D sweep was clock-pack only.sweep-ndvaries dotted TOML keys of a[base]scenario over the Cartesian product of its axes, re-dispatches each grid node through the normal run path, and reads one or more metrics out of the result by dotted JSON path — so it works for every pack (inertial, gnss-ins, integrity, spoof, …) without coupling to each pack's Rust type. Grid nodes are evaluated in parallel across OS threads on native targets (no added dependency); wasm falls back to sequential. Deterministic and row-major regardless of thread count. Newscenarios/sweep-nd-inertial.tomlexample. - TOML-configurable deterministic IMU error model in the
gnss-inspack. The three-axis strapdown error chain (scale-factor, misalignment, g-sensitivity, quantization, rate-ramp; IEEE Std 952-1997 §A.2, Groves 2013 §4.3) is now reachable per sensor from a scenario file via an optional[imu_*.error_model]block, layered on top of the constant turn-on biases. Omitting the block leaves each sensor a pure constant-bias source, so existinggnss-insruns are unchanged. This wires the previously library-only error model into a runnable pack and figure of merit.
0.10.0 - 2026-06-04#
Changed#
- Real-data validation. The multi-GNSS RINEX navigation parser, the GLONASS
RK4 propagator, and the SP3 reader are now exercised against genuine IGS/DLR
files (a real RINEX 3 mixed broadcast nav file and an IGS SP3-c orbit product),
not only self-authored samples — asserting non-empty satellite sets and finite,
physically-sized ECEF positions. The fixtures are test-only (excluded from the
published crate); see
tests/fixtures/igs/NOTICE. - RAIM on real reference-orbit geometry. The snapshot, solution-separation
(MHSS), and ARAIM protection-level cores are now validated against the real IGS
precise-orbit (SP3) geometry, not synthetic constellations alone: the line-of-sight
geometry is built from the first SP3 epoch at a real ground station, and the tests
assert metre-level, APV-I-available protection levels, that a 60 m pseudorange bias
trips the χ² monitor, that solution separation identifies the faulted satellite,
and that ARAIM's levels meet the allocated
P_HMI. Closes the validated-on-synthetic-geometry-only gap (receiver-domain gLAB parity over a full RINEX arc remains a roadmap item — it needs a pseudorange solution).
Added#
- Per-node confidence intervals for the N-D parameter sweep (
sweep::nd_sweep_ensemble). Each grid node of the N-dimensional Cartesian-product sweep can now be evaluated as a Monte-Carlo ensemble of seeds, reporting the metric's mean, percentiles, and a percentile-bootstrap 95% CI per node (for both clocks) — a statistically honest sweep rather than one draw per node. Reuses the ensemble/bootstrap machinery (metric_stat); deterministic;runs = 1reduces exactly to the single-seednd_sweep. (Generalising the sweep across all packs, entangled with the typed-Scenario refactor, and parallel execution remain.) - NaveGo cross-validation of the IMU-noise pipeline (
tests/navego_imu_crossval.rs). An external cross-check against NaveGo (R. Gonzalez's open-source INS/GNSS toolbox): reproduces the synthetic round-trip ofnavego_example_allan.mon its published Microstrain 3DM-GX3-35 reference profile, confirming our overlapping-ADEV estimator recovers NaveGo's velocity- and angle-random-walk coefficients (ADEV(1 s) = σ·√dt) to under 5% with the expected −1/2 white-noise slope. (The 40 MB recorded STIM300.matlog is not ingested — binary-format-gated.) - Tightly-coupled (pseudorange) GNSS/INS update.
GnssInsEkf::update_tightly_coupled(and theClosedLoopInsGnss::fuse_tightly_coupledwrapper) implement the previously-stubbed range-domain measurement: the innovation is the predicted range from the INS position to each satellite versus the measured pseudorange, with a line-of-sight Jacobian on the position error. Because each satellite is a scalar measurement, the filter keeps correcting with fewer than four satellites — where a loosely-coupled PVT fix does not exist. Five tests cover four-satellite nulling, two-satellite correction (no PVT possible), single- satellite along-line-of-sight observability, and input validation. Pseudorange- only; carrier phase and an explicit receiver-clock state remain roadmap. The unusedtight_couplingcargo feature (which gated the old error stub) is removed. - Loosely-coupled GNSS/INS scenario pack (
kind = "gnss-ins",src/fusion/pack.rs). Wires the three-axis strapdown navigator and the 15-state error-state EKF (closed_loop/gnss_ins_ekf) into a runnable scenario with a figure of merit — the EKF disciplines the mechanization against noisy GNSS fixes while coverage is up, then coasts through the outage, replacing the legacy 1-DOF scalar pack's truth-snap reset with genuine fusion. The result reports the fused horizontal error series, the scored position FoM (availability / outage RMS / holdover), and the open-loop free-INS RMS for comparison; a quantum/classical IMU pair differs only in true bias. Dispatched from the CLI/Python/wasm entry point with ascenarios/gnss-ins.tomlexample. Honest framing: loosely-coupled only, one deterministic trajectory, and the fused outage error is floor-limited by the hand-over attitude error (so it is not claimed to scale with bias) — the robust findings are that fusion beats unaided dead-reckoning for a biased sensor and that a lower-bias sensor has the better unaided coast. - Constellation design on the validated SGP4 core (
src/walker.rs). A newwalkermodule emits a designed Walker-delta pattern (i: T/P/F) as SGP4 mean elements, so the synthetic constellation propagates through the same SGP4 path validated to 4.12 mm against the AIAA 2006-6753 vectors — not the analytic Keplerian generator. On top of it:pdop_sweeptabulates coverage and median/worst PDOP over a{planes × sats × inclination}design grid, andcoverage_revisitreports the coverage fraction and revisit gaps (worst/mean) at a ground point. Validated by the physical monotonicities a trade must obey (more satellites ⇒ higher coverage, lower PDOP, shorter revisit). Separately, a genuine Celestrakgps-opsTLE snapshot (2021-07-28, 30 operational GPS satellites) is added as a test-only fixture and the real-TLE → SGP4 → ECEF geometry path validated against it (full MEO shell within 1%, nine-satellite all-in-view at PDOP 1.64), alongside the existing SP3 and RINEX real-data paths. - Noise-type-specific effective degrees of freedom for the Allan confidence
intervals.
allan::edf_overlapping_adevimplements the NIST SP 1065 Table 5 closed forms (the Stable32 simple set) for all five canonical power-law noise types — white/flicker PM, white/flicker FM, random-walk FM — replacing the conservative non-overlapping count as the χ² degrees of freedom. A newPowerLawNoiseenum andclassify_power_lawidentify the dominant type from the record's modified Allan-deviation slope (MDEV separates white from flicker PM where ADEV cannot), andoverlapping_adev_curvenow attaches the identified noise type, its edf, and a 95% confidence band to every point of the exported ADEV curve (AdevPointgainsnoise/edf/ci_lo/ci_hi, additive with serde defaults). Validated two ways: the five formulas match hand-evaluated values to 1e-12, and a 4 000-record Monte-Carlo white-FM ensemble confirms the formula predicts the estimator's actual chi-squared edf within 20% (and that it materially beats the conservative count). Eight new tests. - Two-way time-transfer stochastic model.
timetransfer::TwoWayLinkreplaces the white-only sampler with a physically-grounded model: the reciprocal (common-mode) path delay cancels in the(m_AB - m_BA)/2estimate (two_way_offset_estimate, so two independent one-way measurements average to1/sqrt(2)), and the residual is the non-reciprocal differential delay — modelled as a colored white-FM + random-walk-FM process (the validatedClockModel), giving the synchronization-error series a realistic Allan signature (sigma_y^2(tau) = q_rw*tau/3) instead of flat white noise.LinkCfggainsq_wf_s/q_rw_s(serde default 0 ⇒ the legacy white-only behaviour, bit-for-bit), the link FoM reportsadev_tau0(the model's Allan deviation at the base step), and thetimetransferscenario/CLI surface it. Golden FoM re-pinned. Six hand-derived tests (common-mode cancellation, the sqrt(2) two-way gain, the RWFMtau/3law via the link's ownstep(), legacy-equivalence atq=0, determinism, and end-to-end FoM exposure). - Stable32 numeric parity for the Allan-family estimators (NBS14).
tests/allan_reference.rsvalidates the overlapping ADEV, modified ADEV, time deviation, and overlapping Hadamard estimators against the Stable32 reference deviations for the canonical NBS14 dataset (W. J. Riley, Handbook of Frequency Stability Analysis, NIST SP 1065, ~p.107) at tau = 1, 2 to a 1e-4 relative tolerance — actual agreement ~1e-6. This pins the estimator mathematics against the de-facto reference implementation, not just against the estimators' own analytic self-consistency. Only the public reference numbers are used; no third-party code. - Vertical Stanford integrity diagram exported by the
integrityscenario. The runnableintegrityscenario kind now exports a vertical Stanford(-ESA) diagram alongside the HPL/VPL availability map: at each protected epoch a seeded, reproducible no-fault range-error draw is mapped through the geometry to an actual vertical position error and classified against the VPL and the vertical alert limit (Available / System-Unavailable / Misleading / Hazardously-Misleading). The diagram (per-epoch points + region counts) is carried in the result JSON and the integrity-event / HMI counts in the CLI summary, so the Stanford classifier — previously library-only — is reachable end-to-end.IntegrityScenariogains aseedfield (default 0) controlling the error realization; the availability map itself remains geometry-only and seed independent. - ARAIM integrity-risk (P_HMI) budget for the protection levels.
raim::araim_raimderives the horizontal and vertical protection levels from an explicit integrity-risk budget rather than a fixedK_mdmultiplier: for the all-in-view solution and every single-satellite exclusion sub-solution it builds the per-mode(prior, detection threshold, σ)on each axis, thenaraim_protection_levelsolves the smallest PL whose summed probability of hazardously-misleading information (araim_integrity_risk,P_HMI = Σ_k p_fault,k · Q((PL − T_k)/σ_k), Blanch et al. Baseline ARAIM) meets the allocatedP_HMI. The result reports the integrity risk the levels actually achieve, so a user can trade integrity against the alert limit explicitly. Six hand-derived tests (fault-free and thresholded single-mode closed forms, multi-mode summation/monotonicity, end-to-end fault-free protection with a 10⁵× tighter budget raising the PL, fault detection/identification, and the six-satellite redundancy floor). Single-fault MHSS is the ARAIM baseline; simultaneous multi-SV-subset faults, the constellation-wide fault mode, and gLAB reference-dataset validation are documented extensions. - Two-speed coning/sculling compensation for the strapdown mechanization.
inertial::mechanization::coning_sculling_compensatefolds the high-rate coning (attitude) and rotation+sculling (velocity) terms out of a coarse update's ordered sub-interval IMU increments, so a moderate-rateNavState::step_incrementsreproduces vibration-rectified motion a coarse step over the raw sums misses. A validation test drives a 10 Hz coning+sculling environment for 60 s and compares fine-rate truth, naive coarse integration, and the folded coarse integration: the fold cuts the position error by ~18× (metres of naive drift → sub-decimetre), confirming the coning/sculling terms are load-bearing. AScalarErrorBudgettype alias names the legacy 1-DOFAccelModelfor what it is, distinct from the three-axisNavStatenavigator. - RINEX observation-file parser. New
rinex_obsmodule reads the RINEX 3.0x / 4.00 observation file — the receiver's actual measurements — completing the RINEX pair alongside the existing navigation-message parser.parse_obsdecodes the header (version/type, the per-systemSYS / # / OBS TYPESlists with continuation lines, approximate position, interval, time of first observation) and each epoch's per-satellite records: pseudorange, carrier phase, Doppler, and signal strength, keyed by their RINEX 3 observation code (C1C,L1C, …) with the loss-of-lock (LLI) and signal-strength (SSI) flags, a blank field preserved as absent rather than zero. Honest scope: this is the standards-format ingest (a real RTKLIB/gLAB/IGS-station observation log in, typed measurements out), not a positioning engine — no pseudorange solution, PPP, or RTK here. - CCSDS OEM (Orbit Ephemeris Message) writer. New
oemmodule exports a propagated constellation as a valid CCSDS 502.0-B OEM 2.0 message — the KVN ephemeris format GMAT, Orekit, STK, and most flight-dynamics tools ingest.OemFile::from_propagatorssamples each satellite's inertial (TEME) state — position and velocity, taken straight from the propagator with no Earth-fixed rotation, unlike the SP3 export — onto a time grid, andOemFile::to_oem_stringserialises theCCSDS_OEM_VERS/CREATION_DATE/ORIGINATORheader plus oneMETA_START … META_STOPsegment per satellite (OBJECT_NAME/OBJECT_ID/CENTER_NAME/REF_FRAME = TEME/TIME_SYSTEM = GPS/START_TIME/STOP_TIME) followed by itsepoch X Y Z X_DOT Y_DOT Z_DOTlines (km, km/s). TheCREATION_DATEis caller-supplied, never wall-clock, so output is byte-identical across runs (the reproducibility contract). This is the spacecraft-ephemeris counterpart to the GNSS SP3 export: a Kshana orbit can now be handed to a flight-dynamics tool in a standard format. - SP3 precise ephemeris as a propagation source.
Sp3File::interpolatorbuilds a per-satelliteSp3Interpolatorthat fills the position between the tabulated SP3 epochs with a 9th-order Lagrange polynomial (standard IGS practice) and rotates it into the shared TEME frame, exposed asPropagator::Sp3Precise. An IGS/analysis-centre precise-orbit file can now drive the same geometry/visibility/integrity pipeline as the broadcast and analytic propagators. Validated round-trip: a Kepler orbit written to SP3 and re-read through the interpolator matches the original to sub-metre at the nodes and < 100 m mid-interval. Clock interpolation is next. - GLONASS broadcast ephemeris (completes multi-GNSS RINEX nav). New
glonassmodule: GLONASS doesn't broadcast Keplerian elements but a PZ-90 Earth-fixed state vector (position, velocity, luni-solar acceleration).parse_glonass_navreads the RINEX 3Rrecords, and the satellite position at any time is obtained by 4th-order Runge–Kutta integration of the GLONASS ICD equations of motion (central gravity +J2+ Earth-rotation Coriolis/centrifugal terms + the broadcast acceleration). Exposed asPropagator::Glonass, so GLONASS satellites flow through the constellation/visibility/integrity pipeline alongside the Keplerian systems; a singlerinexconstellation block can now mix GPS, Galileo, QZSS, BeiDou, and GLONASS. - Multi-GNSS RINEX navigation (GPS, Galileo, QZSS, BeiDou). The RINEX 3
navigation parser now decodes Galileo (
E), QZSS (J), and BeiDou (C, MEO/IGSO) records alongside GPS (G) — they share the Keplerian layout and user algorithm — each evaluated with its own gravitational constant and Earth-rotation rate (Galileo/BeiDou μ, BeiDou Ω̇ₑ). A mixed-constellation file yields all of them, flowing through the constellation/visibility/integrity pipeline asPropagator::Rinex. BeiDou geostationary satellites use a different coordinate rotation and are skipped pending a reference fixture to validate against. The record walker uses per-system line counts, fixing a latent bug where four-line GLONASS/SBAS records were skipped as if eight lines long. GLONASS (a state-vector model) is next. - SP3-c/d precise-ephemeris reader and writer. New
sp3module parses IGS/analysis-centre SP3 precise orbit files (parse_sp3) — the post-processed ECEF position/clock product that PPP engines (Ginan, RTKLIB, gLAB) treat as reference — into a structuredSp3File(header, epoch grid, per-satellite position km→m, clock µs, and velocity dm/s→m/s forVproducts), preserving the SP3 bad-value sentinels. The reverse direction is also covered:Sp3File::from_propagatorsbuilds an SP3 from a propagated constellation (TEME→ECEF per epoch) andto_sp3_stringserialises it, so Kshana orbits can be exported in the format external PPP tools ingest — the read↔write round trip. Epoch interpolation and an SP3 propagator source are next. - RINEX broadcast ephemeris as a runnable constellation source. A
constellation now accepts an inline
rinexblock (RINEX 3 GPS navigation text) alongside the existingtleoption, so a real broadcast file drives a scenario end-to-end from the CLI, Python, or the in-browser playground — RINEX in, PNT geometry out. Newscenarios/orbit-rinex.tomldemonstrates GNSS availability and DOP from eight GPS satellites built straight from broadcast records. (GPS LNAV only; multi-GNSS and SP3 are next.) - RINEX broadcast ephemeris as a propagation source. A parsed
RinexEphemerisnow converts to anorbit::Propagator(Propagator::Rinex): position is the IS-GPS-200 broadcast orbit rotated from ECEF into the shared TEME inertial frame (sv_position_teme, with leap-second-correct GPS→UT1 time), velocity by central finite difference, and the Keplerian orbital period. Real GPS broadcast data can now drive the same geometry, visibility, and integrity (RAIM) pipeline as the analytic SGP4/Keplerian propagators. (Not yet exposed as a RINEX-file scenario kind — that and multi-GNSS/SP3 are next.)
0.9.2 - 2026-06-03#
Added#
- Archival DOI. Releases are now deposited to Zenodo and assigned a citable DOI.
0.9.1 - 2026-06-03#
Changed#
- Documentation. Refreshed the README to institutional grade: a Capabilities overview of the full v0.9.0 stack, a Versioning & releases section, a clean header with the new mark, and a concise status line. No engine changes.
0.9.0 - 2026-06-03#
This release adds three substantial capability areas on top of the 0.8.0 SGP4
substrate: a genuine three-axis strapdown INS, a loosely-coupled GNSS/INS
error-state EKF with closed-loop feedback, real snapshot and solution-separation
(ARAIM-style) RAIM with HPL/VPL and a runnable integrity scenario, and the first
step of GNSS-format interoperability (RINEX-3 GPS ephemeris ingestion).
Added#
- RINEX 3 GPS navigation-message parser (
src/rinex.rs). First step toward GNSS-format interoperability:parse_navreads a RINEX 3.x navigation file and decodes each GPS (G) broadcast-ephemeris record — the eight-line SV/epoch +BROADCAST ORBITblock — into aRinexEphemerisof Keplerian elements and clock corrections, with field names and units per IS-GPS-200. Handles the FortranD-exponent float format (parse_d) and fixed-width column layout; records for non-GPS systems are skipped, not rejected, so a mixed file still yields its GPS ephemerides. Four tests:D-exponent parsing (including blanks and errors), a full record decoded against known field values with a GPS semi-major-axis sanity check (√A² ≈ 26 560 km), non-GPS skipping, and the empty-file case. - GPS broadcast-ephemeris → ECEF position (
src/rinex.rs).RinexEphemeris::sv_position_ecef(t_tow)evaluates the satellite's Earth-fixed position from the parsed ephemeris via the IS-GPS-200 §20.3.3.4.3.1 user algorithm: Newton solution of Kepler's equation for the eccentric anomaly, the second-harmonic argument-of-latitude / radius / inclination corrections, and the rotation into ECEF accounting for Earth rotation since the reference epoch (with the GPSμandΩ̇ₑmandated by the spec, and a week-rollovertkfold). Three tests: the geocentric radius stays in the GPS band (≈ 26 560 km), the Earth-fixed speed is ~3.9 km/s, and evaluating a full week away reproduces the same position. - GPS SV clock bias with the relativistic correction (
src/rinex.rs).RinexEphemeris::sv_clock_bias_s(t_tow)evaluates the broadcast clock polynomialaf0 + af1·Δt + af2·Δt²aboutTocplus the relativistic eccentricity termF·e·√A·sin Ek(IS-GPS-200 §20.3.3.3.3.1). A newEpochUtc::gps_time_of_weekconverts the record's calendar epoch to GPS time-of-week via Julian-day arithmetic from the GPS epoch (1980-01-06), and the Kepler solve is shared with the position evaluation. Tests: GPS time-of-week for a Sunday/Tuesday/Saturday (week boundaries), and the clock bias being af0-dominated with a present, bounded relativistic term. The L1 group-delayTGDis exposed but deliberately not folded in. Honest scope: aPropagatorsource, Galileo/BeiDou/GLONASS, and SP3 remain next steps. (docs/CAPABILITY.mdupdated to match.) - User-runnable
integrityscenario kind (scenarios/integrity-raim.toml). The RAIM availability capability is now reachable from the CLI/TOML like every other pack:kind = "integrity"parses anIntegrityScenario(user orbit, one or more GNSS constellations, elevation mask, and the(sigma, P_fa, P_md, AL_H, AL_V)integrity config), runsconstellation_raim_availability, and emits the per-epoch HPL/VPL availability map as JSON plus a self-contained SVG — protection levels over time against the alert limits, with a green/red availability strip.kshana scenarios/integrity-raim.tomlwrites the JSON, the chart, and an HTML report. The bundled scenario (24-satellite Walker, 1 m dual-frequency ranging, APV-I limits) reports ~95 % availability; it documents that single-frequency RAIM does not meet the vertical APV-I limit on one constellation, which is why vertical guidance uses SBAS/dual-frequency/ARAIM. Tests cover the dispatch, the availability-map JSON, and the SVG. - Runnable RAIM availability over a constellation (
src/raim.rs). The integrity module had no caller —constellation_raim_availabilitymakes it a genuine end-to-end entry point: at each epoch on a time grid it propagates the visible satellites (the same SGP4/KeplerianPropagators the engine uses), computes the no-fault protection levels, and judges availability against the horizontal/vertical alert limits, returning aserde-serializableRaimAvailabilityReport(per-epochn_visible/HPL/VPL/availableplus the availability fraction). ARaimConfigbundles(sigma, P_fa, P_md, AL_H, AL_V)and the per-epochraim_availability_epochis exposed for callers that resolve their own geometry. Three tests: an epoch judged available under APV-I limits on a ten-satellite geometry, made unavailable by an impossibly tight limit, andNonelevels below five satellites; and an end-to-end run over a 24-satellite Walker constellation that yields a finite availability map and serializes. (Six satellites — the residual-RAIM redundancy floor — honestly do not meet APV-I even at 1 m ranging; APV-I availability needs the denser geometry the test uses.) - Stanford(-ESA) integrity diagram accumulator (
src/raim.rs). The standard way to summarise an integrity monitor over many epochs: it plots actual position error (x) against protection level (y) and classifies each epoch, by the diagonaly = xand the alert limit, intoAvailable(PL bounds error, within AL),SystemUnavailable(PL bounds error but exceeds AL — safe, unusable),MisleadingInformation(PL < error ≤ AL), orHazardouslyMisleadingInformation(PL < error and error > AL — the unsafe failure).classify_stanfordis the pure classifier;StanfordDiagramaccumulates(error, PL)points against a fixed alert limit, exposing region counts, availability, integrity-event totals, andserde-serializable points for plotting/JSON export. Four tests: every region (including theerror == PLbounded boundary), count/availability accumulation, and JSON round-trip. This is the reporting surface for the RAIM protection levels; wiring it into the constellation scenario and validating against a public dataset remain roadmap items. - Solution-separation (ARAIM-style) RAIM (
src/raim.rs). A multiple-hypothesis integrity monitor alongside the existing residual/parity chi-squaredsnapshot_raim. For the all-in-view least-squares solution and every single-satellite exclusion sub-solution, it forms the separationΔ_k = x_k − x₀— zero-mean Gaussian under no fault with covarianceCov(x_k) − Cov(x₀)(the nested-estimator identity, valid because the all-in-view solution is BLUE) — and so it both detects a fault and identifies the faulted satellite (the one whose exclusion gives the largest normalized separation). Horizontal/vertical protection levels follow the standard MHSS allocationPL = max(K_md·σ₀, max_k[K_fa·σ_ss,k + K_md·σ_k]), withK_fa = Φ⁻¹(1−P_fa/2),K_md = Φ⁻¹(1−P_md). New dependency-freenormal_cdf/normal_quantilebuilt from the module's existing regularized incomplete gamma (erf(x) = P(½,x²)). Four hand-derived tests: normal CDF / quantile against textbook values (Φ(1.95996)=0.975, the 1e-7 tail = 5.1993, symmetry); a fault-free geometry that does not alarm and yields finite, positive HPL/VPL; a 60-σ single-satellite bias that is detected and correctly identified (excluded_sv == 2); and the six-satellite redundancy floor. Closes the audit's "tautological integrity — no real RAIM/HPL/VPL" P0 gap on the algorithm side; gLAB-dataset validation and the Stanford-diagram accumulator remain roadmap items. - Closed-loop GNSS/INS integration (
src/fusion/closed_loop.rs).ClosedLoopInsGnsswires the error-state EKF kernel to the three-axis strapdown mechanization: each IMU sample is corrected by the running bias estimates, mechanized forward, and the EKF covariance time-propagated with the matching navigation context; each GNSS position/velocity fix forms the INS−GNSS innovation and feeds the estimated position, velocity, attitude error (ψ, as a quaternion rotation) and accelerometer/gyro biases back into the solution, resetting the error-state mean. Feeding the attitude back (not only the biases) is required for stability — the tilt and accelerometer bias are a coupled pair, so correcting one without the other diverges. INS and GNSS are compared in a local tangent-plane NED frame using the mechanization's own radii of curvature (newmechanization::radii_of_curvature;NavState::omega_ie_n/omega_en_nexposed). This is the honest replacement for the hybrid pack's truth-snap reset. Three tests: a closed loop nulling an injected 8 m / −5 m position error to <0.1 m; an aided solution staying metre-bounded (<6 m) on a driving trajectory while a free-running INS diverges past 100 m; and the milestone benchmark — the fused solution's Monte-Carlo position RMS over a 60 s GNSS outage beats an unaided open-loop dead-reckoner by >2× (≈4× across seeds). Honest limitation documented in the module: in loosely-coupled mode the accel bias and tilt are only weakly separable (both couple through gravity), so the delivered value is the bounded, corrected state and a clean outage-entry — not a precise inertial calibration; richer dynamics and the tightly-coupled extension remain roadmap items. - Loosely-coupled GNSS/INS error-state EKF kernel (
src/fusion/gnss_ins_ekf.rs). A 15-state error-state extended Kalman filter —δx = [δp, δv, ψ, b_a, b_g]— with the strapdown error dynamics from Groves 2013 §14.2 (specific-force/tilt coupling, Coriolis, body→nav bias projection, Gauss–Markov bias models), a first-order discrete transitionΦ = I + F·dt, and a loosely-coupled position+velocity measurement update (H = [I₃ 0 0 0 0; 0 I₃ 0 0 0]) in Joseph form. Dependency-free dense linear algebra (Gauss–Jordan inverse, Joseph covariance update). Atight_couplingcargo feature gates a documented, not-yet-implemented pseudorange/Doppler update. 7 tests with hand-derived expectations: the skew/cross-product identity, a verified 3×3 inverse, covariance staying symmetric/PSD under prediction (and position uncertainty growing un-aided), a position fix shrinking the position covariance, exact recovery of a known position error at the analytic Kalman gainP/(P+R), and smaller corrections under larger measurement noise. This is the kernel that will replace the hybrid pack's open-loop truth-snap reset with closed-loop feedback (pack wiring + NaveGo validation to follow). - Deterministic IMU error model for the 3-axis strapdown navigator (
src/inertial/imu_errors.rs).ImuErrorModeldistorts a true body-frame(ω, f)pair into a measured one through five systematic categories (IEEE Std 952-1997 §A.2; Groves 2013 §4.3, Table 4.1): scale-factor (per-axis ppm gain error), misalignment / cross-coupling (off-diagonal triad non-orthogonality), g-sensitivity (a gyro rate bias proportional to specific force), quantization (rounding to the output LSB), and rate-ramp (a linear-in-time drift — the third Allan region), plus a constant turn-on bias. Every term defaults to zero, soImuErrorModel::ideal()is a transparent pass-through and existing scenarios are unaffected. Each error source has an isolation test (scale linear to <0.01%, misalignment cross-axis above the VRW floor, g-sensitivity bias, LSB grid, linear ramp), and an end-to-end test drives a navigation error through the mechanization from a distorted IMU. Not modelled: vibration rectification error, temperature-gradient drift. (The shippedinertialscenario pack still runs the legacy 1-DOF scalar budget; this model feeds the 3-axis library.) - Coning and sculling compensation for the strapdown integrator. The
attitude path adds the two-sample
coning_increment(½ Δθ_prev × Δθ_cur); a coarse-rate (30 Hz, 5-samples/cycle) integration of a 5 Hz coning environment is verified to track fine-rate truth ≥ 3× better with the correction than naive increment-summing. The velocity path addssculling_increment(½ Δθ × Δv, Groves eq. 5.82) and resolves the body velocity increment through a newNavState::step_incrementsincrement-based update using the body-relative rotationΔθ_rel = Δθ_b − C_n^b ζ, so an Earth-fixed platform incurs no spurious sculling while a genuine vibration triggers the full term. - Full three-axis strapdown mechanization in the NED frame (
src/inertial/mechanization.rs).NavState { q, v_ned, p_llh }is advanced bystep(gyro_b, accel_f_b, dt)using the standard terrestrial-frame NED equations (Groves §5.4): body→NED attitude corrected for the inertial-to-nav rateω_in = ω_ie + ω_en(Earth rotation + transport rate); specific force resolved body→NED through the DCM; velocity integratingv̇ = f_n − (2 ω_ie + ω_en) × v + g_n(Coriolis/transport + gravity); and geodetic position via the meridian/transverse radii of curvature. Gravity is the WGS-84 closed-form Somigliana normal (plumb-bob) gravity with a NIMA free-air altitude correction — never a hard-coded constant. This is the genuine three-axis navigator that supersedes the 1-DOF scalar error-budget path. Verified by physical invariants: a platform bolted to the rotating Earth at 45°N (sensing Earth rate + 1 g) stays within 1 mm over 60 s; a level north specific force givesv_N ≈ a·tand½ a t²displacement; normal gravity matches the known equator/pole/45° surface values and the free-air lapse rate. - Three-axis attitude representation for strapdown INS (
src/inertial/attitude.rs). A unit-quaternionQuaterniontype (scalar-first, Hamilton convention) carrying body→nav rotation, with a DCM view (to_dcm/from_dcmvia Shepperd's method), Hamilton product, axis-angle and rotation-vector (exact exp-map) constructors, and quaternion kinematics — both a first-order RK rate update (q̇ = ½ q ⊗ ω) and a coning-corrected rotation-vector update. The two-sampleconing_increment(Savage / Bryan–Lewantowski,½ Δθ_prev × Δθ_cur) supplies the rotation-rate cross-coupling term that scalar dead-reckoning omits. This is the attitude foundation for the full 3-axis mechanization that replaces the legacy 1-DOF error-budget path. Verified against closed-form rotations: constant-rate propagation matches the axis-angle quaternion to 1e-6, DCMs are orthonormal with unit determinant, and coning vanishes for single-axis motion. (src/inertial.rsis now thesrc/inertial/module directory; the public pathcrate::inertialis unchanged.) - Geodetically-correct ground-station visibility (
src/frames.rs).elevation,is_visible, andvisible_countcompute a satellite's elevation above a ground station's horizon against the WGS-84 ellipsoid normal (the true local vertical), not the geocentric radial — the two differ by up to the ~0.19° geodetic deflection, enough to flip near-horizon satellites in or out of an elevation mask. Verified end-to-end (a Walker constellation propagated, rotated TEME→ECEF, and counted from a geodetic site) and against the geocentric approximation.
Changed#
- CI reliability. The
test-python-bindingsjob now builds the wheel withPyO3/maturin-action(manylinux container) instead of a raw hostmaturin build, eliminating an intermittentrustfmt-preview/cargo-fmtrustup conflict on the runner image. Thedenyjob installscargo-denyas a prebuilt binary viataiki-e/install-actioninstead of the Docker-basedcargo-deny-action, removing a Docker Hub registry-pull timeout. Neither change affects the checks performed.
0.8.0 - 2026-06-02#
Added#
Inertial velocity is exposed downstream.
Propagator::velocity_eciandPropagator::state_eci(returningStateEci { r_m, v_m_s }) thread the analytic TEME velocity SGP4 already computes — previously discarded — through to callers in m/s;Orbit::velocity_ecigives the Keplerian path a consistent velocity. The AIAA 2006-6753 verification test now also checks velocity for every reference row (worst velocity error 1.85e-9 km/s across all 666 states) and pins the compared row count at exactly 666.Stricter, panic-free TLE parsing. Lines are required to be ASCII and are sliced safely (no more byte-index panics on multi-byte input); elements are range-checked (inclination, eccentricity, mean motion); the column-69 checksum can be enforced via
ParseOpts { strict_checksum }/parse_propagators_optsand the newstrict_checksumflag onConstellationCfg(lenient by default).Allan-deviation curve in the output. Each clock run now reports an
adev_curve([{tau_s, adev, n_samples}]) and the browser playground renders a log-log "Clock stability (ADEV)" chart.Time-grid input validation.
TimeCfg::validaterejects a non-finite, zero, negative, or oversized time grid (a step larger than the duration, or more thanMAX_TIME_STEPSsamples) before any allocation, so a malformed scenario returns an error instead of panicking or exhausting memory.Monte Carlo ensembles for the inertial pack.
runs = Non an inertial scenario runs N seeds and reports each metric's mean, standard deviation, percentiles, and a percentile-bootstrap 95% confidence interval (ensemble). Every inertial run now carries amonte_carloflag, so a single-realisation FoM is no longer mistaken for a distribution. (CEP/2DRMS are intentionally not reported — they require the 3-axis model on the roadmap.)Guided playground mode. The browser playground no longer drops you onto a raw TOML wall: a "Start here" strip of one-click scenario cards loads and runs a worked example, sliders expose the universal knobs (seed, timing threshold) without touching the TOML, a "How to read this" note explains the result, and the full TOML is one collapsible away. Every run is shareable — Copy share link encodes the whole scenario into the URL fragment (nothing is uploaded) so a link reproduces the exact run on load. The codec is unit-tested (
web/share.test.mjs, run in CI).N-dimensional parameter sweeps (
src/sweep.rs).nd_sweepevaluates a metric over the full Cartesian product of severalSweepAxisranges (the multi-parameter trade study), in row-major order, deterministically. Additive — the existing 1-D sweep API is unchanged. Per-node bootstrap confidence intervals and generalisation beyond the clock pack remain on the roadmap.Real snapshot RAIM (
src/raim.rs). Genuine position-domain Receiver Autonomous Integrity Monitoring: it builds the line-of-sight geometry to the visible satellites, forms the least-squares solution and residuals, runs a χ² residual fault-detection test (exact threshold from a dependency-free incomplete-gamma χ²/non-central-χ²), and computes slope-based horizontal and vertical protection levels (HPL/VPL) with the missed-detection bias derived for the configured P_fa/P_md. This is distinct from — and is not yet wired into — the scenario pipeline's filter-self-consistency Integrity figure; fault exclusion, alert-limit/P_HMI budgeting, and ARAIM remain on the roadmap.Frequency-stability suite: MDEV, TDEV, HDEV + confidence intervals (
src/allan.rs). Alongside the overlapping ADEV: the modified Allan deviation (separates white from flicker phase noise), the time deviation (TDEV = tau/sqrt(3) * MDEV), and the Hadamard deviation (rejects linear frequency drift exactly and converges for divergent red-noise types). χ²-based confidence intervals (deviation_ci) use a dependency-free normal/χ² quantile pair (Acklam + Wilson-Hilferty) with a conservative non-overlapping edf; noise-type-specific edf and Stable32 numeric parity remain on the roadmap.Reference-frame reduction (
src/frames.rs). GMST-based TEME↔ECEF rotation (using the same IAU-1982 sidereal time as the propagator), exact WGS-84 geodetic↔ECEF with a Bowring-seeded iterative inverse (machine-precision at all altitudes, including MEO/GEO), and a geodetic ground-station observer that returns azimuth / elevation / range in the local East-North-Up frame. Polar motion and sub-arcsecond nutation are not applied (GMST-only, sub-kilometre on the ground track); an ITRF-precise CIO chain is on the roadmap.Time-scale foundation (
src/timescales.rs). A dependency-free Julian-date API (Gregorian civil ↔ JD, MJD), the full IERS integer leap-second history (UTC↔TAI, 10 s in 1972 to 37 s since 2017), the defined TAI→TT offset, the UT1 correction via a supplied DUT1, and the IAU-2000 Earth Rotation Angle. This is the time substrate that Earth-fixed frame reduction (planned) sits on. Instants are single-f64Julian Dates (~50 µs resolution near the present epoch; a two-part JD is on the roadmap), and the pre-1972 rubber-second era is not modelled — both documented in the module.Reproducibility & provenance. A deterministic CycloneDX SBOM generator (
scripts/gen-sbom.sh) and a SLSA build-provenance attestation on the release binary and SBOM; the release toolchain is pinned to match CI. Determinism guarantees, the cross-platformlibmcaveat, and the golden-pinning approach are documented indocs/REPRODUCIBILITY.md.Property-based and fuzz tests (
tests/property.rs). Deterministic randomized tests (no new dependency) assert invariants over thousands of inputs: the TLE and scenario parsers never panic on garbage, non-ASCII, mutated, or truncated input;TimeCfg::validatenever panics on NaN/inf/negative grids; the TLE checksum is consistent and column-69-only; geodetic↔ECEF round-trips and the TEME→ECEF rotation preserves norm across the globe and a wide altitude band.
Changed#
- Golden tests now pin the figures of merit field-by-field for the four
reference scenarios (with a tolerance that absorbs cross-platform
libmjitter), replacing the earlier inequality-only checks, so a silent numerical regression is caught immediately. schema_versionin result artifacts bumped from0.1to0.7(it was frozen while the engine moved on).cargo-denynow denies (not warns on) yanked dependencies.- New docs:
CAPABILITY.md(honest scope map),SCHEMA.md(result-field reference),INTEGRITY.md,QUANTUM-MODELS.md,REAL_TLE_GUIDE.md. A CI guard fails if the README version badge drifts fromCargo.toml.
0.7.0 - 2026-06-02#
Added#
- SGP4/SDP4 orbit propagation. A full, dependency-free implementation of the standard simplified-perturbations propagator — near-Earth SGP4 together with the deep-space SDP4 extension (lunar-solar secular and periodic perturbations and 12 h / 24 h geopotential resonance). It is validated against the official AIAA 2006-6753 ("Revisiting Spacetrack Report #3") verification vectors: all 666 reference states across the near-Earth, deep-space, resonant, and error-code cases match to a worst-case position error of about 4 mm. This is the model two-line element sets are defined against, so it represents real constellations — notably the ~12 h GNSS orbits, which are deep-space and resonant and which the earlier two-body + J2-secular model cannot capture.
- A constellation given as full two-line element sets (line 1 + line 2) is now
propagated with SGP4/SDP4; a constellation given as line-2-only elements keeps
the analytic Keplerian two-body propagation, unchanged. The two forms can be
mixed in one block. New
orbit-sgp4-gps.tomlreference scenario (a GPS-like MEO constellation in real two-line format, propagated with SGP4) — drop in a current Celestrak "gps-ops" set to study the live constellation.
0.6.0 - 2026-06-02#
Added#
- Active spoofing-attack demonstrator. A new
spoofscenario kind injects a ramping false-time spoof and runs each clock's clock-aided integrity monitor, reporting whether and when the spoof is detected and whether it reaches the operational spec undetected — turning the Security figure of merit into a concrete attack/defence demonstration. Newspoof-attack.tomlreference scenario. - Multi-constellation availability. An orbit scenario can combine several
constellations (a
[[constellations]]list alongside the primary[constellation]) for multi-GNSS availability and dilution of precision — e.g. GPS plus Galileo. Neworbit-multignss.tomlreference scenario.
0.5.0 - 2026-06-02#
Added#
- HTML scorecard report. Every run now also produces a self-contained, branded HTML scorecard — the one-line summary, the chart (embedded as an inert data-URI image), and the full JSON — written by the CLI alongside the JSON and SVG. A shareable single-file artifact for a study deliverable or annex.
- Joint sensor-fusion estimator. A new
fusionscenario kind runs a single recursive Kalman filter as the navigation solution — fusing the clock state[phase, frequency]and the position state[position, velocity], disciplined by GNSS (learning the frequency offset and velocity) and aided by optical time transfer during the outage — rather than composing independent predictors. It reports fused timing/position holdover and a joint-covariance integrity. Newfusion-pnt.tomlreference scenario. - Fuller IMU noise model. The accelerometer now models the remaining
Allan-variance terms beyond the constant bias and velocity random walk:
bias instability (a 1/f flicker floor at the standard Allan bias-instability
coefficient, reusing the clock's flicker synthesis) and acceleration random
walk. New optional
bias_instabilityandq_aainertial scenario fields; a GNSS re-fix re-calibrates the residual bias drift. - Real constellation geometry from TLEs. A constellation can be given as a block
of two-line element sets (the standard NORAD/Celestrak format) via a
tlefield, so availability and dilution of precision use a real constellation's published geometry instead of a synthetic Walker pattern. The engine reads each TLE's mean Keplerian elements and propagates them two-body — not SGP4 — which is sound for a snapshot study from a common epoch. Neworbit-real-tle.tomlreference scenario.
0.4.0 - 2026-06-02#
Added#
- Trade-study parameter sweeps. A new
sweepscenario kind varies one parameter (threshold_ns,duration_s,quantum_q_wf, orclassical_q_wf) across a linear or logarithmic range and records a chosen figure of merit at each point for both clocks, producing the "how does holdover scale with clock stability?" comparison chart a design trade needs. Newsweep-clock-stability.tomlreference scenario. - Monte Carlo confidence bands. The clock-holdover scenario can run many
realizations (new optional
runsfield): each figure of merit is then reported as a mean with a 5th–95th-percentile spread, and the chart shades the 5–95% error envelope around the median for each clock. A single run remains the default. Newclock-ensemble.tomlreference scenario. - Eccentric orbits and J2 drift. The orbit type is now a full Keplerian orbit
(semi-major axis, eccentricity, inclination, RAAN, argument of perigee, mean
anomaly), propagated by solving Kepler's equation, with optional secular J2 nodal
regression and apsidal precession. New optional
eccentricity,argp_deg, andj2scenario fields, and anorbit-molniya.tomlreference scenario (a 12 h highly-eccentric critically-inclined user). Circular orbits keep the original closed-form path bit-for-bit. - The hybrid (combined-PNT) pack now reports Integrity and Security, so all four packs cover the full set of operational figures of merit. Integrity is the timing-channel protection-bound containment from a Kalman estimator disciplined to truth while GNSS is nominal and re-anchored (more loosely) at each optical re-sync; its bound includes the link's measurement-noise floor, so a clock far better than the link is scored against the delivered solution's actual noise. Security is the clock-aided spoof-detection score against the timing spec.
Changed#
- Release notes are now generated from the curated CHANGELOG section for the tag
(
scripts/changelog-extract.sh), so each GitHub release highlights what changed instead of listing raw commits.
0.3.0 - 2026-06-02#
Added#
- Security figure of merit (previously unpopulated): a clock-aided
spoof-detection score for the clock-holdover and orbit packs. It models an
integrity monitor that cross-checks GNSS-derived time against the clock's own
coasted prediction over a coherent window; the detection floor combines the
averaged measurement noise with the clock's coast uncertainty, so a quieter
clock detects smaller, slower time-spoofs. The score is reported in
[0, 1]relative to the timing spec, completing the six operational figures of merit. - Geometry-derived position accuracy for the orbit pack: from the
line-of-sight geometry to the visible satellites it forms the design matrix
and its covariance factor
Q = (HᵀH)⁻¹, yielding the dilution-of-precision factors (GDOP/PDOP/HDOP/VDOP/TDOP). Position accuracy is the position DOP scaled by a configurable user-equivalent range error (new optionalsigma_uere_mscenario field). An orbit result now carries a geometry summary (fraction of samples with a fix, best and median PDOP and position sigma). - An in-browser playground (
web/) that runs the engine client-side as WebAssembly: pick a reference scenario or edit the TOML, run it, and see the summary, chart, and full JSON, with nothing uploaded. Apagesworkflow builds and publishes it to GitHub Pages, and a newsummaryWebAssembly export backs the readout. - Labelled y-axes on the SVG charts: gridlines, numeric tick labels, and a units
axis title (via a shared
charthelper), so magnitudes are readable. - Package-publishing workflow (
publish) for crates.io, PyPI, and npm, each gated on its registry token and triggered by a published release.
0.2.0 - 2026-06-02#
Added#
- Flicker (1/f) FM floor for the clock error model, synthesised as a sum of
log-spaced Ornstein-Uhlenbeck processes and calibrated so the flat
Allan-deviation floor sits at a configurable level. Off by default; enabled
per clock via the optional
flicker_floorscenario field. - Gyro channel for the inertial model: residual gyro bias and angular random
walk drive an attitude (tilt) error that couples gravity into a horizontal
specific-force error, the dominant strapdown error-growth mechanism. Off by
default; enabled per sensor via the optional
gyro_biasandq_arwfields. - Two-state (phase, frequency) Kalman clock estimator with exact van Loan
process-noise discretisation. Coasting from a known state reproduces the
analytic holdover error growth (
q_wf*T + q_rw*T^3/3) exactly, and the filter additionally yields an online 1-sigma uncertainty bound. - The clock run now reports the Integrity figure of merit (previously unpopulated): the fraction of outage samples whose error stays inside the filter's 3-sigma protection bound, surfaced in the JSON result and CLI summary.
- Geometry-derived GNSS availability: circular-orbit propagation, a Walker-delta
constellation generator, and line-of-sight visibility (Earth-occultation plus
elevation mask) produce the availability timeline from real orbital geometry.
New
orbitscenario kind and theorbit-gnss-challenged.tomlreference scenario (a spacecraft inside the GNSS shell with intermittent coverage). - Optional Python extension (PyO3, abi3) exposing
run,run_full, andversion, packaged with maturin (pyproject.toml) and built for Linux, macOS, and Windows by a release-tagwheelsworkflow. The binding is a feature-gated, optional dependency: the default build, tests, and dependency-audit gate are unaffected. - Optional WebAssembly module (wasm-bindgen) exposing
run,chart_svg, andversion, built with wasm-pack under thewasmfeature;getrandomis target-gated to use the browser entropy source onwasm32. - Shared
api::run_tomldispatch used by the CLI and both bindings, so the command line and the bindings cannot drift.
Changed#
- Holdover scoring is now segment-aware: outage timelines are split into contiguous segments at GNSS re-acquisition, and the reported holdover is the worst-case (shortest) coast across them. Single-outage scenarios are unchanged. Applies to the clock, inertial, and hybrid scorers.
- The inertial model's reported
kindis nowinertial(wasaccelerometer), reflecting the combined accelerometer and gyro channels.
0.1.0 - 2026-06-01#
Initial release.
Added#
- Deterministic simulation engine for hybrid quantum/classical PNT: a common
error-model interface, declarative GNSS-availability scenarios, holdover /
dead-reckoning estimators, and figure-of-merit scoring against the standard
operational PNT criteria. Results are reproducible from
scenario + seed + engine version(versioned, self-describing JSON with a scenario hash) and rendered as SVG charts. The CLI dispatches scenarios bykind. - Four sensor packs, each calibrated to published data and validated against the
standard relation:
- Clock holdover — white FM, random-walk FM, and linear aging; validated by overlapping Allan deviation (Riley, NIST SP 1065). Chip-scale atomic clock vs strontium optical lattice clock.
- Inertial dead-reckoning — residual bias + velocity random walk, double integrated to position error; validated against Groves' error-growth relations. Cold-atom vs navigation-grade accelerometer.
- Time transfer — optical vs RF link timing jitter → synchronization precision → one-way ranging.
- Hybrid fusion (capstone) — a combined PNT suite that must hold both timing and position, with optional optical inter-satellite time-transfer clock-aiding.
- One cited reference scenario per pack under
scenarios/, every numeric parameter carrying a peer-reviewedprovenance. - Reproducibility and repository-hygiene guards; CI (format, clippy, tests, guards, MSRV) and a tag-gated release pipeline that re-runs all checks.
- Documentation: README with architecture diagrams, validation-status report, contributing guide, security policy, and code of conduct; Apache-2.0 license; issue/PR templates and Dependabot configuration.
- Vendor-neutral throughout; peer-reviewed scientific and metrology citations retained.
- Apache-2.0 license hygiene: SPDX headers on all sources, a
NOTICEwith trademark notice, Developer Certificate of Origin (DCO) sign-off for contributions, andcargo-denyenforcement of dependency licenses/advisories in CI. - Open-core positioning (README): a free Apache-2.0 engine plus available commercial support, integration, and proprietary extensions from Ashforde OÜ — sustained by services, not license fees.
CITATION.cffso the software can be cited.