@voluma/vlam 0.3.2 → 0.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"streaming.js","sources":["../src/lib/streaming/streamed-splat-mesh-utils.ts","../src/lib/streaming/lod-manifest.ts","../src/lib/streaming/lod-source.ts","../src/lib/streaming/dataset-source.ts","../src/lib/formats/rad/frontier-worker-protocol.ts","../src/lib/streaming/streamed-splat-mesh.ts","../src/lib/streaming/budget-governor.ts","../src/lib/streaming/camera-budget-governor.ts","../src/lib/streaming/chunk-fetch-scheduler.ts","../src/lib/streaming/chunk-cache-budget.ts"],"sourcesContent":["/** Scheduling and data helpers shared by the streamed mesh implementation. */\nimport type { SplatRange } from '../core/splat-mesh';\nimport type { SplatData } from '../core/splat-data';\nimport type { LodRun } from './lod-scheduler';\nimport { resolveCpuCacheBytes } from '../core/splat-budget';\n\nconst APPEND_CAP = 32_000;\n\n/** One resident entry: the run description plus its pool handle. */\ntype ResidentEntry = [string, { run: LodRun; handle: SplatRange }];\n\n/** A set of adds and removals covering one contiguous leaf region. */\nexport interface SwapGroup {\n adds: LodRun[];\n removes: ResidentEntry[];\n leafStart: number;\n leafEnd: number;\n addCount: number;\n}\n\n/**\n * One swap group per run for the startup hold. The mesh is invisible, so L0\n * cell-atomicity is unnecessary; committing slice-by-slice lets the hold finish\n * as soon as the capped set is resident instead of waiting on sibling subchunks.\n */\nexport function buildHoldSwapGroups(toAdd: readonly LodRun[]): SwapGroup[] {\n return toAdd\n .map((run) => ({\n adds: [run],\n removes: [] as ResidentEntry[],\n leafStart: run.leafStart,\n leafEnd: run.leafEnd,\n addCount: run.count,\n }))\n .sort((a, b) => a.leafStart - b.leafStart);\n}\n\n/**\n * Groups adds and removals into visible-cell transactions.\n *\n * Classic LCC (`coverageGroup` set on every run): one transaction per sub-leaf\n * interval at every resolved level (including L0), so a cached slice can\n * replace its own prior coverage while siblings still fetch. Hierarchical\n * sources without coverage groups keep interval-overlap grouping.\n */\nexport function buildSwapGroups(toAdd: LodRun[], toRemove: ResidentEntry[]): SwapGroup[] {\n const coverageRuns = [...toAdd, ...toRemove.map(([, entry]) => entry.run)];\n if (coverageRuns.length > 0 && coverageRuns.every((run) => run.coverageGroup !== undefined)) {\n type Bucket = { adds: LodRun[]; removes: ResidentEntry[] };\n const buckets = new Map<number, Bucket>();\n const touch = (coverageGroup: number): Bucket => {\n let bucket = buckets.get(coverageGroup);\n if (!bucket) {\n bucket = { adds: [], removes: [] };\n buckets.set(coverageGroup, bucket);\n }\n return bucket;\n };\n for (const run of toAdd) {\n touch(run.coverageGroup as number).adds.push(run);\n }\n for (const entry of toRemove) {\n touch(entry[1].run.coverageGroup as number).removes.push(entry);\n }\n\n const groups: SwapGroup[] = [];\n for (const bucket of buckets.values()) {\n // Every classic-LCC cut is one transaction per sub-leaf - including\n // resolved L0. Quality cells split into dozens of finest slices; waiting\n // for the whole cell before any swap left near detail stuck on coarse\n // (green) while a few in-flight fetches churned across siblings forever.\n // Per-slice: a ready L0 patch replaces its own prior coverage immediately;\n // siblings keep theirs until their chunks land.\n const byLeaf = new Map<string, SwapGroup>();\n const leafKey = (start: number, end: number): string => `${start}:${end}`;\n const ensure = (start: number, end: number): SwapGroup => {\n const key = leafKey(start, end);\n let group = byLeaf.get(key);\n if (!group) {\n group = { adds: [], removes: [], leafStart: start, leafEnd: end, addCount: 0 };\n byLeaf.set(key, group);\n }\n return group;\n };\n for (const run of bucket.adds) {\n const group = ensure(run.leafStart, run.leafEnd);\n group.adds.push(run);\n group.addCount += run.count;\n }\n for (const entry of bucket.removes) {\n const run = entry[1].run;\n const group = ensure(run.leafStart, run.leafEnd);\n group.removes.push(entry);\n group.leafStart = Math.min(group.leafStart, run.leafStart);\n group.leafEnd = Math.max(group.leafEnd, run.leafEnd);\n }\n groups.push(...byLeaf.values());\n }\n return groups.sort((a, b) => a.leafStart - b.leafStart);\n }\n\n const items = [\n ...toAdd.map((run) => ({\n start: run.leafStart,\n end: run.leafEnd,\n add: run,\n remove: undefined as ResidentEntry | undefined,\n })),\n ...toRemove.map((entry) => ({\n start: entry[1].run.leafStart,\n end: entry[1].run.leafEnd,\n add: undefined as LodRun | undefined,\n remove: entry,\n })),\n ].sort((a, b) => a.start - b.start || b.end - a.end);\n\n // Sorted interval sweep finds the same overlap-connected components in\n // O(n log n). Longer equal-start intervals come first so an octree parent\n // opens the full component before its adjacent children are visited.\n const groups: SwapGroup[] = [];\n for (const item of items) {\n const last = groups[groups.length - 1];\n if (!last || item.start >= last.leafEnd) {\n groups.push({\n adds: [],\n removes: [],\n leafStart: item.start,\n leafEnd: item.end,\n addCount: 0,\n });\n }\n const group = groups[groups.length - 1] as SwapGroup;\n group.leafEnd = Math.max(group.leafEnd, item.end);\n if (item.add) {\n group.adds.push(item.add);\n group.addCount += item.add.count;\n }\n if (item.remove) group.removes.push(item.remove);\n }\n return groups;\n}\n\n/**\n * Groups that only add coverage first (coarse before fine), then the ones that\n * retire it - pure removals last of all.\n *\n * The order is what makes the wave gate in `reschedule` decidable: by the time a\n * retiring group is reached, every purely additive group has already had its\n * turn, so \"have the replacements landed?\" is answered rather than guessed.\n * Pure removals free pool rows, so they go last, where they relieve the\n * over-draw the gate deliberately trades for.\n *\n * Exported for tests only - not part of the public API surface.\n */\nexport function groupPriority(group: SwapGroup): number {\n if (group.adds.length === 0) return 1000;\n const finest = -Math.max(...group.adds.map((run) => run.level));\n return group.removes.length === 0 ? -1000 + finest : finest;\n}\n\n/** True when every transaction belongs to classic LCC physical coverage. */\nexport function isClassicLccSwapSet(groups: readonly SwapGroup[]): boolean {\n return (\n groups.length > 0 &&\n groups.every((group) =>\n [...group.adds, ...group.removes.map(([, entry]) => entry.run)].every(\n (run) => run.coverageGroup !== undefined,\n ),\n )\n );\n}\n\n/**\n * Orders classic display transactions by what the camera can see. This is\n * separate from {@link groupPriority}: its global coarse-before-retirement\n * wave preserves hierarchical RAD coverage, whereas LCC has an independent\n * coarse shell for every L1+ slice and can safely commit a ready centre slice.\n * Exported for tests only - not part of the public API surface.\n */\nexport function compareClassicSwapGroups(a: SwapGroup, b: SwapGroup): number {\n const describe = (\n group: SwapGroup,\n ): {\n removeOnly: number;\n view: number;\n finest: number;\n screen: number;\n distance: number;\n } => {\n const runs = [...group.adds, ...group.removes.map(([, entry]) => entry.run)];\n return {\n removeOnly: group.adds.length === 0 ? 1 : 0,\n view: runs.some((run) => run.inView !== false) ? 0 : 1,\n finest: group.adds.some((run) => run.level === 0) ? 0 : 1,\n screen: Math.min(...runs.map((run) => run.screenImportance ?? Number.POSITIVE_INFINITY)),\n distance: Math.min(...runs.map((run) => run.distance ?? Number.POSITIVE_INFINITY)),\n };\n };\n const aa = describe(a);\n const bb = describe(b);\n return (\n aa.removeOnly - bb.removeOnly ||\n aa.view - bb.view ||\n aa.finest - bb.finest ||\n aa.screen - bb.screen ||\n aa.distance - bb.distance ||\n a.leafStart - b.leafStart\n );\n}\n\n/** One classic-path chunk want, ranked before {@link StreamedSplatMesh} issues it. */\nexport type ClassicFetchPhase =\n 'environment' | 'finest-target' | 'coverage' | 'target' | 'background';\n\nexport interface ClassicFetchWant {\n /** Cross-mesh scheduler kind derived from {@link phase}. */\n kind: 'priority' | 'base';\n /** Internal classic ranking tier (not exported on ChunkFetchKind). */\n phase: ClassicFetchPhase;\n distance: number;\n level: number;\n /** Prefer in-frustum wants; false means behind-camera / out of view. */\n inView: boolean;\n /** Classic LCC physical cell; `-1` when the source has no coverage groups. */\n coverageGroup: number;\n /** Source interval: L1+ uses this as its progressive display transaction. */\n leafStart: number;\n /** One past {@link leafStart}. */\n leafEnd: number;\n /** Fetch-only angular distance from the screen centre; smaller wins. */\n screenImportance: number;\n /**\n * Min distance among pending wants in this coverage group. Stamped in\n * {@link stampClassicFetchGroups} so a split cell's slices stay together.\n */\n groupDistance: number;\n /** Pending file count in this coverage group; denser near cells win ties. */\n groupPending: number;\n /** True when any pending run in this group is in-frustum. */\n groupInView: boolean;\n /** Best screen-centre score among runs in this fetch transaction. */\n groupScreenImportance: number;\n /** True when this transaction contains a finest-level (L0) target. */\n groupFinest: boolean;\n /** Stable aggregate key: L0 cell, L1+ slice, or singleton file. */\n groupId: string;\n /** Visible(0) / near-out-of-view(1) / background(2) ranking bucket. */\n groupClass: 0 | 1 | 2;\n}\n\nfunction classicFetchPhaseRank(phase: ClassicFetchPhase): number {\n switch (phase) {\n case 'environment':\n return -1;\n case 'finest-target':\n return 0;\n case 'coverage':\n return 1;\n case 'target':\n return 2;\n default:\n return 3;\n }\n}\n\nfunction kindForClassicFetchPhase(phase: ClassicFetchPhase): ClassicFetchWant['kind'] {\n return phase === 'background' ? 'base' : 'priority';\n}\n\n/**\n * Fetch identity mirrors the display transaction. Finest L0 must arrive as a\n * whole physical cell; L1+ is intentionally progressive, one leaf slice at a\n * time, so hidden siblings cannot hold the visible slice in the queue.\n */\nfunction classicFetchGroupKey(\n want: Pick<ClassicFetchWant, 'phase' | 'coverageGroup' | 'leafStart' | 'leafEnd'>,\n file: number,\n): string {\n if (want.coverageGroup < 0) return `file:${file}`;\n if (want.phase === 'finest-target') return `cell:${want.coverageGroup}`;\n return `slice:${want.coverageGroup}:${want.leafStart}:${want.leafEnd}`;\n}\n\n/**\n * Fills {@link ClassicFetchWant.groupDistance} / `groupPending` so ranking can\n * finish one near cell before sprinkling bandwidth across neighbors.\n */\nexport function stampClassicFetchGroups(\n pending: Map<number, ClassicFetchWant>,\n lodBaseDistance = 10,\n forceNearPriority = false,\n): void {\n const near = nearDisplayDistance(lodBaseDistance);\n const aggregates = new Map<\n string,\n { distance: number; count: number; inView: boolean; screenImportance: number; finest: boolean }\n >();\n for (const [file, want] of pending) {\n if (want.phase === 'environment') continue;\n const key = classicFetchGroupKey(want, file);\n const prev = aggregates.get(key);\n if (!prev) {\n aggregates.set(key, {\n distance: want.distance,\n count: 1,\n inView: want.inView,\n screenImportance: want.screenImportance,\n finest: want.phase === 'finest-target',\n });\n continue;\n }\n if (want.distance < prev.distance) prev.distance = want.distance;\n if (want.inView) prev.inView = true;\n if (want.screenImportance < prev.screenImportance) {\n prev.screenImportance = want.screenImportance;\n }\n if (want.phase === 'finest-target') prev.finest = true;\n prev.count++;\n }\n for (const [file, want] of pending) {\n if (want.phase === 'environment') {\n want.kind = 'priority';\n want.groupDistance = 0;\n want.groupPending = 1;\n want.groupInView = true;\n want.groupScreenImportance = Number.NEGATIVE_INFINITY;\n want.groupFinest = true;\n want.groupId = 'environment';\n want.groupClass = 0;\n continue;\n }\n const groupId = classicFetchGroupKey(want, file);\n const agg = aggregates.get(groupId);\n if (!agg) continue;\n const groupClass: 0 | 1 | 2 = agg.inView ? 0 : agg.distance <= near ? 1 : 2;\n const kind: ClassicFetchWant['kind'] =\n groupClass === 0 ? 'priority' : groupClass === 1 && forceNearPriority ? 'priority' : 'base';\n want.groupDistance = agg.distance;\n want.groupPending = agg.count;\n want.groupInView = agg.inView;\n want.groupScreenImportance = agg.screenImportance;\n want.groupFinest = agg.finest;\n want.groupId = groupId;\n want.groupClass = groupClass;\n want.kind = kind;\n }\n}\n\n/**\n * Distance inside which classic LCC treats a cut as short-range for fetch\n * ranking. Matches {@link LodSourceOptions.lodBaseDistance} (default 10).\n */\nexport function nearDisplayDistance(lodBaseDistance: number, _lodMultiplier = 2): number {\n return lodBaseDistance;\n}\n\n/**\n * True when this deferred group is waiting on resolved finest (L0) - never\n * flash coarsest discs as a stand-in.\n */\nexport function isWaitingOnFinest(group: SwapGroup): boolean {\n return group.adds.some((run) => run.level === 0);\n}\n\n/**\n * Classic fetch phase for a **resolved** desired run. Ambition-only levels are\n * never requested - callers pass runs from `computeDesiredRuns` only.\n */\nexport function classicFetchPhaseForDesired(\n run: LodRun,\n lodBaseDistance: number,\n lodMultiplier = 2,\n): ClassicFetchPhase {\n void lodMultiplier;\n if (run.level === 0) return 'finest-target';\n const distance = run.distance ?? Number.POSITIVE_INFINITY;\n if (run.inView === false && distance > nearDisplayDistance(lodBaseDistance)) return 'background';\n return 'target';\n}\n\n/** Classic fetch phase for a pinned coarsest substitute of an L1+ gap. */\nexport function classicFetchPhaseForCoverage(\n run: LodRun,\n lodBaseDistance: number,\n lodMultiplier = 2,\n): ClassicFetchPhase {\n void lodMultiplier;\n const distance = run.distance ?? Number.POSITIVE_INFINITY;\n if (run.inView === false && distance > nearDisplayDistance(lodBaseDistance)) return 'background';\n return 'coverage';\n}\n\n/**\n * Maps a desired run to the cross-mesh fetch kind. Prefer\n * {@link classicFetchPhaseForDesired} for ranking; this remains for tests.\n */\nexport function classicFetchKindForDesired(\n run: LodRun,\n lodBaseDistance: number,\n lodMultiplier = 2,\n): ClassicFetchWant['kind'] {\n return kindForClassicFetchPhase(classicFetchPhaseForDesired(run, lodBaseDistance, lodMultiplier));\n}\n\nexport function enqueueClassicFetch(\n pending: Map<number, ClassicFetchWant>,\n file: number,\n phase: ClassicFetchPhase,\n run: LodRun,\n): void {\n const distance = run.distance ?? Number.POSITIVE_INFINITY;\n const level = run.level;\n const inView = run.inView !== false;\n const coverageGroup = run.coverageGroup ?? -1;\n const screenImportance = run.screenImportance ?? Number.POSITIVE_INFINITY;\n const kind = kindForClassicFetchPhase(phase);\n const prev = pending.get(file);\n if (!prev) {\n const groupId = classicFetchGroupKey(\n { phase, coverageGroup, leafStart: run.leafStart, leafEnd: run.leafEnd },\n file,\n );\n pending.set(file, {\n kind,\n phase,\n distance,\n level,\n inView,\n coverageGroup,\n leafStart: run.leafStart,\n leafEnd: run.leafEnd,\n screenImportance,\n groupDistance: distance,\n groupPending: 1,\n groupInView: inView,\n groupScreenImportance: screenImportance,\n groupFinest: phase === 'finest-target',\n groupId,\n // Provisional; finalized in stampClassicFetchGroups().\n groupClass: 2,\n });\n return;\n }\n const betterPhase = classicFetchPhaseRank(phase) < classicFetchPhaseRank(prev.phase);\n const samePhaseNearer =\n phase === prev.phase &&\n (distance < prev.distance || (distance === prev.distance && level < prev.level));\n const samePhaseBetterView =\n phase === prev.phase &&\n distance === prev.distance &&\n level === prev.level &&\n inView &&\n !prev.inView;\n if (betterPhase || samePhaseNearer || samePhaseBetterView) {\n pending.set(file, {\n kind,\n phase,\n distance,\n level,\n inView: inView || prev.inView,\n coverageGroup: coverageGroup >= 0 ? coverageGroup : prev.coverageGroup,\n leafStart: run.leafStart,\n leafEnd: run.leafEnd,\n screenImportance,\n groupDistance: Math.min(distance, prev.groupDistance),\n groupPending: prev.groupPending,\n groupInView: inView || prev.groupInView,\n groupScreenImportance: Math.min(screenImportance, prev.groupScreenImportance),\n groupFinest: phase === 'finest-target' || prev.groupFinest,\n groupId: prev.groupId,\n groupClass: prev.groupClass,\n });\n }\n}\n\n/**\n * Sort by coverage group first: visible groups, then near out-of-view, then\n * background. Within a group: coverage → resolved target (L0/L1+) → background.\n */\nexport function compareClassicFetches(\n a: ClassicFetchWant,\n b: ClassicFetchWant,\n fileA: number,\n fileB: number,\n): number {\n const groupDistA = a.groupDistance ?? a.distance;\n const groupDistB = b.groupDistance ?? b.distance;\n const pendingA = a.groupPending ?? 1;\n const pendingB = b.groupPending ?? 1;\n const groupA = a.groupId ?? classicFetchGroupKey(a, fileA);\n const groupB = b.groupId ?? classicFetchGroupKey(b, fileB);\n const classA = a.groupClass ?? (a.inView ? 0 : 2);\n const classB = b.groupClass ?? (b.inView ? 0 : 2);\n const screenA = a.groupScreenImportance ?? a.screenImportance;\n const screenB = b.groupScreenImportance ?? b.screenImportance;\n const finestA = a.groupFinest ? 0 : 1;\n const finestB = b.groupFinest ? 0 : 1;\n const rankInGroup = (phase: ClassicFetchPhase): number => {\n if (phase === 'coverage') return 0;\n if (phase === 'background') return 2;\n return 1; // target + finest-target\n };\n const envA = a.phase === 'environment' ? 0 : 1;\n const envB = b.phase === 'environment' ? 0 : 1;\n return (\n envA - envB ||\n classA - classB ||\n finestA - finestB ||\n screenA - screenB ||\n groupDistA - groupDistB ||\n pendingB - pendingA ||\n (groupA < groupB ? -1 : groupA > groupB ? 1 : 0) ||\n rankInGroup(a.phase) - rankInGroup(b.phase) ||\n a.level - b.level ||\n fileA - fileB\n );\n}\n\n/** Zero-copy view of one contiguous splat range within a decoded chunk.\n * Exported for tests only - not part of the public API surface. */\nexport function sliceSplatData(chunk: SplatData, offset: number, count: number): SplatData {\n // A manifest can over-declare a range against the chunk it points into;\n // subarray would silently clamp, yielding a SplatData whose count exceeds\n // its arrays and corrupting the shared pool. Fail the chunk instead.\n if (offset < 0 || count < 0 || offset + count > chunk.count) {\n throw new Error(\n `Splat range [${offset}, ${offset + count}) exceeds its chunk's ${chunk.count} splats; ` +\n 'the manifest and chunk data disagree.',\n );\n }\n const sh = chunk.shPacked;\n return {\n count,\n positions: chunk.positions.subarray(offset * 3, (offset + count) * 3),\n colors: chunk.colors.subarray(offset * 4, (offset + count) * 4),\n covariances: chunk.covariances.subarray(offset * 6, (offset + count) * 6),\n // Per-splat SH is splat-major, so it slices like everything else. Palette\n // shN (`chunk.sh`) is deliberately not carried: its labels index a\n // per-file codebook the shared pool has no way to hold.\n ...(sh\n ? {\n shPacked: {\n ...sh,\n packed: sh.packed.subarray(\n offset * shWordsPerSplat(sh.bands),\n (offset + count) * shWordsPerSplat(sh.bands),\n ),\n },\n }\n : {}),\n // Per-splat frontier `parent_size` (foveated `.rad`) slices splat-major like\n // the rest; uploaded into `covarianceB.w` by the pool.\n ...(chunk.frontierParent\n ? { frontierParent: chunk.frontierParent.subarray(offset, offset + count) }\n : {}),\n };\n}\n\n/** Packed SH words each splat carries at a band count (1, 2 or 3). */\nfunction shWordsPerSplat(bands: 1 | 2 | 3): number {\n return bands === 1 ? 3 : bands === 2 ? 8 : 15;\n}\n\nexport function chunkBytes(data: SplatData): number {\n return (\n data.positions.byteLength +\n data.colors.byteLength +\n data.covariances.byteLength +\n // SH is the largest part of an LCC Quality chunk (64 B/splat against the\n // base 32); omitting it would let the CPU cache run ~3x over its cap.\n (data.shPacked?.packed.byteLength ?? 0)\n );\n}\n\n/** An abort signal's reason as an Error, whatever the caller aborted with. */\nexport function abortReason(signal: AbortSignal): Error {\n const reason: unknown = signal.reason;\n return reason instanceof Error ? reason : new DOMException('Aborted', 'AbortError');\n}\n\nexport function validateAppendCap(value: number | undefined): number {\n const cap = value ?? APPEND_CAP;\n if (!Number.isInteger(cap) || cap <= 0) {\n throw new RangeError('StreamedSplatMesh maxSplatsPerSwap must be a positive integer.');\n }\n return cap;\n}\n\n/** Validates Spark's per-mesh `lodScale`; `undefined` means the neutral 1. */\nexport function validateLodScale(value: number | undefined): number {\n const scale = value ?? 1;\n if (!Number.isFinite(scale) || scale <= 0) {\n throw new RangeError('StreamedSplatMesh lodScale must be a positive finite number.');\n }\n return scale;\n}\n\n/**\n * The decoded-chunk cache cap.\n *\n * Delegates to {@link resolveCpuCacheBytes} rather than re-deriving it. The\n * local copy this replaces read `navigator.deviceMemory ?? 4`, which looks\n * equivalent but is not: **iOS never reports `deviceMemory` at all**, so every\n * iPhone took the `4` fallback and a 128 MiB cache, where the profile-aware\n * policy gives a memory-less device 32 MiB. That is 96 MiB of decoded chunks\n * held on the one platform whose tab gets killed for holding too much.\n */\nexport function defaultCpuCacheBytes(): number {\n return resolveCpuCacheBytes();\n}\n","import * as THREE from 'three/webgpu';\nimport type { SplatDatasetSource } from './dataset-source';\n\n/**\n * Parser for the Streamed SOG manifest (`lod-meta.json`, version 1).\n *\n * The manifest describes a large scene as a binary spatial tree whose leaves\n * each cover one region at several levels of detail. LOD level 0 is the\n * finest; higher levels are progressively coarser. Each leaf references, per\n * level, a contiguous `[offset, offset + count)` splat range inside one chunk\n * file (an unbundled SOG v2 directory). One chunk file serves many leaves.\n *\n * Spec:\n * https://developer.playcanvas.com/user-manual/gaussian-splatting/formats/streamed-sog/\n */\n\n/** A leaf's splat range at one LOD level. */\nexport interface LodRange {\n /** Index into {@link LodManifest.chunkUrls}. */\n readonly file: number;\n /** First splat row within that chunk's decoded arrays. */\n readonly offset: number;\n /** Number of splats. */\n readonly count: number;\n}\n\n/** A spatial region, present at one or more LOD levels. */\nexport interface LodLeaf {\n readonly bounds: THREE.Box3;\n /** Range per level; `lods[level]` is undefined if absent at that level. */\n readonly lods: readonly (LodRange | undefined)[];\n /**\n * Leaves with the same group are budgeted atomically. Formats may use this\n * when one spatial region is split into several independently streamed\n * ranges: the ranges may arrive separately, but must select one LOD cut.\n */\n readonly budgetGroup?: number;\n}\n\nexport interface LodManifest {\n /** Leaves in tree-traversal order (load-bearing: adjacent leaves within a\n * chunk have contiguous ranges, which the scheduler coalesces into runs). */\n readonly leaves: readonly LodLeaf[];\n /** Absolute chunk-directory URLs, indexed by {@link LodRange.file}. */\n readonly chunkUrls: readonly string[];\n /**\n * Chunk directories relative to the manifest, aligned with {@link chunkUrls}.\n * Only the unbundled-SOG layout has them; formats whose chunks are single\n * files (LCC of either generation) leave this out.\n */\n readonly chunkDirectories?: readonly string[];\n /** Total splats per LOD level (index = level). */\n readonly counts: readonly number[];\n readonly lodLevels: number;\n /** Root bounding box of the whole scene. */\n readonly bounds: THREE.Box3;\n}\n\ninterface RawNode {\n bound: { min: [number, number, number]; max: [number, number, number] };\n children?: [RawNode, RawNode];\n lods?: Record<string, { file: number; offset: number; count: number }>;\n}\n\ninterface RawManifest {\n version: number;\n counts: number[];\n lodLevels: number;\n filenames: string[];\n tree: RawNode;\n}\n\n/**\n * Parses a `lod-meta.json` object into a flat, render-ready manifest.\n *\n * @param json - The parsed manifest JSON.\n * @param baseUrl - URL of the manifest, used to resolve chunk directories.\n * @throws {Error} on an unsupported version or malformed tree.\n */\nexport function parseLodManifest(json: unknown, source: SplatDatasetSource): LodManifest {\n const raw = json as RawManifest;\n if (typeof raw !== 'object' || raw === null) {\n throw new Error('Streamed SOG manifest is not a JSON object.');\n }\n if (raw.version !== 1) {\n throw new Error(`Unsupported Streamed SOG manifest version: ${raw.version} (expected 1).`);\n }\n if (!Array.isArray(raw.filenames) || raw.filenames.some((name) => typeof name !== 'string')) {\n throw new Error('Streamed SOG manifest \"filenames\" must be an array of strings.');\n }\n if (\n !Array.isArray(raw.counts) ||\n raw.counts.some((count) => !Number.isSafeInteger(count) || count < 0)\n ) {\n throw new Error('Streamed SOG manifest \"counts\" must be an array of non-negative integers.');\n }\n if (!Number.isSafeInteger(raw.lodLevels) || raw.lodLevels < 1 || raw.lodLevels > 64) {\n throw new Error(`Streamed SOG manifest declares an invalid lodLevels: ${raw.lodLevels}.`);\n }\n\n // \"0_0/meta.json\" -> the chunk *directory* \"0_0/\". Over HTTP that resolves\n // to a real URL the worker fetches images beneath; from a dropped folder\n // there is nothing to resolve against, so the directory's files travel with\n // the chunk instead (see StreamedScene.chunkOptions).\n const directories = raw.filenames.map((name) => name.replace(/\\/?meta\\.json$/, '/'));\n const chunkUrls = directories.map(\n (directory) => source.resolve(directory) ?? `${source.manifestUrl}#${directory}`,\n );\n\n const leaves: LodLeaf[] = [];\n const lodLevels = raw.lodLevels;\n // Iterative depth-first traversal (explicit stack, children pushed in\n // reverse so leaf order is unchanged): a hostile manifest with a\n // pathologically deep tree must not overflow the call stack.\n const stack: RawNode[] = [raw.tree];\n while (stack.length > 0) {\n const node = stack.pop() as RawNode;\n if (typeof node !== 'object' || node === null) {\n throw new Error('Streamed SOG manifest tree contains a malformed node.');\n }\n if (node.children) {\n if (!Array.isArray(node.children) || node.children.length !== 2) {\n throw new Error('Streamed SOG manifest tree node must have exactly two children.');\n }\n stack.push(node.children[1], node.children[0]);\n continue;\n }\n const lods: (LodRange | undefined)[] = new Array<LodRange | undefined>(lodLevels).fill(\n undefined,\n );\n for (const [levelKey, range] of Object.entries(node.lods ?? {})) {\n const level = Number(levelKey);\n if (level >= 0 && level < lodLevels) {\n assertSaneRange(range, raw.filenames.length);\n lods[level] = range;\n }\n }\n leaves.push({ bounds: boxFromBound(node.bound), lods });\n }\n\n return {\n leaves,\n chunkUrls,\n chunkDirectories: directories,\n counts: raw.counts,\n lodLevels,\n bounds: boxFromBound(raw.tree.bound),\n };\n}\n\n/** Asserts a leaf's untrusted splat range is sane before it drives pool I/O. */\nfunction assertSaneRange(range: LodRange, fileCount: number): void {\n if (\n typeof range !== 'object' ||\n range === null ||\n !Number.isSafeInteger(range.file) ||\n range.file < 0 ||\n range.file >= fileCount ||\n !Number.isSafeInteger(range.offset) ||\n range.offset < 0 ||\n !Number.isSafeInteger(range.count) ||\n range.count < 0\n ) {\n throw new Error(\n 'Streamed SOG manifest leaf declares an invalid LOD range ' +\n `(file ${range?.file}, offset ${range?.offset}, count ${range?.count}).`,\n );\n }\n}\n\nfunction boxFromBound(bound: RawNode['bound']): THREE.Box3 {\n return new THREE.Box3(\n new THREE.Vector3(bound.min[0], bound.min[1], bound.min[2]),\n new THREE.Vector3(bound.max[0], bound.max[1], bound.max[2]),\n );\n}\n","import * as THREE from 'three/webgpu';\nimport { LodScheduler, type LodRun } from './lod-scheduler';\nimport { parseLodManifest, type LodManifest } from './lod-manifest';\nimport type { ChunkFileFormat } from '../loaders/loading';\nimport type { RadChunkRangeRequest } from '../loaders/load-worker-protocol';\nimport type { LccChunkParams } from '../formats/lcc/parse-lcc';\nimport type { SplatData } from '../core/splat-data';\nimport type { SplatDatasetSource } from './dataset-source';\n\n/**\n * Per-frame LOD decision maker: given the camera, returns the set of runs\n * (`{file, offset, count}` slices with a leaf interval) that should be\n * resident. Two implementations exist - {@link LodScheduler} for the flat\n * Streamed SOG leaves-with-levels model, and `OctreeLodSource` for the\n * cut-based LCC2 octree - so {@link StreamedSplatMesh} is format-agnostic.\n */\nexport interface LodSource {\n /** Active-splat budget the returned set stays within. */\n budget: number;\n /** World-unit distance inside which the finest LOD is used. */\n lodBaseDistance: number;\n /** Distance ratio between successive LOD levels. */\n lodMultiplier: number;\n /** The runs that should be resident for the given camera. */\n computeDesiredRuns(\n cameraLocal: THREE.Vector3,\n frustum: THREE.Frustum,\n now: number,\n cameraForward?: THREE.Vector3,\n ): LodRun[];\n /** Coarsest-level runs covering finest cells `[from, to)` - always-cached\n * substitute coverage while a finer level is fetching. */\n coarsestRunsFor(from: number, to: number): LodRun[];\n /**\n * Runs covering `[from, to)` at a requested LOD level (clamped per leaf to an\n * available rung). Used by classic-LCC startup hold to coarsen a nearby home\n * cell when the resolved L0 cut overflows the pool. Optional: sources without\n * a flat leaf ladder leave it undefined.\n */\n runsAtLevelFor?(from: number, to: number, level: number): LodRun[];\n /**\n * Coarsest covering runs for finest cells currently in the camera frustum.\n * Used by `.lcc2` startup `initialReveal: 'hold-coverage'` so the first\n * painted frame has no empty cells. Optional: sources without a nested\n * octree cover leave it undefined (the hold then stays disabled).\n */\n coverageRunsFor?(cameraLocal: THREE.Vector3, frustum: THREE.Frustum): LodRun[];\n /**\n * Notified when a chunk finishes decoding, so a source that discovers its\n * structure from chunk payloads (a `.rad` LOD tree lives in the chunks, not\n * a manifest) can incorporate it. Sources with a fully-known manifest\n * (Streamed SOG, LCC) leave this undefined.\n */\n onChunkDecoded?(file: number, data: SplatData): void;\n}\n\n/**\n * Per-chunk fetch and decode settings, for formats where the URL alone is not\n * enough. LCC (`.lcc`, manifest v3–v5) uses this to name a byte range inside the one big\n * `data.bin` and to carry that range's dequantization ranges. LCC2 and local\n * Streamed SOG use it so a `blob:` URL (no file extension) still selects the\n * SOG parser.\n */\nexport interface StreamedChunkOptions {\n readonly format: ChunkFileFormat;\n readonly lcc?: LccChunkParams;\n /** Byte range within the single-file `.rad`; carried by a `rad-chunk`. */\n readonly rad?: RadChunkRangeRequest;\n /**\n * Convert this SOG chunk's palette-compressed shN into per-splat packed shN\n * at decode, keeping this many bands, so it survives the shared pool (M11).\n * Absent leaves the palette shN to be dropped, as streamed scenes did before.\n */\n readonly sog?: { readonly packShBands: 1 | 2 | 3 };\n /**\n * For an unbundled SOG chunk from a dropped folder: the directory's files as\n * `name → blob URL`. Absent over HTTP, where the worker resolves each image\n * against the chunk's directory URL instead.\n */\n readonly files?: Readonly<Record<string, string>>;\n}\n\n/**\n * A single always-resident environment/background tile a format ships outside\n * its LOD structure - the `.lcc2` `env.sog` sky, loaded once and toggled with\n * {@link StreamedSplatMesh.setEnvironmentEnabled} rather than scheduled by\n * camera distance. Its splat count is absent from the manifest and measured\n * when the tile decodes (see `docs/formats/lcc2-notes.md`).\n */\nexport interface EnvironmentTile {\n /** Chunk-file index (into {@link StreamedScene.chunkUrls}) of the tile. */\n readonly file: number;\n}\n\n/** One collision-mesh tile a scene ships alongside its splats. */\nexport interface CollisionMeshDescriptor {\n /**\n * Fetchable URL of the tile: a binary triangle-mesh PLY (`.lcc2`), or a\n * classic-LCC `collision.lci` that expands into per-cell meshes at load.\n */\n readonly url: string;\n /**\n * The tile's source-local bounds, when the format declares them. Lets a host\n * order or cull tile work without parsing them first. Unused for a whole-file\n * `.lci` descriptor - per-cell bounds come from the parser.\n */\n readonly bounds?: THREE.Box3;\n}\n\n/**\n * Collision geometry a format ships next to its splats - absent for formats\n * that carry none (Streamed SOG), present for XGRIDS `.lcc` / `.lcc2` captures\n * that include a collision sidecar.\n *\n * Coordinates are source-local, the same frame as {@link StreamedScene.bounds}:\n * {@link StreamedScene.formatTransform} maps them to world.\n */\nexport interface SplatCollisionData {\n readonly meshes: readonly CollisionMeshDescriptor[];\n}\n\n/**\n * Everything {@link StreamedSplatMesh} needs about a scene, independent of\n * whether it came from a Streamed SOG manifest or an LCC dataset.\n */\nexport interface StreamedScene {\n readonly source: LodSource;\n /** Chunk-file URLs, indexed by a run's `file`. */\n readonly chunkUrls: readonly string[];\n /** How each chunk URL is fetched: an unbundled SOG directory (Streamed\n * SOG) or a single bundled `.sog`/`.ply` file (LCC2 tiles). */\n readonly chunkKind: 'directory' | 'file';\n /** Optional per-chunk overrides, aligned with {@link chunkUrls}. */\n readonly chunkOptions?: readonly (StreamedChunkOptions | undefined)[];\n /**\n * Per-splat SH bands this scene's chunks actually carry, after the format\n * has had its say - a `Portable` LCC capture reports 0 however many bands\n * the caller asked for. The pool sizes its SH textures from this, so it\n * never allocates ~64 B/splat for SH that will never arrive.\n */\n readonly shBands?: 0 | 1 | 2 | 3;\n /** Root bounds of the whole scene (valid before any chunk loads). */\n readonly bounds: THREE.Box3;\n /** Files backing the coarsest levels - pinned in cache as substitutes. */\n readonly pinnedFiles: ReadonlySet<number>;\n /** Finest-level splat total, for sizing the pool. */\n readonly maxResidentSplats: number;\n /**\n * The capture's real content size - the splat count a host would need to hold\n * it at full resolution. Distinct from {@link maxResidentSplats}, which a\n * foveated source reports as the *requested budget* because its pool holds a\n * camera-directed resident set rather than the whole tree. A host budgeting\n * across several streamed meshes needs the real number: a mesh cannot spend\n * more than it contains, so this is the clamp on any share handed to it.\n *\n * Undefined when the format does not declare it.\n */\n readonly contentSplatCount?: number;\n /** Smallest budget that can retain full-scene coarsest coverage. */\n readonly minimumCoverageSplats: number;\n /**\n * Optional local-to-world format correction applied once to the mesh at\n * load time. Bounds and LOD data remain source-local so scheduling and\n * picking consistently use the encoded coordinate frame.\n */\n readonly formatTransform?: THREE.Matrix4;\n /** Optional collision geometry the format ships; undefined when it has none. */\n readonly collision?: SplatCollisionData;\n /**\n * Optional always-resident environment tile the format ships outside its LOD\n * structure (the `.lcc2` sky) - present only when the dataset carries one.\n */\n readonly environment?: EnvironmentTile;\n /**\n * Screen-space foveation band (px), for a scene that renders whole resident\n * chunks and lets the material's projected-radius cull pick the LOD cut per\n * splat - a `.rad` too large to load whole. Only splats sized `(min, max]` on\n * screen draw, so near view rays land on fine leaves and far rays on coarse\n * nodes. `StreamedSplatMesh` applies it as the mesh's screen-radius band.\n */\n readonly foveation?: { readonly minScreenRadiusPx: number; readonly maxScreenRadiusPx: number };\n /**\n * Splats per chunk file, so a `(file, localIndex)` pair maps to a stable global\n * splat index (`file * chunkSize + local`). Set by `.rad`; the page-table\n * renderer (`foveationMode: 'page-table'`) keys frontier splats by that global.\n */\n readonly chunkSize?: number;\n /**\n * A chunk the scene builder already fetched and decoded (`.rad` decodes chunk\n * 0 for the scene bounds and the SH codebook). Handing it straight to the\n * renderer saves a second round trip - and in `page-table` mode it is what\n * seeds the worker's tree roots, without which the first traversals return an\n * empty frontier and the view stays blank until chunk 0 arrives a second time.\n */\n readonly bootstrapChunk?: { readonly file: number; readonly data: SplatData };\n /**\n * Source-local xyz subsample of the coarsest overview (`.rad` chunk 0).\n * Survives after page-table mode transfers chunk-0 buffers to the worker, so\n * a host can estimate terrain height before any later chunk decodes.\n */\n readonly overviewPositions?: Float32Array;\n}\n\nexport interface LodSourceOptions {\n budget: number;\n lodBaseDistance: number;\n lodMultiplier: number;\n}\n\n/**\n * Builds a scene from a parsed Streamed SOG manifest (JSON already fetched).\n *\n * `shBands` opts into view-dependent color (M11): when ≥ 1, each chunk's\n * palette shN is converted to per-splat packed shN at decode so it survives\n * the shared pool, and the pool allocates that many SH bands. Unset/0 keeps\n * the historical behavior (palette shN dropped). It is opt-in because the\n * manifest does not declare whether the tiles carry shN; a scene with none\n * renders unchanged but wastes the allocated SH textures, so ask deliberately.\n */\nexport function buildSogScene(\n json: unknown,\n source: SplatDatasetSource,\n options: LodSourceOptions,\n shBands: 0 | 1 | 2 | 3 = 0,\n): StreamedScene {\n const manifest = parseLodManifest(json, source);\n const packShBands = shBands >= 1 ? (shBands as 1 | 2 | 3) : undefined;\n // A dropped folder has no directory URL to fetch images beneath, so each\n // chunk carries its own files instead. Over HTTP this is all undefined and\n // the worker resolves against the directory URL as before. When SH is\n // requested every chunk also carries the pack-bands directive.\n const chunkOptions = manifest.chunkUrls.map((_, index) => {\n const directory = manifest.chunkDirectories?.[index];\n const files = directory ? source.directoryFiles(directory) : null;\n if (!files && !packShBands) return undefined;\n return {\n format: 'sog',\n ...(files ? { files } : {}),\n ...(packShBands ? { sog: { packShBands } } : {}),\n } as const;\n });\n return {\n source: new LodScheduler(manifest, options),\n chunkUrls: manifest.chunkUrls,\n ...(chunkOptions.some(Boolean) ? { chunkOptions } : {}),\n ...(packShBands ? { shBands: packShBands } : {}),\n chunkKind: 'directory', // Streamed SOG chunks are unbundled directories\n bounds: manifest.bounds,\n pinnedFiles: computeSogPinnedFiles(manifest),\n maxResidentSplats: manifest.counts[0] ?? options.budget,\n minimumCoverageSplats: computeSogMinimumCoverage(manifest),\n };\n}\n\n/** Total of each leaf's coarsest available range. */\nfunction computeSogMinimumCoverage(manifest: LodManifest): number {\n let total = 0;\n for (const leaf of manifest.leaves) {\n for (let level = leaf.lods.length - 1; level >= 0; level--) {\n const range = leaf.lods[level];\n if (range) {\n total += range.count;\n break;\n }\n }\n }\n return Math.max(1, total);\n}\n\n/** Files backing some leaf's coarsest level - the always-available floor. */\nfunction computeSogPinnedFiles(manifest: LodManifest): Set<number> {\n const pinned = new Set<number>();\n for (const leaf of manifest.leaves) {\n for (let level = leaf.lods.length - 1; level >= 0; level--) {\n const range = leaf.lods[level];\n if (range) {\n pinned.add(range.file);\n break;\n }\n }\n }\n return pinned;\n}\n","import {\n SplatLoadError,\n toRequestInit,\n type SplatRequestOptions,\n type StreamedSplatFormat,\n} from '../loaders/loading';\n\n/**\n * Where a streamed dataset's files come from.\n *\n * A streamed scene is never one file: it is a manifest plus sidecars and chunk\n * files that the manifest names *relatively* (`data.bin`, `data/3dgs/x.sog`,\n * `0_0/meta.json`). Over HTTP those resolve against the manifest's URL; from a\n * dropped folder there is no URL to resolve against, only `File` objects.\n *\n * This is the seam between the two. Everything downstream - including the\n * worker's ranged chunk reads - keeps working on plain URLs, because a\n * dropped file's `blob:` URL answers `Range` with a real 206 (verified on\n * Chromium/WebKit/Gecko), so only *name resolution* has to differ.\n */\nexport interface SplatDatasetSource {\n /** Fetchable URL of the manifest itself. */\n readonly manifestUrl: string;\n /** Fetchable URL for a dataset-relative path, or null when absent. */\n resolve(path: string): string | null;\n /** Byte length of a file, or null when absent. */\n size(path: string): Promise<number | null>;\n /**\n * The files inside a chunk *directory* (unbundled SOG), as\n * `name → fetchable URL`. Null when the chunk should be fetched by URL\n * instead - the HTTP case, where the directory is a real path.\n */\n directoryFiles(path: string): Record<string, string> | null;\n /** Releases any resources held for this dataset (e.g. blob URLs). */\n dispose(): void;\n}\n\n/** A dataset served over HTTP, resolved against the manifest's URL. */\nexport function httpDatasetSource(\n manifestUrl: string,\n request?: SplatRequestOptions,\n): SplatDatasetSource {\n return {\n manifestUrl,\n resolve: (path) => new URL(path, manifestUrl).href,\n size: (path) => probeSize(new URL(path, manifestUrl).href, request),\n directoryFiles: () => null,\n dispose: () => {},\n };\n}\n\n/**\n * Determines a remote file's length without downloading it: `HEAD` first, and\n * a one-byte ranged `GET` for origins that disallow `HEAD` but do serve\n * ranges. A missing file is not an error - callers treat null as \"absent\".\n */\nasync function probeSize(url: string, request?: SplatRequestOptions): Promise<number | null> {\n try {\n const response = await fetch(url, { ...toRequestInit(request), method: 'HEAD' });\n await cancelBody(response);\n if (response.ok) {\n const length = response.headers.get('content-length');\n if (length !== null) return saneSize(Number(length));\n } else if (response.status === 404) {\n return null;\n }\n } catch {\n // Fall through to the ranged GET below.\n }\n try {\n const response = await fetch(url, {\n ...toRequestInit(request),\n headers: { ...(request?.headers ?? {}), Range: 'bytes=0-0' },\n });\n await cancelBody(response);\n // A plain 200 means the server ignored the Range header - the body would\n // have been the whole file, and Content-Range is absent; treat the size\n // as unknown rather than misreading a full response as a probe.\n if (response.status !== 206) return null;\n const total = response.headers.get('content-range')?.split('/')[1];\n return total === undefined || total === '*' ? null : saneSize(Number(total));\n } catch {\n return null;\n }\n}\n\n/** Discards a probe response's body so the connection is not left downloading. */\nasync function cancelBody(response: Response): Promise<void> {\n try {\n await response.body?.cancel();\n } catch {\n // A body that cannot be cancelled (already consumed/locked) is fine.\n }\n}\n\n/** A probed size only counts if it is a non-negative safe integer. */\nfunction saneSize(size: number): number | null {\n return Number.isSafeInteger(size) && size >= 0 ? size : null;\n}\n\n/** A dataset picked out of a dropped folder. */\nexport interface LocalDataset {\n readonly source: SplatDatasetSource;\n /** The streamed format the folder's manifest identifies it as. */\n readonly format: Exclude<StreamedSplatFormat, 'auto'>;\n /** The manifest's file name, for display. */\n readonly name: string;\n}\n\n/** Manifest names that identify a streamed dataset, most specific first. */\nconst MANIFESTS: {\n test: (name: string) => boolean;\n format: Exclude<StreamedSplatFormat, 'auto'>;\n}[] = [\n { test: (n) => n.endsWith('.lcc2'), format: 'lcc2' },\n { test: (n) => n.endsWith('.lcc'), format: 'lcc' },\n // The `.rad` header of a `--rad-chunked` set; `.radc` chunk files are not\n // manifests (they end in `.radc`, so `endsWith('.rad')` excludes them).\n { test: (n) => n.endsWith('.rad'), format: 'rad' },\n { test: (n) => n === 'lod-meta.json', format: 'streamed-sog' },\n];\n\n/**\n * Builds a dataset from a dropped folder's files, keyed by their paths\n * relative to the folder root.\n *\n * Every file gets a `blob:` URL, so the rest of the pipeline - including the\n * worker's ranged reads into a multi-hundred-megabyte `data.bin` - is\n * identical to the HTTP path. Nothing is copied or read here: a blob URL is a\n * handle to the file on disk, so a 300 MB `data.bin` costs nothing until a\n * chunk actually reads a range out of it.\n *\n * @throws {SplatLoadError} with `phase: 'manifest'` when the folder holds no\n * recognizable manifest, or more than one (which would make the choice\n * arbitrary). Neither is retryable: the same folder fails the same way.\n */\nexport function createLocalDataset(files: ReadonlyMap<string, File>): LocalDataset {\n const found: { path: string; format: Exclude<StreamedSplatFormat, 'auto'> }[] = [];\n for (const path of files.keys()) {\n const name = basename(path).toLowerCase();\n const match = MANIFESTS.find((candidate) => candidate.test(name));\n // Only a manifest at the top level counts: an `.lcc2` capture nests whole\n // datasets under `data/`, and picking one of those would load a fragment.\n if (match && depth(path) === minDepth(files, match)) found.push({ path, format: match.format });\n }\n if (found.length === 0) {\n throw new SplatLoadError(\n 'That folder has no streamed-scene manifest in it (expected a .lcc, .lcc2 or lod-meta.json file).',\n { phase: 'manifest', url: 'local-folder', retryable: false },\n );\n }\n if (found.length > 1) {\n const names = found.map((entry) => basename(entry.path)).join(', ');\n throw new SplatLoadError(\n `That folder holds more than one scene manifest (${names}) - drop one scene at a time.`,\n { phase: 'manifest', url: 'local-folder', retryable: false },\n );\n }\n\n const manifest = found[0] as { path: string; format: Exclude<StreamedSplatFormat, 'auto'> };\n // Paths resolve relative to the manifest, exactly as they would over HTTP.\n const root = manifest.path.includes('/')\n ? manifest.path.slice(0, manifest.path.lastIndexOf('/') + 1)\n : '';\n const urls = new Map<string, string>();\n const urlFor = (path: string): string | null => {\n const existing = urls.get(path);\n if (existing !== undefined) return existing;\n const file = files.get(path);\n if (!file) return null;\n const url = URL.createObjectURL(file);\n urls.set(path, url);\n return url;\n };\n\n const source: SplatDatasetSource = {\n manifestUrl: urlFor(manifest.path) as string,\n resolve: (path) => urlFor(root + normalize(path)),\n size: (path) => Promise.resolve(files.get(root + normalize(path))?.size ?? null),\n directoryFiles: (path) => {\n // An unbundled SOG chunk is a folder of images the worker fetches by\n // name; hand it the whole folder rather than a URL to resolve against.\n const prefix = root + normalize(path).replace(/\\/?$/, '/');\n const entries: Record<string, string> = {};\n for (const candidate of files.keys()) {\n if (!candidate.startsWith(prefix)) continue;\n const url = urlFor(candidate);\n if (url) entries[candidate.slice(prefix.length)] = url;\n }\n return Object.keys(entries).length > 0 ? entries : null;\n },\n dispose: () => {\n for (const url of urls.values()) URL.revokeObjectURL(url);\n urls.clear();\n },\n };\n return { source, format: manifest.format, name: basename(manifest.path) };\n}\n\n/** Strips the `./` and leading `/` a manifest path may carry. */\nfunction normalize(path: string): string {\n return path.replace(/^\\.?\\//, '');\n}\n\nfunction basename(path: string): string {\n return path.slice(path.lastIndexOf('/') + 1);\n}\n\nfunction depth(path: string): number {\n return path.split('/').length - 1;\n}\n\n/** The shallowest depth any manifest of this kind sits at. */\nfunction minDepth(\n files: ReadonlyMap<string, File>,\n match: { test: (name: string) => boolean },\n): number {\n let shallowest = Infinity;\n for (const path of files.keys()) {\n if (match.test(basename(path).toLowerCase())) shallowest = Math.min(shallowest, depth(path));\n }\n return shallowest;\n}\n","/**\n * Wire protocol for the `.rad` page-table frontier worker\n * (`frontier-worker.ts`).\n *\n * Split from the worker module so `StreamedSplatMesh` can be typed against it\n * without pulling worker source into the published declarations - the worker is\n * reached only through `?worker&inline` and is not a public entry point.\n */\nimport type { SplatData } from '../../core/splat-data';\n\n/**\n * Foveation ramp for the frontier traversal, matching Spark's `SparkRenderer`\n * defaults (`coneFov0` / `coneFov` / `coneFoveate` / `behindFoveate`). Detail is\n * full inside `coneFov0`, falls to `coneFoveate` by `coneFov`, and to\n * `behindFoveate` directly behind the camera - a *weight*, never a cull, so the\n * scene stays covered when the camera turns. Lives here (a dependency-free\n * module) because both the worker and `StreamedSplatMesh` need it.\n */\nexport const FRONTIER_FOVEATION_DEFAULTS = {\n coneFov0: 90,\n coneFov: 120,\n coneFoveate: 0.4,\n behindFoveate: 0.2,\n} as const;\n\n/** Degrees / weights describing the foveation ramp. */\nexport interface FrontierFoveation {\n readonly coneFov0: number;\n readonly coneFov: number;\n readonly coneFoveate: number;\n readonly behindFoveate: number;\n}\n\n/** A decoded chunk's arrays, as forwarded from the main thread. */\nexport interface FrontierChunkMessage {\n readonly type: 'chunk';\n readonly file: number;\n readonly count: number;\n readonly positions: Float32Array;\n readonly colors: Uint8Array;\n readonly covariances: Float32Array;\n readonly childCount: Uint16Array;\n readonly childStart: Uint32Array;\n readonly size: Float32Array;\n readonly shBands: 0 | 1 | 2 | 3;\n readonly shPacked?: Uint32Array;\n readonly shRange?: {\n min: readonly [number, number, number];\n max: readonly [number, number, number];\n };\n}\n\nexport interface FrontierInitMessage {\n readonly type: 'init';\n readonly capacity: number;\n readonly chunkSize: number;\n readonly cpuCacheBytes: number;\n}\n\n/**\n * Changes how many slots the pager may fill, after the host grew or shrank the\n * storage behind them (a near mesh climbing its budget, a distant one giving\n * pages back). The chunk cache is untouched - only the pager is resized - so no\n * chunk is re-downloaded.\n */\nexport interface FrontierResizeMessage {\n readonly type: 'resize';\n readonly capacity: number;\n}\n\n/**\n * Changes the byte cap the chunk cache evicts against, after the scene's shared\n * `ChunkCacheBudget` re-split it - a near mesh climbing, a far one giving bytes\n * back.\n *\n * Only the cap moves; nothing is dropped here. Eviction stays on the one path\n * that knows what the frontier still needs (`evict` runs inside `reschedule`,\n * against `neededFiles` and the pager's resident set), and the `evicted` list\n * only reaches the main thread on a plan. Evicting off that path would drop\n * chunks out from under resident splats with no way to tell the host - the\n * dark-speckle failure the resident-set guard exists to prevent.\n */\nexport interface FrontierCacheBudgetMessage {\n readonly type: 'cacheBudget';\n readonly cpuCacheBytes: number;\n}\n\nexport interface FrontierRescheduleMessage {\n readonly type: 'reschedule';\n readonly seq: number;\n readonly cameraLocal: [number, number, number];\n /** Unit camera forward in mesh-local space. Detail falls off away from it -\n * the traversal foveates rather than frustum-culls, so the scene stays covered\n * when the camera turns or zooms out. */\n readonly cameraForward: [number, number, number];\n /** Foveation ramp, in degrees / weights (Spark's `coneFov0`/`coneFov`/…). */\n readonly coneFov0: number;\n readonly coneFov: number;\n readonly coneFoveate: number;\n readonly behindFoveate: number;\n /** Cut on foveated `size / distance` - `2·tan(fovY/2) / renderHeight`, scaled\n * by `foveationTargetPx`. Fixed per frame; the budget is what bounds the cut. */\n readonly limit: number;\n /** Maximum drawn splats; enforced inside the traversal, never after. */\n readonly budget: number;\n}\n\nexport type FrontierRequest =\n | FrontierInitMessage\n | FrontierChunkMessage\n | FrontierResizeMessage\n | FrontierCacheBudgetMessage\n | FrontierRescheduleMessage;\n\n/** Packed splats to write, in slot order (a subset of {@link SplatData}). */\nexport interface PlanSplats {\n readonly count: number;\n readonly positions: Float32Array;\n readonly colors: Uint8Array;\n readonly covariances: Float32Array;\n readonly shPacked?: SplatData['shPacked'];\n}\n\nexport interface FrontierPlanMessage {\n readonly type: 'plan';\n readonly seq: number;\n /** Survivors relocated by swap-remove: write `moves` splat j at `moveSlots[j]`. */\n readonly moveSlots: Uint32Array;\n readonly moves: PlanSplats;\n /** Newcomers written contiguously at `[appendStart, appendStart + appends.count)`. */\n readonly appendStart: number;\n readonly appends: PlanSplats;\n /** Freed tail slots to degenerate. */\n readonly degenerateStart: number;\n readonly degenerateCount: number;\n /** Chunks the frontier wants next, biggest-on-screen first. */\n readonly touched: Uint32Array;\n /** Drawn (non-degenerate) frontier size - the true on-screen splat count. */\n readonly residentCount: number;\n /**\n * Splats this plan could not gather because their chunk had been evicted, and\n * so wrote as zeros into slots that are still drawn - coverage holes, seen as\n * dark speckle in a region while it refines. Eviction protects every chunk\n * with resident splats, so this is 0; a non-zero value is a bug.\n */\n readonly gatherMissing: number;\n /** Newcomers the slab had no room for. The traversal is budget-bounded, so\n * this is 0 unless the slab is smaller than the draw budget - a real bug. */\n readonly dropped: number;\n /** Chunks evicted from the worker cache this round. The main thread must\n * forget them (`pageTableCachedFiles`) or they could never be refetched. */\n readonly evicted: Uint32Array;\n /**\n * The cut this plan was built at - at or below the requested `limit`, because\n * the worker refines past the quality target to spend the draw budget.\n *\n * The host needs it because its screen-radius band was chosen for the *target*\n * cut: a finer cut selects smaller splats, and a band left at the coarse\n * setting would cull exactly the detail the refinement just bought.\n */\n readonly solvedLimit: number;\n /**\n * The pager capacity this plan was built against.\n *\n * The host grows and shrinks the storage behind the slots, so a plan can\n * arrive describing slots that no longer exist: a `reschedule` posted before a\n * `resize` is answered from the old capacity, and applying that answer writes\n * some splats nowhere while leaving their slots holding whatever was there\n * before - a coarse node's data in a slot the frontier now wants fine, which\n * draws as a single enormous splat. The host compares this and drops such a\n * plan instead.\n */\n readonly capacity: number;\n /**\n * False when the plan was held short of the traversal's frontier to bound how\n * much the host must write in one tick, so the resident set is an intermediate\n * one - some newcomers deferred, some replaced nodes still drawn. The host must\n * reschedule promptly, otherwise convergence stalls wherever the cap left it.\n */\n readonly converged: boolean;\n /**\n * Decoded bytes the worker's chunk cache is holding, and the cap it evicts\n * against.\n *\n * Reported because the cache lives entirely in the worker, so a host watching\n * a scene that will not stop streaming cannot otherwise tell \"the working set\n * does not fit\" from \"still converging\" - and the main thread's own mirror of\n * which files are cached is not enough to reconstruct the byte total.\n */\n readonly cacheBytes: number;\n readonly cacheLimitBytes: number;\n readonly pendingFrontierSplats?: number;\n readonly staleResidentSplats?: number;\n readonly lastPlanAppends?: number;\n readonly lastPlanMoves?: number;\n readonly cameraLocal?: readonly [number, number, number];\n readonly planBudget?: number;\n readonly planGeneration?: number;\n}\n","import {\n abortReason,\n buildHoldSwapGroups,\n buildSwapGroups,\n chunkBytes,\n isClassicLccSwapSet,\n enqueueClassicFetch,\n classicFetchPhaseForDesired,\n classicFetchPhaseForCoverage,\n compareClassicFetches,\n compareClassicSwapGroups,\n defaultCpuCacheBytes,\n groupPriority,\n isWaitingOnFinest,\n sliceSplatData,\n stampClassicFetchGroups,\n validateAppendCap,\n validateLodScale,\n type ClassicFetchWant,\n type SwapGroup,\n} from './streamed-splat-mesh-utils';\nexport * from './streamed-splat-mesh-utils';\nimport * as THREE from 'three/webgpu';\nimport {\n DEFAULT_FOVEATION_TARGET_PX,\n MAX_SH_BANDS,\n resolveSplatPerformanceProfile,\n SplatMesh,\n isPageTableFoveation,\n resolveSplatFoveationMode,\n type SplatRange,\n type SplatChannelType,\n type SplatChannelOptions,\n type SplatMeshOptions,\n type SplatUpdateOptions,\n} from '../core/splat-mesh';\nimport type { SplatData } from '../core/splat-data';\nimport { runKey, type LodRun, type LodScheduler } from './lod-scheduler';\nimport { buildSogScene, type StreamedScene } from './lod-source';\nimport type { CollisionMeshTile } from '../formats/lcc/collision-mesh';\nimport { createLocalDataset, httpDatasetSource, type SplatDatasetSource } from './dataset-source';\nimport {\n isAbortError,\n resolveSplatUrl,\n SplatLoadError,\n toRequestInit,\n toSplatLoadError,\n type SplatRequestOptions,\n type StreamedSplatFormat,\n} from '../loaders/loading';\nimport {\n liftBudgetToFinestLevel,\n recommendedRadMaxStdDev,\n resolveSplatBudget,\n type SplatDeviceProfile,\n} from '../core/splat-budget';\nimport { resolveXrView } from '../core/xr-view';\nimport { ChunkLoader } from '../loaders/chunk-loader';\nimport { yUpTransformForFormat } from '../core/orientation';\nimport {\n FRONTIER_FOVEATION_DEFAULTS,\n type FrontierFoveation,\n type FrontierPlanMessage,\n type FrontierRequest,\n type PlanSplats,\n} from '../formats/rad/frontier-worker-protocol';\nimport { shCoefficientCount } from '../core/sh-pack';\nimport { warn } from '../core/logging';\nimport type {\n ChunkFetchHandle,\n ChunkFetchKind,\n ChunkFetchScheduler,\n} from './chunk-fetch-scheduler';\nimport type { ChunkCacheBudget, ChunkCacheHandle } from './chunk-cache-budget';\n\n/** Vite's `?worker&inline` default export - a Worker subclass constructor. */\ntype InlineWorkerCtor = new () => Worker;\n\nconst DATA_TEXTURE_WIDTH = 2048;\n/** Max splats appended per frame (bounds the copy + staging-upload cost). */\n/**\n * A classic-LCC initial reveal normally waits for the complete nearby cut.\n * This is the escape hatch for a pathological or exceptionally slow capture:\n * after one minute it reveals the best staged coverage and continues refining.\n */\nconst INITIAL_REVEAL_TIMEOUT_MS = 60_000;\n// Keep the attribution event aligned with WebGpuSortScheduler's content\n// invalidation policy: only a region-sized visibility change forces a sort.\nconst CONTENT_FORCE_FRACTION = 0.25;\n/**\n * Max chunk fetches in flight at once on the classic (non-page-table) path.\n * Matches the page-table pager so near-finest detail can fill the pipe instead\n * of waiting behind a long far-coarse pin queue.\n */\nconst MAX_INFLIGHT = 8;\n/**\n * Backstop on how long the wave gate may hold a retirement back.\n *\n * Pool pressure, not elapsed time, is what should release a retirement: the rows\n * it frees only matter once something else needs them, and that is exactly the\n * condition `applyGroup` reports. A tick bound on top of that trades coverage\n * for nothing, and measurably so - on the 132-chunk `oldtimers-route` capture,\n * bounds of 8/24/64 ticks left 157/69/35 frames losing coverage, while releasing\n * on pool pressure alone left 3, none worse than 0.38% of the drawn set (against\n * 258 frames and 2.14% before the gate). Short bounds are worse than no gate in\n * one respect too: they retire in bulk when they fire.\n *\n * So this is set well past the point of interference and kept only so that a\n * pool roomy enough never to report pressure cannot hold superseded coverage for\n * the entire session. At 60 fps it is about ten seconds.\n */\nconst MAX_RETIRE_HELD_TICKS = 600;\n/** Reschedule at least this often even when the camera is still, ms. */\nconst IDLE_RESCHEDULE_MS = 250;\n/** Attempts before a chunk is given up on (a transient error retries). */\nconst MAX_CHUNK_ATTEMPTS = 4;\n/** First retry delay; doubles each attempt (500, 1000, 2000 ms). */\nconst RETRY_BASE_MS = 500;\n/** Fetch slots the page-table sweep keeps free for frontier-requested chunks, so\n * the detail the camera is pointed at never queues behind a file-order sweep.\n * Matches Spark's `numLodFetchers`. */\nconst PAGETABLE_PRIORITY_SLOTS = 3;\n/** Default drawn-splat target for the page-table frontier (Spark's `maxSplats`)\n * when the caller gives no `foveationDrawBudget`. Sized to Spark's own default\n * for this class of scene - 800K is far too coarse for a 16M-leaf interior, so\n * the frontier coarsens and evicts near-camera detail to fit. Overridable via\n * `foveationDrawBudget` (`?foveationDraw=`), and always ≤ the pool budget. */\nconst PAGETABLE_DRAW_BUDGET = 4_000_000;\n\n/**\n * Splats per slab page in `foveationMode: 'page-table'`.\n *\n * The frontier's slots are backed by pages of this size rather than one\n * contiguous reservation, so a mesh's storage need not be one block - the\n * property that lets meshes interleave in a shared pool, and lets a mesh\n * release storage as its budget falls. Matches Spark's `pageSplats` and the\n * `.rad` chunk size, and is a whole number of 2048-texel pool rows (32), so\n * page writes stay row-aligned.\n */\nconst SLAB_PAGE_SPLATS = 65_536;\n\n/**\n * Ceiling on the page-table cache floor. A `.rad` frontier refines only into\n * chunks that are resident together, so a cache far smaller than the working set\n * thrashes and the view stays coarse - this is the headroom that prevents that,\n * for a scene big enough to need it.\n *\n * It is a *ceiling on a floor*, not a per-mesh allowance: `min(this, the\n * capture's own decoded size)` means a small mesh asks for what it can\n * actually use, and a host that set a larger share still gets it.\n */\nconst PAGETABLE_CACHE_FLOOR_BYTES = 2 * 1024 * 1024 * 1024;\n\n/**\n * Rough decoded size of a whole streamed scene, for sizing the cache floor:\n * positions (12 B) + colors (4 B) + covariances (24 B) per splat, plus the LOD\n * tree arrays a `.rad` chunk carries (`childCount` 4 B + `childStart` 4 B +\n * `size` 4 B) and packed SH when the scene carries it. Deliberately an\n * over-estimate - the floor should never be the reason a capture cannot hold\n * itself, and it must match what the frontier worker charges its cache, or a\n * capture the cap was sized to hold starts evicting itself mid-load.\n *\n * Exported for unit testing only; not part of the public API.\n */\nexport function estimateSceneDecodedBytes(scene: StreamedScene): number {\n const perSplat =\n 12 + 4 + 24 + 12 + (scene.shBands ? 16 * Math.ceil(shCoefficientCount(scene.shBands) / 4) : 0);\n // Size from what the cache actually holds: whole decoded *chunks*, every splat\n // in them. `contentSplatCount` is the wrong number for a LOD tree - for `.rad`\n // it is the **leaf** count, while a chunk carries internal (merged) nodes too,\n // and those are most of what a coarse frontier draws. On the 5.9M-leaf\n // reference capture the tree holds 8.59M nodes, so counting leaves alone\n // under-estimated by 32% and produced a floor *below* the frontier's working\n // set - the exact opposite of this function's purpose. The symptom was a\n // permanent 1-chunk oscillation: resident chunks alternating 75/76 with a\n // fetch every couple of seconds, the cache reporting full, and the frontier\n // refetching what it had just been forced to evict.\n //\n // Raising the ceiling does not by itself cost memory: it is a cap on a cache\n // that only ever holds what has been fetched, and with the background sweep\n // declined on mobile that is the working set and nothing more.\n //\n // Still approximate on the low side: a `.rad` chunk also carries the LOD tree\n // columns (`child_count` u16 + `child_start` u32, ~6 B/splat) which this does\n // not count. That was the last ~5% of the overshoot above - 76 chunks measured\n // ~229 MB against the old 224 MB floor - and the chunk-count fix now clears it\n // by a wide enough margin that adding the columns is not worth the extra\n // memory it would reserve on every device. Revisit if a capture thrashes with\n // resident chunks close to this estimate.\n const chunkSplats =\n scene.chunkSize === undefined ? undefined : scene.chunkSize * scene.chunkUrls.length;\n const splats = chunkSplats ?? scene.contentSplatCount ?? scene.maxResidentSplats;\n return Math.max(1, splats) * perSplat;\n}\n\n/**\n * Read-only startup-hold progress for {@link StreamedSplatMeshOptions.initialReveal}.\n * Exported for hosts that gate visibility on the first useful coverage frame\n * (classic `.lcc` nearby L0, or `.lcc2` in-view coarsest cells).\n */\nexport type InitialRevealState =\n | { readonly status: 'disabled' }\n | {\n readonly status: 'pending';\n readonly stagedSplats: number;\n readonly totalSplats: number;\n readonly readyGroups: number;\n readonly totalGroups: number;\n }\n | { readonly status: 'ready' }\n | {\n readonly status: 'degraded';\n readonly reason: 'capacity' | 'fetch-failed' | 'timeout';\n readonly stagedSplats: number;\n readonly totalSplats: number;\n readonly readyGroups: number;\n readonly totalGroups: number;\n };\n\n/** Options for {@link StreamedSplatMesh.load}. */\nexport interface StreamedSplatMeshOptions extends SplatMeshOptions {\n /** Active-splat budget. Defaults to {@link resolveSplatBudget}. */\n budget?: number;\n /**\n * A ceiling on the *resolved default* budget, for callers that want to\n * tighten without overriding what the library knows.\n *\n * `budget` is absolute: it wins over the device tier, the format's cost class\n * and everything else, because a caller who names a number has said they know\n * better. That is the right contract, and the wrong tool for \"the same as\n * usual, but no more than N\" - which is what a performance toggle or a host\n * default actually means. Pinning a number there has twice shipped as a bug:\n * a demo performance mode that *raised* the load on the weakest device tested,\n * and a host whose default bypassed every device tier.\n *\n * Applied only when `budget` is omitted, and only downward - it never raises\n * a budget the device would not otherwise have taken. It also suppresses the\n * finest-level lift, which exists to raise a budget far enough to hold a\n * scene whole and is exactly what \"no more than N\" rules out. Unrelated to\n * {@link maxBudget}, which sizes the pool and bounds\n * {@link StreamedSplatMesh.setBudget}.\n *\n * Forwarded to `resolveSplatBudget` as `SplatBudgetOptions.cap`.\n *\n * @throws {RangeError} at load if not a positive finite number.\n */\n budgetCap?: number;\n /**\n * Device signals for budget / quality defaults. Defaults to\n * {@link detectSplatDeviceProfile}. Pass a profile enriched with\n * {@link probeSplatGpuClass} so desktop integrated GPUs take the laptop\n * tier instead of the workstation 8M path.\n */\n deviceProfile?: SplatDeviceProfile;\n /**\n * Ceiling {@link StreamedSplatMesh.setBudget} may raise this mesh to, and the\n * size its pool is allocated from. Defaults to `budget`.\n *\n * Set this above `budget` when a `CameraBudgetGovernor` or `BudgetGovernor`\n * should be able to *grow* this mesh's share: the pool is allocated once at\n * construction and never grows, so without headroom reserved here a governed\n * mesh can only ever be shrunk below the budget it was built with. That is\n * the whole reason a hand-split `pool / N` mesh stays coarse near the\n * camera - every mesh's ceiling was fixed at a quarter of the pool.\n *\n * It is not free: the pool costs its *ceiling* in memory whether or not the\n * budget ever reaches it (~64 B of GPU pool plus ~56 B of CPU backing per\n * splat, 1.5× for capacity slack). Price it with `estimateSplatPoolBytes`\n * before choosing - for several additional meshes the\n * sum of the ceilings is what has to fit, not the shared budget. A ceiling\n * around 1.5–2× a member's fair share is usually the right trade.\n *\n * @throws {RangeError} at load if below `budget`, or not a positive finite\n * number.\n */\n maxBudget?: number;\n /**\n * Lets a host that pins {@link budget} and/or {@link maxBudget} still take the\n * finest-level lift for `.rad` strategy selection and pool sizing. Without it,\n * pinning either option disables the lift and a capture whose leaf count sits\n * between the host ceiling and {@link FOVEATION_LEAF_THRESHOLD} incorrectly\n * lands on the foveated page-table path instead of the prefix reader.\n *\n * {@link budgetCap} still vetoes the lift when set. Mobile and fill-constrained\n * desktops remain exempt inside {@link liftBudgetToFinestLevel}.\n */\n allowFinestLevelLift?: boolean;\n /**\n * Multiplier on this mesh's LOD detail, matching Spark's per-mesh `lodScale`:\n * `> 1` refines further (finer cut, more splats drawn), `< 1` coarsens.\n * Default `1`.\n *\n * **`.rad` `foveationMode: 'page-table'` only** - it scales the frontier cut\n * the page-table traversal is given (`pixel_scale × lodScale ≤ limit`, exactly\n * Spark's formula). It does nothing on a mesh with no per-splat cut to scale:\n * a moderate `.rad` read as a chunk prefix, or a Streamed SOG / LCC scene. For\n * the GPU cut modes (`'band'` / `'frontier'`) the equivalent is\n * {@link SplatMeshOptions.foveationTargetPx} at `1 / lodScale`.\n *\n * The draw budget still bounds the result, so raising this past the point\n * where the budget binds sharpens nothing - give the mesh budget as well.\n */\n lodScale?: number;\n /** Explicit format; by default the manifest's extension decides. */\n format?: StreamedSplatFormat;\n /** Serializable fetch settings for the manifest and its chunks. */\n request?: SplatRequestOptions;\n /**\n * Cancels the load: the manifest fetch aborts, and {@link StreamedSplatMesh.load}\n * rejects with a `DOMException` named `AbortError`. A mesh partially built\n * when the signal fires is disposed - nothing leaks. Only read during load;\n * later streaming is stopped by {@link SplatMesh.dispose}.\n */\n signal?: AbortSignal;\n /** Base URL a relative manifest URL resolves against (like {@link loadSplatData}). */\n baseUrl?: string | URL;\n /** World-unit distance inside which the finest LOD is used. Default 10. */\n lodBaseDistance?: number;\n /** Distance ratio between successive LOD levels. Default 2. */\n lodMultiplier?: number;\n /** Cap on decoded chunk arrays cached on the CPU. Default by device memory. */\n cpuCacheBytes?: number;\n /**\n * Foveation ramp for the `.rad` page-table frontier: detail is full inside\n * `coneFov0` degrees of the view direction, falls off to `coneFoveate` by\n * `coneFov`, and to `behindFoveate` directly behind the camera. Off-cone\n * content is kept **coarse**, never dropped, so turning or zooming out never\n * exposes an unpainted region. Defaults match Spark\n * ({@link FRONTIER_FOVEATION_DEFAULTS}).\n */\n frontierFoveation?: Partial<FrontierFoveation>;\n /**\n * Keeps a complete multi-run replacement hidden while its uploads are\n * spread over frames, then switches the region atomically. Enabled by\n * default; set `false` only for legacy A/B comparison.\n */\n experimentalStagedSwaps?: boolean;\n /**\n * Maximum splats copied into the pool per LOD mutation tick. Defaults to\n * 32,000; lower debug values trade refinement latency for shorter frames.\n */\n maxSplatsPerSwap?: number;\n /**\n * First-frame reveal policy for streamed formats that can hide empty cells.\n *\n * - `'progressive'`: cells become visible as each swap group commits — can\n * show sparse near-detail (classic `.lcc`) or empty octree squares\n * (`.lcc2`) while siblings load.\n * - `'hold-near-l0'` (the default for classic `.lcc` when unset): hide the\n * mesh until the camera's home coverage group is resident (L0 when it fits;\n * otherwise coarsen via the leaf ladder L1→L2). Neighbours are not part of\n * the hold - they compete via screenImportance and would steal the first\n * fetch slots. Home selection uses distance within `lodBaseDistance` and\n * does not require frustum intersection (HiRes tiles often fail `inView`\n * when the camera stands inside looking out). Coarser rungs come from\n * `LodSource.runsAtLevelFor`. Only home files are fetched during the hold.\n * A one-minute watchdog also degrades if the cut cannot finish.\n * - `'hold-coverage'` (the default for `.lcc2` when unset): hide the mesh\n * until every in-view finest cell has a coarsest covering node resident\n * (any LOD), and until the always-resident environment tile is in the pool\n * when the scene ships one and it starts enabled. Does not wait for finest\n * tiles or the rest of the stream. An empty frustum falls back to the\n * nearest cell. Requires `LodSource.coverageRunsFor`; other formats treat\n * this as disabled.\n *\n * A one-minute watchdog degrades to progressive if the frozen set cannot\n * finish. Does not make detail downloads instantaneous. Classic `.lcc` uses\n * the **resolved** cut from the first schedule (after camera + format\n * transform), not distance ambition alone. Other streamed formats default\n * to `'progressive'`.\n */\n initialReveal?: 'progressive' | 'hold-near-l0' | 'hold-coverage';\n /** Receives lightweight LOD mutation events for performance attribution. */\n onPerformanceEvent?: (event: StreamedSplatPerformanceEvent) => void;\n /**\n * View-dependent color (higher-order SH). This is the streaming counterpart\n * of {@link SplatMeshOptions.shBands}: besides sizing the pool it decides\n * whether SH is fetched/decoded at all. Two sources feed it - a `Quality` LCC\n * `Quality` LCC (`.lcc`) capture, which stores SH per splat, and a Streamed SOG scene,\n * whose per-file palette shN is converted to that same packed form at decode\n * (M11; see `docs/formats/streamed-shn-notes.md`).\n *\n * **For LCC, unset (the default) means every band the capture carries** - so\n * a Quality scene shows its real view-dependent color without the caller\n * having to know the format. The exception is a `smooth` performance profile\n * (the default on mobile), which defaults this to 0: SH roughly triples\n * per-chunk bandwidth (`shcoef.bin` is 64 B/splat against `data.bin`'s 32) and\n * adds up to 64 B/splat of pool textures (~384 MB over a 6M-splat pool at 3\n * bands) - precisely the costs that profile avoids.\n *\n * **For Streamed SOG it is strictly opt-in** (unset = off): the manifest does\n * not declare whether the tiles carry shN, so enabling the conversion - and\n * the pool textures it needs - must be a deliberate choice, not a default.\n *\n * Set it explicitly to override either way: 0 forces SH off, and 1, 2 or 3\n * keep 3, 8 or 15 coefficients per channel. For LCC the value is clamped to\n * what the scene actually has (a `Portable` capture fetches and allocates\n * nothing regardless); for SOG a scene with fewer bands zero-pads and one with\n * no shN simply renders DC color, wasting the allocated textures.\n *\n * Only read at load: the pool's SH textures are allocated once, so a later\n * {@link SplatMesh.setPerformanceProfile} does not change this.\n */\n shBands?: 0 | 1 | 2 | 3;\n /**\n * Whether the scene's always-resident environment/background tile (the\n * `.lcc2` sky) starts visible. Default `true`. Toggle it live afterwards with\n * {@link StreamedSplatMesh.setEnvironmentEnabled}. No effect on a scene that\n * ships no environment tile.\n */\n environmentEnabled?: boolean;\n /**\n * This mesh's share of the scene's fetch bandwidth, as a camera-projected\n * weight - normally `() => governor.weightOf(mesh) ?? 0`, so fetching is\n * ordered by the same measure that already orders drawing.\n *\n * Read on demand, so it always reflects the current camera. Zero means hidden\n * or suspended, and has one effect on its own: the background sweep that\n * pre-warms the whole capture into the page-table cache stops. That sweep is\n * pure speculation about a camera move that has not happened, and on a\n * multi-mesh scene it is most of the traffic competing with the mesh the\n * viewer is actually looking at.\n *\n * Unset (the default) leaves fetching exactly as it was: every mesh sweeps.\n * Supply a {@link fetchScheduler} as well to also bound the total.\n */\n fetchWeight?: () => number;\n /**\n * Scene-wide fetch arbitration, shared by every streamed mesh the way a\n * {@link SplatMeshOptions.pool} is - see {@link ChunkFetchScheduler}. Without\n * one, each mesh fetches toward its own in-flight cap and a near mesh's\n * detail queues behind a dozen far meshes' background traffic.\n *\n * The scheduler is *not* owned by the mesh: dispose unregisters this mesh and\n * leaves the scheduler running for its siblings. Weights come from\n * {@link fetchWeight}; without that every mesh weighs the same and the\n * scheduler only bounds the total.\n */\n fetchScheduler?: ChunkFetchScheduler;\n /**\n * Scene-wide decoded-chunk cache ceiling, shared exactly as\n * {@link fetchScheduler} and {@link SplatMeshOptions.pool} are - see\n * {@link ChunkCacheBudget}.\n *\n * Without one, each `.rad` page-table mesh caps its own cache at\n * `max(cpuCacheBytes, min(2 GiB, this capture's decoded size))`: the right\n * number for a lone streamed scene, and no bound at all across a scene of\n * additional meshes, because every mesh gets its own and each is sized to its own\n * capture. With one, that figure becomes this mesh's *ceiling* and the budget\n * splits a scene total across every registered mesh by camera weight.\n *\n * This bounds retention, not prefetching: the background sweep still runs and\n * still warms the cache, it just stops at the scene's allowance instead of at\n * the size of the capture.\n *\n * The budget is *not* owned by the mesh: dispose unregisters this mesh and\n * leaves it running for its siblings. Weights come from {@link fetchWeight}.\n */\n cacheBudget?: ChunkCacheBudget;\n}\n\n/** One streamed-LOD mutation tick, measured on the main thread. */\nexport interface StreamedSplatPerformanceEvent {\n /** Timestamp after the tick, on the same clock as requestAnimationFrame. */\n timestamp: number;\n /** Main-thread time spent rescheduling and applying this tick. */\n cpuMs: number;\n /** Same-frame packed active-index rebuild time, after the LOD mutation. */\n activeListMs: number;\n /** Same-frame partial texture upload submission time. */\n uploadMs: number;\n /** CPU submission time for the depth-sort passes. */\n sortSubmitMs: number;\n /** Exact-height staging textures allocated during this update. */\n stagingTextureAllocations: number;\n /** WebGPU source-index ranges queued for upload before this tick's sort. */\n activeListUpdateRanges: number;\n appendedCount: number;\n removedCount: number;\n stagedCount: number;\n uploadCount: number;\n activeCount: number;\n forcedSort: boolean;\n compacted: boolean;\n}\n\ninterface CachedChunk {\n data: SplatData;\n bytes: number;\n lastUsed: number;\n}\n\n/** Options for {@link StreamedSplatMesh.definePersistentChannel}. */\nexport interface PersistentChannelOptions extends SplatChannelOptions {\n /**\n * Cap on the number of `(chunk, splat)` edits stored for this channel.\n * Editing past the cap is dropped with a one-time warning. Default 1,000,000.\n */\n maxEdits?: number;\n}\n\n/** A per-channel sparse edit store, keyed by `(chunk file, local index)`. */\ninterface PersistentChannel {\n readonly type: SplatChannelType;\n /** The channel's default value - unedited splats must reload at this, not 0. */\n readonly fill: number;\n readonly maxEdits: number;\n /** file → (local splat index within that chunk → value). */\n readonly edits: Map<number, Map<number, number>>;\n total: number;\n warned: boolean;\n}\n\n/**\n * Streams a large splat scene - a Streamed SOG dataset (`lod-meta.json`), or\n * an XGRIDS `.lcc2` or `.lcc` (manifest v3–v5) dataset - into the pool of a\n * dynamic-capacity {@link SplatMesh}, keeping the resident splat count within\n * a per-device budget.\n *\n * Each frame it asks the scene's {@link LodSource} which spatial regions\n * should be resident for the current camera, fetches and decodes the chunk\n * files that back them (off the main thread, via {@link ChunkLoader}), and\n * appends/removes pool ranges to match - loading coarse first so the scene\n * appears quickly and refining near the camera. A coarse full-scene shell\n * always fits the budget, so the view is never blank and the budget is\n * never exceeded.\n *\n * WebGPU only (inherited from the dynamic-capacity pool). View-dependent color\n * (higher-order SH) works for every streamed format: LCC `Quality` captures store it per\n * splat, and a SOG scene's per-file palette shN is converted to that same\n * per-splat packed form at decode so it too survives the shared pool (M11, opt\n * in via {@link StreamedSplatMeshOptions.shBands}; see\n * `docs/formats/streamed-shn-notes.md`).\n */\nexport class StreamedSplatMesh extends SplatMesh {\n private readonly scene: StreamedScene;\n private readonly loader = new ChunkLoader();\n /**\n * Cap the *classic* (non-page-table) chunk cache evicts against.\n *\n * Mutable because a shared {@link ChunkCacheBudget} re-splits it as the camera\n * moves; without a budget it stays at the value `options.cpuCacheBytes` or the\n * device default set at construction.\n */\n private cpuCacheBytes: number;\n private budgetValue: number;\n private readonly maximumBudget: number;\n /** Spark's per-mesh `lodScale`; divides the page-table cut limit. */\n private lodScaleValue: number;\n /** Set once the governed budget has been reported as exceeding an explicit\n * `foveationDrawBudget`, so the warning is issued at most once. */\n private warnedDrawTargetCap = false;\n private readonly stagedSwapsEnabled: boolean;\n /** Classic LCC must keep old cell coverage while a replacement is pending. */\n private readonly neverRetireCoverageEarly: boolean;\n private readonly appendCap: number;\n private readonly onPerformanceEvent: ((event: StreamedSplatPerformanceEvent) => void) | undefined;\n private compactionCount = 0;\n\n private readonly cache = new Map<number, CachedChunk>();\n /** Running byte total of {@link cache}; maintained by {@link cacheChunk}, eviction and dispose. */\n private cacheBytesTotal = 0;\n /** In-flight chunk fetches. The kind is kept so a weight change can shed the\n * speculative ones without touching the detail that is actually on screen. */\n private readonly fetching = new Map<\n number,\n { controller: AbortController; kind: ChunkFetchKind; classicWant?: ClassicFetchWant }\n >();\n /** This mesh's camera-projected share of the scene's fetch bandwidth. */\n private fetchWeight: (() => number) | undefined;\n /** Scene-wide fetch arbitration, when the host shares one; see `requestChunk`. */\n private readonly fetchScheduler: ChunkFetchScheduler | undefined;\n private readonly fetchHandle: ChunkFetchHandle | undefined;\n /**\n * Blob-URL dataset from {@link loadLocal}, owned by this mesh so its object\n * URLs are revoked on {@link dispose} rather than leaking for the document's\n * lifetime. Undefined for every network-loaded mesh.\n */\n private localSource: SplatDatasetSource | undefined;\n /** Scene-wide chunk-cache ceiling, when the host shares one. */\n private readonly cacheBudget: ChunkCacheBudget | undefined;\n private cacheBudgetHandle: ChunkCacheHandle | undefined;\n /**\n * The cap this mesh's frontier worker is currently evicting against.\n *\n * Mirrored on the main thread so `applyCacheAllowance` can skip no-op posts\n * and so `fetchCounts.cacheLimitBytes` stays truthful between plans.\n */\n private cacheLimitBytes = 0;\n private readonly resident = new Map<string, { run: LodRun; handle: SplatRange }>();\n /** Replacement runs hidden while their pool data is uploaded in bounded segments. */\n private readonly staged = new Map<\n string,\n {\n run: LodRun;\n handle: SplatRange;\n uploadedCount: number;\n }\n >();\n /** Files awaiting a backoff retry after a transient fetch/decode error. */\n private readonly retrying = new Map<number, { attempts: number; readyAt: number }>();\n /** Files given up on after {@link MAX_CHUNK_ATTEMPTS} failures. */\n private readonly failedFiles = new Set<number>();\n\n /**\n * When true, each resident run writes its LOD `level` into the `lodLevel`\n * float channel for false-color debug modifiers.\n */\n private lodLevelDebug = false;\n private lodLevelChannelReady = false;\n private lodLevelScratch: Float32Array | undefined;\n\n /** Desired-but-not-resident files this tick; protected from cache eviction. */\n private readonly neededFiles = new Set<number>();\n\n /** Non-null in `foveationMode: 'page-table'`: the worker that owns the chunk\n * cache + traversal + pager off the main thread, and the always-active slab it\n * pages the returned frontier into. */\n private readonly frontierWorker: Worker | null;\n /**\n * The frontier's slots, as a list of equally sized pages rather than one\n * contiguous run.\n *\n * Slot `i` lives in page `i / slabPageSplats` at offset `i %\n * slabPageSplats`. The pager only ever addresses slots, so where those\n * pages sit in the pool is the mesh's business - which is what lets a mesh\n * hold non-contiguous storage, and ultimately lets several meshes interleave\n * in one pool instead of each reserving its whole ceiling as one block.\n * (Spark's pager does the same thing one level down, binding fixed pages to\n * `(source, chunk)` pairs.)\n */\n private readonly slabPages: SplatRange[] = [];\n /** Most slots the slab may ever hold - the construction capacity. */\n private slabCeiling = 0;\n /** Slot count the worker's pager was last told about. */\n private pagerSlots = 0;\n /** Consecutive ticks the wave gate has held retirements back. */\n private retireHeldTicks = 0;\n /** Backing store for {@link planTimings}. */\n private readonly planTimingsValue = {\n applyMs: 0,\n worstApplyMs: 0,\n writeMs: 0,\n residentMs: 0,\n moves: 0,\n appends: 0,\n worstSplats: 0,\n };\n /**\n * Backing store for {@link fetchCounts}. Lifetime totals, because the question\n * they answer is about a *steady state* - \"this keeps streaming after the view\n * settled\" - which a per-frame or windowed number cannot express.\n */\n private readonly fetchCountsValue = {\n priority: 0,\n base: 0,\n sweep: 0,\n evicted: 0,\n uncovered: 0,\n retiredEarly: 0,\n cacheFull: false,\n cacheBytes: 0,\n cacheLimitBytes: 0,\n };\n /**\n * The screen-radius band the scene asked for, kept so the band can be scaled\n * with the solved frontier cut and always relative to the original - scaling\n * the live values repeatedly would drift. Null when the scene has no band.\n */\n private readonly frontierBandBase: { min: number; max: number } | null = null;\n /**\n * Splats per slab page for this mesh: {@link SLAB_PAGE_SPLATS}, or the whole\n * capacity when that is smaller. Spark can use one fixed page size because\n * its pool is a single large arena; here a mesh may be smaller than a page,\n * and rounding it up to one would waste most of the reservation.\n */\n private readonly slabPageSplats: number = SLAB_PAGE_SPLATS;\n /** Target drawn-splat count for the page-table frontier; see the constructor. */\n private pageTableDrawBudget = 0;\n /** The unclamped draw target (`foveationDrawBudget` or the default), kept so\n * `setBudget` can re-derive the effective draw budget when the pool budget\n * moves (e.g. under a `BudgetGovernor`). */\n private pageTableDrawTarget = 0;\n /** Whether {@link pageTableDrawTarget} came from an explicit\n * `foveationDrawBudget` - a caller-chosen hard cap worth warning about when a\n * governed budget outgrows it, rather than the library's own default. */\n private pageTableDrawTargetExplicit = false;\n /** Last frontier's drawn (non-degenerate) splat count - the true on-screen size\n * in `page-table` mode, where the slab is fully \"active\" but mostly degenerate. */\n private pageTableDrawn = 0;\n private frontierConverged = true;\n private pendingFrontierSplats = 0;\n private staleResidentSplats = 0;\n private lastPlanAppends = 0;\n private lastPlanMoves = 0;\n private lastPlanGeneration = 0;\n private lastPlanBudget = 0;\n private lastPlanCamera: readonly [number, number, number] | null = null;\n private firstFrontierCamera: readonly [number, number, number] | null = null;\n /** Monotonic reschedule id; a stale plan (superseded by a newer request) is\n * dropped. `pageTableInFlight` coalesces to one outstanding traversal. */\n private pageTableSeq = 0;\n private pageTableInFlight = false;\n private pageTableDisposed = false;\n /** Files whose data has been forwarded to the worker (so we don't refetch). */\n private readonly pageTableCachedFiles = new Set<number>();\n /** Chunks the last frontier wanted but did not have, biggest-on-screen first.\n * These outrank the background sweep - they are the detail actually on screen. */\n private pageTableFetchPriority: readonly number[] = [];\n /** Frontier-cut target node size (px) and foveation ramp; see `frontierView`. */\n private pageTableTargetPx = DEFAULT_FOVEATION_TARGET_PX;\n private pageTableFoveation: FrontierFoveation = FRONTIER_FOVEATION_DEFAULTS;\n /** Drawing-buffer height, sampled in `update` so `reschedule` can derive the\n * cut limit the same way the material does (`targetPx / focalY`). */\n private pageTableViewportY = 0;\n /** Frontier cut on foveated `size / distance`. Re-derived each reschedule once\n * the drawing buffer is known; the initial value only covers the first frame. */\n private pageTableLimit = 0.02;\n /**\n * Whether the worker's cache is sitting at its cap: sweeping past that point\n * only evicts what the frontier is using.\n *\n * Re-derived from every plan rather than latched. It used to latch on the\n * first eviction, which was safe only while the cap was sized to the capture\n * and evictions therefore meant \"this will never fit\". Under a scene-wide\n * {@link ChunkCacheBudget} evictions are routine - a far mesh gives bytes back\n * and is trimmed - and latching would kill its sweep for the session, so a\n * mesh that went cold could never re-warm when the camera returned.\n */\n private pageTableCacheAtLimit = false;\n\n /** Per-splat channels whose edits survive chunk eviction/reload (M7.6). */\n private readonly persistentChannels = new Map<string, PersistentChannel>();\n\n /** Chunk-file index of the always-resident environment tile, if the scene ships one. */\n private readonly envFile: number | undefined;\n /** Whether the environment tile should be visible; toggled live. */\n private envEnabled: boolean;\n /** Pool handle of the environment tile once it has loaded (kept for toggling). */\n private envHandle: SplatRange | undefined;\n /** Env splat count, measured when the tile decodes; 0 until then. */\n private envSplatCount = 0;\n /** Set when the env tile is larger than the whole pool - terminal, warned once. */\n private envUnfit = false;\n\n /**\n * Startup hold. `'capture'` waits for the first schedule after the host\n * applies the final camera; `'holding'` freezes that coverage set.\n */\n private initialRevealPhase: 'off' | 'capture' | 'holding' | 'released' = 'off';\n /** Which hold, if any, was armed at construction. Survives release for recapture. */\n private readonly initialRevealHold: 'off' | 'hold-near-l0' | 'hold-coverage' = 'off';\n /** Frozen nearby-detail / in-view coverage runs for {@link initialRevealPhase} `'holding'`. */\n private frozenCriticalRuns: LodRun[] | null = null;\n /** Timestamp of the final-camera capture that began the current hold. */\n private initialRevealStartedAt: number | undefined;\n private initialRevealStateValue: InitialRevealState = { status: 'disabled' };\n\n /** Fetch settings this mesh was loaded with, reused for collision meshes. */\n private readonly requestOptions: SplatRequestOptions | undefined;\n /** In-flight or settled collision load; see {@link loadCollisionMeshes}. */\n private collisionTiles: Promise<readonly CollisionMeshTile[]> | undefined;\n private collisionAbort: AbortController | undefined;\n\n private pendingWork = true;\n private lastScheduleTime = -Infinity;\n /** Reused leaf-coverage bitmap for {@link substituteCoverage}; grows only. */\n private coverageScratch: Uint8Array | undefined;\n private readonly lastCameraPos = new THREE.Vector3(Infinity, Infinity, Infinity);\n private readonly lastCameraQuat = new THREE.Quaternion();\n\n /**\n * Fetches a scene manifest and prepares a mesh sized to the budget.\n * Accepts a Streamed SOG manifest (`lod-meta.json`) or an XGRIDS `.lcc2` or\n * `.lcc` (manifest v3–v5) dataset - all stream through the same machinery. Both LCC\n * generations are normalized to the established XGRIDS/Spark Three.js\n * coordinate frame; streamed SOG orientation is unchanged.\n *\n * A `.lcc` dataset needs a server that answers HTTP range requests: its\n * splats live in one large `data.bin` that is never fetched whole.\n *\n * @param manifestUrl - URL of the scene's `lod-meta.json`, `.lcc2` or `.lcc`\n * file; relative URLs resolve against `options.baseUrl` (or the page).\n * @throws Rejects with {@link SplatLoadError} on any resolve/fetch/parse\n * failure, or a `DOMException` named `AbortError` when `options.signal` fires.\n */\n static async load(\n manifestUrl: string | URL,\n options: StreamedSplatMeshOptions = {},\n ): Promise<StreamedSplatMesh> {\n const absoluteUrl = resolveSplatUrl(manifestUrl, options.baseUrl).href;\n const lower = absoluteUrl.toLowerCase();\n const format: Exclude<StreamedSplatFormat, 'auto'> =\n options.format !== undefined && options.format !== 'auto'\n ? options.format\n : lower.endsWith('.lcc2')\n ? 'lcc2'\n : lower.endsWith('.lcc')\n ? 'lcc'\n : lower.endsWith('.rad')\n ? 'rad'\n : 'streamed-sog';\n return StreamedSplatMesh.fromSource(\n httpDatasetSource(absoluteUrl, options.request),\n format,\n options,\n );\n }\n\n /**\n * Prepares a mesh from a folder dropped into the page - the same streamed\n * formats, read straight off the user's disk with no server and no upload.\n *\n * Every file becomes a `blob:` URL, which answers range requests exactly as\n * an HTTP origin does, so a multi-hundred-megabyte `.lcc` `data.bin` streams\n * chunk-by-chunk rather than being read whole.\n *\n * @param files - The folder's files, keyed by path relative to its root\n * (as the demo drop-zone `readDirectory` walk produces).\n * @throws Rejects with {@link SplatLoadError} - phase `'manifest'` when the\n * folder holds no (or more than one) recognizable scene manifest - or a\n * `DOMException` named `AbortError` when `options.signal` fires.\n */\n static async loadLocal(\n files: ReadonlyMap<string, File>,\n options: StreamedSplatMeshOptions = {},\n ): Promise<StreamedSplatMesh> {\n let dataset: ReturnType<typeof createLocalDataset>;\n try {\n dataset = createLocalDataset(files);\n } catch (error) {\n // Not `toSplatLoadError`: a folder without a manifest is not retryable.\n throw error instanceof SplatLoadError\n ? error\n : new SplatLoadError(error instanceof Error ? error.message : String(error), {\n phase: 'manifest',\n url: 'local-folder',\n retryable: false,\n cause: error,\n });\n }\n try {\n const mesh = await StreamedSplatMesh.fromSource(dataset.source, dataset.format, options);\n // Hand ownership to the mesh rather than disposing here: a streamed mesh\n // keeps fetching chunk URLs for its whole life, so revoking now would\n // break it. Without this the blob URLs (and the `File` blobs they pin)\n // stayed registered for the document's lifetime - `dispose` was reachable\n // only from the catch below, i.e. only when the load *failed*.\n mesh.localSource = dataset.source;\n return mesh;\n } catch (error) {\n dataset.source.dispose(); // release the blob URLs this drop created\n throw error;\n }\n }\n\n /** Shared load path: fetch the manifest from a source, then build the scene. */\n private static async fromSource(\n source: SplatDatasetSource,\n format: Exclude<StreamedSplatFormat, 'auto'>,\n options: StreamedSplatMeshOptions,\n ): Promise<StreamedSplatMesh> {\n // `format` is what makes this per-scene rather than per-device: an LCC-class\n // capture's splats grow as its budget tightens, so the two classes want\n // different ceilings on the same phone. An explicit `budget` still wins;\n // `budgetCap` tightens the resolved default without replacing it.\n const deviceProfile = options.deviceProfile;\n const deviceBudget = resolveSplatBudget(options.budget, deviceProfile, {\n format,\n ...(options.budgetCap === undefined ? {} : { cap: options.budgetCap }),\n });\n // `maxBudget` separates two things the budget used to conflate: what the\n // mesh renders now, and the most it could ever be asked to render. The pool\n // is sized from the ceiling (it cannot grow later), so a governed mesh has\n // somewhere to grow into; without one the two are equal and every existing\n // caller behaves exactly as before.\n const ceilingBudget =\n options.maxBudget === undefined\n ? deviceBudget\n : resolveSplatBudget(options.maxBudget, deviceProfile);\n if (ceilingBudget < deviceBudget) {\n throw new RangeError(\n `StreamedSplatMesh: maxBudget (${ceilingBudget}) must be >= budget (${deviceBudget}).`,\n );\n }\n // `.rad` now defaults to the `page-table` selected-index pager, which pages only\n // the *selected* frontier (Spark's model) and wants the full device budget -\n // Spark runs this scene at ~4M. (The old 2.5M `RAD_PREFIX_DEFAULT_BUDGET` cap\n // was a fallback for the whole-scene prefix reader before the pager landed;\n // capping the frontier at 2.5M starves it and it stays coarse near the camera.)\n //\n // The scene is built against the *ceiling*: a foveated `.rad` reports\n // `maxResidentSplats: options.budget`, and that number caps the pool below -\n // so seeding it with the initial budget would undo the headroom. The source's\n // live budget is overwritten with the initial value once the scene exists.\n const sourceOptions = {\n budget: ceilingBudget,\n lodBaseDistance: options.lodBaseDistance ?? 10,\n lodMultiplier: options.lodMultiplier ?? 2,\n };\n // Unset means \"every band the capture carries\", so a Quality scene shows\n // its real colors without the caller knowing the format - except on a\n // `smooth` profile (the default on mobile), where the bandwidth and the\n // ~64 B/splat of extra pool textures are exactly what that profile exists\n // to avoid. The scene then clamps this to what the file actually has.\n const shBands =\n options.shBands ??\n (resolveSplatPerformanceProfile(options.performanceProfile, deviceProfile) === 'smooth'\n ? 0\n : MAX_SH_BANDS);\n // Resolved once here rather than at the options bag below, so the device is\n // probed a single time per load.\n const radMaxStdDev = recommendedRadMaxStdDev(deviceProfile);\n // Whether the finest-level lift below may raise this mesh's budget. A caller\n // that named a size gets that size - see the `ceiling` computation. `.rad`\n // needs to know up front, because the lift decides whether its leaves fit\n // the budget and therefore whether it reads as a prefix or foveates.\n //\n // `budgetCap` counts as naming a size for this purpose even though it is\n // only a ceiling: the lift raises the budget to hold a finest level whole,\n // which is exactly what a caller asking for \"no more than N\" has ruled out.\n // Without this a desktop performance mode would lift straight back over its\n // own cap, to as much as `FINEST_LEVEL_BUDGET_MAX`.\n const budgetLifts =\n options.allowFinestLevelLift === true\n ? options.budgetCap === undefined\n : options.budget === undefined &&\n options.maxBudget === undefined &&\n options.budgetCap === undefined;\n\n // A `.rad` \"manifest\" is the file's own binary header, read by range - it\n // must not be fetched whole (it is the multi-hundred-megabyte scene) or\n // JSON-parsed like the other formats' manifests.\n const signal = options.signal;\n signal?.throwIfAborted();\n let scene: StreamedScene;\n if (format === 'rad') {\n // The manifest here is the `.rad` file's own header (ranged reads).\n // Contract: only SplatLoadError or AbortError leaves this path.\n try {\n const { buildRadScene } = await import('../formats/rad');\n // `shBands` is a *cap* here: a `.rad` declares its own `maxSh`, so the\n // resolved value decides how much of it to keep. Passing it is what lets\n // the `smooth` profile (and an explicit `shBands: 0`) decline SH on a\n // `.rad` at all - without it the file's bands were adopted wholesale.\n scene = await buildRadScene(source, sourceOptions, options.request, shBands, budgetLifts);\n } catch (error) {\n if (isAbortError(error)) throw error;\n throw toSplatLoadError(error, { phase: 'manifest', url: source.manifestUrl });\n }\n } else {\n let response: Response;\n try {\n response = await fetch(source.manifestUrl, toRequestInit(options.request, signal));\n } catch (error) {\n // A raw fetch TypeError (network/CORS) must not escape unwrapped.\n if (isAbortError(error)) throw error;\n throw toSplatLoadError(error, { phase: 'fetch', url: source.manifestUrl });\n }\n if (!response.ok) {\n throw toSplatLoadError(\n new Error(`Failed to load manifest ${source.manifestUrl}: HTTP ${response.status}`),\n { phase: 'manifest', url: source.manifestUrl, status: response.status },\n );\n }\n try {\n // `response.json()` is typed `any`; the parsers below validate it.\n const json: unknown = await response.json();\n if (format === 'lcc2') {\n // Import the public format entry rather than an internal chunk. Rollup may\n // represent internal chunks through synthetic namespace exports, which a\n // consuming production build can incorrectly tree-shake while rebundling.\n const { buildLcc2Scene } = await import('../formats/lcc');\n // LCC2 tiles are SOG v2; SH is opt-in like Streamed SOG (tiles may be DC-only).\n scene = buildLcc2Scene(json, source, sourceOptions, options.shBands ?? 0);\n } else if (format === 'lcc') {\n const { buildLccScene } = await import('../formats/lcc');\n scene = await buildLccScene(json, source, { ...sourceOptions, shBands });\n } else {\n // Streamed SOG SH is strictly opt-in: unlike LCC (whose manifest\n // states its band count), a SOG manifest never says whether the\n // tiles carry shN, so the \"every band the capture carries\" default\n // cannot apply - an explicit `shBands` turns it on.\n scene = buildSogScene(json, source, sourceOptions, options.shBands ?? 0);\n }\n } catch (error) {\n if (isAbortError(error)) throw error;\n throw toSplatLoadError(error, { phase: 'manifest', url: source.manifestUrl });\n }\n }\n // Aborted while the manifest was in flight or parsing: nothing built yet.\n signal?.throwIfAborted();\n\n // An LCC capture offers only its finest level (see `buildLccScene`),\n // so a budget below it does not soften the scene - it deletes whole 30 m\n // cells. Take the level whole when it is small enough to be worth it. A\n // `.rad` refines uniformly (no camera foveation - its chunk DAG is too\n // entangled for a chunk-cut, see `docs/formats/rad-notes.md`), so a budget below the\n // leaf count leaves coarse blobs *everywhere*, worst up close; lifting to the\n // full leaf set when it fits makes a moderate scene sharp. An explicit budget\n // is a hard cap for A/B runs and always wins.\n //\n // The lift raises the *ceiling*, since that is what sizes the pool, and with\n // nothing pinned the initial budget rides up with it - the established\n // behavior. A host that pinned `maxBudget` gets exactly that ceiling and no\n // more: it asked for a specific memory envelope, and silently allocating\n // past it would be the one surprise this option must not spring.\n // `budgetLifts` is the same predicate `buildRadScene` was handed above, so\n // the path it chose and the budget applied here cannot disagree.\n const ceiling =\n (format === 'lcc' || format === 'rad') && budgetLifts\n ? liftBudgetToFinestLevel(ceilingBudget, scene.maxResidentSplats, deviceProfile)\n : ceilingBudget;\n const budget = options.maxBudget === undefined ? ceiling : Math.min(deviceBudget, ceiling);\n scene.source.budget = budget;\n\n // Pool capacity: 40% over whatever can actually be resident - the ceiling,\n // or the finest level if the whole scene is smaller than it. The\n // slack absorbs per-run row-alignment waste (hundreds of runs each waste\n // up to a row) and the append-before-remove window during LOD swaps;\n // too little slack makes small-budget swaps converge slowly under\n // capacity pre-check pressure. ~64 B/splat of GPU memory.\n const residentCeiling = Math.min(ceiling, scene.maxResidentSplats);\n // Inactive staging must temporarily hold both sides of a large atomic\n // replacement. Ten extra percentage points avoid full-pool compaction on\n // the measured 1.6M-splat restaurant swap (~22 MB at a 3.5M budget).\n const capacityFactor = options.experimentalStagedSwaps !== false ? 1.5 : 1.4;\n const capacityRows = Math.max(\n 1,\n Math.ceil((residentCeiling * capacityFactor) / DATA_TEXTURE_WIDTH),\n );\n\n // Resolve page-table worker before construction (constructors cannot await).\n // SOG/LCC hosts never pay for the frontier worker blob.\n const resolvedFoveationMode = scene.foveation\n ? resolveSplatFoveationMode(\n options.foveationMode,\n format === 'rad' ? 'page-table' : 'frontier',\n )\n : options.foveationMode === undefined\n ? undefined\n : resolveSplatFoveationMode(options.foveationMode);\n let FrontierWorkerCtor: InlineWorkerCtor | undefined;\n if (isPageTableFoveation(resolvedFoveationMode)) {\n const mod = await import('../formats/rad/frontier-worker?worker&inline');\n FrontierWorkerCtor = mod.default;\n signal?.throwIfAborted();\n // The worker cuts the tree itself; per-splat `parent_size` (a GPU-cut input)\n // would be computed for every chunk and never read. See `needsParentSizes`.\n const source = scene.source as { needsParentSizes?: boolean };\n if (source.needsParentSizes !== undefined) source.needsParentSizes = false;\n }\n\n // The scene decides the effective bands: asking for SH on a capture that\n // has none must not allocate SH textures for it.\n const mesh = new StreamedSplatMesh(\n scene,\n budget,\n capacityRows * DATA_TEXTURE_WIDTH,\n {\n ...options,\n // Classic LCC's useful first frame is the bounded nearby-detail set,\n // not its coarse shell. `.lcc2` waits for in-view coarsest coverage\n // so empty octree squares do not flash. Keep every other format\n // progressive, and let a caller explicitly request progressive for A/B.\n ...(format === 'lcc' && options.initialReveal === undefined\n ? { initialReveal: 'hold-near-l0' as const }\n : {}),\n ...(format === 'lcc2' && options.initialReveal === undefined\n ? { initialReveal: 'hold-coverage' as const }\n : {}),\n // The resolved ceiling, not the caller's raw option: it may have been\n // lifted to a moderate capture's leaf count above.\n maxBudget: ceiling,\n // This line overrides `...options` above, so a declined request has to\n // survive it - that is how `.rad` came to ignore both the `smooth`\n // profile and an explicit `shBands: 0`. Only *zero* is re-applied here,\n // never a partial reduction: the builders already honour partial\n // requests by generating that many bands, whereas forcing a smaller\n // count past one would mismatch the decoded chunk and degrade to\n // neutral SH (see `SplatMesh.writePackedSh`).\n shBands: shBands === 0 ? 0 : (scene.shBands ?? 0),\n // Spark ships Mip-Splatting antialiasing ON (blurAmount 0.3 *with* opacity\n // compensation `α·√(detRaw/detBlur)`). Match that default for `.rad`: the\n // 0.3 low-pass without the compensation makes splats too opaque (uniform\n // blur) and leaves anisotropic splats bright (needle spikes).\n antialias: options.antialias ?? (format === 'rad' ? true : undefined),\n // Older XGRIDS LCC uses a smaller, compensated projected low-pass.\n ...(format === 'lcc' ? { projectedFilterProfile: 'lcc' as const } : {}),\n // Match Spark's `.rad` render exactly: the LOD alpha encoding + merged-node\n // σ-cutoff/super-Gaussian, and the √8 (≈2.83σ) base cutoff Spark defaults to.\n // An explicit `lodAlpha` (e.g. `?lodAlpha=0`) wins for A/B.\n //\n // The √8 cutoff is *desktop only* - see `recommendedRadMaxStdDev`, which\n // returns undefined on mobile so the `SplatMesh` constructor applies the\n // same 4 ceiling `.rad` was the only format escaping. An explicit\n // `maxStdDev` still wins, through `...options` above.\n ...(format === 'rad'\n ? {\n lodAlpha: options.lodAlpha ?? true,\n ...(options.maxStdDev === undefined && radMaxStdDev !== undefined\n ? { maxStdDev: radMaxStdDev }\n : {}),\n }\n : {}),\n // A foveated scene renders whole chunks and picks the LOD cut per splat.\n // `.rad` defaults to Spark's selected-index page table (only the frontier is\n // paged to the GPU, so the whole device budget buys on-screen detail); other\n // foveated formats keep the GPU `frontier` cut. `foveationMode: 'band'` (or\n // `'frontier'`) forces the legacy paths for A/B. Overrides any caller blob cull.\n ...(scene.foveation\n ? {\n foveationMode: resolvedFoveationMode,\n minSplatScreenRadius: scene.foveation.minScreenRadiusPx,\n maxSplatScreenRadius: scene.foveation.maxScreenRadiusPx,\n }\n : {}),\n },\n FrontierWorkerCtor,\n format === 'lcc',\n );\n // LCC carries its Z-up→Y-up matrix in both orientation modes (format\n // semantics); streamed SOG and Spark `.rad` get the cosmetic 180°-X flip in\n // 'y-up', matching Spark's documented OpenCV→OpenGL scene correction.\n const correction =\n scene.formatTransform ?? (mesh.orientation === 'y-up' ? yUpTransformForFormat(format) : null);\n if (correction) {\n mesh.matrix.copy(correction);\n mesh.matrix.decompose(mesh.position, mesh.quaternion, mesh.scale);\n mesh.matrixWorldNeedsUpdate = true;\n }\n // A last-instant abort must not leak the mesh (its loader worker, frontier\n // worker, and pool textures) - dispose it and reject like every other abort.\n if (signal?.aborted) {\n mesh.dispose();\n signal.throwIfAborted();\n }\n return mesh;\n }\n\n private constructor(\n scene: StreamedScene,\n budget: number,\n capacity: number,\n options: StreamedSplatMeshOptions,\n FrontierWorkerCtor?: InlineWorkerCtor,\n neverRetireCoverageEarly = false,\n ) {\n super({ capacity }, options);\n this.scene = scene;\n this.budgetValue = budget;\n // The pool was allocated for the ceiling, so `setBudget` may climb to it.\n // Never below `budget` - that would make the mesh's own starting budget\n // unreachable.\n this.maximumBudget =\n options.maxBudget === undefined\n ? budget\n : Math.max(budget, resolveSplatBudget(options.maxBudget));\n this.lodScaleValue = validateLodScale(options.lodScale);\n this.stagedSwapsEnabled = options.experimentalStagedSwaps !== false;\n this.neverRetireCoverageEarly = neverRetireCoverageEarly;\n this.appendCap = validateAppendCap(options.maxSplatsPerSwap);\n const holdCoverage =\n options.initialReveal === 'hold-coverage' && this.scene.source.coverageRunsFor !== undefined;\n const holdNearL0 = options.initialReveal === 'hold-near-l0' && neverRetireCoverageEarly;\n if (holdCoverage || holdNearL0) {\n this.initialRevealHold = holdCoverage ? 'hold-coverage' : 'hold-near-l0';\n this.initialRevealPhase = 'capture';\n this.initialRevealStateValue = {\n status: 'pending',\n stagedSplats: 0,\n totalSplats: 0,\n readyGroups: 0,\n totalGroups: 0,\n };\n } else {\n this.initialRevealHold = 'off';\n this.initialRevealPhase = 'off';\n this.initialRevealStateValue = { status: 'disabled' };\n }\n this.onPerformanceEvent = options.onPerformanceEvent;\n this.cpuCacheBytes = options.cpuCacheBytes ?? defaultCpuCacheBytes();\n this.requestOptions = options.request;\n this.envFile = scene.environment?.file;\n this.envEnabled = options.environmentEnabled !== false;\n this.fetchWeight = options.fetchWeight;\n this.fetchScheduler = options.fetchScheduler;\n this.cacheBudget = options.cacheBudget;\n // Registered from the constructor so the very first reschedule is already\n // arbitrated - on a multi-mesh scene the load-time burst is the whole\n // problem, and a mesh that joins late has already taken its slots.\n this.fetchHandle = this.fetchScheduler?.register({\n // No weight supplied: claim an equal share rather than none, so a partly\n // wired host degrades to round-robin instead of silently starving.\n weight: () => this.fetchWeight?.() ?? 1,\n onSlotAvailable: () => {\n this.pendingWork = true;\n },\n shedFetches: (kind) => this.abortFetches(kind),\n });\n\n // Join the scene's cache envelope, if the host shares one. Registered here\n // rather than beside `fetchScheduler` because the ceiling needs `scene`, and\n // *before* the page-table branch because every streamed mesh has a chunk\n // cache - a scene of `.lcc2` additional meshes would otherwise sit outside the one\n // number that is supposed to bound the whole scene.\n //\n // The ceiling is the most this mesh could put to use. A page-table mesh\n // needs more: its frontier can only refine into chunks that are resident\n // *together*, so a whole `.rad` view spans many chunks (cest_ca: ~249 x\n // ~6.5 MB decoded ~ 1.6 GB) and a cache holding a fraction of them leaves\n // the near frontier thrashing and the scene \"coarse forever\". Hence a\n // ceiling above the host's per-mesh figure, bounded by what this capture\n // could even hold.\n //\n // That figure used to be the *cap*, at a flat 2 GiB: right for one big scene\n // and wrong for a wall of additional meshes, where 13 meshes were each allowed 2 GiB\n // against a 4 GiB tab heap. Bounding it by the capture helped and did not\n // fix it - thirteen 500 MB captures still allow 6.5 GB, because nothing\n // related the meshes to each other. The budget is that missing relation.\n const isPageTable = isPageTableFoveation(options.foveationMode);\n const cacheCeilingBytes = isPageTable\n ? Math.max(\n this.cpuCacheBytes,\n Math.min(PAGETABLE_CACHE_FLOOR_BYTES, estimateSceneDecodedBytes(scene)),\n )\n : this.cpuCacheBytes;\n this.cacheBudgetHandle = this.cacheBudget?.register({\n // The governor weight `fetchWeight` already carries, so cache and network\n // follow the same camera-projected measure. The `1` fallback matches\n // `requestChunk`: a host that never wired weights gives every mesh an\n // equal claim rather than none.\n weight: () => this.fetchWeight?.() ?? 1,\n ceilingBytes: cacheCeilingBytes,\n onAllowanceChanged: (bytes) => this.applyCacheAllowance(bytes),\n });\n this.cacheLimitBytes =\n this.cacheBudget && this.cacheBudgetHandle\n ? this.cacheBudget.allowanceFor(this.cacheBudgetHandle)\n : cacheCeilingBytes;\n // The classic path evicts against `cpuCacheBytes` directly; the page-table\n // path evicts inside its worker, which is told the number in `init` below.\n if (!isPageTable) this.cpuCacheBytes = this.cacheLimitBytes;\n this.fetchCountsValue.cacheLimitBytes = this.cacheLimitBytes;\n\n // Page-table mode: reserve the whole pool as one always-active slab (all-zeros\n // → degenerate/invisible until paged) and spin up the worker that owns the\n // chunk cache + traversal + pager. Spark's selected-index model, off-thread.\n if (isPageTableFoveation(options.foveationMode)) {\n if (!FrontierWorkerCtor) {\n throw new Error('StreamedSplatMesh: page-table foveation requires the frontier worker.');\n }\n // Frontier draw target: an explicit `foveationDrawBudget` (`?foveationDraw=`)\n // wins for A/B; otherwise Spark's default. Never above the pool budget.\n this.pageTableDrawTarget = options.foveationDrawBudget ?? PAGETABLE_DRAW_BUDGET;\n this.pageTableDrawTargetExplicit = options.foveationDrawBudget !== undefined;\n this.pageTableDrawBudget = Math.min(budget, this.pageTableDrawTarget);\n this.pageTableTargetPx = options.foveationTargetPx ?? DEFAULT_FOVEATION_TARGET_PX;\n this.pageTableFoveation = { ...FRONTIER_FOVEATION_DEFAULTS, ...options.frontierFoveation };\n if (\n options.minSplatScreenRadius !== undefined ||\n options.maxSplatScreenRadius !== undefined\n ) {\n this.frontierBandBase = {\n min: options.minSplatScreenRadius ?? 0,\n max: options.maxSplatScreenRadius ?? 0,\n };\n }\n // The slab starts empty: only the used prefix is ever active (drawn and\n // sorted) - each plan advances it to the resident count. Activating the\n // whole pool-sized slab would sort and vertex-process millions of\n // degenerate tail slots every frame.\n //\n // Reserved as pages rather than one block: the pager addresses slots, so\n // the storage behind them need not be contiguous, and page-sized\n // reservations are what let this mesh later grow and release storage with\n // its budget instead of holding its ceiling for the whole session.\n this.slabPageSplats = Math.min(SLAB_PAGE_SPLATS, capacity);\n // Reserve only what the current draw budget needs. The ceiling stays a\n // *permission* to grow rather than an up-front claim, which is what lets\n // many meshes share one pool: a distant mesh holds a page or two while\n // the one the camera approaches climbs. `syncSlabPages` moves the line as\n // the governor changes the budget.\n this.slabCeiling = capacity;\n this.syncSlabPages(this.pageTableDrawBudget);\n this.frontierWorker = new FrontierWorkerCtor();\n this.frontierWorker.onmessage = (e: MessageEvent<FrontierPlanMessage>) =>\n this.applyFrontierPlan(e.data);\n this.pagerSlots = this.slabSlots;\n // The frontier can only refine into chunks that are resident *together*.\n // A whole `.rad` view spans many chunks (cest_ca: ~249 × ~6.5 MB decoded ≈\n // 1.6 GB); a 512 MB cache holds only ~80, so the near frontier thrashes\n // and the scene \"stays coarse forever\". Hence a floor above the host's\n // per-mesh share - but bounded by what this capture could even hold, and\n // never below what the host asked for.\n //\n // The floor used to be a flat 2 GiB, which is right for one big scene and\n // wrong for a wall of additional meshes: 13 of them were each *allowed* 2 GiB\n // against a 4 GiB tab heap, so the one number that was supposed to stop\n // thrashing became the largest single memory risk in the viewer. Bounding\n // it by the capture helped and did not fix it - thirteen 500 MB captures\n // still allow 6.5 GB, because nothing relates the meshes to each other.\n //\n // So with a scene-wide `cacheBudget` this figure stops being the cap and\n // becomes this mesh's *ceiling*: the most it could put to use, which the\n // budget hands out from a scene total by camera weight.\n this.postToWorker({\n type: 'init',\n capacity: this.pagerSlots,\n chunkSize: scene.chunkSize ?? 65536,\n cpuCacheBytes: this.cacheLimitBytes,\n });\n // Seed the worker with the chunk the scene builder already decoded. The\n // tree roots are derived from chunk 0, so without this every traversal up\n // to the (redundant) refetch of chunk 0 returns an empty frontier.\n const bootstrap = scene.bootstrapChunk;\n if (bootstrap) this.forwardChunkToWorker(bootstrap.file, bootstrap.data);\n } else {\n this.frontierWorker = null;\n }\n }\n\n /** Slots the currently reserved pages can hold. */\n private get slabSlots(): number {\n let slots = 0;\n for (const page of this.slabPages) slots += page.count;\n return slots;\n }\n\n /**\n * Reserves or releases slab pages so the slab can hold `wanted` slots, and\n * tells the worker's pager the new slot count.\n *\n * This is the mechanism that makes a shared pool worth having: storage follows\n * the governed budget, so approaching a mesh grows its pages while the ones\n * behind you hand theirs back, instead of every mesh holding its ceiling for\n * the whole session. Growth stops at the construction ceiling and at whatever\n * the pool can actually spare - a mesh that cannot grow simply stays coarse\n * rather than throwing.\n */\n private syncSlabPages(wanted: number): void {\n if (this.slabCeiling === 0) return;\n const target = Math.max(this.slabPageSplats, Math.min(this.slabCeiling, wanted));\n let slots = this.slabSlots;\n\n while (slots < target) {\n const size = Math.min(this.slabPageSplats, this.slabCeiling - slots);\n if (size <= 0) break;\n try {\n this.slabPages.push(this.reserveInactiveRange(size));\n } catch {\n // The pool has no room right now (a nearer mesh holds it). Keep what we\n // have; the next budget change retries.\n break;\n }\n slots += size;\n }\n\n while (this.slabPages.length > 1) {\n const last = this.slabPages[this.slabPages.length - 1] as SplatRange;\n if (slots - last.count < target) break;\n this.slabPages.pop();\n slots -= last.count;\n this.removeRange(last);\n }\n\n if (slots !== this.pagerSlots) {\n this.pagerSlots = slots;\n this.postToWorker({ type: 'resize', capacity: slots });\n // Slots beyond the new count are gone from the pager, so stop drawing\n // them; the next plan re-establishes the resident prefix.\n if (this.pageTableDrawn > slots) this.setSlabResident(slots);\n this.pendingWork = true;\n this.lastScheduleTime = -Infinity;\n }\n }\n\n /**\n * Writes `data` at slot `slot`, splitting the write where it crosses a page\n * boundary. The pager's runs are contiguous in *slot* space, which page\n * storage no longer guarantees is contiguous in the pool.\n */\n private writeSlabSlots(data: SplatData, slot: number, count: number): void {\n let written = 0;\n while (written < count) {\n const at = slot + written;\n const page = this.slabPages[Math.floor(at / this.slabPageSplats)];\n if (!page) return; // beyond reserved storage; `dropped` already warns\n const offset = at % this.slabPageSplats;\n const run = Math.min(count - written, this.slabPageSplats - offset);\n this.overwriteRangeData(page, sliceSplatData(data, written, run), offset);\n written += run;\n }\n }\n\n /** Zeros slots `[slot, slot + count)`, splitting at page boundaries like\n * {@link writeSlabSlots}, so freed slots hold nothing drawable. */\n private degenerateSlabSlots(slot: number, count: number): void {\n let done = 0;\n while (done < count) {\n const at = slot + done;\n const page = this.slabPages[Math.floor(at / this.slabPageSplats)];\n if (!page) return; // beyond reserved storage\n const offset = at % this.slabPageSplats;\n const run = Math.min(count - done, this.slabPageSplats - offset);\n this.degenerateRange(page, offset, run);\n done += run;\n }\n }\n\n /**\n * Draws exactly the first `resident` slots: pages below the boundary are\n * fully active, the page containing it is partially active, the rest are\n * inactive. Freed tail slots simply leave the active list; they are also\n * degenerated (see {@link degenerateSlabSlots}) so that even a slot drawn by\n * mistake shows nothing.\n */\n private setSlabResident(resident: number): void {\n for (let page = 0; page < this.slabPages.length; page++) {\n const prefix = Math.min(\n this.slabPageSplats,\n Math.max(0, resident - page * this.slabPageSplats),\n );\n this.setRangeActivePrefix(this.slabPages[page] as SplatRange, prefix);\n }\n }\n\n /** Typed post to the frontier worker. */\n private postToWorker(msg: FrontierRequest, transfer: Transferable[] = []): void {\n this.frontierWorker?.postMessage(msg, transfer);\n }\n\n /**\n * Applies a new decoded-chunk allowance from the scene's shared\n * {@link ChunkCacheBudget}.\n *\n * Only the cap moves; nothing is dropped here. Both cache implementations\n * evict lazily against it - the page-table worker inside its next\n * `reschedule`, against a frontier that is still current, and the classic\n * path in `evictChunks` on the next tick. Dropping chunks synchronously would\n * pull them out from under resident splats.\n *\n * `pageTableCacheAtLimit` is recomputed here rather than waiting for the next\n * plan, so a *raised* allowance re-arms the background sweep on this tick\n * instead of one idle interval later.\n */\n private applyCacheAllowance(bytes: number): void {\n if (this.disposed) return;\n if (bytes === this.cacheLimitBytes) return;\n this.cacheLimitBytes = bytes;\n if (this.frontierWorker) {\n this.postToWorker({ type: 'cacheBudget', cpuCacheBytes: bytes });\n } else {\n // The classic cache lives on this thread and `evictChunks` reads this\n // field directly, so moving it is the whole update.\n this.cpuCacheBytes = bytes;\n }\n this.fetchCountsValue.cacheLimitBytes = bytes;\n this.pageTableCacheAtLimit = this.fetchCountsValue.cacheBytes >= bytes;\n // Land the new cap on the next tick rather than at the idle interval: a\n // shrink should stop the sweep now, and a grow should resume it now.\n this.pendingWork = true;\n }\n\n /**\n * Whether this scene ships collision meshes - true for an XGRIDS `.lcc` /\n * `.lcc2` dataset that carries them, false for a Streamed SOG scene, which\n * has none.\n */\n get hasCollisionMeshes(): boolean {\n return (this.scene.collision?.meshes.length ?? 0) > 0;\n }\n\n /**\n * Fetches and parses this scene's collision geometry: the triangle meshes an\n * XGRIDS `.lcc` (`collision.lci`) or `.lcc2` (`data/mesh/*.ply`) capture\n * ships beside its splats, for hosts that want collision, ground probes or\n * other spatial queries.\n *\n * The geometry is source-local, like {@link StreamedScene.bounds} - apply\n * this mesh's `matrixWorld` to put it in the frame the splats render in.\n * VLAM! builds no acceleration structure over it and never consults it.\n *\n * Tiles are fetched once and cached; concurrent callers share one load, and\n * a failed load can be retried by calling again. Resolves `[]` for a scene\n * without collision.\n *\n * @throws a `DOMException` named `AbortError` if cancelled, or if\n * {@link dispose} is called while the load is in flight.\n */\n async loadCollisionMeshes(\n options: { signal?: AbortSignal } = {},\n ): Promise<readonly CollisionMeshTile[]> {\n options.signal?.throwIfAborted();\n const collision = this.scene.collision;\n if (!collision || collision.meshes.length === 0) return [];\n\n if (!this.collisionTiles) {\n // Disposing the mesh cancels the load; a caller's own signal is honored\n // per call, so one caller giving up cannot cancel it for the others.\n const controller = new AbortController();\n this.collisionAbort = controller;\n this.collisionTiles = import('../formats/lcc')\n .then(({ loadCollisionMeshTiles }) =>\n loadCollisionMeshTiles(collision, {\n ...(this.requestOptions ? { request: this.requestOptions } : {}),\n signal: controller.signal,\n }),\n )\n .catch((error: unknown) => {\n this.collisionTiles = undefined; // let a retry try again\n throw error;\n });\n }\n\n const { signal } = options;\n if (!signal) return this.collisionTiles;\n // One caller giving up must not cancel the shared load, so its signal\n // races the load rather than aborting it.\n //\n // The listener is removed in `finally` rather than left to `{ once: true }`:\n // when the load wins the race the abort never fires, so `once` never\n // collects it. A host that passes one long-lived signal and calls this\n // repeatedly (a viewer re-loading collision per scene) would otherwise\n // accumulate listeners on that signal, each closing over this mesh.\n let abortListener: (() => void) | undefined;\n try {\n return await Promise.race([\n this.collisionTiles,\n new Promise<never>((_resolve, reject) => {\n abortListener = () => reject(abortReason(signal));\n signal.addEventListener('abort', abortListener, { once: true });\n }),\n ]);\n } finally {\n if (abortListener) signal.removeEventListener('abort', abortListener);\n }\n }\n\n /**\n * Whether this scene ships an always-resident environment/background tile -\n * true for an XGRIDS `.lcc2` capture that carries one (its `env.sog` sky),\n * false for Streamed SOG, `.lcc`, `.rad`, or an `.lcc2` without one.\n */\n get hasEnvironment(): boolean {\n return this.envFile !== undefined;\n }\n\n /** Whether the environment tile is currently set to render. */\n get environmentEnabled(): boolean {\n return this.envEnabled;\n }\n\n /**\n * Splats in the environment tile, measured when it decoded - the manifest\n * does not carry the count. 0 until the tile has loaded (or if the scene\n * ships none). These sit outside the LOD budget, drawing from the pool's\n * capacity headroom.\n */\n get environmentSplatCount(): number {\n return this.envSplatCount;\n }\n\n /**\n * Shows or hides the scene's environment/background tile. The switch is\n * instant and never refetches: once loaded, the tile stays in the pool and\n * only its active flag flips. Enabling before the tile has loaded triggers\n * its (one-time) load on the next update. No-op on a scene without one.\n */\n setEnvironmentEnabled(enabled: boolean): void {\n if (this.envFile === undefined || enabled === this.envEnabled) return;\n this.envEnabled = enabled;\n if (this.envHandle !== undefined) {\n this.setRangeActive(this.envHandle, enabled);\n } else if (enabled) {\n // Not loaded yet - kick a reschedule so updateEnvironment fetches it.\n this.pendingWork = true;\n this.lastScheduleTime = -Infinity;\n }\n }\n\n /** The active-splat budget this mesh keeps within. */\n get budget(): number {\n return this.budgetValue;\n }\n\n /**\n * The ceiling {@link setBudget} clamps to - {@link StreamedSplatMeshOptions.maxBudget}\n * when one was given, otherwise the construction budget.\n *\n * The pool was allocated for this number and cannot grow, so it is a hard\n * limit on what any governor can hand this mesh. Read it to check that a\n * shared-budget setup can actually deliver the share it is computing.\n */\n get maxBudget(): number {\n return this.maximumBudget;\n }\n\n /** Alias used by hosts that manage static and streamed auto-LOD uniformly. */\n get budgetCeiling(): number {\n return this.maximumBudget;\n }\n\n /**\n * The capture's real content size, when the format declares it (`.rad` reports\n * its leaf count) - the splat count needed to hold this mesh at full\n * resolution, independent of the budget it was constructed with.\n *\n * A host splitting one budget across several streamed meshes should clamp each\n * share to this: a mesh cannot spend more than it contains, so budget handed\n * past it buys nothing and is better given to a mesh that can use it. Note it\n * is *not* `maxBudget`: a foveated `.rad` reports `maxResidentSplats` as the\n * requested budget, because its pool holds a camera-directed resident set\n * rather than the whole tree.\n *\n * `undefined` when the format does not declare a content size.\n */\n get contentSplatCount(): number | undefined {\n return this.scene.contentSplatCount;\n }\n\n /**\n * Which `.rad` streaming strategy this mesh selected at load, or `null` when\n * the scene is not a Spark `.rad` capture.\n */\n get radStrategy(): 'prefix' | 'page-table' | null {\n if (this.scene.chunkOptions?.[0]?.format !== 'rad-chunk') return null;\n return this.frontierWorker ? 'page-table' : 'prefix';\n }\n\n /**\n * The drawn-splat target currently driving the `.rad` page-table frontier -\n * the governed budget, capped by\n * {@link SplatMeshOptions.foveationDrawBudget}. `0` on a mesh that is not in\n * `foveationMode: 'page-table'`, which has no frontier to target.\n *\n * This is the number that decides how deep the traversal descends, so it is\n * what to watch when checking that a near mesh really did receive more\n * detail: {@link budget} is the pool's allowance, this is what is spent.\n */\n get drawBudget(): number {\n return this.frontierWorker ? this.pageTableDrawBudget : 0;\n }\n\n /**\n * Page-table frontier coherence for hosts that gate preload/transitions.\n * `undefined` fields stay 0 when this mesh is not in page-table mode.\n */\n get frontierState(): Readonly<{\n frontierConverged: boolean;\n pendingFrontierSplats: number;\n staleResidentSplats: number;\n lastPlanAppends: number;\n lastPlanMoves: number;\n planGeneration: number;\n planBudget: number;\n lastPlanCamera: readonly [number, number, number] | null;\n firstFrontierCamera: readonly [number, number, number] | null;\n }> {\n return {\n frontierConverged: this.frontierWorker ? this.frontierConverged : true,\n pendingFrontierSplats: this.pendingFrontierSplats,\n staleResidentSplats: this.staleResidentSplats,\n lastPlanAppends: this.lastPlanAppends,\n lastPlanMoves: this.lastPlanMoves,\n planGeneration: this.lastPlanGeneration,\n planBudget: this.lastPlanBudget,\n lastPlanCamera: this.lastPlanCamera,\n firstFrontierCamera: this.firstFrontierCamera,\n };\n }\n\n /**\n * Spark's per-mesh `lodScale` (see {@link StreamedSplatMeshOptions.lodScale}).\n * Mutable: raise it to sharpen a focused mesh, lower it to coarsen a\n * background one. Page-table `.rad` only.\n *\n * @throws {RangeError} if set to a value that is not positive and finite.\n */\n get lodScale(): number {\n return this.lodScaleValue;\n }\n set lodScale(value: number) {\n const next = validateLodScale(value);\n if (next === this.lodScaleValue) return;\n this.lodScaleValue = next;\n this.pendingWork = true;\n this.lastScheduleTime = -Infinity;\n }\n\n /** In `page-table` mode the slab is fully active but mostly degenerate, so the\n * base `activeSplatCount` (slab size) is not the on-screen count - report the\n * frontier's drawn size instead. */\n override get activeSplatCount(): number {\n return this.frontierWorker ? this.pageTableDrawn : super.activeSplatCount;\n }\n\n /**\n * Updates the LOD budget used for future scheduling within allocated capacity.\n *\n * @returns the budget actually in effect, which is `budget` clamped to\n * {@link maxBudget}. A `BudgetGovernor` reads this return value to detect a\n * capped member and hand the remainder to the others, so the clamp is\n * reported rather than hidden.\n */\n setBudget(budget: number): number {\n const next = Math.min(resolveSplatBudget(budget), this.maximumBudget);\n if (next === this.budgetValue) return this.budgetValue;\n this.budgetValue = next;\n this.scene.source.budget = next;\n if (this.frontierWorker) {\n // Page-table mode draws the frontier, not the LOD schedule - keep its\n // draw target under the (possibly shared/governed) pool budget too.\n this.pageTableDrawBudget = Math.min(next, this.pageTableDrawTarget);\n // Storage follows the budget: climb toward the new draw target, or hand\n // pages back when the governor shrinks this mesh.\n this.syncSlabPages(this.pageTableDrawBudget);\n // A caller-pinned `foveationDrawBudget` outranks the budget, so a governor\n // that grows this mesh past it buys nothing and the mesh stays coarse for\n // a reason nothing on screen explains. Say so once.\n if (\n this.pageTableDrawTargetExplicit &&\n this.pageTableDrawTarget < next &&\n !this.warnedDrawTargetCap\n ) {\n this.warnedDrawTargetCap = true;\n warn(\n `StreamedSplatMesh: budget raised to ${next} but foveationDrawBudget caps the drawn ` +\n `frontier at ${this.pageTableDrawTarget}; the extra budget cannot buy detail. ` +\n `Raise or drop foveationDrawBudget to let the shared budget through.`,\n );\n }\n }\n this.pendingWork = true;\n this.lastScheduleTime = -Infinity;\n return next;\n }\n\n /**\n * What the LOD scheduler last decided, or `undefined` on sources that do not\n * schedule by leaf (the `.rad` page table, prefix readers).\n *\n * Distinct from {@link activeSplatCount}, and the distinction is the whole\n * point: `desired` is what the scheduler asked for, `activeSplatCount` is\n * what the pool ended up drawing. Equal means the cut is applied; `desired`\n * far below the budget means the *scheduler* declined to spend it, which is a\n * different bug from the mesh failing to apply what it was given.\n */\n get lodStats():\n Readonly<{ inFrustum: number; leaves: number; desired: number; filled: number }> | undefined {\n const source = this.scene.source as { stats?: LodScheduler['stats'] };\n return source.stats;\n }\n\n /** Number of chunk files currently decoded and held. In page-table mode the\n * worker owns the cache - the main-thread map is always empty there, so report\n * what has been forwarded to it instead of a permanent zero. */\n get residentChunkCount(): number {\n return this.frontierWorker ? this.pageTableCachedFiles.size : this.cache.size;\n }\n\n /** Chunk fetches currently in flight. */\n get pendingChunkCount(): number {\n return this.fetching.size;\n }\n\n /**\n * Main-thread cost of applying paging plans in `foveationMode: 'page-table'`.\n *\n * A plan is applied whole, off the render loop's own timing, so its cost does\n * not appear in {@link getUpdateTimings} - but it lands on the same thread and\n * a churning frontier can make it the largest stall in a frame. `worst*`\n * accumulate over the mesh's lifetime; the rest describe the most recent plan.\n */\n get planTimings(): Readonly<{\n applyMs: number;\n worstApplyMs: number;\n writeMs: number;\n residentMs: number;\n moves: number;\n appends: number;\n worstSplats: number;\n }> {\n return this.planTimingsValue;\n }\n\n /**\n * Lifetime chunk-fetch totals by kind, plus page-table cache state.\n *\n * Diagnostic for the question \"why is this still streaming after the view\n * settled?\", which the three fetch sources answer differently and which no\n * other reading distinguishes:\n *\n * - **`sweep` climbing** - speculative file-order pre-warming of the whole\n * capture. Declined by the `smooth` profile; see `sweepAllowed`.\n * - **`priority` / `base` climbing while `evicted` climbs too** - the\n * frontier's touched set does not fit the worker cache, so chunks are\n * evicted and immediately refetched. Streaming never ends because it cannot.\n * - **`priority` / `base` climbing with `evicted` flat** - ordinary refinement\n * still converging on the cut; it should stop on its own.\n *\n * `uncovered` and `retiredEarly` answer a different question - \"why are there\n * holes?\" - and between them cover both ways this class can render nothing\n * where it should render something:\n *\n * - **`uncovered` climbing after the scene settles** - `substituteCoverage`\n * wanted a leaf's coarsest level as a stand-in and its chunk was not\n * cached. Expected briefly during initial load; afterwards it should not\n * move, because the coarsest files are pinned against eviction.\n * - **`retiredEarly` climbing** - coverage was retired before its replacement\n * landed, under pool pressure or past the retirement hold bound. This is\n * the swap path rather than the substitute path, and it is the one that\n * scales with the budget.\n *\n * Both are counted in whole leaves/groups, monotonically: they answer \"did\n * this happen, and is it still happening\", not \"how much is missing now\".\n */\n get fetchCounts(): Readonly<{\n priority: number;\n base: number;\n sweep: number;\n evicted: number;\n uncovered: number;\n retiredEarly: number;\n cacheFull: boolean;\n cacheBytes: number;\n cacheLimitBytes: number;\n }> {\n return this.fetchCountsValue;\n }\n\n /** Chunk files given up on after repeated fetch/decode failures. */\n get failedChunkCount(): number {\n return this.failedFiles.size;\n }\n\n /**\n * Forgets all permanent chunk failures so their regions are fetched again.\n * Failures are otherwise terminal for the mesh's lifetime - call this when\n * the cause was transient (e.g. connectivity restored, `online` event).\n */\n retryFailedChunks(): void {\n if (this.failedFiles.size === 0) return;\n this.failedFiles.clear();\n this.retrying.clear();\n this.pendingWork = true;\n }\n\n /**\n * Whether the scene is still resolving toward its target detail - chunks\n * are fetching, or a retry/append is pending. Goes false once the view\n * has settled (useful to drive a loading indicator).\n */\n get isStreaming(): boolean {\n return this.pendingWork || this.fetching.size > 0 || this.retrying.size > 0;\n }\n\n /** The LOD distance model (mutable; e.g. raise to force the finest level). */\n get lodBaseDistance(): number {\n return this.scene.source.lodBaseDistance;\n }\n set lodBaseDistance(value: number) {\n this.scene.source.lodBaseDistance = value;\n this.pendingWork = true;\n }\n\n override update(\n camera: THREE.PerspectiveCamera,\n renderer: THREE.WebGPURenderer,\n options: SplatUpdateOptions = {},\n ): void {\n const now = performance.now();\n camera.updateMatrixWorld();\n this.updateWorldMatrix(true, false);\n // The page-table cut limit is `targetPx / focalY`, and focalY needs the\n // drawing-buffer height - sample it here, before rescheduling, since the\n // base class only writes its view uniforms afterwards. In XR use the\n // per-eye height, not the stereo framebuffer (which is twice as wide and\n // would throw the cut off).\n //\n // LOD must follow the *head*, not the application camera. While an XR\n // session presents, three drives an internal array camera and the app\n // camera stops moving - scheduling from it would hold detail wherever that\n // camera was left and frustum-cull whatever the user turns to face, so a\n // scene stays blurry however far you walk into it. The head's union\n // projection is also the correct frustum here: it spans both eyes.\n // (`super.update` resolves the view again; that is idempotent and costs a\n // handful of matrix products.)\n const xrView = resolveXrView(camera, renderer);\n if (this.frontierWorker) {\n if (xrView) {\n this.pageTableViewportY = xrView.height;\n } else {\n renderer.getDrawingBufferSize(_drawSize);\n this.pageTableViewportY = _drawSize.y;\n }\n }\n const lodCamera = xrView?.head ?? camera;\n const performanceEvent = this.shouldReschedule(lodCamera, now)\n ? this.reschedule(lodCamera, now)\n : null;\n super.update(camera, renderer, options);\n if (performanceEvent && this.onPerformanceEvent) {\n const timings = this.getUpdateTimings();\n performanceEvent.cpuMs += performance.now() - performanceEvent.timestamp;\n performanceEvent.activeListMs = timings.activeListMs;\n performanceEvent.uploadMs = timings.uploadMs;\n performanceEvent.sortSubmitMs = timings.sortSubmitMs;\n performanceEvent.stagingTextureAllocations = timings.stagingTextureAllocations;\n performanceEvent.activeListUpdateRanges = timings.activeListUpdateRanges;\n this.onPerformanceEvent(performanceEvent);\n }\n }\n\n /** Root bounds of the whole scene, valid before any chunk has loaded. */\n override computeSplatBounds(): THREE.Box3 {\n return this.scene.bounds.clone();\n }\n\n /**\n * Enables or disables writing each resident run's **resolved** LOD level into\n * the `lodLevel` float channel (for a false-color debug modifier: 0 = finest).\n * Values come from applied desired runs after budget resolution - not from\n * distance ambition alone. Call before assigning a modifier that reads the channel.\n */\n setLodLevelDebug(enabled: boolean): void {\n if (enabled) {\n if (!this.lodLevelChannelReady) {\n this.defineChannel('lodLevel', { type: 'float', fill: -1 });\n this.lodLevelChannelReady = true;\n }\n this.lodLevelDebug = true;\n for (const { run, handle } of this.resident.values()) {\n this.writeLodLevelChannel(handle, run.level);\n }\n for (const { run, handle, uploadedCount } of this.staged.values()) {\n if (uploadedCount === run.count) this.writeLodLevelChannel(handle, run.level);\n }\n return;\n }\n this.lodLevelDebug = false;\n }\n\n /** Whether {@link setLodLevelDebug} is currently writing levels. */\n get isLodLevelDebug(): boolean {\n return this.lodLevelDebug;\n }\n\n /**\n * Startup-hold progress for {@link StreamedSplatMeshOptions.initialReveal}.\n * Hosts using `'hold-near-l0'` or `'hold-coverage'` should keep the mesh\n * invisible while `status === 'pending'`, then reveal on `'ready'` or\n * `'degraded'`.\n */\n get initialRevealState(): InitialRevealState {\n return this.initialRevealStateValue;\n }\n\n /**\n * Captures a fresh startup-hold set on the next {@link update}. Hosts that\n * apply their final initial camera pose after the mesh first receives frames\n * should call this before lifting their loading cover. It is a no-op when\n * the hold was never armed (progressive startup, or a format without the\n * matching LodSource hook).\n */\n recaptureInitialReveal(): void {\n if (this.initialRevealHold === 'off') return;\n this.frozenCriticalRuns = null;\n this.initialRevealStartedAt = undefined;\n this.initialRevealPhase = 'capture';\n this.initialRevealStateValue = {\n status: 'pending',\n stagedSplats: 0,\n totalSplats: 0,\n readyGroups: 0,\n totalGroups: 0,\n };\n this.pendingWork = true;\n }\n\n private writeLodLevelChannel(handle: SplatRange, level: number): void {\n if (!this.lodLevelDebug || handle.count === 0) return;\n if (!this.lodLevelScratch || this.lodLevelScratch.length < handle.count) {\n this.lodLevelScratch = new Float32Array(handle.count);\n }\n this.lodLevelScratch.fill(level, 0, handle.count);\n this.writeChannel(handle, 'lodLevel', this.lodLevelScratch.subarray(0, handle.count));\n }\n\n /**\n * Declares a per-splat channel whose values **persist across LOD churn**:\n * edits are stored sparsely keyed by `(chunk file, local index)` - a stable\n * splat identity in the streaming design - and re-applied whenever a chunk\n * is (re)appended. Paint a region with {@link paintPersistent}, orbit away\n * until it is evicted, come back, and the values return. See M7.6.\n *\n * Wraps {@link SplatMesh.defineChannel}; read it from a modifier with\n * `ctx.channel(name)` as usual.\n */\n definePersistentChannel(name: string, options: PersistentChannelOptions = {}): void {\n this.defineChannel(name, options);\n this.persistentChannels.set(name, {\n type: options.type ?? 'float',\n fill: options.fill ?? 0,\n maxEdits: Math.max(1, Math.floor(options.maxEdits ?? 1_000_000)),\n edits: new Map(),\n total: 0,\n warned: false,\n });\n }\n\n /**\n * Sets a persistent channel to `value` for every currently-resident splat\n * within `radius` (world units) of `worldPoint`, and records the edit so it\n * survives eviction/reload. Splats whose chunk is not currently decoded on\n * the CPU cannot be located and are skipped (they are usually far from the\n * camera); their painted neighbours in resident chunks are unaffected.\n *\n * The radius assumes this mesh's world transform is rigid (rotation +\n * translation, as the built-in format transforms are); a scaled mesh would\n * distort the brush. Edit a persistent channel only through this method -\n * direct {@link SplatMesh.writeChannel} writes are not recorded and are\n * overwritten by the next re-apply.\n *\n * @returns the number of splats edited this call.\n * @throws {Error} if the channel was not declared with\n * {@link definePersistentChannel}.\n */\n paintPersistent(name: string, worldPoint: THREE.Vector3, radius: number, value: number): number {\n const channel = this.persistentChannels.get(name);\n if (!channel) {\n throw new Error(\n `StreamedSplatMesh.paintPersistent: channel \"${name}\" is not a persistent channel. ` +\n `Call definePersistentChannel(\"${name}\") first.`,\n );\n }\n _paintLocal.copy(worldPoint);\n this.worldToLocal(_paintLocal);\n const r2 = radius * radius;\n let edited = 0;\n const touchedFiles = new Set<number>();\n\n for (const { run } of this.resident.values()) {\n const chunk = this.cache.get(run.file);\n if (!chunk) continue; // positions evicted from the CPU cache\n const positions = chunk.data.positions;\n const fileEdits = channel.edits.get(run.file) ?? new Map<number, number>();\n let touched = false;\n for (let k = 0; k < run.count; k++) {\n const li = run.offset + k;\n const px = (positions[li * 3 + 0] as number) - _paintLocal.x;\n const py = (positions[li * 3 + 1] as number) - _paintLocal.y;\n const pz = (positions[li * 3 + 2] as number) - _paintLocal.z;\n if (px * px + py * py + pz * pz > r2) continue;\n // First paint wins - keep the stored color/index for already-edited splats.\n if (fileEdits.has(li)) continue;\n if (channel.total >= channel.maxEdits) {\n if (!channel.warned) {\n channel.warned = true;\n warn(\n `StreamedSplatMesh.paintPersistent: channel \"${name}\" hit its ` +\n `maxEdits cap (${channel.maxEdits}); further new edits are dropped.`,\n );\n }\n continue;\n }\n channel.total++;\n fileEdits.set(li, value);\n touched = true;\n edited++;\n }\n if (touched) {\n channel.edits.set(run.file, fileEdits);\n touchedFiles.add(run.file);\n }\n }\n\n // Re-derive and upload each touched resident run from the store, so the\n // paint shows immediately (not only after the next reload).\n if (touchedFiles.size > 0) {\n for (const { run, handle } of this.resident.values()) {\n if (touchedFiles.has(run.file)) this.applyPersistentRun(name, channel, run, handle);\n }\n }\n return edited;\n }\n\n /**\n * Clears every stored edit for a persistent channel and zeroes the value on\n * all currently-resident splats. Chunks that are not resident are covered by\n * the emptied store - they reload at the channel's fill value.\n *\n * @throws {Error} if the channel is not a persistent channel.\n */\n clearPersistentChannel(name: string): void {\n const channel = this.persistentChannels.get(name);\n if (!channel) {\n throw new Error(\n `StreamedSplatMesh.clearPersistentChannel: channel \"${name}\" is not a persistent channel.`,\n );\n }\n channel.edits.clear();\n channel.total = 0;\n for (const { run, handle } of this.resident.values()) {\n const data =\n channel.type === 'byte' ? new Uint8Array(run.count) : new Float32Array(run.count);\n this.writeChannel(handle, name, data);\n }\n }\n\n override dispose(): void {\n if (this.disposed) return;\n this.loader.dispose();\n // Terminating drops any in-flight traversal; clearing the handler also\n // frees the closure over this mesh for a message already dispatched.\n if (this.frontierWorker) {\n this.frontierWorker.onmessage = null;\n this.frontierWorker.terminate();\n }\n this.pageTableDisposed = true;\n this.collisionAbort?.abort();\n // A rejected cached promise is nobody's to handle once the mesh is gone.\n this.collisionTiles?.catch(() => {});\n this.collisionTiles = undefined;\n // Abort before unregistering: each abort settles through `requestChunk`'s\n // `finally`, which releases the slot back to the mesh's siblings.\n for (const { controller } of this.fetching.values()) controller.abort();\n this.fetching.clear();\n if (this.fetchHandle) this.fetchScheduler?.unregister(this.fetchHandle);\n // Hands this mesh's cache allowance back to its siblings. Cleared so a\n // reallocation triggered by the unregister itself cannot call back into a\n // disposed mesh and post to a terminated worker.\n if (this.cacheBudgetHandle) {\n const handle = this.cacheBudgetHandle;\n this.cacheBudgetHandle = undefined;\n this.cacheBudget?.unregister(handle);\n }\n // Revokes the object URLs a dropped local folder created, and releases the\n // `File` blobs they pin. No-op for a network-loaded mesh.\n this.localSource?.dispose();\n this.localSource = undefined;\n this.cache.clear();\n this.cacheBytesTotal = 0;\n this.retrying.clear();\n this.failedFiles.clear();\n this.neededFiles.clear();\n this.persistentChannels.clear();\n this.resident.clear();\n this.staged.clear();\n this.pageTableCachedFiles.clear();\n this.envHandle = undefined;\n this.envSplatCount = 0;\n super.dispose();\n }\n\n private shouldReschedule(camera: THREE.Camera, now: number): boolean {\n if (this.pendingWork) return true;\n if (now - this.lastScheduleTime > IDLE_RESCHEDULE_MS) return true;\n\n camera.getWorldPosition(_cameraWorldPos);\n const radius = this.scene.bounds.getBoundingSphere(_sphere).radius || 1;\n if (_cameraWorldPos.distanceTo(this.lastCameraPos) > radius * 0.0025) return true;\n\n camera.getWorldQuaternion(_cameraWorldQuat);\n return _cameraWorldQuat.angleTo(this.lastCameraQuat) > 0.0087; // ~0.5°\n }\n\n private reschedule(camera: THREE.Camera, now: number): StreamedSplatPerformanceEvent | null {\n const startedAt = performance.now();\n // The before-snapshots exist only to diff for the performance event; with\n // no listener installed this per-reschedule allocation work is skipped.\n let before: {\n resident: Map<string, number>;\n staged: Map<string, number>;\n } | null = null;\n if (this.onPerformanceEvent !== undefined) {\n before = { resident: new Map(), staged: new Map() };\n for (const [key, entry] of this.resident) before.resident.set(key, entry.run.count);\n for (const [key, entry] of this.staged) before.staged.set(key, entry.uploadedCount);\n }\n const compactionCountBefore = this.compactionCount;\n this.pendingWork = false;\n this.lastScheduleTime = now;\n camera.getWorldPosition(this.lastCameraPos);\n camera.getWorldQuaternion(this.lastCameraQuat);\n\n // Camera position and frustum in this mesh's local space.\n _cameraLocal.copy(this.lastCameraPos);\n this.worldToLocal(_cameraLocal);\n _projScreen\n .multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse)\n .multiply(this.matrixWorld);\n _frustum.setFromProjectionMatrix(_projScreen);\n\n if (this.frontierWorker) {\n // Camera forward as a mesh-local direction: transform a point one unit\n // ahead and subtract the local eye, so any affine mesh transform is\n // handled without a separate normal matrix.\n camera.getWorldDirection(_cameraForward).add(this.lastCameraPos);\n this.worldToLocal(_cameraForward);\n _cameraForward.sub(_cameraLocal).normalize();\n // Cut limit, exactly as the material derives it: a node is fine enough\n // when `size / distance ≤ targetPx / focalY`.\n const focalY = (camera.projectionMatrix.elements[5] * this.pageTableViewportY) / 2;\n if (focalY > 0) this.pageTableLimit = this.pageTableTargetPx / focalY;\n this.reschedulePageTable(_cameraLocal, _cameraForward, _frustum, now);\n // The page-table path still reports its per-update CPU cost. Returning\n // null here made `onPerformanceEvent` silent on the one path a `.rad`\n // actually takes, so a host watching `cpuMs` / `uploadMs` / `sortSubmitMs`\n // saw nothing at all - and could not tell an upload stall from a sort\n // stall on the only format where the question comes up.\n //\n // The chunk-swap fields stay zero: this path pages slots through the\n // frontier plan rather than swapping LOD runs, so `swapped`/`appended`\n // and the resident/staged diffs have no meaning here. `applyFrontierPlan`\n // is timed separately, by `planTimings`.\n if (this.onPerformanceEvent === undefined) return null;\n // Same convention as the classic path below: `timestamp` marks the end of\n // the reschedule and `cpuMs` covers it, so the caller's\n // `cpuMs += now - timestamp` adds `super.update` rather than double-counting.\n const pageTableTimestamp = performance.now();\n return {\n timestamp: pageTableTimestamp,\n cpuMs: pageTableTimestamp - startedAt,\n activeListMs: 0,\n uploadMs: 0,\n sortSubmitMs: 0,\n stagingTextureAllocations: 0,\n activeListUpdateRanges: 0,\n appendedCount: 0,\n removedCount: 0,\n stagedCount: 0,\n uploadCount: 0,\n activeCount: this.pageTableDrawn,\n forcedSort: false,\n compacted: false,\n };\n }\n\n // Fetch ranking needs a more precise signal than LCC's broad-box frustum\n // bit. It must not influence the source's distance/budget LOD decision.\n camera.getWorldDirection(_cameraForward).add(this.lastCameraPos);\n this.worldToLocal(_cameraForward);\n _cameraForward.sub(_cameraLocal).normalize();\n const scheduledRuns = this.scene.source.computeDesiredRuns(\n _cameraLocal,\n _frustum,\n now,\n _cameraForward,\n );\n const holdingRuns = this.captureOrContinueInitialReveal(\n scheduledRuns,\n now,\n _cameraLocal,\n _frustum,\n );\n const holding = holdingRuns !== null;\n // During the startup hold, ignore later camera cuts: only the frozen\n // coverage set is desired. After release, the normal swap transaction\n // keeps that coverage active while the live cut stages, then replaces it\n // atomically rather than drawing coarse and fine runs together.\n const desiredRuns = holdingRuns ?? scheduledRuns;\n const desired = new Map<string, LodRun>();\n const desiredFiles = new Set<number>();\n for (const run of desiredRuns) {\n desired.set(runKey(run), run);\n desiredFiles.add(run.file);\n }\n for (const [key, entry] of this.staged) {\n if (desired.has(key)) continue;\n // During hold, keep staging progress for frozen runs even if a bug drops\n // them from desired - the freeze list is authoritative.\n if (holding && this.frozenCriticalRuns?.some((run) => runKey(run) === key)) continue;\n this.removeRange(entry.handle);\n this.staged.delete(key);\n }\n\n // Cancel fetches whose file no longer backs any desired run. Pinned\n // (coarsest-level) files are never cancelled: they are the substitute\n // coverage every deferred swap relies on, and the environment tile is\n // pinned for the same reason. During hold, only critical coverage files\n // (and pins) stay - neighbours and far coarse lose their slots.\n for (const [file, { controller }] of this.fetching) {\n if (!desiredFiles.has(file) && !this.scene.pinnedFiles.has(file)) controller.abort();\n }\n // Drop retry state for files no longer wanted, for the same reason -\n // otherwise a chunk that failed once before the camera moved away keeps\n // `isStreaming` (and the demo's spinner) stuck true forever.\n for (const file of this.retrying.keys()) {\n if (!desiredFiles.has(file) && !this.scene.pinnedFiles.has(file)) this.retrying.delete(file);\n }\n // Chunks fetched for a still-deferred group have a stale `lastUsed`;\n // remember every desired-but-not-yet-resident file so eviction cannot\n // discard them before their swap group applies (fetch → evict → refetch\n // livelock under CPU-cache pressure). Fully staged runs may leave the CPU\n // cache: their GPU inactive range already holds the bytes.\n this.neededFiles.clear();\n for (const run of desiredRuns) {\n const key = runKey(run);\n if (this.resident.has(key)) continue;\n const staged = this.staged.get(key);\n if (staged && staged.uploadedCount === run.count) continue;\n this.neededFiles.add(run.file);\n }\n if (\n this.envFile !== undefined &&\n this.envEnabled &&\n this.envHandle === undefined &&\n !this.envUnfit &&\n !this.failedFiles.has(this.envFile)\n ) {\n this.neededFiles.add(this.envFile);\n }\n\n const toAdd = desiredRuns.filter((run) => !this.resident.has(runKey(run)));\n // During hold, never retire unrelated resident coverage - the viewer is\n // hidden and we only build the critical set.\n const toRemove = holding\n ? []\n : [...this.resident.entries()].filter(([key]) => !desired.has(key));\n\n // A region must never render twice (bright flash) or not at all (black\n // hole), so adds and their superseded removals apply together, within\n // one tick - one frame sees only complete before/after states. Groups\n // are connected components of (toAdd ∪ toRemove) by leaf-interval\n // overlap; a group that cannot fully apply this tick (chunk still\n // fetching, append cap, pool pressure) is deferred whole, its old runs\n // still rendering.\n const groups = holding\n ? // Mesh is invisible during the hold, so L0 cell-atomicity (no holes) is\n // irrelevant - commit each frozen slice as it lands so a partial home\n // cell cannot block reveal behind sibling subchunks still fetching.\n buildHoldSwapGroups(toAdd)\n : buildSwapGroups(toAdd, toRemove);\n const classicLccGroups = !holding && isClassicLccSwapSet(groups);\n // The generic RAD wave must land all replacement coverage before any\n // retirements. Classic LCC already has per-slice coverage transactions;\n // applying that global wave to it starves a ready visible L1+ slice behind\n // every coarse shell elsewhere in the scene.\n groups.sort((a, b) =>\n classicLccGroups ? compareClassicSwapGroups(a, b) : groupPriority(a) - groupPriority(b),\n );\n\n // Classic path used to `requestChunk` in leafStart / group order, so far\n // coarse pins filled the in-flight cap while the camera cell stayed on\n // discs. Collect every miss this tick and flush nearest/finest first -\n // same contract as the page-table `pageTableFetchPriority` path.\n const pendingFetches = new Map<number, ClassicFetchWant>();\n // Environment first: append it before coverage consumes pool rows, and\n // enqueue its fetch ahead of LOD wants so the sky is not last in the pipe.\n this.updateEnvironment(now, pendingFetches);\n\n // A `.rad` refinement splits across groups, and that is what used to punch\n // holes in a region while it sharpened. Grouping pairs adds with removals by\n // leaf-interval overlap, which works for the octree formats because a\n // parent's interval contains its children's - but `.rad` keys runs by global\n // splat index and a node's children live in a *later chunk*, so parent and\n // children can never share a group. The parent's group therefore committed\n // at once while the children's group was still staging, and for those frames\n // the region drew with its coarse splats gone and their replacements not yet\n // visible.\n //\n // Spark cannot reach that state: it publishes a refined cut only once every\n // splat in it is drawable, holding the previous complete frame meanwhile\n // (`SparkRenderer.driveSort` advances `display` only after a sort of the new\n // mapping lands). Do the same - a group that retires coverage waits until\n // every group that purely adds has landed. Waiting costs brief over-draw,\n // never a hole, since a deferred group keeps rendering its old runs.\n let appended = 0;\n let addsPending = false;\n let poolPressure = false;\n let held = false;\n for (const group of groups) {\n if (!classicLccGroups && group.removes.length > 0 && addsPending) {\n // Bounded so the wait can never strand coverage: the replacements\n // normally land within a few ticks, and past that the pool matters more\n // than the seam.\n if (\n this.neverRetireCoverageEarly ||\n (!poolPressure && this.retireHeldTicks < MAX_RETIRE_HELD_TICKS)\n ) {\n this.pendingWork = true;\n held = true;\n continue;\n }\n // Falling through here retires coverage whose replacement has *not*\n // landed - the one deliberate hole in this path. Two causes, one\n // consequence: the pool needs the rows more than the seam needs hiding\n // (`poolPressure`), or the hold has run past MAX_RETIRE_HELD_TICKS. A\n // higher budget makes the first likelier - more and larger groups in\n // flight against a pool sized from the same budget - so this is the\n // first thing to read when holes appear only at a raised budget.\n this.fetchCountsValue.retiredEarly++;\n }\n if (group.adds.length === 0) {\n this.applyGroup(group, now); // dropped regions: just free them\n continue;\n }\n const missing = group.adds.filter((run) => !this.cache.has(run.file));\n // Resolved L0: skip coarse stand-in for empty gaps (keep prior coverage\n // on each sub-leaf until that slice's L0 commits). L1+: allow per-slice\n // coarsest substitute while that slice's target loads.\n // Startup hold never paints coarse for the critical set.\n const holdForTarget =\n holding || (isWaitingOnFinest(group) && this.initialRevealHold !== 'hold-coverage');\n if (missing.length > 0) {\n // Only keep re-scheduling if some missing chunk is still\n // recoverable (fetching or awaiting a retry); a group whose chunks\n // have all permanently failed settles on its coarse substitute.\n let recoverable = false;\n for (const run of missing) {\n if (this.failedFiles.has(run.file)) continue;\n enqueueClassicFetch(\n pendingFetches,\n run.file,\n classicFetchPhaseForDesired(run, this.scene.source.lodBaseDistance),\n run,\n );\n recoverable = true;\n }\n // Far / L1+ gaps: install coarsest shell. L0 hold: do not fetch or\n // paint that shell - only the resolved L0 target is requested.\n // Startup hold: still stage any siblings already in cache (below).\n if (!holding) {\n this.substituteCoverage(group, now, pendingFetches, holdForTarget);\n }\n if (recoverable) {\n this.pendingWork = true;\n addsPending = true;\n }\n // During startup, continue into staging so available chunks upload\n // before every sibling is cached.\n if (!holding) continue;\n }\n if (holding && this.environmentPendingForReveal()) {\n // Keep pool headroom for the env tile; coverage stays cached until it\n // lands. Fetches for the frozen set are already queued above.\n this.pendingWork = true;\n addsPending = true;\n continue;\n }\n const forceStage = holding || (this.stagedSwapsEnabled && group.addCount > this.appendCap);\n if (forceStage && this.canStageGroup(group)) {\n const stagedNow = this.stageGroup(group, now, Math.max(0, this.appendCap - appended));\n appended += stagedNow;\n if (!group.adds.every((run) => this.staged.get(runKey(run))?.uploadedCount === run.count)) {\n this.pendingWork = true;\n addsPending = true;\n continue;\n }\n // Keep the old region for one additional frame when this tick wrote\n // the final hidden segment. The following tick performs only the\n // atomic active-list switch and forced sort, rather than combining\n // those costs with the last texture upload.\n if (stagedNow > 0 && !holding) {\n this.deferNextSortRequest();\n this.pendingWork = true;\n addsPending = true;\n continue;\n }\n this.commitStagedGroup(group);\n continue;\n }\n // The cap bounds per-tick upload work, but a group is indivisible\n // (splitting it would break region atomicity), so a single group larger\n // than the cap is deliberately let through when it comes first - the\n // one-frame hitch beats never applying it at all.\n if (!holding && appended > 0 && appended + group.addCount > this.appendCap) {\n this.pendingWork = true;\n addsPending = true;\n continue;\n }\n // Startup hold always stages (above); if staging could not start, keep\n // pending rather than applying visible coverage while the viewer is gated.\n if (holding) {\n this.pendingWork = true;\n addsPending = true;\n continue;\n }\n if (!this.applyGroup(group, now)) {\n this.pendingWork = true; // transient pool pressure; retry next tick\n // Rows are the scarce resource now, so stop holding retirements back.\n poolPressure = true;\n continue;\n }\n appended += group.addCount;\n }\n this.flushClassicFetches(pendingFetches, this.scene.source.lodBaseDistance, holding);\n // Counts only ticks that actually held something back, so reaching the bound\n // releases the retirement and starts the count over rather than latching the\n // gate off for the rest of the session.\n this.retireHeldTicks = held ? this.retireHeldTicks + 1 : 0;\n\n if (holding) {\n this.finishInitialRevealIfComplete();\n // Recheck failure after this tick's fetch outcomes land next frame; keep\n // streaming until release.\n if (this.initialRevealPhase === 'holding') this.pendingWork = true;\n }\n // Publish the CPU cache state before evicting, so `cacheBytes` reports the\n // peak the tick actually reached rather than the post-eviction figure - the\n // latter always sits at or under the limit and so can never show pressure.\n // These three were previously written only by the page-table plan, leaving\n // the streamed path reporting a permanent 0/0 that looked like \"no cache in\n // use\" when it meant \"not measured\" - the same blind spot `evicted` had.\n this.fetchCountsValue.cacheBytes = this.cacheBytesTotal;\n this.fetchCountsValue.cacheLimitBytes = this.cpuCacheBytes;\n if (this.cacheBytesTotal > this.cpuCacheBytes) this.fetchCountsValue.cacheFull = true;\n this.evictChunks(now);\n if (before === null) return null;\n return this.createPerformanceEvent(\n before.resident,\n before.staged,\n compactionCountBefore,\n startedAt,\n );\n }\n\n private rowAlignedSplats(count: number): number {\n return Math.ceil(count / DATA_TEXTURE_WIDTH) * DATA_TEXTURE_WIDTH;\n }\n\n /**\n * Startup hold seeds: the coverage group containing (or nearest to) the\n * camera within {@link LodSource.lodBaseDistance}. HiRes tiles often fail the\n * frustum test when most of the cell sits behind the camera - do **not**\n * require `inView`, or the hold seeds a screen-facing neighbour instead.\n * Coarser home levels come from {@link LodSource.runsAtLevelFor}.\n */\n private selectHomeSeedRuns(desiredRuns: readonly LodRun[]): LodRun[] {\n const base = this.scene.source.lodBaseDistance;\n const nearestCandidates = desiredRuns\n .filter(\n (run) =>\n run.coverageGroup !== undefined && (run.distance ?? Number.POSITIVE_INFINITY) <= base,\n )\n .sort(\n (a, b) =>\n (a.distance ?? Number.POSITIVE_INFINITY) - (b.distance ?? Number.POSITIVE_INFINITY) ||\n // Same distance: prefer in-view, then finer.\n (a.inView === true ? 0 : 1) - (b.inView === true ? 0 : 1) ||\n a.level - b.level ||\n a.leafStart - b.leafStart,\n );\n const homeGroup = nearestCandidates[0]?.coverageGroup;\n if (homeGroup === undefined) return [];\n return nearestCandidates.filter((run) => run.coverageGroup === homeGroup);\n }\n\n private coarsenHomeRuns(seeds: readonly LodRun[], nearLevel: number): LodRun[] {\n const out: LodRun[] = [];\n const source = this.scene.source;\n for (const seed of seeds) {\n if (seed.level === nearLevel) {\n out.push(seed);\n continue;\n }\n const alt = source.runsAtLevelFor?.(seed.leafStart, seed.leafEnd, nearLevel) ?? [];\n if (alt.length === 0) continue;\n for (const run of alt) {\n out.push({\n ...run,\n distance: seed.distance,\n inView: seed.inView,\n ...(seed.coverageGroup === undefined ? {} : { coverageGroup: seed.coverageGroup }),\n ...(seed.screenImportance === undefined\n ? {}\n : { screenImportance: seed.screenImportance }),\n });\n }\n }\n return out;\n }\n\n private criticalRunsFitCapacity(runs: readonly LodRun[]): boolean {\n let neededRows = 0;\n for (const run of runs) neededRows += this.rowAlignedSplats(run.count);\n return neededRows <= this.freeSplatCapacity;\n }\n\n private publishInitialRevealProgress(runs: readonly LodRun[]): void {\n const groups = new Map<string, LodRun[]>();\n for (const run of runs) {\n const g = run.coverageGroup !== undefined ? `g:${run.coverageGroup}` : runKey(run);\n let list = groups.get(g);\n if (!list) {\n list = [];\n groups.set(g, list);\n }\n list.push(run);\n }\n let stagedSplats = 0;\n let totalSplats = 0;\n let readyGroups = 0;\n for (const groupRuns of groups.values()) {\n let groupReady = true;\n for (const run of groupRuns) {\n totalSplats += run.count;\n const key = runKey(run);\n if (this.resident.has(key)) {\n stagedSplats += run.count;\n continue;\n }\n const staged = this.staged.get(key);\n stagedSplats += staged?.uploadedCount ?? 0;\n if (!staged || staged.uploadedCount !== run.count) groupReady = false;\n }\n if (groupReady) readyGroups++;\n }\n const prev = this.initialRevealStateValue;\n if (prev.status === 'degraded') {\n this.initialRevealStateValue = {\n status: 'degraded',\n reason: prev.reason,\n stagedSplats,\n totalSplats,\n readyGroups,\n totalGroups: groups.size,\n };\n return;\n }\n this.initialRevealStateValue = {\n status: 'pending',\n stagedSplats,\n totalSplats,\n readyGroups,\n totalGroups: groups.size,\n };\n }\n\n private releaseInitialReveal(\n status: 'ready' | 'degraded',\n reason?: 'capacity' | 'fetch-failed' | 'timeout',\n ): void {\n const runs = this.frozenCriticalRuns ?? [];\n this.publishInitialRevealProgress(runs);\n const progress = this.initialRevealStateValue;\n const stagedSplats =\n progress.status === 'pending' || progress.status === 'degraded' ? progress.stagedSplats : 0;\n const totalSplats =\n progress.status === 'pending' || progress.status === 'degraded' ? progress.totalSplats : 0;\n const readyGroups =\n progress.status === 'pending' || progress.status === 'degraded' ? progress.readyGroups : 0;\n const totalGroups =\n progress.status === 'pending' || progress.status === 'degraded' ? progress.totalGroups : 0;\n if (status === 'ready') {\n this.initialRevealStateValue = { status: 'ready' };\n } else {\n this.initialRevealStateValue = {\n status: 'degraded',\n reason: reason ?? 'fetch-failed',\n stagedSplats,\n totalSplats,\n readyGroups,\n totalGroups,\n };\n }\n this.frozenCriticalRuns = null;\n this.initialRevealPhase = 'released';\n this.pendingWork = true;\n }\n\n /**\n * `.lcc2` coverage hold: freeze coarsest covering runs for in-view cells.\n * Missing `coverageRunsFor` (or an empty result after fallback) releases\n * immediately so the mesh does not stay hidden with nothing to fetch.\n */\n private captureCoverageHold(\n cameraLocal: THREE.Vector3,\n frustum: THREE.Frustum,\n now: number,\n ): void {\n const coverage = this.scene.source.coverageRunsFor?.(cameraLocal, frustum) ?? [];\n if (coverage.length === 0) {\n if (this.environmentPendingForReveal()) {\n this.frozenCriticalRuns = [];\n this.initialRevealStartedAt = now;\n this.initialRevealPhase = 'holding';\n this.publishInitialRevealProgress([]);\n return;\n }\n this.initialRevealStateValue = { status: 'ready' };\n this.initialRevealPhase = 'released';\n return;\n }\n if (!this.criticalRunsFitCapacity(coverage)) {\n this.frozenCriticalRuns = coverage;\n this.releaseInitialReveal('degraded', 'capacity');\n return;\n }\n this.frozenCriticalRuns = coverage;\n this.initialRevealStartedAt = now;\n this.initialRevealPhase = 'holding';\n this.publishInitialRevealProgress(coverage);\n }\n\n private captureOrContinueInitialReveal(\n scheduledRuns: LodRun[],\n now: number,\n cameraLocal: THREE.Vector3,\n frustum: THREE.Frustum,\n ): LodRun[] | null {\n if (this.initialRevealPhase === 'off' || this.initialRevealPhase === 'released') return null;\n\n if (this.initialRevealPhase === 'capture') {\n if (this.initialRevealHold === 'hold-coverage') {\n this.captureCoverageHold(cameraLocal, frustum, now);\n } else {\n // Prefer a full nearby L0 hold of the camera cell only. Tight pools\n // coarsen via the leaf ladder (L1, then L2) before degrading. Neighbours\n // are left for progressive streaming - they often beat home on\n // screenImportance. `desiredRuns` only has the *resolved* rung, so coarser\n // home cuts come from `runsAtLevelFor`.\n const seeds = this.selectHomeSeedRuns(scheduledRuns);\n let critical: LodRun[] = [];\n if (seeds.length === 0) {\n // Cold camera: nothing inside lodBaseDistance. Hold the nearest\n // coverage group in the near band (distance first, not screenImportance).\n const horizon =\n this.scene.source.lodBaseDistance *\n this.scene.source.lodMultiplier *\n this.scene.source.lodMultiplier;\n const fallback = scheduledRuns.filter(\n (run) =>\n run.level <= 2 &&\n run.coverageGroup !== undefined &&\n (run.distance ?? Number.POSITIVE_INFINITY) <= horizon,\n );\n const nearest = [...fallback].sort(\n (a, b) =>\n (a.distance ?? Number.POSITIVE_INFINITY) - (b.distance ?? Number.POSITIVE_INFINITY) ||\n (a.inView === true ? 0 : 1) - (b.inView === true ? 0 : 1) ||\n (a.screenImportance ?? Number.POSITIVE_INFINITY) -\n (b.screenImportance ?? Number.POSITIVE_INFINITY) ||\n a.leafStart - b.leafStart,\n )[0];\n if (!nearest) {\n this.initialRevealStateValue = { status: 'ready' };\n this.initialRevealPhase = 'released';\n return null;\n }\n critical =\n nearest.coverageGroup !== undefined\n ? fallback.filter((run) => run.coverageGroup === nearest.coverageGroup)\n : [nearest];\n if (!this.criticalRunsFitCapacity(critical)) {\n // Prefer a single fitting run over degrading the whole hold.\n critical =\n nearest.coverageGroup !== undefined\n ? fallback\n .filter(\n (run) =>\n run.coverageGroup === nearest.coverageGroup &&\n this.criticalRunsFitCapacity([run]),\n )\n .slice(0, 1)\n : fallback.filter((run) => this.criticalRunsFitCapacity([run])).slice(0, 1);\n }\n if (critical.length === 0 || !this.criticalRunsFitCapacity(critical)) {\n this.frozenCriticalRuns = critical.length > 0 ? critical : [nearest];\n this.releaseInitialReveal('degraded', 'capacity');\n return null;\n }\n this.frozenCriticalRuns = critical;\n this.initialRevealStartedAt = now;\n this.initialRevealPhase = 'holding';\n this.publishInitialRevealProgress(critical);\n } else {\n for (const nearLevel of [0, 1, 2] as const) {\n const home = this.coarsenHomeRuns(seeds, nearLevel);\n if (home.length === 0) continue;\n if (this.criticalRunsFitCapacity(home)) {\n critical = home;\n break;\n }\n critical = home;\n }\n if (critical.length === 0) {\n this.initialRevealStateValue = { status: 'ready' };\n this.initialRevealPhase = 'released';\n return null;\n }\n if (!this.criticalRunsFitCapacity(critical)) {\n this.frozenCriticalRuns = critical;\n this.releaseInitialReveal('degraded', 'capacity');\n return null;\n }\n this.frozenCriticalRuns = critical;\n this.initialRevealStartedAt = now;\n this.initialRevealPhase = 'holding';\n this.publishInitialRevealProgress(critical);\n }\n }\n }\n\n if (this.initialRevealPhase !== 'holding' || !this.frozenCriticalRuns) return null;\n\n if (\n this.initialRevealStartedAt !== undefined &&\n now - this.initialRevealStartedAt >= INITIAL_REVEAL_TIMEOUT_MS\n ) {\n this.releaseInitialReveal('degraded', 'timeout');\n return null;\n }\n\n for (const run of this.frozenCriticalRuns) {\n if (this.failedFiles.has(run.file) && !this.resident.has(runKey(run))) {\n const staged = this.staged.get(runKey(run));\n if (!staged || staged.uploadedCount !== run.count) {\n this.releaseInitialReveal('degraded', 'fetch-failed');\n return null;\n }\n }\n }\n\n this.publishInitialRevealProgress(this.frozenCriticalRuns);\n return this.frozenCriticalRuns;\n }\n\n /** After staging/commits, release the hold when every frozen run is resident. */\n private finishInitialRevealIfComplete(): void {\n if (this.initialRevealPhase !== 'holding' || !this.frozenCriticalRuns) return;\n this.publishInitialRevealProgress(this.frozenCriticalRuns);\n if (this.environmentPendingForReveal()) return;\n if (this.frozenCriticalRuns.every((run) => this.resident.has(runKey(run)))) {\n this.releaseInitialReveal('ready');\n }\n }\n\n /**\n * Startup hold still needs the environment tile when the scene ships one\n * and it starts enabled. Failed / unfit / disabled tiles do not block reveal.\n */\n private environmentPendingForReveal(): boolean {\n return (\n this.envFile !== undefined &&\n this.envEnabled &&\n !this.envUnfit &&\n this.envHandle === undefined &&\n !this.failedFiles.has(this.envFile)\n );\n }\n\n /** Creates a performance event for a changed streamed-LOD tick, if any. */\n private createPerformanceEvent(\n residentBefore: ReadonlyMap<string, number>,\n stagedBefore: ReadonlyMap<string, number>,\n compactionCountBefore: number,\n startedAt: number,\n ): StreamedSplatPerformanceEvent | null {\n let appendedCount = 0;\n let activeCount = 0;\n for (const [key, entry] of this.resident) {\n activeCount += entry.run.count;\n if (!residentBefore.has(key)) appendedCount += entry.run.count;\n }\n let removedCount = 0;\n for (const [key, count] of residentBefore) {\n if (!this.resident.has(key)) removedCount += count;\n }\n let stagedCount = 0;\n for (const [key, entry] of this.staged) {\n stagedCount += Math.max(0, entry.uploadedCount - (stagedBefore.get(key) ?? 0));\n }\n const compacted = this.compactionCount !== compactionCountBefore;\n if (appendedCount === 0 && removedCount === 0 && stagedCount === 0 && !compacted) return null;\n const timestamp = performance.now();\n return {\n timestamp,\n cpuMs: timestamp - startedAt,\n activeListMs: 0,\n uploadMs: 0,\n sortSubmitMs: 0,\n stagingTextureAllocations: 0,\n activeListUpdateRanges: 0,\n appendedCount,\n removedCount,\n stagedCount,\n uploadCount: appendedCount + stagedCount,\n activeCount,\n forcedSort:\n activeCount === 0 || appendedCount + removedCount >= activeCount * CONTENT_FORCE_FRACTION,\n compacted,\n };\n }\n\n /** Returns whether all new rows can coexist with the currently visible region. */\n private canStageGroup(group: SwapGroup): boolean {\n const unstaged = group.adds\n .filter((run) => !this.staged.has(runKey(run)))\n .reduce((sum, run) => sum + this.rowAlignedSplats(run.count), 0);\n return unstaged <= this.freeSplatCapacity;\n }\n\n /**\n * Uploads a bounded part of a replacement without rendering it.\n * Skips runs whose chunks are not yet cached so siblings can stage out of order.\n */\n private stageGroup(group: SwapGroup, now: number, allowance: number): number {\n if (allowance <= 0) return 0;\n let appended = 0;\n for (const run of group.adds) {\n const key = runKey(run);\n const chunk = this.cache.get(run.file);\n if (!chunk) continue;\n let entry = this.staged.get(key);\n if (!entry) {\n let handle: SplatRange;\n try {\n handle = this.reserveInactiveRange(run.count);\n } catch {\n this.compactionCount++;\n this.compact();\n try {\n handle = this.reserveInactiveRange(run.count);\n } catch {\n this.pendingWork = true;\n break;\n }\n }\n entry = { run, handle, uploadedCount: 0 };\n this.staged.set(key, entry);\n }\n // A swap group can contain several replacement runs. Once one run is\n // fully staged, advance to the next one instead of treating its zero\n // remaining count as an exhausted per-frame allowance.\n if (entry.uploadedCount === run.count) continue;\n const count = Math.min(run.count - entry.uploadedCount, allowance - appended);\n if (count <= 0) break;\n this.writeInactiveRange(\n entry.handle,\n sliceSplatData(chunk.data, run.offset + entry.uploadedCount, count),\n entry.uploadedCount,\n );\n entry.uploadedCount += count;\n chunk.lastUsed = now;\n appended += count;\n if (entry.uploadedCount === run.count) {\n this.writeLodLevelChannel(entry.handle, run.level);\n for (const [name, channel] of this.persistentChannels) {\n this.applyPersistentRun(name, channel, run, entry.handle);\n }\n }\n if (appended >= allowance) break;\n }\n return appended;\n }\n\n /** Switches a fully staged region from old to new visibility in one tick. */\n private commitStagedGroup(group: SwapGroup): void {\n for (const run of group.adds) {\n const entry = this.staged.get(runKey(run));\n if (!entry || entry.uploadedCount !== run.count) {\n throw new Error('StreamedSplatMesh: incomplete staged group commit.');\n }\n }\n\n for (const [key, entry] of group.removes) {\n if (!this.resident.has(key)) continue;\n this.removeRange(entry.handle);\n this.resident.delete(key);\n }\n for (const run of group.adds) {\n const key = runKey(run);\n const entry = this.staged.get(key) as {\n run: LodRun;\n handle: SplatRange;\n uploadedCount: number;\n };\n this.setRangeActive(entry.handle, true);\n this.resident.set(key, entry);\n this.staged.delete(key);\n }\n }\n\n /**\n * Applies one swap group atomically within this tick: removals first\n * (freeing pool rows for the replacements), then all adds. Returns false\n * without touching anything when the group cannot fit even after its own\n * removals - the caller defers it and the old runs keep rendering.\n */\n private applyGroup(group: SwapGroup, now: number): boolean {\n const rowSplats = (count: number): number =>\n Math.ceil(count / DATA_TEXTURE_WIDTH) * DATA_TEXTURE_WIDTH;\n const needed = group.adds.reduce((sum, run) => sum + rowSplats(run.count), 0);\n const freed = group.removes.reduce((sum, [, entry]) => sum + rowSplats(entry.run.count), 0);\n if (needed > this.freeSplatCapacity + freed) return false;\n\n for (const [key, entry] of group.removes) {\n if (!this.resident.has(key)) continue;\n this.removeRange(entry.handle);\n this.resident.delete(key);\n }\n for (const run of group.adds) {\n this.appendRun(run, now);\n }\n return true;\n }\n\n /** Appends one run from the cache, compacting the pool on fragmentation. */\n private appendRun(run: LodRun, now: number): void {\n const chunk = this.cache.get(run.file);\n if (!chunk) return; // caller pre-checked; only reachable on races\n const slice = sliceSplatData(chunk.data, run.offset, run.count);\n let handle: SplatRange;\n try {\n handle = this.appendRange(slice);\n } catch {\n this.compactionCount++;\n this.compact();\n try {\n handle = this.appendRange(slice);\n } catch {\n this.pendingWork = true; // retry next tick\n return;\n }\n }\n this.resident.set(runKey(run), { run, handle });\n chunk.lastUsed = now;\n this.writeLodLevelChannel(handle, run.level);\n // Re-apply any persistent channel edits for this file's splats - this is\n // what makes a painted mask survive the chunk being evicted and reloaded\n // (the pool row is fresh, but `(file, local index)` is a stable identity).\n for (const [name, channel] of this.persistentChannels) {\n this.applyPersistentRun(name, channel, run, handle);\n }\n }\n\n /**\n * Writes the stored edits for one run's `[offset, offset + count)` splats\n * into its freshly appended pool range. No-op when the file has no edits.\n */\n private applyPersistentRun(\n name: string,\n channel: PersistentChannel,\n run: LodRun,\n handle: SplatRange,\n ): void {\n const fileEdits = channel.edits.get(run.file);\n if (!fileEdits || fileEdits.size === 0) return;\n const data = channel.type === 'byte' ? new Uint8Array(run.count) : new Float32Array(run.count);\n // Seed with the channel's fill: this whole-run write must leave unedited\n // splats at their default, not clobber them to 0.\n if (channel.fill) data.fill(channel.fill);\n let any = false;\n for (let k = 0; k < run.count; k++) {\n const value = fileEdits.get(run.offset + k);\n if (value !== undefined) {\n data[k] = value;\n any = true;\n }\n }\n if (any) this.writeChannel(handle, name, data);\n }\n\n /**\n * Covers a deferred group's leaves that no resident run covers with each\n * leaf's coarsest (pinned, hence cached) level, so a region waiting on a\n * fetch shows coarse detail instead of nothing. The substitutes are\n * intentionally not \"desired\": the next reschedule swaps them for the\n * real level once its chunk has arrived.\n *\n * Near-camera refinements (`distance <= lodBaseDistance`) skip the coarse\n * paint and its pin fetch entirely - cold load requests only the target cut\n * for that cell. Far gaps keep the shell.\n */\n private substituteCoverage(\n group: SwapGroup,\n now: number,\n pendingFetches: Map<number, ClassicFetchWant>,\n holdForFinest: boolean,\n ): void {\n const span = group.leafEnd - group.leafStart;\n // Reused across calls: this runs for every deferred group of every streamed\n // mesh, every reschedule - ~800 times a second on a multi-mesh scene, at\n // a measured mean span of 60k leaves. Allocating the bitmap each time threw\n // away half a gigabyte in ten seconds and made this the single most\n // expensive function in the frame. The scratch only grows.\n if (this.coverageScratch === undefined || this.coverageScratch.length < span) {\n this.coverageScratch = new Uint8Array(span);\n }\n const covered = this.coverageScratch;\n covered.fill(0, 0, span);\n for (const { run } of this.resident.values()) {\n const from = Math.max(run.leafStart, group.leafStart);\n const to = Math.min(run.leafEnd, group.leafEnd);\n // `fill` over the overlap rather than a per-leaf loop: same marking, but\n // one memset instead of ~18k interpreted iterations per call.\n if (to > from) covered.fill(1, from - group.leafStart, to - group.leafStart);\n }\n\n // Walk the gaps with native scans rather than leaf-by-leaf in JS: the span\n // averages 60k leaves and is mostly covered, so the old loop spent its time\n // stepping over ones. `indexOf` on the bitmap does the same walk in memchr.\n // The scratch is oversized, hence the exact-length view to bound the search.\n const view = covered.subarray(0, span);\n let offset = 0;\n while (offset < span) {\n const gapStart = view.indexOf(0, offset);\n if (gapStart < 0) break;\n const nextCovered = view.indexOf(1, gapStart);\n const gapEnd = nextCovered < 0 ? span : nextCovered;\n const cursor = group.leafStart + gapStart;\n const end = group.leafStart + gapEnd;\n for (const run of this.scene.source.coarsestRunsFor(cursor, end)) {\n if (this.resident.has(runKey(run))) continue;\n if (holdForFinest) {\n // Do not paint coarsest discs while finest downloads. Do not enqueue\n // the pin - those slots belong to the group's finest fetches.\n this.fetchCountsValue.uncovered +=\n Math.min(run.leafEnd, end) - Math.max(run.leafStart, cursor);\n continue;\n }\n if (!this.cache.has(run.file)) {\n enqueueClassicFetch(\n pendingFetches,\n run.file,\n classicFetchPhaseForCoverage(run, this.scene.source.lodBaseDistance),\n run,\n );\n // This is the one path in the substitute that gives up: the gap keeps\n // no coverage at all until the chunk lands, so those leaves render as\n // nothing. Expected once during initial load (the coarsest level has\n // not arrived yet) and *not* expected afterwards, because the coarsest\n // files are pinned against eviction - so a count that climbs after the\n // scene has settled localizes a hole to here rather than to the swap\n // path. Counted in leaves, clipped to the gap, since a coarsest run\n // may span past it.\n this.fetchCountsValue.uncovered +=\n Math.min(run.leafEnd, end) - Math.max(run.leafStart, cursor);\n continue;\n }\n // A coarsest run may span beyond `[cursor, end)` - LCC2 root children\n // cover whole subtrees and cannot be clipped to a leaf sub-interval.\n // Octree intervals nest, so every resident run overlapping it lies\n // fully inside it: remove those first (the whole region temporarily\n // shows coarse), or their leaves would render twice - a bright flash\n // for the fetch window, permanent if the missing chunk never loads.\n for (const [key, entry] of this.resident) {\n if (entry.run.leafStart < run.leafEnd && entry.run.leafEnd > run.leafStart) {\n this.removeRange(entry.handle);\n this.resident.delete(key);\n }\n }\n this.appendRun(run, now);\n }\n offset = gapEnd;\n }\n }\n\n /** Issues pending classic-path chunk wants in group-priority order. */\n private flushClassicFetches(\n pending: Map<number, ClassicFetchWant>,\n lodBaseDistance: number,\n holdingNearL0 = false,\n ): void {\n if (pending.size === 0) return;\n stampClassicFetchGroups(pending, lodBaseDistance, holdingNearL0);\n const ordered = [...pending.entries()].sort((a, b) =>\n compareClassicFetches(a[1], b[1], a[0], b[0]),\n );\n this.preemptClassicFetches(ordered);\n for (const [file, want] of ordered) this.requestChunk(file, want.kind, want);\n }\n\n /**\n * A camera turn must not wait for all eight old visible requests to finish.\n * Only classic requests carry a precise rank; page-table work retains its\n * own scheduler and is never cancelled here.\n */\n private preemptClassicFetches(ordered: readonly [number, ClassicFetchWant][]): void {\n for (const [file, want] of ordered) {\n if (this.cache.has(file) || this.fetching.has(file)) continue;\n let worstFile: number | undefined;\n let worstWant: ClassicFetchWant | undefined;\n for (const [activeFile, active] of this.fetching) {\n if (!active.classicWant) continue;\n if (\n !worstWant ||\n compareClassicFetches(active.classicWant, worstWant, activeFile, worstFile as number) > 0\n ) {\n worstFile = activeFile;\n worstWant = active.classicWant;\n }\n }\n if (\n worstWant &&\n worstFile !== undefined &&\n compareClassicFetches(want, worstWant, file, worstFile) < 0\n ) {\n this.fetching.get(worstFile)?.controller.abort();\n }\n // The current request waits for the abort's finally callback to release\n // its slot; do not churn through every queued request in one tick.\n if (this.fetching.size >= this.maxInflight) return;\n }\n }\n\n /**\n * Loads the always-resident environment tile once, on the first update after\n * it is wanted. The tile has no LOD ladder and no manifest count, so it is\n * appended whole (measuring its splat count at decode) and thereafter toggled\n * by flipping its pool range active - never scheduled, refetched, or evicted.\n * When `pending` is supplied, a miss is ranked as an `'environment'` want so\n * it issues ahead of LOD coverage.\n */\n private updateEnvironment(now: number, pending?: Map<number, ClassicFetchWant>): void {\n const file = this.envFile;\n if (file === undefined || this.envHandle !== undefined || !this.envEnabled || this.envUnfit) {\n return;\n }\n const chunk = this.cache.get(file);\n if (!chunk) {\n if (!this.failedFiles.has(file)) {\n // The environment tile is always-resident coverage, never speculation:\n // a mesh that cannot fetch it renders no background at all.\n if (pending) this.enqueueEnvironmentFetch(pending, file);\n else this.requestChunk(file, 'priority');\n this.pendingWork = true;\n }\n return;\n }\n // The env sits outside the LOD budget, in the pool's capacity headroom -\n // which nothing guarantees is free (`maxResidentSplats` cannot include a\n // count only known at decode). Pre-check before touching the pool: without\n // this, an env that never fits would pay a full-pool compact() every\n // reschedule tick, forever.\n const rowAligned = Math.ceil(chunk.data.count / DATA_TEXTURE_WIDTH) * DATA_TEXTURE_WIDTH;\n if (rowAligned > this.capacity) {\n this.envUnfit = true; // could never fit even an empty pool\n warn(\n `the environment tile (${chunk.data.count} splats) exceeds the ` +\n `pool capacity (${this.capacity}); it will not be shown. Raise the ` +\n `splat budget to fit it.`,\n );\n return;\n }\n if (rowAligned > this.freeSplatCapacity) {\n // No free rows yet - compaction only defragments, it cannot create\n // them. Retry cheaply once LOD churn frees room.\n this.pendingWork = true;\n return;\n }\n let handle: SplatRange;\n try {\n handle = this.appendRange(chunk.data);\n } catch {\n // Enough rows exist but no contiguous span does; defragment once.\n this.compactionCount++;\n this.compact();\n try {\n handle = this.appendRange(chunk.data);\n } catch {\n this.pendingWork = true; // no room this tick; retry next\n return;\n }\n }\n this.envHandle = handle;\n this.envSplatCount = chunk.data.count;\n chunk.lastUsed = now;\n }\n\n /** Ranks the env tile ahead of every LOD want in {@link flushClassicFetches}. */\n private enqueueEnvironmentFetch(pending: Map<number, ClassicFetchWant>, file: number): void {\n if (pending.has(file)) return;\n pending.set(file, {\n kind: 'priority',\n phase: 'environment',\n distance: 0,\n level: 0,\n inView: true,\n coverageGroup: -1,\n leafStart: 0,\n leafEnd: 0,\n screenImportance: Number.NEGATIVE_INFINITY,\n groupDistance: 0,\n groupPending: 1,\n groupInView: true,\n groupScreenImportance: Number.NEGATIVE_INFINITY,\n groupFinest: true,\n groupId: 'environment',\n groupClass: 0,\n });\n }\n\n /**\n * Page-table reschedule (`foveationMode: 'page-table'`): posts the camera to the\n * worker, which owns the cache + traversal + pager and replies asynchronously\n * with a paging plan. Coalesced to one outstanding request so the main thread\n * never blocks. Also drives chunk fetching, in priority order.\n */\n private reschedulePageTable(\n cameraLocal: THREE.Vector3,\n forwardLocal: THREE.Vector3,\n frustum: THREE.Frustum,\n now: number,\n ): void {\n // 1. What the last frontier wanted and did not have, biggest-on-screen\n // first. This is the detail the camera is pointed at, so it takes the\n // fetch slots before anything else - issued *after* the sweep below it\n // was silently dropped by the in-flight cap on every tick, and the whole\n // capture downloaded in file order while the view stayed coarse.\n for (const file of this.pageTableFetchPriority) this.requestChunk(file, 'priority');\n // 2. The source's camera-directed coarse base, for far coverage.\n const desiredFiles = new Set(this.pageTableFetchPriority);\n for (const run of this.scene.source.computeDesiredRuns(cameraLocal, frustum, now)) {\n desiredFiles.add(run.file);\n this.requestChunk(run.file, 'base');\n }\n // Cancel detail this mesh no longer wants, so its slots go back to the\n // scene now rather than when a superseded request happens to finish. The\n // classic path has always done this; the page-table path never did, which\n // on a shared pipe means a camera cut kept paying for the old view.\n // Sweep fetches are exempt: they are file-order pre-warming that no\n // frontier plan ever names, so matching them against `desiredFiles` would\n // abort every one of them on the very next reschedule.\n for (const [file, entry] of this.fetching) {\n if (entry.kind === 'sweep') continue;\n if (!desiredFiles.has(file) && !this.scene.pinnedFiles.has(file)) entry.controller.abort();\n }\n // 3. Background sweep over the slots that remain: pull the lowest uncached\n // chunk (file order is coarse → fine). Once the cache holds the scene,\n // turning the camera is served from RAM in one traversal instead of a\n // level-by-level network ladder. It keeps a reserve free so (1) is never\n // starved, and pauses whenever the worker cache is at its cap - past that\n // point sweeping only evicts what the frontier is using. It resumes on\n // its own when the cap rises, which under a scene-wide `ChunkCacheBudget`\n // is what happens as the camera approaches this mesh.\n //\n // Only a mesh with weight sweeps. This is speculation about a camera\n // move that has not happened, and it is unbounded - it wants the entire\n // capture. On a scene of streamed additional meshes, every hidden and distant one\n // speculating at once is the traffic that delays the mesh the viewer\n // is looking at. The cost of gating it is that re-focusing a mesh that\n // went cold refetches instead of hitting a warm cache.\n if (!this.pageTableCacheAtLimit && this.sweepAllowed()) {\n const sweepCap = Math.max(1, this.maxInflight - PAGETABLE_PRIORITY_SLOTS);\n const files = this.scene.chunkUrls.length;\n for (let f = 0; f < files && this.fetching.size < sweepCap; f++) {\n if (!this.pageTableCachedFiles.has(f)) this.requestChunk(f, 'sweep');\n }\n }\n if (this.pageTableInFlight) return; // one traversal outstanding - coalesce\n\n this.pageTableInFlight = true;\n this.postToWorker({\n type: 'reschedule',\n seq: ++this.pageTableSeq,\n cameraLocal: [cameraLocal.x, cameraLocal.y, cameraLocal.z],\n cameraForward: [forwardLocal.x, forwardLocal.y, forwardLocal.z],\n ...this.pageTableFoveation,\n // Spark's cut is `pixel_scale × lodScale ≤ limit`, and the traversal only\n // ever sees one side of that - so scaling the limit down by `lodScale` is\n // the same comparison, with no protocol change.\n limit: this.pageTableLimit / this.lodScaleValue,\n budget: this.pageTableDrawBudget,\n });\n }\n\n /**\n * Applies a paging plan from the worker to the slab - fast memcpy writes only,\n * no traversal or gather on the main thread - then fetches the chunks the\n * frontier wants next, and reschedules again if chunks are still streaming.\n */\n private applyFrontierPlan(plan: FrontierPlanMessage): void {\n this.pageTableInFlight = false;\n if (this.pageTableDisposed || this.slabPages.length === 0) return;\n // Storage may have moved since this plan was built (a reschedule answered\n // from the old capacity, then a resize landed). Such a plan must still be\n // applied, clamped to the slots that exist: the worker's pager has already\n // mutated itself as if the whole plan ran, so dropping it desynchronizes the\n // two permanently - later plans only carry deltas, and the un-applied slots\n // keep stale (or never-written) content underneath a live resident count.\n //\n // Clamping is exact rather than approximate because a resize never remaps\n // the slots below the boundary: `syncSlabPages` only pushes or pops tail\n // pages, and `FrontierPager.resize` keeps `[0, keep)` untouched. So \"the\n // plan, truncated at the new capacity\" is precisely the pager's own state.\n const limit = this.pagerSlots;\n if (plan.capacity !== limit) {\n // The worker will re-traverse at the new capacity; make sure it does.\n this.pendingWork = true;\n this.lastScheduleTime = -Infinity;\n }\n // The pager emits moves in ascending slot order, and a swap-remove of a\n // contiguous block of leavers produces long runs of consecutive slots. Write\n // them a run at a time: one call per moved splat meant a `Box3` pass, a\n // bounds union and a row-range mark for every one of them, which stalled the\n // main thread for seconds whenever a camera move churned the frontier.\n const applyStartedAt = performance.now();\n const slots = plan.moveSlots;\n for (let i = 0; i < slots.length;) {\n let run = 1;\n while (i + run < slots.length && (slots[i + run] as number) === (slots[i] as number) + run) {\n run++;\n }\n const start = slots[i] as number;\n const clamped = Math.min(run, limit - start);\n if (clamped > 0) this.writeSlabSlots(slicePlanRun(plan.moves, i, clamped), start, clamped);\n i += run;\n }\n if (plan.appends.count > 0) {\n const clamped = Math.min(plan.appends.count, limit - plan.appendStart);\n if (clamped > 0) this.writeSlabSlots(plan.appends, plan.appendStart, clamped);\n }\n const writeFinishedAt = performance.now();\n // Draw exactly the used prefix.\n const resident = Math.min(plan.residentCount, limit);\n this.setSlabResident(resident);\n // Freed tail slots leave the active list, so their data is not drawn - but\n // zero it anyway. It costs a fill over the freed range only, and it means a\n // slot that somehow ends up drawn without being written renders nothing\n // instead of whichever coarse node used to own it (one enormous splat).\n const degenerateStart = Math.min(plan.degenerateStart, limit);\n const degenerateCount = Math.min(plan.degenerateCount, limit - degenerateStart);\n if (degenerateCount > 0) this.degenerateSlabSlots(degenerateStart, degenerateCount);\n const residentFinishedAt = performance.now();\n this.pageTableDrawn = resident;\n this.frontierConverged = plan.converged;\n this.pendingFrontierSplats = plan.pendingFrontierSplats ?? 0;\n this.staleResidentSplats = plan.staleResidentSplats ?? 0;\n this.lastPlanAppends = plan.lastPlanAppends ?? plan.appends.count;\n this.lastPlanMoves = plan.lastPlanMoves ?? plan.moveSlots.length;\n this.lastPlanGeneration = plan.planGeneration ?? this.lastPlanGeneration + 1;\n this.lastPlanBudget = plan.planBudget ?? this.pageTableDrawBudget;\n if (plan.cameraLocal) {\n this.lastPlanCamera = plan.cameraLocal;\n this.firstFrontierCamera ??= plan.cameraLocal;\n }\n if (plan.gatherMissing > 0) {\n // Splats whose chunk was evicted under them were written as zeros into\n // slots that are still drawn - holes in the coverage. Eviction protects\n // every chunk with resident splats, so this should be unreachable.\n warn(\n `StreamedSplatMesh: page-table plan gathered ${plan.gatherMissing} splats from ` +\n `evicted chunks; they render as holes.`,\n );\n }\n // Applying a plan runs off the render loop's own timing, so its cost is\n // invisible to `getUpdateTimings` even though it lands on the same thread.\n // Recorded because a churning frontier can make this the largest stall in a\n // frame, and a cap has to be aimed at whichever half dominates.\n const planTimings = this.planTimingsValue;\n planTimings.applyMs = residentFinishedAt - applyStartedAt;\n planTimings.writeMs = writeFinishedAt - applyStartedAt;\n planTimings.residentMs = residentFinishedAt - writeFinishedAt;\n planTimings.moves = plan.moveSlots.length;\n planTimings.appends = plan.appends.count;\n if (planTimings.applyMs > planTimings.worstApplyMs) {\n planTimings.worstApplyMs = planTimings.applyMs;\n planTimings.worstSplats = planTimings.moves + planTimings.appends;\n }\n // Follow the cut with the screen-radius band. The worker refines below the\n // quality target to spend the draw budget, and those finer nodes project\n // smaller - a band still sized for the target cut would cull them, so the\n // extra budget would buy nothing visible. Scaling by the same ratio keeps\n // the band spanning one LOD level.\n if (this.frontierBandBase !== null && plan.solvedLimit > 0 && this.pageTableLimit > 0) {\n const ratio = Math.min(1, plan.solvedLimit / this.pageTableLimit);\n this.setScreenRadiusBand(\n this.frontierBandBase.min * ratio,\n this.frontierBandBase.max * ratio,\n );\n }\n this.invalidateSort();\n if (plan.dropped > 0) {\n // The traversal is budget-bounded, so the slab always has room. If it does\n // not, the pool is smaller than the draw budget and part of the frontier is\n // silently missing - say so rather than render a hole.\n warn(\n `StreamedSplatMesh: page-table slab full, dropped ${plan.dropped} frontier splats ` +\n `(draw budget ${this.pageTableDrawBudget} exceeds the pool).`,\n );\n }\n // Worker-evicted chunks must be forgotten here too, or they can never refetch.\n for (let i = 0; i < plan.evicted.length; i++) {\n this.pageTableCachedFiles.delete(plan.evicted[i] as number);\n }\n this.fetchCountsValue.cacheBytes = plan.cacheBytes;\n this.fetchCountsValue.cacheLimitBytes = plan.cacheLimitBytes;\n // Recomputed every plan, not latched: the sweep must resume when the scene\n // budget raises this mesh's allowance. `cacheFull`/`evicted` stay monotonic\n // - they are diagnostics answering \"did this happen\", not live state.\n this.pageTableCacheAtLimit = plan.cacheBytes >= plan.cacheLimitBytes;\n if (plan.evicted.length > 0) {\n this.fetchCountsValue.cacheFull = true;\n this.fetchCountsValue.evicted += plan.evicted.length;\n }\n // The chunks the frontier wants next, biggest-on-screen first - requested now\n // and kept as the priority list the next reschedule fetches before anything.\n this.pageTableFetchPriority = Array.from(plan.touched);\n for (const file of this.pageTableFetchPriority) this.requestChunk(file, 'priority');\n // Keep refining while chunks stream in (the frontier keeps changing), and\n // while the worker is still ramping its budget up to the governed one - that\n // ramp is what keeps a hard camera cut from arriving as one ~100 ms plan, so\n // the next pass must follow immediately or detail stalls where it stopped.\n if (this.fetching.size > 0 || !plan.converged) {\n this.pendingWork = true;\n if (!plan.converged) this.lastScheduleTime = -Infinity;\n }\n }\n\n /** Forwards a decoded chunk's arrays to the worker (buffers transferred) so the\n * worker's cache/traversal/gather can use it. */\n private forwardChunkToWorker(file: number, data: SplatData): void {\n const tree = data.radTree;\n if (!tree) return;\n this.pageTableCachedFiles.add(file);\n // Only forward SH the pool will actually render. A `.rad` chunk decodes\n // whatever bands the file carries regardless of what was asked for, and the\n // worker charges its cache for every byte it is handed - 15 coefficients is\n // 60 B/splat against 40 B for position, colour and covariance combined, so\n // SH the mesh has declined was **60% of the chunk cache**.\n //\n // Measured on the reference capture with SH declined: the worker counted\n // 100 B/splat where the cache-floor estimate assumes 40, so the cache filled\n // at ~52 chunks' worth of its limit instead of the 132 the estimate predicts\n // and the frontier thrashed - one eviction and one refetch every couple of\n // seconds, forever, with resident chunks oscillating in the low 70s.\n //\n // Dropping it here also makes `estimateSceneDecodedBytes` correct rather\n // than merely larger: both sides then agree on 40 B/splat.\n //\n // `shBands` rather than the pool's `packedShBands` (which is private, and\n // protected would put it in the published `.d.ts`): they agree here, because\n // the only way they differ is palette SH, and a streamed mesh never has it -\n // the slicer drops `chunk.sh` before a chunk ever reaches the pool.\n const sh = this.shBands > 0 ? data.shPacked : undefined;\n this.postToWorker(\n {\n type: 'chunk',\n file,\n count: data.count,\n positions: data.positions,\n colors: data.colors,\n covariances: data.covariances,\n childCount: tree.childCount,\n childStart: tree.childStart,\n size: tree.size,\n shBands: sh?.bands ?? 0,\n ...(sh ? { shPacked: sh.packed, shRange: sh.range } : {}),\n },\n [\n data.positions.buffer,\n data.colors.buffer,\n data.covariances.buffer,\n tree.childCount.buffer,\n tree.childStart.buffer,\n tree.size.buffer,\n ...(sh ? [sh.packed.buffer] : []),\n ],\n );\n }\n\n /** Parallel chunk fetches. HTTP/2 multiplexes them; on HTTP/1.1 the browser's\n * per-host cap simply queues. Same cap for classic and page-table so near\n * detail is not structurally starved on the non-page-table path. */\n private get maxInflight(): number {\n return MAX_INFLIGHT;\n }\n\n /**\n * Whether this mesh may run its speculative background sweep. A mesh with no\n * weight is hidden or suspended; a mesh with no `fetchWeight` at all is a\n * host that never asked for arbitration, and keeps the old behaviour.\n */\n /**\n * Sets this mesh's share of the scene's fetch bandwidth, as\n * {@link StreamedSplatMeshOptions.fetchWeight} does at load.\n *\n * The weight normally closes over the mesh itself (`() =>\n * governor.weightOf(mesh)`), which a host cannot express until `load`\n * resolves - hence a setter as well as an option. Pass `undefined` to go back\n * to unarbitrated sweeping.\n */\n setFetchWeight(weight: (() => number) | undefined): void {\n this.fetchWeight = weight;\n }\n\n private sweepAllowed(): boolean {\n // The `smooth` profile (the default on mobile) declines the sweep outright.\n //\n // The sweep is speculative pre-warming of the *whole capture*, and without a\n // scene-wide `cacheBudget` it does not stop until every chunk is cached: the\n // cap is sized from the capture itself (`min(PAGETABLE_CACHE_FLOOR_BYTES,\n // estimateSceneDecodedBytes)`), so on any capture that fits there is never\n // an eviction to reach it. Measured on the 5.9M-leaf reference `.rad` with\n // SH declined: a 235 MB cache floor against a 235 MB decoded capture, i.e. a\n // steady ~1 chunk/second drip pulling all 447 MB down and decoding it, long\n // after the view had settled at full detail. Multiply that by the meshes in\n // a multi-mesh scene and it is the largest memory risk in a viewer - which is\n // what `cacheBudget` bounds, without stopping the sweep itself.\n //\n // On a desktop that is a good trade - RAM is cheap and turning the camera is\n // then served from memory instead of a level-by-level network ladder. On a\n // phone it is the wrong one in every currency at once: hundreds of MB of\n // possibly-metered download, a decoded cache that rivals the splat pool on a\n // device that gets its tab killed for exactly that, and continuous decode\n // CPU (and therefore heat) spent on a camera move that may never happen.\n // What it costs to decline: refinement after a turn fetches on demand.\n if (this.performanceProfile === 'smooth') return false;\n if (this.fetchWeight === undefined) return true;\n const weight = this.fetchWeight();\n return Number.isFinite(weight) && weight > 0;\n }\n\n /** Aborts in-flight fetches of one kind; their slots return through `finally`. */\n private abortFetches(kind: ChunkFetchKind): void {\n for (const entry of this.fetching.values()) {\n if (entry.kind === kind) entry.controller.abort();\n }\n }\n\n private requestChunk(file: number, kind: ChunkFetchKind, classicWant?: ClassicFetchWant): void {\n if (\n this.cache.has(file) ||\n this.pageTableCachedFiles.has(file) ||\n this.fetching.has(file) ||\n this.fetching.size >= this.maxInflight\n ) {\n return;\n }\n if (this.failedFiles.has(file)) return; // given up\n const backoff = this.retrying.get(file);\n if (backoff && performance.now() < backoff.readyAt) return; // waiting to retry\n\n // Counted here, past every \"already have it / already fetching / capped\"\n // guard, so the totals mean \"requests that became real network work\".\n this.fetchCountsValue[kind]++;\n const url = this.scene.chunkUrls[file];\n if (url === undefined) {\n // A manifest referencing an out-of-range file index can never load;\n // fail it terminally so its groups settle on their coarse substitutes\n // instead of rescheduling (and spinning the indicator) forever.\n warn(`StreamedSplatMesh: manifest references unknown chunk file #${file}.`);\n this.failedFiles.add(file);\n return;\n }\n // Scene-wide arbitration, after every local reason not to fetch: a slot\n // taken here is a slot denied to a sibling, so it must not be spent on a\n // request the mesh would have skipped anyway. A denial is not a failure and\n // deliberately leaves `retrying` alone - the mesh simply did not fetch this\n // tick, and the scheduler wakes it when the pipe frees up.\n if (this.fetchHandle && !this.fetchScheduler?.tryAcquire(this.fetchHandle, kind)) return;\n const controller = new AbortController();\n this.fetching.set(file, { controller, kind, classicWant });\n this.loader\n .load(url, {\n kind: this.scene.chunkKind,\n signal: controller.signal,\n ...this.scene.chunkOptions?.[file],\n })\n .then((data) => {\n // A chunk that resolved just before dispose still lands here one\n // microtask later; keeping it would repopulate the cleared cache (or\n // post to a terminated frontier worker).\n if (this.disposed) return;\n this.retrying.delete(file);\n // Formats whose LOD structure lives in the chunks (a `.rad` tree) learn\n // it here - the source uses it for its coarse-base ranking. Read it before\n // any transfer.\n this.scene.source.onChunkDecoded?.(file, data);\n if (this.frontierWorker) {\n // Page-table mode: the worker owns the cache. Forward the chunk (its\n // buffers are transferred, so the main thread does not keep it).\n this.forwardChunkToWorker(file, data);\n this.pendingWork = true; // a new chunk changes the frontier\n } else {\n this.cacheChunk(file, data);\n }\n })\n .catch((error: unknown) => {\n // Aborts (the camera moved on, or the mesh was disposed) are not\n // failures: a later reschedule re-requests the file if it is still\n // wanted. `isAbortError` also matches the non-DOMException AbortError\n // `ChunkLoader.dispose` raises where DOMException is unavailable -\n // treating that as a failure would log and retry against a dead worker.\n if (isAbortError(error)) return;\n const attempts = (this.retrying.get(file)?.attempts ?? 0) + 1;\n if (attempts >= MAX_CHUNK_ATTEMPTS) {\n this.retrying.delete(file);\n this.failedFiles.add(file);\n // Terminal: the region silently settles on its coarse substitute\n // forever, so say why once - otherwise a scene that is simply\n // missing detail looks like a renderer bug.\n warn(\n `StreamedSplatMesh: gave up on chunk #${file} (${url}) after ${attempts} attempts.`,\n error,\n );\n } else {\n // Exponential backoff; the idle reschedule (≤250 ms) picks it up.\n const delay = RETRY_BASE_MS * 2 ** (attempts - 1);\n this.retrying.set(file, { attempts, readyAt: performance.now() + delay });\n }\n })\n .finally(() => {\n this.fetching.delete(file);\n // Released here rather than on success, so an aborted or failed fetch\n // hands its slot back too - a leak here silently shrinks the scene's\n // whole pipe until the pool is torn down.\n if (this.fetchHandle) this.fetchScheduler?.release(this.fetchHandle);\n this.pendingWork = true;\n });\n }\n\n /**\n * Stores a decoded chunk while keeping {@link cacheBytesTotal} in step. The\n * counter replaces a full-cache re-sum on every reschedule; every mutation\n * of {@link cache} (this method, eviction, dispose's clear) maintains it.\n */\n private cacheChunk(file: number, data: SplatData): void {\n const previous = this.cache.get(file);\n if (previous !== undefined) this.cacheBytesTotal -= previous.bytes;\n const bytes = chunkBytes(data);\n this.cache.set(file, { data, bytes, lastUsed: performance.now() });\n this.cacheBytesTotal += bytes;\n }\n\n private evictChunks(now: number): void {\n let total = this.cacheBytesTotal;\n if (total <= this.cpuCacheBytes) return;\n\n // Evict least-recently-used chunks first; never a chunk touched this\n // tick, and never a pinned (coarsest-level) chunk - those are the\n // substitute coverage and must stay sliceable. Evicting a chunk that\n // still backs a resident run is safe - its splats already live in the\n // pool; only future re-slicing would refetch.\n const candidates = [...this.cache.entries()]\n .filter(\n ([file, chunk]) =>\n chunk.lastUsed !== now &&\n !this.scene.pinnedFiles.has(file) &&\n !this.neededFiles.has(file),\n )\n .sort((a, b) => a[1].lastUsed - b[1].lastUsed);\n for (const [file, chunk] of candidates) {\n if (total <= this.cpuCacheBytes) break;\n this.cache.delete(file);\n this.cacheBytesTotal -= chunk.bytes;\n total -= chunk.bytes;\n // Counted for the same reason the page-table path counts its worker's\n // evictions: `base` climbing with this flat is refinement converging,\n // while `base` climbing *with* this is a cache too small for the cut, and\n // the two look identical from outside. Until this existed the streamed\n // path reported a constant `evicted: 0`, which read as \"no thrashing\"\n // when it only ever meant \"not measured\".\n this.fetchCountsValue.evicted++;\n }\n }\n}\n\nconst _paintLocal = new THREE.Vector3();\nconst _cameraWorldPos = new THREE.Vector3();\nconst _cameraWorldQuat = new THREE.Quaternion();\nconst _cameraLocal = new THREE.Vector3();\nconst _projScreen = new THREE.Matrix4();\nconst _frustum = new THREE.Frustum();\nconst _sphere = new THREE.Sphere();\n/** Camera forward in mesh-local space, for the page-table traversal's foveation. */\nconst _cameraForward = new THREE.Vector3();\nconst _drawSize = new THREE.Vector2();\n\n/** A `SplatData` view over a contiguous run `[j, j + count)` of a plan's packed\n * splats, so one pool write covers a whole run of slots. Zero-copy subarrays. */\nfunction shWordsPerSplat(bands: 1 | 2 | 3): number {\n return Math.ceil((3 * shCoefficientCount(bands)) / 4);\n}\n\nfunction slicePlanRun(splats: PlanSplats, j: number, count: number): SplatData {\n const sh = splats.shPacked;\n return {\n count,\n positions: splats.positions.subarray(j * 3, (j + count) * 3),\n colors: splats.colors.subarray(j * 4, (j + count) * 4),\n covariances: splats.covariances.subarray(j * 6, (j + count) * 6),\n ...(sh\n ? {\n shPacked: {\n ...sh,\n packed: sh.packed.subarray(\n j * shWordsPerSplat(sh.bands),\n (j + count) * shWordsPerSplat(sh.bands),\n ),\n },\n }\n : {}),\n };\n}\n","/**\n * Shared splat-budget governance across multiple streamed meshes.\n *\n * A single `StreamedSplatMesh` keeps itself within a per-device budget, but a\n * host that shows several streamed scenes at once (a main capture plus additional\n * or inset meshes) must not let each mesh claim the whole device budget -\n * their pools are separate, so the costs add. Historically hosts hand-tuned\n * this (\"shrink the main mesh to 0.7 when additional meshes exist\"); the\n * {@link BudgetGovernor} makes it a first-class policy: register each mesh\n * with a priority weight and the governor splits one total budget across the\n * members, reallocating when membership, weights, or the total change.\n *\n * The governor steers members exclusively through their public\n * `setBudget`, so every downstream consumer of a member's budget - the\n * flat-leaf `LodScheduler`, the LCC2 octree cut, and the RAD page-table draw\n * target - sees the governed value through the exact path an explicit host\n * `setBudget` call would take. Meshes never registered with a governor are\n * completely unaffected.\n */\n\nimport { resolveSplatBudget } from '../core/splat-budget';\n\n/**\n * Anything the governor can steer. `StreamedSplatMesh` satisfies this\n * structurally; a custom member only needs the same clamp-and-report\n * `setBudget` contract.\n */\nexport interface BudgetGovernedMember {\n /** The member's current effective active-splat budget. */\n readonly budget: number;\n /**\n * Applies a budget and returns the value actually in effect - which may be\n * lower than asked when the member clamps to a fixed ceiling (for\n * `StreamedSplatMesh`, its `maxBudget`).\n */\n setBudget(budget: number): number;\n /**\n * The ceiling `setBudget` clamps to, when the member knows one\n * (`StreamedSplatMesh.maxBudget`). **Advisory only** - allocation still\n * discovers real caps from `setBudget`'s return value, so a member that\n * omits this is governed exactly as well.\n */\n readonly maxBudget?: number;\n}\n\n/** Options for {@link BudgetGovernor}. */\nexport interface BudgetGovernorOptions {\n /**\n * Total active-splat budget shared by all members. Defaults to the\n * per-device {@link resolveSplatBudget} - i.e. the group as a whole gets\n * what one mesh alone would get today.\n */\n totalBudget?: number;\n /**\n * Grow dead-band as a fraction of a member's current budget (default\n * `0.1`). A reallocation that would *raise* a member's budget by no more\n * than this fraction is skipped, so brief membership churn (an additional mesh\n * appearing for a moment) does not thrash LOD schedules. Shrinks always\n * apply immediately - that is what keeps `sum(member budgets) ≤ total` an\n * invariant rather than a goal.\n */\n hysteresis?: number;\n}\n\n/**\n * The budget a suspended (`weight: 0`) member is held at.\n *\n * Not 0: a member's `setBudget` may reject a non-positive budget outright\n * (`StreamedSplatMesh` routes through `resolveSplatBudget`, which throws\n * `RangeError` on `<= 0`). 1 splat is the smallest legal value and matches the\n * floor the weighted split already uses.\n */\nconst SUSPENDED_BUDGET = 1;\n\ninterface MemberEntry {\n member: BudgetGovernedMember;\n weight: number;\n /** Budget the member reported after the governor's last applied call. */\n applied: number;\n /** The member's budget at registration, restored on unregister/dispose. */\n restoreBudget: number;\n}\n\n/**\n * Splits one total splat budget across registered members by priority weight.\n *\n * Allocation is weighted and cap-aware: a member whose `setBudget` clamps\n * below its weighted share (a small scene, or a mesh with a small pool)\n * releases the difference to the remaining members, so the total is spent\n * where it can buy detail. Reallocation runs automatically on\n * register/unregister and on weight or total changes.\n *\n * A member at `weight: 0` is **suspended**: held at\n * {@link SUSPENDED_BUDGET}, excluded from the weighted split, and its whole\n * share released to the others - Spark's `lodScale: 0` hidden tier, without\n * unregistering (so the mesh stays warm and re-weighting it costs nothing).\n * Note that a suspended member is not *free*: its pool was allocated at\n * construction and is never released, and a streamed mesh keeps its pinned\n * coarse shell resident, so it consumes ≈0 of the budget rather than exactly 0.\n * To give the memory back, dispose the mesh.\n *\n * For camera-driven weights - nearby meshes automatically taking a larger\n * share - see `CameraBudgetGovernor`, which drives this class.\n *\n * Invariant: the sum of budgets the governor has applied to active members\n * never exceeds {@link totalBudget}.\n */\nexport class BudgetGovernor {\n private readonly entries = new Map<BudgetGovernedMember, MemberEntry>();\n private total: number;\n private readonly hysteresis: number;\n\n constructor(options: BudgetGovernorOptions = {}) {\n this.total = resolveSplatBudget(options.totalBudget);\n const hysteresis = options.hysteresis ?? 0.1;\n if (!Number.isFinite(hysteresis) || hysteresis < 0) {\n throw new RangeError('BudgetGovernor hysteresis must be a non-negative finite number.');\n }\n this.hysteresis = hysteresis;\n }\n\n /** The shared budget currently being split across members. */\n get totalBudget(): number {\n return this.total;\n }\n\n /** Replaces the shared total and reallocates. */\n setTotalBudget(totalBudget: number): void {\n const next = resolveSplatBudget(totalBudget);\n if (next === this.total) return;\n this.total = next;\n this.reallocate();\n }\n\n /** Number of registered members. */\n get size(): number {\n return this.entries.size;\n }\n\n /**\n * Adds a member and reallocates the shared budget. The member's current\n * budget is remembered and restored when it leaves the governor.\n *\n * @param member - The mesh (or compatible object) to govern.\n * @param options - `weight` (default `1`): the member's share is\n * proportional to its weight - e.g. main mesh `7`, additional mesh `3` reproduces\n * the old 0.7 host split. `0` registers the member suspended.\n */\n register(member: BudgetGovernedMember, options: { weight?: number } = {}): void {\n if (this.entries.has(member)) {\n throw new Error('BudgetGovernor: member is already registered.');\n }\n const weight = validateWeight(options.weight ?? 1);\n this.entries.set(member, {\n member,\n weight,\n applied: member.budget,\n restoreBudget: member.budget,\n });\n this.reallocate();\n }\n\n /**\n * Removes a member, restores the budget it had when it registered, and\n * reallocates the total across the remaining members. No-op for a member\n * that is not registered (so disposing hosts need not track membership).\n */\n unregister(member: BudgetGovernedMember): void {\n const entry = this.entries.get(member);\n if (entry === undefined) return;\n this.entries.delete(member);\n entry.member.setBudget(entry.restoreBudget);\n this.reallocate();\n }\n\n /**\n * Changes a member's priority weight and reallocates. `0` suspends the\n * member (see the class doc); any positive weight resumes it.\n */\n setWeight(member: BudgetGovernedMember, weight: number): void {\n const entry = this.entryOf(member);\n const next = validateWeight(weight);\n if (next === entry.weight) return;\n entry.weight = next;\n this.reallocate();\n }\n\n /**\n * Writes several weights, then reallocates **once**.\n *\n * Prefer this to a loop of {@link setWeight} whenever more than one weight\n * changes together - as a camera-driven reweight does. Each reallocation\n * pushes `setBudget` to every member, and for a streamed mesh that forces an\n * LOD reschedule, so N separate calls cost N passes over the whole group to\n * reach a state one pass would have produced.\n *\n * @param weights - `[member, weight]` pairs. Every member must be registered;\n * unlisted members keep their current weight.\n * @throws {Error} if any member is not registered - checked before anything\n * is written, so a bad pair leaves every weight untouched.\n * @throws {RangeError} if any weight is not a non-negative finite number.\n */\n setWeights(weights: Iterable<readonly [BudgetGovernedMember, number]>): void {\n // Validate the whole batch first: a partially applied reweight would leave\n // the group in a state no caller asked for.\n const pending = [...weights].map(\n ([member, weight]) => [this.entryOf(member), validateWeight(weight)] as const,\n );\n let changed = false;\n for (const [entry, weight] of pending) {\n if (entry.weight === weight) continue;\n entry.weight = weight;\n changed = true;\n }\n if (changed) this.reallocate();\n }\n\n /** The budget the governor last applied to a member, if registered. */\n budgetOf(member: BudgetGovernedMember): number | undefined {\n return this.entries.get(member)?.applied;\n }\n\n /**\n * Recomputes and applies every member's share. Called automatically by all\n * mutators; call it manually only if a member's internal ceiling changed\n * outside the governor's view.\n */\n reallocate(): void {\n // Suspended members first, so the share they release is available to the\n // waterfill below in the same pass. Held at the floor rather than skipped:\n // a member that was carrying a large budget when it was suspended must\n // actually give it up, or the sum invariant breaks.\n let pool: MemberEntry[] = [];\n for (const entry of this.entries.values()) {\n if (entry.weight === 0) this.applyTarget(entry, SUSPENDED_BUDGET);\n else pool.push(entry);\n }\n let budget = this.total;\n // Cap-aware waterfill: give each member its weighted share of what is\n // left; a member that clamps below its share is finalized at its cap and\n // releases the difference to the others on the next pass. Terminates in at\n // most `size` passes (each non-final pass finalizes at least one member).\n // An all-suspended group leaves `pool` empty and skips the loop, so the\n // weight sum is never 0 here.\n while (pool.length > 0) {\n const weightSum = pool.reduce((sum, entry) => sum + entry.weight, 0);\n const capped: MemberEntry[] = [];\n let cappedSpend = 0;\n for (const entry of pool) {\n const target = Math.max(1, Math.floor((budget * entry.weight) / weightSum));\n if (this.applyTarget(entry, target)) {\n capped.push(entry);\n cappedSpend += entry.applied;\n }\n }\n if (capped.length === 0) break;\n budget = Math.max(0, budget - cappedSpend);\n pool = pool.filter((entry) => !capped.includes(entry));\n }\n }\n\n /**\n * Restores every member's pre-registration budget and empties the governor.\n * The governor itself stays usable (dispose is just \"unregister everyone\").\n */\n dispose(): void {\n for (const entry of this.entries.values()) {\n entry.member.setBudget(entry.restoreBudget);\n }\n this.entries.clear();\n }\n\n /** The entry for a registered member, or a thrown error naming the problem. */\n private entryOf(member: BudgetGovernedMember): MemberEntry {\n const entry = this.entries.get(member);\n if (entry === undefined) {\n throw new Error('BudgetGovernor: member is not registered.');\n }\n return entry;\n }\n\n /**\n * Pushes `target` to a member, with the grow dead-band. Returns true when\n * the member clamped below its target (it is capped and cannot absorb more).\n */\n private applyTarget(entry: MemberEntry, target: number): boolean {\n if (target > entry.applied) {\n // Grows within the dead-band are skipped: staying low never violates\n // the sum invariant, and the skipped headroom is reclaimed by the next\n // meaningful reallocation.\n if (target - entry.applied <= this.hysteresis * entry.applied) return false;\n } else if (target === entry.applied) {\n return false;\n }\n entry.applied = entry.member.setBudget(target);\n return entry.applied < target;\n }\n}\n\n/** Validates a member weight: any non-negative finite number, `0` = suspended. */\nfunction validateWeight(weight: number): number {\n if (!Number.isFinite(weight) || weight < 0) {\n throw new RangeError(\n 'BudgetGovernor member weight must be a non-negative finite number (0 suspends the member).',\n );\n }\n return weight;\n}\n","/**\n * Camera-driven splat-budget weighting across several streamed meshes.\n *\n * {@link BudgetGovernor} splits one total by *priority*, which a host must\n * choose and maintain. That is the wrong axis for a scene of additional meshes:\n * what makes a mesh worth splats is that the camera is near it, and that changes\n * every frame. Splitting a pool evenly instead (`pool / N`) gives the mesh\n * you fly up to a quarter of the budget it needs while three meshes nobody is\n * looking at hold the rest.\n *\n * {@link CameraBudgetGovernor} closes that loop: each update it measures every\n * member's projected size from the camera, multiplies in the host's priority\n * tier, and writes the resulting weights to a `BudgetGovernor` in one batch.\n * Approaching one of four additional meshes pulls budget off the far ones automatically.\n *\n * This is Spark's model, reached differently. Spark shares one `lodSplatCount`\n * and biases meshes with a per-mesh `lodScale` (focused 2, adjacent 0.25,\n * hidden 0); its GPU traversal then favors near, on-screen detail on its own.\n * VLAM's meshes each own a pool, so the near/far bias has to be applied to the\n * *budget* - which is what this class does, with `priority` playing the part of\n * `lodScale`. (For a `.rad` page-table mesh, `StreamedSplatMesh.lodScale` is\n * also available and is Spark's knob exactly.)\n *\n * Composition, not inheritance: `BudgetGovernor` stays a pure allocation\n * policy with no camera and no frame lifecycle, and a host that wants fixed\n * weights keeps using it directly.\n */\n\nimport * as THREE from 'three/webgpu';\n\nimport {\n BudgetGovernor,\n type BudgetGovernedMember,\n type BudgetGovernorOptions,\n} from './budget-governor';\n\n/**\n * A governed member this class can measure. Every `SplatMesh` (and so every\n * `StreamedSplatMesh`) satisfies it structurally - there is no extra host\n * plumbing to write.\n */\nexport interface CameraBudgetMember extends BudgetGovernedMember {\n /**\n * The member's splat bounds in its own local frame. `StreamedSplatMesh`\n * overrides this to the whole scene's bounds, which are known from the\n * manifest - so weighting is correct from the first frame, before a single\n * chunk has loaded.\n */\n computeSplatBounds(): THREE.Box3;\n /** Local→world transform, read after {@link updateWorldMatrix}. */\n readonly matrixWorld: THREE.Matrix4;\n /** `Object3D.updateWorldMatrix`; called so a fresh member is placed correctly. */\n updateWorldMatrix(updateParents: boolean, updateChildren: boolean): void;\n /**\n * `Object3D.visible`. A hidden member is suspended (weight 0) - it draws\n * nothing, so it should hold no budget. This is the member's own flag, not an\n * ancestor walk: a host hiding a whole group should set `priority: 0`.\n */\n readonly visible: boolean;\n /**\n * The visibility that actually decides whether the member's splats reach the\n * screen, when that differs from `visible`. `SplatMesh` provides it: a\n * `UnifiedSplatMesh` forces `visible = false` on every source it owns\n * (only to keep the regular scene draw from double-drawing them) while the\n * source may be fully on screen through the unified draw - without this, the\n * governor would suspend every unified source and freeze its streaming.\n * When present it wins over `visible`.\n */\n readonly effectiveVisibility?: boolean;\n}\n\n/** Options for {@link CameraBudgetGovernor}. */\nexport interface CameraBudgetGovernorOptions extends BudgetGovernorOptions {\n /**\n * An existing governor to drive, when the host already has one (or wants to\n * mix camera-weighted and fixed-weight members). By default one is built\n * from the inherited {@link BudgetGovernorOptions}.\n */\n governor?: BudgetGovernor;\n /**\n * Minimum milliseconds between reweights. Default `250`, matching\n * `StreamedSplatMesh`'s own idle reschedule interval: a mesh cannot act on a\n * budget change faster than it reschedules, so reweighting more often buys\n * nothing and costs a forced reschedule on every member. Membership and\n * priority changes bypass it.\n */\n minIntervalMs?: number;\n /**\n * Relative weight change below which a reweight is skipped entirely, as a\n * fraction of the weight in effect. Default `0.15`.\n *\n * This sits *above* `BudgetGovernor`'s grow dead-band and does a different\n * job: that one damps budget churn on a member, this one suppresses the\n * reallocation altogether so an idling camera does no work at all.\n */\n weightDeadband?: number;\n /**\n * Exponent on projected size. `1` (default) weights by angular size -\n * halving the distance doubles the share. `2` weights by projected *area*,\n * which concentrates the budget harder on the nearest member.\n */\n falloff?: number;\n /**\n * Weight multiplier for a member outside the view frustum. Default `0.25`:\n * suppressed, never starved. Off-screen members must keep enough budget for\n * their coarse shell, or turning the camera exposes an unpainted region -\n * the same foveate-don't-cull policy the `.rad` frontier traversal follows.\n */\n offScreenWeight?: number;\n /** Floor on the projected-size term, so a very distant member still holds a\n * coarse shell. Default `0.05`. */\n minWeight?: number;\n /** Ceiling on the projected-size term, so a member the camera is inside\n * cannot take the entire total. Default `8`. */\n maxWeight?: number;\n}\n\n/** Per-member options for {@link CameraBudgetGovernor.register}. */\nexport interface CameraBudgetMemberOptions {\n /**\n * Host priority multiplied into the camera term - Spark's `lodScale` tiers:\n * focused `2`, default `1`, adjacent `0.25`, hidden `0`. Default `1`.\n * `0` suspends the member (see {@link BudgetGovernor}).\n */\n priority?: number;\n /**\n * Pins this member's weight, opting it out of camera weighting while it still\n * competes for the same total. Use it for a main scene that should hold a\n * steady share while additional meshes fight over the rest - e.g. `fixedWeight: 4`\n * against extras averaging `1`.\n */\n fixedWeight?: number;\n}\n\ninterface CameraEntry {\n member: CameraBudgetMember;\n priority: number;\n fixedWeight: number | undefined;\n /**\n * Weight currently written to the governor - seeded at registration, so a\n * member never reads back as \"unweighted\". `register` forces the next update\n * regardless, which is what makes the seed safe to compare against.\n */\n applied: number;\n}\n\n/** Reused across updates - this runs every frame and must not allocate. */\nconst _cameraPos = new THREE.Vector3();\nconst _projScreen = new THREE.Matrix4();\nconst _frustum = new THREE.Frustum();\nconst _box = new THREE.Box3();\nconst _sphere = new THREE.Sphere();\n\n/**\n * Weights a {@link BudgetGovernor}'s members by how large each one projects\n * from the camera, so nearby meshes take budget from distant ones.\n *\n * ```js\n * const governor = new CameraBudgetGovernor({ totalBudget: 4_000_000 });\n * governor.register(main, { fixedWeight: 4 }); // steady share\n * governor.register(extraA); // camera-weighted, priority 1\n * governor.register(extraB);\n *\n * // once per frame, after the scene graph is up to date:\n * governor.update(camera);\n * ```\n *\n * Every guarantee of the underlying governor still holds - most importantly\n * that the applied budgets never sum above the total, and that unregistering a\n * member restores the budget it had when it joined.\n *\n * **A member can only grow into a budget its pool can hold.** A streamed mesh\n * allocates its pool once, from its construction budget, and clamps `setBudget`\n * to it - so a mesh built at a quarter of the total can never be given more\n * than a quarter, however close the camera gets. Construct governed meshes with\n * `maxBudget` set to the largest share they should ever reach, and price those\n * ceilings with `estimateSplatPoolBytes` first: the pools cost their ceilings\n * whatever the budget is split to.\n */\nexport class CameraBudgetGovernor {\n private readonly entries = new Map<CameraBudgetMember, CameraEntry>();\n private readonly budgetGovernor: BudgetGovernor;\n private readonly minIntervalMs: number;\n private readonly weightDeadband: number;\n private readonly falloff: number;\n private readonly offScreenWeight: number;\n private readonly minWeight: number;\n private readonly maxWeight: number;\n private lastUpdateAt = -Infinity;\n /** Set by membership/priority changes: the next update ignores both damps. */\n private forceNext = true;\n\n constructor(options: CameraBudgetGovernorOptions = {}) {\n this.budgetGovernor = options.governor ?? new BudgetGovernor(options);\n this.minIntervalMs = nonNegative(options.minIntervalMs ?? 250, 'minIntervalMs');\n this.weightDeadband = nonNegative(options.weightDeadband ?? 0.15, 'weightDeadband');\n this.falloff = positive(options.falloff ?? 1, 'falloff');\n this.offScreenWeight = positive(options.offScreenWeight ?? 0.25, 'offScreenWeight');\n this.minWeight = positive(options.minWeight ?? 0.05, 'minWeight');\n this.maxWeight = positive(options.maxWeight ?? 8, 'maxWeight');\n if (this.maxWeight < this.minWeight) {\n throw new RangeError('CameraBudgetGovernor maxWeight must be >= minWeight.');\n }\n }\n\n /** The governor this drives; use it for fixed-weight members and diagnostics. */\n get governor(): BudgetGovernor {\n return this.budgetGovernor;\n }\n\n /** The shared total being split, from the underlying governor. */\n get totalBudget(): number {\n return this.budgetGovernor.totalBudget;\n }\n\n /** Replaces the shared total (e.g. the presenting budget on `sessionstart`). */\n setTotalBudget(totalBudget: number): void {\n this.budgetGovernor.setTotalBudget(totalBudget);\n }\n\n /** Number of camera-weighted members. */\n get size(): number {\n return this.entries.size;\n }\n\n /**\n * Adds a member, registers it with the underlying governor, and forces a\n * reweight on the next {@link update}.\n *\n * @throws {Error} if the member is already registered here.\n * @throws {RangeError} if `priority` or `fixedWeight` is invalid.\n */\n register(member: CameraBudgetMember, options: CameraBudgetMemberOptions = {}): void {\n if (this.entries.has(member)) {\n throw new Error('CameraBudgetGovernor: member is already registered.');\n }\n const priority = nonNegative(options.priority ?? 1, 'priority');\n const fixedWeight =\n options.fixedWeight === undefined\n ? undefined\n : nonNegative(options.fixedWeight, 'fixedWeight');\n // Register at the weight this member will hold anyway, so the very first\n // allocation is already roughly right rather than an even split that the\n // first update immediately overwrites.\n const initial = fixedWeight ?? priority;\n this.budgetGovernor.register(member, { weight: initial });\n this.entries.set(member, { member, priority, fixedWeight, applied: initial });\n this.forceNext = true;\n }\n\n /**\n * Removes a member from this helper and the underlying governor, restoring\n * the budget it had when it joined. No-op for an unknown member.\n */\n unregister(member: CameraBudgetMember): void {\n if (!this.entries.delete(member)) return;\n this.budgetGovernor.unregister(member);\n this.forceNext = true;\n }\n\n /**\n * Changes a member's priority tier. Takes effect on the next {@link update},\n * which is forced - a deliberate focus change should not wait out the\n * interval or be swallowed by the dead-band.\n *\n * @throws {Error} if the member is not registered here.\n * @throws {RangeError} if `priority` is negative or not finite.\n */\n setPriority(member: CameraBudgetMember, priority: number): void {\n const entry = this.entries.get(member);\n if (entry === undefined) {\n throw new Error('CameraBudgetGovernor: member is not registered.');\n }\n const next = nonNegative(priority, 'priority');\n // No-op on an unchanged tier. Hosts re-assert tiers from sync loops, and an\n // unconditional force would bypass the interval *and* the dead-band on the\n // next update - every reallocation pushes setBudget to every member, and a\n // streamed mesh answers each one with a forced LOD reschedule.\n if (next === entry.priority) return;\n entry.priority = next;\n this.forceNext = true;\n }\n\n /**\n * Recomputes every member's weight from the camera and applies them in one\n * batch. Call once per frame, after the scene graph is up to date.\n *\n * Needs no renderer: weights are a ratio, so viewport size cancels out.\n *\n * Skipped - returning `false` - when called inside `minIntervalMs` of the\n * last reweight, or when no member's weight moved by more than\n * `weightDeadband`. Membership and priority changes force it through both.\n *\n * @param camera - The view detail should follow. In an immersive session pass\n * the head/`ArrayCamera`, not the idle application camera.\n * @param now - Timestamp in ms on the `performance.now` clock; defaults to it.\n * @returns whether weights were reapplied.\n */\n update(camera: THREE.Camera, now: number = performance.now()): boolean {\n if (this.entries.size === 0) return false;\n const forced = this.forceNext;\n if (!forced && now - this.lastUpdateAt < this.minIntervalMs) return false;\n\n camera.updateMatrixWorld();\n camera.getWorldPosition(_cameraPos);\n _projScreen.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse);\n _frustum.setFromProjectionMatrix(_projScreen);\n\n const next: [CameraBudgetMember, number][] = [];\n let significant = false;\n for (const entry of this.entries.values()) {\n const weight = this.weightFor(entry);\n next.push([entry.member, weight]);\n if (!significant && this.isSignificant(entry.applied, weight)) significant = true;\n }\n if (!forced && !significant) return false;\n\n // One batched write: N separate setWeight calls would each reallocate and\n // force an LOD reschedule on every member.\n this.budgetGovernor.setWeights(next);\n for (const [member, weight] of next) {\n const entry = this.entries.get(member);\n if (entry !== undefined) entry.applied = weight;\n }\n this.lastUpdateAt = now;\n this.forceNext = false;\n return true;\n }\n\n /** The weight currently written for a member, if registered here. */\n weightOf(member: CameraBudgetMember): number | undefined {\n return this.entries.get(member)?.applied;\n }\n\n /** The budget the underlying governor last applied to a member. */\n budgetOf(member: CameraBudgetMember): number | undefined {\n return this.budgetGovernor.budgetOf(member);\n }\n\n /**\n * Unregisters every member, restoring the budget each had when it joined.\n * The helper stays usable afterwards. A governor passed in by the host keeps\n * any members the host registered on it directly.\n */\n dispose(): void {\n for (const member of [...this.entries.keys()]) this.budgetGovernor.unregister(member);\n this.entries.clear();\n this.lastUpdateAt = -Infinity;\n this.forceNext = true;\n }\n\n /**\n * A member's weight: `priority × clamp((radius / distance) ^ falloff) ×\n * offScreen`.\n *\n * `radius / distance` is the tangent of the member's half angular size - the\n * same `size / distance` measure Spark's `pixel_scale` traversal ranks nodes\n * by, one level up at whole-mesh granularity. Distance is measured to the\n * bounding sphere's *surface*, so a large mesh is not penalized for having a\n * distant center, and is floored so a camera inside the bounds saturates at\n * `maxWeight` rather than dividing by zero.\n */\n private weightFor(entry: CameraEntry): number {\n if (entry.fixedWeight !== undefined) return entry.fixedWeight;\n const { member, priority } = entry;\n if (priority === 0 || !(member.effectiveVisibility ?? member.visible)) return 0;\n\n member.updateWorldMatrix(true, false);\n _box.copy(member.computeSplatBounds());\n // Nothing measurable yet (a dynamic mesh with no appended ranges): hold the\n // floor rather than reporting an infinite or NaN size.\n if (_box.isEmpty()) return priority * this.minWeight;\n _box.applyMatrix4(member.matrixWorld).getBoundingSphere(_sphere);\n\n const radius = _sphere.radius;\n if (!(radius > 0)) return priority * this.minWeight;\n // Floor the distance relative to the member's own size, so the saturation\n // point scales with the scene instead of being a fixed world distance.\n const surface = _sphere.center.distanceTo(_cameraPos) - radius;\n const distance = Math.max(surface, radius * 1e-3, 1e-6);\n const projected = clamp((radius / distance) ** this.falloff, this.minWeight, this.maxWeight);\n if (!Number.isFinite(projected)) return priority * this.minWeight;\n\n const onScreen = _frustum.intersectsSphere(_sphere) ? 1 : this.offScreenWeight;\n return priority * projected * onScreen;\n }\n\n /** Whether a weight moved enough to be worth a reallocation. */\n private isSignificant(applied: number, next: number): boolean {\n // Crossing into or out of suspension always matters, however small the\n // absolute change: it decides whether the member holds budget at all.\n if (applied === 0 || next === 0) return applied !== next;\n return Math.abs(next - applied) > this.weightDeadband * applied;\n }\n}\n\nfunction clamp(value: number, min: number, max: number): number {\n return Math.min(max, Math.max(min, value));\n}\n\nfunction nonNegative(value: number, name: string): number {\n if (!Number.isFinite(value) || value < 0) {\n throw new RangeError(`CameraBudgetGovernor ${name} must be a non-negative finite number.`);\n }\n return value;\n}\n\nfunction positive(value: number, name: string): number {\n if (!Number.isFinite(value) || value <= 0) {\n throw new RangeError(`CameraBudgetGovernor ${name} must be a positive finite number.`);\n }\n return value;\n}\n","/**\n * Cross-mesh chunk-fetch arbitration.\n *\n * Every {@link StreamedSplatMesh} owns its own `ChunkLoader` worker and issues\n * chunk requests toward its own in-flight cap, so a scene of streamed meshes\n * fetches with no shared ordering: a mesh the camera is pointed at competes\n * for bandwidth and connection slots against a dozen distant ones, each of\n * which is equally entitled to its own cap. Spark does not have this problem\n * structurally - one global traversal orders every fetch want\n * biggest-on-screen-first through one pager, so its network order *is* its\n * visual priority.\n *\n * This is that ordering at whole-mesh granularity: one scheduler shared by\n * every streamed mesh in a scene (the same sharing model as `SplatPool`),\n * handing out a bounded number of fetch slots in proportion to the same\n * camera-projected weight a `CameraBudgetGovernor` already computes for\n * drawing. Within a mesh the existing order still applies - a frontier plan's\n * `touched` list is already sorted biggest-on-screen first - so the two\n * granularities compose.\n *\n * The scheduler brokers **slots**, never fetches: it does not know about\n * `ChunkLoader`, URLs or workers, which is what keeps a future scene-level\n * shared loader an independent change.\n */\n\n/**\n * Why a mesh wants a slot. The kind is the mesh's own statement of intent, and\n * decides what a weightless (hidden, or suspended) mesh may still do:\n *\n * - `priority` - detail the frontier asked for and does not have. The chunks\n * actually on screen.\n * - `base` - the camera-directed coarse base, and the pinned coverage every\n * deferred swap substitutes from. A far mesh must keep trickling these or it\n * has nothing to draw at all.\n * - `sweep` - the background file-order sweep that pulls a whole capture into\n * the worker cache so later camera moves are served from RAM. Pure\n * pre-warming: valuable when a mesh is on screen, and the first thing to give\n * up when it is not.\n */\nexport type ChunkFetchKind = 'priority' | 'base' | 'sweep';\n\n/** A streamed mesh, as the scheduler sees it. */\nexport interface ChunkFetchClient {\n /**\n * This mesh's camera-projected weight - larger means more of the pipe.\n * Zero means hidden or suspended: such a mesh keeps its floor for `priority`\n * and `base` work but is denied `sweep` entirely.\n *\n * Read on demand rather than pushed, so the scheduler never holds a stale\n * weight; a `CameraBudgetGovernor.weightOf` call is the intended source.\n */\n weight(): number;\n /**\n * A slot may now be free. The mesh should re-run its reschedule, which\n * re-issues whatever it still wants. The scheduler deliberately keeps no\n * queue of pending requests: the reschedule paths are already idempotent\n * re-issuers, so a poke carries strictly more current information than a\n * request recorded when the camera was somewhere else.\n */\n onSlotAvailable(): void;\n /**\n * Abort in-flight fetches of this kind - the mesh's weight dropped to zero\n * while it held slots. Aborted fetches release their slots through the normal\n * path, so this is how a focus change hands the pipe over immediately rather\n * than after the far meshes' current requests drain.\n */\n shedFetches(kind: ChunkFetchKind): void;\n}\n\n/** Options for {@link ChunkFetchScheduler}. */\nexport interface ChunkFetchSchedulerOptions {\n /**\n * Total chunk fetches in flight across every registered mesh. Default 16.\n *\n * This is the number that decides whether the scheduler helps at all. Too low\n * and a single mesh scene streams slower than it does today; too high and\n * there is nothing to arbitrate, because the browser's own queueing (six per\n * origin on HTTP/1.1; a much larger multiplexed window on HTTP/2) becomes the\n * real scheduler again - and it orders by request time, not by what is on\n * screen. Tune it from a measured concurrency high-water mark, not by feel.\n */\n maxGlobalInflight?: number;\n /**\n * Slots every registered mesh may hold regardless of weight. Default 1.\n *\n * Without a floor a scene's far meshes stop fetching entirely while the\n * focused one streams, and a mesh with no coarse coverage draws nothing -\n * so the floor is what turns \"far meshes wait\" into \"far meshes trickle\".\n *\n * One slot is always granted regardless of this setting: a mesh entitled to\n * zero would never fetch and never settle. Set this above 1 to widen every\n * mesh's guaranteed trickle.\n */\n perMeshFloor?: number;\n}\n\n/**\n * A mesh's registration. Opaque to callers: hold it from {@link\n * ChunkFetchScheduler.register} and pass it back to acquire, release and\n * unregister.\n */\nexport interface ChunkFetchHandle {\n readonly client: ChunkFetchClient;\n}\n\ninterface Entry extends ChunkFetchHandle {\n /** Slots currently held (granted and not yet released). */\n held: number;\n /** Denied at least once since the last grant - wake this mesh on a release. */\n demanding: boolean;\n /** Registered? Cleared by `unregister` so late releases stay accountable. */\n live: boolean;\n}\n\nconst DEFAULT_MAX_GLOBAL_INFLIGHT = 16;\nconst DEFAULT_PER_MESH_FLOOR = 1;\n\n/** See the module comment. Construct one per scene and pass it to every mesh. */\nexport class ChunkFetchScheduler {\n private readonly entries = new Set<Entry>();\n private readonly maxGlobalInflight: number;\n private readonly perMeshFloor: number;\n private inflightCount = 0;\n private disposed = false;\n\n constructor(options: ChunkFetchSchedulerOptions = {}) {\n this.maxGlobalInflight = Math.max(\n 1,\n Math.floor(options.maxGlobalInflight ?? DEFAULT_MAX_GLOBAL_INFLIGHT),\n );\n this.perMeshFloor = Math.max(0, Math.floor(options.perMeshFloor ?? DEFAULT_PER_MESH_FLOOR));\n }\n\n /** Total slots granted and not yet released. */\n get inflight(): number {\n return this.inflightCount;\n }\n\n /** Meshes currently registered. */\n get clientCount(): number {\n return this.entries.size;\n }\n\n register(client: ChunkFetchClient): ChunkFetchHandle {\n const entry: Entry = { client, held: 0, demanding: false, live: true };\n this.entries.add(entry);\n return entry;\n }\n\n /**\n * Drops a mesh. Slots it still holds are released here: a disposing mesh\n * aborts its fetches, and those aborts land after the handle is gone.\n */\n unregister(handle: ChunkFetchHandle): void {\n const entry = handle as Entry;\n if (!this.entries.delete(entry)) return;\n entry.live = false;\n if (entry.held > 0) {\n this.inflightCount -= entry.held;\n entry.held = 0;\n this.wake();\n }\n }\n\n /**\n * Grants a fetch slot, or denies and remembers the demand.\n *\n * A denial is not a failure and must not feed a mesh's retry backoff - the\n * mesh simply did not fetch this tick, and will be woken (or reschedule on\n * its own within the idle interval) to ask again.\n */\n tryAcquire(handle: ChunkFetchHandle, kind: ChunkFetchKind): boolean {\n const entry = handle as Entry;\n if (this.disposed || !entry.live) return false;\n\n const weight = normalizeWeight(entry.client.weight());\n // A weightless mesh pre-warming its cache is the exact traffic that starves\n // the focused mesh, and it buys nothing while nobody is looking at it.\n if (kind === 'sweep' && weight <= 0) {\n entry.demanding = false;\n return false;\n }\n if (\n this.inflightCount >= this.maxGlobalInflight ||\n entry.held >= this.shareFor(entry, weight)\n ) {\n entry.demanding = true;\n return false;\n }\n\n entry.held++;\n entry.demanding = false;\n this.inflightCount++;\n return true;\n }\n\n /**\n * Returns a slot. Must be called exactly once for every granted acquire -\n * on success, on failure **and** on abort - or the pipe leaks capacity until\n * the scene is torn down.\n */\n release(handle: ChunkFetchHandle): void {\n const entry = handle as Entry;\n if (entry.held <= 0) return;\n entry.held--;\n this.inflightCount--;\n this.wake();\n }\n\n /**\n * Re-examines weights after the camera moved. Call it once per frame, right\n * after the budget governor's own update.\n *\n * Meshes that just lost all weight shed their pre-warming sweeps, so a focus\n * change frees the pipe now rather than when a dozen far requests happen to\n * finish.\n */\n weightsChanged(): void {\n if (this.disposed) return;\n for (const entry of this.entries) {\n if (entry.held > 0 && normalizeWeight(entry.client.weight()) <= 0) {\n entry.client.shedFetches('sweep');\n }\n }\n this.wake();\n }\n\n /**\n * Releases every registration. Meshes are not disposed - the scheduler is\n * shared and does not own them, exactly as a shared `SplatPool` does not own\n * its meshes.\n */\n dispose(): void {\n this.disposed = true;\n for (const entry of this.entries) {\n entry.live = false;\n entry.held = 0;\n entry.demanding = false;\n }\n this.entries.clear();\n this.inflightCount = 0;\n }\n\n /**\n * The most slots this mesh may hold: its weight-proportional share of the\n * *whole* pipe, floored so it can always trickle and capped so its siblings\n * can always reach their own floors.\n *\n * This is a ceiling, not a reservation. The global cap does the real\n * limiting, so a heavy mesh can use capacity its idle siblings are leaving on\n * the table, while a far mesh's ceiling stays at its floor however early it\n * asks - which is what keeps the first mesh to reschedule from taking the\n * pipe and holding it. Dividing the *remainder* after every participant's\n * floor instead would invert the whole point on a large scene: thirteen\n * additional meshes and a main at floor 1 consume a 16-slot pipe entirely, leaving the\n * focused mesh a smaller share than the far ones it is competing with.\n *\n * The denominator counts every mesh that plausibly wants the pipe - all\n * registered meshes except those both weightless and idle, plus the caller.\n */\n private shareFor(entry: Entry, weight: number): number {\n let participants = 0;\n let totalWeight = 0;\n for (const other of this.entries) {\n const otherWeight = other === entry ? weight : normalizeWeight(other.client.weight());\n // A hidden mesh with nothing in flight is not competing for anything.\n if (other !== entry && otherWeight <= 0 && other.held === 0 && !other.demanding) continue;\n participants++;\n totalWeight += otherWeight;\n }\n if (participants <= 1) return this.maxGlobalInflight;\n\n // All-zero weights (every mesh hidden) share evenly rather than dividing by\n // zero.\n const share =\n totalWeight > 0\n ? (this.maxGlobalInflight * weight) / totalWeight\n : this.maxGlobalInflight / participants;\n // At least one slot, whatever the arithmetic and whatever the configured\n // floor: a mesh wedged at zero entitlement while the pipe has room would\n // never fetch and never settle, and `isStreaming` would stay true forever.\n const floored = Math.max(1, this.perMeshFloor, Math.round(share));\n // Never so much that another participant could not reach its own floor.\n const reservedForOthers = Math.max(1, this.perMeshFloor) * (participants - 1);\n return Math.max(1, Math.min(floored, this.maxGlobalInflight - reservedForOthers));\n }\n\n /** Pokes the heaviest mesh that was denied since its last grant. */\n private wake(): void {\n if (this.disposed || this.inflightCount >= this.maxGlobalInflight) return;\n let best: Entry | null = null;\n let bestWeight = -Infinity;\n for (const entry of this.entries) {\n if (!entry.demanding) continue;\n const weight = normalizeWeight(entry.client.weight());\n if (weight > bestWeight) {\n best = entry;\n bestWeight = weight;\n }\n }\n if (!best) return;\n // Cleared before the callback: the poke re-runs a reschedule that will\n // acquire (clearing it anyway) or be denied again (setting it again), and\n // leaving it set would make this mesh the perpetual answer to every wake.\n best.demanding = false;\n best.client.onSlotAvailable();\n }\n}\n\n/** Negative, NaN and infinite weights are all \"no claim on the pipe\". */\nfunction normalizeWeight(weight: number): number {\n return Number.isFinite(weight) && weight > 0 ? weight : 0;\n}\n","/**\n * Cross-mesh decoded-chunk cache arbitration.\n *\n * Every {@link StreamedSplatMesh} caps its own decoded-chunk cache, and in\n * `foveationMode: 'page-table'` that cap is `min(2 GiB, this capture's decoded\n * size)` - a number sized for *one* streamed scene. A scene of streamed additional meshes\n * therefore has no ceiling at all: thirteen extras plus a main are thirteen\n * plus one independent caps, and because each is sized to its own capture, a\n * desktop that fits them never evicts. The background sweep then runs to\n * completion against every one of them, pulling every capture in the scene into\n * RAM and keeping it there.\n *\n * This is the missing ceiling, at whole-mesh granularity: one budget shared by\n * every streamed mesh in a scene (the same sharing model as `SplatPool` and\n * {@link ChunkFetchScheduler}), splitting a scene total by the same\n * camera-projected weight a `CameraBudgetGovernor` already computes for drawing.\n *\n * It bounds **retention**, not prefetching. The sweep still runs and still warms\n * the cache; it simply stops at an allowance the whole scene agreed on rather\n * than at the size of each capture. A mesh the camera approaches gets a larger\n * allowance and resumes sweeping; one it leaves gives bytes back.\n *\n * The budget brokers **bytes**, never chunks: it does not know about\n * `ChunkLoader`, chunk ids or workers, which is what keeps the eviction policy\n * where it belongs - inside the frontier worker, which is the only place that\n * knows what the current cut still needs.\n */\n\n/** A streamed mesh, as the cache budget sees it. */\nexport interface ChunkCacheClient {\n /**\n * This mesh's camera-projected weight - larger means more of the cache.\n *\n * Read on demand rather than pushed, so the budget never holds a stale\n * weight; a `CameraBudgetGovernor.weightOf` call is the intended source, and\n * is the same one {@link ChunkFetchScheduler} reads. Zero (hidden or\n * suspended) still keeps `perMeshFloorBytes`: a mesh whose coarse base has\n * been evicted draws nothing at all when the camera comes back to it.\n */\n weight(): number;\n /**\n * The most this mesh could ever put to use - for a page-table mesh,\n * `min(PAGETABLE_CACHE_FLOOR_BYTES, estimateSceneDecodedBytes(scene))`.\n *\n * Bytes above it are handed to siblings that can use them, the way\n * `BudgetGovernor` waterfills past a member's `maxBudget`. Without it a scene\n * of one large capture and a dozen small additional meshes would reserve most of the\n * envelope for extras that cannot fill it.\n */\n readonly ceilingBytes: number;\n /**\n * This mesh's allowance moved. The mesh forwards it to its frontier worker;\n * nothing is dropped synchronously, because only the worker's own eviction\n * pass knows which chunks the current cut still needs.\n */\n onAllowanceChanged(bytes: number): void;\n}\n\n/** Options for {@link ChunkCacheBudget}. */\nexport interface ChunkCacheBudgetOptions {\n /**\n * Decoded-chunk bytes every registered mesh may hold **in total**.\n *\n * This is the number that decides whether the budget helps at all. Too low\n * and the focused mesh re-fetches chunks it just evicted; too high and it is\n * inert, because each mesh's own `ceilingBytes` becomes binding again. Size\n * it against the tab's heap, not against any one capture.\n */\n totalBytes: number;\n /**\n * Bytes each mesh keeps regardless of weight. Default 32 MiB, matching\n * `resolveCpuCacheBytes`'s own minimum.\n *\n * Without a floor a far mesh evicts its own coarse base and re-fetches it on\n * the next reschedule, forever - the same thrash {@link\n * ChunkFetchScheduler.perMeshFloor} exists to prevent on the network side.\n */\n perMeshFloorBytes?: number;\n /**\n * Minimum ms between weight-driven reallocations. Default 250, matching the\n * idle reschedule interval - there is no point re-splitting faster than the\n * meshes can act on it. Membership changes bypass it.\n */\n minIntervalMs?: number;\n /**\n * Relative allowance change below which a mesh is not notified. Default 0.15.\n *\n * Every notification is a worker post and, indirectly, an eviction pass. A\n * camera drifting slowly would otherwise repost a 1% different number every\n * quarter second for no change in behaviour.\n */\n deadband?: number;\n}\n\n/**\n * A mesh's registration. Opaque to callers: hold it from {@link\n * ChunkCacheBudget.register} and pass it back to read the allowance or\n * unregister.\n */\nexport interface ChunkCacheHandle {\n readonly client: ChunkCacheClient;\n}\n\ninterface Entry extends ChunkCacheHandle {\n /** Bytes currently allocated to this mesh. */\n allowance: number;\n /** Registered? Cleared by `unregister` so a late callback stays inert. */\n live: boolean;\n}\n\nconst DEFAULT_PER_MESH_FLOOR_BYTES = 32 * 1024 * 1024;\nconst DEFAULT_MIN_INTERVAL_MS = 250;\nconst DEFAULT_DEADBAND = 0.15;\n\n/** See the module comment. Construct one per scene and pass it to every mesh. */\nexport class ChunkCacheBudget {\n private readonly entries = new Set<Entry>();\n private readonly perMeshFloorBytes: number;\n private readonly minIntervalMs: number;\n private readonly deadband: number;\n private totalBytesValue: number;\n private lastAllocationMs = Number.NEGATIVE_INFINITY;\n private disposed = false;\n\n constructor(options: ChunkCacheBudgetOptions) {\n if (!Number.isFinite(options.totalBytes) || options.totalBytes <= 0) {\n throw new RangeError('Chunk cache totalBytes must be a positive finite number.');\n }\n this.totalBytesValue = Math.floor(options.totalBytes);\n this.perMeshFloorBytes = Math.max(\n 0,\n Math.floor(options.perMeshFloorBytes ?? DEFAULT_PER_MESH_FLOOR_BYTES),\n );\n this.minIntervalMs = Math.max(0, options.minIntervalMs ?? DEFAULT_MIN_INTERVAL_MS);\n this.deadband = Math.max(0, options.deadband ?? DEFAULT_DEADBAND);\n }\n\n /** Bytes shared across every registered mesh. */\n get totalBytes(): number {\n return this.totalBytesValue;\n }\n\n /**\n * Resizes the scene envelope and re-splits immediately. For a host reacting\n * to a quality change or a device-memory signal.\n */\n setTotalBytes(bytes: number): void {\n if (!Number.isFinite(bytes) || bytes <= 0) {\n throw new RangeError('Chunk cache totalBytes must be a positive finite number.');\n }\n const next = Math.floor(bytes);\n if (next === this.totalBytesValue) return;\n this.totalBytesValue = next;\n this.allocate();\n }\n\n /** Meshes currently registered. */\n get clientCount(): number {\n return this.entries.size;\n }\n\n /**\n * Adds a mesh and re-splits at once, so the caller can read its opening\n * allowance out of {@link allowanceFor} before posting worker init.\n */\n register(client: ChunkCacheClient): ChunkCacheHandle {\n const entry: Entry = { client, allowance: 0, live: true };\n this.entries.add(entry);\n this.allocate({ force: true, silentFor: entry });\n return entry;\n }\n\n /** Drops a mesh and hands its bytes back to the siblings. */\n unregister(handle: ChunkCacheHandle): void {\n const entry = handle as Entry;\n if (!this.entries.delete(entry)) return;\n entry.live = false;\n entry.allowance = 0;\n this.allocate({ force: true });\n }\n\n /** This mesh's current allowance in bytes, or 0 once unregistered. */\n allowanceFor(handle: ChunkCacheHandle): number {\n const entry = handle as Entry;\n return entry.live ? entry.allowance : 0;\n }\n\n /**\n * Re-splits after the camera moved. Call it once per frame, right beside\n * {@link ChunkFetchScheduler.weightsChanged} - the two read the same weights,\n * so cache and network follow the same measure.\n *\n * Rate-limited and dead-banded; calling it every frame is the intended use.\n */\n weightsChanged(now: number = Date.now()): void {\n if (this.disposed) return;\n if (now - this.lastAllocationMs < this.minIntervalMs) return;\n // Only this path stamps the limiter, and only with the caller's own clock.\n // Structural allocations (register/unregister/resize) must not stamp it:\n // they would otherwise mix `Date.now()` into a host that drives this from a\n // frame clock, and throttle every later call against a timestamp from a\n // different epoch.\n this.lastAllocationMs = now;\n this.allocate();\n }\n\n /**\n * Releases every registration. Meshes are not disposed - the budget is shared\n * and does not own them, exactly as a shared `SplatPool` does not own its\n * meshes.\n */\n dispose(): void {\n this.disposed = true;\n for (const entry of this.entries) {\n entry.live = false;\n entry.allowance = 0;\n }\n this.entries.clear();\n }\n\n /**\n * Cap-aware waterfill: weight-proportional shares, floored so every mesh can\n * hold its coarse base, clamped at each mesh's `ceilingBytes`, and the surplus\n * from clamped meshes redistributed over the rest until it settles.\n *\n * The invariant callers depend on is `Σ allowance <= totalBytes`. It holds in\n * every branch, including the degenerate one where the floors alone exceed\n * the envelope - there the floors are scaled down proportionally rather than\n * silently overcommitting, because a budget that can be exceeded by\n * registering more meshes is the bug this class exists to fix.\n */\n private allocate(options: { force?: boolean; silentFor?: Entry } = {}): void {\n if (this.disposed) return;\n const entries = [...this.entries];\n if (entries.length === 0) return;\n\n const total = this.totalBytesValue;\n // A floor is a claim *inside* the envelope, not a reservation on top of it:\n // cap it at an even split so N floors can never sum past the total, then\n // hand out only what is left over by weight. Adding a weighted share to an\n // unreserved floor would overcommit whenever any mesh sat below its share.\n const floor = Math.min(this.perMeshFloorBytes, Math.floor(total / entries.length));\n const next = new Map<Entry, number>();\n const unclamped = new Set<Entry>(entries);\n let pool = total;\n\n // Waterfill. Each pass either clamps at least one mesh at its ceiling and\n // returns what it could not use to the pool, or reaches the fixpoint and\n // stops - so it terminates in at most one pass per mesh.\n for (let pass = 0; pass <= entries.length && unclamped.size > 0; pass++) {\n const distributable = Math.max(0, pool - floor * unclamped.size);\n let totalWeight = 0;\n for (const entry of unclamped) totalWeight += normalizeWeight(entry.client.weight());\n const clamped: Entry[] = [];\n for (const entry of unclamped) {\n const weight = normalizeWeight(entry.client.weight());\n // All-zero weights (every mesh hidden) share the remainder evenly\n // rather than dividing by zero.\n const share =\n totalWeight > 0 ? (distributable * weight) / totalWeight : distributable / unclamped.size;\n const ceiling = normalizeCeiling(entry.client.ceilingBytes);\n const wanted = Math.floor(floor + share);\n // Never above what this mesh could actually put to use: holding a 4 MB\n // capture at a 32 MB floor would strand 28 MB its siblings can use.\n if (wanted >= ceiling) {\n next.set(entry, ceiling);\n clamped.push(entry);\n } else {\n next.set(entry, wanted);\n }\n }\n if (clamped.length === 0) break;\n for (const entry of clamped) {\n unclamped.delete(entry);\n pool -= next.get(entry) ?? 0;\n }\n pool = Math.max(0, pool);\n }\n\n for (const entry of entries) {\n const value = next.get(entry) ?? 0;\n const previous = entry.allowance;\n entry.allowance = value;\n if (entry === options.silentFor) continue;\n if (!options.force && !this.movedEnough(previous, value)) continue;\n if (previous === value) continue;\n entry.client.onAllowanceChanged(value);\n }\n }\n\n /** Suppresses reposts for changes too small to alter any mesh's behaviour. */\n private movedEnough(previous: number, next: number): boolean {\n if (previous === next) return false;\n if (previous === 0 || next === 0) return true;\n return Math.abs(next - previous) / previous >= this.deadband;\n }\n}\n\n/** Negative, NaN and infinite weights are all \"no claim on the cache\". */\nfunction normalizeWeight(weight: number): number {\n return Number.isFinite(weight) && weight > 0 ? weight : 0;\n}\n\n/** A missing or nonsensical ceiling means \"this mesh can use whatever it is given\". */\nfunction normalizeCeiling(ceiling: number): number {\n return Number.isFinite(ceiling) && ceiling > 0 ? Math.floor(ceiling) : Number.MAX_SAFE_INTEGER;\n}\n"],"names":["APPEND_CAP","buildHoldSwapGroups","toAdd","run","a","b","buildSwapGroups","toRemove","coverageRuns","entry","buckets","touch","coverageGroup","bucket","groups","byLeaf","leafKey","start","end","ensure","key","group","items","item","last","groupPriority","finest","isClassicLccSwapSet","compareClassicSwapGroups","describe","runs","aa","bb","classicFetchPhaseRank","phase","kindForClassicFetchPhase","classicFetchGroupKey","want","file","stampClassicFetchGroups","pending","lodBaseDistance","forceNearPriority","near","nearDisplayDistance","aggregates","prev","groupId","agg","groupClass","kind","_lodMultiplier","isWaitingOnFinest","classicFetchPhaseForDesired","lodMultiplier","distance","classicFetchPhaseForCoverage","enqueueClassicFetch","level","inView","screenImportance","betterPhase","samePhaseNearer","samePhaseBetterView","compareClassicFetches","fileA","fileB","groupDistA","groupDistB","pendingA","pendingB","groupA","groupB","classA","classB","screenA","screenB","finestA","finestB","rankInGroup","envA","envB","sliceSplatData","chunk","offset","count","sh","shWordsPerSplat","bands","chunkBytes","data","_a","abortReason","signal","reason","validateAppendCap","value","cap","validateLodScale","scale","defaultCpuCacheBytes","resolveCpuCacheBytes","parseLodManifest","json","source","raw","name","directories","chunkUrls","directory","leaves","lodLevels","stack","node","lods","levelKey","range","assertSaneRange","boxFromBound","fileCount","bound","THREE","buildSogScene","options","shBands","manifest","packShBands","chunkOptions","_","index","files","LodScheduler","computeSogPinnedFiles","computeSogMinimumCoverage","total","leaf","pinned","httpDatasetSource","manifestUrl","request","path","probeSize","url","response","toRequestInit","cancelBody","length","saneSize","size","MANIFESTS","n","createLocalDataset","found","basename","match","candidate","depth","minDepth","SplatLoadError","names","root","urls","urlFor","existing","normalize","prefix","entries","shallowest","FRONTIER_FOVEATION_DEFAULTS","DATA_TEXTURE_WIDTH","INITIAL_REVEAL_TIMEOUT_MS","CONTENT_FORCE_FRACTION","MAX_INFLIGHT","MAX_RETIRE_HELD_TICKS","IDLE_RESCHEDULE_MS","MAX_CHUNK_ATTEMPTS","RETRY_BASE_MS","PAGETABLE_PRIORITY_SLOTS","PAGETABLE_DRAW_BUDGET","SLAB_PAGE_SPLATS","PAGETABLE_CACHE_FLOOR_BYTES","estimateSceneDecodedBytes","scene","perSplat","shCoefficientCount","splats","StreamedSplatMesh","SplatMesh","budget","capacity","FrontierWorkerCtor","neverRetireCoverageEarly","__publicField","ChunkLoader","DEFAULT_FOVEATION_TARGET_PX","resolveSplatBudget","holdCoverage","holdNearL0","_b","isPageTable","isPageTableFoveation","cacheCeilingBytes","_c","bytes","e","bootstrap","absoluteUrl","resolveSplatUrl","lower","format","dataset","error","mesh","deviceProfile","deviceBudget","ceilingBudget","sourceOptions","resolveSplatPerformanceProfile","MAX_SH_BANDS","radMaxStdDev","recommendedRadMaxStdDev","budgetLifts","buildRadScene","isAbortError","toSplatLoadError","buildLcc2Scene","buildLccScene","ceiling","liftBudgetToFinestLevel","residentCeiling","capacityFactor","capacityRows","resolvedFoveationMode","resolveSplatFoveationMode","correction","yUpTransformForFormat","slots","page","wanted","target","slot","written","at","done","resident","msg","transfer","collision","controller","loadCollisionMeshTiles","abortListener","_resolve","reject","enabled","next","warn","camera","renderer","now","xrView","resolveXrView","_drawSize","lodCamera","performanceEvent","timings","handle","uploadedCount","worldPoint","radius","channel","_paintLocal","r2","edited","touchedFiles","positions","fileEdits","touched","k","li","px","py","pz","_d","_e","_cameraWorldPos","_sphere","_cameraWorldQuat","startedAt","before","compactionCountBefore","_cameraLocal","_projScreen","_frustum","_cameraForward","focalY","pageTableTimestamp","scheduledRuns","holdingRuns","holding","desiredRuns","desired","desiredFiles","runKey","staged","classicLccGroups","pendingFetches","appended","addsPending","poolPressure","held","missing","holdForTarget","recoverable","stagedNow","base","nearestCandidates","homeGroup","seeds","nearLevel","out","seed","alt","neededRows","g","list","stagedSplats","totalSplats","readyGroups","groupRuns","groupReady","status","progress","totalGroups","cameraLocal","frustum","coverage","critical","horizon","fallback","nearest","home","residentBefore","stagedBefore","appendedCount","activeCount","removedCount","stagedCount","compacted","timestamp","sum","allowance","rowSplats","needed","freed","slice","any","holdForFinest","span","covered","from","to","view","gapStart","nextCovered","gapEnd","cursor","holdingNearL0","ordered","worstFile","worstWant","activeFile","active","rowAligned","forwardLocal","sweepCap","f","plan","limit","applyStartedAt","i","clamped","slicePlanRun","writeFinishedAt","degenerateStart","degenerateCount","residentFinishedAt","planTimings","ratio","tree","weight","classicWant","backoff","attempts","delay","previous","candidates","j","SUSPENDED_BUDGET","BudgetGovernor","hysteresis","totalBudget","member","validateWeight","weights","changed","pool","weightSum","capped","cappedSpend","_cameraPos","_box","CameraBudgetGovernor","nonNegative","positive","priority","fixedWeight","initial","forced","significant","surface","projected","clamp","onScreen","applied","min","max","DEFAULT_MAX_GLOBAL_INFLIGHT","DEFAULT_PER_MESH_FLOOR","ChunkFetchScheduler","client","normalizeWeight","participants","totalWeight","other","otherWeight","share","floored","reservedForOthers","best","bestWeight","DEFAULT_PER_MESH_FLOOR_BYTES","DEFAULT_MIN_INTERVAL_MS","DEFAULT_DEADBAND","ChunkCacheBudget","floor","unclamped","pass","distributable","normalizeCeiling"],"mappings":"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"}
1
+ {"version":3,"file":"streaming.js","sources":["../src/lib/streaming/streamed-splat-mesh-utils.ts","../src/lib/streaming/lod-manifest.ts","../src/lib/streaming/lod-source.ts","../src/lib/streaming/dataset-source.ts","../src/lib/formats/rad/frontier-worker-protocol.ts","../src/lib/streaming/streamed-splat-mesh.ts","../src/lib/streaming/budget-governor.ts","../src/lib/streaming/camera-budget-governor.ts","../src/lib/streaming/chunk-fetch-scheduler.ts","../src/lib/streaming/chunk-cache-budget.ts"],"sourcesContent":["/** Scheduling and data helpers shared by the streamed mesh implementation. */\nimport type { SplatRange } from '../core/splat-mesh';\nimport type { SplatData } from '../core/splat-data';\nimport type { LodRun } from './lod-scheduler';\nimport { resolveCpuCacheBytes } from '../core/splat-budget';\n\nconst APPEND_CAP = 32_000;\n\n/** One resident entry: the run description plus its pool handle. */\ntype ResidentEntry = [string, { run: LodRun; handle: SplatRange }];\n\n/** A set of adds and removals covering one contiguous leaf region. */\nexport interface SwapGroup {\n adds: LodRun[];\n removes: ResidentEntry[];\n leafStart: number;\n leafEnd: number;\n addCount: number;\n}\n\n/**\n * One swap group per run for the startup hold. The mesh is invisible, so L0\n * cell-atomicity is unnecessary; committing slice-by-slice lets the hold finish\n * as soon as the capped set is resident instead of waiting on sibling subchunks.\n */\nexport function buildHoldSwapGroups(toAdd: readonly LodRun[]): SwapGroup[] {\n return toAdd\n .map((run) => ({\n adds: [run],\n removes: [] as ResidentEntry[],\n leafStart: run.leafStart,\n leafEnd: run.leafEnd,\n addCount: run.count,\n }))\n .sort((a, b) => a.leafStart - b.leafStart);\n}\n\n/**\n * Groups adds and removals into visible-cell transactions.\n *\n * Classic LCC (`coverageGroup` set on every run): one transaction per sub-leaf\n * interval at every resolved level (including L0), so a cached slice can\n * replace its own prior coverage while siblings still fetch. Hierarchical\n * sources without coverage groups keep interval-overlap grouping.\n */\nexport function buildSwapGroups(toAdd: LodRun[], toRemove: ResidentEntry[]): SwapGroup[] {\n const coverageRuns = [...toAdd, ...toRemove.map(([, entry]) => entry.run)];\n if (coverageRuns.length > 0 && coverageRuns.every((run) => run.coverageGroup !== undefined)) {\n type Bucket = { adds: LodRun[]; removes: ResidentEntry[] };\n const buckets = new Map<number, Bucket>();\n const touch = (coverageGroup: number): Bucket => {\n let bucket = buckets.get(coverageGroup);\n if (!bucket) {\n bucket = { adds: [], removes: [] };\n buckets.set(coverageGroup, bucket);\n }\n return bucket;\n };\n for (const run of toAdd) {\n touch(run.coverageGroup as number).adds.push(run);\n }\n for (const entry of toRemove) {\n touch(entry[1].run.coverageGroup as number).removes.push(entry);\n }\n\n const groups: SwapGroup[] = [];\n for (const bucket of buckets.values()) {\n // Every classic-LCC cut is one transaction per sub-leaf - including\n // resolved L0. Quality cells split into dozens of finest slices; waiting\n // for the whole cell before any swap left near detail stuck on coarse\n // (green) while a few in-flight fetches churned across siblings forever.\n // Per-slice: a ready L0 patch replaces its own prior coverage immediately;\n // siblings keep theirs until their chunks land.\n const byLeaf = new Map<string, SwapGroup>();\n const leafKey = (start: number, end: number): string => `${start}:${end}`;\n const ensure = (start: number, end: number): SwapGroup => {\n const key = leafKey(start, end);\n let group = byLeaf.get(key);\n if (!group) {\n group = { adds: [], removes: [], leafStart: start, leafEnd: end, addCount: 0 };\n byLeaf.set(key, group);\n }\n return group;\n };\n for (const run of bucket.adds) {\n const group = ensure(run.leafStart, run.leafEnd);\n group.adds.push(run);\n group.addCount += run.count;\n }\n for (const entry of bucket.removes) {\n const run = entry[1].run;\n const group = ensure(run.leafStart, run.leafEnd);\n group.removes.push(entry);\n group.leafStart = Math.min(group.leafStart, run.leafStart);\n group.leafEnd = Math.max(group.leafEnd, run.leafEnd);\n }\n groups.push(...byLeaf.values());\n }\n return groups.sort((a, b) => a.leafStart - b.leafStart);\n }\n\n const items = [\n ...toAdd.map((run) => ({\n start: run.leafStart,\n end: run.leafEnd,\n add: run,\n remove: undefined as ResidentEntry | undefined,\n })),\n ...toRemove.map((entry) => ({\n start: entry[1].run.leafStart,\n end: entry[1].run.leafEnd,\n add: undefined as LodRun | undefined,\n remove: entry,\n })),\n ].sort((a, b) => a.start - b.start || b.end - a.end);\n\n // Sorted interval sweep finds the same overlap-connected components in\n // O(n log n). Longer equal-start intervals come first so an octree parent\n // opens the full component before its adjacent children are visited.\n const groups: SwapGroup[] = [];\n for (const item of items) {\n const last = groups[groups.length - 1];\n if (!last || item.start >= last.leafEnd) {\n groups.push({\n adds: [],\n removes: [],\n leafStart: item.start,\n leafEnd: item.end,\n addCount: 0,\n });\n }\n const group = groups[groups.length - 1] as SwapGroup;\n group.leafEnd = Math.max(group.leafEnd, item.end);\n if (item.add) {\n group.adds.push(item.add);\n group.addCount += item.add.count;\n }\n if (item.remove) group.removes.push(item.remove);\n }\n return groups;\n}\n\n/**\n * Groups that only add coverage first (coarse before fine), then the ones that\n * retire it - pure removals last of all.\n *\n * The order is what makes the wave gate in `reschedule` decidable: by the time a\n * retiring group is reached, every purely additive group has already had its\n * turn, so \"have the replacements landed?\" is answered rather than guessed.\n * Pure removals free pool rows, so they go last, where they relieve the\n * over-draw the gate deliberately trades for.\n *\n * Exported for tests only - not part of the public API surface.\n */\nexport function groupPriority(group: SwapGroup): number {\n if (group.adds.length === 0) return 1000;\n const finest = -Math.max(...group.adds.map((run) => run.level));\n return group.removes.length === 0 ? -1000 + finest : finest;\n}\n\n/** True when every transaction belongs to classic LCC physical coverage. */\nexport function isClassicLccSwapSet(groups: readonly SwapGroup[]): boolean {\n return (\n groups.length > 0 &&\n groups.every((group) =>\n [...group.adds, ...group.removes.map(([, entry]) => entry.run)].every(\n (run) => run.coverageGroup !== undefined,\n ),\n )\n );\n}\n\n/**\n * Orders classic display transactions by what the camera can see. This is\n * separate from {@link groupPriority}: its global coarse-before-retirement\n * wave preserves hierarchical RAD coverage, whereas LCC has an independent\n * coarse shell for every L1+ slice and can safely commit a ready centre slice.\n * Exported for tests only - not part of the public API surface.\n */\nexport function compareClassicSwapGroups(a: SwapGroup, b: SwapGroup): number {\n const describe = (\n group: SwapGroup,\n ): {\n removeOnly: number;\n view: number;\n finest: number;\n screen: number;\n distance: number;\n } => {\n const runs = [...group.adds, ...group.removes.map(([, entry]) => entry.run)];\n return {\n removeOnly: group.adds.length === 0 ? 1 : 0,\n view: runs.some((run) => run.inView !== false) ? 0 : 1,\n finest: group.adds.some((run) => run.level === 0) ? 0 : 1,\n screen: Math.min(...runs.map((run) => run.screenImportance ?? Number.POSITIVE_INFINITY)),\n distance: Math.min(...runs.map((run) => run.distance ?? Number.POSITIVE_INFINITY)),\n };\n };\n const aa = describe(a);\n const bb = describe(b);\n return (\n aa.removeOnly - bb.removeOnly ||\n aa.view - bb.view ||\n aa.finest - bb.finest ||\n aa.screen - bb.screen ||\n aa.distance - bb.distance ||\n a.leafStart - b.leafStart\n );\n}\n\n/** One classic-path chunk want, ranked before {@link StreamedSplatMesh} issues it. */\nexport type ClassicFetchPhase =\n 'environment' | 'finest-target' | 'coverage' | 'target' | 'background';\n\nexport interface ClassicFetchWant {\n /** Cross-mesh scheduler kind derived from {@link phase}. */\n kind: 'priority' | 'base';\n /** Internal classic ranking tier (not exported on ChunkFetchKind). */\n phase: ClassicFetchPhase;\n distance: number;\n level: number;\n /** Prefer in-frustum wants; false means behind-camera / out of view. */\n inView: boolean;\n /** Classic LCC physical cell; `-1` when the source has no coverage groups. */\n coverageGroup: number;\n /** Source interval: L1+ uses this as its progressive display transaction. */\n leafStart: number;\n /** One past {@link leafStart}. */\n leafEnd: number;\n /** Fetch-only angular distance from the screen centre; smaller wins. */\n screenImportance: number;\n /**\n * Min distance among pending wants in this coverage group. Stamped in\n * {@link stampClassicFetchGroups} so a split cell's slices stay together.\n */\n groupDistance: number;\n /** Pending file count in this coverage group; denser near cells win ties. */\n groupPending: number;\n /** True when any pending run in this group is in-frustum. */\n groupInView: boolean;\n /** Best screen-centre score among runs in this fetch transaction. */\n groupScreenImportance: number;\n /** True when this transaction contains a finest-level (L0) target. */\n groupFinest: boolean;\n /** Stable aggregate key: L0 cell, L1+ slice, or singleton file. */\n groupId: string;\n /** Visible(0) / near-out-of-view(1) / background(2) ranking bucket. */\n groupClass: 0 | 1 | 2;\n}\n\nfunction classicFetchPhaseRank(phase: ClassicFetchPhase): number {\n switch (phase) {\n case 'environment':\n return -1;\n case 'finest-target':\n return 0;\n case 'coverage':\n return 1;\n case 'target':\n return 2;\n default:\n return 3;\n }\n}\n\nfunction kindForClassicFetchPhase(phase: ClassicFetchPhase): ClassicFetchWant['kind'] {\n return phase === 'background' ? 'base' : 'priority';\n}\n\n/**\n * Fetch identity mirrors the display transaction. Finest L0 must arrive as a\n * whole physical cell; L1+ is intentionally progressive, one leaf slice at a\n * time, so hidden siblings cannot hold the visible slice in the queue.\n */\nfunction classicFetchGroupKey(\n want: Pick<ClassicFetchWant, 'phase' | 'coverageGroup' | 'leafStart' | 'leafEnd'>,\n file: number,\n): string {\n if (want.coverageGroup < 0) return `file:${file}`;\n if (want.phase === 'finest-target') return `cell:${want.coverageGroup}`;\n return `slice:${want.coverageGroup}:${want.leafStart}:${want.leafEnd}`;\n}\n\n/**\n * Fills {@link ClassicFetchWant.groupDistance} / `groupPending` so ranking can\n * finish one near cell before sprinkling bandwidth across neighbors.\n */\nexport function stampClassicFetchGroups(\n pending: Map<number, ClassicFetchWant>,\n lodBaseDistance = 10,\n forceNearPriority = false,\n): void {\n const near = nearDisplayDistance(lodBaseDistance);\n const aggregates = new Map<\n string,\n { distance: number; count: number; inView: boolean; screenImportance: number; finest: boolean }\n >();\n for (const [file, want] of pending) {\n if (want.phase === 'environment') continue;\n const key = classicFetchGroupKey(want, file);\n const prev = aggregates.get(key);\n if (!prev) {\n aggregates.set(key, {\n distance: want.distance,\n count: 1,\n inView: want.inView,\n screenImportance: want.screenImportance,\n finest: want.phase === 'finest-target',\n });\n continue;\n }\n if (want.distance < prev.distance) prev.distance = want.distance;\n if (want.inView) prev.inView = true;\n if (want.screenImportance < prev.screenImportance) {\n prev.screenImportance = want.screenImportance;\n }\n if (want.phase === 'finest-target') prev.finest = true;\n prev.count++;\n }\n for (const [file, want] of pending) {\n if (want.phase === 'environment') {\n want.kind = 'priority';\n want.groupDistance = 0;\n want.groupPending = 1;\n want.groupInView = true;\n want.groupScreenImportance = Number.NEGATIVE_INFINITY;\n want.groupFinest = true;\n want.groupId = 'environment';\n want.groupClass = 0;\n continue;\n }\n const groupId = classicFetchGroupKey(want, file);\n const agg = aggregates.get(groupId);\n if (!agg) continue;\n const groupClass: 0 | 1 | 2 = agg.inView ? 0 : agg.distance <= near ? 1 : 2;\n const kind: ClassicFetchWant['kind'] =\n groupClass === 0 ? 'priority' : groupClass === 1 && forceNearPriority ? 'priority' : 'base';\n want.groupDistance = agg.distance;\n want.groupPending = agg.count;\n want.groupInView = agg.inView;\n want.groupScreenImportance = agg.screenImportance;\n want.groupFinest = agg.finest;\n want.groupId = groupId;\n want.groupClass = groupClass;\n want.kind = kind;\n }\n}\n\n/**\n * Distance inside which classic LCC treats a cut as short-range for fetch\n * ranking. Matches {@link LodSourceOptions.lodBaseDistance} (default 10).\n */\nexport function nearDisplayDistance(lodBaseDistance: number, _lodMultiplier = 2): number {\n return lodBaseDistance;\n}\n\n/**\n * True when this deferred group is waiting on resolved finest (L0) - never\n * flash coarsest discs as a stand-in.\n */\nexport function isWaitingOnFinest(group: SwapGroup): boolean {\n return group.adds.some((run) => run.level === 0);\n}\n\n/**\n * Classic fetch phase for a **resolved** desired run. Ambition-only levels are\n * never requested - callers pass runs from `computeDesiredRuns` only.\n */\nexport function classicFetchPhaseForDesired(\n run: LodRun,\n lodBaseDistance: number,\n lodMultiplier = 2,\n): ClassicFetchPhase {\n void lodMultiplier;\n if (run.level === 0) return 'finest-target';\n const distance = run.distance ?? Number.POSITIVE_INFINITY;\n if (run.inView === false && distance > nearDisplayDistance(lodBaseDistance)) return 'background';\n return 'target';\n}\n\n/** Classic fetch phase for a pinned coarsest substitute of an L1+ gap. */\nexport function classicFetchPhaseForCoverage(\n run: LodRun,\n lodBaseDistance: number,\n lodMultiplier = 2,\n): ClassicFetchPhase {\n void lodMultiplier;\n const distance = run.distance ?? Number.POSITIVE_INFINITY;\n if (run.inView === false && distance > nearDisplayDistance(lodBaseDistance)) return 'background';\n return 'coverage';\n}\n\n/**\n * Maps a desired run to the cross-mesh fetch kind. Prefer\n * {@link classicFetchPhaseForDesired} for ranking; this remains for tests.\n */\nexport function classicFetchKindForDesired(\n run: LodRun,\n lodBaseDistance: number,\n lodMultiplier = 2,\n): ClassicFetchWant['kind'] {\n return kindForClassicFetchPhase(classicFetchPhaseForDesired(run, lodBaseDistance, lodMultiplier));\n}\n\nexport function enqueueClassicFetch(\n pending: Map<number, ClassicFetchWant>,\n file: number,\n phase: ClassicFetchPhase,\n run: LodRun,\n): void {\n const distance = run.distance ?? Number.POSITIVE_INFINITY;\n const level = run.level;\n const inView = run.inView !== false;\n const coverageGroup = run.coverageGroup ?? -1;\n const screenImportance = run.screenImportance ?? Number.POSITIVE_INFINITY;\n const kind = kindForClassicFetchPhase(phase);\n const prev = pending.get(file);\n if (!prev) {\n const groupId = classicFetchGroupKey(\n { phase, coverageGroup, leafStart: run.leafStart, leafEnd: run.leafEnd },\n file,\n );\n pending.set(file, {\n kind,\n phase,\n distance,\n level,\n inView,\n coverageGroup,\n leafStart: run.leafStart,\n leafEnd: run.leafEnd,\n screenImportance,\n groupDistance: distance,\n groupPending: 1,\n groupInView: inView,\n groupScreenImportance: screenImportance,\n groupFinest: phase === 'finest-target',\n groupId,\n // Provisional; finalized in stampClassicFetchGroups().\n groupClass: 2,\n });\n return;\n }\n const betterPhase = classicFetchPhaseRank(phase) < classicFetchPhaseRank(prev.phase);\n const samePhaseNearer =\n phase === prev.phase &&\n (distance < prev.distance || (distance === prev.distance && level < prev.level));\n const samePhaseBetterView =\n phase === prev.phase &&\n distance === prev.distance &&\n level === prev.level &&\n inView &&\n !prev.inView;\n if (betterPhase || samePhaseNearer || samePhaseBetterView) {\n pending.set(file, {\n kind,\n phase,\n distance,\n level,\n inView: inView || prev.inView,\n coverageGroup: coverageGroup >= 0 ? coverageGroup : prev.coverageGroup,\n leafStart: run.leafStart,\n leafEnd: run.leafEnd,\n screenImportance,\n groupDistance: Math.min(distance, prev.groupDistance),\n groupPending: prev.groupPending,\n groupInView: inView || prev.groupInView,\n groupScreenImportance: Math.min(screenImportance, prev.groupScreenImportance),\n groupFinest: phase === 'finest-target' || prev.groupFinest,\n groupId: prev.groupId,\n groupClass: prev.groupClass,\n });\n }\n}\n\n/**\n * Sort by coverage group first: visible groups, then near out-of-view, then\n * background. Within a group: coverage → resolved target (L0/L1+) → background.\n */\nexport function compareClassicFetches(\n a: ClassicFetchWant,\n b: ClassicFetchWant,\n fileA: number,\n fileB: number,\n): number {\n const groupDistA = a.groupDistance ?? a.distance;\n const groupDistB = b.groupDistance ?? b.distance;\n const pendingA = a.groupPending ?? 1;\n const pendingB = b.groupPending ?? 1;\n const groupA = a.groupId ?? classicFetchGroupKey(a, fileA);\n const groupB = b.groupId ?? classicFetchGroupKey(b, fileB);\n const classA = a.groupClass ?? (a.inView ? 0 : 2);\n const classB = b.groupClass ?? (b.inView ? 0 : 2);\n const screenA = a.groupScreenImportance ?? a.screenImportance;\n const screenB = b.groupScreenImportance ?? b.screenImportance;\n const finestA = a.groupFinest ? 0 : 1;\n const finestB = b.groupFinest ? 0 : 1;\n const rankInGroup = (phase: ClassicFetchPhase): number => {\n if (phase === 'coverage') return 0;\n if (phase === 'background') return 2;\n return 1; // target + finest-target\n };\n const envA = a.phase === 'environment' ? 0 : 1;\n const envB = b.phase === 'environment' ? 0 : 1;\n return (\n envA - envB ||\n classA - classB ||\n finestA - finestB ||\n screenA - screenB ||\n groupDistA - groupDistB ||\n pendingB - pendingA ||\n (groupA < groupB ? -1 : groupA > groupB ? 1 : 0) ||\n rankInGroup(a.phase) - rankInGroup(b.phase) ||\n a.level - b.level ||\n fileA - fileB\n );\n}\n\n/** Zero-copy view of one contiguous splat range within a decoded chunk.\n * Exported for tests only - not part of the public API surface. */\nexport function sliceSplatData(chunk: SplatData, offset: number, count: number): SplatData {\n // A manifest can over-declare a range against the chunk it points into;\n // subarray would silently clamp, yielding a SplatData whose count exceeds\n // its arrays and corrupting the shared pool. Fail the chunk instead.\n if (offset < 0 || count < 0 || offset + count > chunk.count) {\n throw new Error(\n `Splat range [${offset}, ${offset + count}) exceeds its chunk's ${chunk.count} splats; ` +\n 'the manifest and chunk data disagree.',\n );\n }\n const sh = chunk.shPacked;\n return {\n count,\n positions: chunk.positions.subarray(offset * 3, (offset + count) * 3),\n colors: chunk.colors.subarray(offset * 4, (offset + count) * 4),\n covariances: chunk.covariances.subarray(offset * 6, (offset + count) * 6),\n // Per-splat SH is splat-major, so it slices like everything else. Palette\n // shN (`chunk.sh`) is deliberately not carried: its labels index a\n // per-file codebook the shared pool has no way to hold.\n ...(sh\n ? {\n shPacked: {\n ...sh,\n packed: sh.packed.subarray(\n offset * shWordsPerSplat(sh.bands),\n (offset + count) * shWordsPerSplat(sh.bands),\n ),\n },\n }\n : {}),\n // Per-splat frontier `parent_size` (foveated `.rad`) slices splat-major like\n // the rest; uploaded into `covarianceB.w` by the pool.\n ...(chunk.frontierParent\n ? { frontierParent: chunk.frontierParent.subarray(offset, offset + count) }\n : {}),\n };\n}\n\n/** Packed SH words each splat carries at a band count (1, 2 or 3). */\nfunction shWordsPerSplat(bands: 1 | 2 | 3): number {\n return bands === 1 ? 3 : bands === 2 ? 8 : 15;\n}\n\nexport function chunkBytes(data: SplatData): number {\n return (\n data.positions.byteLength +\n data.colors.byteLength +\n data.covariances.byteLength +\n // SH is the largest part of an LCC Quality chunk (64 B/splat against the\n // base 32); omitting it would let the CPU cache run ~3x over its cap.\n (data.shPacked?.packed.byteLength ?? 0)\n );\n}\n\n/** An abort signal's reason as an Error, whatever the caller aborted with. */\nexport function abortReason(signal: AbortSignal): Error {\n const reason: unknown = signal.reason;\n return reason instanceof Error ? reason : new DOMException('Aborted', 'AbortError');\n}\n\nexport function validateAppendCap(value: number | undefined): number {\n const cap = value ?? APPEND_CAP;\n if (!Number.isInteger(cap) || cap <= 0) {\n throw new RangeError('StreamedSplatMesh maxSplatsPerSwap must be a positive integer.');\n }\n return cap;\n}\n\n/** Validates Spark's per-mesh `lodScale`; `undefined` means the neutral 1. */\nexport function validateLodScale(value: number | undefined): number {\n const scale = value ?? 1;\n if (!Number.isFinite(scale) || scale <= 0) {\n throw new RangeError('StreamedSplatMesh lodScale must be a positive finite number.');\n }\n return scale;\n}\n\n/**\n * The decoded-chunk cache cap.\n *\n * Delegates to {@link resolveCpuCacheBytes} rather than re-deriving it. The\n * local copy this replaces read `navigator.deviceMemory ?? 4`, which looks\n * equivalent but is not: **iOS never reports `deviceMemory` at all**, so every\n * iPhone took the `4` fallback and a 128 MiB cache, where the profile-aware\n * policy gives a memory-less device 32 MiB. That is 96 MiB of decoded chunks\n * held on the one platform whose tab gets killed for holding too much.\n */\nexport function defaultCpuCacheBytes(): number {\n return resolveCpuCacheBytes();\n}\n","import * as THREE from 'three/webgpu';\nimport type { SplatDatasetSource } from './dataset-source';\n\n/**\n * Parser for the Streamed SOG manifest (`lod-meta.json`, version 1).\n *\n * The manifest describes a large scene as a binary spatial tree whose leaves\n * each cover one region at several levels of detail. LOD level 0 is the\n * finest; higher levels are progressively coarser. Each leaf references, per\n * level, a contiguous `[offset, offset + count)` splat range inside one chunk\n * file (an unbundled SOG v2 directory). One chunk file serves many leaves.\n *\n * Spec:\n * https://developer.playcanvas.com/user-manual/gaussian-splatting/formats/streamed-sog/\n */\n\n/** A leaf's splat range at one LOD level. */\nexport interface LodRange {\n /** Index into {@link LodManifest.chunkUrls}. */\n readonly file: number;\n /** First splat row within that chunk's decoded arrays. */\n readonly offset: number;\n /** Number of splats. */\n readonly count: number;\n}\n\n/** A spatial region, present at one or more LOD levels. */\nexport interface LodLeaf {\n readonly bounds: THREE.Box3;\n /** Range per level; `lods[level]` is undefined if absent at that level. */\n readonly lods: readonly (LodRange | undefined)[];\n /**\n * Leaves with the same group are budgeted atomically. Formats may use this\n * when one spatial region is split into several independently streamed\n * ranges: the ranges may arrive separately, but must select one LOD cut.\n */\n readonly budgetGroup?: number;\n}\n\nexport interface LodManifest {\n /** Leaves in tree-traversal order (load-bearing: adjacent leaves within a\n * chunk have contiguous ranges, which the scheduler coalesces into runs). */\n readonly leaves: readonly LodLeaf[];\n /** Absolute chunk-directory URLs, indexed by {@link LodRange.file}. */\n readonly chunkUrls: readonly string[];\n /**\n * Chunk directories relative to the manifest, aligned with {@link chunkUrls}.\n * Only the unbundled-SOG layout has them; formats whose chunks are single\n * files (LCC of either generation) leave this out.\n */\n readonly chunkDirectories?: readonly string[];\n /** Total splats per LOD level (index = level). */\n readonly counts: readonly number[];\n readonly lodLevels: number;\n /** Root bounding box of the whole scene. */\n readonly bounds: THREE.Box3;\n}\n\ninterface RawNode {\n bound: { min: [number, number, number]; max: [number, number, number] };\n children?: [RawNode, RawNode];\n lods?: Record<string, { file: number; offset: number; count: number }>;\n}\n\ninterface RawManifest {\n version: number;\n counts: number[];\n lodLevels: number;\n filenames: string[];\n tree: RawNode;\n}\n\n/**\n * Parses a `lod-meta.json` object into a flat, render-ready manifest.\n *\n * @param json - The parsed manifest JSON.\n * @param baseUrl - URL of the manifest, used to resolve chunk directories.\n * @throws {Error} on an unsupported version or malformed tree.\n */\nexport function parseLodManifest(json: unknown, source: SplatDatasetSource): LodManifest {\n const raw = json as RawManifest;\n if (typeof raw !== 'object' || raw === null) {\n throw new Error('Streamed SOG manifest is not a JSON object.');\n }\n if (raw.version !== 1) {\n throw new Error(`Unsupported Streamed SOG manifest version: ${raw.version} (expected 1).`);\n }\n if (!Array.isArray(raw.filenames) || raw.filenames.some((name) => typeof name !== 'string')) {\n throw new Error('Streamed SOG manifest \"filenames\" must be an array of strings.');\n }\n if (\n !Array.isArray(raw.counts) ||\n raw.counts.some((count) => !Number.isSafeInteger(count) || count < 0)\n ) {\n throw new Error('Streamed SOG manifest \"counts\" must be an array of non-negative integers.');\n }\n if (!Number.isSafeInteger(raw.lodLevels) || raw.lodLevels < 1 || raw.lodLevels > 64) {\n throw new Error(`Streamed SOG manifest declares an invalid lodLevels: ${raw.lodLevels}.`);\n }\n\n // \"0_0/meta.json\" -> the chunk *directory* \"0_0/\". Over HTTP that resolves\n // to a real URL the worker fetches images beneath; from a dropped folder\n // there is nothing to resolve against, so the directory's files travel with\n // the chunk instead (see StreamedScene.chunkOptions).\n const directories = raw.filenames.map((name) => name.replace(/\\/?meta\\.json$/, '/'));\n const chunkUrls = directories.map(\n (directory) => source.resolve(directory) ?? `${source.manifestUrl}#${directory}`,\n );\n\n const leaves: LodLeaf[] = [];\n const lodLevels = raw.lodLevels;\n // Iterative depth-first traversal (explicit stack, children pushed in\n // reverse so leaf order is unchanged): a hostile manifest with a\n // pathologically deep tree must not overflow the call stack.\n const stack: RawNode[] = [raw.tree];\n while (stack.length > 0) {\n const node = stack.pop() as RawNode;\n if (typeof node !== 'object' || node === null) {\n throw new Error('Streamed SOG manifest tree contains a malformed node.');\n }\n if (node.children) {\n if (!Array.isArray(node.children) || node.children.length !== 2) {\n throw new Error('Streamed SOG manifest tree node must have exactly two children.');\n }\n stack.push(node.children[1], node.children[0]);\n continue;\n }\n const lods: (LodRange | undefined)[] = new Array<LodRange | undefined>(lodLevels).fill(\n undefined,\n );\n for (const [levelKey, range] of Object.entries(node.lods ?? {})) {\n const level = Number(levelKey);\n if (level >= 0 && level < lodLevels) {\n assertSaneRange(range, raw.filenames.length);\n lods[level] = range;\n }\n }\n leaves.push({ bounds: boxFromBound(node.bound), lods });\n }\n\n return {\n leaves,\n chunkUrls,\n chunkDirectories: directories,\n counts: raw.counts,\n lodLevels,\n bounds: boxFromBound(raw.tree.bound),\n };\n}\n\n/** Asserts a leaf's untrusted splat range is sane before it drives pool I/O. */\nfunction assertSaneRange(range: LodRange, fileCount: number): void {\n if (\n typeof range !== 'object' ||\n range === null ||\n !Number.isSafeInteger(range.file) ||\n range.file < 0 ||\n range.file >= fileCount ||\n !Number.isSafeInteger(range.offset) ||\n range.offset < 0 ||\n !Number.isSafeInteger(range.count) ||\n range.count < 0\n ) {\n throw new Error(\n 'Streamed SOG manifest leaf declares an invalid LOD range ' +\n `(file ${range?.file}, offset ${range?.offset}, count ${range?.count}).`,\n );\n }\n}\n\nfunction boxFromBound(bound: RawNode['bound']): THREE.Box3 {\n return new THREE.Box3(\n new THREE.Vector3(bound.min[0], bound.min[1], bound.min[2]),\n new THREE.Vector3(bound.max[0], bound.max[1], bound.max[2]),\n );\n}\n","import * as THREE from 'three/webgpu';\nimport { LodScheduler, type LodRun } from './lod-scheduler';\nimport { parseLodManifest, type LodManifest } from './lod-manifest';\nimport type { ChunkFileFormat } from '../loaders/loading';\nimport type { RadChunkRangeRequest } from '../loaders/load-worker-protocol';\nimport type { LccChunkParams } from '../formats/lcc/parse-lcc';\nimport type { SplatData } from '../core/splat-data';\nimport type { SplatDatasetSource } from './dataset-source';\n\n/**\n * Per-frame LOD decision maker: given the camera, returns the set of runs\n * (`{file, offset, count}` slices with a leaf interval) that should be\n * resident. Two implementations exist - {@link LodScheduler} for the flat\n * Streamed SOG leaves-with-levels model, and `OctreeLodSource` for the\n * cut-based LCC2 octree - so {@link StreamedSplatMesh} is format-agnostic.\n */\nexport interface LodSource {\n /** Active-splat budget the returned set stays within. */\n budget: number;\n /** World-unit distance inside which the finest LOD is used. */\n lodBaseDistance: number;\n /** Distance ratio between successive LOD levels. */\n lodMultiplier: number;\n /** The runs that should be resident for the given camera. */\n computeDesiredRuns(\n cameraLocal: THREE.Vector3,\n frustum: THREE.Frustum,\n now: number,\n cameraForward?: THREE.Vector3,\n ): LodRun[];\n /** Coarsest-level runs covering finest cells `[from, to)` - always-cached\n * substitute coverage while a finer level is fetching. */\n coarsestRunsFor(from: number, to: number): LodRun[];\n /**\n * Runs covering `[from, to)` at a requested LOD level (clamped per leaf to an\n * available rung). Used by classic-LCC startup hold to coarsen a nearby home\n * cell when the resolved L0 cut overflows the pool. Optional: sources without\n * a flat leaf ladder leave it undefined.\n */\n runsAtLevelFor?(from: number, to: number, level: number): LodRun[];\n /**\n * Covering runs for finest cells currently in the camera frustum (or\n * containing the camera). Used by `.lcc` / `.lcc2` startup\n * `initialReveal: 'hold-coverage'` so the first painted frame has no empty\n * cells. Classic LCC freezes nearby groups at finest+1 and farther in-view\n * groups at coarsest; `.lcc2` still returns coarsest root-children.\n * `cameraForward` (mesh-local) lets classic LCC ignore full-Z cells that\n * sit entirely behind the camera plane — their AABBs otherwise hit the\n * frustum from every indoor pose. Optional: sources without coverage groups\n * or a nested octree cover leave it undefined (the hold then stays disabled).\n */\n coverageRunsFor?(\n cameraLocal: THREE.Vector3,\n frustum: THREE.Frustum,\n cameraForward?: THREE.Vector3,\n ): LodRun[];\n /**\n * Notified when a chunk finishes decoding, so a source that discovers its\n * structure from chunk payloads (a `.rad` LOD tree lives in the chunks, not\n * a manifest) can incorporate it. Sources with a fully-known manifest\n * (Streamed SOG, LCC) leave this undefined.\n */\n onChunkDecoded?(file: number, data: SplatData): void;\n}\n\n/**\n * Per-chunk fetch and decode settings, for formats where the URL alone is not\n * enough. LCC (`.lcc`, manifest v3–v5) uses this to name a byte range inside the one big\n * `data.bin` and to carry that range's dequantization ranges. LCC2 and local\n * Streamed SOG use it so a `blob:` URL (no file extension) still selects the\n * SOG parser.\n */\nexport interface StreamedChunkOptions {\n readonly format: ChunkFileFormat;\n readonly lcc?: LccChunkParams;\n /** Byte range within the single-file `.rad`; carried by a `rad-chunk`. */\n readonly rad?: RadChunkRangeRequest;\n /**\n * Convert this SOG chunk's palette-compressed shN into per-splat packed shN\n * at decode, keeping this many bands, so it survives the shared pool (M11).\n * Absent leaves the palette shN to be dropped, as streamed scenes did before.\n */\n readonly sog?: { readonly packShBands: 1 | 2 | 3 };\n /**\n * For an unbundled SOG chunk from a dropped folder: the directory's files as\n * `name → blob URL`. Absent over HTTP, where the worker resolves each image\n * against the chunk's directory URL instead.\n */\n readonly files?: Readonly<Record<string, string>>;\n}\n\n/**\n * A single always-resident environment/background tile a format ships outside\n * its LOD structure - classic `.lcc` `environment.bin` and `.lcc2` `env.sog`,\n * loaded once and toggled with {@link StreamedSplatMesh.setEnvironmentEnabled}\n * rather than scheduled by camera distance. `.lcc2` measures the splat count\n * at decode (see `docs/formats/lcc2-notes.md`); classic `.lcc` sizes it from\n * the file length.\n */\nexport interface EnvironmentTile {\n /** Chunk-file index (into {@link StreamedScene.chunkUrls}) of the tile. */\n readonly file: number;\n}\n\n/** One collision-mesh tile a scene ships alongside its splats. */\nexport interface CollisionMeshDescriptor {\n /**\n * Fetchable URL of the tile: a binary triangle-mesh PLY (`.lcc2`), or a\n * classic-LCC `collision.lci` that expands into per-cell meshes at load.\n */\n readonly url: string;\n /**\n * The tile's source-local bounds, when the format declares them. Lets a host\n * order or cull tile work without parsing them first. Unused for a whole-file\n * `.lci` descriptor - per-cell bounds come from the parser.\n */\n readonly bounds?: THREE.Box3;\n}\n\n/**\n * Collision geometry a format ships next to its splats - absent for formats\n * that carry none (Streamed SOG), present for XGRIDS `.lcc` / `.lcc2` captures\n * that include a collision sidecar.\n *\n * Coordinates are source-local, the same frame as {@link StreamedScene.bounds}:\n * {@link StreamedScene.formatTransform} maps them to world.\n */\nexport interface SplatCollisionData {\n readonly meshes: readonly CollisionMeshDescriptor[];\n}\n\n/**\n * Everything {@link StreamedSplatMesh} needs about a scene, independent of\n * whether it came from a Streamed SOG manifest or an LCC dataset.\n */\nexport interface StreamedScene {\n readonly source: LodSource;\n /** Chunk-file URLs, indexed by a run's `file`. */\n readonly chunkUrls: readonly string[];\n /** How each chunk URL is fetched: an unbundled SOG directory (Streamed\n * SOG) or a single bundled `.sog`/`.ply` file (LCC2 tiles). */\n readonly chunkKind: 'directory' | 'file';\n /** Optional per-chunk overrides, aligned with {@link chunkUrls}. */\n readonly chunkOptions?: readonly (StreamedChunkOptions | undefined)[];\n /**\n * Per-splat SH bands this scene's chunks actually carry, after the format\n * has had its say - a `Portable` LCC capture reports 0 however many bands\n * the caller asked for. The pool sizes its SH textures from this, so it\n * never allocates ~64 B/splat for SH that will never arrive.\n */\n readonly shBands?: 0 | 1 | 2 | 3;\n /** Root bounds of the whole scene (valid before any chunk loads). */\n readonly bounds: THREE.Box3;\n /** Files backing the coarsest levels - pinned in cache as substitutes. */\n readonly pinnedFiles: ReadonlySet<number>;\n /** Finest-level splat total, for sizing the pool. */\n readonly maxResidentSplats: number;\n /**\n * The capture's real content size - the splat count a host would need to hold\n * it at full resolution. Distinct from {@link maxResidentSplats}, which a\n * foveated source reports as the *requested budget* because its pool holds a\n * camera-directed resident set rather than the whole tree. A host budgeting\n * across several streamed meshes needs the real number: a mesh cannot spend\n * more than it contains, so this is the clamp on any share handed to it.\n *\n * Undefined when the format does not declare it.\n */\n readonly contentSplatCount?: number;\n /** Smallest budget that can retain full-scene coarsest coverage. */\n readonly minimumCoverageSplats: number;\n /**\n * Optional local-to-world format correction applied once to the mesh at\n * load time. Bounds and LOD data remain source-local so scheduling and\n * picking consistently use the encoded coordinate frame.\n */\n readonly formatTransform?: THREE.Matrix4;\n /** Optional collision geometry the format ships; undefined when it has none. */\n readonly collision?: SplatCollisionData;\n /**\n * Optional always-resident environment tile the format ships outside its LOD\n * structure (the `.lcc2` sky) - present only when the dataset carries one.\n */\n readonly environment?: EnvironmentTile;\n /**\n * Screen-space foveation band (px), for a scene that renders whole resident\n * chunks and lets the material's projected-radius cull pick the LOD cut per\n * splat - a `.rad` too large to load whole. Only splats sized `(min, max]` on\n * screen draw, so near view rays land on fine leaves and far rays on coarse\n * nodes. `StreamedSplatMesh` applies it as the mesh's screen-radius band.\n */\n readonly foveation?: { readonly minScreenRadiusPx: number; readonly maxScreenRadiusPx: number };\n /**\n * Splats per chunk file, so a `(file, localIndex)` pair maps to a stable global\n * splat index (`file * chunkSize + local`). Set by `.rad`; the page-table\n * renderer (`foveationMode: 'page-table'`) keys frontier splats by that global.\n */\n readonly chunkSize?: number;\n /**\n * A chunk the scene builder already fetched and decoded (`.rad` decodes chunk\n * 0 for the scene bounds and the SH codebook). Handing it straight to the\n * renderer saves a second round trip - and in `page-table` mode it is what\n * seeds the worker's tree roots, without which the first traversals return an\n * empty frontier and the view stays blank until chunk 0 arrives a second time.\n */\n readonly bootstrapChunk?: { readonly file: number; readonly data: SplatData };\n /**\n * Source-local xyz subsample of the coarsest overview (`.rad` chunk 0).\n * Survives after page-table mode transfers chunk-0 buffers to the worker, so\n * a host can estimate terrain height before any later chunk decodes.\n */\n readonly overviewPositions?: Float32Array;\n}\n\nexport interface LodSourceOptions {\n budget: number;\n lodBaseDistance: number;\n lodMultiplier: number;\n}\n\n/**\n * Builds a scene from a parsed Streamed SOG manifest (JSON already fetched).\n *\n * `shBands` opts into view-dependent color (M11): when ≥ 1, each chunk's\n * palette shN is converted to per-splat packed shN at decode so it survives\n * the shared pool, and the pool allocates that many SH bands. Unset/0 keeps\n * the historical behavior (palette shN dropped). It is opt-in because the\n * manifest does not declare whether the tiles carry shN; a scene with none\n * renders unchanged but wastes the allocated SH textures, so ask deliberately.\n */\nexport function buildSogScene(\n json: unknown,\n source: SplatDatasetSource,\n options: LodSourceOptions,\n shBands: 0 | 1 | 2 | 3 = 0,\n): StreamedScene {\n const manifest = parseLodManifest(json, source);\n const packShBands = shBands >= 1 ? (shBands as 1 | 2 | 3) : undefined;\n // A dropped folder has no directory URL to fetch images beneath, so each\n // chunk carries its own files instead. Over HTTP this is all undefined and\n // the worker resolves against the directory URL as before. When SH is\n // requested every chunk also carries the pack-bands directive.\n const chunkOptions = manifest.chunkUrls.map((_, index) => {\n const directory = manifest.chunkDirectories?.[index];\n const files = directory ? source.directoryFiles(directory) : null;\n if (!files && !packShBands) return undefined;\n return {\n format: 'sog',\n ...(files ? { files } : {}),\n ...(packShBands ? { sog: { packShBands } } : {}),\n } as const;\n });\n return {\n source: new LodScheduler(manifest, options),\n chunkUrls: manifest.chunkUrls,\n ...(chunkOptions.some(Boolean) ? { chunkOptions } : {}),\n ...(packShBands ? { shBands: packShBands } : {}),\n chunkKind: 'directory', // Streamed SOG chunks are unbundled directories\n bounds: manifest.bounds,\n pinnedFiles: computeSogPinnedFiles(manifest),\n maxResidentSplats: manifest.counts[0] ?? options.budget,\n minimumCoverageSplats: computeSogMinimumCoverage(manifest),\n };\n}\n\n/** Total of each leaf's coarsest available range. */\nfunction computeSogMinimumCoverage(manifest: LodManifest): number {\n let total = 0;\n for (const leaf of manifest.leaves) {\n for (let level = leaf.lods.length - 1; level >= 0; level--) {\n const range = leaf.lods[level];\n if (range) {\n total += range.count;\n break;\n }\n }\n }\n return Math.max(1, total);\n}\n\n/** Files backing some leaf's coarsest level - the always-available floor. */\nfunction computeSogPinnedFiles(manifest: LodManifest): Set<number> {\n const pinned = new Set<number>();\n for (const leaf of manifest.leaves) {\n for (let level = leaf.lods.length - 1; level >= 0; level--) {\n const range = leaf.lods[level];\n if (range) {\n pinned.add(range.file);\n break;\n }\n }\n }\n return pinned;\n}\n","import {\n SplatLoadError,\n toRequestInit,\n type SplatRequestOptions,\n type StreamedSplatFormat,\n} from '../loaders/loading';\n\n/**\n * Where a streamed dataset's files come from.\n *\n * A streamed scene is never one file: it is a manifest plus sidecars and chunk\n * files that the manifest names *relatively* (`data.bin`, `data/3dgs/x.sog`,\n * `0_0/meta.json`). Over HTTP those resolve against the manifest's URL; from a\n * dropped folder there is no URL to resolve against, only `File` objects.\n *\n * This is the seam between the two. Everything downstream - including the\n * worker's ranged chunk reads - keeps working on plain URLs, because a\n * dropped file's `blob:` URL answers `Range` with a real 206 (verified on\n * Chromium/WebKit/Gecko), so only *name resolution* has to differ.\n */\nexport interface SplatDatasetSource {\n /** Fetchable URL of the manifest itself. */\n readonly manifestUrl: string;\n /** Fetchable URL for a dataset-relative path, or null when absent. */\n resolve(path: string): string | null;\n /** Byte length of a file, or null when absent. */\n size(path: string): Promise<number | null>;\n /**\n * The files inside a chunk *directory* (unbundled SOG), as\n * `name → fetchable URL`. Null when the chunk should be fetched by URL\n * instead - the HTTP case, where the directory is a real path.\n */\n directoryFiles(path: string): Record<string, string> | null;\n /** Releases any resources held for this dataset (e.g. blob URLs). */\n dispose(): void;\n}\n\n/** A dataset served over HTTP, resolved against the manifest's URL. */\nexport function httpDatasetSource(\n manifestUrl: string,\n request?: SplatRequestOptions,\n): SplatDatasetSource {\n return {\n manifestUrl,\n resolve: (path) => new URL(path, manifestUrl).href,\n size: (path) => probeSize(new URL(path, manifestUrl).href, request),\n directoryFiles: () => null,\n dispose: () => {},\n };\n}\n\n/**\n * Determines a remote file's length without downloading it: `HEAD` first, and\n * a one-byte ranged `GET` for origins that disallow `HEAD` but do serve\n * ranges. A missing file is not an error - callers treat null as \"absent\".\n */\nasync function probeSize(url: string, request?: SplatRequestOptions): Promise<number | null> {\n try {\n const response = await fetch(url, { ...toRequestInit(request), method: 'HEAD' });\n await cancelBody(response);\n if (response.ok) {\n const length = response.headers.get('content-length');\n if (length !== null) return saneSize(Number(length));\n } else if (response.status === 404) {\n return null;\n }\n } catch {\n // Fall through to the ranged GET below.\n }\n try {\n const response = await fetch(url, {\n ...toRequestInit(request),\n headers: { ...(request?.headers ?? {}), Range: 'bytes=0-0' },\n });\n await cancelBody(response);\n // A plain 200 means the server ignored the Range header - the body would\n // have been the whole file, and Content-Range is absent; treat the size\n // as unknown rather than misreading a full response as a probe.\n if (response.status !== 206) return null;\n const total = response.headers.get('content-range')?.split('/')[1];\n return total === undefined || total === '*' ? null : saneSize(Number(total));\n } catch {\n return null;\n }\n}\n\n/** Discards a probe response's body so the connection is not left downloading. */\nasync function cancelBody(response: Response): Promise<void> {\n try {\n await response.body?.cancel();\n } catch {\n // A body that cannot be cancelled (already consumed/locked) is fine.\n }\n}\n\n/** A probed size only counts if it is a non-negative safe integer. */\nfunction saneSize(size: number): number | null {\n return Number.isSafeInteger(size) && size >= 0 ? size : null;\n}\n\n/** A dataset picked out of a dropped folder. */\nexport interface LocalDataset {\n readonly source: SplatDatasetSource;\n /** The streamed format the folder's manifest identifies it as. */\n readonly format: Exclude<StreamedSplatFormat, 'auto'>;\n /** The manifest's file name, for display. */\n readonly name: string;\n}\n\n/** Manifest names that identify a streamed dataset, most specific first. */\nconst MANIFESTS: {\n test: (name: string) => boolean;\n format: Exclude<StreamedSplatFormat, 'auto'>;\n}[] = [\n { test: (n) => n.endsWith('.lcc2'), format: 'lcc2' },\n { test: (n) => n.endsWith('.lcc'), format: 'lcc' },\n // The `.rad` header of a `--rad-chunked` set; `.radc` chunk files are not\n // manifests (they end in `.radc`, so `endsWith('.rad')` excludes them).\n { test: (n) => n.endsWith('.rad'), format: 'rad' },\n { test: (n) => n === 'lod-meta.json', format: 'streamed-sog' },\n];\n\n/**\n * Builds a dataset from a dropped folder's files, keyed by their paths\n * relative to the folder root.\n *\n * Every file gets a `blob:` URL, so the rest of the pipeline - including the\n * worker's ranged reads into a multi-hundred-megabyte `data.bin` - is\n * identical to the HTTP path. Nothing is copied or read here: a blob URL is a\n * handle to the file on disk, so a 300 MB `data.bin` costs nothing until a\n * chunk actually reads a range out of it.\n *\n * @throws {SplatLoadError} with `phase: 'manifest'` when the folder holds no\n * recognizable manifest, or more than one (which would make the choice\n * arbitrary). Neither is retryable: the same folder fails the same way.\n */\nexport function createLocalDataset(files: ReadonlyMap<string, File>): LocalDataset {\n const found: { path: string; format: Exclude<StreamedSplatFormat, 'auto'> }[] = [];\n for (const path of files.keys()) {\n const name = basename(path).toLowerCase();\n const match = MANIFESTS.find((candidate) => candidate.test(name));\n // Only a manifest at the top level counts: an `.lcc2` capture nests whole\n // datasets under `data/`, and picking one of those would load a fragment.\n if (match && depth(path) === minDepth(files, match)) found.push({ path, format: match.format });\n }\n if (found.length === 0) {\n throw new SplatLoadError(\n 'That folder has no streamed-scene manifest in it (expected a .lcc, .lcc2 or lod-meta.json file).',\n { phase: 'manifest', url: 'local-folder', retryable: false },\n );\n }\n if (found.length > 1) {\n const names = found.map((entry) => basename(entry.path)).join(', ');\n throw new SplatLoadError(\n `That folder holds more than one scene manifest (${names}) - drop one scene at a time.`,\n { phase: 'manifest', url: 'local-folder', retryable: false },\n );\n }\n\n const manifest = found[0] as { path: string; format: Exclude<StreamedSplatFormat, 'auto'> };\n // Paths resolve relative to the manifest, exactly as they would over HTTP.\n const root = manifest.path.includes('/')\n ? manifest.path.slice(0, manifest.path.lastIndexOf('/') + 1)\n : '';\n const urls = new Map<string, string>();\n const urlFor = (path: string): string | null => {\n const existing = urls.get(path);\n if (existing !== undefined) return existing;\n const file = files.get(path);\n if (!file) return null;\n const url = URL.createObjectURL(file);\n urls.set(path, url);\n return url;\n };\n\n const source: SplatDatasetSource = {\n manifestUrl: urlFor(manifest.path) as string,\n resolve: (path) => urlFor(root + normalize(path)),\n size: (path) => Promise.resolve(files.get(root + normalize(path))?.size ?? null),\n directoryFiles: (path) => {\n // An unbundled SOG chunk is a folder of images the worker fetches by\n // name; hand it the whole folder rather than a URL to resolve against.\n const prefix = root + normalize(path).replace(/\\/?$/, '/');\n const entries: Record<string, string> = {};\n for (const candidate of files.keys()) {\n if (!candidate.startsWith(prefix)) continue;\n const url = urlFor(candidate);\n if (url) entries[candidate.slice(prefix.length)] = url;\n }\n return Object.keys(entries).length > 0 ? entries : null;\n },\n dispose: () => {\n for (const url of urls.values()) URL.revokeObjectURL(url);\n urls.clear();\n },\n };\n return { source, format: manifest.format, name: basename(manifest.path) };\n}\n\n/** Strips the `./` and leading `/` a manifest path may carry. */\nfunction normalize(path: string): string {\n return path.replace(/^\\.?\\//, '');\n}\n\nfunction basename(path: string): string {\n return path.slice(path.lastIndexOf('/') + 1);\n}\n\nfunction depth(path: string): number {\n return path.split('/').length - 1;\n}\n\n/** The shallowest depth any manifest of this kind sits at. */\nfunction minDepth(\n files: ReadonlyMap<string, File>,\n match: { test: (name: string) => boolean },\n): number {\n let shallowest = Infinity;\n for (const path of files.keys()) {\n if (match.test(basename(path).toLowerCase())) shallowest = Math.min(shallowest, depth(path));\n }\n return shallowest;\n}\n","/**\n * Wire protocol for the `.rad` page-table frontier worker\n * (`frontier-worker.ts`).\n *\n * Split from the worker module so `StreamedSplatMesh` can be typed against it\n * without pulling worker source into the published declarations - the worker is\n * reached only through `?worker&inline` and is not a public entry point.\n */\nimport type { SplatData } from '../../core/splat-data';\n\n/**\n * Foveation ramp for the frontier traversal, matching Spark's `SparkRenderer`\n * defaults (`coneFov0` / `coneFov` / `coneFoveate` / `behindFoveate`). Detail is\n * full inside `coneFov0`, falls to `coneFoveate` by `coneFov`, and to\n * `behindFoveate` directly behind the camera - a *weight*, never a cull, so the\n * scene stays covered when the camera turns. Lives here (a dependency-free\n * module) because both the worker and `StreamedSplatMesh` need it.\n */\nexport const FRONTIER_FOVEATION_DEFAULTS = {\n coneFov0: 90,\n coneFov: 120,\n coneFoveate: 0.4,\n behindFoveate: 0.2,\n} as const;\n\n/** Degrees / weights describing the foveation ramp. */\nexport interface FrontierFoveation {\n readonly coneFov0: number;\n readonly coneFov: number;\n readonly coneFoveate: number;\n readonly behindFoveate: number;\n}\n\n/** A decoded chunk's arrays, as forwarded from the main thread. */\nexport interface FrontierChunkMessage {\n readonly type: 'chunk';\n readonly file: number;\n readonly count: number;\n readonly positions: Float32Array;\n readonly colors: Uint8Array;\n readonly covariances: Float32Array;\n readonly childCount: Uint16Array;\n readonly childStart: Uint32Array;\n readonly size: Float32Array;\n readonly shBands: 0 | 1 | 2 | 3;\n readonly shPacked?: Uint32Array;\n readonly shRange?: {\n min: readonly [number, number, number];\n max: readonly [number, number, number];\n };\n}\n\nexport interface FrontierInitMessage {\n readonly type: 'init';\n readonly capacity: number;\n readonly chunkSize: number;\n readonly cpuCacheBytes: number;\n}\n\n/**\n * Changes how many slots the pager may fill, after the host grew or shrank the\n * storage behind them (a near mesh climbing its budget, a distant one giving\n * pages back). The chunk cache is untouched - only the pager is resized - so no\n * chunk is re-downloaded.\n */\nexport interface FrontierResizeMessage {\n readonly type: 'resize';\n readonly capacity: number;\n}\n\n/**\n * Changes the byte cap the chunk cache evicts against, after the scene's shared\n * `ChunkCacheBudget` re-split it - a near mesh climbing, a far one giving bytes\n * back.\n *\n * Only the cap moves; nothing is dropped here. Eviction stays on the one path\n * that knows what the frontier still needs (`evict` runs inside `reschedule`,\n * against `neededFiles` and the pager's resident set), and the `evicted` list\n * only reaches the main thread on a plan. Evicting off that path would drop\n * chunks out from under resident splats with no way to tell the host - the\n * dark-speckle failure the resident-set guard exists to prevent.\n */\nexport interface FrontierCacheBudgetMessage {\n readonly type: 'cacheBudget';\n readonly cpuCacheBytes: number;\n}\n\nexport interface FrontierRescheduleMessage {\n readonly type: 'reschedule';\n readonly seq: number;\n readonly cameraLocal: [number, number, number];\n /** Unit camera forward in mesh-local space. Detail falls off away from it -\n * the traversal foveates rather than frustum-culls, so the scene stays covered\n * when the camera turns or zooms out. */\n readonly cameraForward: [number, number, number];\n /** Foveation ramp, in degrees / weights (Spark's `coneFov0`/`coneFov`/…). */\n readonly coneFov0: number;\n readonly coneFov: number;\n readonly coneFoveate: number;\n readonly behindFoveate: number;\n /** Cut on foveated `size / distance` - `2·tan(fovY/2) / renderHeight`, scaled\n * by `foveationTargetPx`. Fixed per frame; the budget is what bounds the cut. */\n readonly limit: number;\n /** Maximum drawn splats; enforced inside the traversal, never after. */\n readonly budget: number;\n}\n\nexport type FrontierRequest =\n | FrontierInitMessage\n | FrontierChunkMessage\n | FrontierResizeMessage\n | FrontierCacheBudgetMessage\n | FrontierRescheduleMessage;\n\n/** Packed splats to write, in slot order (a subset of {@link SplatData}). */\nexport interface PlanSplats {\n readonly count: number;\n readonly positions: Float32Array;\n readonly colors: Uint8Array;\n readonly covariances: Float32Array;\n readonly shPacked?: SplatData['shPacked'];\n}\n\nexport interface FrontierPlanMessage {\n readonly type: 'plan';\n readonly seq: number;\n /** Survivors relocated by swap-remove: write `moves` splat j at `moveSlots[j]`. */\n readonly moveSlots: Uint32Array;\n readonly moves: PlanSplats;\n /** Newcomers written contiguously at `[appendStart, appendStart + appends.count)`. */\n readonly appendStart: number;\n readonly appends: PlanSplats;\n /** Freed tail slots to degenerate. */\n readonly degenerateStart: number;\n readonly degenerateCount: number;\n /** Chunks the frontier wants next, biggest-on-screen first. */\n readonly touched: Uint32Array;\n /** Drawn (non-degenerate) frontier size - the true on-screen splat count. */\n readonly residentCount: number;\n /**\n * Splats this plan could not gather because their chunk had been evicted, and\n * so wrote as zeros into slots that are still drawn - coverage holes, seen as\n * dark speckle in a region while it refines. Eviction protects every chunk\n * with resident splats, so this is 0; a non-zero value is a bug.\n */\n readonly gatherMissing: number;\n /** Newcomers the slab had no room for. The traversal is budget-bounded, so\n * this is 0 unless the slab is smaller than the draw budget - a real bug. */\n readonly dropped: number;\n /** Chunks evicted from the worker cache this round. The main thread must\n * forget them (`pageTableCachedFiles`) or they could never be refetched. */\n readonly evicted: Uint32Array;\n /**\n * The cut this plan was built at - at or below the requested `limit`, because\n * the worker refines past the quality target to spend the draw budget.\n *\n * The host needs it because its screen-radius band was chosen for the *target*\n * cut: a finer cut selects smaller splats, and a band left at the coarse\n * setting would cull exactly the detail the refinement just bought.\n */\n readonly solvedLimit: number;\n /**\n * The pager capacity this plan was built against.\n *\n * The host grows and shrinks the storage behind the slots, so a plan can\n * arrive describing slots that no longer exist: a `reschedule` posted before a\n * `resize` is answered from the old capacity, and applying that answer writes\n * some splats nowhere while leaving their slots holding whatever was there\n * before - a coarse node's data in a slot the frontier now wants fine, which\n * draws as a single enormous splat. The host compares this and drops such a\n * plan instead.\n */\n readonly capacity: number;\n /**\n * False when the plan was held short of the traversal's frontier to bound how\n * much the host must write in one tick, so the resident set is an intermediate\n * one - some newcomers deferred, some replaced nodes still drawn. The host must\n * reschedule promptly, otherwise convergence stalls wherever the cap left it.\n */\n readonly converged: boolean;\n /**\n * Decoded bytes the worker's chunk cache is holding, and the cap it evicts\n * against.\n *\n * Reported because the cache lives entirely in the worker, so a host watching\n * a scene that will not stop streaming cannot otherwise tell \"the working set\n * does not fit\" from \"still converging\" - and the main thread's own mirror of\n * which files are cached is not enough to reconstruct the byte total.\n */\n readonly cacheBytes: number;\n readonly cacheLimitBytes: number;\n readonly pendingFrontierSplats?: number;\n readonly staleResidentSplats?: number;\n readonly lastPlanAppends?: number;\n readonly lastPlanMoves?: number;\n readonly cameraLocal?: readonly [number, number, number];\n readonly planBudget?: number;\n readonly planGeneration?: number;\n}\n","import {\n abortReason,\n buildHoldSwapGroups,\n buildSwapGroups,\n chunkBytes,\n isClassicLccSwapSet,\n enqueueClassicFetch,\n classicFetchPhaseForDesired,\n classicFetchPhaseForCoverage,\n compareClassicFetches,\n compareClassicSwapGroups,\n defaultCpuCacheBytes,\n groupPriority,\n isWaitingOnFinest,\n sliceSplatData,\n stampClassicFetchGroups,\n validateAppendCap,\n validateLodScale,\n type ClassicFetchWant,\n type SwapGroup,\n} from './streamed-splat-mesh-utils';\nexport * from './streamed-splat-mesh-utils';\nimport * as THREE from 'three/webgpu';\nimport {\n DEFAULT_FOVEATION_TARGET_PX,\n MAX_SH_BANDS,\n resolveSplatPerformanceProfile,\n SplatMesh,\n isPageTableFoveation,\n resolveSplatFoveationMode,\n type SplatRange,\n type SplatChannelType,\n type SplatChannelOptions,\n type SplatMeshOptions,\n type SplatUpdateOptions,\n} from '../core/splat-mesh';\nimport type { SplatData } from '../core/splat-data';\nimport { runKey, type LodRun, type LodScheduler } from './lod-scheduler';\nimport { buildSogScene, type StreamedScene } from './lod-source';\nimport type { CollisionMeshTile } from '../formats/lcc/collision-mesh';\nimport { createLocalDataset, httpDatasetSource, type SplatDatasetSource } from './dataset-source';\nimport {\n isAbortError,\n resolveSplatUrl,\n SplatLoadError,\n toRequestInit,\n toSplatLoadError,\n type SplatRequestOptions,\n type StreamedSplatFormat,\n} from '../loaders/loading';\nimport {\n liftBudgetToFinestLevel,\n recommendedRadMaxStdDev,\n resolveSplatBudget,\n type SplatDeviceProfile,\n} from '../core/splat-budget';\nimport { resolveXrView } from '../core/xr-view';\nimport { ChunkLoader } from '../loaders/chunk-loader';\nimport { yUpTransformForFormat } from '../core/orientation';\nimport {\n FRONTIER_FOVEATION_DEFAULTS,\n type FrontierFoveation,\n type FrontierPlanMessage,\n type FrontierRequest,\n type PlanSplats,\n} from '../formats/rad/frontier-worker-protocol';\nimport { shCoefficientCount } from '../core/sh-pack';\nimport { warn } from '../core/logging';\nimport type {\n ChunkFetchHandle,\n ChunkFetchKind,\n ChunkFetchScheduler,\n} from './chunk-fetch-scheduler';\nimport type { ChunkCacheBudget, ChunkCacheHandle } from './chunk-cache-budget';\n\n/** Vite's `?worker&inline` default export - a Worker subclass constructor. */\ntype InlineWorkerCtor = new () => Worker;\n\nconst DATA_TEXTURE_WIDTH = 2048;\n/** Max splats appended per frame (bounds the copy + staging-upload cost). */\n/**\n * A coverage hold waits for in-view covering cells (classic nearby L1 / far\n * coarsest, or the nearby L0 home set when `'hold-near-l0'` is explicit).\n * After one minute it reveals the best staged coverage and continues refining.\n */\nconst INITIAL_REVEAL_TIMEOUT_MS = 60_000;\n// Keep the attribution event aligned with WebGpuSortScheduler's content\n// invalidation policy: only a region-sized visibility change forces a sort.\nconst CONTENT_FORCE_FRACTION = 0.25;\n/**\n * Max chunk fetches in flight at once on the classic (non-page-table) path.\n * Matches the page-table pager so near-finest detail can fill the pipe instead\n * of waiting behind a long far-coarse pin queue.\n */\nconst MAX_INFLIGHT = 8;\n/**\n * Backstop on how long the wave gate may hold a retirement back.\n *\n * Pool pressure, not elapsed time, is what should release a retirement: the rows\n * it frees only matter once something else needs them, and that is exactly the\n * condition `applyGroup` reports. A tick bound on top of that trades coverage\n * for nothing, and measurably so - on the 132-chunk `oldtimers-route` capture,\n * bounds of 8/24/64 ticks left 157/69/35 frames losing coverage, while releasing\n * on pool pressure alone left 3, none worse than 0.38% of the drawn set (against\n * 258 frames and 2.14% before the gate). Short bounds are worse than no gate in\n * one respect too: they retire in bulk when they fire.\n *\n * So this is set well past the point of interference and kept only so that a\n * pool roomy enough never to report pressure cannot hold superseded coverage for\n * the entire session. At 60 fps it is about ten seconds.\n */\nconst MAX_RETIRE_HELD_TICKS = 600;\n/** Reschedule at least this often even when the camera is still, ms. */\nconst IDLE_RESCHEDULE_MS = 250;\n/** Attempts before a chunk is given up on (a transient error retries). */\nconst MAX_CHUNK_ATTEMPTS = 4;\n/** First retry delay; doubles each attempt (500, 1000, 2000 ms). */\nconst RETRY_BASE_MS = 500;\n/** Fetch slots the page-table sweep keeps free for frontier-requested chunks, so\n * the detail the camera is pointed at never queues behind a file-order sweep.\n * Matches Spark's `numLodFetchers`. */\nconst PAGETABLE_PRIORITY_SLOTS = 3;\n/** Default drawn-splat target for the page-table frontier (Spark's `maxSplats`)\n * when the caller gives no `foveationDrawBudget`. Sized to Spark's own default\n * for this class of scene - 800K is far too coarse for a 16M-leaf interior, so\n * the frontier coarsens and evicts near-camera detail to fit. Overridable via\n * `foveationDrawBudget` (`?foveationDraw=`), and always ≤ the pool budget. */\nconst PAGETABLE_DRAW_BUDGET = 4_000_000;\n\n/**\n * Splats per slab page in `foveationMode: 'page-table'`.\n *\n * The frontier's slots are backed by pages of this size rather than one\n * contiguous reservation, so a mesh's storage need not be one block - the\n * property that lets meshes interleave in a shared pool, and lets a mesh\n * release storage as its budget falls. Matches Spark's `pageSplats` and the\n * `.rad` chunk size, and is a whole number of 2048-texel pool rows (32), so\n * page writes stay row-aligned.\n */\nconst SLAB_PAGE_SPLATS = 65_536;\n\n/**\n * Ceiling on the page-table cache floor. A `.rad` frontier refines only into\n * chunks that are resident together, so a cache far smaller than the working set\n * thrashes and the view stays coarse - this is the headroom that prevents that,\n * for a scene big enough to need it.\n *\n * It is a *ceiling on a floor*, not a per-mesh allowance: `min(this, the\n * capture's own decoded size)` means a small mesh asks for what it can\n * actually use, and a host that set a larger share still gets it.\n */\nconst PAGETABLE_CACHE_FLOOR_BYTES = 2 * 1024 * 1024 * 1024;\n\n/**\n * Rough decoded size of a whole streamed scene, for sizing the cache floor:\n * positions (12 B) + colors (4 B) + covariances (24 B) per splat, plus the LOD\n * tree arrays a `.rad` chunk carries (`childCount` 4 B + `childStart` 4 B +\n * `size` 4 B) and packed SH when the scene carries it. Deliberately an\n * over-estimate - the floor should never be the reason a capture cannot hold\n * itself, and it must match what the frontier worker charges its cache, or a\n * capture the cap was sized to hold starts evicting itself mid-load.\n *\n * Exported for unit testing only; not part of the public API.\n */\nexport function estimateSceneDecodedBytes(scene: StreamedScene): number {\n const perSplat =\n 12 + 4 + 24 + 12 + (scene.shBands ? 16 * Math.ceil(shCoefficientCount(scene.shBands) / 4) : 0);\n // Size from what the cache actually holds: whole decoded *chunks*, every splat\n // in them. `contentSplatCount` is the wrong number for a LOD tree - for `.rad`\n // it is the **leaf** count, while a chunk carries internal (merged) nodes too,\n // and those are most of what a coarse frontier draws. On the 5.9M-leaf\n // reference capture the tree holds 8.59M nodes, so counting leaves alone\n // under-estimated by 32% and produced a floor *below* the frontier's working\n // set - the exact opposite of this function's purpose. The symptom was a\n // permanent 1-chunk oscillation: resident chunks alternating 75/76 with a\n // fetch every couple of seconds, the cache reporting full, and the frontier\n // refetching what it had just been forced to evict.\n //\n // Raising the ceiling does not by itself cost memory: it is a cap on a cache\n // that only ever holds what has been fetched, and with the background sweep\n // declined on mobile that is the working set and nothing more.\n //\n // Still approximate on the low side: a `.rad` chunk also carries the LOD tree\n // columns (`child_count` u16 + `child_start` u32, ~6 B/splat) which this does\n // not count. That was the last ~5% of the overshoot above - 76 chunks measured\n // ~229 MB against the old 224 MB floor - and the chunk-count fix now clears it\n // by a wide enough margin that adding the columns is not worth the extra\n // memory it would reserve on every device. Revisit if a capture thrashes with\n // resident chunks close to this estimate.\n const chunkSplats =\n scene.chunkSize === undefined ? undefined : scene.chunkSize * scene.chunkUrls.length;\n const splats = chunkSplats ?? scene.contentSplatCount ?? scene.maxResidentSplats;\n return Math.max(1, splats) * perSplat;\n}\n\n/**\n * Read-only startup-hold progress for {@link StreamedSplatMeshOptions.initialReveal}.\n * Exported for hosts that gate visibility on the first useful coverage frame\n * (classic `.lcc` nearby L1 / far coarsest, `.lcc2` in-view coarsest, or an\n * explicit nearby-L0 hold).\n */\nexport type InitialRevealState =\n | { readonly status: 'disabled' }\n | {\n readonly status: 'pending';\n readonly stagedSplats: number;\n readonly totalSplats: number;\n readonly readyGroups: number;\n readonly totalGroups: number;\n }\n | { readonly status: 'ready' }\n | {\n readonly status: 'degraded';\n readonly reason: 'capacity' | 'fetch-failed' | 'timeout';\n readonly stagedSplats: number;\n readonly totalSplats: number;\n readonly readyGroups: number;\n readonly totalGroups: number;\n };\n\n/** Options for {@link StreamedSplatMesh.load}. */\nexport interface StreamedSplatMeshOptions extends SplatMeshOptions {\n /** Active-splat budget. Defaults to {@link resolveSplatBudget}. */\n budget?: number;\n /**\n * A ceiling on the *resolved default* budget, for callers that want to\n * tighten without overriding what the library knows.\n *\n * `budget` is absolute: it wins over the device tier, the format's cost class\n * and everything else, because a caller who names a number has said they know\n * better. That is the right contract, and the wrong tool for \"the same as\n * usual, but no more than N\" - which is what a performance toggle or a host\n * default actually means. Pinning a number there has twice shipped as a bug:\n * a demo performance mode that *raised* the load on the weakest device tested,\n * and a host whose default bypassed every device tier.\n *\n * Applied only when `budget` is omitted, and only downward - it never raises\n * a budget the device would not otherwise have taken. It also suppresses the\n * finest-level lift, which exists to raise a budget far enough to hold a\n * scene whole and is exactly what \"no more than N\" rules out. Unrelated to\n * {@link maxBudget}, which sizes the pool and bounds\n * {@link StreamedSplatMesh.setBudget}.\n *\n * Forwarded to `resolveSplatBudget` as `SplatBudgetOptions.cap`.\n *\n * @throws {RangeError} at load if not a positive finite number.\n */\n budgetCap?: number;\n /**\n * Device signals for budget / quality defaults. Defaults to\n * {@link detectSplatDeviceProfile}. Pass a profile enriched with\n * {@link probeSplatGpuClass} so desktop integrated GPUs take the laptop\n * tier instead of the workstation 8M path.\n */\n deviceProfile?: SplatDeviceProfile;\n /**\n * Ceiling {@link StreamedSplatMesh.setBudget} may raise this mesh to, and the\n * size its pool is allocated from. Defaults to `budget`.\n *\n * Set this above `budget` when a `CameraBudgetGovernor` or `BudgetGovernor`\n * should be able to *grow* this mesh's share: the pool is allocated once at\n * construction and never grows, so without headroom reserved here a governed\n * mesh can only ever be shrunk below the budget it was built with. That is\n * the whole reason a hand-split `pool / N` mesh stays coarse near the\n * camera - every mesh's ceiling was fixed at a quarter of the pool.\n *\n * It is not free: the pool costs its *ceiling* in memory whether or not the\n * budget ever reaches it (~64 B of GPU pool plus ~56 B of CPU backing per\n * splat, 1.5× for capacity slack). Price it with `estimateSplatPoolBytes`\n * before choosing - for several additional meshes the\n * sum of the ceilings is what has to fit, not the shared budget. A ceiling\n * around 1.5–2× a member's fair share is usually the right trade.\n *\n * @throws {RangeError} at load if below `budget`, or not a positive finite\n * number.\n */\n maxBudget?: number;\n /**\n * Lets a host that pins {@link budget} and/or {@link maxBudget} still take the\n * finest-level lift for `.rad` strategy selection and pool sizing. Without it,\n * pinning either option disables the lift and a capture whose leaf count sits\n * between the host ceiling and {@link FOVEATION_LEAF_THRESHOLD} incorrectly\n * lands on the foveated page-table path instead of the prefix reader.\n *\n * {@link budgetCap} still vetoes the lift when set. Mobile and fill-constrained\n * desktops remain exempt inside {@link liftBudgetToFinestLevel}.\n */\n allowFinestLevelLift?: boolean;\n /**\n * Multiplier on this mesh's LOD detail, matching Spark's per-mesh `lodScale`:\n * `> 1` refines further (finer cut, more splats drawn), `< 1` coarsens.\n * Default `1`.\n *\n * **`.rad` `foveationMode: 'page-table'` only** - it scales the frontier cut\n * the page-table traversal is given (`pixel_scale × lodScale ≤ limit`, exactly\n * Spark's formula). It does nothing on a mesh with no per-splat cut to scale:\n * a moderate `.rad` read as a chunk prefix, or a Streamed SOG / LCC scene. For\n * the GPU cut modes (`'band'` / `'frontier'`) the equivalent is\n * {@link SplatMeshOptions.foveationTargetPx} at `1 / lodScale`.\n *\n * The draw budget still bounds the result, so raising this past the point\n * where the budget binds sharpens nothing - give the mesh budget as well.\n */\n lodScale?: number;\n /** Explicit format; by default the manifest's extension decides. */\n format?: StreamedSplatFormat;\n /** Serializable fetch settings for the manifest and its chunks. */\n request?: SplatRequestOptions;\n /**\n * Cancels the load: the manifest fetch aborts, and {@link StreamedSplatMesh.load}\n * rejects with a `DOMException` named `AbortError`. A mesh partially built\n * when the signal fires is disposed - nothing leaks. Only read during load;\n * later streaming is stopped by {@link SplatMesh.dispose}.\n */\n signal?: AbortSignal;\n /** Base URL a relative manifest URL resolves against (like {@link loadSplatData}). */\n baseUrl?: string | URL;\n /** World-unit distance inside which the finest LOD is used. Default 10. */\n lodBaseDistance?: number;\n /** Distance ratio between successive LOD levels. Default 2. */\n lodMultiplier?: number;\n /** Cap on decoded chunk arrays cached on the CPU. Default by device memory. */\n cpuCacheBytes?: number;\n /**\n * Foveation ramp for the `.rad` page-table frontier: detail is full inside\n * `coneFov0` degrees of the view direction, falls off to `coneFoveate` by\n * `coneFov`, and to `behindFoveate` directly behind the camera. Off-cone\n * content is kept **coarse**, never dropped, so turning or zooming out never\n * exposes an unpainted region. Defaults match Spark\n * ({@link FRONTIER_FOVEATION_DEFAULTS}).\n */\n frontierFoveation?: Partial<FrontierFoveation>;\n /**\n * Keeps a complete multi-run replacement hidden while its uploads are\n * spread over frames, then switches the region atomically. Enabled by\n * default; set `false` only for legacy A/B comparison.\n */\n experimentalStagedSwaps?: boolean;\n /**\n * Maximum splats copied into the pool per LOD mutation tick. Defaults to\n * 32,000; lower debug values trade refinement latency for shorter frames.\n */\n maxSplatsPerSwap?: number;\n /**\n * First-frame reveal policy for streamed formats that can hide empty cells.\n *\n * - `'progressive'`: cells become visible as each swap group commits — can\n * show sparse near-detail (classic `.lcc`) or empty octree squares\n * (`.lcc2`) while siblings load.\n * - `'hold-near-l0'` (opt-in): hide the mesh until the camera's home coverage\n * group is resident (L0 when it fits; otherwise coarsen via the leaf ladder\n * L1→L2). Neighbours are not part of the hold - they compete via\n * screenImportance and would steal the first fetch slots. Home selection\n * uses distance within `lodBaseDistance` and does not require frustum\n * intersection (HiRes tiles often fail `inView` when the camera stands\n * inside looking out). Coarser rungs come from `LodSource.runsAtLevelFor`.\n * Only home files are fetched during the hold. A one-minute watchdog also\n * degrades if the cut cannot finish. Classic `.lcc` uses the **resolved**\n * cut from the first schedule (after camera + format transform), not\n * distance ambition alone.\n * - `'hold-coverage'` (the default for classic `.lcc` and `.lcc2` when\n * unset): hide the mesh until every in-view finest cell has covering\n * coverage resident, and until the always-resident environment tile is in\n * the pool when the scene ships one and it starts enabled. Classic `.lcc`\n * freezes nearby cells (within `lodBaseDistance · lodMultiplier`) at\n * finest+1 (L1, never L0) and farther in-view cells at coarsest. A cell\n * counts as in-view when the camera stands inside it, or when the unpadded\n * AABB hits the frustum and pokes in front of the camera plane (support\n * vertex — centres behind the look still count), **or** the cell is within\n * `lodBaseDistance` and pokes forward (30 m neighbours that fill the\n * frame while the look is off-axis). `.lcc2` still waits on\n * coarsest root-children. Does not wait for finest tiles or the rest of\n * the stream. An empty frustum falls back to the nearest cell. Requires\n * `LodSource.coverageRunsFor`; other formats treat this as disabled.\n *\n * A one-minute watchdog degrades to progressive if the frozen set cannot\n * finish. Does not make detail downloads instantaneous. Other streamed\n * formats default to `'progressive'`.\n */\n initialReveal?: 'progressive' | 'hold-near-l0' | 'hold-coverage';\n /** Receives lightweight LOD mutation events for performance attribution. */\n onPerformanceEvent?: (event: StreamedSplatPerformanceEvent) => void;\n /**\n * View-dependent color (higher-order SH). This is the streaming counterpart\n * of {@link SplatMeshOptions.shBands}: besides sizing the pool it decides\n * whether SH is fetched/decoded at all. Two sources feed it - a `Quality` LCC\n * `Quality` LCC (`.lcc`) capture, which stores SH per splat, and a Streamed SOG scene,\n * whose per-file palette shN is converted to that same packed form at decode\n * (M11; see `docs/formats/streamed-shn-notes.md`).\n *\n * **For LCC, unset (the default) means every band the capture carries** - so\n * a Quality scene shows its real view-dependent color without the caller\n * having to know the format. The exception is a `smooth` performance profile\n * (the default on mobile), which defaults this to 0: SH roughly triples\n * per-chunk bandwidth (`shcoef.bin` is 64 B/splat against `data.bin`'s 32) and\n * adds up to 64 B/splat of pool textures (~384 MB over a 6M-splat pool at 3\n * bands) - precisely the costs that profile avoids.\n *\n * **For Streamed SOG it is strictly opt-in** (unset = off): the manifest does\n * not declare whether the tiles carry shN, so enabling the conversion - and\n * the pool textures it needs - must be a deliberate choice, not a default.\n *\n * Set it explicitly to override either way: 0 forces SH off, and 1, 2 or 3\n * keep 3, 8 or 15 coefficients per channel. For LCC the value is clamped to\n * what the scene actually has (a `Portable` capture fetches and allocates\n * nothing regardless); for SOG a scene with fewer bands zero-pads and one with\n * no shN simply renders DC color, wasting the allocated textures.\n *\n * Only read at load: the pool's SH textures are allocated once, so a later\n * {@link SplatMesh.setPerformanceProfile} does not change this.\n */\n shBands?: 0 | 1 | 2 | 3;\n /**\n * Whether the scene's always-resident environment/background tile (the\n * `.lcc2` sky) starts visible. Default `true`. Toggle it live afterwards with\n * {@link StreamedSplatMesh.setEnvironmentEnabled}. No effect on a scene that\n * ships no environment tile.\n */\n environmentEnabled?: boolean;\n /**\n * This mesh's share of the scene's fetch bandwidth, as a camera-projected\n * weight - normally `() => governor.weightOf(mesh) ?? 0`, so fetching is\n * ordered by the same measure that already orders drawing.\n *\n * Read on demand, so it always reflects the current camera. Zero means hidden\n * or suspended, and has one effect on its own: the background sweep that\n * pre-warms the whole capture into the page-table cache stops. That sweep is\n * pure speculation about a camera move that has not happened, and on a\n * multi-mesh scene it is most of the traffic competing with the mesh the\n * viewer is actually looking at.\n *\n * Unset (the default) leaves fetching exactly as it was: every mesh sweeps.\n * Supply a {@link fetchScheduler} as well to also bound the total.\n */\n fetchWeight?: () => number;\n /**\n * Scene-wide fetch arbitration, shared by every streamed mesh the way a\n * {@link SplatMeshOptions.pool} is - see {@link ChunkFetchScheduler}. Without\n * one, each mesh fetches toward its own in-flight cap and a near mesh's\n * detail queues behind a dozen far meshes' background traffic.\n *\n * The scheduler is *not* owned by the mesh: dispose unregisters this mesh and\n * leaves the scheduler running for its siblings. Weights come from\n * {@link fetchWeight}; without that every mesh weighs the same and the\n * scheduler only bounds the total.\n */\n fetchScheduler?: ChunkFetchScheduler;\n /**\n * Scene-wide decoded-chunk cache ceiling, shared exactly as\n * {@link fetchScheduler} and {@link SplatMeshOptions.pool} are - see\n * {@link ChunkCacheBudget}.\n *\n * Without one, each `.rad` page-table mesh caps its own cache at\n * `max(cpuCacheBytes, min(2 GiB, this capture's decoded size))`: the right\n * number for a lone streamed scene, and no bound at all across a scene of\n * additional meshes, because every mesh gets its own and each is sized to its own\n * capture. With one, that figure becomes this mesh's *ceiling* and the budget\n * splits a scene total across every registered mesh by camera weight.\n *\n * This bounds retention, not prefetching: the background sweep still runs and\n * still warms the cache, it just stops at the scene's allowance instead of at\n * the size of the capture.\n *\n * The budget is *not* owned by the mesh: dispose unregisters this mesh and\n * leaves it running for its siblings. Weights come from {@link fetchWeight}.\n */\n cacheBudget?: ChunkCacheBudget;\n}\n\n/** One streamed-LOD mutation tick, measured on the main thread. */\nexport interface StreamedSplatPerformanceEvent {\n /** Timestamp after the tick, on the same clock as requestAnimationFrame. */\n timestamp: number;\n /** Main-thread time spent rescheduling and applying this tick. */\n cpuMs: number;\n /** Same-frame packed active-index rebuild time, after the LOD mutation. */\n activeListMs: number;\n /** Same-frame partial texture upload submission time. */\n uploadMs: number;\n /** CPU submission time for the depth-sort passes. */\n sortSubmitMs: number;\n /** Exact-height staging textures allocated during this update. */\n stagingTextureAllocations: number;\n /** WebGPU source-index ranges queued for upload before this tick's sort. */\n activeListUpdateRanges: number;\n appendedCount: number;\n removedCount: number;\n stagedCount: number;\n uploadCount: number;\n activeCount: number;\n forcedSort: boolean;\n compacted: boolean;\n}\n\ninterface CachedChunk {\n data: SplatData;\n bytes: number;\n lastUsed: number;\n}\n\n/** Options for {@link StreamedSplatMesh.definePersistentChannel}. */\nexport interface PersistentChannelOptions extends SplatChannelOptions {\n /**\n * Cap on the number of `(chunk, splat)` edits stored for this channel.\n * Editing past the cap is dropped with a one-time warning. Default 1,000,000.\n */\n maxEdits?: number;\n}\n\n/** A per-channel sparse edit store, keyed by `(chunk file, local index)`. */\ninterface PersistentChannel {\n readonly type: SplatChannelType;\n /** The channel's default value - unedited splats must reload at this, not 0. */\n readonly fill: number;\n readonly maxEdits: number;\n /** file → (local splat index within that chunk → value). */\n readonly edits: Map<number, Map<number, number>>;\n total: number;\n warned: boolean;\n}\n\n/**\n * Streams a large splat scene - a Streamed SOG dataset (`lod-meta.json`), or\n * an XGRIDS `.lcc2` or `.lcc` (manifest v3–v5) dataset - into the pool of a\n * dynamic-capacity {@link SplatMesh}, keeping the resident splat count within\n * a per-device budget.\n *\n * Each frame it asks the scene's {@link LodSource} which spatial regions\n * should be resident for the current camera, fetches and decodes the chunk\n * files that back them (off the main thread, via {@link ChunkLoader}), and\n * appends/removes pool ranges to match - loading coarse first so the scene\n * appears quickly and refining near the camera. A coarse full-scene shell\n * always fits the budget, so the view is never blank and the budget is\n * never exceeded.\n *\n * WebGPU only (inherited from the dynamic-capacity pool). View-dependent color\n * (higher-order SH) works for every streamed format: LCC `Quality` captures store it per\n * splat, and a SOG scene's per-file palette shN is converted to that same\n * per-splat packed form at decode so it too survives the shared pool (M11, opt\n * in via {@link StreamedSplatMeshOptions.shBands}; see\n * `docs/formats/streamed-shn-notes.md`).\n */\nexport class StreamedSplatMesh extends SplatMesh {\n private readonly scene: StreamedScene;\n private readonly loader = new ChunkLoader();\n /**\n * Cap the *classic* (non-page-table) chunk cache evicts against.\n *\n * Mutable because a shared {@link ChunkCacheBudget} re-splits it as the camera\n * moves; without a budget it stays at the value `options.cpuCacheBytes` or the\n * device default set at construction.\n */\n private cpuCacheBytes: number;\n private budgetValue: number;\n private readonly maximumBudget: number;\n /** Spark's per-mesh `lodScale`; divides the page-table cut limit. */\n private lodScaleValue: number;\n /** Set once the governed budget has been reported as exceeding an explicit\n * `foveationDrawBudget`, so the warning is issued at most once. */\n private warnedDrawTargetCap = false;\n private readonly stagedSwapsEnabled: boolean;\n /** Classic LCC must keep old cell coverage while a replacement is pending. */\n private readonly neverRetireCoverageEarly: boolean;\n private readonly appendCap: number;\n private readonly onPerformanceEvent: ((event: StreamedSplatPerformanceEvent) => void) | undefined;\n private compactionCount = 0;\n\n private readonly cache = new Map<number, CachedChunk>();\n /** Running byte total of {@link cache}; maintained by {@link cacheChunk}, eviction and dispose. */\n private cacheBytesTotal = 0;\n /** In-flight chunk fetches. The kind is kept so a weight change can shed the\n * speculative ones without touching the detail that is actually on screen. */\n private readonly fetching = new Map<\n number,\n { controller: AbortController; kind: ChunkFetchKind; classicWant?: ClassicFetchWant }\n >();\n /** This mesh's camera-projected share of the scene's fetch bandwidth. */\n private fetchWeight: (() => number) | undefined;\n /** Scene-wide fetch arbitration, when the host shares one; see `requestChunk`. */\n private readonly fetchScheduler: ChunkFetchScheduler | undefined;\n private readonly fetchHandle: ChunkFetchHandle | undefined;\n /**\n * Blob-URL dataset from {@link loadLocal}, owned by this mesh so its object\n * URLs are revoked on {@link dispose} rather than leaking for the document's\n * lifetime. Undefined for every network-loaded mesh.\n */\n private localSource: SplatDatasetSource | undefined;\n /** Scene-wide chunk-cache ceiling, when the host shares one. */\n private readonly cacheBudget: ChunkCacheBudget | undefined;\n private cacheBudgetHandle: ChunkCacheHandle | undefined;\n /**\n * The cap this mesh's frontier worker is currently evicting against.\n *\n * Mirrored on the main thread so `applyCacheAllowance` can skip no-op posts\n * and so `fetchCounts.cacheLimitBytes` stays truthful between plans.\n */\n private cacheLimitBytes = 0;\n private readonly resident = new Map<string, { run: LodRun; handle: SplatRange }>();\n /** Replacement runs hidden while their pool data is uploaded in bounded segments. */\n private readonly staged = new Map<\n string,\n {\n run: LodRun;\n handle: SplatRange;\n uploadedCount: number;\n }\n >();\n /** Files awaiting a backoff retry after a transient fetch/decode error. */\n private readonly retrying = new Map<number, { attempts: number; readyAt: number }>();\n /** Files given up on after {@link MAX_CHUNK_ATTEMPTS} failures. */\n private readonly failedFiles = new Set<number>();\n\n /**\n * When true, each resident run writes its LOD `level` into the `lodLevel`\n * float channel for false-color debug modifiers.\n */\n private lodLevelDebug = false;\n private lodLevelChannelReady = false;\n private lodLevelScratch: Float32Array | undefined;\n\n /** Desired-but-not-resident files this tick; protected from cache eviction. */\n private readonly neededFiles = new Set<number>();\n\n /** Non-null in `foveationMode: 'page-table'`: the worker that owns the chunk\n * cache + traversal + pager off the main thread, and the always-active slab it\n * pages the returned frontier into. */\n private readonly frontierWorker: Worker | null;\n /**\n * The frontier's slots, as a list of equally sized pages rather than one\n * contiguous run.\n *\n * Slot `i` lives in page `i / slabPageSplats` at offset `i %\n * slabPageSplats`. The pager only ever addresses slots, so where those\n * pages sit in the pool is the mesh's business - which is what lets a mesh\n * hold non-contiguous storage, and ultimately lets several meshes interleave\n * in one pool instead of each reserving its whole ceiling as one block.\n * (Spark's pager does the same thing one level down, binding fixed pages to\n * `(source, chunk)` pairs.)\n */\n private readonly slabPages: SplatRange[] = [];\n /** Most slots the slab may ever hold - the construction capacity. */\n private slabCeiling = 0;\n /** Slot count the worker's pager was last told about. */\n private pagerSlots = 0;\n /** Consecutive ticks the wave gate has held retirements back. */\n private retireHeldTicks = 0;\n /** Backing store for {@link planTimings}. */\n private readonly planTimingsValue = {\n applyMs: 0,\n worstApplyMs: 0,\n writeMs: 0,\n residentMs: 0,\n moves: 0,\n appends: 0,\n worstSplats: 0,\n };\n /**\n * Backing store for {@link fetchCounts}. Lifetime totals, because the question\n * they answer is about a *steady state* - \"this keeps streaming after the view\n * settled\" - which a per-frame or windowed number cannot express.\n */\n private readonly fetchCountsValue = {\n priority: 0,\n base: 0,\n sweep: 0,\n evicted: 0,\n uncovered: 0,\n retiredEarly: 0,\n cacheFull: false,\n cacheBytes: 0,\n cacheLimitBytes: 0,\n };\n /**\n * The screen-radius band the scene asked for, kept so the band can be scaled\n * with the solved frontier cut and always relative to the original - scaling\n * the live values repeatedly would drift. Null when the scene has no band.\n */\n private readonly frontierBandBase: { min: number; max: number } | null = null;\n /**\n * Splats per slab page for this mesh: {@link SLAB_PAGE_SPLATS}, or the whole\n * capacity when that is smaller. Spark can use one fixed page size because\n * its pool is a single large arena; here a mesh may be smaller than a page,\n * and rounding it up to one would waste most of the reservation.\n */\n private readonly slabPageSplats: number = SLAB_PAGE_SPLATS;\n /** Target drawn-splat count for the page-table frontier; see the constructor. */\n private pageTableDrawBudget = 0;\n /** The unclamped draw target (`foveationDrawBudget` or the default), kept so\n * `setBudget` can re-derive the effective draw budget when the pool budget\n * moves (e.g. under a `BudgetGovernor`). */\n private pageTableDrawTarget = 0;\n /** Whether {@link pageTableDrawTarget} came from an explicit\n * `foveationDrawBudget` - a caller-chosen hard cap worth warning about when a\n * governed budget outgrows it, rather than the library's own default. */\n private pageTableDrawTargetExplicit = false;\n /** Last frontier's drawn (non-degenerate) splat count - the true on-screen size\n * in `page-table` mode, where the slab is fully \"active\" but mostly degenerate. */\n private pageTableDrawn = 0;\n private frontierConverged = true;\n private pendingFrontierSplats = 0;\n private staleResidentSplats = 0;\n private lastPlanAppends = 0;\n private lastPlanMoves = 0;\n private lastPlanGeneration = 0;\n private lastPlanBudget = 0;\n private lastPlanCamera: readonly [number, number, number] | null = null;\n private firstFrontierCamera: readonly [number, number, number] | null = null;\n /** Monotonic reschedule id; a stale plan (superseded by a newer request) is\n * dropped. `pageTableInFlight` coalesces to one outstanding traversal. */\n private pageTableSeq = 0;\n private pageTableInFlight = false;\n private pageTableDisposed = false;\n /** Files whose data has been forwarded to the worker (so we don't refetch). */\n private readonly pageTableCachedFiles = new Set<number>();\n /** Chunks the last frontier wanted but did not have, biggest-on-screen first.\n * These outrank the background sweep - they are the detail actually on screen. */\n private pageTableFetchPriority: readonly number[] = [];\n /** Frontier-cut target node size (px) and foveation ramp; see `frontierView`. */\n private pageTableTargetPx = DEFAULT_FOVEATION_TARGET_PX;\n private pageTableFoveation: FrontierFoveation = FRONTIER_FOVEATION_DEFAULTS;\n /** Drawing-buffer height, sampled in `update` so `reschedule` can derive the\n * cut limit the same way the material does (`targetPx / focalY`). */\n private pageTableViewportY = 0;\n /** Frontier cut on foveated `size / distance`. Re-derived each reschedule once\n * the drawing buffer is known; the initial value only covers the first frame. */\n private pageTableLimit = 0.02;\n /**\n * Whether the worker's cache is sitting at its cap: sweeping past that point\n * only evicts what the frontier is using.\n *\n * Re-derived from every plan rather than latched. It used to latch on the\n * first eviction, which was safe only while the cap was sized to the capture\n * and evictions therefore meant \"this will never fit\". Under a scene-wide\n * {@link ChunkCacheBudget} evictions are routine - a far mesh gives bytes back\n * and is trimmed - and latching would kill its sweep for the session, so a\n * mesh that went cold could never re-warm when the camera returned.\n */\n private pageTableCacheAtLimit = false;\n\n /** Per-splat channels whose edits survive chunk eviction/reload (M7.6). */\n private readonly persistentChannels = new Map<string, PersistentChannel>();\n\n /** Chunk-file index of the always-resident environment tile, if the scene ships one. */\n private readonly envFile: number | undefined;\n /** Whether the environment tile should be visible; toggled live. */\n private envEnabled: boolean;\n /** Pool handle of the environment tile once it has loaded (kept for toggling). */\n private envHandle: SplatRange | undefined;\n /** Env splat count, measured when the tile decodes; 0 until then. */\n private envSplatCount = 0;\n /** Set when the env tile is larger than the whole pool - terminal, warned once. */\n private envUnfit = false;\n\n /**\n * Startup hold. `'capture'` waits for the first schedule after the host\n * applies the final camera; `'holding'` freezes that coverage set.\n */\n private initialRevealPhase: 'off' | 'capture' | 'holding' | 'released' = 'off';\n /** Which hold, if any, was armed at construction. Survives release for recapture. */\n private readonly initialRevealHold: 'off' | 'hold-near-l0' | 'hold-coverage' = 'off';\n /** Frozen nearby-detail / in-view coverage runs for {@link initialRevealPhase} `'holding'`. */\n private frozenCriticalRuns: LodRun[] | null = null;\n /** Timestamp of the final-camera capture that began the current hold. */\n private initialRevealStartedAt: number | undefined;\n private initialRevealStateValue: InitialRevealState = { status: 'disabled' };\n\n /** Fetch settings this mesh was loaded with, reused for collision meshes. */\n private readonly requestOptions: SplatRequestOptions | undefined;\n /** In-flight or settled collision load; see {@link loadCollisionMeshes}. */\n private collisionTiles: Promise<readonly CollisionMeshTile[]> | undefined;\n private collisionAbort: AbortController | undefined;\n\n private pendingWork = true;\n private lastScheduleTime = -Infinity;\n /** Reused leaf-coverage bitmap for {@link substituteCoverage}; grows only. */\n private coverageScratch: Uint8Array | undefined;\n private readonly lastCameraPos = new THREE.Vector3(Infinity, Infinity, Infinity);\n private readonly lastCameraQuat = new THREE.Quaternion();\n\n /**\n * Fetches a scene manifest and prepares a mesh sized to the budget.\n * Accepts a Streamed SOG manifest (`lod-meta.json`) or an XGRIDS `.lcc2` or\n * `.lcc` (manifest v3–v5) dataset - all stream through the same machinery. Both LCC\n * generations are normalized to the established XGRIDS/Spark Three.js\n * coordinate frame; streamed SOG orientation is unchanged.\n *\n * A `.lcc` dataset needs a server that answers HTTP range requests: its\n * splats live in one large `data.bin` that is never fetched whole.\n *\n * @param manifestUrl - URL of the scene's `lod-meta.json`, `.lcc2` or `.lcc`\n * file; relative URLs resolve against `options.baseUrl` (or the page).\n * @throws Rejects with {@link SplatLoadError} on any resolve/fetch/parse\n * failure, or a `DOMException` named `AbortError` when `options.signal` fires.\n */\n static async load(\n manifestUrl: string | URL,\n options: StreamedSplatMeshOptions = {},\n ): Promise<StreamedSplatMesh> {\n const absoluteUrl = resolveSplatUrl(manifestUrl, options.baseUrl).href;\n const lower = absoluteUrl.toLowerCase();\n const format: Exclude<StreamedSplatFormat, 'auto'> =\n options.format !== undefined && options.format !== 'auto'\n ? options.format\n : lower.endsWith('.lcc2')\n ? 'lcc2'\n : lower.endsWith('.lcc')\n ? 'lcc'\n : lower.endsWith('.rad')\n ? 'rad'\n : 'streamed-sog';\n return StreamedSplatMesh.fromSource(\n httpDatasetSource(absoluteUrl, options.request),\n format,\n options,\n );\n }\n\n /**\n * Prepares a mesh from a folder dropped into the page - the same streamed\n * formats, read straight off the user's disk with no server and no upload.\n *\n * Every file becomes a `blob:` URL, which answers range requests exactly as\n * an HTTP origin does, so a multi-hundred-megabyte `.lcc` `data.bin` streams\n * chunk-by-chunk rather than being read whole.\n *\n * @param files - The folder's files, keyed by path relative to its root\n * (as the demo drop-zone `readDirectory` walk produces).\n * @throws Rejects with {@link SplatLoadError} - phase `'manifest'` when the\n * folder holds no (or more than one) recognizable scene manifest - or a\n * `DOMException` named `AbortError` when `options.signal` fires.\n */\n static async loadLocal(\n files: ReadonlyMap<string, File>,\n options: StreamedSplatMeshOptions = {},\n ): Promise<StreamedSplatMesh> {\n let dataset: ReturnType<typeof createLocalDataset>;\n try {\n dataset = createLocalDataset(files);\n } catch (error) {\n // Not `toSplatLoadError`: a folder without a manifest is not retryable.\n throw error instanceof SplatLoadError\n ? error\n : new SplatLoadError(error instanceof Error ? error.message : String(error), {\n phase: 'manifest',\n url: 'local-folder',\n retryable: false,\n cause: error,\n });\n }\n try {\n const mesh = await StreamedSplatMesh.fromSource(dataset.source, dataset.format, options);\n // Hand ownership to the mesh rather than disposing here: a streamed mesh\n // keeps fetching chunk URLs for its whole life, so revoking now would\n // break it. Without this the blob URLs (and the `File` blobs they pin)\n // stayed registered for the document's lifetime - `dispose` was reachable\n // only from the catch below, i.e. only when the load *failed*.\n mesh.localSource = dataset.source;\n return mesh;\n } catch (error) {\n dataset.source.dispose(); // release the blob URLs this drop created\n throw error;\n }\n }\n\n /** Shared load path: fetch the manifest from a source, then build the scene. */\n private static async fromSource(\n source: SplatDatasetSource,\n format: Exclude<StreamedSplatFormat, 'auto'>,\n options: StreamedSplatMeshOptions,\n ): Promise<StreamedSplatMesh> {\n // `format` is what makes this per-scene rather than per-device: an LCC-class\n // capture's splats grow as its budget tightens, so the two classes want\n // different ceilings on the same phone. An explicit `budget` still wins;\n // `budgetCap` tightens the resolved default without replacing it.\n const deviceProfile = options.deviceProfile;\n const deviceBudget = resolveSplatBudget(options.budget, deviceProfile, {\n format,\n ...(options.budgetCap === undefined ? {} : { cap: options.budgetCap }),\n });\n // `maxBudget` separates two things the budget used to conflate: what the\n // mesh renders now, and the most it could ever be asked to render. The pool\n // is sized from the ceiling (it cannot grow later), so a governed mesh has\n // somewhere to grow into; without one the two are equal and every existing\n // caller behaves exactly as before.\n const ceilingBudget =\n options.maxBudget === undefined\n ? deviceBudget\n : resolveSplatBudget(options.maxBudget, deviceProfile);\n if (ceilingBudget < deviceBudget) {\n throw new RangeError(\n `StreamedSplatMesh: maxBudget (${ceilingBudget}) must be >= budget (${deviceBudget}).`,\n );\n }\n // `.rad` now defaults to the `page-table` selected-index pager, which pages only\n // the *selected* frontier (Spark's model) and wants the full device budget -\n // Spark runs this scene at ~4M. (The old 2.5M `RAD_PREFIX_DEFAULT_BUDGET` cap\n // was a fallback for the whole-scene prefix reader before the pager landed;\n // capping the frontier at 2.5M starves it and it stays coarse near the camera.)\n //\n // The scene is built against the *ceiling*: a foveated `.rad` reports\n // `maxResidentSplats: options.budget`, and that number caps the pool below -\n // so seeding it with the initial budget would undo the headroom. The source's\n // live budget is overwritten with the initial value once the scene exists.\n const sourceOptions = {\n budget: ceilingBudget,\n lodBaseDistance: options.lodBaseDistance ?? 10,\n lodMultiplier: options.lodMultiplier ?? 2,\n };\n // Unset means \"every band the capture carries\", so a Quality scene shows\n // its real colors without the caller knowing the format - except on a\n // `smooth` profile (the default on mobile), where the bandwidth and the\n // ~64 B/splat of extra pool textures are exactly what that profile exists\n // to avoid. The scene then clamps this to what the file actually has.\n const shBands =\n options.shBands ??\n (resolveSplatPerformanceProfile(options.performanceProfile, deviceProfile) === 'smooth'\n ? 0\n : MAX_SH_BANDS);\n // Resolved once here rather than at the options bag below, so the device is\n // probed a single time per load.\n const radMaxStdDev = recommendedRadMaxStdDev(deviceProfile);\n // Whether the finest-level lift below may raise this mesh's budget. A caller\n // that named a size gets that size - see the `ceiling` computation. `.rad`\n // needs to know up front, because the lift decides whether its leaves fit\n // the budget and therefore whether it reads as a prefix or foveates.\n //\n // `budgetCap` counts as naming a size for this purpose even though it is\n // only a ceiling: the lift raises the budget to hold a finest level whole,\n // which is exactly what a caller asking for \"no more than N\" has ruled out.\n // Without this a desktop performance mode would lift straight back over its\n // own cap, to as much as `FINEST_LEVEL_BUDGET_MAX`.\n const budgetLifts =\n options.allowFinestLevelLift === true\n ? options.budgetCap === undefined\n : options.budget === undefined &&\n options.maxBudget === undefined &&\n options.budgetCap === undefined;\n\n // A `.rad` \"manifest\" is the file's own binary header, read by range - it\n // must not be fetched whole (it is the multi-hundred-megabyte scene) or\n // JSON-parsed like the other formats' manifests.\n const signal = options.signal;\n signal?.throwIfAborted();\n let scene: StreamedScene;\n if (format === 'rad') {\n // The manifest here is the `.rad` file's own header (ranged reads).\n // Contract: only SplatLoadError or AbortError leaves this path.\n try {\n const { buildRadScene } = await import('../formats/rad');\n // `shBands` is a *cap* here: a `.rad` declares its own `maxSh`, so the\n // resolved value decides how much of it to keep. Passing it is what lets\n // the `smooth` profile (and an explicit `shBands: 0`) decline SH on a\n // `.rad` at all - without it the file's bands were adopted wholesale.\n scene = await buildRadScene(source, sourceOptions, options.request, shBands, budgetLifts);\n } catch (error) {\n if (isAbortError(error)) throw error;\n throw toSplatLoadError(error, { phase: 'manifest', url: source.manifestUrl });\n }\n } else {\n let response: Response;\n try {\n response = await fetch(source.manifestUrl, toRequestInit(options.request, signal));\n } catch (error) {\n // A raw fetch TypeError (network/CORS) must not escape unwrapped.\n if (isAbortError(error)) throw error;\n throw toSplatLoadError(error, { phase: 'fetch', url: source.manifestUrl });\n }\n if (!response.ok) {\n throw toSplatLoadError(\n new Error(`Failed to load manifest ${source.manifestUrl}: HTTP ${response.status}`),\n { phase: 'manifest', url: source.manifestUrl, status: response.status },\n );\n }\n try {\n // `response.json()` is typed `any`; the parsers below validate it.\n const json: unknown = await response.json();\n if (format === 'lcc2') {\n // Import the public format entry rather than an internal chunk. Rollup may\n // represent internal chunks through synthetic namespace exports, which a\n // consuming production build can incorrectly tree-shake while rebundling.\n const { buildLcc2Scene } = await import('../formats/lcc');\n // LCC2 tiles are SOG v2; SH is opt-in like Streamed SOG (tiles may be DC-only).\n scene = buildLcc2Scene(json, source, sourceOptions, options.shBands ?? 0);\n } else if (format === 'lcc') {\n const { buildLccScene } = await import('../formats/lcc');\n scene = await buildLccScene(json, source, { ...sourceOptions, shBands });\n } else {\n // Streamed SOG SH is strictly opt-in: unlike LCC (whose manifest\n // states its band count), a SOG manifest never says whether the\n // tiles carry shN, so the \"every band the capture carries\" default\n // cannot apply - an explicit `shBands` turns it on.\n scene = buildSogScene(json, source, sourceOptions, options.shBands ?? 0);\n }\n } catch (error) {\n if (isAbortError(error)) throw error;\n throw toSplatLoadError(error, { phase: 'manifest', url: source.manifestUrl });\n }\n }\n // Aborted while the manifest was in flight or parsing: nothing built yet.\n signal?.throwIfAborted();\n\n // An LCC capture offers only its finest level (see `buildLccScene`),\n // so a budget below it does not soften the scene - it deletes whole 30 m\n // cells. Take the level whole when it is small enough to be worth it. A\n // `.rad` refines uniformly (no camera foveation - its chunk DAG is too\n // entangled for a chunk-cut, see `docs/formats/rad-notes.md`), so a budget below the\n // leaf count leaves coarse blobs *everywhere*, worst up close; lifting to the\n // full leaf set when it fits makes a moderate scene sharp. An explicit budget\n // is a hard cap for A/B runs and always wins.\n //\n // The lift raises the *ceiling*, since that is what sizes the pool, and with\n // nothing pinned the initial budget rides up with it - the established\n // behavior. A host that pinned `maxBudget` gets exactly that ceiling and no\n // more: it asked for a specific memory envelope, and silently allocating\n // past it would be the one surprise this option must not spring.\n // `budgetLifts` is the same predicate `buildRadScene` was handed above, so\n // the path it chose and the budget applied here cannot disagree.\n const ceiling =\n (format === 'lcc' || format === 'rad') && budgetLifts\n ? liftBudgetToFinestLevel(ceilingBudget, scene.maxResidentSplats, deviceProfile)\n : ceilingBudget;\n const budget = options.maxBudget === undefined ? ceiling : Math.min(deviceBudget, ceiling);\n scene.source.budget = budget;\n\n // Pool capacity: 40% over whatever can actually be resident - the ceiling,\n // or the finest level if the whole scene is smaller than it. The\n // slack absorbs per-run row-alignment waste (hundreds of runs each waste\n // up to a row) and the append-before-remove window during LOD swaps;\n // too little slack makes small-budget swaps converge slowly under\n // capacity pre-check pressure. ~64 B/splat of GPU memory.\n const residentCeiling = Math.min(ceiling, scene.maxResidentSplats);\n // Inactive staging must temporarily hold both sides of a large atomic\n // replacement. Ten extra percentage points avoid full-pool compaction on\n // the measured 1.6M-splat restaurant swap (~22 MB at a 3.5M budget).\n const capacityFactor = options.experimentalStagedSwaps !== false ? 1.5 : 1.4;\n const capacityRows = Math.max(\n 1,\n Math.ceil((residentCeiling * capacityFactor) / DATA_TEXTURE_WIDTH),\n );\n\n // Resolve page-table worker before construction (constructors cannot await).\n // SOG/LCC hosts never pay for the frontier worker blob.\n const resolvedFoveationMode = scene.foveation\n ? resolveSplatFoveationMode(\n options.foveationMode,\n format === 'rad' ? 'page-table' : 'frontier',\n )\n : options.foveationMode === undefined\n ? undefined\n : resolveSplatFoveationMode(options.foveationMode);\n let FrontierWorkerCtor: InlineWorkerCtor | undefined;\n if (isPageTableFoveation(resolvedFoveationMode)) {\n const mod = await import('../formats/rad/frontier-worker?worker&inline');\n FrontierWorkerCtor = mod.default;\n signal?.throwIfAborted();\n // The worker cuts the tree itself; per-splat `parent_size` (a GPU-cut input)\n // would be computed for every chunk and never read. See `needsParentSizes`.\n const source = scene.source as { needsParentSizes?: boolean };\n if (source.needsParentSizes !== undefined) source.needsParentSizes = false;\n }\n\n // The scene decides the effective bands: asking for SH on a capture that\n // has none must not allocate SH textures for it.\n const mesh = new StreamedSplatMesh(\n scene,\n budget,\n capacityRows * DATA_TEXTURE_WIDTH,\n {\n ...options,\n // Classic `.lcc` and `.lcc2` wait for in-view coverage so first paint\n // has no empty cells (classic nearby cells at L1, farther at coarsest).\n // Keep every other format progressive, and let a caller explicitly\n // request progressive or hold-near-l0.\n ...((format === 'lcc' || format === 'lcc2') && options.initialReveal === undefined\n ? { initialReveal: 'hold-coverage' as const }\n : {}),\n // The resolved ceiling, not the caller's raw option: it may have been\n // lifted to a moderate capture's leaf count above.\n maxBudget: ceiling,\n // This line overrides `...options` above, so a declined request has to\n // survive it - that is how `.rad` came to ignore both the `smooth`\n // profile and an explicit `shBands: 0`. Only *zero* is re-applied here,\n // never a partial reduction: the builders already honour partial\n // requests by generating that many bands, whereas forcing a smaller\n // count past one would mismatch the decoded chunk and degrade to\n // neutral SH (see `SplatMesh.writePackedSh`).\n shBands: shBands === 0 ? 0 : (scene.shBands ?? 0),\n // Spark ships Mip-Splatting antialiasing ON (blurAmount 0.3 *with* opacity\n // compensation `α·√(detRaw/detBlur)`). Match that default for `.rad`: the\n // 0.3 low-pass without the compensation makes splats too opaque (uniform\n // blur) and leaves anisotropic splats bright (needle spikes).\n antialias: options.antialias ?? (format === 'rad' ? true : undefined),\n // Older XGRIDS LCC uses a smaller, compensated projected low-pass.\n ...(format === 'lcc' ? { projectedFilterProfile: 'lcc' as const } : {}),\n // Match Spark's `.rad` render exactly: the LOD alpha encoding + merged-node\n // σ-cutoff/super-Gaussian, and the √8 (≈2.83σ) base cutoff Spark defaults to.\n // An explicit `lodAlpha` (e.g. `?lodAlpha=0`) wins for A/B.\n //\n // The √8 cutoff is *desktop only* - see `recommendedRadMaxStdDev`, which\n // returns undefined on mobile so the `SplatMesh` constructor applies the\n // same 4 ceiling `.rad` was the only format escaping. An explicit\n // `maxStdDev` still wins, through `...options` above.\n ...(format === 'rad'\n ? {\n lodAlpha: options.lodAlpha ?? true,\n ...(options.maxStdDev === undefined && radMaxStdDev !== undefined\n ? { maxStdDev: radMaxStdDev }\n : {}),\n }\n : {}),\n // A foveated scene renders whole chunks and picks the LOD cut per splat.\n // `.rad` defaults to Spark's selected-index page table (only the frontier is\n // paged to the GPU, so the whole device budget buys on-screen detail); other\n // foveated formats keep the GPU `frontier` cut. `foveationMode: 'band'` (or\n // `'frontier'`) forces the legacy paths for A/B. Overrides any caller blob cull.\n ...(scene.foveation\n ? {\n foveationMode: resolvedFoveationMode,\n minSplatScreenRadius: scene.foveation.minScreenRadiusPx,\n maxSplatScreenRadius: scene.foveation.maxScreenRadiusPx,\n }\n : {}),\n },\n FrontierWorkerCtor,\n format === 'lcc',\n );\n // LCC carries its Z-up→Y-up matrix in both orientation modes (format\n // semantics); streamed SOG and Spark `.rad` get the cosmetic 180°-X flip in\n // 'y-up', matching Spark's documented OpenCV→OpenGL scene correction.\n const correction =\n scene.formatTransform ?? (mesh.orientation === 'y-up' ? yUpTransformForFormat(format) : null);\n if (correction) {\n mesh.matrix.copy(correction);\n mesh.matrix.decompose(mesh.position, mesh.quaternion, mesh.scale);\n mesh.matrixWorldNeedsUpdate = true;\n }\n // A last-instant abort must not leak the mesh (its loader worker, frontier\n // worker, and pool textures) - dispose it and reject like every other abort.\n if (signal?.aborted) {\n mesh.dispose();\n signal.throwIfAborted();\n }\n return mesh;\n }\n\n private constructor(\n scene: StreamedScene,\n budget: number,\n capacity: number,\n options: StreamedSplatMeshOptions,\n FrontierWorkerCtor?: InlineWorkerCtor,\n neverRetireCoverageEarly = false,\n ) {\n super({ capacity }, options);\n this.scene = scene;\n this.budgetValue = budget;\n // The pool was allocated for the ceiling, so `setBudget` may climb to it.\n // Never below `budget` - that would make the mesh's own starting budget\n // unreachable.\n this.maximumBudget =\n options.maxBudget === undefined\n ? budget\n : Math.max(budget, resolveSplatBudget(options.maxBudget));\n this.lodScaleValue = validateLodScale(options.lodScale);\n this.stagedSwapsEnabled = options.experimentalStagedSwaps !== false;\n this.neverRetireCoverageEarly = neverRetireCoverageEarly;\n this.appendCap = validateAppendCap(options.maxSplatsPerSwap);\n const holdCoverage =\n options.initialReveal === 'hold-coverage' && this.scene.source.coverageRunsFor !== undefined;\n const holdNearL0 = options.initialReveal === 'hold-near-l0' && neverRetireCoverageEarly;\n if (holdCoverage || holdNearL0) {\n this.initialRevealHold = holdCoverage ? 'hold-coverage' : 'hold-near-l0';\n this.initialRevealPhase = 'capture';\n this.initialRevealStateValue = {\n status: 'pending',\n stagedSplats: 0,\n totalSplats: 0,\n readyGroups: 0,\n totalGroups: 0,\n };\n } else {\n this.initialRevealHold = 'off';\n this.initialRevealPhase = 'off';\n this.initialRevealStateValue = { status: 'disabled' };\n }\n this.onPerformanceEvent = options.onPerformanceEvent;\n this.cpuCacheBytes = options.cpuCacheBytes ?? defaultCpuCacheBytes();\n this.requestOptions = options.request;\n this.envFile = scene.environment?.file;\n this.envEnabled = options.environmentEnabled !== false;\n this.fetchWeight = options.fetchWeight;\n this.fetchScheduler = options.fetchScheduler;\n this.cacheBudget = options.cacheBudget;\n // Registered from the constructor so the very first reschedule is already\n // arbitrated - on a multi-mesh scene the load-time burst is the whole\n // problem, and a mesh that joins late has already taken its slots.\n this.fetchHandle = this.fetchScheduler?.register({\n // No weight supplied: claim an equal share rather than none, so a partly\n // wired host degrades to round-robin instead of silently starving.\n weight: () => this.fetchWeight?.() ?? 1,\n onSlotAvailable: () => {\n this.pendingWork = true;\n },\n shedFetches: (kind) => this.abortFetches(kind),\n });\n\n // Join the scene's cache envelope, if the host shares one. Registered here\n // rather than beside `fetchScheduler` because the ceiling needs `scene`, and\n // *before* the page-table branch because every streamed mesh has a chunk\n // cache - a scene of `.lcc2` additional meshes would otherwise sit outside the one\n // number that is supposed to bound the whole scene.\n //\n // The ceiling is the most this mesh could put to use. A page-table mesh\n // needs more: its frontier can only refine into chunks that are resident\n // *together*, so a whole `.rad` view spans many chunks (cest_ca: ~249 x\n // ~6.5 MB decoded ~ 1.6 GB) and a cache holding a fraction of them leaves\n // the near frontier thrashing and the scene \"coarse forever\". Hence a\n // ceiling above the host's per-mesh figure, bounded by what this capture\n // could even hold.\n //\n // That figure used to be the *cap*, at a flat 2 GiB: right for one big scene\n // and wrong for a wall of additional meshes, where 13 meshes were each allowed 2 GiB\n // against a 4 GiB tab heap. Bounding it by the capture helped and did not\n // fix it - thirteen 500 MB captures still allow 6.5 GB, because nothing\n // related the meshes to each other. The budget is that missing relation.\n const isPageTable = isPageTableFoveation(options.foveationMode);\n const cacheCeilingBytes = isPageTable\n ? Math.max(\n this.cpuCacheBytes,\n Math.min(PAGETABLE_CACHE_FLOOR_BYTES, estimateSceneDecodedBytes(scene)),\n )\n : this.cpuCacheBytes;\n this.cacheBudgetHandle = this.cacheBudget?.register({\n // The governor weight `fetchWeight` already carries, so cache and network\n // follow the same camera-projected measure. The `1` fallback matches\n // `requestChunk`: a host that never wired weights gives every mesh an\n // equal claim rather than none.\n weight: () => this.fetchWeight?.() ?? 1,\n ceilingBytes: cacheCeilingBytes,\n onAllowanceChanged: (bytes) => this.applyCacheAllowance(bytes),\n });\n this.cacheLimitBytes =\n this.cacheBudget && this.cacheBudgetHandle\n ? this.cacheBudget.allowanceFor(this.cacheBudgetHandle)\n : cacheCeilingBytes;\n // The classic path evicts against `cpuCacheBytes` directly; the page-table\n // path evicts inside its worker, which is told the number in `init` below.\n if (!isPageTable) this.cpuCacheBytes = this.cacheLimitBytes;\n this.fetchCountsValue.cacheLimitBytes = this.cacheLimitBytes;\n\n // Page-table mode: reserve the whole pool as one always-active slab (all-zeros\n // → degenerate/invisible until paged) and spin up the worker that owns the\n // chunk cache + traversal + pager. Spark's selected-index model, off-thread.\n if (isPageTableFoveation(options.foveationMode)) {\n if (!FrontierWorkerCtor) {\n throw new Error('StreamedSplatMesh: page-table foveation requires the frontier worker.');\n }\n // Frontier draw target: an explicit `foveationDrawBudget` (`?foveationDraw=`)\n // wins for A/B; otherwise Spark's default. Never above the pool budget.\n this.pageTableDrawTarget = options.foveationDrawBudget ?? PAGETABLE_DRAW_BUDGET;\n this.pageTableDrawTargetExplicit = options.foveationDrawBudget !== undefined;\n this.pageTableDrawBudget = Math.min(budget, this.pageTableDrawTarget);\n this.pageTableTargetPx = options.foveationTargetPx ?? DEFAULT_FOVEATION_TARGET_PX;\n this.pageTableFoveation = { ...FRONTIER_FOVEATION_DEFAULTS, ...options.frontierFoveation };\n if (\n options.minSplatScreenRadius !== undefined ||\n options.maxSplatScreenRadius !== undefined\n ) {\n this.frontierBandBase = {\n min: options.minSplatScreenRadius ?? 0,\n max: options.maxSplatScreenRadius ?? 0,\n };\n }\n // The slab starts empty: only the used prefix is ever active (drawn and\n // sorted) - each plan advances it to the resident count. Activating the\n // whole pool-sized slab would sort and vertex-process millions of\n // degenerate tail slots every frame.\n //\n // Reserved as pages rather than one block: the pager addresses slots, so\n // the storage behind them need not be contiguous, and page-sized\n // reservations are what let this mesh later grow and release storage with\n // its budget instead of holding its ceiling for the whole session.\n this.slabPageSplats = Math.min(SLAB_PAGE_SPLATS, capacity);\n // Reserve only what the current draw budget needs. The ceiling stays a\n // *permission* to grow rather than an up-front claim, which is what lets\n // many meshes share one pool: a distant mesh holds a page or two while\n // the one the camera approaches climbs. `syncSlabPages` moves the line as\n // the governor changes the budget.\n this.slabCeiling = capacity;\n this.syncSlabPages(this.pageTableDrawBudget);\n this.frontierWorker = new FrontierWorkerCtor();\n this.frontierWorker.onmessage = (e: MessageEvent<FrontierPlanMessage>) =>\n this.applyFrontierPlan(e.data);\n this.pagerSlots = this.slabSlots;\n // The frontier can only refine into chunks that are resident *together*.\n // A whole `.rad` view spans many chunks (cest_ca: ~249 × ~6.5 MB decoded ≈\n // 1.6 GB); a 512 MB cache holds only ~80, so the near frontier thrashes\n // and the scene \"stays coarse forever\". Hence a floor above the host's\n // per-mesh share - but bounded by what this capture could even hold, and\n // never below what the host asked for.\n //\n // The floor used to be a flat 2 GiB, which is right for one big scene and\n // wrong for a wall of additional meshes: 13 of them were each *allowed* 2 GiB\n // against a 4 GiB tab heap, so the one number that was supposed to stop\n // thrashing became the largest single memory risk in the viewer. Bounding\n // it by the capture helped and did not fix it - thirteen 500 MB captures\n // still allow 6.5 GB, because nothing relates the meshes to each other.\n //\n // So with a scene-wide `cacheBudget` this figure stops being the cap and\n // becomes this mesh's *ceiling*: the most it could put to use, which the\n // budget hands out from a scene total by camera weight.\n this.postToWorker({\n type: 'init',\n capacity: this.pagerSlots,\n chunkSize: scene.chunkSize ?? 65536,\n cpuCacheBytes: this.cacheLimitBytes,\n });\n // Seed the worker with the chunk the scene builder already decoded. The\n // tree roots are derived from chunk 0, so without this every traversal up\n // to the (redundant) refetch of chunk 0 returns an empty frontier.\n const bootstrap = scene.bootstrapChunk;\n if (bootstrap) this.forwardChunkToWorker(bootstrap.file, bootstrap.data);\n } else {\n this.frontierWorker = null;\n }\n }\n\n /** Slots the currently reserved pages can hold. */\n private get slabSlots(): number {\n let slots = 0;\n for (const page of this.slabPages) slots += page.count;\n return slots;\n }\n\n /**\n * Reserves or releases slab pages so the slab can hold `wanted` slots, and\n * tells the worker's pager the new slot count.\n *\n * This is the mechanism that makes a shared pool worth having: storage follows\n * the governed budget, so approaching a mesh grows its pages while the ones\n * behind you hand theirs back, instead of every mesh holding its ceiling for\n * the whole session. Growth stops at the construction ceiling and at whatever\n * the pool can actually spare - a mesh that cannot grow simply stays coarse\n * rather than throwing.\n */\n private syncSlabPages(wanted: number): void {\n if (this.slabCeiling === 0) return;\n const target = Math.max(this.slabPageSplats, Math.min(this.slabCeiling, wanted));\n let slots = this.slabSlots;\n\n while (slots < target) {\n const size = Math.min(this.slabPageSplats, this.slabCeiling - slots);\n if (size <= 0) break;\n try {\n this.slabPages.push(this.reserveInactiveRange(size));\n } catch {\n // The pool has no room right now (a nearer mesh holds it). Keep what we\n // have; the next budget change retries.\n break;\n }\n slots += size;\n }\n\n while (this.slabPages.length > 1) {\n const last = this.slabPages[this.slabPages.length - 1] as SplatRange;\n if (slots - last.count < target) break;\n this.slabPages.pop();\n slots -= last.count;\n this.removeRange(last);\n }\n\n if (slots !== this.pagerSlots) {\n this.pagerSlots = slots;\n this.postToWorker({ type: 'resize', capacity: slots });\n // Slots beyond the new count are gone from the pager, so stop drawing\n // them; the next plan re-establishes the resident prefix.\n if (this.pageTableDrawn > slots) this.setSlabResident(slots);\n this.pendingWork = true;\n this.lastScheduleTime = -Infinity;\n }\n }\n\n /**\n * Writes `data` at slot `slot`, splitting the write where it crosses a page\n * boundary. The pager's runs are contiguous in *slot* space, which page\n * storage no longer guarantees is contiguous in the pool.\n */\n private writeSlabSlots(data: SplatData, slot: number, count: number): void {\n let written = 0;\n while (written < count) {\n const at = slot + written;\n const page = this.slabPages[Math.floor(at / this.slabPageSplats)];\n if (!page) return; // beyond reserved storage; `dropped` already warns\n const offset = at % this.slabPageSplats;\n const run = Math.min(count - written, this.slabPageSplats - offset);\n this.overwriteRangeData(page, sliceSplatData(data, written, run), offset);\n written += run;\n }\n }\n\n /** Zeros slots `[slot, slot + count)`, splitting at page boundaries like\n * {@link writeSlabSlots}, so freed slots hold nothing drawable. */\n private degenerateSlabSlots(slot: number, count: number): void {\n let done = 0;\n while (done < count) {\n const at = slot + done;\n const page = this.slabPages[Math.floor(at / this.slabPageSplats)];\n if (!page) return; // beyond reserved storage\n const offset = at % this.slabPageSplats;\n const run = Math.min(count - done, this.slabPageSplats - offset);\n this.degenerateRange(page, offset, run);\n done += run;\n }\n }\n\n /**\n * Draws exactly the first `resident` slots: pages below the boundary are\n * fully active, the page containing it is partially active, the rest are\n * inactive. Freed tail slots simply leave the active list; they are also\n * degenerated (see {@link degenerateSlabSlots}) so that even a slot drawn by\n * mistake shows nothing.\n */\n private setSlabResident(resident: number): void {\n for (let page = 0; page < this.slabPages.length; page++) {\n const prefix = Math.min(\n this.slabPageSplats,\n Math.max(0, resident - page * this.slabPageSplats),\n );\n this.setRangeActivePrefix(this.slabPages[page] as SplatRange, prefix);\n }\n }\n\n /** Typed post to the frontier worker. */\n private postToWorker(msg: FrontierRequest, transfer: Transferable[] = []): void {\n this.frontierWorker?.postMessage(msg, transfer);\n }\n\n /**\n * Applies a new decoded-chunk allowance from the scene's shared\n * {@link ChunkCacheBudget}.\n *\n * Only the cap moves; nothing is dropped here. Both cache implementations\n * evict lazily against it - the page-table worker inside its next\n * `reschedule`, against a frontier that is still current, and the classic\n * path in `evictChunks` on the next tick. Dropping chunks synchronously would\n * pull them out from under resident splats.\n *\n * `pageTableCacheAtLimit` is recomputed here rather than waiting for the next\n * plan, so a *raised* allowance re-arms the background sweep on this tick\n * instead of one idle interval later.\n */\n private applyCacheAllowance(bytes: number): void {\n if (this.disposed) return;\n if (bytes === this.cacheLimitBytes) return;\n this.cacheLimitBytes = bytes;\n if (this.frontierWorker) {\n this.postToWorker({ type: 'cacheBudget', cpuCacheBytes: bytes });\n } else {\n // The classic cache lives on this thread and `evictChunks` reads this\n // field directly, so moving it is the whole update.\n this.cpuCacheBytes = bytes;\n }\n this.fetchCountsValue.cacheLimitBytes = bytes;\n this.pageTableCacheAtLimit = this.fetchCountsValue.cacheBytes >= bytes;\n // Land the new cap on the next tick rather than at the idle interval: a\n // shrink should stop the sweep now, and a grow should resume it now.\n this.pendingWork = true;\n }\n\n /**\n * Whether this scene ships collision meshes - true for an XGRIDS `.lcc` /\n * `.lcc2` dataset that carries them, false for a Streamed SOG scene, which\n * has none.\n */\n get hasCollisionMeshes(): boolean {\n return (this.scene.collision?.meshes.length ?? 0) > 0;\n }\n\n /**\n * Fetches and parses this scene's collision geometry: the triangle meshes an\n * XGRIDS `.lcc` (`collision.lci`) or `.lcc2` (`data/mesh/*.ply`) capture\n * ships beside its splats, for hosts that want collision, ground probes or\n * other spatial queries.\n *\n * The geometry is source-local, like {@link StreamedScene.bounds} - apply\n * this mesh's `matrixWorld` to put it in the frame the splats render in.\n * VLAM! builds no acceleration structure over it and never consults it.\n *\n * Tiles are fetched once and cached; concurrent callers share one load, and\n * a failed load can be retried by calling again. Resolves `[]` for a scene\n * without collision.\n *\n * @throws a `DOMException` named `AbortError` if cancelled, or if\n * {@link dispose} is called while the load is in flight.\n */\n async loadCollisionMeshes(\n options: { signal?: AbortSignal } = {},\n ): Promise<readonly CollisionMeshTile[]> {\n options.signal?.throwIfAborted();\n const collision = this.scene.collision;\n if (!collision || collision.meshes.length === 0) return [];\n\n if (!this.collisionTiles) {\n // Disposing the mesh cancels the load; a caller's own signal is honored\n // per call, so one caller giving up cannot cancel it for the others.\n const controller = new AbortController();\n this.collisionAbort = controller;\n this.collisionTiles = import('../formats/lcc')\n .then(({ loadCollisionMeshTiles }) =>\n loadCollisionMeshTiles(collision, {\n ...(this.requestOptions ? { request: this.requestOptions } : {}),\n signal: controller.signal,\n }),\n )\n .catch((error: unknown) => {\n this.collisionTiles = undefined; // let a retry try again\n throw error;\n });\n }\n\n const { signal } = options;\n if (!signal) return this.collisionTiles;\n // One caller giving up must not cancel the shared load, so its signal\n // races the load rather than aborting it.\n //\n // The listener is removed in `finally` rather than left to `{ once: true }`:\n // when the load wins the race the abort never fires, so `once` never\n // collects it. A host that passes one long-lived signal and calls this\n // repeatedly (a viewer re-loading collision per scene) would otherwise\n // accumulate listeners on that signal, each closing over this mesh.\n let abortListener: (() => void) | undefined;\n try {\n return await Promise.race([\n this.collisionTiles,\n new Promise<never>((_resolve, reject) => {\n abortListener = () => reject(abortReason(signal));\n signal.addEventListener('abort', abortListener, { once: true });\n }),\n ]);\n } finally {\n if (abortListener) signal.removeEventListener('abort', abortListener);\n }\n }\n\n /**\n * Whether this scene ships an always-resident environment/background tile -\n * true for an XGRIDS `.lcc2` capture that carries one (its `env.sog` sky),\n * false for Streamed SOG, `.lcc`, `.rad`, or an `.lcc2` without one.\n */\n get hasEnvironment(): boolean {\n return this.envFile !== undefined;\n }\n\n /** Whether the environment tile is currently set to render. */\n get environmentEnabled(): boolean {\n return this.envEnabled;\n }\n\n /**\n * Splats in the environment tile, measured when it decoded - the manifest\n * does not carry the count. 0 until the tile has loaded (or if the scene\n * ships none). These sit outside the LOD budget, drawing from the pool's\n * capacity headroom.\n */\n get environmentSplatCount(): number {\n return this.envSplatCount;\n }\n\n /**\n * Shows or hides the scene's environment/background tile. The switch is\n * instant and never refetches: once loaded, the tile stays in the pool and\n * only its active flag flips. Enabling before the tile has loaded triggers\n * its (one-time) load on the next update. No-op on a scene without one.\n */\n setEnvironmentEnabled(enabled: boolean): void {\n if (this.envFile === undefined || enabled === this.envEnabled) return;\n this.envEnabled = enabled;\n if (this.envHandle !== undefined) {\n this.setRangeActive(this.envHandle, enabled);\n } else if (enabled) {\n // Not loaded yet - kick a reschedule so updateEnvironment fetches it.\n this.pendingWork = true;\n this.lastScheduleTime = -Infinity;\n }\n }\n\n /** The active-splat budget this mesh keeps within. */\n get budget(): number {\n return this.budgetValue;\n }\n\n /**\n * The ceiling {@link setBudget} clamps to - {@link StreamedSplatMeshOptions.maxBudget}\n * when one was given, otherwise the construction budget.\n *\n * The pool was allocated for this number and cannot grow, so it is a hard\n * limit on what any governor can hand this mesh. Read it to check that a\n * shared-budget setup can actually deliver the share it is computing.\n */\n get maxBudget(): number {\n return this.maximumBudget;\n }\n\n /** Alias used by hosts that manage static and streamed auto-LOD uniformly. */\n get budgetCeiling(): number {\n return this.maximumBudget;\n }\n\n /**\n * The capture's real content size, when the format declares it (`.rad` reports\n * its leaf count) - the splat count needed to hold this mesh at full\n * resolution, independent of the budget it was constructed with.\n *\n * A host splitting one budget across several streamed meshes should clamp each\n * share to this: a mesh cannot spend more than it contains, so budget handed\n * past it buys nothing and is better given to a mesh that can use it. Note it\n * is *not* `maxBudget`: a foveated `.rad` reports `maxResidentSplats` as the\n * requested budget, because its pool holds a camera-directed resident set\n * rather than the whole tree.\n *\n * `undefined` when the format does not declare a content size.\n */\n get contentSplatCount(): number | undefined {\n return this.scene.contentSplatCount;\n }\n\n /**\n * Which `.rad` streaming strategy this mesh selected at load, or `null` when\n * the scene is not a Spark `.rad` capture.\n */\n get radStrategy(): 'prefix' | 'page-table' | null {\n if (this.scene.chunkOptions?.[0]?.format !== 'rad-chunk') return null;\n return this.frontierWorker ? 'page-table' : 'prefix';\n }\n\n /**\n * The drawn-splat target currently driving the `.rad` page-table frontier -\n * the governed budget, capped by\n * {@link SplatMeshOptions.foveationDrawBudget}. `0` on a mesh that is not in\n * `foveationMode: 'page-table'`, which has no frontier to target.\n *\n * This is the number that decides how deep the traversal descends, so it is\n * what to watch when checking that a near mesh really did receive more\n * detail: {@link budget} is the pool's allowance, this is what is spent.\n */\n get drawBudget(): number {\n return this.frontierWorker ? this.pageTableDrawBudget : 0;\n }\n\n /**\n * Page-table frontier coherence for hosts that gate preload/transitions.\n * `undefined` fields stay 0 when this mesh is not in page-table mode.\n */\n get frontierState(): Readonly<{\n frontierConverged: boolean;\n pendingFrontierSplats: number;\n staleResidentSplats: number;\n lastPlanAppends: number;\n lastPlanMoves: number;\n planGeneration: number;\n planBudget: number;\n lastPlanCamera: readonly [number, number, number] | null;\n firstFrontierCamera: readonly [number, number, number] | null;\n }> {\n return {\n frontierConverged: this.frontierWorker ? this.frontierConverged : true,\n pendingFrontierSplats: this.pendingFrontierSplats,\n staleResidentSplats: this.staleResidentSplats,\n lastPlanAppends: this.lastPlanAppends,\n lastPlanMoves: this.lastPlanMoves,\n planGeneration: this.lastPlanGeneration,\n planBudget: this.lastPlanBudget,\n lastPlanCamera: this.lastPlanCamera,\n firstFrontierCamera: this.firstFrontierCamera,\n };\n }\n\n /**\n * Spark's per-mesh `lodScale` (see {@link StreamedSplatMeshOptions.lodScale}).\n * Mutable: raise it to sharpen a focused mesh, lower it to coarsen a\n * background one. Page-table `.rad` only.\n *\n * @throws {RangeError} if set to a value that is not positive and finite.\n */\n get lodScale(): number {\n return this.lodScaleValue;\n }\n set lodScale(value: number) {\n const next = validateLodScale(value);\n if (next === this.lodScaleValue) return;\n this.lodScaleValue = next;\n this.pendingWork = true;\n this.lastScheduleTime = -Infinity;\n }\n\n /** In `page-table` mode the slab is fully active but mostly degenerate, so the\n * base `activeSplatCount` (slab size) is not the on-screen count - report the\n * frontier's drawn size instead. */\n override get activeSplatCount(): number {\n return this.frontierWorker ? this.pageTableDrawn : super.activeSplatCount;\n }\n\n /**\n * Updates the LOD budget used for future scheduling within allocated capacity.\n *\n * @returns the budget actually in effect, which is `budget` clamped to\n * {@link maxBudget}. A `BudgetGovernor` reads this return value to detect a\n * capped member and hand the remainder to the others, so the clamp is\n * reported rather than hidden.\n */\n setBudget(budget: number): number {\n const next = Math.min(resolveSplatBudget(budget), this.maximumBudget);\n if (next === this.budgetValue) return this.budgetValue;\n this.budgetValue = next;\n this.scene.source.budget = next;\n if (this.frontierWorker) {\n // Page-table mode draws the frontier, not the LOD schedule - keep its\n // draw target under the (possibly shared/governed) pool budget too.\n this.pageTableDrawBudget = Math.min(next, this.pageTableDrawTarget);\n // Storage follows the budget: climb toward the new draw target, or hand\n // pages back when the governor shrinks this mesh.\n this.syncSlabPages(this.pageTableDrawBudget);\n // A caller-pinned `foveationDrawBudget` outranks the budget, so a governor\n // that grows this mesh past it buys nothing and the mesh stays coarse for\n // a reason nothing on screen explains. Say so once.\n if (\n this.pageTableDrawTargetExplicit &&\n this.pageTableDrawTarget < next &&\n !this.warnedDrawTargetCap\n ) {\n this.warnedDrawTargetCap = true;\n warn(\n `StreamedSplatMesh: budget raised to ${next} but foveationDrawBudget caps the drawn ` +\n `frontier at ${this.pageTableDrawTarget}; the extra budget cannot buy detail. ` +\n `Raise or drop foveationDrawBudget to let the shared budget through.`,\n );\n }\n }\n this.pendingWork = true;\n this.lastScheduleTime = -Infinity;\n return next;\n }\n\n /**\n * What the LOD scheduler last decided, or `undefined` on sources that do not\n * schedule by leaf (the `.rad` page table, prefix readers).\n *\n * Distinct from {@link activeSplatCount}, and the distinction is the whole\n * point: `desired` is what the scheduler asked for, `activeSplatCount` is\n * what the pool ended up drawing. Equal means the cut is applied; `desired`\n * far below the budget means the *scheduler* declined to spend it, which is a\n * different bug from the mesh failing to apply what it was given.\n */\n get lodStats():\n Readonly<{ inFrustum: number; leaves: number; desired: number; filled: number }> | undefined {\n const source = this.scene.source as { stats?: LodScheduler['stats'] };\n return source.stats;\n }\n\n /** Number of chunk files currently decoded and held. In page-table mode the\n * worker owns the cache - the main-thread map is always empty there, so report\n * what has been forwarded to it instead of a permanent zero. */\n get residentChunkCount(): number {\n return this.frontierWorker ? this.pageTableCachedFiles.size : this.cache.size;\n }\n\n /** Chunk fetches currently in flight. */\n get pendingChunkCount(): number {\n return this.fetching.size;\n }\n\n /**\n * Main-thread cost of applying paging plans in `foveationMode: 'page-table'`.\n *\n * A plan is applied whole, off the render loop's own timing, so its cost does\n * not appear in {@link getUpdateTimings} - but it lands on the same thread and\n * a churning frontier can make it the largest stall in a frame. `worst*`\n * accumulate over the mesh's lifetime; the rest describe the most recent plan.\n */\n get planTimings(): Readonly<{\n applyMs: number;\n worstApplyMs: number;\n writeMs: number;\n residentMs: number;\n moves: number;\n appends: number;\n worstSplats: number;\n }> {\n return this.planTimingsValue;\n }\n\n /**\n * Lifetime chunk-fetch totals by kind, plus page-table cache state.\n *\n * Diagnostic for the question \"why is this still streaming after the view\n * settled?\", which the three fetch sources answer differently and which no\n * other reading distinguishes:\n *\n * - **`sweep` climbing** - speculative file-order pre-warming of the whole\n * capture. Declined by the `smooth` profile; see `sweepAllowed`.\n * - **`priority` / `base` climbing while `evicted` climbs too** - the\n * frontier's touched set does not fit the worker cache, so chunks are\n * evicted and immediately refetched. Streaming never ends because it cannot.\n * - **`priority` / `base` climbing with `evicted` flat** - ordinary refinement\n * still converging on the cut; it should stop on its own.\n *\n * `uncovered` and `retiredEarly` answer a different question - \"why are there\n * holes?\" - and between them cover both ways this class can render nothing\n * where it should render something:\n *\n * - **`uncovered` climbing after the scene settles** - `substituteCoverage`\n * wanted a leaf's coarsest level as a stand-in and its chunk was not\n * cached. Expected briefly during initial load; afterwards it should not\n * move, because the coarsest files are pinned against eviction.\n * - **`retiredEarly` climbing** - coverage was retired before its replacement\n * landed, under pool pressure or past the retirement hold bound. This is\n * the swap path rather than the substitute path, and it is the one that\n * scales with the budget.\n *\n * Both are counted in whole leaves/groups, monotonically: they answer \"did\n * this happen, and is it still happening\", not \"how much is missing now\".\n */\n get fetchCounts(): Readonly<{\n priority: number;\n base: number;\n sweep: number;\n evicted: number;\n uncovered: number;\n retiredEarly: number;\n cacheFull: boolean;\n cacheBytes: number;\n cacheLimitBytes: number;\n }> {\n return this.fetchCountsValue;\n }\n\n /** Chunk files given up on after repeated fetch/decode failures. */\n get failedChunkCount(): number {\n return this.failedFiles.size;\n }\n\n /**\n * Forgets all permanent chunk failures so their regions are fetched again.\n * Failures are otherwise terminal for the mesh's lifetime - call this when\n * the cause was transient (e.g. connectivity restored, `online` event).\n */\n retryFailedChunks(): void {\n if (this.failedFiles.size === 0) return;\n this.failedFiles.clear();\n this.retrying.clear();\n this.pendingWork = true;\n }\n\n /**\n * Whether the scene is still resolving toward its target detail - chunks\n * are fetching, or a retry/append is pending. Goes false once the view\n * has settled (useful to drive a loading indicator).\n */\n get isStreaming(): boolean {\n return this.pendingWork || this.fetching.size > 0 || this.retrying.size > 0;\n }\n\n /** The LOD distance model (mutable; e.g. raise to force the finest level). */\n get lodBaseDistance(): number {\n return this.scene.source.lodBaseDistance;\n }\n set lodBaseDistance(value: number) {\n this.scene.source.lodBaseDistance = value;\n this.pendingWork = true;\n }\n\n override update(\n camera: THREE.PerspectiveCamera,\n renderer: THREE.WebGPURenderer,\n options: SplatUpdateOptions = {},\n ): void {\n const now = performance.now();\n camera.updateMatrixWorld();\n this.updateWorldMatrix(true, false);\n // The page-table cut limit is `targetPx / focalY`, and focalY needs the\n // drawing-buffer height - sample it here, before rescheduling, since the\n // base class only writes its view uniforms afterwards. In XR use the\n // per-eye height, not the stereo framebuffer (which is twice as wide and\n // would throw the cut off).\n //\n // LOD must follow the *head*, not the application camera. While an XR\n // session presents, three drives an internal array camera and the app\n // camera stops moving - scheduling from it would hold detail wherever that\n // camera was left and frustum-cull whatever the user turns to face, so a\n // scene stays blurry however far you walk into it. The head's union\n // projection is also the correct frustum here: it spans both eyes.\n // (`super.update` resolves the view again; that is idempotent and costs a\n // handful of matrix products.)\n const xrView = resolveXrView(camera, renderer);\n if (this.frontierWorker) {\n if (xrView) {\n this.pageTableViewportY = xrView.height;\n } else {\n renderer.getDrawingBufferSize(_drawSize);\n this.pageTableViewportY = _drawSize.y;\n }\n }\n const lodCamera = xrView?.head ?? camera;\n const performanceEvent = this.shouldReschedule(lodCamera, now)\n ? this.reschedule(lodCamera, now)\n : null;\n super.update(camera, renderer, options);\n if (performanceEvent && this.onPerformanceEvent) {\n const timings = this.getUpdateTimings();\n performanceEvent.cpuMs += performance.now() - performanceEvent.timestamp;\n performanceEvent.activeListMs = timings.activeListMs;\n performanceEvent.uploadMs = timings.uploadMs;\n performanceEvent.sortSubmitMs = timings.sortSubmitMs;\n performanceEvent.stagingTextureAllocations = timings.stagingTextureAllocations;\n performanceEvent.activeListUpdateRanges = timings.activeListUpdateRanges;\n this.onPerformanceEvent(performanceEvent);\n }\n }\n\n /** Root bounds of the whole scene, valid before any chunk has loaded. */\n override computeSplatBounds(): THREE.Box3 {\n return this.scene.bounds.clone();\n }\n\n /**\n * Enables or disables writing each resident run's **resolved** LOD level into\n * the `lodLevel` float channel (for a false-color debug modifier: 0 = finest).\n * Values come from applied desired runs after budget resolution - not from\n * distance ambition alone. Call before assigning a modifier that reads the channel.\n */\n setLodLevelDebug(enabled: boolean): void {\n if (enabled) {\n if (!this.lodLevelChannelReady) {\n this.defineChannel('lodLevel', { type: 'float', fill: -1 });\n this.lodLevelChannelReady = true;\n }\n this.lodLevelDebug = true;\n for (const { run, handle } of this.resident.values()) {\n this.writeLodLevelChannel(handle, run.level);\n }\n for (const { run, handle, uploadedCount } of this.staged.values()) {\n if (uploadedCount === run.count) this.writeLodLevelChannel(handle, run.level);\n }\n return;\n }\n this.lodLevelDebug = false;\n }\n\n /** Whether {@link setLodLevelDebug} is currently writing levels. */\n get isLodLevelDebug(): boolean {\n return this.lodLevelDebug;\n }\n\n /**\n * Startup-hold progress for {@link StreamedSplatMeshOptions.initialReveal}.\n * Hosts using `'hold-near-l0'` or `'hold-coverage'` should keep the mesh\n * invisible while `status === 'pending'`, then reveal on `'ready'` or\n * `'degraded'`.\n */\n get initialRevealState(): InitialRevealState {\n return this.initialRevealStateValue;\n }\n\n /**\n * Captures a fresh startup-hold set on the next {@link update}. Hosts that\n * apply their final initial camera pose after the mesh first receives frames\n * should call this before lifting their loading cover. It is a no-op when\n * the hold was never armed (progressive startup, or a format without the\n * matching LodSource hook).\n */\n recaptureInitialReveal(): void {\n if (this.initialRevealHold === 'off') return;\n this.frozenCriticalRuns = null;\n this.initialRevealStartedAt = undefined;\n this.initialRevealPhase = 'capture';\n this.initialRevealStateValue = {\n status: 'pending',\n stagedSplats: 0,\n totalSplats: 0,\n readyGroups: 0,\n totalGroups: 0,\n };\n this.pendingWork = true;\n }\n\n private writeLodLevelChannel(handle: SplatRange, level: number): void {\n if (!this.lodLevelDebug || handle.count === 0) return;\n if (!this.lodLevelScratch || this.lodLevelScratch.length < handle.count) {\n this.lodLevelScratch = new Float32Array(handle.count);\n }\n this.lodLevelScratch.fill(level, 0, handle.count);\n this.writeChannel(handle, 'lodLevel', this.lodLevelScratch.subarray(0, handle.count));\n }\n\n /**\n * Declares a per-splat channel whose values **persist across LOD churn**:\n * edits are stored sparsely keyed by `(chunk file, local index)` - a stable\n * splat identity in the streaming design - and re-applied whenever a chunk\n * is (re)appended. Paint a region with {@link paintPersistent}, orbit away\n * until it is evicted, come back, and the values return. See M7.6.\n *\n * Wraps {@link SplatMesh.defineChannel}; read it from a modifier with\n * `ctx.channel(name)` as usual.\n */\n definePersistentChannel(name: string, options: PersistentChannelOptions = {}): void {\n this.defineChannel(name, options);\n this.persistentChannels.set(name, {\n type: options.type ?? 'float',\n fill: options.fill ?? 0,\n maxEdits: Math.max(1, Math.floor(options.maxEdits ?? 1_000_000)),\n edits: new Map(),\n total: 0,\n warned: false,\n });\n }\n\n /**\n * Sets a persistent channel to `value` for every currently-resident splat\n * within `radius` (world units) of `worldPoint`, and records the edit so it\n * survives eviction/reload. Splats whose chunk is not currently decoded on\n * the CPU cannot be located and are skipped (they are usually far from the\n * camera); their painted neighbours in resident chunks are unaffected.\n *\n * The radius assumes this mesh's world transform is rigid (rotation +\n * translation, as the built-in format transforms are); a scaled mesh would\n * distort the brush. Edit a persistent channel only through this method -\n * direct {@link SplatMesh.writeChannel} writes are not recorded and are\n * overwritten by the next re-apply.\n *\n * @returns the number of splats edited this call.\n * @throws {Error} if the channel was not declared with\n * {@link definePersistentChannel}.\n */\n paintPersistent(name: string, worldPoint: THREE.Vector3, radius: number, value: number): number {\n const channel = this.persistentChannels.get(name);\n if (!channel) {\n throw new Error(\n `StreamedSplatMesh.paintPersistent: channel \"${name}\" is not a persistent channel. ` +\n `Call definePersistentChannel(\"${name}\") first.`,\n );\n }\n _paintLocal.copy(worldPoint);\n this.worldToLocal(_paintLocal);\n const r2 = radius * radius;\n let edited = 0;\n const touchedFiles = new Set<number>();\n\n for (const { run } of this.resident.values()) {\n const chunk = this.cache.get(run.file);\n if (!chunk) continue; // positions evicted from the CPU cache\n const positions = chunk.data.positions;\n const fileEdits = channel.edits.get(run.file) ?? new Map<number, number>();\n let touched = false;\n for (let k = 0; k < run.count; k++) {\n const li = run.offset + k;\n const px = (positions[li * 3 + 0] as number) - _paintLocal.x;\n const py = (positions[li * 3 + 1] as number) - _paintLocal.y;\n const pz = (positions[li * 3 + 2] as number) - _paintLocal.z;\n if (px * px + py * py + pz * pz > r2) continue;\n // First paint wins - keep the stored color/index for already-edited splats.\n if (fileEdits.has(li)) continue;\n if (channel.total >= channel.maxEdits) {\n if (!channel.warned) {\n channel.warned = true;\n warn(\n `StreamedSplatMesh.paintPersistent: channel \"${name}\" hit its ` +\n `maxEdits cap (${channel.maxEdits}); further new edits are dropped.`,\n );\n }\n continue;\n }\n channel.total++;\n fileEdits.set(li, value);\n touched = true;\n edited++;\n }\n if (touched) {\n channel.edits.set(run.file, fileEdits);\n touchedFiles.add(run.file);\n }\n }\n\n // Re-derive and upload each touched resident run from the store, so the\n // paint shows immediately (not only after the next reload).\n if (touchedFiles.size > 0) {\n for (const { run, handle } of this.resident.values()) {\n if (touchedFiles.has(run.file)) this.applyPersistentRun(name, channel, run, handle);\n }\n }\n return edited;\n }\n\n /**\n * Clears every stored edit for a persistent channel and zeroes the value on\n * all currently-resident splats. Chunks that are not resident are covered by\n * the emptied store - they reload at the channel's fill value.\n *\n * @throws {Error} if the channel is not a persistent channel.\n */\n clearPersistentChannel(name: string): void {\n const channel = this.persistentChannels.get(name);\n if (!channel) {\n throw new Error(\n `StreamedSplatMesh.clearPersistentChannel: channel \"${name}\" is not a persistent channel.`,\n );\n }\n channel.edits.clear();\n channel.total = 0;\n for (const { run, handle } of this.resident.values()) {\n const data =\n channel.type === 'byte' ? new Uint8Array(run.count) : new Float32Array(run.count);\n this.writeChannel(handle, name, data);\n }\n }\n\n override dispose(): void {\n if (this.disposed) return;\n this.loader.dispose();\n // Terminating drops any in-flight traversal; clearing the handler also\n // frees the closure over this mesh for a message already dispatched.\n if (this.frontierWorker) {\n this.frontierWorker.onmessage = null;\n this.frontierWorker.terminate();\n }\n this.pageTableDisposed = true;\n this.collisionAbort?.abort();\n // A rejected cached promise is nobody's to handle once the mesh is gone.\n this.collisionTiles?.catch(() => {});\n this.collisionTiles = undefined;\n // Abort before unregistering: each abort settles through `requestChunk`'s\n // `finally`, which releases the slot back to the mesh's siblings.\n for (const { controller } of this.fetching.values()) controller.abort();\n this.fetching.clear();\n if (this.fetchHandle) this.fetchScheduler?.unregister(this.fetchHandle);\n // Hands this mesh's cache allowance back to its siblings. Cleared so a\n // reallocation triggered by the unregister itself cannot call back into a\n // disposed mesh and post to a terminated worker.\n if (this.cacheBudgetHandle) {\n const handle = this.cacheBudgetHandle;\n this.cacheBudgetHandle = undefined;\n this.cacheBudget?.unregister(handle);\n }\n // Revokes the object URLs a dropped local folder created, and releases the\n // `File` blobs they pin. No-op for a network-loaded mesh.\n this.localSource?.dispose();\n this.localSource = undefined;\n this.cache.clear();\n this.cacheBytesTotal = 0;\n this.retrying.clear();\n this.failedFiles.clear();\n this.neededFiles.clear();\n this.persistentChannels.clear();\n this.resident.clear();\n this.staged.clear();\n this.pageTableCachedFiles.clear();\n this.envHandle = undefined;\n this.envSplatCount = 0;\n super.dispose();\n }\n\n private shouldReschedule(camera: THREE.Camera, now: number): boolean {\n if (this.pendingWork) return true;\n if (now - this.lastScheduleTime > IDLE_RESCHEDULE_MS) return true;\n\n camera.getWorldPosition(_cameraWorldPos);\n const radius = this.scene.bounds.getBoundingSphere(_sphere).radius || 1;\n if (_cameraWorldPos.distanceTo(this.lastCameraPos) > radius * 0.0025) return true;\n\n camera.getWorldQuaternion(_cameraWorldQuat);\n return _cameraWorldQuat.angleTo(this.lastCameraQuat) > 0.0087; // ~0.5°\n }\n\n private reschedule(camera: THREE.Camera, now: number): StreamedSplatPerformanceEvent | null {\n const startedAt = performance.now();\n // The before-snapshots exist only to diff for the performance event; with\n // no listener installed this per-reschedule allocation work is skipped.\n let before: {\n resident: Map<string, number>;\n staged: Map<string, number>;\n } | null = null;\n if (this.onPerformanceEvent !== undefined) {\n before = { resident: new Map(), staged: new Map() };\n for (const [key, entry] of this.resident) before.resident.set(key, entry.run.count);\n for (const [key, entry] of this.staged) before.staged.set(key, entry.uploadedCount);\n }\n const compactionCountBefore = this.compactionCount;\n this.pendingWork = false;\n this.lastScheduleTime = now;\n camera.getWorldPosition(this.lastCameraPos);\n camera.getWorldQuaternion(this.lastCameraQuat);\n\n // Camera position and frustum in this mesh's local space.\n _cameraLocal.copy(this.lastCameraPos);\n this.worldToLocal(_cameraLocal);\n _projScreen\n .multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse)\n .multiply(this.matrixWorld);\n _frustum.setFromProjectionMatrix(_projScreen);\n\n if (this.frontierWorker) {\n // Camera forward as a mesh-local direction: transform a point one unit\n // ahead and subtract the local eye, so any affine mesh transform is\n // handled without a separate normal matrix.\n camera.getWorldDirection(_cameraForward).add(this.lastCameraPos);\n this.worldToLocal(_cameraForward);\n _cameraForward.sub(_cameraLocal).normalize();\n // Cut limit, exactly as the material derives it: a node is fine enough\n // when `size / distance ≤ targetPx / focalY`.\n const focalY = (camera.projectionMatrix.elements[5] * this.pageTableViewportY) / 2;\n if (focalY > 0) this.pageTableLimit = this.pageTableTargetPx / focalY;\n this.reschedulePageTable(_cameraLocal, _cameraForward, _frustum, now);\n // The page-table path still reports its per-update CPU cost. Returning\n // null here made `onPerformanceEvent` silent on the one path a `.rad`\n // actually takes, so a host watching `cpuMs` / `uploadMs` / `sortSubmitMs`\n // saw nothing at all - and could not tell an upload stall from a sort\n // stall on the only format where the question comes up.\n //\n // The chunk-swap fields stay zero: this path pages slots through the\n // frontier plan rather than swapping LOD runs, so `swapped`/`appended`\n // and the resident/staged diffs have no meaning here. `applyFrontierPlan`\n // is timed separately, by `planTimings`.\n if (this.onPerformanceEvent === undefined) return null;\n // Same convention as the classic path below: `timestamp` marks the end of\n // the reschedule and `cpuMs` covers it, so the caller's\n // `cpuMs += now - timestamp` adds `super.update` rather than double-counting.\n const pageTableTimestamp = performance.now();\n return {\n timestamp: pageTableTimestamp,\n cpuMs: pageTableTimestamp - startedAt,\n activeListMs: 0,\n uploadMs: 0,\n sortSubmitMs: 0,\n stagingTextureAllocations: 0,\n activeListUpdateRanges: 0,\n appendedCount: 0,\n removedCount: 0,\n stagedCount: 0,\n uploadCount: 0,\n activeCount: this.pageTableDrawn,\n forcedSort: false,\n compacted: false,\n };\n }\n\n // Fetch ranking needs a more precise signal than LCC's broad-box frustum\n // bit. It must not influence the source's distance/budget LOD decision.\n camera.getWorldDirection(_cameraForward).add(this.lastCameraPos);\n this.worldToLocal(_cameraForward);\n _cameraForward.sub(_cameraLocal).normalize();\n const scheduledRuns = this.scene.source.computeDesiredRuns(\n _cameraLocal,\n _frustum,\n now,\n _cameraForward,\n );\n const holdingRuns = this.captureOrContinueInitialReveal(\n scheduledRuns,\n now,\n _cameraLocal,\n _frustum,\n _cameraForward,\n );\n const holding = holdingRuns !== null;\n // During the startup hold, ignore later camera cuts: only the frozen\n // coverage set is desired. After release, the normal swap transaction\n // keeps that coverage active while the live cut stages, then replaces it\n // atomically rather than drawing coarse and fine runs together.\n const desiredRuns = holdingRuns ?? scheduledRuns;\n const desired = new Map<string, LodRun>();\n const desiredFiles = new Set<number>();\n for (const run of desiredRuns) {\n desired.set(runKey(run), run);\n desiredFiles.add(run.file);\n }\n for (const [key, entry] of this.staged) {\n if (desired.has(key)) continue;\n // During hold, keep staging progress for frozen runs even if a bug drops\n // them from desired - the freeze list is authoritative.\n if (holding && this.frozenCriticalRuns?.some((run) => runKey(run) === key)) continue;\n this.removeRange(entry.handle);\n this.staged.delete(key);\n }\n\n // Cancel fetches whose file no longer backs any desired run. Pinned\n // (coarsest-level) files are never cancelled: they are the substitute\n // coverage every deferred swap relies on, and the environment tile is\n // pinned for the same reason. During hold, only critical coverage files\n // (and pins) stay - neighbours and far coarse lose their slots.\n for (const [file, { controller }] of this.fetching) {\n if (!desiredFiles.has(file) && !this.scene.pinnedFiles.has(file)) controller.abort();\n }\n // Drop retry state for files no longer wanted, for the same reason -\n // otherwise a chunk that failed once before the camera moved away keeps\n // `isStreaming` (and the demo's spinner) stuck true forever.\n for (const file of this.retrying.keys()) {\n if (!desiredFiles.has(file) && !this.scene.pinnedFiles.has(file)) this.retrying.delete(file);\n }\n // Chunks fetched for a still-deferred group have a stale `lastUsed`;\n // remember every desired-but-not-yet-resident file so eviction cannot\n // discard them before their swap group applies (fetch → evict → refetch\n // livelock under CPU-cache pressure). Fully staged runs may leave the CPU\n // cache: their GPU inactive range already holds the bytes.\n this.neededFiles.clear();\n for (const run of desiredRuns) {\n const key = runKey(run);\n if (this.resident.has(key)) continue;\n const staged = this.staged.get(key);\n if (staged && staged.uploadedCount === run.count) continue;\n this.neededFiles.add(run.file);\n }\n if (\n this.envFile !== undefined &&\n this.envEnabled &&\n this.envHandle === undefined &&\n !this.envUnfit &&\n !this.failedFiles.has(this.envFile)\n ) {\n this.neededFiles.add(this.envFile);\n }\n\n const toAdd = desiredRuns.filter((run) => !this.resident.has(runKey(run)));\n // During hold, never retire unrelated resident coverage - the viewer is\n // hidden and we only build the critical set.\n const toRemove = holding\n ? []\n : [...this.resident.entries()].filter(([key]) => !desired.has(key));\n\n // A region must never render twice (bright flash) or not at all (black\n // hole), so adds and their superseded removals apply together, within\n // one tick - one frame sees only complete before/after states. Groups\n // are connected components of (toAdd ∪ toRemove) by leaf-interval\n // overlap; a group that cannot fully apply this tick (chunk still\n // fetching, append cap, pool pressure) is deferred whole, its old runs\n // still rendering.\n const groups = holding\n ? // Mesh is invisible during the hold, so L0 cell-atomicity (no holes) is\n // irrelevant - commit each frozen slice as it lands so a partial home\n // cell cannot block reveal behind sibling subchunks still fetching.\n buildHoldSwapGroups(toAdd)\n : buildSwapGroups(toAdd, toRemove);\n const classicLccGroups = !holding && isClassicLccSwapSet(groups);\n // The generic RAD wave must land all replacement coverage before any\n // retirements. Classic LCC already has per-slice coverage transactions;\n // applying that global wave to it starves a ready visible L1+ slice behind\n // every coarse shell elsewhere in the scene.\n groups.sort((a, b) =>\n classicLccGroups ? compareClassicSwapGroups(a, b) : groupPriority(a) - groupPriority(b),\n );\n\n // Classic path used to `requestChunk` in leafStart / group order, so far\n // coarse pins filled the in-flight cap while the camera cell stayed on\n // discs. Collect every miss this tick and flush nearest/finest first -\n // same contract as the page-table `pageTableFetchPriority` path.\n const pendingFetches = new Map<number, ClassicFetchWant>();\n // Environment first: append it before coverage consumes pool rows, and\n // enqueue its fetch ahead of LOD wants so the sky is not last in the pipe.\n this.updateEnvironment(now, pendingFetches);\n\n // A `.rad` refinement splits across groups, and that is what used to punch\n // holes in a region while it sharpened. Grouping pairs adds with removals by\n // leaf-interval overlap, which works for the octree formats because a\n // parent's interval contains its children's - but `.rad` keys runs by global\n // splat index and a node's children live in a *later chunk*, so parent and\n // children can never share a group. The parent's group therefore committed\n // at once while the children's group was still staging, and for those frames\n // the region drew with its coarse splats gone and their replacements not yet\n // visible.\n //\n // Spark cannot reach that state: it publishes a refined cut only once every\n // splat in it is drawable, holding the previous complete frame meanwhile\n // (`SparkRenderer.driveSort` advances `display` only after a sort of the new\n // mapping lands). Do the same - a group that retires coverage waits until\n // every group that purely adds has landed. Waiting costs brief over-draw,\n // never a hole, since a deferred group keeps rendering its old runs.\n let appended = 0;\n let addsPending = false;\n let poolPressure = false;\n let held = false;\n for (const group of groups) {\n if (!classicLccGroups && group.removes.length > 0 && addsPending) {\n // Bounded so the wait can never strand coverage: the replacements\n // normally land within a few ticks, and past that the pool matters more\n // than the seam.\n if (\n this.neverRetireCoverageEarly ||\n (!poolPressure && this.retireHeldTicks < MAX_RETIRE_HELD_TICKS)\n ) {\n this.pendingWork = true;\n held = true;\n continue;\n }\n // Falling through here retires coverage whose replacement has *not*\n // landed - the one deliberate hole in this path. Two causes, one\n // consequence: the pool needs the rows more than the seam needs hiding\n // (`poolPressure`), or the hold has run past MAX_RETIRE_HELD_TICKS. A\n // higher budget makes the first likelier - more and larger groups in\n // flight against a pool sized from the same budget - so this is the\n // first thing to read when holes appear only at a raised budget.\n this.fetchCountsValue.retiredEarly++;\n }\n if (group.adds.length === 0) {\n this.applyGroup(group, now); // dropped regions: just free them\n continue;\n }\n const missing = group.adds.filter((run) => !this.cache.has(run.file));\n // Resolved L0: skip coarse stand-in for empty gaps (keep prior coverage\n // on each sub-leaf until that slice's L0 commits). L1+: allow per-slice\n // coarsest substitute while that slice's target loads.\n // Startup hold never paints coarse for the critical set.\n const holdForTarget =\n holding || (isWaitingOnFinest(group) && this.initialRevealHold !== 'hold-coverage');\n if (missing.length > 0) {\n // Only keep re-scheduling if some missing chunk is still\n // recoverable (fetching or awaiting a retry); a group whose chunks\n // have all permanently failed settles on its coarse substitute.\n let recoverable = false;\n for (const run of missing) {\n if (this.failedFiles.has(run.file)) continue;\n enqueueClassicFetch(\n pendingFetches,\n run.file,\n classicFetchPhaseForDesired(run, this.scene.source.lodBaseDistance),\n run,\n );\n recoverable = true;\n }\n // Far / L1+ gaps: install coarsest shell. L0 hold: do not fetch or\n // paint that shell - only the resolved L0 target is requested.\n // Startup hold: still stage any siblings already in cache (below).\n if (!holding) {\n this.substituteCoverage(group, now, pendingFetches, holdForTarget);\n }\n if (recoverable) {\n this.pendingWork = true;\n addsPending = true;\n }\n // During startup, continue into staging so available chunks upload\n // before every sibling is cached.\n if (!holding) continue;\n }\n if (holding && this.environmentPendingForReveal()) {\n // Keep pool headroom for the env tile; coverage stays cached until it\n // lands. Fetches for the frozen set are already queued above.\n this.pendingWork = true;\n addsPending = true;\n continue;\n }\n const forceStage = holding || (this.stagedSwapsEnabled && group.addCount > this.appendCap);\n if (forceStage && this.canStageGroup(group)) {\n const stagedNow = this.stageGroup(group, now, Math.max(0, this.appendCap - appended));\n appended += stagedNow;\n if (!group.adds.every((run) => this.staged.get(runKey(run))?.uploadedCount === run.count)) {\n this.pendingWork = true;\n addsPending = true;\n continue;\n }\n // Keep the old region for one additional frame when this tick wrote\n // the final hidden segment. The following tick performs only the\n // atomic active-list switch and forced sort, rather than combining\n // those costs with the last texture upload.\n if (stagedNow > 0 && !holding) {\n this.deferNextSortRequest();\n this.pendingWork = true;\n addsPending = true;\n continue;\n }\n this.commitStagedGroup(group);\n continue;\n }\n // The cap bounds per-tick upload work, but a group is indivisible\n // (splitting it would break region atomicity), so a single group larger\n // than the cap is deliberately let through when it comes first - the\n // one-frame hitch beats never applying it at all.\n if (!holding && appended > 0 && appended + group.addCount > this.appendCap) {\n this.pendingWork = true;\n addsPending = true;\n continue;\n }\n // Startup hold always stages (above); if staging could not start, keep\n // pending rather than applying visible coverage while the viewer is gated.\n if (holding) {\n this.pendingWork = true;\n addsPending = true;\n continue;\n }\n if (!this.applyGroup(group, now)) {\n this.pendingWork = true; // transient pool pressure; retry next tick\n // Rows are the scarce resource now, so stop holding retirements back.\n poolPressure = true;\n continue;\n }\n appended += group.addCount;\n }\n this.flushClassicFetches(pendingFetches, this.scene.source.lodBaseDistance, holding);\n // Counts only ticks that actually held something back, so reaching the bound\n // releases the retirement and starts the count over rather than latching the\n // gate off for the rest of the session.\n this.retireHeldTicks = held ? this.retireHeldTicks + 1 : 0;\n\n if (holding) {\n this.finishInitialRevealIfComplete();\n // Recheck failure after this tick's fetch outcomes land next frame; keep\n // streaming until release.\n if (this.initialRevealPhase === 'holding') this.pendingWork = true;\n }\n // Publish the CPU cache state before evicting, so `cacheBytes` reports the\n // peak the tick actually reached rather than the post-eviction figure - the\n // latter always sits at or under the limit and so can never show pressure.\n // These three were previously written only by the page-table plan, leaving\n // the streamed path reporting a permanent 0/0 that looked like \"no cache in\n // use\" when it meant \"not measured\" - the same blind spot `evicted` had.\n this.fetchCountsValue.cacheBytes = this.cacheBytesTotal;\n this.fetchCountsValue.cacheLimitBytes = this.cpuCacheBytes;\n if (this.cacheBytesTotal > this.cpuCacheBytes) this.fetchCountsValue.cacheFull = true;\n this.evictChunks(now);\n if (before === null) return null;\n return this.createPerformanceEvent(\n before.resident,\n before.staged,\n compactionCountBefore,\n startedAt,\n );\n }\n\n private rowAlignedSplats(count: number): number {\n return Math.ceil(count / DATA_TEXTURE_WIDTH) * DATA_TEXTURE_WIDTH;\n }\n\n /**\n * Startup hold seeds: the coverage group containing (or nearest to) the\n * camera within {@link LodSource.lodBaseDistance}. HiRes tiles often fail the\n * frustum test when most of the cell sits behind the camera - do **not**\n * require `inView`, or the hold seeds a screen-facing neighbour instead.\n * Coarser home levels come from {@link LodSource.runsAtLevelFor}.\n */\n private selectHomeSeedRuns(desiredRuns: readonly LodRun[]): LodRun[] {\n const base = this.scene.source.lodBaseDistance;\n const nearestCandidates = desiredRuns\n .filter(\n (run) =>\n run.coverageGroup !== undefined && (run.distance ?? Number.POSITIVE_INFINITY) <= base,\n )\n .sort(\n (a, b) =>\n (a.distance ?? Number.POSITIVE_INFINITY) - (b.distance ?? Number.POSITIVE_INFINITY) ||\n // Same distance: prefer in-view, then finer.\n (a.inView === true ? 0 : 1) - (b.inView === true ? 0 : 1) ||\n a.level - b.level ||\n a.leafStart - b.leafStart,\n );\n const homeGroup = nearestCandidates[0]?.coverageGroup;\n if (homeGroup === undefined) return [];\n return nearestCandidates.filter((run) => run.coverageGroup === homeGroup);\n }\n\n private coarsenHomeRuns(seeds: readonly LodRun[], nearLevel: number): LodRun[] {\n const out: LodRun[] = [];\n const source = this.scene.source;\n for (const seed of seeds) {\n if (seed.level === nearLevel) {\n out.push(seed);\n continue;\n }\n const alt = source.runsAtLevelFor?.(seed.leafStart, seed.leafEnd, nearLevel) ?? [];\n if (alt.length === 0) continue;\n for (const run of alt) {\n out.push({\n ...run,\n distance: seed.distance,\n inView: seed.inView,\n ...(seed.coverageGroup === undefined ? {} : { coverageGroup: seed.coverageGroup }),\n ...(seed.screenImportance === undefined\n ? {}\n : { screenImportance: seed.screenImportance }),\n });\n }\n }\n return out;\n }\n\n private criticalRunsFitCapacity(runs: readonly LodRun[]): boolean {\n let neededRows = 0;\n for (const run of runs) neededRows += this.rowAlignedSplats(run.count);\n return neededRows <= this.freeSplatCapacity;\n }\n\n private publishInitialRevealProgress(runs: readonly LodRun[]): void {\n const groups = new Map<string, LodRun[]>();\n for (const run of runs) {\n const g = run.coverageGroup !== undefined ? `g:${run.coverageGroup}` : runKey(run);\n let list = groups.get(g);\n if (!list) {\n list = [];\n groups.set(g, list);\n }\n list.push(run);\n }\n let stagedSplats = 0;\n let totalSplats = 0;\n let readyGroups = 0;\n for (const groupRuns of groups.values()) {\n let groupReady = true;\n for (const run of groupRuns) {\n totalSplats += run.count;\n const key = runKey(run);\n if (this.resident.has(key)) {\n stagedSplats += run.count;\n continue;\n }\n const staged = this.staged.get(key);\n stagedSplats += staged?.uploadedCount ?? 0;\n if (!staged || staged.uploadedCount !== run.count) groupReady = false;\n }\n if (groupReady) readyGroups++;\n }\n const prev = this.initialRevealStateValue;\n if (prev.status === 'degraded') {\n this.initialRevealStateValue = {\n status: 'degraded',\n reason: prev.reason,\n stagedSplats,\n totalSplats,\n readyGroups,\n totalGroups: groups.size,\n };\n return;\n }\n this.initialRevealStateValue = {\n status: 'pending',\n stagedSplats,\n totalSplats,\n readyGroups,\n totalGroups: groups.size,\n };\n }\n\n private releaseInitialReveal(\n status: 'ready' | 'degraded',\n reason?: 'capacity' | 'fetch-failed' | 'timeout',\n ): void {\n const runs = this.frozenCriticalRuns ?? [];\n this.publishInitialRevealProgress(runs);\n const progress = this.initialRevealStateValue;\n const stagedSplats =\n progress.status === 'pending' || progress.status === 'degraded' ? progress.stagedSplats : 0;\n const totalSplats =\n progress.status === 'pending' || progress.status === 'degraded' ? progress.totalSplats : 0;\n const readyGroups =\n progress.status === 'pending' || progress.status === 'degraded' ? progress.readyGroups : 0;\n const totalGroups =\n progress.status === 'pending' || progress.status === 'degraded' ? progress.totalGroups : 0;\n if (status === 'ready') {\n this.initialRevealStateValue = { status: 'ready' };\n } else {\n this.initialRevealStateValue = {\n status: 'degraded',\n reason: reason ?? 'fetch-failed',\n stagedSplats,\n totalSplats,\n readyGroups,\n totalGroups,\n };\n }\n this.frozenCriticalRuns = null;\n this.initialRevealPhase = 'released';\n this.pendingWork = true;\n }\n\n /**\n * Coverage hold: freeze covering runs for in-view cells (classic `.lcc`\n * physical cells at L1 near / coarsest far, `.lcc2` octree root-children).\n * Missing `coverageRunsFor` (or an empty result after fallback) releases\n * immediately so the mesh does not stay hidden with nothing to fetch.\n * If the mixed set overflows the pool, coarsen only the near (non-coarsest)\n * groups one more rung before degrading to progressive.\n */\n private captureCoverageHold(\n cameraLocal: THREE.Vector3,\n frustum: THREE.Frustum,\n now: number,\n cameraForward: THREE.Vector3,\n ): void {\n let coverage = this.scene.source.coverageRunsFor?.(cameraLocal, frustum, cameraForward) ?? [];\n if (coverage.length === 0) {\n if (this.environmentPendingForReveal()) {\n this.frozenCriticalRuns = [];\n this.initialRevealStartedAt = now;\n this.initialRevealPhase = 'holding';\n this.publishInitialRevealProgress([]);\n return;\n }\n this.initialRevealStateValue = { status: 'ready' };\n this.initialRevealPhase = 'released';\n return;\n }\n if (!this.criticalRunsFitCapacity(coverage)) {\n const coarsened = this.coarsenCoverageNearRuns(coverage);\n if (this.criticalRunsFitCapacity(coarsened)) {\n coverage = coarsened;\n } else {\n this.frozenCriticalRuns = coverage;\n this.releaseInitialReveal('degraded', 'capacity');\n return;\n }\n }\n this.frozenCriticalRuns = coverage;\n this.initialRevealStartedAt = now;\n this.initialRevealPhase = 'holding';\n this.publishInitialRevealProgress(coverage);\n }\n\n /**\n * Bump each coverage run one coarser rung when the source has one. Already-\n * coarsest (far) runs stay put so a tight pool only drops near L1 → L2.\n */\n private coarsenCoverageNearRuns(runs: readonly LodRun[]): LodRun[] {\n const out: LodRun[] = [];\n const source = this.scene.source;\n for (const run of runs) {\n const alt = source.runsAtLevelFor?.(run.leafStart, run.leafEnd, run.level + 1) ?? [];\n if (alt.length === 0 || alt.every((next) => next.level <= run.level)) {\n out.push(run);\n continue;\n }\n for (const next of alt) {\n out.push({\n ...next,\n distance: run.distance,\n inView: run.inView,\n ...(run.coverageGroup === undefined ? {} : { coverageGroup: run.coverageGroup }),\n ...(run.screenImportance === undefined ? {} : { screenImportance: run.screenImportance }),\n });\n }\n }\n return out;\n }\n\n private captureOrContinueInitialReveal(\n scheduledRuns: LodRun[],\n now: number,\n cameraLocal: THREE.Vector3,\n frustum: THREE.Frustum,\n cameraForward: THREE.Vector3,\n ): LodRun[] | null {\n if (this.initialRevealPhase === 'off' || this.initialRevealPhase === 'released') return null;\n\n if (this.initialRevealPhase === 'capture') {\n if (this.initialRevealHold === 'hold-coverage') {\n this.captureCoverageHold(cameraLocal, frustum, now, cameraForward);\n } else {\n // Prefer a full nearby L0 hold of the camera cell only. Tight pools\n // coarsen via the leaf ladder (L1, then L2) before degrading. Neighbours\n // are left for progressive streaming - they often beat home on\n // screenImportance. `desiredRuns` only has the *resolved* rung, so coarser\n // home cuts come from `runsAtLevelFor`.\n const seeds = this.selectHomeSeedRuns(scheduledRuns);\n let critical: LodRun[] = [];\n if (seeds.length === 0) {\n // Cold camera: nothing inside lodBaseDistance. Hold the nearest\n // coverage group in the near band (distance first, not screenImportance).\n const horizon =\n this.scene.source.lodBaseDistance *\n this.scene.source.lodMultiplier *\n this.scene.source.lodMultiplier;\n const fallback = scheduledRuns.filter(\n (run) =>\n run.level <= 2 &&\n run.coverageGroup !== undefined &&\n (run.distance ?? Number.POSITIVE_INFINITY) <= horizon,\n );\n const nearest = [...fallback].sort(\n (a, b) =>\n (a.distance ?? Number.POSITIVE_INFINITY) - (b.distance ?? Number.POSITIVE_INFINITY) ||\n (a.inView === true ? 0 : 1) - (b.inView === true ? 0 : 1) ||\n (a.screenImportance ?? Number.POSITIVE_INFINITY) -\n (b.screenImportance ?? Number.POSITIVE_INFINITY) ||\n a.leafStart - b.leafStart,\n )[0];\n if (!nearest) {\n this.initialRevealStateValue = { status: 'ready' };\n this.initialRevealPhase = 'released';\n return null;\n }\n critical =\n nearest.coverageGroup !== undefined\n ? fallback.filter((run) => run.coverageGroup === nearest.coverageGroup)\n : [nearest];\n if (!this.criticalRunsFitCapacity(critical)) {\n // Prefer a single fitting run over degrading the whole hold.\n critical =\n nearest.coverageGroup !== undefined\n ? fallback\n .filter(\n (run) =>\n run.coverageGroup === nearest.coverageGroup &&\n this.criticalRunsFitCapacity([run]),\n )\n .slice(0, 1)\n : fallback.filter((run) => this.criticalRunsFitCapacity([run])).slice(0, 1);\n }\n if (critical.length === 0 || !this.criticalRunsFitCapacity(critical)) {\n this.frozenCriticalRuns = critical.length > 0 ? critical : [nearest];\n this.releaseInitialReveal('degraded', 'capacity');\n return null;\n }\n this.frozenCriticalRuns = critical;\n this.initialRevealStartedAt = now;\n this.initialRevealPhase = 'holding';\n this.publishInitialRevealProgress(critical);\n } else {\n for (const nearLevel of [0, 1, 2] as const) {\n const home = this.coarsenHomeRuns(seeds, nearLevel);\n if (home.length === 0) continue;\n if (this.criticalRunsFitCapacity(home)) {\n critical = home;\n break;\n }\n critical = home;\n }\n if (critical.length === 0) {\n this.initialRevealStateValue = { status: 'ready' };\n this.initialRevealPhase = 'released';\n return null;\n }\n if (!this.criticalRunsFitCapacity(critical)) {\n this.frozenCriticalRuns = critical;\n this.releaseInitialReveal('degraded', 'capacity');\n return null;\n }\n this.frozenCriticalRuns = critical;\n this.initialRevealStartedAt = now;\n this.initialRevealPhase = 'holding';\n this.publishInitialRevealProgress(critical);\n }\n }\n }\n\n if (this.initialRevealPhase !== 'holding' || !this.frozenCriticalRuns) return null;\n\n if (\n this.initialRevealStartedAt !== undefined &&\n now - this.initialRevealStartedAt >= INITIAL_REVEAL_TIMEOUT_MS\n ) {\n this.releaseInitialReveal('degraded', 'timeout');\n return null;\n }\n\n for (const run of this.frozenCriticalRuns) {\n if (this.failedFiles.has(run.file) && !this.resident.has(runKey(run))) {\n const staged = this.staged.get(runKey(run));\n if (!staged || staged.uploadedCount !== run.count) {\n this.releaseInitialReveal('degraded', 'fetch-failed');\n return null;\n }\n }\n }\n\n this.publishInitialRevealProgress(this.frozenCriticalRuns);\n return this.frozenCriticalRuns;\n }\n\n /** After staging/commits, release the hold when every frozen run is resident. */\n private finishInitialRevealIfComplete(): void {\n if (this.initialRevealPhase !== 'holding' || !this.frozenCriticalRuns) return;\n this.publishInitialRevealProgress(this.frozenCriticalRuns);\n if (this.environmentPendingForReveal()) return;\n if (this.frozenCriticalRuns.every((run) => this.resident.has(runKey(run)))) {\n this.releaseInitialReveal('ready');\n }\n }\n\n /**\n * Startup hold still needs the environment tile when the scene ships one\n * and it starts enabled. Failed / unfit / disabled tiles do not block reveal.\n */\n private environmentPendingForReveal(): boolean {\n return (\n this.envFile !== undefined &&\n this.envEnabled &&\n !this.envUnfit &&\n this.envHandle === undefined &&\n !this.failedFiles.has(this.envFile)\n );\n }\n\n /** Creates a performance event for a changed streamed-LOD tick, if any. */\n private createPerformanceEvent(\n residentBefore: ReadonlyMap<string, number>,\n stagedBefore: ReadonlyMap<string, number>,\n compactionCountBefore: number,\n startedAt: number,\n ): StreamedSplatPerformanceEvent | null {\n let appendedCount = 0;\n let activeCount = 0;\n for (const [key, entry] of this.resident) {\n activeCount += entry.run.count;\n if (!residentBefore.has(key)) appendedCount += entry.run.count;\n }\n let removedCount = 0;\n for (const [key, count] of residentBefore) {\n if (!this.resident.has(key)) removedCount += count;\n }\n let stagedCount = 0;\n for (const [key, entry] of this.staged) {\n stagedCount += Math.max(0, entry.uploadedCount - (stagedBefore.get(key) ?? 0));\n }\n const compacted = this.compactionCount !== compactionCountBefore;\n if (appendedCount === 0 && removedCount === 0 && stagedCount === 0 && !compacted) return null;\n const timestamp = performance.now();\n return {\n timestamp,\n cpuMs: timestamp - startedAt,\n activeListMs: 0,\n uploadMs: 0,\n sortSubmitMs: 0,\n stagingTextureAllocations: 0,\n activeListUpdateRanges: 0,\n appendedCount,\n removedCount,\n stagedCount,\n uploadCount: appendedCount + stagedCount,\n activeCount,\n forcedSort:\n activeCount === 0 || appendedCount + removedCount >= activeCount * CONTENT_FORCE_FRACTION,\n compacted,\n };\n }\n\n /** Returns whether all new rows can coexist with the currently visible region. */\n private canStageGroup(group: SwapGroup): boolean {\n const unstaged = group.adds\n .filter((run) => !this.staged.has(runKey(run)))\n .reduce((sum, run) => sum + this.rowAlignedSplats(run.count), 0);\n return unstaged <= this.freeSplatCapacity;\n }\n\n /**\n * Uploads a bounded part of a replacement without rendering it.\n * Skips runs whose chunks are not yet cached so siblings can stage out of order.\n */\n private stageGroup(group: SwapGroup, now: number, allowance: number): number {\n if (allowance <= 0) return 0;\n let appended = 0;\n for (const run of group.adds) {\n const key = runKey(run);\n const chunk = this.cache.get(run.file);\n if (!chunk) continue;\n let entry = this.staged.get(key);\n if (!entry) {\n let handle: SplatRange;\n try {\n handle = this.reserveInactiveRange(run.count);\n } catch {\n this.compactionCount++;\n this.compact();\n try {\n handle = this.reserveInactiveRange(run.count);\n } catch {\n this.pendingWork = true;\n break;\n }\n }\n entry = { run, handle, uploadedCount: 0 };\n this.staged.set(key, entry);\n }\n // A swap group can contain several replacement runs. Once one run is\n // fully staged, advance to the next one instead of treating its zero\n // remaining count as an exhausted per-frame allowance.\n if (entry.uploadedCount === run.count) continue;\n const count = Math.min(run.count - entry.uploadedCount, allowance - appended);\n if (count <= 0) break;\n this.writeInactiveRange(\n entry.handle,\n sliceSplatData(chunk.data, run.offset + entry.uploadedCount, count),\n entry.uploadedCount,\n );\n entry.uploadedCount += count;\n chunk.lastUsed = now;\n appended += count;\n if (entry.uploadedCount === run.count) {\n this.writeLodLevelChannel(entry.handle, run.level);\n for (const [name, channel] of this.persistentChannels) {\n this.applyPersistentRun(name, channel, run, entry.handle);\n }\n }\n if (appended >= allowance) break;\n }\n return appended;\n }\n\n /** Switches a fully staged region from old to new visibility in one tick. */\n private commitStagedGroup(group: SwapGroup): void {\n for (const run of group.adds) {\n const entry = this.staged.get(runKey(run));\n if (!entry || entry.uploadedCount !== run.count) {\n throw new Error('StreamedSplatMesh: incomplete staged group commit.');\n }\n }\n\n for (const [key, entry] of group.removes) {\n if (!this.resident.has(key)) continue;\n this.removeRange(entry.handle);\n this.resident.delete(key);\n }\n for (const run of group.adds) {\n const key = runKey(run);\n const entry = this.staged.get(key) as {\n run: LodRun;\n handle: SplatRange;\n uploadedCount: number;\n };\n this.setRangeActive(entry.handle, true);\n this.resident.set(key, entry);\n this.staged.delete(key);\n }\n }\n\n /**\n * Applies one swap group atomically within this tick: removals first\n * (freeing pool rows for the replacements), then all adds. Returns false\n * without touching anything when the group cannot fit even after its own\n * removals - the caller defers it and the old runs keep rendering.\n */\n private applyGroup(group: SwapGroup, now: number): boolean {\n const rowSplats = (count: number): number =>\n Math.ceil(count / DATA_TEXTURE_WIDTH) * DATA_TEXTURE_WIDTH;\n const needed = group.adds.reduce((sum, run) => sum + rowSplats(run.count), 0);\n const freed = group.removes.reduce((sum, [, entry]) => sum + rowSplats(entry.run.count), 0);\n if (needed > this.freeSplatCapacity + freed) return false;\n\n for (const [key, entry] of group.removes) {\n if (!this.resident.has(key)) continue;\n this.removeRange(entry.handle);\n this.resident.delete(key);\n }\n for (const run of group.adds) {\n this.appendRun(run, now);\n }\n return true;\n }\n\n /** Appends one run from the cache, compacting the pool on fragmentation. */\n private appendRun(run: LodRun, now: number): void {\n const chunk = this.cache.get(run.file);\n if (!chunk) return; // caller pre-checked; only reachable on races\n const slice = sliceSplatData(chunk.data, run.offset, run.count);\n let handle: SplatRange;\n try {\n handle = this.appendRange(slice);\n } catch {\n this.compactionCount++;\n this.compact();\n try {\n handle = this.appendRange(slice);\n } catch {\n this.pendingWork = true; // retry next tick\n return;\n }\n }\n this.resident.set(runKey(run), { run, handle });\n chunk.lastUsed = now;\n this.writeLodLevelChannel(handle, run.level);\n // Re-apply any persistent channel edits for this file's splats - this is\n // what makes a painted mask survive the chunk being evicted and reloaded\n // (the pool row is fresh, but `(file, local index)` is a stable identity).\n for (const [name, channel] of this.persistentChannels) {\n this.applyPersistentRun(name, channel, run, handle);\n }\n }\n\n /**\n * Writes the stored edits for one run's `[offset, offset + count)` splats\n * into its freshly appended pool range. No-op when the file has no edits.\n */\n private applyPersistentRun(\n name: string,\n channel: PersistentChannel,\n run: LodRun,\n handle: SplatRange,\n ): void {\n const fileEdits = channel.edits.get(run.file);\n if (!fileEdits || fileEdits.size === 0) return;\n const data = channel.type === 'byte' ? new Uint8Array(run.count) : new Float32Array(run.count);\n // Seed with the channel's fill: this whole-run write must leave unedited\n // splats at their default, not clobber them to 0.\n if (channel.fill) data.fill(channel.fill);\n let any = false;\n for (let k = 0; k < run.count; k++) {\n const value = fileEdits.get(run.offset + k);\n if (value !== undefined) {\n data[k] = value;\n any = true;\n }\n }\n if (any) this.writeChannel(handle, name, data);\n }\n\n /**\n * Covers a deferred group's leaves that no resident run covers with each\n * leaf's coarsest (pinned, hence cached) level, so a region waiting on a\n * fetch shows coarse detail instead of nothing. The substitutes are\n * intentionally not \"desired\": the next reschedule swaps them for the\n * real level once its chunk has arrived.\n *\n * Near-camera refinements (`distance <= lodBaseDistance`) skip the coarse\n * paint and its pin fetch entirely - cold load requests only the target cut\n * for that cell. Far gaps keep the shell.\n */\n private substituteCoverage(\n group: SwapGroup,\n now: number,\n pendingFetches: Map<number, ClassicFetchWant>,\n holdForFinest: boolean,\n ): void {\n const span = group.leafEnd - group.leafStart;\n // Reused across calls: this runs for every deferred group of every streamed\n // mesh, every reschedule - ~800 times a second on a multi-mesh scene, at\n // a measured mean span of 60k leaves. Allocating the bitmap each time threw\n // away half a gigabyte in ten seconds and made this the single most\n // expensive function in the frame. The scratch only grows.\n if (this.coverageScratch === undefined || this.coverageScratch.length < span) {\n this.coverageScratch = new Uint8Array(span);\n }\n const covered = this.coverageScratch;\n covered.fill(0, 0, span);\n for (const { run } of this.resident.values()) {\n const from = Math.max(run.leafStart, group.leafStart);\n const to = Math.min(run.leafEnd, group.leafEnd);\n // `fill` over the overlap rather than a per-leaf loop: same marking, but\n // one memset instead of ~18k interpreted iterations per call.\n if (to > from) covered.fill(1, from - group.leafStart, to - group.leafStart);\n }\n\n // Walk the gaps with native scans rather than leaf-by-leaf in JS: the span\n // averages 60k leaves and is mostly covered, so the old loop spent its time\n // stepping over ones. `indexOf` on the bitmap does the same walk in memchr.\n // The scratch is oversized, hence the exact-length view to bound the search.\n const view = covered.subarray(0, span);\n let offset = 0;\n while (offset < span) {\n const gapStart = view.indexOf(0, offset);\n if (gapStart < 0) break;\n const nextCovered = view.indexOf(1, gapStart);\n const gapEnd = nextCovered < 0 ? span : nextCovered;\n const cursor = group.leafStart + gapStart;\n const end = group.leafStart + gapEnd;\n for (const run of this.scene.source.coarsestRunsFor(cursor, end)) {\n if (this.resident.has(runKey(run))) continue;\n if (holdForFinest) {\n // Do not paint coarsest discs while finest downloads. Do not enqueue\n // the pin - those slots belong to the group's finest fetches.\n this.fetchCountsValue.uncovered +=\n Math.min(run.leafEnd, end) - Math.max(run.leafStart, cursor);\n continue;\n }\n if (!this.cache.has(run.file)) {\n enqueueClassicFetch(\n pendingFetches,\n run.file,\n classicFetchPhaseForCoverage(run, this.scene.source.lodBaseDistance),\n run,\n );\n // This is the one path in the substitute that gives up: the gap keeps\n // no coverage at all until the chunk lands, so those leaves render as\n // nothing. Expected once during initial load (the coarsest level has\n // not arrived yet) and *not* expected afterwards, because the coarsest\n // files are pinned against eviction - so a count that climbs after the\n // scene has settled localizes a hole to here rather than to the swap\n // path. Counted in leaves, clipped to the gap, since a coarsest run\n // may span past it.\n this.fetchCountsValue.uncovered +=\n Math.min(run.leafEnd, end) - Math.max(run.leafStart, cursor);\n continue;\n }\n // A coarsest run may span beyond `[cursor, end)` - LCC2 root children\n // cover whole subtrees and cannot be clipped to a leaf sub-interval.\n // Octree intervals nest, so every resident run overlapping it lies\n // fully inside it: remove those first (the whole region temporarily\n // shows coarse), or their leaves would render twice - a bright flash\n // for the fetch window, permanent if the missing chunk never loads.\n for (const [key, entry] of this.resident) {\n if (entry.run.leafStart < run.leafEnd && entry.run.leafEnd > run.leafStart) {\n this.removeRange(entry.handle);\n this.resident.delete(key);\n }\n }\n this.appendRun(run, now);\n }\n offset = gapEnd;\n }\n }\n\n /** Issues pending classic-path chunk wants in group-priority order. */\n private flushClassicFetches(\n pending: Map<number, ClassicFetchWant>,\n lodBaseDistance: number,\n holdingNearL0 = false,\n ): void {\n if (pending.size === 0) return;\n stampClassicFetchGroups(pending, lodBaseDistance, holdingNearL0);\n const ordered = [...pending.entries()].sort((a, b) =>\n compareClassicFetches(a[1], b[1], a[0], b[0]),\n );\n this.preemptClassicFetches(ordered);\n for (const [file, want] of ordered) this.requestChunk(file, want.kind, want);\n }\n\n /**\n * A camera turn must not wait for all eight old visible requests to finish.\n * Only classic requests carry a precise rank; page-table work retains its\n * own scheduler and is never cancelled here.\n */\n private preemptClassicFetches(ordered: readonly [number, ClassicFetchWant][]): void {\n for (const [file, want] of ordered) {\n if (this.cache.has(file) || this.fetching.has(file)) continue;\n let worstFile: number | undefined;\n let worstWant: ClassicFetchWant | undefined;\n for (const [activeFile, active] of this.fetching) {\n if (!active.classicWant) continue;\n if (\n !worstWant ||\n compareClassicFetches(active.classicWant, worstWant, activeFile, worstFile as number) > 0\n ) {\n worstFile = activeFile;\n worstWant = active.classicWant;\n }\n }\n if (\n worstWant &&\n worstFile !== undefined &&\n compareClassicFetches(want, worstWant, file, worstFile) < 0\n ) {\n this.fetching.get(worstFile)?.controller.abort();\n }\n // The current request waits for the abort's finally callback to release\n // its slot; do not churn through every queued request in one tick.\n if (this.fetching.size >= this.maxInflight) return;\n }\n }\n\n /**\n * Loads the always-resident environment tile once, on the first update after\n * it is wanted. The tile has no LOD ladder and no manifest count, so it is\n * appended whole (measuring its splat count at decode) and thereafter toggled\n * by flipping its pool range active - never scheduled, refetched, or evicted.\n * When `pending` is supplied, a miss is ranked as an `'environment'` want so\n * it issues ahead of LOD coverage.\n */\n private updateEnvironment(now: number, pending?: Map<number, ClassicFetchWant>): void {\n const file = this.envFile;\n if (file === undefined || this.envHandle !== undefined || !this.envEnabled || this.envUnfit) {\n return;\n }\n const chunk = this.cache.get(file);\n if (!chunk) {\n if (!this.failedFiles.has(file)) {\n // The environment tile is always-resident coverage, never speculation:\n // a mesh that cannot fetch it renders no background at all.\n if (pending) this.enqueueEnvironmentFetch(pending, file);\n else this.requestChunk(file, 'priority');\n this.pendingWork = true;\n }\n return;\n }\n // The env sits outside the LOD budget, in the pool's capacity headroom -\n // which nothing guarantees is free (`maxResidentSplats` cannot include a\n // count only known at decode). Pre-check before touching the pool: without\n // this, an env that never fits would pay a full-pool compact() every\n // reschedule tick, forever.\n const rowAligned = Math.ceil(chunk.data.count / DATA_TEXTURE_WIDTH) * DATA_TEXTURE_WIDTH;\n if (rowAligned > this.capacity) {\n this.envUnfit = true; // could never fit even an empty pool\n warn(\n `the environment tile (${chunk.data.count} splats) exceeds the ` +\n `pool capacity (${this.capacity}); it will not be shown. Raise the ` +\n `splat budget to fit it.`,\n );\n return;\n }\n if (rowAligned > this.freeSplatCapacity) {\n // No free rows yet - compaction only defragments, it cannot create\n // them. Retry cheaply once LOD churn frees room.\n this.pendingWork = true;\n return;\n }\n let handle: SplatRange;\n try {\n handle = this.appendRange(chunk.data);\n } catch {\n // Enough rows exist but no contiguous span does; defragment once.\n this.compactionCount++;\n this.compact();\n try {\n handle = this.appendRange(chunk.data);\n } catch {\n this.pendingWork = true; // no room this tick; retry next\n return;\n }\n }\n this.envHandle = handle;\n this.envSplatCount = chunk.data.count;\n chunk.lastUsed = now;\n }\n\n /** Ranks the env tile ahead of every LOD want in {@link flushClassicFetches}. */\n private enqueueEnvironmentFetch(pending: Map<number, ClassicFetchWant>, file: number): void {\n if (pending.has(file)) return;\n pending.set(file, {\n kind: 'priority',\n phase: 'environment',\n distance: 0,\n level: 0,\n inView: true,\n coverageGroup: -1,\n leafStart: 0,\n leafEnd: 0,\n screenImportance: Number.NEGATIVE_INFINITY,\n groupDistance: 0,\n groupPending: 1,\n groupInView: true,\n groupScreenImportance: Number.NEGATIVE_INFINITY,\n groupFinest: true,\n groupId: 'environment',\n groupClass: 0,\n });\n }\n\n /**\n * Page-table reschedule (`foveationMode: 'page-table'`): posts the camera to the\n * worker, which owns the cache + traversal + pager and replies asynchronously\n * with a paging plan. Coalesced to one outstanding request so the main thread\n * never blocks. Also drives chunk fetching, in priority order.\n */\n private reschedulePageTable(\n cameraLocal: THREE.Vector3,\n forwardLocal: THREE.Vector3,\n frustum: THREE.Frustum,\n now: number,\n ): void {\n // 1. What the last frontier wanted and did not have, biggest-on-screen\n // first. This is the detail the camera is pointed at, so it takes the\n // fetch slots before anything else - issued *after* the sweep below it\n // was silently dropped by the in-flight cap on every tick, and the whole\n // capture downloaded in file order while the view stayed coarse.\n for (const file of this.pageTableFetchPriority) this.requestChunk(file, 'priority');\n // 2. The source's camera-directed coarse base, for far coverage.\n const desiredFiles = new Set(this.pageTableFetchPriority);\n for (const run of this.scene.source.computeDesiredRuns(cameraLocal, frustum, now)) {\n desiredFiles.add(run.file);\n this.requestChunk(run.file, 'base');\n }\n // Cancel detail this mesh no longer wants, so its slots go back to the\n // scene now rather than when a superseded request happens to finish. The\n // classic path has always done this; the page-table path never did, which\n // on a shared pipe means a camera cut kept paying for the old view.\n // Sweep fetches are exempt: they are file-order pre-warming that no\n // frontier plan ever names, so matching them against `desiredFiles` would\n // abort every one of them on the very next reschedule.\n for (const [file, entry] of this.fetching) {\n if (entry.kind === 'sweep') continue;\n if (!desiredFiles.has(file) && !this.scene.pinnedFiles.has(file)) entry.controller.abort();\n }\n // 3. Background sweep over the slots that remain: pull the lowest uncached\n // chunk (file order is coarse → fine). Once the cache holds the scene,\n // turning the camera is served from RAM in one traversal instead of a\n // level-by-level network ladder. It keeps a reserve free so (1) is never\n // starved, and pauses whenever the worker cache is at its cap - past that\n // point sweeping only evicts what the frontier is using. It resumes on\n // its own when the cap rises, which under a scene-wide `ChunkCacheBudget`\n // is what happens as the camera approaches this mesh.\n //\n // Only a mesh with weight sweeps. This is speculation about a camera\n // move that has not happened, and it is unbounded - it wants the entire\n // capture. On a scene of streamed additional meshes, every hidden and distant one\n // speculating at once is the traffic that delays the mesh the viewer\n // is looking at. The cost of gating it is that re-focusing a mesh that\n // went cold refetches instead of hitting a warm cache.\n if (!this.pageTableCacheAtLimit && this.sweepAllowed()) {\n const sweepCap = Math.max(1, this.maxInflight - PAGETABLE_PRIORITY_SLOTS);\n const files = this.scene.chunkUrls.length;\n for (let f = 0; f < files && this.fetching.size < sweepCap; f++) {\n if (!this.pageTableCachedFiles.has(f)) this.requestChunk(f, 'sweep');\n }\n }\n if (this.pageTableInFlight) return; // one traversal outstanding - coalesce\n\n this.pageTableInFlight = true;\n this.postToWorker({\n type: 'reschedule',\n seq: ++this.pageTableSeq,\n cameraLocal: [cameraLocal.x, cameraLocal.y, cameraLocal.z],\n cameraForward: [forwardLocal.x, forwardLocal.y, forwardLocal.z],\n ...this.pageTableFoveation,\n // Spark's cut is `pixel_scale × lodScale ≤ limit`, and the traversal only\n // ever sees one side of that - so scaling the limit down by `lodScale` is\n // the same comparison, with no protocol change.\n limit: this.pageTableLimit / this.lodScaleValue,\n budget: this.pageTableDrawBudget,\n });\n }\n\n /**\n * Applies a paging plan from the worker to the slab - fast memcpy writes only,\n * no traversal or gather on the main thread - then fetches the chunks the\n * frontier wants next, and reschedules again if chunks are still streaming.\n */\n private applyFrontierPlan(plan: FrontierPlanMessage): void {\n this.pageTableInFlight = false;\n if (this.pageTableDisposed || this.slabPages.length === 0) return;\n // Storage may have moved since this plan was built (a reschedule answered\n // from the old capacity, then a resize landed). Such a plan must still be\n // applied, clamped to the slots that exist: the worker's pager has already\n // mutated itself as if the whole plan ran, so dropping it desynchronizes the\n // two permanently - later plans only carry deltas, and the un-applied slots\n // keep stale (or never-written) content underneath a live resident count.\n //\n // Clamping is exact rather than approximate because a resize never remaps\n // the slots below the boundary: `syncSlabPages` only pushes or pops tail\n // pages, and `FrontierPager.resize` keeps `[0, keep)` untouched. So \"the\n // plan, truncated at the new capacity\" is precisely the pager's own state.\n const limit = this.pagerSlots;\n if (plan.capacity !== limit) {\n // The worker will re-traverse at the new capacity; make sure it does.\n this.pendingWork = true;\n this.lastScheduleTime = -Infinity;\n }\n // The pager emits moves in ascending slot order, and a swap-remove of a\n // contiguous block of leavers produces long runs of consecutive slots. Write\n // them a run at a time: one call per moved splat meant a `Box3` pass, a\n // bounds union and a row-range mark for every one of them, which stalled the\n // main thread for seconds whenever a camera move churned the frontier.\n const applyStartedAt = performance.now();\n const slots = plan.moveSlots;\n for (let i = 0; i < slots.length;) {\n let run = 1;\n while (i + run < slots.length && (slots[i + run] as number) === (slots[i] as number) + run) {\n run++;\n }\n const start = slots[i] as number;\n const clamped = Math.min(run, limit - start);\n if (clamped > 0) this.writeSlabSlots(slicePlanRun(plan.moves, i, clamped), start, clamped);\n i += run;\n }\n if (plan.appends.count > 0) {\n const clamped = Math.min(plan.appends.count, limit - plan.appendStart);\n if (clamped > 0) this.writeSlabSlots(plan.appends, plan.appendStart, clamped);\n }\n const writeFinishedAt = performance.now();\n // Draw exactly the used prefix.\n const resident = Math.min(plan.residentCount, limit);\n this.setSlabResident(resident);\n // Freed tail slots leave the active list, so their data is not drawn - but\n // zero it anyway. It costs a fill over the freed range only, and it means a\n // slot that somehow ends up drawn without being written renders nothing\n // instead of whichever coarse node used to own it (one enormous splat).\n const degenerateStart = Math.min(plan.degenerateStart, limit);\n const degenerateCount = Math.min(plan.degenerateCount, limit - degenerateStart);\n if (degenerateCount > 0) this.degenerateSlabSlots(degenerateStart, degenerateCount);\n const residentFinishedAt = performance.now();\n this.pageTableDrawn = resident;\n this.frontierConverged = plan.converged;\n this.pendingFrontierSplats = plan.pendingFrontierSplats ?? 0;\n this.staleResidentSplats = plan.staleResidentSplats ?? 0;\n this.lastPlanAppends = plan.lastPlanAppends ?? plan.appends.count;\n this.lastPlanMoves = plan.lastPlanMoves ?? plan.moveSlots.length;\n this.lastPlanGeneration = plan.planGeneration ?? this.lastPlanGeneration + 1;\n this.lastPlanBudget = plan.planBudget ?? this.pageTableDrawBudget;\n if (plan.cameraLocal) {\n this.lastPlanCamera = plan.cameraLocal;\n this.firstFrontierCamera ??= plan.cameraLocal;\n }\n if (plan.gatherMissing > 0) {\n // Splats whose chunk was evicted under them were written as zeros into\n // slots that are still drawn - holes in the coverage. Eviction protects\n // every chunk with resident splats, so this should be unreachable.\n warn(\n `StreamedSplatMesh: page-table plan gathered ${plan.gatherMissing} splats from ` +\n `evicted chunks; they render as holes.`,\n );\n }\n // Applying a plan runs off the render loop's own timing, so its cost is\n // invisible to `getUpdateTimings` even though it lands on the same thread.\n // Recorded because a churning frontier can make this the largest stall in a\n // frame, and a cap has to be aimed at whichever half dominates.\n const planTimings = this.planTimingsValue;\n planTimings.applyMs = residentFinishedAt - applyStartedAt;\n planTimings.writeMs = writeFinishedAt - applyStartedAt;\n planTimings.residentMs = residentFinishedAt - writeFinishedAt;\n planTimings.moves = plan.moveSlots.length;\n planTimings.appends = plan.appends.count;\n if (planTimings.applyMs > planTimings.worstApplyMs) {\n planTimings.worstApplyMs = planTimings.applyMs;\n planTimings.worstSplats = planTimings.moves + planTimings.appends;\n }\n // Follow the cut with the screen-radius band. The worker refines below the\n // quality target to spend the draw budget, and those finer nodes project\n // smaller - a band still sized for the target cut would cull them, so the\n // extra budget would buy nothing visible. Scaling by the same ratio keeps\n // the band spanning one LOD level.\n if (this.frontierBandBase !== null && plan.solvedLimit > 0 && this.pageTableLimit > 0) {\n const ratio = Math.min(1, plan.solvedLimit / this.pageTableLimit);\n this.setScreenRadiusBand(\n this.frontierBandBase.min * ratio,\n this.frontierBandBase.max * ratio,\n );\n }\n this.invalidateSort();\n if (plan.dropped > 0) {\n // The traversal is budget-bounded, so the slab always has room. If it does\n // not, the pool is smaller than the draw budget and part of the frontier is\n // silently missing - say so rather than render a hole.\n warn(\n `StreamedSplatMesh: page-table slab full, dropped ${plan.dropped} frontier splats ` +\n `(draw budget ${this.pageTableDrawBudget} exceeds the pool).`,\n );\n }\n // Worker-evicted chunks must be forgotten here too, or they can never refetch.\n for (let i = 0; i < plan.evicted.length; i++) {\n this.pageTableCachedFiles.delete(plan.evicted[i] as number);\n }\n this.fetchCountsValue.cacheBytes = plan.cacheBytes;\n this.fetchCountsValue.cacheLimitBytes = plan.cacheLimitBytes;\n // Recomputed every plan, not latched: the sweep must resume when the scene\n // budget raises this mesh's allowance. `cacheFull`/`evicted` stay monotonic\n // - they are diagnostics answering \"did this happen\", not live state.\n this.pageTableCacheAtLimit = plan.cacheBytes >= plan.cacheLimitBytes;\n if (plan.evicted.length > 0) {\n this.fetchCountsValue.cacheFull = true;\n this.fetchCountsValue.evicted += plan.evicted.length;\n }\n // The chunks the frontier wants next, biggest-on-screen first - requested now\n // and kept as the priority list the next reschedule fetches before anything.\n this.pageTableFetchPriority = Array.from(plan.touched);\n for (const file of this.pageTableFetchPriority) this.requestChunk(file, 'priority');\n // Keep refining while chunks stream in (the frontier keeps changing), and\n // while the worker is still ramping its budget up to the governed one - that\n // ramp is what keeps a hard camera cut from arriving as one ~100 ms plan, so\n // the next pass must follow immediately or detail stalls where it stopped.\n if (this.fetching.size > 0 || !plan.converged) {\n this.pendingWork = true;\n if (!plan.converged) this.lastScheduleTime = -Infinity;\n }\n }\n\n /** Forwards a decoded chunk's arrays to the worker (buffers transferred) so the\n * worker's cache/traversal/gather can use it. */\n private forwardChunkToWorker(file: number, data: SplatData): void {\n const tree = data.radTree;\n if (!tree) return;\n this.pageTableCachedFiles.add(file);\n // Only forward SH the pool will actually render. A `.rad` chunk decodes\n // whatever bands the file carries regardless of what was asked for, and the\n // worker charges its cache for every byte it is handed - 15 coefficients is\n // 60 B/splat against 40 B for position, colour and covariance combined, so\n // SH the mesh has declined was **60% of the chunk cache**.\n //\n // Measured on the reference capture with SH declined: the worker counted\n // 100 B/splat where the cache-floor estimate assumes 40, so the cache filled\n // at ~52 chunks' worth of its limit instead of the 132 the estimate predicts\n // and the frontier thrashed - one eviction and one refetch every couple of\n // seconds, forever, with resident chunks oscillating in the low 70s.\n //\n // Dropping it here also makes `estimateSceneDecodedBytes` correct rather\n // than merely larger: both sides then agree on 40 B/splat.\n //\n // `shBands` rather than the pool's `packedShBands` (which is private, and\n // protected would put it in the published `.d.ts`): they agree here, because\n // the only way they differ is palette SH, and a streamed mesh never has it -\n // the slicer drops `chunk.sh` before a chunk ever reaches the pool.\n const sh = this.shBands > 0 ? data.shPacked : undefined;\n this.postToWorker(\n {\n type: 'chunk',\n file,\n count: data.count,\n positions: data.positions,\n colors: data.colors,\n covariances: data.covariances,\n childCount: tree.childCount,\n childStart: tree.childStart,\n size: tree.size,\n shBands: sh?.bands ?? 0,\n ...(sh ? { shPacked: sh.packed, shRange: sh.range } : {}),\n },\n [\n data.positions.buffer,\n data.colors.buffer,\n data.covariances.buffer,\n tree.childCount.buffer,\n tree.childStart.buffer,\n tree.size.buffer,\n ...(sh ? [sh.packed.buffer] : []),\n ],\n );\n }\n\n /** Parallel chunk fetches. HTTP/2 multiplexes them; on HTTP/1.1 the browser's\n * per-host cap simply queues. Same cap for classic and page-table so near\n * detail is not structurally starved on the non-page-table path. */\n private get maxInflight(): number {\n return MAX_INFLIGHT;\n }\n\n /**\n * Whether this mesh may run its speculative background sweep. A mesh with no\n * weight is hidden or suspended; a mesh with no `fetchWeight` at all is a\n * host that never asked for arbitration, and keeps the old behaviour.\n */\n /**\n * Sets this mesh's share of the scene's fetch bandwidth, as\n * {@link StreamedSplatMeshOptions.fetchWeight} does at load.\n *\n * The weight normally closes over the mesh itself (`() =>\n * governor.weightOf(mesh)`), which a host cannot express until `load`\n * resolves - hence a setter as well as an option. Pass `undefined` to go back\n * to unarbitrated sweeping.\n */\n setFetchWeight(weight: (() => number) | undefined): void {\n this.fetchWeight = weight;\n }\n\n private sweepAllowed(): boolean {\n // The `smooth` profile (the default on mobile) declines the sweep outright.\n //\n // The sweep is speculative pre-warming of the *whole capture*, and without a\n // scene-wide `cacheBudget` it does not stop until every chunk is cached: the\n // cap is sized from the capture itself (`min(PAGETABLE_CACHE_FLOOR_BYTES,\n // estimateSceneDecodedBytes)`), so on any capture that fits there is never\n // an eviction to reach it. Measured on the 5.9M-leaf reference `.rad` with\n // SH declined: a 235 MB cache floor against a 235 MB decoded capture, i.e. a\n // steady ~1 chunk/second drip pulling all 447 MB down and decoding it, long\n // after the view had settled at full detail. Multiply that by the meshes in\n // a multi-mesh scene and it is the largest memory risk in a viewer - which is\n // what `cacheBudget` bounds, without stopping the sweep itself.\n //\n // On a desktop that is a good trade - RAM is cheap and turning the camera is\n // then served from memory instead of a level-by-level network ladder. On a\n // phone it is the wrong one in every currency at once: hundreds of MB of\n // possibly-metered download, a decoded cache that rivals the splat pool on a\n // device that gets its tab killed for exactly that, and continuous decode\n // CPU (and therefore heat) spent on a camera move that may never happen.\n // What it costs to decline: refinement after a turn fetches on demand.\n if (this.performanceProfile === 'smooth') return false;\n if (this.fetchWeight === undefined) return true;\n const weight = this.fetchWeight();\n return Number.isFinite(weight) && weight > 0;\n }\n\n /** Aborts in-flight fetches of one kind; their slots return through `finally`. */\n private abortFetches(kind: ChunkFetchKind): void {\n for (const entry of this.fetching.values()) {\n if (entry.kind === kind) entry.controller.abort();\n }\n }\n\n private requestChunk(file: number, kind: ChunkFetchKind, classicWant?: ClassicFetchWant): void {\n if (\n this.cache.has(file) ||\n this.pageTableCachedFiles.has(file) ||\n this.fetching.has(file) ||\n this.fetching.size >= this.maxInflight\n ) {\n return;\n }\n if (this.failedFiles.has(file)) return; // given up\n const backoff = this.retrying.get(file);\n if (backoff && performance.now() < backoff.readyAt) return; // waiting to retry\n\n // Counted here, past every \"already have it / already fetching / capped\"\n // guard, so the totals mean \"requests that became real network work\".\n this.fetchCountsValue[kind]++;\n const url = this.scene.chunkUrls[file];\n if (url === undefined) {\n // A manifest referencing an out-of-range file index can never load;\n // fail it terminally so its groups settle on their coarse substitutes\n // instead of rescheduling (and spinning the indicator) forever.\n warn(`StreamedSplatMesh: manifest references unknown chunk file #${file}.`);\n this.failedFiles.add(file);\n return;\n }\n // Scene-wide arbitration, after every local reason not to fetch: a slot\n // taken here is a slot denied to a sibling, so it must not be spent on a\n // request the mesh would have skipped anyway. A denial is not a failure and\n // deliberately leaves `retrying` alone - the mesh simply did not fetch this\n // tick, and the scheduler wakes it when the pipe frees up.\n if (this.fetchHandle && !this.fetchScheduler?.tryAcquire(this.fetchHandle, kind)) return;\n const controller = new AbortController();\n this.fetching.set(file, { controller, kind, classicWant });\n this.loader\n .load(url, {\n kind: this.scene.chunkKind,\n signal: controller.signal,\n ...this.scene.chunkOptions?.[file],\n })\n .then((data) => {\n // A chunk that resolved just before dispose still lands here one\n // microtask later; keeping it would repopulate the cleared cache (or\n // post to a terminated frontier worker).\n if (this.disposed) return;\n this.retrying.delete(file);\n // Formats whose LOD structure lives in the chunks (a `.rad` tree) learn\n // it here - the source uses it for its coarse-base ranking. Read it before\n // any transfer.\n this.scene.source.onChunkDecoded?.(file, data);\n if (this.frontierWorker) {\n // Page-table mode: the worker owns the cache. Forward the chunk (its\n // buffers are transferred, so the main thread does not keep it).\n this.forwardChunkToWorker(file, data);\n this.pendingWork = true; // a new chunk changes the frontier\n } else {\n this.cacheChunk(file, data);\n }\n })\n .catch((error: unknown) => {\n // Aborts (the camera moved on, or the mesh was disposed) are not\n // failures: a later reschedule re-requests the file if it is still\n // wanted. `isAbortError` also matches the non-DOMException AbortError\n // `ChunkLoader.dispose` raises where DOMException is unavailable -\n // treating that as a failure would log and retry against a dead worker.\n if (isAbortError(error)) return;\n const attempts = (this.retrying.get(file)?.attempts ?? 0) + 1;\n if (attempts >= MAX_CHUNK_ATTEMPTS) {\n this.retrying.delete(file);\n this.failedFiles.add(file);\n // Terminal: the region silently settles on its coarse substitute\n // forever, so say why once - otherwise a scene that is simply\n // missing detail looks like a renderer bug.\n warn(\n `StreamedSplatMesh: gave up on chunk #${file} (${url}) after ${attempts} attempts.`,\n error,\n );\n } else {\n // Exponential backoff; the idle reschedule (≤250 ms) picks it up.\n const delay = RETRY_BASE_MS * 2 ** (attempts - 1);\n this.retrying.set(file, { attempts, readyAt: performance.now() + delay });\n }\n })\n .finally(() => {\n this.fetching.delete(file);\n // Released here rather than on success, so an aborted or failed fetch\n // hands its slot back too - a leak here silently shrinks the scene's\n // whole pipe until the pool is torn down.\n if (this.fetchHandle) this.fetchScheduler?.release(this.fetchHandle);\n this.pendingWork = true;\n });\n }\n\n /**\n * Stores a decoded chunk while keeping {@link cacheBytesTotal} in step. The\n * counter replaces a full-cache re-sum on every reschedule; every mutation\n * of {@link cache} (this method, eviction, dispose's clear) maintains it.\n */\n private cacheChunk(file: number, data: SplatData): void {\n const previous = this.cache.get(file);\n if (previous !== undefined) this.cacheBytesTotal -= previous.bytes;\n const bytes = chunkBytes(data);\n this.cache.set(file, { data, bytes, lastUsed: performance.now() });\n this.cacheBytesTotal += bytes;\n }\n\n private evictChunks(now: number): void {\n let total = this.cacheBytesTotal;\n if (total <= this.cpuCacheBytes) return;\n\n // Evict least-recently-used chunks first; never a chunk touched this\n // tick, and never a pinned (coarsest-level) chunk - those are the\n // substitute coverage and must stay sliceable. Evicting a chunk that\n // still backs a resident run is safe - its splats already live in the\n // pool; only future re-slicing would refetch.\n const candidates = [...this.cache.entries()]\n .filter(\n ([file, chunk]) =>\n chunk.lastUsed !== now &&\n !this.scene.pinnedFiles.has(file) &&\n !this.neededFiles.has(file),\n )\n .sort((a, b) => a[1].lastUsed - b[1].lastUsed);\n for (const [file, chunk] of candidates) {\n if (total <= this.cpuCacheBytes) break;\n this.cache.delete(file);\n this.cacheBytesTotal -= chunk.bytes;\n total -= chunk.bytes;\n // Counted for the same reason the page-table path counts its worker's\n // evictions: `base` climbing with this flat is refinement converging,\n // while `base` climbing *with* this is a cache too small for the cut, and\n // the two look identical from outside. Until this existed the streamed\n // path reported a constant `evicted: 0`, which read as \"no thrashing\"\n // when it only ever meant \"not measured\".\n this.fetchCountsValue.evicted++;\n }\n }\n}\n\nconst _paintLocal = new THREE.Vector3();\nconst _cameraWorldPos = new THREE.Vector3();\nconst _cameraWorldQuat = new THREE.Quaternion();\nconst _cameraLocal = new THREE.Vector3();\nconst _projScreen = new THREE.Matrix4();\nconst _frustum = new THREE.Frustum();\nconst _sphere = new THREE.Sphere();\n/** Camera forward in mesh-local space, for the page-table traversal's foveation. */\nconst _cameraForward = new THREE.Vector3();\nconst _drawSize = new THREE.Vector2();\n\n/** A `SplatData` view over a contiguous run `[j, j + count)` of a plan's packed\n * splats, so one pool write covers a whole run of slots. Zero-copy subarrays. */\nfunction shWordsPerSplat(bands: 1 | 2 | 3): number {\n return Math.ceil((3 * shCoefficientCount(bands)) / 4);\n}\n\nfunction slicePlanRun(splats: PlanSplats, j: number, count: number): SplatData {\n const sh = splats.shPacked;\n return {\n count,\n positions: splats.positions.subarray(j * 3, (j + count) * 3),\n colors: splats.colors.subarray(j * 4, (j + count) * 4),\n covariances: splats.covariances.subarray(j * 6, (j + count) * 6),\n ...(sh\n ? {\n shPacked: {\n ...sh,\n packed: sh.packed.subarray(\n j * shWordsPerSplat(sh.bands),\n (j + count) * shWordsPerSplat(sh.bands),\n ),\n },\n }\n : {}),\n };\n}\n","/**\n * Shared splat-budget governance across multiple streamed meshes.\n *\n * A single `StreamedSplatMesh` keeps itself within a per-device budget, but a\n * host that shows several streamed scenes at once (a main capture plus additional\n * or inset meshes) must not let each mesh claim the whole device budget -\n * their pools are separate, so the costs add. Historically hosts hand-tuned\n * this (\"shrink the main mesh to 0.7 when additional meshes exist\"); the\n * {@link BudgetGovernor} makes it a first-class policy: register each mesh\n * with a priority weight and the governor splits one total budget across the\n * members, reallocating when membership, weights, or the total change.\n *\n * The governor steers members exclusively through their public\n * `setBudget`, so every downstream consumer of a member's budget - the\n * flat-leaf `LodScheduler`, the LCC2 octree cut, and the RAD page-table draw\n * target - sees the governed value through the exact path an explicit host\n * `setBudget` call would take. Meshes never registered with a governor are\n * completely unaffected.\n */\n\nimport { resolveSplatBudget } from '../core/splat-budget';\n\n/**\n * Anything the governor can steer. `StreamedSplatMesh` satisfies this\n * structurally; a custom member only needs the same clamp-and-report\n * `setBudget` contract.\n */\nexport interface BudgetGovernedMember {\n /** The member's current effective active-splat budget. */\n readonly budget: number;\n /**\n * Applies a budget and returns the value actually in effect - which may be\n * lower than asked when the member clamps to a fixed ceiling (for\n * `StreamedSplatMesh`, its `maxBudget`).\n */\n setBudget(budget: number): number;\n /**\n * The ceiling `setBudget` clamps to, when the member knows one\n * (`StreamedSplatMesh.maxBudget`). **Advisory only** - allocation still\n * discovers real caps from `setBudget`'s return value, so a member that\n * omits this is governed exactly as well.\n */\n readonly maxBudget?: number;\n}\n\n/** Options for {@link BudgetGovernor}. */\nexport interface BudgetGovernorOptions {\n /**\n * Total active-splat budget shared by all members. Defaults to the\n * per-device {@link resolveSplatBudget} - i.e. the group as a whole gets\n * what one mesh alone would get today.\n */\n totalBudget?: number;\n /**\n * Grow dead-band as a fraction of a member's current budget (default\n * `0.1`). A reallocation that would *raise* a member's budget by no more\n * than this fraction is skipped, so brief membership churn (an additional mesh\n * appearing for a moment) does not thrash LOD schedules. Shrinks always\n * apply immediately - that is what keeps `sum(member budgets) ≤ total` an\n * invariant rather than a goal.\n */\n hysteresis?: number;\n}\n\n/**\n * The budget a suspended (`weight: 0`) member is held at.\n *\n * Not 0: a member's `setBudget` may reject a non-positive budget outright\n * (`StreamedSplatMesh` routes through `resolveSplatBudget`, which throws\n * `RangeError` on `<= 0`). 1 splat is the smallest legal value and matches the\n * floor the weighted split already uses.\n */\nconst SUSPENDED_BUDGET = 1;\n\ninterface MemberEntry {\n member: BudgetGovernedMember;\n weight: number;\n /** Budget the member reported after the governor's last applied call. */\n applied: number;\n /** The member's budget at registration, restored on unregister/dispose. */\n restoreBudget: number;\n}\n\n/**\n * Splits one total splat budget across registered members by priority weight.\n *\n * Allocation is weighted and cap-aware: a member whose `setBudget` clamps\n * below its weighted share (a small scene, or a mesh with a small pool)\n * releases the difference to the remaining members, so the total is spent\n * where it can buy detail. Reallocation runs automatically on\n * register/unregister and on weight or total changes.\n *\n * A member at `weight: 0` is **suspended**: held at\n * {@link SUSPENDED_BUDGET}, excluded from the weighted split, and its whole\n * share released to the others - Spark's `lodScale: 0` hidden tier, without\n * unregistering (so the mesh stays warm and re-weighting it costs nothing).\n * Note that a suspended member is not *free*: its pool was allocated at\n * construction and is never released, and a streamed mesh keeps its pinned\n * coarse shell resident, so it consumes ≈0 of the budget rather than exactly 0.\n * To give the memory back, dispose the mesh.\n *\n * For camera-driven weights - nearby meshes automatically taking a larger\n * share - see `CameraBudgetGovernor`, which drives this class.\n *\n * Invariant: the sum of budgets the governor has applied to active members\n * never exceeds {@link totalBudget}.\n */\nexport class BudgetGovernor {\n private readonly entries = new Map<BudgetGovernedMember, MemberEntry>();\n private total: number;\n private readonly hysteresis: number;\n\n constructor(options: BudgetGovernorOptions = {}) {\n this.total = resolveSplatBudget(options.totalBudget);\n const hysteresis = options.hysteresis ?? 0.1;\n if (!Number.isFinite(hysteresis) || hysteresis < 0) {\n throw new RangeError('BudgetGovernor hysteresis must be a non-negative finite number.');\n }\n this.hysteresis = hysteresis;\n }\n\n /** The shared budget currently being split across members. */\n get totalBudget(): number {\n return this.total;\n }\n\n /** Replaces the shared total and reallocates. */\n setTotalBudget(totalBudget: number): void {\n const next = resolveSplatBudget(totalBudget);\n if (next === this.total) return;\n this.total = next;\n this.reallocate();\n }\n\n /** Number of registered members. */\n get size(): number {\n return this.entries.size;\n }\n\n /**\n * Adds a member and reallocates the shared budget. The member's current\n * budget is remembered and restored when it leaves the governor.\n *\n * @param member - The mesh (or compatible object) to govern.\n * @param options - `weight` (default `1`): the member's share is\n * proportional to its weight - e.g. main mesh `7`, additional mesh `3` reproduces\n * the old 0.7 host split. `0` registers the member suspended.\n */\n register(member: BudgetGovernedMember, options: { weight?: number } = {}): void {\n if (this.entries.has(member)) {\n throw new Error('BudgetGovernor: member is already registered.');\n }\n const weight = validateWeight(options.weight ?? 1);\n this.entries.set(member, {\n member,\n weight,\n applied: member.budget,\n restoreBudget: member.budget,\n });\n this.reallocate();\n }\n\n /**\n * Removes a member, restores the budget it had when it registered, and\n * reallocates the total across the remaining members. No-op for a member\n * that is not registered (so disposing hosts need not track membership).\n */\n unregister(member: BudgetGovernedMember): void {\n const entry = this.entries.get(member);\n if (entry === undefined) return;\n this.entries.delete(member);\n entry.member.setBudget(entry.restoreBudget);\n this.reallocate();\n }\n\n /**\n * Changes a member's priority weight and reallocates. `0` suspends the\n * member (see the class doc); any positive weight resumes it.\n */\n setWeight(member: BudgetGovernedMember, weight: number): void {\n const entry = this.entryOf(member);\n const next = validateWeight(weight);\n if (next === entry.weight) return;\n entry.weight = next;\n this.reallocate();\n }\n\n /**\n * Writes several weights, then reallocates **once**.\n *\n * Prefer this to a loop of {@link setWeight} whenever more than one weight\n * changes together - as a camera-driven reweight does. Each reallocation\n * pushes `setBudget` to every member, and for a streamed mesh that forces an\n * LOD reschedule, so N separate calls cost N passes over the whole group to\n * reach a state one pass would have produced.\n *\n * @param weights - `[member, weight]` pairs. Every member must be registered;\n * unlisted members keep their current weight.\n * @throws {Error} if any member is not registered - checked before anything\n * is written, so a bad pair leaves every weight untouched.\n * @throws {RangeError} if any weight is not a non-negative finite number.\n */\n setWeights(weights: Iterable<readonly [BudgetGovernedMember, number]>): void {\n // Validate the whole batch first: a partially applied reweight would leave\n // the group in a state no caller asked for.\n const pending = [...weights].map(\n ([member, weight]) => [this.entryOf(member), validateWeight(weight)] as const,\n );\n let changed = false;\n for (const [entry, weight] of pending) {\n if (entry.weight === weight) continue;\n entry.weight = weight;\n changed = true;\n }\n if (changed) this.reallocate();\n }\n\n /** The budget the governor last applied to a member, if registered. */\n budgetOf(member: BudgetGovernedMember): number | undefined {\n return this.entries.get(member)?.applied;\n }\n\n /**\n * Recomputes and applies every member's share. Called automatically by all\n * mutators; call it manually only if a member's internal ceiling changed\n * outside the governor's view.\n */\n reallocate(): void {\n // Suspended members first, so the share they release is available to the\n // waterfill below in the same pass. Held at the floor rather than skipped:\n // a member that was carrying a large budget when it was suspended must\n // actually give it up, or the sum invariant breaks.\n let pool: MemberEntry[] = [];\n for (const entry of this.entries.values()) {\n if (entry.weight === 0) this.applyTarget(entry, SUSPENDED_BUDGET);\n else pool.push(entry);\n }\n let budget = this.total;\n // Cap-aware waterfill: give each member its weighted share of what is\n // left; a member that clamps below its share is finalized at its cap and\n // releases the difference to the others on the next pass. Terminates in at\n // most `size` passes (each non-final pass finalizes at least one member).\n // An all-suspended group leaves `pool` empty and skips the loop, so the\n // weight sum is never 0 here.\n while (pool.length > 0) {\n const weightSum = pool.reduce((sum, entry) => sum + entry.weight, 0);\n const capped: MemberEntry[] = [];\n let cappedSpend = 0;\n for (const entry of pool) {\n const target = Math.max(1, Math.floor((budget * entry.weight) / weightSum));\n if (this.applyTarget(entry, target)) {\n capped.push(entry);\n cappedSpend += entry.applied;\n }\n }\n if (capped.length === 0) break;\n budget = Math.max(0, budget - cappedSpend);\n pool = pool.filter((entry) => !capped.includes(entry));\n }\n }\n\n /**\n * Restores every member's pre-registration budget and empties the governor.\n * The governor itself stays usable (dispose is just \"unregister everyone\").\n */\n dispose(): void {\n for (const entry of this.entries.values()) {\n entry.member.setBudget(entry.restoreBudget);\n }\n this.entries.clear();\n }\n\n /** The entry for a registered member, or a thrown error naming the problem. */\n private entryOf(member: BudgetGovernedMember): MemberEntry {\n const entry = this.entries.get(member);\n if (entry === undefined) {\n throw new Error('BudgetGovernor: member is not registered.');\n }\n return entry;\n }\n\n /**\n * Pushes `target` to a member, with the grow dead-band. Returns true when\n * the member clamped below its target (it is capped and cannot absorb more).\n */\n private applyTarget(entry: MemberEntry, target: number): boolean {\n if (target > entry.applied) {\n // Grows within the dead-band are skipped: staying low never violates\n // the sum invariant, and the skipped headroom is reclaimed by the next\n // meaningful reallocation.\n if (target - entry.applied <= this.hysteresis * entry.applied) return false;\n } else if (target === entry.applied) {\n return false;\n }\n entry.applied = entry.member.setBudget(target);\n return entry.applied < target;\n }\n}\n\n/** Validates a member weight: any non-negative finite number, `0` = suspended. */\nfunction validateWeight(weight: number): number {\n if (!Number.isFinite(weight) || weight < 0) {\n throw new RangeError(\n 'BudgetGovernor member weight must be a non-negative finite number (0 suspends the member).',\n );\n }\n return weight;\n}\n","/**\n * Camera-driven splat-budget weighting across several streamed meshes.\n *\n * {@link BudgetGovernor} splits one total by *priority*, which a host must\n * choose and maintain. That is the wrong axis for a scene of additional meshes:\n * what makes a mesh worth splats is that the camera is near it, and that changes\n * every frame. Splitting a pool evenly instead (`pool / N`) gives the mesh\n * you fly up to a quarter of the budget it needs while three meshes nobody is\n * looking at hold the rest.\n *\n * {@link CameraBudgetGovernor} closes that loop: each update it measures every\n * member's projected size from the camera, multiplies in the host's priority\n * tier, and writes the resulting weights to a `BudgetGovernor` in one batch.\n * Approaching one of four additional meshes pulls budget off the far ones automatically.\n *\n * This is Spark's model, reached differently. Spark shares one `lodSplatCount`\n * and biases meshes with a per-mesh `lodScale` (focused 2, adjacent 0.25,\n * hidden 0); its GPU traversal then favors near, on-screen detail on its own.\n * VLAM's meshes each own a pool, so the near/far bias has to be applied to the\n * *budget* - which is what this class does, with `priority` playing the part of\n * `lodScale`. (For a `.rad` page-table mesh, `StreamedSplatMesh.lodScale` is\n * also available and is Spark's knob exactly.)\n *\n * Composition, not inheritance: `BudgetGovernor` stays a pure allocation\n * policy with no camera and no frame lifecycle, and a host that wants fixed\n * weights keeps using it directly.\n */\n\nimport * as THREE from 'three/webgpu';\n\nimport {\n BudgetGovernor,\n type BudgetGovernedMember,\n type BudgetGovernorOptions,\n} from './budget-governor';\n\n/**\n * A governed member this class can measure. Every `SplatMesh` (and so every\n * `StreamedSplatMesh`) satisfies it structurally - there is no extra host\n * plumbing to write.\n */\nexport interface CameraBudgetMember extends BudgetGovernedMember {\n /**\n * The member's splat bounds in its own local frame. `StreamedSplatMesh`\n * overrides this to the whole scene's bounds, which are known from the\n * manifest - so weighting is correct from the first frame, before a single\n * chunk has loaded.\n */\n computeSplatBounds(): THREE.Box3;\n /** Local→world transform, read after {@link updateWorldMatrix}. */\n readonly matrixWorld: THREE.Matrix4;\n /** `Object3D.updateWorldMatrix`; called so a fresh member is placed correctly. */\n updateWorldMatrix(updateParents: boolean, updateChildren: boolean): void;\n /**\n * `Object3D.visible`. A hidden member is suspended (weight 0) - it draws\n * nothing, so it should hold no budget. This is the member's own flag, not an\n * ancestor walk: a host hiding a whole group should set `priority: 0`.\n */\n readonly visible: boolean;\n /**\n * The visibility that actually decides whether the member's splats reach the\n * screen, when that differs from `visible`. `SplatMesh` provides it: a\n * `UnifiedSplatMesh` forces `visible = false` on every source it owns\n * (only to keep the regular scene draw from double-drawing them) while the\n * source may be fully on screen through the unified draw - without this, the\n * governor would suspend every unified source and freeze its streaming.\n * When present it wins over `visible`.\n */\n readonly effectiveVisibility?: boolean;\n}\n\n/** Options for {@link CameraBudgetGovernor}. */\nexport interface CameraBudgetGovernorOptions extends BudgetGovernorOptions {\n /**\n * An existing governor to drive, when the host already has one (or wants to\n * mix camera-weighted and fixed-weight members). By default one is built\n * from the inherited {@link BudgetGovernorOptions}.\n */\n governor?: BudgetGovernor;\n /**\n * Minimum milliseconds between reweights. Default `250`, matching\n * `StreamedSplatMesh`'s own idle reschedule interval: a mesh cannot act on a\n * budget change faster than it reschedules, so reweighting more often buys\n * nothing and costs a forced reschedule on every member. Membership and\n * priority changes bypass it.\n */\n minIntervalMs?: number;\n /**\n * Relative weight change below which a reweight is skipped entirely, as a\n * fraction of the weight in effect. Default `0.15`.\n *\n * This sits *above* `BudgetGovernor`'s grow dead-band and does a different\n * job: that one damps budget churn on a member, this one suppresses the\n * reallocation altogether so an idling camera does no work at all.\n */\n weightDeadband?: number;\n /**\n * Exponent on projected size. `1` (default) weights by angular size -\n * halving the distance doubles the share. `2` weights by projected *area*,\n * which concentrates the budget harder on the nearest member.\n */\n falloff?: number;\n /**\n * Weight multiplier for a member outside the view frustum. Default `0.25`:\n * suppressed, never starved. Off-screen members must keep enough budget for\n * their coarse shell, or turning the camera exposes an unpainted region -\n * the same foveate-don't-cull policy the `.rad` frontier traversal follows.\n */\n offScreenWeight?: number;\n /** Floor on the projected-size term, so a very distant member still holds a\n * coarse shell. Default `0.05`. */\n minWeight?: number;\n /** Ceiling on the projected-size term, so a member the camera is inside\n * cannot take the entire total. Default `8`. */\n maxWeight?: number;\n}\n\n/** Per-member options for {@link CameraBudgetGovernor.register}. */\nexport interface CameraBudgetMemberOptions {\n /**\n * Host priority multiplied into the camera term - Spark's `lodScale` tiers:\n * focused `2`, default `1`, adjacent `0.25`, hidden `0`. Default `1`.\n * `0` suspends the member (see {@link BudgetGovernor}).\n */\n priority?: number;\n /**\n * Pins this member's weight, opting it out of camera weighting while it still\n * competes for the same total. Use it for a main scene that should hold a\n * steady share while additional meshes fight over the rest - e.g. `fixedWeight: 4`\n * against extras averaging `1`.\n */\n fixedWeight?: number;\n}\n\ninterface CameraEntry {\n member: CameraBudgetMember;\n priority: number;\n fixedWeight: number | undefined;\n /**\n * Weight currently written to the governor - seeded at registration, so a\n * member never reads back as \"unweighted\". `register` forces the next update\n * regardless, which is what makes the seed safe to compare against.\n */\n applied: number;\n}\n\n/** Reused across updates - this runs every frame and must not allocate. */\nconst _cameraPos = new THREE.Vector3();\nconst _projScreen = new THREE.Matrix4();\nconst _frustum = new THREE.Frustum();\nconst _box = new THREE.Box3();\nconst _sphere = new THREE.Sphere();\n\n/**\n * Weights a {@link BudgetGovernor}'s members by how large each one projects\n * from the camera, so nearby meshes take budget from distant ones.\n *\n * ```js\n * const governor = new CameraBudgetGovernor({ totalBudget: 4_000_000 });\n * governor.register(main, { fixedWeight: 4 }); // steady share\n * governor.register(extraA); // camera-weighted, priority 1\n * governor.register(extraB);\n *\n * // once per frame, after the scene graph is up to date:\n * governor.update(camera);\n * ```\n *\n * Every guarantee of the underlying governor still holds - most importantly\n * that the applied budgets never sum above the total, and that unregistering a\n * member restores the budget it had when it joined.\n *\n * **A member can only grow into a budget its pool can hold.** A streamed mesh\n * allocates its pool once, from its construction budget, and clamps `setBudget`\n * to it - so a mesh built at a quarter of the total can never be given more\n * than a quarter, however close the camera gets. Construct governed meshes with\n * `maxBudget` set to the largest share they should ever reach, and price those\n * ceilings with `estimateSplatPoolBytes` first: the pools cost their ceilings\n * whatever the budget is split to.\n */\nexport class CameraBudgetGovernor {\n private readonly entries = new Map<CameraBudgetMember, CameraEntry>();\n private readonly budgetGovernor: BudgetGovernor;\n private readonly minIntervalMs: number;\n private readonly weightDeadband: number;\n private readonly falloff: number;\n private readonly offScreenWeight: number;\n private readonly minWeight: number;\n private readonly maxWeight: number;\n private lastUpdateAt = -Infinity;\n /** Set by membership/priority changes: the next update ignores both damps. */\n private forceNext = true;\n\n constructor(options: CameraBudgetGovernorOptions = {}) {\n this.budgetGovernor = options.governor ?? new BudgetGovernor(options);\n this.minIntervalMs = nonNegative(options.minIntervalMs ?? 250, 'minIntervalMs');\n this.weightDeadband = nonNegative(options.weightDeadband ?? 0.15, 'weightDeadband');\n this.falloff = positive(options.falloff ?? 1, 'falloff');\n this.offScreenWeight = positive(options.offScreenWeight ?? 0.25, 'offScreenWeight');\n this.minWeight = positive(options.minWeight ?? 0.05, 'minWeight');\n this.maxWeight = positive(options.maxWeight ?? 8, 'maxWeight');\n if (this.maxWeight < this.minWeight) {\n throw new RangeError('CameraBudgetGovernor maxWeight must be >= minWeight.');\n }\n }\n\n /** The governor this drives; use it for fixed-weight members and diagnostics. */\n get governor(): BudgetGovernor {\n return this.budgetGovernor;\n }\n\n /** The shared total being split, from the underlying governor. */\n get totalBudget(): number {\n return this.budgetGovernor.totalBudget;\n }\n\n /** Replaces the shared total (e.g. the presenting budget on `sessionstart`). */\n setTotalBudget(totalBudget: number): void {\n this.budgetGovernor.setTotalBudget(totalBudget);\n }\n\n /** Number of camera-weighted members. */\n get size(): number {\n return this.entries.size;\n }\n\n /**\n * Adds a member, registers it with the underlying governor, and forces a\n * reweight on the next {@link update}.\n *\n * @throws {Error} if the member is already registered here.\n * @throws {RangeError} if `priority` or `fixedWeight` is invalid.\n */\n register(member: CameraBudgetMember, options: CameraBudgetMemberOptions = {}): void {\n if (this.entries.has(member)) {\n throw new Error('CameraBudgetGovernor: member is already registered.');\n }\n const priority = nonNegative(options.priority ?? 1, 'priority');\n const fixedWeight =\n options.fixedWeight === undefined\n ? undefined\n : nonNegative(options.fixedWeight, 'fixedWeight');\n // Register at the weight this member will hold anyway, so the very first\n // allocation is already roughly right rather than an even split that the\n // first update immediately overwrites.\n const initial = fixedWeight ?? priority;\n this.budgetGovernor.register(member, { weight: initial });\n this.entries.set(member, { member, priority, fixedWeight, applied: initial });\n this.forceNext = true;\n }\n\n /**\n * Removes a member from this helper and the underlying governor, restoring\n * the budget it had when it joined. No-op for an unknown member.\n */\n unregister(member: CameraBudgetMember): void {\n if (!this.entries.delete(member)) return;\n this.budgetGovernor.unregister(member);\n this.forceNext = true;\n }\n\n /**\n * Changes a member's priority tier. Takes effect on the next {@link update},\n * which is forced - a deliberate focus change should not wait out the\n * interval or be swallowed by the dead-band.\n *\n * @throws {Error} if the member is not registered here.\n * @throws {RangeError} if `priority` is negative or not finite.\n */\n setPriority(member: CameraBudgetMember, priority: number): void {\n const entry = this.entries.get(member);\n if (entry === undefined) {\n throw new Error('CameraBudgetGovernor: member is not registered.');\n }\n const next = nonNegative(priority, 'priority');\n // No-op on an unchanged tier. Hosts re-assert tiers from sync loops, and an\n // unconditional force would bypass the interval *and* the dead-band on the\n // next update - every reallocation pushes setBudget to every member, and a\n // streamed mesh answers each one with a forced LOD reschedule.\n if (next === entry.priority) return;\n entry.priority = next;\n this.forceNext = true;\n }\n\n /**\n * Recomputes every member's weight from the camera and applies them in one\n * batch. Call once per frame, after the scene graph is up to date.\n *\n * Needs no renderer: weights are a ratio, so viewport size cancels out.\n *\n * Skipped - returning `false` - when called inside `minIntervalMs` of the\n * last reweight, or when no member's weight moved by more than\n * `weightDeadband`. Membership and priority changes force it through both.\n *\n * @param camera - The view detail should follow. In an immersive session pass\n * the head/`ArrayCamera`, not the idle application camera.\n * @param now - Timestamp in ms on the `performance.now` clock; defaults to it.\n * @returns whether weights were reapplied.\n */\n update(camera: THREE.Camera, now: number = performance.now()): boolean {\n if (this.entries.size === 0) return false;\n const forced = this.forceNext;\n if (!forced && now - this.lastUpdateAt < this.minIntervalMs) return false;\n\n camera.updateMatrixWorld();\n camera.getWorldPosition(_cameraPos);\n _projScreen.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse);\n _frustum.setFromProjectionMatrix(_projScreen);\n\n const next: [CameraBudgetMember, number][] = [];\n let significant = false;\n for (const entry of this.entries.values()) {\n const weight = this.weightFor(entry);\n next.push([entry.member, weight]);\n if (!significant && this.isSignificant(entry.applied, weight)) significant = true;\n }\n if (!forced && !significant) return false;\n\n // One batched write: N separate setWeight calls would each reallocate and\n // force an LOD reschedule on every member.\n this.budgetGovernor.setWeights(next);\n for (const [member, weight] of next) {\n const entry = this.entries.get(member);\n if (entry !== undefined) entry.applied = weight;\n }\n this.lastUpdateAt = now;\n this.forceNext = false;\n return true;\n }\n\n /** The weight currently written for a member, if registered here. */\n weightOf(member: CameraBudgetMember): number | undefined {\n return this.entries.get(member)?.applied;\n }\n\n /** The budget the underlying governor last applied to a member. */\n budgetOf(member: CameraBudgetMember): number | undefined {\n return this.budgetGovernor.budgetOf(member);\n }\n\n /**\n * Unregisters every member, restoring the budget each had when it joined.\n * The helper stays usable afterwards. A governor passed in by the host keeps\n * any members the host registered on it directly.\n */\n dispose(): void {\n for (const member of [...this.entries.keys()]) this.budgetGovernor.unregister(member);\n this.entries.clear();\n this.lastUpdateAt = -Infinity;\n this.forceNext = true;\n }\n\n /**\n * A member's weight: `priority × clamp((radius / distance) ^ falloff) ×\n * offScreen`.\n *\n * `radius / distance` is the tangent of the member's half angular size - the\n * same `size / distance` measure Spark's `pixel_scale` traversal ranks nodes\n * by, one level up at whole-mesh granularity. Distance is measured to the\n * bounding sphere's *surface*, so a large mesh is not penalized for having a\n * distant center, and is floored so a camera inside the bounds saturates at\n * `maxWeight` rather than dividing by zero.\n */\n private weightFor(entry: CameraEntry): number {\n if (entry.fixedWeight !== undefined) return entry.fixedWeight;\n const { member, priority } = entry;\n if (priority === 0 || !(member.effectiveVisibility ?? member.visible)) return 0;\n\n member.updateWorldMatrix(true, false);\n _box.copy(member.computeSplatBounds());\n // Nothing measurable yet (a dynamic mesh with no appended ranges): hold the\n // floor rather than reporting an infinite or NaN size.\n if (_box.isEmpty()) return priority * this.minWeight;\n _box.applyMatrix4(member.matrixWorld).getBoundingSphere(_sphere);\n\n const radius = _sphere.radius;\n if (!(radius > 0)) return priority * this.minWeight;\n // Floor the distance relative to the member's own size, so the saturation\n // point scales with the scene instead of being a fixed world distance.\n const surface = _sphere.center.distanceTo(_cameraPos) - radius;\n const distance = Math.max(surface, radius * 1e-3, 1e-6);\n const projected = clamp((radius / distance) ** this.falloff, this.minWeight, this.maxWeight);\n if (!Number.isFinite(projected)) return priority * this.minWeight;\n\n const onScreen = _frustum.intersectsSphere(_sphere) ? 1 : this.offScreenWeight;\n return priority * projected * onScreen;\n }\n\n /** Whether a weight moved enough to be worth a reallocation. */\n private isSignificant(applied: number, next: number): boolean {\n // Crossing into or out of suspension always matters, however small the\n // absolute change: it decides whether the member holds budget at all.\n if (applied === 0 || next === 0) return applied !== next;\n return Math.abs(next - applied) > this.weightDeadband * applied;\n }\n}\n\nfunction clamp(value: number, min: number, max: number): number {\n return Math.min(max, Math.max(min, value));\n}\n\nfunction nonNegative(value: number, name: string): number {\n if (!Number.isFinite(value) || value < 0) {\n throw new RangeError(`CameraBudgetGovernor ${name} must be a non-negative finite number.`);\n }\n return value;\n}\n\nfunction positive(value: number, name: string): number {\n if (!Number.isFinite(value) || value <= 0) {\n throw new RangeError(`CameraBudgetGovernor ${name} must be a positive finite number.`);\n }\n return value;\n}\n","/**\n * Cross-mesh chunk-fetch arbitration.\n *\n * Every {@link StreamedSplatMesh} owns its own `ChunkLoader` worker and issues\n * chunk requests toward its own in-flight cap, so a scene of streamed meshes\n * fetches with no shared ordering: a mesh the camera is pointed at competes\n * for bandwidth and connection slots against a dozen distant ones, each of\n * which is equally entitled to its own cap. Spark does not have this problem\n * structurally - one global traversal orders every fetch want\n * biggest-on-screen-first through one pager, so its network order *is* its\n * visual priority.\n *\n * This is that ordering at whole-mesh granularity: one scheduler shared by\n * every streamed mesh in a scene (the same sharing model as `SplatPool`),\n * handing out a bounded number of fetch slots in proportion to the same\n * camera-projected weight a `CameraBudgetGovernor` already computes for\n * drawing. Within a mesh the existing order still applies - a frontier plan's\n * `touched` list is already sorted biggest-on-screen first - so the two\n * granularities compose.\n *\n * The scheduler brokers **slots**, never fetches: it does not know about\n * `ChunkLoader`, URLs or workers, which is what keeps a future scene-level\n * shared loader an independent change.\n */\n\n/**\n * Why a mesh wants a slot. The kind is the mesh's own statement of intent, and\n * decides what a weightless (hidden, or suspended) mesh may still do:\n *\n * - `priority` - detail the frontier asked for and does not have. The chunks\n * actually on screen.\n * - `base` - the camera-directed coarse base, and the pinned coverage every\n * deferred swap substitutes from. A far mesh must keep trickling these or it\n * has nothing to draw at all.\n * - `sweep` - the background file-order sweep that pulls a whole capture into\n * the worker cache so later camera moves are served from RAM. Pure\n * pre-warming: valuable when a mesh is on screen, and the first thing to give\n * up when it is not.\n */\nexport type ChunkFetchKind = 'priority' | 'base' | 'sweep';\n\n/** A streamed mesh, as the scheduler sees it. */\nexport interface ChunkFetchClient {\n /**\n * This mesh's camera-projected weight - larger means more of the pipe.\n * Zero means hidden or suspended: such a mesh keeps its floor for `priority`\n * and `base` work but is denied `sweep` entirely.\n *\n * Read on demand rather than pushed, so the scheduler never holds a stale\n * weight; a `CameraBudgetGovernor.weightOf` call is the intended source.\n */\n weight(): number;\n /**\n * A slot may now be free. The mesh should re-run its reschedule, which\n * re-issues whatever it still wants. The scheduler deliberately keeps no\n * queue of pending requests: the reschedule paths are already idempotent\n * re-issuers, so a poke carries strictly more current information than a\n * request recorded when the camera was somewhere else.\n */\n onSlotAvailable(): void;\n /**\n * Abort in-flight fetches of this kind - the mesh's weight dropped to zero\n * while it held slots. Aborted fetches release their slots through the normal\n * path, so this is how a focus change hands the pipe over immediately rather\n * than after the far meshes' current requests drain.\n */\n shedFetches(kind: ChunkFetchKind): void;\n}\n\n/** Options for {@link ChunkFetchScheduler}. */\nexport interface ChunkFetchSchedulerOptions {\n /**\n * Total chunk fetches in flight across every registered mesh. Default 16.\n *\n * This is the number that decides whether the scheduler helps at all. Too low\n * and a single mesh scene streams slower than it does today; too high and\n * there is nothing to arbitrate, because the browser's own queueing (six per\n * origin on HTTP/1.1; a much larger multiplexed window on HTTP/2) becomes the\n * real scheduler again - and it orders by request time, not by what is on\n * screen. Tune it from a measured concurrency high-water mark, not by feel.\n */\n maxGlobalInflight?: number;\n /**\n * Slots every registered mesh may hold regardless of weight. Default 1.\n *\n * Without a floor a scene's far meshes stop fetching entirely while the\n * focused one streams, and a mesh with no coarse coverage draws nothing -\n * so the floor is what turns \"far meshes wait\" into \"far meshes trickle\".\n *\n * One slot is always granted regardless of this setting: a mesh entitled to\n * zero would never fetch and never settle. Set this above 1 to widen every\n * mesh's guaranteed trickle.\n */\n perMeshFloor?: number;\n}\n\n/**\n * A mesh's registration. Opaque to callers: hold it from {@link\n * ChunkFetchScheduler.register} and pass it back to acquire, release and\n * unregister.\n */\nexport interface ChunkFetchHandle {\n readonly client: ChunkFetchClient;\n}\n\ninterface Entry extends ChunkFetchHandle {\n /** Slots currently held (granted and not yet released). */\n held: number;\n /** Denied at least once since the last grant - wake this mesh on a release. */\n demanding: boolean;\n /** Registered? Cleared by `unregister` so late releases stay accountable. */\n live: boolean;\n}\n\nconst DEFAULT_MAX_GLOBAL_INFLIGHT = 16;\nconst DEFAULT_PER_MESH_FLOOR = 1;\n\n/** See the module comment. Construct one per scene and pass it to every mesh. */\nexport class ChunkFetchScheduler {\n private readonly entries = new Set<Entry>();\n private readonly maxGlobalInflight: number;\n private readonly perMeshFloor: number;\n private inflightCount = 0;\n private disposed = false;\n\n constructor(options: ChunkFetchSchedulerOptions = {}) {\n this.maxGlobalInflight = Math.max(\n 1,\n Math.floor(options.maxGlobalInflight ?? DEFAULT_MAX_GLOBAL_INFLIGHT),\n );\n this.perMeshFloor = Math.max(0, Math.floor(options.perMeshFloor ?? DEFAULT_PER_MESH_FLOOR));\n }\n\n /** Total slots granted and not yet released. */\n get inflight(): number {\n return this.inflightCount;\n }\n\n /** Meshes currently registered. */\n get clientCount(): number {\n return this.entries.size;\n }\n\n register(client: ChunkFetchClient): ChunkFetchHandle {\n const entry: Entry = { client, held: 0, demanding: false, live: true };\n this.entries.add(entry);\n return entry;\n }\n\n /**\n * Drops a mesh. Slots it still holds are released here: a disposing mesh\n * aborts its fetches, and those aborts land after the handle is gone.\n */\n unregister(handle: ChunkFetchHandle): void {\n const entry = handle as Entry;\n if (!this.entries.delete(entry)) return;\n entry.live = false;\n if (entry.held > 0) {\n this.inflightCount -= entry.held;\n entry.held = 0;\n this.wake();\n }\n }\n\n /**\n * Grants a fetch slot, or denies and remembers the demand.\n *\n * A denial is not a failure and must not feed a mesh's retry backoff - the\n * mesh simply did not fetch this tick, and will be woken (or reschedule on\n * its own within the idle interval) to ask again.\n */\n tryAcquire(handle: ChunkFetchHandle, kind: ChunkFetchKind): boolean {\n const entry = handle as Entry;\n if (this.disposed || !entry.live) return false;\n\n const weight = normalizeWeight(entry.client.weight());\n // A weightless mesh pre-warming its cache is the exact traffic that starves\n // the focused mesh, and it buys nothing while nobody is looking at it.\n if (kind === 'sweep' && weight <= 0) {\n entry.demanding = false;\n return false;\n }\n if (\n this.inflightCount >= this.maxGlobalInflight ||\n entry.held >= this.shareFor(entry, weight)\n ) {\n entry.demanding = true;\n return false;\n }\n\n entry.held++;\n entry.demanding = false;\n this.inflightCount++;\n return true;\n }\n\n /**\n * Returns a slot. Must be called exactly once for every granted acquire -\n * on success, on failure **and** on abort - or the pipe leaks capacity until\n * the scene is torn down.\n */\n release(handle: ChunkFetchHandle): void {\n const entry = handle as Entry;\n if (entry.held <= 0) return;\n entry.held--;\n this.inflightCount--;\n this.wake();\n }\n\n /**\n * Re-examines weights after the camera moved. Call it once per frame, right\n * after the budget governor's own update.\n *\n * Meshes that just lost all weight shed their pre-warming sweeps, so a focus\n * change frees the pipe now rather than when a dozen far requests happen to\n * finish.\n */\n weightsChanged(): void {\n if (this.disposed) return;\n for (const entry of this.entries) {\n if (entry.held > 0 && normalizeWeight(entry.client.weight()) <= 0) {\n entry.client.shedFetches('sweep');\n }\n }\n this.wake();\n }\n\n /**\n * Releases every registration. Meshes are not disposed - the scheduler is\n * shared and does not own them, exactly as a shared `SplatPool` does not own\n * its meshes.\n */\n dispose(): void {\n this.disposed = true;\n for (const entry of this.entries) {\n entry.live = false;\n entry.held = 0;\n entry.demanding = false;\n }\n this.entries.clear();\n this.inflightCount = 0;\n }\n\n /**\n * The most slots this mesh may hold: its weight-proportional share of the\n * *whole* pipe, floored so it can always trickle and capped so its siblings\n * can always reach their own floors.\n *\n * This is a ceiling, not a reservation. The global cap does the real\n * limiting, so a heavy mesh can use capacity its idle siblings are leaving on\n * the table, while a far mesh's ceiling stays at its floor however early it\n * asks - which is what keeps the first mesh to reschedule from taking the\n * pipe and holding it. Dividing the *remainder* after every participant's\n * floor instead would invert the whole point on a large scene: thirteen\n * additional meshes and a main at floor 1 consume a 16-slot pipe entirely, leaving the\n * focused mesh a smaller share than the far ones it is competing with.\n *\n * The denominator counts every mesh that plausibly wants the pipe - all\n * registered meshes except those both weightless and idle, plus the caller.\n */\n private shareFor(entry: Entry, weight: number): number {\n let participants = 0;\n let totalWeight = 0;\n for (const other of this.entries) {\n const otherWeight = other === entry ? weight : normalizeWeight(other.client.weight());\n // A hidden mesh with nothing in flight is not competing for anything.\n if (other !== entry && otherWeight <= 0 && other.held === 0 && !other.demanding) continue;\n participants++;\n totalWeight += otherWeight;\n }\n if (participants <= 1) return this.maxGlobalInflight;\n\n // All-zero weights (every mesh hidden) share evenly rather than dividing by\n // zero.\n const share =\n totalWeight > 0\n ? (this.maxGlobalInflight * weight) / totalWeight\n : this.maxGlobalInflight / participants;\n // At least one slot, whatever the arithmetic and whatever the configured\n // floor: a mesh wedged at zero entitlement while the pipe has room would\n // never fetch and never settle, and `isStreaming` would stay true forever.\n const floored = Math.max(1, this.perMeshFloor, Math.round(share));\n // Never so much that another participant could not reach its own floor.\n const reservedForOthers = Math.max(1, this.perMeshFloor) * (participants - 1);\n return Math.max(1, Math.min(floored, this.maxGlobalInflight - reservedForOthers));\n }\n\n /** Pokes the heaviest mesh that was denied since its last grant. */\n private wake(): void {\n if (this.disposed || this.inflightCount >= this.maxGlobalInflight) return;\n let best: Entry | null = null;\n let bestWeight = -Infinity;\n for (const entry of this.entries) {\n if (!entry.demanding) continue;\n const weight = normalizeWeight(entry.client.weight());\n if (weight > bestWeight) {\n best = entry;\n bestWeight = weight;\n }\n }\n if (!best) return;\n // Cleared before the callback: the poke re-runs a reschedule that will\n // acquire (clearing it anyway) or be denied again (setting it again), and\n // leaving it set would make this mesh the perpetual answer to every wake.\n best.demanding = false;\n best.client.onSlotAvailable();\n }\n}\n\n/** Negative, NaN and infinite weights are all \"no claim on the pipe\". */\nfunction normalizeWeight(weight: number): number {\n return Number.isFinite(weight) && weight > 0 ? weight : 0;\n}\n","/**\n * Cross-mesh decoded-chunk cache arbitration.\n *\n * Every {@link StreamedSplatMesh} caps its own decoded-chunk cache, and in\n * `foveationMode: 'page-table'` that cap is `min(2 GiB, this capture's decoded\n * size)` - a number sized for *one* streamed scene. A scene of streamed additional meshes\n * therefore has no ceiling at all: thirteen extras plus a main are thirteen\n * plus one independent caps, and because each is sized to its own capture, a\n * desktop that fits them never evicts. The background sweep then runs to\n * completion against every one of them, pulling every capture in the scene into\n * RAM and keeping it there.\n *\n * This is the missing ceiling, at whole-mesh granularity: one budget shared by\n * every streamed mesh in a scene (the same sharing model as `SplatPool` and\n * {@link ChunkFetchScheduler}), splitting a scene total by the same\n * camera-projected weight a `CameraBudgetGovernor` already computes for drawing.\n *\n * It bounds **retention**, not prefetching. The sweep still runs and still warms\n * the cache; it simply stops at an allowance the whole scene agreed on rather\n * than at the size of each capture. A mesh the camera approaches gets a larger\n * allowance and resumes sweeping; one it leaves gives bytes back.\n *\n * The budget brokers **bytes**, never chunks: it does not know about\n * `ChunkLoader`, chunk ids or workers, which is what keeps the eviction policy\n * where it belongs - inside the frontier worker, which is the only place that\n * knows what the current cut still needs.\n */\n\n/** A streamed mesh, as the cache budget sees it. */\nexport interface ChunkCacheClient {\n /**\n * This mesh's camera-projected weight - larger means more of the cache.\n *\n * Read on demand rather than pushed, so the budget never holds a stale\n * weight; a `CameraBudgetGovernor.weightOf` call is the intended source, and\n * is the same one {@link ChunkFetchScheduler} reads. Zero (hidden or\n * suspended) still keeps `perMeshFloorBytes`: a mesh whose coarse base has\n * been evicted draws nothing at all when the camera comes back to it.\n */\n weight(): number;\n /**\n * The most this mesh could ever put to use - for a page-table mesh,\n * `min(PAGETABLE_CACHE_FLOOR_BYTES, estimateSceneDecodedBytes(scene))`.\n *\n * Bytes above it are handed to siblings that can use them, the way\n * `BudgetGovernor` waterfills past a member's `maxBudget`. Without it a scene\n * of one large capture and a dozen small additional meshes would reserve most of the\n * envelope for extras that cannot fill it.\n */\n readonly ceilingBytes: number;\n /**\n * This mesh's allowance moved. The mesh forwards it to its frontier worker;\n * nothing is dropped synchronously, because only the worker's own eviction\n * pass knows which chunks the current cut still needs.\n */\n onAllowanceChanged(bytes: number): void;\n}\n\n/** Options for {@link ChunkCacheBudget}. */\nexport interface ChunkCacheBudgetOptions {\n /**\n * Decoded-chunk bytes every registered mesh may hold **in total**.\n *\n * This is the number that decides whether the budget helps at all. Too low\n * and the focused mesh re-fetches chunks it just evicted; too high and it is\n * inert, because each mesh's own `ceilingBytes` becomes binding again. Size\n * it against the tab's heap, not against any one capture.\n */\n totalBytes: number;\n /**\n * Bytes each mesh keeps regardless of weight. Default 32 MiB, matching\n * `resolveCpuCacheBytes`'s own minimum.\n *\n * Without a floor a far mesh evicts its own coarse base and re-fetches it on\n * the next reschedule, forever - the same thrash {@link\n * ChunkFetchScheduler.perMeshFloor} exists to prevent on the network side.\n */\n perMeshFloorBytes?: number;\n /**\n * Minimum ms between weight-driven reallocations. Default 250, matching the\n * idle reschedule interval - there is no point re-splitting faster than the\n * meshes can act on it. Membership changes bypass it.\n */\n minIntervalMs?: number;\n /**\n * Relative allowance change below which a mesh is not notified. Default 0.15.\n *\n * Every notification is a worker post and, indirectly, an eviction pass. A\n * camera drifting slowly would otherwise repost a 1% different number every\n * quarter second for no change in behaviour.\n */\n deadband?: number;\n}\n\n/**\n * A mesh's registration. Opaque to callers: hold it from {@link\n * ChunkCacheBudget.register} and pass it back to read the allowance or\n * unregister.\n */\nexport interface ChunkCacheHandle {\n readonly client: ChunkCacheClient;\n}\n\ninterface Entry extends ChunkCacheHandle {\n /** Bytes currently allocated to this mesh. */\n allowance: number;\n /** Registered? Cleared by `unregister` so a late callback stays inert. */\n live: boolean;\n}\n\nconst DEFAULT_PER_MESH_FLOOR_BYTES = 32 * 1024 * 1024;\nconst DEFAULT_MIN_INTERVAL_MS = 250;\nconst DEFAULT_DEADBAND = 0.15;\n\n/** See the module comment. Construct one per scene and pass it to every mesh. */\nexport class ChunkCacheBudget {\n private readonly entries = new Set<Entry>();\n private readonly perMeshFloorBytes: number;\n private readonly minIntervalMs: number;\n private readonly deadband: number;\n private totalBytesValue: number;\n private lastAllocationMs = Number.NEGATIVE_INFINITY;\n private disposed = false;\n\n constructor(options: ChunkCacheBudgetOptions) {\n if (!Number.isFinite(options.totalBytes) || options.totalBytes <= 0) {\n throw new RangeError('Chunk cache totalBytes must be a positive finite number.');\n }\n this.totalBytesValue = Math.floor(options.totalBytes);\n this.perMeshFloorBytes = Math.max(\n 0,\n Math.floor(options.perMeshFloorBytes ?? DEFAULT_PER_MESH_FLOOR_BYTES),\n );\n this.minIntervalMs = Math.max(0, options.minIntervalMs ?? DEFAULT_MIN_INTERVAL_MS);\n this.deadband = Math.max(0, options.deadband ?? DEFAULT_DEADBAND);\n }\n\n /** Bytes shared across every registered mesh. */\n get totalBytes(): number {\n return this.totalBytesValue;\n }\n\n /**\n * Resizes the scene envelope and re-splits immediately. For a host reacting\n * to a quality change or a device-memory signal.\n */\n setTotalBytes(bytes: number): void {\n if (!Number.isFinite(bytes) || bytes <= 0) {\n throw new RangeError('Chunk cache totalBytes must be a positive finite number.');\n }\n const next = Math.floor(bytes);\n if (next === this.totalBytesValue) return;\n this.totalBytesValue = next;\n this.allocate();\n }\n\n /** Meshes currently registered. */\n get clientCount(): number {\n return this.entries.size;\n }\n\n /**\n * Adds a mesh and re-splits at once, so the caller can read its opening\n * allowance out of {@link allowanceFor} before posting worker init.\n */\n register(client: ChunkCacheClient): ChunkCacheHandle {\n const entry: Entry = { client, allowance: 0, live: true };\n this.entries.add(entry);\n this.allocate({ force: true, silentFor: entry });\n return entry;\n }\n\n /** Drops a mesh and hands its bytes back to the siblings. */\n unregister(handle: ChunkCacheHandle): void {\n const entry = handle as Entry;\n if (!this.entries.delete(entry)) return;\n entry.live = false;\n entry.allowance = 0;\n this.allocate({ force: true });\n }\n\n /** This mesh's current allowance in bytes, or 0 once unregistered. */\n allowanceFor(handle: ChunkCacheHandle): number {\n const entry = handle as Entry;\n return entry.live ? entry.allowance : 0;\n }\n\n /**\n * Re-splits after the camera moved. Call it once per frame, right beside\n * {@link ChunkFetchScheduler.weightsChanged} - the two read the same weights,\n * so cache and network follow the same measure.\n *\n * Rate-limited and dead-banded; calling it every frame is the intended use.\n */\n weightsChanged(now: number = Date.now()): void {\n if (this.disposed) return;\n if (now - this.lastAllocationMs < this.minIntervalMs) return;\n // Only this path stamps the limiter, and only with the caller's own clock.\n // Structural allocations (register/unregister/resize) must not stamp it:\n // they would otherwise mix `Date.now()` into a host that drives this from a\n // frame clock, and throttle every later call against a timestamp from a\n // different epoch.\n this.lastAllocationMs = now;\n this.allocate();\n }\n\n /**\n * Releases every registration. Meshes are not disposed - the budget is shared\n * and does not own them, exactly as a shared `SplatPool` does not own its\n * meshes.\n */\n dispose(): void {\n this.disposed = true;\n for (const entry of this.entries) {\n entry.live = false;\n entry.allowance = 0;\n }\n this.entries.clear();\n }\n\n /**\n * Cap-aware waterfill: weight-proportional shares, floored so every mesh can\n * hold its coarse base, clamped at each mesh's `ceilingBytes`, and the surplus\n * from clamped meshes redistributed over the rest until it settles.\n *\n * The invariant callers depend on is `Σ allowance <= totalBytes`. It holds in\n * every branch, including the degenerate one where the floors alone exceed\n * the envelope - there the floors are scaled down proportionally rather than\n * silently overcommitting, because a budget that can be exceeded by\n * registering more meshes is the bug this class exists to fix.\n */\n private allocate(options: { force?: boolean; silentFor?: Entry } = {}): void {\n if (this.disposed) return;\n const entries = [...this.entries];\n if (entries.length === 0) return;\n\n const total = this.totalBytesValue;\n // A floor is a claim *inside* the envelope, not a reservation on top of it:\n // cap it at an even split so N floors can never sum past the total, then\n // hand out only what is left over by weight. Adding a weighted share to an\n // unreserved floor would overcommit whenever any mesh sat below its share.\n const floor = Math.min(this.perMeshFloorBytes, Math.floor(total / entries.length));\n const next = new Map<Entry, number>();\n const unclamped = new Set<Entry>(entries);\n let pool = total;\n\n // Waterfill. Each pass either clamps at least one mesh at its ceiling and\n // returns what it could not use to the pool, or reaches the fixpoint and\n // stops - so it terminates in at most one pass per mesh.\n for (let pass = 0; pass <= entries.length && unclamped.size > 0; pass++) {\n const distributable = Math.max(0, pool - floor * unclamped.size);\n let totalWeight = 0;\n for (const entry of unclamped) totalWeight += normalizeWeight(entry.client.weight());\n const clamped: Entry[] = [];\n for (const entry of unclamped) {\n const weight = normalizeWeight(entry.client.weight());\n // All-zero weights (every mesh hidden) share the remainder evenly\n // rather than dividing by zero.\n const share =\n totalWeight > 0 ? (distributable * weight) / totalWeight : distributable / unclamped.size;\n const ceiling = normalizeCeiling(entry.client.ceilingBytes);\n const wanted = Math.floor(floor + share);\n // Never above what this mesh could actually put to use: holding a 4 MB\n // capture at a 32 MB floor would strand 28 MB its siblings can use.\n if (wanted >= ceiling) {\n next.set(entry, ceiling);\n clamped.push(entry);\n } else {\n next.set(entry, wanted);\n }\n }\n if (clamped.length === 0) break;\n for (const entry of clamped) {\n unclamped.delete(entry);\n pool -= next.get(entry) ?? 0;\n }\n pool = Math.max(0, pool);\n }\n\n for (const entry of entries) {\n const value = next.get(entry) ?? 0;\n const previous = entry.allowance;\n entry.allowance = value;\n if (entry === options.silentFor) continue;\n if (!options.force && !this.movedEnough(previous, value)) continue;\n if (previous === value) continue;\n entry.client.onAllowanceChanged(value);\n }\n }\n\n /** Suppresses reposts for changes too small to alter any mesh's behaviour. */\n private movedEnough(previous: number, next: number): boolean {\n if (previous === next) return false;\n if (previous === 0 || next === 0) return true;\n return Math.abs(next - previous) / previous >= this.deadband;\n }\n}\n\n/** Negative, NaN and infinite weights are all \"no claim on the cache\". */\nfunction normalizeWeight(weight: number): number {\n return Number.isFinite(weight) && weight > 0 ? weight : 0;\n}\n\n/** A missing or nonsensical ceiling means \"this mesh can use whatever it is given\". */\nfunction normalizeCeiling(ceiling: number): number {\n return Number.isFinite(ceiling) && ceiling > 0 ? Math.floor(ceiling) : Number.MAX_SAFE_INTEGER;\n}\n"],"names":["APPEND_CAP","buildHoldSwapGroups","toAdd","run","a","b","buildSwapGroups","toRemove","coverageRuns","entry","buckets","touch","coverageGroup","bucket","groups","byLeaf","leafKey","start","end","ensure","key","group","items","item","last","groupPriority","finest","isClassicLccSwapSet","compareClassicSwapGroups","describe","runs","aa","bb","classicFetchPhaseRank","phase","kindForClassicFetchPhase","classicFetchGroupKey","want","file","stampClassicFetchGroups","pending","lodBaseDistance","forceNearPriority","near","nearDisplayDistance","aggregates","prev","groupId","agg","groupClass","kind","_lodMultiplier","isWaitingOnFinest","classicFetchPhaseForDesired","lodMultiplier","distance","classicFetchPhaseForCoverage","enqueueClassicFetch","level","inView","screenImportance","betterPhase","samePhaseNearer","samePhaseBetterView","compareClassicFetches","fileA","fileB","groupDistA","groupDistB","pendingA","pendingB","groupA","groupB","classA","classB","screenA","screenB","finestA","finestB","rankInGroup","envA","envB","sliceSplatData","chunk","offset","count","sh","shWordsPerSplat","bands","chunkBytes","data","_a","abortReason","signal","reason","validateAppendCap","value","cap","validateLodScale","scale","defaultCpuCacheBytes","resolveCpuCacheBytes","parseLodManifest","json","source","raw","name","directories","chunkUrls","directory","leaves","lodLevels","stack","node","lods","levelKey","range","assertSaneRange","boxFromBound","fileCount","bound","THREE","buildSogScene","options","shBands","manifest","packShBands","chunkOptions","_","index","files","LodScheduler","computeSogPinnedFiles","computeSogMinimumCoverage","total","leaf","pinned","httpDatasetSource","manifestUrl","request","path","probeSize","url","response","toRequestInit","cancelBody","length","saneSize","size","MANIFESTS","n","createLocalDataset","found","basename","match","candidate","depth","minDepth","SplatLoadError","names","root","urls","urlFor","existing","normalize","prefix","entries","shallowest","FRONTIER_FOVEATION_DEFAULTS","DATA_TEXTURE_WIDTH","INITIAL_REVEAL_TIMEOUT_MS","CONTENT_FORCE_FRACTION","MAX_INFLIGHT","MAX_RETIRE_HELD_TICKS","IDLE_RESCHEDULE_MS","MAX_CHUNK_ATTEMPTS","RETRY_BASE_MS","PAGETABLE_PRIORITY_SLOTS","PAGETABLE_DRAW_BUDGET","SLAB_PAGE_SPLATS","PAGETABLE_CACHE_FLOOR_BYTES","estimateSceneDecodedBytes","scene","perSplat","shCoefficientCount","splats","StreamedSplatMesh","SplatMesh","budget","capacity","FrontierWorkerCtor","neverRetireCoverageEarly","__publicField","ChunkLoader","DEFAULT_FOVEATION_TARGET_PX","resolveSplatBudget","holdCoverage","holdNearL0","_b","isPageTable","isPageTableFoveation","cacheCeilingBytes","_c","bytes","e","bootstrap","absoluteUrl","resolveSplatUrl","lower","format","dataset","error","mesh","deviceProfile","deviceBudget","ceilingBudget","sourceOptions","resolveSplatPerformanceProfile","MAX_SH_BANDS","radMaxStdDev","recommendedRadMaxStdDev","budgetLifts","buildRadScene","isAbortError","toSplatLoadError","buildLcc2Scene","buildLccScene","ceiling","liftBudgetToFinestLevel","residentCeiling","capacityFactor","capacityRows","resolvedFoveationMode","resolveSplatFoveationMode","correction","yUpTransformForFormat","slots","page","wanted","target","slot","written","at","done","resident","msg","transfer","collision","controller","loadCollisionMeshTiles","abortListener","_resolve","reject","enabled","next","warn","camera","renderer","now","xrView","resolveXrView","_drawSize","lodCamera","performanceEvent","timings","handle","uploadedCount","worldPoint","radius","channel","_paintLocal","r2","edited","touchedFiles","positions","fileEdits","touched","k","li","px","py","pz","_d","_e","_cameraWorldPos","_sphere","_cameraWorldQuat","startedAt","before","compactionCountBefore","_cameraLocal","_projScreen","_frustum","_cameraForward","focalY","pageTableTimestamp","scheduledRuns","holdingRuns","holding","desiredRuns","desired","desiredFiles","runKey","staged","classicLccGroups","pendingFetches","appended","addsPending","poolPressure","held","missing","holdForTarget","recoverable","stagedNow","base","nearestCandidates","homeGroup","seeds","nearLevel","out","seed","alt","neededRows","g","list","stagedSplats","totalSplats","readyGroups","groupRuns","groupReady","status","progress","totalGroups","cameraLocal","frustum","cameraForward","coverage","coarsened","critical","horizon","fallback","nearest","home","residentBefore","stagedBefore","appendedCount","activeCount","removedCount","stagedCount","compacted","timestamp","sum","allowance","rowSplats","needed","freed","slice","any","holdForFinest","span","covered","from","to","view","gapStart","nextCovered","gapEnd","cursor","holdingNearL0","ordered","worstFile","worstWant","activeFile","active","rowAligned","forwardLocal","sweepCap","f","plan","limit","applyStartedAt","i","clamped","slicePlanRun","writeFinishedAt","degenerateStart","degenerateCount","residentFinishedAt","planTimings","ratio","tree","weight","classicWant","backoff","attempts","delay","previous","candidates","j","SUSPENDED_BUDGET","BudgetGovernor","hysteresis","totalBudget","member","validateWeight","weights","changed","pool","weightSum","capped","cappedSpend","_cameraPos","_box","CameraBudgetGovernor","nonNegative","positive","priority","fixedWeight","initial","forced","significant","surface","projected","clamp","onScreen","applied","min","max","DEFAULT_MAX_GLOBAL_INFLIGHT","DEFAULT_PER_MESH_FLOOR","ChunkFetchScheduler","client","normalizeWeight","participants","totalWeight","other","otherWeight","share","floored","reservedForOthers","best","bestWeight","DEFAULT_PER_MESH_FLOOR_BYTES","DEFAULT_MIN_INTERVAL_MS","DEFAULT_DEADBAND","ChunkCacheBudget","floor","unclamped","pass","distributable","normalizeCeiling"],"mappings":"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"}