space-data-module-sdk 0.8.15 → 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.
- package/docs/_shell/consumer-assets.html +5 -0
- package/docs/browser-wasmedge-isomorphic.html +261 -0
- package/docs/byo-wasm-quickstart.html +226 -0
- package/docs/byo-wasm-quickstart.md +204 -0
- package/docs/conformance.html +156 -0
- package/docs/emception-shared-memory-verdict.md +209 -0
- package/docs/events-abi.html +313 -0
- package/docs/events-abi.md +530 -0
- package/docs/families/analytics.html +178 -0
- package/docs/families/analytics.md +104 -0
- package/docs/families/attitude.html +157 -0
- package/docs/families/attitude.md +36 -0
- package/docs/families/behavior.html +157 -0
- package/docs/families/behavior.md +36 -0
- package/docs/families/breakup.html +157 -0
- package/docs/families/breakup.md +36 -0
- package/docs/families/conjunction.html +186 -0
- package/docs/families/conjunction.md +121 -0
- package/docs/families/data-source.html +318 -0
- package/docs/families/effects.html +157 -0
- package/docs/families/effects.md +36 -0
- package/docs/families/environment.html +170 -0
- package/docs/families/environment.md +73 -0
- package/docs/families/estimation.html +190 -0
- package/docs/families/estimation.md +128 -0
- package/docs/families/gnc.html +157 -0
- package/docs/families/gnc.md +36 -0
- package/docs/families/maneuver.html +209 -0
- package/docs/families/maneuver.md +206 -0
- package/docs/families/obstruction.html +191 -0
- package/docs/families/obstruction.md +96 -0
- package/docs/families/propagator.html +333 -0
- package/docs/families/propulsion.html +157 -0
- package/docs/families/propulsion.md +36 -0
- package/docs/families/reentry.html +157 -0
- package/docs/families/reentry.md +36 -0
- package/docs/families/rf.html +193 -0
- package/docs/families/rf.md +136 -0
- package/docs/families/scheduler.html +157 -0
- package/docs/families/scheduler.md +36 -0
- package/docs/families/sensor.html +157 -0
- package/docs/families/sensor.md +36 -0
- package/docs/families/signature.html +157 -0
- package/docs/families/signature.md +36 -0
- package/docs/flatsql-host-contract.html +176 -0
- package/docs/flatsql-streaming-standard.html +375 -0
- package/docs/gpu-module-abi.html +233 -0
- package/docs/harness-family-matrix.md +135 -0
- package/docs/index.html +216 -85
- package/docs/isomorphic-pthreads.html +228 -0
- package/docs/isomorphic-pthreads.md +84 -0
- package/docs/isomorphic-sdn-runtime-plan.html +274 -0
- package/docs/language-runtime-matrix.html +182 -0
- package/docs/llms.txt +86 -0
- package/docs/module-bundle-runtime-plan.html +146 -0
- package/docs/module-publication-standard.html +503 -0
- package/docs/node-red-default-node-parity.html +201 -0
- package/docs/propagator-abi.md +45 -12
- package/docs/protect-and-sign.html +181 -0
- package/docs/protect-and-sign.md +120 -0
- package/docs/protocol-installation.html +296 -0
- package/docs/publication-submission.html +158 -0
- package/docs/publication-submission.md +78 -0
- package/docs/secrets-capability.html +206 -0
- package/docs/styles.css +860 -167
- package/docs/testing-harness.html +408 -0
- package/docs/tri-runtime-parity-gate.html +182 -0
- package/docs/tri-runtime-parity.html +203 -0
- package/include/orbpro/orbpro_event_runner.h +951 -0
- package/include/orbpro/orbpro_events_abi.h +565 -0
- package/include/orbpro/orbpro_propagator_abi.h +17 -0
- package/package.json +11 -4
- package/schemas/orbpro/Events.fbs +501 -0
- package/schemas/orbpro/Propagator.fbs +72 -9
- package/schemas/orbpro/reference-frame-crosswalk.json +108 -0
- package/schemas/orbpro/reference-frame.lock.json +98 -0
- package/src/browser.js +13 -0
- package/src/generated/orbpro/events-abi.js +206 -0
- package/src/generated/orbpro/events-abi.ts +319 -0
- package/src/generated/orbpro/propagator/reference-frame.js +17 -0
- package/src/generated/orbpro/propagator/reference-frame.ts +91 -1
- package/src/generated/orbpro/propagator-abi.js +17 -0
- package/src/generated/orbpro/propagator-abi.ts +17 -0
- package/src/generated/spacedatastandards/plg/CCT.d.ts +94 -0
- package/src/generated/spacedatastandards/plg/CCT.d.ts.map +1 -0
- package/src/generated/spacedatastandards/plg/CCT.js +208 -0
- package/src/generated/spacedatastandards/plg/CCT.ts +281 -0
- package/src/generated/spacedatastandards/plg/CCTCategory.d.ts +98 -0
- package/src/generated/spacedatastandards/plg/CCTCategory.d.ts.map +1 -0
- package/src/generated/spacedatastandards/plg/CCTCategory.js +177 -0
- package/src/generated/spacedatastandards/plg/CCTCategory.ts +269 -0
- package/src/generated/spacedatastandards/plg/CCTCategoryRollup.d.ts +54 -0
- package/src/generated/spacedatastandards/plg/CCTCategoryRollup.d.ts.map +1 -0
- package/src/generated/spacedatastandards/plg/CCTCategoryRollup.js +106 -0
- package/src/generated/spacedatastandards/plg/CCTCategoryRollup.ts +144 -0
- package/src/generated/spacedatastandards/plg/PLG.d.ts +46 -2
- package/src/generated/spacedatastandards/plg/PLG.d.ts.map +1 -1
- package/src/generated/spacedatastandards/plg/PLG.js +78 -5
- package/src/generated/spacedatastandards/plg/PLG.ts +90 -5
- package/src/generated/spacedatastandards/plg/capabilityClass.d.ts +181 -0
- package/src/generated/spacedatastandards/plg/capabilityClass.d.ts.map +1 -0
- package/src/generated/spacedatastandards/plg/capabilityClass.js +183 -0
- package/src/generated/spacedatastandards/plg/capabilityClass.ts +208 -0
- package/src/generated/spacedatastandards/plg/main.d.ts +4 -0
- package/src/generated/spacedatastandards/plg/main.d.ts.map +1 -1
- package/src/generated/spacedatastandards/plg/main.js +4 -0
- package/src/generated/spacedatastandards/plg/main.ts +4 -0
- package/src/generated/spacedatastandards/plg/pluginCategory.d.ts +40 -2
- package/src/generated/spacedatastandards/plg/pluginCategory.d.ts.map +1 -1
- package/src/generated/spacedatastandards/plg/pluginCategory.js +40 -2
- package/src/generated/spacedatastandards/plg/pluginCategory.ts +40 -2
- package/src/host/browserModuleHarness.js +8 -0
- package/src/host/wasiThreadHost.js +189 -14
- package/src/index.d.ts +18 -1
- package/src/transport/index.js +16 -0
- package/src/transport/records.js +1015 -3
|
@@ -0,0 +1,951 @@
|
|
|
1
|
+
/* ===========================================================================
|
|
2
|
+
* orbpro_event_runner.h — THE event runner. Hand-written, and deliberately so.
|
|
3
|
+
*
|
|
4
|
+
* orbpro_events_abi.h is GENERATED from schemas/orbpro/Events.fbs because it is
|
|
5
|
+
* LAYOUT. This file is ALGORITHM, and there is exactly one copy of it: the
|
|
6
|
+
* bracketing scan, the root refinement, the occurrence counting and the
|
|
7
|
+
* interval bookkeeping that every locator shares.
|
|
8
|
+
*
|
|
9
|
+
* That is the whole point of the family. A locator supplies ONE function —
|
|
10
|
+
* the vector of scalar event functions g_i(t) — and gets the rest. Eclipse,
|
|
11
|
+
* station contact, sensor intrusion, apsides, node crossings and a
|
|
12
|
+
* propagate-to-condition stop differ only in g. Adding the fifth locator does
|
|
13
|
+
* not touch this file, and a locator that needed it to change would be
|
|
14
|
+
* evidence the ABI is wrong, not that this file is incomplete.
|
|
15
|
+
*
|
|
16
|
+
* Contract : docs/events-abi.md
|
|
17
|
+
* Wire : schemas/orbpro/Events.fbs -> include/orbpro/orbpro_events_abi.h
|
|
18
|
+
* Ruling : Janus AMEND 2026-08-30 (gmat-06-parameter-catalog-and-event-locators)
|
|
19
|
+
*
|
|
20
|
+
* ---------------------------------------------------------------------------
|
|
21
|
+
* DETERMINISM IS STRUCTURAL, NOT ASPIRATIONAL
|
|
22
|
+
* ---------------------------------------------------------------------------
|
|
23
|
+
* Nothing here reads a clock, allocates, threads, or calls libm beyond fabs.
|
|
24
|
+
* Every operation is IEEE-754 +, -, *, / and comparison, which WebAssembly
|
|
25
|
+
* specifies exactly and which has no fused-multiply-add form in the MVP. Two
|
|
26
|
+
* runs of the same locator on the same inputs therefore produce byte-identical
|
|
27
|
+
* hits in the browser, in native WasmEdge and in Docker WasmEdge — not because
|
|
28
|
+
* the lanes were tested into agreement, but because there is nothing in the
|
|
29
|
+
* arithmetic for them to disagree about. Any divergence is a P1 SDK defect.
|
|
30
|
+
*
|
|
31
|
+
* The one thing this file CANNOT make deterministic is the state source. That
|
|
32
|
+
* is why the epochs it asks for are exact functions of the interval and the
|
|
33
|
+
* scan step, and why the consumer decides nothing.
|
|
34
|
+
*
|
|
35
|
+
* ---------------------------------------------------------------------------
|
|
36
|
+
* TIME
|
|
37
|
+
* ---------------------------------------------------------------------------
|
|
38
|
+
* Internally the runner works in SECONDS FROM THE INTERVAL START, as a double.
|
|
39
|
+
* A Julian date in one float64 resolves to 4.02e-5 s at a 2026 epoch; seconds
|
|
40
|
+
* from a start epoch resolve to 1.5e-11 s over a week. Epochs cross the ABI as
|
|
41
|
+
* (jd_day, seconds) pairs and are NEVER collapsed to a single JD inside this
|
|
42
|
+
* file. `epoch_seconds` is allowed to exceed 86400 and is not normalized: the
|
|
43
|
+
* fine coordinate stays fine, and two epochs from one scan stay directly
|
|
44
|
+
* comparable.
|
|
45
|
+
* ===========================================================================
|
|
46
|
+
*/
|
|
47
|
+
|
|
48
|
+
#ifndef ORBPRO_EVENT_RUNNER_H
|
|
49
|
+
#define ORBPRO_EVENT_RUNNER_H
|
|
50
|
+
|
|
51
|
+
#include <stdint.h>
|
|
52
|
+
#include <stddef.h>
|
|
53
|
+
|
|
54
|
+
#include "orbpro/orbpro_events_abi.h"
|
|
55
|
+
#include "orbpro/orbpro_propagator_abi.h"
|
|
56
|
+
|
|
57
|
+
#ifdef __cplusplus
|
|
58
|
+
extern "C" {
|
|
59
|
+
#endif
|
|
60
|
+
|
|
61
|
+
/* --------------------------------------------------------------------------
|
|
62
|
+
* Fixed capacities. A locator may raise any of these by defining it BEFORE
|
|
63
|
+
* including this header. They are compile-time because this runner never
|
|
64
|
+
* allocates: a wasm guest that mallocs during a scan has a failure mode the
|
|
65
|
+
* three runtimes do not share, and a fixed arena has none.
|
|
66
|
+
* -------------------------------------------------------------------------- */
|
|
67
|
+
|
|
68
|
+
#ifndef ORBPRO_EVENT_MAX_COMPONENTS
|
|
69
|
+
#define ORBPRO_EVENT_MAX_COMPONENTS 32u
|
|
70
|
+
#endif
|
|
71
|
+
|
|
72
|
+
#ifndef ORBPRO_EVENT_MAX_OBJECTS
|
|
73
|
+
#define ORBPRO_EVENT_MAX_OBJECTS 16u
|
|
74
|
+
#endif
|
|
75
|
+
|
|
76
|
+
#ifndef ORBPRO_EVENT_MAX_HITS
|
|
77
|
+
#define ORBPRO_EVENT_MAX_HITS 512u
|
|
78
|
+
#endif
|
|
79
|
+
|
|
80
|
+
/** Coarse-scan epochs requested per round trip. Batching the SCAN is what lets
|
|
81
|
+
* a consumer drive it through `plugin_propagate_batch`; the REFINEMENT is
|
|
82
|
+
* inherently one abscissa at a time and always asks for exactly one. */
|
|
83
|
+
#ifndef ORBPRO_EVENT_SCAN_BATCH
|
|
84
|
+
#define ORBPRO_EVENT_SCAN_BATCH 64u
|
|
85
|
+
#endif
|
|
86
|
+
|
|
87
|
+
/** Brackets discovered in one scan batch, awaiting refinement. */
|
|
88
|
+
#ifndef ORBPRO_EVENT_MAX_PENDING
|
|
89
|
+
#define ORBPRO_EVENT_MAX_PENDING 64u
|
|
90
|
+
#endif
|
|
91
|
+
|
|
92
|
+
/** 2^-52. Written out rather than pulled from <float.h> so the header has no
|
|
93
|
+
* dependency a wasi-libc-less build could miss. */
|
|
94
|
+
#define ORBPRO_EVENT_DBL_EPSILON 2.220446049250313e-16
|
|
95
|
+
|
|
96
|
+
/** `EventInterval.component` value meaning "every component". */
|
|
97
|
+
#define ORBPRO_EVENT_ALL_COMPONENTS 0xFFFFFFFFu
|
|
98
|
+
|
|
99
|
+
/* --------------------------------------------------------------------------
|
|
100
|
+
* The locator's one obligation.
|
|
101
|
+
* -------------------------------------------------------------------------- */
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Evaluate EVERY configured component at one epoch, for the states of every
|
|
105
|
+
* configured object at that epoch.
|
|
106
|
+
*
|
|
107
|
+
* Vector-valued on purpose: one state fetch feeds every component, so a
|
|
108
|
+
* ten-component locator costs one propagation per epoch and not ten. It is
|
|
109
|
+
* also what makes eclipse-with-three-occulting-bodies and
|
|
110
|
+
* contact-with-eight-stations ordinary rather than special.
|
|
111
|
+
*
|
|
112
|
+
* MUST be a pure function of its arguments. A g that depends on call order,
|
|
113
|
+
* on a cached previous epoch, or on anything outside these parameters breaks
|
|
114
|
+
* bracketing: Brent evaluates epochs out of chronological order by design.
|
|
115
|
+
*
|
|
116
|
+
* Returns 0, or a negative `OrbProEventError`.
|
|
117
|
+
*/
|
|
118
|
+
typedef int32_t (*orbpro_event_eval_fn)(double epoch_jd_day,
|
|
119
|
+
double epoch_seconds,
|
|
120
|
+
const OrbProStateVector* states,
|
|
121
|
+
uint32_t object_count,
|
|
122
|
+
double* g_out,
|
|
123
|
+
uint32_t component_count,
|
|
124
|
+
void* user);
|
|
125
|
+
|
|
126
|
+
/* --------------------------------------------------------------------------
|
|
127
|
+
* Internal state. Public only so it can live in a locator's static storage.
|
|
128
|
+
* -------------------------------------------------------------------------- */
|
|
129
|
+
|
|
130
|
+
typedef enum {
|
|
131
|
+
ORBPRO_EVENT_PHASE_IDLE = 0,
|
|
132
|
+
ORBPRO_EVENT_PHASE_SCAN_WAIT = 1,
|
|
133
|
+
ORBPRO_EVENT_PHASE_REFINE_WAIT = 2,
|
|
134
|
+
ORBPRO_EVENT_PHASE_DONE = 3
|
|
135
|
+
} OrbProEventPhase;
|
|
136
|
+
|
|
137
|
+
/** One bracketed root, mid-refinement. */
|
|
138
|
+
typedef struct {
|
|
139
|
+
double a, b, c; /* abscissae, SECONDS from the interval start */
|
|
140
|
+
double fa, fb, fc; /* g at a, b, c */
|
|
141
|
+
double d, e; /* Brent step bookkeeping */
|
|
142
|
+
double tol; /* effective epoch tolerance, SECONDS */
|
|
143
|
+
double vtol; /* value tolerance, component units */
|
|
144
|
+
uint32_t component;
|
|
145
|
+
uint32_t iterations;
|
|
146
|
+
uint32_t max_iterations;
|
|
147
|
+
uint32_t evaluations;
|
|
148
|
+
uint32_t flags;
|
|
149
|
+
int32_t direction; /* the crossing direction that qualified it */
|
|
150
|
+
uint8_t method;
|
|
151
|
+
uint8_t status;
|
|
152
|
+
uint8_t awaiting; /* 1 when fb for the current b is outstanding */
|
|
153
|
+
} OrbProEventBracket;
|
|
154
|
+
|
|
155
|
+
typedef struct {
|
|
156
|
+
/* ---- wiring ---- */
|
|
157
|
+
orbpro_event_eval_fn eval;
|
|
158
|
+
void* user;
|
|
159
|
+
|
|
160
|
+
/* ---- configuration ---- */
|
|
161
|
+
uint32_t component_count;
|
|
162
|
+
uint32_t object_count;
|
|
163
|
+
uint8_t reference_frame;
|
|
164
|
+
double state_epoch_resolution_seconds;
|
|
165
|
+
int32_t comp_direction[ORBPRO_EVENT_MAX_COMPONENTS];
|
|
166
|
+
uint32_t comp_occurrence[ORBPRO_EVENT_MAX_COMPONENTS];
|
|
167
|
+
uint8_t comp_continuity[ORBPRO_EVENT_MAX_COMPONENTS];
|
|
168
|
+
uint8_t comp_is_stop[ORBPRO_EVENT_MAX_COMPONENTS];
|
|
169
|
+
|
|
170
|
+
/* ---- the scan ---- */
|
|
171
|
+
OrbProEventInterval interval;
|
|
172
|
+
OrbProRootPolicy policy;
|
|
173
|
+
double effective_tolerance; /* SECONDS, clamped to the source resolution */
|
|
174
|
+
int clamped; /* 1 when the clamp actually bound */
|
|
175
|
+
double span_seconds; /* signed: negative means backward */
|
|
176
|
+
double step_seconds; /* signed */
|
|
177
|
+
double total_steps; /* how many whole steps fit in the span */
|
|
178
|
+
uint32_t next_sample; /* index of the next scan sample to request */
|
|
179
|
+
uint32_t sample_count; /* total scan samples, inclusive of both ends */
|
|
180
|
+
int have_previous; /* 1 once sample 0 has been evaluated */
|
|
181
|
+
double prev_t;
|
|
182
|
+
double prev_g[ORBPRO_EVENT_MAX_COMPONENTS];
|
|
183
|
+
int8_t prev_sign[ORBPRO_EVENT_MAX_COMPONENTS];
|
|
184
|
+
double first_g[ORBPRO_EVENT_MAX_COMPONENTS];
|
|
185
|
+
int8_t first_sign[ORBPRO_EVENT_MAX_COMPONENTS];
|
|
186
|
+
double last_g[ORBPRO_EVENT_MAX_COMPONENTS];
|
|
187
|
+
int8_t last_sign[ORBPRO_EVENT_MAX_COMPONENTS];
|
|
188
|
+
uint32_t crossings[ORBPRO_EVENT_MAX_COMPONENTS];
|
|
189
|
+
uint32_t qualifying[ORBPRO_EVENT_MAX_COMPONENTS];
|
|
190
|
+
uint32_t comp_evals[ORBPRO_EVENT_MAX_COMPONENTS];
|
|
191
|
+
|
|
192
|
+
/* ---- round-trip bookkeeping ---- */
|
|
193
|
+
OrbProEventPhase phase;
|
|
194
|
+
uint32_t outstanding_epochs; /* what the last `next` asked for */
|
|
195
|
+
double outstanding_t[ORBPRO_EVENT_SCAN_BATCH];
|
|
196
|
+
|
|
197
|
+
/* ---- brackets awaiting refinement, in scan order ---- */
|
|
198
|
+
OrbProEventBracket pending[ORBPRO_EVENT_MAX_PENDING];
|
|
199
|
+
uint32_t pending_head;
|
|
200
|
+
uint32_t pending_tail;
|
|
201
|
+
|
|
202
|
+
/* ---- results ---- */
|
|
203
|
+
OrbProEventHit hits[ORBPRO_EVENT_MAX_HITS];
|
|
204
|
+
uint32_t hit_count;
|
|
205
|
+
uint32_t evaluation_count;
|
|
206
|
+
uint32_t truncated;
|
|
207
|
+
|
|
208
|
+
/* ---- scratch ---- */
|
|
209
|
+
double g_scratch[ORBPRO_EVENT_MAX_COMPONENTS];
|
|
210
|
+
} OrbProEventRunner;
|
|
211
|
+
|
|
212
|
+
/* --------------------------------------------------------------------------
|
|
213
|
+
* Small helpers. Static so a locator that includes this header in one
|
|
214
|
+
* translation unit emits no duplicate symbols.
|
|
215
|
+
* -------------------------------------------------------------------------- */
|
|
216
|
+
|
|
217
|
+
static inline double orbpro_event_fabs(double v) { return v < 0.0 ? -v : v; }
|
|
218
|
+
|
|
219
|
+
static inline int8_t orbpro_event_sign(double v) {
|
|
220
|
+
if (v > 0.0) return (int8_t)1;
|
|
221
|
+
if (v < 0.0) return (int8_t)-1;
|
|
222
|
+
return (int8_t)0;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
static inline double orbpro_event_signed_tol(double magnitude, double reference) {
|
|
226
|
+
return reference >= 0.0 ? magnitude : -magnitude;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
static inline void orbpro_event_zero(void* p, size_t n) {
|
|
230
|
+
unsigned char* b = (unsigned char*)p;
|
|
231
|
+
for (size_t i = 0; i < n; ++i) b[i] = 0;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/* --------------------------------------------------------------------------
|
|
235
|
+
* Lifecycle.
|
|
236
|
+
* -------------------------------------------------------------------------- */
|
|
237
|
+
|
|
238
|
+
/** Wire a locator's g to the runner. Call once, before configure. */
|
|
239
|
+
static inline void orbpro_event_runner_init(OrbProEventRunner* r,
|
|
240
|
+
orbpro_event_eval_fn eval,
|
|
241
|
+
void* user) {
|
|
242
|
+
orbpro_event_zero(r, sizeof(*r));
|
|
243
|
+
r->eval = eval;
|
|
244
|
+
r->user = user;
|
|
245
|
+
r->phase = ORBPRO_EVENT_PHASE_IDLE;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Declare the shape of the scan: how many components, how many objects, which
|
|
250
|
+
* frame the states must arrive in, and — critically — the state source's own
|
|
251
|
+
* epoch resolution in seconds. See `EventLocatorConfig.stateEpochResolutionSeconds`;
|
|
252
|
+
* 0 means "not declared", and the runner then trusts the tolerance it is given.
|
|
253
|
+
*/
|
|
254
|
+
static inline int32_t orbpro_event_runner_configure(OrbProEventRunner* r,
|
|
255
|
+
uint32_t component_count,
|
|
256
|
+
uint32_t object_count,
|
|
257
|
+
uint8_t reference_frame,
|
|
258
|
+
double state_epoch_resolution_seconds) {
|
|
259
|
+
if (r->eval == NULL) return ORBPRO_EVENT_E_NOT_CONFIGURED;
|
|
260
|
+
if (component_count == 0u || component_count > ORBPRO_EVENT_MAX_COMPONENTS)
|
|
261
|
+
return ORBPRO_EVENT_E_BAD_INPUT;
|
|
262
|
+
if (object_count == 0u || object_count > ORBPRO_EVENT_MAX_OBJECTS)
|
|
263
|
+
return ORBPRO_EVENT_E_BAD_OBJECT_INDEX;
|
|
264
|
+
if (state_epoch_resolution_seconds < 0.0 ||
|
|
265
|
+
state_epoch_resolution_seconds != state_epoch_resolution_seconds)
|
|
266
|
+
return ORBPRO_EVENT_E_BAD_INPUT;
|
|
267
|
+
|
|
268
|
+
r->component_count = component_count;
|
|
269
|
+
r->object_count = object_count;
|
|
270
|
+
r->reference_frame = reference_frame;
|
|
271
|
+
r->state_epoch_resolution_seconds = state_epoch_resolution_seconds;
|
|
272
|
+
for (uint32_t i = 0; i < component_count; ++i) {
|
|
273
|
+
r->comp_direction[i] = (int32_t)ORBPRO_CROSSING_ANY;
|
|
274
|
+
r->comp_occurrence[i] = 0u;
|
|
275
|
+
r->comp_continuity[i] = (uint8_t)ORBPRO_CONTINUITY_SMOOTH;
|
|
276
|
+
r->comp_is_stop[i] = 0u;
|
|
277
|
+
}
|
|
278
|
+
r->phase = ORBPRO_EVENT_PHASE_IDLE;
|
|
279
|
+
return 0;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/** Per-component filters, from the locator's parsed `EventLocatorConfig`. */
|
|
283
|
+
static inline int32_t orbpro_event_runner_set_component(OrbProEventRunner* r,
|
|
284
|
+
uint32_t component,
|
|
285
|
+
OrbProCrossingDirection direction,
|
|
286
|
+
uint32_t occurrence,
|
|
287
|
+
OrbProComponentContinuity continuity,
|
|
288
|
+
int is_stop_condition) {
|
|
289
|
+
if (component >= r->component_count) return ORBPRO_EVENT_E_UNKNOWN_COMPONENT;
|
|
290
|
+
r->comp_direction[component] = (int32_t)direction;
|
|
291
|
+
r->comp_occurrence[component] = occurrence;
|
|
292
|
+
r->comp_continuity[component] = (uint8_t)continuity;
|
|
293
|
+
r->comp_is_stop[component] = is_stop_condition ? 1u : 0u;
|
|
294
|
+
return 0;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/* --------------------------------------------------------------------------
|
|
298
|
+
* The scan.
|
|
299
|
+
* -------------------------------------------------------------------------- */
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Begin a scan. A zero `scan_step_seconds` is refused: a silent default step
|
|
303
|
+
* is how a scan misses every event shorter than it, and the miss looks exactly
|
|
304
|
+
* like "there was no event".
|
|
305
|
+
*/
|
|
306
|
+
static inline int32_t orbpro_event_runner_begin(OrbProEventRunner* r,
|
|
307
|
+
const OrbProEventInterval* interval,
|
|
308
|
+
const OrbProRootPolicy* policy) {
|
|
309
|
+
if (interval == NULL || policy == NULL) return ORBPRO_EVENT_E_NULL_OUTPUT;
|
|
310
|
+
if (r->component_count == 0u) return ORBPRO_EVENT_E_NOT_CONFIGURED;
|
|
311
|
+
if (!(policy->scan_step_seconds > 0.0)) return ORBPRO_EVENT_E_BAD_INPUT;
|
|
312
|
+
if (!(policy->epoch_tolerance_seconds > 0.0)) return ORBPRO_EVENT_E_BAD_INPUT;
|
|
313
|
+
if (policy->max_iterations == 0u) return ORBPRO_EVENT_E_BAD_INPUT;
|
|
314
|
+
if (policy->value_tolerance < 0.0 || policy->value_tolerance != policy->value_tolerance)
|
|
315
|
+
return ORBPRO_EVENT_E_BAD_INPUT;
|
|
316
|
+
if (interval->component != ORBPRO_EVENT_ALL_COMPONENTS &&
|
|
317
|
+
interval->component >= r->component_count)
|
|
318
|
+
return ORBPRO_EVENT_E_UNKNOWN_COMPONENT;
|
|
319
|
+
|
|
320
|
+
r->interval = *interval;
|
|
321
|
+
r->policy = *policy;
|
|
322
|
+
r->hit_count = 0u;
|
|
323
|
+
r->evaluation_count = 0u;
|
|
324
|
+
r->truncated = 0u;
|
|
325
|
+
r->pending_head = 0u;
|
|
326
|
+
r->pending_tail = 0u;
|
|
327
|
+
r->have_previous = 0;
|
|
328
|
+
r->next_sample = 0u;
|
|
329
|
+
r->outstanding_epochs = 0u;
|
|
330
|
+
|
|
331
|
+
for (uint32_t i = 0; i < r->component_count; ++i) {
|
|
332
|
+
r->crossings[i] = 0u;
|
|
333
|
+
r->qualifying[i] = 0u;
|
|
334
|
+
r->comp_evals[i] = 0u;
|
|
335
|
+
r->prev_g[i] = 0.0;
|
|
336
|
+
r->prev_sign[i] = 0;
|
|
337
|
+
r->first_g[i] = 0.0;
|
|
338
|
+
r->first_sign[i] = 0;
|
|
339
|
+
r->last_g[i] = 0.0;
|
|
340
|
+
r->last_sign[i] = 0;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/* Seconds from the interval start. The day difference is exact for
|
|
344
|
+
* integral Julian days, so the span carries no JD quantization. */
|
|
345
|
+
r->span_seconds = (interval->stop_jd_day - interval->start_jd_day) * 86400.0 +
|
|
346
|
+
(interval->stop_seconds - interval->start_seconds);
|
|
347
|
+
if (r->span_seconds == 0.0) return ORBPRO_EVENT_E_BAD_INPUT;
|
|
348
|
+
|
|
349
|
+
r->step_seconds = r->span_seconds < 0.0 ? -policy->scan_step_seconds
|
|
350
|
+
: policy->scan_step_seconds;
|
|
351
|
+
|
|
352
|
+
/* Sample count includes BOTH endpoints, and the last sample is clamped to
|
|
353
|
+
* the interval end EXACTLY rather than overshooting it: a scan that runs
|
|
354
|
+
* past its own interval reports events outside the window it was asked
|
|
355
|
+
* about, which is worse than missing them. */
|
|
356
|
+
{
|
|
357
|
+
double whole = orbpro_event_fabs(r->span_seconds) / policy->scan_step_seconds;
|
|
358
|
+
double floored = (double)(uint32_t)whole;
|
|
359
|
+
uint32_t steps = (uint32_t)floored;
|
|
360
|
+
if (whole - floored > 0.0) steps += 1u;
|
|
361
|
+
if (steps == 0u) steps = 1u;
|
|
362
|
+
r->total_steps = (double)steps;
|
|
363
|
+
r->sample_count = steps + 1u;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
r->effective_tolerance = policy->epoch_tolerance_seconds;
|
|
367
|
+
r->clamped = 0;
|
|
368
|
+
if (r->state_epoch_resolution_seconds > r->effective_tolerance) {
|
|
369
|
+
r->effective_tolerance = r->state_epoch_resolution_seconds;
|
|
370
|
+
r->clamped = 1;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
r->phase = ORBPRO_EVENT_PHASE_SCAN_WAIT;
|
|
374
|
+
return 0;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/** Sample k's offset in seconds from the interval start, endpoint-exact. */
|
|
378
|
+
static inline double orbpro_event_sample_time(const OrbProEventRunner* r, uint32_t k) {
|
|
379
|
+
if (k >= r->sample_count - 1u) return r->span_seconds;
|
|
380
|
+
return r->step_seconds * (double)k;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
static inline void orbpro_event_emit_epoch(const OrbProEventRunner* r, double t, double* out) {
|
|
384
|
+
out[0] = r->interval.start_jd_day;
|
|
385
|
+
out[1] = r->interval.start_seconds + t;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
static inline uint32_t orbpro_event_pending_count(const OrbProEventRunner* r) {
|
|
389
|
+
return r->pending_tail - r->pending_head;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/* ---- the refinement state machine ------------------------------------------
|
|
393
|
+
* Brent's method, suspended at every function evaluation. `advance` runs from
|
|
394
|
+
* the top of the classical loop and returns 1 having written the next abscissa
|
|
395
|
+
* into `br->b`, or 0 having finished with the root in `br->b`.
|
|
396
|
+
* -------------------------------------------------------------------------- */
|
|
397
|
+
|
|
398
|
+
static inline int orbpro_event_bracket_advance(OrbProEventBracket* br) {
|
|
399
|
+
for (;;) {
|
|
400
|
+
if (br->method == (uint8_t)ORBPRO_ROOT_BISECTION ||
|
|
401
|
+
br->method == (uint8_t)ORBPRO_ROOT_ILLINOIS) {
|
|
402
|
+
/* a/c are the live bracket, b is the last probe. */
|
|
403
|
+
if (br->iterations > 0u) {
|
|
404
|
+
if ((br->fb > 0.0) == (br->fa > 0.0)) {
|
|
405
|
+
br->a = br->b;
|
|
406
|
+
br->fa = br->fb;
|
|
407
|
+
if (br->method == (uint8_t)ORBPRO_ROOT_ILLINOIS) br->fc *= 0.5;
|
|
408
|
+
} else {
|
|
409
|
+
br->c = br->b;
|
|
410
|
+
br->fc = br->fb;
|
|
411
|
+
if (br->method == (uint8_t)ORBPRO_ROOT_ILLINOIS) br->fa *= 0.5;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
if (br->fb == 0.0 ||
|
|
415
|
+
(br->vtol > 0.0 && br->iterations > 0u && orbpro_event_fabs(br->fb) <= br->vtol) ||
|
|
416
|
+
orbpro_event_fabs(br->c - br->a) <= br->tol) {
|
|
417
|
+
br->status = (uint8_t)ORBPRO_ROOT_STATUS_CONVERGED;
|
|
418
|
+
if (br->iterations > 0u) return 0;
|
|
419
|
+
br->b = 0.5 * (br->a + br->c);
|
|
420
|
+
return 0;
|
|
421
|
+
}
|
|
422
|
+
if (br->iterations >= br->max_iterations) {
|
|
423
|
+
br->status = (uint8_t)ORBPRO_ROOT_STATUS_MAX_ITERATIONS;
|
|
424
|
+
br->b = 0.5 * (br->a + br->c);
|
|
425
|
+
return 0;
|
|
426
|
+
}
|
|
427
|
+
br->iterations += 1u;
|
|
428
|
+
if (br->method == (uint8_t)ORBPRO_ROOT_BISECTION ||
|
|
429
|
+
br->fc == br->fa) {
|
|
430
|
+
br->b = 0.5 * (br->a + br->c);
|
|
431
|
+
} else {
|
|
432
|
+
br->b = (br->a * br->fc - br->c * br->fa) / (br->fc - br->fa);
|
|
433
|
+
/* Never leave the bracket, whatever the interpolation says. */
|
|
434
|
+
double lo = br->a < br->c ? br->a : br->c;
|
|
435
|
+
double hi = br->a < br->c ? br->c : br->a;
|
|
436
|
+
if (!(br->b > lo && br->b < hi)) br->b = 0.5 * (br->a + br->c);
|
|
437
|
+
}
|
|
438
|
+
return 1;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/* Brent. */
|
|
442
|
+
if ((br->fb > 0.0 && br->fc > 0.0) || (br->fb < 0.0 && br->fc < 0.0)) {
|
|
443
|
+
br->c = br->a;
|
|
444
|
+
br->fc = br->fa;
|
|
445
|
+
br->e = br->b - br->a;
|
|
446
|
+
br->d = br->e;
|
|
447
|
+
}
|
|
448
|
+
if (orbpro_event_fabs(br->fc) < orbpro_event_fabs(br->fb)) {
|
|
449
|
+
br->a = br->b;
|
|
450
|
+
br->b = br->c;
|
|
451
|
+
br->c = br->a;
|
|
452
|
+
br->fa = br->fb;
|
|
453
|
+
br->fb = br->fc;
|
|
454
|
+
br->fc = br->fa;
|
|
455
|
+
}
|
|
456
|
+
{
|
|
457
|
+
double tol1 = 2.0 * ORBPRO_EVENT_DBL_EPSILON * orbpro_event_fabs(br->b) +
|
|
458
|
+
0.5 * br->tol;
|
|
459
|
+
double xm = 0.5 * (br->c - br->b);
|
|
460
|
+
if (orbpro_event_fabs(xm) <= tol1 || br->fb == 0.0 ||
|
|
461
|
+
(br->vtol > 0.0 && orbpro_event_fabs(br->fb) <= br->vtol)) {
|
|
462
|
+
br->status = (uint8_t)ORBPRO_ROOT_STATUS_CONVERGED;
|
|
463
|
+
return 0;
|
|
464
|
+
}
|
|
465
|
+
if (br->iterations >= br->max_iterations) {
|
|
466
|
+
br->status = (uint8_t)ORBPRO_ROOT_STATUS_MAX_ITERATIONS;
|
|
467
|
+
return 0;
|
|
468
|
+
}
|
|
469
|
+
br->iterations += 1u;
|
|
470
|
+
|
|
471
|
+
if (orbpro_event_fabs(br->e) >= tol1 &&
|
|
472
|
+
orbpro_event_fabs(br->fa) > orbpro_event_fabs(br->fb)) {
|
|
473
|
+
double s = br->fb / br->fa;
|
|
474
|
+
double p, q, rr;
|
|
475
|
+
if (br->a == br->c) {
|
|
476
|
+
p = 2.0 * xm * s;
|
|
477
|
+
q = 1.0 - s;
|
|
478
|
+
} else {
|
|
479
|
+
q = br->fa / br->fc;
|
|
480
|
+
rr = br->fb / br->fc;
|
|
481
|
+
p = s * (2.0 * xm * q * (q - rr) - (br->b - br->a) * (rr - 1.0));
|
|
482
|
+
q = (q - 1.0) * (rr - 1.0) * (s - 1.0);
|
|
483
|
+
}
|
|
484
|
+
if (p > 0.0) q = -q;
|
|
485
|
+
p = orbpro_event_fabs(p);
|
|
486
|
+
{
|
|
487
|
+
double min1 = 3.0 * xm * q - orbpro_event_fabs(tol1 * q);
|
|
488
|
+
double min2 = orbpro_event_fabs(br->e * q);
|
|
489
|
+
double lim = min1 < min2 ? min1 : min2;
|
|
490
|
+
if (2.0 * p < lim) {
|
|
491
|
+
br->e = br->d;
|
|
492
|
+
br->d = p / q;
|
|
493
|
+
} else {
|
|
494
|
+
br->d = xm;
|
|
495
|
+
br->e = br->d;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
} else {
|
|
499
|
+
br->d = xm;
|
|
500
|
+
br->e = br->d;
|
|
501
|
+
}
|
|
502
|
+
br->a = br->b;
|
|
503
|
+
br->fa = br->fb;
|
|
504
|
+
if (orbpro_event_fabs(br->d) > tol1) br->b += br->d;
|
|
505
|
+
else br->b += orbpro_event_signed_tol(tol1, xm);
|
|
506
|
+
return 1;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
static inline void orbpro_event_bracket_begin(OrbProEventBracket* br,
|
|
512
|
+
const OrbProEventRunner* r,
|
|
513
|
+
uint32_t component,
|
|
514
|
+
double t_lo, double g_lo,
|
|
515
|
+
double t_hi, double g_hi,
|
|
516
|
+
int32_t direction) {
|
|
517
|
+
orbpro_event_zero(br, sizeof(*br));
|
|
518
|
+
br->component = component;
|
|
519
|
+
br->direction = direction;
|
|
520
|
+
br->tol = r->effective_tolerance;
|
|
521
|
+
br->vtol = r->policy.value_tolerance;
|
|
522
|
+
br->max_iterations = r->policy.max_iterations;
|
|
523
|
+
br->method = (uint8_t)r->policy.method;
|
|
524
|
+
if (r->comp_continuity[component] == (uint8_t)ORBPRO_CONTINUITY_PIECEWISE &&
|
|
525
|
+
br->method == (uint8_t)ORBPRO_ROOT_BRENT) {
|
|
526
|
+
/* Declared non-smooth: interpolation is not admissible, so the runner
|
|
527
|
+
* falls back to the method whose iteration count depends on the
|
|
528
|
+
* bracket width alone. This is the locator's declaration doing work,
|
|
529
|
+
* not a heuristic sniffing the numbers. */
|
|
530
|
+
br->method = (uint8_t)ORBPRO_ROOT_BISECTION;
|
|
531
|
+
}
|
|
532
|
+
br->a = t_lo;
|
|
533
|
+
br->fa = g_lo;
|
|
534
|
+
br->b = t_hi;
|
|
535
|
+
br->fb = g_hi;
|
|
536
|
+
br->c = t_hi;
|
|
537
|
+
br->fc = g_hi;
|
|
538
|
+
br->status = (uint8_t)ORBPRO_ROOT_STATUS_CONVERGED;
|
|
539
|
+
br->iterations = 0u;
|
|
540
|
+
br->evaluations = 0u;
|
|
541
|
+
br->awaiting = 0u;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
static inline void orbpro_event_record_hit(OrbProEventRunner* r,
|
|
545
|
+
uint32_t component,
|
|
546
|
+
double t,
|
|
547
|
+
double value,
|
|
548
|
+
int32_t direction,
|
|
549
|
+
uint8_t status,
|
|
550
|
+
uint32_t iterations,
|
|
551
|
+
uint32_t evaluations,
|
|
552
|
+
uint32_t extra_flags) {
|
|
553
|
+
r->crossings[component] += 1u;
|
|
554
|
+
r->qualifying[component] += 1u;
|
|
555
|
+
if (r->comp_occurrence[component] != 0u &&
|
|
556
|
+
r->qualifying[component] != r->comp_occurrence[component]) {
|
|
557
|
+
return;
|
|
558
|
+
}
|
|
559
|
+
if (r->interval.occurrence != 0u &&
|
|
560
|
+
r->qualifying[component] != r->interval.occurrence) {
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
if (r->hit_count >= ORBPRO_EVENT_MAX_HITS) {
|
|
564
|
+
r->truncated = 1u;
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
{
|
|
568
|
+
/* INSERT IN SCAN ORDER, never in discovery order.
|
|
569
|
+
*
|
|
570
|
+
* Roots are only known after refinement, and refinement completes in
|
|
571
|
+
* bracket-queue order — which is chronological PER COMPONENT but
|
|
572
|
+
* interleaved ACROSS components. Reporting discovery order therefore
|
|
573
|
+
* made the hit list a function of the scan step: with a 60 s step two
|
|
574
|
+
* components' events fell in separate samples and came out in epoch
|
|
575
|
+
* order, and with a 137 s step they fell in the same sample and came
|
|
576
|
+
* out in component order. Measured: the same eight events, two of them
|
|
577
|
+
* transposed by half an orbit. The step-independence invariant is
|
|
578
|
+
* about the LIST, not only about each epoch, so the list is ordered
|
|
579
|
+
* here.
|
|
580
|
+
*
|
|
581
|
+
* Scan order is increasing epoch forward and DECREASING epoch
|
|
582
|
+
* backward — the order a consumer walks the arc in. */
|
|
583
|
+
double key = r->interval.start_seconds + t;
|
|
584
|
+
uint32_t index = r->hit_count;
|
|
585
|
+
while (index > 0u) {
|
|
586
|
+
double prior = r->hits[index - 1u].epoch_seconds;
|
|
587
|
+
int later = r->span_seconds < 0.0 ? (prior < key) : (prior > key);
|
|
588
|
+
if (!later) break;
|
|
589
|
+
r->hits[index] = r->hits[index - 1u];
|
|
590
|
+
index -= 1u;
|
|
591
|
+
}
|
|
592
|
+
OrbProEventHit* hit = &r->hits[index];
|
|
593
|
+
orbpro_event_hit_init(hit);
|
|
594
|
+
hit->epoch_jd_day = r->interval.start_jd_day;
|
|
595
|
+
hit->epoch_seconds = r->interval.start_seconds + t;
|
|
596
|
+
hit->value = value;
|
|
597
|
+
hit->component = component;
|
|
598
|
+
hit->iterations = iterations;
|
|
599
|
+
hit->evaluations = evaluations;
|
|
600
|
+
hit->flags = extra_flags;
|
|
601
|
+
if (r->span_seconds < 0.0) hit->flags |= (uint32_t)ORBPRO_EVENT_BACKWARD;
|
|
602
|
+
if (r->comp_is_stop[component]) hit->flags |= (uint32_t)ORBPRO_EVENT_STOP_CONDITION;
|
|
603
|
+
if (r->first_sign[component] != 0 &&
|
|
604
|
+
r->crossings[component] == 1u &&
|
|
605
|
+
r->first_sign[component] == orbpro_event_sign(value)) {
|
|
606
|
+
hit->flags |= (uint32_t)ORBPRO_EVENT_TANGENTIAL;
|
|
607
|
+
}
|
|
608
|
+
orbpro_event_hit_set_direction(hit, (OrbProCrossingDirection)direction);
|
|
609
|
+
{
|
|
610
|
+
uint8_t reported = status;
|
|
611
|
+
if (reported == (uint8_t)ORBPRO_ROOT_STATUS_CONVERGED && r->clamped) {
|
|
612
|
+
reported = (uint8_t)ORBPRO_ROOT_STATUS_EPOCH_RESOLUTION_LIMITED;
|
|
613
|
+
}
|
|
614
|
+
orbpro_event_hit_set_status(hit, (OrbProRootStatus)reported);
|
|
615
|
+
}
|
|
616
|
+
r->hit_count += 1u;
|
|
617
|
+
}
|
|
618
|
+
if (r->interval.max_events != 0u) {
|
|
619
|
+
/* `max_events` means the first N IN SCAN ORDER. An event discovered
|
|
620
|
+
* later but lying earlier displaces the last one, which is then
|
|
621
|
+
* dropped and the result marked truncated — the alternative is a cap
|
|
622
|
+
* that returns a different set depending on the scan step. */
|
|
623
|
+
if (r->hit_count > r->interval.max_events) {
|
|
624
|
+
r->hit_count = r->interval.max_events;
|
|
625
|
+
r->truncated = 1u;
|
|
626
|
+
}
|
|
627
|
+
if (r->hit_count >= r->interval.max_events) {
|
|
628
|
+
r->phase = ORBPRO_EVENT_PHASE_DONE;
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
/**
|
|
634
|
+
* Ask for the next batch of epochs.
|
|
635
|
+
*
|
|
636
|
+
* Returns the number of epochs written (each as a (jd_day, seconds) PAIR, so
|
|
637
|
+
* `2 * n` doubles), 0 when the scan is finished, or a negative
|
|
638
|
+
* `OrbProEventError`.
|
|
639
|
+
*/
|
|
640
|
+
static inline int32_t orbpro_event_runner_next(OrbProEventRunner* r,
|
|
641
|
+
OrbProEventStateRequest* request,
|
|
642
|
+
double* epochs,
|
|
643
|
+
uint32_t epoch_capacity) {
|
|
644
|
+
if (request == NULL || epochs == NULL) return ORBPRO_EVENT_E_NULL_OUTPUT;
|
|
645
|
+
if (r->phase == ORBPRO_EVENT_PHASE_IDLE) return ORBPRO_EVENT_E_NOT_STARTED;
|
|
646
|
+
if (r->outstanding_epochs != 0u) return ORBPRO_EVENT_E_PROTOCOL_ORDER;
|
|
647
|
+
|
|
648
|
+
orbpro_event_state_request_init(request);
|
|
649
|
+
request->object_count = r->object_count;
|
|
650
|
+
orbpro_event_state_request_set_reference_frame(
|
|
651
|
+
request, (OrbProReferenceFrame)r->reference_frame);
|
|
652
|
+
|
|
653
|
+
if (r->interval.max_evaluations != 0u &&
|
|
654
|
+
r->evaluation_count >= r->interval.max_evaluations) {
|
|
655
|
+
r->truncated = 1u;
|
|
656
|
+
r->phase = ORBPRO_EVENT_PHASE_DONE;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
if (r->phase == ORBPRO_EVENT_PHASE_DONE) {
|
|
660
|
+
/* Brackets still queued when the scan stopped ARE dropped events, and
|
|
661
|
+
* `max_events` is the usual reason. Saying so is the difference
|
|
662
|
+
* between "these are the events" and "these are the first N events";
|
|
663
|
+
* a consumer that cannot tell them apart will publish the second as
|
|
664
|
+
* the first. Unscanned samples are the same story. */
|
|
665
|
+
if (orbpro_event_pending_count(r) > 0u || r->next_sample < r->sample_count) {
|
|
666
|
+
r->truncated = 1u;
|
|
667
|
+
}
|
|
668
|
+
request->epoch_count = 0u;
|
|
669
|
+
return 0;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
/* Refinement first: brackets found in the last scan batch are refined in
|
|
673
|
+
* scan order BEFORE the scan advances, so occurrence counting is
|
|
674
|
+
* chronological and a `max_events` stop lands on the right event. */
|
|
675
|
+
if (orbpro_event_pending_count(r) > 0u) {
|
|
676
|
+
OrbProEventBracket* br = &r->pending[r->pending_head % ORBPRO_EVENT_MAX_PENDING];
|
|
677
|
+
if (epoch_capacity < 1u) return ORBPRO_EVENT_E_BUFFER_TOO_SMALL;
|
|
678
|
+
r->phase = ORBPRO_EVENT_PHASE_REFINE_WAIT;
|
|
679
|
+
r->outstanding_epochs = 1u;
|
|
680
|
+
r->outstanding_t[0] = br->b;
|
|
681
|
+
br->awaiting = 1u;
|
|
682
|
+
request->epoch_count = 1u;
|
|
683
|
+
request->flags = (uint32_t)ORBPRO_EVENT_NONE;
|
|
684
|
+
orbpro_event_emit_epoch(r, br->b, &epochs[0]);
|
|
685
|
+
return 1;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
if (r->next_sample >= r->sample_count) {
|
|
689
|
+
r->phase = ORBPRO_EVENT_PHASE_DONE;
|
|
690
|
+
request->epoch_count = 0u;
|
|
691
|
+
return 0;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
{
|
|
695
|
+
uint32_t remaining = r->sample_count - r->next_sample;
|
|
696
|
+
uint32_t want = remaining < ORBPRO_EVENT_SCAN_BATCH ? remaining : ORBPRO_EVENT_SCAN_BATCH;
|
|
697
|
+
if (want > epoch_capacity) want = epoch_capacity;
|
|
698
|
+
if (want == 0u) return ORBPRO_EVENT_E_BUFFER_TOO_SMALL;
|
|
699
|
+
for (uint32_t i = 0; i < want; ++i) {
|
|
700
|
+
double t = orbpro_event_sample_time(r, r->next_sample + i);
|
|
701
|
+
r->outstanding_t[i] = t;
|
|
702
|
+
orbpro_event_emit_epoch(r, t, &epochs[2u * i]);
|
|
703
|
+
}
|
|
704
|
+
r->outstanding_epochs = want;
|
|
705
|
+
r->phase = ORBPRO_EVENT_PHASE_SCAN_WAIT;
|
|
706
|
+
request->epoch_count = want;
|
|
707
|
+
request->flags = (uint32_t)ORBPRO_EVENT_NONE;
|
|
708
|
+
return (int32_t)want;
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
/**
|
|
713
|
+
* Feed back the states for the epochs the last `next` asked for.
|
|
714
|
+
*
|
|
715
|
+
* `count` MUST be `epoch_count * object_count`, ordered EPOCH-MAJOR: state
|
|
716
|
+
* `[e * object_count + o]` is object `o` at epoch `e`. A mismatch is
|
|
717
|
+
* `SUPPLY_COUNT_MISMATCH`, never a partial evaluation — a locator that
|
|
718
|
+
* silently accepted a short supply would report events computed from another
|
|
719
|
+
* object's trajectory.
|
|
720
|
+
*/
|
|
721
|
+
static inline int32_t orbpro_event_runner_supply(OrbProEventRunner* r,
|
|
722
|
+
const OrbProStateVector* states,
|
|
723
|
+
uint32_t count) {
|
|
724
|
+
if (states == NULL) return ORBPRO_EVENT_E_NULL_OUTPUT;
|
|
725
|
+
if (r->outstanding_epochs == 0u) return ORBPRO_EVENT_E_PROTOCOL_ORDER;
|
|
726
|
+
if (count != r->outstanding_epochs * r->object_count)
|
|
727
|
+
return ORBPRO_EVENT_E_SUPPLY_COUNT_MISMATCH;
|
|
728
|
+
|
|
729
|
+
uint32_t supplied = r->outstanding_epochs;
|
|
730
|
+
r->outstanding_epochs = 0u;
|
|
731
|
+
|
|
732
|
+
if (r->phase == ORBPRO_EVENT_PHASE_REFINE_WAIT) {
|
|
733
|
+
OrbProEventBracket* br = &r->pending[r->pending_head % ORBPRO_EVENT_MAX_PENDING];
|
|
734
|
+
int32_t rc = r->eval(r->interval.start_jd_day,
|
|
735
|
+
r->interval.start_seconds + r->outstanding_t[0],
|
|
736
|
+
states, r->object_count,
|
|
737
|
+
r->g_scratch, r->component_count, r->user);
|
|
738
|
+
if (rc < 0) return rc;
|
|
739
|
+
r->evaluation_count += 1u;
|
|
740
|
+
r->comp_evals[br->component] += 1u;
|
|
741
|
+
br->evaluations += 1u;
|
|
742
|
+
br->fb = r->g_scratch[br->component];
|
|
743
|
+
br->awaiting = 0u;
|
|
744
|
+
if (orbpro_event_bracket_advance(br) == 0) {
|
|
745
|
+
orbpro_event_record_hit(r, br->component, br->b, br->fb, br->direction,
|
|
746
|
+
br->status, br->iterations, br->evaluations,
|
|
747
|
+
br->flags);
|
|
748
|
+
r->pending_head += 1u;
|
|
749
|
+
if (r->pending_head == r->pending_tail) {
|
|
750
|
+
r->pending_head = 0u;
|
|
751
|
+
r->pending_tail = 0u;
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
if (r->phase != ORBPRO_EVENT_PHASE_DONE) r->phase = ORBPRO_EVENT_PHASE_SCAN_WAIT;
|
|
755
|
+
return 0;
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
for (uint32_t i = 0; i < supplied; ++i) {
|
|
759
|
+
double t = r->outstanding_t[i];
|
|
760
|
+
int32_t rc = r->eval(r->interval.start_jd_day,
|
|
761
|
+
r->interval.start_seconds + t,
|
|
762
|
+
&states[(size_t)i * r->object_count], r->object_count,
|
|
763
|
+
r->g_scratch, r->component_count, r->user);
|
|
764
|
+
if (rc < 0) return rc;
|
|
765
|
+
r->evaluation_count += 1u;
|
|
766
|
+
|
|
767
|
+
for (uint32_t comp = 0; comp < r->component_count; ++comp) {
|
|
768
|
+
if (r->interval.component != ORBPRO_EVENT_ALL_COMPONENTS &&
|
|
769
|
+
r->interval.component != comp)
|
|
770
|
+
continue;
|
|
771
|
+
double g = r->g_scratch[comp];
|
|
772
|
+
int8_t s = orbpro_event_sign(g);
|
|
773
|
+
r->comp_evals[comp] += 1u;
|
|
774
|
+
|
|
775
|
+
if (!r->have_previous) {
|
|
776
|
+
r->first_g[comp] = g;
|
|
777
|
+
r->first_sign[comp] = s;
|
|
778
|
+
}
|
|
779
|
+
r->last_g[comp] = g;
|
|
780
|
+
r->last_sign[comp] = s;
|
|
781
|
+
|
|
782
|
+
if (r->have_previous) {
|
|
783
|
+
int8_t sp = r->prev_sign[comp];
|
|
784
|
+
double gp = r->prev_g[comp];
|
|
785
|
+
int crosses = (sp != 0 && s != 0 && sp != s) || (s == 0 && sp != 0);
|
|
786
|
+
if (crosses) {
|
|
787
|
+
int32_t dir = (g > gp) ? 1 : -1;
|
|
788
|
+
int32_t want = r->comp_direction[comp];
|
|
789
|
+
int32_t iwant = (int32_t)r->interval.direction;
|
|
790
|
+
int qualifies = (want == 0 || want == dir) && (iwant == 0 || iwant == dir);
|
|
791
|
+
if (qualifies) {
|
|
792
|
+
if (r->comp_continuity[comp] == (uint8_t)ORBPRO_CONTINUITY_DISCRETE) {
|
|
793
|
+
/* A step, not a root. Report where the step was
|
|
794
|
+
* SEEN, to within the scan step, and say so. */
|
|
795
|
+
orbpro_event_record_hit(r, comp, t, g, dir,
|
|
796
|
+
(uint8_t)ORBPRO_ROOT_STATUS_DISCONTINUOUS,
|
|
797
|
+
0u, 1u, 0u);
|
|
798
|
+
} else if (s == 0) {
|
|
799
|
+
orbpro_event_record_hit(r, comp, t, g, dir,
|
|
800
|
+
(uint8_t)ORBPRO_ROOT_STATUS_CONVERGED,
|
|
801
|
+
0u, 1u, 0u);
|
|
802
|
+
} else if (orbpro_event_pending_count(r) >= ORBPRO_EVENT_MAX_PENDING) {
|
|
803
|
+
r->truncated = 1u;
|
|
804
|
+
} else {
|
|
805
|
+
OrbProEventBracket* br =
|
|
806
|
+
&r->pending[r->pending_tail % ORBPRO_EVENT_MAX_PENDING];
|
|
807
|
+
orbpro_event_bracket_begin(br, r, comp, r->prev_t, gp, t, g, dir);
|
|
808
|
+
/* Prime the state machine: the first advance
|
|
809
|
+
* consumes the bracket endpoints and produces the
|
|
810
|
+
* first abscissa to evaluate. */
|
|
811
|
+
if (orbpro_event_bracket_advance(br) == 0) {
|
|
812
|
+
orbpro_event_record_hit(r, comp, br->b, br->fb, dir,
|
|
813
|
+
br->status, br->iterations,
|
|
814
|
+
br->evaluations, br->flags);
|
|
815
|
+
} else {
|
|
816
|
+
r->pending_tail += 1u;
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
} else {
|
|
820
|
+
r->crossings[comp] += 1u;
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
r->prev_g[comp] = g;
|
|
825
|
+
r->prev_sign[comp] = s;
|
|
826
|
+
}
|
|
827
|
+
r->prev_t = t;
|
|
828
|
+
r->have_previous = 1;
|
|
829
|
+
if (r->phase == ORBPRO_EVENT_PHASE_DONE) break;
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
r->next_sample += supplied;
|
|
833
|
+
if (r->phase != ORBPRO_EVENT_PHASE_DONE) {
|
|
834
|
+
r->phase = (r->next_sample >= r->sample_count && orbpro_event_pending_count(r) == 0u)
|
|
835
|
+
? ORBPRO_EVENT_PHASE_DONE
|
|
836
|
+
: ORBPRO_EVENT_PHASE_SCAN_WAIT;
|
|
837
|
+
}
|
|
838
|
+
return 0;
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
/* --------------------------------------------------------------------------
|
|
842
|
+
* Results.
|
|
843
|
+
* -------------------------------------------------------------------------- */
|
|
844
|
+
|
|
845
|
+
static inline int32_t orbpro_event_runner_hit_count(const OrbProEventRunner* r) {
|
|
846
|
+
return (int32_t)r->hit_count;
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
static inline int32_t orbpro_event_runner_hits(const OrbProEventRunner* r,
|
|
850
|
+
OrbProEventHit* out,
|
|
851
|
+
uint32_t capacity) {
|
|
852
|
+
if (out == NULL) return ORBPRO_EVENT_E_NULL_OUTPUT;
|
|
853
|
+
if (capacity < r->hit_count) return ORBPRO_EVENT_E_BUFFER_TOO_SMALL;
|
|
854
|
+
for (uint32_t i = 0; i < r->hit_count; ++i) out[i] = r->hits[i];
|
|
855
|
+
return (int32_t)r->hit_count;
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
/**
|
|
859
|
+
* One summary per component: the endpoint values and SIGNS, without which
|
|
860
|
+
* "no hits" is ambiguous between "never in the interval" and "inside it the
|
|
861
|
+
* whole time".
|
|
862
|
+
*/
|
|
863
|
+
static inline int32_t orbpro_event_runner_summaries(const OrbProEventRunner* r,
|
|
864
|
+
OrbProEventScanSummary* out,
|
|
865
|
+
uint32_t capacity) {
|
|
866
|
+
if (out == NULL) return ORBPRO_EVENT_E_NULL_OUTPUT;
|
|
867
|
+
if (capacity < r->component_count) return ORBPRO_EVENT_E_BUFFER_TOO_SMALL;
|
|
868
|
+
for (uint32_t i = 0; i < r->component_count; ++i) {
|
|
869
|
+
OrbProEventScanSummary* s = &out[i];
|
|
870
|
+
orbpro_event_scan_summary_init(s);
|
|
871
|
+
s->initial_value = r->first_g[i];
|
|
872
|
+
s->final_value = r->last_g[i];
|
|
873
|
+
s->component = i;
|
|
874
|
+
s->crossing_count = r->crossings[i];
|
|
875
|
+
s->evaluation_count = r->comp_evals[i];
|
|
876
|
+
orbpro_event_scan_summary_set_initial_sign(s, (OrbProSign)r->first_sign[i]);
|
|
877
|
+
orbpro_event_scan_summary_set_final_sign(s, (OrbProSign)r->last_sign[i]);
|
|
878
|
+
}
|
|
879
|
+
return (int32_t)r->component_count;
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
/** Did the scan stop early? `TRUNCATED` is a real answer, not an error. */
|
|
883
|
+
static inline int orbpro_event_runner_truncated(const OrbProEventRunner* r) {
|
|
884
|
+
return r->truncated != 0u;
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
/** The tolerance actually used, after the state-source resolution clamp. */
|
|
888
|
+
static inline double orbpro_event_runner_effective_tolerance(const OrbProEventRunner* r) {
|
|
889
|
+
return r->effective_tolerance;
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
static inline uint32_t orbpro_event_runner_evaluation_count(const OrbProEventRunner* r) {
|
|
893
|
+
return r->evaluation_count;
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
static inline void orbpro_event_runner_reset(OrbProEventRunner* r) {
|
|
897
|
+
orbpro_event_eval_fn eval = r->eval;
|
|
898
|
+
void* user = r->user;
|
|
899
|
+
orbpro_event_runner_init(r, eval, user);
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
/* --------------------------------------------------------------------------
|
|
903
|
+
* The export set, for free.
|
|
904
|
+
*
|
|
905
|
+
* A locator writes plugin_event_describe, plugin_event_configure and
|
|
906
|
+
* plugin_event_eval. Everything else is THIS macro. That is the structural
|
|
907
|
+
* form of "adding a new locator requires no change to the runner": there is
|
|
908
|
+
* no runner to change, only a g to write.
|
|
909
|
+
* -------------------------------------------------------------------------- */
|
|
910
|
+
|
|
911
|
+
#if defined(__cplusplus)
|
|
912
|
+
#define ORBPRO_EVENT_EXPORT(name) extern "C" __attribute__((export_name(name)))
|
|
913
|
+
#else
|
|
914
|
+
#define ORBPRO_EVENT_EXPORT(name) __attribute__((export_name(name)))
|
|
915
|
+
#endif
|
|
916
|
+
|
|
917
|
+
#define ORBPRO_EVENT_RUNNER_EXPORTS(RUNNER) \
|
|
918
|
+
ORBPRO_EVENT_EXPORT("plugin_event_begin") \
|
|
919
|
+
int32_t plugin_event_begin(const OrbProEventInterval* interval, \
|
|
920
|
+
const OrbProRootPolicy* policy) { \
|
|
921
|
+
return orbpro_event_runner_begin(&(RUNNER), interval, policy); \
|
|
922
|
+
} \
|
|
923
|
+
ORBPRO_EVENT_EXPORT("plugin_event_next") \
|
|
924
|
+
int32_t plugin_event_next(OrbProEventStateRequest* request, double* epochs, \
|
|
925
|
+
uint32_t epoch_capacity) { \
|
|
926
|
+
return orbpro_event_runner_next(&(RUNNER), request, epochs, epoch_capacity); \
|
|
927
|
+
} \
|
|
928
|
+
ORBPRO_EVENT_EXPORT("plugin_event_supply") \
|
|
929
|
+
int32_t plugin_event_supply(const OrbProStateVector* states, uint32_t count) { \
|
|
930
|
+
return orbpro_event_runner_supply(&(RUNNER), states, count); \
|
|
931
|
+
} \
|
|
932
|
+
ORBPRO_EVENT_EXPORT("plugin_event_hit_count") \
|
|
933
|
+
int32_t plugin_event_hit_count(void) { \
|
|
934
|
+
return orbpro_event_runner_hit_count(&(RUNNER)); \
|
|
935
|
+
} \
|
|
936
|
+
ORBPRO_EVENT_EXPORT("plugin_event_hits") \
|
|
937
|
+
int32_t plugin_event_hits(OrbProEventHit* out, uint32_t capacity) { \
|
|
938
|
+
return orbpro_event_runner_hits(&(RUNNER), out, capacity); \
|
|
939
|
+
} \
|
|
940
|
+
ORBPRO_EVENT_EXPORT("plugin_event_summaries") \
|
|
941
|
+
int32_t plugin_event_summaries(OrbProEventScanSummary* out, uint32_t capacity) { \
|
|
942
|
+
return orbpro_event_runner_summaries(&(RUNNER), out, capacity); \
|
|
943
|
+
} \
|
|
944
|
+
ORBPRO_EVENT_EXPORT("plugin_event_destroy") \
|
|
945
|
+
void plugin_event_destroy(void) { orbpro_event_runner_reset(&(RUNNER)); }
|
|
946
|
+
|
|
947
|
+
#ifdef __cplusplus
|
|
948
|
+
} /* extern "C" */
|
|
949
|
+
#endif
|
|
950
|
+
|
|
951
|
+
#endif /* ORBPRO_EVENT_RUNNER_H */
|