Offline-capture quiescence: 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.
A latched archive fault skips all work that could start or wait for new
loads, but still waits for loads already in flight to finish committing.
The latch prevents any further automatic kick, so every other unmet
condition would be permanently false until an explicit or connectivity
retry clears it. An already-started load still clears loading in its
finally block and may commit geometry, so releasing the capture frame
before that transition would allow a one-frame pop.
A partition leaf load failure that does not latch an archive fault has no
success commit to refresh its stale stamp. The predicate remains false;
the capture drain's bounded timeout and consecutive-timeout latch are the
escape hatch for that failed part.
Per entry, in order:
Off-screen entries are skipped entirely.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.
Entries that are not EFFECTIVELY VISIBLE are skipped too (a layer
toggled off in the panel, or authored visible=false, anywhere up the
ancestor chain). They draw nothing, and — decisively —
LODGroupRegistry.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.
An entry with no child at the aspiration index is skipped —
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.
The aspiration must be isReady.
When freshness is tracked (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.
The aspiration's additive ladder must be complete, on BOTH available
signals. A lazy child's live 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.
No child that can affect the frame may be loading — an in-flight
commit can change what renders later. A hidden band preload is exempt
until it becomes the selected level.
No level dissolve may be in flight (LODGroupRegistry.isAnimating). The dissolve
is driven by WALL time, which an offline capture does not follow, so
filming one mid-way would make each exported frame depend on how long
the previous one took to render. Waiting turns it into a clean cut.
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.
Offline-capture quiescence: 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 onerequestAnimationFrameper 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.
loadingin itsfinallyblock and may commit geometry, so releasing the capture frame before that transition would allow a one-frame pop.Per entry, in order:
offScreenmeans 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 —LODGroupRegistry.kickDeferredLoadIfVisiblerefuses to START a deferred load while the group is hidden, whereas the selector's frustum test is purely geometric and still records a finedesiredChildIndexfor it. Without this skip such an entry hasdesired !== activewith 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.childrencan legitimately be EMPTY (every level failed itsgetObjectByNameattach inload-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-rangedesiredChildIndexis the same shape but is NOT skipped — the rest of the entry is still checked and only the missingdesiredis 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 isfailed, or absent (an out-of-rangedesired, 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 (seeLODGroupEntry.desiredChildIndex); it is also the whole in-flight load. Afailedlevel 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.getViewVersionwired), the aspiration must be FRESH for the current view version — via the group-aware childFreshAndCount, not the leaf-onlyisFresh, so a deferredkind=partitionsubtree 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'ssubtreeLadderComplete— the commit-timecommittedLadderCompletestamp, taken from the child itself for a tracked LEAF and folded over the visible stamped leaves of a deferred GROUP child (a nestedkind=partition/kind=lodsubtree — theoverviewrecipe'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 later. A hidden band preload is exempt until it becomes the selected level.LODGroupRegistry.isAnimating). The dissolve is driven by WALL time, which an offline capture does not follow, so filming one mid-way would make each exported frame depend on how long the previous one took to render. Waiting turns it into a clean cut.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 withfor…ofand resolves freshness throughchildFreshAndCount, which returns a fresh object per entry. That cost is genuinely irrelevant here.