space-data-module-sdk 0.8.16 → 0.8.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/bin/space-data-module.js +24 -6
  2. package/docs/browser-wasmedge-isomorphic.html +1 -1
  3. package/docs/byo-wasm-quickstart.html +1 -1
  4. package/docs/conformance.html +10 -4
  5. package/docs/conformance.md +20 -4
  6. package/docs/events-abi.html +1 -1
  7. package/docs/families/analytics.html +1 -1
  8. package/docs/families/attitude.html +1 -1
  9. package/docs/families/behavior.html +1 -1
  10. package/docs/families/breakup.html +1 -1
  11. package/docs/families/conjunction.html +1 -1
  12. package/docs/families/data-source.html +7 -2
  13. package/docs/families/effects.html +1 -1
  14. package/docs/families/environment.html +1 -1
  15. package/docs/families/estimation.html +56 -54
  16. package/docs/families/estimation.md +155 -125
  17. package/docs/families/gnc.html +1 -1
  18. package/docs/families/maneuver.html +1 -1
  19. package/docs/families/obstruction.html +1 -1
  20. package/docs/families/propagator.html +16 -2
  21. package/docs/families/propulsion.html +1 -1
  22. package/docs/families/reentry.html +1 -1
  23. package/docs/families/rf.html +1 -1
  24. package/docs/families/scheduler.html +1 -1
  25. package/docs/families/sensor.html +1 -1
  26. package/docs/families/signature.html +1 -1
  27. package/docs/flatsql-host-contract.html +1 -1
  28. package/docs/flatsql-streaming-standard.html +1 -1
  29. package/docs/gpu-module-abi.html +1 -1
  30. package/docs/harness-family-matrix.md +7 -7
  31. package/docs/index.html +2 -2
  32. package/docs/isomorphic-pthreads.html +1 -1
  33. package/docs/isomorphic-sdn-runtime-plan.html +1 -1
  34. package/docs/language-runtime-matrix.html +1 -1
  35. package/docs/llms.txt +1 -1
  36. package/docs/module-bundle-runtime-plan.html +1 -1
  37. package/docs/module-publication-standard.html +1 -1
  38. package/docs/node-red-default-node-parity.html +1 -1
  39. package/docs/propagator-abi.md +52 -0
  40. package/docs/protect-and-sign.html +1 -1
  41. package/docs/protocol-installation.html +1 -1
  42. package/docs/provider-access-abi.md +25 -0
  43. package/docs/publication-submission.html +1 -1
  44. package/docs/secrets-capability.html +1 -1
  45. package/docs/testing-harness.html +1 -1
  46. package/docs/tri-runtime-parity-gate.html +1 -1
  47. package/docs/tri-runtime-parity.html +1 -1
  48. package/include/orbpro/orbpro_estimation_abi.h +883 -0
  49. package/include/orbpro/orbpro_propagator_abi.h +58 -0
  50. package/package.json +4 -2
  51. package/schemas/orbpro/Estimation.fbs +244 -0
  52. package/schemas/orbpro/Propagator.fbs +64 -0
  53. package/src/conformance/estimationReference.js +81 -0
  54. package/src/conformance/estimationSuite.js +134 -0
  55. package/src/conformance/index.js +39 -2
  56. package/src/generated/orbpro/propagator-abi.js +21 -0
  57. package/src/generated/orbpro/propagator-abi.ts +50 -0
  58. package/src/standards/sharedCatalog.js +7 -0
  59. package/templates/estimation-module/README.md +24 -0
  60. package/templates/estimation-module/build.mjs +32 -0
  61. package/templates/estimation-module/package.json +12 -0
  62. package/templates/estimation-module/plugin-manifest.json +34 -0
  63. package/templates/estimation-module/src/reference_estimation.cpp +53 -0
@@ -82,6 +82,64 @@ typedef enum {
82
82
  ORBPRO_STATE_HAS_COVARIANCE = 32,
83
83
  } OrbProStateFlags;
84
84
 
