space-data-module-sdk 0.8.14 → 0.8.16

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 (125) hide show
  1. package/bin/space-data-module.js +91 -0
  2. package/docs/_shell/consumer-assets.html +5 -0
  3. package/docs/browser-wasmedge-isomorphic.html +261 -0
  4. package/docs/byo-wasm-quickstart.html +226 -0
  5. package/docs/byo-wasm-quickstart.md +204 -0
  6. package/docs/conformance.html +156 -0
  7. package/docs/conformance.md +92 -0
  8. package/docs/emception-shared-memory-verdict.md +209 -0
  9. package/docs/events-abi.html +313 -0
  10. package/docs/events-abi.md +530 -0
  11. package/docs/families/analytics.html +178 -0
  12. package/docs/families/analytics.md +104 -0
  13. package/docs/families/attitude.html +157 -0
  14. package/docs/families/attitude.md +36 -0
  15. package/docs/families/behavior.html +157 -0
  16. package/docs/families/behavior.md +36 -0
  17. package/docs/families/breakup.html +157 -0
  18. package/docs/families/breakup.md +36 -0
  19. package/docs/families/conjunction.html +186 -0
  20. package/docs/families/conjunction.md +121 -0
  21. package/docs/families/data-source.html +318 -0
  22. package/docs/families/effects.html +157 -0
  23. package/docs/families/effects.md +36 -0
  24. package/docs/families/environment.html +170 -0
  25. package/docs/families/environment.md +73 -0
  26. package/docs/families/estimation.html +190 -0
  27. package/docs/families/estimation.md +128 -0
  28. package/docs/families/gnc.html +157 -0
  29. package/docs/families/gnc.md +36 -0
  30. package/docs/families/maneuver.html +209 -0
  31. package/docs/families/maneuver.md +206 -0
  32. package/docs/families/obstruction.html +191 -0
  33. package/docs/families/obstruction.md +96 -0
  34. package/docs/families/propagator.html +333 -0
  35. package/docs/families/propulsion.html +157 -0
  36. package/docs/families/propulsion.md +36 -0
  37. package/docs/families/reentry.html +157 -0
  38. package/docs/families/reentry.md +36 -0
  39. package/docs/families/rf.html +193 -0
  40. package/docs/families/rf.md +136 -0
  41. package/docs/families/scheduler.html +157 -0
  42. package/docs/families/scheduler.md +36 -0
  43. package/docs/families/sensor.html +157 -0
  44. package/docs/families/sensor.md +36 -0
  45. package/docs/families/signature.html +157 -0
  46. package/docs/families/signature.md +36 -0
  47. package/docs/flatsql-host-contract.html +176 -0
  48. package/docs/flatsql-streaming-standard.html +375 -0
  49. package/docs/gpu-module-abi.html +233 -0
  50. package/docs/harness-family-matrix.md +135 -0
  51. package/docs/index.html +216 -85
  52. package/docs/isomorphic-pthreads.html +228 -0
  53. package/docs/isomorphic-pthreads.md +84 -0
  54. package/docs/isomorphic-sdn-runtime-plan.html +274 -0
  55. package/docs/language-runtime-matrix.html +182 -0
  56. package/docs/llms.txt +86 -0
  57. package/docs/module-bundle-runtime-plan.html +146 -0
  58. package/docs/module-publication-standard.html +503 -0
  59. package/docs/node-red-default-node-parity.html +201 -0
  60. package/docs/propagator-abi.md +73 -21
  61. package/docs/protect-and-sign.html +181 -0
  62. package/docs/protect-and-sign.md +120 -0
  63. package/docs/protocol-installation.html +296 -0
  64. package/docs/publication-submission.html +158 -0
  65. package/docs/publication-submission.md +78 -0
  66. package/docs/secrets-capability.html +206 -0
  67. package/docs/styles.css +860 -167
  68. package/docs/testing-harness.html +408 -0
  69. package/docs/tri-runtime-parity-gate.html +182 -0
  70. package/docs/tri-runtime-parity.html +203 -0
  71. package/include/orbpro/orbpro_event_runner.h +951 -0
  72. package/include/orbpro/orbpro_events_abi.h +565 -0
  73. package/include/orbpro/orbpro_propagator_abi.h +17 -0
  74. package/package.json +15 -5
  75. package/schemas/orbpro/Events.fbs +501 -0
  76. package/schemas/orbpro/Propagator.fbs +72 -9
  77. package/schemas/orbpro/reference-frame-crosswalk.json +108 -0
  78. package/schemas/orbpro/reference-frame.lock.json +98 -0
  79. package/src/browser.js +13 -0
  80. package/src/conformance/abiDriver.js +225 -0
  81. package/src/conformance/index.js +151 -0
  82. package/src/conformance/propagatorSuite.js +526 -0
  83. package/src/conformance/selfTest.js +275 -0
  84. package/src/conformance/selfTestCorpus.js +68 -0
  85. package/src/conformance/twoBodyReference.js +132 -0
  86. package/src/generated/orbpro/events-abi.js +206 -0
  87. package/src/generated/orbpro/events-abi.ts +319 -0
  88. package/src/generated/orbpro/propagator/reference-frame.js +17 -0
  89. package/src/generated/orbpro/propagator/reference-frame.ts +91 -1
  90. package/src/generated/orbpro/propagator-abi.js +17 -0
  91. package/src/generated/orbpro/propagator-abi.ts +17 -0
  92. package/src/generated/spacedatastandards/plg/CCT.d.ts +94 -0
  93. package/src/generated/spacedatastandards/plg/CCT.d.ts.map +1 -0
  94. package/src/generated/spacedatastandards/plg/CCT.js +208 -0
  95. package/src/generated/spacedatastandards/plg/CCT.ts +281 -0
  96. package/src/generated/spacedatastandards/plg/CCTCategory.d.ts +98 -0
  97. package/src/generated/spacedatastandards/plg/CCTCategory.d.ts.map +1 -0
  98. package/src/generated/spacedatastandards/plg/CCTCategory.js +177 -0
  99. package/src/generated/spacedatastandards/plg/CCTCategory.ts +269 -0
  100. package/src/generated/spacedatastandards/plg/CCTCategoryRollup.d.ts +54 -0
  101. package/src/generated/spacedatastandards/plg/CCTCategoryRollup.d.ts.map +1 -0
  102. package/src/generated/spacedatastandards/plg/CCTCategoryRollup.js +106 -0
  103. package/src/generated/spacedatastandards/plg/CCTCategoryRollup.ts +144 -0
  104. package/src/generated/spacedatastandards/plg/PLG.d.ts +46 -2
  105. package/src/generated/spacedatastandards/plg/PLG.d.ts.map +1 -1
  106. package/src/generated/spacedatastandards/plg/PLG.js +78 -5
  107. package/src/generated/spacedatastandards/plg/PLG.ts +90 -5
  108. package/src/generated/spacedatastandards/plg/capabilityClass.d.ts +181 -0
  109. package/src/generated/spacedatastandards/plg/capabilityClass.d.ts.map +1 -0
  110. package/src/generated/spacedatastandards/plg/capabilityClass.js +183 -0
  111. package/src/generated/spacedatastandards/plg/capabilityClass.ts +208 -0
  112. package/src/generated/spacedatastandards/plg/main.d.ts +4 -0
  113. package/src/generated/spacedatastandards/plg/main.d.ts.map +1 -1
  114. package/src/generated/spacedatastandards/plg/main.js +4 -0
  115. package/src/generated/spacedatastandards/plg/main.ts +4 -0
  116. package/src/generated/spacedatastandards/plg/pluginCategory.d.ts +40 -2
  117. package/src/generated/spacedatastandards/plg/pluginCategory.d.ts.map +1 -1
  118. package/src/generated/spacedatastandards/plg/pluginCategory.js +40 -2
  119. package/src/generated/spacedatastandards/plg/pluginCategory.ts +40 -2
  120. package/src/host/browserModuleHarness.js +8 -0
  121. package/src/host/wasiThreadHost.js +189 -14
  122. package/src/index.d.ts +18 -1
  123. package/src/testing/isomorphicHarness.js +233 -0
  124. package/src/transport/index.js +16 -0
  125. package/src/transport/records.js +1015 -3
