Partition gating: per-part frustum and slice culling of kind=partition
groups, the targeted resync of parts that re-enter the frustum, and lazy
(B4) activation of deferred parts.
A kind=partition group's parts are tested every frame against a frustum
padded by config.lod.partitionFrustumMargin; a part outside it (or
outside the COMMITTED slice) is hidden and stamped
userData.partitionFrustumVisible = false, which the scene loader's sweep
and refinement read to skip its loaders. Because a culled part misses slice
updates, its RE-ENTRY requests a resync of exactly that part's loaders
(deps.requestReprocess(partPaths), coalesced per wrapper across frames
and gated on isUpdateInProgress) — unless every leaf of the part is
already committed for the current view with a complete ladder. The resync
re-sweeps under the UNCHANGED view version: bumping it would read every lazy
fine level scene-wide as stale and drop all groups to coarse on camera
motion. A deferred part's activation rides the same resync.
The registry owns one gate and drives it each frame: PartitionGate.beginFrame gates every visible wrapper before the LOD pass (so
a lod_group nested in a part sees this frame's cull), PartitionGate.endFrame re-gates the wrappers the LOD pass revealed and
flushes the pending resyncs.
Liveness (TickDemand): the loop must keep ticking while a visible
resync waits for the loader — only when requestReprocess is wired, since
nothing else can serve it. A deferred part's activation request that no pass
reaches needs no ticks: one wake at its expiry
(config.lod.lazyActivationRequestTimeoutMs, backed off per unanswered
request — retry-wakes.ts) asks again.
Partition gating: per-part frustum and slice culling of
kind=partitiongroups, the targeted resync of parts that re-enter the frustum, and lazy (B4) activation of deferred parts.A
kind=partitiongroup's parts are tested every frame against a frustum padded byconfig.lod.partitionFrustumMargin; a part outside it (or outside the COMMITTED slice) is hidden and stampeduserData.partitionFrustumVisible = false, which the scene loader's sweep and refinement read to skip its loaders. Because a culled part misses slice updates, its RE-ENTRY requests a resync of exactly that part's loaders (deps.requestReprocess(partPaths), coalesced per wrapper across frames and gated onisUpdateInProgress) — unless every leaf of the part is already committed for the current view with a complete ladder. The resync re-sweeps under the UNCHANGED view version: bumping it would read every lazy fine level scene-wide as stale and drop all groups to coarse on camera motion. A deferred part's activation rides the same resync.The registry owns one gate and drives it each frame: PartitionGate.beginFrame gates every visible wrapper before the LOD pass (so a lod_group nested in a part sees this frame's cull), PartitionGate.endFrame re-gates the wrappers the LOD pass revealed and flushes the pending resyncs.
Liveness (TickDemand): the loop must keep ticking while a visible resync waits for the loader — only when
requestReprocessis wired, since nothing else can serve it. A deferred part's activation request that no pass reaches needs no ticks: one wake at its expiry (config.lod.lazyActivationRequestTimeoutMs, backed off per unanswered request —retry-wakes.ts) asks again.