Luxar Viewer API Documentation - v2026.10.19
    Preparing search index...

    Tracks loaded lod_group and kind=partition nodes in a scene; evaluates per-frame to pick the active LOD and frustum-visible parts.

    Index
    entries: Map<string, LODGroupEntry> = ...
    partitions: PartitionGate

    The kind=partition groups and their per-part gating (partition-gate.ts).

    caches: Map<string, LODGroupEntryCache> = ...

    Per-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).

    matrixScratch: number[] = ...

    Reused per frame to feed transformBoundingBox's matrix arg.

    tick: number = 0

    Monotonic 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.

    warnedNoReadyChild: Set<string> = ...

    Entry 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.

    warnedEmptyLevel: Set<string> = ...

    Entry 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.

    settleTracker: SettleTracker = ...

    Tracks 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.

    fadeWasManaged: boolean = false

    Whether 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).

    drawnStateChanged: boolean = false

    Set 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.

    dissolves: LodDissolves<LODGroupEntry> = ...

    In-flight level dissolves, by group path (lod-dissolve.ts).

    dissolveHost: DissolveHost<LODGroupEntry> = ...

    Asks this registry whether a pair of levels can dissolve (no per-frame closure).

    entryFrame: EntryFrame = ...

    The per-frame inputs of evaluateEntry, refilled by beginFrame.

    dissolveFrame: { nowMs: number; fadeMs: number; version: number | null } = ...

    Reused argument of LodDissolves.advance.

    preloads: Map<string, PreloadVisit> = ...

    Band preloads in progress, by group path (see preloadNeighbour).

    frame: { nowMs: number; playbackPeriodMs: number | null } = ...

    This frame's clock reading and playback period (see evaluatePerFrame).

    playback: PlaybackAspiration = ...

    Playback aspiration: reload timing, admission, probes (playback-aspiration.ts).

    retryWakes: RetryWakes = ...

    One-shot wakes for failure cooldowns and unanswered part requests (retry-wakes.ts).

    captureSource: CaptureQuiescenceSource = ...

    What isCaptureQuiescent reads (capture-quiescence.ts); built once.

    ownViews: ViewContextProvider | null

    Snapshot source when no shared getViewContext is injected.

    • Register 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.

      Parameters

      Returns void

    • Parameters

      • groupObject: Object3D
      • bounds: readonly { min: readonly number[]; max: readonly number[] }[]

      Returns { inFrustum: boolean[]; order: number[] } | null

    • Drop an lod_group from the registry (called on scene teardown).

      Parameters

      • path: string

      Returns void

    • 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.

      Parameters

      • path: string

      Returns void

    • Per-path state an entry accumulates beyond its registration.

      Parameters

      • path: string

      Returns void

    • Failure reason for a latched lazy level, if that path is still failed.

      Parameters

      • path: string

      Returns string | undefined

    • 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.

      Returns boolean

    • 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.)

      Returns boolean

    • Retry 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.

      Parameters

      • path: string

      Returns boolean

    • 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.)

      Parameters

      Returns void

    • 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.

      Returns LODFrameChanges

    • Start 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.

      Parameters

      • view: ViewContext
      • viewport: { width: number; height: number }
      • displayDims: readonly number[]

      Returns EntryFrame

    • End 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.

      Parameters

      • loadsTickUntil: number
      • camera: Camera
      • displayDims: readonly number[]

      Returns void

    • 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.

      Returns boolean

    • Until 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).

      Parameters

      Returns number

    • Cancel the retry wakes of the lod_group registered at path.

      Parameters

      • path: string

      Returns void

    • During 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.

      Parameters

      Returns number

    • 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.

      Returns boolean

    • One 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.

      Parameters

      Returns boolean

    • Pick 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).

      Parameters

      Returns number | null

    • What 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.

      Parameters

      Returns number

    • Whether 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.

      Parameters

      Returns { ready: boolean; fresh: boolean }

    • Pick 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.

      Parameters

      Returns number

      PICK_OK; PICK_SKIP when the entry has no registration cache; PICK_DROP when its bounds fold to no world box.

    • The 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.

      Parameters

      Returns void

    • The 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.

      Parameters

      Returns number

    • Advance 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.

      Parameters

      Returns boolean

      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.

    • Slice-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.

      Parameters

      • entry: LODGroupEntry
      • showable: boolean
      • version: number | undefined

      Returns number

    • Fresh-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.

      Parameters

      Returns number

    • Never-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.

      Parameters

      • entry: LODGroupEntry
      • displayIdx: number
      • staleHoldEnded: boolean
      • version: number | undefined

      Returns number

    • Level 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.)

      Parameters

      • entry: LODGroupEntry
      • displayIdx: number
      • aspirationFresh: boolean
      • version: number | null

      Returns void

    • Settle-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.

      Parameters

      Returns void

    • Apply 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).

      Parameters

      Returns boolean

    • One 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.

      Parameters

      Returns void

    • Show 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.

      Parameters

      Returns boolean

    • A shown child's dissolve weight: null (⇒ 1) unless a partner is in flight.

      Parameters

      • isPrimary: boolean

      Returns number | null

    • Mark 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).

      Parameters

      Returns void

    • Band 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:

      • with the dissolve off (?noLodFade) — a hard swap was never preceded by a band, and that mode stays byte-identical;
      • during playback, where playbackAspiration alone decides what fits;
      • while the selected level is not ready yet, so the load being waited for is not slowed by a speculative one;
      • for a deferred GROUP level, whose activation attaches a subtree (loadChildren) with loaders of its own;
      • off-screen, under a lock, or for a footprint- or force-finest pick (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.

      Parameters

      • entry: LODGroupEntry
      • desired: number
      • metric: number | null
      • version: number | null
      • settled: boolean

      Returns void

    • Forget a group's preload visit; always null (nothing to advance).

      Parameters

      • path: string

      Returns null

    • The 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.

      Parameters

      • path: string
      • thresholds: readonly number[]
      • desired: number
      • metric: number

      Returns PreloadVisit | null

    • One 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.

      Parameters

      Returns void

    • Fold 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.

      Parameters

      • entry: LODGroupEntry
      • displayDims: readonly number[]
      • useLodBounds: boolean = false

      Returns BoundingBox | null

    • Index 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.

      Parameters

      Returns number

    • Advance (or start) the dissolve of entry toward displayIdx on this frame's clock (LodDissolves.advance), or null when the group should draw displayIdx alone.

      Parameters

      Returns LevelFade | null

    • Whether fromIdx can be drawn dissolving against toIdx this frame.

      Parameters

      • entry: LODGroupEntry
      • fromIdx: number
      • toIdx: number
      • version: number | null

      Returns boolean

    • Apply 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.

      Parameters

      Returns void

    • Coarsest 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.

      Parameters

      Returns number

    • Should 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:

      • the previous display is still ready and is FINER than the fallback (holding something coarser than the fallback would be a downgrade),
      • the fallback is a SEVERE downgrade — below 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),
      • the hold has not exhausted its 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.

      Parameters

      Returns number | undefined

    • The 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).

      Parameters

      Returns number | undefined

    • Index 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).

      Parameters

      Returns number

    • Kick 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).

      Parameters

      Returns void

    • Reload 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.

      Parameters

      Returns void

    • Effective-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:

      • it never hides or unloads anything already resident (a hidden layer draws nothing anyway, and retention keeps a re-show free);
      • the eager 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;
      • the walk is ancestor-aware via entry.groupObject (the hidden flag usually sits on an ANCESTOR layer/group, not on the lod_group itself);
      • the gate is re-evaluated every frame, so toggling the layer back on (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.

      Parameters

      Returns void

    • Shared 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

      • byte-eviction), which the old "failed until released" behaviour left stuck. The automatic caller separately gates loader-level archive faults. The per-child latch keeps concurrent failed branches individually addressable by Retry; an explicit retry clears that selected branch as it starts.

      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.

      Parameters

      Returns boolean

    • Stamp 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.

      Parameters

      Returns void

    • Bound 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.

      Parameters

      • camera: Camera
      • frustum: Frustum
      • displayDims: readonly number[]

      Returns void