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,313 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <meta name="description" content="Status: EXPERIMENTAL. The schema, the generated header, the drift gate and" />
7
+ <title>Event locator ABI — Space Data Module SDK</title>
8
+ <link rel="stylesheet" href="./styles.css" />
9
+ <!-- SDN_CONSUMER_ASSETS_START -->
10
+ <link rel="stylesheet" href="https://static.spacedatanetwork.org/assets/hd-wallet-ui/2.0.28/sdn-wallet-public-client.c3f68d1cfd88478f10d836a5e829d1dfc6a10157972cf0f7d4d319d0636f2cc4.css" integrity="sha384-fICuhN4I9xqOK1F5vGGzl26opuO+xQIvTZXMSV76lWTBiQ6AfzztMGLLDw8yRT8i" crossorigin="anonymous">
11
+ <script defer src="https://static.spacedatanetwork.org/assets/hd-wallet-ui/2.0.28/sdn-wallet-public-client.f611e7e151a3b5c38384272f8894a4c6634f0a29bf925b0e02e0e27f8db0bfff.js" integrity="sha384-R+QqsKoWJIS7iWrtPmgJ49DVnb1hSGjZokupbLInJd1PLp8RwQhtF/hnaerc33ci" crossorigin="anonymous" data-sdn-wallet-public-client="v1"></script>
12
+ <script defer src="https://static.spacedatanetwork.org/assets/sdn-stack-nav/1.0.0/sdn-stack-nav.52fde607eee38ffa116188201f50258ed50bd18c9f06c1af9678f763147a8fe5.js" integrity="sha384-dgeojhJ8vTszHXIbv7O7nZcEzqD10oUYYJjBmPrV7+kLQEUlXLQD4ek5Q7HOFiO5" crossorigin="anonymous" data-nav-style-url="https://static.spacedatanetwork.org/assets/sdn-stack-nav/1.0.0/sdn-stack-nav.36a36359ce18322185e9ff179f88175bab67d5ad84a14d9c08a54f2ff27267e7.css" data-nav-style-integrity="sha384-c4M8Fg+kYaeOYYtJVr7jJsde24IhSWkWaRyAOHEZU9jozhRck089aw+mnHwxymds" data-wallet-client-url="https://static.spacedatanetwork.org/assets/hd-wallet-ui/2.0.28/sdn-wallet-public-client.f611e7e151a3b5c38384272f8894a4c6634f0a29bf925b0e02e0e27f8db0bfff.js" data-wallet-client-integrity="sha384-R+QqsKoWJIS7iWrtPmgJ49DVnb1hSGjZokupbLInJd1PLp8RwQhtF/hnaerc33ci" data-wallet-style-url="https://static.spacedatanetwork.org/assets/hd-wallet-ui/2.0.28/sdn-wallet-public-client.c3f68d1cfd88478f10d836a5e829d1dfc6a10157972cf0f7d4d319d0636f2cc4.css" data-wallet-style-integrity="sha384-fICuhN4I9xqOK1F5vGGzl26opuO+xQIvTZXMSV76lWTBiQ6AfzztMGLLDw8yRT8i" data-wallet-client-id="sdn-module-sdk-pages-v1" data-wallet-callback-uri="https://digitalarsenal.github.io/space-data-module-sdk/wallet-callback.html" data-wallet-registry-sha256="e1ce6fe903c9700484a8a87d96581c8cad97063dabf63030b4518a31a3bdaa93"></script>
13
+ <!-- SDN_CONSUMER_ASSETS_END -->
14
+ </head>
15
+ <body>
16
+ <a class="skip-link" href="#main">Skip to content</a>
17
+ <sdn-stack-nav active="module-sdk"></sdn-stack-nav>
18
+ <header class="site-header">
19
+ <a class="site-brand" href="./">Space Data Module SDK</a>
20
+ <nav class="header-links" aria-label="Site">
21
+ <a href="./byo-wasm-quickstart.html">Quickstart</a>
22
+ <a href="./conformance.html">Conformance</a>
23
+ <a href="https://github.com/DigitalArsenal/space-data-module-sdk">GitHub</a>
24
+ <a class="stack-button" href="#stack">Stack</a>
25
+ </nav>
26
+ </header>
27
+ <div class="shell">
28
+ <nav class="sidebar" id="sidebar" aria-label="Documentation">
29
+ <h2>Start here</h2><ul>
30
+ <li><a href="./index.html"><span>Harness family matrix</span></a></li>
31
+ <li><a href="./byo-wasm-quickstart.html"><span>BYO-wasm quickstart</span></a></li>
32
+ <li><a href="./conformance.html"><span>Conformance kit</span></a></li>
33
+ <li><a href="./protect-and-sign.html"><span>Protect and sign</span></a></li>
34
+ <li><a href="./publication-submission.html"><span>Publication and listing</span></a></li>
35
+ </ul>
36
+ <h2>Dynamics</h2><ul>
37
+ <li><a href="./families/propagator.html"><span>Propagator</span><span class="tag tag-shipped">shipped</span></a></li>
38
+ <li><a href="./families/maneuver.html"><span>Maneuver</span><span class="tag tag-experimental">experimental</span></a></li>
39
+ <li><a href="./families/propulsion.html"><span>Propulsion</span><span class="tag tag-planned">planned</span></a></li>
40
+ <li><a href="./families/attitude.html"><span>Attitude</span><span class="tag tag-planned">planned</span></a></li>
41
+ <li><a href="./families/gnc.html"><span>GNC</span><span class="tag tag-planned">planned</span></a></li>
42
+ </ul>
43
+ <h2>Environment and interaction</h2><ul>
44
+ <li><a href="./families/rf.html"><span>RF</span><span class="tag tag-designed">designed</span></a></li>
45
+ <li><a href="./families/sensor.html"><span>Sensor</span><span class="tag tag-planned">planned</span></a></li>
46
+ <li><a href="./families/signature.html"><span>Signature</span><span class="tag tag-planned">planned</span></a></li>
47
+ <li><a href="./families/environment.html"><span>Environment</span><span class="tag tag-planned">planned</span></a></li>
48
+ <li><a href="./families/obstruction.html"><span>Obstruction</span><span class="tag tag-designed">designed</span></a></li>
49
+ </ul>
50
+ <h2>Event physics</h2><ul>
51
+ <li><a href="./families/breakup.html"><span>Breakup</span><span class="tag tag-planned">planned</span></a></li>
52
+ <li><a href="./families/reentry.html"><span>Reentry</span><span class="tag tag-planned">planned</span></a></li>
53
+ <li><a href="./families/conjunction.html"><span>Conjunction</span><span class="tag tag-designed">designed</span></a></li>
54
+ <li><a href="./families/effects.html"><span>Effects</span><span class="tag tag-planned">planned</span></a></li>
55
+ </ul>
56
+ <h2>Estimation, data and logic</h2><ul>
57
+ <li><a href="./families/estimation.html"><span>Estimation</span><span class="tag tag-experimental">experimental</span></a></li>
58
+ <li><a href="./families/data-source.html"><span>Data source</span><span class="tag tag-shipped">shipped</span></a></li>
59
+ <li><a href="./families/analytics.html"><span>Analytics</span><span class="tag tag-experimental">experimental</span></a></li>
60
+ <li><a href="./families/scheduler.html"><span>Scheduler</span><span class="tag tag-planned">planned</span></a></li>
61
+ <li><a href="./families/behavior.html"><span>Behavior</span><span class="tag tag-planned">planned</span></a></li>
62
+ </ul>
63
+ <h2>Runtime contract</h2><ul>
64
+ <li><a href="./events-abi.html" aria-current="page"><span>Event locator ABI</span></a></li>
65
+ <li><a href="./module-publication-standard.html"><span>Module publication standard</span></a></li>
66
+ <li><a href="./browser-wasmedge-isomorphic.html"><span>Browser / WasmEdge isomorphism</span></a></li>
67
+ <li><a href="./isomorphic-pthreads.html"><span>Isomorphic pthreads</span></a></li>
68
+ <li><a href="./tri-runtime-parity.html"><span>Tri-runtime parity</span></a></li>
69
+ <li><a href="./tri-runtime-parity-gate.html"><span>Tri-runtime parity gate</span></a></li>
70
+ <li><a href="./testing-harness.html"><span>Testing harness</span></a></li>
71
+ <li><a href="./language-runtime-matrix.html"><span>Language and runtime matrix</span></a></li>
72
+ </ul>
73
+ <h2>Host surfaces</h2><ul>
74
+ <li><a href="./flatsql-host-contract.html"><span>FlatSQL host contract</span></a></li>
75
+ <li><a href="./flatsql-streaming-standard.html"><span>FlatSQL streaming standard</span></a></li>
76
+ <li><a href="./secrets-capability.html"><span>Credential lanes</span></a></li>
77
+ <li><a href="./protocol-installation.html"><span>Protocol installation</span></a></li>
78
+ <li><a href="./gpu-module-abi.html"><span>GPU module ABI</span></a></li>
79
+ <li><a href="./module-bundle-runtime-plan.html"><span>Module bundle runtime</span></a></li>
80
+ <li><a href="./isomorphic-sdn-runtime-plan.html"><span>Isomorphic SDN runtime</span></a></li>
81
+ <li><a href="./node-red-default-node-parity.html"><span>Node-RED node parity</span></a></li>
82
+ </ul>
83
+ </nav>
84
+ <main class="doc" id="main">
85
+ <h1>Event locator ABI</h1>
86
+ <p><strong>Status: EXPERIMENTAL.</strong> The schema, the generated header, the drift gate and the runner have landed and are measured. There is no conformance kit and no reference module yet, so this is not <code>Shipped</code> and a commercial module should not be sold against it. It is the <strong>event-location sub-harness of the <a href="./families/analytics.html">Analytics</a> family</strong> — not a twentieth harness family. The nineteen-family taxonomy is owner-ratified and this ABI does not amend it.</p>
87
+ <h2 id="doctrine"><a class="anchor" href="#doctrine" aria-hidden="true">#</a>Doctrine</h2>
88
+ <p><strong>One ABI, N locators.</strong> Eclipse (umbra / penumbra / antumbra, any number of occulting bodies), station contact with masks and light time, sensor-FOV intrusion, apsides, node crossings and a propagate-to-condition stop are the SAME computation: a vector of scalar event functions <code>g_i(t)</code> whose sign changes are refined to roots. They differ only in <code>g</code>.</p>
89
+ <p><strong>The locator supplies <code>g</code>. It supplies nothing else.</strong> The bracketing scan, the root refinement, the direction filtering, the occurrence counting, the endpoint bookkeeping and the epoch ordering live in <code>include/orbpro/orbpro_event_runner.h</code>, which every locator compiles in unchanged. &quot;Adding a new locator requires no change to the runner&quot; is therefore a structural fact and not a promise: there is no runner to change, only a <code>g</code> to write. A locator that needed the runner to change would be evidence that this ABI is wrong.</p>
90
+ <p><strong>The state source is PULLED, never imported.</strong> A locator needs states at epochs only it can choose — Brent picks the next abscissa from the last three. It does not import a host function to fetch them. <code>plugin_event_next</code> reports the epochs it wants, the consumer evaluates them through whatever module is wired to the propagator port, and <code>plugin_event_supply</code> feeds the states back. Three consequences, and they are the reason the design is this way:</p>
91
+ <ul>
92
+ <li>The propagator stays a <strong>port</strong>, per the pluggable-propagation law (owner, 2026-07-29). No provider is named anywhere in this ABI.</li>
93
+ <li><strong>No new host capability and no new import.</strong> The browser, native-WasmEdge and Docker-WasmEdge lanes are byte-identical by construction, because the consumer shuttles buffers and decides nothing.</li>
94
+ <li>The refinement stays <strong>inside the guest</strong>. A host-side root finder would be physics in JavaScript and its arithmetic would differ per lane.</li>
95
+ </ul>
96
+ <p><strong>A hit is a crossing, not an interval.</strong> Apsides and node crossings are instants; eclipse and contact are intervals. Emitting crossings and pairing them is total over both; an interval-shaped hit would put a sentinel in half the family. See <a href="#interval-pairing">Interval pairing</a>.</p>
97
+ <h2 id="capability"><a class="anchor" href="#capability" aria-hidden="true">#</a>Capability</h2>
98
+ <p><strong>None.</strong> An event locator declares no capability at all: it computes, and every byte it consumes arrives through its own exports. This is not an oversight to be corrected later — the pull protocol exists precisely so that locating an eclipse does not require a module to reach outside itself.</p>
99
+ <p>A locator that also wants to <em>emit</em> an SDS <code>$EVL</code> record through the invoke surface declares whatever that surface already requires, and nothing new.</p>
100
+ <h2 id="the-export-set"><a class="anchor" href="#the-export-set" aria-hidden="true">#</a>The export set</h2>
101
+ <p>Three exports are the locator's. Seven are the runner's, and <code>ORBPRO_EVENT_RUNNER_EXPORTS(runner)</code> emits all seven.</p>
102
+ <h3 id="the-locator-writes-these"><a class="anchor" href="#the-locator-writes-these" aria-hidden="true">#</a>The locator writes these</h3>
103
+ <div class="table-wrap"><table><thead><tr><th>Export</th><th>Signature</th><th>Returns</th></tr></thead><tbody><tr><td><code>plugin_event_describe</code></td><td><code>int32_t(uint8_t* out, uint32_t capacity)</code></td><td>bytes of the <code>EventLocatorDescription</code> FlatBuffer written, the required size if <code>capacity</code> is short, or a negative <a href="#error-codes">error code</a></td></tr><tr><td><code>plugin_event_configure</code></td><td><code>int32_t(const uint8_t* config, uint32_t len)</code></td><td>components configured (&gt;0), or negative</td></tr><tr><td><code>plugin_event_eval</code></td><td><code>int32_t(double epoch_jd_day, double epoch_seconds, const OrbProStateVector* states, uint32_t object_count, double* g_out, uint32_t component_count)</code></td><td><code>0</code>, or negative</td></tr></tbody></table></div>
104
+ <p><code>plugin_event_eval</code> <strong>must be a pure function of its arguments.</strong> A <code>g</code> that depends on call order or on a cached previous epoch breaks bracketing: Brent evaluates epochs out of chronological order by design. The conformance obligation is that <code>plugin_event_eval</code> at an epoch and the <code>g</code> the runner used to find a root at that epoch agree exactly.</p>
105
+ <p>It is vector-valued on purpose: one state fetch feeds every component, so a ten-component locator costs one propagation per epoch rather than ten. That is what makes &quot;eclipse against three occulting bodies&quot; and &quot;contact with eight stations&quot; ordinary rather than special.</p>
106
+ <h3 id="the-runner-provides-these"><a class="anchor" href="#the-runner-provides-these" aria-hidden="true">#</a>The runner provides these</h3>
107
+ <div class="table-wrap"><table><thead><tr><th>Export</th><th>Signature</th><th>Returns</th></tr></thead><tbody><tr><td><code>plugin_event_begin</code></td><td><code>int32_t(const OrbProEventInterval*, const OrbProRootPolicy*)</code></td><td><code>0</code>, or negative</td></tr><tr><td><code>plugin_event_next</code></td><td><code>int32_t(OrbProEventStateRequest*, double* epochs, uint32_t epoch_capacity)</code></td><td>epochs written (<code>2 * n</code> doubles), <code>0</code> when finished, or negative</td></tr><tr><td><code>plugin_event_supply</code></td><td><code>int32_t(const OrbProStateVector*, uint32_t count)</code></td><td><code>0</code>, or negative</td></tr><tr><td><code>plugin_event_hit_count</code></td><td><code>int32_t(void)</code></td><td>hits held</td></tr><tr><td><code>plugin_event_hits</code></td><td><code>int32_t(OrbProEventHit*, uint32_t capacity)</code></td><td>hits written, or negative</td></tr><tr><td><code>plugin_event_summaries</code></td><td><code>int32_t(OrbProEventScanSummary*, uint32_t capacity)</code></td><td>summaries written, or negative</td></tr><tr><td><code>plugin_event_destroy</code></td><td><code>void(void)</code></td><td>—</td></tr></tbody></table></div>
108
+ <p>Plus <code>memory</code>, <code>plugin_alloc</code> and <code>plugin_free</code>, as every family requires.</p>
109
+ <h2 id="the-pull-protocol"><a class="anchor" href="#the-pull-protocol" aria-hidden="true">#</a>The pull protocol</h2>
110
+ <div class="codeblock"><pre><code> configure ──► begin ──► next ──┬──► (n &gt; 0) consumer propagates ──► supply ──┐
111
+ │ │
112
+ └──► (n == 0) ──► hits / summaries │
113
+ ▲ │
114
+ └─────────────────────────────────────────────┘</code></pre></div>
115
+ <ul>
116
+ <li><code>next</code> writes <code>2 * n</code> doubles: <code>(jd_day, seconds)</code> <strong>pairs</strong>, in scan order, and fills the <code>OrbProEventStateRequest</code> with the object count and the frame the states must be in.</li>
117
+ <li>The consumer evaluates <strong>every</strong> requested epoch for <strong>every</strong> object.</li>
118
+ <li><code>supply</code> takes <code>epoch_count * object_count</code> states, ordered <strong>epoch-major</strong>: <code>states[e * object_count + o]</code> is object <code>o</code> at epoch <code>e</code>. A count that is not exactly that is <code>SUPPLY_COUNT_MISMATCH</code> — never a partial evaluation, because a locator that accepted a short supply would report events computed from another object's trajectory.</li>
119
+ <li>The <strong>coarse scan</strong> batches (up to <code>ORBPRO_EVENT_SCAN_BATCH</code>, default 64), so a consumer drives it through <code>plugin_propagate_batch</code>. The <strong>refinement</strong> asks for exactly one epoch: it is inherently sequential.</li>
120
+ <li>Brackets found in a scan batch are refined <strong>before</strong> the scan advances, so occurrence counting is chronological and a <code>max_events</code> stop lands on the right event.</li>
121
+ </ul>
122
+ <p>Calling <code>next</code> twice without an intervening <code>supply</code>, or <code>supply</code> with nothing outstanding, is <code>PROTOCOL_ORDER</code>.</p>
123
+ <h2 id="wire-layout"><a class="anchor" href="#wire-layout" aria-hidden="true">#</a>Wire layout</h2>
124
+ <p>Generated header: <code>include/orbpro/orbpro_events_abi.h</code>. Source of truth: <code>schemas/orbpro/Events.fbs</code>. Drift gate: <code>node scripts/check-events-abi.mjs</code>.</p>
125
+ <div class="table-wrap"><table><thead><tr><th>Struct</th><th>Size</th><th>Align</th></tr></thead><tbody><tr><td><code>OrbProRootPolicy</code></td><td>32</td><td>8</td></tr><tr><td><code>OrbProEventInterval</code></td><td>56</td><td>8</td></tr><tr><td><code>OrbProEventHit</code></td><td>48</td><td>8</td></tr><tr><td><code>OrbProEventScanSummary</code></td><td>32</td><td>8</td></tr><tr><td><code>OrbProEventStateRequest</code></td><td>16</td><td>4</td></tr></tbody></table></div>
126
+ <p><code>OrbProStateVector</code> (64 bytes) and <code>OrbProReferenceFrame</code> are <strong>not</strong> redeclared here — they come from <code>orbpro/orbpro_propagator_abi.h</code>, which the generated header includes. A second <code>ReferenceFrame</code> typedef is refused by <code>scripts/check-reference-frame-uniqueness.mjs</code> C1, and it would fail to compile the moment both headers met in one translation unit, which they always do.</p>
127
+ <p>Every struct has a generated <code>_init</code> that zeroes the padding, and a generated <code>_set_&lt;field&gt;</code> for every enum field that clears the padding behind it. Use them. A partial write leaves the previous call's bytes in the padding the IDL requires to be zero.</p>
128
+ <h2 id="units-frames-and-epochs"><a class="anchor" href="#units-frames-and-epochs" aria-hidden="true">#</a>Units, frames and epochs</h2>
129
+ <ul>
130
+ <li><code>scan_step_seconds</code>, <code>epoch_tolerance_seconds</code>, <code>state_epoch_resolution_seconds</code> and <code>epoch_seconds</code>: <strong>SECONDS</strong>.</li>
131
+ <li><code>value</code>, <code>goalValue</code>, <code>value_tolerance</code>, <code>initial_value</code>, <code>final_value</code>: the component's own declared <code>unit</code> string. The ABI does not convert.</li>
132
+ <li><code>reference_frame</code>: an <code>OrbProReferenceFrame</code> value. A consumer whose propagator emits another frame converts through the frames port; a consumer that cannot <strong>fails loudly</strong> rather than supplying a differently-framed state.</li>
133
+ </ul>
134
+ <h3 id="epochs-are-split-pairs-and-that-is-not-decoration"><a class="anchor" href="#epochs-are-split-pairs-and-that-is-not-decoration" aria-hidden="true">#</a>Epochs are split pairs, and that is not decoration</h3>
135
+ <p>Every epoch here is <code>(jd_day, seconds)</code>: an exactly-representable Julian day plus an offset in seconds.</p>
136
+ <p>A Julian date carried in one <code>float64</code> resolves to <code>ulp(2460000.5) = 2^-31 d =</code> <strong>4.02e-5 s</strong> — forty microseconds. A single-double JD therefore <em>cannot</em> express a stop epoch to 1e-6 s, and a locator that reported one would be reporting rounding noise. The split pair resolves 1.5e-11 s. Internally the runner works in seconds from the interval start, and never collapses the pair.</p>
137
+ <p><code>epoch_seconds</code> may exceed 86400 and is <strong>not normalized</strong>. The fine coordinate stays fine and two epochs from one scan stay directly comparable.</p>
138
+ <h3 id="the-state-source-bounds-the-answer-and-says-so"><a class="anchor" href="#the-state-source-bounds-the-answer-and-says-so" aria-hidden="true">#</a>The state source bounds the answer, and says so</h3>
139
+ <p><code>OrbProStateVector.epoch</code> is a single <code>float64</code> and is <strong>frozen</strong> — 64 bytes, on the wire, in the field. So a propagator whose ABI takes a single Julian date quantizes <code>g</code> at about 4.02e-5 s, and no refinement can resolve a root below that no matter what tolerance it is handed.</p>
140
+ <p>This is declared, not assumed. <code>EventLocatorConfig.stateEpochResolutionSeconds</code> carries the source's resolution; the runner clamps <code>epoch_tolerance_seconds</code> up to it and reports <code>ORBPRO_ROOT_STATUS_EPOCH_RESOLUTION_LIMITED</code> on every root that hit the clamp. <code>orbpro_event_runner_effective_tolerance()</code> reports the tolerance actually used.</p>
141
+ <p><strong>Consequence for acceptance criteria.</strong> A &quot;stop epoch reproducible to 1e-6 s&quot; claim is only meaningful against a state source that resolves better than 1e-6 s. Against a single-double-JD propagator the honest bar is <code>max(requested, source resolution)</code>, and the receipt must record the resolution. Extending the propagator ABI to a split epoch would be a breaking change to a frozen 64-byte struct and is an <strong>owner</strong> decision, not an oracle call.</p>
142
+ <h2 id="sentinels"><a class="anchor" href="#sentinels" aria-hidden="true">#</a>Sentinels</h2>
143
+ <div class="table-wrap"><table><thead><tr><th>Value</th><th>Meaning</th></tr></thead><tbody><tr><td><code>EventInterval.component == 0xFFFFFFFF</code></td><td>every component</td></tr><tr><td><code>EventInterval.max_events == 0</code></td><td>unbounded (still bounded by <code>max_evaluations</code> and the caller's buffer). A nonzero cap returns the first N IN SCAN ORDER and sets <code>truncated</code>, so &quot;these are the events&quot; and &quot;these are the first N&quot; are distinguishable</td></tr><tr><td><code>EventInterval.max_evaluations == 0</code></td><td>unbounded</td></tr><tr><td><code>EventInterval.occurrence == 0</code></td><td>report every qualifying crossing</td></tr><tr><td><code>RootPolicy.value_tolerance == 0</code></td><td>converge on the bracket width alone</td></tr><tr><td><code>EventLocatorConfig.stateEpochResolutionSeconds == 0</code></td><td>not declared; the runner trusts the tolerance it was given</td></tr><tr><td><code>EventLocatorDescription.maxSafeScanStepSeconds == 0</code></td><td>the locator declines to bound it — a NAMED GAP, never a pass</td></tr></tbody></table></div>
144
+ <p><code>RootPolicy.scan_step_seconds == 0</code> is <strong>not</strong> a sentinel. It is refused with <code>BAD_INPUT</code>: a silent default step is how a scan misses every event shorter than it, and the miss looks exactly like &quot;there was no event&quot;.</p>
145
+ <h3 id="the-scan-start-is-not-a-bracket"><a class="anchor" href="#the-scan-start-is-not-a-bracket" aria-hidden="true">#</a>The scan start is not a bracket</h3>
146
+ <p>An event exactly at the interval <strong>start</strong> has no preceding sample to bracket it and is therefore not a crossing. One exactly at the interval <strong>end</strong> is. The asymmetry is real and deliberate; <code>EventScanSummary.initial_sign == ZERO</code> is how a consumer learns an event sits on the start.</p>
147
+ <h2 id="identity"><a class="anchor" href="#identity" aria-hidden="true">#</a>Identity</h2>
148
+ <p>A component is a dense 0-based index, stable for the lifetime of one configuration, and it is the ONLY handle the ABI structs carry. Names live in <code>EventComponent.name</code> and resolve through the SDS parameter-catalog record — this schema deliberately carries no parameter roster of its own, because a second roster is a second source of truth.</p>
149
+ <p>Objects are positional: object <code>o</code> is the <code>o</code>-th object the consumer supplies states for, in the order <code>EventLocatorDescription.objectCount</code> declares.</p>
150
+ <h2 id="threading"><a class="anchor" href="#threading" aria-hidden="true">#</a>Threading</h2>
151
+ <p>Locators compile <code>clang --target=wasm32-wasip1-threads</code>, per the isomorphic-pthreads law. <strong>Never <code>emcc -pthread</code>.</strong></p>
152
+ <p><strong>Declare <code>wasi-sequential</code>, with <code>sequentialJustification.kind = &quot;inherently-sequential-algorithm&quot;</code>.</strong> Root refinement carries state from iteration <code>k-1</code> into iteration <code>k</code> — that is the literal definition of that justification kind, and it is also the strongest available guarantee of the step-independence and byte-identity properties above: a threaded refinement would evaluate abscissae in a nondeterministic order and would not be reproducible.</p>
153
+ <p>A <strong>parameter-catalog</strong> module, which evaluates named quantities from a state and has nothing to fan out inside one call, declares <code>wasi-sequential</code> with <code>kind = &quot;caller-level-parallelism&quot;</code> when it exposes a batch export the host shards, or <code>&quot;pure-transform&quot;</code> when it does not.</p>
154
+ <blockquote><p><strong>Known defect, still live:</strong> <code>resolveThreadModel</code> reads the compile OPTION, not <code>manifest.threadModel</code>, and otherwise infers the model from <code>runtimeTargets</code>. Pass <code>threadModel</code> explicitly to <code>compileModuleFromSource</code> and assert <code>result.threadModel</code> came back as declared, until <code>sdk-manifest-threadmodel-silently-ignored</code> lands. <code>threadModel: &quot;single-thread&quot;</code> is the <strong>legacy Emscripten</strong> model and routes to <code>em++</code>, not to the sanctioned clang toolchain — it is not a synonym for &quot;does not thread&quot;.</p></blockquote>
155
+ <p>The runner never allocates, never reads a clock and never spawns. Its state is a fixed arena sized by <code>ORBPRO_EVENT_MAX_COMPONENTS</code>, <code>ORBPRO_EVENT_MAX_OBJECTS</code>, <code>ORBPRO_EVENT_MAX_HITS</code>, <code>ORBPRO_EVENT_SCAN_BATCH</code> and <code>ORBPRO_EVENT_MAX_PENDING</code>, each overridable by the locator before the include.</p>
156
+ <h2 id="error-codes"><a class="anchor" href="#error-codes" aria-hidden="true">#</a>Error codes</h2>
157
+ <p>Every failure returns its OWN code. A locator that answers <code>-1</code> for everything is unconformable: the consumer cannot place the failure on the degradation ladder, so it cannot decide between retrying, widening the interval and refusing. Codes <code>-1..-6</code> are deliberately the same numbers and meanings as the propagator family's; the events-specific codes start at <code>-20</code> so no consumer can confuse the two tables by value.</p>
158
+ <div class="table-wrap"><table><thead><tr><th>Code</th><th>Name</th><th>When</th></tr></thead><tbody><tr><td><code>-1</code></td><td><code>NOT_CONFIGURED</code></td><td><code>plugin_event_configure</code> has not been called, or it failed</td></tr><tr><td><code>-2</code></td><td><code>BAD_OBJECT_INDEX</code></td><td>an object index outside <code>[0, object_count)</code></td></tr><tr><td><code>-3</code></td><td><code>NULL_OUTPUT</code></td><td>a required output pointer was null</td></tr><tr><td><code>-4</code></td><td><code>BAD_INPUT</code></td><td>malformed policy or interval (zero step, zero span, NaN tolerance, zero <code>max_iterations</code>)</td></tr><tr><td><code>-5</code></td><td><code>NOT_CONVERGED</code></td><td>strict convergence was demanded and not reached</td></tr><tr><td><code>-6</code></td><td><code>UNPHYSICAL</code></td><td>geometry outside the locator's domain</td></tr><tr><td><code>-20</code></td><td><code>NOT_STARTED</code></td><td><code>plugin_event_begin</code> has not been called</td></tr><tr><td><code>-21</code></td><td><code>UNKNOWN_COMPONENT</code></td><td>a component, parameter or object this locator does not implement</td></tr><tr><td><code>-22</code></td><td><code>BUFFER_TOO_SMALL</code></td><td>a caller buffer was short; nothing is truncated silently</td></tr><tr><td><code>-23</code></td><td><code>PROTOCOL_ORDER</code></td><td><code>next</code>/<code>supply</code> out of order</td></tr><tr><td><code>-24</code></td><td><code>SUPPLY_COUNT_MISMATCH</code></td><td><code>count != epoch_count * object_count</code></td></tr><tr><td><code>-25</code></td><td><code>INTERNAL</code></td><td>never returned by a conformant locator</td></tr></tbody></table></div>
159
+ <p>Non-convergence of one root is normally reported per hit in <code>EventHit.status</code>, not as a call failure. The statuses are <code>CONVERGED</code>, <code>MAX_ITERATIONS</code>, <code>FLAT_BRACKET</code>, <code>DISCONTINUOUS</code>, <code>TRUNCATED</code> and <code>EPOCH_RESOLUTION_LIMITED</code>, and they are distinct because a root that stopped on the iteration cap and a root that converged are different answers.</p>
160
+ <h2 id="interval-pairing"><a class="anchor" href="#interval-pairing" aria-hidden="true">#</a>Interval pairing</h2>
161
+ <p>Hits are crossings. An interval is a <code>FALLING</code> hit followed by the next <code>RISING</code> hit on the same component (for the usual &quot;<code>g &gt; 0</code> means outside&quot; convention). The pairing is total because <code>EventScanSummary</code> carries the endpoint signs:</p>
162
+ <div class="table-wrap"><table><thead><tr><th><code>initial_sign</code></th><th><code>final_sign</code></th><th>Reading</th></tr></thead><tbody><tr><td><code>POSITIVE</code></td><td><code>POSITIVE</code></td><td>intervals fully inside the scan; pair <code>FALLING</code>→<code>RISING</code></td></tr><tr><td><code>NEGATIVE</code></td><td>…</td><td>the first interval was <strong>already open</strong> at the scan start; its opening epoch is before the interval, and the first <code>RISING</code> hit closes it (<code>INTERVAL_OPEN_AT_START</code>)</td></tr><tr><td>…</td><td><code>NEGATIVE</code></td><td>the last interval is still open at the scan end (<code>INTERVAL_OPEN_AT_END</code>)</td></tr></tbody></table></div>
163
+ <p>Without the endpoint signs, &quot;no hits&quot; is ambiguous between &quot;never in eclipse&quot; and &quot;in eclipse the entire time&quot;, and those are opposite answers. This is the classic event-scan defect and the summary struct exists to make it unrepresentable.</p>
164
+ <p>Hits are reported in <strong>scan order</strong> — increasing epoch forward, decreasing backward — regardless of the order refinement completed them.</p>
165
+ <h2 id="propagate-to-condition"><a class="anchor" href="#propagate-to-condition" aria-hidden="true">#</a>Propagate-to-condition</h2>
166
+ <p>A GMAT-style <code>Propagate ... {Sat.Altitude = 400}</code> is this ABI with one component whose <code>isStopCondition</code> is true and whose <code>g = parameter - goalValue</code>. Nothing is added to the propagator ABI, and that is an engineering decision, not a scoping convenience:</p>
167
+ <ol>
168
+ <li><strong>It is the same algorithm.</strong> A stop condition is <code>(event function, root refinement)</code>. Putting a second copy of Brent behind <code>plugin_propagate</code> would be two implementations of one algorithm that must agree — exactly the drift the generated-ABI lane exists to end.</li>
169
+ <li><strong>It would break every shipped propagator.</strong> The propagator export set is a per-epoch state evaluation. Adding stop conditions to it makes root refinement a family-wide obligation on <code>sgp4</code>, <code>hpop</code> and <code>keplerian-reference</code>, all in the field. That is a breaking ABI change and an owner decision.</li>
170
+ <li><strong>A propagator is a state SOURCE.</strong> It has no opinion about what is interesting, and giving it one couples every propagator to the parameter catalog.</li>
171
+ </ol>
172
+ <p><strong>Backward propagation is not a mode.</strong> It is <code>stop</code> earlier than <code>start</code>. The runner steps negatively, the consumer's propagator is asked for earlier epochs, and every hit carries <code>BACKWARD</code>. There is no second code path to keep in agreement with the first.</p>
173
+ <p><strong>Synchronized multi-spacecraft (<code>Formation</code>) stops</strong> are <code>object_count &gt; 1</code>. Every configured object is evaluated at <strong>every</strong> requested epoch, so the states are aligned by construction rather than by the consumer remembering to align them.</p>
174
+ <h2 id="lifetime"><a class="anchor" href="#lifetime" aria-hidden="true">#</a>Lifetime</h2>
175
+ <p><code>plugin_event_destroy</code> is <strong>required</strong> and must actually release: it resets the runner to its post-<code>init</code> state while keeping the <code>eval</code> wiring, so a second <code>configure</code>/<code>begin</code> cycle starts clean. It is idempotent, and after it <code>plugin_event_hit_count()</code> is <code>0</code>.</p>
176
+ <p>Because the runner's arena is static and fixed, a locator's page count after N scan cycles must equal its page count after one. That is the leak invariant, and it is free here by construction.</p>
177
+ <h2 id="versioning"><a class="anchor" href="#versioning" aria-hidden="true">#</a>Versioning</h2>
178
+ <p>Additive-only within a MAJOR. New enum members are <strong>appended</strong>; existing values are never renumbered. New struct fields are appended only where they do not move an existing offset — in practice that means a new struct, because every struct in this ABI is packed to its alignment. The size and offset locks in the generated header are what make a violation a compile error rather than a silently-wrong-numbers defect.</p>
179
+ <h2 id="parity-envelope"><a class="anchor" href="#parity-envelope" aria-hidden="true">#</a>Parity envelope</h2>
180
+ <p>Byte-identical across browser, native WasmEdge and Docker WasmEdge, for identical inputs:</p>
181
+ <ul>
182
+ <li>the hit list: every <code>epoch_jd_day</code>, <code>epoch_seconds</code>, <code>value</code>, <code>component</code>, <code>direction</code>, <code>status</code>, <code>iterations</code>, <code>evaluations</code> and <code>flags</code>;</li>
183
+ <li>the summaries: every endpoint value, sign and count;</li>
184
+ <li>the epoch sequence <code>plugin_event_next</code> produces, and the evaluation count;</li>
185
+ <li>the trap class of every refusal.</li>
186
+ </ul>
187
+ <p>This is not a hope. The runner's arithmetic is IEEE-754 <code>+ - * /</code> and comparison, which WebAssembly specifies exactly and which has no fused-multiply-add form in the MVP. There is nothing for the lanes to disagree about, and a divergence is a <strong>P1 SDK defect</strong>.</p>
188
+ <p><strong>A native reference build must use <code>-ffp-contract=off</code>.</strong> On arm64, clang contracts <code>a*b + c</code> into a single fused multiply-add; wasm has no <code>fma</code>, so the same source computes a different (both correct) double. Measured on this ABI: the Illinois regula-falsi step diverged by 1 ulp in the root epoch, which is 2.6e-10 s — inside every tolerance, and still a byte difference. A native build that leaves contraction on is not comparing the same arithmetic.</p>
189
+ <h3 id="measured-on-this-candidate"><a class="anchor" href="#measured-on-this-candidate" aria-hidden="true">#</a>Measured, on this candidate</h3>
190
+ <p><code>test/events-abi.test.js</code> compiles <code>test/fixtures/event-runner-harness.c</code> twice — natively and to <code>wasm32-wasip1-threads</code> — and diffs the raw IEEE-754 bit patterns of every reported number. Result: <strong>identical</strong>.</p>
191
+ <p>Against the closed form (<code>g0 = z</code>, roots at <code>kT/2</code>; <code>g1 = x</code>, roots at <code>T/4 + kT/2</code>), worst <code>|root - analytic|</code> over two orbits:</p>
192
+ <div class="table-wrap"><table><thead><tr><th>Scan</th><th>Method / step</th><th>Worst residual</th><th>g evaluations</th></tr></thead><tbody><tr><td>A</td><td>Brent, 60 s</td><td><strong>7.3e-12 s</strong></td><td>227</td></tr><tr><td>B</td><td>Brent, 137 s</td><td><strong>5.5e-12 s</strong></td><td>118</td></tr><tr><td>C</td><td>Brent, 300 s</td><td><strong>2.4e-11 s</strong></td><td>70</td></tr><tr><td>D</td><td>Brent, 60 s, BACKWARD over the same arc</td><td><strong>4.5e-12 s</strong></td><td>226</td></tr><tr><td>E</td><td>Bisection, 60 s</td><td><strong>8.2e-10 s</strong></td><td>484</td></tr><tr><td>F</td><td>Illinois, 60 s</td><td><strong>3.3e-10 s</strong></td><td>388</td></tr><tr><td>G</td><td>Brent, source resolution 4.0233e-5 s</td><td><strong>2.1e-7 s</strong></td><td>111</td></tr></tbody></table></div>
193
+ <p>All eight events, in the same order, at all three scan steps; the backward scan returns the same eight; the three methods agree to better than 1e-6 s while costing 227 / 484 / 388 evaluations. Scan G is the clamp working: it reports <code>EPOCH_RESOLUTION_LIMITED</code> and its residual is bounded by the source resolution it was told about, not by the 1e-9 s it was asked for.</p>
194
+ <p><strong>The ordering defect this measurement caught.</strong> Hits were originally reported in <em>discovery</em> order, which is chronological per component but interleaved across components. With a 60 s step two components' events fell in separate scan samples and came out in epoch order; with a 137 s step they fell in the same sample and came out in component order — the same eight events, two of them transposed by half an orbit. Step-independence is a property of the LIST, not only of each epoch, so the runner now inserts in scan order.</p>
195
+ <h2 id="conformance"><a class="anchor" href="#conformance" aria-hidden="true">#</a>Conformance</h2>
196
+ <p>There is <strong>no events conformance kit yet</strong>, and <code>space-data-module conformance</code> refuses the family by name rather than coercing it — a family with no kit can never be <code>CORE</code>. What exists today and is wired into <code>npm test</code>:</p>
197
+ <div class="codeblock"><pre><code>node scripts/check-events-abi.mjs # the drift gate
198
+ node --test test/events-abi.test.js # the both-harness proof</code></pre></div>
199
+ <p>The kit that would make this family <code>Shipped</code> owes: Tier 0 (the export set), Tier C (<code>plugin_event_eval</code> agrees exactly with the <code>g</code> the runner used; determinism as bytes across destroy/reconfigure; typed refusals; step-independence; forward-backward closure; root convergence on a known-analytic event), Tier 4 (lifecycle), and a self-test with planted defects — including a locator whose <code>g</code> is impure, which is the failure mode this ABI is most exposed to.</p>
200
+ <h2 id="consumer-seam"><a class="anchor" href="#consumer-seam" aria-hidden="true">#</a>Consumer seam</h2>
201
+ <p>Per the pluggable-propagation law, the consumer resolves the propagator ONCE and feeds it to the locator's supply loop. The locator never learns which propagator it was. Concretely:</p>
202
+ <div class="codeblock"><div class="codeblock-head">js</div><pre><code>const request = new OrbProEventStateRequest(); // from events-abi.js offsets
203
+ for (;;) {
204
+ const n = wasm.plugin_event_next(requestPtr, epochsPtr, capacity);
205
+ if (n &lt; 0) throw new Error(eventErrorName(n));
206
+ if (n === 0) break;
207
+ for (let e = 0; e &lt; n; e += 1) {
208
+ const jd = epochs[2 * e] + epochs[2 * e + 1] / 86400.0;
209
+ for (let o = 0; o &lt; objectCount; o += 1) {
210
+ propagator.propagate(jd, o, statesPtr + (e * objectCount + o) * 64);
211
+ }
212
+ }
213
+ const rc = wasm.plugin_event_supply(statesPtr, n * objectCount);
214
+ if (rc &lt; 0) throw new Error(eventErrorName(rc));
215
+ }</code></pre></div>
216
+ <p>That loop is the whole host obligation. It contains no physics, no tolerance, no decision about which epochs matter, and it is identical in all three runtimes.</p>
217
+ <h2 id="guest-usage"><a class="anchor" href="#guest-usage" aria-hidden="true">#</a>Guest usage</h2>
218
+ <div class="codeblock"><div class="codeblock-head">c</div><pre><code>#include &quot;orbpro/orbpro_event_runner.h&quot; /* pulls in the generated ABI */
219
+
220
+ static OrbProEventRunner g_runner;
221
+
222
+ /* THE locator. Everything else is the runner. */
223
+ static int32_t eclipse_g(double jd_day, double seconds,
224
+ const OrbProStateVector* states, uint32_t objects,
225
+ double* g, uint32_t components, void* user) {
226
+ (void)jd_day; (void)seconds; (void)user;
227
+ if (objects &lt; 1u || components &lt; 1u) return ORBPRO_EVENT_E_BAD_INPUT;
228
+ /* g &gt; 0 outside the shadow, g &lt; 0 inside. A FALLING crossing is entry. */
229
+ g[0] = shadow_function(&amp;states[0]);
230
+ return 0;
231
+ }
232
+
233
+ ORBPRO_EVENT_EXPORT(&quot;plugin_event_eval&quot;)
234
+ int32_t plugin_event_eval(double jd_day, double seconds,
235
+ const OrbProStateVector* states, uint32_t objects,
236
+ double* g, uint32_t components) {
237
+ return eclipse_g(jd_day, seconds, states, objects, g, components, NULL);
238
+ }
239
+
240
+ /* plugin_event_begin / next / supply / hit_count / hits / summaries / destroy */
241
+ ORBPRO_EVENT_RUNNER_EXPORTS(g_runner)</code></pre></div>
242
+ <p><code>plugin_event_describe</code> and <code>plugin_event_configure</code> are the locator's too: they encode and decode the <code>EventLocatorDescription</code> / <code>EventLocatorConfig</code> FlatBuffers and end in <code>orbpro_event_runner_configure</code> plus one <code>orbpro_event_runner_set_component</code> per component.</p>
243
+ <h2 id="regenerating"><a class="anchor" href="#regenerating" aria-hidden="true">#</a>Regenerating</h2>
244
+ <div class="codeblock"><pre><code>node scripts/generate-events-abi.mjs # regenerate the header + bindings
245
+ node scripts/check-events-abi.mjs # the drift gate, wired into npm test</code></pre></div>
246
+ <p>Edit <code>schemas/orbpro/Events.fbs</code> — it is the single source of truth. A hand edit to the generated header is erased by the next run and failed by the gate in between. <code>include/orbpro/orbpro_event_runner.h</code> is <strong>not</strong> generated: it is algorithm, not layout, and there is exactly one copy of it.</p>
247
+
248
+ <p class="doc-footer">
249
+ Source of truth for this page:
250
+ <code>docs/events-abi.md</code>. Regenerate with
251
+ <code>npm run build:docs</code>.
252
+ </p>
253
+ </main>
254
+ <nav class="rail" aria-label="On this page"><h2>On this page</h2><ul><li><a class="depth-2" href="#doctrine">Doctrine</a></li>
255
+ <li><a class="depth-2" href="#capability">Capability</a></li>
256
+ <li><a class="depth-2" href="#the-export-set">The export set</a></li>
257
+ <li><a class="depth-3" href="#the-locator-writes-these">The locator writes these</a></li>
258
+ <li><a class="depth-3" href="#the-runner-provides-these">The runner provides these</a></li>
259
+ <li><a class="depth-2" href="#the-pull-protocol">The pull protocol</a></li>
260
+ <li><a class="depth-2" href="#wire-layout">Wire layout</a></li>
261
+ <li><a class="depth-2" href="#units-frames-and-epochs">Units, frames and epochs</a></li>
262
+ <li><a class="depth-3" href="#epochs-are-split-pairs-and-that-is-not-decoration">Epochs are split pairs, and that is not decoration</a></li>
263
+ <li><a class="depth-3" href="#the-state-source-bounds-the-answer-and-says-so">The state source bounds the answer, and says so</a></li>
264
+ <li><a class="depth-2" href="#sentinels">Sentinels</a></li>
265
+ <li><a class="depth-3" href="#the-scan-start-is-not-a-bracket">The scan start is not a bracket</a></li>
266
+ <li><a class="depth-2" href="#identity">Identity</a></li>
267
+ <li><a class="depth-2" href="#threading">Threading</a></li>
268
+ <li><a class="depth-2" href="#error-codes">Error codes</a></li>
269
+ <li><a class="depth-2" href="#interval-pairing">Interval pairing</a></li>
270
+ <li><a class="depth-2" href="#propagate-to-condition">Propagate-to-condition</a></li>
271
+ <li><a class="depth-2" href="#lifetime">Lifetime</a></li>
272
+ <li><a class="depth-2" href="#versioning">Versioning</a></li>
273
+ <li><a class="depth-2" href="#parity-envelope">Parity envelope</a></li>
274
+ <li><a class="depth-3" href="#measured-on-this-candidate">Measured, on this candidate</a></li>
275
+ <li><a class="depth-2" href="#conformance">Conformance</a></li>
276
+ <li><a class="depth-2" href="#consumer-seam">Consumer seam</a></li>
277
+ <li><a class="depth-2" href="#guest-usage">Guest usage</a></li>
278
+ <li><a class="depth-2" href="#regenerating">Regenerating</a></li></ul></nav>
279
+ </div>
280
+ <section id="stack" class="section" aria-labelledby="stack-title">
281
+ <div class="section-head">
282
+ <p class="eyebrow">SDN Stack</p>
283
+ <h2 id="stack-title">Connected sites</h2>
284
+ </div>
285
+ <div class="link-list">
286
+ <a href="https://spacedatastandards.org/">
287
+ <span>Standards</span>
288
+ <small>Canonical schemas, record contracts, and generated bindings.</small>
289
+ </a>
290
+ <a href="https://digitalarsenal.github.io/flatbuffers/">
291
+ <span>FlatBuffers</span>
292
+ <small>Binary encoding, schema tooling, and runtime documentation.</small>
293
+ </a>
294
+ <a href="https://digitalarsenal.github.io/flatsql/">
295
+ <span>FlatSQL</span>
296
+ <small>SQL-style queries over FlatBuffer-backed datasets and streams.</small>
297
+ </a>
298
+ <a href="https://spacedatanetwork.org/">
299
+ <span>SDN</span>
300
+ <small>Distributed publication, discovery, delivery, and marketplace infrastructure.</small>
301
+ </a>
302
+ <a href="https://digitalarsenal.github.io/space-data-module-sdk/" class="active">
303
+ <span>Module SDK</span>
304
+ <small>WASM module packaging, validation, and host compatibility tooling.</small>
305
+ </a>
306
+ </div>
307
+ </section>
308
+ <footer class="site-footer">
309
+ Space Data Module SDK — Apache-2.0. Every ABI page renders a Markdown
310
+ source in <code>docs/</code>; edit the Markdown, never the HTML.
311
+ </footer>
312
+ </body>
313
+ </html>