Summary
The chunked working-set cap is computed once from the base volume's residency budget and then the same chunk indices are requested from every combined-overlay mirror. Combined overlays get a smaller budget (OVERLAY_RESIDENCY_FRACTION of the base budget, divided by overlay count), so the capped set can exceed what a mirror can hold. Because every capped chunk is stamped needed-this-frame, the same-frame eviction guard stops the mirror from shedding any of them, and it stays over budget.
Where
Both backends, in the frustum and slice working-set paths:
packages/niivue/src/gl/render.ts: _requestChunksInFrustum and _requestVisibleChunksInView call chunkIndicesForResidentBudget(entry.plan, ordered, entry.manager.budgetBytes, ...) and then for (const m of mirrors) m.requestUpload(ci).
packages/niivue/src/wgpu/render.ts: same structure.
The budget split is in _updateCombinedOverlayChunked.
Proposed fix
Cap against the tightest participating manager before mirroring: run chunkIndicesForResidentBudget for the base and for each mirror (each with its own budgetBytes and its own getChunk(ci)?.hasGradient pricing), and request the intersection, or the shortest prefix of ordered that fits all of them. Add a ChunkResidencyManager test with one mirror at a fraction of the base budget and assert the mirror never exceeds budgetBytes after a frame that touches every capped chunk.
Context
Pre-existing on main; surfaced by Copilot's review of #167, which changed only the per-chunk price (4 bytes/voxel for unlit chunks) and not the mirroring. The ratio between the capped set and a mirror's budget is unchanged by #167 because mirrors share the renderer's lighting state.
Summary
The chunked working-set cap is computed once from the base volume's residency budget and then the same chunk indices are requested from every combined-overlay mirror. Combined overlays get a smaller budget (
OVERLAY_RESIDENCY_FRACTIONof the base budget, divided by overlay count), so the capped set can exceed what a mirror can hold. Because every capped chunk is stamped needed-this-frame, the same-frame eviction guard stops the mirror from shedding any of them, and it stays over budget.Where
Both backends, in the frustum and slice working-set paths:
packages/niivue/src/gl/render.ts:_requestChunksInFrustumand_requestVisibleChunksInViewcallchunkIndicesForResidentBudget(entry.plan, ordered, entry.manager.budgetBytes, ...)and thenfor (const m of mirrors) m.requestUpload(ci).packages/niivue/src/wgpu/render.ts: same structure.The budget split is in
_updateCombinedOverlayChunked.Proposed fix
Cap against the tightest participating manager before mirroring: run
chunkIndicesForResidentBudgetfor the base and for each mirror (each with its ownbudgetBytesand its owngetChunk(ci)?.hasGradientpricing), and request the intersection, or the shortest prefix oforderedthat fits all of them. Add aChunkResidencyManagertest with one mirror at a fraction of the base budget and assert the mirror never exceedsbudgetBytesafter a frame that touches every capped chunk.Context
Pre-existing on
main; surfaced by Copilot's review of #167, which changed only the per-chunk price (4 bytes/voxel for unlit chunks) and not the mirroring. The ratio between the capped set and a mirror's budget is unchanged by #167 because mirrors share the renderer's lighting state.