Repository navigation
Added
-
Detection ROC of the IQ monitors (PRO-32,
docs/design/DETECTION-ROC.md). A campaign with monitors
keeps each test statistic's maximum over stated-event windows (positive) and tiled windows
outside the events (negative,roc_window_s). The report pools them per alarm kind into an
ROC with its AUC and the operating point at the configured threshold, asroc.jsonand a
report section. On synthetic campaigns the chance-level check is met. The C/N0 AUC bar
(0.95) is not met (0.79), because the negative windows right after an event inherit the
CUSUM's decay from the detection; this is recorded. MODELLED. -
IQ monitors in the lab-replay campaign (PRO-31,
docs/design/MONITORS-IN-CAMPAIGN.md). An optional
[monitors]table in a campaign file runs the IQ monitors in the runner's single pass: power
and spectral after each front-end chain, and C/N0, SQM and lock per tracked channel. Their
alarms are scored against the stated events: detected or missed, time to detect, and false
alarms per hour of exposure. Each test's closed-form false-alarm figure is stated, including
a new two-sided Gaussian figure for the SQM tests (2Q(k)). Without the table nothing
changes. On synthetic campaigns the power and SQM false-alarm bars and the C/N0 false-alarm
bar are met; 18 of 18 attenuations are detected, but the time-to-detect bound, below one
detector sample by construction, is not met and is recorded. MODELLED; ledger row "IQ
monitors scored in lab-replay campaigns". -
Lab fit from IQ campaigns, follow-up (
tests/iq_labfit_followup.rs, CI job
labfit-iq-followup). It tests the two hypotheses §13.6 ofdocs/design/LABFIT-FROM-IQ.mdleft
open, against bars pre-registered in 1e532f8. It adds two synthetic campaigns: S, with a
stepped J/S (seed 2026_1014), and R, a fresh ramp (seed 2026_1015). Results are in
docs/design/evidence/labfit-followup/RESULTS.md.- S1 met: with each J/S level held,
δ_effis recovered within +0.185 dB. The ramp campaigns'
−1.1 dB error is estimator lag. - S2 not met, pinned by a test asserting the miss: (d) as written on the stepped campaign
holds for the tracking-loop model (0.45 dB) but not for the empirical one (6.42 dB). - R1 met: on the fresh ramp campaign, (d) with the fitted drop dwell times each event's ramp
rate added is within 1 dB for both models. - The (a′) and (d) misses replicate on R.
- A campaign may now state its J/S as a
stepprofile in the test harness. - No §13 verdict changes, and the ledger row stays MODELLED.
- S1 met: with each J/S level held,
-
Capped FLL pull-in, opt-in (DS-LOOP-05): a new loop-design key
[design.carrier] fll_pull_in_max_s(s), RustLoopConfig::fll_pull_in_max_s. Underfll_assist = "pull-in"
it forces the FLL-to-PLL hand-over after that long, whatever the PLI. Unset by default, and
then out of the design hash; the default design and its hash are unchanged.- Checked against bars pre-registered at 58789af, on injected truth. The control is met: the
default never hands over in 9 of 10 seeds at 33 dB-Hz. - Not met: with a 1.0 s cap, 16 of 20 runs hold lock at 33 dB-Hz and 8 of 20 at 31 dB-Hz; the
others cycle between forced hand-overs and the FLL's return. Phase jitter is 1.8× the PLL
alone at 33 dB-Hz. - The ~35 dB-Hz pull-in floor stays the default's documented limit.
- MODELLED; ledger row "Capped FLL pull-in below the default design's FLL-to-PLL hand-over
floor (opt-in)".
- Checked against bars pre-registered at 58789af, on injected truth. The control is met: the
-
Acquisition hysteresis and the front-end quantiser in the jamming kind (CAP-06c): two
optional sections for ajammingscenario.[acquisition]:acquisition_threshold_dbhz(dB-Hz, at or above the tracking threshold)
andreacquisition_delay_s(s, default 0). A satellite is acquired at or above the
acquisition threshold, once it has been unacquired for the delay. It is held while at or
above the tracking threshold. Outputs:epochs[].sats[].acquired(true/false),
epochs[].acquired(count) andfom.availability_acquired(1).[front_end]:adc_bits(1 to 16) andbandwidth_mhz(MHz, default twice the chip
rate). Outputs afront_endblock:agc_gain_reduction_db(dB,10·log10(1 + J/N)),
quantiser_loss_db(dB, taken off every link's effective C/N0) andloss_model. The loss
is the Gaussian-input one, flagged as not valid for CW, narrowband and swept jammers.- Without the sections every output is byte-identical.
- Checked against bars pre-registered at 70c72fc. The quantiser loss is within 0.0007 dB of
an independent Monte Carlo, and the AGC reading within 0.012 dB of the IQ layer's AGC loop. - The registered hysteresis runs were uninformative (every link lost), so that bar is NOT
MET. A supplementary run set registered at 3779329 meets it: every flag equals an
independent recomputation, and 732 of 1824 links show the hysteresis. - Finding: with a CW jammer a 1-bit ADC loses about 5 dB more than the Gaussian figure.
- MODELLED; ledger row "Acquisition hysteresis and the front-end quantiser in the jamming
kind against independent recomputations".
-
Terrain shadowing of the jammer in the jamming kind (CAP-06b): an optional
[terrain]
section in ajammingscenario with a jammer. Fields:dem_hgt: an SRTM.hgttile, relative to the working directory;samples_per_side,ll_lat_degandll_lon_deg(deg): the tile's size and corner;geoid_undulation_m(m, default 0),k_factor(default 4/3) andprofile_step_m(m,
default 30).
The engine samples the great-circle terrain profile between jammer and receiver and finds the
dominant knife edge. The jammer's received power, and so every J/S, is lowered by the single
knife-edge loss of ITU-R P.526-15 §4.1.- The result gains a
jammer_terrainblock:nu_max(1),diffraction_loss_db(dB),
obstacle_distance_m(m),obstacle_terrain_m(m),path_length_m(m) and
profile_points(count). - Without the section every output is byte-identical.
- Checked against bars and ten placements pre-registered at 92cd2bf, on the committed real
SRTM tile of Death Valley, against an independent numpy recomputation. All met: worst
difference 1.6e-10 onνand on the loss (bar 1e-9), the loss monotone in jammer height, and
the loss reaching J/S exactly. - MODELLED; ledger row "Terrain shadowing of the jammer in the jamming kind against an
independent numpy recomputation on a real SRTM tile".
-
CRPA antenna gains in the jamming kind (CAP-06a): an optional
[crpa]section in a
jammingscenario. Fields:elements_enu_m(element positions in local east-north-up, m),
weighting("null-steering"or"mvdr"), andbandwidth_mhz(MVDR noise bandwidth, MHz,
default twice the chip rate).- Each channel forms a distortionless beam toward its satellite. The link's noise is scaled by
‖w‖²and the jammer by|wᴴa_j|²before the existing anti-jam C/N0 equation. - Each link gains a
crpablock:array_noise_gain_db(dB) andjammer_gain_db(dB). - Without the section every output is byte-identical.
- An invalid section (an empty element list, an unknown weighting, a non-positive bandwidth) is
an error, throughkshanaandapi::run_tomlas well asjamming::run_jamming. - Checked against bars pre-registered at 08d83b4. Met: additivity (after a fix: the first
build added the new units to every jamming run's units table); one element is no array;
noise gain and effective C/N0 against an independent numpy recomputation within 1e-9 dB;
MVDR tends to the null at high JNR. - Not met: the MVDR jammer gain on 194 of 456 links on the 0.36 portable maths (202 on host
maths;docs/revisions/STUDIO15-batch4-rebaseline.md) (worst 1.7e-6 dB). The residuals of −100
to −188 dB are below float64's resolution at that bar, and numpy disagrees with itself
by the same order. - MODELLED; ledger row "CRPA antenna gains in the jamming kind against an independent numpy
recomputation".
- Each channel forms a distortionless beam toward its satellite. The link's noise is scaled by
-
IQ tracking engine against GNSS-SDR 0.0.19 (PRO-33):
kshana iq trackand GNSS-SDR track
the same independent numpy recordings of GPS L1 C/A at 30 to 45 dB-Hz.- Bars pre-registered at 48edd4a. The registered calibration failed on a wrong GNSS-SDR
hand-off (F3), so Amendment 1 (1e4288c) adds a pull-in-only FLL to both receivers, committed
before any test recording. - Met: carrier and code jitter and Doppler agree within 5 % at every C/N0.
- Not met: the engine's NWPR C/N0 reads 2.3–7.1 dB below GNSS-SDR's SNV (F4, a known
estimator limit; M2M4 is within 0.06 dB of truth); the lock indicators differ by definition
(F5); one 30 dB-Hz slip disagreement (F6). - MODELLED; ledger row "IQ tracking engine against GNSS-SDR 0.0.19 on independent recordings".
- Bars pre-registered at 48edd4a. The registered calibration failed on a wrong GNSS-SDR
-
Two VALIDATED rows for IQ-layer codes (DS-GIQ-06b, PRO-39).
- BeiDou B1I. All 63 B1I primary codes match GNSS-SDR v0.0.19's independent generator chip
for chip (bar pre-registered at 911106d). Only that program's output is committed: the B1I
PRN codes the public ICD defines, as a prefix and a hash. - GPS L1C. All 126 L1CP/L1CD codes and 63 L1CO overlays match the check columns
IS-GPS-800J prints, and the computed Legendre sequence matches its Table 6.2-1 (bars
pre-registered at fe372e2). - Rows: "BeiDou B1I primary ranging codes against an independent generator" and "GPS L1C ranging
and overlay codes against the IS-GPS-800J printed check columns".
- BeiDou B1I. All 63 B1I primary codes match GNSS-SDR v0.0.19's independent generator chip
-
GLONASS, BeiDou and Galileo E6 bands in the spectrum model (CAP-12, signals): new band
namesglonass-l1ofandglonass-l2of(any FDMA channel asglonass-l1of-k<K>,
K = −7 to 6),beidou-b1i,beidou-b1c,beidou-b2a,beidou-b3iandgalileo-e6. Each
carries its ICD carrier, modulation and minimum received power. They are opt-in by name: the
default band set and every existing output are unchanged.- Checked against bars pre-registered at 5806f28: the ICD values, nulls, the B1C composite
and the C/N0 path are met. - Two bars were not met. The unit-area bar was unattainable as written (F1). The IQ layer's
B1I and B1C waveforms match the model only band-averaged, because of their codes' uneven
line spectra (F2). - MODELLED; ledger row "Spectrum model: GLONASS, BeiDou and Galileo E6 bands (opt-in)".
- Checked against bars pre-registered at 5806f28: the ICD values, nulls, the B1C composite
-
Cell-averaging CFAR option in IQ acquisition (PRO-37):
iq::acq::ca_cfarand
ca_cfar_cellsdecide on an acquisition grid by comparing each cell with the mean of
reference cells in its Doppler row. The threshold comes from the closed-form CA-CFAR
false-alarm probability forMnon-coherent blocks (ca_cfar_pfa,ca_cfar_alpha), so the
decision does not depend on the sample-power normalisation. It is opt-in; the default
detector and every existing output are unchanged.- Checked against bars pre-registered at 3724aef: the closed form against Monte Carlo
(18 of 18 cases), and a 1 % design rate measured at 0.97–1.01 % on Gaussian and 2-bit
quantised noise. - One bar was not met: on a noise-free 2-bit signal, 1.28 % of the off-peak cells exceed the
threshold, at the code's correlation sidelobes (finding F1). - MODELLED; ledger row "Cell-averaging CFAR option in IQ acquisition".
- Checked against bars pre-registered at 3724aef: the closed form against Monte Carlo
-
Scintillation model against measured data (PRO-34): the IQ channel's Cornell
scintillation model was compared with measured 50 Hz GPS L1 intensity and phase from an
equatorial scintillation night (Zenodo record 16941224, INGV, Malindi, CC BY 4.0), under bars
pre-registered at b6ccb49. Met: the intensity distribution and the phase scintillation tied
to S4. Not met: the model fades 2.4 times as often below −10 dB and 3.5 times as long
(finding F1). The registered processing reads the model's wrapped phase (finding F2); the
phase result holds with it unwrapped.scripts/fetch_scint_malindi.shfetches the data.
The row stays MODELLED: "IQ ionospheric scintillation channel (Cornell model) against
measured 50 Hz GPS L1 scintillation". -
Corrected LuGRE sample reader (MR-09):
realdata::lugre::parse_iqs_sdrxreads a
LuGRE IQ batch with I in the high nibble and the mid-rise levels2v + 1
(SdrLayout::mid_rise), which remove the −½ mean the bare two's-complement integers carry
on I and Q.run_acq.py --correctedgives GNSS-SDR the same reading. The generic ION reader
and every existing output are unchanged: 149 scenario results and a study are byte-identical
before and after, and the pinned LuGRE findings read as registered
(xval/lugre-reader/RESULTS.md). No ledger row changes. -
Opt-in BOC(1,1) tracking (CAP-07 (a)):
LoopConfig::bocrescales the DLL
discriminators for the sine BOC(1,1) main peak and adds a bump-jump monitor (very-early and
very-late correlators at ±0.5 chip, up/down counters,N = 8) that moves the code NCO off a
side peak;Channel::bump_jumpslists the jumps. Off by default, and every existing output
is unchanged. Checked against bars pre-registered at 6b68f6c on a synthetic BeiDou B1C
signal: a side-peak start that stays 0.55 chip off without the option is pulled onto the
main peak by one jump at 0.08 s with it, and no false jump was seen from the main peak. One
bar was not met: the unit-test grid for the scaling reaches outside the discriminators'
linear range (finding F1 indocs/design/evidence/boc-bump-jump/RESULTS.md). MODELLED;
ledger row "BOC(1,1) tracking: DLL scaling and bump-jump ambiguity resolution (opt-in)". -
Lab fit from IQ campaigns (B7.1,
docs/design/LABFIT-FROM-IQ.md).
A labfit scenario's[input] campaign = "<out-dir>"fits akshana iq campaignoutput folder
on the CLI (kshana iq labfit), in Python (kshana.iq_labfit) and in the MCP labfit tool
(folders inside its work directory only). It fits one unit per signal, loop design, front-end
chain and jammer type:- the C/N0 model against stated J/S, with censoring;
- the tracking threshold, directly and in closed form;
- the tracking-loop and empirical lock models, coupled through the C/N0 model, with their
dwells; - a seeded cluster bootstrap over recordings, 90 % prediction intervals, and
leave-one-recording-out hold-outs; - a pre-registered
[validation]table, hashed, with PASS/FAIL per bar.
The report is
kshana.labfit-iq/1as JSON, CSV and Markdown, with a units block.
Q_effis not identifiable from C/N0 data without an independent power measurement. It
is stated, never fitted, and the fitted parameter isdelta_eff = delta - 10 log10 Q(design
amendment 2026-10-12). An unknown jammer type takes the most damaging statedQ(cw, 1). On
synthetic campaigns from Kshana's own scenes, the hold-out coverage bar (c) passes. The
delta_effbars (a') and (b') (0 of 50 campaigns covered) and the closed-form threshold bar
(d) are not met: they are recorded with their values. MODELLED; ledger row "Calibration of the open receiver's C/N0 and lock models to
IQ campaign results, with stated prediction error". The receiver-log labfit path and its
outputs are unchanged. -
solar-systemon a JPL kernel (CAP-31):planetary_kernel_pathtakes each body the SPK
holds (with de440s: Mercury, Venus, the Earth, the Moon) from the kernel, labelKERNEL, and
adds akernelblock only when set. New row, VALIDATED against SPICE (pre-registered
f8e946e; worst 3.3e-5 m, 1.0e-11 m/s). The default report is unchanged. -
Porkchop on the DE440 ephemeris (CAP-20b):
maneuver::porkchop_kernelsweeps C3 and
arrival V∞ on heliocentric states from a DE440 SPK. New row, VALIDATED against SPICE states- pykep 3.0.1 (pre-registered 2d857fb; Earth to Mars 2026-2028, 304 cells, worst 2.6e-14,
the same minimum-C3 cell).
- pykep 3.0.1 (pre-registered 2d857fb; Earth to Mars 2026-2028, 304 cells, worst 2.6e-14,
-
LRO non-gravitational macro-model: NOT MET (DS-LRO-10, pre-registered e82047e).
lunar_od::LunarMacroModel(opt-in viaLunarForceModel::macro_model): flat-plate direct
solar radiation with the Moon's shadow, a Sun-tracking array, and lunar albedo and thermal
IR;LunarMacroModel::lro_genericis a labelled generic LRO approximation from published
facts (no bus plates: the plate table could not be obtained). On the 4 h LRO arc it leaves
the reduced-dynamic RMS at 6.668 m and the dynamic at 11.94 m, with an implausible radiation
factor (about +21 / −22), so all three bars are missed. The finding is pinned by a test, and
no default output changes (docs/design/evidence/ds-lro-10-macro-model/RESULTS.md). The opt-in
lunar cannonball SRP now uses the Moon's conical shadow instead of the Earth's
(forces::conical_shadow_body). -
Lunar OD: optional k2 solid tide and selenocentric relativity (DS-LRO-11), off by
default (LunarForceModel::lunar_tide,::relativity); aKSHANA_LRO_DEGREEswitch for
the ANISE cross-validation (DS-LRO-04); a 24 h LRO arc fixture and an information-only
long-arc fit (DS-LRO-09). Information runs are recorded in
docs/design/evidence/ds-lro-09-11-longer-arc/RESULTS.md; no published number changes. -
CR3BP 9:2 NRHO against CAPSTONE (CAP-29d): new MODELLED row measuring the engine's
circular-restricted NRHO against CAPSTONE's flown orbit (JPL Horizons, March 2023;
pre-registered 4e86817): period 1.07 %, apolune 0.05 %, perilune 4.1 % from CAPSTONE's means,
inside the registered 3 / 5 / 20 % bars. A measurement, not a validation. -
CR3BP rotating states to Moon-centred ICRF (RM-34, FUT-06):
cr3bp::to_moon_centred_icrf
/from_moon_centred_icrf/to_moon_centred_icrf_de440map a nondimensional Earth-Moon
rotating state onto the instantaneous DE440 Earth-Moon line, including the frame's
out-of-plane rate. New row, VALIDATED against a SPICE two-vector frame (pre-registered
2313eb1, amended before any run d5ef79b; 72 cases, worst 1.8e-7 m and 1.2e-9 m/s). -
CR3BP family members against the JPL Three-Body Periodic Orbits API: NOT MET (RM-35,
pre-registered a9074f6).cr3bp_family(halo, NRHO and DRO correction, monodromy stability
index, pseudo-arclength continuation) matches 54 of 58 API members on every bar (state,
period and Jacobi within 1e-10 everywhere). One L1 halo stability index (9.5e-2) and three
small-DRO closures (1.5e-8 to 3.3e-8 against 1e-8) miss, so the module is#[doc(hidden)]
and no row claims it. Results:docs/design/evidence/cr3bp-families/RESULTS.md. -
Opt-in DE440 lunar orientation and environment (FUT-05, DS-LRO-08; defaults unchanged):
lunar_frame::try_icrf_to_moon_pa_de440,lunar::try_mci_to_mcmf_de440/
try_mcmf_to_mci_de440(epoch-anchored, DE440 principal axes) and
lunar_od::De440LunarEnvironment(embedded DE440 PA orientation plus the geocentric Sun and
Moon from a user-supplied DE440 SPK). Against ANISE (pre-registered ea53771): orientation
within 3.5e-7 rad and LRO fits within 1 mm, but the ephemeris leg missed its bar (Sun 30.7 m,
Moon 1.1 m, a ~1 ms time offset on the ANISE side), so it is not a validated row. Results:
docs/design/evidence/fut05-de440-lunar-env/RESULTS.md. -
Lunar frame campaign datum, condition-scaled NumPy bar: NOT MET (pre-registered b887eb0;
stays MODELLED). Step 0 rules out the pseudo-inverse fallback as the cause of the 0.30
stations-estimated gap; NumPy agrees within the bar on six of eight scenarios, but
stations_estimated misses it (6.8e-6 against 5.9e-7), and on the fresh date 2026-12-01 an
unobserved south-pole beacon makes the beacon information rank-deficient, which the
unchanged oracle cannot evaluate. Results:docs/design/evidence/mr05-lunar-datum-cond/RESULTS.md. -
Lunar frame datum from a real observing campaign (analytic Moon) VALIDATED on fresh 2025
EDC normal points (pre-registered 31f5283 before any was fetched): the unchanged SPICE and
NumPy oracle agrees on bookkeeping, ranks and zero coupling, and within 1.11e-3 on sigmas
(bar 1e-2), 8.95e-4 on the condition and 1.79e-3 on the across/along ratios (bar 2e-2). A
Library check of the covariance computation only; the bars do not discriminate Moon models.
Results:docs/design/evidence/mr06-llr-analytic-fresh/RESULTS.md. -
IAU body orientation and GM of the fourteen added solar-system bodies split out of the
"Physical constants of every solar-system body" row into a VALIDATED row (pre-registered
154bc54):Body::iau_rotation_etagrees with SPICEpxformon pck00011 within 1e-9 rad on
574 matrices at the original epochs and on 574 at 41 fresh epochs never compared before (worst
Phobos 1.38e-10 rad), andBody::muequals gm_de440 for all fourteen bodies. The rest of the
original row (J2, mean radius, the report) stays MODELLED. Results:
docs/design/evidence/mr08-body-orientation/RESULTS.md. -
Multi-revolution Lambert (
maneuver::lambert_multirev): every solution with up to three
revolutions, both branches. New ledger row, VALIDATED against pykep 3.0.1 (pre-registered
a75de85; 382 cases, 1058 solutions, worst velocity difference 1.6e-13 against 1e-8). The
internal Kepler-landing guard found one limitation ofmaneuver::kepler_universalon an
e = 0.994 arc; it is recorded indocs/design/evidence/lambert-multirev/RESULTS.md. -
Box-wing and ECOM2 solar radiation pressure (DS-PA-14), opt-in.
srp_boxwing: a
flat-plate box-wing model with the Galileo FOC/IOV tables from the GSC satellite metadata, in
nominal GNSS yaw steering, with radiation-factor and per-panel partials, and the ECOM2
empirical model with its partials. Selected withForceModel::box_wing_srp/
PreciseForceModel::box_wing_srp(fieldssrp_boxwing, defaultNone: the cannonball path
and every golden are unchanged); with a box-wing set,cris its radiation factor. Compared
against Orekit 12.2 under a pre-registration (xval/srp-boxwing-orekit/): every bar met, about
1e-15 relative; VALIDATED as a model evaluation, the Galileo SRP prediction stays MODELLED. -
Oblate-Earth SRP shadow and solar limb darkening (FUT-09, RM-11). New
srp_shadowmodule:
oblate_conical_shadow(WGS-84 occulting ellipsoid),limb_darkened_shadow(Eddington law), and
ShadowModel, selected withForceModel::srp_shadow(default: the existing spherical conical
shadow, unchanged);forces::srp_accel_with_lit_fraction. The oblate shadow agrees with Orekit
12.2's lighting ratio within 7.4e-9 over 2400 geometries (VALIDATED,
xval/srp-oblate-shadow-orekit/); limb darkening has no external oracle (MODELLED). -
NRLMSISE-00 thermosphere (FUT-08, RM-10, DS-PA-13).
nrlmsise00: a port ofgtd7and
gtd7d(switches, daily-Ap and 3-hourly ap-array modes) from the public-domain C version of the
NRL Fortran. It is bit-identical to that C port and within 1.9e-6 relative of pymsis 0.13.0
(NRL Fortran, single precision) on a 720-point grid, and it reproduces the 17 published test
cases (VALIDATED;xval/nrlmsise00-pymsis/). This validates the port, not the model's accuracy
against measured density. -
Opt-in NRLMSISE-00 drag-density tier.
forces::DensityModel::{Exponential, Nrlmsise00 { f107a, f107, ap }},forces::drag_accel_with_density,density_at, and
propagator::ForceModel::drag_density. The static exponential model stays the default, bit for
bit. Space weather is constant per model. VALIDATED at orbit level against Orekit 12.2's own
NRLMSISE-00 drag over 24 h (worst 0.03 m against up to 142 km of drag displacement;
xval/drag-tier-orekit/). -
Opt-in DE440 Sun and Moon in the Cowell propagator (RM-33, FUT-10).
propagator::ForceModel::with_ephemeristakes anyEphemerisProvider(e.g. a DE440
KernelEphemeris) for the third-body and SRP terms; it refuses a provider that cannot serve the
epoch, counts fallbacks to the analytic series, and is off by default.
EphemerisProvider::relative_position_jd2keeps two-part precision. Against Orekit 12.2 on its
own DE440 reader, 24 h orbits agree to 0.017 m; the pre-registered Sun/Moon position bar missed
(6.4 m / 0.24 m; SPICE shows the same gap), so MODELLED (xval/propagator-de440-orekit/). -
Opt-in S1 atmospheric tide (DS-PA-03).
tides::atmospheric_tide_s1and
s1_air_tide_acceleration(Biancale & Bode 2006, GFZ STR06/01 Table 1 model N1, degrees 2–6),
enabled withair_tide_s1()onpropagator::ForceModelandprecise_od::PreciseForceModel;
off by default. MODELLED (coefficients re-checked against the published table; no external
oracle for the force effect). -
Range-rate and azimuth/elevation batch orbit determination with an elevation mask
(RM-36, FUT-13).orbit_determination::determine_orbit_batch_tracking,simulate_tracking,
predict_tracking,propagate_with_stm(variational STM with analytic two-body + J2 partials),
is_visible,measurement_with_partials;batch_ls::gauss_newton_analytic. The range-only
path is unchanged. Compared with Orekit 12.2 batch least squares over 8 pre-registered scenarios:
every bar met except the noiseless state agreement (≤ 1 cm), missed on 2 of 4 scenarios at 1.46
and 1.74 cm with kshana within 1e-8 m of truth; recorded, MODELLED. -
SP3 accuracy weighting (DS-PA-06).
sp3::parse_sp3_accuracyand
Sp3Accuracy::position_sigma_mmap the header and per-record accuracy codes to observation
sigmas; an SP3-weighted Galileo fit variant (Tier-2 3D RMS 0.035 m against 0.070 m unweighted)
is added beside the unchanged recorded runs. MODELLED. -
Swarm-A full-arc dispatch job (DS-PA-08) in
.github/workflows/agency-od-validation.yml. -
Planetary gravity from PDS SHADR tables (CAP-30, CAP-31 part).
SphericalHarmonicField::from_shadr
(src/gravity_pds.rs) reads Mars GMM-3 (d/o 120) and GRAIL GL0660B (d/o 660);
Body::with_gravity_fieldattaches any loaded field (defaults unchanged). Degree-20 crops are
bundled (NASA PDS, public domain;tests/fixtures/planetary_gravity/NOTICE.md); the full files
are fetched byxval/planetary-gravity-pyshtools/fetch_data.sh. VALIDATED against pyshtools 4.14.1
(within 4e-15 of total gravity at d/o 120 over the whole sphere, 5e-14 at d/o 660 for |lat| ≤ 60°);
the body-frame wiring stays MODELLED (each field's native frame is not the IAU frame used). -
High-degree gravity to degree 2190 (RM-14, FUT-07).
SphericalHarmonicField::acceleration_hf
/potential_hf(Holmes–Featherstone modified forward column, no underflow, no polar
singularity),prepare_high_degree, a streaming ICGEM loaderfrom_gfc_file/from_gfc_reader
(norm and max-degree checks, tide system recorded, time-variable terms rejected), and
scripts/fetch_egm2008.sh(full EGM2008, SHA-256 pinned, not bundled; tests gated on
KSHANA_EGM2008_GFC).propagator::FieldEvaluatorselects it on the opt-inGeopotential.
Agrees with Orekit 12.2 to 7.3e-17 m/s² through degree 2190, and a 24 h 200×200 orbit agrees
to 0.13 m. MODELLED: pyshtools missed the pre-registered near-pole bar (traced to pyshtools)
and the orbit's degree-70 sensitivity control missed (0.73 m < 1 m); both are recorded in
xval/. -
DOP853 integrator (FUT-11).
integrator::integrate_dop853,integrate_dop853_checked,
integrate_dop853_dense(7th-order dense output),dop853_step,dop853_initial_step, with a
fail-closed step guard;propagator::Integrator::Dop853,propagate_dop853,
propagate_dop853_checked. The default driver is unchanged. VALIDATED against SciPy 1.18.1
solve_ivp(method="DOP853"): single steps within 5.6e-15, matching step sequences, end states
within 1.6e-12 (xval/dop853-scipy/). -
Robust Kepler solver (FUT-12). New
keplermodule:solve_kepler_markley(Markley 1995
starter, cancellation-free residual) for all 0 ≤ e < 1, wrapped by
propagator::solve_kepler_robust. VALIDATED against 50-digit mpmath roots to 0.9 ε·|E| up to
e = 0.999999 (xval/kepler-mpmath/). -
Opt-in tesseral Earth gravity in the Cowell propagator (RM-09).
propagator::ForceModel
gainsgeopotential: Option<Geopotential>(with_geopotential,with_egm2008(nmax)). It
evaluates aSphericalHarmonicField(bundled EGM2008 to degree/order 70, or any ICGEM.gfc) in
an Earth-fixed frame reached by the Earth rotation angle (EarthFixedFrame::Era) or the CIO
GCRS-to-ITRS chain (EarthFixedFrame::Cio). DefaultNone: no existing output changes.
VALIDATED against Orekit 12.2 Holmes–Featherstone over 24 h (6 cases, worst 0.036 m), with the
bars pre-registered inxval/cowell-tesseral-orekit/PREREGISTRATION.md. -
Two-part Julian dates through Earth rotation on the orbit path:
jd2::gmst82,
cio::gcrs_to_itrs_matrix_jd2,frames::teme_to_itrf_jd2/itrf_to_teme_jd2,
orbit::Propagator::position_in_frame_jd2andown_jd2_utc,Fromconversions between
jd2::Jd2andtimescales::TwoPartJd, and an opt-intwo_part_jd = truefor the
ephemerisscenario (ECEF, ground track and station view; default off and unchanged, and the
key is left out of the scenario hash when off). New ledger row, VALIDATED against pyerfa
gmst82/c2t06a(pre-registered b10475e; 40 dates: GMST bit-identical, matrix within
2.7e-14, rotation over 1 µs steps within 1.1e-14 rad, where the single-f64path misses
them). The Cowell force-model epoch stays single-f64; the reason is in its documentation. -
EOP prediction beyond the table (
EopSeries::with_ls_ar_prediction,EopSeries::last_mjd):
an opt-in extension of an IERS Earth-orientation series by the LS+AR predictor issued at its
last row, so the frame arguments of a later epoch follow the forecast instead of clamping to
the last row. In-table answers are bit-identical (tested); the series still clamps unless
this is called. The forecast's accuracy remains the existing LS+AR finding against
Bulletin A (MODELLED). -
Celestial-pole offsets dX, dY on the CIO chain (
cio::gcrs_to_cirs_matrix_with_offsets),
and direct ERFA vector checks ofprecession_matrix,teme_to_gcrs_matrixand the new
function. New ledger row, VALIDATED against pyerfa (pre-registered 979c1e3; 40 epochs
1950–2100: precession and CIO bit-identical, TEME→GCRS within 1.1e-10 rad, the predicted
effect of the two-term equation-of-the-equinoxes series). Existing functions are unchanged. -
Pre-1972 UTC on the two-part time path (
timescales::tai_minus_utc_on_date, and
Jd2::from_utc_calendar,jd2::utc_to_taiandjd2::tai_to_utcfrom 1960-01-01): the
drifting-rate ("rubber-second") era, with SOFA's drift table, fractional end-of-day steps, and
the UTC-to-SI scaling of the day fraction. New ledger row, VALIDATED against pyerfa
dtf2d/utctai/taiutc(pre-registered 23c4066; 117 instants 1960–1971, worst 2.9e-11 s
against a 1 µs bar). Every 1972+ result is bit-identical (checksum pinned before the change).
Before 1960 TAI − UTC is 0, as in SOFA. The single-f64timescales::tai_minus_utcis unchanged. -
GLONASS broadcast satellite clock (
GlonassEphemeris::sv_clock_bias_s):−τn + γn·(t − tb)
from the RINEX record, at a time read on the satellite clock (two fixed-point iterations, the
RTKLIB convention). The parsed−τnandγnwere previously unused. New ledger row
"GLONASS broadcast satellite clock and broadcast-orbit propagation", VALIDATED against RTKLIB
2.4.2-p13geph2clk/geph2pos(pre-registered e099c42; 292 cases on a real 2013 broadcast
file: clock difference 0 s, worst position 1.1 mm against a 1 cm bar). Library function; no
scenario output changes. No PZ-90.11 → ITRF transformation is applied, and the module says so. -
PPP time solution against the IGS station clock (0.36, FUT-19): the receiver clock of the
float PPP filter on WTZR agrees with the IGS final combined clock for the station to 0.075 ns
(standard deviation) with a constant offset of −0.66 ns over the converged two hours. The bars
were pre-registered inxval/ppp-time/. VALIDATED (new ledger row). New fixture
igs_2025055_WTZR_05m_0006.clk, the station-clock records of the IGS final clock file already
used for the satellite clocks. -
PPP parity with gLAB (0.36, WP-07, RM-31): gLAB 6.0.0 was run on the same WTZR inputs
as a separate tool (Apache-2.0 core; download checksum verified against the published list),
with the command line of a procedural amendment committed before the run. On its first
registered run, Kshana's float PPP and gLAB's agree within 0.05 m at 240 of 240 epochs and to
5.0 mm at the last. The SPP bar was missed (median 1.187 m against 0.50 m) and is recorded as a
finding: gLAB's and RTKLIB's own SPP solutions differ from each other by 0.852 m. The
registered rule therefore leaves both tools' legs not met as a whole, while PPP parity holds. -
RINEX 4 navigation reading (0.36, WP-07, PRO-41):
rinex::parse_navreads RINEX
4.00-4.02 navigation files (> EPHGPS LNAV, Galileo I/NAV and F/NAV, BeiDou D1/D2, QZSS LNAV;
other records skipped to the next record header), andparse_nav_withtakes options.
VALIDATED against the same day's IGS RINEX 3 merged file: every decoded field identical and 0 m
in position. RINEX 2/3 reading is unchanged. -
BeiDou GEO broadcast positions (opt-in):
NavParseOptions::include_beidou_geoand
RinexEphemeris::sv_position_ecef_bds_geo(BDS ICD, −5 deg tilt).parse_navstill skips GEOs.
MODELLED: the pre-registered comparison with a precise orbit is data-gated and not yet run. -
RINEX 4 observation files: a real RINEX 4.00 MGEX observation file is read in tests
(MODELLED, format conformance). -
RTK with LAMBDA on real RINEX (0.36, WP-07 ★):
rtk_real::run_rtk, short-baseline double
differences of dual-frequency GPS code and phase with a float Kalman filter and LAMBDA integer
fixing (ratio test). VALIDATED on the IGS stations WTZR and WTZZ (1.4 m apart) against their
ITRF2020 baseline under pre-registered bars: fixed at every epoch after the first ten minutes,
baseline error median 4.5 mm and 95th percentile 5.5 mm. -
RTKLIB parity of the real-data PPP (RM-31):
xval/ppp-parity/run.shand
tests/ppp_parity_glab_rtklib.rswith pre-registered bars. RTKLIB v2.4.2-p13 PPP on the same
WTZR inputs agrees with Kshana within 5 cm at every epoch of the last two hours (9.7 mm at the
end) on the third registration; the first two runs, recorded as findings, had the solid tide off
and the receiver antenna on RTKLIB's base slot. The single-point parity bar was missed (1.09 m
median against 0.5 m). The gLAB leg has not been run (data-gated). -
Navigation-message encoders beyond LNAV (0.36, WP-07, PRO-40):
gnss_navmsgencodes GPS
CNAV (message types 10, 11, 30; CRC-24Q; continuous rate-1/2 K=7 coding), Galileo I/NAV (word
types 1-5 in even/odd nominal pages; 30x8 interleaver; sync0101100000) and Galileo F/NAV (page
types 1-4; 61x8 interleaver; sync101101110000), with test decoders. MODELLED: round trips and
ICD known answers; a pre-registered check that broadcast Galileo ephemerides lie on the encoder's
LSB grid held (worst 3.6e-3 LSB) but missed its satellite-count bar (15 of 20), recorded. The
encoders are library functions; scene wiring is not part of this change. -
IONEX file reader (0.36, WP-07):
ionex::parse_ionex_filewithIonexFile,IonexHeader,
IonexEpoch,IonexFileMapandIonexTimeInterp: a strict fixed-column IONEX 1.0 reader with
epochs counted from the first map's day,9999read as missing, RMS maps, and the format's
nearest, consecutive and rotated-map time interpolations; slant TEC at the header's shell
height. Checked on a committed synthetic map and, data-gated (fetched at test time with a
pinned SHA-256, not vendored), on a crop of a real CODE GIM: every node exact; interpolation
matches a formula reference to 4e-13 TECU. RTKLIB'siontecagrees to 3.6e-13 TECU at 6885
points (pre-registered B6). VALIDATED on data-gated evidence (owner decision, 2026-10-13): the
test fails on any map other than the SHA-pinned one. -
Hatch carrier smoothing (0.36, WP-07):
hatch::HatchFilterand
pvt::assemble_epoch_precise_smoothed, the divergence-free (ionosphere-free) form in the precise
single-point assembly. MODELLED with a recorded finding: on the IGS station WTZR it lowers the
median single-point error from 1.55 m to 1.12 m and the 95th percentile from 3.47 m to 3.21 m,
short of the pre-registered 40 % reduction (the remaining error is bias-like, not noise). -
Float PPP on real RINEX (0.36, WP-07):
ppp_real::run_ppp, a static float precise point
positioning filter on real dual-frequency GPS code and phase with IGS final products (antenna
offsets and variations, solid Earth tide, phase wind-up, Shapiro delay, estimated wet delay,
cycle-slip detection), withparse_antex_receiver. VALIDATED on the held-out IGS station WTZR
against its ITRF2020 coordinate under pre-registered bars: after 6 h the error is 1.5 mm
horizontal and 2.9 cm vertical, and every epoch of the last two hours is within 10 cm. -
SP3 precise-clock interpolation (0.36, WP-07):
Sp3File::clock_sandclock_nodes
interpolate the tabulated SP3 satellite clocks linearly between the two bracketing valid nodes
(bad-clock sentinel skipped), andClockRinex::from_sp3feeds them toPreciseProducts, so
the precise chain runs from an SP3 alone. VALIDATED against the IGS final 30 s clocks of
2025-02-24 under pre-registered bars: 32 GPS satellites, mid-point RMS 0.121 ns (bar 0.15 ns),
95th percentile 0.265 ns (bar 0.30 ns). -
RAIM beside the clock-holdover figures of merit (CAP-05b): an opt-in
[integrity]section on the
clock kind reports a separateintegrity_fomblock (snapshot RAIM on a preset sky from a stated site:
per-epoch verdict and protection levels, availability; unavailable while GNSS is denied, never
extrapolated). The holdover figures are unchanged; without the section the output is byte-identical.
MODELLED. -
Receiver-trust thresholds calibrated on JammerTest 2024 (CAP-13): a pre-registered calibration of
the C/N0-drop, AGC and jamming-indicator monitors on the public JammerTest 2024 dataset (fetched at run
time, never vendored;examples/jammertest_trust_calibration.rs), shipped only as the opt-in
[monitors] profile = "jammertest-2024-calibrated"(1.5 dB, 3.5 sigma, 29). Held-out: the AGC,
jamming-indicator and union bars pass; the C/N0 false-alarm bar fails (0.023 against 0.02), so the row is
MODELLED (xval/jammertest-trust/RESULTS.md). Defaults and outputs without the profile are unchanged. -
Multi-satellite ARAIM fault hypotheses (CAP-05a): the ADD v4.2 subset rule generalised beyond
pairs (add_v42_subset_plan,add_v42_protection_levels_to_order; order 2 is the existing
MAAST-checked path exactly) and an opt-in advanced RAIM over those hypotheses
(araim_subset_raim), which reports the subset of largest separation when a fault is detected.
Priors needing deeper subsets are refused, never truncated. MODELLED. -
Skyline in receiver trust (FUT-01): a receiver-trust session may state a
[skyline]; satellites
behind it are lost to the sky (sky-blocked,behind_skyline,open_fraction) and their losses are
classedgeometry, neverenvironment; without one, the azimuth-gap fallback and every output
are as before. The crew-training generator gains anatural-degradationevent (a blocked sky, not
interference) and the scenarioscenarios/training/fjord-natural-degradation.toml. The UERE x DOP
Monte Carlo gains the design note's two masked-skyline geometries, pre-registered alone: all 26
checks pass. -
RFC 3161 timestamp verification (CAP-14):
kshana::evidence::tsa::verify_tokenchecks a
timestamp token's signature (RSA PKCS#1 v1.5, RSA-PSS, ECDSA P-256 and P-384), its ESS
signing-certificate binding, the signer's timeStamping usage and its chain to a trusted anchor at
the token's genTime, offline;kshana evidence verify --tsa-ca <anchors.pem>applies it to a pack's
timestamp.tsr(opt-in; without it the report is unchanged). VALIDATED, narrowly: all 27
self-generated cases agree with OpenSSL 3.0.13 on the verdict and the genTime, under a comparison
pre-registered before the verifier was written. Revocation is not checked. -
Per-figure verification tiers for every kind (RM-04): every scenario kind's result now
ends with afigure_tiersblock naming its headline figures, the verification-matrix row
that owns each one and the tier read from that row (figures no row owns are listed as
untiered). The block is appended after the run and strips back to the previous document
byte for byte (api::without_figure_tiers).lunar-station-coveragehas nofigure_tiers
until a new dated prediction replaces its frozen one;leo-ppp,solar-system,sweepand
sweep-ndhave no scalar headline figure, andlunar-attack-surfacewrites compact JSON. Campaign members keep their untiered documents. -
Quantum preset
quantum-cai-coast(CAP-08c): a bundled inertial scenario in which a
cold-atom interferometer accelerometer, specified by its interferometer physics
([accel_quantum.cai]with a stated platform vibration level), coasts against a
navigation-grade quartz accelerometer through a 110-minute GNSS outage. MODELLED. -
Greenhall exact equivalent degrees of freedom (CAP-03):
allan::edf_greenhall, the full
Greenhall and Riley (2003) algorithm (difference orders 1 to 3, noise exponents +2 to -4,
overlapping and non-overlapping, modified and unmodified, with the large-J tables), with ADEV,
MDEV and HDEV wrappers. VALIDATED under a pre-registered comparison with allantools 2024.6:
4 876 grid points within 6.0e-13 relative; allantools fails at 32 further points, which are
recorded without an oracle value. The confidence intervals ofassurance::uncertaintyare
unchanged bit for bit. -
Cold-atom interferometer error terms (RM-26/27/28): Raman laser-phase noise (with the
retro-reflection delay term), the wavefront-curvature bias of a thermal cloud (pure defocus,
with a detection radius) and fringe-ambiguity resolution (classical-aided and two
interrogation times), as library functions insrc/inertial/cai_error_terms.rs, each closed
form checked against quadrature or Monte Carlo. MODELLED; not yet reachable from a scenario
kind (docs/QUANTUM-MODELS.md). -
Clock-log formats (RM-03):
src/clock_log.rsreads RINEX clock files, TimeLab-style
ASCII phase or frequency logs and TAPR TICC counter logs into one normalised phase series
(tau0 stated or found, gaps counted, a gap policy). Opt-in: a telecom-timing series can name
a log ([series.clock_log]), and a clock scenario's[clock_log]table adds ameasured
block (ADEV, TDEV, MTIE). Without the table both kinds are byte-identical
(docs/CLOCK-LOG-FORMATS.md). A pre-registered comparison on three
synthetic logs met its phase and wiring bars; the estimators agree with allantools within
2.5e-14 wherever it evaluates, and it does not evaluate the whole-record MTIE point
(recorded). MODELLED. -
Frequency-synchronisation wander masks (RM-01): the
telecom-timingkind gains seven
constant-temperature MTIE and TDEV wander-generation masks, ITU-T G.811 PRC, G.812 Types I to
III, G.8262 EEC Options 1 and 2 and G.8262.1 eEEC, with the power-law pieces the tables write,
and the reference scenarioscenarios/telecom-synce-eec.toml(a free-running OCXO against the
EEC masks: it fails at 1 000 s, where a loop must do the work). Every row was re-read against
the itu.int text; G.812 (06/2004) is superseded by a pre-published 08/2026 edition that could
not be read. MODELLED (a transcription); existing masks and outputs are unchanged. -
Filter-prior guidance for the
gnss-inskind (0.36, WP-09, RM-21 finding): the pack's
default filter prior suits a tactical-grade IMU. With a navigation-grade IMU, the default can
make the fused coast worse than free INS: 52 m against 11 m on a 70 m/s aircraft profile.
Set[imu_*.filter_prior]to match the sensor's grade, as the new maritime, rail and
aviation packs do. No bundled scenario pairs a navigation-grade IMU with the default prior:
every one that uses the default has residual accelerometer biases of at least 0.015 m/s².
Documentation only; no output changes. -
Test-bench export checked with a simulator's own readers (0.36, WP-09, CAP-16): the
kshana bench-exportmotion files of 8 bundled scenarios were read back by gps-sdr-sim's
NMEA, latitude/longitude/height and Earth-fixed motion readers (MIT, commit 28ca29a6, used as
a separate tool throughxval/testbench-gpssdrsim/; no signal function is called). They
return the exported trajectory to 1.2 mm, 0.15 mm and exactly. FINDING: that simulator's
Earth-fixed reader cannot take the full-width CSV, because its 100-character line buffer
splits each row.docs/TEST-BENCH.mdnow gives the column cuts to use and the simulator's
300 s cap. VALIDATED (new ledger row); the export's bytes are unchanged. -
GNSS/INS on the KF-GINS dataset (0.36, WP-09, FUT-17):
fusion::kfginsloaders and runner
and the lever-arm measurement model in the closed-loop EKF. On the KF-GINS demo dataset
(fetched at test time, not vendored) Kshana's horizontal RMS against the reference trajectory is
1.09 cm against KF-GINS's own 1.11 cm, and every pre-registered bar held. VALIDATED on
data-gated evidence (owner decision, 2026-10-13): the dataset is fetched with SHA-256 pins and
the reproduce path is inxval/kfgins/PREREGISTRATION.md. -
Coupled PNT filter in the fusion pack (0.36, WP-09, FUT-16): the
fusionkind takes an
optional[coupled]table that runs one stacked filter on pseudoranges with an accelerometer
bias state (fusion::coupled::CoupledPntBiasFilter), andfusion::coupled::CoupledPnt3dis
the 3-D 8-state version. New bundled scenariofusion-pnt-coupled: with biased
accelerometers the coupled mode holds the quantum suite's position to 18.7 m (95th percentile)
with full integrity, where the decoupled filters reach 430 m. Without the table bothfusion
andhybridare unchanged (pinned);hybridrefuses the table. MODELLED. -
Maritime, rail and aviation GNSS/INS packs (0.36, WP-09, RM-21): bundled scenarios
gnss-ins-maritime,gnss-ins-railandgnss-ins-aviationon the trajectory library, and an
optional[tight]table for thegnss-inskind that fuses pseudoranges from an SGP4 Walker
constellation (all satellites when nominal, the highest few when degraded). On the rail pack
tight coupling keeps the navigation-grade solution at 5.3 m through a cutting and tunnel,
against 12.9 m loosely coupled. Without[tight]the pack is unchanged (pinned). MODELLED. -
Carrier-phase tight coupling (0.36, WP-09, RM-25 / FUT-17):
fusion::carrier_tc, an
error-state EKF with an explicit two-state receiver clock and float carrier ambiguities, driven
by an SGP4 Walker constellation with Klobuchar and Saastamoinen corrections in the measurement
loop, time-differenced-carrier slip detection and ambiguity re-initialisation, and a code-only
run on the same measurements for comparison. MODELLED: on a figure-8 drive the carrier solution
is 2.5 cm against 47 cm code-only, and NIS/NEES are consistent over seeds. -
GNSS/INS trajectory library (0.36, WP-09, RM-24): the
gnss-inskind takes an optional
[trajectory]table withprofile = "straight" | "figure8" | "stop_go" | "aircraft" | "uas" | "csv". The profiles command Euler rates and along-track acceleration from the truth state and
derive the true IMU from rigid-body kinematics (coordinated turns, climbs, Coriolis);csv
imports a truth trajectory (t, lat, lon, alt, vn, ve, vd, roll, pitch, yaw) and solves the IMU
that reproduces it. Without the table the pack flies its historical drive, bit for bit (pinned).
MODELLED. -
Latitude/longitude DEM tiles for the lunar horizon:
[terrain] dem_tile_pathreads a
realdata::lola_demtext tile (heights above the mean sphere on a latitude/longitude grid) in
place of the polar stereographicdem_path, for bothlunar-station-coverageand
moonlight-service-volume; the report echoes the tile (terrain.dem_tile, with its SHA-256).
realdata::lola_demis now wired into the horizon ray march. A tile resampled from the vendored
crop gives the station the crop's horizon to 0.22 deg. MODELLED. -
Terrain at the service-volume export site: with a
terrainsection, each row of the
moonlight-service-volumeper-satellite geometry export carriesclears_terrain(the satellite
clears the flat mask and the export site's horizon at its azimuth), the report carries the site's
horizon (export_site_terrain), and the runtime CSV gains the column. Without terrain the export
is unchanged. MODELLED. -
Headline fields from the engine (0.36, STUDIO-06):
kshana::api::HEADLINE_FIELDS,
field_importance_json()and the WebAssemblyfield_importance()state, for twelve kinds, which
fields of a scenario answer its question, best first, as dotted paths (jammers[].eirp_dbw). The
Studio reads them ahead of its own ranking. Display metadata only: the engine never reads it, no
scenario file needs it, no result depends on it.tests/field_importance.rsfails if a kind is not
dispatchable or a path is found in none of the kind's bundled scenarios (it caught two paths that
the Studio's flattened ids had spelled without the array brackets). -
Kind-to-module table from the engine (0.36, STUDIO-05):
kshana::api::KIND_MODULES,
kind_modules_json()and the WebAssemblykind_modules()give, for each scenario kind whose
verification-matrix rows name another module than its own, the module names. The Studio's
capability map used its own hand-written copy;tests/kind_modules.rsfails if a kind in the table
is not dispatchable or a module appears in no matrix row. No output changed. -
Short IQ run in the browser build (0.36):
iq_signals()andiq_run_short(fs_hz, duration_s, signal, ids, dopplers_hz, cn0_dbhz, seed)in the WebAssembly module build a synthetic scene,
acquire each satellite and track the acquired ones, all in memory, bounded to 0.25 s, 1 000 000
samples and 8 satellites. The scene builder and the signal lookup by name moved out of the native-only
iq::cli(toiq::scene::syntheticandiq::signals::by_name) so both builds share them; the
command line's output is unchanged. The native test andweb/smoke.mjscompare the same request
against one golden (tests/fixtures/iq_short_golden.json), byte for byte: since the portable-maths change
(STUDIO-15, below) the browser build and the native build give the same bits. -
Typed Python and TypeScript APIs (CAP-34):
kshana.pyitypes the closed
vocabularies (ScenarioKind, every kind name;ErrorKind;ProvenanceClass) and the
KindMetaandFieldUnitdictionaries;scenario_kinds()anderror_kind()return
them. The WebAssembly package addsrun_typed(one run as a typedRunOutputobject
with the parsed document asdata) andscenario_kinds(the parsedKindMeta[]), and
itskshana.d.tscarries the same vocabulary;error_kindis typedErrorKind | "".
tests/typed_api_sync.rskeeps both lists equal to the engine's. -
Units for every
sweep-ndcolumn (FUT-28): asweep-ndresult now carries
key_unitsandmetric_units, the unit of each swept key and each recorded metric, read
from the swept pack's own units table. An axis may declareunitfor a key the pack
does not echo; a declared unit that contradicts the pack's is refused, and a unit that
neither gives isnull, never guessed.points[].coords[]andpoints[].metrics[]are
described, so every one of the 76 kinds is now fully covered by the field-units gate
(uncovered-kind ceiling 1 -> 0). -
NumPy arrays from
RunOutput(FUT-26): the PythonRunOutputgainsarray(path),
columns(path)andseries(), which returnnumpy.ndarrays for a numeric list, the
numeric columns of a list of records, or every record series in the document. Each array
equalsnumpy.asarrayof the same values fromdata()(dtype and bits). NumPy is
imported only when one of them is called and stays out of the runtime requirements.
Typed inkshana.pyiand checked by stubtest. -
Campaign optimiser (CAP-32): the
campaignkind's new, optional[optimize]section
searches one to three scenario keys inside stated bounds for the best value of one metric:
minimise, maximise, or hit a target. It searches exhaustively on a grid, or with a
deterministic bounded Nelder–Mead simplex. Every evaluation is a real run of the named kind
and is listed in the result'strace. Example:scenarios/campaign-optimize-link-budget.toml.
The bars were pre-registered (docs/design/evidence/cap32-optimize/):- the Rosenbrock function is met (f* = 1.7e-20);
- a closed-form 1-D target solve is met (6e-15 relative);
- the grid is bit-equal to an independent brute force;
- the simplex does at least as well as a 41 × 41 grid in 177 runs.
Round 1's 1-D bar is recorded NOT MET, because the pre-registered box excluded the
solution. MODELLED; ledger row "Bounded optimisation of one metric over campaign
parameters". Additive: campaigns without the section are unchanged. -
Calibration ensembles (CAP-10): three single-seed checks re-run over 200 independent
seeds against pre-registered bars, indocs/CALIBRATION-ENSEMBLES.md
(generated bygen_calibration_ensembles; per-seed values indocs/data/calibration-ensembles.csv).- The campaign Monte Carlo bootstrap 95 % interval covers the true mean on 192 of 200
ensembles, and its chi-square check passes on 196. - The single-seed percentile tolerance passes on only 183 of 200: NOT MET, as predicted.
It is about a 2-sigma bar, recorded and not loosened. - The white-FM ADEV calibrations pass on every seed, with their worst error far inside
tolerance.
MODELLED.
- The campaign Monte Carlo bootstrap 95 % interval covers the true mean on 192 of 200
-
Built-in kinds through the
Scenariotrait (FUT-27, in progress): theBuiltinKindtrait
(api::BuiltinKind, with a per-figure tier hook andappend_figure_tiers), and the first
kinds moved onto it (inertial,spoof,passes,link-budget,jamming). The output is
unchanged: a whole-catalogue manifest of every output's SHA-256 is identical before and after
(tests/scenario_output_manifest.rs). -
Playground no-fetch check (RM-07):
tests/playground_no_fetch.rschecks three things:- every Studio library scenario is in the browser bundle;
- the bundle is byte-identical to
scenarios/; - every browser scenario runs from an empty working directory.
-
Alt-PNT maps: real gravity field, map-error Monte Carlo, full-resolution SRTM (0.36,
WP-10). Thegravity-mapkind takes an optional[reference]: a gravity anomaly
synthesised from the bundled EGM2008 coefficients (or an ICGEM.gfcfile via
gfc_path, native builds only). That synthesis matches the ICGEM Calculation Service to
about 1e-8 mGal over 794 nodes in two regions, against pre-registered bars of 0.005 mGal
RMS and 0.02 mGal max (VALIDATED; ledger row "Gravity-anomaly synthesis from a
spherical-harmonic model"). The new bundled scenariogps-denied-gravity-nav-egm2008runs
the 60-minute benchmark on that real degree-70 field. Pre-registered outcome: matched
error 3106 m, so the < 500 m bar is NOT MET, as predicted (degree 70 is too smooth), and
the tenfold cut on the drift is MET (MODELLED). An optional[map_error_mc]reruns the
benchmark over seeded, Gaussian-correlated map errors and reports the matched-error
distribution (bundled scenariogravity-map-error-mc). Exploratory and not
pre-registered: with a 2 mGal map error correlated over 0.2 deg (the size the matcher's own
sigma budgets), 1 of 20 realisations stays within 500 m (median 2.9 km, p95 14.7 km),
against 144 m with a perfect map. The synthetic benchmark's sub-500 m fix assumes the map is
exact. Without either option the document is byte-identical to earlier releases.
combined-altpnttakes an optional[magnetic_grid](csv_path,column) that reads the
magnetic channel from an EMAG2v3-format CSV. EMAG2 is not vendored because its terms are
not explicit:tools/fetch_emag2_tile.pyfetches it with a pinned SHA-256 and crops a box.
No navigation outcome is claimed on the real EMAG2 grid.
Joint map estimation (terrain SLAM, RM-18):altpnt::sequential::run_sequential_trn_on_map
flies the sequential terrain navigator over a stored map that differs from the truth, either
trusting it or estimating a per-cell map correction with a FastSLAM-style Rao-Blackwellised
filter. Pre-registered against known-map SITAN on a 15 m correlated map error over 20 seeds:
a 1.5 % mean gain, better in 10 of 20, so both improvement bars are NOT MET (10 % and 12 of
20), with no harm on a perfect map (MET). A single pass without revisits does not let the
map be learned (xval/terrain-slam-rbpf/, MODELLED).terrain-slamdocuments are
byte-identical.
The real-SRTM tests now run in CI on two committed full-resolution crops; all
pre-registered bars are met, with the nearest node equal to GDAL 3.8.4 at 8 of 8 points
(xval/srtm-ungate/). -
Interoperability, WP-08 (0.36; each item pre-registered and committed alone before its
oracle run, each a new VALIDATED ledger row except the telemetry item, which widens an
existing row):- CCSDS Orbit Data Messages (
src/odm.rs): OPM 3.0/2.0, OMM 3.0/2.0 and OCM 3.0 (TRAJ,
PHYS, COV, MAN, USER blocks) written and read in KVN and NDM/XML, and a full-precision OMM
from an SGP4 element set (OmmMessage::from_omm_file);OmmFile::to_omm_kvnoutput is
unchanged. Against Orekit 13.1.8 both ways: 27 of 30 Kshana messages parse in Orekit and
Kshana reads every Orekit-written one, values within 1e-14 relative and epochs within
1e-9 s; four findings pinned (Orekit requiresELEMENT_SET_NO, refusesCOMMENTin XML
user-defined parameters, will not writeBTERMwithoutBSTAR, and reads OCM relative
epochs under TDB in SI seconds). The legacy OMM writer'sMETA_STARTis refused by Orekit
(recorded). Row "CCSDS Orbit Data Messages (OPM, OMM, OCM)". - CCSDS Attitude Data Messages (
src/adm.rs,src/ndm_xml.rs): AEM 2.0/1.0 and APM 2.0
in KVN and XML against Orekit 13.1.8, every bar met; Orekit's APM writer refuses a maneuver
withoutMAN_DELTA_MASS(pinned). Row "CCSDS Attitude Data Messages (AEM, APM)". - SPICE SPK export (
src/spk_writer.rs): SPK types 9 and 13 writer and reader and OEM to
SPK export; against CSPICE N0067 the kernels are byte-identical to SPICE's and evaluate
bit-identically. Row "SPICE SPK export (types 9 and 13)". - SPICE SPK types 3 and 21 (
src/spk_writer.rs): type 3 (Chebyshev) and type 21
(extended difference lines) segments written from given records and evaluated as SPICE
does; against CSPICE N0067 the kernels are byte-identical to SPICE's and every evaluation
agrees bit for bit both ways. Row "SPICE SPK types 3 and 21 (Chebyshev, extended
difference lines)". - Space Packet extensions (
src/space_packet.rs): secondary header with CUC/CDS time codes,
packet error control, segmentation and stream checks, against ccsdspy 2.0.1, spacepackets-rs
0.18.0, spacepackets-py 0.32.0 and CPythoncrc_hqx, zero mismatches. Row "CCSDS Space
Packet secondary header, packet error control and segmentation". - Telemetry schema 1.3 through real consumers: a synthetic schema-1.3 stream's Prometheus
exposition and OTLP/HTTP JSON, including the geometry series and theclasslabel, read
exactly by prometheus_client and the OTLP proto, accepted by promtool 3.5.0check metrics
with no lint problem, and passed unchanged through the OpenTelemetry Collector contrib
0.135.0 (data-gated: tools fetched with pinned SHA-256, outputs committed).
- CCSDS Orbit Data Messages (
-
Mars orbit determination, run 2 (0.36, WP-14, CAP-30):
mars_od::fit_dampedscales the
normal matrix by its diagonal and damps the step (Levenberg-Marquardt), after the registered
Gauss-Newton fit failed on a matrix whose raw diagonal spans eight orders of magnitude.
Registered separately (xval/mro-od/PREREGISTRATION-RUN2.md, same arc, bars and editing). On
the scored MRO passes the orbit is within 0.36 km RMS (0.53 km maximum) of the MRO navigation
reconstruction, bars met. The post-fit Doppler is 48.6 mHz against the 30 mHz bar, not met
(finding pinned). A scoring-only amendment, R2-A1, is disclosed indocs/revisions. The row
stays MODELLED. -
Mars ramped 2-way Doppler model and orbit determination (0.36, WP-14, CAP-30 part,
stage 2):mars_odpredicts DSN ramped X-band 2-way Doppler from ODF ramp tables (light
time, Shapiro, troposphere, station time, DSN stations via the CIO chain and IERS EOP) and
fits a Mars orbiter (GMM-3, third bodies, SRP, drag).naif_kernelreads SPK type 1 (checked
against CSPICE) andgravity_sh::from_shadrreads PDS SHADR fields. Pre-registered against
the MRO navigation reconstruction on real MRO passes (data-gated,scripts/fetch_mro_od.sh,
monthly CI): the predicted Doppler matches the observations to 12.5 mHz (bar 30 mHz, met);
the registered orbit fit failed on the scored arc and produced no orbit (finding pinned).
The row stays MODELLED. -
DSN Orbit Data File reader (0.36, WP-14, CAP-30 part):
odf::parse_odfreads TRK-2-18
ODFs. On a real Mars Reconnaissance Orbiter ODF (NASA PDS, vendored), the reader matches the
PDS label's record count, group positions, stations and times, and all its data types are
known. The label's end-of-file table is one row short of the file, a label inconsistency
recorded as a finding. MODELLED. Orbit determination on these data is not yet done. -
TWSTFT budget and time-transfer stability: the
time-transfer-budgetkind (0.36, WP-14,
FUT-19).- Two-way equation:
twstft_budgetsolves it for T_A − T_B with station equipment
delays, the Ku-band ionospheric up/down asymmetry, the transponder delay difference and the
Sagnac terms, and recovers the forward model to 1e-15 s. - Uncertainty budget: combined in quadrature, with the link TDEV as the type A term.
- Link noise: the two-way series gains a flicker phase-noise floor, a diurnal
non-reciprocity and a constant reciprocity residual. The white-jittertimetransferpack is
unchanged. - Real series: the stability of supplied BIPM Circular T,
MJD,nsCSV or IGS station-clock
series.
MODELLED. New bundled scenario
time-transfer-budget. The PPP time solution is VALIDATED
separately against the IGS station clock on the GNSS real-data branch. - Two-way equation:
-
Entry heating, EO radiometry and ascent losses (0.36, WP-14, CAP-28): three new kinds.
entry-heating: a 3-DoF point-mass entry over a rotating Earth or Mars with Sutton-Graves
stagnation-point heating.eo-radiometry: imager SNR, MTF at Nyquist, relative edge response and GIQE 4 NIIRS.ascent-losses: a multi-stage gravity-turn ascent with gravity, drag, steering and
back-pressure losses, checked by the exact ideal-dv-minus-losses identity.
The entry's peak deceleration was checked against the published Stardust nominal under a
pre-registered bar: 35.44 g against 33.4 g (+6.1 %, bar 10 %). That row is VALIDATED.
Heating, radiometry and ascent are MODELLED. Also new:reentry::us76_pressure, bundled
scenariosentry-heating-stardust,eo-radiometryandascent-losses, and
docs/ENTRY-EO-ASCENT.md. -
Attitude control with reaction wheels: the
aocskind (0.36, WP-14, CAP-21). An
inertial-pointing slew of a rigid hub with three or more balanced reaction wheels under MRP
feedback, with minimum-norm wheel torques clipped to each motor's limit (aocs). It can also
close the loop on a multiplicative EKF from a simulated gyro and star tracker (aocs_mekf).
The true-state loop was compared with Basilisk 2.9.1 on three cases pre-registered before the
code existed (three orthogonal wheels, a four-wheel pyramid, a saturating case). Every MRP
component, body rate, wheel speed and motor torque agreed at every sample, worst 2.4e-14 in
sigma and 2.9e-11 rad/s in wheel speed. This row is VALIDATED; the MEKF is MODELLED (NEES
consistency). New bundled scenariosaocs-slewandaocs-slew-mekf, anddocs/AOCS.md. -
Ground-contact scheduling and DVB-S2 ACM (0.36, WP-14, CAP-22/23): two new scenario
kinds.acm-linkgives, for one satellite over one station, each pass's Es/N0 and the DVB-S2
MODCOD chosen along it (ETSI EN 302 307-1 Table 13 thresholds plus a margin). It also gives
the data volume ACM moves against the constant-coding baseline.contact-scheduledoes the
same for several satellites over several stations and builds a conflict-free contact plan
(antenna turnaround, one station per satellite, priority-weighted delivery capped at each
backlog). It reports a marginal-value greedy plan, a first-come baseline and the exact optimum
on small sets, with the greedy's optimality gap. Access windows come from the VALIDATED
apparent pass predictor. New bundled scenariosacm-linkandcontact-schedule(illustrative
inputs) anddocs/CONTACT-SCHEDULING.md. MODELLED (two new ledger rows). -
Starlink Doppler positioning against a published experiment (0.36, WP-14, CAP-11 part,
MODELLED). This is a pre-registered proxy comparison of theleo-pvtDoppler mode with the
starlink-soppreset against the data-set-3 percentiles of "Unveiling Starlink for PNT"
(NAVIGATION 72(1), 2025, doi:10.33012/navi.685). The model used 3 satellites (a 47 deg mask
chosen by a fixed rule), no height aid and a 30 Hz Doppler sigma, over 200 seeds. Its
90th-percentile 3-D error is 92.2 m at 20 s and 870 m at 8 s, against the published 2 m and
10 m, so both factor-of-two bars are NOT MET. The model is pessimistic, not optimistic as
predicted: three fixed satellites give far less geometric diversity than the experiment's
63 satellites over 10 minutes with channel switching every 15 s. Both misses are asserted
(tests/starlink_doppler_published_proxy.rs). -
Impulsive transfer optimiser on DE440 (0.36, WP-14, CAP-20c/d):
maneuver::optimise_transfer_kernel
finds the departure and arrival dates of a single-revolution ballistic two-impulse transfer that
minimise departure C3 or arrival V-infinity within Type I or Type II. It searches a 2-day grid,
then refines with 2-D Nelder-Mead, on heliocentric states from the DE440 kernel; it builds on
porkchop_kernel. The comparison was pre-registered (bf0439e) against an independent SPICE +
pykep 3.0.1 + SciPy 1.18.1 optimiser on the 2026 Earth-to-Mars opportunity: all four
optimisations agree to 8.7e-13 relative, with dates within 1e-5 day (VALIDATED, new ledger row).
As a secondary published reference, NASA/TM-2010-216764 (the 2026-2045 Earth-to-Mars handbook)
is matched for the Type II minimum C3 (9.182 vs 9.144) and the Type II minimum arrival V-infinity
(2.564 vs 2.565). Its Type I minimum C3 is not matched: 10.535 vs 11.11 (-5.18 %), where the
library oracle agrees with Kshana. That miss is recorded and asserted. -
Lunar reference-station study (0.36, prompted by published material on a planned lunar
south-pole reference station; sources indocs/LUNAR-REFERENCE-STATION.md):
thelunar-station-coveragekind, a Monte Carlo sweep of the lunar differential-PNT model over
a south-polar user grid and a day of epochs, giving per point and over the region the
percentile corrected and uncorrected error, availability with reasons, coverage against a
stated threshold, protection levels, the coverage radius per azimuth and the correction-link
budget. It reduces exactly tolunar-differential-pntat one point, epoch and trial; its
arithmetic was re-computed by numpy under a pre-registered bar (positions and percentiles
met; the station corrections missed theirs by double-precision rounding, recorded). MODELLED;
ledger row "Lunar reference-station coverage and accuracy map". Not affiliated with or
endorsed by any agency; no statement about the performance of any real system. -
Pre-registered prediction: lunar reference-station coverage (not a validation; MODELLED).
docs/predictions/lunar-reference-station.md
freezes, at commit c05dac1, thelunar-station-coverageresults under the publicly stated
assumptions, with SHA-256 hashes of the inputs and results and a scoring protocol fixed now
(factor 2 for accuracies and protection levels, 10 percentage points for availability and
coverage; a changed assumption gets a new dated prediction, never an edit; a miss is published
as prominently as a hit). Every figure is dominated by the four-satellite constellation's
unpublished element set, which is ILLUSTRATIVE, and says so.tests/prediction_frozen.rsguards
it;docs/VALIDATION.mdgains a "Pre-registered predictions" section. No ledger row. -
Lunar reference-station extensions (0.36; the station study's limitations 2 and 3 and the
sources' solution style), each opt-in onlunar-station-coverageand absent by default, so every
existing document, the frozen prediction included, is unchanged:
[receiver](multipatha + b exp(-el / el0)at the user and the station, the station's
optionally Gauss-Markov over epochs, DLL code noise from C/N0, inter-channel biases; values
ILLUSTRATIVE on the Moon);orbit_err_growth_m_per_s(linear growth of the broadcast orbit error
over the correction latency, ILLUSTRATIVE);[solution](height constraint, a three-satellite
minimum with it, a static-user window over epochs). Two addenda to the published-claims
comparison, each pre-registered alone before its run, sit beside the unedited recorded run: K5
with growth fails at every rate; with the sources' solution style K1 to K5 fail and K6 passes
(1 of 6), the R300 corrected p95 3-D error falling from 280.6 m to 2.11 m. MODELLED; three ledger
rows. -
Lunar terrain horizon masks (0.36; the first limitation of the lunar reference-station
study): an optional[terrain]section onlunar-station-coverage(the station and every user
point) andmoonlight-service-volume(every grid point) gives each site an azimuth-dependent
horizon in the 0.36 skyline table format, from a stated profile labelled with its source
(station_horizon,user_horizon) and/or a ray march over a polar DEM (dem_path) along great
circles of the lunar sphere with exact curvature. Vendors a 0.98 MB crop of the public-domain
LRO LOLALDEM_75S_240Msouth-polar DEM, 5 x 5 block-mean resampled to 1200 m
(data/lunar-dem/, with its NOTICE, SHA-256 andscripts/make_lola_south_polar_crop.py), and
the repository-only scenarioscenarios/lunar-station-coverage-terrain.toml. Checked against
closed-form horizons (bare sphere, crater in a plateau, rasterised cone); MODELLED, ledger row
"Lunar terrain horizon masks from a stated profile or a polar DEM". Without the section every
document, units block included, is byte-identical. -
Geometry-aware trust (0.36 geometry design note, section 4). Every
receiver-trustepoch,
static or vessel, batch or live, carries its satellite geometry:good,degraded(reasons
few-satellites,high-pdop,high-hdop, and informationalsky-blocked) orunknownwhere
the log gives no satellite geometry, with HDOP, PDOP, VDOP and the satellites used. It is derived
from the elevations and azimuths the receiver reports (NMEA GSV, UBX NAV-SAT) through the existing
orbit::dopkernel, or taken from NMEA GSA. Every epoch also carries a label,good,fairor
poor, always reported and never scored, and a note with the HDOP, PDOP, VDOP, satellite count
and signal loss in words. A vessel's score gains ageometrymonitor that deducts nothing unless
the geometry is poor (HDOP above 4, PDOP above 6 or fewer than 5 satellites used), then ramps to
a cap of 15 points; geometry alone reaches the degraded band at most, never the untrusted one
(no alarm). Every deduction gets a class,geometry,environmentorinconsistency, so a
poor sky does not read as an attack.loss-of-lockisgeometrywhen the satellites lost were
all below 15 degrees at their last report; a poor-geometry deduction isinconsistencywhen the
HDOP the receiver states disagrees with its stated sky by more than max(1.0, 25 % of the derived
HDOP), so a forged poor sky cannot buy the label. A new[geometry]table states every
threshold with its provenance (HDOP 4, PDOP 6, at least 5 used,
sigma_UERE 1.5 m, an HDOP tolerance of max(1.0, 25 % of the derived HDOP) with the 25 % a
representative choice for receivers with per-constellation clocks or subset solutions,
15 degrees; the IEC 61108-1 and GPS SPS figures as cited, to be checked against the standards'
text). Total signal loss is not labelled as sky: when satellites in view are not tracked and
the DOP of the satellites in view is good (and the untracked ones are not confined to one half of
the horizon), the epoch carriessignal_lossand the geometry deduction isenvironment. A
stated HDOP or PDOP of 99 or more, GGA quality 0 and GSA mode 1 are read as no fix, not as DOP.
The cap (cap_points15), the ramp's end (full_ratio1.5) and the floor (floor, default the
score'sdegraded_min) are in[geometry], as are the label's good levels (HDOP 2, PDOP 3, 6
satellites, representative); the cap in force ismin(cap_points, 100 - floor), so a[score]
table that raisesdegraded_min(a 0.35 one included) lowers the cap instead of being refused,
and a floor belowdegraded_minis refused. MODELLED; advisory software, not type-approved
navigation equipment (IEC 61108, IEC 61162): the operator remains responsible for the navigation
of the vessel. -
The geometry reaches every surface: the result JSON (
epochs[].geometry,classon every
deduction,geometry_model,geometry_summary), the live JSON lines (schema 1.3), the Python and
WebAssemblyreceiver_trust,assess_vessel_logandreceiver_trust_replay, the MCPassess_*
tools, evidence packs (epochs.json,config.json, a geometry lane on the summary page) and
trust-telemetry(Prometheuskshana_trust_geometry*and aclasslabel on
kshana_trust_reason_active, OTLPkshana.geometry.*, CEF custom fieldscs6(geometry status and
classes),cfp3(HDOP) andcfp4(PDOP), checked against both CEF oracle parsers, and LEEF
geoStatus,geoHdop,geoClass). The geometry label is exported too, appended after every
earlier series and field and only for inputs that carry it: Prometheus
kshana_trust_geometry_label{label}andkshana_trust_geometry_vdop, OTLP
kshana.geometry.labelandkshana.geometry.vdop, LEEFgeoLabel,geoPdop,geoVdop,
geoUsed, and CEF a thirdcs6field (the label) andcn1(satellites used); the VDOP is not
in CEF (the floating-point custom fields are taken). Both CEF oracle parsers read the label
cases back as written (amendment 5: 176 lines, 172 parsed by pycef, 176 by Logstash). A stated
HDOP that the stated sky does not explain is flagged on every epoch and counted
(geometry_summary.hdop_mismatch, a line on the evidence summary page), scored only when the
geometry is poor. Signal loss is exported on its own, since the geometry status
staysdegradedwhile it holds: Prometheuskshana_trust_geometry_signal_loss, OTLP
kshana.geometry.signal_loss, LEEFgeoSignalLoss=1; in CEF it shows as theenvironmentclass
incs6. -
orbit::dop_cofactor: the covariance factor(HᵀH)⁻¹behindorbit::dop, factored out of it
with the same arithmetic. -
Ledger row "Position error from geometry: RMS error = sigma_UERE x DOP" (MODELLED,
internal consistency): a seeded Monte Carlo of the engine's least-squares fix on the 8
gnss_lib_py-matched DOP geometries against the covariance factor of the validated DOP kernel,
with tolerances pre-registered before the first run and two negative controls that fail as
required; beside it, an earlier check on four hand-chosen skies with chi-square confidence
intervals, reported as run before the pre-registration. -
Azimuth-dependent skyline masks (
src/skyline.rs,docs/SKYLINE.md,
design note item 1): an elevation cut that depends on azimuth, the pointwise maximum of tables
(each interpolated on its own, wrapped at 360°), a street canyon in closed form (with optional open ends),
sharp-edged sectors and a terrain profile with the Earth's curvature; the effective cut is
max(mask_deg, skyline). Four REPRESENTATIVE presets with stated provenance (city-street,
harbour-approach,fjord,container-terminal), overridable field by field. An optional
[skyline]section ingnss-sim,jamming,leo-pvt(every mode, the polar sweep included),
leo-passandnmea-scenariotraining files, and inorbitandintegritythrough a new
ground-site user,user_site = { lat_deg, lon_deg, height_m }(geodetic look angles on the
rotating Earth, with the geodetic up for the ARAIM horizontal and vertical split; each sample
is one absolute instant,epoch + t, to which element sets, broadcast ephemerides and SP3
files are advanced from their own epochs, dated from the newest satellite epoch when no
epochis stated; an orbitinguserrefuses a skyline). Results gainskyline { source, open_fraction, provenance }and per-samplemasked_by_skyline. Without the section every
scenario is byte-identical to 0.35.0, checked against digests from the release commit. Six
bundled examples:orbit-urban-canyon-skyline,integrity-harbour-approach,
gnss-sim-container-terminal-skyline,jamming-harbour-approach-skyline,
leo-pvt-fjord-skylineand the campaignleo-vertical-autonomous-vehicle-skyline(the
autonomous-vehicle canyon as a flat cut and as a street; the existing campaign is
unchanged). MODELLED. -
Sky geometry functions (
src/geometry.rs, design note item 2):sky_geometry(visible
and masked satellites from a site, a time and constellation presets, two-line element sets
(tle, at most 4 MiB, propagated by the engine's SGP4 to a UTCepoch) or given satellites;
sky_sourceandsky_statementsay whether the sky is representative or the sky the element
sets give at that epoch, not a measurement;tle_skippedlists every unused line with its
reason; input caps of 2000 satellites above the horizon, checked before a system table is
built, and 16 receiver clocks; the
five DOPs; the ENU cofactor and covariance with one clock per system or one in common; the
horizontal error ellipse at one sigma and at a stated confidence),sky_monte_carlo(seeded
least-squares errors, at most 5000),skyline_presets,skyline_profileandskyplot_svg.
orbit::dop_covexposes the covariance behindorbit::dop, which is now its reduction (the
validated scalars are unchanged). Surfaces:kshana skyandkshana skyline, Python and WASM
functions of the same names, and the MCP toolsky_geometry(40 tools), and the notebook
notebooks/geometry.ipynb(with its.mdmirror; run bytests/python/test_notebooks.py).
Every result carries the MODELLED label, the error-model statement and the advisory. -
Ledger: five skyline and sky-geometry rows. VALIDATED, each in a
comparison pre-registered before its oracle ran: Azimuth-dependent skyline mask: visible set,
azimuth and elevation, and DOP under the mask against Orekit 12.2 (TopocentricFrame,
ElevationMask) and gnss_lib_py 1.0.4 (get_dop), worst look-angle and mask differences
1.1e-12° against 1e-9°, identical visible sets, DOP 3.3e-13 relative
(xval/orekit-skyline/); Horizontal error ellipse and per-system cofactor from the geometry
covariance against numpylinalg.invandlinalg.eigh(standard P2), 270 cases, axes 5.1e-13
relative (xval/numpy-ellipse/). MODELLED: the canyon, sector and terrain reductions; the
presets; the sky-geometry composition (preset sky, Monte Carlo cloud). -
With the geometry-trust, skyline and lunar reference-station rows, the ledger has 274 rows:
134 Validated, 136 Modelled, 4 Partner. -
Lunar reference station: station time laboratory (MODELLED). An optional
[time_lab]
block onlunar-time-budget(src/lunar_time_lab.rs). It models a station clock (default the
miniRAFS class, any named class, or a device card fitted to stated stability points) steered to
LTC, then free-running or re-steered every N seconds. It emits the time-error curve x(t), the time
to 1, 10 and 100 ns (bisection beside the closed form) and the dominant term per horizon, all with
units. The steer residual is ILLUSTRATIVE. The LTC rate is not re-derived and keeps its own
VALIDATED row. The nanosecond-level target of the public ICG WG-B Lunar PNT material (June 2024)
is quoted as a source statement, not a claim. The clock ordering optical < PHM < RAFS < miniRAFS
holds from 1 s to 7e5 s, and the test pins the crossing of the PHM and RAFS curves near 7.5e5 s.
That crossing is a property of the single-noise-type clock models (pure flicker FM against pure
white FM), not a physical result. Without the block, the report, its table and its summary are
unchanged. Example:scenarios/lunar-station-time-lab.toml. Adds one ledger row, labelled
MODELLED. -
Lunar reference station: station geodesy (MODELLED). Optional
extra_reflectorsand
extra_beaconsinputs onlunar-llr-datumadd astation_geodesyblock. The five-array datum
information from the archived laser-ranging slice is combined with what an added retroreflector
and an added VLBI beacon at stated sites would contribute. The reflector is ranged at the archived
epochs it can see, with their archived weights. The beacon uses thelunar-frame-campaign
network and schedule, with the stations fixed. The block gives each Helmert 1-sigma before and
after, and their ratio. These are Cramer-Rao bounds for a reduced parameter set, on an
ILLUSTRATIVE schedule and site. Absent or empty inputs leave the report byte-identical. Examples:
scenarios/lunar-station-geodesy-baseline.tomland-with-station.toml. Adds one ledger row,
labelled MODELLED. -
Lunar reference station: comparison against published claims (MODELLED). Every numeric claim
in five public sources is listed with its exact wording, page and stated assumptions, and classified
as comparable, partly comparable or not comparable (tests/fixtures/lunar_station_published/CLAIMS.md;
sources with URL, retrieval date, SHA-256 and licence intests/fixtures/lunar_station/SOURCES.md).
The partly comparable claims were scored under six one-sided bars pre-registered before the run
(PREREGISTRATION.md, revision 2, 05bf9a1): 1 of 6 claims within the pre-registered bar
(pre-registered 05bf9a1). Coverage is 0.00 against >= 0.95 on both south-polar circles, and the
corrected p95 is 280.6 m against <= 1.5 m. The cause is the satellites' orbit elements, which no
source publishes: on the ILLUSTRATIVE in-tree elements, four satellites are in common view in 8 % of
epochs, at near-singular geometry. The run does not reproduce the claims on those elements and is not
evidence that the claims are wrong. Pinned intests/lunar_station_published_oracle.rs. One ledger
row, labelled MODELLED with the finding. -
Lunar reference station: clock-file data readiness.
realdata::clk::rinex_clock_seriesnow
states the time system of its epochs. It is theTIME SYSTEM IDvalue, or, when that is
undeclared, the default of the header's satellite system. The result also says whether the system
was declared. The function refuses an unknown time system (a lunar time scale, for example), an
undeclared mixed-system file, and files that disagree, rather than reading them as GPS time. It
ships a synthetic station clock fixture. No corrections-file format is defined, because no public
one exists.
Changed
-
Module docs of
fusion::ukf,fusion::tightly_coupledandfusion::gnss_ins_ekfno longer
call the 17-state UKF navigator, the full-INS tight coupling and the explicit clock state
future work; they point tohybrid_ukf,tightly_coupled17andcarrier_tc. -
RINEX 4 is no longer refused (0.36, WP-07): a RINEX 4 navigation file used to be rejected
on purpose; it is now read (see Added). For observation files of version 4 and later, header
labels are matched in columns 61-80 only, so the new free-text records cannot pass for labels. -
ionex::parse_ionexreads real IONEX files (0.36, WP-07): a full 16-value record fills
columns 61-80, which the reader took for a label, so it returnedNoneon every real IONEX
file. It now reads a record whose label field is numeric as values. No shipped output used it. -
Galileo E1 and E5a received powers to OS SIS ICD Issue 2.1 (revision, 2026-10-14). The
galileo-e1andgalileo-e5aspectrum bands, and the LEO-signal GNSS victims that take
their power from them, now use the minimum received powers of Table 12 of the Galileo OS SIS
ICD Issue 2.1: −157.25 dBW (was −157.0) and −155.25 dBW (was −155.0).- Nine scenario results move: the spectrum examples, the spectrum campaign, three
LEO-resilience verticals and three LEO signal designs. Effective C/N0 moves −0.25 dB and J/S
+0.25 dB on these bands. - Every moved value (1766, old → new) is listed in
docs/revisions/CAP12-galileo-icd21-power-changes.mdand its CSV, with the doc tables
that moved. No ledger row changes status. - Second part, the same day: the LEO-signal victims
galileo-e5b(−155.0 → −155.25 dBW,
Table 12 E5b) andgalileo-e5-altboc(−152.0 → −152.24 dBW). The ICD has no full-band E5
row, so the AltBOC value is the power sum of the Table 12 E5a and E5b totals, built as the
old value was. Three LEO signal-design results move (64 values: these received powers and
the LEO-to-GNSS C/N0 degradation on the two victims), listed in the same revision (§5) and
CSV. Nothing else moves.
- Nine scenario results move: the spectrum examples, the spectrum campaign, three
-
S2 atmospheric tide fix (
tides::atmospheric_tide). Ray (2001)'s S2 air-pressure
harmonics are coefficients of unnormalisedP_nmwith cosine phase lags; they were used as fully
normalised with the lag as the IERS sine lead. They now enter divided byN_nmwith
ε = 90° − ψ; the newtides::s2_air_wavesreproduces model A2 of GFZ STR06/01 Table 5 for all
21 waves (worst 0.0005 cm, 0.05°; VALIDATED). Only tide-enabled outputs move: the Galileo CI fit
0.132 → 0.133 m, Swarm-A components by 1–2 mm (pre-registered inxval/s2-air-tide-fix/). -
Docs only:
propagator::solve_kepler_checked,orbit::solve_keplerandephem::solve_kepler
are documented as legacy solvers, numerically unchanged, pointing tokepler::solve_kepler_markley. -
D9 round 3 (
utck_bound) status. BIPM Circular T 465 came out on 2026-10-12. The
pre-registered prospective score is still not run: issue 465 holds about 285 test values,
and the stopping rule needs 1000, so about issues 465–468. The run also needs the archived
2026-09-30 per-laboratory files, because BIPM has since rewritten 87 of the 113. The ledger
row and the test header say so; the test, its bars and its split are unchanged. -
Docs truth pass (0.36). Roadmap, capability rows, design-doc status lines and module
comments brought in line with the code; no output changes. Items already built but still
listed as open are now marked done: the ITU-T mask checks, the 24 h holdover scenario and
clock-log ingest (telecom timing), the first-minute quickstarts, Lense–Thirring and the
Orekit propagator cross-check, the ITRF-precise GCRS chain, golden and cross-platform
hashes, provenance, multi-fault ARAIM (ADD v4.2), single-satellite FDE, the CRPA library,
the CAI fringe-ambiguity limit, streets-of-coverage sizing and the 3-D orbit view,
rigid-body attitude dynamics, the OMM writer and NAIF kernel reader, DE440 lunar
libration, the run-meaning sentence in the playground,PositioningFom, the 17-state
tightly-coupled navigator and the gravity-map kind. The rows also state precisely what
is still open (for example two-part JD inorbit::Propagator, thesrc/ionex.rsreader
against a real product, and multi-satellite exclusion inraim). The missing
docs/design/evidence/cn0-m2m4/RESULTS.mdis written from a fresh run of its tests (M1–M4
pass), and the partner-facing IQ validation draft has current ledger counts. -
Galileo E1 memory codes are no longer bundled.
data/galileo-os-sis-icd/C7_E1B.txtand
C8_E1C.txt(ICD Annex C.7/C.8) are removed from the repository and packages. The ICD's
terms (quoted inLICENSING.md) let a user store the codes with the source acknowledged and
use them in a product, but allow redistribution only for non-commercial purposes, and Kshana
is also offered under a commercial licence.galileo::e1b_table/e1c_tablenow return a
Resultand read the user's own copy fromKSHANA_GALILEO_E1B/KSHANA_GALILEO_E1C, or, in
a source checkout, fromdata/galileo-os-sis-icd/, where the new
scripts/fetch_galileo_e1_codes.shextracts them from the ICD PDF (hash-checked).
galileo::E1B_TABLE_TEXT/E1C_TABLE_TEXTare removed. Without the tables, the Galileo E1
signals (galileo-e1b,galileo-e1c) return an error that says how to supply them, and the
E1 tests skip with a message. With the tables in place, every E1 output is unchanged. No
scenario, golden or spectrum output uses the code tables. The E5a Annex C.3/C.4 test fixtures
(the LFSR oracle intests/iq_signals.rs) are handled the same way: fetched by the same
script, git-ignored, and the test skips without them. -
Portable maths everywhere (0.36, STUDIO-15): every
f64transcendental in the library, and the
rand_distrnormal, gamma and exponential samplers, now compute through the pure-Rustlibm
(portable_math::PortableFloat,portable_distr), so a native build and the WebAssembly build return
byte-identical result JSON for all 141 bundled scenarios and for the short IQ run. The cost is that the
last bits of results computed with the host's maths library moved: 98 of the 141 bundled scenarios
changed, 43 did not; 5 changed by more than 1e-3 in their largest field, and the rest by less. Every
moved value is listed old -> new in
docs/revisions/STUDIO15-portable-math-rebaseline.md
(with a CSV of all 136,127 moved numbers); no bar, tolerance or threshold changed. Published numbers
that changed:europa-surface-pntavailability with the Earth range 0.719 -> 0.713 and RMS error
86.147 -> 86.499 m (one epoch on the availability edge now resolves the same way everywhere; it
printed 0.725 / 85.800 m on macOS arm64);lunar-joint-od-clockstation error 3.541350 -> 3.536885 m
with VLBI and 2183.558 -> 2184.021 m without;paper/crossover/inertial.json, eightvibration_psd
values by 3 units in the last place (erratum in the technical report); the released
cislunar-observabilitydocument in round-off-floor quantities (eight values beyond six significant
figures, up to 6.8e-5 relative; no rank or threshold moved). The pinned hashes of the byte-identity
guards (skyline_scenarios,geometry_corpus_identity,geometry_trust_no_change) are re-based to a
named "0.36 portable" baseline with the 0.35.0 values kept as the historical record; the frozen lunar
reference-station prediction is untouched and its portable re-run is stored beside it
(docs/predictions/*.portable-0.36.json).tests/lunar_joint_od_numpy_oracle.rs(the validated lunar
joint OD row): fixtures re-exported and the numpy comparison re-run with numpy 2.4.6, the registered
version, script and bar unchanged; all six networks pass
(docs/revisions/STUDIO15-lunar-joint-od-oracle-rerecord.md).
The batch 2-3 work merged after STUDIO-15 is on portable maths too (457 call sites in 62 files); what
that moved is indocs/revisions/STUDIO15-batch4-rebaseline.md:
12 bundled scenarios (largest relative change 5.2e-8, no printed figure moves), 28 skyline guard rows
re-blessed, theiq_campaignM2M4 digest, the legacygnss-instruth hashes, the unobserved-beacon
datum sigma (632.3769 -> 632.3772 m), and two recorded findings (CR3BP DRO closures; CRPA A3, 194 of 456
links). The Greenhall exact edf keeps its validated bar with a double-double logarithm in its kernel
(docs/revisions/STUDIO15-greenhall-edf-portable.md),
and the gps-sdr-sim test-bench oracle was re-run on the moved NMEA exports, every bar holding
(docs/revisions/STUDIO15-testbench-gpssdrsim-rerun.md). -
README: the lunar suite row states each piece's evidence tier (it said all were MODELLED).
-
realdata::clk::rinex_clock_seriesreturnsRinexClockSeries(series, gridding log, time system,
whether it was declared, record type) instead of a(PhaseSeries, GriddingLog)tuple. It now
falls back from satellite (AS) records to receiver/station (AR) records of the requested name. -
No score changes unless the geometry is poor; the label is always reported. With good or fair
geometry (a GPS-only open sky of 8 satellites included) or unknown geometry (the monitor does not
run) every score is as in 0.35:tests/geometry_trust_no_change.rsholds both example sessions'
outputs andtests/geometry_corpus_identity.rsevery committed vessel NMEA corpus (also as
GPS-only open sky) and the crew-training scenarios to the 0.35.0 bytes once the appended keys are
removed; in the two training scenarios whose jamming makes the geometry poor, the poor epochs
differ by the geometry deduction alone. A log with poor geometry loses up to 15 points it did not
lose in 0.35. Intended changes otherwise, all additions: the keys above in the result JSON and
evidence packs (score_model.weightsis unchanged); the live
JSON-lines schema goes from 1.2 to 1.3 (geometryappended,classin each deduction); the
excerpt summary gainsgeometry; telemetry gains geometry series, fields and theclasslabel
only for inputs that carry them, so a 0.35 stream or result renders byte for byte as before.
geometryis the 17th monitor name; it is listed last inmonitors_runwhen it decided (a
vessel whose log gives the geometry; a 0.35 list keeps its order and the summary's monitor count
goes up by one) and has no[score.weights]entry (setting one is refused: its deduction is set in[geometry]). -
tests/fixtures/dop/NOTICEnamed gnss_lib_py's licence Apache-2.0; the 1.0.4 release is MIT. -
The NMEA reader now reads GSA (a short GSA is ignored, not counted as skipped) and keeps GSV
elevations and azimuths, including for satellites listed without a signal; the UBX reader keeps
NAV-SAT elevation and azimuth. C/N0 is read exactly as before. -
Result change: broadband and swept jammers now use Q = 2. The anti-jam equation uses the
textbook spread-spectrum adjustment Q for these jammers, where it used 1. Q = 2 is white noise
over the main lobe with the in-band signal power normalised; it is pinned in the tests to the
closed form fromnavsignal.- Unchanged: CW and narrowband jammers (Q = 1), an unrecognised jammer type (Q = 1, the
most damaging value), andq_override. - Effect: where a broadband or swept jammer acts, effective C/N0 rises by up to 3.0103 dB,
derived J/S thresholds rise by 3.0103 dB, and free-space denial radii shrink by 0.7071. - Changed bundled scenarios: 12 of 144 change, all as pre-registered
(xval/jammer-q/PREREGISTRATION.md, results inxval/jammer-q/RESULTS.md):jamming-demo: C/N0 +3.01 dB;jamming-harbour-approach-skyline:min_tracking6 → 7;lunar-jamming: satellites lost 26 → 0, loss-of-lock standoff median 27.4 → 19.4 km;tracking-loop: loop-dynamics denial radius 75.2 → 53.2 km;- and the campaigns and spectrum cross-checks that contain these.
- Affected consumers: the
jamming,lunar-jamming,tracking-loopand
impairment-evalkinds, the spectrum kinds'jamming_kind_cross_check, the IQ campaign's
MODELLED reference curve and the labfit default Q, and interop scene export.
- Unchanged: CW and narrowband jammers (Q = 1), an unrecognised jammer type (Q = 1, the
-
JetBrains plugin: no deprecated or scheduled-for-removal platform API. The five choice dialogs use
a smallDialogWrapperwith a drop-down in place of the deprecatedMessages.showChooseDialog;
single-file choosers use theFileChooserDescriptorconstructor in place of the deprecated
FileChooserDescriptorFactory.createSingleFileDescriptor(); the animation opens with
BrowserUtil.browse(Path)in place ofbrowse(File), which the 2026.3 platform schedules for
removal. Since-build stays 243. The IntelliJ Plugin Verifier reports Compatible with no
deprecated or scheduled-for-removal usage against 2024.3 (IC-243.21565.193), 2025.2
(IC-252.23892.409), 2026.2.3 (IU-262.10968.63) and 2026.3 EAP (IU-263.6259.32);verifyPlugin
checks 2024.3 by default and any list given inKSHANA_VERIFY_IDES. -
gnss_sim::run_gnss_simandjamming::run_jammingreturnResult: an invalid[skyline]
section is an error when called directly, as it already was throughapi::run_toml, rather
than a silent fall-back to the flat mask. Breaking for Rust callers of these two functions.
Fixed
-
Campaign scorer: a real tracker false lock was never counted (
iq::campaign). The
tracker's false-lock check moves a channel from LOCKED to LOST as it fires. The scorer marked
the channel's next update instead, which was then unlocked and so never counted. A
false-lockevent reported from LOCKED is now one episode at its time, in the run and in
every stated event containing it. One found during pull-in, or repeated by a channel already
LOST, is not a new episode. A new test drives a real false lock: a legitimate GPS L1 C/A
carrier stepping by 1/(2T) while locked (tests/iq_campaign_false_lock.rs).- In the reference campaign of
tests/iq_campaign.rs, only PRN 11 of ramp/agc-3bit changes
(false-lock episodes 0 → 1, 426 and 437 per locked hour). - Its cell DIGESTs are re-pinned.
- The pre-registered scored-results pin stays live, and still reproduces exactly: in its hash
only those six fields, changed by this fix made after registration, are mapped back to
their pre-fix values. The fixed values are pinned on their own. - No bar was changed, and no VALIDATED row, figure or web number moved.
- In the reference campaign of
-
lunar-frame-campaign: a beacon with no observation is now reported (found by MR-05). The
report gainsunobserved_beaconsandwarningonly when some beacon has no observation, and
then the observed beacons' sigmas come from their own information instead of all being null.
The datum is unchanged, and no default, bundled or published output moves
(docs/revisions/BEACON-unobserved-cell-changes.md). -
Lunar principal-axis frame (
lunar_frame::icrf_to_moon_pa, used bylunar_odand the LRO
fit): the DE421 mean-Earth to principal-axis offset ofmoon_080317.tfwas applied in the
wrong direction and with the wrong Euler order, so the analytic PA frame sat about 208″ from
PA instead of on it. It is now transcribed as SPICE's Frames Required Reading defines it and
agrees with SPICEpxform('MOON_ME','MOON_PA')to 2.6e-16 rad; the analytic PA frame is 6″ to
8″ from DE440 PA (pre-registered e1ead19, re-registered be33382 after the first proposed
change missed the 1e-12 bar;docs/design/evidence/fut05b-me-pa-offset-sign/). Published
numbers that move: the LRO fit (tests/agency_lro.rs) raw overlap 53.8 → 39.9 m, dynamic
12.6 → 12.05 m, reduced-dynamic 6.65 → 6.67 m (bars unchanged). The analytic stack now matches
the ANISE DE-grade cross-validation (12.0 / 6.67 m) on both tiers; the earlier dynamic-tier
gap was this error. -
The MCP server refuses
[terrain] dem_hgt(CAP-06b follow-up). The jamming kind's terrain
tile path is now ininline_only::FILE_SOURCE_KEYS. An inline surface refuses a scenario that
names one, with a message that carries neither the path nor an operating-system error. The
file-source guard test now also flags fields ending in_hgt. -
Jd2::from_partswith a fractional day part dropped the fraction in the UTC, TAI, UT1 and
Earth-rotation conversions (one hour of day part turned the Earth 15° wrong); the day part is
now normalised to an integer. No engine caller was affected: every one passes an integer day,
and every shipped output is bit-identical.
Planned
- The geometry from a stated skyline (0.36 item 1, now in the engine as
src/skyline.rs):sky-blockedand
open_fractionfrom the skyline where the input gives one (the azimuth gap as the fallback), a
natural-degradation (fjord or terminal) event innmea-scenario, and the design note's two
masked geometries in the sigma_UERE x DOP row, pre-registered as an addendum. - Geometry for RINEX logs with navigation (from the engine's own fix) and Android logs (from the
Statusrows); both readunknowntoday. Geometry in$PKSHT(version 1 unchanged in 0.36)
and the Signal K and OpenCPN integrations. - A pre-existing gap, not introduced in 0.36: once every satellite's SNR field is empty, no
loss-of-lockor C/N0 deduction fires; total signal loss is visible only through the geometry
monitor'ssignal_loss(classedenvironment). A deduction for it is planned. - A schema 1.3 stream in the external telemetry oracles (Prometheus text-format parser and the
OTLP collector), which cover 1.2 streams only today. - A two-sided obstruction (a canyon or narrow fjord) reads as signal loss rather than a blocked sky
until the skyline is integrated.
Get this release
Download — attached below, prebuilt (no toolchain needed); each artifact carries
SLSA build-provenance (verify with gh attestation verify <file> --repo AshfordeOU/kshana):
kshana— the simulator CLI / engine (Linux x86-64)kshana-aarch64-apple-darwin·kshana-x86_64-apple-darwin— the same CLI for
macOS on Apple silicon and on Intel (releases after 0.27.2)kshana-x86_64-pc-windows-msvc.exe— the same CLI for Windows x86-64 (releases after 0.27.2)kshana-mcp— the Model Context Protocol server (Linux x86-64)kshana-sbom.cdx.json— CycloneDX SBOMkshana-validation-summary.html— the per-release validation summarykshana-channels-sbom.cdx.json— CycloneDX SBOM for the OpenCPN plugin, the Signal K plugin,
the reference-build image and the Grafana dashboard (the engine's crates are inkshana-sbom.cdx.json)kshana_pi-0.36.0-1_ubuntu-wx32-24.04-x86_64.tar.gz·kshana_pi-0.36.0-ubuntu-wx32-x86_64-24.04.xml—
the OpenCPN trust-panel plugin (GPL-3.0-or-later; Ubuntu 24.04, wxWidgets 3.2, x86-64) and its plugin-catalogue
metadata;kshana-opencpn-plugin-0.36.0-source.tar.gzis its source. Install the tarball with
OpenCPN's Import Plugin button. Not yet in OpenCPN's own catalogue.signalk-kshana-trust-0.36.0.tgz— the Signal K server plugin, the same file as the npm packagekshana-grafana-*.json— the Grafana dashboard for the GNSS trust telemetrySHA256SUMS— a SHA-256 (Secure Hash Algorithm) checksum of every file above, so a
download can be checked withsha256sum -c SHA256SUMS --ignore-missing(releases after 0.27.2)
The PyPI wheels, the npm/WASM package and the Docker image below are cross-platform too.
Install from a package registry:
| Channel | Get it |
|---|---|
| crates.io | cargo install kshana · cargo install kshana-mcp |
| PyPI | pip install kshana |
| npm | npm install kshana |
| ghcr.io | docker run -i ghcr.io/ashfordeou/kshana-mcp:0.36.0 |
| npm | signalk-kshana-trust — the Signal K plugin (Signal K app store, keyword signalk-node-server-plugin) |
| ghcr.io | docker pull ghcr.io/ashfordeou/kshana-reference-build:0.36.0 — the trust gate service image (amd64, arm64) |
| MCP registry | io.github.ashfordeOU/kshana-mcp (auto-discovered by MCP clients) |
| JetBrains Marketplace | search "Kshana" in your IDE → Plugins |
No install: run it in your browser at kshana.dev · Cite: DOI 10.5281/zenodo.20528627
Full changelog: CHANGELOG.md · Docs: README