SliceCache ("S-cache") — a per-(node, view) LRU of decoded per-slice geometry.
Sits ABOVE the L0/L1/L2 chunk caches. Where those are keyed by chunk and skip
only decompression, the SliceCache is keyed by the view (a node path + the
query's slice/tolerance/displayDims signature) and caches the fully decoded
per-slice geometry a progressive loader assembled. That lets revisiting a
slice — e.g. scrubbing back to a timepoint in a 4D dataset — skip the whole
query + fetch + dequant + gather pipeline (~100 ms on a large timelapse) and
only re-run the cheap nD→3D projection downstream.
The payload is OPAQUE to this cache: the owning loader stores a cloned snapshot
of its decoded per-LOD data and casts it back on retrieval. The clone is the
loader's responsibility because its decoded arrays are views into a reused
accumulator buffer (see the spatial-index loaders) — a cross-view cache must
not alias buffers the next load overwrites. The cache only tracks bytes for
its byte-budget LRU eviction, so the loader passes the retained byte count.
Invalidation: content-hash change / dataset switch clears the whole cache
(wired next to L0 in cache-setup.ts). The view signature in the key covers
slice/tolerance/displayDims; nothing projection-affecting (truncate, colormap,
opacity) enters the KEY — see the stage outputs below for how the projection
is cached without widening it.
PRE- vs POST-projection payloads (a deliberate, inherent asymmetry):
Lines/GSplats cache PRE-projection decoded data — their projection is a
downstream worker step owned by the scene-loader's data processors, and it
depends on inputs outside the view key (gsplats: the live uTruncate
material uniform; lines: the membership tolerance derived from dimension
metadata + extend_to_all). Points caches POST-projection data because its
projection is folded into the loader itself and consumes only key fields +
node-static context (no live uniform) — so a hit safely skips the WASM
projection too.
STAGE OUTPUTS (#2944 B2). A Lines/GSplats entry may additionally carry ONE
derived stage output — the projected buffers the commit consumes — tagged by
a caller-built signature of every projection input that is NOT the slice
payload itself (data/scene-loader/process/projection-stage-cache.ts).
Keying on the payload OBJECT (see slice-cache-origin.ts) plus that
signature means a revisit with unchanged parameters skips the worker
projection, while any parameter change simply misses and the new projection
replaces the old one. The stage output shares the entry's byte count (so it
lives inside this cache's budget and is released with its slice), but it is
the CHEAPER thing to lose: it is admitted only into spare budget, never by
evicting a slice, and when a slice store needs room the stage outputs are
shed first (least recently used first; most recently used first inside a
playback scan, mirroring the slices' own scan eviction). A saturated cache
therefore degrades to exactly the pre-B2 behaviour rather than trading
decoded slices for projections.
SliceCache ("S-cache") — a per-(node, view) LRU of decoded per-slice geometry.
Sits ABOVE the L0/L1/L2 chunk caches. Where those are keyed by chunk and skip only decompression, the SliceCache is keyed by the view (a node path + the query's slice/tolerance/displayDims signature) and caches the fully decoded per-slice geometry a progressive loader assembled. That lets revisiting a slice — e.g. scrubbing back to a timepoint in a 4D dataset — skip the whole query + fetch + dequant + gather pipeline (~100 ms on a large timelapse) and only re-run the cheap nD→3D projection downstream.
The payload is OPAQUE to this cache: the owning loader stores a cloned snapshot of its decoded per-LOD data and casts it back on retrieval. The clone is the loader's responsibility because its decoded arrays are views into a reused accumulator buffer (see the spatial-index loaders) — a cross-view cache must not alias buffers the next load overwrites. The cache only tracks bytes for its byte-budget LRU eviction, so the loader passes the retained byte count.
Invalidation: content-hash change / dataset switch clears the whole cache (wired next to L0 in cache-setup.ts). The view signature in the key covers slice/tolerance/displayDims; nothing projection-affecting (truncate, colormap, opacity) enters the KEY — see the stage outputs below for how the projection is cached without widening it.
PRE- vs POST-projection payloads (a deliberate, inherent asymmetry): Lines/GSplats cache PRE-projection decoded data — their projection is a downstream worker step owned by the scene-loader's data processors, and it depends on inputs outside the view key (gsplats: the live
uTruncatematerial uniform; lines: the membership tolerance derived from dimension metadata +extend_to_all). Points caches POST-projection data because its projection is folded into the loader itself and consumes only key fields + node-static context (no live uniform) — so a hit safely skips the WASM projection too.STAGE OUTPUTS (#2944 B2). A Lines/GSplats entry may additionally carry ONE derived stage output — the projected buffers the commit consumes — tagged by a caller-built signature of every projection input that is NOT the slice payload itself (
data/scene-loader/process/projection-stage-cache.ts). Keying on the payload OBJECT (seeslice-cache-origin.ts) plus that signature means a revisit with unchanged parameters skips the worker projection, while any parameter change simply misses and the new projection replaces the old one. The stage output shares the entry's byte count (so it lives inside this cache's budget and is released with its slice), but it is the CHEAPER thing to lose: it is admitted only into spare budget, never by evicting a slice, and when a slice store needs room the stage outputs are shed first (least recently used first; most recently used first inside a playback scan, mirroring the slices' own scan eviction). A saturated cache therefore degrades to exactly the pre-B2 behaviour rather than trading decoded slices for projections.