Build the view signature used as the SliceCache key (namespaced per node by
the cache itself). It is a CANONICAL projection of the query determinants,
so two viewStates that decode the SAME elements produce the SAME key
regardless of which builder created them.
Why canonical (not a raw JSON of the four fields): two builders feed the
loaders — the navigation/playback builder (dims-to-view-state.ts) and the
init/reprocess builder (view-state-manager.ts) — and they differ in ways
that DON'T change which elements load: the discrete-dim ride-along tolerance
(0.5 vs 0), a displayed dim's step (1 vs null), and JSON property
order. A raw serialization keys the same timepoint under two strings, so a
scrub/playback revisit misses forever (observed: N entries, 0 evictions,
~0 hits). The projection strips exactly that query-irrelevant noise:
Displayed dims: the in-plane query uses a fixed huge tolerance, so neither
position, tolerance, nor metadata changes which elements load — contribute
only a marker so a change to the DISPLAYED SET still rekeys.
Non-displayed discrete non-spatial dims: the executed query derives its
reach from step (0.25×step via tolerance-computer.ts), NOT from the
ride-along tolerance value — key on step, drop the ride-along.
Non-displayed continuous/spatial dims: key on position + tolerance +
spatial. Keying on the tolerance here is CONSERVATIVE rather than
demonstrated: since #1183 no query path derives its continuous reach from
the ride-along (gsplats/lines recompute via tolerance-computer.ts;
points-with-config and fallbackQueryTolerance both use the node's
maxRadius), so it is kept only because it is not PROVEN irrelevant for
every geometry/loader — not because it selects the decoded set.
This is never NARROWER than the decoded-set determinant (it drops only fields
that provably cannot change the loaded elements), so it upholds the loaders'
viewStatesEqual stale-skip contract — it cannot restore a snapshot the
loader would have reloaded. Nothing projection- or render-dependent enters
the key: projection re-runs on every hit, and a dataset content-hash change
clears the whole cache. dimensions may be undefined (each dim then keys on
position/tolerance alone — deterministic).
name is deliberately absent even though viewStatesEqual compares it:
the one query consumer of dimension names — extend_to_all tolerance
matching — is applied UPSTREAM by deriveNodeViewState, so a name-driven
reach change materializes in the per-node tolerance this key already
covers. (The equality's name comparison is belt-and-braces on top.)
Build the view signature used as the SliceCache key (namespaced per node by the cache itself). It is a CANONICAL projection of the query determinants, so two viewStates that decode the SAME elements produce the SAME key regardless of which builder created them.
Why canonical (not a raw JSON of the four fields): two builders feed the loaders — the navigation/playback builder (
dims-to-view-state.ts) and the init/reprocess builder (view-state-manager.ts) — and they differ in ways that DON'T change which elements load: the discrete-dim ride-along tolerance (0.5vs0), a displayed dim'sstep(1vsnull), and JSON property order. A raw serialization keys the same timepoint under two strings, so a scrub/playback revisit misses forever (observed: N entries, 0 evictions, ~0 hits). The projection strips exactly that query-irrelevant noise:step(0.25×step viatolerance-computer.ts), NOT from the ride-alongtolerancevalue — key onstep, drop the ride-along.tolerance-computer.ts; points-with-config andfallbackQueryToleranceboth use the node'smaxRadius), so it is kept only because it is not PROVEN irrelevant for every geometry/loader — not because it selects the decoded set.This is never NARROWER than the decoded-set determinant (it drops only fields that provably cannot change the loaded elements), so it upholds the loaders'
viewStatesEqualstale-skip contract — it cannot restore a snapshot the loader would have reloaded. Nothing projection- or render-dependent enters the key: projection re-runs on every hit, and a dataset content-hash change clears the whole cache.dimensionsmay be undefined (each dim then keys on position/tolerance alone — deterministic).nameis deliberately absent even thoughviewStatesEqualcompares it: the one query consumer of dimension names —extend_to_alltolerance matching — is applied UPSTREAM byderiveNodeViewState, so a name-driven reach change materializes in the per-nodetolerancethis key already covers. (The equality's name comparison is belt-and-braces on top.)