Scene path (for diagnostics + UI lookup).
The lod_group's THREE container. World matrix lives here.
Children in coarsest→finest order (== insertion order on disk,
== ascending coverageFraction).
OptionalselectorUnits of the children's coverageFraction thresholds (the on-disk
group's selector attr): 'screen-area' compares them against the
projected bbox rect's fraction of the viewport AREA
(projectBoxAreaFraction); 'coverage' — the legacy diagonal metric
(projectBoxDiagonalPx / (FILL_FACTOR × min(viewport.width, viewport.height)), the fitted screen axis) — is the default when
absent, so older stores and test-constructed entries keep their
behaviour.
Current selector mode ('auto' or { lockLevel: i }).
Initial active level, used when nothing else has selected yet.
Index into children of the screen-DESIRED level (the aspiration). This
is the hysteresis anchor and the level the auto-selector wants on screen.
It is NOT necessarily what is displayed: when its committed geometry is
stale for the current view version, the registry shows a coarser fresh
level (displayedChildIndex) until the aspiration commits.
OptionaldisplayedIndex into children of the level ACTUALLY visible this frame. Equals
activeChildIndex in steady state; during a re-slice it transiently
points at the coarsest fresh level while the aspiration reloads, and
during a never-downgrade hold it can also point at a FINER
previously-displayed level while a coarser streaming aspiration catches
up. During a stale hold it can instead remain on a finer STALE level while
the next slice decodes. A per-frame transient written by evaluateEntry
and read by enforceByteBudget (same synchronous evaluatePerFrame pass)
so eviction never releases the on-screen level. undefined before the
first evaluation ⇒ treated as activeChildIndex. Tracks what is ACTUALLY
on screen every frame — including the coarse level shown while the group is
off-screen — which is what eviction needs, but is therefore NOT the
never-downgrade gate's memory (that is heldDisplayChildIndex).
OptionalheldThe last level displayed while the group was ON SCREEN — shared memory for
the never-downgrade gate and stale hold. Distinct from
displayedChildIndex because the off-screen gate transiently displays
(and would otherwise record) the coarsest ready level; folding that into
the gate memory would let a mere look-away-and-back clobber a held finer
level and re-pop it to chunk-1 on return. Written by evaluateEntry
only on frames where the group is on screen. undefined before the
first on-screen evaluation ⇒ neither hold policy has a prior level.
OptionalstaleWall-clock ms at which the current stale-hold budget started — see
staleHoldDisplayIndex. Set on the first eligible hold and retained when
a later ratio check declines to hold, so the budget cannot restart during
the same scrub. Cleared only when the aspiration recommits fresh or the
budget is exhausted.
OptionalstaleTrue once a stale hold has exhausted STALE_HOLD_MS without the
aspiration recommitting. Latches the coarse fallback for the rest of this
scrub; cleared when the aspiration finally lands fresh.
OptionaloffWhether the auto-selector is currently holding this group at its
coarsest-ready level because its world bounds are outside the camera
frustum (the off-screen gate). false when on-screen or when a
level is explicitly locked. Surfaced in the layers-panel readout as an
"(off-screen)" hint so a coarse level on close inspection isn't
mistaken for a selection bug. Updated each evaluatePerFrame.
OptionaldesiredThe level index the selector WANTED this frame, recorded BEFORE the
ready/freshness gates below it get a say. Written by evaluateEntry
once a desired has been computed — the explicit lock and all three
auto branches (off-screen hold, screen-area pick, legacy coverage pick).
undefined (never evaluated) ⇒ read it as activeChildIndex.
It is NOT rewritten on every frame: evaluateEntry returns before
computing a desired when the entry has no registration cache or no
world box, and evaluatePerFrame returns before reaching the entries at
all on a zero-sized viewport or with fewer than two display dims. The
field then keeps its previous value. That is benign — both early returns
are stable properties of the entry/viewport rather than transient states,
so a stale value cannot describe a level the selector has since moved off,
and an entry that never got one reads as activeChildIndex (i.e.
"nothing pending"), which is the right answer for a group the selector has
never been able to evaluate.
Purely diagnostic for the renderer — nothing about display reads it. It
exists so an OFFLINE CAPTURE can tell "the selector wants a finer level it
has not got yet" apart from "settled" (see
LODGroupRegistry.isCaptureQuiescent). activeChildIndex alone
cannot express that: the aspiration only ever advances ONTO A READY LEVEL,
so in the frame where a lazy fine level's async load lands (the thunk sets
ready=true and clears loading) the registry has not swapped yet —
that happens on the NEXT selector pass. A quiescence predicate reading only
loading / ready / displayedChildIndex would call that window
"settled" and the capture would film the coarse level one frame before the
swap, which is exactly the LOD pop this field exists to close.
One LOD-group entry tracked by the registry.