The engine half of the provider-access ABI: native, read-only access to the
terrain and imagery bytes the engine has ALREADY decoded.
WHY THIS EXISTS (owner directive 2026-08-07, verbatim):
"Also, why is it so fucking slow when rendered? … did we do the C++ / WASM
access to terrain and imagery, or are we doing some crazy loop around /
sampling through the Cesium engine? The solve should be way faster"
It was a loop around. Every RF terrain solve read heights through
sampleTerrain, which re-requests EVERY tile through
`terrainProvider.requestTileGeometry` — network round trip, gunzip and
quantized-mesh parse per tile, serialized behind the RequestScheduler with a
100 ms retry delay — for tiles the globe was already rendering from its own
decoded quadtree cache a few pointers away.
This port reads that cache instead. No request, no decode, no re-parse.
THE PART THAT IS NOT OBVIOUS, and the reason this is a real engine surface
rather than a three-line helper: the decoded cache is NOT randomly
addressable. `QuantizedMeshTerrainData.interpolateHeight` — the call
`sampleTerrain` makes once it has paid for the tile — is a LINEAR SCAN over
every triangle in the tile until one contains the point
(`QuantizedMeshTerrainData.js:635-676` and the `_mesh` variant at 579-631).
A tile carries 5k-25k triangles. A 512x512 coverage field is 262,144 samples.
Calling it per sample is O(samples x triangles) — billions of barycentric
tests, far slower than the network path it was meant to replace.
So the port builds a bucketed triangle index over the tile's own quantized
u/v arrays ONCE (O(triangles), counting sort, no allocation per sample) and
caches it against the `TerrainData` instance. Reads are then O(1) per sample
with the SAME arithmetic the engine's own `interpolateHeight` uses, so the
answers are bit-identical to the slow path rather than merely close.
ZERO COPY WHERE THE DATA ALREADY IS. `_uValues`, `_vValues` and
`_heightValues` are zero-copy `Uint16Array` subarrays of the tile's
`_quantizedVertices` (`QuantizedMeshTerrainData.js:127-139`). The index holds
references to them; it never copies height data. The only allocation is the
index itself (two typed arrays sized by triangle count) and the caller's
output buffer.
PRIVATE FIELDS LIVE HERE, NOT IN THE SDK. `_surface`, `_levelZeroTiles`,
`_southwestChild`, `_quantizedVertices`, `_structure`, `_imageryCache` move on
every upstream pin advance. The engine owns them and keeps them under its own
specs; the module SDK owns the wasm ABI and calls only
ProviderAccessPort#adapters.
| Name | Type | Description | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
scene |
Scene | The scene whose providers are exposed. | ||||||||
options |
object |
optional
|
- https://github.com/DigitalArsenal/space-data-module-sdk `src/host/providerAccess.js`
See:
Classes
Members
What an acquire actually cost. Reported per result, never assumed.
Plane encodings.
ABI error codes. An adapter throws
ProviderAccessPort.PortError with
one of these; anything else becomes SDM_PROVIDER_E_HOST at the SDK boundary.
Descriptor flags.
No-data sentinel for f64 planes.
NaN is deliberately not used: WebAssembly does not canonicalize NaN payloads
across every producing operation, so two runtimes can hold different bits for
"a NaN" and a byte-identical-output assertion would fail on values that are
semantically equal. -DBL_MAX has exactly one encoding and is never a real
terrain height.
The row order of every imagery record the pre-upload tap stores: row 0 is
the northern edge of the tile, column 0 the western edge. Carried on each
record as
rowOrder so a consumer can assert it instead of
assuming it.
How the level was chosen. Travels in the descriptor because the consumers
that most need the provenance are wasm modules.
readonly imageryCaptured : Event
Raised once per imagery tile that passes through the armed pre-upload tap,
as
(record, layer), synchronously from inside
ImageryLayer#_createTexture AFTER the record has been stored and
FIFO eviction has run — so by the time a listener sees a record,
ProviderAccessPort#imageryTiles already returns it.
The record is the same object the port holds (see
ProviderAccessPort#imageryTiles for its shape); consumers must not
mutate it. Rows are north-first: row 0 is the northern edge of the tile,
column 0 the western edge.
Listeners run inside the render loop, on the frame that uploads the tile.
They must be cheap: enqueue the record and return. Decoding, inference or
any allocation beyond a queue push belongs outside the listener.
Never raised while the tap is disarmed, and never raised for compressed
(KTX2) sources, which are not decoded on the CPU and are counted in
stats.imageryCompressedSkipped instead.
True when a consumer has armed the pre-upload imagery pixel tap. Off by
default: the tap costs a GPU-free canvas readback per tile at upload time,
and nothing in the render loop should pay for a feature nobody asked for.
Bound on the pre-upload imagery pixel cache, in tiles PER LAYER. Records
beyond the bound are evicted oldest-first. Lowering the bound evicts
immediately, for every layer, so the cache never holds more than the
current value.
Must be an integer of at least 1. Default 64; a consumer that wants to
replay a wider set of tiles through
ProviderAccessPort#imageryTiles
raises it, paying width * height * 4 bytes per held tile.
readonly scene : Scene
The scene this port reads.
Counters for the resident read path and the imagery tap. Cumulative for
the port's lifetime; a consumer that wants a per-solve delta snapshots
them either side.
imageryCompressedSkipped counts tiles the
armed tap declined because they were compressed (KTX2) sources with no
CPU-decoded pixels.
readonly terrainProvider : TerrainProvider|undefined
The terrain provider currently backing the globe, or undefined.
Methods
Coarsest level whose sample spacing satisfies a target spacing in metres.
Replicated bit-for-bit from the SDK's `providerLevelForSpacing` on purpose.
If the browser port and the host tile store picked levels by their own
arithmetic they would sample different ground, and byte parity would be lost
for a reason nobody could see in a diff.
| Name | Type | Description | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
spacingMeters |
number | Target sample spacing. | ||||||||||||||||
options |
object |
optional
|
Returns:
The chosen level.
The adapter set handed to the module SDK.
`createEngineProviderAdapters({ scene })` uses this VERBATIM when
`scene.providerAccessPort` exists, so the list is complete on its own:
resident terrain first (it becomes the default selection for `kind:
"terrain"`), the sampler tier second, then one adapter per imagery layer.
Returns:
Adapter objects.
Arm or disarm the pre-upload imagery pixel tap.
ImageryLayer#_createTexture nulls `imagery.image` the instant the
texture is uploaded, so a tile the renderer has finished with has no CPU
pixels left anywhere. The only place zero-re-fetch imagery exists is the
moment before that upload, so that is where the tap sits.
Off by default and deliberately explicit: armed, every imagery tile pays a
canvas readback during load. Nothing in the render loop should pay for a
capability no consumer asked for.
| Name | Type | Description |
|---|---|---|
armed |
boolean |
Returns:
The new state.
Resolve once the globe reports its load queue empty, or the deadline passes.
Bounded on purpose. An unbounded wait on a camera-driven loader is a hang
whenever the region is not reachable at the requested detail, and a solve that
hangs is worse than a solve that reports PARTIAL.
| Name | Type | Description | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
options |
object |
optional
|
Returns:
`{ready, pending, waitedMs}`.
Release everything the port holds. Pins are released explicitly because a
leaked pin keeps terrain resident forever.
The records currently held by the pre-upload imagery tap.
Each record is the object stored by the tap and handed to
ProviderAccessPort#imageryCaptured; consumers must not mutate it:
{
pixels: Uint8Array, // RGBA8, width * height * 4 bytes, north-first rows
width: number,
height: number,
level: number,
x: number,
y: number,
key: string, // "level/x/y"
rectangle: Rectangle, // the imagery tile's full rectangle, radians
layer: ImageryLayer,
encoding: ProviderAccessPort.Encoding.RGBA8,
rowOrder: "north-first"
}
Returns a NEW array on every call, oldest record first, so a caller can
replay tiles captured before it subscribed without holding a reference into
the port's cache. Empty while the tap is disarmed.
| Name | Type | Description |
|---|---|---|
layer |
ImageryLayer | optional Restrict to one layer. When omitted, the records of every layer are returned. |
Returns:
The held records.
Ask the engine to load terrain over a rectangle at a target detail.
The engine loads camera-driven; `loadTileDataAvailability` loads metadata,
not geometry. The ONE native surface that makes the quadtree keep and refine
tiles for a region regardless of where the camera is pointing is OrbPro's own
terrain pinning (
Globe#pinBoundingSphere), which both exempts the
region from culling and lowers its screen-space error. This method is that
surface, addressed by rectangle instead of by bounding sphere, and it is why
the ABI can now answer `supported: true` instead of reporting a LACK.
It is a REQUEST, not a guarantee: loading still happens on render frames.
ProviderAccessPort#awaitRegion is the completion half.
| Name | Type | Description | |||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
request |
object |
|
Returns:
`{supported, requested, pending, id, level}`.
Read terrain heights straight out of the loaded quadtree.
This is the engine-facing entry point; engine consumers (RfTerrainAnalysis)
call it directly and get the provenance object, while wasm guests reach the
same code through
ProviderAccessPort#adapters and the SDK's ABI.
Positions are read as `[longitudeRadians, latitudeRadians]` pairs or as
anything exposing `longitude`/`latitude` in radians — a `Cartographic` array
works unchanged, which is what makes the solver cutover a body swap instead
of a rewrite.
Samples with no resident tile are written as ProviderAccessPort.NO_DATA
and counted in `missing`. The port NEVER quietly falls back to a request: a
caller that wants the sampler asks for it, at its own cost class.
| Name | Type | Description | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
positions |
Array | Positions in radians. | ||||||||||||
options |
object |
optional
|
Returns:
`{heights, missing, resident, minLevel, maxLevel,
minValue, maxValue, level, strategy, strategyCode, costClass}`.
Heights for a whole field at ONE level, using the indexed gather for every
tile — resident or not.
WHY THIS EXISTS, measured rather than assumed. On the live Mount Rainier
scene the coverage solve asks for a GRID-MATCHED level (7 for a 1.1 km
raster). The renderer has no reason to hold level 7: near the camera it
refines well past it, far away it stops short. Measured residency for that
field was 0 of 36,864 samples — a strictly-correct resident read is a
guaranteed miss, and a lenient one silently answers from whatever the camera
loaded, which erased the terrain shadowing entirely.
So residency is the wrong axis. The expensive part of `sampleTerrain` is not
only the fetch: once it HAS the tile it calls `interpolateHeight` per point,
and that is the O(triangles) linear scan. 36,865 points cost 55 ms of a
138 ms solve with the tiles already in the browser cache.
This method keeps the engine's tile acquisition and replaces only the gather:
group the field by tile, take a resident tile when the quadtree has one at
exactly this level, request the rest ONCE each, then answer every point
through the bucket index. Same tiles, same arithmetic, bit-identical output —
and the per-point cost stops scaling with triangle count.
| Name | Type | Description | ||||||
|---|---|---|---|---|---|---|---|---|
positions |
Array | `[lonRadians, latRadians]` pairs or Cartographics. | ||||||
level |
number | The level to read. Required: this method exists to serve a level the camera did not choose. | ||||||
options |
object |
optional
|
Returns:
`{heights, missing, resident, fetched, tiles,
level, strategy, costClass}`.
Release a pin taken by
ProviderAccessPort#prefetchRegion.
| Name | Type | Description |
|---|---|---|
id |
string |
Returns:
True when a pin was released.