PrivateentriesPrivatepartitionPrivatepartitionPrivatecachesPer-entry register-time cache (parallel to entries by path).
Populated by :meth:register. Stored on the side so the public
LODGroupEntry interface stays test-friendly (callers don't
have to compute thresholds or allocate scratch boxes).
Private ReadonlymatrixReused per frame to feed transformBoundingBox's matrix arg.
PrivatetickMonotonic per-frame counter. Each frame the active (visible) child of every entry is stamped with the current tick; the resident-byte eviction LRU evicts the lowest-tick (coldest) loaded levels first.
PrivatewarnedEntry paths already warned about a missing-ready-child invariant break in
coarsestReadyIndex. The off-screen gate calls that method every frame,
so without a dedupe a genuinely-stuck group (eager default failed to
attach) would log at frame rate. One warning per entry surfaces the break
without the flood.
PrivatewarnedEntry paths already warned about the fresh-but-empty display guard
firing (see evaluateEntry). The guard is evaluated every frame, so
without a dedupe a persistently inconsistent dataset would warn at
frame rate.
PrivatesettleTracks when the global view-update version last changed (in ticks) so the
selector can defer a stale fine level's reload until the scrub settles —
the debounce behind maybeKickReload. See scene/lod-freshness.ts.
PrivatefadeWhether fade management (cross-fade and/or energy compensation) was ON
during the previous evaluatePerFrame. Falling-edge detector for the
one-shot residual-opacity restore in evaluateEntry: toggling BOTH
anti-popping flags off MID-fade would otherwise strand a half-faded
level's opacity forever (manageFade === false skips the per-frame
restore branch). Updated once per frame after all entries are evaluated;
one restore pass on the edge keeps the both-flags-off steady state
byte-identical (no material writes, no subtree traversal).
Private ReadonlypartitionPartition rising edges waiting for their wrapper to be visible and the
loader to be idle: wrapper path → the re-entering PART paths. A set that
contains the wrapper path itself means "resync the whole partition" (a
pathless part). Coalesced across frames; flushed as ONE
requestReprocess(paths) call.
PrivatedepsRegister a newly-loaded lod_group (called by the scene loader).
Register a partition whose children are independently frustum-gated.
Mark the owning partition part's rendered footprint stale after a geometry commit.
SceneLoader.updatePointsGeometry and the three commit*Geometry methods
are the complete geometry-attach funnels, including lazy LOD children and
additive rungs. They dirty the part before writing, so a commit that hands off
geometry and then throws cannot leave the previous footprint cached.
registerPartition starts every part dirty, which also covers a commit that
races registration. A path under a partition that matches no registered child
dirties the whole partition conservatively rather than allowing an
under-covering stale box. Footprints are cached in world space; the per-frame
gate separately detects transform changes.
Drop an lod_group from the registry (called on scene teardown).
Clear all entries (called on full scene tear-down).
PrivaterestoreNumber of registered lod_groups (mainly for tests / diagnostics).
Number of registered groups that can require an offline-capture drain.
Lookup an entry by path (mainly for tests / UI).
All registered entries (mainly for the layers panel UI).
Authored paths of lazy levels currently latched on an archive fault.
Failure reason for a latched lazy level, if that path is still failed.
Whether every lod_group and partition part that contributes pixels to the CURRENT view is already at final committed quality — i.e. one more frame of waiting would not improve what is on screen.
Why this exists. An offline turntable capture (OfflineCaptureStrategy)
takes exactly one requestAnimationFrame per exported frame. Since the
rAF loop runs for the whole sweep, the auto-selector is live and
frustum-aware, so a tile that leaves the frustum mid-orbit is demoted to
its coarsest ready level and the resident-byte budget may release its fine
one. When it swings back into view the fine level reloads ASYNCHRONOUSLY —
and without a wait those frames go into the ZIP/MP4 at the coarse level and
pop back a few frames later. The capture loop therefore drains on this
predicate (bounded) before grabbing each frame. Forcing finest instead was
deliberately rejected: a capture visits the whole scene, so peak residency
would be the entire dataset.
Partition parts block while a visible rising edge is pending, while any load pass is queued or committing and any part is visible, or while a visible stamped leaf is stale / still climbing its additive ladder. Hidden or re-culled parts are excluded because they contribute no pixels. Unstamped leaves carry no freshness or ladder signal and remain non-blocking, matching the lod-group subtree fold below.
loading in its
finally block and may commit geometry, so releasing the capture frame
before that transition would allow a one-frame pop.Per entry, in order:
offScreen means the
selector is deliberately holding the group coarse because it draws
nothing this frame — blocking on it would wait for a level that will
never be selected while it is culled.visible=false, anywhere up the
ancestor chain). They draw nothing, and — decisively —
kickDeferredLoadIfVisible refuses to START a deferred load while
the group is hidden, whereas the selector's frustum test is purely
geometric and still records a fine desiredChildIndex for it. Without
this skip such an entry has desired !== active with nothing ever
loading, failing or becoming ready, so the predicate would be
PERMANENTLY false and every capture frame would burn the full drain
budget before giving up.children can legitimately be EMPTY (every level failed its
getObjectByName attach in load-lod-group-node, which warns and
carries on). Nothing at a non-existent index can ever become ready, so
blocking on it is the permanently-false trap again: the drain would burn
its whole budget on every frame and then report a degraded-LOD verdict
the scene never earned. An out-of-range desiredChildIndex is the same
shape but is NOT skipped — the rest of the entry is still checked and
only the missing desired is let through (see its bullet below).displayed !== activeChildIndex ⇒ not quiescent. A stale slice
fallback or a never-downgrade hold is on screen instead of the
aspiration, so what renders is not what the selector settled on.desired !== activeChildIndex ⇒ not quiescent — UNLESS that desired
child is failed, or absent (an out-of-range desired, handled
right here rather than by skipping the entry). The aspiration only
advances onto a READY level, so this is the one-frame window after a lazy
load lands but before the next selector pass swaps (see
LODGroupEntry.desiredChildIndex); it is also the whole in-flight
load. A failed level can never become ready this frame, so blocking
on it only buys a timeout — treat it as the best available and keep
checking the rest.isReady.getViewVersion wired), the aspiration must
be FRESH for the current view version — via the group-aware
childFreshAndCount, not the leaf-only isFresh, so a deferred
kind=partition subtree stamped for an older slice counts as stale.hasMoreLODs() thunk answers first:
still true means only a prefix of the level has committed. Then
childFreshAndCount's subtreeLadderComplete — the
commit-time committedLadderComplete stamp, taken from the child
itself for a tracked LEAF and folded over the visible stamped leaves of
a deferred GROUP child (a nested kind=partition / kind=lod
subtree — the overview recipe's fine branch). Neither alone is
enough: a group child carries no thunk, so without the fold the drain
released the frame the moment such a branch became ready, with its part
leaves at chunk-1 by construction; and only the DEFERRED path gets a
thunk, so without the stamp the eagerly-loaded default level — still
climbing its ladder under the sweep-driven refinement loop — read as
complete. Anything with no stamp at all (never committed, or a
non-progressive loader) carries no signal and counts as complete.loading — an in-flight commit can change
what renders on a later frame.An empty registry (and an entry-free scene) is quiescent: there is nothing to wait for.
Called from the capture drain — once per drain rAF, so up to the drain's
own frame cap (LOD_SETTLE_MAX_FRAMES, which is itself INCLUSIVE of the
mandatory catch-up tick) plus one for the strategy's opening tri-state
probe, per exported frame in the worst case; twice on a scene that is
already settled (probe + one poll), and once on a latched frame (the
re-arm probe alone). Either way NOT the rAF hot path, so unlike the rest of this file
it does not avoid allocation: it walks the entry Map with for…of and
resolves freshness through childFreshAndCount, which returns a fresh
object per entry. That cost is genuinely irrelevant here.
PrivatepartitionsWhether visible partition parts have no pending resync or incomplete commit. Unlike hasVisiblePendingPartitionResync, pending resyncs count here only while their specific parts contribute pixels to the capture frame.
PrivatependingPrivatependingPrivatepartitionPrivatepartitionPrivateanyWhether any registered partition part contributes pixels to this frame.
PrivatepartitionPrivatepartitionWhether any lazy LOD-group level has an ensureLoaded fetch in flight.
These promotions run outside every updateView cycle, so neither
isUpdateInProgress() nor getState().isLoading sees them; the perf
snapshot's isSettled does. (Deferred partition parts load through
updateView and are covered by the update lock instead.)
Whether a visible partition has a rising-edge resync waiting for the owning loader to become idle. Unlike pendingPartitionResyncsQuiescent, this deliberately mirrors flushPartitionResyncs's wrapper-level visibility gate: every queued part under a visible wrapper is dispatched, even if that part re-exits before the flush. Pending work retained under a hidden wrapper is not actionable and must not keep wide settledness false indefinitely; neither can work when no resync dispatcher is wired.
PrivateanyRetry a LAZY lod_group level by its authored scene-node path. The path is
stored beside the placeholder in LODGroupChild so anonymous deferred
GROUP placeholders remain addressable without duplicating scene identity
onto the THREE object.
Clears the failure cooldown (failed/failedTick) through the shared
kickDeferredLoad gate, which owns setting loading before firing
ensureLoaded (the thunk itself never sets loading — only the
registry does; keep that invariant here).
Returns true when a retry was kicked OR one is already in flight
(loading), false when no retryable lazy child with that path
exists. Explicit retries bypass the owning loader's automatic archive-fault
gate; the per-child marker keeps concurrent failed branches independently
targetable.
Fire-and-forget semantics: true means "retry started", not "retry
succeeded" — the thunk owns the ready/failed outcome, and a repeat
failure re-enters the normal cooldown cycle.
Update an lod_group's selector mode. 'auto' re-enables
view-driven selection; { lockLevel: i } pins the lod_group to
child index i (0-based in coarsest→finest order). An
out-of-range lockLevel is clamped into [0, n-1] with a
warning — throwing here would force every UI caller to guard
against stale registry state.
Visibility is not swapped synchronously — the next
evaluatePerFrame() call will pick the new desired child. (This
matches the per-frame contract for the auto path; a synchronous
swap would diverge.)
Per-frame evaluation. Wired through
AnimationController.addPerFrameCallback by the app pipeline.
Returns true when at least one lod_group swapped its active
child this frame, so the caller can refresh anything that depends
on which level renders (e.g. the data-monitor's visible-element
tally). Returns false on a no-op frame (the common case), which
keeps the per-frame cost to the projection math alone.
PrivatenoteRising edge (rare): remember WHICH parts of wrapperPath came back so
the resync can be targeted at their loaders instead of re-sweeping the
whole scene. Coalesces with parts already pending for the same wrapper.
PrivateflushHand every pending rising edge whose wrapper is visible to the loader as
ONE requestReprocess(paths) call; hidden wrappers stay pending, dropped
wrappers are forgotten. A set that contains its own wrapper path (a
pathless part) collapses to the wrapper path alone — it already covers
every part.
PrivateevaluateFrustum-gate one partition's parts. Returns the PARTITION_* bit flags;
parts that re-entered this frame are added to risingParts by node path
(child.path), or as the WRAPPER path when a part has none so the caller
resyncs the whole partition rather than missing it.
PrivateevaluateReturns true if this entry's displayed child changed.
PrivatecomputeFold an entry's children nD bounds into one world-space
:type:BoundingBox (see computeEntryWorldBox in
lod-selector-math.ts for the math). The default uses raw
positionBounds for frustum gating and eviction; useLodBounds uses
robust bounds with a per-child raw fallback for selector metrics. This
wrapper supplies per-entry local/world scratch boxes and the registry's
matrixScratch. Raw and robust metric bounds use distinct world boxes
because both remain live during one selector evaluation.
PrivatechildFreshness + committed element count of a child, resolving a GROUP-typed LOD
child (a deferred kind=partition / nested lod subtree — the
overview recipe) through subtreeDisplayProgress rather than the
leaf-only stamps. A bare THREE.Group carries no leaf nodeType, so
isFresh would report it unconditionally fresh and visibleElementCount
would return null — hiding a stale re-slice and defeating the empty
guard. Mirrors the never-downgrade gate's sideProgress so both paths
agree on what "fresh" means for a group. Leaf children (a direct count
stamp) keep the exact pre-existing behaviour.
fresh implies ready for every child shape: the leaf branch's
isFresh is ready-gated, and a NOT-ready group child (a deferred
placeholder whose subtree never committed, or a released level awaiting
reload) reports fresh: false regardless of any stamps its subtree may
retain — it cannot draw, so no display path (slice-aware fallback,
empty-guard redirect, blend pairing) may ever elect it. A READY group with
no stamped leaf (nested group with no slice-dependent geometry) carries no
per-slice staleness signal and reports fresh: true, count: null.
subtreeLadderComplete is the third answer, folded from the same walk
(SubtreeDisplayProgress.complete): false when any visible stamped leaf
under the subtree has committed only a prefix of its additive ladder. A
tracked LEAF has no subtree to fold, so it answers with its OWN
committedLadderComplete stamp.
That stamp rather than the child's hasMoreLODs() thunk, because the
thunk does not exist on every leaf: load-lod-group-node attaches it
only on the DEFERRED path, so the eagerly-loaded default level — whose
ladder is advanced by the sweep-driven background refinement loop — has
none, and reporting an unconditional true here declared a still-
streaming coarse level complete. The stamp is also the safer of the two
where both exist (see lod-display-gate's "committed state only" note:
a live getter flips when the last fetch resolves, frames before the commit
lands). Callers still read hasMoreLODs() directly on top of this, since
it is what re-fires ensureLoaded to advance a lazy ladder. Only
isCaptureQuiescent consults this field; the display paths ignore
it.
version === null means no view-version tracking is wired: the per-slice
staleness test is skipped and every READY child reads fresh — which is
exactly what the version != null guards at the display call sites
already assume, so those are unaffected.
PrivatecoarsestIndex of the coarsest child strictly before beforeIndex that is fresh
for version AND has a non-zero committed element count — or -1 when
none qualifies. The group-aware counterpart of the empty-level display
guard's fallback: it resolves each child through childFreshAndCount,
so a fresh-but-empty GROUP child (a deferred kind=partition subtree
whose visible leaves all committed 0) is correctly skipped rather than
treated as non-empty (a bare THREE.Group has no leaf count stamp). A
READY child with an UNTRACKED count (null — group with no stamped leaf)
is accepted: the guard only redirects away from KNOWN-empty levels. The
caller bounds the search at the chosen display level or the selector's
aspiration, whichever is finer, so no finer level can override it. Because
childFreshAndCount's fresh implies ready, a NOT-ready placeholder
can never be returned — the guard must only redirect to a level that can
actually draw. When nothing qualifies (-1) the caller keeps the
fresh-but-empty current level: an empty-but-real level beats a blank
placeholder.
PrivateisWhether every fadeable leaf material under child.object uses a blend
mode that cross-fades correctly — see isBlendableSubtree
(lod-fade.ts) for the criteria.
PrivateapplyApply the per-leaf LOD anti-popping opacity (cross-fade weight ×
streaming 1/e(k) energy compensation) to a child's leaf materials, or
restore the authored opacity — see applyLodFade
(lod-fade.ts) for the full mechanics. This wrapper supplies the
registry's registerMaterial dep so a clone-on-first-fade material
keeps receiving per-frame camera-uniform updates.
PrivatecoarsestCoarsest child that is ready AND fresh for version, falling back to the
coarsest READY level when none is fresh yet (the ≤1-frame window right after
a re-slice) so the group shows stale-but-ready geometry rather than going
blank. GROUP-AWARE: each child resolves through childFreshAndCount, the
same freshness the sibling display paths (aspiration check, empty guard,
blend pairing) use — so a ready GROUP child whose subtree leaves are stamped
for an older slice is correctly skipped. The leaf-only
coarsestFreshIndex it replaced treated any non-leaf as unconditionally
fresh, which displayed the OLD slice from a stale partition/overview branch
after a re-slice even while a genuinely fresh level was resident.
PrivatestaleShould a STALE previously-displayed level be kept on screen for a few more
frames instead of dropping to fallbackIdx, the coarsest fresh level?
Returns the index to hold, or undefined to take the fallback.
The slice-aware fallback exists so a scrub shows the new slice immediately at low detail. It becomes a defect when the aspiration is only a few frames behind: stepping a 4D timelapse one timepoint made the display drop from the finest level to the coarsest and climb back within ~70 ms, every step — a flash to 1.6% of the geometry while the finest level's data was already cached and decoding. What is on screen is the PREVIOUS slice, which for a timelapse step is the previous frame: the same thing a video player leaves up while the next frame decodes, and far closer to the truth than 108 of 6,900 splats.
Held only when ALL of:
STALE_HOLD_MIN_RATIO of the held level's committed count —
so a fallback that is nearly as good is taken immediately (it is
fresh, and freshness wins whenever quality is comparable),STALE_HOLD_MS budget.The budget is deliberately spent from when the hold STARTS and is not
refreshed by later version bumps, and once exhausted it latches until the
aspiration commits fresh. So a continuous drag degrades to exactly the
pre-existing behaviour after STALE_HOLD_MS, rather than freezing on
one frame for as long as the user keeps dragging.
PrivatecoarsestIndex of the coarsest currently-ready child. Children are stored coarsest→finest, so the first ready index is the coarsest available (resident) level. Used by the off-screen gate to hold a culled group on geometry that is already loaded — never a not-ready lazy level, so it cannot kick a load. Falls back to the current active index when nothing is ready (shouldn't happen: the eager default level is always ready).
PrivatemaybeKick a lazy child's deferred loader if eligible: not ready, has an
ensureLoaded thunk, not already loading, and not inside an
un-expired failure cooldown. Centralises the lazy-load gate used by
both the desired-target swap path and the active-child self-heal so
the failure/cooldown logic lives in exactly one place.
A freshly-failed child is stamped with the current tick; once
FAILED_RETRY_FRAMES elapse the failed flag is cleared and the
load retried — recovering a level that failed on reload (after a
successful load + byte-eviction), which the old "failed until
released" behaviour left permanently stuck (a failed child is never an
eviction candidate).
PrivatemaybeReload a READY-but-STALE lazy fine level for the current view — the sibling
of maybeKickLoad for a child whose geometry is committed but reflects an
older slice/displayDims version. A fine level leaves the per-slice sweep
once loaded (see load-lod-group-node.ts), so the registry — not the
sweep — drives its reload, gated on settle by the caller. Re-fires
ensureLoaded, which re-runs the expensive loader (overwrites the
geometry in place, commits independently, re-stamps loadedViewVersion
fresh). Unlike maybeKickLoad it does NOT early-return on isReady —
refreshing a ready level is the whole point. The stale level stays hidden
behind the coarse fallback meanwhile (the display pass), so this never
blanks the screen, and the shared loading/cooldown guards make
re-calling it every settled frame safe.
PrivatekickEffective-visibility gate — the single place the per-frame paths
(maybeKickLoad / maybeKickReload) decide whether a deferred load is
worth STARTING at all.
A layer authored visible=false (or toggled off in the layers panel)
hides the LAYER object; the lod_group and its levels underneath keep their
own visible flags, so the selector happily kept aspiring to — and
lazily loading — fine levels that cannot be drawn. Those loads compete for
the shared fetch gate, the worker pool, and VRAM with the layer the user is
actually looking at (measured: a hidden 9.75M-point level finished FIRST,
roughly doubling scene load time). So: no group visible ⇒ no new loads.
The same gate refuses every automatic kick while the owning loader has a
latched archive fault; explicit retry remains the only bypass.
Scope is deliberately narrow — this only stops STARTING work:
default_level is loaded by loadLodGroupNode, not from
here, so a hidden layer still has its cheap coarse level ready to
display the instant the panel toggles it on;entry.groupObject (the hidden flag
usually sits on an ANCESTOR layer/group, not on the lod_group itself);LayerApplyEngine.applyVisibility → requestRender →
AnimationController → evaluatePerFrame) resumes loading on the
very next frame with no extra wiring.retryLazyChildByNodePath (an explicit user retry of a FAILED level)
deliberately bypasses this and calls kickDeferredLoad directly: an
explicit request for a retryable child is honoured whatever the layer's
visibility or the current archive-fault latch.
PrivatekickShared lazy-load gate for maybeKickLoad (initial load of a not-ready
level) and maybeKickReload (refresh of a ready-but-stale level): fire
ensureLoaded unless already loading or inside the failure cooldown. A
freshly-failed child is stamped with the current tick; once
FAILED_RETRY_FRAMES elapse the failed flag clears and the load
retries — recovering a level that failed on reload (after a successful load
PrivateenforceBound resident LOD geometry to the GPU-pool byte budget — see
enforceResidentByteBudget (lod-eviction.ts) for the full
policy. This wrapper supplies the registry's entries, the pool-accounting
deps, and the raw per-entry world-box fold, so eviction matches the
selector's frustum gate rather than its optional robust metric bounds.
Tracks loaded
lod_groupandkind=partitionnodes in a scene; evaluates per-frame to pick the active LOD and frustum-visible parts.