space-data-module-sdk 0.8.6 → 0.8.7
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/README.md +13 -0
- package/docs/AGENTS.md +5 -0
- package/docs/browser-wasmedge-isomorphic.md +64 -0
- package/docs/provider-access-abi.md +600 -0
- package/package.json +3 -2
- package/schemas/orbpro/Propagator.fbs +19 -3
- package/src/compiler/compileModule.js +24 -1
- package/src/flow/flowCompiler.js +141 -2
- package/src/flow/flowRuntimeHost.js +7 -1
- package/src/flow/vendor/sdn-flow/MethodRegistry.js +6 -1
- package/src/generated/orbpro/propagator/propagator-source-description.js +2 -2
- package/src/generated/orbpro/propagator/propagator-source-description.ts +2 -2
- package/src/generated/orbpro/propagator/propagator-source-kind.js +13 -2
- package/src/generated/orbpro/propagator/propagator-source-kind.ts +13 -2
- package/src/generated/spacedatastandards/plg/pluginCategory.d.ts +18 -1
- package/src/generated/spacedatastandards/plg/pluginCategory.d.ts.map +1 -1
- package/src/generated/spacedatastandards/plg/pluginCategory.js +17 -0
- package/src/generated/spacedatastandards/plg/pluginCategory.ts +21 -1
- package/src/host/index.js +6 -0
- package/src/host/providerAccess.js +727 -0
- package/src/host/providerAccessAbi.js +403 -0
- package/src/host/providerAccessEngineAdapter.js +338 -0
- package/src/host/providerAccessFixtureAdapter.js +444 -0
- package/src/host/providerAccessTileStoreAdapter.js +366 -0
- package/src/host/terrainSourceSeam.js +205 -0
- package/src/host/wasiThreadHost.js +12 -1
- package/src/index.d.ts +29 -2
- package/src/testing/browserModuleHarness.js +48 -0
- package/src/testing/index.d.ts +12 -1
- package/src/testing/parityBrowserRunner.js +8 -1
- package/src/testing/workerModuleHarness.js +36 -7
- package/src/testing/workerModuleHarnessWorker.js +5 -4
- package/templates/provider-access-module/include/space_data_provider_abi.h +227 -0
|
@@ -0,0 +1,600 @@
|
|
|
1
|
+
# Provider Access ABI
|
|
2
|
+
|
|
3
|
+
**Status:** v1 — owner directive 2026-08-07, graph task `sdk-provider-access-abi`.
|
|
4
|
+
|
|
5
|
+
One generalized port that lets a WASM module **control** imagery and terrain
|
|
6
|
+
providers and **read the already-decoded bytes** those providers hold in
|
|
7
|
+
memory — identical import names and signatures in the browser, in native
|
|
8
|
+
WasmEdge, and in the Docker WasmEdge container.
|
|
9
|
+
|
|
10
|
+
This is a *provider access* port, not a *Cesium* port and not an *OrbPro* port.
|
|
11
|
+
Nothing in the guest ABI names an engine, a tile scheme, or a vendor. The same
|
|
12
|
+
three imports serve a browser engine's live quadtree cache, a host-side tile
|
|
13
|
+
store, and a deterministic test fixture.
|
|
14
|
+
|
|
15
|
+
## Table of contents
|
|
16
|
+
|
|
17
|
+
- [Doctrine](#doctrine)
|
|
18
|
+
- [Capability](#capability)
|
|
19
|
+
- [The import set](#the-import-set)
|
|
20
|
+
- [Tile descriptor](#tile-descriptor)
|
|
21
|
+
- [Acquire requests](#acquire-requests)
|
|
22
|
+
- [Control operations](#control-operations)
|
|
23
|
+
- [Error codes](#error-codes)
|
|
24
|
+
- [Copy contract](#copy-contract)
|
|
25
|
+
- [Runtime satisfaction](#runtime-satisfaction)
|
|
26
|
+
- [Parity envelope](#parity-envelope)
|
|
27
|
+
- [Guest usage](#guest-usage)
|
|
28
|
+
|
|
29
|
+
## Doctrine
|
|
30
|
+
|
|
31
|
+
Four rules govern every line below.
|
|
32
|
+
|
|
33
|
+
1. **One port, two planes.** Control is metadata and rides the existing
|
|
34
|
+
`space_data_module_host` sync hostcall bridge as `provider.*` operations.
|
|
35
|
+
Data is bulk bytes and rides three dedicated imports that write straight
|
|
36
|
+
into guest linear memory. Control never carries pixels; data never carries
|
|
37
|
+
JSON.
|
|
38
|
+
2. **No runtime detection in module code.** A module that reads terrain never
|
|
39
|
+
asks which runtime it is in. Every difference is absorbed in the SDK host
|
|
40
|
+
shims.
|
|
41
|
+
3. **Present everywhere, honest everywhere.** The imports link in *every*
|
|
42
|
+
runtime. There is no lane where `space_data_provider.acquire` is missing —
|
|
43
|
+
a missing import is a link-time divergence, the worst class. When nothing
|
|
44
|
+
can serve a request the port returns a **value** (a negative error code),
|
|
45
|
+
never a trap, and the same code in every runtime.
|
|
46
|
+
4. **Decoded or nothing, by default.** The port hands over buffers the provider
|
|
47
|
+
already decoded. It never re-fetches and never re-parses *unless the caller
|
|
48
|
+
explicitly raised its cost ceiling*. Every acquire carries a declared
|
|
49
|
+
[cost class](#cost-classes); the default ceiling admits only already-resident
|
|
50
|
+
bytes, and an adapter that would have to exceed it returns
|
|
51
|
+
`SDM_PROVIDER_E_UNSUPPORTED` rather than quietly paying it.
|
|
52
|
+
|
|
53
|
+
## Capability
|
|
54
|
+
|
|
55
|
+
The port is gated by the existing coarse capability, with a scope:
|
|
56
|
+
|
|
57
|
+
```json
|
|
58
|
+
{
|
|
59
|
+
"capability": "scene_access",
|
|
60
|
+
"scope": "provider.v1",
|
|
61
|
+
"required": false,
|
|
62
|
+
"description": "Read and control imagery/terrain providers."
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
`scene_access` is already in the SDK recommended capability vocabulary
|
|
67
|
+
(`src/capabilities.js`) and already maps to the typed
|
|
68
|
+
`CapabilityKind.SCENE_ACCESS` PLG enum (`src/manifest/normalize.js`). **This
|
|
69
|
+
ABI introduces no new host capability** — it is a scope on an existing one,
|
|
70
|
+
exactly as `gpu_compute` uses `scope: "webgpu.v1"`. No owner sign-off gate,
|
|
71
|
+
no SDS enum change, no new generic hook.
|
|
72
|
+
|
|
73
|
+
`required: false` is the recommended posture. A module whose analysis degrades
|
|
74
|
+
gracefully without provider bytes must declare it optional and handle
|
|
75
|
+
`SDM_PROVIDER_E_NO_PROVIDER` as a first-class outcome, because that is the
|
|
76
|
+
answer it will get on a headless node with no tile store configured.
|
|
77
|
+
|
|
78
|
+
## The import set
|
|
79
|
+
|
|
80
|
+
Import module: **`space_data_provider`**. Three functions. Every parameter and
|
|
81
|
+
every result is `i32`.
|
|
82
|
+
|
|
83
|
+
```wat
|
|
84
|
+
(import "space_data_provider" "acquire"
|
|
85
|
+
(func (param i32 i32 i32) (result i32)))
|
|
86
|
+
(import "space_data_provider" "read"
|
|
87
|
+
(func (param i32 i32 i32 i32 i32) (result i32)))
|
|
88
|
+
(import "space_data_provider" "release"
|
|
89
|
+
(func (param i32) (result i32)))
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
| function | params | result |
|
|
93
|
+
| --------- | -------------------------------------------------- | ------ |
|
|
94
|
+
| `acquire` | `reqPtr, reqLen, descPtr` | handle `> 0`, or negative error code |
|
|
95
|
+
| `read` | `handle, plane, srcOffset, dstPtr, dstLen` | bytes written `>= 0`, or negative error code |
|
|
96
|
+
| `release` | `handle` | `0`, or negative error code |
|
|
97
|
+
|
|
98
|
+
Open, read, close. Three doors.
|
|
99
|
+
|
|
100
|
+
**No `i64` appears anywhere in the boundary signature, and no `f64` either.**
|
|
101
|
+
This is deliberate. The i64 legalization trap measured on this SDK — a
|
|
102
|
+
64-bit-parameter import legalizes differently depending on how the host
|
|
103
|
+
instantiates it, and the mismatch surfaces as a link failure or a silently
|
|
104
|
+
truncated argument in exactly one runtime — is sidestepped by construction, not
|
|
105
|
+
by convention. Every 64-bit quantity in this ABI (bounding rectangles, height
|
|
106
|
+
extrema, sample coordinates) travels either inside the JSON acquire request or
|
|
107
|
+
inside the descriptor struct in guest memory, where it is plain
|
|
108
|
+
little-endian IEEE-754 the guest reads with a normal load. The boundary itself
|
|
109
|
+
carries only pointers, lengths, indices and codes.
|
|
110
|
+
|
|
111
|
+
- `acquire(reqPtr, reqLen, descPtr)` — `reqPtr/reqLen` is a UTF-8 JSON request
|
|
112
|
+
(see [Acquire requests](#acquire-requests)). `descPtr` is a guest pointer to
|
|
113
|
+
at least `SDM_PROVIDER_TILE_DESC_BYTES` (128) writable bytes; on success the
|
|
114
|
+
host fills it with the [tile descriptor](#tile-descriptor). The returned
|
|
115
|
+
handle pins the provider's decoded buffer on the host side until `release`.
|
|
116
|
+
The call blocks until the tile is resident (browser: `Atomics.wait` on the
|
|
117
|
+
SAB hostcall channel, the same mechanism `http` and `storage` already use;
|
|
118
|
+
WasmEdge: a blocking host call). `descPtr` is left untouched on failure.
|
|
119
|
+
- `read(handle, plane, srcOffset, dstPtr, dstLen)` — copies at most `dstLen`
|
|
120
|
+
bytes of `plane`, starting at `srcOffset` bytes into that plane, into guest
|
|
121
|
+
memory at `dstPtr`. Returns the number of bytes written, which is
|
|
122
|
+
`min(dstLen, planeByteLength - srcOffset)` and may legally be less than
|
|
123
|
+
`dstLen` at the tail. Reading a plane in several chunks is supported and
|
|
124
|
+
yields exactly the same bytes as one whole-plane read.
|
|
125
|
+
- `release(handle)` — unpins. Releasing an already-released handle returns
|
|
126
|
+
`SDM_PROVIDER_E_BAD_HANDLE`, in every runtime. Handles are per-instance and
|
|
127
|
+
per-thread-group; they are never valid across an instance restart.
|
|
128
|
+
|
|
129
|
+
Error detail (a message, a name, the failing operation) is retrieved through
|
|
130
|
+
the control plane with `provider.lastError`. It is deliberately not an extra
|
|
131
|
+
import: strings are metadata and metadata rides the existing bridge.
|
|
132
|
+
|
|
133
|
+
## Tile descriptor
|
|
134
|
+
|
|
135
|
+
`SDM_PROVIDER_TILE_DESC_BYTES = 128`. Little-endian. `u32` fields are 4-byte
|
|
136
|
+
aligned; `f64` fields start at offset 48 and are all 8-byte aligned, so a
|
|
137
|
+
guest may read them with aligned loads on every target.
|
|
138
|
+
|
|
139
|
+
| offset | type | field | meaning |
|
|
140
|
+
| ------ | ----- | ----------------- | ------- |
|
|
141
|
+
| 0 | u32 | `magic` | `0x53445054` (`'SDPT'`) |
|
|
142
|
+
| 4 | u32 | `version` | `1` |
|
|
143
|
+
| 8 | u32 | `kind` | `1` terrain, `2` imagery |
|
|
144
|
+
| 12 | u32 | `encoding` | see below |
|
|
145
|
+
| 16 | u32 | `width` | elements per row |
|
|
146
|
+
| 20 | u32 | `height` | rows |
|
|
147
|
+
| 24 | u32 | `planeCount` | number of readable planes, `>= 1` |
|
|
148
|
+
| 28 | u32 | `bytesPerElement` | of plane 0 |
|
|
149
|
+
| 32 | u32 | `rowStrideBytes` | of plane 0; `>= width * bytesPerElement` |
|
|
150
|
+
| 36 | u32 | `byteLength` | of plane 0 |
|
|
151
|
+
| 40 | u32 | `flags` | see below |
|
|
152
|
+
| 44 | u32 | `level` | source level, or `0xFFFFFFFF` for derived tiles |
|
|
153
|
+
| 48 | f64 | `west` | radians |
|
|
154
|
+
| 56 | f64 | `south` | radians |
|
|
155
|
+
| 64 | f64 | `east` | radians |
|
|
156
|
+
| 72 | f64 | `north` | radians |
|
|
157
|
+
| 80 | f64 | `minValue` | terrain: min height, metres. imagery: `0` |
|
|
158
|
+
| 88 | f64 | `maxValue` | terrain: max height, metres. imagery: `0` |
|
|
159
|
+
| 96 | u32 | `tileX` | source tile X, or `0xFFFFFFFF` |
|
|
160
|
+
| 100 | u32 | `tileY` | source tile Y, or `0xFFFFFFFF` |
|
|
161
|
+
| 104 | u32 | `hostCopies` | host→guest copies this acquire will cost per whole-plane read |
|
|
162
|
+
| 108 | u32 | `sourceId` | FNV-1a 32 of the provider id — stable, comparable across runtimes |
|
|
163
|
+
| 112 | u32 | `costClass` | what this acquire actually cost — see [Cost classes](#cost-classes) |
|
|
164
|
+
| 116 | u32 | `strategy` | how the level was chosen: `0` default, `1` grid-matched-level, `2` most-detailed, `3` fixed-level |
|
|
165
|
+
| 120 | u32[2]| `reserved` | zero |
|
|
166
|
+
|
|
167
|
+
`encoding`:
|
|
168
|
+
|
|
169
|
+
| value | name | element |
|
|
170
|
+
| ----- | ---- | ------- |
|
|
171
|
+
| 1 | `SDM_PROVIDER_ENC_HEIGHT_F32` | `float` metres above the ellipsoid |
|
|
172
|
+
| 2 | `SDM_PROVIDER_ENC_HEIGHT_F64` | `double` metres above the ellipsoid |
|
|
173
|
+
| 16 | `SDM_PROVIDER_ENC_RGBA8` | 4 × `uint8_t` |
|
|
174
|
+
| 17 | `SDM_PROVIDER_ENC_RGB8` | 3 × `uint8_t` |
|
|
175
|
+
| 18 | `SDM_PROVIDER_ENC_GRAY8` | 1 × `uint8_t` |
|
|
176
|
+
| 19 | `SDM_PROVIDER_ENC_GRAY16` | 1 × `uint16_t` |
|
|
177
|
+
| 20 | `SDM_PROVIDER_ENC_RGBA_F32` | 4 × `float`, 0..1 |
|
|
178
|
+
|
|
179
|
+
**Terrain heights are always delivered in metres**, as `f32` or `f64`. A
|
|
180
|
+
provider's on-the-wire encoding — a 16-bit heightmap with
|
|
181
|
+
`{heightScale, heightOffset, elementsPerHeight, stride, elementMultiplier,
|
|
182
|
+
isBigEndian}` structure, or a quantized-mesh's zig-zag-encoded `u16`
|
|
183
|
+
vertices — is the *provider's* business. The adapter dequantizes into metres
|
|
184
|
+
exactly once and pins the result. Guests never see a scale/offset field
|
|
185
|
+
because guests must never reimplement a provider's quantization; that is the
|
|
186
|
+
single most likely place for two runtimes to disagree by one ULP, and the ABI
|
|
187
|
+
removes the opportunity rather than documenting it.
|
|
188
|
+
|
|
189
|
+
`flags`:
|
|
190
|
+
|
|
191
|
+
| bit | name | meaning |
|
|
192
|
+
| --- | ---- | ------- |
|
|
193
|
+
| 0 | `SDM_PROVIDER_FLAG_INTERPOLATED` | values were interpolated from a coarser level than requested |
|
|
194
|
+
| 1 | `SDM_PROVIDER_FLAG_STAGED` | host had to stage a copy; `hostCopies` is `2` |
|
|
195
|
+
| 2 | `SDM_PROVIDER_FLAG_PARTIAL` | some elements have no data and hold the no-data sentinel |
|
|
196
|
+
| 3 | `SDM_PROVIDER_FLAG_DERIVED` | not a source tile (profile/region request) |
|
|
197
|
+
| 4 | `SDM_PROVIDER_FLAG_FIXTURE` | served by the deterministic fixture adapter |
|
|
198
|
+
|
|
199
|
+
Bit 4 exists so a parity test can *prove* it compared fixture bytes against
|
|
200
|
+
fixture bytes, rather than assuming it.
|
|
201
|
+
|
|
202
|
+
### The no-data sentinel
|
|
203
|
+
|
|
204
|
+
A terrain sample with no data is **not** absent, **not** `0`, and **not** NaN.
|
|
205
|
+
It is an exact bit pattern:
|
|
206
|
+
|
|
207
|
+
| encoding | sentinel | bits |
|
|
208
|
+
| -------- | -------- | ---- |
|
|
209
|
+
| `HEIGHT_F32` | `-FLT_MAX` | `0xFF7FFFFF` |
|
|
210
|
+
| `HEIGHT_F64` | `-DBL_MAX` | `0xFFEFFFFFFFFFFFFF` |
|
|
211
|
+
|
|
212
|
+
NaN is specifically rejected. WebAssembly does not canonicalize NaN payloads
|
|
213
|
+
across all producing operations, so two runtimes can hold *different bits* for
|
|
214
|
+
"a NaN" and a byte-identical-output assertion would fail on a value that is
|
|
215
|
+
semantically equal. A sentinel with one exact encoding cannot do that.
|
|
216
|
+
|
|
217
|
+
This also closes a real defect class in the existing engine seam, where "no
|
|
218
|
+
data" is expressed as `Cartographic.height === undefined` — a JS-object-only
|
|
219
|
+
state that a typed array cannot represent at all, and which therefore silently
|
|
220
|
+
becomes `0` (sea level) the moment anyone packs those samples into a buffer.
|
|
221
|
+
Sea level under a ridge is exactly the failure the RF terrain solver was filed
|
|
222
|
+
for. The sentinel is defined here, in the ABI, so no consumer has to invent it.
|
|
223
|
+
|
|
224
|
+
### Cost classes
|
|
225
|
+
|
|
226
|
+
`costClass` states what an acquire actually cost. The acquire request carries
|
|
227
|
+
`"maxCost"`, and an adapter that cannot serve within it returns
|
|
228
|
+
`SDM_PROVIDER_E_UNSUPPORTED`.
|
|
229
|
+
|
|
230
|
+
| value | name | meaning |
|
|
231
|
+
| ----- | ---- | ------- |
|
|
232
|
+
| 0 | `SDM_PROVIDER_COST_RESIDENT` | bytes already decoded and resident; pure memcpy |
|
|
233
|
+
| 1 | `SDM_PROVIDER_COST_DEQUANTIZE` | resident, but dequantized to metres by the adapter |
|
|
234
|
+
| 2 | `SDM_PROVIDER_COST_REDECODE` | adapter re-decoded already-fetched source data |
|
|
235
|
+
| 3 | `SDM_PROVIDER_COST_REFETCH` | adapter went to the network |
|
|
236
|
+
| 4 | `SDM_PROVIDER_COST_READBACK` | adapter stalled the GPU to read a texture back |
|
|
237
|
+
|
|
238
|
+
**`maxCost` defaults to `1`.** That default *is* the owner's "never re-fetch or
|
|
239
|
+
re-parse" rule, enforced by the ABI rather than by discipline. A caller who
|
|
240
|
+
genuinely wants the expensive path must raise the ceiling and thereby say so in
|
|
241
|
+
its own source.
|
|
242
|
+
|
|
243
|
+
Class 1 exists because it is unavoidable and honest: a quantized-mesh terrain
|
|
244
|
+
tile holds `uint16` vertices and **no metre-space array exists anywhere** in
|
|
245
|
+
the provider. The adapter dequantizes with the provider's own formula, once,
|
|
246
|
+
into the pinned buffer. That is not a re-decode — nothing is re-parsed and
|
|
247
|
+
nothing is re-fetched — but it is not free either, so it gets its own class
|
|
248
|
+
instead of being hidden inside class 0.
|
|
249
|
+
|
|
250
|
+
## Acquire requests
|
|
251
|
+
|
|
252
|
+
The JSON request selects one of three shapes. All angles are **radians**.
|
|
253
|
+
|
|
254
|
+
**`tile`** — one source tile, exactly as the provider decoded it. Zero
|
|
255
|
+
resampling, zero interpolation.
|
|
256
|
+
|
|
257
|
+
```json
|
|
258
|
+
{"op":"tile","providerId":"terrain.fixture","level":9,"x":123,"y":45}
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
**`profile`** — heights along a path. The seam the RF terrain solver consumes:
|
|
262
|
+
one call, one copy, `width = samples`, `height = 1`,
|
|
263
|
+
`encoding = SDM_PROVIDER_ENC_HEIGHT_F64`.
|
|
264
|
+
|
|
265
|
+
```json
|
|
266
|
+
{"op":"profile","providerId":"terrain.fixture",
|
|
267
|
+
"start":[-1.9,0.65],"end":[-1.88,0.66],"samples":256,"level":"mostDetailed"}
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
or with explicit positions:
|
|
271
|
+
|
|
272
|
+
```json
|
|
273
|
+
{"op":"profile","providerId":"terrain.fixture",
|
|
274
|
+
"positions":[[-1.9,0.65],[-1.899,0.6501]]}
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
**Raster in.** A coverage field is commonly 512x512 = **262,144 positions**.
|
|
278
|
+
Encoding those as JSON would be a multi-megabyte request string, parsed on
|
|
279
|
+
every call — a worse cost than the read it is asking for, and it would make the
|
|
280
|
+
control plane carry bulk data, which this ABI exists to prevent. So positions
|
|
281
|
+
may instead be a pointer:
|
|
282
|
+
|
|
283
|
+
```json
|
|
284
|
+
{"op":"profile","providerId":"terrain.fixture",
|
|
285
|
+
"positionsPtr":1048576,"positionsCount":262144,"spacing":30}
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
The host reads interleaved f64 lon/lat pairs straight out of guest memory with
|
|
289
|
+
ONE copy, symmetric with the way results come back. The request string stays
|
|
290
|
+
under 200 bytes no matter how large the field is. A pointer or count outside
|
|
291
|
+
linear memory is `SDM_PROVIDER_E_BOUNDS` — a value, never a trap and never a
|
|
292
|
+
silent short read.
|
|
293
|
+
|
|
294
|
+
**`spacing` beats `level`.** Any shape may carry `"spacing": <metres>` — the
|
|
295
|
+
sample stride the caller intends to march at — and the port picks the coarsest
|
|
296
|
+
level whose sample spacing is at or below it. This is the preferred form:
|
|
297
|
+
a consumer knows its own stride, it does not know a provider's level scheme.
|
|
298
|
+
Asking for `"mostDetailed"` everywhere is what makes a solve slow; asking for
|
|
299
|
+
coarser than the march stride is what makes it wrong. Level selection lives in
|
|
300
|
+
one shared helper (`providerLevelForSpacing`) used by every adapter, so the
|
|
301
|
+
browser and a host tile store resolve the same spacing to the same level — two
|
|
302
|
+
adapters doing their own arithmetic would sample different ground and lose byte
|
|
303
|
+
parity for a reason no diff would show.
|
|
304
|
+
|
|
305
|
+
The level actually used comes back in `descriptor.level`, always. A consumer
|
|
306
|
+
that cannot see which level answered cannot tell a solve that resolved the
|
|
307
|
+
ridges from one that interpolated them away.
|
|
308
|
+
|
|
309
|
+
`level` is an integer, `"mostDetailed"`, or omitted (adapter's default). The
|
|
310
|
+
sampled positions are great-circle-interpolated between `start` and `end` by
|
|
311
|
+
the *host adapter*, using the adapter's native sampler, so that two runtimes
|
|
312
|
+
sampling the same source agree byte-for-byte. A guest that interpolates its
|
|
313
|
+
own positions and passes them explicitly gets exactly what it asked for.
|
|
314
|
+
|
|
315
|
+
**`region`** — a rectangle resampled onto a `width × height` grid. What a
|
|
316
|
+
viewshed, a coverage raster, or a cloud-mask lane wants.
|
|
317
|
+
|
|
318
|
+
```json
|
|
319
|
+
{"op":"region","providerId":"imagery.fixture",
|
|
320
|
+
"rectangle":[-1.91,0.64,-1.87,0.67],"width":256,"height":256,"level":9}
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
`region` on a terrain provider yields heights; on an imagery provider it
|
|
324
|
+
yields pixels. The port does not care which — that is the whole point.
|
|
325
|
+
|
|
326
|
+
Every shape accepts `"maxCost": N` (default `1`) and `"plane": "height" |
|
|
327
|
+
"pixels" | N`.
|
|
328
|
+
|
|
329
|
+
### Imagery is not terrain, and the ABI says so
|
|
330
|
+
|
|
331
|
+
Terrain can honour `maxCost: 0..1` because a decoded terrain tile stays
|
|
332
|
+
resident in the provider for as long as the tile is loaded. **Imagery cannot**,
|
|
333
|
+
on the current engine tree: the CPU-side pixel buffer is released immediately
|
|
334
|
+
after the texture is uploaded to the GPU, so for any tile the renderer has
|
|
335
|
+
finished with, the decoded pixels are simply gone.
|
|
336
|
+
|
|
337
|
+
The consequence is stated here rather than discovered later:
|
|
338
|
+
|
|
339
|
+
- imagery `acquire` at the default `maxCost: 1` succeeds **only** for tiles
|
|
340
|
+
caught in the window between decode and upload, and returns
|
|
341
|
+
`SDM_PROVIDER_E_UNSUPPORTED` otherwise;
|
|
342
|
+
- imagery at `maxCost: 4` may be served by GPU readback — but those are the
|
|
343
|
+
*reprojected* pixels the renderer holds, not the source pixels, and the
|
|
344
|
+
descriptor says so via `FLAG_DERIVED`;
|
|
345
|
+
- imagery at `maxCost: 3` re-fetches and re-decodes from the provider.
|
|
346
|
+
|
|
347
|
+
An adapter that wants to offer resident imagery must tap the pixels *before*
|
|
348
|
+
upload; that is an engine-side change and belongs to the engine owner, not to
|
|
349
|
+
this ABI. Until it exists, **`SDM_PROVIDER_E_UNSUPPORTED` is the correct and
|
|
350
|
+
final answer for resident imagery pixels**, and it is the same answer in all
|
|
351
|
+
three runtimes. The ABI does not promise imagery parity with terrain, because
|
|
352
|
+
the engine cannot currently deliver it, and a port that promised it would be
|
|
353
|
+
lying in exactly the way this SDK exists to prevent.
|
|
354
|
+
|
|
355
|
+
## Control operations
|
|
356
|
+
|
|
357
|
+
Ride the existing `space_data_module_host.call` bridge. No new imports.
|
|
358
|
+
|
|
359
|
+
| operation | request | response |
|
|
360
|
+
| --------- | ------- | -------- |
|
|
361
|
+
| `provider.list` | `{"kind":"terrain"\|"imagery"\|null}` | `{"providers":[{id,kind,name,ready,minLevel,maxLevel,tileWidth,tileHeight,encoding,credit}]}` |
|
|
362
|
+
| `provider.describe` | `{"id":"..."}` | the same record, plus adapter-specific `attributes` |
|
|
363
|
+
| `provider.select` | `{"kind":"terrain","id":"..."}` | `{"selected":"..."}` |
|
|
364
|
+
| `provider.configure` | `{"id":"...","settings":{...}}` | `{"applied":["alpha","show"],"rejected":[]}` |
|
|
365
|
+
| `provider.availability` | `{"id":"...","level":9,"x":1,"y":2}` | `{"available":true}` |
|
|
366
|
+
| `provider.prefetch` | `{"id":"...","rectangle":[w,s,e,n],"level":9}` | `{"requested":N,"pending":M,"supported":bool}` |
|
|
367
|
+
| `provider.await` | `{"id":"...","rectangle":[...],"level":9,"timeoutMs":30000}` | `{"ready":true,"pending":0}` |
|
|
368
|
+
| `provider.lastError` | `{}` | `{"code":-3,"name":"...","message":"...","operation":"..."}` |
|
|
369
|
+
| `provider.stats` | `{}` | `{"acquires":N,"reads":N,"bytesCopied":N,"hostCopies":N,"pinned":N}` |
|
|
370
|
+
|
|
371
|
+
`provider.list` returning `{"providers":[]}` is a **success**, not an error. A
|
|
372
|
+
headless node with nothing configured is a legitimate, enumerable state; making
|
|
373
|
+
it an error would mean the "nothing here" path differs from the "something
|
|
374
|
+
here" path in shape, and modules would grow runtime-shaped branches. They
|
|
375
|
+
return the same shape, so they don't.
|
|
376
|
+
|
|
377
|
+
`provider.configure` is intentionally a key/value bag with an explicit
|
|
378
|
+
`applied`/`rejected` split. Adapters apply what their underlying surface
|
|
379
|
+
natively supports and *report* the rest as rejected. An adapter must never
|
|
380
|
+
emulate a setting its provider does not have.
|
|
381
|
+
|
|
382
|
+
`provider.stats.hostCopies` is what a copy-count assertion reads. It is
|
|
383
|
+
maintained identically by every adapter.
|
|
384
|
+
|
|
385
|
+
`provider.prefetch` carries `"supported"` because **region prefetch is a
|
|
386
|
+
genuine engine LACK**, ruled by the engine owner: the browser engine loads
|
|
387
|
+
tiles from camera position only, and its one region-shaped API loads
|
|
388
|
+
*availability metadata*, not geometry. Rather than invent an engine API — which
|
|
389
|
+
the native-API law forbids — the port reports `"supported": false` with
|
|
390
|
+
`"requested": 0`. A host-side tile-store adapter, which has no camera and can
|
|
391
|
+
simply fetch, reports `"supported": true`. This is a real capability difference
|
|
392
|
+
and it is **declared in the response, not hidden**: a module reads one boolean
|
|
393
|
+
and does not branch on runtime. Closing the gap is engine work, tracked
|
|
394
|
+
separately as `orbpro-provider-access-port`.
|
|
395
|
+
|
|
396
|
+
## Error codes
|
|
397
|
+
|
|
398
|
+
Negative `i32`, returned from `acquire`, `read` and `release`. Identical value,
|
|
399
|
+
identical meaning, identical trap class (none — these are values) in every
|
|
400
|
+
runtime.
|
|
401
|
+
|
|
402
|
+
| code | name | when |
|
|
403
|
+
| ---- | ---- | ---- |
|
|
404
|
+
| `-1` | `SDM_PROVIDER_E_INVALID_REQUEST` | malformed JSON, unknown `op`, out-of-range field |
|
|
405
|
+
| `-2` | `SDM_PROVIDER_E_NO_CAPABILITY` | `scene_access`/`provider.v1` not granted |
|
|
406
|
+
| `-3` | `SDM_PROVIDER_E_NO_PROVIDER` | no provider with that id or kind |
|
|
407
|
+
| `-4` | `SDM_PROVIDER_E_NOT_READY` | provider exists but is not ready; `provider.await` first |
|
|
408
|
+
| `-5` | `SDM_PROVIDER_E_NOT_AVAILABLE` | no data at that level/x/y |
|
|
409
|
+
| `-6` | `SDM_PROVIDER_E_BOUNDS` | pointer/length outside guest memory, or offset past the plane |
|
|
410
|
+
| `-7` | `SDM_PROVIDER_E_BAD_HANDLE` | unknown or already-released handle |
|
|
411
|
+
| `-8` | `SDM_PROVIDER_E_BAD_PLANE` | `plane >= planeCount` |
|
|
412
|
+
| `-9` | `SDM_PROVIDER_E_UNSUPPORTED` | adapter cannot serve this without re-fetch or re-decode |
|
|
413
|
+
| `-10` | `SDM_PROVIDER_E_TIMEOUT` | load did not complete in the adapter's budget |
|
|
414
|
+
| `-11` | `SDM_PROVIDER_E_HOST` | adapter threw; detail via `provider.lastError` |
|
|
415
|
+
| `-12` | `SDM_PROVIDER_E_PORT_UNAVAILABLE`| no provider port bound in this runtime at all |
|
|
416
|
+
|
|
417
|
+
`-9` is the honesty code. An adapter that *could* produce the answer by
|
|
418
|
+
re-downloading a tile or re-decoding a PNG must return `-9` rather than pay a
|
|
419
|
+
cost the caller did not ask for. A caller that wants the expensive path asks
|
|
420
|
+
for it explicitly through `provider.prefetch` + `provider.await`.
|
|
421
|
+
|
|
422
|
+
## Copy contract
|
|
423
|
+
|
|
424
|
+
The owner's budget is **one copy host→wasm, zero re-decode**. The port meets it
|
|
425
|
+
where the memory topology permits and reports honestly where it does not.
|
|
426
|
+
`descriptor.hostCopies` and `provider.stats.hostCopies` are the measurement,
|
|
427
|
+
readable from inside the guest, identical field in every runtime.
|
|
428
|
+
|
|
429
|
+
| lane | copies for a whole-plane `read` | why |
|
|
430
|
+
| ---- | ------------------------------- | --- |
|
|
431
|
+
| WasmEdge native | **1** | host writes the pinned decoded buffer into linear memory |
|
|
432
|
+
| WasmEdge in Docker | **1** | same host path |
|
|
433
|
+
| Browser, wasi-threads guest | **1** | guest memory is a `SharedArrayBuffer`; the controlling thread that owns the provider caches writes into it directly at `dstPtr` |
|
|
434
|
+
| Browser, sequential guest | **2** (`FLAG_STAGED`) | guest memory is not shared, so the controlling thread stages into the SAB and the worker performs the final copy |
|
|
435
|
+
|
|
436
|
+
Zero of these paths encode the bytes into a hostcall envelope. That matters:
|
|
437
|
+
the generic `space_data_module_host` envelope route costs **five** copies for a
|
|
438
|
+
tile in the browser worker topology (provider array → envelope encode → SAB
|
|
439
|
+
`data.set` → worker `new Uint8Array(length)` → bridge `lastResponseBytes` →
|
|
440
|
+
`read_response` into linear memory). A 262 KB heightmap through the generic
|
|
441
|
+
route is over a megabyte of memcpy per tile. That measured cost is the entire
|
|
442
|
+
justification for three dedicated imports instead of one more `provider.*`
|
|
443
|
+
operation on the existing bridge.
|
|
444
|
+
|
|
445
|
+
The sequential-browser `2` is the **already-sanctioned browser-sequential
|
|
446
|
+
throughput divergence** and nothing more. Bytes are identical, error codes are
|
|
447
|
+
identical, `FLAG_STAGED` and `hostCopies` state the difference out loud, and a
|
|
448
|
+
module that wants to assert `hostCopies == 1` can — it will simply be asserting
|
|
449
|
+
that it is running threaded. It closes when `browser-worker-topology` lands.
|
|
450
|
+
|
|
451
|
+
## Runtime satisfaction
|
|
452
|
+
|
|
453
|
+
The imports are always present. What is bound behind them varies, and only in
|
|
454
|
+
the SDK host shims.
|
|
455
|
+
|
|
456
|
+
**Browser, engine present.** `createEngineProviderAdapter({ scene })` satisfies
|
|
457
|
+
the port from the engine's live provider objects, through engine-native APIs
|
|
458
|
+
only. It binds in two tiers, and which tier answered is visible to the guest in
|
|
459
|
+
`descriptor.costClass`:
|
|
460
|
+
|
|
461
|
+
- **Tier A — the engine's own `ProviderAccessPort`,** when the engine exposes
|
|
462
|
+
one. This is the `costClass 0/1` path: it walks the loaded tile cache and
|
|
463
|
+
hands over the decoded terrain data the engine already holds. Reaching into
|
|
464
|
+
a provider's private fields is *engine* work, not SDK work — those fields
|
|
465
|
+
rot on every upstream pin advance, and the engine's owner keeps them tested
|
|
466
|
+
inside the engine's own gates. The SDK calls the public port and nothing
|
|
467
|
+
else.
|
|
468
|
+
- **Tier B — public engine API only,** when no port is present. Terrain is
|
|
469
|
+
served through the engine's exported most-detailed terrain sampler, which
|
|
470
|
+
re-requests and re-decodes: `costClass 2`. Under the default `maxCost: 1`
|
|
471
|
+
this tier therefore **refuses** with `SDM_PROVIDER_E_UNSUPPORTED`, and a
|
|
472
|
+
caller gets bytes only by explicitly raising its ceiling.
|
|
473
|
+
|
|
474
|
+
Tier B works today against real provider objects with no engine change, which
|
|
475
|
+
is what makes the port usable before the engine port lands; Tier A is what
|
|
476
|
+
makes it cheap afterwards. Neither tier ever silently upgrades its cost.
|
|
477
|
+
|
|
478
|
+
**WasmEdge, native or Docker.** There is no engine. The ruling, explicitly:
|
|
479
|
+
|
|
480
|
+
> The server-side satisfaction is a **host-side tile-store adapter**, not a
|
|
481
|
+
> refusal — and not an engine port. `createTileStoreProviderAdapter(...)`
|
|
482
|
+
> serves the identical operations from tile sources reachable by the host,
|
|
483
|
+
> using **only capabilities that already exist**: `filesystem` for a local
|
|
484
|
+
> tileset directory, `http` for a remote tile service. It introduces no new
|
|
485
|
+
> generic hook, so it needs no owner sign-off and no new connector. Where the
|
|
486
|
+
> SDN node wants to expose its own curated terrain/imagery sources through
|
|
487
|
+
> this port, that is a node-side *configuration* of this adapter — a `sdn`
|
|
488
|
+
> escalation for wiring, never a change to this ABI.
|
|
489
|
+
|
|
490
|
+
**Any runtime with nothing bound.** The port answers and does not lie:
|
|
491
|
+
`provider.list` → `{"providers":[]}`; `acquire` → `SDM_PROVIDER_E_NO_PROVIDER`;
|
|
492
|
+
with the capability withheld entirely, `SDM_PROVIDER_E_NO_CAPABILITY`; with no
|
|
493
|
+
port installed by the host at all, `SDM_PROVIDER_E_PORT_UNAVAILABLE`. All three
|
|
494
|
+
are values, none is a trap, and **all three are reachable in the browser too** —
|
|
495
|
+
ask a browser adapter for a provider id that does not exist and you get `-3`,
|
|
496
|
+
the same `-3`. The failure path is therefore parity-testable in every lane
|
|
497
|
+
rather than being a WasmEdge-only special case, which is the difference between
|
|
498
|
+
a mirror and an excuse.
|
|
499
|
+
|
|
500
|
+
A module never branches on runtime. It branches on `-3`, and that branch is
|
|
501
|
+
exercised in all three lanes.
|
|
502
|
+
|
|
503
|
+
## Parity envelope
|
|
504
|
+
|
|
505
|
+
**Inside the envelope** — byte-identical across browser, native WasmEdge and
|
|
506
|
+
Docker WasmEdge, at any thread count:
|
|
507
|
+
|
|
508
|
+
- every descriptor field for a given request against a given source
|
|
509
|
+
- every byte of every plane read from the **fixture provider**
|
|
510
|
+
- every error code, for every malformed and unsatisfiable request
|
|
511
|
+
- `provider.list` / `describe` / `availability` shapes and ordering
|
|
512
|
+
- the digest returned by the `provider-digest` demo module
|
|
513
|
+
|
|
514
|
+
The fixture provider is a deterministic analytic source (a closed-form height
|
|
515
|
+
field and a closed-form pixel field, both functions of tile coordinates only).
|
|
516
|
+
It is bound in the browser as a real engine-shaped provider object and under
|
|
517
|
+
WasmEdge as a fixture tile store, so both lanes exercise the *adapter* code
|
|
518
|
+
paths rather than a shared shortcut. `FLAG_FIXTURE` proves which one answered.
|
|
519
|
+
|
|
520
|
+
**Outside the envelope** — and this is stated so it is never mistaken for a
|
|
521
|
+
defect: bytes from *live* network providers. Two runtimes reading a live
|
|
522
|
+
world-terrain service are not obliged to hold the same tiles at the same
|
|
523
|
+
instant, and asserting so would be false rigor. Their **ABI behaviour** —
|
|
524
|
+
codes, descriptor shapes, plane counts, bounds handling, copy accounting — is
|
|
525
|
+
fully inside the envelope.
|
|
526
|
+
|
|
527
|
+
**Divergence in anything listed as inside the envelope is a P1 SDK defect.**
|
|
528
|
+
Not a platform quirk. File, block, fix.
|
|
529
|
+
|
|
530
|
+
## Consumer seam — terrain source
|
|
531
|
+
|
|
532
|
+
The immediate consumer is the RF terrain solver, and the point of defining the
|
|
533
|
+
terrain half first is that it must not ship a private terrain path.
|
|
534
|
+
|
|
535
|
+
`src/host/terrainSourceSeam.js` defines the one JS interface both the engine
|
|
536
|
+
port and the SDK adapters implement, plus
|
|
537
|
+
`assertTerrainSourceConformance(source)` so a consumer can prove at wiring time
|
|
538
|
+
that whatever it was handed is the real seam:
|
|
539
|
+
|
|
540
|
+
```js
|
|
541
|
+
{
|
|
542
|
+
id, // stable provider id
|
|
543
|
+
costClass, // what readHeights will cost
|
|
544
|
+
readHeights(positions, out, opts) -> Promise<Float64Array>, // bulk metres
|
|
545
|
+
readProfile(positions, opts) -> Promise<{ // + provenance
|
|
546
|
+
heights, level, strategy, costClass, partial, interpolated
|
|
547
|
+
}>,
|
|
548
|
+
sampleCompat(provider, positions) -> Promise<Cartographic[]>, // legacy shape
|
|
549
|
+
}
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
`opts` carries `spacing` (metres — the caller's march stride, preferred),
|
|
553
|
+
`level`, `maxCost`, and `out`. One call carries a whole field: the solver's
|
|
554
|
+
coverage rasters reach 512×512 = 262,144 points, and a per-point call cannot
|
|
555
|
+
survive a boundary crossing at that count, which is why the seam is batched and
|
|
556
|
+
in-place rather than scalar.
|
|
557
|
+
|
|
558
|
+
`sampleCompat` is signature-compatible with the sampler the solver's injectable
|
|
559
|
+
statics already hold, so the seam can be assigned on day one with no call-site
|
|
560
|
+
change. `readHeights` is the real entry: contiguous metres, no per-sample JS
|
|
561
|
+
object, no-data expressed as the sentinel rather than `undefined`.
|
|
562
|
+
|
|
563
|
+
The cutover is therefore two recorded steps, not a rewrite:
|
|
564
|
+
|
|
565
|
+
1. **Now** — the solver keeps its injectable statics and receives a
|
|
566
|
+
conforming seam through them. No private terrain path is introduced.
|
|
567
|
+
2. **On engine-port landing** — the terrain source becomes an explicit
|
|
568
|
+
parameter on the analysis entry points (the pluggable-propagation law
|
|
569
|
+
applied to terrain) and call sites move from `sampleCompat` to
|
|
570
|
+
`readHeights`. Tracked as `orbpro-provider-access-port`.
|
|
571
|
+
|
|
572
|
+
## Guest usage
|
|
573
|
+
|
|
574
|
+
`templates/provider-access-module/include/space_data_provider_abi.h` declares
|
|
575
|
+
the imports and the descriptor for `clang --target=wasm32-wasip1-threads`. The
|
|
576
|
+
module is EH-free and compiles identically for every lane; there is exactly one
|
|
577
|
+
`dist/isomorphic/module.wasm`.
|
|
578
|
+
|
|
579
|
+
```c
|
|
580
|
+
#include "space_data_provider_abi.h"
|
|
581
|
+
|
|
582
|
+
sdm_provider_tile_desc_t desc;
|
|
583
|
+
const char *req =
|
|
584
|
+
"{\"op\":\"profile\",\"providerId\":\"terrain.fixture\","
|
|
585
|
+
"\"start\":[-1.9,0.65],\"end\":[-1.88,0.66],\"samples\":256,\"maxCost\":1}";
|
|
586
|
+
|
|
587
|
+
int32_t h = sdm_provider_acquire(req, (int32_t)strlen(req), &desc);
|
|
588
|
+
if (h < 0) {
|
|
589
|
+
/* -3 here on a node with no terrain configured. Same branch, every lane. */
|
|
590
|
+
return handle_no_terrain(h);
|
|
591
|
+
}
|
|
592
|
+
double heights[256];
|
|
593
|
+
int32_t n = sdm_provider_read(h, 0, 0, (int32_t)(uintptr_t)heights,
|
|
594
|
+
(int32_t)sizeof heights);
|
|
595
|
+
sdm_provider_release(h);
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
`desc.hostCopies` is `1` on WasmEdge and on a threaded browser guest, `2` on a
|
|
599
|
+
sequential browser guest, and the 2048 bytes in `heights` are identical in all
|
|
600
|
+
of them.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "space-data-module-sdk",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.7",
|
|
4
4
|
"description": "Module SDK for building, validating, signing, and deploying WebAssembly modules on the Space Data Network.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"types": "./src/index.d.ts",
|
|
@@ -82,6 +82,7 @@
|
|
|
82
82
|
"start:lab": "node ./lab/server.js",
|
|
83
83
|
"check:nav": "node scripts/check-sdn-stack-nav.mjs module-sdk docs/index.html docs/styles.css",
|
|
84
84
|
"check:compliance": "node ./bin/space-data-module.js check --repo-root .",
|
|
85
|
+
"check:generated-bindings": "node scripts/check-generated-bindings.mjs",
|
|
85
86
|
"generate:vectors": "node ./examples/single-file-bundle/generate-vectors.mjs",
|
|
86
87
|
"prepublishOnly": "npm test && npm run check:compliance"
|
|
87
88
|
},
|
|
@@ -91,7 +92,7 @@
|
|
|
91
92
|
"flatsql": "^0.4.2",
|
|
92
93
|
"hd-wallet-wasm": "2.0.28",
|
|
93
94
|
"sdn-emception": "1.0.0",
|
|
94
|
-
"spacedatastandards.org": "https://github.com/DigitalArsenal/spacedatastandards.org/archive/
|
|
95
|
+
"spacedatastandards.org": "https://github.com/DigitalArsenal/spacedatastandards.org/archive/06deda5079204a46bd97a7ce6ac2868e991c6b8f.tar.gz"
|
|
95
96
|
},
|
|
96
97
|
"devDependencies": {
|
|
97
98
|
"esbuild": "^0.28.0",
|
|
@@ -21,9 +21,25 @@ enum StateFlags : uint {
|
|
|
21
21
|
HAS_COVARIANCE = 32
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
// Synced to the ratified SDS VCM.propagatorFamily vocabulary (schema/VCM/main.fbs)
|
|
25
|
+
// so any provider self-identifying in PropagatorDescribeSourcesBatchResult uses
|
|
26
|
+
// the SAME family codes SDS already carries — HPOP/Cowell-class numerical
|
|
27
|
+
// integrators map to COWELL (no HPOP-specific code needed); new codes go
|
|
28
|
+
// through Themis's SDS auto-ratification law, never invented here first.
|
|
24
29
|
enum PropagatorSourceKind : ubyte {
|
|
25
|
-
|
|
26
|
-
|
|
30
|
+
NONE = 0,
|
|
31
|
+
SEMI_ANALYTICAL = 1,
|
|
32
|
+
VINTI = 2,
|
|
33
|
+
SGP4 = 3,
|
|
34
|
+
COWELL = 4,
|
|
35
|
+
RK4 = 5,
|
|
36
|
+
NYX = 6,
|
|
37
|
+
GMAT = 7,
|
|
38
|
+
SPICE = 8,
|
|
39
|
+
SGP = 9,
|
|
40
|
+
SDP4 = 10,
|
|
41
|
+
SGP8 = 11,
|
|
42
|
+
SDP8 = 12
|
|
27
43
|
}
|
|
28
44
|
|
|
29
45
|
struct StateVector {
|
|
@@ -41,7 +57,7 @@ table PropagatorDescribeSourcesBatchRequest {
|
|
|
41
57
|
|
|
42
58
|
table PropagatorSourceDescription {
|
|
43
59
|
sourceHandle:uint = 0;
|
|
44
|
-
sourceKind:PropagatorSourceKind =
|
|
60
|
+
sourceKind:PropagatorSourceKind = NONE;
|
|
45
61
|
objectName:string;
|
|
46
62
|
objectId:string;
|
|
47
63
|
noradCatId:uint = 0;
|