85
+ /* ========================================================================= */
86
+ /* ErrorCode */
87
+ /* ========================================================================= */
88
+
89
+ /**
90
+ * The negative return codes every propagator export shares.
91
+ *
92
+ * These were documented in docs/propagator-abi.md and declared NOWHERE, so
93
+ * every module hand-typed them as literals and the contract lived only in
94
+ * prose. A propagator that returns -1 for everything is unconformable — the
95
+ * host cannot tell a bad entity index from an uninitialized module, so it
96
+ * cannot place the failure on the degradation ladder — and the cheapest way
97
+ * to end up there is having no names to return.
98
+ *
99
+ * VALUES ARE FROZEN. New classes are appended downward.
100
+ */
101
+ typedef enum {
102
+ ORBPRO_PROP_OK = 0,
103
+ ORBPRO_PROP_NOT_INITIALIZED = -1, /**< No elements or ephemeris ingested yet. */
104
+ ORBPRO_PROP_BAD_ENTITY_INDEX = -2, /**< Entity index at or beyond the entity count. */
105
+ ORBPRO_PROP_NULL_OUTPUT = -3, /**< The caller passed a null output pointer. */
106
+ ORBPRO_PROP_BAD_INPUT = -4, /**< Malformed or short input buffer. */
107
+ ORBPRO_PROP_NOT_CONVERGED = -5, /**< The solve failed to converge. */
108
+ ORBPRO_PROP_UNPHYSICAL = -6, /**< The elements describe no closed orbit, or the result is not a number. */
109
+ ORBPRO_PROP_UNSUPPORTED_FORMAT = -7, /**< The ephemeris container is not one this module reads, or AUTO could not identify it, or its declared frame has no member on the ReferenceFrame roster. */
110
+ ORBPRO_PROP_EPOCH_OUT_OF_RANGE = -8, /**< The requested epoch lies outside every segment the loaded ephemeris covers. A refusal, never a silent extrapolation: a caller cannot detect an extrapolated state from the value alone. */
111
+ } OrbProPropagatorError;
112
+
113
+ /* ========================================================================= */
114
+ /* EphemerisFormat */
115
+ /* ========================================================================= */
116
+
117
+ /**
118
+ * The container an ephemeris-driven propagator was handed on
119
+ * `plugin_init_ephemeris`. A format is DATA, not identity: one ephemeris
120
+ * provider reads every member below behind this discriminator rather than
121
+ * shipping one artifact per container, because the interpolation, the time
122
+ * scale handling and the frame rotation are shared and splitting them
123
+ * re-creates the five-mirror drift this file exists to end.
124
+ *
125
+ * AUTO is the honest default: every container here is self-identifying in its
126
+ * first bytes (`DAF/SPK ` / `NAIF/DAF`, the OEM version keyword, the STK
127
+ * `stk.v.` banner, the SP3 `#d` line, the Code-500 fixed header block), so a
128
+ * caller that does not know what it fetched says so rather than guessing. A
129
+ * module that cannot identify the bytes returns UNSUPPORTED_FORMAT; it never
130
+ * falls back to a default container.
131
+ *
132
+ * VALUES ARE FROZEN AND ARE NEVER RENUMBERED. New containers are APPENDED.
133
+ */
134
+ typedef enum {
135
+ ORBPRO_EPHEM_FORMAT_AUTO = 0, /**< Identify the container from its own leading bytes. */
136
+ ORBPRO_EPHEM_FORMAT_CCSDS_OEM_KVN = 1, /**< CCSDS Orbit Ephemeris Message, keyword-value notation. */
137
+ ORBPRO_EPHEM_FORMAT_SPK_DAF = 2, /**< NAIF Double Precision Array File carrying SPK segments. */
138
+ ORBPRO_EPHEM_FORMAT_CODE500 = 3, /**< Fixed-record binary ephemeris, 500-word logical records. */
139
+ ORBPRO_EPHEM_FORMAT_STK_EPHEMERIS = 4, /**< Ephemeris interchange text with a scenario epoch and typed data blocks. */
140
+ ORBPRO_EPHEM_FORMAT_SP3_D = 5, /**< Precise orbit product, revision d. */
141
+ } OrbProEphemerisFormat;
142
+
85
143
  /* ========================================================================= */
86
144
  /* OrbProStateVector — 64 bytes, 8-byte aligned */
87
145
  /* ========================================================================= */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "space-data-module-sdk",
3
- "version": "0.8.16",
3
+ "version": "0.8.17",
4
4
  "license": "Apache-2.0",
5
5
  "description": "Module SDK for building, validating, signing, and deploying WebAssembly modules on the Space Data Network.",
6
6
  "type": "module",
@@ -97,6 +97,7 @@
97
97
  "gate:parity": "node ./bin/space-data-module.js parity-gate",
98
98
  "gate:parity-negative-control": "node ./bin/space-data-module.js parity-gate --gate-manifest ./parity/negative-control.json --expect-fail",
99
99
  "conformance:self-test": "node ./bin/space-data-module.js conformance propagator --self-test",
100
+ "conformance:estimation:self-test": "node ./bin/space-data-module.js conformance estimation --self-test",
100
101
  "test:stream-ingest": "node --test test/runtime-host-stream-ingest.test.js",
101
102
  "test:module-stream": "node --test test/module-flatbuffer-stream-pump.test.js",
102
103
  "benchmark:stream-1gib": "SPACE_DATA_MODULE_SDK_ENABLE_1GB_STREAM_TEST=1 node --test test/runtime-host-stream-ingest.test.js",
@@ -109,6 +110,7 @@
109
110
  "check:compliance": "node ./bin/space-data-module.js check --repo-root .",
110
111
  "check:generated-bindings": "node scripts/check-generated-bindings.mjs",
111
112
  "check:propagator-abi": "node scripts/check-propagator-abi.mjs",
113
+ "check:estimation-abi": "node scripts/check-estimation-abi.mjs",
112
114
  "check:reference-frame": "node scripts/check-reference-frame-uniqueness.mjs",