@@ -0,0 +1,275 @@
1
+ /**
2
+ * `space-data-module conformance propagator --self-test` — must exit 0 BY
3
+ * failing (finding §5). A gate never observed to fail is indistinguishable
4
+ * from one that cannot fail, so the runner ships with a battery of mock
5
+ * propagators, each carrying ONE planted defect drawn from a real defect this
6
+ * program found in the wild, and requires the suite to catch every one of
7
+ * them — plus a conformant baseline it must NOT flag.
8
+ *
9
+ * The mocks implement the same driver interface the WASM ABI driver exposes,
10
+ * so the suite under test is byte-for-byte the suite that adjudicates real
11
+ * artifacts. No toolchain, no artifact, no network.
12
+ */
13
+
14
+ import { ORBPRO_STATE_VECTOR, ReferenceFrame, StateFlags, ErrorCode, REQUIRED_ABI_EXPORTS } from "./abiDriver.js";
15
+ import { computeVerdict, runPropagatorSuite } from "./propagatorSuite.js";
16
+ import { BANDS_DEFAULT, buildSelfTestCorpus } from "./selfTestCorpus.js";
17
+ import { propagateTwoBody } from "./twoBodyReference.js";
18
+
19
+ const PAGE = 65536;
20
+
21
+ /** Encode a state through the GENERATED layout, so byte-identity is honest. */
22
+ function packStateVector(state) {
23
+ const bytes = new Uint8Array(ORBPRO_STATE_VECTOR.size);
24
+ const view = new DataView(bytes.buffer);
25
+ const { offsets } = ORBPRO_STATE_VECTOR;
26
+ view.setFloat64(offsets.epoch, state.epoch, true);
27
+ for (const axis of [0, 1, 2]) {
28
+ view.setFloat64(offsets.position + axis * 8, state.position[axis], true);
29
+ view.setFloat64(offsets.velocity + axis * 8, state.velocity[axis], true);
30
+ }
31
+ view.setUint8(offsets.reference_frame, state.referenceFrame);
32
+ bytes[offsets.reference_frame + 1] = state.reserved?.[0] ?? 0;
33
+ bytes[offsets.reference_frame + 2] = state.reserved?.[1] ?? 0;
34
+ bytes[offsets.reference_frame + 3] = state.reserved?.[2] ?? 0;
35
+ view.setUint32(offsets.flags, state.flags, true);
36
+ return bytes;
37
+ }
38
+
39
+ function decodePacked(bytes) {
40
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
41
+ const { offsets } = ORBPRO_STATE_VECTOR;
42
+ return {
43
+ epoch: view.getFloat64(offsets.epoch, true),
44
+ position: [0, 1, 2].map((axis) => view.getFloat64(offsets.position + axis * 8, true)),
45
+ velocity: [0, 1, 2].map((axis) => view.getFloat64(offsets.velocity + axis * 8, true)),
46
+ referenceFrame: view.getUint8(offsets.reference_frame),
47
+ reserved: [1, 2, 3].map((i) => view.getUint8(offsets.reference_frame + i)),
48
+ flags: view.getUint32(offsets.flags, true),
49
+ };
50
+ }
51
+
52
+ /**
53
+ * A mock propagator with a simulated linear memory (never shrinks, grows in
54
+ * whole pages — WebAssembly semantics, so the leak test measures the same
55
+ * thing it measures on a real artifact).
56
+ */
57
+ class MockPropagatorDriver {
58
+ constructor(defect = null) {
59
+ this.defect = defect;
60
+ this.records = null;
61
+ // One page: tight enough that a per-cycle leak of a few KiB reaches a
62
+ // page boundary inside the self-test's measurement window.
63
+ this.capacityBytes = PAGE;
64
+ this.arenaBytes = 0;
65
+ this.bumpBytes = 0;
66
+ this.callSerial = 0;
67
+ }
68
+
69
+ exportNames() {
70
+ if (this.defect === "missing-exports") {
71
+ return REQUIRED_ABI_EXPORTS.filter((name) => name !== "plugin_destroy");
72
+ }
73
+ return [...REQUIRED_ABI_EXPORTS];
74
+ }
75
+
76
+ memoryBytes() {
77
+ return this.capacityBytes;
78
+ }
79
+
80
+ #reserve(byteLength) {
81
+ this.arenaBytes += byteLength;
82
+ const needed = this.arenaBytes + this.bumpBytes;
83
+ while (needed > this.capacityBytes) {
84
+ this.capacityBytes += PAGE;
85
+ }
86
+ }
87
+
88
+ alloc(byteLength) {
89
+ this.bumpBytes += byteLength;
90
+ const needed = this.arenaBytes + this.bumpBytes;
91
+ while (needed > this.capacityBytes) {
92
+ this.capacityBytes += PAGE;
93
+ }
94
+ return 8; // aligned, non-zero; the mock has no real address space
95
+ }
96
+
97
+ free() {
98
+ // bump allocator: freed on destroy, like the reference module's arena
99
+ }
100
+
101
+ #validate(record) {
102
+ if (!(record.eccentricity < 1) || !(record.meanMotionRevPerDay > 0)) {
103
+ return false;
104
+ }
105
+ return true;
106
+ }
107
+
108
+ initFromOmm(records) {
109
+ if (this.defect !== "confident-nonsense") {
110
+ for (const record of records) {
111
+ if (!this.#validate(record)) return ErrorCode.UNPHYSICAL;
112
+ }
113
+ }
114
+ this.records = [...records];
115
+ this.#reserve(records.length * 88);
116
+ return this.records.length;
117
+ }
118
+
119
+ ingestOne(record) {
120
+ if (this.defect !== "confident-nonsense" && !this.#validate(record)) {
121
+ return ErrorCode.UNPHYSICAL;
122
+ }
123
+ if (!this.records) this.records = [];
124
+ this.records.push(record);
125
+ this.#reserve(88);
126
+ if (this.defect === "count-fallback") {
127
+ // The defect the create-returns-handle primitive exists to kill: the
128
+ // module "returns success" instead of the handle it assigned.
129
+ return 0;
130
+ }
131
+ return this.records.length - 1;
132
+ }
133
+
134
+ #state(julianDate, record) {
135
+ const propagated = propagateTwoBody(record, julianDate);
136
+ let { position, velocity } = propagated;
137
+ if (this.defect === "units-km") {
138
+ position = position.map((value) => value / 1000);
139
+ velocity = velocity.map((value) => value / 1000);
140
+ }
141
+ if (this.defect === "nondeterministic") {
142
+ this.callSerial += 1;
143
+ position = position.map((value) => value + this.callSerial * 1e-9);
144
+ }
145
+ return {
146
+ epoch: julianDate,
147
+ position,
148
+ velocity,
149
+ referenceFrame:
150
+ this.defect === "frame-lies" ? ReferenceFrame.TEME : ReferenceFrame.ECEF,
151
+ reserved: [0, 0, 0],
152
+ flags: StateFlags.VALID,
153
+ };
154
+ }
155
+
156
+ propagate(julianDate, entityIndex) {
157
+ if (!this.records || this.records.length === 0) {
158
+ return { status: ErrorCode.NOT_INITIALIZED, state: null, bytes: new Uint8Array(64) };
159
+ }
160
+ if (!(entityIndex >= 0 && entityIndex < this.records.length)) {
161
+ return { status: ErrorCode.BAD_ENTITY_INDEX, state: null, bytes: new Uint8Array(64) };
162
+ }
163
+ const bytes = packStateVector(this.#state(julianDate, this.records[entityIndex]));
164
+ return { status: ErrorCode.OK, state: decodePacked(bytes), bytes };
165
+ }
166
+
167
+ propagateBatch(julianDate, count) {
168
+ if (!this.records || this.records.length === 0) {
169
+ return { status: ErrorCode.NOT_INITIALIZED, states: [] };
170
+ }
171
+ if (!(count >= 0 && count <= this.records.length)) {
172
+ return { status: ErrorCode.BAD_ENTITY_INDEX, states: [] };
173
+ }
174
+ const states = [];
175
+ for (let index = 0; index < count; index += 1) {
176
+ const state = this.#state(julianDate, this.records[index]);
177
+ if (this.defect === "batch-divergence") {
178
+ state.position = [state.position[0] + 1e-3, state.position[1], state.position[2]];
179
+ }
180
+ states.push(decodePacked(packStateVector(state)));
181
+ }
182
+ return { status: ErrorCode.OK, states };
183
+ }
184
+
185
+ entityCount() {
186
+ return this.records ? this.records.length : 0;
187
+ }
188
+
189
+ destroy() {
190
+ this.records = null;
191
+ this.bumpBytes = 0;
192
+ if (this.defect !== "leaky-destroy") {
193
+ this.arenaBytes = 0;
194
+ }
195
+ // capacityBytes deliberately never shrinks: wasm linear memory semantics.
196
+ }
197
+ }
198
+
199
+ /**
200
+ * Every planted defect names the check that must catch it. Each is a real
201
+ * defect class this program found live (finding §4): the 1000x units error,
202
+ * the {} destroy, the silent acceptance of unphysical elements, the count-1
203
+ * identity race, an undeclared frame, a batch path that is not the same
204
+ * physics, nondeterminism.
205
+ */
206
+ export const PLANTED_DEFECTS = Object.freeze([
207
+ { defect: "units-km", mustFail: "tierB/anchors" },
208
+ { defect: "leaky-destroy", mustFail: "tier4/lifecycle-leak" },
209
+ { defect: "confident-nonsense", mustFail: "tierC/typed-refusals" },
210
+ { defect: "count-fallback", mustFail: "tierC/create-returns-handle" },
211
+ { defect: "frame-lies", mustFail: "tierC/frame-flags-reserved-declared" },
212
+ { defect: "batch-divergence", mustFail: "tierC/batch-single-agreement" },
213
+ { defect: "nondeterministic", mustFail: "tierC/determinism-byte-identity" },
214
+ { defect: "missing-exports", mustFail: "tier0/instantiation-and-exports" },
215
+ ]);
216
+
217
+ /**
218
+ * Run the self-test. Returns {ok, results}: ok only when the baseline mock is
219
+ * clean AND every planted defect is caught by the check that owns it.
220
+ */
221
+ export async function runPropagatorSelfTest(options = {}) {
222
+ const corpus = buildSelfTestCorpus();
223
+ // Small leak windows: the mocks' allocator is deterministic, so the steady
224
+ // state is reached immediately; what matters is that the LEAKY mock grows.
225
+ const leak = options.leak ?? { warmupCycles: 6, measureCycles: 40, entities: 128 };
226
+ const results = [];
227
+
228
+ const baselineChecks = await runPropagatorSuite(
229
+ async () => new MockPropagatorDriver(null),
230
+ { corpus, leak },
231
+ );
232
+ const baselineVerdict = computeVerdict(baselineChecks);
233
+ const baselineOk = baselineVerdict !== "FAIL";
234
+ results.push({
235
+ scenario: "baseline-conformant-mock",
236
+ expected: "no failures",
237
+ verdict: baselineVerdict,
238
+ ok: baselineOk,
239
+ failed: baselineChecks.filter((check) => check.status === "fail").map((c) => c.id),
240
+ });
241
+
242
+ for (const { defect, mustFail } of PLANTED_DEFECTS) {
243
+ const checks = await runPropagatorSuite(
244
+ async () => new MockPropagatorDriver(defect),
245
+ { corpus, leak },
246
+ );
247
+ const verdict = computeVerdict(checks);
248
+ const failedIds = checks.filter((check) => check.status === "fail").map((c) => c.id);
249
+ const caught = verdict === "FAIL" && failedIds.includes(mustFail);
250
+ results.push({
251
+ scenario: `planted:${defect}`,
252
+ expected: `FAIL at ${mustFail}`,
253
+ verdict,
254
+ ok: caught,
255
+ failed: failedIds,
256
+ });
257
+ }
258
+
259
+ return { ok: results.every((entry) => entry.ok), results, bands: BANDS_DEFAULT };
260
+ }
261
+
262
+ export function formatSelfTestReport(outcome) {
263
+ const lines = [];
264
+ lines.push(
265
+ `conformance self-test — ${outcome.ok ? "OK (the gate has been seen to fail)" : "BROKEN"}`,
266
+ );
267
+ for (const entry of outcome.results) {
268
+ const marker = entry.ok ? "ok " : "MISS";
269
+ lines.push(
270
+ ` [${marker}] ${entry.scenario}: expected ${entry.expected}, verdict ${entry.verdict}` +
271
+ (entry.failed.length > 0 ? ` (failed: ${entry.failed.join(", ")})` : ""),
272
+ );
273
+ }
274
+ return lines.join("\n");
275
+ }
@@ -0,0 +1,68 @@
1
+ /**
2
+ * The self-test's corpus: generated at runtime from the independent two-body
3
+ * closed form, in exactly the vectors.json format the real kits use
4
+ * (space-data-network-modules propagator/keplerian-reference/vectors/ is the
5
+ * committed exemplar). Generating rather than committing keeps the self-test
6
+ * free of a data file that could drift from the generator — the corpus IS the
7
+ * generator here, which is admissible only because the self-test's job is to
8
+ * prove the SUITE can fail, not to conformance-test a real module.
9
+ */
10
+
11
+ import { ReferenceFrame, StateFlags } from "./abiDriver.js";
12
+ import { GENERIC_ELEMENTS } from "./propagatorSuite.js";
13
+ import { MU, propagateTwoBody } from "./twoBodyReference.js";
14
+
15
+ /** The exemplar corpus's tolerance bands, per quantity. */
16
+ export const BANDS_DEFAULT = Object.freeze({
17
+ position: Object.freeze({ abs: 1e-6, rel: 1e-9 }),
18
+ velocity: Object.freeze({ abs: 1e-9, rel: 1e-9 }),
19
+ time: Object.freeze({ abs: 1e-9, rel: 0 }),
20
+ });
21
+
22
+ export function buildSelfTestCorpus() {
23
+ const offsetsMinutes = [0, 45, 720];
24
+ const cases = [];
25
+ for (const elements of GENERIC_ELEMENTS) {
26
+ for (const minutes of offsetsMinutes) {
27
+ const julianDate = elements.epochJd + minutes / 1440;
28
+ const { position, velocity } = propagateTwoBody(elements, julianDate);
29
+ cases.push({
30
+ id: `self-test-${elements.noradCatId}@+${minutes}min`,
31
+ tier: "B",
32
+ operation: "plugin_propagate",
33
+ params: { elements, julianDate },
34
+ expect: {
35
+ "position.0": position[0],
36
+ "position.1": position[1],
37
+ "position.2": position[2],
38
+ "velocity.0": velocity[0],
39
+ "velocity.1": velocity[1],
40
+ "velocity.2": velocity[2],
41
+ epoch: julianDate,
42
+ reference_frame: ReferenceFrame.ECEF,
43
+ flags: StateFlags.VALID,
44
+ },
45
+ band: BANDS_DEFAULT,
46
+ });
47
+ }
48
+ }
49
+
50
+ return {
51
+ schemaVersion: 1,
52
+ conformance: {
53
+ model: "two-body point-mass, no drag, no J2, no third bodies",
54
+ mu: MU,
55
+ units: "SI throughout: metres, metres/second, Julian days, degrees on input",
56
+ outputFrame: `ECEF (${ReferenceFrame.ECEF})`,
57
+ },
58
+ tolerancePolicy: "fail <=> |observed - expected| > abs + rel * |expected|",
59
+ invariants: [
60
+ { id: "vis-viva-closure", tier: "C", applies: "every propagated state" },
61
+ { id: "period-closure", tier: "C", applies: "every element set" },
62
+ { id: "determinism", tier: "C", applies: "every case" },
63
+ { id: "frame-and-flags-declared", tier: "C", applies: "every propagated state" },
64
+ { id: "refusal-is-typed", tier: "C", applies: "every documented failure" },
65
+ ],
66
+ cases,
67
+ };
68
+ }
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Independent two-body closed form, for the conformance runner's SELF-TEST.
3
+ *
4
+ * This is adjudication scaffolding, not product physics: the finding's Tier B
5
+ * definition REQUIRES anchors computed independently of any module under test
6
+ * (graph/findings/official-harness-shapes.md §5), and the self-test needs a
7
+ * conformant baseline to prove that every planted defect is caught. Written
8
+ * from the textbook relations; it mirrors — by construction, not by copy-check
9
+ * — the reference corpus generator in space-data-network-modules
10
+ * propagator/keplerian-reference/vectors/index.mjs. Where the two agree, they
11
+ * agree because two-body motion has one answer.
12
+ */
13
+
14
+ /// WGS-72 gravitational parameter, m^3/s^2 — the model OMM mean elements are
15
+ /// fitted under.
16
+ export const MU = 398600.8e9;
17
+
18
+ /// Earth rotation rate, rad/s (IAU 1982).
19
+ export const EARTH_ROTATION_RATE = 7.292115146706979e-5;
20
+
21
+ export const SECONDS_PER_DAY = 86400.0;
22
+ export const J2000_JD = 2451545.0;
23
+
24
+ /** `fail <=> |observed - expected| > abs + rel * |expected|` */
25
+ export function withinBand(observed, expected, band) {
26
+ return Math.abs(observed - expected) <= band.abs + band.rel * Math.abs(expected);
27
+ }
28
+
29
+ /** Solve Kepler's equation independently of any module under test. */
30
+ export function solveKepler(meanAnomaly, eccentricity) {
31
+ let M = meanAnomaly % (2 * Math.PI);
32
+ if (M < 0) M += 2 * Math.PI;
33
+ let E = eccentricity < 0.8 ? M : Math.PI;
34
+ for (let i = 0; i < 200; i += 1) {
35
+ const f = E - eccentricity * Math.sin(E) - M;
36
+ const fp = 1 - eccentricity * Math.cos(E);
37
+ const d = f / fp;
38
+ E -= d;
39
+ if (Math.abs(d) < 1e-15) return E;
40
+ }
41
+ throw new Error("reference Kepler solve did not converge");
42
+ }
43
+
44
+ /** Greenwich Mean Sidereal Time, radians (IAU 1982). */
45
+ export function gmstRadians(julianDate) {
46
+ const tut1 = (julianDate - J2000_JD) / 36525.0;
47
+ const seconds =
48
+ 67310.54841 +
49
+ (876600.0 * 3600.0 + 8640184.812866) * tut1 +
50
+ 0.093104 * tut1 * tut1 -
51
+ 6.2e-6 * tut1 * tut1 * tut1;
52
+ let gmst = (seconds * ((2 * Math.PI) / SECONDS_PER_DAY)) % (2 * Math.PI);
53
+ if (gmst < 0) gmst += 2 * Math.PI;
54
+ return gmst;
55
+ }
56
+
57
+ /**
58
+ * Closed-form two-body propagation from mean elements to an ECEF state, in
59
+ * METERS (the ABI's normative unit — the km/meters contradiction was W0.1).
60
+ */
61
+ export function propagateTwoBody(elements, julianDate) {
62
+ const {
63
+ epochJd,
64
+ meanMotionRevPerDay,
65
+ eccentricity,
66
+ inclinationDeg,
67
+ raOfAscNodeDeg,
68
+ argOfPericenterDeg,
69
+ meanAnomalyDeg,
70
+ } = elements;
71
+
72
+ const deg = Math.PI / 180;
73
+ const n = (meanMotionRevPerDay * 2 * Math.PI) / SECONDS_PER_DAY;
74
+ const a = Math.cbrt(MU / (n * n));
75
+ const dt = (julianDate - epochJd) * SECONDS_PER_DAY;
76
+ const M = meanAnomalyDeg * deg + n * dt;
77
+ const E = solveKepler(M, eccentricity);
78
+
79
+ const cosE = Math.cos(E);
80
+ const sinE = Math.sin(E);
81
+ const beta = Math.sqrt(1 - eccentricity * eccentricity);
82
+ const xPqw = a * (cosE - eccentricity);
83
+ const yPqw = a * beta * sinE;
84
+ const eDot = n / (1 - eccentricity * cosE);
85
+ const vxPqw = -a * sinE * eDot;
86
+ const vyPqw = a * beta * cosE * eDot;
87
+
88
+ const O = raOfAscNodeDeg * deg;
89
+ const i = inclinationDeg * deg;
90
+ const w = argOfPericenterDeg * deg;
91
+ const cO = Math.cos(O);
92
+ const sO = Math.sin(O);
93
+ const ci = Math.cos(i);
94
+ const si = Math.sin(i);
95
+ const cw = Math.cos(w);
96
+ const sw = Math.sin(w);
97
+
98
+ const r11 = cO * cw - sO * sw * ci;
99
+ const r12 = -cO * sw - sO * cw * ci;
100
+ const r21 = sO * cw + cO * sw * ci;
101
+ const r22 = -sO * sw + cO * cw * ci;
102
+ const r31 = sw * si;
103
+ const r32 = cw * si;
104
+
105
+ const xI = r11 * xPqw + r12 * yPqw;
106
+ const yI = r21 * xPqw + r22 * yPqw;
107
+ const zI = r31 * xPqw + r32 * yPqw;
108
+ const vxI = r11 * vxPqw + r12 * vyPqw;
109
+ const vyI = r21 * vxPqw + r22 * vyPqw;
110
+ const vzI = r31 * vxPqw + r32 * vyPqw;
111
+
112
+ const theta = gmstRadians(julianDate);
113
+ const ct = Math.cos(theta);
114
+ const st = Math.sin(theta);
115
+
116
+ const x = ct * xI + st * yI;
117
+ const y = -st * xI + ct * yI;
118
+ const z = zI;
119
+ const vxRot = ct * vxI + st * vyI;
120
+ const vyRot = -st * vxI + ct * vyI;
121
+
122
+ return {
123
+ position: [x, y, z],
124
+ velocity: [
125
+ vxRot + EARTH_ROTATION_RATE * y,
126
+ vyRot - EARTH_ROTATION_RATE * x,
127
+ vzI,
128
+ ],
129
+ semiMajorAxis: a,
130
+ meanMotion: n,
131
+ };
132
+ }
@@ -0,0 +1,206 @@
1
+ // ===========================================================================
2
+ // GENERATED FILE — DO NOT EDIT.
3
+ //
4
+ // Source of truth : schemas/orbpro/Events.fbs
5
+ // Generator : scripts/generate-events-abi.mjs
6
+ // Drift gate : scripts/check-events-abi.mjs (runs in `npm test`)
7
+ // Contract : docs/events-abi.md
8
+ //
9
+ // These constants exist so that no JavaScript consumer ever hard-codes a byte
10
+ // offset again. Read a state vector with ORBPRO_STATE_VECTOR.offsets.position,
11
+ // never with the literal 8.
12
+ // ===========================================================================
13
+
14
+ export { ReferenceFrame } from "./propagator-abi.js";
15
+
16
+ export const RootMethod = Object.freeze({
17
+ BRENT: 0,
18
+ BISECTION: 1,
19
+ ILLINOIS: 2,
20
+ });
21
+
22
+ export const CrossingDirection = Object.freeze({
23
+ FALLING: -1,
24
+ ANY: 0,
25
+ RISING: 1,
26
+ });
27
+
28
+ export const RootStatus = Object.freeze({
29
+ CONVERGED: 0,
30
+ MAX_ITERATIONS: 1,
31
+ FLAT_BRACKET: 2,
32
+ DISCONTINUOUS: 3,
33
+ TRUNCATED: 4,
34
+ EPOCH_RESOLUTION_LIMITED: 5,
35
+ });
36
+
37
+ export const Sign = Object.freeze({
38
+ NEGATIVE: -1,
39
+ ZERO: 0,
40
+ POSITIVE: 1,
41
+ });
42
+
43
+ export const EventFlags = Object.freeze({
44
+ NONE: 0,
45
+ INTERVAL_OPEN_AT_START: 1,
46
+ INTERVAL_OPEN_AT_END: 2,
47
+ TANGENTIAL: 4,
48
+ ON_SCAN_BOUNDARY: 8,
49
+ BACKWARD: 16,
50
+ STOP_CONDITION: 32,
51
+ });
52
+
53
+ export const EventError = Object.freeze({
54
+ INTERNAL: -25,
55
+ SUPPLY_COUNT_MISMATCH: -24,
56
+ PROTOCOL_ORDER: -23,
57
+ BUFFER_TOO_SMALL: -22,
58
+ UNKNOWN_COMPONENT: -21,
59
+ NOT_STARTED: -20,
60
+ UNPHYSICAL: -6,
61
+ NOT_CONVERGED: -5,
62
+ BAD_INPUT: -4,
63
+ NULL_OUTPUT: -3,
64
+ BAD_OBJECT_INDEX: -2,
65
+ NOT_CONFIGURED: -1,
66
+ OK: 0,
67
+ });
68
+
69
+ export const ComponentContinuity = Object.freeze({
70
+ SMOOTH: 0,
71
+ PIECEWISE: 1,
72
+ DISCRETE: 2,
73
+ });
74
+
75
+ export const ORBPRO_ROOT_POLICY = Object.freeze({
76
+ name: "RootPolicy",
77
+ cName: "OrbProRootPolicy",
78
+ size: 32,
79
+ alignment: 8,
80
+ offsets: Object.freeze({
81
+ scan_step_seconds: 0,
82
+ epoch_tolerance_seconds: 8,
83
+ value_tolerance: 16,
84
+ max_iterations: 24,
85
+ method: 28,
86
+ }),
87
+ fields: Object.freeze({
88
+ scan_step_seconds: Object.freeze({ offset: 0, size: 8, length: 1, view: "Float64" }),
89
+ epoch_tolerance_seconds: Object.freeze({ offset: 8, size: 8, length: 1, view: "Float64" }),
90
+ value_tolerance: Object.freeze({ offset: 16, size: 8, length: 1, view: "Float64" }),
91
+ max_iterations: Object.freeze({ offset: 24, size: 4, length: 1, view: "Uint32" }),
92
+ method: Object.freeze({ offset: 28, size: 1, length: 1, view: "Uint8" }),
93
+ }),
94
+ });
95
+
96
+ export const ORBPRO_EVENT_INTERVAL = Object.freeze({
97
+ name: "EventInterval",
98
+ cName: "OrbProEventInterval",
99
+ size: 56,
100
+ alignment: 8,
101
+ offsets: Object.freeze({
102
+ start_jd_day: 0,
103
+ start_seconds: 8,
104
+ stop_jd_day: 16,
105
+ stop_seconds: 24,
106
+ max_events: 32,
107
+ max_evaluations: 36,
108
+ occurrence: 40,
109
+ component: 44,
110
+ flags: 48,
111
+ direction: 52,
112
+ }),
113
+ fields: Object.freeze({
114
+ start_jd_day: Object.freeze({ offset: 0, size: 8, length: 1, view: "Float64" }),
115
+ start_seconds: Object.freeze({ offset: 8, size: 8, length: 1, view: "Float64" }),
116
+ stop_jd_day: Object.freeze({ offset: 16, size: 8, length: 1, view: "Float64" }),
117
+ stop_seconds: Object.freeze({ offset: 24, size: 8, length: 1, view: "Float64" }),
118
+ max_events: Object.freeze({ offset: 32, size: 4, length: 1, view: "Uint32" }),
119
+ max_evaluations: Object.freeze({ offset: 36, size: 4, length: 1, view: "Uint32" }),
120
+ occurrence: Object.freeze({ offset: 40, size: 4, length: 1, view: "Uint32" }),
121
+ component: Object.freeze({ offset: 44, size: 4, length: 1, view: "Uint32" }),
122
+ flags: Object.freeze({ offset: 48, size: 4, length: 1, view: "Uint32" }),
123
+ direction: Object.freeze({ offset: 52, size: 1, length: 1, view: "Int8" }),
124
+ }),
125
+ });
126
+
127
+ export const ORBPRO_EVENT_HIT = Object.freeze({
128
+ name: "EventHit",
129
+ cName: "OrbProEventHit",
130
+ size: 48,
131
+ alignment: 8,
132
+ offsets: Object.freeze({
133
+ epoch_jd_day: 0,
134
+ epoch_seconds: 8,
135
+ value: 16,
136
+ component: 24,
137
+ iterations: 28,
138
+ evaluations: 32,
139
+ flags: 36,
140
+ direction: 40,
141
+ status: 41,
142
+ }),
143
+ fields: Object.freeze({
144
+ epoch_jd_day: Object.freeze({ offset: 0, size: 8, length: 1, view: "Float64" }),
145
+ epoch_seconds: Object.freeze({ offset: 8, size: 8, length: 1, view: "Float64" }),
146
+ value: Object.freeze({ offset: 16, size: 8, length: 1, view: "Float64" }),
147
+ component: Object.freeze({ offset: 24, size: 4, length: 1, view: "Uint32" }),
148
+ iterations: Object.freeze({ offset: 28, size: 4, length: 1, view: "Uint32" }),
149
+ evaluations: Object.freeze({ offset: 32, size: 4, length: 1, view: "Uint32" }),
150
+ flags: Object.freeze({ offset: 36, size: 4, length: 1, view: "Uint32" }),
151
+ direction: Object.freeze({ offset: 40, size: 1, length: 1, view: "Int8" }),
152
+ status: Object.freeze({ offset: 41, size: 1, length: 1, view: "Uint8" }),
153
+ }),
154
+ });
155
+
156
+ export const ORBPRO_EVENT_SCAN_SUMMARY = Object.freeze({
157
+ name: "EventScanSummary",
158
+ cName: "OrbProEventScanSummary",
159
+ size: 32,
160
+ alignment: 8,
161
+ offsets: Object.freeze({
162
+ initial_value: 0,
163
+ final_value: 8,
164
+ component: 16,
165
+ crossing_count: 20,
166
+ evaluation_count: 24,
167
+ initial_sign: 28,
168
+ final_sign: 29,
169
+ }),
170
+ fields: Object.freeze({
171
+ initial_value: Object.freeze({ offset: 0, size: 8, length: 1, view: "Float64" }),
172
+ final_value: Object.freeze({ offset: 8, size: 8, length: 1, view: "Float64" }),
173
+ component: Object.freeze({ offset: 16, size: 4, length: 1, view: "Uint32" }),
174
+ crossing_count: Object.freeze({ offset: 20, size: 4, length: 1, view: "Uint32" }),
175
+ evaluation_count: Object.freeze({ offset: 24, size: 4, length: 1, view: "Uint32" }),
176
+ initial_sign: Object.freeze({ offset: 28, size: 1, length: 1, view: "Int8" }),
177
+ final_sign: Object.freeze({ offset: 29, size: 1, length: 1, view: "Int8" }),
178
+ }),
179
+ });
180
+
181
+ export const ORBPRO_EVENT_STATE_REQUEST = Object.freeze({
182
+ name: "EventStateRequest",
183
+ cName: "OrbProEventStateRequest",
184
+ size: 16,
185
+ alignment: 4,
186
+ offsets: Object.freeze({
187
+ epoch_count: 0,
188
+ object_count: 4,
189
+ reference_frame: 8,
190
+ flags: 12,
191
+ }),
192
+ fields: Object.freeze({
193
+ epoch_count: Object.freeze({ offset: 0, size: 4, length: 1, view: "Uint32" }),
194
+ object_count: Object.freeze({ offset: 4, size: 4, length: 1, view: "Uint32" }),
195
+ reference_frame: Object.freeze({ offset: 8, size: 1, length: 1, view: "Uint8" }),
196
+ flags: Object.freeze({ offset: 12, size: 4, length: 1, view: "Uint32" }),
197
+ }),
198
+ });
199
+
200
+ export const ORBPRO_EVENTS_ABI = Object.freeze({
201
+ RootPolicy: ORBPRO_ROOT_POLICY,
202
+ EventInterval: ORBPRO_EVENT_INTERVAL,
203
+ EventHit: ORBPRO_EVENT_HIT,
204
+ EventScanSummary: ORBPRO_EVENT_SCAN_SUMMARY,
205
+ EventStateRequest: ORBPRO_EVENT_STATE_REQUEST,
206
+ });