PrivateentriesPrivate ReadonlypartitionsThe kind=partition groups and their per-part gating (partition-gate.ts).
PrivatecachesPer-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 (level dissolve 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).
PrivatedrawnSet when a per-frame evaluation changed what the next frame DRAWS — any level shown OR hidden, a fade opacity written with a new value. Broader than evaluatePerFrame's return (which reports only a newly shown level, for the monitor tally): hiding a level, a time-driven stale-hold expiry, or a dissolve weight step all change pixels too. Read and cleared by takeDrawnStateChanged — the render-on-change loop's signal to redraw.
Private ReadonlydissolvesIn-flight level dissolves, by group path (lod-dissolve.ts).
Private ReadonlydissolveAsks this registry whether a pair of levels can dissolve (no per-frame closure).
Private ReadonlyentryThe per-frame inputs of evaluateEntry, refilled by beginFrame.
Private ReadonlydissolveReused argument of LodDissolves.advance.
Private ReadonlypreloadsBand preloads in progress, by group path (see preloadNeighbour).
Private ReadonlyframeThis frame's clock reading and playback period (see evaluatePerFrame).
Private ReadonlyplaybackPlayback aspiration: reload timing, admission, probes (playback-aspiration.ts).
Private ReadonlyretryOne-shot wakes for failure cooldowns and unanswered part requests (retry-wakes.ts).
Private ReadonlycaptureWhat isCaptureQuiescent reads (capture-quiescence.ts); built once.
Private ReadonlyownSnapshot source when no shared getViewContext is injected.
PrivatedepsPrivate ReadonlyclockThe registry clock, bound once (foldLoadTime reads it lazily).
PrivateviewRegister a newly-loaded lod_group (called by the scene loader). A re-registration of a path (a re-activated partition part) starts the new entry's per-path state — dissolve, band preload, one-shot warnings — fresh.
Register a partition whose children are independently frustum-gated.
Optionaltargets: ReadonlySet<string>Drop an lod_group from the registry (called on scene teardown).
Drop every lod_group and partition at or under path — a subtree that
was discarded (a failed partition part activation), whose entries would
otherwise keep being evaluated against detached objects.
PrivateforgetPer-path state an entry accumulates beyond its registration.
Clear all entries (called on full scene tear-down).
Number 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, so an offline capture
may grab the frame — see capture-quiescence.ts for the rules and why.
Whether 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.)
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/failedAtMs) 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.
A deferred partition part (B4) whose activation failed is retried by its part path the same way: it is re-armed, and the per-frame gate asks for a pass to activate it again while it is wanted.
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.
Reports, separately, whether at least one lod_group swapped its active
child (levelChanged) and whether a partition part's frustum-cull
visibility flipped (cullChanged) this frame — see
LODFrameChanges for why the two must not be conflated. Either
one changes what the visible-element tally counts. A no-op frame (the
common case) returns LOD_FRAME_UNCHANGED, which keeps the
per-frame cost to the projection math alone.
PrivatebeginStart an evaluated frame: advance the tick, take the frame's clock reading and playback period, and fill the shared EntryFrame.
view.projView (projection×view, default WebGL coordinate system, the
NDC convention of the manual divide inside projectBoxDiagonalPx) is
shared three ways: its frustum gates off-screen groups and ranks eviction,
and the per-group projections reuse the matrix. The settle tracker records
whether the (global) view version has settled, to gate deferred fine-level
reloads; an undefined view version (no wiring / tests) ⇒ settled, so
the trigger is inert.
PrivateendEnd an evaluated frame.
Liveness (tick-demand.ts): keep the on-demand loop ticking while any
component still has work only a later frame can do — a load landing (the
swap fires on that frame), a dissolve advancing (a function of TIME: each
step writes a new opacity, which marks the frame dirty through
takeDrawnStateChanged), a partition resync waiting for the loader. A
failed level's cooldown and an unanswered part activation request need no
ticks: each wakes the loop once at its expiry (retry-wakes.ts).
Record whether fade management was ON this frame — the falling-edge
detector behind the one-shot residual-opacity restore (see
fadeWasManaged), written AFTER the entry loop so every entry in one
frame sees the same previous-frame value. Then bound resident LOD geometry
against the shared GPU-pool byte budget (one VRAM authority): retention
keeps loaded levels resident so re-shows are free; this LRU-evicts only
hidden levels when over budget — hidden-layer (undrawable) first, then
off-screen / furthest-from-camera — so no per-swap release, hence no reload
churn.
PrivateskipA frame the registry cannot select for (fewer than two display dims, a collapsed canvas). No group is evaluated, so no dissolve can land: drop them all rather than leave isAnimating true until the view returns (the next evaluated frame then draws each group's level alone).
Whether a level dissolve is still in flight: the frames it spans are animation frames, drawn although neither the camera nor the data moves.
Read-only. Only evaluatePerFrame retires a dissolve, on the one end
rule it draws by (levelFade), so "not animating" means the last
evaluated frame drew one level. A poll between evaluates (the offline
capture drain through isCaptureQuiescent, the debug settle probe)
used to delete a fade whose wall-clock end had passed while the drawn frame
still showed both levels — releasing the capture on a mid-dissolve frame
and swallowing the landing's levelChanged. Every evaluated entry reaches
levelFade or drops its fade, so an entry that stops dissolving never
holds this true.
PrivateobserveUntil when entry's children need the loop ticking: while one is loading
(its swap fires on the frame it lands). A child cooling down after a
failure needs no ticks — one wake at the cooldown's end lets a parked
camera retry it (#2944 A6) — and a child neither failed nor loading drops
its retry backoff. For each child whose ensureLoaded has finished since
it was fired, fold the measured load+commit time into its loadEwmaMs (a
failure is not a timing).
PrivatecancelCancel the retry wakes of the lod_group registered at path.
PrivateplaybackDuring playback, the finest level at or below desired whose reload fits
the period (see playback-aspiration.ts). desired is unchanged when
not playing, locked, or forced finest.
PrivatekeepKeep the loop ticking (see LODGroupRegistryDeps.requestTick).
Whether anything this registry drives changed what the next frame draws
since the last call (level visibility either way, a fade opacity step, a
partition part culled or restored) — then clear it. The app pipeline
returns it from the 'lod-group-selector' per-frame callback so the
render-on-change loop redraws exactly then.
PrivateevaluateOne group's frame, in stages: pick the level the selector wants and
advance the aspiration toward it (selectAspiration), resolve what can
be DISPLAYED (resolveDisplay), advance the dissolve, drive the
aspiration's reload / refinement and the band preload, then apply
visibility and opacity (the single owner of object.visible). Returns
true if this entry's displayed child changed.
PrivateselectPick the desired level (pickDesired), cap it during playback, record
it, and advance the aspiration toward it. Returns the desired index, or
null when the entry cannot be evaluated this frame (its dissolve, if
any, is dropped where that applies).
PrivatedropdropFade, for a stage that answers null (no evaluation).
PrivateresolveWhat the group displays this frame: the slice-aware fallback / stale hold,
then the fresh-but-empty guard, then the never-downgrade gate. Fills
ASPIRATION for the later stages.
PrivateaspirationWhether the aspiration is ready and fresh for version (always fresh
when freshness is untracked), into ASPIRATION. Freshness resolves a
GROUP-typed aspiration (deferred kind=partition / nested lod subtree — the
overview recipe) through the subtree aggregate, not the leaf-only stamp: a
bare THREE.Group has no leaf nodeType, so isFresh would call it
unconditionally fresh and a re-slice would show the stale subtree with no
coarse fallback. Leaf aspirations are unchanged.
PrivatepickPick the desired child index into PICK (desired, the band-preload
metric, whether the footprint pick decided). An explicit lock bypasses the
off-screen gate: a user who pins a level keeps it whether or not the group
is on screen.
Off-screen gate: if the group's world bounds are entirely outside the
camera frustum, hold it at the coarsest ready level instead of selecting
— and lazily loading — a fine level the renderer will frustum-cull anyway.
This turns frustum culling into a LOD/loading input, not just a draw-time
skip. coarsestReadyIndex never targets a not-ready lazy child, so no
load is kicked while off-screen, and the outgoing fine levels fall out of
the visible tally and become eviction candidates. On re-entry, retention
usually makes the upgrade a free visibility toggle rather than a reload.
PICK_OK; PICK_SKIP when the entry has no registration
cache; PICK_DROP when its bounds fold to no world box.
PrivatepickThe on-screen pick: the threshold pick on the (biased) coverage metric, or
— for a screen-area group whose levels carry complete footprint stamps —
the projected-footprint pick, which then decides alone (and leaves the band
preload and the dissolve out, see dissolveStep). Not when the metric
saturated (the camera is inside the box, or forceFinest): the footprint
is sized at the box-centre depth and says nothing about the splats at the
eye, so the finest level stands.
PrivatecoverageThe dimensionless coverage metric for this frame, in the units the
entry's selector names: the screen-area fraction, or the projected
diagonal ÷ FILL_FACTOR·fittedAxisPx (legacy coverage).
The screen metrics project the group's LOCAL box through
projView × matrixWorld, i.e. the 8 corners of the box as oriented on
screen. Projecting the corners of its world AABB instead (the frustum
gate's box) inflated a rotated group twice and picked too fine a level.
computeWorldBox refreshed matrixWorld in pickDesired.
PrivateadvanceAdvance the aspiration (activeChildIndex) toward desired. The
aspiration is the hysteresis anchor and only moves onto a READY level; a
not-ready desired kicks its deferred loader and we keep aspiring to the
current level until it commits. Visibility is NOT touched here — the
display-resolution pass is the single owner of object.visible.
false when a lock index outlives its children (an empty entry
is registered on purpose and setSelectorMode skips clamping for it):
nothing to aspire to, nothing to kick — never throw per frame.
PrivateslicedSlice-aware display resolution. Show the aspiration when its committed
geometry is fresh for the current view version (showable); while it is
stale (a time/displayDims scrub reloaded it in place without flipping
ready) show the coarsest FRESH level so the new slice appears
immediately at low detail — falling back to the coarsest ready level if
none is fresh yet, so it never goes blank — unless what is already on
screen is far better and the aspiration is about to land
(staleHoldDisplayIndex). getViewVersion undefined ⇒ freshness
untracked: a not-ready aspiration (a lazy level still loading) keeps the
previously-displayed level if it is still ready, otherwise nothing until
the load commits — the legacy behaviour.
PrivatenonFresh-but-EMPTY display guard. If the chosen display level committed 0 elements, prefer the coarsest fresh NON-empty level no finer than the chosen display or the selector's aspiration, whichever is finer. An intermediate level can legitimately fill a stale fallback gap; only redirecting to a level coarser than the chosen display signals inconsistent/corrupt data and warrants a warning. Finer levels must not override the selector, even when a coarse slice is legitimately empty (see #1600). Group-aware: a deferred kind=partition / nested lod subtree whose visible stamped leaves are all fresh-but-empty (poisoned/stale cache serving an old layout) would otherwise slip past a leaf-only count check and blank the group; the redirect target stays the coarsest fresh non-empty leaf level.
PrivatewarnWarn (once per group) that a fresh level committed nothing while a coarser one has geometry.
PrivateneverNever-downgrade display gate. A lazy level flips ready after its
FIRST additive chunk commits, so an ungated swap to a fresh-but-still-
streaming aspiration pops displayed quality down to chunk-1 and climbs
back. Hold the previously-displayed level while the streaming aspiration
is strictly worse than what is on screen; release on ladder completion,
committed-count crossover, ladder failure, or the previous level losing
freshness. Bypassed for an explicit lock and while off-screen. This is a
STREAMING/loading concern (WHEN a just-loaded level is good enough to
show) — orthogonal to the time-driven level dissolve, and stays a hard
hold.
PrivatedissolveLevel dissolve (time-driven) into BLEND. When the DISPLAYED level
changes, dissolve from the outgoing level to the incoming one over
config.lod.fadeMs — incoming at w = smoothstep of the elapsed fraction,
outgoing at (1−w) — so the substitutive switch dissolves instead of
popping. A function of TIME since the change, never of the camera's
distance to a threshold: a parked camera always settles on ONE level at
full weight (#2925 — a distance band drew two levels for as long as the
camera stayed inside it). Brightness is preserved by the levels'
build-time mass conservation (both integrate to the same DC). Blendable
modes only (BLENDABLE_MODES = additive/luminous/volumetric — energy sums
linearly, or opacity linearly scales optical depth τ so the pair
interpolates monotonically between the two levels' absorptions; see that
set's doc for what volumetric does NOT guarantee). For two mid-fade
volumetric siblings the mesh draw order may come from the render-order
containment rule (near-identical bounds); acceptable because combined
TRANSMITTANCE is order-independent (transmittances multiply), so
occlusion of content behind the pair is exact at every weight. The
emission ordering residual stays bounded by the local inter-level radiance
difference, not by the rendered hard-swap pop.
Off / non-blendable / off-screen / locked / a held-stale display ⇒ no
dissolve (byte-identical hard swap). Nor for a level picked by the
projected-FOOTPRINT rule (PICK.usedFootprint; only GSplat levels carry
the stamps, see scene/README step 5). That rule picks the
coarsest level whose median splat stays under
config.lod.maxMedianFootprintPx, so its swaps happen where splats are
about a pixel across and the two levels are expected to read alike — the
case the dissolve exists for (a visible pop) should not arise, and drawing
both levels for fadeMs would add a second level's overdraw. The
footprint path leaves preloadMetric null too, so it gets no band
preload: the band is measured against coverage thresholds, and its switch
point is a pixel-size threshold. (A design reading, not a measured
guarantee: if footprint swaps are seen to pop, this is the gate to open.)
PrivaterefreshSettle-gated reload / progressive refinement of the (ready) lazy
aspiration. A lazy level no longer joins the per-slice sweep (see
load-lod-group-node.ts), so the registry drives its (re)loading HERE,
settle-gated: during active scrubbing (version changing every frame)
nothing fires, so only the cheap coarse level (still sweep-driven) shows
the new slice; once the user pauses we (a) reload a STALE level for the
new slice, and (b) advance a PROGRESSIVE level that is fresh but still has
additive LODs to stream — both by re-firing the same ensureLoaded,
until the level is ready, fresh, AND complete. Eager (coarse) levels have
no ensureLoaded and stay sweep-driven, so this only ever targets lazy
levels. Only (a) is a reload TIMING; a ladder refinement step is not.
Never for a deferred GROUP child (the overview recipe's fine partition /
nested lod branch): it has an ensureLoaded too, but its expensive step
is loadChildren — re-running it attaches a SECOND copy of the whole
subtree under the placeholder (double-drawn geometry, duplicate names,
re-registered loaders, leaked buffers). Its leaves are sweep-registered
and re-stamp themselves, so staleness needs no kick. Keyed on the explicit
deferredGroup flag, not on the leaf's nodeType stamp: a lazy leaf
whose placeholder is not stamped yet must still drain its ladder here.
During playback a STALE aspiration reloads on every timepoint (the version
never settles there, and playbackAspiration kept it to a level that
fits the period); refinement of a fresh one still waits.
PrivateapplyApply visibility (single owner). At most the display child plus its
dissolve partner is visible (displayIdx / BLEND.partner, each only
if READY — never force-show a not-ready placeholder). Returns whether a
SHOWN level changed, so evaluatePerFrame refreshes the monitor's
visible tally, which counts the displayed level, not the aspiration.
Opacity is managed only while at least one anti-popping feature is on: the
level dissolve and/or the streaming energy compensation (per-leaf
1/e(k) on the displayed streaming level). When BOTH are off this reduces
to the original single-level visibility swap with no material writes —
byte-identical. On the falling edge of fade management (both flags just
toggled OFF, possibly MID-fade) every child's authored opacity is restored
ONCE so a half-faded level does not stay dim forever (fadeWasManaged is
updated per frame after all entries run, so the edge fires exactly one
frame for each entry).
PrivateapplyOne child's opacity under FADE_MODE. Dissolve weight only when a
partner is in flight (primary at α, partner at 1−α); otherwise no coverage
weight (null ⇒ 1). Energy compensation is folded in PER-LEAF inside
applyChildFade, so it also covers a plainly-displayed streaming level with
no cross-fade partner. A hidden child gets its authored opacity back
(idempotent), as does every child once on the fade-management falling edge.
PrivatesetShow or hide one child. Returns whether that changes the visible tally: a new level became visible, or a dissolve's outgoing level left on its own (its partner has been on screen since the dissolve started) — the monitor must recount then too, like the swap it completes.
PrivateblendA shown child's dissolve weight: null (⇒ 1) unless a partner is in flight.
PrivaterecordMark the on-screen level most-recently-used and record it for the eviction pass, which must never release the level currently displayed. Only update when a ready level is actually shown — otherwise keep the last shown index so eviction still protects whatever the user last saw.
Then stamp any already-ready child that has never been shown so it ages
into the eviction LRU. Without this, a lazy level that finished loading but
was never displayed (camera/slice moved away mid-load) keeps
lastVisibleTick == null and is permanently exempt from eviction,
leaking VRAM. A SENTINEL, not the current tick: the eviction LRU's final
tiebreak is coldest-first on this field, so stamping "now" would make the
one level the user never saw the HOTTEST in its group and evict levels they
looked at moments ago before it. 0 is older than any real tick (ticks start
at 1).
PrivatepreloadBand preload. While the selector metric sits within
config.lod.preloadBandFraction of the smaller adjacent inter-threshold
gap from a threshold of the selected level (desired), make the level
across that threshold resident in the background, WITHOUT drawing it: the
visibility pass shows only the displayed level and its dissolve partner,
so a preloaded level stays hidden, at its authored opacity, and outside
every drawn tally until the selector picks it. Crossing the threshold then
finds it ready and the dissolve starts that frame, instead of after its
load (the band the retired distance cross-fade DREW both levels in).
It drives the same ensureLoaded the selection would, through the same
gates (hidden layer, archive fault, failure cooldown, one load in flight),
and the same settle-gated reload / ladder refinement the aspiration gets,
so a level that becomes the aspiration is already as far along as it would
have been. It never runs:
?noLodFade) — a hard swap was never
preceded by a band, and that mode stays byte-identical;playbackAspiration alone decides what fits;loadChildren) with loaders of its own;metric null), or while the group's layer is hidden.
Any of these ends the visit. The fetches ride the same fetch class as any
lazy level load; a load in flight when the camera leaves the band simply
lands hidden, as one does when the selection moves away mid-load.Residency is otherwise left to the byte budget: a preloaded level is an
ordinary hidden eviction candidate. Once it has landed, a release is not
followed by a reload until the metric leaves the wider exit band
(config.lod.preloadExitBandFraction) and comes back, so VRAM pressure cannot
turn a parked camera into a load/evict loop.
PrivatepreloadThe group's thresholds when a band preload may run this frame (see
preloadNeighbour for the conditions), else null.
PrivateendForget a group's preload visit; always null (nothing to advance).
PrivatepreloadThe preload visit to advance this frame, or null. A metric inside the
entry band of the neighbour already being preloaded continues that visit;
inside another neighbour's band starts a new one; between the entry and
the exit band of the current visit keeps it without advancing it; anywhere
else ends it.
PrivateadvanceOne frame of a preload visit: load the level if it is not resident (unless it already landed this visit and was released since — no reload loop); once resident, reload it for a new slice or advance its ladder on the same settle gate as the aspiration. Deferred GROUP levels are never preloaded.
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.
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.
PrivatelevelAdvance (or start) the dissolve of entry toward displayIdx on this
frame's clock (LodDissolves.advance), or null when the group should
draw displayIdx alone.
PrivatedropForget entry's dissolve on an evaluateEntry path that returns before
levelFade (so isAnimating cannot stay true for a group that is no
longer dissolving). Returns false: no level change this frame.
PrivatenowThe registry's wall clock, in milliseconds (deps.now in tests).
PrivatecanWhether fromIdx can be drawn dissolving against toIdx this frame.
PrivateapplyApply the per-leaf LOD anti-popping opacity (dissolve 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:
config.lod.staleHoldMinRatio 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),config.lod.staleHoldMs 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 config.lod.staleHoldMs, rather than freezing on
one frame for as long as the user keeps dragging. The one exception is
playback with the held level's own reload in flight: that hold lasts
until the reload lands, and its budget restarts on each such frame.
PrivatestaleThe previously-displayed level a stale hold could keep, or undefined:
the group is auto-selected, on screen and has budget left; the level is
ready and FINER than the fallback; and the fallback is a SEVERE downgrade
(see staleHoldDisplayIndex).
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 frame clock; once its cooldown
(config.lod.failedRetryMs, backed off per consecutive failure) elapses
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. timed is false for a ladder
refinement step of a FRESH level: only a full reload is a reload timing
for loadEwmaMs.
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 frame clock; once its cooldown
(config.lod.failedRetryMs, backed off) elapses the failed flag clears
and the load retries — recovering a level that failed on reload (after a successful load
A timed load (every full load or reload) is measured into
loadEwmaMs; the kick that starts this frame's playback probe marks it
playbackProbePending. An untimed one (a ladder refinement step) is not
measured.
PrivatestartStamp the start of a timed load (its end is stamped by
onLoadSettled) and let the playback probe claim it; an untimed one (a
ladder refinement step) is not measured.
PrivatefailureWhether a failed child's retry cooldown has elapsed. The first frame that observes the failure starts the cooldown clock (and so answers no).
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.