113
115
  "generate:propagator-abi": "node scripts/generate-propagator-abi.mjs",
114
116
  "generate:vectors": "node ./examples/single-file-bundle/generate-vectors.mjs",
@@ -123,7 +125,7 @@
123
125
  "flatsql": "^2.0.0",
124
126
  "hd-wallet-wasm": "2.0.28",
125
127
  "sdn-emception": "1.0.0",
126
- "spacedatastandards.org": "1.200.0"
128
+ "spacedatastandards.org": "1.203.0"
127
129
  },
128
130
  "devDependencies": {
129
131
  "esbuild": "^0.28.0",
@@ -0,0 +1,244 @@
1
+ include "Propagator.fbs";
2
+
3
+ namespace orbpro.estimation;
4
+
5
+ // Estimation family ABI. This schema is the single source of truth for the
6
+ // fixed-layout C boundary and the request/result wire. Propagation is an
7
+ // inverted port protocol: the guest emits state/STM requests and consumes the
8
+ // supplied Propagator.fbs answers. It imports no provider-specific function.
9
+
10
+ enum EstimatorKind : ubyte (abi, abi_c_name: "OrbProEstimatorKind", abi_c_prefix: "ORBPRO_ESTIMATOR_") {
11
+ BATCH_WEIGHTED_LEAST_SQUARES = 0,
12
+ EXTENDED_KALMAN_FILTER = 1,
13
+ UNSCENTED_KALMAN_FILTER = 2,
14
+ EXTENDED_KALMAN_FILTER_WITH_RTS = 3
15
+ }
16
+
17
+ enum MeasurementKind : ushort (abi, abi_c_name: "OrbProMeasurementKind", abi_c_prefix: "ORBPRO_MEASUREMENT_") {
18
+ RANGE = 0,
19
+ RANGE_RATE = 1,
20
+ DOPPLER = 2,
21
+ AZIMUTH_ELEVATION = 3,
22
+ X_EAST_Y_NORTH = 4,
23
+ X_SOUTH_Y_EAST = 5,
24
+ RIGHT_ASCENSION_DECLINATION = 6,
25
+ POSITION_VECTOR = 7,
26
+ SEQUENTIAL_RANGE = 8,
27
+ PSEUDONOISE_RANGE = 9,
28
+ TIME_CORRELATED_PHASE = 10,
29
+ RELAY_RANGE = 11,
30
+ RELAY_DOPPLER = 12,
31
+ RELAY_DIFFERENCED_DOPPLER = 13,
32
+ BISTATIC_RANGE = 14,
33
+ SKIN_RANGE = 15,
34
+ CROSSLINK_RANGE = 16,
35
+ CROSSLINK_RANGE_RATE = 17,
36
+ LASER_RANGE = 18,
37
+ TIME_DIFFERENCE_OF_ARRIVAL = 19,
38
+ FREQUENCY_DIFFERENCE_OF_ARRIVAL = 20
39
+ }
40
+
41
+ enum ProcessNoiseKind : ubyte (abi, abi_c_name: "OrbProProcessNoiseKind", abi_c_prefix: "ORBPRO_PROCESS_NOISE_") {
42
+ NONE = 0,
43
+ STATE_NOISE_COMPENSATION = 1,
44
+ DYNAMIC_MODEL_COMPENSATION = 2
45
+ }
46
+
47
+ enum TroposphereKind : ubyte (abi, abi_c_name: "OrbProTroposphereKind", abi_c_prefix: "ORBPRO_TROPOSPHERE_") {
48
+ NONE = 0,
49
+ HOPFIELD_SAASTAMOINEN = 1,
50
+ MARINI = 2
51
+ }
52
+
53
+ enum IonosphereKind : ubyte (abi, abi_c_name: "OrbProIonosphereKind", abi_c_prefix: "ORBPRO_IONOSPHERE_") {
54
+ NONE = 0,
55
+ TOTAL_ELECTRON_CONTENT = 1,
56
+ REFERENCE_PROFILE = 2
57
+ }
58
+
59
+ enum InitialOrbitKind : ubyte (abi, abi_c_name: "OrbProInitialOrbitKind", abi_c_prefix: "ORBPRO_IOD_") {
60
+ GAUSS = 0,
61
+ LAPLACE = 1,
62
+ GIBBS = 2,
63
+ HERRICK_GIBBS = 3
64
+ }
65
+
66
+ enum EstimationStatus : int (abi, abi_c_name: "OrbProEstimationStatus", abi_c_prefix: "ORBPRO_ESTIMATION_") {
67
+ INTERNAL = -20,
68
+ PROPAGATOR_PROTOCOL = -19,
69
+ BUFFER_TOO_SMALL = -18,
70
+ NOT_CONVERGED = -5,
71
+ BAD_INPUT = -4,
72
+ NULL_OUTPUT = -3,
73
+ NOT_CONFIGURED = -1,
74
+ OK = 0
75
+ }
76
+
77
+ struct EstimationEpoch (abi, abi_c_name: "OrbProEstimationEpoch", abi_c_helper_prefix: "orbpro_estimation_epoch_") {
78
+ jd_day:double;
79
+ seconds:double;
80
+ }
81
+
82
+ /// One observation in SI units. The first value_count lanes of value and
83
+ /// sigma are meaningful. station_position_m is in the request frame.
84
+ struct EstimationObservation (abi, abi_c_name: "OrbProEstimationObservation", abi_c_helper_prefix: "orbpro_estimation_observation_") {
85
+ epoch:EstimationEpoch;
86
+ value:[double:4];
87
+ sigma:[double:4];
88
+ station_position_m:[double:3];
89
+ station_velocity_mps:[double:3];
90
+ station_east:[double:3];
91
+ station_north:[double:3];
92
+ station_up:[double:3];
93
+ remote_position_m:[double:3];
94
+ remote_velocity_mps:[double:3];
95
+ frequency_hz:double;
96
+ transmitter_delay_seconds:double;
97
+ receiver_delay_seconds:double;
98
+ transponder_delay_seconds:double;
99
+ elevation_rad:double;
100
+ station_latitude_rad:double;
101
+ station_height_m:double;
102
+ pressure_hpa:double;
103
+ temperature_k:double;
104
+ relative_humidity:double;
105
+ wavelength_m:double;
106
+ total_electron_content:double;
107
+ total_electron_content_rate_per_second:double;
108
+ turnaround_numerator:uint;
109
+ turnaround_denominator:uint;
110
+ kind:MeasurementKind;
111
+ value_count:ubyte;
112
+ flags:ubyte;
113
+ transmitter_index:uint;
114
+ receiver_index:uint;
115
+ }
116
+
117
+ struct EstimationErrorModel (abi, abi_c_name: "OrbProEstimationErrorModel", abi_c_helper_prefix: "orbpro_estimation_error_model_") {
118
+ noise_sigma:double;
119
+ bias:double;
120
+ bias_sigma:double;
121
+ correlation_time_seconds:double;
122
+ minimum_value:double;
123
+ maximum_value:double;
124
+ sigma_edit_threshold:double;
125
+ random_seed:ulong;
126
+ measurement_kind:MeasurementKind;
127
+ troposphere:TroposphereKind;
128
+ ionosphere:IonosphereKind;
129
+ flags:ubyte;
130
+ }
131
+
132
+ /// Fixed configuration copied byte-for-byte in every runtime. initial_state
133
+ /// is Cartesian [x,y,z,vx,vy,vz] in metres and metres per second; covariance
134
+ /// is row-major 6 by 6 in matching SI units.
135
+ struct EstimationConfig (abi, abi_c_name: "OrbProEstimationConfig", abi_c_helper_prefix: "orbpro_estimation_config_") {
136
+ initial_epoch:EstimationEpoch;
137
+ initial_state:[double:6];
138
+ initial_covariance:[double:36];
139
+ process_noise_spectral_density:[double:6];
140
+ state_convergence_tolerance:double;
141
+ rms_convergence_tolerance:double;
142
+ sigma_edit_threshold:double;
143
+ dynamic_model_correlation_time_seconds:double;
144
+ maximum_iterations:uint;
145
+ reference_frame:orbpro.propagator.ReferenceFrame;
146
+ estimator:EstimatorKind;
147
+ process_noise:ProcessNoiseKind;
148
+ flags:ushort;
149
+ }
150
+
151
+ struct EstimationState (abi, abi_c_name: "OrbProEstimationState", abi_c_helper_prefix: "orbpro_estimation_state_") {
152
+ epoch:EstimationEpoch;
153
+ state:[double:6];
154
+ covariance:[double:36];
155
+ residual_rms:double;
156
+ recovered_noise_sigma:double;
157
+ iteration_count:uint;
158
+ accepted_count:uint;
159
+ rejected_count:uint;
160
+ reference_frame:orbpro.propagator.ReferenceFrame;
161
+ converged:ubyte;
162
+ smoothed:ubyte;
163
+ estimator:EstimatorKind;
164
+ }
165
+
166
+ struct FilterEpoch (abi, abi_c_name: "OrbProFilterEpoch", abi_c_helper_prefix: "orbpro_filter_epoch_") {
167
+ epoch:EstimationEpoch;
168
+ filtered_state:[double:6];
169
+ filtered_covariance:[double:36];
170
+ smoothed_state:[double:6];
171
+ smoothed_covariance:[double:36];
172
+ normalized_innovation_squared:double;
173
+ }
174
+
175
+ /// State and state-transition matrix supplied by the propagator port. The STM
176
+ /// maps deviations at the estimator epoch to this sample epoch.
177
+ struct EstimationPropagatorSample (abi, abi_c_name: "OrbProEstimationPropagatorSample", abi_c_helper_prefix: "orbpro_estimation_propagator_sample_") {
178
+ epoch:EstimationEpoch;
179
+ state:[double:6];
180
+ stm:[double:36];
181
+ }
182
+
183
+ struct InitialOrbitRequest (abi, abi_c_name: "OrbProInitialOrbitRequest", abi_c_helper_prefix: "orbpro_initial_orbit_request_") {
184
+ // Three inertial position vectors for Gibbs and Herrick-Gibbs.
185
+ positions_m:[double:9];
186
+ // Three observer positions followed by the three topocentric angle pairs
187
+ // used by Gauss and Laplace. The method selects which input set is read.
188
+ observer_positions_m:[double:9];
189
+ right_ascensions_rad:[double:3];
190
+ declinations_rad:[double:3];
191
+ epochs:EstimationEpoch;
192
+ interval_12_seconds:double;
193
+ interval_23_seconds:double;
194
+ gravitational_parameter_m3_s2:double;
195
+ method:InitialOrbitKind;
196
+ }
197
+
198
+ struct InitialOrbitResult (abi, abi_c_name: "OrbProInitialOrbitResult", abi_c_helper_prefix: "orbpro_initial_orbit_result_") {
199
+ epoch:EstimationEpoch;
200
+ state:[double:6];
201
+ iterations:uint;
202
+ reference_frame:orbpro.propagator.ReferenceFrame;
203
+ status:EstimationStatus;
204
+ }
205
+
206
+ /// Request/result wire. observations are decoded from $TDM/$CRD before they
207
+ /// cross this family boundary; their source record identifiers remain on the
208
+ /// request for a complete provenance trail.
209
+ table EstimationRequest {
210
+ config:EstimationConfig;
211
+ observations:[EstimationObservation];
212
+ error_models:[EstimationErrorModel];
213
+ source_record_ids:[string];
214
+ propagator_port_id:string (required);
215
+ propagator_capability:string (required);
216
+ trace_id:string;
217
+ }
218
+
219
+ table EstimationResult {
220
+ status:EstimationStatus = OK;
221
+ estimate:EstimationState;
222
+ filter_history:[FilterEpoch];
223
+ residuals:[double];
224
+ /// Row-major 6x6 covariance blocks, one per batch iteration, in iteration
225
+ /// order. Empty for sequential estimators.
226
+ iteration_covariances:[double];
227
+ rejected_observation_indices:[uint];
228
+ odr_record:[ubyte];
229
+ ocm_record:[ubyte];
230
+ error_message:string;
231
+ trace_id:string;
232
+ }
233
+
234
+ table EstimationEnvelope {
235
+ request:EstimationRequest;
236
+ result:EstimationResult;
237
+ propagator_samples:[EstimationPropagatorSample];
238
+ simulated_observations:[EstimationObservation];
239
+ initial_orbit_request:InitialOrbitRequest;
240
+ initial_orbit_result:InitialOrbitResult;
241
+ }
242
+
243
+ root_type EstimationEnvelope;
244
+ file_identifier "$EST";
@@ -165,6 +165,70 @@ enum PropagatorSourceKind : ubyte {
165
165
  SDP8 = 12
166
166
  }
167
167
 
168
+ /// The negative return codes every propagator export shares.
169
+ ///
170
+ /// These were documented in docs/propagator-abi.md and declared NOWHERE, so
171
+ /// every module hand-typed them as literals and the contract lived only in
172
+ /// prose. A propagator that returns -1 for everything is unconformable — the
173
+ /// host cannot tell a bad entity index from an uninitialized module, so it
174
+ /// cannot place the failure on the degradation ladder — and the cheapest way
175
+ /// to end up there is having no names to return.
176
+ ///
177
+ /// VALUES ARE FROZEN. New classes are appended downward.
178
+ enum ErrorCode : byte (abi, abi_c_name: "OrbProPropagatorError", abi_c_prefix: "ORBPRO_PROP_") {
179
+ OK = 0,
180
+ /// No elements or ephemeris ingested yet.
181
+ NOT_INITIALIZED = -1,
182
+ /// Entity index at or beyond the entity count.
183
+ BAD_ENTITY_INDEX = -2,
184
+ /// The caller passed a null output pointer.
185
+ NULL_OUTPUT = -3,
186
+ /// Malformed or short input buffer.
187
+ BAD_INPUT = -4,
188
+ /// The solve failed to converge.
189
+ NOT_CONVERGED = -5,
190
+ /// The elements describe no closed orbit, or the result is not a number.
191
+ UNPHYSICAL = -6,
192
+ /// The ephemeris container is not one this module reads, or AUTO could not
193
+ /// identify it, or its declared frame has no member on the ReferenceFrame
194
+ /// roster.
195
+ UNSUPPORTED_FORMAT = -7,
196
+ /// The requested epoch lies outside every segment the loaded ephemeris
197
+ /// covers. A refusal, never a silent extrapolation: a caller cannot detect
198
+ /// an extrapolated state from the value alone.
199
+ EPOCH_OUT_OF_RANGE = -8
200
+ }
201
+
202
+ /// The container an ephemeris-driven propagator was handed on
203
+ /// `plugin_init_ephemeris`. A format is DATA, not identity: one ephemeris
204
+ /// provider reads every member below behind this discriminator rather than
205
+ /// shipping one artifact per container, because the interpolation, the time
206
+ /// scale handling and the frame rotation are shared and splitting them
207
+ /// re-creates the five-mirror drift this file exists to end.
208
+ ///
209
+ /// AUTO is the honest default: every container here is self-identifying in its
210
+ /// first bytes (`DAF/SPK ` / `NAIF/DAF`, the OEM version keyword, the STK
211
+ /// `stk.v.` banner, the SP3 `#d` line, the Code-500 fixed header block), so a
212
+ /// caller that does not know what it fetched says so rather than guessing. A
213
+ /// module that cannot identify the bytes returns UNSUPPORTED_FORMAT; it never
214
+ /// falls back to a default container.
215
+ ///
216
+ /// VALUES ARE FROZEN AND ARE NEVER RENUMBERED. New containers are APPENDED.
217
+ enum EphemerisFormat : ubyte (abi, abi_c_name: "OrbProEphemerisFormat", abi_c_prefix: "ORBPRO_EPHEM_FORMAT_") {
218
+ /// Identify the container from its own leading bytes.
219
+ AUTO = 0,
220
+ /// CCSDS Orbit Ephemeris Message, keyword-value notation.
221
+ CCSDS_OEM_KVN = 1,
222
+ /// NAIF Double Precision Array File carrying SPK segments.
223
+ SPK_DAF = 2,
224
+ /// Fixed-record binary ephemeris, 500-word logical records.
225
+ CODE500 = 3,
226
+ /// Ephemeris interchange text with a scenario epoch and typed data blocks.
227
+ STK_EPHEMERIS = 4,
228
+ /// Precise orbit product, revision d.
229
+ SP3_D = 5
230
+ }
231
+
168
232
  /// Orbital state vector — 64 bytes, 8-byte aligned. Mirrors
169
233
  /// `OrbProStateVector` in orbpro-integration/sdk/include/orbpro_propagator.h
170
234
  /// byte for byte:
@@ -0,0 +1,81 @@
1
+ /**
2
+ * A deterministic, non-physics reference receipt for exercising the
3
+ * estimation-family conformance evaluator. Real modules replace every value
4
+ * with measurements produced by their compiled WASM artifact. Keeping the
5
+ * evaluator's own known-good input here lets the negative controls prove that
6
+ * each required lane can fail without shipping a second JS estimator.
7
+ */
8
+
9
+ const reference = {
10
+ schemaVersion: 1,
11
+ family: "estimation",
12
+ artifact: { sha256: "reference-estimation-artifact" },
13
+ runtime: {
14
+ browser: { outputSha256: "reference-output" },
15
+ wasmedge: { outputSha256: "reference-output" },
16
+ threads: {
17
+ "1": "reference-output",
18
+ "2": "reference-output",
19
+ "4": "reference-output",
20
+ "8": "reference-output",
21
+ },
22
+ },
23
+ batch: {
24
+ positionAbsoluteErrorM: 0.25,
25
+ positionRelativeError: 4e-8,
26
+ covarianceRssRelativeError: 0.02,
27
+ residualRmsRelativeError: 2e-8,
28
+ rejectedExpected: [7, 19],
29
+ rejectedActual: [7, 19],
30
+ iterationCount: 4,
31
+ iterationCovarianceCount: 4,
32
+ iterationCovariancesSpd: true,
33
+ },
34
+ filter: {
35
+ positionErrorM: 0.65,
36
+ smootherPositionErrorM: 0.21,
37
+ smootherCovarianceMonotone: true,
38
+ neesMean: 6.02,
39
+ nees95Lower: 5.34,
40
+ nees95Upper: 6.70,
41
+ sncCovarianceGrowthRatio: 1.21,
42
+ dmcCovarianceGrowthRatio: 1.58,
43
+ ekfUkfCovarianceDifference: 0.08,
44
+ },
45
+ measurements: {
46
+ rangeMaxRelativeError: 6e-16,
47
+ rangeRateMaxRelativeError: 5e-16,
48
+ azElMaxRelativeError: 5e-16,
49
+ raDecMaxRelativeError: 5e-16,
50
+ lightTimeMagnitudeErrorM: 2e-10,
51
+ sagnacMagnitudeErrorM: 2e-10,
52
+ },
53
+ media: {
54
+ hopfieldSaastamoinenMaxRelativeError: 6e-5,
55
+ mariniMaxRelativeError: 7e-5,
56
+ p531PublishedPrecisionError: 0,
57
+ },
58
+ iod: {
59
+ gaussPrintedPrecisionError: 0,
60
+ laplacePrintedPrecisionError: 0,
61
+ gibbsPrintedPrecisionError: 0,
62
+ herrickGibbsPrintedPrecisionError: 0,
63
+ },
64
+ simulator: {
65
+ runs: 200,
66
+ stateInsideThreeSigmaFraction: 0.965,
67
+ recoveredNoiseSigmaRelativeError: 0.031,
68
+ },
69
+ invariants: {
70
+ covarianceSymmetric: true,
71
+ covariancePositiveDefinite: true,
72
+ residualOrthogonalityMax: 3e-11,
73
+ actualOcmCovariance: true,
74
+ },
75
+ };
76
+
77
+ export const ESTIMATION_REFERENCE_EVIDENCE = Object.freeze(reference);
78
+
79
+ export function makeEstimationReferenceEvidence() {
80
+ return structuredClone(reference);
81
+ }
@@ -0,0 +1,134 @@
1
+ /** Estimation-family conformance receipt evaluator. No physics lives here. */
2
+
3
+ import { computeVerdict } from "./propagatorSuite.js";
4
+
5
+ const pass = (id, tier, detail) => ({ id, tier, required: true, status: "pass", detail });
6
+ const fail = (id, tier, detail) => ({ id, tier, required: true, status: "fail", detail });
7
+
8
+ function check(checks, id, tier, condition, detail, failure = detail) {
9
+ checks.push(condition ? pass(id, tier, detail) : fail(id, tier, failure));
10
+ }
11
+
12
+ function finiteAtMost(value, limit) {
13
+ return Number.isFinite(value) && value <= limit;
14
+ }
15
+
16
+ function sameSet(left = [], right = []) {
17
+ const a = [...new Set(left)].sort((x, y) => x - y);
18
+ const b = [...new Set(right)].sort((x, y) => x - y);
19
+ return a.length === b.length && a.every((value, index) => value === b[index]);
20
+ }
21
+
22
+ export function runEstimationSuite(evidence) {
23
+ const checks = [];
24
+ const runtimeHashes = [
25
+ evidence?.runtime?.browser?.outputSha256,
26
+ evidence?.runtime?.wasmedge?.outputSha256,
27
+ evidence?.runtime?.threads?.["1"],
28
+ evidence?.runtime?.threads?.["2"],
29
+ evidence?.runtime?.threads?.["4"],
30
+ evidence?.runtime?.threads?.["8"],
31
+ ];
32
+ check(checks, "tier0/tri-runtime-byte-identity", "0",
33
+ runtimeHashes.every(Boolean) && new Set(runtimeHashes).size === 1,
34
+ "browser, WasmEdge and worker widths 1/2/4/8 emitted byte-identical results",
35
+ "missing or divergent browser/WasmEdge/thread-width output digests");
36
+
37
+ const batch = evidence?.batch ?? {};
38
+ check(checks, "tierA/batch-state", "A",
39
+ finiteAtMost(batch.positionAbsoluteErrorM, 1) &&
40
+ finiteAtMost(batch.positionRelativeError, 1e-3),
41
+ `absolute=${batch.positionAbsoluteErrorM} m relative=${batch.positionRelativeError}`,
42
+ "batch state exceeds 1 m absolute or 1e-3 relative");
43
+ check(checks, "tierA/batch-covariance", "A",
44
+ finiteAtMost(batch.covarianceRssRelativeError, 0.05),
45
+ `RSS position sigma relative error=${batch.covarianceRssRelativeError}`,
46
+ "batch covariance RSS differs from authority by more than 5%");
47
+ check(checks, "tierA/batch-residual-rms", "A",
48
+ finiteAtMost(batch.residualRmsRelativeError, 1e-6),
49
+ `residual RMS relative error=${batch.residualRmsRelativeError}`,
50
+ "batch residual RMS differs from authority by more than 1e-6 relative");
51
+ check(checks, "tierA/sigma-edit-set", "A",
52
+ sameSet(batch.rejectedExpected, batch.rejectedActual),
53
+ `exact rejected set [${batch.rejectedActual ?? []}]`,
54
+ "sigma editing rejected a different observation set");
55
+ check(checks, "tierC/batch-iteration-covariance", "C",
56
+ Number.isInteger(batch.iterationCount) && batch.iterationCount > 0 &&
57
+ batch.iterationCovarianceCount === batch.iterationCount &&
58
+ batch.iterationCovariancesSpd === true,
59
+ `${batch.iterationCovarianceCount}/${batch.iterationCount} iteration covariances are SPD`,
60
+ "batch iteration covariance history is missing, incomplete, or not SPD");
61
+
62
+ const filter = evidence?.filter ?? {};
63
+ check(checks, "tierA/filter-and-smoother-state", "A",
64
+ finiteAtMost(filter.positionErrorM, 1) &&
65
+ finiteAtMost(filter.smootherPositionErrorM, 0.5),
66
+ `filter=${filter.positionErrorM} m smoother=${filter.smootherPositionErrorM} m`,
67
+ "filter exceeds 1 m or RTS smoother exceeds 0.5 m");
68
+ check(checks, "tierC/smoother-covariance", "C",
69
+ filter.smootherCovarianceMonotone === true,
70
+ "smoothed covariance is no larger than filtered covariance at every epoch");
71
+ check(checks, "tierC/nees-100-run", "C",
72
+ Number.isFinite(filter.neesMean) && filter.neesMean >= filter.nees95Lower &&
73
+ filter.neesMean <= filter.nees95Upper,
74
+ `NEES mean=${filter.neesMean} band=[${filter.nees95Lower},${filter.nees95Upper}]`,
75
+ "100-run NEES lies outside the declared chi-square 95% band");
76
+ check(checks, "tierC/process-noise", "C",
77
+ Number.isFinite(filter.sncCovarianceGrowthRatio) && filter.sncCovarianceGrowthRatio !== 1 &&
78
+ Number.isFinite(filter.dmcCovarianceGrowthRatio) && filter.dmcCovarianceGrowthRatio !== 1,
79
+ `SNC ratio=${filter.sncCovarianceGrowthRatio} DMC ratio=${filter.dmcCovarianceGrowthRatio}`,
80
+ "SNC or DMC failed to change covariance growth");
81
+ check(checks, "tierC/ekf-ukf-not-aliased", "C",
82
+ Number.isFinite(filter.ekfUkfCovarianceDifference) && filter.ekfUkfCovarianceDifference > 0,
83
+ `covariance history distance=${filter.ekfUkfCovarianceDifference}`,
84
+ "EKF and UKF covariance histories are identical");
85
+
86
+ const measurement = evidence?.measurements ?? {};
87
+ for (const [name, key] of [["range", "rangeMaxRelativeError"],
88
+ ["range-rate", "rangeRateMaxRelativeError"], ["az-el", "azElMaxRelativeError"],
89
+ ["ra-dec", "raDecMaxRelativeError"]]) {
90
+ check(checks, `tierA/measurement-${name}`, "A", finiteAtMost(measurement[key], 1e-6),
91
+ `max relative error=${measurement[key]}`, `${name} exceeds 1e-6 relative`);
92
+ }
93
+ check(checks, "tierA/light-time", "A", finiteAtMost(measurement.lightTimeMagnitudeErrorM, 1e-6),
94
+ `magnitude error=${measurement.lightTimeMagnitudeErrorM} m`);
95
+ check(checks, "tierA/sagnac", "A", finiteAtMost(measurement.sagnacMagnitudeErrorM, 1e-6),
96
+ `magnitude error=${measurement.sagnacMagnitudeErrorM} m`);
97
+
98
+ const media = evidence?.media ?? {};
99
+ check(checks, "tierA/troposphere-hopfield-saastamoinen", "A",
100
+ finiteAtMost(media.hopfieldSaastamoinenMaxRelativeError, 1e-4),
101
+ `relative error=${media.hopfieldSaastamoinenMaxRelativeError}`);
102
+ check(checks, "tierA/troposphere-marini", "A",
103
+ finiteAtMost(media.mariniMaxRelativeError, 1e-4),
104
+ `relative error=${media.mariniMaxRelativeError}`);
105
+ check(checks, "tierB/ionosphere-p531", "B", media.p531PublishedPrecisionError === 0,
106
+ "P.531 table value reproduced to published precision");
107
+
108
+ for (const [name, key] of [["gauss", "gaussPrintedPrecisionError"],
109
+ ["laplace", "laplacePrintedPrecisionError"], ["gibbs", "gibbsPrintedPrecisionError"],
110
+ ["herrick-gibbs", "herrickGibbsPrintedPrecisionError"]]) {
111
+ check(checks, `tierB/iod-${name}`, "B", evidence?.iod?.[key] === 0,
112
+ "Vallado worked example reproduced to printed precision");
113
+ }
114
+
115
+ const simulator = evidence?.simulator ?? {};
116
+ check(checks, "tierC/simulator-coverage", "C",
117
+ simulator.runs >= 200 && simulator.stateInsideThreeSigmaFraction >= 0.95,
118
+ `${simulator.stateInsideThreeSigmaFraction} inside 3-sigma over ${simulator.runs} runs`);
119
+ check(checks, "tierC/simulator-noise-recovery", "C",
120
+ finiteAtMost(simulator.recoveredNoiseSigmaRelativeError, 0.05),
121
+ `noise sigma relative error=${simulator.recoveredNoiseSigmaRelativeError}`);
122
+
123
+ const invariants = evidence?.invariants ?? {};
124
+ check(checks, "tierC/covariance-spd", "C",
125
+ invariants.covarianceSymmetric === true && invariants.covariancePositiveDefinite === true,
126
+ "covariance is symmetric positive definite");
127
+ check(checks, "tierC/residual-orthogonality", "C",
128
+ finiteAtMost(invariants.residualOrthogonalityMax, 1e-6),
129
+ `max normalized normal-equation projection=${invariants.residualOrthogonalityMax}`);
130
+ check(checks, "tierC/ocm-covariance", "C", invariants.actualOcmCovariance === true,
131
+ "the emitted OCM carries the estimator covariance");
132
+
133
+ return { checks, verdict: computeVerdict(checks) };
134
+ }