@voluma/vlam 0.1.0 → 0.2.1
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.
- package/LICENSE +21 -0
- package/README.md +68 -0
- package/THIRD_PARTY_NOTICES.md +86 -0
- package/dist/budget-governor.d.ts +154 -0
- package/dist/camera-budget-governor.d.ts +234 -0
- package/dist/chunk-cache-budget.d.ts +156 -0
- package/dist/chunk-fetch-scheduler.d.ts +168 -0
- package/dist/chunk-loader.d.ts +85 -0
- package/dist/compute-sorter.d.ts +131 -0
- package/dist/create-splat-renderer.d.ts +99 -0
- package/dist/dataset-source.d.ts +55 -0
- package/dist/depth-of-field.d.ts +73 -0
- package/dist/effects.d.ts +337 -0
- package/dist/effects.js +180 -0
- package/dist/effects.js.map +1 -0
- package/dist/formats/ksplat/index.d.ts +8 -0
- package/dist/formats/ksplat/parse-ksplat.d.ts +9 -0
- package/dist/formats/ksplat.js +167 -0
- package/dist/formats/ksplat.js.map +1 -0
- package/dist/formats/lcc/collision-mesh.d.ts +37 -0
- package/dist/formats/lcc/collision-partition.d.ts +35 -0
- package/dist/formats/lcc/index.d.ts +16 -0
- package/dist/formats/lcc/lcc.d.ts +28 -0
- package/dist/formats/lcc/lcc2-transform.d.ts +18 -0
- package/dist/formats/lcc/lcc2.d.ts +4 -0
- package/dist/formats/lcc/parse-collision-lci.d.ts +28 -0
- package/dist/formats/lcc/parse-lcc.d.ts +177 -0
- package/dist/formats/lcc/parse-mesh-ply.d.ts +21 -0
- package/dist/formats/lcc.js +969 -0
- package/dist/formats/lcc.js.map +1 -0
- package/dist/formats/ply/index.d.ts +12 -0
- package/dist/formats/ply/parse-compressed-ply.d.ts +16 -0
- package/dist/formats/ply/parse-splat-ply.d.ts +46 -0
- package/dist/formats/ply.js +230 -0
- package/dist/formats/ply.js.map +1 -0
- package/dist/formats/rad/frontier-worker-protocol.d.ts +177 -0
- package/dist/formats/rad/index.d.ts +10 -0
- package/dist/formats/rad/parse-rad.d.ts +88 -0
- package/dist/formats/rad/rad-column-decoders.d.ts +15 -0
- package/dist/formats/rad/rad-foveated-source.d.ts +71 -0
- package/dist/formats/rad/rad-parent-sizes.d.ts +45 -0
- package/dist/formats/rad/rad.d.ts +132 -0
- package/dist/formats/rad.js +1098 -0
- package/dist/formats/rad.js.map +1 -0
- package/dist/formats/sog/index.d.ts +9 -0
- package/dist/formats/sog/parse-sog.d.ts +45 -0
- package/dist/formats/sog.js +257 -0
- package/dist/formats/sog.js.map +1 -0
- package/dist/formats/splat/index.d.ts +8 -0
- package/dist/formats/splat/parse-splat.d.ts +10 -0
- package/dist/formats/splat.js +39 -0
- package/dist/formats/splat.js.map +1 -0
- package/dist/formats/spz/index.d.ts +8 -0
- package/dist/formats/spz/parse-spz.d.ts +10 -0
- package/dist/formats/spz.js +218 -0
- package/dist/formats/spz.js.map +1 -0
- package/dist/frontier-worker-CAnK_Cmu.js +566 -0
- package/dist/frontier-worker-CAnK_Cmu.js.map +1 -0
- package/dist/half-float-Cs4FDq8Q.js +30 -0
- package/dist/half-float-Cs4FDq8Q.js.map +1 -0
- package/dist/half-float.d.ts +15 -0
- package/dist/index.d.ts +54 -0
- package/dist/index.js +9054 -0
- package/dist/index.js.map +1 -0
- package/dist/load-scene.d.ts +50 -0
- package/dist/load-worker-protocol.d.ts +82 -0
- package/dist/loading-wLo8vRbA.js +102 -0
- package/dist/loading-wLo8vRbA.js.map +1 -0
- package/dist/loading.d.ts +150 -0
- package/dist/lod-manifest.d.ts +61 -0
- package/dist/lod-scheduler.d.ts +229 -0
- package/dist/lod-source.d.ts +207 -0
- package/dist/logging-BfPdd7NJ.js +19 -0
- package/dist/logging-BfPdd7NJ.js.map +1 -0
- package/dist/logging.d.ts +37 -0
- package/dist/one-shot-worker-G80btnth.js +664 -0
- package/dist/one-shot-worker-G80btnth.js.map +1 -0
- package/dist/orientation.d.ts +35 -0
- package/dist/ply-header-BwgExCn-.js +125 -0
- package/dist/ply-header-BwgExCn-.js.map +1 -0
- package/dist/ply-header.d.ts +87 -0
- package/dist/radix-sort.d.ts +28 -0
- package/dist/radix-sorter.d.ts +46 -0
- package/dist/relighting.d.ts +50 -0
- package/dist/selection-volume.d.ts +87 -0
- package/dist/sh-pack-D5wAe5gg.js +54 -0
- package/dist/sh-pack-D5wAe5gg.js.map +1 -0
- package/dist/sh-pack.d.ts +49 -0
- package/dist/sort-scheduler.d.ts +62 -0
- package/dist/sort-worker-protocol.d.ts +37 -0
- package/dist/sorter.d.ts +31 -0
- package/dist/source-transform.d.ts +57 -0
- package/dist/splat-budget-PSojLJPO.js +157 -0
- package/dist/splat-budget-PSojLJPO.js.map +1 -0
- package/dist/splat-budget.d.ts +404 -0
- package/dist/splat-data-BZM5dhhx.js +10 -0
- package/dist/splat-data-BZM5dhhx.js.map +1 -0
- package/dist/splat-data.d.ts +150 -0
- package/dist/splat-depth-pack.d.ts +39 -0
- package/dist/splat-mesh-material.d.ts +255 -0
- package/dist/splat-mesh-picking.d.ts +87 -0
- package/dist/splat-mesh-pool.d.ts +241 -0
- package/dist/splat-mesh-types-8QDjTbZO.js +390 -0
- package/dist/splat-mesh-types-8QDjTbZO.js.map +1 -0
- package/dist/splat-mesh-types.d.ts +390 -0
- package/dist/splat-mesh.d.ts +737 -0
- package/dist/splat-modifier-stack.d.ts +38 -0
- package/dist/splat-modifier.d.ts +156 -0
- package/dist/splat-partition.d.ts +37 -0
- package/dist/splat-query.d.ts +51 -0
- package/dist/splat-scene.d.ts +127 -0
- package/dist/splat-sort-bounds.d.ts +11 -0
- package/dist/static-lod-splat-mesh.d.ts +53 -0
- package/dist/static-lod-worker-D2fyqQZO.js +474 -0
- package/dist/static-lod-worker-D2fyqQZO.js.map +1 -0
- package/dist/static-lod-worker-protocol.d.ts +37 -0
- package/dist/static-lod.d.ts +24 -0
- package/dist/storage-attribute-mirror.d.ts +103 -0
- package/dist/streamed-splat-mesh-utils.d.ts +146 -0
- package/dist/streamed-splat-mesh.d.ts +985 -0
- package/dist/unified-splat-renderer.d.ts +205 -0
- package/dist/webgpu-limits.d.ts +133 -0
- package/dist/work-buffer-gather.d.ts +119 -0
- package/dist/work-buffer-material.d.ts +42 -0
- package/dist/worker-sorter.d.ts +57 -0
- package/dist/xr-view.d.ts +92 -0
- package/package.json +135 -21
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/lib/orientation.ts","../src/lib/splat-modifier-stack.ts","../src/lib/splat-mesh-pool.ts","../src/lib/depth-of-field.ts","../src/lib/splat-mesh-material.ts","../src/lib/splat-sort-bounds.ts","../src/lib/storage-attribute-mirror.ts","../src/lib/compute-sorter.ts","../src/lib/worker-sorter.ts","../src/lib/sort-scheduler.ts","../src/lib/splat-depth-pack.ts","../src/lib/xr-view.ts","../src/lib/splat-mesh-picking.ts","../src/lib/relighting.ts","../src/lib/splat-query.ts","../src/lib/splat-mesh.ts","../src/lib/streamed-splat-mesh-utils.ts","../src/lib/lod-manifest.ts","../src/lib/lod-source.ts","../src/lib/dataset-source.ts","../src/lib/chunk-loader.ts","../src/lib/formats/rad/frontier-worker-protocol.ts","../src/lib/streamed-splat-mesh.ts","../src/lib/load-scene.ts","../src/lib/static-lod-splat-mesh.ts","../src/lib/source-transform.ts","../src/lib/splat-scene.ts","../src/lib/radix-sort.ts","../src/lib/radix-sorter.ts","../src/lib/work-buffer-gather.ts","../src/lib/work-buffer-material.ts","../src/lib/webgpu-limits.ts","../src/lib/unified-splat-renderer.ts","../src/lib/budget-governor.ts","../src/lib/camera-budget-governor.ts","../src/lib/create-splat-renderer.ts","../src/lib/chunk-fetch-scheduler.ts","../src/lib/chunk-cache-budget.ts","../src/lib/selection-volume.ts","../src/lib/splat-partition.ts","../src/lib/splat-modifier.ts"],"sourcesContent":["import * as THREE from 'three/webgpu';\n\n/**\n * How a loaded scene is oriented into the three.js Y-up world.\n *\n * - `'y-up'` (default): VLAM normalizes every known format to three.js Y-up -\n * the OpenCV-frame formats (PLY/`.splat`/`.ksplat`/SOG/`.rad`) are flipped\n * 180° about X, SPZ is already Y-up, and LCC carries its own Z-up→Y-up matrix.\n * \"Drop a file and it stands up\", matching engine viewers (PlayCanvas, Babylon).\n * - `'source'`: no cosmetic correction - the scene renders in its data frame,\n * matching raw Spark / mkkellogg. A host that manages orientation itself (e.g.\n * swapping viewers while keeping one stored per-scene rotation) uses this.\n *\n * Note LCC's Z-up→Y-up matrix is *format semantics*, not a cosmetic default: it\n * is applied in **both** modes (Spark applies it too). Only the 180°-X flip for\n * the Y-down formats is gated by this option.\n */\nexport type SplatOrientation = 'y-up' | 'source';\n\n/**\n * Formats {@link yUpTransformForFormat} understands: the self-contained scene\n * formats ({@link SplatData.sourceFormat}) plus the streamed ones.\n */\nexport type OrientableFormat =\n 'ply' | 'splat' | 'ksplat' | 'sog' | 'spz' | 'rad' | 'streamed-sog' | 'lcc' | 'lcc2';\n\n/**\n * The 180°-about-X rotation that maps a 3DGS Y-down capture to three.js Y-up -\n * the same correction Spark documents as `quaternion.set(1, 0, 0, 0)`. Returned\n * as a fresh {@link THREE.Matrix4} so callers can mutate/decompose it freely.\n */\nexport function createYUpTransform(): THREE.Matrix4 {\n return new THREE.Matrix4().makeRotationX(Math.PI);\n}\n\n/**\n * The `'y-up'` correction for a format, or `null` when nothing is needed:\n * SPZ is already Y-up, and LCC self-orients via its `formatTransform`\n * (callers apply that in both modes, so this returns `null` for LCC to avoid\n * doubling it). Y-down formats (PLY/`.splat`/`.ksplat`/SOG) get the 180°-X flip.\n */\nexport function yUpTransformForFormat(format: OrientableFormat | undefined): THREE.Matrix4 | null {\n switch (format) {\n case 'ply':\n case 'splat':\n case 'ksplat':\n case 'sog':\n case 'rad':\n case 'streamed-sog':\n return createYUpTransform();\n case 'spz':\n case 'lcc':\n case 'lcc2':\n case undefined:\n return null;\n }\n}\n","/** Modifier-stack folding for the splat material graph. */\nimport * as THREE from 'three/webgpu';\nimport { bool, float, mat3, modelViewMatrix, modelWorldMatrix, vec3, vec4 } from 'three/tsl';\nimport type { SplatContext, SplatModifier, SplatOutputs } from './splat-modifier';\nimport type { Vec3Uniform } from './splat-mesh-material';\n\nfunction asNode<T extends string>(node: unknown): THREE.Node<T> {\n return node as THREE.Node<T>;\n}\n\n/**\n * Builds the per-splat {@link SplatContext}, folds the modifier stack\n * over it in order, and returns the graph fragments the renderer applies.\n * `null` fragments mean \"untouched\": with an empty stack every fragment\n * is null and the caller emits exactly the unhooked graph.\n *\n * Modifiers are called once, here, at material build time - they are\n * graph builders, not per-frame callbacks. Derived context fields\n * (worldCenter, viewCenter, normal) are memoized lazily so only the\n * modifiers that read them add their nodes to the graph.\n */\nexport function foldSplatModifierStack(\n modifierList: readonly SplatModifier[],\n localCameraPosition: Vec3Uniform,\n inputs: {\n index: THREE.Node<'int'>;\n localCenter: THREE.Node<'vec3'>;\n /** Pre-placement pool position; defaults to {@link localCenter}. */\n sourceCenter?: THREE.Node<'vec3'>;\n /** Source-data-frame → mesh-local linear transform; defaults to identity. */\n sourceToLocal?: THREE.Node<'mat3'>;\n color: THREE.Node<'vec4'>;\n makeNormal: () => THREE.Node<'vec3'>;\n makeChannel: (name: string) => THREE.Node<'float'>;\n /** Optional coordinate-space adapters for compute-time modifier folding. */\n makeWorldCenter?: () => THREE.Node<'vec3'>;\n makeViewCenter?: () => THREE.Node<'vec3'>;\n },\n): {\n color: THREE.Node<'vec4'>;\n offset: THREE.Node<'vec3'> | null;\n scaleSquared: THREE.Node<'float'> | null;\n rotation: THREE.Node<'mat3'> | null;\n visible: THREE.Node<'bool'> | null;\n isotropicCovarianceMix: THREE.Node<'float'> | null;\n isotropicVarianceScale: THREE.Node<'float'> | null;\n isotropicScreenRadiusPx: THREE.Node<'float'> | null;\n} {\n if (modifierList.length === 0) {\n return {\n color: inputs.color,\n offset: null,\n scaleSquared: null,\n rotation: null,\n visible: null,\n isotropicCovarianceMix: null,\n isotropicVarianceScale: null,\n isotropicScreenRadiusPx: null,\n };\n }\n\n const cameraLocal = asNode<'vec3'>(localCameraPosition);\n const state = {\n color: inputs.color,\n offset: asNode<'vec3'>(vec3(0.0, 0.0, 0.0)),\n scale: asNode<'float'>(float(1.0)),\n rotation: asNode<'mat3'>(mat3(vec3(1, 0, 0), vec3(0, 1, 0), vec3(0, 0, 1))),\n visible: asNode<'bool'>(bool(true)),\n };\n const used = { offset: false, scale: false, rotation: false, visible: false };\n let isotropicCovarianceMix: THREE.Node<'float'> | null = null;\n let isotropicVarianceScale: THREE.Node<'float'> | null = null;\n let isotropicScreenRadiusPx: THREE.Node<'float'> | null = null;\n\n let worldCenter: THREE.Node<'vec3'> | null = null;\n let viewCenter: THREE.Node<'vec3'> | null = null;\n let normal: THREE.Node<'vec3'> | null = null;\n const channelNodes = new Map<string, THREE.Node<'float'>>();\n const context: SplatContext = {\n index: inputs.index,\n localCenter: inputs.localCenter,\n sourceCenter: inputs.sourceCenter ?? inputs.localCenter,\n sourceToLocal: inputs.sourceToLocal ?? state.rotation,\n cameraLocal,\n baseColor: inputs.color,\n get worldCenter() {\n return (worldCenter ??= asNode<'vec3'>(\n inputs.makeWorldCenter\n ? inputs.makeWorldCenter()\n : modelWorldMatrix.mul(vec4(inputs.localCenter, 1.0)).xyz,\n ));\n },\n get viewCenter() {\n return (viewCenter ??= asNode<'vec3'>(\n inputs.makeViewCenter\n ? inputs.makeViewCenter()\n : modelViewMatrix.mul(vec4(inputs.localCenter, 1.0)).xyz,\n ));\n },\n get normal() {\n return (normal ??= inputs.makeNormal());\n },\n channel(name: string) {\n let node = channelNodes.get(name);\n if (!node) {\n node = inputs.makeChannel(name);\n channelNodes.set(name, node);\n }\n return node;\n },\n get color() {\n return state.color;\n },\n get offset() {\n return state.offset;\n },\n get scale() {\n return state.scale;\n },\n get rotation() {\n return state.rotation;\n },\n get visible() {\n return state.visible;\n },\n };\n\n for (const modifier of modifierList) {\n const outputs: SplatOutputs = modifier(context);\n if (outputs.color) state.color = outputs.color;\n if (outputs.offset) {\n state.offset = outputs.offset;\n used.offset = true;\n }\n if (outputs.scale) {\n state.scale = outputs.scale;\n used.scale = true;\n }\n if (outputs.rotation) {\n state.rotation = outputs.rotation;\n used.rotation = true;\n }\n if (outputs.visible) {\n state.visible = outputs.visible;\n used.visible = true;\n }\n if (outputs.isotropicCovarianceMix !== undefined) {\n isotropicCovarianceMix = outputs.isotropicCovarianceMix;\n }\n if (outputs.isotropicVarianceScale !== undefined) {\n isotropicVarianceScale = outputs.isotropicVarianceScale;\n }\n if (outputs.isotropicScreenRadiusPx !== undefined) {\n isotropicScreenRadiusPx = outputs.isotropicScreenRadiusPx;\n }\n }\n\n return {\n color: state.color,\n offset: used.offset ? state.offset : null,\n scaleSquared: used.scale ? asNode<'float'>(state.scale.mul(state.scale)) : null,\n rotation: used.rotation ? state.rotation : null,\n visible: used.visible ? state.visible : null,\n isotropicCovarianceMix,\n isotropicVarianceScale,\n isotropicScreenRadiusPx,\n };\n}\n","/**\n * Texture-pool primitives behind `SplatMesh`.\n *\n * Splat attributes live in pool data textures, a fixed number of texels wide,\n * and every range the mesh hands out is a whole number of rows in them. This\n * module holds the parts of that scheme with no dependency on the mesh's own\n * state: creating the textures, measuring the backend's limits, and the row-span\n * bookkeeping the allocator runs on.\n *\n * {@link SplatPool} is public - a host builds one to let several meshes draw\n * from a shared memory envelope. The rest is internal.\n */\nimport * as THREE from 'three/webgpu';\n\n/**\n * Texels per row in every pool data texture.\n *\n * A range is row-aligned, so this also sets the allocation granularity: a\n * 10-splat chunk still occupies a whole 2048-texel row. Wider rows waste more\n * on small chunks; narrower rows need more rows than a backend may allow.\n */\nexport const SPLAT_DATA_TEXTURE_WIDTH = 2048;\n\n/** A free span of rows in the pool, as a row index and a row count. */\nexport interface RowSpan {\n start: number;\n count: number;\n}\n\n/**\n * Takes `rowCount` contiguous rows out of `spans`, best-fit.\n *\n * Best-fit rather than first-fit: a chunk pool churns ranges of very different\n * sizes, and spending the smallest span that fits keeps the large ones intact\n * for the large chunks that have nowhere else to go.\n *\n * Mutates `spans` in place. Throws when nothing contiguous is left, which is\n * the caller's cue to compact.\n *\n * @param poolRows - Total rows in the pool; named in the failure only.\n * @returns The starting row of the allocated span.\n */\nexport function allocateRowSpan(spans: RowSpan[], rowCount: number, poolRows: number): number {\n let bestIndex = -1;\n for (let i = 0; i < spans.length; i++) {\n const span = spans[i] as RowSpan;\n if (span.count < rowCount) continue;\n if (bestIndex === -1 || span.count < (spans[bestIndex] as RowSpan).count) {\n bestIndex = i;\n }\n if (span.count === rowCount) break;\n }\n if (bestIndex !== -1) {\n const span = spans[bestIndex] as RowSpan;\n const start = span.start;\n span.start += rowCount;\n span.count -= rowCount;\n if (span.count === 0) spans.splice(bestIndex, 1);\n return start;\n }\n throw new Error(\n `SplatMesh capacity exceeded: no contiguous space for ${rowCount} rows ` +\n `(${SPLAT_DATA_TEXTURE_WIDTH} splats each) in a ${poolRows}-row pool.`,\n );\n}\n\n/**\n * Returns a span to the free list, coalescing it with any neighbours.\n *\n * Merging on release is what keeps {@link allocateRowSpan} able to satisfy a\n * large request after many small ranges have come and gone; without it the\n * free list fragments into unusable slivers.\n *\n * @returns The new free list, sorted by start row.\n */\nexport function releaseRowSpan(spans: RowSpan[], start: number, count: number): RowSpan[] {\n spans.push({ start, count });\n spans.sort((a, b) => a.start - b.start);\n const merged: RowSpan[] = [];\n for (const span of spans) {\n const last = merged[merged.length - 1];\n if (last && last.start + last.count > span.start) {\n // Overlapping free spans mean a double (or overlapping) release - left\n // silent, the same rows would be handed out twice by allocateRowSpan.\n throw new Error(\n `SplatMesh pool released rows [${start}, ${start + count}) overlap an already-free span.`,\n );\n }\n if (last && last.start + last.count === span.start) last.count += span.count;\n else merged.push(span);\n }\n return merged;\n}\n\n/** CPU-side mirror of the pool textures, kept for partial uploads. */\nexport interface SplatPoolBacking {\n centers: Float32Array;\n colors: Uint8Array;\n covarianceA: Float32Array;\n covarianceB: Float32Array;\n /** Per-splat packed SH; texture t holds coefficients 4t..4t+3. */\n shPacked: Uint32Array[];\n}\n\n/** A tenant's allocation in the pool, as a row index and a row count. */\nexport interface SplatPoolRange {\n startRow: number;\n rowCount: number;\n}\n\n/**\n * A mesh that draws from a {@link SplatPool}.\n *\n * Only compaction needs this: packing the pool relocates rows belonging to\n * every tenant, so each must be able to enumerate its ranges, follow them to\n * their new rows, and rebuild whatever it keyed by pool index.\n */\nexport interface SplatPoolTenant {\n /** The tenant's current allocations. */\n poolRanges(): Iterable<SplatPoolRange>;\n /**\n * Adopts a new start row for one of this tenant's ranges. The pool has\n * already moved the splat data; the tenant moves anything else keyed by pool\n * row (per-splat channels) and records that those rows need re-uploading.\n */\n relocatePoolRange(range: SplatPoolRange, targetRow: number): void;\n /** Called once after a compaction, so the tenant can rebuild draw state. */\n onPoolCompacted(): void;\n}\n\n/** Options for {@link SplatPool}. */\nexport interface SplatPoolOptions {\n /** Splats the pool must hold; rounded up to whole rows. */\n capacity: number;\n /**\n * Texture precision for centers and covarianceA. `'float16'` halves their\n * VRAM; covarianceB stays float32 either way because it packs integer IDs\n * (SOG palette label, `.rad` frontier parent) that halves cannot represent\n * exactly above 2048.\n */\n floatTextures?: 'float32' | 'float16';\n /** Bands of per-splat (non-palette) SH to allocate for; 0 disables. */\n packedShBands?: 0 | 1 | 2 | 3;\n /** Packed-SH coefficient count for `packedShBands`, supplied by the caller\n * so this module stays free of the SH packing tables. */\n packedShTextureCount?: number;\n /**\n * The device's `maxTextureDimension2D`, so a pool too tall for it fails here\n * rather than at first draw. Pass {@link deviceMaxTextureSize}.\n *\n * `SplatMesh.update` also checks this, but only once and only if it is ever\n * reached: a pool created past the limit and drawn through a path that\n * swallows that first throw renders forever against invalid textures, which\n * surfaces as an unreadable cascade of WebGPU \"invalid due to a previous\n * error\" validation failures and a black canvas. Checking at construction is\n * both earlier (before the allocation) and unskippable.\n *\n * Omitted or 0 skips the check, so a caller that cannot read the limit is not\n * falsely rejected.\n */\n maxTextureSize?: number;\n}\n\n/**\n * The splat storage a mesh draws from: the data textures, their CPU backing,\n * and the row allocator that hands out space in them.\n *\n * Split out of `SplatMesh` because storage and drawing have different\n * lifetimes and, ultimately, different owners. A mesh's *draw identity* is\n * already independent of where its splats sit - `splatIndex` maps each\n * instance to a pool index and the sorter rewrites it freely - so the pool\n * behind those indices is a separate concern, and one pool can in principle\n * back several meshes (each keeping its own active list of pool indices).\n *\n * Allocation is row-aligned and best-fit; see {@link allocateRowSpan}.\n */\nexport class SplatPool {\n readonly width = SPLAT_DATA_TEXTURE_WIDTH;\n readonly rows: number;\n readonly floatTextures: 'float32' | 'float16';\n readonly packedShBands: 0 | 1 | 2 | 3;\n readonly backing: SplatPoolBacking;\n readonly centersTexture: THREE.DataTexture;\n readonly colorsTexture: THREE.DataTexture;\n readonly covarianceATexture: THREE.DataTexture;\n readonly covarianceBTexture: THREE.DataTexture;\n readonly shPackedTextures: readonly THREE.DataTexture[];\n /** Free row spans, sorted by start row. Mutated in place by the allocator. */\n freeRowSpans: RowSpan[];\n private readonly tenants = new Set<SplatPoolTenant>();\n /**\n * Pool index → its owner's packed active-list slot, valid only while that\n * pool index is active.\n *\n * Pool-owned rather than per-mesh because it is keyed by *pool* index: a\n * per-mesh copy would have to span the whole pool, so N meshes sharing one\n * pool would each pay 4 B per pool splat (a 4 M-splat pool shared by 13\n * meshes = ~200 MB of reverse maps). One array is safe because a row belongs\n * to exactly one tenant at a time, so no two tenants ever write the same\n * entry - the slot numbers in it are simply read back by whoever owns\n * the row.\n */\n readonly activeSlotByPoolIndex: Uint32Array;\n private poolIndexTemplate: Uint32Array | null = null;\n\n constructor(options: SplatPoolOptions) {\n if (!(options.capacity > 0)) throw new Error('SplatPool capacity must be positive.');\n this.rows = Math.ceil(options.capacity / this.width);\n assertPoolRowsFitDevice(this.rows, options.maxTextureSize ?? 0, this.width);\n const texelCount = this.width * this.rows;\n this.floatTextures = options.floatTextures === 'float16' ? 'float16' : 'float32';\n this.packedShBands = options.packedShBands ?? 0;\n const floatType = this.floatTextures === 'float16' ? THREE.HalfFloatType : THREE.FloatType;\n\n this.backing = {\n centers: new Float32Array(texelCount * 4),\n colors: new Uint8Array(texelCount * 4),\n covarianceA: new Float32Array(texelCount * 4),\n covarianceB: new Float32Array(texelCount * 4),\n // One RGBA32UI texel per splat per texture; sharing the pool's row\n // geometry lets the whole partial-upload/compaction path reuse the\n // existing row arithmetic unchanged.\n shPacked: Array.from(\n { length: options.packedShTextureCount ?? 0 },\n () => new Uint32Array(texelCount * 4),\n ),\n };\n // Under float16 the textures get their own half-encoded images; the float32\n // backing above stays authoritative and is re-encoded on upload.\n const centersImage: Float32Array | Uint16Array =\n this.floatTextures === 'float16' ? new Uint16Array(texelCount * 4) : this.backing.centers;\n const covarianceAImage: Float32Array | Uint16Array =\n this.floatTextures === 'float16' ? new Uint16Array(texelCount * 4) : this.backing.covarianceA;\n\n this.centersTexture = createDataTexture(centersImage, this.width, this.rows, floatType);\n this.colorsTexture = createDataTexture(\n this.backing.colors,\n this.width,\n this.rows,\n THREE.UnsignedByteType,\n );\n this.covarianceATexture = createDataTexture(covarianceAImage, this.width, this.rows, floatType);\n this.covarianceBTexture = createDataTexture(\n this.backing.covarianceB,\n this.width,\n this.rows,\n THREE.FloatType,\n );\n this.shPackedTextures = this.backing.shPacked.map((data) =>\n createIntegerDataTexture(data, this.width, this.rows),\n );\n this.freeRowSpans = [{ start: 0, count: this.rows }];\n this.activeSlotByPoolIndex = new Uint32Array(texelCount);\n }\n\n /**\n * A reusable `0, 1, 2, …` ramp over the pool, used to fill runs of pool\n * indices and active-list slots without a per-element loop. Pool-owned so\n * tenants sharing a pool share the one copy.\n */\n indexTemplate(): Uint32Array {\n if (this.poolIndexTemplate) return this.poolIndexTemplate;\n const indices = new Uint32Array(this.capacity);\n for (let index = 0; index < indices.length; index++) indices[index] = index;\n this.poolIndexTemplate = indices;\n return indices;\n }\n\n /** Splats the pool holds, always a whole number of rows. */\n get capacity(): number {\n return this.rows * this.width;\n }\n\n /** Whether `texture` belongs to this pool (and so outlives any one tenant). */\n isPoolTexture(texture: THREE.Texture): boolean {\n return (\n texture === this.centersTexture ||\n texture === this.colorsTexture ||\n texture === this.covarianceATexture ||\n texture === this.covarianceBTexture ||\n this.shPackedTextures.includes(texture as THREE.DataTexture)\n );\n }\n\n /** The four core textures, in the order the upload path indexes them. */\n get coreTextures(): readonly THREE.DataTexture[] {\n return [\n this.centersTexture,\n this.colorsTexture,\n this.covarianceATexture,\n this.covarianceBTexture,\n ];\n }\n\n /** Rows still free, whether or not they are contiguous. */\n get freeRows(): number {\n let rows = 0;\n for (const span of this.freeRowSpans) rows += span.count;\n return rows;\n }\n\n /** Takes `rowCount` contiguous rows; throws when none are left (compact cue). */\n allocateRows(rowCount: number): number {\n return allocateRowSpan(this.freeRowSpans, rowCount, this.rows);\n }\n\n /** Returns rows to the free list, coalescing neighbours. */\n releaseRows(start: number, count: number): void {\n this.freeRowSpans = releaseRowSpan(this.freeRowSpans, start, count);\n }\n\n /** Resets the free list to one span covering the whole pool. */\n resetFreeRows(fromRow = 0): void {\n this.freeRowSpans = fromRow < this.rows ? [{ start: fromRow, count: this.rows - fromRow }] : [];\n }\n\n /**\n * Adds a mesh that draws from this pool. Registration exists for\n * {@link compact}: packing the pool moves rows that belong to *every*\n * tenant, so each has to be told where its ranges went.\n */\n register(tenant: SplatPoolTenant): void {\n this.tenants.add(tenant);\n }\n\n /**\n * Drops a tenant. Release its ranges first: an unregistered tenant is\n * invisible to {@link compact}, which would then treat rows it still holds\n * as free and hand them to someone else. `compact` throws rather than let\n * that happen silently.\n */\n unregister(tenant: SplatPoolTenant): void {\n this.tenants.delete(tenant);\n }\n\n /** Number of meshes drawing from this pool. */\n get tenantCount(): number {\n return this.tenants.size;\n }\n\n /**\n * Packs every tenant's ranges toward row 0, removing the gaps that add /\n * remove churn leaves behind and restoring a single contiguous free span.\n * Callers reach this when {@link allocateRows} throws despite enough total\n * free rows - row-alignment fragmentation.\n *\n * The CPU backing arrays are authoritative, so nothing is re-fetched; moved\n * rows are re-uploaded on the tenant's next update.\n *\n * Note this is a whole-pool stall: with several tenants, one mesh's\n * fragmentation relocates the others' rows too, and every tenant rebuilds\n * its draw state. (Spark sidesteps the equivalent by allocating fixed-size,\n * interchangeable pages that never need packing; vlam's ranges are variable\n * runs, so it packs instead.)\n */\n compact(): void {\n const width = this.width;\n // Move in ascending row order so each target row is <= its current row and\n // no not-yet-moved range is ever overwritten. Ordering is global across\n // tenants, because they interleave in one address space.\n const placements: { tenant: SplatPoolTenant; range: SplatPoolRange }[] = [];\n let heldRows = 0;\n for (const tenant of this.tenants) {\n for (const range of tenant.poolRanges()) {\n placements.push({ tenant, range });\n heldRows += range.rowCount;\n }\n }\n // Every row is either free or held by a registered tenant. A shortfall\n // means rows are allocated to something the pool cannot see - an\n // unregistered tenant, or a range the tenant forgot to report - and\n // packing would hand those rows out a second time.\n if (heldRows + this.freeRows !== this.rows) {\n throw new Error(\n `SplatPool compaction found ${this.rows - heldRows - this.freeRows} unaccounted row(s): ` +\n `${heldRows} held by ${this.tenants.size} tenant(s), ${this.freeRows} free, ${this.rows} total. ` +\n `Release a tenant's ranges before unregistering it.`,\n );\n }\n placements.sort((a, b) => a.range.startRow - b.range.startRow);\n\n let targetRow = 0;\n for (const { tenant, range } of placements) {\n if (range.startRow !== targetRow) {\n const from = range.startRow * width * 4;\n const to = targetRow * width * 4;\n const length = range.rowCount * width * 4;\n this.backing.centers.copyWithin(to, from, from + length);\n this.backing.colors.copyWithin(to, from, from + length);\n this.backing.covarianceA.copyWithin(to, from, from + length);\n this.backing.covarianceB.copyWithin(to, from, from + length);\n // Packed SH shares the pool's row geometry and its RGBA texel stride,\n // so it relocates on exactly the same arithmetic.\n for (const group of this.backing.shPacked) group.copyWithin(to, from, from + length);\n // The tenant moves whatever else is keyed by pool row (channels) and\n // adopts the new start.\n tenant.relocatePoolRange(range, targetRow);\n }\n targetRow += range.rowCount;\n }\n this.resetFreeRows(targetRow);\n for (const tenant of this.tenants) tenant.onPoolCompacted();\n }\n\n dispose(): void {\n for (const texture of this.coreTextures) texture.dispose();\n for (const texture of this.shPackedTextures) texture.dispose();\n this.tenants.clear();\n }\n}\n\nexport function createDataTexture(\n data: Float32Array | Uint8Array | Uint16Array,\n width: number,\n height: number,\n type: THREE.TextureDataType,\n): THREE.DataTexture {\n const texture = new THREE.DataTexture(data, width, height, THREE.RGBAFormat, type);\n texture.needsUpdate = true;\n return texture;\n}\n\n/**\n * An RGBA32UI data texture: four raw `uint32` per texel, delivered to the\n * shader as a `uvec4` with no filtering or normalization. Packed SH words go\n * through this rather than a float texture so they survive bit-exact.\n */\nexport function createIntegerDataTexture(\n data: Uint32Array,\n width: number,\n height: number,\n): THREE.DataTexture {\n const texture = new THREE.DataTexture(\n data,\n width,\n height,\n THREE.RGBAIntegerFormat,\n THREE.UnsignedIntType,\n );\n // Integer textures cannot be filtered; nearest is the only legal choice and\n // is what a per-texel lookup wants anyway. The backend derives the concrete\n // format (`rgba32uint` on WebGPU, `RGBA32UI` on WebGL2) from format + type,\n // so `internalFormat` is deliberately left alone.\n texture.magFilter = THREE.NearestFilter;\n texture.minFilter = THREE.NearestFilter;\n texture.generateMipmaps = false;\n texture.needsUpdate = true;\n return texture;\n}\n\n/**\n * The renderer backend's maximum 2D texture dimension, or 0 when it can't be\n * read (in which case callers skip any limit check rather than falsely reject).\n * WebGPU exposes `device.limits.maxTextureDimension2D`; WebGL2 exposes\n * `gl.MAX_TEXTURE_SIZE`.\n */\nexport function deviceMaxTextureSize(renderer: THREE.WebGPURenderer): number {\n const backend = renderer.backend as\n | {\n device?: { limits?: { maxTextureDimension2D?: number } };\n gl?: WebGL2RenderingContext;\n }\n | undefined;\n const wgpu = backend?.device?.limits?.maxTextureDimension2D;\n if (typeof wgpu === 'number' && wgpu > 0) return wgpu;\n const gl = backend?.gl;\n if (gl) {\n const max = gl.getParameter(gl.MAX_TEXTURE_SIZE) as unknown;\n if (typeof max === 'number' && max > 0) return max;\n }\n return 0;\n}\n\n/**\n * Throws when a pool's data textures would be taller than the device allows.\n *\n * `maxTextureSize <= 0` means \"limit unknown\" and skips the check rather than\n * risk a false rejection; see {@link deviceMaxTextureSize}.\n *\n * @param rows - Rows the pool needs.\n * @param maxTextureSize - The device's `maxTextureDimension2D`.\n * @param width - Texels per row, for the splat count in the message.\n */\nexport function assertPoolRowsFitDevice(\n rows: number,\n maxTextureSize: number,\n width: number = SPLAT_DATA_TEXTURE_WIDTH,\n): void {\n if (!(maxTextureSize > 0) || rows <= maxTextureSize) return;\n throw new Error(\n `SplatPool: this scene needs a ${width}×${rows} data texture, but this device ` +\n `caps texture dimensions at ${maxTextureSize} (about ` +\n `${(maxTextureSize * width).toLocaleString('en-US')} pool splats). Lower the ` +\n `splat budget, or raise maxTextureDimension2D in the renderer's requiredLimits ` +\n `if the adapter advertises more.`,\n );\n}\n\n/**\n * Three's WebGPU backend submits one `queue.writeBuffer` per update range.\n * Merge touching mutations here so a region-atomic commit does not turn a\n * handful of contiguous backfills into several main-thread submissions.\n */\nexport function addMergedUpdateRange(\n attribute: THREE.BufferAttribute,\n start: number,\n count: number,\n): void {\n let mergedStart = start;\n let mergedEnd = start + count;\n const disjoint: { start: number; count: number }[] = [];\n for (const range of attribute.updateRanges) {\n const rangeEnd = range.start + range.count;\n if (rangeEnd < mergedStart || range.start > mergedEnd) {\n disjoint.push(range);\n continue;\n }\n mergedStart = Math.min(mergedStart, range.start);\n mergedEnd = Math.max(mergedEnd, rangeEnd);\n }\n disjoint.push({ start: mergedStart, count: mergedEnd - mergedStart });\n disjoint.sort((a, b) => a.start - b.start);\n attribute.clearUpdateRanges();\n for (const range of disjoint) attribute.addUpdateRange(range.start, range.count);\n}\n","/**\n * Core projected-2D depth of field (post-EWA isotropic CoC).\n *\n * Spark-style pinhole CoC:\n * focusBlur = |depth − focus| / depth\n * apertureRadius = focalPx · tan(0.5 · apertureAngle)\n * cocRadius = focusBlur · apertureRadius\n * blurVariance = clamp(cocRadius², …, maxRadius²)\n *\n * `/ depth` is Spark's exact falloff (`splatVertex.glsl`), restored for visual\n * parity: scenes authored against Spark's foreground defocus rendered flat here\n * under the previous `/ max(depth, focus)`, which capped focusBlur at 1 and so\n * limited the CoC to a single apertureRadius - sub-pixel at the aperture sizes\n * ModifyCamera produces. Behind the focus plane the two forms are identical\n * (`max(depth, focus) === depth`), so only the near side changes.\n *\n * Note `/ depth` is unbounded as depth → 0. The fill-rate guard is the same one\n * Spark uses: {@link MAX_DOF_RADIUS_PX} clamps the CoC. Do not \"fix\" the near\n * side by dividing by focus instead - that pushes every *background* splat to\n * the cap when zooming out with a near focus plane (huge quads → fill-rate\n * death), which is what the bounded form was originally guarding against.\n *\n * Voluma's ModifyCamera `apertureSize` maps to an angle through the *live*\n * focus distance - `apertureAngle = 2·atan(0.5·size / focus)` - matching the\n * host helper Voluma ships for Spark. A fixed reference distance was tried\n * instead to stop near focus planes from\n * ballooning every splat, but it decouples aperture from focus and so flattens\n * the intended effect: racking focus toward the camera is exactly how authors\n * get the whole scene to go soft. Keep the mapping focus-relative; the CoC cap\n * is what bounds fill-rate.\n *\n * Unlike the M13 `depthOfFieldPreset` modifier (3D scale), this path adds\n * `coc²·I` to the projected 2D covariance with `√(det)` opacity fade.\n */\n\n/**\n * Soft CoC radius cap in pixels (Spark clamps with `maxPixelRadius`).\n * Kept modest for interactive fill-rate; raise only if cinematic stills need it.\n */\nexport const MAX_DOF_RADIUS_PX = 24;\n\n/** Cap on CoC variance (px²) added to the projected 2D covariance diagonal. */\nexport const MAX_DOF_VARIANCE = MAX_DOF_RADIUS_PX * MAX_DOF_RADIUS_PX;\n\n/** Live camera depth-of-field settings for {@link SplatMesh.setDepthOfField}. */\nexport type DepthOfFieldSettings = {\n /** View-space distance to the focal plane (world units). Must stay positive. */\n focusDistance: number;\n /** Aperture size in the same units as Voluma `DofSettings.apertureSize`. `0` = off. */\n aperture: number;\n};\n\n/**\n * Maps Voluma aperture size → full aperture angle (radians) through the focus\n * distance, identical to the host helper Voluma ships for Spark:\n * `2·atan(0.5·size / focus)`. Pulling focus toward the camera widens the angle,\n * which is what makes a near focus plane soften the whole scene.\n */\nexport function apertureAngleFromSize(aperture: number, focusDistance: number): number {\n const size = Math.max(0, aperture);\n if (size <= 0) return 0;\n const focus = Math.max(1e-4, focusDistance);\n return 2 * Math.atan((0.5 * size) / focus);\n}\n\n/**\n * Circle-of-confusion variance in px² for a splat at view-space depth.\n * Returns `0` when aperture is disabled. Mirrors Spark's vertex DoF branch.\n */\nexport function computeDofCocVariancePx2(options: {\n depth: number;\n focusDistance: number;\n aperture: number;\n focalPx: number;\n maxVariance?: number;\n}): number {\n const depth = Math.max(1e-4, options.depth);\n const focus = Math.max(1e-4, options.focusDistance);\n const apertureAngle = apertureAngleFromSize(options.aperture, focus);\n if (apertureAngle <= 0 || !(options.focalPx > 0)) return 0;\n // Spark's falloff. Unbounded as depth → 0; the px cap below is the guard.\n const focusBlur = Math.abs(depth - focus) / depth;\n const apertureRadius = options.focalPx * Math.tan(0.5 * apertureAngle);\n const cocRadiusPx = focusBlur * apertureRadius;\n const variance = cocRadiusPx * cocRadiusPx;\n const maxVariance = options.maxVariance ?? MAX_DOF_VARIANCE;\n return Math.min(variance, maxVariance);\n}\n\n/**\n * Opacity fade that conserves Gaussian mass after isotropic screen-space\n * dilation: `√(detRaw / detBlur)`.\n */\nexport function computeDofOpacityFade(detRaw: number, detBlur: number): number {\n return Math.sqrt(Math.max(0, detRaw) / Math.max(1e-9, detBlur));\n}\n\n/** Clamps host-supplied DoF settings before writing live uniforms. */\nexport function clampDepthOfFieldSettings(\n settings: Partial<DepthOfFieldSettings>,\n previous: DepthOfFieldSettings = { focusDistance: 10, aperture: 0 },\n): DepthOfFieldSettings {\n const focusDistance =\n typeof settings.focusDistance === 'number' && Number.isFinite(settings.focusDistance)\n ? Math.max(1e-4, settings.focusDistance)\n : previous.focusDistance;\n const aperture =\n typeof settings.aperture === 'number' && Number.isFinite(settings.aperture)\n ? Math.max(0, settings.aperture)\n : previous.aperture;\n return { focusDistance, aperture };\n}\n","/**\n * The TSL material graph behind `SplatMesh`.\n *\n * One graph serves both of the mesh's materials: `display` writes premultiplied\n * color, `pick` encodes linear view depth. They must stay one builder - a pick\n * that disagreed with the display about projection, covariance or visibility\n * would return hits for splats the viewer cannot see.\n *\n * Everything here runs once, at material build time, and reads only what the\n * mesh hands it, so these are free functions rather than methods. Note the two\n * inputs that must be passed live rather than copied: the uniform *node\n * instances* (display and pick share them, so a frame updates both at once) and\n * the channels map (a rebuild after `defineChannel` has to see the new entry).\n *\n * Internal. Nothing here is exported from `index.ts`.\n */\nimport * as THREE from 'three/webgpu';\nimport {\n Discard,\n Fn,\n If,\n attribute,\n colorSpaceToWorking,\n float,\n int,\n ivec2,\n mat3,\n mix,\n modelViewMatrix,\n cameraProjectionMatrix,\n positionGeometry,\n screenUV,\n texture as tslTexture,\n textureLoad,\n uint,\n uniform,\n uniformArray,\n varying,\n vec2,\n vec3,\n vec4,\n} from 'three/tsl';\nimport type { SplatModifier } from './splat-modifier';\nimport { foldSplatModifierStack } from './splat-modifier-stack';\nimport type { SplatPerformanceProfile } from './splat-mesh';\nimport { SPLAT_DATA_TEXTURE_WIDTH } from './splat-mesh-pool';\nimport { MAX_DOF_VARIANCE } from './depth-of-field';\n\n/**\n * Largest on-screen radius a splat quad may reach, in pixels. A splat crossing\n * the near plane projects to an unbounded size; clamping keeps one degenerate\n * splat from covering the screen and stalling the rasterizer.\n */\nconst MAX_SPLAT_RADIUS_PX = 1024;\n\n/**\n * Stand-in for an infinite `parent_size` (a root LOD node, or a splat whose\n * parent has not decoded yet) in the frontier cut: any finite `limit·distance`\n * is below it, so the node always passes the \"parent too big\" half of the test.\n * A large finite value avoids `Infinity` arithmetic in the shader.\n */\nconst FRONTIER_ROOT_SIZE = 1e30;\n\n/** Default variance scale for isotropic point mode: (0.35 × min-axis)². */\nexport const DEFAULT_ISOTROPIC_VARIANCE_SCALE = 0.35 * 0.35;\n/** Default isotropic screen-space sigma radius (px); only used when a modifier opts in. */\nexport const DEFAULT_ISOTROPIC_SCREEN_RADIUS_PX = 1.0;\n\n/**\n * Blends `Σ` toward σ²·I where σ² = λ_min(Σ) · varianceScale. Spark point\n * mode sets every scale axis to min(scale)·0.35; since λ_min(Σ) = min(scale)²,\n * that is exactly σ² = (min·0.35)².\n *\n * λ_min is estimated without `Σ⁻¹` (near-singular on flat Gaussians, which\n * made n·Σ·n blow up into large blobs): take the min of the three axis\n * Rayleigh quotients and the Rayleigh along the longest row-cross of Σ (a\n * stable thin-axis hint when Σ is rank-deficient). The `normal` argument is\n * kept for call-site compatibility and ignored.\n */\nexport function applyIsotropicCovarianceOverride(\n covariance: THREE.Node<'mat3'>,\n _normal: THREE.Node<'vec3'>,\n mixFactor: THREE.Node<'float'>,\n varianceScale: THREE.Node<'float'>,\n): THREE.Node<'mat3'> {\n const e0 = vec3(1, 0, 0);\n const e1 = vec3(0, 1, 0);\n const e2 = vec3(0, 0, 1);\n const row0 = covariance.mul(e0);\n const row1 = covariance.mul(e1);\n const row2 = covariance.mul(e2);\n const r0 = asNode<'float'>(row0.x);\n const r1 = asNode<'float'>(row1.y);\n const r2 = asNode<'float'>(row2.z);\n const crossA = row0.cross(row1);\n const crossB = row1.cross(row2);\n const crossC = row2.cross(row0);\n const lenA = crossA.length();\n const lenB = crossB.length();\n const lenC = crossC.length();\n const useA = lenA.greaterThanEqual(lenB).and(lenA.greaterThanEqual(lenC));\n const useB = lenB.greaterThan(lenA).and(lenB.greaterThanEqual(lenC));\n const axis = useA.select(crossA, useB.select(crossB, crossC));\n const thin = axis.div(axis.length().max(1e-12));\n const rThin = asNode<'float'>(thin.dot(covariance.mul(thin)));\n const minVar = asNode<'float'>(r0.min(r1).min(r2).min(rThin).max(1e-12));\n const isoVar = minVar.mul(varianceScale);\n const isoCov = mat3(vec3(isoVar, 0, 0), vec3(0, isoVar, 0), vec3(0, 0, isoVar));\n return asNode<'mat3'>(covariance.mul(float(1).sub(mixFactor)).add(isoCov.mul(mixFactor)));\n}\n\n/** Sets both screen-space eigenvalues to min(λ1, λ2) for a circular footprint. */\nexport function equalizeProjectedEigenvalues(\n lambda1: THREE.Node<'float'>,\n lambda2: THREE.Node<'float'>,\n mixFactor: THREE.Node<'float'>,\n): { lambda1: THREE.Node<'float'>; lambda2: THREE.Node<'float'> } {\n const circle = lambda1.min(lambda2);\n return {\n lambda1: asNode<'float'>(mix(lambda1, circle, mixFactor)),\n lambda2: asNode<'float'>(mix(lambda2, circle, mixFactor)),\n };\n}\n\n/**\n * Caps isotropic λ to a screen-space sigma radius while blending by mix.\n * `screenRadiusPx ≤ 0` is a no-op (opt-in only; 0 must not shrink λ toward zero).\n */\nexport function capProjectedEigenvaluesToScreenRadius(\n lambda1: THREE.Node<'float'>,\n lambda2: THREE.Node<'float'>,\n mixFactor: THREE.Node<'float'>,\n screenRadiusPx: THREE.Node<'float'>,\n maxStdDev: THREE.Node<'float'>,\n): { lambda1: THREE.Node<'float'>; lambda2: THREE.Node<'float'> } {\n const targetVariance = screenRadiusPx.div(maxStdDev).pow(2);\n const circle = lambda1.min(lambda2).min(targetVariance);\n const capped1 = asNode<'float'>(mix(lambda1, circle, mixFactor));\n const capped2 = asNode<'float'>(mix(lambda2, circle, mixFactor));\n const enabled = screenRadiusPx.greaterThan(0);\n return {\n lambda1: asNode<'float'>(enabled.select(capped1, lambda1)),\n lambda2: asNode<'float'>(enabled.select(capped2, lambda2)),\n };\n}\n\n/** The pool data textures a graph samples per splat. */\nexport interface SplatMaterialTextures {\n centersTexture: THREE.DataTexture;\n colorsTexture: THREE.DataTexture;\n covarianceATexture: THREE.DataTexture;\n covarianceBTexture: THREE.DataTexture;\n}\n\n/** Narrow a TSL expression to the typed {@link THREE.Node} our hook contract\n * expects. Identity at runtime - satisfies TypeScript only. */\nexport function asNode<T extends string>(node: unknown): THREE.Node<T> {\n return node as THREE.Node<T>;\n}\n\n/** A `vec3` uniform, named so its type can be referred to in field decls. */\nexport function vec3Uniform() {\n return uniform(new THREE.Vector3());\n}\nexport type Vec3Uniform = ReturnType<typeof vec3Uniform>;\n\nfunction vec2Uniform() {\n return uniform(new THREE.Vector2());\n}\n/** A `vec2` uniform (focal, viewport). */\nexport type Vec2Uniform = ReturnType<typeof vec2Uniform>;\n\nfunction floatUniform() {\n return uniform(0);\n}\n/** A scalar uniform (pick thresholds and planes). */\nexport type FloatUniform = ReturnType<typeof floatUniform>;\n\n/**\n * How the material reads a splat's higher-order SH coefficients. The two\n * sources differ only in where a coefficient comes from - the band\n * accumulation and view-direction math are shared:\n *\n * - `palette`: SOG/`.lcc2` shN. Coefficients live in a per-file codebook and\n * each splat stores a label; only a static mesh can use it, because two\n * files' palettes cannot be merged into one pool.\n * - `packed`: LCC `Quality`, `.rad`, etc. Each splat carries its own coefficients as packed\n * words in pool-shaped textures, so appended ranges keep their SH.\n */\nexport type SplatShInputs =\n | { mode: 'palette'; bands: number; paletteTexture: THREE.DataTexture }\n | {\n mode: 'packed';\n bands: 1 | 2 | 3;\n textures: readonly THREE.DataTexture[];\n range: { min: Vec3Uniform; max: Vec3Uniform };\n };\n\n/**\n * Per-source placement inputs for a unified pool ({@link SplatScene}): the\n * splat's source id plus the shared array of source matrices. Drives two things\n * at once - the splat's mesh-local position (`M · poolCenter`, applied before\n * the modifier stack) and the frame view-dependent SH is evaluated in.\n */\nexport interface SplatSourcePlacement {\n /** Pool-aligned source id channel. */\n sourceIdTexture: THREE.DataTexture;\n /** Four column vectors per source world matrix. */\n columns: ReturnType<typeof uniformArray>;\n}\n\n/**\n * The `mat4 · (poolCenter, 1)` placed center for source id `s`, plus the linear\n * 3×3 part as a `mat3`, read from the shared column array (three.js\n * `Matrix4.elements` are column-major, so a column maps to\n * `elements[k*4 .. k*4+3]`).\n *\n * Lives here rather than in `source-transform.ts` because the material graph and\n * the sorter must place a splat identically. Keeping it on the material side\n * also avoids a runtime dependency from `source-transform.ts` back into the\n * material graph.\n */\nexport function sourceWorldTransform(\n columns: ReturnType<typeof uniformArray>,\n sourceId: THREE.Node<'int'>,\n localCenter: THREE.Node<'vec3'>,\n): { worldCenter: THREE.Node<'vec3'>; linear: THREE.Node<'mat3'> } {\n const base = sourceId.mul(int(4));\n const c0 = asNode<'vec4'>(columns.element(base));\n const c1 = asNode<'vec4'>(columns.element(base.add(int(1))));\n const c2 = asNode<'vec4'>(columns.element(base.add(int(2))));\n const c3 = asNode<'vec4'>(columns.element(base.add(int(3))));\n const worldCenter = asNode<'vec3'>(\n c0.xyz\n .mul(localCenter.x)\n .add(c1.xyz.mul(localCenter.y))\n .add(c2.xyz.mul(localCenter.z))\n .add(c3.xyz),\n );\n const linear = asNode<'mat3'>(mat3(c0.xyz, c1.xyz, c2.xyz));\n return { worldCenter, linear };\n}\n\n/** Coefficients per channel for a band count (0 → none, 3 → 3rd order). */\nexport function shCoefficientCount(bands: number): number {\n return [0, 3, 8, 15][bands] ?? 0;\n}\n\n/**\n * Builds the per-coefficient accessor for whichever SH source the mesh has -\n * the only part of the SH graph that differs between them.\n *\n * `palette` indirects through the splat's codebook label; `packed` reads the\n * splat's own words straight out of the pool-shaped integer textures and\n * unpacks them (R: bits 0-10, G: 11-20, B: 21-31, each a unit fraction of its\n * field, dequantized across the scene's range). The packed texel loads are\n * hoisted into variables so all 15 coefficients cost at most four fetches.\n */\nexport function shCoefficientReader(\n sh: SplatShInputs,\n textures: { covarianceBTexture: THREE.DataTexture },\n splatTexel: THREE.Node<'ivec2'>,\n): (c: number) => THREE.Node<'vec3'> {\n if (sh.mode === 'palette') {\n const label = textureLoad(textures.covarianceBTexture, splatTexel).z.toInt();\n const column = label.mod(int(64)).mul(int(shCoefficientCount(sh.bands)));\n const row = label.div(int(64));\n return (c) => textureLoad(sh.paletteTexture, ivec2(column.add(int(c)), row)).xyz;\n }\n\n // An RGBA32UI fetch is a uvec4; TSL's types describe textureLoad as vec4,\n // so the element type is asserted rather than inferred. `toVar` hoists each\n // fetch, so all 15 coefficients cost at most four texture reads.\n const groups = sh.textures.map((texture) =>\n asNode<'uvec4'>(textureLoad(texture, splatTexel).toVar()),\n );\n const span = sh.range.max.sub(sh.range.min);\n return (c) => {\n const group = groups[c >> 2] as THREE.Node<'uvec4'>;\n const word = asNode<'uint'>([group.x, group.y, group.z, group.w][c & 3]);\n const channels = vec3(\n word.bitAnd(uint(0x7ff)).toFloat().div(2047),\n word.shiftRight(uint(11)).bitAnd(uint(0x3ff)).toFloat().div(1023),\n word.shiftRight(uint(21)).bitAnd(uint(0x7ff)).toFloat().div(2047),\n );\n return sh.range.min.add(span.mul(channels));\n };\n}\n\n/** Spherical harmonics basis constants for bands 1–3 (3DGS convention). */\nconst SH_C1 = 0.4886025119029199;\nconst SH_C2 = [\n 1.0925484305920792, -1.0925484305920792, 0.31539156525252005, -1.0925484305920792,\n 0.5462742152960396,\n] as const;\nconst SH_C3 = [\n -0.5900435899266435, 2.890611442640554, -0.4570457994644658, 0.3731763325901154,\n -0.4570457994644658, 1.445305721320277, -0.5900435899266435,\n] as const;\n\n/** Evaluates a source's higher-order SH contribution for a local view direction. */\nexport function evaluateSplatSh(\n sh: SplatShInputs,\n textures: { covarianceBTexture: THREE.DataTexture },\n splatTexel: THREE.Node<'ivec2'>,\n direction: THREE.Node<'vec3'>,\n): THREE.Node<'vec3'> {\n const x = direction.x;\n const y = direction.y;\n const z = direction.z;\n const coefficient = shCoefficientReader(sh, textures, splatTexel);\n const band1 = coefficient(0)\n .mul(y.mul(-SH_C1))\n .add(coefficient(1).mul(z.mul(SH_C1)))\n .add(coefficient(2).mul(x.mul(-SH_C1)));\n if (sh.bands === 1) return band1;\n\n const xx = x.mul(x);\n const yy = y.mul(y);\n const zz = z.mul(z);\n const band2 = band1\n .add(coefficient(3).mul(x.mul(y).mul(SH_C2[0])))\n .add(coefficient(4).mul(y.mul(z).mul(SH_C2[1])))\n .add(coefficient(5).mul(zz.mul(2.0).sub(xx).sub(yy).mul(SH_C2[2])))\n .add(coefficient(6).mul(x.mul(z).mul(SH_C2[3])))\n .add(coefficient(7).mul(xx.sub(yy).mul(SH_C2[4])));\n if (sh.bands === 2) return band2;\n\n return band2\n .add(coefficient(8).mul(y.mul(xx.mul(3.0).sub(yy)).mul(SH_C3[0])))\n .add(coefficient(9).mul(x.mul(y).mul(z).mul(SH_C3[1])))\n .add(coefficient(10).mul(y.mul(zz.mul(4.0).sub(xx).sub(yy)).mul(SH_C3[2])))\n .add(coefficient(11).mul(z.mul(zz.mul(2.0).sub(xx.mul(3.0)).sub(yy.mul(3.0))).mul(SH_C3[3])))\n .add(coefficient(12).mul(x.mul(zz.mul(4.0).sub(xx).sub(yy)).mul(SH_C3[4])))\n .add(coefficient(13).mul(z.mul(xx.sub(yy)).mul(SH_C3[5])))\n .add(coefficient(14).mul(x.mul(xx.sub(yy.mul(3.0))).mul(SH_C3[6])));\n}\n\n/** Everything the material graph reads, gathered by the mesh. */\nexport interface SplatMaterialBuildInputs {\n textures: SplatMaterialTextures;\n sh: SplatShInputs | null;\n /**\n * Per-source placement for a unified pool, or `null` for a plain mesh. When\n * present, a splat's mesh-local center is `M · poolCenter` and SH is\n * evaluated in the source's own frame.\n */\n sourcePlacement: SplatSourcePlacement | null;\n /**\n * The mesh's uniform *node instances*, not their values: display and pick\n * share them, so a per-frame write reaches both graphs.\n */\n uniforms: {\n focal: Vec2Uniform;\n viewport: Vec2Uniform;\n localCameraPosition: Vec3Uniform;\n /** Frontier-cut limit on `own_size / distance` (`foveationMode: 'frontier'`). */\n pixelScaleLimit: FloatUniform;\n /**\n * Core projected-2D DoF focus plane (world/view units). Live uniform -\n * racking focus does not rebuild the material. See `depth-of-field.ts`.\n */\n dofFocusDistance: FloatUniform;\n /** Core DoF aperture; `0` disables. Live uniform. */\n dofAperture: FloatUniform;\n /**\n * Screen-radius band bounds in px, live so a foveated mesh can follow its\n * own LOD cut. Whether the band exists at all is still decided at build\n * time from `settings.minScreenRadiusPx` / `maxScreenRadiusPx`; only the\n * bounds move.\n *\n * They have to move: the band spans one LOD level, so a mesh that refines\n * its cut to spend spare budget would have exactly that new detail culled\n * for being smaller than a bound chosen for the coarser cut.\n */\n screenBandMin: FloatUniform;\n screenBandMax: FloatUniform;\n /**\n * Proxy-mesh relight map (RGB = lit, A = coverage). Live texture binding -\n * swap via the TextureNode / rebuild when the mesh updates `setRelighting`.\n * Display fragment only; pick ignores this.\n */\n relightMap: THREE.Texture;\n /** `0` = baked color only; `1` = full modulate. Live uniform. */\n relightBlend: FloatUniform;\n /** Scales lit RGB (PlayCanvas default ~2 for 0.5 gray proxy). Live. */\n relightBrightness: FloatUniform;\n /** Multiplier where coverage alpha is 0 (sky). Live. */\n relightBackground: FloatUniform;\n /**\n * Coverage soft edge in screen pixels. Live; `0` = hard mask.\n * See {@link RelightingSettings.softness}.\n */\n relightSoftness: FloatUniform;\n };\n /** Pick-only uniforms. Required when building in `'pick'` mode. */\n pick: {\n alphaThreshold: FloatUniform;\n near: FloatUniform;\n far: FloatUniform;\n } | null;\n /** Baked at build time; changing any of these needs a rebuild. */\n settings: {\n maxStdDev: number;\n /** Screen-space floor on each quad axis, px (0/undefined = off). */\n minSplatSizePx?: number;\n antialias: boolean;\n /** Classic LCC uses XGRIDS' smaller, always-compensated low-pass. */\n projectedFilterProfile: 'default' | 'lcc';\n srgbOutput: boolean;\n performanceProfile: SplatPerformanceProfile;\n /** Cull splats whose projected radius exceeds this many px (0 = off). */\n maxScreenRadiusPx?: number;\n /** Foveation band lower bound: cull splats *below* this many px (0 = off). */\n minScreenRadiusPx?: number;\n /**\n * `.rad` foveation cut. `'band'` (default/undefined) uses the screen-radius\n * band above; `'frontier'` uses Spark's exact per-splat tree cut driven by\n * `own_size` (from the covariance) and the `parent_size` packed in\n * `covarianceB.w`. See `docs/formats/rad-notes.md` M14.6.\n */\n foveationMode?: 'band' | 'frontier' | 'pagetable';\n /**\n * Cap on a rendered splat's major/minor axis ratio (0/undefined = off). Tames\n * far-field needle/spike artifacts from very anisotropic Gaussians and\n * expansion-enlarged coarse LOD nodes. Baked into the material graph.\n */\n maxAspect?: number;\n /**\n * Spark's LOD alpha encoding (`.rad`): the stored opacity byte is `alpha/2`,\n * so the shader multiplies by 2 to recover `alpha ∈ [0,2]`; `alpha > 1` marks\n * a merged node whose σ-cutoff grows (`+0.7·(remap−1)`) and whose falloff\n * becomes a super-Gaussian, covering its subtree without scaling covariance.\n */\n lodAlpha?: boolean;\n };\n /**\n * The mesh's live channel map, not a copy: `defineChannel` adds to it and\n * then rebuilds, and the graph must resolve names against the new entry.\n */\n channels: ReadonlyMap<string, { texture: THREE.DataTexture }>;\n modifiers: readonly SplatModifier[];\n}\n\n/**\n * Shared TSL graph for display and pick materials: projection, covariance,\n * modifiers, visibility, and Gaussian falloff. Display writes premultiplied\n * color; pick encodes linear view depth into RGB with alpha as the hit flag.\n *\n * @param inputs.pick - Required when `mode` is `'pick'`.\n */\nexport function applySplatMaterialGraph(\n material: THREE.NodeMaterial,\n mode: 'display' | 'pick',\n inputs: SplatMaterialBuildInputs,\n): void {\n const { textures, sh, uniforms, settings, pick } = inputs;\n const textureWidth = int(SPLAT_DATA_TEXTURE_WIDTH);\n const maxRadius = float(MAX_SPLAT_RADIUS_PX);\n // The quad spans ±maxStdDev σ, so |quadPosition| = 1 sits at maxStdDev σ\n // and the Gaussian exponent -½·(maxStdDev·|q|)² folds to this constant.\n // Vertex extent and this exponent must agree or the falloff rescales.\n const gaussianExponent = -0.5 * settings.maxStdDev * settings.maxStdDev;\n\n // Per-instance index -> texel coordinate in the data textures.\n const splatIndex = attribute<'float'>('splatIndex', 'float').toInt();\n const splatTexel = ivec2(splatIndex.mod(textureWidth), splatIndex.div(textureWidth));\n\n // Varyings (computed in the vertex stage, constant across each quad).\n // With SH data, the view-dependent contribution - the higher SH bands\n // evaluated with the local view direction (Kerbl et al. convention,\n // coefficients read from the SOG palette) - is added to the base color.\n const baseColor = textureLoad(textures.colorsTexture, splatTexel);\n /** The splat's center as stored in the pool - its own source's data frame. */\n const poolCenter = textureLoad(textures.centersTexture, splatTexel).xyz;\n // Per-source placement is resolved here, ahead of everything else, so the\n // rest of the graph - modifier stack included - sees the splat where it\n // visually is. In a `SplatScene` the pool frame is an internal storage\n // detail; the splat's real mesh-local position is `M · poolCenter`. Applying\n // it outside the fold (rather than as modifier #0, which is what this used to\n // be) also makes it impossible for a host modifier's `offset`/`rotation` to\n // overwrite the placement - the fold replaces those fields, it does not\n // accumulate them.\n const placement = inputs.sourcePlacement;\n const placed = placement\n ? sourceWorldTransform(\n placement.columns,\n asNode<'int'>(textureLoad(placement.sourceIdTexture, splatTexel).r.toInt()),\n asNode<'vec3'>(poolCenter),\n )\n : null;\n /** Splat center in mesh-local space: the pool texel, or its placed position. */\n const localCenter = placed ? placed.worldCenter : asNode<'vec3'>(poolCenter);\n const shSum =\n sh === null\n ? null\n : (() => {\n const direction = (() => {\n if (!placed) return localCenter.sub(uniforms.localCameraPosition).normalize();\n // SH coefficients stay in their source frame. Transforming the\n // camera ray by the inverse linear placement is equivalent to\n // rotating every l=1..3 coefficient band, without re-uploading\n // per-splat packed coefficients when a source moves.\n return placed.linear\n .inverse()\n .mul(localCenter.sub(uniforms.localCameraPosition))\n .normalize();\n })();\n const x = direction.x;\n const y = direction.y;\n const z = direction.z;\n\n /** Coefficient c of this splat, per color channel. */\n const coefficient = shCoefficientReader(sh, textures, splatTexel);\n\n const band1 = coefficient(0)\n .mul(y.mul(-SH_C1))\n .add(coefficient(1).mul(z.mul(SH_C1)))\n .add(coefficient(2).mul(x.mul(-SH_C1)));\n if (sh.bands === 1) return band1;\n\n const xx = x.mul(x);\n const yy = y.mul(y);\n const zz = z.mul(z);\n const band2 = band1\n .add(coefficient(3).mul(x.mul(y).mul(SH_C2[0])))\n .add(coefficient(4).mul(y.mul(z).mul(SH_C2[1])))\n .add(coefficient(5).mul(zz.mul(2.0).sub(xx).sub(yy).mul(SH_C2[2])))\n .add(coefficient(6).mul(x.mul(z).mul(SH_C2[3])))\n .add(coefficient(7).mul(xx.sub(yy).mul(SH_C2[4])));\n if (sh.bands === 2) return band2;\n\n return (\n band2\n .add(coefficient(8).mul(y.mul(xx.mul(3.0).sub(yy)).mul(SH_C3[0])))\n .add(coefficient(9).mul(x.mul(y).mul(z).mul(SH_C3[1])))\n .add(coefficient(10).mul(y.mul(zz.mul(4.0).sub(xx).sub(yy)).mul(SH_C3[2])))\n .add(\n coefficient(11).mul(\n z.mul(zz.mul(2.0).sub(xx.mul(3.0)).sub(yy.mul(3.0))).mul(SH_C3[3]),\n ),\n )\n .add(coefficient(12).mul(x.mul(zz.mul(4.0).sub(xx).sub(yy)).mul(SH_C3[4])))\n .add(coefficient(13).mul(z.mul(xx.sub(yy)).mul(SH_C3[5])))\n // l=3, m=3 is x(x² − 3y²) - the mirror of m=−3's y(3x² − y²)\n // above. This read x(x² − y²) until 2026-07-17, tinting the\n // band-3 lobes of every shN scene.\n .add(coefficient(14).mul(x.mul(xx.sub(yy.mul(3.0))).mul(SH_C3[6])))\n );\n })();\n const colorAfterSh =\n shSum === null ? baseColor : vec4(baseColor.rgb.add(shSum).clamp(0.0, 1.0), baseColor.a);\n /** Approximate surface normal for lighting hooks: Σ⁻¹ amplifies the\n * least-variance axis (inverse iteration, two applications), oriented\n * toward the camera. Built only when a modifier reads `ctx.normal`. */\n const makeNormal = (): THREE.Node<'vec3'> => {\n const covA = textureLoad(textures.covarianceATexture, splatTexel);\n const covB = textureLoad(textures.covarianceBTexture, splatTexel);\n const poolSigma = mat3(\n vec3(covA.x.add(1e-8), covA.y, covA.z),\n vec3(covA.y, covA.w.add(1e-8), covB.x),\n vec3(covA.z, covB.x, covB.y.add(1e-8)),\n );\n // Run the inverse iteration in the *placed* frame, so a rotated source is\n // shaded consistently with the rest of the scene. Regularizing before the\n // placement (rather than adding εI to A·Σ·Aᵀ) avoids mat3 diagonal surgery\n // in TSL and still yields A·Σ·Aᵀ + ε·A·Aᵀ, positive-definite for any\n // nonsingular A - it degrades only for a source scaled to ~0, which is\n // degenerate everywhere else too.\n //\n // Note the A⁻ᵀ·n \"normal transform\" shortcut does not apply here: that\n // identity is for a level-set gradient, and the least-variance eigenvector\n // of A·Σ·Aᵀ is not A⁻ᵀ times the least-variance eigenvector of Σ.\n const sigma = placed\n ? asNode<'mat3'>(placed.linear.mul(poolSigma).mul(placed.linear.transpose()))\n : asNode<'mat3'>(poolSigma);\n const inverse = sigma.inverse();\n const toCamera = uniforms.localCameraPosition.sub(localCenter);\n const axis = inverse.mul(inverse.mul(toCamera)).normalize();\n return asNode<'vec3'>(axis.mul(axis.dot(toCamera).sign()));\n };\n\n /** Reads a per-splat channel (M7.3). Undefined name is a build error, not\n * a silent zero. `.r` holds the value (normalized for byte channels). */\n const makeChannel = (name: string): THREE.Node<'float'> => {\n const channel = inputs.channels.get(name);\n if (!channel) {\n throw new Error(\n `SplatMesh: a modifier reads channel \"${name}\", which is not defined. ` +\n `Call defineChannel(\"${name}\") before assigning the modifier.`,\n );\n }\n return asNode<'float'>(textureLoad(channel.texture, splatTexel).r);\n };\n\n // Fold the modifier stack (empty stack ⇒ all fragments null and the\n // graph below is emitted exactly as the unhooked renderer).\n const stack = foldSplatModifierStack(inputs.modifiers, uniforms.localCameraPosition, {\n index: asNode<'int'>(splatIndex),\n localCenter,\n sourceCenter: asNode<'vec3'>(poolCenter),\n sourceToLocal: placed?.linear,\n color: asNode<'vec4'>(colorAfterSh),\n makeNormal,\n makeChannel,\n });\n\n // Source formats store display-ready sRGB colors while a regular Three.js\n // scene renders in its linear working space. Keep that conversion local to\n // the splat material so standard meshes can share the renderer without\n // forcing the entire canvas into LinearSRGBColorSpace.\n // With `srgbOutput` the stored sRGB color is emitted as-is: the renderer is\n // expected to skip output conversion, so splats alpha-composite on\n // gamma-encoded values (3DGS training / WebGL-viewer semantics).\n // Pick only needs opacity (a); keep the same color path so modifiers that\n // tint/fade alpha stay consistent with the display pass.\n const splatColor = varying(\n settings.srgbOutput\n ? asNode<'vec4'>(stack.color)\n : asNode<'vec4'>(colorSpaceToWorking(stack.color, THREE.SRGBColorSpace)),\n );\n const quadPosition = varying(positionGeometry.xy);\n const viewDepthVarying = mode === 'pick' ? varying(float(0), 'pickViewDepth') : null;\n // Opacity compensation for screen-space dilation (mip antialias and/or\n // core projected-2D DoF). Always present so DoF can fade opacity when\n // antialias is off; with both disabled the fade stays 1.\n const opacityCompensation = varying(float(1), 'vOpacityCompensation');\n // Spark LOD alpha (`.rad`): per-splat σ-cutoff and the recovered `alpha ∈ [0,2]`,\n // computed in the vertex stage and used by both the quad extent and the\n // fragment falloff. Only present with `lodAlpha`, so other formats are byte-\n // identical.\n const vAdjustedStdDev = settings.lodAlpha\n ? varying(float(settings.maxStdDev), 'vAdjustedStdDev')\n : null;\n const vAlpha2 = settings.lodAlpha ? varying(float(1), 'vAlpha2') : null;\n\n material.vertexNode = Fn(() => {\n const center = stack.offset === null ? localCenter : localCenter.add(stack.offset);\n const viewCenter = modelViewMatrix.mul(vec4(center, 1.0)).toVar();\n const clipCenter = cameraProjectionMatrix.mul(viewCenter).toVar();\n\n // Default: outside clip space, so culled splats emit no fragments.\n const clipPosition = vec4(0.0, 0.0, 2.0, 1.0).toVar();\n\n // Skip splats behind the camera or far outside the frustum - and\n // splats a modifier hides. Display uses a 1.2 NDC pad around the\n // center. Pick crops one source pixel onto the full NDC cube (1 px =\n // 2 NDC), so the same pad would keep only centers inside ~0.6 px of\n // the cursor. Expand by the screen-radius cap so a splat that covers\n // the clicked pixel still emits a quad.\n const frustumNdc = mode === 'pick' ? 1.2 + 2 * MAX_SPLAT_RADIUS_PX : 1.2;\n const margin = clipCenter.w.mul(frustumNdc);\n const inFrustum = clipCenter.z\n .greaterThan(margin.negate())\n .and(clipCenter.x.abs().lessThan(margin))\n .and(clipCenter.y.abs().lessThan(margin));\n const isVisible = stack.visible === null ? inFrustum : inFrustum.and(stack.visible);\n\n If(isVisible, () => {\n if (viewDepthVarying) viewDepthVarying.assign(viewCenter.z.negate());\n\n const covA = textureLoad(textures.covarianceATexture, splatTexel);\n const covB = textureLoad(textures.covarianceBTexture, splatTexel);\n const covarianceBase = mat3(\n vec3(covA.x, covA.y, covA.z),\n vec3(covA.y, covA.w, covB.x),\n vec3(covA.z, covB.x, covB.y),\n );\n // Placement first, outside the fold: Σ_placed = A·Σ·Aᵀ = (A·M)(A·M)ᵀ,\n // exact for any linear A (rotation, non-uniform scale, shear) because Σ\n // is by definition an outer product of a linear map. A maps the source's\n // data frame to mesh-local, so it is innermost; the host stack's rigid\n // rotation is authored in mesh-local and wraps it.\n const placedCovariance = placed\n ? asNode<'mat3'>(placed.linear.mul(covarianceBase).mul(placed.linear.transpose()))\n : asNode<'mat3'>(covarianceBase);\n // Rigid rotation is covariance-exact: Σ' = R·Σ·Rᵀ.\n let covariance3d: THREE.Node<'mat3'> =\n stack.rotation === null\n ? placedCovariance\n : asNode<'mat3'>(stack.rotation.mul(placedCovariance).mul(stack.rotation.transpose()));\n if (stack.isotropicCovarianceMix !== null) {\n const varianceScale =\n stack.isotropicVarianceScale ?? float(DEFAULT_ISOTROPIC_VARIANCE_SCALE);\n covariance3d = applyIsotropicCovarianceOverride(\n covariance3d,\n vec3(0, 0, 1),\n stack.isotropicCovarianceMix,\n varianceScale,\n );\n }\n\n // EWA splatting: project Σ to screen space, Σ' = J·W·Σ·Wᵀ·Jᵀ, with\n // J the Jacobian of the perspective projection. Written as dot\n // products: Σ'ₐᵦ = uₐᵀ·Σ·uᵦ where uₐ = Wᵀ·jₐ and jₐ are J's rows.\n const invZ = float(1.0).div(viewCenter.z);\n const invZ2 = invZ.mul(invZ);\n const j1 = vec3(\n uniforms.focal.x.mul(invZ),\n 0.0,\n uniforms.focal.x.negate().mul(viewCenter.x).mul(invZ2),\n );\n const j2 = vec3(\n 0.0,\n uniforms.focal.y.mul(invZ),\n uniforms.focal.y.negate().mul(viewCenter.y).mul(invZ2),\n );\n const viewRotationT = modelViewMatrix.toMat3().transpose();\n const u1 = viewRotationT.mul(j1);\n const u2 = viewRotationT.mul(j2);\n\n // 2×2 screen covariance [[a, b], [b, d]], low-pass filtered so every\n // splat covers at least about one pixel (3DGS paper). Uniform splat\n // scaling factors out of the quadratic form: Σ' = s²·Σ.\n const aQ = u1.dot(covariance3d.mul(u1));\n const dQ = u2.dot(covariance3d.mul(u2));\n const bQ = u1.dot(covariance3d.mul(u2));\n const aRaw = stack.scaleSquared === null ? aQ : aQ.mul(stack.scaleSquared);\n const dRaw = stack.scaleSquared === null ? dQ : dQ.mul(stack.scaleSquared);\n const bRaw = stack.scaleSquared === null ? bQ : bQ.mul(stack.scaleSquared);\n\n // Core projected-2D DoF (Spark splatVertex.glsl). Aperture maps through\n // the live focus plane, so pulling focus toward the camera widens the\n // angle and softens the whole scene - the authored effect.\n const depth = viewCenter.z.negate().max(1e-4);\n const focus = uniforms.dofFocusDistance.max(1e-4);\n const halfApertureAngle = uniforms.dofAperture.max(0).mul(0.5).div(focus).atan();\n // Spark's falloff (`/depth`). Unbounded near the camera; MAX_DOF_VARIANCE\n // below is the fill-rate guard, exactly as Spark's maxPixelRadius is.\n const focusBlur = depth.sub(focus).abs().div(depth);\n const apertureRadius = uniforms.focal.x.mul(halfApertureAngle.tan());\n const cocRadiusPx = focusBlur.mul(apertureRadius);\n const cocVar = cocRadiusPx.mul(cocRadiusPx).min(float(MAX_DOF_VARIANCE));\n\n // XGRIDS classic LCC uses a 0.1 px² low-pass with integral-preserving\n // opacity compensation. Other formats retain the 3DGS 0.3 px² path.\n const lowPassVariance = settings.projectedFilterProfile === 'lcc' ? 0.1 : 0.3;\n const a = aRaw.add(lowPassVariance).add(cocVar);\n const d = dRaw.add(lowPassVariance).add(cocVar);\n const b = bRaw;\n\n // Mass conservation: √(det Σ / det(Σ + dilation)). Classic LCC always\n // compensates its low-pass; the standard path retains the existing\n // antialias-controlled compatibility behavior. Screen-capped isotropic\n // points skip the fade.\n const detBlur = a.mul(d).sub(b.mul(b)).max(1e-9);\n const detForFade =\n settings.antialias || settings.projectedFilterProfile === 'lcc'\n ? aRaw.mul(dRaw).sub(bRaw.mul(bRaw)).max(0.0)\n : aRaw.add(lowPassVariance).mul(dRaw.add(lowPassVariance)).sub(bRaw.mul(bRaw)).max(1e-9);\n const mipFade = detForFade.div(detBlur).sqrt();\n opacityCompensation.assign(\n stack.isotropicCovarianceMix === null\n ? mipFade\n : mix(mipFade, float(1), stack.isotropicCovarianceMix),\n );\n\n // Eigen-decomposition of the 2×2 covariance gives the ellipse axes.\n // λ is variance in px², so √λ is the standard deviation in pixels;\n // the quad reaches `maxStdDev` σ per axis (3 = the reference 3DGS\n // rasterizer). The epsilon keeps the axis-aligned case finite.\n const mid = a.add(d).mul(0.5);\n const radius = vec2(a.sub(d).mul(0.5), b).length();\n let lambda1 = mid.add(radius);\n let lambda2 = mid.sub(radius).max(0.0);\n if (stack.isotropicCovarianceMix !== null) {\n const equalized = equalizeProjectedEigenvalues(\n lambda1,\n lambda2,\n stack.isotropicCovarianceMix,\n );\n lambda1 = equalized.lambda1;\n lambda2 = equalized.lambda2;\n // Screen-radius cap is opt-in; Spark-style point mode omits it so\n // world-space dots grow mildly on zoom-in instead of locking to 1 px.\n if (stack.isotropicScreenRadiusPx !== null) {\n const capped = capProjectedEigenvaluesToScreenRadius(\n lambda1,\n lambda2,\n stack.isotropicCovarianceMix,\n stack.isotropicScreenRadiusPx,\n float(settings.maxStdDev),\n );\n lambda1 = capped.lambda1;\n lambda2 = capped.lambda2;\n }\n }\n const eigenvector1 = vec2(b, lambda1.sub(a)).add(vec2(1e-6, 0.0)).normalize();\n // Spark's LOD alpha: recover `alpha ∈ [0,2]` (stored ÷2). A merged node\n // (`alpha > 1`) grows the σ-cutoff `maxStdDev + 0.7·(remap−1)` (remap maps\n // 1..2 → 1..5) so it covers its subtree; the covariance is untouched. A leaf\n // keeps the base cutoff. Off (`lodAlpha` false) → the plain constant cutoff.\n let stdDev: THREE.Node<'float'> = float(settings.maxStdDev);\n if (settings.lodAlpha && vAdjustedStdDev && vAlpha2) {\n const alpha2 = asNode<'float'>(stack.color.a.mul(2.0));\n const remap = alpha2.mul(4.0).sub(3.0).min(5.0);\n stdDev = asNode<'float'>(\n alpha2\n .greaterThan(1.0)\n .select(\n float(settings.maxStdDev).add(remap.sub(1.0).mul(0.7)),\n float(settings.maxStdDev),\n ),\n );\n vAdjustedStdDev.assign(stdDev);\n vAlpha2.assign(alpha2);\n }\n\n // Optional aspect-ratio clamp (off by default; Spark does not clamp aspect).\n const majorLambda =\n settings.maxAspect && settings.maxAspect > 0\n ? lambda1.min(lambda2.mul(settings.maxAspect * settings.maxAspect))\n : lambda1;\n // A screen-space *minimum* on each axis, the counterpart to `maxRadius`.\n // A splat that projects below the floor - distant, or the whole scene\n // zoomed out - grows to it so its Gaussian tiles with its neighbours\n // instead of leaving the background visible between them; the falloff\n // normalizes to the quad, so the bigger quad just renders a bigger soft\n // splat. `.max` after `.min` so an already-large splat is untouched (no\n // extra fill where no gap can open), and `minSplat <= maxRadius` keeps the\n // clamp order well-defined. Composes with the isotropic-point screen cap\n // above as long as the floor stays below it.\n const minSplat = float(settings.minSplatSizePx ?? 0);\n const majorAxis = eigenvector1.mul(\n majorLambda.sqrt().mul(stdDev).min(maxRadius).max(minSplat),\n );\n const minorAxis = vec2(eigenvector1.y, eigenvector1.x.negate()).mul(\n lambda2.sqrt().mul(stdDev).min(maxRadius).max(minSplat),\n );\n\n const writePosition = (): void => {\n const pixelOffset = majorAxis\n .mul(positionGeometry.x)\n .add(minorAxis.mul(positionGeometry.y));\n const ndcCenter = clipCenter.xy.div(clipCenter.w);\n clipPosition.assign(\n vec4(\n ndcCenter.add(pixelOffset.mul(2.0).div(uniforms.viewport)),\n clipCenter.z.div(clipCenter.w),\n 1.0,\n ),\n );\n };\n // Per-splat LOD cut. `notBlob` is the \"keep this splat\" predicate (named\n // for the historical blob cull); null means \"no cull, always draw\".\n let notBlob: THREE.Node<'bool'> | null;\n if (settings.foveationMode === 'pagetable') {\n // Spark's selected-index model: the CPU frontier already picked exactly\n // one node per root→leaf ray, and only those splats are paged into the\n // slab. Draw them all - any screen-size band here would re-cull the\n // selection (e.g. a 1.6–4px band reduces the scene to point dust and\n // makes splats *vanish* as the camera approaches and they outgrow it).\n notBlob = null;\n } else if (settings.foveationMode === 'frontier') {\n // Spark's exact tree cut (see `docs/formats/rad-notes.md` M14.6): draw splat i\n // iff its parent is too big on screen but it is small enough -\n // parent_size / distance > limit ≥ own_size / distance\n // - so exactly one node per root→leaf ray survives (full coverage, no\n // band leapfrogging). A leaf has no finer level, so it draws whenever\n // its parent does.\n //\n // `own_size` (world) is recovered from the covariance: Σ = R·S²·Rᵀ, so\n // trace(Σ) = Σ scaleᵢ², and with the merged-node-expanded scales this\n // already carries the expansion - own_size = 2·√(trace/3) ≈\n // 2·expansion·rms(scale), the same measure `parent_size` uses.\n // `parent_size` is packed in `covarianceB.w`, sign-encoding leaf-ness:\n // >0 internal, <0 leaf, |v| = parent size (`FRONTIER_ROOT_SIZE` ≈ ∞ for a\n // root), and 0 (unwritten) is treated as a root so a splat never vanishes.\n const distance = viewCenter.z.negate();\n const trace = covA.x.add(covA.w).add(covB.y);\n const ownSize = trace\n .max(0.0)\n .mul(1 / 3)\n .sqrt()\n .mul(2.0);\n const packedParent = covB.w;\n const isLeaf = packedParent.lessThan(0.0);\n const absParent = packedParent.abs();\n const parentSize = absParent.equal(0.0).select(float(FRONTIER_ROOT_SIZE), absParent);\n const limitDist = uniforms.pixelScaleLimit.mul(distance);\n const ownCut = isLeaf.select(float(0.0), ownSize);\n notBlob = asNode<'bool'>(\n parentSize.greaterThan(limitDist).and(ownCut.lessThanEqual(limitDist)),\n );\n } else {\n // Optional screen-radius cull, using the *unclamped* projected radius\n // (`majorAxis`/`minorAxis` cap at maxRadius, so every big splat would look\n // identically sized). Two uses:\n // - Blob cull (`maxScreenRadiusPx` only): drop splats bigger than the\n // upper bound - a coarse merged `.rad` node near the camera hides,\n // leaving a hole, without touching fine detail.\n // - Foveation band (`minScreenRadiusPx` too): keep only splats whose\n // on-screen radius is in `(min, max]`.\n const maxScreen = settings.maxScreenRadiusPx ?? 0;\n const minScreen = settings.minScreenRadiusPx ?? 0;\n const projRadius = lambda1.sqrt().mul(settings.maxStdDev);\n let inBand: THREE.Node<'bool'> | null = null;\n // The bounds are uniforms, not constants: a foveated mesh moves them\n // with its LOD cut (see `uniforms.screenBandMin`). The build-time\n // numbers only decide *whether* each side of the band exists.\n if (maxScreen > 0)\n inBand = asNode<'bool'>(projRadius.lessThanEqual(uniforms.screenBandMax));\n if (minScreen > 0) {\n const above = projRadius.greaterThan(uniforms.screenBandMin);\n inBand = asNode<'bool'>(inBand ? inBand.and(above) : above);\n }\n notBlob = inBand;\n }\n\n if (settings.performanceProfile === 'smooth') {\n // PlayCanvas-compatible contribution rejection. The library's\n // default quality profile bypasses this branch entirely.\n // Screen-capped isotropic points (~1 px) fail opacity·major·minor ≥ 3\n // once the camera approaches - skip the cull while mix > 0 so point\n // mode does not vanish on zoom-in (hosts typically default to `smooth`).\n const majorRadius = majorAxis.length();\n const minorRadius = minorAxis.length();\n const opacity = stack.color.a;\n const contributionOk = opacity\n .greaterThanEqual(1 / 255)\n .and(majorRadius.max(minorRadius).mul(2).greaterThanEqual(2))\n .and(opacity.mul(majorRadius).mul(minorRadius).greaterThanEqual(3));\n const passes =\n stack.isotropicCovarianceMix === null\n ? contributionOk\n : asNode<'bool'>(contributionOk.or(stack.isotropicCovarianceMix.greaterThan(0)));\n If(notBlob ? passes.and(notBlob) : passes, writePosition);\n } else if (notBlob) {\n If(notBlob, writePosition);\n } else {\n writePosition();\n }\n });\n\n return clipPosition;\n })();\n\n if (mode === 'display') {\n material.fragmentNode = Fn(() => {\n const squaredDistance = quadPosition.dot(quadPosition);\n Discard(squaredDistance.greaterThan(1.0));\n let opacity: THREE.Node<'float'>;\n if (settings.lodAlpha && vAdjustedStdDev && vAlpha2) {\n // Spark's LOD falloff. `g = exp(-½·adjustedStdDev²·|q|²)` is the Gaussian\n // at this fragment. A leaf composites `g · alpha`. A merged node\n // (`alpha > 1`) uses a super-Gaussian plateau `1 − (1 − g)^a`,\n // `a = exp((remap² − 1)/e)`, so a single coarse splat fills the footprint\n // of the subtree it stands in for - no covariance inflation.\n const g = squaredDistance.mul(vAdjustedStdDev.mul(vAdjustedStdDev).mul(-0.5)).exp();\n const remap = vAlpha2.mul(4.0).sub(3.0).min(5.0);\n const aExp = remap\n .mul(remap)\n .sub(1.0)\n .mul(1 / Math.E)\n .exp();\n const merged = g.oneMinus().pow(aExp).oneMinus();\n opacity = asNode<'float'>(vAlpha2.greaterThan(1.0).select(merged, g.mul(vAlpha2)));\n } else {\n // True Gaussian falloff. |quadPosition| = 1 is `maxStdDev` σ from center.\n opacity = asNode<'float'>(squaredDistance.mul(gaussianExponent).exp().mul(splatColor.a));\n }\n const alpha = opacity.mul(opacityCompensation);\n // PlayCanvas-style screen-space relight: sample the host's lit proxy RT\n // at this fragment, then multiply baked RGB. `blend === 0` leaves color\n // unchanged (placeholder map is fine). Pick path does not use this.\n // Softness > 0 box-filters the map. Average RGB **weighted by alpha** so\n // uncovered clear pixels (A=0) do not pull coverage edges toward black —\n // that used to draw a dark outline of every collision triangle.\n const litCenter = tslTexture(uniforms.relightMap, screenUV);\n const ox = uniforms.relightSoftness.div(uniforms.viewport.x.max(1));\n const oy = uniforms.relightSoftness.div(uniforms.viewport.y.max(1));\n const s0 = litCenter;\n const s1 = tslTexture(uniforms.relightMap, screenUV.add(vec2(ox, 0)));\n const s2 = tslTexture(uniforms.relightMap, screenUV.add(vec2(ox.negate(), 0)));\n const s3 = tslTexture(uniforms.relightMap, screenUV.add(vec2(0, oy)));\n const s4 = tslTexture(uniforms.relightMap, screenUV.add(vec2(0, oy.negate())));\n const wSum = s0.a.add(s1.a).add(s2.a).add(s3.a).add(s4.a).max(1e-4);\n const rgbSoft = s0.rgb\n .mul(s0.a)\n .add(s1.rgb.mul(s1.a))\n .add(s2.rgb.mul(s2.a))\n .add(s3.rgb.mul(s3.a))\n .add(s4.rgb.mul(s4.a))\n .div(wSum);\n const aSoft = wSum.mul(0.2);\n const litSoft = vec4(rgbSoft, aSoft);\n const lit = uniforms.relightSoftness.greaterThan(0.5).select(litSoft, litCenter);\n const factor = mix(\n vec3(uniforms.relightBackground),\n lit.rgb.mul(uniforms.relightBrightness),\n lit.a,\n );\n const rgb = mix(splatColor.rgb, splatColor.rgb.mul(factor), uniforms.relightBlend);\n return vec4(rgb.mul(alpha), alpha); // premultiplied alpha\n })();\n\n // Premultiplied \"over\" compositing; splats are sorted back-to-front.\n material.transparent = true;\n material.depthTest = true;\n material.depthWrite = false;\n material.side = THREE.DoubleSide;\n material.blending = THREE.CustomBlending;\n material.blendSrc = THREE.OneFactor;\n material.blendDst = THREE.OneMinusSrcAlphaFactor;\n material.blendSrcAlpha = THREE.OneFactor;\n material.blendDstAlpha = THREE.OneMinusSrcAlphaFactor;\n // Splat colors are already authored for display; scene exposure should not\n // re-grade them, but the renderer still performs the final sRGB encoding.\n material.toneMapped = false;\n } else {\n if (!pick) {\n throw new Error(\"applySplatMaterialGraph in 'pick' mode requires pick uniforms.\");\n }\n material.fragmentNode = Fn(() => {\n const squaredDistance = quadPosition.dot(quadPosition);\n Discard(squaredDistance.greaterThan(1.0));\n const gaussian = squaredDistance.mul(gaussianExponent).exp().mul(splatColor.a);\n const alpha = gaussian.mul(opacityCompensation);\n Discard(alpha.lessThan(pick.alphaThreshold));\n\n // 24-bit normalized linear view depth → RGB; alpha marks a hit.\n const normalized = viewDepthVarying!\n .sub(pick.near)\n .div(pick.far.sub(pick.near))\n .clamp(0.0, 1.0);\n const depth = normalized.mul(16777215.0);\n const r = depth.div(65536.0).floor();\n const g = depth.mod(65536.0).div(256.0).floor();\n const b = depth.mod(256.0).floor();\n return vec4(r.div(255.0), g.div(255.0), b.div(255.0), 1.0);\n })();\n\n material.transparent = false;\n material.depthTest = true;\n material.depthWrite = true;\n material.side = THREE.DoubleSide;\n material.blending = THREE.NoBlending;\n material.toneMapped = false;\n }\n}\n\nexport { foldSplatModifierStack };\n","import type * as THREE from 'three/webgpu';\n\n/**\n * The greatest view-space depth displacement of a local bounding sphere.\n *\n * View depth is the dot product of the local point and row 2 of `modelView`.\n * A sphere with radius `r` therefore spans exactly `r · ||row2.xyz||` either\n * side of its transformed center. Unlike a maximum-axis-scale estimate, this\n * remains correct when a non-uniformly scaled ancestor and a rotated child\n * compose into a shear.\n */\nexport function viewDepthRadius(modelView: THREE.Matrix4, radius: number): number {\n const m = modelView.elements;\n return radius * Math.hypot(m[2], m[6], m[10]);\n}\n","/**\n * Frees the JS-heap mirror of storage buffers only the GPU ever reads.\n *\n * ## The gap\n *\n * Every `THREE.StorageBufferAttribute` is constructed around a JS typed array,\n * and three's WebGPU backend never releases it. `WebGPUAttributeUtils`'s\n * `createAttribute` copies the array into a `mappedAtCreation` GPU buffer,\n * unmaps it, stores the buffer - and never calls `bufferAttribute\n * .onUploadCallback()`, the hook that exists for exactly this and that the\n * legacy WebGL renderer does call (`WebGLAttributes.js`). So a buffer of N\n * slots costs N bytes on the GPU *and the same again on the JS heap,\n * permanently*, even when nothing ever reads the CPU copy back.\n *\n * For VLAM that is not a rounding error. The unified work buffer alone is\n * 72 B/slot, gathered entirely on the GPU and never read back - at a 10M-slot\n * scene that is ~720 MB of JS heap holding zeroes, next to the ~720 MB of GPU\n * memory doing the actual work. Add the sort scratch and it is most of a\n * gigabyte that no on-screen number accounts for.\n *\n * ## Why replacing `.array` is safe\n *\n * Four properties of three 0.185 hold simultaneously, and this module is only\n * correct while they all do:\n *\n * 1. `createAttribute` is guarded by `if (buffer === undefined)` - once the GPU\n * buffer exists, every later call short-circuits without reading `.array`.\n * 2. `updateAttribute` *does* re-read `.array`, but `Attributes.update` only\n * reaches it when `data.version < attribute.version` or the attribute uses\n * `DynamicDrawUsage`. VLAM sets neither on the buffers released here - which\n * is what {@link StorageMirrorReleaser}'s drift guard verifies at runtime.\n * 3. The vertex-layout paths read `.array.constructor` and\n * `.array.BYTES_PER_ELEMENT`, never the contents - and only for *geometry*\n * attributes, which these are not. Both survive a zero-length array of the\n * same constructor.\n * 4. Device loss is terminal: `Renderer._onDeviceLost` sets a flag and every\n * later render/compute returns early. There is no re-upload-from-`.array`\n * path to break.\n *\n * A zero-length array rather than a detached buffer, deliberately: if a future\n * change ever does bump `version` on a released attribute, a zero-length array\n * degrades to a legal 0-byte `writeBuffer` (silent, and caught by the drift\n * guard) where a detached one would throw inside the render loop.\n *\n * Peak memory is unchanged - the array has to exist for the upload copy. Only\n * steady state improves, which is the number that was hurting.\n *\n * ## Relationship to `releaseRendererAttributes`\n *\n * They are complements, and compose in either order.\n * `releaseRendererAttributes` frees the **GPU** buffer at dispose; this frees\n * the **CPU** mirror during the first frame. If dispose somehow ran first, the\n * upload probe simply reports \"not uploaded\" and the mirror is never released -\n * fail-safe.\n *\n * Upstream fix: a one-line `bufferAttribute.onUploadCallback()` in\n * `WebGPUAttributeUtils.createAttribute` would make the documented\n * `BufferAttribute.onUpload()` contract work on WebGPU, and this module would\n * become a shim for older three.\n */\nimport type * as THREE from 'three/webgpu';\n\nimport { warn } from './logging';\n\n/** The internal shape this module probes for; absent on WebGL2 and in test doubles. */\ninterface AttributeBackend {\n isWebGPUBackend?: boolean;\n has?(object: object): boolean;\n get?(object: object): { buffer?: unknown };\n}\n\n/**\n * Whether three has already created this attribute's GPU buffer.\n *\n * `has` before `get` is not optional: the backend's `DataMap.get` *creates* an\n * empty record on a miss, so probing with `get` alone would both pollute the\n * map and always report \"uploaded\".\n */\nfunction hasUploaded(renderer: THREE.WebGPURenderer, attribute: THREE.BufferAttribute): boolean {\n const backend = (renderer as unknown as { backend?: AttributeBackend }).backend;\n if (!backend || backend.isWebGPUBackend !== true) return false;\n if (typeof backend.has !== 'function' || typeof backend.get !== 'function') return false;\n if (!backend.has(attribute)) return false;\n return backend.get(attribute).buffer !== undefined;\n}\n\n/** Swaps in a zero-length array of the same type, keeping `count` intact. */\nfunction dropMirror(attribute: THREE.BufferAttribute): number {\n const array = attribute.array;\n const bytes = array.byteLength;\n if (bytes === 0) return 0;\n const Ctor = array.constructor as new (length: number) => typeof array;\n attribute.array = new Ctor(0);\n return bytes;\n}\n\n/**\n * Drops the JS mirrors of storage attributes three has already uploaded.\n *\n * Owners construct one over the attributes they own **outright** and call\n * {@link release} after each dispatch; it settles on the first frame and costs\n * one WeakMap probe per pending attribute until then. Attributes the owner only\n * borrows (a mesh's `splatIndex`/`sourceIndex` passed into a sorter) must never\n * be handed to it - the same ownership line `releaseRendererAttributes` draws.\n *\n * Only pass attributes that are written from the CPU *at most once*, at\n * construction. Anything the CPU rewrites per frame will silently stop\n * uploading; the drift guard turns that into a warning rather than a mystery.\n */\nexport class StorageMirrorReleaser {\n private readonly pending: THREE.BufferAttribute[];\n /** Released attributes and the `version` they carried when released. */\n private readonly released = new Map<THREE.BufferAttribute, number>();\n private releasedBytesValue = 0;\n private warnedDrift = false;\n\n constructor(attributes: readonly THREE.BufferAttribute[]) {\n this.pending = [...attributes];\n }\n\n /** Nothing left to release. */\n get settled(): boolean {\n return this.pending.length === 0;\n }\n\n /** Bytes still mirrored on the JS heap. */\n get pendingBytes(): number {\n let bytes = 0;\n for (const attribute of this.pending) bytes += attribute.array.byteLength;\n return bytes;\n }\n\n /** Bytes freed so far. Diagnostic. */\n get releasedBytes(): number {\n return this.releasedBytesValue;\n }\n\n /**\n * Releases every pending mirror three has uploaded, and checks the ones\n * already released for version drift. Idempotent and cheap once settled.\n *\n * @returns Bytes freed by this call.\n */\n release(renderer: THREE.WebGPURenderer): number {\n this.checkDrift();\n if (this.pending.length === 0) return 0;\n let freed = 0;\n // Backwards so a splice cannot skip the next entry.\n for (let i = this.pending.length - 1; i >= 0; i--) {\n const attribute = this.pending[i] as THREE.BufferAttribute;\n if (!hasUploaded(renderer, attribute)) continue;\n freed += dropMirror(attribute);\n this.released.set(attribute, attribute.version);\n this.pending.splice(i, 1);\n }\n this.releasedBytesValue += freed;\n return freed;\n }\n\n /**\n * A released attribute whose `version` moved means someone added a CPU write\n * to a buffer this class promised was GPU-only. Three would then upload the\n * zero-length array - a legal no-op - and the data would simply never reach\n * the GPU. Warn once, so that failure is visible rather than a silent wrong\n * render.\n */\n private checkDrift(): void {\n if (this.warnedDrift || this.released.size === 0) return;\n for (const [attribute, version] of this.released) {\n if (attribute.version === version) continue;\n this.warnedDrift = true;\n warn(\n `StorageMirrorReleaser: storage attribute \"${attribute.name || 'unnamed'}\" was written ` +\n `from the CPU after its mirror was released; that write will not reach the GPU. ` +\n `Remove it from the releaser's attribute list.`,\n );\n return;\n }\n }\n}\n","import * as THREE from 'three/webgpu';\nimport {\n Fn,\n If,\n Loop,\n atomicAdd,\n atomicLoad,\n atomicStore,\n float,\n instanceIndex,\n int,\n ivec2,\n storage,\n textureLoad,\n uint,\n uniform,\n} from 'three/tsl';\nimport type { SplatSorter } from './sorter';\nimport { sourceWorldTransform } from './splat-mesh-material';\nimport { viewDepthRadius } from './splat-sort-bounds';\nimport { StorageMirrorReleaser } from './storage-attribute-mirror';\nimport type { uniformArray } from 'three/tsl';\n\n/**\n * Optional per-source world transform for a unified {@link SplatScene} pool.\n * When present, each splat's center is transformed to world space by its\n * source's matrix (selected by a per-splat id read from `sourceIdTexture`)\n * before its depth is computed, so clouds with different world transforms sort\n * against each other correctly. Absent for an ordinary single-transform mesh.\n */\nexport interface PerSourceSortTransform {\n /** RedFormat float texture holding each pool slot's source id (the channel). */\n sourceIdTexture: THREE.DataTexture;\n /** Shared array of source-matrix columns; see `SourceMatrixArray`. */\n columns: ReturnType<typeof uniformArray>;\n /** CPU mirror of the source-id channel, used by the WebGL2 worker sorter. */\n sourceIds: Float32Array;\n /** Column-major source matrices, used by the WebGL2 worker sorter. */\n matrices: Float32Array;\n}\n\n/**\n * GPU depth sorter: a single-pass counting sort over 2²² (~4M) depth\n * buckets, running entirely in TSL compute passes on the WebGPU backend.\n *\n * 1. clear - zero the histogram\n * 2. histogram - depth per active splat → bucket id; count per bucket\n * 3. scanBlocks - exclusive prefix sum within each 256-bucket block\n * 4. scanBlockSums - parallel exclusive scans over groups of block totals\n * 5. scanSuperSums - exclusive scan over the remaining super-block totals\n * 6. addBlockOffsets - combine both block-sum scan levels\n * 7. addOffsets - combine block and bucket offsets\n * 8. scatter - order[offset[bucket]++] = pool index (atomic)\n *\n * Why ~4M buckets, not 64K: on a large scene, 16-bit buckets made a\n * near-camera bucket span thousands of overlapping grass/leaf splats whose\n * arbitrary intra-bucket order reshuffled as the camera moved - visible\n * popping. Each depth bucket is now a sub-splat-width slice, so splats that\n * share a bucket are effectively coplanar and their (still arbitrary) order\n * cannot be seen. (A multi-pass radix would give exact order but needs a\n * *stable* scatter, which the parallel `atomicAdd` scatter is not - hence a\n * single wide pass instead.)\n *\n * Dynamic capacity: the sorter walks the mesh's `sourceIndex` buffer (pool\n * indices of the active splats) and sorts only the first `activeCount`\n * entries, so ranges can be appended and removed without rebuilding\n * pipelines - the per-splat passes are dispatched with a dynamic invocation\n * count and guarded by an `activeCount` uniform.\n *\n * Portable by construction: cross-workgroup communication only happens\n * through the implicit synchronization WebGPU guarantees between dispatches\n * - no spin-waits or scheduling assumptions.\n *\n * The depth range for bucket quantization comes from the mesh's bounding\n * sphere transformed to view space, so no GPU min/max reduction is needed.\n */\nexport class ComputeSorter implements SplatSorter {\n readonly kind = 'counting' as const;\n private static readonly BUCKET_COUNT = 1 << 22;\n private static readonly BLOCK_SIZE = 256;\n\n /**\n * Smallest bucket count a sort will dispatch. Below this the fixed passes\n * are already cheap, and the floor keeps every block-scan index exact.\n */\n private static readonly MIN_BUCKET_COUNT = 1 << 16;\n\n private readonly renderer: THREE.WebGPURenderer;\n private readonly clearPass: THREE.ComputeNode;\n private readonly histogramPass: THREE.ComputeNode;\n private readonly scanBlocksPass: THREE.ComputeNode;\n private readonly scanBlockSumsPass: THREE.ComputeNode;\n private readonly addBlockSumOffsetsPass: THREE.ComputeNode;\n private readonly addOffsetsPass: THREE.ComputeNode;\n private readonly scatterPass: THREE.ComputeNode;\n /** All stages in dependency order, submitted as one WebGPU compute pass. */\n private readonly sortPasses: THREE.ComputeNode[];\n /** Sorter-owned working buffers, released on {@link dispose}. */\n private readonly workingAttributes: THREE.StorageBufferAttribute[];\n /** Frees the JS mirrors three keeps behind the GPU-only working buffers. */\n private readonly mirrors: StorageMirrorReleaser;\n /** Set by {@link dispose}; makes a second dispose a no-op. */\n private disposed = false;\n\n /** Row 2 of the model-view matrix; view-space z = row2 · (position, 1). */\n private readonly viewRow2 = uniform(new THREE.Vector4());\n private readonly depthMin = uniform(0);\n private readonly depthScale = uniform(0);\n /** Active splat count; a float compares exactly for counts < 2²⁴. */\n private readonly activeCount = uniform(0);\n /** Highest bucket index this sort uses; see {@link effectiveBucketCount}. */\n private readonly bucketMax = uniform(0);\n\n private readonly viewCenter = new THREE.Vector3();\n\n constructor(options: {\n renderer: THREE.WebGPURenderer;\n /** Pool capacity in splats; all buffers are sized once from this. */\n capacity: number;\n /** RGBA32F texture holding splat centers (xyz), pool-indexed. */\n centersTexture?: THREE.DataTexture;\n /** Width of the centers texture, to map pool index → texel. */\n dataTextureWidth?: number;\n /** Work-buffer centers (xyzw per splat), as written by a gather pass. */\n centersBuffer?: THREE.StorageBufferAttribute;\n /** The float `splatIndex` buffer the render material reads. */\n splatIndexAttribute: THREE.StorageInstancedBufferAttribute;\n /** Pool indices of the active splats (first `activeCount` entries). */\n sourceIndexAttribute: THREE.StorageBufferAttribute;\n /** Per-source world transform for a unified pool; omit for a single mesh. */\n perSource?: PerSourceSortTransform;\n }) {\n const { renderer, capacity, centersTexture, dataTextureWidth, centersBuffer, perSource } =\n options;\n const { BUCKET_COUNT, BLOCK_SIZE } = ComputeSorter;\n if (!centersTexture && !centersBuffer) {\n throw new Error('ComputeSorter: provide centersTexture or centersBuffer.');\n }\n if (centersTexture && dataTextureWidth === undefined) {\n throw new Error('ComputeSorter: dataTextureWidth is required with centersTexture.');\n }\n if (centersBuffer && perSource) {\n throw new Error('ComputeSorter: work-buffer centers are already in world space.');\n }\n\n this.renderer = renderer;\n\n // GPU-only working buffers. The histogram is accessed atomically in\n // every pass so all pipelines see one consistent buffer declaration;\n // after `addOffsets` it holds the global write offsets that `scatter`\n // consumes (and destroys - it is rebuilt on every sort).\n const histogramAttribute = new THREE.StorageBufferAttribute(new Uint32Array(BUCKET_COUNT), 1);\n const blockSumsAttribute = new THREE.StorageBufferAttribute(\n new Uint32Array(BUCKET_COUNT / BLOCK_SIZE),\n 1,\n );\n const superBlockSumsAttribute = new THREE.StorageBufferAttribute(\n new Uint32Array(BUCKET_COUNT / BLOCK_SIZE / BLOCK_SIZE),\n 1,\n );\n const bucketsAttribute = new THREE.StorageBufferAttribute(new Uint32Array(capacity), 1);\n this.workingAttributes = [\n histogramAttribute,\n blockSumsAttribute,\n superBlockSumsAttribute,\n bucketsAttribute,\n ];\n this.mirrors = new StorageMirrorReleaser(this.workingAttributes);\n const histogram = storage(histogramAttribute, 'uint', BUCKET_COUNT).toAtomic();\n const blockSums = storage(blockSumsAttribute, 'uint', BUCKET_COUNT / BLOCK_SIZE);\n const superBlockSums = storage(\n superBlockSumsAttribute,\n 'uint',\n BUCKET_COUNT / BLOCK_SIZE / BLOCK_SIZE,\n );\n const buckets = storage(bucketsAttribute, 'uint', capacity);\n const sourceIndex = storage(options.sourceIndexAttribute, 'uint', capacity);\n const order = storage(options.splatIndexAttribute, 'float', capacity);\n const workCenters = centersBuffer ? storage(centersBuffer, 'vec4', capacity) : null;\n\n this.clearPass = Fn(() => {\n atomicStore(histogram.element(instanceIndex), uint(0));\n })().compute(BUCKET_COUNT, [BLOCK_SIZE]);\n\n this.histogramPass = Fn(() => {\n If(float(instanceIndex).lessThan(this.activeCount), () => {\n const poolIndex = int(sourceIndex.element(instanceIndex));\n const texel = centersTexture\n ? ivec2(\n poolIndex.mod(int(dataTextureWidth as number)),\n poolIndex.div(int(dataTextureWidth as number)),\n )\n : null;\n const center = workCenters\n ? workCenters.element(poolIndex).xyz\n : textureLoad(centersTexture, texel as THREE.Node<'ivec2'>).xyz;\n\n // In a unified pool each splat lives in its source's local frame; move\n // it to world space by that source's matrix before measuring depth, so\n // clouds with different transforms share one back-to-front order. The\n // branch is resolved at build time, so a single-transform mesh compiles\n // exactly the original local-center path.\n const depthPoint = perSource\n ? sourceWorldTransform(\n perSource.columns,\n int(textureLoad(perSource.sourceIdTexture, texel as THREE.Node<'ivec2'>).r),\n center,\n ).worldCenter\n : center;\n const depth = this.viewRow2.xyz.dot(depthPoint).add(this.viewRow2.w);\n const bucket = depth\n .sub(this.depthMin)\n .mul(this.depthScale)\n .clamp(0, this.bucketMax)\n .toUint();\n\n buckets.element(instanceIndex).assign(bucket);\n atomicAdd(histogram.element(bucket), uint(1));\n });\n })().compute(capacity, [BLOCK_SIZE]);\n\n // One thread serially scans one 256-bucket block: threads never share\n // data within a dispatch, so no barriers are needed anywhere.\n this.scanBlocksPass = Fn(() => {\n const base = instanceIndex.mul(uint(BLOCK_SIZE)).toVar();\n const runningTotal = uint(0).toVar();\n Loop(BLOCK_SIZE, ({ i }) => {\n const slot = histogram.element(base.add(uint(i)));\n const bucketCount = atomicLoad(slot).toVar();\n atomicStore(slot, runningTotal);\n runningTotal.addAssign(bucketCount);\n });\n blockSums.element(instanceIndex).assign(runningTotal);\n })().compute(BUCKET_COUNT / BLOCK_SIZE, [64]);\n\n // Scan 256 block totals per invocation, then scan the resulting 64\n // super-block totals. The old single invocation walked all 16,384 totals\n // serially; this keeps every dependency chain at 256 additions or fewer.\n this.scanBlockSumsPass = Fn(() => {\n const base = instanceIndex.mul(uint(BLOCK_SIZE)).toVar();\n const runningTotal = uint(0).toVar();\n Loop(BLOCK_SIZE, ({ i }) => {\n const slot = blockSums.element(base.add(uint(i)));\n const blockTotal = slot.toVar();\n slot.assign(runningTotal);\n runningTotal.addAssign(blockTotal);\n });\n superBlockSums.element(instanceIndex).assign(runningTotal);\n })().compute(BUCKET_COUNT / BLOCK_SIZE / BLOCK_SIZE, [64]);\n\n const scanSuperBlockSums = Fn(() => {\n If(instanceIndex.equal(uint(0)), () => {\n const runningTotal = uint(0).toVar();\n Loop(BUCKET_COUNT / BLOCK_SIZE / BLOCK_SIZE, ({ i }) => {\n const superBlockTotal = superBlockSums.element(i).toVar();\n superBlockSums.element(i).assign(runningTotal);\n runningTotal.addAssign(superBlockTotal);\n });\n });\n })().compute(1, [64]);\n\n this.addBlockSumOffsetsPass = Fn(() => {\n blockSums\n .element(instanceIndex)\n .addAssign(superBlockSums.element(instanceIndex.div(uint(BLOCK_SIZE))));\n })().compute(BUCKET_COUNT / BLOCK_SIZE, [BLOCK_SIZE]);\n\n this.addOffsetsPass = Fn(() => {\n const slot = histogram.element(instanceIndex);\n const offset = blockSums.element(instanceIndex.shiftRight(uint(Math.log2(BLOCK_SIZE))));\n atomicStore(slot, atomicLoad(slot).add(offset));\n })().compute(BUCKET_COUNT, [BLOCK_SIZE]);\n\n // Ascending view-space z puts the most negative (farthest) splats\n // first: back-to-front, matching the CPU sorter.\n this.scatterPass = Fn(() => {\n If(float(instanceIndex).lessThan(this.activeCount), () => {\n const poolIndex = sourceIndex.element(instanceIndex);\n const bucket = buckets.element(instanceIndex);\n const destination = atomicAdd(histogram.element(bucket), uint(1));\n order.element(destination).assign(float(poolIndex));\n });\n })().compute(capacity, [BLOCK_SIZE]);\n\n this.sortPasses = [\n this.clearPass,\n this.histogramPass,\n this.scanBlocksPass,\n this.scanBlockSumsPass,\n scanSuperBlockSums,\n this.addBlockSumOffsetsPass,\n this.addOffsetsPass,\n this.scatterPass,\n ];\n }\n\n /**\n * Buckets to actually use for `activeCount` splats, rounded up to a power of\n * two so block indexing stays exact.\n *\n * The histogram is allocated once for the pool's worst case, but clearing and\n * scanning all 2²² buckets costs the same whether one splat is resident or\n * four million - a fixed per-sort bill that dominates on a mobile GPU when\n * the pool is only partly filled (a streaming scene ramping up, or any small\n * scene). Sizing the dispatch to the live splat count keeps the design's\n * ~1-bucket-per-splat depth resolution, so ordering is unaffected, while\n * skipping work on buckets no splat can land in.\n */\n private effectiveBucketCount(activeCount: number): number {\n const exponent = Math.ceil(Math.log2(Math.max(1, activeCount)));\n const rounded = 2 ** exponent;\n return Math.min(Math.max(rounded, ComputeSorter.MIN_BUCKET_COUNT), ComputeSorter.BUCKET_COUNT);\n }\n\n sort(modelView: THREE.Matrix4, activeCount: number, bounds: THREE.Sphere): boolean {\n if (activeCount === 0) return true;\n\n const m = modelView.elements;\n this.viewRow2.value.set(m[2], m[6], m[10], m[14]);\n this.activeCount.value = activeCount;\n\n const buckets = this.effectiveBucketCount(activeCount);\n this.viewCenter.copy(bounds.center).applyMatrix4(modelView);\n // `bounds` is in mesh-local space, but the keys are view-space z. Its\n // exact z extent is the radius times the norm of modelView's depth row;\n // that remains correct for any linear transform, including hierarchy-\n // induced shear from a rotated child under a non-uniformly scaled parent.\n const viewRadius = viewDepthRadius(modelView, bounds.radius);\n const near = this.viewCenter.z - viewRadius;\n const far = this.viewCenter.z + viewRadius;\n this.depthMin.value = near;\n this.depthScale.value = (buckets - 1) / (far - near || 1);\n this.bucketMax.value = buckets - 1;\n\n // Keep every stage limited to the buckets and splats in play while batching\n // all dependent stages into one command encoder, compute pass, and submit.\n const blockCount = buckets / ComputeSorter.BLOCK_SIZE;\n this.histogramPass.count = activeCount;\n this.scatterPass.count = activeCount;\n this.clearPass.count = buckets;\n this.scanBlocksPass.count = blockCount;\n this.scanBlockSumsPass.count = blockCount / ComputeSorter.BLOCK_SIZE;\n this.addBlockSumOffsetsPass.count = blockCount;\n this.addOffsetsPass.count = buckets;\n this.renderer.compute(this.sortPasses);\n // The working buffers are GPU-only: the histogram is zeroed by `clearPass`\n // and read atomically on the GPU, and `buckets` never leaves it. Three keeps\n // a JS mirror of each anyway - 16 MiB for the histogram alone, plus 4 B per\n // splat of capacity - so drop it once the dispatch has uploaded them.\n // Deliberately *not* `sourceIndex`/`splatIndex`: those belong to the mesh\n // and are rewritten from the CPU every frame. Same ownership line as\n // `releaseRendererAttributes` draws below.\n if (!this.mirrors.settled) this.mirrors.release(this.renderer);\n return true;\n }\n\n dispose(): void {\n // Idempotent: a mesh disposed twice (or a unified renderer torn down after\n // its owner already released the sorter) must not double-free GPU state.\n if (this.disposed) return;\n this.disposed = true;\n // Disposing each compute node releases its pipeline, bind groups and\n // node state from the renderer - without this, every scene switch on a\n // long-lived renderer leaked the sorter's working set (~32 MB GPU + CPU).\n for (const pass of this.sortPasses) pass.dispose();\n // Only the sorter-owned working buffers are freed here -\n // sourceIndex/splatIndex belong to the mesh (see releaseRendererAttributes).\n releaseRendererAttributes(this.renderer, this.workingAttributes);\n }\n}\n\n/**\n * Frees the GPU buffers of storage attributes that never sat in a geometry.\n * The renderer has no public API for this, so it reaches for the renderer's\n * attribute map directly; a harmless no-op if the internal shape changes, and\n * idempotent (the map's `delete` ignores unknown entries). Shared by the\n * sorters and by owners of mesh-level storage attributes (`sourceIndex`, the\n * unified work buffer) on dispose.\n */\nexport function releaseRendererAttributes(\n renderer: THREE.WebGPURenderer,\n attributes: readonly THREE.BufferAttribute[],\n): void {\n const map = (renderer as unknown as { _attributes?: { delete(a: object): void } })._attributes;\n if (!map) return;\n for (const attribute of attributes) map.delete(attribute);\n}\n","import type * as THREE from 'three/webgpu';\nimport type { SplatSorter } from './sorter';\nimport type { SortWorkerRequest, OrderMessage } from './sort-worker-protocol';\n// Inlined worker (blob URL): survives library bundling in any consumer\n// setup, unlike an asset file referenced via `new URL(...)`.\nimport SortWorker from './sort-worker?worker&inline';\nimport { logError } from './logging';\n\n/**\n * CPU depth sorter: counting sort in a Web Worker. Used on the WebGL2\n * fallback backend, which has no compute shaders. Works for static and\n * dynamic-capacity (streamed) meshes alike: the worker keeps a mirror of\n * the pool's centers, and each sort covers only the active pool spans.\n *\n * One sort runs at a time; requests that arrive while the worker is busy\n * are declined so the caller retries with the then-current camera on a\n * later frame.\n */\nexport class WorkerSorter implements SplatSorter {\n readonly kind = 'worker' as const;\n private readonly worker: Worker;\n private readonly splatIndexAttribute: THREE.InstancedBufferAttribute;\n private readonly host: WorkerSorterHost;\n private inFlight = false;\n /** Set by {@link dispose}; drops any already-delivered order message. */\n private disposed = false;\n /** The active spans the in-flight sort was computed against. */\n private sentSpans: Uint32Array | null = null;\n\n constructor(host: WorkerSorterHost) {\n this.host = host;\n this.splatIndexAttribute = host.splatIndexAttribute;\n this.worker = new SortWorker();\n this.worker.onmessage = (event: MessageEvent<OrderMessage>) => {\n this.applyOrder(event.data.order);\n };\n // Without these, one worker-side exception would leave `inFlight` stuck\n // true and silently freeze depth ordering for the rest of the session.\n this.worker.onerror = (event: ErrorEvent) => {\n logError('sort worker error - retrying on a later frame.', event.message);\n this.inFlight = false;\n };\n this.worker.onmessageerror = () => {\n logError('sort worker message deserialization failed.');\n this.inFlight = false;\n };\n const init: SortWorkerRequest = { type: 'init', capacity: host.capacity };\n this.worker.postMessage(init);\n // Everything written so far is one dirty span from the mirror's view.\n this.pushCenters([{ start: 0, count: Math.ceil(host.capacity / host.rowWidth) }]);\n }\n\n sort(modelView: THREE.Matrix4, _activeCount: number, _bounds: THREE.Sphere): boolean {\n if (this.disposed || this.inFlight) return false;\n this.inFlight = true;\n\n this.pushCenters(this.host.takeDirtyRows());\n const spans = this.host.getActiveSpans();\n this.sentSpans = spans;\n const message: SortWorkerRequest = {\n type: 'sort',\n modelView: new Float32Array(modelView.elements),\n spans,\n matrices: this.host.perSource ? new Float32Array(this.host.perSource.matrices) : undefined,\n };\n this.worker.postMessage(message);\n return true;\n }\n\n dispose(): void {\n if (this.disposed) return;\n this.disposed = true;\n this.worker.terminate();\n }\n\n /** Sends written pool rows to keep the worker's centers mirror current. */\n private pushCenters(rows: readonly { start: number; count: number }[]): void {\n const width = this.host.rowWidth;\n for (const row of rows) {\n const centers = this.host.centers.slice(\n row.start * width * 4,\n (row.start + row.count) * width * 4,\n );\n const start = row.start * width;\n const count = row.count * width;\n const sourceIds = this.host.perSource?.sourceIds.slice(start, start + count);\n const message: SortWorkerRequest = { type: 'write', start, centers, sourceIds };\n this.worker.postMessage(message, [centers.buffer]);\n }\n }\n\n private applyOrder(order: Uint32Array): void {\n this.inFlight = false;\n // An order event already dispatched when dispose ran still lands here;\n // writing it would flag a post-dispose GPU upload on the dead draw list.\n if (this.disposed) return;\n // A reply computed against an outdated active set must not overwrite the\n // identity draw list `rebuildActiveList` wrote for the new one - it would\n // resurrect the pool slots of a just-removed range for several frames.\n // The caller's forced re-sort delivers a fresh order right after.\n const current = this.host.getActiveSpans();\n const sent = this.sentSpans;\n this.sentSpans = null;\n if (!sent || sent.length !== current.length) return;\n for (let i = 0; i < sent.length; i++) {\n if (sent[i] !== current[i]) return;\n }\n const indexes = this.splatIndexAttribute.array as Float32Array;\n for (let i = 0; i < order.length; i++) {\n indexes[i] = order[i] as number;\n }\n // An explicit update range covering the whole order: if the attribute\n // already carries narrow ranges from an active-list patch this frame, a\n // bare `needsUpdate` would upload only those slots and clip the new\n // permutation to a fragment - draw-list garbage until the next sort.\n this.splatIndexAttribute.addUpdateRange(0, order.length);\n this.splatIndexAttribute.needsUpdate = true;\n // The draw list now holds a depth permutation, not the identity active\n // order - the host must stop patching it incrementally (see\n // SplatMesh.commitActiveListMutation).\n this.host.onOrderApplied?.();\n }\n}\n\n/** What the sorter needs from its mesh; see SplatMesh.createSorter. */\nexport interface WorkerSorterHost {\n /** Pool capacity in splats. */\n readonly capacity: number;\n /** Splats per pool texture row. */\n readonly rowWidth: number;\n /** The pool's centers backing array (vec4 stride; xyz used). */\n readonly centers: Float32Array;\n /** Source metadata for a unified pool; omitted for a normal mesh. */\n readonly perSource?: {\n readonly sourceIds: Float32Array;\n readonly matrices: Float32Array;\n };\n readonly splatIndexAttribute: THREE.InstancedBufferAttribute;\n /** Drains the row spans written since the last call. */\n takeDirtyRows(): { start: number; count: number }[];\n /** Active ranges as (start, count) pool-index pairs, active-list order. */\n getActiveSpans(): Uint32Array;\n /**\n * Called after a worker order lands in the draw list, so the host knows the\n * list holds a sorted permutation (not identity active order) and must fully\n * resync - not patch - it on the next active-list mutation.\n */\n onOrderApplied?(): void;\n}\n","import * as THREE from 'three/webgpu';\n\nconst TWO_MILLION = 2_000_000;\nconst FIVE_MILLION = 5_000_000;\nconst EIGHT_MILLION = 8_000_000;\nconst MODEL_VIEW_EPSILON = 1e-6;\n/** Validates the public fixed sort-interval override. */\nexport function validateSortIntervalMs(value: number | undefined): number | undefined {\n if (value !== undefined && (!Number.isFinite(value) || value < 0)) {\n throw new RangeError(\n 'SplatMesh sortIntervalMs must be a finite number greater than or equal to 0.',\n );\n }\n return value;\n}\n\n/**\n * Returns the automatic WebGPU sort interval for the current active splat count.\n *\n * Mobile keeps a floor even for small scenes. A sort is never free: the\n * counting sorter's clear and scan passes cost the same whatever the splat\n * count, so on a mobile GPU an every-changed-frame sort stalls the frame\n * (measured on an Adreno 750: a few frames, then a freeze, repeating). Two\n * frames of sort latency while the camera moves is imperceptible next to that\n * - and {@link WebGpuSortScheduler.shouldSubmit} still sorts immediately once\n * the camera settles, so a stationary view is always exactly ordered.\n */\nexport function automaticSortIntervalMs(activeCount: number, isMobile = false): number {\n if (activeCount < TWO_MILLION) return isMobile ? 33 : 0;\n if (activeCount < FIVE_MILLION) return isMobile ? 66 : 50;\n if (activeCount < EIGHT_MILLION) return isMobile ? 133 : 100;\n return 1000 / 6;\n}\n\n/**\n * Gates WebGPU sort submissions while retaining content swaps and the\n * final camera pose. Accepted-sort state is committed separately so sorter\n * backpressure never causes a request to be forgotten.\n */\nexport class WebGpuSortScheduler {\n private readonly sortIntervalMs: number | undefined;\n private readonly isMobile: boolean;\n private readonly previousModelView = new THREE.Matrix4();\n private hasPreviousModelView = false;\n private legacyWasMoving = false;\n private forcePending = true;\n private lastAcceptedAt = -Infinity;\n private acceptedCount = 0;\n\n constructor(sortIntervalMs?: number, isMobile = false) {\n this.sortIntervalMs = validateSortIntervalMs(sortIntervalMs);\n this.isMobile = isMobile;\n }\n\n /** Forces the next changed or content-invalidated pose to bypass throttling. */\n invalidate(): void {\n this.forcePending = true;\n }\n\n /**\n * Content changes replace pool indices underneath the current draw order.\n * They must bypass cadence so a newly active range never renders through a\n * stale sort while the camera is moving.\n */\n invalidateContent(): void {\n this.forcePending = true;\n }\n\n /**\n * Whether an invalidation is still waiting for an accepted sort. The WebGL2\n * worker path consults this directly: it has no cadence, but a content swap\n * under a stationary camera must still trigger a re-sort - the swap reset\n * the draw list to unsorted active order, and without this the scene would\n * render in that unsorted order until the camera next moved (visible\n * blend-order flicker on every streaming swap).\n */\n hasPendingForce(): boolean {\n return this.forcePending;\n }\n\n /**\n * Returns whether a sort should be submitted at this timestamp.\n */\n shouldSubmit(\n modelView: THREE.Matrix4,\n lastAcceptedModelView: THREE.Matrix4,\n activeCount: number,\n now: number,\n ): boolean {\n return this.shouldSubmitLegacy(modelView, lastAcceptedModelView, activeCount, now);\n }\n\n /**\n * Commits timing state only after the sorter accepts a submission.\n */\n markAccepted(now: number): void {\n this.lastAcceptedAt = now;\n this.acceptedCount++;\n this.forcePending = false;\n }\n\n /** Internal diagnostics for the demo HUD; it does not alter scheduling. */\n snapshot(): { acceptedCount: number; lastAcceptedAt: number } {\n return { acceptedCount: this.acceptedCount, lastAcceptedAt: this.lastAcceptedAt };\n }\n\n private shouldSubmitLegacy(\n modelView: THREE.Matrix4,\n lastAcceptedModelView: THREE.Matrix4,\n activeCount: number,\n now: number,\n ): boolean {\n let settled = false;\n if (this.hasPreviousModelView) {\n const moved = maximumElementDelta(modelView, this.previousModelView) > MODEL_VIEW_EPSILON;\n settled = this.legacyWasMoving && !moved;\n this.legacyWasMoving = moved;\n } else {\n this.hasPreviousModelView = true;\n }\n this.previousModelView.copy(modelView);\n\n const interval = this.sortIntervalMs ?? automaticSortIntervalMs(activeCount, this.isMobile);\n if (this.forcePending) return true;\n if (modelView.equals(lastAcceptedModelView)) return false;\n if (settled) return true;\n return interval === 0 || now - this.lastAcceptedAt >= interval;\n }\n}\n\nfunction maximumElementDelta(a: THREE.Matrix4, b: THREE.Matrix4): number {\n let maximum = 0;\n for (let i = 0; i < 16; i++) {\n maximum = Math.max(maximum, Math.abs((a.elements[i] as number) - (b.elements[i] as number)));\n }\n return maximum;\n}\n","/**\n * 24-bit RGB packing for linear view-space depth used by GPU splat picking.\n *\n * Depth is stored as a normalized value in [0, 1] mapped across the camera\n * near/far range, then quantized into three 8-bit channels (R = high byte).\n * This matches an RGBA8 render target without a depth-texture readback path.\n */\nimport * as THREE from 'three/webgpu';\n\n/** Maximum integer representable in 24 bits. */\nconst DEPTH_24_MAX = (1 << 24) - 1;\n\nconst _ndc = new THREE.Vector2();\nconst _raycaster = new THREE.Raycaster();\nconst _dirView = new THREE.Vector3();\n\n/**\n * Packs a normalized depth in [0, 1] into 8-bit RGB (high → low).\n * Values outside [0, 1] are clamped.\n */\nexport function packNormalizedDepth(normalized: number): { r: number; g: number; b: number } {\n const t = Math.min(1, Math.max(0, normalized));\n const encoded = Math.round(t * DEPTH_24_MAX);\n return {\n r: (encoded >> 16) & 0xff,\n g: (encoded >> 8) & 0xff,\n b: encoded & 0xff,\n };\n}\n\n/**\n * Unpacks 8-bit RGB channels back to a normalized depth in [0, 1].\n */\nexport function unpackNormalizedDepth(r: number, g: number, b: number): number {\n const encoded = ((r & 0xff) << 16) | ((g & 0xff) << 8) | (b & 0xff);\n return encoded / DEPTH_24_MAX;\n}\n\n/**\n * Maps a positive linear view-space depth (OpenGL: −z) into [0, 1] using the\n * camera near/far plane. Clamps to the range.\n */\nexport function normalizeViewDepth(viewDepth: number, near: number, far: number): number {\n if (!(far > near)) return 0;\n return Math.min(1, Math.max(0, (viewDepth - near) / (far - near)));\n}\n\n/**\n * Inverse of {@link normalizeViewDepth}.\n */\nexport function denormalizeViewDepth(normalized: number, near: number, far: number): number {\n return near + normalized * (far - near);\n}\n\n/**\n * Reconstructs a world-space hit from NDC and a positive view-space depth\n * (−z in OpenGL camera space). Writes into `outPoint` and returns camera\n * distance. The camera must have an up-to-date world matrix.\n */\nexport function unprojectViewDepth(\n ndcX: number,\n ndcY: number,\n viewDepth: number,\n camera: THREE.Camera,\n outPoint: THREE.Vector3,\n): { point: THREE.Vector3; distance: number } {\n _ndc.set(ndcX, ndcY);\n _raycaster.setFromCamera(_ndc, camera);\n // View-space Z of the hit is −viewDepth; scale the world ray so its view-Z\n // matches. Camera upper-3×3 is orthonormal (no non-uniform scale).\n _dirView.copy(_raycaster.ray.direction).transformDirection(camera.matrixWorldInverse);\n const t = viewDepth / -_dirView.z;\n outPoint.copy(_raycaster.ray.origin).addScaledVector(_raycaster.ray.direction, t);\n return { point: outPoint, distance: outPoint.distanceTo(_raycaster.ray.origin) };\n}\n","/**\n * Resolving the active WebXR view for a presenting renderer.\n *\n * Every render path (a static {@link SplatMesh}, a streamed one, the unified\n * renderer) needs the same three things while an immersive session presents -\n * a cyclopean camera to sort and place SH by, one eye camera to take the\n * projection from, and the *per-eye* viewport in pixels - so they all resolve\n * them here rather than each reading `renderer.xr` its own way.\n *\n * Internal. Nothing here is exported from `index.ts`.\n */\nimport * as THREE from 'three/webgpu';\n\n/** The cameras and pixel size describing one frame of a presenting XR session. */\nexport interface XrView {\n /**\n * The XR array camera: the head pose, midway between the eyes. Depth order\n * and the SH view direction come from here - at a ~63 mm IPD the per-eye\n * difference is imperceptible except within the near plane, so one\n * cyclopean sort serves both eyes instead of doubling the frame's dominant\n * cost. Its `projectionMatrix` is the union frustum covering both eyes,\n * which is also the right frustum for LOD scheduling.\n */\n head: THREE.Camera;\n /**\n * The first eye camera, for projection-derived quantities. Left and right\n * XR projections differ only in their center offset, not their focal\n * length, so a focal length taken from one eye is exact for both - it is\n * not an approximation.\n */\n eye: THREE.PerspectiveCamera;\n /** Per-eye viewport width in device pixels. */\n width: number;\n /** Per-eye viewport height in device pixels. */\n height: number;\n}\n\n/**\n * Structural view of the bits of three's XR manager this module uses. Every\n * member is optional: the renderer may be a stub in tests, and older three\n * versions predate parts of this surface.\n */\ninterface XrManagerLike {\n isPresenting?: boolean;\n cameraAutoUpdate?: boolean;\n getCamera?: () => THREE.Camera;\n updateCamera?: (camera: THREE.PerspectiveCamera) => void;\n}\n\nconst _drawingBufferSize = new THREE.Vector2();\n\n/**\n * The active XR view, or `null` when the renderer is not presenting one.\n *\n * **Freshens the XR camera before reading it.** three splits its per-frame XR\n * work in two: the sub-cameras' *local* `matrix`, `projectionMatrix` and\n * `viewport` are set in the XR manager's own animation-frame callback (which\n * runs before the host's loop body), but their `matrixWorld` /\n * `matrixWorldInverse`, the head's world matrices, and the two-eye union\n * projection are only computed in `XRManager.updateCamera()` - which the\n * renderer calls from inside `render()`. A `SplatMesh.update()` that runs\n * before `render()` would therefore read last frame's world pose, and on the\n * first presenting frame an identity one (sorting the scene from the world\n * origin). Calling `updateCamera` here closes that gap; it is idempotent and\n * cheap (a handful of matrix products - `session.updateRenderState` only\n * fires when near/far actually change), so the renderer's own call moments\n * later costs nothing.\n *\n * @param camera - The *application* camera. `updateCamera` reads its near/far,\n * layers and parent, so the app camera is required here - not the head.\n * @returns The resolved view, or `null` when not presenting (or before the\n * session has produced its first viewer pose).\n */\nexport function resolveXrView(\n camera: THREE.PerspectiveCamera,\n renderer: THREE.WebGPURenderer,\n): XrView | null {\n const xr = (renderer as { xr?: XrManagerLike }).xr;\n if (xr?.isPresenting !== true || typeof xr.getCamera !== 'function') return null;\n // A host that drives the XR camera itself sets `cameraAutoUpdate = false`;\n // freshening it here would fight that.\n if (xr.cameraAutoUpdate !== false && typeof xr.updateCamera === 'function') {\n xr.updateCamera(camera);\n }\n const head = xr.getCamera();\n const eye = (head as { cameras?: THREE.PerspectiveCamera[] }).cameras?.[0];\n if (!eye) return null;\n\n // The eye viewport is authoritative: the drawing buffer spans both eyes, so\n // using it would halve every splat's apparent horizontal size. It is only\n // the fallback for the frames before the XR layer reports viewports.\n const viewport = (eye as { viewport?: THREE.Vector4 }).viewport;\n let width = viewport?.z ?? 0;\n let height = viewport?.w ?? 0;\n if (!(width > 0) || !(height > 0)) {\n renderer.getDrawingBufferSize(_drawingBufferSize);\n width = _drawingBufferSize.x;\n height = _drawingBufferSize.y;\n }\n return { head, eye, width, height };\n}\n\n/**\n * Whether `camera` is an XR array camera. Code that needs a single frustum -\n * picking, anything unprojecting depth through `near`/`far` - must reject one\n * rather than silently read the defaults off a camera that has neither.\n */\nexport function isXrArrayCamera(camera: THREE.Camera): boolean {\n return (camera as { isArrayCamera?: boolean }).isArrayCamera === true;\n}\n\n/**\n * Session options for `navigator.xr.requestSession('immersive-vr', …)` that\n * match the renderer's backend.\n *\n * three's XR manager **throws** out of `setSession` when a WebGPU-backed\n * renderer meets a session that did not enable the `webgpu` feature - it has a\n * genuine WebGPU XR path (an `XRGPUBinding` projection layer) and refuses to\n * silently fall back to WebGL. three's own `VRButton` never asks for that\n * feature, so a host pairing it with a `WebGPURenderer` fails to enter VR on\n * exactly the browsers that support XR best. This returns the init that avoids\n * the mismatch:\n *\n * ```js\n * const session = await navigator.xr.requestSession('immersive-vr', xrSessionInit(renderer));\n * await renderer.xr.setSession(session);\n * ```\n *\n * `webgpu` is marked **required** rather than optional deliberately: an\n * optional feature the browser declines still yields a session, which is the\n * very mismatch that throws. Required turns it into a `requestSession`\n * rejection the host can catch and explain - typically by falling back to a\n * WebGL2 renderer, since three's own `WebGLXRFallback` helper swaps the entire\n * renderer and is unusable once GPU resources are bound to the current one.\n *\n * @param base - Extra options to merge. Its `requiredFeatures` and\n * `optionalFeatures` are concatenated, not replaced.\n */\nexport function xrSessionInit(\n renderer: THREE.WebGPURenderer,\n base: XRSessionInit = {},\n): XRSessionInit {\n const isWebGPU = (renderer.backend as { isWebGPUBackend?: boolean }).isWebGPUBackend === true;\n const required = [...(base.requiredFeatures ?? []), ...(isWebGPU ? ['webgpu'] : [])];\n const optional = base.optionalFeatures ?? [];\n return {\n ...base,\n ...(required.length > 0 ? { requiredFeatures: required } : {}),\n ...(optional.length > 0 ? { optionalFeatures: optional } : {}),\n };\n}\n","/**\n * GPU screen-space picking for `SplatMesh`.\n *\n * Picking renders the splats once more into a 1×1 target, with a\n * material that writes linear view depth instead of color; the depth comes back\n * through an async readback and unprojects to a world point. That needs a whole\n * subsystem of its own - a render target, a proxy mesh, a private scene, saved\n * renderer state - none of which the mesh itself ever touches, so it lives here\n * rather than as ten more fields on an already large class.\n *\n * The picker reaches back into the mesh through {@link SplatPickHost} rather\n * than holding the mesh itself: the four things it needs are otherwise private,\n * and widening them for the picker would put them in the published .d.ts.\n *\n * Internal. Nothing here is exported from `index.ts`.\n */\nimport * as THREE from 'three/webgpu';\nimport { uniform } from 'three/tsl';\nimport {\n denormalizeViewDepth,\n unpackNormalizedDepth,\n unprojectViewDepth,\n} from './splat-depth-pack';\nimport type { SplatPickOptions, SplatPickResult } from './splat-mesh';\nimport type { FloatUniform } from './splat-mesh-material';\nimport { isXrArrayCamera } from './xr-view';\n\n/** What the picker needs from the mesh it picks. */\nexport interface SplatPickHost {\n /** The mesh, for the transform/visibility the proxy mirrors and its geometry. */\n readonly mesh: THREE.Mesh;\n isDisposed(): boolean;\n getActiveCount(): number;\n /** The mesh's live viewport uniform value, in drawing-buffer pixels. */\n getViewportSize(): THREE.Vector2;\n /** Visibility as resolved by an owning unified renderer, when any. */\n getPickVisible(): boolean;\n /** Whether a sorter exists. A pick never creates one - see {@link prepare}. */\n hasSorter(): boolean;\n updateWorldMatrix(): void;\n /**\n * Brings the GPU to the state `update()` would leave it in: flush pending\n * uploads, refresh the projection uniforms, and refresh an existing sorter's\n * draw list. Ordering matters, so the mesh owns it.\n */\n prepare(camera: THREE.Camera, renderer: THREE.WebGPURenderer): void;\n /**\n * Writes focal / viewport / local-camera uniforms for a camera drawing into\n * a viewport of `width`×`height` pixels. The pick pass crops the frustum to\n * one source pixel and renders into a 1×1 target, so it must rewrite these\n * after the crop and restore the canvas-sized values before returning.\n */\n setView(camera: THREE.Camera, width: number, height: number): void;\n /** Builds the pick-mode TSL graph onto a freshly created material. */\n applyPickGraph(material: THREE.NodeMaterial): void;\n}\n\n/**\n * Owns the pick pass and everything it allocates. Resources are created on the\n * first pick, so a mesh that is never picked pays nothing.\n */\nexport class SplatPicker {\n /** Alpha below which a splat is transparent enough to pick through. */\n private readonly alphaThreshold = uniform(0.1);\n /** Camera planes for the pick material's depth encoding. */\n private readonly near = uniform(0.1);\n private readonly far = uniform(1000);\n private material: THREE.NodeMaterial | null = null;\n private proxy: THREE.Mesh | null = null;\n private target: THREE.RenderTarget | null = null;\n /** Reused sub-frustum camera; preserves the caller's concrete camera type. */\n private pickCamera: THREE.Camera | null = null;\n private readonly scene = new THREE.Scene();\n /** Serializes picks: they share one render target and one renderer. */\n private queue: Promise<unknown> = Promise.resolve();\n private readonly point = new THREE.Vector3();\n private readonly savedClearColor = new THREE.Color();\n\n constructor(private readonly host: SplatPickHost) {}\n\n /** The pick uniforms the material graph binds. */\n get uniforms(): { alphaThreshold: FloatUniform; near: FloatUniform; far: FloatUniform } {\n return { alphaThreshold: this.alphaThreshold, near: this.near, far: this.far };\n }\n\n pick(\n ndc: THREE.Vector2,\n camera: THREE.Camera,\n renderer: THREE.WebGPURenderer,\n options?: SplatPickOptions,\n ): Promise<SplatPickResult | null> {\n const run = this.queue.then(() => this.run(ndc, camera, renderer, options));\n // Keep the queue alive even when a pick rejects, so later calls still run.\n this.queue = run.then(\n () => undefined,\n () => undefined,\n );\n return run;\n }\n\n /** Rebuilds the pick graph after a settings change. No-op before the first pick. */\n rebuildMaterial(): void {\n if (!this.material) return;\n this.host.applyPickGraph(this.material);\n }\n\n /** Flags the pick graph for recompile (e.g. the modifier list changed). */\n markNeedsUpdate(): void {\n if (this.material) this.material.needsUpdate = true;\n }\n\n dispose(): void {\n this.material?.dispose();\n this.material = null;\n this.scene.clear();\n this.proxy = null;\n this.target?.dispose();\n this.target = null;\n this.pickCamera = null;\n }\n\n private async run(\n ndc: THREE.Vector2,\n camera: THREE.Camera,\n renderer: THREE.WebGPURenderer,\n options?: SplatPickOptions,\n ): Promise<SplatPickResult | null> {\n if (this.host.isDisposed()) return null;\n if (this.host.getActiveCount() === 0) return null;\n if (ndc.x < -1 || ndc.x > 1 || ndc.y < -1 || ndc.y > 1) return null;\n // An XR array camera has no single frustum: no `near`/`far` to unproject\n // the encoded depth through (the fallbacks below would silently substitute\n // 0.1/1000 and return a plausible-looking but wrong world point), and the\n // pass would rasterize stereo into a mono target. Fail loudly instead.\n if (isXrArrayCamera(camera)) {\n throw new Error(\n 'SplatMesh.pick: an XR array camera has no single frustum to pick through. ' +\n 'Pass one eye (renderer.xr.getCamera().cameras[i]) or a mono camera, and ' +\n 'give `ndc` in that eye’s viewport.',\n );\n }\n\n camera.updateMatrixWorld(true);\n this.host.updateWorldMatrix();\n // Match the state `update()` establishes: appended-but-unflushed rows must\n // reach the GPU, and an existing sorter's draw list must be refreshed for\n // the current active set - or the pick pass rasterizes stale pool data\n // (garbage centers, or \"ghost\" splats from a just-removed range still in\n // the GPU-sorted order). The pick pass itself is depth-tested, so it does\n // not need a *sorted* order - only a valid one - hence no sorter is\n // created here when none exists yet (the identity draw list is valid).\n this.host.prepare(camera, renderer);\n\n const width = Math.max(1, Math.floor(this.host.getViewportSize().x));\n const height = Math.max(1, Math.floor(this.host.getViewportSize().y));\n const px = Math.floor((ndc.x * 0.5 + 0.5) * width);\n // NDC +y is up, so this row index is measured from the BOTTOM edge.\n const pyBottom = Math.floor((ndc.y * 0.5 + 0.5) * height);\n if (px < 0 || pyBottom < 0 || px >= width || pyBottom >= height) return null;\n this.ensureResources(camera);\n const pickTarget = this.target!;\n const pickProxy = this.proxy!;\n const pickCamera = this.pickCamera!;\n this.cropCameraToPixel(pickCamera, camera, width, height, px, pyBottom);\n // The crop maps one source pixel onto NDC [-1, 1]. Quad extent is\n // `pixelOffset * 2 / viewport`, so viewport must be the 1×1 pick target\n // or a splat tens of pixels wide shrinks to a sliver that never covers\n // the clicked pixel. Focal follows the cropped projection at this size\n // and matches the canvas-pixel covariance the display pass used.\n this.host.setView(pickCamera, 1, 1);\n\n const near = 'near' in camera && typeof camera.near === 'number' ? camera.near : 0.1;\n const far = 'far' in camera && typeof camera.far === 'number' ? camera.far : 1000;\n this.near.value = near;\n this.far.value = far;\n this.alphaThreshold.value =\n options?.alphaThreshold !== undefined ? options.alphaThreshold : 0.1;\n\n const mesh = this.host.mesh;\n const previousTarget = renderer.getRenderTarget();\n const previousScissorTest = renderer.getScissorTest();\n renderer.getClearColor(this.savedClearColor);\n const previousClearAlpha = renderer.getClearAlpha();\n const previousAutoClear = renderer.autoClear;\n\n pickProxy.matrix.copy(mesh.matrixWorld);\n pickProxy.matrixWorld.copy(mesh.matrixWorld);\n pickProxy.visible = this.host.getPickVisible();\n pickProxy.layers.mask = mesh.layers.mask;\n pickProxy.renderOrder = mesh.renderOrder;\n pickTarget.viewport.set(0, 0, 1, 1);\n pickTarget.scissor.set(0, 0, 1, 1);\n\n // Start readback while the target is bound, but restore shared renderer\n // state synchronously. Awaiting while mutated would corrupt normal frames.\n const readback = (() => {\n try {\n renderer.setRenderTarget(pickTarget);\n renderer.setScissorTest(false);\n renderer.setClearColor(0x000000, 0);\n renderer.autoClear = true;\n renderer.clear();\n\n renderer.setScissorTest(true);\n renderer.autoClear = false;\n renderer.render(this.scene, pickCamera);\n return renderer.readRenderTargetPixelsAsync(pickTarget, 0, 0, 1, 1);\n } finally {\n this.host.setView(camera, width, height);\n renderer.setRenderTarget(previousTarget);\n renderer.setScissorTest(previousScissorTest);\n renderer.setClearColor(this.savedClearColor, previousClearAlpha);\n renderer.autoClear = previousAutoClear;\n }\n })();\n\n let rgba: Uint8Array | Uint8ClampedArray | Float32Array;\n try {\n rgba = (await readback) as Uint8Array;\n } catch (error) {\n // A dispose while the readback was in flight tears down the render\n // target the GPU was copying from; the readback rejection is then an\n // expected consequence of dispose, not a pick failure - resolve as a\n // clean miss so hosts never need a try/catch around a cursor pick.\n if (this.host.isDisposed()) return null;\n throw error;\n }\n if (this.host.isDisposed()) return null;\n\n const r = rgba[0] as number;\n const g = rgba[1] as number;\n const b = rgba[2] as number;\n const a = rgba[3] as number;\n if (a === 0) return null;\n\n const viewDepth = denormalizeViewDepth(unpackNormalizedDepth(r, g, b), near, far);\n const { point, distance } = unprojectViewDepth(ndc.x, ndc.y, viewDepth, camera, this.point);\n return { point: point.clone(), distance };\n }\n\n private ensureResources(camera: THREE.Camera): void {\n if (this.target === null) {\n this.target = new THREE.RenderTarget(1, 1, {\n format: THREE.RGBAFormat,\n type: THREE.UnsignedByteType,\n depthBuffer: true,\n stencilBuffer: false,\n });\n }\n if (this.material === null) {\n this.material = new THREE.NodeMaterial();\n this.host.applyPickGraph(this.material);\n this.proxy = new THREE.Mesh(this.host.mesh.geometry, this.material);\n this.proxy.matrixAutoUpdate = false;\n this.proxy.frustumCulled = false;\n this.scene.add(this.proxy);\n }\n if (this.pickCamera === null || this.pickCamera.constructor !== camera.constructor) {\n this.pickCamera = camera.clone();\n }\n }\n\n /** Maps one original framebuffer pixel onto the complete 1×1 pick target. */\n private cropCameraToPixel(\n target: THREE.Camera,\n source: THREE.Camera,\n width: number,\n height: number,\n px: number,\n pyBottom: number,\n ): void {\n // The concrete subclasses have compatible `copy` overrides; ensureResources\n // keeps source and target constructors equal before this call.\n target.copy(source);\n const projection = target.projectionMatrix;\n const elements = projection.elements;\n const xOffset = 2 * px + 1 - width;\n const yOffset = 2 * pyBottom + 1 - height;\n for (let column = 0; column < 4; column++) {\n const rowW = elements[column * 4 + 3] as number;\n elements[column * 4] = (elements[column * 4] as number) * width - xOffset * rowW;\n elements[column * 4 + 1] = (elements[column * 4 + 1] as number) * height - yOffset * rowW;\n }\n target.projectionMatrixInverse.copy(projection).invert();\n }\n}\n","/**\n * PlayCanvas-style proxy-mesh splat relighting (screen-space modulate).\n *\n * Host lights a proxy mesh into an RGBA render target (RGB = lit color,\n * A = coverage), then {@link SplatMesh.setRelighting} /\n * {@link UnifiedSplatRenderer.setRelighting} multiplies baked splat color in\n * the display fragment. Not a `SplatModifier` - coverage is per-pixel.\n *\n * See `docs/guide/relighting.md`.\n */\nimport * as THREE from 'three/webgpu';\n\n/** Defaults match PlayCanvas `GsplatRelighting` (0.5 gray proxy albedo → brightness 2). */\nexport const DEFAULT_RELIGHT_BLEND = 1;\nexport const DEFAULT_RELIGHT_BRIGHTNESS = 2;\nexport const DEFAULT_RELIGHT_BACKGROUND = 1;\n/** Screen-space soft edge on coverage (px). Softens coarse proxy silhouettes. */\nexport const DEFAULT_RELIGHT_SOFTNESS = 0;\n\n/** Live screen-space relighting settings for {@link SplatMesh.setRelighting}. */\nexport type RelightingSettings = {\n /** Lit proxy render: RGB = lighting, A = mesh coverage (0 = sky / uncovered). */\n map: THREE.Texture;\n /** How much the map affects splat color (`0` = baked only, `1` = full modulate). */\n blend?: number;\n /** Scales `map.rgb` before multiply; `2` compensates a 0.5 gray proxy albedo. */\n brightness?: number;\n /** Multiplier for splats where `map.a ≈ 0` (sky / uncovered). */\n background?: number;\n /**\n * Softens the coverage mask over this many screen pixels (box filter). Use\n * `2`–`4` when the proxy is a coarse collision mesh so triangle silhouettes\n * do not read as a static shadow. `0` = hard PlayCanvas-style edges.\n */\n softness?: number;\n};\n\n/** Resolved numeric fields after clamping (map omitted - still the host texture). */\nexport type RelightingUniforms = {\n blend: number;\n brightness: number;\n background: number;\n softness: number;\n};\n\n/**\n * Clamps blend / brightness / background / softness.\n */\nexport function clampRelightingSettings(\n partial: Partial<Pick<RelightingSettings, 'blend' | 'brightness' | 'background' | 'softness'>>,\n previous: RelightingUniforms = {\n blend: DEFAULT_RELIGHT_BLEND,\n brightness: DEFAULT_RELIGHT_BRIGHTNESS,\n background: DEFAULT_RELIGHT_BACKGROUND,\n softness: DEFAULT_RELIGHT_SOFTNESS,\n },\n): RelightingUniforms {\n const blend = partial.blend !== undefined ? partial.blend : previous.blend;\n const brightness = partial.brightness !== undefined ? partial.brightness : previous.brightness;\n const background = partial.background !== undefined ? partial.background : previous.background;\n const softness = partial.softness !== undefined ? partial.softness : previous.softness;\n return {\n blend: Number.isFinite(blend) ? Math.min(1, Math.max(0, blend)) : previous.blend,\n brightness: Number.isFinite(brightness) ? Math.max(0, brightness) : previous.brightness,\n background: Number.isFinite(background) ? Math.max(0, background) : previous.background,\n softness: Number.isFinite(softness) ? Math.min(8, Math.max(0, softness)) : previous.softness,\n };\n}\n\n/**\n * 1×1 opaque white placeholder so the fragment graph can always sample a map\n * when relighting is off (`blend === 0`).\n */\nexport function createPlaceholderRelightTexture(): THREE.DataTexture {\n const data = new Uint8Array([255, 255, 255, 255]);\n const texture = new THREE.DataTexture(data, 1, 1);\n texture.needsUpdate = true;\n texture.magFilter = THREE.NearestFilter;\n texture.minFilter = THREE.NearestFilter;\n texture.generateMipmaps = false;\n return texture;\n}\n","/**\n * A lazily built uniform spatial grid over splat centers, for the CPU spatial\n * queries `SplatMesh` exposes (M9). Pure array math - no THREE, no GPU - over\n * the pool's CPU-side centers, so it is cheap to build and to test.\n *\n * The grid indexes a set of *pool indices* into a shared centers array (the\n * pool's `backing.centers`, RGBA-strided: splat `p`'s center is\n * `centers[p*4 + 0..2]`). Only the indices handed in are indexed, so a static\n * mesh grids its whole scene and a streamed mesh grids exactly its resident\n * splats. Everything is in the mesh's **local** space; the caller converts\n * world queries in and results out.\n */\n\n/** Largest per-axis cell count, to bound the cell-start array's size. */\nconst MAX_DIM = 1024;\n/** Largest total cell count; above this the grid coarsens rather than allocate. */\nconst MAX_CELLS = 1 << 21;\n\nexport class UniformGrid {\n /** Populated-region minimum corner (local space). */\n private readonly min = new Float64Array(3);\n /** Per-axis cell size (local units). */\n private readonly cellSize = new Float64Array(3);\n /** Per-axis cell counts. */\n private readonly dims = new Int32Array(3);\n /** CSR cell offsets: cell c owns `items[start[c] .. start[c+1])`. */\n private readonly cellStart: Int32Array;\n /** Pool indices, bucketed by cell. */\n private readonly items: Uint32Array;\n /** Pool centers array this grid points into (RGBA stride 4). */\n private readonly centers: Float32Array;\n\n readonly count: number;\n\n /**\n * @param centers - The pool's CPU centers (stride 4: x,y,z,_ per splat).\n * @param poolIndices - The pool indices to index (e.g. the active list).\n * @param count - How many entries of `poolIndices` are valid.\n */\n constructor(centers: Float32Array, poolIndices: Uint32Array, count: number) {\n this.centers = centers;\n this.count = count;\n if (count === 0) {\n this.dims.set([1, 1, 1]);\n this.cellSize.set([1, 1, 1]);\n this.cellStart = new Int32Array(2);\n this.items = new Uint32Array(0);\n return;\n }\n\n // 1. Bounds of the populated region.\n const lo = [Infinity, Infinity, Infinity];\n const hi = [-Infinity, -Infinity, -Infinity];\n for (let i = 0; i < count; i++) {\n const base = (poolIndices[i] as number) * 4;\n for (let a = 0; a < 3; a++) {\n const v = centers[base + a] as number;\n if (v < (lo[a] as number)) lo[a] = v;\n if (v > (hi[a] as number)) hi[a] = v;\n }\n }\n\n // 2. Cell sizing. Target ~one splat per cell using only the populated\n // dimensions, so a planar or linear scene still resolves well.\n const size = [\n (hi[0] as number) - (lo[0] as number),\n (hi[1] as number) - (lo[1] as number),\n (hi[2] as number) - (lo[2] as number),\n ];\n const nonZero = size.filter((s) => s > 0);\n const spanProduct = nonZero.reduce((a, b) => a * b, 1);\n let cell = nonZero.length > 0 ? Math.pow(spanProduct / count, 1 / nonZero.length) : 1;\n if (!(cell > 0) || !Number.isFinite(cell)) cell = 1;\n\n const chooseDims = (cellEdge: number): void => {\n for (let a = 0; a < 3; a++) {\n const s = size[a] as number;\n this.dims[a] = s > 0 ? Math.max(1, Math.min(MAX_DIM, Math.round(s / cellEdge) || 1)) : 1;\n // A cell must span the whole axis when there is only one of them, so a\n // point exactly at the max corner still lands in-range.\n this.cellSize[a] = (this.dims[a] as number) > 0 ? s / (this.dims[a] as number) || 1 : 1;\n this.min[a] = lo[a] as number;\n }\n };\n chooseDims(cell);\n // Coarsen until the cell array fits the cap (huge, near-degenerate scenes).\n while (this.dims[0]! * this.dims[1]! * this.dims[2]! > MAX_CELLS) {\n cell *= 1.5;\n chooseDims(cell);\n }\n\n const cellCount = this.dims[0]! * this.dims[1]! * this.dims[2]!;\n // 3. Counting sort into CSR.\n const counts = new Int32Array(cellCount + 1);\n for (let i = 0; i < count; i++) counts[this.cellOf(poolIndices[i] as number) + 1]!++;\n for (let c = 0; c < cellCount; c++) counts[c + 1]! += counts[c]!;\n this.cellStart = counts;\n this.items = new Uint32Array(count);\n const cursor = Int32Array.from(counts.subarray(0, cellCount));\n for (let i = 0; i < count; i++) {\n const p = poolIndices[i] as number;\n this.items[cursor[this.cellOf(p)]!++] = p;\n }\n }\n\n /** The cell index of pool splat `p`, clamped into range. */\n private cellOf(p: number): number {\n const base = p * 4;\n const ix = this.axisCell(0, this.centers[base] as number);\n const iy = this.axisCell(1, this.centers[base + 1] as number);\n const iz = this.axisCell(2, this.centers[base + 2] as number);\n return (iz * (this.dims[1] as number) + iy) * (this.dims[0] as number) + ix;\n }\n\n private axisCell(a: number, value: number): number {\n const i = Math.floor((value - (this.min[a] as number)) / (this.cellSize[a] as number));\n return Math.max(0, Math.min((this.dims[a] as number) - 1, i));\n }\n\n /**\n * Visits every indexed splat within `radius` (local units) of the local\n * point, calling `visit(poolIndex, distanceSq)`. Cells are pruned by their\n * axis span and each candidate is distance-filtered here, so the callback\n * only ever sees splats truly within the radius.\n */\n forEachWithin(\n x: number,\n y: number,\n z: number,\n radius: number,\n visit: (poolIndex: number, distanceSq: number) => void,\n ): void {\n if (this.count === 0 || radius < 0) return;\n const r2 = radius * radius;\n const lo = [0, 0, 0];\n const hi = [0, 0, 0];\n const q = [x, y, z];\n for (let a = 0; a < 3; a++) {\n const span = Math.ceil(radius / (this.cellSize[a] as number));\n const c = this.axisCell(a, q[a] as number);\n lo[a] = Math.max(0, c - span);\n hi[a] = Math.min((this.dims[a] as number) - 1, c + span);\n }\n for (let iz = lo[2] as number; iz <= (hi[2] as number); iz++) {\n for (let iy = lo[1] as number; iy <= (hi[1] as number); iy++) {\n const rowBase = (iz * (this.dims[1] as number) + iy) * (this.dims[0] as number);\n const from = this.cellStart[rowBase + (lo[0] as number)] as number;\n const to = this.cellStart[rowBase + (hi[0] as number) + 1] as number;\n for (let k = from; k < to; k++) {\n const p = this.items[k] as number;\n const base = p * 4;\n const dx = (this.centers[base] as number) - x;\n const dy = (this.centers[base + 1] as number) - y;\n const dz = (this.centers[base + 2] as number) - z;\n const d2 = dx * dx + dy * dy + dz * dz;\n if (d2 <= r2) visit(p, d2);\n }\n }\n }\n }\n\n /**\n * The nearest indexed splat to the local point within `radius`, or null.\n * Returns its pool index and squared local distance.\n */\n nearest(\n x: number,\n y: number,\n z: number,\n radius: number,\n ): { poolIndex: number; distSq: number } | null {\n let bestIndex = -1;\n let bestSq = Infinity;\n this.forEachWithin(x, y, z, radius, (p, d2) => {\n if (d2 < bestSq) {\n bestSq = d2;\n bestIndex = p;\n }\n });\n return bestIndex < 0 ? null : { poolIndex: bestIndex, distSq: bestSq };\n }\n}\n","import {\n MAX_SH_BANDS,\n resolveSplatPerformanceProfile,\n type ProjectedFilterProfile,\n type SplatChannelOptions,\n type SplatChannelType,\n type SplatHeightResult,\n type SplatMeshOptions,\n type SplatNearestResult,\n type SplatPerformanceProfile,\n type SplatPickOptions,\n type SplatPickResult,\n type SplatRayResult,\n type SplatRange,\n type SplatSortStrategy,\n type SplatUpdateOptions,\n type UnifiedSourceView,\n} from './splat-mesh-types';\nexport * from './splat-mesh-types';\nimport * as THREE from 'three/webgpu';\nimport { uniform } from 'three/tsl';\nimport type { SplatData } from './splat-data';\nimport { type SplatOrientation, yUpTransformForFormat } from './orientation';\nimport type { SplatModifier } from './splat-modifier';\nimport type { SplatSorter } from './sorter';\nimport {\n ComputeSorter,\n releaseRendererAttributes,\n type PerSourceSortTransform,\n} from './compute-sorter';\nimport { WorkerSorter } from './worker-sorter';\nimport { WebGpuSortScheduler, validateSortIntervalMs } from './sort-scheduler';\nimport { detectSplatDeviceProfile } from './splat-budget';\nimport { encodeFloat32ToHalf } from './half-float';\nimport { SplatPicker } from './splat-mesh-picking';\nimport { clampDepthOfFieldSettings, type DepthOfFieldSettings } from './depth-of-field';\nimport {\n clampRelightingSettings,\n createPlaceholderRelightTexture,\n DEFAULT_RELIGHT_BACKGROUND,\n DEFAULT_RELIGHT_BRIGHTNESS,\n DEFAULT_RELIGHT_SOFTNESS,\n type RelightingSettings,\n type RelightingUniforms,\n} from './relighting';\nimport {\n applySplatMaterialGraph,\n shCoefficientCount,\n vec3Uniform,\n type SplatMaterialBuildInputs,\n type SplatMaterialTextures,\n type SplatShInputs,\n type Vec3Uniform,\n} from './splat-mesh-material';\nimport {\n neutralShWord as neutralShWordFor,\n packedRangesEqual,\n requantizeShWord,\n type ShRange,\n} from './sh-pack';\nimport { UniformGrid } from './splat-query';\nimport { resolveXrView } from './xr-view';\nimport {\n SPLAT_DATA_TEXTURE_WIDTH,\n SplatPool,\n addMergedUpdateRange,\n allocateRowSpan,\n createDataTexture,\n deviceMaxTextureSize,\n releaseRowSpan,\n type SplatPoolBacking,\n type SplatPoolRange,\n type SplatPoolTenant,\n} from './splat-mesh-pool';\nimport { warn } from './logging';\n\ninterface ChannelRecord {\n readonly type: SplatChannelType;\n /** Default value; rows are reset to it when {@link SplatMesh.appendRange} reuses them. */\n readonly fill: number;\n readonly backing: Uint8Array | Float32Array;\n readonly texture: THREE.DataTexture;\n readonly textureType: THREE.TextureDataType;\n /** Row spans written since the last flush, awaiting GPU upload. */\n pendingRows: { start: number; count: number }[];\n}\n\n/** Reusable GPU source texture for a same-layout pool upload. */\n/** Number of exact-size upload textures retained per data channel. */\nconst UPLOAD_STAGING_CACHE_SIZE = 4;\n\ninterface RangeRecord {\n startRow: number;\n rowCount: number;\n /** First pool splat index (startRow · texture width). */\n start: number;\n count: number;\n active: boolean;\n /** When set, only the first `activePrefix` splats of the range participate in\n * the active (drawn/sorted) list - the rest stay allocated but invisible.\n * Used by the page-table slab, whose used slots are densely packed at the\n * front; drawing the full slab would sort and vertex-process every\n * degenerate tail slot each frame. Undefined = the whole range. */\n activePrefix?: number;\n}\n\n/**\n * Renders a set of 3D Gaussians with three.js's WebGPURenderer.\n *\n * Rendering technique: EWA splatting per the 3DGS paper (Kerbl et al.,\n * SIGGRAPH 2023). Each splat is an instanced quad; the vertex stage projects\n * the splat's 3D covariance to a screen-space ellipse (the same math used by\n * antimatter15/splat and PlayCanvas, both MIT) and the fragment stage applies\n * the Gaussian falloff. Shaders are written in TSL, so three.js compiles\n * them to WGSL on WebGPU and to GLSL on the automatic WebGL2 fallback.\n *\n * Splat data lives in pool data textures, indexed through one small\n * per-instance `splatIndex` attribute. Correct alpha blending needs\n * back-to-front ordering, so the indices are depth-sorted whenever the\n * camera moves - only that index buffer is rewritten, never the splat data\n * itself. On the WebGPU backend the sort runs in TSL compute passes\n * (`ComputeSorter`); the WebGL2 fallback uses a Web Worker (`WorkerSorter`).\n *\n * Two construction modes:\n *\n * - `new SplatMesh(data)` - static: capacity equals the data's count and\n * the whole scene is resident. Works on WebGPU and the WebGL2 fallback,\n * including SOG view-dependent color (shN).\n * - `new SplatMesh({ capacity })` - dynamic: an empty pool for up to\n * `capacity` splats. Content is managed with {@link appendRange} /\n * {@link removeRange} (ranges are row-aligned in the pool textures, so\n * uploads are rectangular). This mode is the substrate for LOD\n * streaming and works on both backends - the WebGL2 fallback sorts the\n * active pool spans on the CPU. shN data on appended ranges is ignored\n * (palettes are per-file and cannot be merged here).\n */\nexport class SplatMesh extends THREE.Mesh implements SplatPoolTenant {\n private static readonly DATA_TEXTURE_WIDTH = SPLAT_DATA_TEXTURE_WIDTH;\n\n /**\n * The storage this mesh draws from. Held rather than inlined because a\n * splat's draw identity is already independent of its pool slot (`splatIndex`\n * indirects every instance), so the pool is a separable concern - see\n * {@link SplatPool}.\n */\n private readonly pool: SplatPool;\n /** False when the pool was supplied by the caller, and so outlives this mesh. */\n private readonly ownsPool: boolean;\n private readonly splatIndexAttribute: THREE.StorageInstancedBufferAttribute;\n private readonly sourceIndexAttribute: THREE.StorageBufferAttribute;\n private dataTextures: readonly THREE.DataTexture[];\n\n /**\n * Pool index → this mesh's packed active-list slot. Lives on the pool: it is\n * keyed by pool index, so a per-mesh copy would span the whole pool and\n * several meshes sharing one would each pay for all of it. A row has one\n * owner at a time, so the single array is unambiguous.\n */\n private get activeSlotByPoolIndex(): Uint32Array {\n return this.pool.activeSlotByPoolIndex;\n }\n\n /** Rows in the pool backing this mesh. */\n private get poolRows(): number {\n return this.pool.rows;\n }\n private get centersTexture(): THREE.DataTexture {\n return this.pool.centersTexture;\n }\n private get backing(): SplatPoolBacking {\n return this.pool.backing;\n }\n private get freeRowSpans(): { start: number; count: number }[] {\n return this.pool.freeRowSpans;\n }\n private set freeRowSpans(spans: { start: number; count: number }[]) {\n this.pool.freeRowSpans = spans;\n }\n private get poolFloatTextures(): 'float32' | 'float16' {\n return this.pool.floatTextures;\n }\n /** Bands of per-splat (non-palette) SH this pool stores; 0 when disabled. */\n private get packedShBands(): 0 | 1 | 2 | 3 {\n return this.pool.packedShBands;\n }\n private get shPackedTextures(): readonly THREE.DataTexture[] {\n return this.pool.shPackedTextures;\n }\n\n /** True when constructed from a complete SplatData (single fixed range). */\n private readonly isStatic: boolean;\n /** Constructor-created range, retained for static in-place LOD replacement. */\n private staticRange: SplatRange | undefined;\n private readonly shEnabled: boolean;\n /**\n * Dequantization range for the packed SH, shared by every coefficient.\n * Uniforms rather than constants: the first chunk to arrive supplies it,\n * long after the material is built.\n */\n private readonly shRange: { min: Vec3Uniform; max: Vec3Uniform };\n /** Set once the first packed-SH chunk has supplied {@link shRange}. */\n private shRangeSet = false;\n /** Whether SH-less rows were neutral-filled before the scene range locked. */\n private wrotePreLockNeutralSh = false;\n\n private readonly ranges = new Map<SplatRange, RangeRecord>();\n private activeCount = 0;\n\n /**\n * Bumped whenever the resident splat set or its positions change (activate,\n * deactivate, compact) - the signal the lazy spatial-query grid rebuilds on.\n * A depth re-sort reorders the active list but changes neither, so it does\n * not bump this. See {@link queryNearest}.\n */\n private queryEpoch = 0;\n private queryGrid: UniformGrid | null = null;\n private queryGridEpoch = -1;\n\n /**\n * True when the shared depth-order buffer holds a *secondary* view's order\n * (left by {@link renderView}), so the next primary {@link update} must\n * re-sort even if its camera has not moved. See {@link renderView} (M10).\n */\n private orderIsForeign = false;\n /**\n * True while the draw list holds a CPU-worker depth permutation rather than\n * the identity active order. Active-list mutations must then resync the\n * whole drawn prefix instead of patching slots: patching a *permutation*\n * with identity active-list values creates duplicates and drops live splats\n * for every frame until the worker's next order lands (the WebGL2 streamed\n * flicker, ROADMAP L5).\n */\n private drawListSorted = false;\n /** CPU timings from the most recent render-preparation update (reset in place per frame). */\n private readonly updateTimings = {\n activeListMs: 0,\n uploadMs: 0,\n sortSubmitMs: 0,\n stagingTextureAllocations: 0,\n activeListUpdateRanges: 0,\n };\n // Protected so a unified {@link SplatScene} subclass can substitute a\n // world-space sort bound (see {@link refreshSortBounds}).\n protected readonly localBounds = new THREE.Box3().makeEmpty();\n protected readonly boundingSphereLocal = new THREE.Sphere();\n protected boundsDirty = true;\n /** Cached {@link getUnifiedSourceView} result; see the invalidation checks there. */\n private cachedUnifiedView: UnifiedSourceView | null = null;\n private readonly cachedUnifiedViewMatrixWorld = new THREE.Matrix4().makeScale(0, 0, 0);\n private readonly cachedUnifiedViewLocalBounds = new THREE.Sphere();\n private readonly cachedUnifiedViewWorldBounds = new THREE.Sphere();\n\n /** Row spans written since the last flush, awaiting GPU upload. */\n private pendingUploadRows: { start: number; count: number }[] = [];\n /** Row spans awaiting mirroring to the CPU sort worker (WebGL2 path). */\n private workerDirtyRows: { start: number; count: number }[] = [];\n /** Persistent copy sources avoid allocating four temporary textures per upload region. */\n private readonly uploadStaging = new Map<string, Map<number, THREE.DataTexture>>();\n /** Reusable float→half encode buffers keyed like {@link uploadStaging}. */\n private readonly halfEncodeBuffers = new Map<string, Uint16Array>();\n\n /** Opt-in per-splat channels by name; see {@link defineChannel}. */\n private readonly channels = new Map<string, ChannelRecord>();\n\n /** Created on the first update, when the renderer backend is known. */\n private sorter: SplatSorter | null = null;\n\n /**\n * Set by a unified {@link SplatScene} subclass before the first sort: makes\n * the WebGPU sorter transform each splat's center to world space by its\n * source's matrix before measuring depth. `null` for an ordinary mesh, whose\n * sorter is byte-identical to before. See `source-transform.ts`.\n */\n protected perSourceSort: PerSourceSortTransform | null = null;\n\n /** Effect hooks folded into the vertex graph; see {@link modifiers}. */\n private modifierList: SplatModifier[] = [];\n /** Increments when a unified gather must rebuild its source graph. */\n private graphRevision = 0;\n /** Version for unified work-buffer caching; source pool writes are observable. */\n private contentRevision = 0;\n /** Material-graph inputs kept for rebuilds when the modifier list changes. */\n private materialInputs!: {\n textures: SplatMaterialTextures;\n sh: SplatShInputs | null;\n };\n\n /** Projection focal lengths in pixels; updated every frame. */\n private readonly focal = uniform(new THREE.Vector2());\n /** Drawing-buffer size in pixels; updated every frame. */\n private readonly viewport = uniform(new THREE.Vector2());\n /** Camera position in this mesh's local space, for SH view dependence. */\n private readonly localCameraPosition = uniform(new THREE.Vector3());\n /**\n * Frontier-cut limit for `foveationMode: 'frontier'` - the maximum\n * `own_size / distance` a splat may have and still draw (Spark's\n * `pixelScaleLimit`). Set each frame to `foveationTargetPx / focalPx` so the\n * cut targets a fixed on-screen size. Unused in `'band'` mode.\n */\n private readonly pixelScaleLimit = uniform(0);\n /**\n * Core projected-2D depth of field (live uniforms). Aperture `0` disables.\n * Prefer this over the M13 `depthOfFieldPreset` modifier for camera DoF.\n */\n private readonly dofFocusDistance = uniform(10);\n private readonly dofAperture = uniform(0);\n /**\n * Proxy-mesh screen-space relighting (live uniforms + map). `blend === 0`\n * disables. Swapping the map rebuilds the material graph once.\n */\n private readonly relightPlaceholder = createPlaceholderRelightTexture();\n private relightMap: THREE.Texture = this.relightPlaceholder;\n private readonly relightBlend = uniform(0);\n private readonly relightBrightness = uniform(DEFAULT_RELIGHT_BRIGHTNESS);\n private readonly relightBackground = uniform(DEFAULT_RELIGHT_BACKGROUND);\n private readonly relightSoftness = uniform(DEFAULT_RELIGHT_SOFTNESS);\n /**\n * Live screen-radius band bounds (px), seeded from\n * {@link SplatMeshOptions.minSplatScreenRadius} / `maxSplatScreenRadius` and\n * moved by {@link setScreenRadiusBand} - so a foveated mesh can widen the band\n * as its LOD cut refines rather than culling the detail the refinement bought.\n */\n private readonly screenBandMin = uniform(0);\n private readonly screenBandMax = uniform(0);\n\n /** The pick pass and everything it allocates; see `splat-mesh-picking.ts`. */\n private readonly picker: SplatPicker = new SplatPicker({\n mesh: this,\n isDisposed: () => this.disposed,\n getActiveCount: () => this.activeCount,\n getViewportSize: () => this.viewport.value,\n getPickVisible: () => this.effectiveVisibility,\n hasSorter: () => this.sorter !== null,\n updateWorldMatrix: () => this.updateWorldMatrix(true, false),\n prepare: (camera, renderer) => {\n this.flushPendingUploads(renderer);\n this.refreshProjectionUniforms(camera, renderer);\n // Deliberately not `createSorter`: a depth-tested pick needs a valid\n // draw list, not a sorted one, and the identity list is valid.\n if (this.sorter !== null) this.requestSortIfNeeded(camera, renderer);\n },\n setView: (camera, width, height) => this.writeViewUniforms(camera, width, height),\n applyPickGraph: (material) =>\n applySplatMaterialGraph(\n material,\n 'pick',\n this.graphInputs(this.materialInputs.textures, this.materialInputs.sh),\n ),\n });\n /** Visibility supplied by an owning unified renderer for source-only picks. */\n private unifiedPickVisibility: boolean | null = null;\n /** Set by {@link dispose}; subclasses use it to drop late async results. */\n protected disposed = false;\n /** The renderer this mesh last updated with, so {@link dispose} can free\n * storage-attribute GPU buffers that never sat in a geometry. */\n private lastRenderer: THREE.WebGPURenderer | null = null;\n\n private readonly currentModelView = new THREE.Matrix4();\n /** Initialized to an impossible matrix so the first frame always sorts. */\n private readonly lastSortedModelView = new THREE.Matrix4().makeScale(0, 0, 0);\n private readonly sortScheduler: WebGpuSortScheduler;\n /** One-frame queue-headroom hint used before a staged atomic commit. */\n private deferSortRequestOnce = false;\n /** Bumped by every active-list mutation; identifies a draw list. */\n private activeListVersion = 0;\n /** The active list the current depth order was built from. */\n private sortedActiveListVersion = -1;\n private readonly sortStrategy: SplatSortStrategy;\n /** Resolved only when `sortStrategy === 'radix'`; see {@link ensureRadixSorter}. */\n private RadixSorterCtor: (typeof import('./radix-sorter'))['RadixSorter'] | null = null;\n private radixSorterLoad: Promise<void> | null = null;\n private performanceProfileValue: SplatPerformanceProfile;\n /** Gaussian cutoff radius in σ; baked into the material graph. */\n private maxStdDevValue: number;\n /** Screen-space minimum splat radius in px; baked into the material graph. */\n private readonly minSplatSizePx: number;\n /** Mip-Splatting 2D antialiasing filter; baked into the material graph. */\n private readonly antialias: boolean;\n /** Format-selected low-pass and opacity-compensation policy. */\n private readonly projectedFilterProfile: ProjectedFilterProfile;\n /** Emit sRGB colors for gamma-space compositing; baked into the material graph. */\n private readonly srgbOutput: boolean;\n /** Screen-radius cull threshold in px (0 = off); baked into the material graph. */\n private readonly maxSplatScreenRadius: number;\n private readonly minSplatScreenRadius: number;\n /** `.rad` foveation cut selector; baked into the material graph. */\n private readonly foveationMode: 'band' | 'frontier' | 'pagetable';\n /** Finest on-screen node size (px) the frontier cut may request. */\n private readonly foveationTargetPx: number;\n /** Target upper bound on the frontier cut's drawn-splat count. */\n private readonly foveationDrawBudget: number;\n /** Rendered major/minor axis-ratio cap (0 = off); baked into the material graph. */\n private readonly maxSplatAspect: number;\n /** Spark LOD alpha encoding + merged-node rendering; baked into the material. */\n private readonly lodAlpha: boolean;\n /** Live frontier limit in px; self-adjusts between `foveationTargetPx` (finest)\n * and coarser to hold `foveationDrawBudget`. Feeds `pixelScaleLimit` each frame. */\n private foveationLimitPx: number;\n /** Throttle timestamp for the adaptive-limit estimate (ms, `performance.now`). */\n private lastFoveationAdaptAt = -Infinity;\n /** Y-up normalization policy; drives the static self-orientation below. */\n readonly orientation: SplatOrientation;\n /** Set once the pool has been checked against the device texture limit. */\n private textureLimitChecked = false;\n\n constructor(source: SplatData | { capacity: number }, options: SplatMeshOptions = {}) {\n const sortIntervalMs = validateSortIntervalMs(options.sortIntervalMs);\n // Mobile GPUs are fragment-bound, so several defaults below trade detail\n // no one can see for the fill rate they cost. Every one is overridable.\n // The footprint floor addresses the low-resolution mobile coverage case,\n // not every device that benefits from the broader smooth/fill policy.\n // In particular, an integrated desktop remains at the reference 0 px\n // floor unless its host explicitly opts in.\n const isMobile = detectSplatDeviceProfile()?.isMobile === true;\n // Keep the reference cutoff on every device. Raising it grows every splat's\n // quad (and therefore its fragment cost), including the already-large\n // near-camera splats that dominate mobile overdraw.\n const maxStdDev = validateMaxStdDev(options.maxStdDev) ?? 3;\n // Phones need a little extra coverage only where distant splats shrink far\n // enough to leave dark gaps. The shader floor grows those undersized discs\n // without increasing fill for the rest; callers can still disable it with 0.\n const minSplatSizePx = validateMinSplatSizePx(options.minSplatSizePx) ?? (isMobile ? 1.5 : 0);\n const isStatic = !('capacity' in source);\n const capacity = isStatic ? source.count : source.capacity;\n if (capacity <= 0) throw new Error('SplatMesh capacity must be positive.');\n\n // Pool data textures (CPU backing arrays kept for partial uploads):\n // centers RGBA32F or RGBA16F x, y, z, (unused)\n // colors RGBA8 r, g, b, opacity\n // covarianceA RGBA32F or RGBA16F m00, m01, m02, m11\n // covarianceB RGBA32F m12, m22, shN palette label, frontier parent\n // shPacked[t] RGBA32UI per-splat SH coefficients 4t..4t+3, still packed\n //\n // Float16 is opt-in for centers/covA only (VRAM). CPU backing stays\n // float32; covarianceB stays float32 because it packs integer IDs.\n // Spherical harmonics a *static* mesh should actually render.\n //\n // A static mesh carries its SH in the data, and this used to read it\n // blindly: `options.shBands` and the `smooth` performance profile (the\n // mobile default) were both ignored, so no caller and no device could\n // decline it. On a 1.4M-splat band-3 capture that is four RGBA32UI pool\n // textures - 128 B/splat of GPU + CPU backing, ~180 MB - plus four texture\n // fetches and a 15-coefficient evaluation per splat *per frame*. The same\n // defect on `.rad` cost 48% of its pool.\n //\n // Resolved the way `StreamedSplatMesh.fromSource` resolves it, so both\n // paths answer the same request: an explicit `shBands` wins, otherwise the\n // performance profile decides.\n //\n // **All-or-nothing**, exactly as `buildRadScene` is: `writePackedSh` copies\n // packed words only when the counts match exactly, so asking for *fewer*\n // bands than the data carries would allocate the smaller texture set and\n // then fill it with neutral words - paying for SH and rendering flat.\n const staticShWanted =\n (options.shBands ??\n (resolveSplatPerformanceProfile(options.performanceProfile) === 'smooth'\n ? 0\n : MAX_SH_BANDS)) !== 0;\n // The dynamic branch keeps `?? 0`: a dynamic pool has no data to read bands\n // from, so defaulting it to MAX would allocate SH textures for every mesh\n // that never asked for any.\n const packedShBands: 0 | 1 | 2 | 3 = isStatic\n ? staticShWanted\n ? (source.shPacked?.bands ?? 0)\n : 0\n : (options.shBands ?? 0);\n // A supplied pool is shared: this mesh draws from it but never frees it.\n const suppliedPool = options.pool;\n const makeOwnPool = () =>\n new SplatPool({\n capacity,\n floatTextures: options.poolFloatTextures,\n packedShBands,\n packedShTextureCount:\n packedShBands === 0 ? 0 : Math.ceil(shCoefficientCount(packedShBands) / 4),\n ...(options.maxTextureSize === undefined ? {} : { maxTextureSize: options.maxTextureSize }),\n });\n // A shared pool allocates its packed-SH textures once, so it can only serve\n // tenants with its own band count. Rather than fail the load, such a mesh\n // falls back to its own pool: in a scene of markers it is usually one odd\n // capture carrying SH, and sizing the shared pool for it would add\n // ~64 B/splat across storage that mostly has no SH to read.\n const shMismatch = suppliedPool !== undefined && packedShBands !== suppliedPool.packedShBands;\n if (shMismatch) {\n warn(\n `a mesh with shBands ${packedShBands} cannot share a pool built for ` +\n `${suppliedPool?.packedShBands}; it allocated its own pool instead.`,\n );\n }\n const ownsPool = suppliedPool === undefined || shMismatch;\n const pool = ownsPool ? makeOwnPool() : suppliedPool;\n // Per-mesh draw state is sized by what *this* mesh can have active, not by\n // the pool: sharing a large pool between many meshes would otherwise give\n // each of them a pool-sized draw list and active list (8 B per pool splat,\n // per mesh). Pool *indices* still range over the whole pool - only the\n // number of slots is bounded here. Identical to `pool.capacity` when the\n // mesh allocated the pool itself.\n const texelCount = Math.min(\n pool.capacity,\n Math.ceil(capacity / SplatMesh.DATA_TEXTURE_WIDTH) * SplatMesh.DATA_TEXTURE_WIDTH,\n );\n const centersTexture = pool.centersTexture;\n const colorsTexture = pool.colorsTexture;\n const covarianceATexture = pool.covarianceATexture;\n const covarianceBTexture = pool.covarianceBTexture;\n // Palette shN (SOG/`.lcc2`) takes the same gate as the packed bands above.\n // Its palette is shared rather than per-splat, so it costs little memory -\n // but the per-frame work is identical: a texture fetch per coefficient plus\n // the full evaluation, on every splat. Leaving this ungated would have made\n // `shBands: 0` silently ineffective for exactly the formats that use it.\n //\n // Gating here also stops `writeSplatRows` storing the per-splat palette\n // labels, since it keys off `shEnabled` (this texture being non-null).\n const shPaletteTexture =\n isStatic && source.sh && staticShWanted\n ? createDataTexture(\n source.sh.palette,\n source.sh.paletteWidth,\n source.sh.paletteHeight,\n THREE.FloatType,\n )\n : null;\n const shPackedTextures = pool.shPackedTextures;\n\n // One quad, instanced per splat. Corners span [-1, 1]; the vertex stage\n // scales them to ±3σ along the projected ellipse axes.\n const geometry = new THREE.InstancedBufferGeometry();\n geometry.setIndex([0, 1, 2, 0, 2, 3]);\n geometry.setAttribute(\n 'position',\n new THREE.BufferAttribute(new Float32Array([-1, -1, 0, 1, -1, 0, 1, 1, 0, -1, 1, 0]), 3),\n );\n // A storage attribute so the GPU sorter can rewrite it in compute\n // passes; on the WebGL2 fallback it acts as a plain instanced\n // attribute that the worker sorter updates from the CPU.\n const splatIndexes = new Float32Array(texelCount);\n for (let i = 0; i < texelCount; i++) splatIndexes[i] = i;\n const splatIndexAttribute = new THREE.StorageInstancedBufferAttribute(splatIndexes, 1);\n geometry.setAttribute('splatIndex', splatIndexAttribute);\n geometry.instanceCount = 0;\n\n super(geometry, new THREE.NodeMaterial());\n this.pool = pool;\n this.ownsPool = ownsPool;\n pool.register(this);\n this.sortScheduler = new WebGpuSortScheduler(sortIntervalMs, isMobile);\n this.sortStrategy = options.sortStrategy ?? 'counting';\n if (this.sortStrategy === 'radix') this.ensureRadixSorter();\n this.performanceProfileValue = resolveSplatPerformanceProfile(options.performanceProfile);\n this.maxStdDevValue = maxStdDev;\n this.minSplatSizePx = minSplatSizePx;\n // Explicit option wins; otherwise honor the source scene's flag (SOG meta).\n this.antialias = options.antialias ?? (isStatic ? (source.antialias ?? false) : false);\n this.projectedFilterProfile = options.projectedFilterProfile ?? 'default';\n this.srgbOutput = options.srgbOutput ?? false;\n this.maxSplatScreenRadius = validateMaxSplatScreenRadius(options.maxSplatScreenRadius);\n this.minSplatScreenRadius = validateMaxSplatScreenRadius(options.minSplatScreenRadius);\n this.screenBandMin.value = this.minSplatScreenRadius;\n this.screenBandMax.value = this.maxSplatScreenRadius;\n this.foveationMode = options.foveationMode ?? 'band';\n this.foveationTargetPx = validateFoveationTargetPx(options.foveationTargetPx);\n this.foveationDrawBudget = validateFoveationDrawBudget(options.foveationDrawBudget);\n this.foveationLimitPx = this.foveationTargetPx;\n this.maxSplatAspect = validateMaxSplatScreenRadius(options.maxSplatAspect); // finite ≥ 0\n this.lodAlpha = options.lodAlpha ?? false;\n this.orientation = options.orientation ?? 'y-up';\n this.splatIndexAttribute = splatIndexAttribute;\n this.sourceIndexAttribute = new THREE.StorageBufferAttribute(new Uint32Array(texelCount), 1);\n this.dataTextures = pool.coreTextures;\n this.isStatic = isStatic;\n this.shEnabled = shPaletteTexture !== null;\n this.shRange = { min: vec3Uniform(), max: vec3Uniform() };\n this.frustumCulled = false; // Culling happens per splat in the shader.\n if (shPaletteTexture) this.dataTextures = [...this.dataTextures, shPaletteTexture];\n if (shPackedTextures.length > 0)\n this.dataTextures = [...this.dataTextures, ...shPackedTextures];\n\n this.materialInputs = {\n textures: { centersTexture, colorsTexture, covarianceATexture, covarianceBTexture },\n sh:\n isStatic && source.sh && shPaletteTexture\n ? { mode: 'palette', bands: source.sh.bands, paletteTexture: shPaletteTexture }\n : packedShBands !== 0\n ? {\n mode: 'packed',\n bands: packedShBands,\n textures: shPackedTextures,\n range: this.shRange,\n }\n : null,\n };\n this.buildMaterial(this.materialInputs.textures, this.materialInputs.sh);\n\n if (isStatic) {\n this.staticRange = this.appendRange(source);\n // Constructor-time writes are covered by the textures' initial\n // `needsUpdate` upload - the GPU cannot have seen them earlier. Any\n // append after construction must go through the staging-copy path,\n // because the backend may already have uploaded the textures by then.\n this.pendingUploadRows = [];\n // Float16 GPU images are separate from the float32 backing: pack the\n // constructor write into the texture image before the initial upload.\n if (this.poolFloatTextures === 'float16') this.syncHalfFloatPoolImages();\n // Orient a known-format scene to Y-up (unless in 'source' mode). The\n // correction is a rigid object-level transform, so the GPU rotates each\n // Gaussian's covariance and view-dependent SH consistently - nothing is\n // baked into the splat data. A dynamic pool has no source format, so it\n // is never auto-oriented (its host applies yUpTransformForFormat itself).\n const correction =\n this.orientation === 'y-up' ? yUpTransformForFormat(source.sourceFormat) : null;\n if (correction) {\n this.matrix.copy(correction);\n this.matrix.decompose(this.position, this.quaternion, this.scale);\n this.matrixWorldNeedsUpdate = true;\n }\n }\n }\n\n /**\n * Higher-order SH bands this mesh actually renders (0 when it has none),\n * whatever the source: a static mesh's palette shN, or per-splat SH in a\n * dynamic pool. Reflects what was resolved at construction, not what was\n * asked for - useful for a debug readout.\n */\n get shBands(): number {\n return this.materialInputs.sh?.bands ?? 0;\n }\n\n /**\n * The contribution-culling profile currently in effect (resolved at\n * construction, or the last {@link setPerformanceProfile} value).\n */\n get performanceProfile(): SplatPerformanceProfile {\n return this.performanceProfileValue;\n }\n\n /**\n * Changes contribution culling without changing scene data or persisted\n * settings.\n *\n * Mutator convention: state that is plain data is a property (get/set pair);\n * a `setX` method exists where the write has behavior - this one rebuilds the\n * material graph, {@link StreamedSplatMesh.setBudget} returns its clamp.\n * Every mutator has a matching readable property.\n */\n setPerformanceProfile(profile: SplatPerformanceProfile): void {\n if (profile === this.performanceProfileValue) return;\n this.performanceProfileValue = profile;\n this.buildMaterial(this.materialInputs.textures, this.materialInputs.sh);\n }\n\n /**\n * Gaussian cutoff radius currently in effect, in standard deviations.\n * Resolved at construction, or the last {@link setMaxStdDev} value.\n */\n get maxStdDev(): number {\n return this.maxStdDevValue;\n }\n\n /**\n * Changes the Gaussian cutoff without changing scene data.\n *\n * Lowering it shrinks every quad and is the main fill-rate lever in a busy\n * view; raising it restores the faint outer tail. Rebuilds the material\n * graph, same convention as {@link setPerformanceProfile}.\n */\n setMaxStdDev(value: number): void {\n const next = validateMaxStdDev(value);\n if (next === undefined || next === this.maxStdDevValue) return;\n this.maxStdDevValue = next;\n this.cachedUnifiedView = null;\n this.buildMaterial(this.materialInputs.textures, this.materialInputs.sh);\n }\n\n /**\n * Copies a decoded scene (or chunk) into the pool and activates it.\n * Ranges are row-aligned in the pool textures, so each allocation may\n * round up to the next multiple of the texture width internally.\n *\n * @returns A handle to pass to {@link removeRange}.\n * @throws {Error} when the remaining pool capacity cannot fit the range.\n */\n appendRange(data: SplatData): SplatRange {\n return this.appendRangeWithState(data, true);\n }\n\n /** Appends uploaded data without exposing it to draw/sort until activated. */\n protected appendInactiveRange(data: SplatData): SplatRange {\n return this.appendRangeWithState(data, false);\n }\n\n /** Reserves an inactive pool range whose data can be filled over multiple frames. */\n protected reserveInactiveRange(count: number): SplatRange {\n if (!Number.isInteger(count) || count < 0) {\n throw new RangeError('SplatMesh.reserveInactiveRange: count must be a non-negative integer.');\n }\n if (count === 0) {\n const empty: SplatRange = Object.freeze({ count: 0 });\n this.ranges.set(empty, { startRow: 0, rowCount: 0, start: 0, count: 0, active: false });\n return empty;\n }\n const width = SplatMesh.DATA_TEXTURE_WIDTH;\n const rowCount = Math.ceil(count / width);\n const startRow = allocateRowSpan(this.freeRowSpans, rowCount, this.poolRows);\n const start = startRow * width;\n const handle: SplatRange = Object.freeze({ count });\n this.ranges.set(handle, { startRow, rowCount, start, count, active: false });\n\n // A reused row may retain channel values from its previous occupant.\n for (const channel of this.channels.values()) {\n channel.backing.fill(channel.fill, start, start + rowCount * width);\n channel.pendingRows.push({ start: startRow, count: rowCount });\n }\n return handle;\n }\n\n /** Writes one contiguous segment of a reserved inactive range. */\n protected writeInactiveRange(handle: SplatRange, data: SplatData, offset: number): void {\n const record = this.ranges.get(handle);\n if (!record) throw new Error('SplatMesh.writeInactiveRange: unknown range handle.');\n if (record.active) throw new Error('SplatMesh.writeInactiveRange: range is already active.');\n if (!Number.isInteger(offset) || offset < 0 || offset + data.count > record.count) {\n throw new RangeError('SplatMesh.writeInactiveRange: write exceeds the reserved range.');\n }\n if (data.count === 0) return;\n this.warnOnIgnoredSh(data, 'writeInactiveRange');\n\n const destination = record.start + offset;\n this.writeSplatRows(destination, data);\n\n const width = SplatMesh.DATA_TEXTURE_WIDTH;\n const firstRow = Math.floor(destination / width);\n const lastRow = Math.floor((destination + data.count - 1) / width);\n this.markRowsWritten(firstRow, lastRow - firstRow + 1);\n _appendBox.setFromArray(data.positions);\n this.localBounds.union(_appendBox);\n this.boundsDirty = true;\n }\n\n /**\n * Overwrites splats at `offset` within a range **in place** - unlike\n * {@link writeInactiveRange} this allows an *active* range, so the frontier\n * page table can page individual slots of its always-active slab. Queues the\n * touched rows for upload; the caller must {@link invalidateSort} once per\n * batch since splat depths changed.\n */\n protected overwriteRangeData(handle: SplatRange, data: SplatData, offset: number): void {\n const record = this.ranges.get(handle);\n if (!record) throw new Error('SplatMesh.overwriteRangeData: unknown range handle.');\n if (!Number.isInteger(offset) || offset < 0 || offset + data.count > record.count) {\n throw new RangeError('SplatMesh.overwriteRangeData: write exceeds the range.');\n }\n if (data.count === 0) return;\n this.warnOnIgnoredSh(data, 'overwriteRangeData');\n const destination = record.start + offset;\n this.writeSplatRows(destination, data);\n const width = SplatMesh.DATA_TEXTURE_WIDTH;\n const firstRow = Math.floor(destination / width);\n const lastRow = Math.floor((destination + data.count - 1) / width);\n this.markRowsWritten(firstRow, lastRow - firstRow + 1);\n _appendBox.setFromArray(data.positions);\n this.localBounds.union(_appendBox);\n this.boundsDirty = true;\n // The unified renderer reuses its gathered work buffer while this is\n // unchanged. A paging plan that only relocates survivors leaves the resident\n // count alone, so without this bump the gather (and the sort that follows\n // it) would be skipped and the frame would keep the previous frontier.\n this.contentRevision++;\n }\n\n /**\n * Zeros splats `[offset, offset + count)` of a range so they draw nothing\n * (zero covariance → degenerate quad, zero color). Used to free frontier slab\n * slots that leave the frontier. Queues the rows for upload.\n */\n protected degenerateRange(handle: SplatRange, offset: number, count: number): void {\n const record = this.ranges.get(handle);\n if (!record) throw new Error('SplatMesh.degenerateRange: unknown range handle.');\n if (count <= 0) return;\n if (!Number.isInteger(offset) || offset < 0 || offset + count > record.count) {\n throw new RangeError('SplatMesh.degenerateRange: range out of bounds.');\n }\n const start = record.start + offset;\n const { centers, colors, covarianceA, covarianceB } = this.backing;\n centers.fill(0, start * 4, (start + count) * 4);\n colors.fill(0, start * 4, (start + count) * 4);\n covarianceA.fill(0, start * 4, (start + count) * 4);\n covarianceB.fill(0, start * 4, (start + count) * 4);\n const width = SplatMesh.DATA_TEXTURE_WIDTH;\n const firstRow = Math.floor(start / width);\n const lastRow = Math.floor((start + count - 1) / width);\n this.markRowsWritten(firstRow, lastRow - firstRow + 1);\n this.contentRevision++;\n }\n\n /**\n * Copies one chunk's splats into the pool's backing arrays at `destination`.\n * Shared by the append and staged-write paths, which differ only in where\n * the rows come from and when they are activated.\n */\n private writeSplatRows(destination: number, data: SplatData): void {\n const { centers, colors, covarianceA, covarianceB } = this.backing;\n colors.set(data.colors, destination * 4);\n // Everything loop-invariant is hoisted into locals, including the two\n // optional arrays. `data` reaches here from several construction sites with\n // different shapes (sliced chunks, worker paging plans, whole SplatData), so\n // reading `data.positions` / `data.frontierParent` *inside* the loop makes\n // those megamorphic property loads - measured at ~5k splats/ms, where a\n // 600k-splat paging plan cost ~139 ms of one frame. The body below touches\n // only locals and typed arrays.\n const count = data.count;\n const positions = data.positions;\n const covariances = data.covariances;\n const labels = this.shEnabled && data.sh ? data.sh.labels : null;\n // covarianceB.w is otherwise unused; the frontier cut packs each splat's\n // signed `parent_size` here (0 = unwritten, treated as a root).\n const frontierParent = data.frontierParent ?? null;\n for (let i = 0; i < count; i++) {\n const p = (destination + i) * 4;\n const p3 = i * 3;\n const p6 = i * 6;\n centers[p + 0] = positions[p3 + 0] as number;\n centers[p + 1] = positions[p3 + 1] as number;\n centers[p + 2] = positions[p3 + 2] as number;\n covarianceA[p + 0] = covariances[p6 + 0] as number;\n covarianceA[p + 1] = covariances[p6 + 1] as number;\n covarianceA[p + 2] = covariances[p6 + 2] as number;\n covarianceA[p + 3] = covariances[p6 + 3] as number;\n covarianceB[p + 0] = covariances[p6 + 4] as number;\n covarianceB[p + 1] = covariances[p6 + 5] as number;\n covarianceB[p + 2] = labels ? (labels[i] as number) : 0;\n covarianceB[p + 3] = frontierParent ? (frontierParent[i] as number) : 0;\n }\n this.writePackedSh(destination, data);\n }\n\n /**\n * Scatters a chunk's packed SH words into the per-group pool textures.\n *\n * A chunk without SH still writes: pool rows are reused, so leaving the\n * previous occupant's words behind would give a DC-only chunk somebody\n * else's view-dependent color. Zero is not neutral (the range is signed and\n * rarely symmetric), so the code for \"0.0\" in this scene's range is.\n */\n private writePackedSh(destination: number, data: SplatData): void {\n if (this.packedShBands === 0) return;\n const groups = this.backing.shPacked;\n const wanted = shCoefficientCount(this.packedShBands);\n const source = data.shPacked;\n\n if (source && source.bands === this.packedShBands) {\n this.applyShRange(source);\n // A chunk quantized against a different range than the scene's (a SOG\n // chunk measures its own extent - M11) is requantized into the scene\n // range word-by-word, so every splat decodes through the one pool\n // uniform. When the ranges already match (LCC/`.rad`, or the range-setting\n // first chunk) this is a verbatim copy.\n const target = this.currentShRange();\n const requantize = target !== null && !packedRangesEqual(source.range, target);\n for (let i = 0; i < data.count; i++) {\n for (let c = 0; c < wanted; c++) {\n const word = source.packed[i * wanted + c] as number;\n (groups[c >> 2] as Uint32Array)[(destination + i) * 4 + (c & 3)] = requantize\n ? requantizeShWord(word, source.range, target)\n : word;\n }\n }\n return;\n }\n // Before the range locks, the neutral word is unknowable (word 0 decodes\n // to `range.min` once a range exists - not 0.0). Write 0 for now and\n // remember to backfill every pre-lock row when the first SH chunk arrives.\n if (!this.shRangeSet) this.wrotePreLockNeutralSh = true;\n const neutral = this.neutralShWord();\n for (let i = 0; i < data.count; i++) {\n for (let c = 0; c < wanted; c++) {\n (groups[c >> 2] as Uint32Array)[(destination + i) * 4 + (c & 3)] = neutral;\n }\n }\n }\n\n /**\n * Adopts the first packed-SH chunk's dequantization range as the scene's one\n * pool uniform. LCC/`.rad` chunks already share one range; a streamed SOG\n * chunk measures its own extent, so later chunks are requantized into this\n * one at write time ({@link writePackedSh}) rather than ignored.\n */\n private applyShRange(source: NonNullable<SplatData['shPacked']>): void {\n if (this.shRangeSet) return;\n this.shRange.min.value.set(source.range.min[0], source.range.min[1], source.range.min[2]);\n this.shRange.max.value.set(source.range.max[0], source.range.max[1], source.range.max[2]);\n this.shRangeSet = true;\n\n // SH-less chunks appended before this lock were filled with word 0, which\n // now decodes to `range.min` in every channel - garbage view-dependent\n // color. Every pre-lock write was such a fill (an SH chunk would have\n // locked the range itself), so refilling the whole pool with the real\n // neutral word is correct even across compactions; the range-setting\n // chunk overwrites its own rows right after this. One-time, and only in\n // mixed captures where an SH-less chunk lands first.\n if (this.wrotePreLockNeutralSh) {\n const neutral = this.neutralShWord();\n for (const group of this.backing.shPacked) group.fill(neutral);\n this.markRowsWritten(0, this.poolRows);\n this.wrotePreLockNeutralSh = false;\n }\n }\n\n /** The scene's locked packed-SH range as a plain tuple, or null if unset. */\n private currentShRange(): ShRange | null {\n if (!this.shRangeSet) return null;\n const { min, max } = this.shRange;\n return {\n min: [min.value.x, min.value.y, min.value.z],\n max: [max.value.x, max.value.y, max.value.z],\n };\n }\n\n /** The packed word decoding to 0 in every channel under the scene's range. */\n private neutralShWord(): number {\n const range = this.currentShRange();\n if (!range) return 0;\n return neutralShWordFor(range);\n }\n\n /** Warns once per call site when a source's SH cannot be stored. */\n private warnOnIgnoredSh(data: SplatData, method: string): void {\n if (data.sh && !this.isStatic) {\n warn(\n `SplatMesh.${method}: palette shN data on appended ranges is ignored in dynamic-capacity mode ` +\n '(per-file palettes cannot be merged into a shared pool).',\n );\n }\n if (data.shPacked && this.packedShBands === 0) {\n warn(\n `SplatMesh.${method}: per-splat SH was supplied but the pool has none allocated ` +\n (this.isStatic\n ? '(the static source had no `shPacked` at construction).'\n : '(construct the mesh with `shBands` to store it).'),\n );\n }\n }\n\n /** Atomically includes or excludes a resident range on the next active-list rebuild. */\n protected setRangeActive(handle: SplatRange, active: boolean): void {\n const record = this.ranges.get(handle);\n if (!record) throw new Error('SplatMesh.setRangeActive: unknown range handle.');\n if (record.active === active) return;\n record.active = active;\n if (active) this.activateRecord(record);\n else this.deactivateRecord(record);\n this.contentRevision++;\n }\n\n /**\n * Activates only the first `prefix` splats of a range (see\n * {@link RangeRecord.activePrefix}). The page-table slab keeps its used slots\n * densely packed at the front, so this bounds per-frame sorting and vertex\n * work to the *drawn* frontier instead of the whole pool-sized slab. Write\n * APIs (`overwriteRangeData` / `degenerateRange`) still address the full range.\n */\n protected setRangeActivePrefix(handle: SplatRange, prefix: number): void {\n const record = this.ranges.get(handle);\n if (!record) throw new Error('SplatMesh.setRangeActivePrefix: unknown range handle.');\n const next = Math.max(0, Math.min(record.count, Math.floor(prefix)));\n const current = record.active ? (record.activePrefix ?? record.count) : 0;\n if (next === current) return;\n // Remove the old prefix from the active list, then re-add at the new length.\n if (record.active) {\n this.deactivateRecord(record);\n record.active = false;\n }\n record.activePrefix = next;\n if (next > 0) {\n record.active = true;\n this.activateRecord(record);\n }\n this.contentRevision++;\n }\n\n /**\n * Replaces the drawn pool indices for an immutable static hierarchy.\n *\n * This is intentionally narrower than the range API: subclasses may select\n * an arbitrary hierarchy frontier, but the referenced pool rows must remain\n * resident for the lifetime of the mesh.\n */\n protected replaceActiveIndices(indices: Uint32Array): void {\n const source = this.sourceIndexAttribute.array as Uint32Array;\n if (indices.length > source.length) {\n throw new RangeError('SplatMesh.replaceActiveIndices: frontier exceeds pool capacity.');\n }\n\n this.activeSlotByPoolIndex.fill(0xffffffff);\n for (let slot = 0; slot < indices.length; slot++) {\n const poolIndex = indices[slot] as number;\n if (poolIndex >= source.length) {\n throw new RangeError(\n 'SplatMesh.replaceActiveIndices: frontier contains an invalid pool index.',\n );\n }\n if (this.activeSlotByPoolIndex[poolIndex] !== 0xffffffff) {\n throw new Error(\n 'SplatMesh.replaceActiveIndices: frontier contains duplicate pool indices.',\n );\n }\n source[slot] = poolIndex;\n this.activeSlotByPoolIndex[poolIndex] = slot;\n }\n\n const previousCount = this.activeCount;\n this.activeCount = indices.length;\n this.commitActiveListMutation(0, Math.max(previousCount, this.activeCount));\n this.queryEpoch++;\n this.contentRevision++;\n }\n\n /** Fast identity-frontier variant that avoids allocating a large index array. */\n protected replaceActivePrefix(count: number): void {\n const source = this.sourceIndexAttribute.array as Uint32Array;\n const next = Math.max(0, Math.min(source.length, Math.floor(count)));\n this.activeSlotByPoolIndex.fill(0xffffffff);\n for (let index = 0; index < next; index++) {\n source[index] = index;\n this.activeSlotByPoolIndex[index] = index;\n }\n const previousCount = this.activeCount;\n this.activeCount = next;\n this.commitActiveListMutation(0, Math.max(previousCount, next));\n this.queryEpoch++;\n this.contentRevision++;\n }\n\n /** Replaces a static mesh's resident cut without increasing its fixed pool allocation. */\n protected replaceStaticData(data: SplatData): void {\n if (!this.isStatic || !this.staticRange) {\n throw new Error('SplatMesh.replaceStaticData: mesh was not constructed from static data.');\n }\n if (data.count > this.capacity) {\n throw new RangeError('SplatMesh.replaceStaticData: cut exceeds pool capacity.');\n }\n this.overwriteRangeData(this.staticRange, data, 0);\n this.setRangeActivePrefix(this.staticRange, data.count);\n this.invalidateSort();\n }\n\n private appendRangeWithState(data: SplatData, active: boolean): SplatRange {\n if (data.count === 0) {\n // Zero rows must not touch the free list: allocateRows(0) would return\n // a span start without consuming it, and the matching removeRange would\n // push a degenerate zero-count span.\n const empty: SplatRange = Object.freeze({ count: 0 });\n this.ranges.set(empty, { startRow: 0, rowCount: 0, start: 0, count: 0, active });\n return empty;\n }\n const width = SplatMesh.DATA_TEXTURE_WIDTH;\n const rowCount = Math.ceil(data.count / width);\n const startRow = allocateRowSpan(this.freeRowSpans, rowCount, this.poolRows);\n const start = startRow * width;\n\n this.warnOnIgnoredSh(data, 'appendRange');\n this.writeSplatRows(start, data);\n // Rows shared with removed ranges may hold stale splats past\n // data.count; they are inactive (not in sourceIndex), so harmless.\n\n const handle: SplatRange = Object.freeze({ count: data.count });\n const record: RangeRecord = { startRow, rowCount, start, count: data.count, active };\n this.ranges.set(handle, record);\n this.markRowsWritten(startRow, rowCount);\n\n // Reset per-splat channels for the allocated rows. Rows reused from a\n // removed range still hold the previous occupant's channel values, and\n // nothing else clears them - without this, a new chunk landing on old rows\n // renders wearing the old chunk's mask (\"ghost paint\").\n for (const channel of this.channels.values()) {\n channel.backing.fill(channel.fill, start, start + rowCount * width);\n channel.pendingRows.push({ start: startRow, count: rowCount });\n }\n\n _appendBox.setFromArray(data.positions);\n this.localBounds.union(_appendBox);\n this.boundsDirty = true;\n\n if (active) this.activateRecord(record);\n this.contentRevision++;\n return handle;\n }\n\n /**\n * Deactivates a previously appended range and frees its pool rows for\n * reuse. The bounding sphere used for depth quantization stays a\n * conservative superset until new ranges grow it again.\n */\n removeRange(handle: SplatRange): void {\n const record = this.ranges.get(handle);\n if (!record) throw new Error('SplatMesh.removeRange: unknown range handle.');\n if (record.active) this.deactivateRecord(record);\n this.ranges.delete(handle);\n if (record.rowCount > 0) {\n this.freeRowSpans = releaseRowSpan(this.freeRowSpans, record.startRow, record.rowCount);\n }\n this.contentRevision++;\n }\n\n /** Maximum number of splats the pool can hold (row-aligned internally). */\n get capacity(): number {\n return this.poolRows * SplatMesh.DATA_TEXTURE_WIDTH;\n }\n\n /** Number of splats currently active (drawn and depth-sorted). */\n get activeSplatCount(): number {\n return this.activeCount;\n }\n\n /**\n * Returns this mesh's current pool state for an internal unified gather.\n * Call {@link update} first for streamed sources so the active list reflects\n * the current LOD cut. Consumers must treat the returned GPU resources as\n * read-only; range lifecycle remains owned by this mesh.\n */\n getUnifiedSourceView(): UnifiedSourceView {\n this.updateWorldMatrix(true, false);\n this.refreshSortBounds();\n // The view is rebuilt only when something it reflects actually changed;\n // a steady frame returns the cached object (and its world-bounds sphere)\n // instead of re-deriving both per source per frame. Identity checks on the\n // GPU resources keep the cache safe against texture/graph replacement even\n // if a future mutation path forgets to bump a revision.\n const cached = this.cachedUnifiedView;\n if (\n cached !== null &&\n cached.contentRevision === this.contentRevision &&\n cached.graphRevision === this.graphRevision &&\n cached.activeCount === this.activeCount &&\n cached.sh === this.materialInputs.sh &&\n cached.modifiers === this.modifierList &&\n cached.hasSourcePlacement === (this.perSourceSort !== null) &&\n cached.centersTexture === this.centersTexture &&\n cached.colorsTexture === this.materialInputs.textures.colorsTexture &&\n this.cachedUnifiedViewMatrixWorld.equals(this.matrixWorld) &&\n this.cachedUnifiedViewLocalBounds.equals(this.boundingSphereLocal)\n ) {\n return cached;\n }\n this.cachedUnifiedViewMatrixWorld.copy(this.matrixWorld);\n this.cachedUnifiedViewLocalBounds.copy(this.boundingSphereLocal);\n this.cachedUnifiedViewWorldBounds.copy(this.boundingSphereLocal).applyMatrix4(this.matrixWorld);\n this.cachedUnifiedView = {\n capacity: this.capacity,\n sourceIndex: this.sourceIndexAttribute,\n activeCount: this.activeCount,\n centersTexture: this.centersTexture,\n colorsTexture: this.materialInputs.textures.colorsTexture,\n covarianceATexture: this.materialInputs.textures.covarianceATexture,\n covarianceBTexture: this.materialInputs.textures.covarianceBTexture,\n dataTextureWidth: SplatMesh.DATA_TEXTURE_WIDTH,\n matrixWorld: this.matrixWorld,\n worldBounds: this.cachedUnifiedViewWorldBounds,\n sh: this.materialInputs.sh,\n modifiers: this.modifierList,\n hasSourcePlacement: this.perSourceSort !== null,\n channels: this.channels,\n localCameraPosition: this.localCameraPosition,\n graphRevision: this.graphRevision,\n srgbOutput: this.srgbOutput,\n maxStdDev: this.maxStdDev,\n minSplatSizePx: this.minSplatSizePx,\n antialias: this.antialias,\n projectedFilterProfile: this.projectedFilterProfile,\n // Fixed at construction, so it needs no cache-invalidation key.\n lodAlpha: this.lodAlpha,\n contentRevision: this.contentRevision,\n };\n return this.cachedUnifiedView;\n }\n\n /**\n * Marks this mesh as a source drawn by {@link UnifiedSplatRenderer}.\n * The source stays invisible to the regular scene draw, while its own picker\n * mirrors this resolved visibility so existing per-source hit testing works.\n * Internal consumers should clear the state with `null` before disposing.\n */\n setUnifiedPickVisibility(visible: boolean | null): void {\n this.unifiedPickVisibility = visible;\n }\n\n /**\n * The visibility that actually decides whether this mesh's splats reach the\n * screen: the owning {@link UnifiedSplatRenderer}'s per-source visibility\n * while one owns the draw, else `Object3D.visible`. Consumers that gate on\n * \"is this mesh showing\" - the picker, `CameraBudgetGovernor` - must read\n * this rather than `visible`, which a unified renderer forces to `false` on\n * every source it owns purely to keep the regular scene draw from\n * double-drawing them.\n */\n get effectiveVisibility(): boolean {\n return this.unifiedPickVisibility ?? this.visible;\n }\n\n /**\n * Pool splats still allocatable, in whole rows. Because allocation is\n * row-aligned, an append of n splats fits when\n * `ceil(n / rowWidth) · rowWidth ≤ freeSplatCapacity` *and* a contiguous\n * span exists; callers use this for conservative pre-checks before\n * falling back to {@link compact}.\n */\n get freeSplatCapacity(): number {\n let rows = 0;\n for (const span of this.freeRowSpans) rows += span.count;\n return rows * SplatMesh.DATA_TEXTURE_WIDTH;\n }\n\n /**\n * Declares a named per-splat data channel - one extra value per pool slot,\n * in its own pool-aligned data texture. Channels are opt-in: nothing is\n * allocated until you call this. Read a channel from a modifier with\n * `ctx.channel(name)` and write values per range with {@link writeChannel}.\n *\n * Because a splat keeps its pool row for its whole residency, a channel\n * value follows the splat across depth re-sorts and pool {@link compact}ion\n * - the persistent per-splat label the SDF/selection effects build on. Call\n * this **before** assigning a modifier that reads the channel; a modifier\n * that reads an undeclared channel is a material-build error.\n *\n * @throws {Error} if a channel of this name already exists.\n */\n defineChannel(name: string, options: SplatChannelOptions = {}): void {\n if (this.channels.has(name)) {\n throw new Error(`SplatMesh.defineChannel: channel \"${name}\" already defined.`);\n }\n const type = options.type ?? 'float';\n const width = SplatMesh.DATA_TEXTURE_WIDTH;\n const texelCount = width * this.poolRows;\n const backing = type === 'byte' ? new Uint8Array(texelCount) : new Float32Array(texelCount);\n const fill = options.fill ?? 0;\n if (fill) backing.fill(fill);\n const textureType = type === 'byte' ? THREE.UnsignedByteType : THREE.FloatType;\n const texture = new THREE.DataTexture(\n backing,\n width,\n this.poolRows,\n THREE.RedFormat,\n textureType,\n );\n texture.needsUpdate = true;\n this.channels.set(name, { type, fill, backing, texture, textureType, pendingRows: [] });\n // No material rebuild here: a modifier that reads this channel cannot have\n // been assigned yet (assigning one before its channel exists throws at\n // build time), so no live graph references the new texture.\n }\n\n /**\n * Writes channel values for a resident range, at `[offset, offset + data.length)`\n * splats within it (default `offset` 0). Values ride the same staging-upload\n * path as pool data, flushed on the next {@link update}. `byte` channels take\n * raw `0..255`; `float` channels take the value verbatim.\n *\n * @throws {Error} for an unknown channel or range, or a write past the range.\n */\n writeChannel(range: SplatRange, name: string, data: ArrayLike<number>, offset = 0): void {\n const channel = this.channels.get(name);\n if (!channel) throw new Error(`SplatMesh.writeChannel: channel \"${name}\" is not defined.`);\n const record = this.ranges.get(range);\n if (!record) throw new Error('SplatMesh.writeChannel: unknown range handle.');\n if (offset < 0 || offset + data.length > record.count) {\n throw new Error(\n `SplatMesh.writeChannel: write of ${data.length} at offset ${offset} exceeds ` +\n `range count ${record.count}.`,\n );\n }\n if (data.length === 0) return;\n const width = SplatMesh.DATA_TEXTURE_WIDTH;\n const first = record.start + offset;\n // Both Uint8Array and Float32Array `set` accept any ArrayLike<number>;\n // values are coerced to the backing type (bytes are truncated to 0..255).\n (channel.backing as { set(a: ArrayLike<number>, o: number): void }).set(data, first);\n const startRow = Math.floor(first / width);\n const endRow = Math.floor((first + data.length - 1) / width);\n channel.pendingRows.push({ start: startRow, count: endRow - startRow + 1 });\n this.contentRevision++;\n }\n\n /**\n * Effect hooks, folded into the vertex stage in array order - each\n * modifier sees the running result of the ones before it. Assigning a\n * changed list rebuilds the material (a pipeline recompile); animating a\n * modifier's own uniforms or storage buffers never does. With an empty\n * list the material is exactly the unhooked renderer.\n * See `docs/guide/effects-and-modifiers.md`.\n */\n get modifiers(): readonly SplatModifier[] {\n return this.modifierList;\n }\n\n set modifiers(value: readonly SplatModifier[]) {\n const unchanged =\n value.length === this.modifierList.length &&\n value.every((modifier, i) => modifier === this.modifierList[i]);\n if (unchanged) return;\n const previous = this.modifierList;\n this.modifierList = [...value];\n try {\n this.rebuildGraph();\n } catch (error) {\n // A modifier threw at build time (e.g. reading an undeclared channel).\n // Roll back so the same list can be re-assigned after the caller fixes\n // the cause - otherwise the identity diff above would short-circuit it.\n // Deliberately `buildMaterial`, not `rebuildGraph`: restoring the graph\n // that was already compiled is not a structural change, so it must not\n // bump `graphRevision` (see modifier-slots.test.ts).\n this.modifierList = previous;\n this.buildMaterial(this.materialInputs.textures, this.materialInputs.sh);\n throw error;\n }\n }\n\n /**\n * Recompiles the material graph from the mesh's current inputs and publishes\n * the rebuild (`needsUpdate`, `graphRevision`, picker invalidation).\n *\n * A seam rather than inline code because a subclass can change a *build\n * input* without changing the modifier list - `SplatScene` installs its\n * per-source placement in the constructor, after the base class has already\n * built a placement-free graph. Note `defineChannel` deliberately does *not*\n * rebuild; only structural graph changes come through here.\n *\n * @internal\n */\n protected rebuildGraph(): void {\n this.buildMaterial(this.materialInputs.textures, this.materialInputs.sh);\n (this.material as THREE.Material).needsUpdate = true;\n this.graphRevision++;\n this.picker.markNeedsUpdate();\n }\n\n /**\n * Core projected-2D depth of field. Adds an isotropic screen-space CoC disc\n * after EWA projection (not the stylized M13 scale modifier). Live uniforms\n * - no material rebuild. Pass `aperture: 0` to disable.\n */\n setDepthOfField(settings: Partial<DepthOfFieldSettings>): void {\n const next = clampDepthOfFieldSettings(settings, this.getDepthOfField());\n this.dofFocusDistance.value = next.focusDistance;\n this.dofAperture.value = next.aperture;\n }\n\n /**\n * PlayCanvas-style proxy-mesh relighting. Multiplies baked splat color in the\n * **display** fragment by a screen-space sample of `map` (RGB = lit proxy,\n * A = coverage). Pass `null` to disable (`blend → 0`, placeholder map).\n *\n * Blend / brightness / background are live uniforms (no rebuild). Changing\n * the map texture identity rebuilds the material once. Does not affect the\n * pick pass. Does not invalidate a unified gather cache.\n */\n setRelighting(options: RelightingSettings | null): void {\n if (options === null) {\n this.relightBlend.value = 0;\n if (this.relightMap !== this.relightPlaceholder) {\n this.relightMap = this.relightPlaceholder;\n this.rebuildGraph();\n }\n return;\n }\n const next = clampRelightingSettings(options, this.getRelighting());\n this.relightBlend.value = next.blend;\n this.relightBrightness.value = next.brightness;\n this.relightBackground.value = next.background;\n this.relightSoftness.value = next.softness;\n if (options.map !== this.relightMap) {\n this.relightMap = options.map;\n this.rebuildGraph();\n }\n }\n\n /** Current relight numeric uniforms (`blend === 0` means off). */\n getRelighting(): RelightingUniforms {\n return {\n blend: this.relightBlend.value,\n brightness: this.relightBrightness.value,\n background: this.relightBackground.value,\n softness: this.relightSoftness.value,\n };\n }\n\n /**\n * Moves the screen-radius band (px) without rebuilding the material.\n *\n * Only meaningful on a mesh constructed *with* a band - the graph decides at\n * build time whether to cull on radius at all, from\n * {@link SplatMeshOptions.minSplatScreenRadius} / `maxSplatScreenRadius`.\n *\n * A foveated mesh uses this to keep the band aligned with its LOD cut: the\n * band spans roughly one level, so refining the cut to spend spare budget\n * selects smaller splats, and a band left where it was would cull precisely\n * the detail that refinement bought.\n */\n protected setScreenRadiusBand(minPx: number, maxPx: number): void {\n this.screenBandMin.value = Math.max(0, minPx);\n this.screenBandMax.value = Math.max(0, maxPx);\n }\n\n /** Current core DoF uniforms (`aperture === 0` means off). */\n getDepthOfField(): DepthOfFieldSettings {\n return {\n focusDistance: this.dofFocusDistance.value,\n aperture: this.dofAperture.value,\n };\n }\n\n /**\n * Packs the resident ranges toward the start of the pool, removing the\n * gaps that add/remove churn leaves behind and restoring a single\n * contiguous free span. Use this when {@link appendRange} throws despite\n * enough total free rows (row-alignment fragmentation).\n *\n * The CPU backing arrays are authoritative, so no data is re-fetched;\n * moved rows are re-uploaded to the GPU on the next {@link update}.\n */\n compact(): void {\n this.pool.compact();\n }\n\n /** {@link SplatPoolTenant}: the ranges this mesh holds in the pool. */\n poolRanges(): Iterable<SplatPoolRange> {\n return this.ranges.values();\n }\n\n /**\n * {@link SplatPoolTenant}: follow one range to its new rows. The pool has\n * already moved the splat data; this moves what the mesh keys by pool row.\n */\n relocatePoolRange(range: SplatPoolRange, targetRow: number): void {\n const record = range as RangeRecord;\n const width = SplatMesh.DATA_TEXTURE_WIDTH;\n // Channels are single-component and pool-row-aligned, so a range's channel\n // data relocates alongside its splats - this is what keeps a painted mask\n // attached to the splat across compaction.\n for (const channel of this.channels.values()) {\n const cFrom = record.startRow * width;\n const cTo = targetRow * width;\n const cLength = record.rowCount * width;\n channel.backing.copyWithin(cTo, cFrom, cFrom + cLength);\n channel.pendingRows.push({ start: targetRow, count: record.rowCount });\n }\n record.startRow = targetRow;\n record.start = targetRow * width;\n this.markRowsWritten(targetRow, record.rowCount);\n }\n\n /** {@link SplatPoolTenant}: rebuild everything keyed by pool index. */\n onPoolCompacted(): void {\n this.queryEpoch++; // rows moved, so pool-index → position changed\n // Pool indices changed, so rebuild immediately before another public pool\n // mutation can consult the reverse active-slot map.\n this.rebuildActiveList();\n this.contentRevision++;\n }\n\n /**\n * Per-frame update: uploads pending pool writes, refreshes the projection\n * uniforms and requests a depth re-sort when the camera or the content\n * has changed. Call this every frame, before `renderer.render`.\n */\n update(\n camera: THREE.PerspectiveCamera,\n renderer: THREE.WebGPURenderer,\n options: SplatUpdateOptions = {},\n ): void {\n // A render loop can outlive the mesh by a frame; updating after dispose\n // would recreate a sorter and upload into disposed textures.\n if (this.disposed) return;\n this.lastRenderer = renderer;\n this.assertPoolFitsDevice(renderer);\n camera.updateMatrixWorld();\n this.updateWorldMatrix(true, false);\n this.updateTimings.activeListMs = 0;\n this.updateTimings.uploadMs = 0;\n this.updateTimings.sortSubmitMs = 0;\n this.updateTimings.stagingTextureAllocations = 0;\n this.updateTimings.activeListUpdateRanges = 0;\n const uploadStartedAt = performance.now();\n this.flushPendingUploads(renderer);\n this.updateTimings.uploadMs = performance.now() - uploadStartedAt;\n // Resolve the drawn view once: XR gives a per-eye viewport and a separate\n // head pose, everything else the whole canvas. Both consumers below must\n // agree on the size - `adaptFoveationLimit` and `writeViewUniforms` share\n // `foveationLimitPx` through a px→normalized conversion that each does\n // with its own focal length, so a size mismatch makes that loop diverge.\n const xrView = resolveXrView(camera, renderer);\n let projectionCamera: THREE.Camera = camera;\n let sortCamera: THREE.Camera = camera;\n let viewWidth: number;\n let viewHeight: number;\n if (xrView) {\n // Per-eye projection reaches the shader through three's per-render-view\n // TSL nodes; the hand-managed uniforms take the eye's focal/viewport and\n // the head's position (cyclopean - at a ~63 mm IPD the per-eye SH/order\n // difference is imperceptible). One sort from the head serves both eyes;\n // a per-eye re-sort would double the frame's dominant cost.\n projectionCamera = xrView.eye;\n sortCamera = xrView.head;\n viewWidth = xrView.width;\n viewHeight = xrView.height;\n } else {\n renderer.getDrawingBufferSize(_viewSize);\n viewWidth = _viewSize.x;\n viewHeight = _viewSize.y;\n }\n this.adaptFoveationLimit(projectionCamera, viewHeight);\n this.writeViewUniforms(projectionCamera, viewWidth, viewHeight, sortCamera);\n this.updateTimings.activeListUpdateRanges = this.sourceIndexAttribute.updateRanges.length;\n if (options.sort !== false) {\n const sortStartedAt = performance.now();\n this.requestSortIfNeeded(sortCamera, renderer);\n this.updateTimings.sortSubmitMs = performance.now() - sortStartedAt;\n }\n }\n\n /** Returns the render-preparation CPU timings for the current update. */\n protected getUpdateTimings(): Readonly<{\n activeListMs: number;\n uploadMs: number;\n sortSubmitMs: number;\n stagingTextureAllocations: number;\n activeListUpdateRanges: number;\n }> {\n return this.updateTimings;\n }\n\n /**\n * Skips one otherwise ordinary sort request, but only while the active list\n * is unchanged since the last sort. Streamed staging uses this immediately\n * before a forced commit sort so the GPU queue has a frame to drain; a\n * concurrent swap group that mutates the active list revokes the request,\n * because the depth order must always describe the list being drawn.\n */\n protected deferNextSortRequest(): void {\n this.deferSortRequestOnce = true;\n }\n\n /**\n * Once, on the first update (when the backend is known), fails loudly if the\n * pool's `2048 × poolRows` data textures exceed the device's maximum texture\n * dimension - otherwise the upload fails with a cryptic backend error and a\n * frozen canvas. Streamed scenes stay under the limit (budget-bounded); this\n * guards very large *static* scenes, especially on mobile (≈8192 max → about\n * 16.7 M splats). If the limit can't be read, the check is skipped rather\n * than risk a false rejection.\n */\n private assertPoolFitsDevice(renderer: THREE.WebGPURenderer): void {\n if (this.textureLimitChecked) return;\n this.textureLimitChecked = true;\n const maxSize = deviceMaxTextureSize(renderer);\n if (maxSize > 0 && this.poolRows > maxSize) {\n const width = SplatMesh.DATA_TEXTURE_WIDTH;\n throw new Error(\n `SplatMesh: this scene needs a ${width}×${this.poolRows} data texture, but this ` +\n `device caps texture dimensions at ${maxSize} (about ` +\n `${(maxSize * width).toLocaleString('en-US')} splats). Stream it with ` +\n `StreamedSplatMesh, or reduce the splat count.`,\n );\n }\n }\n\n /** Bounding box of the appended splats, in this mesh's local space. */\n computeSplatBounds(): THREE.Box3 {\n return this.localBounds.clone();\n }\n\n /**\n * Asynchronously picks the frontmost visible splat under an NDC coordinate.\n *\n * Renders a one-pixel GPU depth pass (shared projection/Gaussian math with\n * the display material), reads the encoded view depth, and reconstructs a\n * world-space point on the splat's rendered center plane. Concurrent calls\n * on this mesh are serialized. Returns `null` for misses, empty meshes,\n * out-of-canvas coordinates, or after {@link dispose}.\n *\n * Does not return a persistent splat id - LOD streaming and pool reuse make\n * identity unstable across frames.\n */\n pick(\n ndc: THREE.Vector2,\n camera: THREE.Camera,\n renderer: THREE.WebGPURenderer,\n options?: SplatPickOptions,\n ): Promise<SplatPickResult | null> {\n return this.picker.pick(ndc, camera, renderer, options);\n }\n\n /**\n * The resident splat center nearest a world point, within `radius` (world\n * units), or `null` if none. A synchronous CPU query over the pool's decoded\n * centers - no GPU round-trip - backed by a uniform grid rebuilt only when\n * the resident set changes (M9). The primitive behind measurement markers and\n * proximity tests.\n *\n * **Resident-only:** a {@link StreamedSplatMesh} searches only the splats\n * currently in the pool (the LOD the camera has resolved), so the answer is\n * the nearest *loaded* splat - coarser far from the camera, absent where no\n * chunk has streamed in. A static mesh searches its whole scene.\n *\n * World-correct under any rotation + per-axis (including non-uniform) scale:\n * candidates are gathered in local space with a conservative radius\n * (`radius / min axis scale`) and then ranked by **world** distance, so the\n * nearest-in-world splat wins even when the axes stretch differently. Shear\n * in an ancestor transform is not supported. Splats displaced by GPU\n * modifiers are queried at their undisplaced CPU positions.\n */\n queryNearest(worldPoint: THREE.Vector3, radius: number): SplatNearestResult | null {\n if (this.activeCount === 0 || !(radius >= 0)) return null;\n this.updateWorldMatrix(true, false);\n _queryLocal.copy(worldPoint);\n this.worldToLocal(_queryLocal);\n const grid = this.ensureQueryGrid();\n // A world sphere of `radius` maps into local space within\n // radius / minAxisScale of the local query point; gather that superset,\n // then rank each candidate by its true world distance.\n const gather = radius / this.queryWorldMinScale();\n const r2 = radius * radius;\n let bestIndex = -1;\n let bestSq = Infinity;\n grid.forEachWithin(_queryLocal.x, _queryLocal.y, _queryLocal.z, gather, (poolIndex) => {\n this.splatWorldPosition(poolIndex, _queryWorld);\n const d2 = _queryWorld.distanceToSquared(worldPoint);\n if (d2 <= r2 && d2 < bestSq) {\n bestSq = d2;\n bestIndex = poolIndex;\n }\n });\n if (bestIndex < 0) return null;\n const point = this.splatWorldPosition(bestIndex, new THREE.Vector3());\n return { point, distance: Math.sqrt(bestSq) };\n }\n\n /**\n * Returns the first resident splat center inside a world-space ray cone.\n * This is a synchronous CPU query, intended for interactions that cannot\n * wait for (or rely on) a GPU readback. `radiusAtUnitDistance` is the cone's\n * world-space radius one unit from the origin; `minimumRadius` keeps nearby\n * point-like splats practical to target.\n *\n * Like {@link queryNearest}, this searches resident, undisplaced CPU centers.\n */\n queryRay(\n ray: THREE.Ray,\n radiusAtUnitDistance = 0.025,\n minimumRadius = 0.05,\n ): SplatRayResult | null {\n if (\n this.activeCount === 0 ||\n !(radiusAtUnitDistance >= 0) ||\n !(minimumRadius >= 0) ||\n ray.direction.lengthSq() === 0\n ) {\n return null;\n }\n this.updateWorldMatrix(true, false);\n const active = (this.sourceIndexAttribute.array as Uint32Array).subarray(0, this.activeCount);\n let bestIndex = -1;\n let bestDistance = Infinity;\n for (const poolIndex of active) {\n this.splatWorldPosition(poolIndex, _queryWorld);\n _queryLocal.subVectors(_queryWorld, ray.origin);\n const distance = _queryLocal.dot(ray.direction);\n if (distance < 0 || distance >= bestDistance) continue;\n _queryLocal.addScaledVector(ray.direction, -distance);\n const radius = Math.max(minimumRadius, distance * radiusAtUnitDistance);\n if (_queryLocal.lengthSq() <= radius * radius) {\n bestIndex = poolIndex;\n bestDistance = distance;\n }\n }\n if (bestIndex < 0) return null;\n return {\n point: this.splatWorldPosition(bestIndex, new THREE.Vector3()),\n distance: bestDistance,\n };\n }\n\n /**\n * The supporting surface beneath a world point: the highest resident splat\n * that lies no more than `maxDrop` below it (world −Y) and within `radius`\n * horizontally, or `null` if none. A downward probe over splat centers - the\n * primitive behind floor-following and teleport validation - without a GPU\n * pick or a collision mesh (M9), so it works for every format, not only the\n * `.lcc2` captures that ship collision geometry.\n *\n * `radius` (default `maxDrop / 2`) is the horizontal tolerance: splats are\n * points, so an exact vertical hit is unlikely and a small disc is searched.\n * Resident-only semantics and the world-correctness contract (rotation +\n * per-axis scale supported, shear not) are the same as {@link queryNearest};\n * every candidate is judged in world space, where the −Y probe direction is\n * defined, so the vertical test never assumes local axis alignment.\n */\n queryHeight(\n worldPoint: THREE.Vector3,\n maxDrop: number,\n radius = maxDrop / 2,\n ): SplatHeightResult | null {\n if (this.activeCount === 0 || !(maxDrop >= 0) || !(radius >= 0)) return null;\n this.updateWorldMatrix(true, false);\n _queryLocal.copy(worldPoint);\n this.worldToLocal(_queryLocal);\n const grid = this.ensureQueryGrid();\n // A supporting splat is at most `maxDrop` below and `radius` aside, so it\n // lies within (maxDrop + radius) of the query in world units - gather that\n // sphere mapped conservatively to local space (divide by the smallest axis\n // scale), then judge each candidate in world space where \"down\" is\n // unambiguous.\n const gatherLocal = (maxDrop + radius) / this.queryWorldMinScale();\n const r2 = radius * radius;\n let bestY = -Infinity;\n let bestIndex = -1;\n grid.forEachWithin(_queryLocal.x, _queryLocal.y, _queryLocal.z, gatherLocal, (poolIndex) => {\n this.splatWorldPosition(poolIndex, _queryWorld);\n const drop = worldPoint.y - _queryWorld.y;\n if (drop < 0 || drop > maxDrop) return; // above the point, or too far below\n const dx = _queryWorld.x - worldPoint.x;\n const dz = _queryWorld.z - worldPoint.z;\n if (dx * dx + dz * dz > r2) return; // outside the horizontal disc\n if (_queryWorld.y > bestY) {\n bestY = _queryWorld.y;\n bestIndex = poolIndex;\n }\n });\n if (bestIndex < 0) return null;\n const point = this.splatWorldPosition(bestIndex, new THREE.Vector3());\n return { point, drop: worldPoint.y - point.y };\n }\n\n /**\n * Renders this mesh from a second camera - into `target`, or the canvas when\n * omitted - with depth order **and** projection correct for *that* camera\n * (M10). The primitive behind mirrors, portals, split panes, and thumbnails.\n *\n * Depth order is view-dependent, but a mesh keeps one sorted order buffer:\n * `update()` sorts it for the single camera it is given, so a second view\n * drawn with that order shows transparency/pop errors. `renderView` re-sorts\n * for `camera` into the shared buffer, sets the view-dependent uniforms\n * (viewport from `target`), draws, and marks the primary order stale so the\n * next `update()` re-sorts for the main camera. GPU submissions execute in\n * order, so each view's draw reads the order and uniforms it just wrote.\n *\n * Call once per extra view per frame, before or after the primary\n * `update()` + render - both are correct. On the WebGL2 fallback the sorter\n * is an asynchronous worker owned by the primary view, so `renderView` does\n * not sort at all there: the secondary view draws with the primary view's\n * order (projection is still per-camera correct) - a documented single-view\n * ordering limitation; WebGPU gets exact per-view order.\n *\n * @param target - Destination render target, or `null`/omitted for the canvas.\n */\n renderView(\n camera: THREE.PerspectiveCamera,\n renderer: THREE.WebGPURenderer,\n target: THREE.RenderTarget | null = null,\n ): void {\n if (this.disposed) return;\n this.lastRenderer = renderer;\n camera.updateMatrixWorld();\n this.updateWorldMatrix(true, false);\n this.flushPendingUploads(renderer);\n if (target) _viewSize.set(target.width, target.height);\n else renderer.getDrawingBufferSize(_viewSize);\n this.writeViewUniforms(camera, _viewSize.x, _viewSize.y);\n const sorted = this.sortForView(camera, renderer);\n\n const previousTarget = renderer.getRenderTarget();\n try {\n renderer.setRenderTarget(target);\n renderer.render(this, camera);\n } finally {\n renderer.setRenderTarget(previousTarget);\n // The shared order buffer now holds this view's order; force the primary\n // view to re-sort even when the secondary draw throws. Only when a sort\n // actually ran: WebGL2 still holds the primary order and must not fight\n // its asynchronous worker for a redundant re-sort.\n if (sorted) this.orderIsForeign = true;\n }\n }\n\n /**\n * Sorts the shared order buffer for a secondary view's camera, bypassing the\n * primary view's sort scheduler and its `lastSortedModelView` record (that\n * state belongs to `update()`'s camera). WebGPU dispatches synchronously into\n * the render queue, so the following draw reads this order.\n */\n private sortForView(camera: THREE.Camera, renderer: THREE.WebGPURenderer): boolean {\n if (this.activeCount === 0) return false;\n // The WebGL2 worker sorter is asynchronous and shared with the primary\n // view: submitting the secondary camera here would land the *secondary*\n // order between frames and claim the worker every frame ahead of the\n // primary's request - permanently starving the main view of its own sort.\n // Skip instead; the secondary view draws with the primary order (the\n // documented WebGL2 limitation).\n const isWebGPU = (renderer.backend as { isWebGPUBackend?: boolean }).isWebGPUBackend === true;\n if (!isWebGPU) return false;\n this.currentModelView.multiplyMatrices(camera.matrixWorldInverse, this.matrixWorld);\n this.refreshSortBounds();\n this.sorter ??= this.createSorter(renderer);\n if (!this.sorter) return false;\n this.sorter.sort(this.currentModelView, this.activeCount, this.boundingSphereLocal);\n return true;\n }\n\n /** Rebuilds the query grid if the resident set changed since it was built. */\n private ensureQueryGrid(): UniformGrid {\n if (this.queryGrid && this.queryGridEpoch === this.queryEpoch) return this.queryGrid;\n const active = (this.sourceIndexAttribute.array as Uint32Array).subarray(0, this.activeCount);\n this.queryGrid = new UniformGrid(this.backing.centers, active, this.activeCount);\n this.queryGridEpoch = this.queryEpoch;\n return this.queryGrid;\n }\n\n /** World-space position of pool splat `poolIndex`, written into `out`. */\n private splatWorldPosition(poolIndex: number, out: THREE.Vector3): THREE.Vector3 {\n const base = poolIndex * 4;\n out.set(\n this.backing.centers[base] as number,\n this.backing.centers[base + 1] as number,\n this.backing.centers[base + 2],\n );\n return out.applyMatrix4(this.matrixWorld);\n }\n\n /**\n * The smallest world axis scale, for conservatively mapping a world radius\n * into local space: a world sphere of radius `r` fits inside a local sphere\n * of `r / minAxisScale`, for any per-axis (non-uniform) scale.\n */\n private queryWorldMinScale(): number {\n this.getWorldScale(_queryScale);\n // decompose() negates scale.x for mirrored (negative-determinant)\n // transforms; a mirror preserves distances, but a negative radius would\n // silently null every query.\n return Math.min(Math.abs(_queryScale.x), Math.abs(_queryScale.y), Math.abs(_queryScale.z)) || 1;\n }\n\n /**\n * Releases every GPU resource and worker this mesh owns. Idempotent: a\n * second call is a no-op, and a render loop that outlives the mesh by a\n * frame is safe - {@link update}, {@link renderView} and {@link pick} all\n * become no-ops after dispose.\n */\n dispose(): void {\n if (this.disposed) return;\n this.disposed = true;\n this.sorter?.dispose();\n this.sorter = null;\n this.geometry.dispose();\n (this.material as THREE.Material).dispose();\n this.picker.dispose();\n this.relightPlaceholder.dispose();\n // Leaving the pool releases this mesh's rows: `compact` accounts for every\n // row against a registered tenant, so a departing one must take its\n // allocations with it.\n for (const record of this.ranges.values())\n this.pool.releaseRows(record.startRow, record.rowCount);\n this.pool.unregister(this);\n // The pool's textures are disposed only by whoever owns the pool. A mesh\n // that built its own pool owns it; one handed a shared pool does not.\n if (this.ownsPool) this.pool.dispose();\n for (const texture of this.dataTextures) {\n if (!this.ownsPool && this.pool.isPoolTexture(texture)) continue;\n texture.dispose();\n }\n for (const channel of this.channels.values()) channel.texture.dispose();\n this.channels.clear();\n for (const cache of this.uploadStaging.values()) {\n for (const texture of cache.values()) texture.dispose();\n }\n this.uploadStaging.clear();\n this.halfEncodeBuffers.clear();\n // `sourceIndex` never sits in a geometry, so `geometry.dispose()` cannot\n // free its GPU buffer - release it through the renderer that uploaded it\n // (4 B/splat leaked per scene swap otherwise).\n if (this.lastRenderer) {\n releaseRendererAttributes(this.lastRenderer, [this.sourceIndexAttribute]);\n this.lastRenderer = null;\n }\n // Drop queued CPU work and cached references so nothing uploads or\n // rebuilds after teardown, and large arrays are unreachable promptly.\n this.pendingUploadRows = [];\n this.workerDirtyRows = [];\n this.queryGrid = null;\n this.ranges.clear();\n }\n\n /** Matches the viewport / focal / local-camera uniforms written by {@link update}. */\n private refreshProjectionUniforms(camera: THREE.Camera, renderer: THREE.WebGPURenderer): void {\n renderer.getDrawingBufferSize(_viewSize);\n this.writeViewUniforms(camera, _viewSize.x, _viewSize.y);\n }\n\n /**\n * Writes the three view-dependent uniforms - viewport, focal (splat screen\n * size), and the camera position in mesh-local space (SH view direction +\n * screen-radius math) - for a camera drawing into a viewport of the given\n * pixel size. {@link update} passes the canvas size (or, while an XR session\n * presents, the per-eye viewport); {@link renderView} passes its render\n * target's size.\n *\n * @param positionCamera - Camera whose world position seeds the SH view\n * direction and screen-radius math. Defaults to `camera`; in XR it is the\n * head, so both eyes shade from one cyclopean point while each still\n * projects through its own eye.\n */\n private writeViewUniforms(\n camera: THREE.Camera,\n viewportX: number,\n viewportY: number,\n positionCamera: THREE.Camera = camera,\n ): void {\n this.viewport.value.set(viewportX, viewportY);\n const projection = camera.projectionMatrix.elements;\n const focalY = (projection[5] * viewportY) / 2;\n this.focal.value.set((projection[0] * viewportX) / 2, focalY);\n positionCamera.getWorldPosition(this.localCameraPosition.value);\n this.worldToLocal(this.localCameraPosition.value);\n // Frontier cut: a splat draws while its projected node size ≈ target px, i.e.\n // own_size · focalY / distance ≤ targetPx ⇔ own_size / distance ≤ targetPx / focalY.\n // focalY is the vertical focal length in px, matching the view-space depth the\n // material uses as `distance`.\n if (this.foveationMode === 'frontier' && focalY > 0) {\n this.pixelScaleLimit.value = this.foveationLimitPx / focalY;\n }\n }\n\n /**\n * Self-adjusts {@link foveationLimitPx} so the frontier cut's drawn-splat count\n * tracks {@link foveationDrawBudget} (Spark's `maxSplats` feedback). Estimates\n * the drawn count by replaying the cut on a strided sample of the pool, then\n * coarsens (grows the limit) when over budget and refines (shrinks toward the\n * finest {@link foveationTargetPx}) when comfortably under. Throttled; a no-op\n * outside `'frontier'` mode.\n *\n * `viewportY` must be the **same** height {@link writeViewUniforms} is given\n * for this frame - the two share `foveationLimitPx` in pixels and each\n * converts it with its own `focalY`, so a mismatch (the full drawing buffer\n * here against a per-eye viewport there) makes the estimator judge a cut the\n * shader is not applying, and the ratchet runs away toward the maximum.\n */\n private adaptFoveationLimit(camera: THREE.Camera, viewportY: number): void {\n if (this.foveationMode !== 'frontier') return;\n const now = performance.now();\n if (now - this.lastFoveationAdaptAt < FOVEATION_ADAPT_MS) return;\n this.lastFoveationAdaptAt = now;\n\n const focalY = (camera.projectionMatrix.elements[5] * viewportY) / 2;\n if (!(focalY > 0)) return;\n const limit = this.foveationLimitPx / focalY;\n camera.getWorldPosition(_camLocal);\n this.worldToLocal(_camLocal);\n // model-view-projection into mesh-local space, so the estimate can frustum-\n // cull exactly like the material's `isVisible` - the budget is about splats\n // actually *on screen*, not the whole resident set (most of which sits behind\n // or beside the camera). Without this the limit over-coarsens ~6× and blurs.\n _adaptView.copy(camera.matrixWorld).invert();\n _adaptMvp.multiplyMatrices(_adaptView, this.matrixWorld).premultiply(camera.projectionMatrix);\n const m = _adaptMvp.elements;\n\n // Replay the material's frontier test (own_size from covariance trace,\n // signed parent_size in covarianceB.w) on every Nth in-frustum slot.\n const { covarianceA: cA, covarianceB: cB, centers } = this.backing;\n const slots = cB.length / 4;\n let sampled = 0;\n let drawn = 0;\n for (let i = 0; i < slots; i += FOVEATION_ADAPT_STRIDE) {\n const base = i * 4;\n const packedParent = cB[base + 3] as number;\n if (packedParent === 0) continue; // empty slot or non-frontier splat\n sampled++;\n const cx = centers[base] as number;\n const cy = centers[base + 1] as number;\n const cz = centers[base + 2] as number;\n // Frustum cull (clip-space, margin 1.2·w to match the material).\n const clipW = m[3] * cx + m[7] * cy + m[11] * cz + m[15];\n const margin = clipW * 1.2;\n const clipZ = m[2] * cx + m[6] * cy + m[10] * cz + m[14];\n if (clipZ <= -margin) continue;\n const clipX = m[0] * cx + m[4] * cy + m[8] * cz + m[12];\n if (clipX > margin || clipX < -margin) continue;\n const clipY = m[1] * cx + m[5] * cy + m[9] * cz + m[13];\n if (clipY > margin || clipY < -margin) continue;\n\n const trace = (cA[base] as number) + (cA[base + 3] as number) + (cB[base + 1] as number);\n const ownSize = 2 * Math.sqrt(Math.max(trace, 0) / 3);\n const isLeaf = packedParent < 0;\n const parentSize = Math.abs(packedParent);\n const dx = cx - _camLocal.x;\n const dy = cy - _camLocal.y;\n const dz = cz - _camLocal.z;\n const limitDist = limit * Math.sqrt(dx * dx + dy * dy + dz * dz);\n const ownCut = isLeaf ? 0 : ownSize;\n if (parentSize > limitDist && ownCut <= limitDist) drawn++;\n }\n if (sampled === 0) return;\n\n const estimatedDrawn = drawn * FOVEATION_ADAPT_STRIDE;\n const budget = this.foveationDrawBudget;\n if (estimatedDrawn > budget * 1.1) {\n this.foveationLimitPx = Math.min(this.foveationLimitPx * 1.15, FOVEATION_LIMIT_MAX_PX);\n } else if (estimatedDrawn < budget * 0.7) {\n this.foveationLimitPx = Math.max(this.foveationLimitPx / 1.12, this.foveationTargetPx);\n }\n }\n\n /** Records a written row span for GPU upload and the sort-worker mirror. */\n private markRowsWritten(start: number, count: number): void {\n this.pendingUploadRows.push({ start, count });\n // The worker mirror only needs deltas once a WorkerSorter exists; it\n // snapshots the whole pool at creation time.\n if (this.sorter?.kind === 'worker') this.workerDirtyRows.push({ start, count });\n }\n\n /** Appends one newly active pool range to the packed source-index list. */\n private activateRecord(record: RangeRecord): void {\n const count = record.activePrefix ?? record.count;\n if (count === 0) return;\n this.queryEpoch++;\n const source = this.sourceIndexAttribute.array as Uint32Array;\n const identity = this.getPoolIndexTemplate();\n const activeStart = this.activeCount;\n source.set(identity.subarray(record.start, record.start + count), activeStart);\n this.activeSlotByPoolIndex.set(\n identity.subarray(activeStart, activeStart + count),\n record.start,\n );\n this.activeCount += count;\n this.commitActiveListMutation(activeStart, count);\n }\n\n /**\n * Removes one active range and backfills its holes from the packed tail.\n * The common contiguous case uses one native block copy; the fallback\n * handles a range split by earlier tail backfills in O(record.count).\n */\n private deactivateRecord(record: RangeRecord): void {\n const activeLen = record.activePrefix ?? record.count;\n if (activeLen === 0) return;\n this.queryEpoch++;\n const source = this.sourceIndexAttribute.array as Uint32Array;\n const firstSlot = this.activeSlotByPoolIndex[record.start] as number;\n let contiguous = true;\n for (let index = 1; index < activeLen; index++) {\n if (this.activeSlotByPoolIndex[record.start + index] !== firstSlot + index) {\n contiguous = false;\n break;\n }\n }\n\n let dirtyStart = this.activeCount;\n let dirtyEnd = 0;\n if (contiguous) {\n const nextActiveCount = this.activeCount - activeLen;\n const copyCount = Math.min(activeLen, Math.max(0, nextActiveCount - firstSlot));\n if (copyCount > 0) {\n source.copyWithin(firstSlot, this.activeCount - copyCount, this.activeCount);\n for (let index = 0; index < copyCount; index++) {\n const movedPoolIndex = source[firstSlot + index] as number;\n this.activeSlotByPoolIndex[movedPoolIndex] = firstSlot + index;\n }\n dirtyStart = firstSlot;\n dirtyEnd = firstSlot + copyCount;\n }\n this.activeCount = nextActiveCount;\n } else {\n for (let poolIndex = record.start; poolIndex < record.start + activeLen; poolIndex++) {\n const slot = this.activeSlotByPoolIndex[poolIndex] as number;\n const lastSlot = this.activeCount - 1;\n if (slot !== lastSlot) {\n const movedPoolIndex = source[lastSlot] as number;\n source[slot] = movedPoolIndex;\n this.activeSlotByPoolIndex[movedPoolIndex] = slot;\n dirtyStart = Math.min(dirtyStart, slot);\n dirtyEnd = Math.max(dirtyEnd, slot + 1);\n }\n this.activeCount--;\n }\n dirtyEnd = Math.min(dirtyEnd, this.activeCount);\n }\n\n this.commitActiveListMutation(dirtyStart, Math.max(0, dirtyEnd - dirtyStart));\n }\n\n /** Uploads only the changed packed slots and invalidates the current depth order. */\n private commitActiveListMutation(start: number, count: number): void {\n if (count > 0) {\n addMergedUpdateRange(this.sourceIndexAttribute, start, count);\n this.sourceIndexAttribute.needsUpdate = true;\n }\n\n // Until the asynchronous WebGL worker returns, keep its draw list on a\n // valid unsorted active permutation. WebGPU compute sorters overwrite\n // the draw storage themselves and do not need this CPU mirror.\n if (this.sorter?.kind === 'worker' || this.sorter === null) {\n const source = this.sourceIndexAttribute.array as Uint32Array;\n const draw = this.splatIndexAttribute.array as Float32Array;\n if (this.drawListSorted) {\n // The draw list holds a depth permutation; a slot-wise patch (or a\n // bare instanceCount truncation on removal, count === 0 here) would\n // draw removed splats twice and drop live ones until the worker's\n // next order arrives. Resync the whole drawn prefix to the identity\n // active order instead - one frame unsorted beats frames of garbage.\n // Later mutations in the same tick take the cheap patch path below.\n draw.set(source.subarray(0, this.activeCount));\n if (this.activeCount > 0) {\n addMergedUpdateRange(this.splatIndexAttribute, 0, this.activeCount);\n }\n this.splatIndexAttribute.needsUpdate = true;\n this.drawListSorted = false;\n } else if (count > 0) {\n draw.set(source.subarray(start, start + count), start);\n addMergedUpdateRange(this.splatIndexAttribute, start, count);\n this.splatIndexAttribute.needsUpdate = true;\n }\n }\n (this.geometry as THREE.InstancedBufferGeometry).instanceCount = this.activeCount;\n this.activeListVersion++;\n this.sortScheduler.invalidateContent();\n }\n\n /** Rewrites the active-splat list (pool indices, range by range). */\n private rebuildActiveList(): void {\n const source = this.sourceIndexAttribute.array as Uint32Array;\n const identity = this.getPoolIndexTemplate();\n let cursor = 0;\n for (const record of this.ranges.values()) {\n if (!record.active) continue;\n // Honor a partial activation: only the used prefix of a page-table slab\n // participates (matching activateRecord), never the whole allocation.\n const count = record.activePrefix ?? record.count;\n source.set(identity.subarray(record.start, record.start + count), cursor);\n this.activeSlotByPoolIndex.set(identity.subarray(cursor, cursor + count), record.start);\n cursor += count;\n }\n this.activeCount = cursor;\n this.sourceIndexAttribute.clearUpdateRanges();\n if (cursor > 0) this.sourceIndexAttribute.addUpdateRange(0, cursor);\n this.sourceIndexAttribute.needsUpdate = true;\n\n // Without a GPU sorter the draw list must hold the active pool indices\n // itself, or the first instances would render pool slots 0..n-1\n // regardless of where the active ranges live. The CPU sorter then\n // replaces this identity order asynchronously.\n if (this.sorter?.kind === 'worker' || this.sorter === null) {\n const draw = this.splatIndexAttribute.array as Float32Array;\n draw.set(source.subarray(0, cursor));\n this.splatIndexAttribute.clearUpdateRanges();\n if (cursor > 0) this.splatIndexAttribute.addUpdateRange(0, cursor);\n this.splatIndexAttribute.needsUpdate = true;\n this.drawListSorted = false;\n }\n (this.geometry as THREE.InstancedBufferGeometry).instanceCount = cursor;\n\n // Pool-index changes invalidate the current draw order. Force the next\n // sort instead of displaying a stale permutation during a streaming swap.\n this.activeListVersion++;\n this.sortScheduler.invalidateContent();\n }\n\n /** The pool's reusable index ramp; shared by every mesh drawing from it. */\n private getPoolIndexTemplate(): Uint32Array {\n return this.pool.indexTemplate();\n }\n\n /**\n * Uploads rows written since the last flush by copying only those rows\n * through a staging texture. (Constructor-time writes never appear here;\n * they ride the textures' initial full upload instead.)\n */\n private flushPendingUploads(renderer: THREE.WebGPURenderer): void {\n // The four core pool textures share one dirty-row set (they are written\n // together), each RGBA (4 components).\n if (this.pendingUploadRows.length > 0) {\n const floatType =\n this.poolFloatTextures === 'float16' ? THREE.HalfFloatType : THREE.FloatType;\n this.uploadRows(renderer, this.pendingUploadRows, THREE.RGBAFormat, 4, [\n {\n key: 'centers',\n texture: this.dataTextures[0] as THREE.DataTexture,\n data: this.backing.centers,\n type: floatType,\n encodeHalf: this.poolFloatTextures === 'float16',\n },\n {\n key: 'colors',\n texture: this.dataTextures[1] as THREE.DataTexture,\n data: this.backing.colors,\n type: THREE.UnsignedByteType,\n },\n {\n key: 'covarianceA',\n texture: this.dataTextures[2] as THREE.DataTexture,\n data: this.backing.covarianceA,\n type: floatType,\n encodeHalf: this.poolFloatTextures === 'float16',\n },\n {\n key: 'covarianceB',\n texture: this.dataTextures[3] as THREE.DataTexture,\n data: this.backing.covarianceB,\n type: THREE.FloatType,\n },\n ]);\n // Packed SH is written by the same calls and so shares those dirty\n // rows, but it is an integer format and cannot ride the same copy.\n if (this.shPackedTextures.length > 0) {\n this.uploadRows(\n renderer,\n this.pendingUploadRows,\n THREE.RGBAIntegerFormat,\n 4,\n this.shPackedTextures.map((texture, group) => ({\n key: `shPacked${group}`,\n texture,\n data: this.backing.shPacked[group] as Uint32Array,\n type: THREE.UnsignedIntType,\n })),\n );\n }\n this.pendingUploadRows = [];\n }\n // Each channel is single-component (Red) and tracks its own dirty rows.\n for (const [name, channel] of this.channels.entries()) {\n if (channel.pendingRows.length === 0) continue;\n this.uploadRows(renderer, channel.pendingRows, THREE.RedFormat, 1, [\n {\n key: `channel:${name}`,\n texture: channel.texture,\n data: channel.backing,\n type: channel.textureType,\n },\n ]);\n channel.pendingRows = [];\n }\n }\n\n /**\n * Packs float32 CPU backing into the half GPU images for centers/covA.\n * Used once after a static constructor write so the initial `needsUpdate`\n * upload carries half bits (staging is skipped for that path).\n */\n private syncHalfFloatPoolImages(): void {\n const centersTex = this.dataTextures[0] as THREE.DataTexture;\n const covATex = this.dataTextures[2] as THREE.DataTexture;\n const centersImg = centersTex.image.data as Uint16Array;\n const covAImg = covATex.image.data as Uint16Array;\n encodeFloat32ToHalf(this.backing.centers, centersImg);\n encodeFloat32ToHalf(this.backing.covarianceA, covAImg);\n centersTex.needsUpdate = true;\n covATex.needsUpdate = true;\n }\n\n /**\n * Uploads the given row spans of one or more same-layout pool textures via\n * per-region staging copies. Spans are merged first so consecutive appends\n * upload as one rectangle each - the copyTextureToTexture call count, not\n * the pixel volume, dominates.\n */\n private uploadRows(\n renderer: THREE.WebGPURenderer,\n rows: { start: number; count: number }[],\n format: THREE.PixelFormat,\n components: number,\n entries: {\n key: string;\n texture: THREE.DataTexture;\n data: Float32Array | Uint8Array | Uint32Array;\n type: THREE.TextureDataType;\n /** When set, `data` is float32 backing encoded to half bits for staging. */\n encodeHalf?: boolean;\n }[],\n ): void {\n const width = SplatMesh.DATA_TEXTURE_WIDTH;\n // Sorting in place is safe: every caller discards its pending-rows list\n // right after the flush, and re-passing an already-sorted list is a no-op.\n const regions = rows.sort((a, b) => a.start - b.start);\n const merged: { start: number; count: number }[] = [];\n for (const region of regions) {\n const last = merged[merged.length - 1];\n if (last && region.start <= last.start + last.count) {\n last.count = Math.max(last.count, region.start + region.count - last.start);\n } else {\n merged.push({ start: region.start, count: region.count });\n }\n }\n for (const region of merged) {\n for (const { key, texture, data, type, encodeHalf } of entries) {\n const view = data.subarray(\n region.start * width * components,\n (region.start + region.count) * width * components,\n );\n let stagingData: Float32Array | Uint8Array | Uint32Array | Uint16Array = view;\n if (encodeHalf) {\n const half = this.acquireHalfEncodeBuffer(key, view.length);\n encodeFloat32ToHalf(view as Float32Array, half, 0, view.length);\n stagingData = half;\n }\n const staging = this.acquireUploadStaging(\n key,\n stagingData,\n width,\n region.count,\n format,\n type,\n );\n renderer.copyTextureToTexture(staging, texture, null, _uploadPosition.set(0, region.start));\n }\n }\n }\n\n /** Reuses a same-length half encode buffer for one staging key. */\n private acquireHalfEncodeBuffer(key: string, length: number): Uint16Array {\n const existing = this.halfEncodeBuffers.get(key);\n if (existing && existing.length === length) return existing;\n const buffer = new Uint16Array(length);\n this.halfEncodeBuffers.set(key, buffer);\n return buffer;\n }\n\n /**\n * Reuses one of the recent same-sized staging textures for this upload. WebGPU\n * texture dimensions are immutable, so each cached entry remains exact-sized. Keeping\n * a small LRU per channel avoids recreating all core/SH staging resources when\n * a stream alternates among a few chunk heights, while bounding GPU memory.\n */\n private acquireUploadStaging(\n key: string,\n data: Float32Array | Uint8Array | Uint32Array | Uint16Array,\n width: number,\n height: number,\n format: THREE.PixelFormat,\n type: THREE.TextureDataType,\n ): THREE.DataTexture {\n let cache = this.uploadStaging.get(key);\n if (!cache) {\n cache = new Map();\n this.uploadStaging.set(key, cache);\n }\n const existing = cache.get(height);\n if (existing) {\n existing.image = { data, width, height };\n existing.needsUpdate = true;\n // Map insertion order supplies a tiny LRU without another allocation.\n cache.delete(height);\n cache.set(height, existing);\n return existing;\n }\n const texture = new THREE.DataTexture(data, width, height, format, type);\n // An integer texture cannot be filtered; a staging texture that says\n // otherwise is rejected when its GPU descriptor is built.\n if (format === THREE.RGBAIntegerFormat) {\n texture.magFilter = THREE.NearestFilter;\n texture.minFilter = THREE.NearestFilter;\n }\n texture.needsUpdate = true;\n cache.set(height, texture);\n if (cache.size > UPLOAD_STAGING_CACHE_SIZE) {\n const oldestHeight = cache.keys().next().value as number;\n cache.get(oldestHeight)?.dispose();\n cache.delete(oldestHeight);\n }\n this.updateTimings.stagingTextureAllocations++;\n return texture;\n }\n\n /** Gathers everything the material graph reads. See `splat-mesh-material.ts`. */\n private graphInputs(\n textures: SplatMaterialTextures,\n sh: SplatShInputs | null,\n ): SplatMaterialBuildInputs {\n return {\n textures,\n sh,\n sourcePlacement: this.perSourceSort,\n // The uniform node instances, shared with the pick graph on purpose: one\n // per-frame write then reaches both.\n uniforms: {\n focal: this.focal,\n viewport: this.viewport,\n localCameraPosition: this.localCameraPosition,\n pixelScaleLimit: this.pixelScaleLimit,\n dofFocusDistance: this.dofFocusDistance,\n dofAperture: this.dofAperture,\n screenBandMin: this.screenBandMin,\n screenBandMax: this.screenBandMax,\n relightMap: this.relightMap,\n relightBlend: this.relightBlend,\n relightBrightness: this.relightBrightness,\n relightBackground: this.relightBackground,\n relightSoftness: this.relightSoftness,\n },\n pick: this.picker.uniforms,\n settings: {\n maxStdDev: this.maxStdDev,\n minSplatSizePx: this.minSplatSizePx,\n antialias: this.antialias,\n projectedFilterProfile: this.projectedFilterProfile,\n srgbOutput: this.srgbOutput,\n performanceProfile: this.performanceProfileValue,\n maxScreenRadiusPx: this.maxSplatScreenRadius,\n minScreenRadiusPx: this.minSplatScreenRadius,\n foveationMode: this.foveationMode,\n maxAspect: this.maxSplatAspect,\n lodAlpha: this.lodAlpha,\n },\n // The live map: a rebuild after defineChannel must see the new entry.\n channels: this.channels,\n modifiers: this.modifierList,\n };\n }\n\n private buildMaterial(textures: SplatMaterialTextures, sh: SplatShInputs | null): void {\n applySplatMaterialGraph(\n this.material as THREE.NodeMaterial,\n 'display',\n this.graphInputs(textures, sh),\n );\n // Keeps the two graphs in step; a no-op until the first pick builds one.\n this.picker.rebuildMaterial();\n }\n\n private requestSortIfNeeded(camera: THREE.Camera, renderer: THREE.WebGPURenderer): void {\n if (this.deferSortRequestOnce) {\n this.deferSortRequestOnce = false;\n // Only a frame whose draw list the current order still describes can be\n // skipped. A staged group asks for this drain frame before it commits,\n // but other swap groups apply within the same tick - and a sort skipped\n // over their mutation renders the new instance count against the old\n // permutation: removed splats linger and live ones vanish for a frame.\n // A foreign order (a secondary renderView's) never describes the primary\n // draw, so it can't be skipped over either.\n if (this.activeListVersion === this.sortedActiveListVersion && !this.orderIsForeign) return;\n }\n if (this.activeCount === 0) return;\n this.currentModelView.multiplyMatrices(camera.matrixWorldInverse, this.matrixWorld);\n\n const isWebGPU = (renderer.backend as { isWebGPUBackend?: boolean }).isWebGPUBackend === true;\n const now = isWebGPU ? performance.now() : 0;\n // A secondary renderView left its own order in the shared buffer, so the\n // primary view must re-sort this frame however little its camera moved.\n if (!this.orderIsForeign) {\n if (isWebGPU) {\n if (\n !this.sortScheduler.shouldSubmit(\n this.currentModelView,\n this.lastSortedModelView,\n this.activeCount,\n now,\n )\n ) {\n return;\n }\n } else if (\n // WebGL2 has no cadence, but a content swap under a stationary camera\n // still invalidated the draw order (the swap reset it to unsorted\n // active order) - skipping here would leave the scene blend-order\n // broken until the camera next moved.\n !this.sortScheduler.hasPendingForce() &&\n this.currentModelView.equals(this.lastSortedModelView)\n ) {\n return;\n }\n }\n\n this.refreshSortBounds();\n\n this.sorter ??= this.createSorter(renderer);\n if (!this.sorter) return;\n if (this.sorter.sort(this.currentModelView, this.activeCount, this.boundingSphereLocal)) {\n this.lastSortedModelView.copy(this.currentModelView);\n this.sortedActiveListVersion = this.activeListVersion;\n this.orderIsForeign = false; // the buffer now holds the primary order again\n // On WebGL2 `now` is 0 - harmless, cadence timing is WebGPU-only; the\n // call still clears the pending-force flag consumed above.\n this.sortScheduler.markAccepted(now);\n }\n }\n\n /**\n * Refreshes {@link boundingSphereLocal} - the sphere the sorter quantizes\n * depth over - when {@link boundsDirty}. The base mesh uses its local splat\n * bounds; a unified {@link SplatScene} overrides this to supply a world-space\n * bound spanning all its sources (whose transforms live in the shader).\n */\n protected refreshSortBounds(): void {\n if (!this.boundsDirty) return;\n this.localBounds.getBoundingSphere(this.boundingSphereLocal);\n this.boundsDirty = false;\n }\n\n /** The data texture backing a channel defined with {@link defineChannel}. */\n protected channelTexture(name: string): THREE.DataTexture | undefined {\n return this.channels.get(name)?.texture;\n }\n\n /** CPU backing storage of a float channel, for internal CPU-side consumers. */\n protected channelBacking(name: string): Float32Array {\n const channel = this.channels.get(name);\n if (!channel || !(channel.backing instanceof Float32Array)) {\n throw new Error(`SplatMesh: float channel \"${name}\" is missing.`);\n }\n return channel.backing;\n }\n\n /**\n * Forces the next {@link update} to re-sort even if the camera has not moved\n * - used by a unified pool when a source's transform changes, since the depth\n * order then changes without any camera motion.\n */\n protected invalidateSort(): void {\n this.sortScheduler.invalidate();\n this.boundsDirty = true;\n }\n\n private createSorter(renderer: THREE.WebGPURenderer): SplatSorter | null {\n const isWebGPU = (renderer.backend as { isWebGPUBackend?: boolean }).isWebGPUBackend === true;\n if (!isWebGPU) {\n return new WorkerSorter({\n capacity: this.capacity,\n rowWidth: SplatMesh.DATA_TEXTURE_WIDTH,\n centers: this.backing.centers,\n perSource: this.perSourceSort\n ? {\n sourceIds: this.perSourceSort.sourceIds,\n matrices: this.perSourceSort.matrices,\n }\n : undefined,\n splatIndexAttribute: this.splatIndexAttribute,\n takeDirtyRows: () => {\n const rows = this.workerDirtyRows;\n this.workerDirtyRows = [];\n return rows;\n },\n getActiveSpans: () => {\n const spans = new Uint32Array(this.ranges.size * 2);\n let cursor = 0;\n for (const record of this.ranges.values()) {\n if (!record.active) continue;\n // The used prefix only (matching the active list): the full slab\n // count would make the worker sort - and the draw list render -\n // the inactive page-table tail in place of live splats.\n const count = record.activePrefix ?? record.count;\n if (count === 0) continue;\n spans[cursor++] = record.start;\n spans[cursor++] = count;\n }\n return spans.subarray(0, cursor);\n },\n onOrderApplied: () => {\n this.drawListSorted = true;\n },\n });\n }\n const options = {\n renderer,\n capacity: this.poolRows * SplatMesh.DATA_TEXTURE_WIDTH,\n centersTexture: this.centersTexture,\n dataTextureWidth: SplatMesh.DATA_TEXTURE_WIDTH,\n splatIndexAttribute: this.splatIndexAttribute,\n sourceIndexAttribute: this.sourceIndexAttribute,\n };\n // A per-source world transform (unified pool) needs the counting sorter's\n // world-depth path; radix has no equivalent, so it is not offered there.\n if (this.perSourceSort) {\n return new ComputeSorter({ ...options, perSource: this.perSourceSort });\n }\n if (this.sortStrategy === 'radix' || this.sortStrategy === 'exact') {\n this.ensureRadixSorter();\n if (!this.RadixSorterCtor) return null; // skip until the module resolves\n return new this.RadixSorterCtor({ ...options, exactDepth: this.sortStrategy === 'exact' });\n }\n return new ComputeSorter(options);\n }\n\n /** Prefetches the experimental radix sorter; safe to call repeatedly. */\n private ensureRadixSorter(): void {\n if (this.RadixSorterCtor || this.radixSorterLoad) return;\n this.radixSorterLoad = import('./radix-sorter')\n .then((mod) => {\n this.RadixSorterCtor = mod.RadixSorter;\n })\n .finally(() => {\n this.radixSorterLoad = null;\n });\n }\n}\n\nconst _appendBox = new THREE.Box3();\nconst _uploadPosition = new THREE.Vector2();\nconst _queryLocal = new THREE.Vector3();\nconst _queryWorld = new THREE.Vector3();\nconst _queryScale = new THREE.Vector3();\nconst _viewSize = new THREE.Vector2();\nconst _camLocal = new THREE.Vector3();\nconst _adaptView = new THREE.Matrix4();\nconst _adaptMvp = new THREE.Matrix4();\n\n/** How often the adaptive frontier limit re-estimates the drawn count (ms). */\nconst FOVEATION_ADAPT_MS = 180;\n/** Sample every Nth pool slot when estimating the drawn count (a prime avoids\n * aliasing with the texture width). ~1/31 of the pool, throttled - a few ms. */\nconst FOVEATION_ADAPT_STRIDE = 31;\n/** Coarsest the adaptive limit may grow to (px), so a pathological view can't\n * drive the whole scene to a single blob. */\nconst FOVEATION_LIMIT_MAX_PX = 64;\n\n/**\n * Validates the public Gaussian cutoff override. `undefined` means \"no\n * override\", so the caller applies the device-aware default.\n */\nfunction validateMaxStdDev(value: number | undefined): number | undefined {\n if (value !== undefined && (!Number.isFinite(value) || value <= 0)) {\n throw new RangeError('SplatMesh maxStdDev must be a finite number greater than 0.');\n }\n return value;\n}\n\n/** A pixel floor of `0` disables it; negatives and non-finite values are errors. */\nfunction validateMinSplatSizePx(value: number | undefined): number | undefined {\n if (value !== undefined && (!Number.isFinite(value) || value < 0)) {\n throw new RangeError('SplatMesh minSplatSizePx must be a finite number >= 0.');\n }\n return value;\n}\n\n/** Validates a screen-radius cull override; `0`/unset both mean \"off\". */\nfunction validateMaxSplatScreenRadius(value: number | undefined): number {\n if (value === undefined) return 0;\n if (!Number.isFinite(value) || value < 0) {\n throw new RangeError('SplatMesh screen-radius cull must be a finite number ≥ 0.');\n }\n return value;\n}\n\n/** Default frontier-cut target size (px). Matches Spark's `lodRenderScale`\n * default of 1 - nodes refine until ~1 px on screen, so the draw budget (not a\n * coarser fixed cut) is what bounds detail. At the old value of 4 the cut\n * stopped ~2 LOD levels early everywhere the tree doesn't bottom out at\n * leaves, which made large-scale scenes visibly coarser than Spark. */\nexport const DEFAULT_FOVEATION_TARGET_PX = 1;\n\nfunction validateFoveationTargetPx(value: number | undefined): number {\n if (value === undefined) return DEFAULT_FOVEATION_TARGET_PX;\n if (!Number.isFinite(value) || value <= 0) {\n throw new RangeError('SplatMesh foveationTargetPx must be a finite number > 0.');\n }\n return value;\n}\n\n/** Default target for the frontier cut's drawn-splat count (Spark's `maxSplats`).\n * Coarsens the cut once the estimate exceeds it, keeping frame cost bounded. */\nexport const DEFAULT_FOVEATION_DRAW_BUDGET = 900_000;\n\nfunction validateFoveationDrawBudget(value: number | undefined): number {\n if (value === undefined) return DEFAULT_FOVEATION_DRAW_BUDGET;\n if (!Number.isFinite(value) || value <= 0) {\n throw new RangeError('SplatMesh foveationDrawBudget must be a finite number > 0.');\n }\n return value;\n}\n","/** Scheduling and data helpers shared by the streamed mesh implementation. */\nimport type { SplatRange } from './splat-mesh';\nimport type { SplatData } from './splat-data';\nimport type { LodRun } from './lod-scheduler';\nimport { resolveCpuCacheBytes } from './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);\n\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 last.leafEnd = Math.max(last.leafEnd, item.end);\n if (item.add) {\n last.adds.push(item.add);\n last.addCount += item.add.count;\n }\n if (item.remove) last.removes.push(item.remove);\n } else {\n groups.push({\n adds: item.add ? [item.add] : [],\n removes: item.remove ? [item.remove] : [],\n leafStart: item.start,\n leafEnd: item.end,\n addCount: item.add?.count ?? 0,\n });\n }\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 = '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 '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 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 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 return (\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 './loading';\nimport type { RadChunkRangeRequest } from './load-worker-protocol';\nimport type { LccChunkParams } from './formats/lcc/parse-lcc';\nimport type { SplatData } from './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 * 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 SceneCollision {\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?: SceneCollision;\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: 'pagetable'`) 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 `pagetable` 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 pagetable 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 './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","import type { SplatData } from './splat-data';\nimport type {\n LoadWorkerRequest,\n LoadWorkerResponse,\n LoadWorkerSource,\n RadChunkRangeRequest,\n} from './load-worker-protocol';\nimport type { LccChunkParams } from './formats/lcc/parse-lcc';\nimport {\n createAbortError,\n deserializeSplatLoadError,\n resolveSplatUrl,\n SplatLoadError,\n splatFormatForExtension,\n splatNameExtension,\n splatUrlExtension,\n type ChunkFileFormat,\n type SplatFileLoadOptions,\n type SplatFormat,\n type SplatInputOptions,\n type SplatSourceFormat,\n type SplatProgressCallback,\n} from './loading';\n// Inlined worker (blob URL): survives library bundling in any consumer\n// setup, unlike an asset file referenced via `new URL(...)`.\nimport LoadWorker from './load-worker?worker&inline';\n\n/**\n * Formats served by the one-shot worker rather than the streaming one.\n *\n * Both workers are inlined, so their parsers are string literals no bundler can\n * tree-shake. Keeping these three behind a dynamic import is what moves SPZ's\n * ~39 KB ZSTD wasm blob out of the main entry and into a chunk that loads only\n * when one of these formats is actually opened.\n */\nconst ONE_SHOT_FORMATS = new Set<ChunkFileFormat>(['spz', 'splat', 'ksplat']);\n\n/**\n * One worker plus the requests in flight on it.\n *\n * `ChunkLoader` runs two of these - see {@link ChunkLoader} - so the request\n * bookkeeping, cancellation and terminal-failure handling live here once.\n */\nclass WorkerClient {\n private nextRequestId = 0;\n private readonly pending = new Map<\n number,\n {\n resolve: (data: SplatData) => void;\n reject: (error: Error) => void;\n removeAbortListener?: () => void;\n onProgress?: SplatProgressCallback;\n }\n >();\n private disposed = false;\n /** Terminal worker failure; subsequent loads reject instead of hanging. */\n private workerFailure: SplatLoadError | null = null;\n\n constructor(private readonly worker: Worker) {\n this.worker.onmessage = (event: MessageEvent<LoadWorkerResponse>) => {\n const request = this.pending.get(event.data.id);\n if (!request) return;\n if (event.data.type === 'progress') {\n // Progress does not settle the request; the result still follows.\n request.onProgress?.(event.data.loaded, event.data.total);\n return;\n }\n this.pending.delete(event.data.id);\n request.removeAbortListener?.();\n if (event.data.ok) {\n request.resolve(event.data.data);\n } else if (event.data.cancelled) {\n request.reject(createAbortError('Chunk load was cancelled.'));\n } else {\n request.reject(deserializeSplatLoadError(event.data.error));\n }\n };\n // A worker-level failure (a CSP that blocks blob: workers is the common\n // one in real deployments) must still honour the documented contract:\n // every rejection is a SplatLoadError or an AbortError, never a bare Error.\n // There is no per-request URL here - the worker itself died - so `url` is\n // empty and the failure is not retryable: retrying constructs the same\n // worker under the same policy.\n this.worker.onerror = (event?: ErrorEvent | Event) => {\n if (this.workerFailure) return;\n const detail =\n event &&\n typeof event === 'object' &&\n 'message' in event &&\n typeof event.message === 'string'\n ? event.message\n : '';\n const failure = new SplatLoadError(\n detail ? `Chunk loading worker failed. ${detail}` : 'Chunk loading worker failed.',\n { phase: 'worker', url: '', retryable: false },\n );\n this.workerFailure = failure;\n this.worker.terminate();\n this.rejectAll(failure);\n };\n }\n\n /** Posts one load to this worker and tracks its pending promise. */\n request(\n message: Omit<Extract<LoadWorkerRequest, { type: 'load' }>, 'id'>,\n signal: AbortSignal | undefined,\n onProgress?: SplatProgressCallback,\n ): Promise<SplatData> {\n if (this.disposed) return Promise.reject(createAbortError('ChunkLoader has been disposed.'));\n if (signal?.aborted) return Promise.reject(createAbortError('Chunk load was cancelled.'));\n if (this.workerFailure) return Promise.reject(this.workerFailure);\n\n const id = this.nextRequestId++;\n const promise = new Promise<SplatData>((resolve, reject) => {\n this.pending.set(id, { resolve, reject, ...(onProgress ? { onProgress } : {}) });\n });\n if (signal) {\n // Settle immediately on abort rather than waiting for the worker's\n // acknowledgement: the worker may be deep in a decode (or may already\n // have posted a success that is still in the message queue), and a\n // caller that cancelled must neither receive that stale result nor see\n // further progress callbacks. The late reply finds no pending entry and\n // is dropped in `onmessage`.\n const onAbort = () => {\n const pending = this.pending.get(id);\n if (!pending) return;\n this.pending.delete(id);\n const cancel: LoadWorkerRequest = { type: 'cancel', id };\n if (!this.disposed) this.worker.postMessage(cancel);\n pending.reject(createAbortError('Chunk load was cancelled.'));\n };\n signal.addEventListener('abort', onAbort, { once: true });\n const pending = this.pending.get(id);\n if (pending) pending.removeAbortListener = () => signal.removeEventListener('abort', onAbort);\n }\n\n const request: LoadWorkerRequest = { ...message, id };\n this.worker.postMessage(request);\n return promise;\n }\n\n /** Terminates this worker and rejects its in-flight requests. */\n dispose(): void {\n if (this.disposed) return;\n this.disposed = true;\n this.worker.terminate();\n this.rejectAll(createAbortError('ChunkLoader has been disposed.'));\n }\n\n private rejectAll(error: Error): void {\n for (const request of this.pending.values()) {\n request.removeAbortListener?.();\n request.reject(error);\n }\n this.pending.clear();\n }\n}\n\n/**\n * Client for the persistent scene/chunk loading workers. Multiple requests\n * may be in flight; each can be cancelled through an `AbortSignal`, which\n * aborts the worker-side fetches (useful when the camera has moved on\n * before a chunk arrived).\n *\n * There are two workers behind this one class. The streaming worker is\n * constructed eagerly and handles everything the LOD paths need; the one-shot\n * worker ({@link ONE_SHOT_FORMATS}) is constructed the first time a `.spz`,\n * `.splat` or `.ksplat` is requested. Both decode off the main thread - the\n * split exists only to keep the one-shot parsers out of the published entry.\n *\n * @example\n * const loader = new ChunkLoader();\n * const chunk = await loader.load('/scene/0_0/', { kind: 'directory' });\n */\nexport class ChunkLoader {\n private readonly streaming = new WorkerClient(new LoadWorker());\n /** The in-flight construction; `null` until a one-shot format is requested. */\n private oneShot: Promise<WorkerClient> | null = null;\n /** The constructed one-shot client, so `dispose` does not have to await. */\n private oneShotClient: WorkerClient | null = null;\n private disposed = false;\n\n /**\n * Fetches and decodes one scene file or chunk directory.\n *\n * @param url - Scene URL, absolute or relative to the page.\n * @param options.kind - 'file' (default) selects a parser by extension;\n * 'directory' decodes an unbundled SOG chunk directory.\n * @param options.format - Explicit parser; `lcc-bin` additionally needs\n * `options.lcc` to say which byte range of the file to fetch.\n * @param options.signal - Abort signal for cancellation; the returned\n * promise then rejects with a `DOMException` named `AbortError`.\n * @param options.lcc - Byte-range plumbing for `lcc-bin`. This path exists\n * for {@link StreamedSplatMesh} and is **not** part of the stable surface;\n * the type is nameable as `LccChunkParams` on `@voluma/vlam/formats/lcc`.\n * @param options.rad - Byte-range plumbing for `rad-chunk`, on the same\n * footing; the type is `RadChunkRangeRequest` on `@voluma/vlam/formats/rad`.\n * @throws Rejects only with {@link SplatLoadError} (resolve, fetch, decode or\n * worker failures - `error.phase` says which; a worker that cannot start at\n * all, e.g. under a CSP that blocks `blob:` workers, reports\n * `phase: 'worker'`) or an `Error` named `AbortError` (cancellation or\n * {@link dispose}).\n */\n // `async` so a URL or format that will not resolve rejects the returned\n // promise rather than throwing synchronously into the caller.\n async load(\n url: string,\n options: SplatInputOptions & {\n kind?: 'file' | 'directory';\n format?: SplatFormat | 'lcc-bin' | 'rad-chunk';\n lcc?: LccChunkParams;\n rad?: RadChunkRangeRequest;\n sog?: { packShBands: 1 | 2 | 3 };\n files?: Readonly<Record<string, string>>;\n } = {},\n ): Promise<SplatData> {\n const kind = options.kind ?? 'file';\n // The worker runs from an inlined blob/data URL, where relative and\n // root-relative URLs cannot be resolved - make the URL absolute here.\n const absoluteUrl = resolveSplatUrl(url, options.baseUrl).href;\n const format = resolveFileFormat(absoluteUrl, kind, options.format ?? 'auto');\n const data = await this.request(\n {\n from: 'url',\n url: absoluteUrl,\n kind,\n ...(options.request ? { request: options.request } : {}),\n },\n format,\n options.signal,\n options.lcc,\n options.rad,\n options.files,\n options.sog,\n options.onProgress,\n );\n return withSourceFormat(data, format);\n }\n\n /**\n * Decodes a local file (from a file input or a drop) in the worker - the\n * bytes are read worker-side, so a multi-million-splat decode still never\n * blocks the main thread.\n *\n * Only self-contained files are supported: `.ply`, `.spz`, `.splat`,\n * `.ksplat` and bundled `.sog`. An unbundled SOG directory needs sibling\n * fetches and so requires {@link load} with `kind: 'directory'`.\n *\n * @param file - The file; its name selects the parser unless `format` is given.\n * @throws Rejects only with {@link SplatLoadError} (unknown extension, read\n * or decode failures) or an `Error` named `AbortError` (cancellation or\n * {@link dispose}).\n */\n // `async` so an unknown extension rejects the returned promise rather than\n // throwing synchronously into the caller.\n async loadFile(file: File, options: SplatFileLoadOptions = {}): Promise<SplatData> {\n const requested = options.format ?? 'auto';\n const format: Exclude<SplatFormat, 'auto'> =\n requested === 'auto'\n ? splatFormatForExtension(splatNameExtension(file.name), file.name)\n : requested;\n const data = await this.request(\n { from: 'file', file },\n format,\n options.signal,\n undefined,\n undefined,\n undefined,\n undefined,\n options.onProgress,\n );\n return withSourceFormat(data, format);\n }\n\n /** Routes one load to the worker that owns its format. */\n private request(\n source: LoadWorkerSource,\n format: ChunkFileFormat,\n signal: AbortSignal | undefined,\n lcc?: LccChunkParams,\n rad?: RadChunkRangeRequest,\n files?: Readonly<Record<string, string>>,\n sog?: { packShBands: 1 | 2 | 3 },\n onProgress?: SplatProgressCallback,\n ): Promise<SplatData> {\n if (this.disposed) return Promise.reject(createAbortError('ChunkLoader has been disposed.'));\n if (signal?.aborted) return Promise.reject(createAbortError('Chunk load was cancelled.'));\n\n const message: Omit<Extract<LoadWorkerRequest, { type: 'load' }>, 'id'> = {\n type: 'load',\n source,\n format,\n ...(lcc ? { lcc } : {}),\n ...(rad ? { rad } : {}),\n ...(sog ? { sog } : {}),\n ...(files ? { files } : {}),\n ...(onProgress ? { progress: true } : {}),\n };\n\n const client = this.clientFor(format);\n // Deliberately not `async`: for a streaming format the client is already\n // there, and the request must reach the worker in the same task the caller\n // made it - a chunk scheduler that posts and then aborts within one frame\n // depends on that ordering. Only the one-shot path, which cannot avoid\n // awaiting its chunk, defers.\n if (!(client instanceof Promise)) return client.request(message, signal, onProgress);\n return client.then((resolved) => {\n // Re-checked because disposal or abort may have happened while the\n // one-shot chunk was in flight, before any listener was registered.\n if (this.disposed) throw createAbortError('ChunkLoader has been disposed.');\n return resolved.request(message, signal, onProgress);\n });\n }\n\n /**\n * The worker for `format`, constructing the one-shot worker on first need.\n *\n * The in-flight promise is cached, not just the resolved client, so two\n * concurrent `.spz` loads share one worker instead of racing to build two.\n */\n private clientFor(format: ChunkFileFormat): WorkerClient | Promise<WorkerClient> {\n if (!ONE_SHOT_FORMATS.has(format)) return this.streaming;\n if (this.oneShotClient) return this.oneShotClient;\n this.oneShot ??= import('./one-shot-worker?worker&inline').then(\n ({ default: OneShotWorker }) => {\n const client = new WorkerClient(new OneShotWorker());\n this.oneShotClient = client;\n // `dispose()` may have run while the chunk was in flight; it could not\n // terminate a worker that did not exist yet, so honour it here.\n if (this.disposed) client.dispose();\n return client;\n },\n );\n return this.oneShot;\n }\n\n /** Terminates both workers and rejects all in-flight requests. */\n dispose(): void {\n if (this.disposed) return;\n this.disposed = true;\n this.streaming.dispose();\n // A one-shot worker still being imported is terminated in `clientFor`,\n // which sees `disposed` when it finally constructs it.\n this.oneShotClient?.dispose();\n }\n}\n\n/** Self-contained scene formats {@link SplatData.sourceFormat} can hold. */\nconst SOURCE_FORMATS: readonly SplatSourceFormat[] = [\n 'ply',\n 'spz',\n 'splat',\n 'ksplat',\n 'sog',\n 'rad',\n];\n\n/**\n * Stamps the resolved format onto decoded scene data so {@link SplatMesh} can\n * pick its `orientation: 'y-up'` correction. Chunk-only formats (`lcc-bin`,\n * `rad-chunk`) carry no self-contained frame, so they are left unstamped.\n */\nfunction withSourceFormat(data: SplatData, format: ChunkFileFormat): SplatData {\n return (SOURCE_FORMATS as readonly string[]).includes(format)\n ? { ...data, sourceFormat: format as SplatSourceFormat }\n : data;\n}\n\nfunction resolveFileFormat(\n url: string,\n kind: 'file' | 'directory',\n format: SplatFormat | 'lcc-bin' | 'rad-chunk',\n): ChunkFileFormat {\n // An LCC or RAD chunk is a byte range of a shared file, so its extension says\n // nothing about the parser - the caller's choice is authoritative.\n if (format === 'lcc-bin' || format === 'rad-chunk') return format;\n if (kind === 'directory') return 'sog';\n if (format !== 'auto') return format;\n return splatFormatForExtension(splatUrlExtension(new URL(url)), url);\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 '../../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}\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 type SplatRange,\n type SplatChannelType,\n type SplatChannelOptions,\n type SplatMeshOptions,\n type SplatUpdateOptions,\n} from './splat-mesh';\nimport type { SplatData } from './splat-data';\nimport type { StaticLodSplatMesh, StaticLodSplatMeshLoadOptions } from './static-lod-splat-mesh';\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 './loading';\nimport {\n liftBudgetToFinestLevel,\n recommendedRadMaxStdDev,\n resolveSplatBudget,\n type SplatDeviceProfile,\n} from './splat-budget';\nimport { resolveXrView } from './xr-view';\nimport { ChunkLoader } from './chunk-loader';\nimport { yUpTransformForFormat } from './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 './sh-pack';\nimport { warn } from './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 marker 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-pagetable) path.\n * Matches the pagetable 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: 'pagetable'`.\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 marker 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 classic `.lcc` {@link StreamedSplatMeshOptions.initialReveal}.\n * Exported for hosts that gate visibility on the first useful near-L0 frame.\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` marker stays coarse near the\n * camera - every marker'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 markers 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 * 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: 'pagetable'` 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 loadScene}). */\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 classic `.lcc` streaming.\n *\n * - `'progressive'`: cells become visible as each L0 coverage group commits\n * - can show sparse near-detail 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. Other\n * streamed formats default to `'progressive'` and treat the hold as\n * disabled.\n *\n * Does not make detail downloads instantaneous; it avoids lower-LOD traffic\n * and multi-cell buildup before the first useful frame. The hold uses the\n * **resolved** cut from the first schedule (after camera + format transform),\n * not distance ambition alone.\n */\n initialReveal?: 'progressive' | 'hold-near-l0';\n /** Receives lightweight LOD swap markers 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-marker scene it is most of the traffic competing with the marker 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 marker's\n * detail queues behind a dozen far markers' 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 * markers, 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 /**\n * Loads a non-streamed splat file into a worker-built merged hierarchy.\n * The returned static mesh shares this class's runtime budget contract.\n */\n static async loadAutoLod(\n input: string | URL,\n options: StaticLodSplatMeshLoadOptions,\n ): Promise<StaticLodSplatMesh> {\n const { StaticLodSplatMesh } = await import('./static-lod-splat-mesh');\n return StaticLodSplatMesh.load(input, options);\n }\n\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: 'pagetable'`: 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 `pagetable` mode, where the slab is fully \"active\" but mostly degenerate. */\n private pageTableDrawn = 0;\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 * Classic `.lcc` startup hold. `'capture'` waits for the first schedule after\n * the host applies the final camera; `'holding'` freezes that nearby-detail set.\n */\n private initialRevealPhase: 'off' | 'capture' | 'holding' | 'released' = 'off';\n /** Frozen nearby-detail 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 `pagetable` 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.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 pagetable worker before construction (constructors cannot await).\n // SOG/LCC hosts never pay for the frontier worker blob.\n const resolvedFoveationMode = scene.foveation\n ? (options.foveationMode ?? (format === 'rad' ? 'pagetable' : 'frontier'))\n : options.foveationMode;\n let FrontierWorkerCtor: InlineWorkerCtor | undefined;\n if (resolvedFoveationMode === 'pagetable') {\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. Keep every other format progressive, and let a\n // caller explicitly request progressive LCC for A/B or legacy hosts.\n ...(format === 'lcc' && options.initialReveal === undefined\n ? { initialReveal: 'hold-near-l0' 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 // Classic `.lcc` only: other formats treat hold-near-l0 as disabled.\n if (options.initialReveal === 'hold-near-l0' && neverRetireCoverageEarly) {\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.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-marker 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` markers 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 markers, 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 = options.foveationMode === 'pagetable';\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 (options.foveationMode === 'pagetable') {\n if (!FrontierWorkerCtor) {\n throw new Error('StreamedSplatMesh: pagetable 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 marker 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 markers: 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 marker grows its pages while the ones\n * behind you hand theirs back, instead of every marker 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 * 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: 'pagetable'`, 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 * 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 `pagetable` 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: 'pagetable'`.\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 * Classic `.lcc` hosts using the default (or explicitly opting into\n * `'hold-near-l0'`) should keep the mesh invisible while\n * `status === 'pending'`, then reveal on `'ready'` or `'degraded'`.\n */\n get initialRevealState(): InitialRevealState {\n return this.initialRevealStateValue;\n }\n\n /**\n * Captures a fresh classic `.lcc` nearby-detail startup set on the next\n * {@link update}. Hosts that apply their final initial camera pose after the\n * mesh first receives frames should call this before lifting their loading\n * cover. It is a no-op for every other format and for progressive startup.\n */\n recaptureInitialReveal(): void {\n if (!this.neverRetireCoverageEarly || this.initialRevealPhase === '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(scheduledRuns, now);\n const holding = holdingRuns !== null;\n // During the startup hold, ignore later camera cuts: only the frozen near-L0\n // set is desired. After release (or when hold is off), use the live schedule.\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. During hold, only critical L0\n // files (and pins) stay - neighbours / coarse / env 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\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 pagetable `pageTableFetchPriority` path.\n const pendingFetches = new Map<number, ClassicFetchWant>();\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 = holding || isWaitingOnFinest(group);\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 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\n // Defer the LCC2 environment tile until the nearby-detail hold releases.\n if (!holding) this.updateEnvironment(now);\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<number, LodRun[]>();\n for (const run of runs) {\n const g = run.coverageGroup as number;\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 private captureOrContinueInitialReveal(scheduledRuns: LodRun[], now: number): LodRun[] | null {\n if (this.initialRevealPhase === 'off' || this.initialRevealPhase === 'released') return null;\n\n if (this.initialRevealPhase === 'capture') {\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 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.frozenCriticalRuns.every((run) => this.resident.has(runKey(run)))) {\n this.releaseInitialReveal('ready');\n }\n }\n\n /** Creates a marker 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 marker-heavy 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 */\n private updateEnvironment(now: number): 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 this.requestChunk(file, 'base');\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 /**\n * Page-table reschedule (`foveationMode: 'pagetable'`): 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 markers, every hidden and distant one\n // speculating at once is the traffic that delays the marker the viewer\n // is looking at. The cost of gating it is that re-focusing a marker 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 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 pagetable so near\n * detail is not structurally starved on the non-pagetable 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 marker 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","import type { SplatData } from './splat-data';\nimport { ChunkLoader } from './chunk-loader';\nimport type { SplatFormat, SplatFileLoadOptions, SplatInputOptions } from './loading';\n\nexport type { SplatFileLoadOptions };\n\n/** Options for {@link loadScene}. */\nexport interface SplatLoadOptions extends SplatInputOptions {\n /** Parser selection; URL extension detection is used only for `auto`. */\n format?: SplatFormat;\n}\n\n/**\n * Fetches and decodes a splat scene (`.sog`, `.ply`, `.spz`, `.splat` or\n * `.ksplat`) in a Web Worker, so\n * even multi-million-splat decodes never block the main thread. The\n * decoded arrays are transferred back, not copied.\n *\n * One-shot convenience over {@link ChunkLoader}; use `ChunkLoader`\n * directly when loading many chunks or when cancellation is needed.\n *\n * @param input - Scene URL; its extension selects the parser unless an\n * explicit `format` option is provided.\n * @returns The decoded splat data, ready for {@link SplatMesh}.\n * @throws Rejects only with `SplatLoadError` (resolve, fetch or decode\n * failures - `error.phase` says which) or a `DOMException` named `AbortError`\n * when `options.signal` fires.\n *\n * @example\n * const splats = new SplatMesh(await loadScene('/scene.sog'));\n * scene.add(splats);\n */\nexport async function loadScene(\n input: string | URL,\n options: SplatLoadOptions = {},\n): Promise<SplatData> {\n const loader = new ChunkLoader();\n try {\n return await loader.load(input instanceof URL ? input.href : input, options);\n } finally {\n loader.dispose();\n }\n}\n\n/**\n * Decodes a local splat file - from a `<input type=\"file\">` or a drag-and-drop\n * - in a Web Worker, without uploading it anywhere. The file's bytes are read\n * worker-side and the decoded arrays are transferred back, not copied.\n *\n * Only self-contained files are supported: `.ply`, `.spz`, `.splat`, `.ksplat`\n * and bundled `.sog`. An unbundled SOG directory needs sibling fetches, so it\n * must be served over HTTP and loaded with {@link loadScene}.\n *\n * @param file - The file; its name selects the parser unless an explicit\n * `format` option is provided.\n * @returns The decoded splat data, ready for {@link SplatMesh}.\n * @throws Rejects only with `SplatLoadError` (unknown extension, read or\n * decode failures) or a `DOMException` named `AbortError` when\n * `options.signal` fires.\n *\n * @example\n * const splats = new SplatMesh(await loadSceneFile(event.dataTransfer.files[0]));\n * scene.add(splats);\n */\nexport async function loadSceneFile(\n file: File,\n options: SplatFileLoadOptions = {},\n): Promise<SplatData> {\n const loader = new ChunkLoader();\n try {\n return await loader.loadFile(file, options);\n } finally {\n loader.dispose();\n }\n}\n","/** @role Bridge - Camera-aware rendering for worker-built static splat hierarchies. */\nimport * as THREE from 'three/webgpu';\nimport { loadScene, type SplatLoadOptions } from './load-scene';\nimport type { SplatData } from './splat-data';\nimport { SplatMesh, type SplatMeshOptions, type SplatUpdateOptions } from './splat-mesh';\nimport type { StaticLodBuildProgress } from './static-lod';\nimport type { StaticLodWorkerRequest, StaticLodWorkerResponse } from './static-lod-worker-protocol';\n\nconst BUDGET_GRANULARITY = 4096;\nconst CAMERA_RESCHEDULE_INTERVAL_MS = 250;\n\nexport interface StaticLodSplatMeshOptions extends SplatMeshOptions {\n /** Initial selected-frontier budget. Defaults to the build ceiling. */\n budget?: number;\n /** Finest frontier retained by the hierarchy and immutable pool ceiling. */\n maxBudget: number;\n /** Reports the spatial ordering and hierarchy-build phase. */\n onLodProgress?: (progress: StaticLodBuildProgress) => void;\n /** Phase-aware progress spanning download, decode completion and LOD build. */\n onAutoLodProgress?: (progress: StaticLodLoadProgress) => void;\n}\n\nexport interface StaticLodLoadProgress {\n readonly phase: 'download' | 'decode' | 'lod-build';\n readonly completed: number;\n readonly total: number;\n}\n\nexport interface StaticLodSplatMeshLoadOptions\n extends StaticLodSplatMeshOptions, SplatLoadOptions {}\n\nconst transferablesFor = (data: SplatData): Transferable[] => {\n const transferables: Transferable[] = [\n data.positions.buffer as ArrayBuffer,\n data.colors.buffer as ArrayBuffer,\n data.covariances.buffer as ArrayBuffer,\n ];\n if (data.sh) transferables.push(data.sh.labels.buffer as ArrayBuffer);\n if (data.shPacked) transferables.push(data.shPacked.packed.buffer as ArrayBuffer);\n return transferables;\n};\n\nconst normalizeBudget = (value: number, ceiling: number): number => {\n if (!Number.isFinite(value) || value <= 0) {\n throw new RangeError('StaticLodSplatMesh budget must be a positive finite number.');\n }\n const clamped = Math.min(ceiling, Math.floor(value));\n if (clamped === ceiling || clamped < BUDGET_GRANULARITY) return Math.max(1, clamped);\n return Math.max(\n BUDGET_GRANULARITY,\n Math.floor(clamped / BUDGET_GRANULARITY) * BUDGET_GRANULARITY,\n );\n};\n\nconst buildInWorker = async (\n source: SplatData,\n options: StaticLodSplatMeshOptions,\n signal: AbortSignal | undefined,\n): Promise<{\n data: SplatData;\n contentSplatCount: number;\n finestSplatCount: number;\n worker: Worker;\n}> => {\n const module = await import('./static-lod-worker?worker&inline');\n const WorkerClass = module.default;\n const worker = new WorkerClass();\n return await new Promise((resolve, reject) => {\n const abort = (): void => {\n worker.terminate();\n reject(new DOMException('Static LOD build aborted.', 'AbortError'));\n };\n if (signal?.aborted) {\n abort();\n return;\n }\n signal?.addEventListener('abort', abort, { once: true });\n worker.onerror = (event): void => {\n signal?.removeEventListener('abort', abort);\n worker.terminate();\n reject(new Error(event.message));\n };\n worker.onmessage = (event: MessageEvent<StaticLodWorkerResponse>): void => {\n const response = event.data;\n if (response.type === 'progress') {\n options.onLodProgress?.(response.progress);\n options.onAutoLodProgress?.({ phase: 'lod-build', ...response.progress });\n return;\n }\n if (response.type === 'error') {\n signal?.removeEventListener('abort', abort);\n worker.terminate();\n reject(new Error(response.message));\n return;\n }\n if (response.type !== 'built') return;\n signal?.removeEventListener('abort', abort);\n resolve({\n data: response.data,\n contentSplatCount: response.contentSplatCount,\n finestSplatCount: response.finestSplatCount,\n worker,\n });\n };\n const request: StaticLodWorkerRequest = {\n type: 'build',\n source,\n maxBudget: options.maxBudget,\n };\n worker.postMessage(request, transferablesFor(source));\n });\n};\n\n/**\n * Static splat mesh backed by a merged hierarchy and camera-aware frontier.\n * The full hierarchy is uploaded once; camera/budget cuts only remap the\n * active pool indices and never rewrite resident splat attributes.\n */\nexport class StaticLodSplatMesh extends SplatMesh {\n private readonly worker: Worker;\n private readonly ceilingValue: number;\n private readonly contentCountValue: number;\n private budgetValue: number;\n private sequence = 0;\n private lastScheduleAt = -Infinity;\n private selectionPending = false;\n private readonly cameraLocal = new THREE.Vector3();\n private readonly cameraForward = new THREE.Vector3(0, 0, -1);\n private readonly inverseWorld = new THREE.Matrix4();\n\n private constructor(\n data: SplatData,\n contentSplatCount: number,\n finestSplatCount: number,\n worker: Worker,\n options: StaticLodSplatMeshOptions,\n ) {\n super(data, options);\n this.worker = worker;\n this.ceilingValue = finestSplatCount;\n this.contentCountValue = contentSplatCount;\n this.budgetValue = normalizeBudget(options.budget ?? this.ceilingValue, this.ceilingValue);\n // Hierarchy layout: finest level occupies pool indices `[0, finest)`.\n this.replaceActivePrefix(this.ceilingValue);\n this.worker.onmessage = (event: MessageEvent<StaticLodWorkerResponse>): void => {\n const response = event.data;\n if (response.type === 'error') {\n this.selectionPending = false;\n return;\n }\n if (response.type !== 'selection' || response.sequence !== this.sequence) return;\n this.selectionPending = false;\n this.replaceActiveIndices(response.indices);\n };\n if (this.budgetValue < this.ceilingValue) this.scheduleSelection(true);\n }\n\n /** Loads, decodes and builds a merged static hierarchy without blocking UI. */\n static async load(\n input: string | URL,\n options: StaticLodSplatMeshLoadOptions,\n ): Promise<StaticLodSplatMesh> {\n let decodeReported = false;\n const source = await loadScene(input, {\n ...options,\n onProgress: (completed, total) => {\n options.onProgress?.(completed, total);\n options.onAutoLodProgress?.({ phase: 'download', completed, total });\n if (!decodeReported && total > 0 && completed >= total) {\n decodeReported = true;\n options.onAutoLodProgress?.({ phase: 'decode', completed: 0, total: 1 });\n }\n },\n });\n if (!decodeReported) {\n options.onAutoLodProgress?.({ phase: 'decode', completed: 0, total: 1 });\n }\n options.onAutoLodProgress?.({ phase: 'decode', completed: 1, total: 1 });\n const result = await buildInWorker(source, options, options.signal);\n return new StaticLodSplatMesh(\n result.data,\n result.contentSplatCount,\n result.finestSplatCount,\n result.worker,\n options,\n );\n }\n\n get budget(): number {\n return this.budgetValue;\n }\n\n get budgetCeiling(): number {\n return this.ceilingValue;\n }\n\n get maxBudget(): number {\n return this.ceilingValue;\n }\n\n get contentSplatCount(): number {\n return this.contentCountValue;\n }\n\n /** Changes the selected frontier budget within the immutable build ceiling. */\n setBudget(value: number): number {\n const next = normalizeBudget(value, this.ceilingValue);\n if (next === this.budgetValue) return next;\n this.budgetValue = next;\n this.selectionPending = false;\n this.scheduleSelection(true);\n return next;\n }\n\n override update(\n camera: THREE.PerspectiveCamera,\n renderer: THREE.WebGPURenderer,\n options: SplatUpdateOptions = {},\n ): void {\n super.update(camera, renderer, options);\n this.updateLod(camera);\n }\n\n /** Schedules a camera-aware cut when a unified renderer owns drawing/sorting. */\n updateLod(camera: THREE.PerspectiveCamera): void {\n camera.updateMatrixWorld();\n this.updateWorldMatrix(true, false);\n this.inverseWorld.copy(this.matrixWorld).invert();\n this.cameraLocal.setFromMatrixPosition(camera.matrixWorld).applyMatrix4(this.inverseWorld);\n camera.getWorldDirection(this.cameraForward).transformDirection(this.inverseWorld);\n this.scheduleSelection(false);\n }\n\n override dispose(): void {\n if (this.disposed) return;\n this.worker.terminate();\n super.dispose();\n }\n\n private scheduleSelection(immediate: boolean): void {\n if (this.disposed || this.selectionPending) return;\n const now = performance.now();\n if (!immediate && now - this.lastScheduleAt < CAMERA_RESCHEDULE_INTERVAL_MS) return;\n this.lastScheduleAt = now;\n this.selectionPending = true;\n const request: StaticLodWorkerRequest = {\n type: 'select',\n sequence: ++this.sequence,\n budget: this.budgetValue,\n cameraLocal: this.cameraLocal.toArray(),\n cameraForward: this.cameraForward.toArray(),\n };\n this.worker.postMessage(request);\n }\n}\n","/**\n * Per-source world transforms for a unified {@link SplatScene} pool.\n *\n * Multiple splat clouds are concatenated into one pool in their own local\n * space; each cloud (\"source\") then carries a world matrix applied **in the\n * shader** - never baked into the pool data. That is what lets a source move\n * every frame at full rate: only this small uniform array changes, and neither\n * the pool textures nor the sort keys are rewritten.\n *\n * The same matrix array node is shared by two consumers so they always agree,\n * both through `sourceWorldTransform` in `splat-mesh-material.ts`:\n * - the material graph, which places a splat's center and covariance **before**\n * the modifier stack runs, so effects see the splat where it visually is, and\n * - the depth sorter, which transforms each splat's center to world space\n * before computing its view-space depth (see `ComputeSorter`).\n *\n * Internal. Nothing here is exported from `index.ts`.\n */\nimport * as THREE from 'three/webgpu';\nimport { uniformArray } from 'three/tsl';\n\n/**\n * Maximum number of sources a unified pool can hold. Sized generously - the\n * uniform array costs `4 · MAX_SOURCES` vec4s (16 KB at 256), well under the\n * uniform-buffer limit - while bounding the per-splat index range.\n */\nexport const MAX_SOURCES = 256;\n\n/** The channel both the material graph and the sorter read as a source id. */\nexport const SOURCE_ID_CHANNEL = 'sourceId';\n\n/**\n * A live, shader-visible array of source world matrices, stored as four vec4\n * **columns** per source (three.js `Matrix4.elements` are column-major, so a\n * column maps to `elements[k*4 .. k*4+3]`). Mutating an entry re-uploads with\n * the frame's uniforms - no material or pipeline rebuild.\n */\nexport class SourceMatrixArray {\n /** `4 · MAX_SOURCES` vec4 columns; the TSL node both consumers index. */\n readonly node: ReturnType<typeof uniformArray>;\n /** CPU mirror of the same column-major matrices, for the WebGL2 sorter. */\n readonly matrices: Float32Array;\n private readonly columns: THREE.Vector4[];\n\n constructor(maxSources = MAX_SOURCES) {\n this.matrices = new Float32Array(16 * maxSources);\n this.columns = Array.from({ length: 4 * maxSources }, (_, i) => {\n // Initialize every slot to the identity matrix's columns.\n const column = i % 4;\n return new THREE.Vector4(\n column === 0 ? 1 : 0,\n column === 1 ? 1 : 0,\n column === 2 ? 1 : 0,\n column === 3 ? 1 : 0,\n );\n });\n for (let i = 0; i < maxSources; i++) {\n const base = i * 16;\n this.matrices[base] = 1;\n this.matrices[base + 5] = 1;\n this.matrices[base + 10] = 1;\n this.matrices[base + 15] = 1;\n }\n this.node = uniformArray(this.columns, 'vec4');\n }\n\n /** Writes source `id`'s world matrix (column-major columns copied verbatim). */\n set(id: number, matrix: THREE.Matrix4): void {\n const e = matrix.elements;\n const base = id * 4;\n for (let c = 0; c < 4; c++) {\n (this.columns[base + c] as THREE.Vector4).set(\n e[c * 4 + 0] as number,\n e[c * 4 + 1] as number,\n e[c * 4 + 2] as number,\n e[c * 4 + 3] as number,\n );\n this.matrices[id * 16 + c * 4 + 0] = e[c * 4 + 0] as number;\n this.matrices[id * 16 + c * 4 + 1] = e[c * 4 + 1] as number;\n this.matrices[id * 16 + c * 4 + 2] = e[c * 4 + 2] as number;\n this.matrices[id * 16 + c * 4 + 3] = e[c * 4 + 3] as number;\n }\n }\n}\n\n/** A source's local bounding sphere plus its world matrix, for pool bounds. */\nexport interface SourceBounds {\n readonly center: THREE.Vector3;\n readonly radius: number;\n readonly matrix: THREE.Matrix4;\n}\n\n/**\n * The world-space bounding sphere enclosing every source, for the sorter's\n * depth quantization range. Each source sphere is transformed by its matrix\n * (radius scaled by a conservative maximum linear stretch) and the results\n * unioned.\n */\nexport function worldBoundsOf(sources: readonly SourceBounds[], out: THREE.Sphere): THREE.Sphere {\n out.makeEmpty();\n const worldCenter = new THREE.Vector3();\n const child = new THREE.Sphere();\n for (const source of sources) {\n worldCenter.copy(source.center).applyMatrix4(source.matrix);\n child.set(worldCenter, source.radius * matrixMaxStretch(source.matrix));\n out.union(child);\n }\n return out;\n}\n\n/**\n * A safe upper bound on the largest singular value of a matrix's linear part.\n *\n * This is `sqrt(||AᵀA||∞)`: exact for rotation + per-axis scale, and still\n * conservative when hierarchy composition introduces shear. The latter is\n * essential because a maximum column length can under-bound a sheared sphere.\n */\nfunction matrixMaxStretch(m: THREE.Matrix4): number {\n const e = m.elements;\n const x0 = e[0],\n y0 = e[1],\n z0 = e[2];\n const x1 = e[4],\n y1 = e[5],\n z1 = e[6];\n const x2 = e[8],\n y2 = e[9],\n z2 = e[10];\n const g00 = x0 * x0 + y0 * y0 + z0 * z0;\n const g11 = x1 * x1 + y1 * y1 + z1 * z1;\n const g22 = x2 * x2 + y2 * y2 + z2 * z2;\n const g01 = x0 * x1 + y0 * y1 + z0 * z1;\n const g02 = x0 * x2 + y0 * y2 + z0 * z2;\n const g12 = x1 * x2 + y1 * y2 + z1 * z2;\n return Math.sqrt(\n Math.max(\n g00 + Math.abs(g01) + Math.abs(g02),\n g11 + Math.abs(g01) + Math.abs(g12),\n g22 + Math.abs(g02) + Math.abs(g12),\n ),\n );\n}\n","/**\n * A scene of several independently-posed Gaussian splat clouds that all sort\n * and blend as one.\n *\n * The problem it solves: two separate {@link SplatMesh} instances are two\n * transparent draw calls, each depth-sorted only within itself, so where the\n * clouds overlap one is painted wholesale in front of the other (\"pasted over\")\n * - the same artifact PlayCanvas's unified rendering and Spark's accumulator\n * exist to fix. `SplatScene` concatenates every cloud into **one pool** and\n * runs **one global depth sort**, so splats from different clouds interleave\n * correctly, from any angle.\n *\n * Each cloud (\"source\") keeps its own world transform, applied in the shader\n * (never baked into the pool): the sorter transforms each splat's center to\n * world space before measuring depth, and the material places each splat's\n * center and covariance by its source's matrix. So {@link setSourceTransform}\n * is a handful of uniform writes - a source can be dragged or animated every\n * frame at full rate, at any splat count, with no data re-upload.\n *\n * The placement runs **before** the modifier stack, so a host effect sees each\n * splat where it visually is: an SDF light shape spanning two sources paints\n * one continuous shape, and a reveal sweeps them together. A modifier that\n * wants to travel *with* a moved source reads `ctx.sourceCenter` instead of\n * `ctx.localCenter`. See `docs/guide/effects-and-modifiers.md`.\n *\n * On WebGPU the world-depth sort runs in a compute pass; on the WebGL2\n * fallback the worker applies the same source matrices on the CPU before\n * sorting. Both backends therefore inter-sort sources correctly.\n *\n * Limitations, all inherited from the dynamic pool: sources may not carry\n * palette (SOG `shN`) view-dependent color - per-file palettes cannot be merged\n * - but DC color and per-splat (LCC) SH are fine; all sources share one\n * antialias setting (from the scene options).\n *\n * Sources are **static**: {@link addSource} takes a fully-resident\n * {@link SplatData} and copies it once into the pool, so a streaming\n * {@link StreamedSplatMesh} (which swaps its resident LOD cut every frame)\n * cannot be a source yet - keep a streamed main splat separate and unify only\n * the static sources. Streamed sources are ROADMAP M15.4.\n */\nimport * as THREE from 'three/webgpu';\nimport { SplatMesh, type SplatMeshOptions, type SplatRange } from './splat-mesh';\nimport type { SplatData } from './splat-data';\nimport { yUpTransformForFormat, type SplatOrientation } from './orientation';\nimport {\n MAX_SOURCES,\n SOURCE_ID_CHANNEL,\n SourceMatrixArray,\n worldBoundsOf,\n type SourceBounds,\n} from './source-transform';\n\n/** Construction options for a {@link SplatScene}. */\nexport interface SplatSceneOptions extends SplatMeshOptions {\n /** Total pool size in splats - must cover the sum of every source added. */\n capacity: number;\n /** Maximum number of sources (default {@link MAX_SOURCES}). */\n maxSources?: number;\n}\n\n/** Per-source placement options. */\nexport interface AddSourceOptions {\n /**\n * Y-up normalization for this source's data frame, like {@link SplatMesh}'s\n * `orientation`. `'y-up'` (default) applies the format's stand-up correction\n * before the placement matrix; `'source'` places the raw data frame.\n */\n orientation?: SplatOrientation;\n}\n\n/** Internal per-source record. */\ninterface SourceRecord extends SourceBounds {\n /** The pool range this source's splats occupy. */\n range: SplatRange;\n /** Data-frame → world correction applied before the placement matrix. */\n correction: THREE.Matrix4 | null;\n /** Live placement (world = placement · correction); kept for re-placement. */\n placement: THREE.Matrix4;\n /** The source's own data-frame bounds, for {@link SplatScene.computeSplatBounds}. */\n box: THREE.Box3;\n}\n\n/**\n * One pool, one global sort, many independently-posed splat sources - see the\n * module overview above for the artifact this exists to remove.\n *\n * @experimental May change in a minor release.\n */\nexport class SplatScene extends SplatMesh {\n private readonly matrices: SourceMatrixArray;\n private readonly maxSources: number;\n /** Indexed by source id; holes are left by {@link removeSource}. */\n private readonly sources: (SourceRecord | undefined)[] = [];\n private nextId = 0;\n\n constructor(options: SplatSceneOptions) {\n const maxSources = options.maxSources ?? MAX_SOURCES;\n super({ capacity: options.capacity }, options);\n this.matrices = new SourceMatrixArray(maxSources);\n this.maxSources = maxSources;\n\n // One float per pool slot naming the splat's source; read by both the\n // material graph and the world-depth sorter.\n this.defineChannel(SOURCE_ID_CHANNEL, { type: 'float' });\n const sourceIdTexture = this.channelTexture(SOURCE_ID_CHANNEL);\n if (!sourceIdTexture) throw new Error('SplatScene: sourceId channel texture missing.');\n this.perSourceSort = {\n sourceIdTexture,\n columns: this.matrices.node,\n sourceIds: this.channelBacking(SOURCE_ID_CHANNEL),\n matrices: this.matrices.matrices,\n };\n // The base constructor already built a graph, before `perSourceSort` was\n // set - rebuild so the material actually places its splats. Without this\n // every source renders at its pool-local position.\n this.rebuildGraph();\n }\n\n /** Number of live sources. */\n get sourceCount(): number {\n let count = 0;\n for (const source of this.sources) if (source) count++;\n return count;\n }\n\n /**\n * Adds a cloud at a world placement and returns its id (for\n * {@link setSourceTransform} / {@link removeSource}). The data is copied into\n * the shared pool in its own local frame; the placement is applied live in\n * the shader.\n *\n * @throws if the pool cannot fit the cloud, or the source limit is reached.\n */\n addSource(\n data: SplatData,\n placement: THREE.Matrix4 = new THREE.Matrix4(),\n options: AddSourceOptions = {},\n ): number {\n const id = this.nextId;\n if (id >= this.maxSources) {\n throw new Error(`SplatScene: source limit reached (${this.maxSources}).`);\n }\n const range = this.appendRange(data); // local space; transform is a uniform\n this.nextId++;\n\n const ids = new Float32Array(data.count);\n ids.fill(id);\n this.writeChannel(range, SOURCE_ID_CHANNEL, ids);\n\n const orientation = options.orientation ?? 'y-up';\n const correction = orientation === 'y-up' ? yUpTransformForFormat(data.sourceFormat) : null;\n const box = new THREE.Box3().setFromArray(data.positions);\n const sphere = box.getBoundingSphere(new THREE.Sphere());\n const record: SourceRecord = {\n range,\n correction,\n placement: placement.clone(),\n box,\n center: sphere.center,\n radius: sphere.radius,\n matrix: new THREE.Matrix4(),\n };\n this.sources[id] = record;\n this.applyPlacement(record);\n return id;\n }\n\n /**\n * Re-places a source. Cheap by design: only the shared matrix uniform and the\n * sort bound change, so it is safe to call every frame while dragging.\n *\n * Bad-id semantics across the triad: this method **throws** (a write to a\n * dead source is a caller bug that would otherwise vanish silently),\n * {@link getSourceTransform} returns `undefined` (querying is how you ask\n * whether a source is live), and {@link removeSource} returns `false`\n * (removal is idempotent).\n *\n * @throws if `id` is not a live source.\n */\n setSourceTransform(id: number, placement: THREE.Matrix4): void {\n const record = this.sources[id];\n if (!record) throw new Error(`SplatScene: no live source ${id}.`);\n record.placement.copy(placement);\n this.applyPlacement(record);\n }\n\n /**\n * The current world matrix of a source (a copy), or `undefined` if `id` is\n * not a live source - see {@link setSourceTransform} for the bad-id triad.\n */\n getSourceTransform(id: number): THREE.Matrix4 | undefined {\n return this.sources[id]?.matrix.clone();\n }\n\n /**\n * Removes a source and frees its pool range. The id is not reused (later ids\n * keep their matrix slots), so long-lived churn should prefer reusing a scene\n * over unbounded add/remove.\n *\n * @returns `true` when a live source was removed, `false` when `id` was not\n * live (idempotent - see {@link setSourceTransform} for the bad-id triad).\n */\n removeSource(id: number): boolean {\n const record = this.sources[id];\n if (!record) return false;\n this.removeRange(record.range);\n this.sources[id] = undefined;\n this.invalidateSort();\n return true;\n }\n\n /** Recomputes a source's world matrix (placement · correction) and republishes it. */\n private applyPlacement(record: SourceRecord): void {\n if (record.correction) record.matrix.multiplyMatrices(record.placement, record.correction);\n else record.matrix.copy(record.placement);\n const id = this.sources.indexOf(record);\n this.matrices.set(id, record.matrix);\n this.invalidateSort();\n }\n\n /** Sort depth is quantized over the world span of every source, not the pool's local box. */\n protected override refreshSortBounds(): void {\n if (!this.boundsDirty) return;\n const live: SourceBounds[] = [];\n for (const source of this.sources) if (source) live.push(source);\n worldBoundsOf(live, this.boundingSphereLocal);\n this.boundsDirty = false;\n }\n\n /**\n * Mesh-local bounds spanning every source at its **current placement** -\n * the pool's own box would be the union of the sources' data frames, which\n * is not where any of them is drawn. Each source's data-frame box is\n * transformed by its matrix, so a rotated source contributes the (standard,\n * conservative) axis-aligned box of its rotated bounds.\n *\n * Hosts use this to place effects, so it has to agree with what\n * `ctx.localCenter` reports; it tracks {@link setSourceTransform}.\n */\n override computeSplatBounds(): THREE.Box3 {\n const bounds = new THREE.Box3().makeEmpty();\n const placed = new THREE.Box3();\n for (const source of this.sources) {\n if (!source) continue;\n bounds.union(placed.copy(source.box).applyMatrix4(source.matrix));\n }\n return bounds;\n }\n}\n","/**\n * @role Utility\n * Shared constants and deterministic reference logic for GPU radix sorting.\n */\n\n/** Depth precision retained by both the GPU radix path and WebGL worker path. */\nexport const RADIX_KEY_BITS = 24;\n/** Exact Float32 depth keys use every sortable IEEE-754 bit. */\nexport const RADIX_EXACT_KEY_BITS = 32;\n/** Four-bit digits keep the portable shader's workgroup memory requirements small. */\nexport const RADIX_BITS_PER_PASS = 4;\nexport const RADIX_BUCKETS = 1 << RADIX_BITS_PER_PASS;\nexport const RADIX_PASS_COUNT = RADIX_KEY_BITS / RADIX_BITS_PER_PASS;\nexport const RADIX_KEY_MAX = (1 << RADIX_KEY_BITS) - 1;\n\n/** Number of four-bit passes required for one unsigned radix key width. */\nexport function radixPassCount(keyBits: number): number {\n if (!Number.isInteger(keyBits) || keyBits <= 0 || keyBits % RADIX_BITS_PER_PASS !== 0) {\n throw new RangeError('Radix key width must be a positive multiple of four bits.');\n }\n return keyBits / RADIX_BITS_PER_PASS;\n}\n\n/**\n * Maps a Float32 bit pattern onto unsigned integer order without losing depth\n * precision. Ascending mapped keys are ascending numeric floats, including\n * negative view-space Z values (farther splats first in VLAM's convention).\n */\nexport function sortableFloat32Bits(bits: number): number {\n const value = bits >>> 0;\n return (value ^ ((value & 0x80000000) === 0 ? 0x80000000 : 0xffffffff)) >>> 0;\n}\n\n/** Quantizes a depth into the unsigned 24-bit key used by the GPU sorter. */\nexport function quantizeDepthKey(depth: number, minimum: number, maximum: number): number {\n const span = maximum - minimum;\n if (!Number.isFinite(depth) || !Number.isFinite(span) || span <= 0) return 0;\n return Math.min(\n RADIX_KEY_MAX,\n Math.max(0, Math.round(((depth - minimum) / span) * RADIX_KEY_MAX)),\n );\n}\n\n/**\n * Stable CPU reference for validating the GPU's six four-bit radix passes.\n * This is deliberately test/debug-only logic, not the WebGL production sorter.\n */\nexport function stableRadixSortReference(\n keys: Uint32Array,\n values?: Uint32Array,\n keyBits = RADIX_KEY_BITS,\n): Uint32Array {\n const count = keys.length;\n let inputKeys = Uint32Array.from(keys);\n let outputKeys = new Uint32Array(count);\n let inputValues = values ? Uint32Array.from(values) : Uint32Array.from(keys.keys());\n let outputValues = new Uint32Array(count);\n const counts = new Uint32Array(RADIX_BUCKETS);\n const offsets = new Uint32Array(RADIX_BUCKETS);\n\n for (let pass = 0; pass < radixPassCount(keyBits); pass++) {\n counts.fill(0);\n const shift = pass * RADIX_BITS_PER_PASS;\n for (let i = 0; i < count; i++) {\n const digit = ((inputKeys[i] as number) >>> shift) & 0xf;\n counts[digit] = (counts[digit] as number) + 1;\n }\n let total = 0;\n for (let digit = 0; digit < RADIX_BUCKETS; digit++) {\n offsets[digit] = total;\n total += counts[digit] as number;\n }\n for (let i = 0; i < count; i++) {\n const key = inputKeys[i] as number;\n const digit = (key >>> shift) & 0xf;\n const target = offsets[digit] as number;\n offsets[digit] = target + 1;\n outputKeys[target] = key;\n outputValues[target] = inputValues[i] as number;\n }\n [inputKeys, outputKeys] = [outputKeys, inputKeys];\n [inputValues, outputValues] = [outputValues, inputValues];\n }\n return inputValues;\n}\n","/**\n * @role Bridge\n * Portable stable WebGPU radix sorting adapted to Three.js TSL compute nodes.\n */\nimport * as THREE from 'three/webgpu';\nimport {\n Fn,\n If,\n Loop,\n atomicAdd,\n atomicLoad,\n atomicOr,\n atomicStore,\n countOneBits,\n float,\n floatBitsToUint,\n instanceIndex,\n int,\n invocationLocalIndex,\n ivec2,\n storage,\n storageBarrier,\n textureLoad,\n uint,\n uniform,\n workgroupArray,\n workgroupBarrier,\n workgroupId,\n} from 'three/tsl';\nimport type { SplatSorter } from './sorter';\nimport { releaseRendererAttributes } from './compute-sorter';\nimport {\n RADIX_BITS_PER_PASS,\n RADIX_EXACT_KEY_BITS,\n RADIX_KEY_BITS,\n RADIX_KEY_MAX,\n radixPassCount,\n} from './radix-sort';\nimport { viewDepthRadius } from './splat-sort-bounds';\nimport { StorageMirrorReleaser } from './storage-attribute-mirror';\n\nconst WORKGROUP_SIZE = 256;\nconst ELEMENTS_PER_THREAD = 8;\nconst ELEMENTS_PER_WORKGROUP = WORKGROUP_SIZE * ELEMENTS_PER_THREAD;\nconst DIGIT_COUNT = 16;\nconst MASK_WORDS_PER_DIGIT = WORKGROUP_SIZE / 32;\nconst MASK_WORDS_PER_GROUP = DIGIT_COUNT * MASK_WORDS_PER_DIGIT;\nconst SCAN_BLOCK_SIZE = 256;\n\ntype UintWorkgroupArray = {\n element(index: THREE.Node<'uint'>): THREE.Node<'uint'>;\n};\n\n/** Isolates gaps in Three's current WorkgroupInfo/Bitcount TypeScript declarations. */\nfunction asUintNode(node: unknown): THREE.Node<'uint'> {\n return node as THREE.Node<'uint'>;\n}\n\n/**\n * Stable four-bit-per-pass WebGPU radix sorter.\n *\n * The ranked scatter follows PlayCanvas/WebGPU-Radix-Sort's per-digit bitmask\n * design. Three.js does not currently expose atomic workgroup arrays in TSL,\n * so masks use disjoint global-storage slices per workgroup. Workgroup and\n * storage barriers retain the same stable ranking semantics without relying\n * on cross-workgroup execution order.\n */\nexport class RadixSorter implements SplatSorter {\n readonly kind = 'radix' as const;\n private readonly renderer: THREE.WebGPURenderer;\n private readonly stages: THREE.ComputeNode[] = [];\n private readonly workingAttributes: THREE.StorageBufferAttribute[];\n /** Frees the JS mirrors three keeps behind the GPU-only ping-pong buffers. */\n private readonly mirrors: StorageMirrorReleaser;\n /** Set by {@link dispose}; makes a second dispose a no-op. */\n private disposed = false;\n private readonly viewRow2 = uniform(new THREE.Vector4());\n private readonly depthMin = uniform(0);\n private readonly depthScale = uniform(0);\n private readonly activeCount = uniform(0);\n private readonly viewCenter = new THREE.Vector3();\n /** Exact mode avoids scene-bounds quantization entirely. */\n private readonly exactDepth: boolean;\n\n constructor(options: {\n renderer: THREE.WebGPURenderer;\n capacity: number;\n centersTexture?: THREE.DataTexture;\n dataTextureWidth?: number;\n /** Gathered world-space centers for a unified renderer. */\n centersBuffer?: THREE.StorageBufferAttribute;\n splatIndexAttribute: THREE.StorageInstancedBufferAttribute;\n sourceIndexAttribute: THREE.StorageBufferAttribute;\n /** Keep every Float32 depth bit instead of quantizing to 24 bits. */\n exactDepth?: boolean;\n }) {\n const { capacity, centersTexture, dataTextureWidth, centersBuffer } = options;\n if (!centersTexture && !centersBuffer) {\n throw new Error('RadixSorter: provide centersTexture or centersBuffer.');\n }\n if (centersTexture && dataTextureWidth === undefined) {\n throw new Error('RadixSorter: dataTextureWidth is required with centersTexture.');\n }\n this.renderer = options.renderer;\n const exactDepth = options.exactDepth === true;\n this.exactDepth = exactDepth;\n const passCount = radixPassCount(exactDepth ? RADIX_EXACT_KEY_BITS : RADIX_KEY_BITS);\n const groupCount = Math.ceil(capacity / ELEMENTS_PER_WORKGROUP);\n const histogramLength = DIGIT_COUNT * groupCount;\n const scanBlockCount = Math.ceil(histogramLength / SCAN_BLOCK_SIZE);\n\n const keysAAttribute = new THREE.StorageBufferAttribute(new Uint32Array(capacity), 1);\n const keysBAttribute = new THREE.StorageBufferAttribute(new Uint32Array(capacity), 1);\n const valuesAAttribute = new THREE.StorageBufferAttribute(new Uint32Array(capacity), 1);\n const valuesBAttribute = new THREE.StorageBufferAttribute(new Uint32Array(capacity), 1);\n const histogramAttribute = new THREE.StorageBufferAttribute(\n new Uint32Array(histogramLength),\n 1,\n );\n const scanSumsAttribute = new THREE.StorageBufferAttribute(new Uint32Array(scanBlockCount), 1);\n const masksAttribute = new THREE.StorageBufferAttribute(\n new Uint32Array(groupCount * MASK_WORDS_PER_GROUP),\n 1,\n );\n this.workingAttributes = [\n keysAAttribute,\n keysBAttribute,\n valuesAAttribute,\n valuesBAttribute,\n histogramAttribute,\n scanSumsAttribute,\n masksAttribute,\n ];\n this.mirrors = new StorageMirrorReleaser(this.workingAttributes);\n\n const keysA = storage(keysAAttribute, 'uint', capacity);\n const keysB = storage(keysBAttribute, 'uint', capacity);\n const valuesA = storage(valuesAAttribute, 'uint', capacity);\n const valuesB = storage(valuesBAttribute, 'uint', capacity);\n const source = storage(options.sourceIndexAttribute, 'uint', capacity);\n const order = storage(options.splatIndexAttribute, 'float', capacity);\n const workCenters = centersBuffer ? storage(centersBuffer, 'vec4', capacity) : null;\n const histogram = storage(histogramAttribute, 'uint', histogramLength).toAtomic();\n const scanSums = storage(scanSumsAttribute, 'uint', scanBlockCount);\n const masks = storage(masksAttribute, 'uint', groupCount * MASK_WORDS_PER_GROUP).toAtomic();\n\n const buildKeys = Fn(() => {\n If(float(instanceIndex).lessThan(this.activeCount), () => {\n const poolIndex = source.element(instanceIndex);\n const texel = centersTexture\n ? ivec2(\n int(poolIndex).mod(int(dataTextureWidth as number)),\n int(poolIndex).div(int(dataTextureWidth as number)),\n )\n : null;\n const center = workCenters\n ? workCenters.element(poolIndex).xyz\n : textureLoad(centersTexture, texel as THREE.Node<'ivec2'>).xyz;\n const depth = this.viewRow2.xyz.dot(center).add(this.viewRow2.w);\n if (exactDepth) {\n // IEEE-754 bits are monotonic only for positive values. Flip the\n // sign partition so ascending unsigned keys remain ascending numeric\n // depth, including VLAM's negative view-space Z convention.\n // Three's BitcastNode typings omit shift/xor; runtime nodes support them.\n const bits = asUintNode(floatBitsToUint(depth));\n const mask = bits\n .shiftRight(uint(31))\n .equal(uint(0))\n .select(uint(0x80000000), uint(0xffffffff));\n keysA.element(instanceIndex).assign(bits.bitXor(mask));\n } else {\n keysA\n .element(instanceIndex)\n .assign(depth.sub(this.depthMin).mul(this.depthScale).clamp(0, RADIX_KEY_MAX).toUint());\n }\n valuesA.element(instanceIndex).assign(poolIndex);\n });\n })().compute(capacity, [WORKGROUP_SIZE]);\n this.stages.push(buildKeys);\n\n for (let pass = 0; pass < passCount; pass++) {\n const inputKeys = pass % 2 === 0 ? keysA : keysB;\n const outputKeys = pass % 2 === 0 ? keysB : keysA;\n const inputValues = pass % 2 === 0 ? valuesA : valuesB;\n const outputValues = pass % 2 === 0 ? valuesB : valuesA;\n const bit = uint(pass * RADIX_BITS_PER_PASS);\n\n const clearHistogram = Fn(() => {\n atomicStore(histogram.element(instanceIndex), uint(0));\n })().compute(histogramLength, [WORKGROUP_SIZE]);\n\n const buildHistogram = Fn(() => {\n const base = workgroupId.x.mul(uint(ELEMENTS_PER_WORKGROUP));\n Loop(ELEMENTS_PER_THREAD, ({ i }) => {\n const index = base.add(uint(i).mul(uint(WORKGROUP_SIZE))).add(invocationLocalIndex);\n If(float(index).lessThan(this.activeCount), () => {\n const digit = inputKeys.element(index).shiftRight(bit).bitAnd(uint(0xf));\n atomicAdd(histogram.element(digit.mul(uint(groupCount)).add(workgroupId.x)), uint(1));\n });\n });\n })().compute(groupCount * WORKGROUP_SIZE, [WORKGROUP_SIZE]);\n\n const scanBlocks = Fn(() => {\n const start = instanceIndex.mul(uint(SCAN_BLOCK_SIZE));\n const running = uint(0).toVar();\n Loop(SCAN_BLOCK_SIZE, ({ i }) => {\n const index = start.add(uint(i));\n If(index.lessThan(uint(histogramLength)), () => {\n const value = atomicLoad(histogram.element(index)).toVar();\n atomicStore(histogram.element(index), running);\n running.addAssign(value);\n });\n });\n scanSums.element(instanceIndex).assign(running);\n })().compute(scanBlockCount, [64]);\n\n const scanBlockSums = Fn(() => {\n If(instanceIndex.equal(uint(0)), () => {\n const running = uint(0).toVar();\n Loop(scanBlockCount, ({ i }) => {\n const value = scanSums.element(i).toVar();\n scanSums.element(i).assign(running);\n running.addAssign(value);\n });\n });\n })().compute(1, [64]);\n\n const addBlockOffsets = Fn(() => {\n const block = instanceIndex.div(uint(SCAN_BLOCK_SIZE));\n atomicStore(\n histogram.element(instanceIndex),\n atomicLoad(histogram.element(instanceIndex)).add(scanSums.element(block)),\n );\n })().compute(histogramLength, [WORKGROUP_SIZE]);\n\n const digitOffsets = workgroupArray('uint', DIGIT_COUNT) as unknown as UintWorkgroupArray;\n const rankedScatter = Fn(() => {\n const thread = invocationLocalIndex;\n const group = workgroupId.x;\n const maskBase = group.mul(uint(MASK_WORDS_PER_GROUP));\n If(thread.lessThan(uint(DIGIT_COUNT)), () => {\n digitOffsets.element(thread).assign(uint(0));\n });\n If(thread.lessThan(uint(MASK_WORDS_PER_GROUP)), () => {\n atomicStore(masks.element(maskBase.add(thread)), uint(0));\n });\n storageBarrier();\n workgroupBarrier();\n\n Loop(ELEMENTS_PER_THREAD, ({ i }) => {\n const index = group\n .mul(uint(ELEMENTS_PER_WORKGROUP))\n .add(i.toUint().mul(uint(WORKGROUP_SIZE)))\n .add(thread);\n const valid = float(index).lessThan(this.activeCount);\n const key = uint(0).toVar();\n const value = uint(0).toVar();\n const digit = uint(0).toVar();\n If(valid, () => {\n key.assign(inputKeys.element(index));\n value.assign(inputValues.element(index));\n digit.assign(key.shiftRight(bit).bitAnd(uint(0xf)));\n const word = thread.shiftRight(uint(5));\n const bitInWord = thread.bitAnd(uint(31));\n const maskIndex = maskBase.add(digit.mul(uint(MASK_WORDS_PER_DIGIT))).add(word);\n atomicOr(masks.element(maskIndex), uint(1).shiftLeft(bitInWord));\n });\n storageBarrier();\n workgroupBarrier();\n\n If(valid, () => {\n const word = thread.shiftRight(uint(5));\n const bitInWord = thread.bitAnd(uint(31));\n const digitBase = maskBase.add(digit.mul(uint(MASK_WORDS_PER_DIGIT)));\n const localRank = digitOffsets.element(digit).toVar();\n Loop(MASK_WORDS_PER_DIGIT, ({ i: wordIndex }) => {\n If(uint(wordIndex).lessThan(word), () => {\n localRank.addAssign(\n asUintNode(\n countOneBits(atomicLoad(masks.element(digitBase.add(wordIndex.toUint())))),\n ),\n );\n });\n });\n const precedingMask = uint(1).shiftLeft(bitInWord).sub(uint(1));\n localRank.addAssign(\n asUintNode(\n countOneBits(atomicLoad(masks.element(digitBase.add(word))).bitAnd(precedingMask)),\n ),\n );\n const prefix = atomicLoad(histogram.element(digit.mul(uint(groupCount)).add(group)));\n const target = prefix.add(localRank);\n outputKeys.element(target).assign(key);\n outputValues.element(target).assign(value);\n });\n\n storageBarrier();\n workgroupBarrier();\n If(thread.lessThan(uint(DIGIT_COUNT)), () => {\n const total = uint(0).toVar();\n Loop(MASK_WORDS_PER_DIGIT, ({ i: wordIndex }) => {\n const maskIndex = maskBase\n .add(thread.mul(uint(MASK_WORDS_PER_DIGIT)))\n .add(wordIndex.toUint());\n total.addAssign(asUintNode(countOneBits(atomicLoad(masks.element(maskIndex)))));\n atomicStore(masks.element(maskIndex), uint(0));\n });\n digitOffsets.element(thread).addAssign(total);\n });\n storageBarrier();\n workgroupBarrier();\n });\n })().compute(groupCount * WORKGROUP_SIZE, [WORKGROUP_SIZE]);\n\n this.stages.push(\n clearHistogram,\n buildHistogram,\n scanBlocks,\n scanBlockSums,\n addBlockOffsets,\n rankedScatter,\n );\n }\n\n const writeOrder = Fn(() => {\n If(float(instanceIndex).lessThan(this.activeCount), () => {\n order.element(instanceIndex).assign(float(valuesA.element(instanceIndex)));\n });\n })().compute(capacity, [WORKGROUP_SIZE]);\n this.stages.push(writeOrder);\n }\n\n sort(modelView: THREE.Matrix4, activeCount: number, bounds: THREE.Sphere): boolean {\n if (activeCount === 0) return true;\n const m = modelView.elements;\n this.viewRow2.value.set(m[2], m[6], m[10], m[14]);\n this.activeCount.value = activeCount;\n if (!this.exactDepth) {\n this.viewCenter.copy(bounds.center).applyMatrix4(modelView);\n const viewRadius = viewDepthRadius(modelView, bounds.radius);\n const minimum = this.viewCenter.z - viewRadius;\n const maximum = this.viewCenter.z + viewRadius;\n this.depthMin.value = minimum;\n this.depthScale.value = RADIX_KEY_MAX / (maximum - minimum || 1);\n }\n this.stages[0]!.count = activeCount;\n this.stages[this.stages.length - 1]!.count = activeCount;\n this.renderer.compute(this.stages);\n // Key/value ping-pong, histogram, scan sums and masks are all written and\n // read entirely on the GPU - at 4 B per splat of capacity each, their JS\n // mirrors are ~16 B/splat of pure waste. Deliberately *not*\n // `sourceIndex`/`splatIndex`: those belong to the mesh and are rewritten\n // from the CPU every frame. Same ownership line as `releaseRendererAttributes`.\n if (!this.mirrors.settled) this.mirrors.release(this.renderer);\n return true;\n }\n\n dispose(): void {\n if (this.disposed) return;\n this.disposed = true;\n for (const stage of this.stages) stage.dispose();\n releaseRendererAttributes(this.renderer, this.workingAttributes);\n }\n}\n","import * as THREE from 'three/webgpu';\nimport {\n Fn,\n If,\n bool,\n float,\n int,\n instanceIndex,\n ivec2,\n mat3,\n storage,\n textureLoad,\n uniform,\n vec3,\n vec4,\n} from 'three/tsl';\nimport {\n evaluateSplatSh,\n foldSplatModifierStack,\n asNode,\n applyIsotropicCovarianceOverride,\n DEFAULT_ISOTROPIC_VARIANCE_SCALE,\n type SplatShInputs,\n type Vec3Uniform,\n} from './splat-mesh-material';\nimport { colorSpaceToWorking } from 'three/tsl';\nimport type { SplatModifier } from './splat-modifier';\nimport { StorageMirrorReleaser } from './storage-attribute-mirror';\n\n/**\n * GPU gather pass for Phase 0 of the unified renderer. It resolves a source\n * pool's active local centers into a shared world-space storage buffer. The\n * work-buffer sorter consumes this buffer directly, so source-local pools can\n * later be heterogeneous (including streamed LOD pools).\n *\n * Internal while M15.4's draw path and public source API are completed.\n */\n/** Shared GPU storage for every gathered source in one unified frame. */\nexport class WorkBuffer {\n /** World-space center xyz; w is the drawable flag (1 draw, 0 modifier-hidden). */\n readonly centers: THREE.StorageBufferAttribute;\n /** Resolved color and opacity per work slot. */\n readonly colors: THREE.StorageBufferAttribute;\n /** First packed covariance row per work slot. */\n readonly covarianceA: THREE.StorageBufferAttribute;\n /** Second packed covariance row per work slot. */\n readonly covarianceB: THREE.StorageBufferAttribute;\n /** Per-slot isotropic covariance mix (0 = keep Σ, 1 = round dot). */\n readonly isotropicMix: THREE.StorageBufferAttribute;\n /** Per-slot isotropic target sigma radius in screen px (0 = disabled). */\n readonly isotropicScreenRadius: THREE.StorageBufferAttribute;\n\n /**\n * Slots past {@link capacity}, reserved so a pipeline warm-up has somewhere\n * harmless to write. See {@link WorkBufferGather.warmUp}: compiling a compute\n * pipeline requires actually dispatching it, and dispatching zero workgroups\n * - while it does compile - makes WebGPU log an \"unusual workgroup count of 0\"\n * warning for every source. One workgroup aimed at this tail compiles just the\n * same, silently, and cannot disturb a drawn slot: admission never allocates\n * past `capacity` and the draw's `instanceCount` never covers it.\n */\n static readonly SCRATCH_SLOTS = 256;\n\n constructor(readonly capacity: number) {\n const slots = capacity + WorkBuffer.SCRATCH_SLOTS;\n this.centers = new THREE.StorageBufferAttribute(new Float32Array(slots * 4), 4);\n this.colors = new THREE.StorageBufferAttribute(new Float32Array(slots * 4), 4);\n this.covarianceA = new THREE.StorageBufferAttribute(new Float32Array(slots * 4), 4);\n this.covarianceB = new THREE.StorageBufferAttribute(new Float32Array(slots * 4), 4);\n this.isotropicMix = new THREE.StorageBufferAttribute(new Float32Array(slots), 1);\n this.isotropicScreenRadius = new THREE.StorageBufferAttribute(new Float32Array(slots), 1);\n this.mirrors = new StorageMirrorReleaser([\n this.centers,\n this.colors,\n this.covarianceA,\n this.covarianceB,\n this.isotropicMix,\n this.isotropicScreenRadius,\n ]);\n }\n\n /**\n * Drops the JS-heap copies three keeps behind these storage buffers.\n *\n * Every one of them is written only by the gather compute pass and read only\n * by the draw material - the CPU side is zeroes that no code ever reads, and\n * three's WebGPU backend never frees it on its own. At 72 B/slot that mirror\n * is ~720 MB of JS heap on a 10M-slot scene. See `storage-attribute-mirror`.\n *\n * Safe to call after any dispatch that bound this buffer; a no-op once\n * settled, and on WebGL2 or a renderer double that has no WebGPU backend.\n */\n releaseCpuMirrors(renderer: THREE.WebGPURenderer): void {\n if (this.mirrors.settled) return;\n this.mirrors.release(renderer);\n }\n\n private readonly mirrors: StorageMirrorReleaser;\n}\n\nexport class WorkBufferGather {\n /** World-space `xyzw` center buffer, one element per unified work slot. */\n readonly centers: THREE.StorageBufferAttribute;\n /** Resolved source RGBA values; production draw consumes this next. */\n readonly colors: THREE.StorageBufferAttribute;\n /** Packed symmetric world-space covariance rows. */\n readonly covarianceA: THREE.StorageBufferAttribute;\n readonly covarianceB: THREE.StorageBufferAttribute;\n readonly isotropicMix: THREE.StorageBufferAttribute;\n readonly isotropicScreenRadius: THREE.StorageBufferAttribute;\n /** Shared target for this source's gathered range. */\n readonly workBuffer: WorkBuffer;\n private readonly capacity: number;\n private readonly activeCount = uniform(0);\n private readonly targetOffset = uniform(0);\n private readonly sourceMatrix = uniform(new THREE.Matrix4());\n private readonly cameraViewMatrix = uniform(new THREE.Matrix4());\n private readonly opacity = uniform(1);\n private readonly pass: THREE.ComputeNode;\n\n constructor(options: {\n capacity: number;\n sourceCapacity: number;\n sourceIndex: THREE.StorageBufferAttribute;\n centersTexture: THREE.DataTexture;\n colorsTexture: THREE.DataTexture;\n covarianceATexture: THREE.DataTexture;\n covarianceBTexture: THREE.DataTexture;\n dataTextureWidth: number;\n sh?: SplatShInputs | null;\n modifiers?: readonly SplatModifier[];\n channels?: ReadonlyMap<string, { texture: THREE.DataTexture }>;\n localCameraPosition?: Vec3Uniform;\n srgbOutput?: boolean;\n /**\n * Source stores Spark LOD alpha (`alpha ÷ 2`, `.rad`). Recover the full\n * `alpha ∈ [0,2]` here so the shared draw material sees one convention;\n * `alpha > 1` then marks a merged node (see `createWorkBufferMaterial`).\n */\n lodAlpha?: boolean;\n /** Reuse one target across all source gather passes. */\n workBuffer?: WorkBuffer;\n }) {\n const {\n capacity,\n sourceCapacity,\n sourceIndex,\n centersTexture,\n colorsTexture,\n covarianceATexture,\n covarianceBTexture,\n dataTextureWidth,\n } = options;\n const sh = options.sh ?? null;\n const modifiers = options.modifiers ?? [];\n const channels = options.channels ?? new Map<string, { texture: THREE.DataTexture }>();\n const localCameraPosition = options.localCameraPosition ?? uniform(new THREE.Vector3());\n this.workBuffer = options.workBuffer ?? new WorkBuffer(capacity);\n this.capacity = this.workBuffer.capacity;\n this.centers = this.workBuffer.centers;\n this.colors = this.workBuffer.colors;\n this.covarianceA = this.workBuffer.covarianceA;\n this.covarianceB = this.workBuffer.covarianceB;\n this.isotropicMix = this.workBuffer.isotropicMix;\n this.isotropicScreenRadius = this.workBuffer.isotropicScreenRadius;\n const source = storage(sourceIndex, 'uint', sourceCapacity);\n const output = storage(this.centers, 'vec4', capacity);\n const outputColor = storage(this.colors, 'vec4', capacity);\n const outputCovA = storage(this.covarianceA, 'vec4', capacity);\n const outputCovB = storage(this.covarianceB, 'vec4', capacity);\n const outputIsotropicMix = storage(this.isotropicMix, 'float', capacity);\n const outputIsotropicScreenRadius = storage(this.isotropicScreenRadius, 'float', capacity);\n this.pass = Fn(() => {\n If(float(instanceIndex).lessThan(this.activeCount), () => {\n const poolIndex = int(source.element(instanceIndex));\n const texel = ivec2(\n poolIndex.mod(int(dataTextureWidth)),\n poolIndex.div(int(dataTextureWidth)),\n );\n const localCenter = textureLoad(centersTexture, texel).xyz;\n const target = instanceIndex.add(this.targetOffset.toUint());\n const inputA = textureLoad(covarianceATexture, texel);\n const inputB = textureLoad(covarianceBTexture, texel);\n const covarianceBase = mat3(\n vec3(inputA.x, inputA.y, inputA.z),\n vec3(inputA.y, inputA.w, inputB.x),\n vec3(inputA.z, inputB.x, inputB.y),\n );\n const baseColor = textureLoad(colorsTexture, texel);\n const colorAfterSh =\n sh === null\n ? baseColor\n : vec4(\n baseColor.rgb\n .add(\n evaluateSplatSh(\n sh,\n { covarianceBTexture },\n texel,\n localCenter.sub(localCameraPosition).normalize(),\n ),\n )\n .clamp(0.0, 1.0),\n baseColor.a,\n );\n const worldCenter = this.sourceMatrix.mul(vec4(localCenter, 1.0)).xyz;\n const stack = foldSplatModifierStack(modifiers, localCameraPosition, {\n index: poolIndex,\n localCenter,\n color: colorAfterSh,\n makeWorldCenter: () => worldCenter,\n makeViewCenter: () => this.cameraViewMatrix.mul(vec4(worldCenter, 1.0)).xyz,\n makeNormal: () => {\n const inverse = covarianceBase.inverse();\n const toCamera = localCameraPosition.sub(localCenter);\n const axis = inverse.mul(inverse.mul(toCamera)).normalize();\n return axis.mul(axis.dot(toCamera).sign());\n },\n makeChannel: (name) => {\n const channel = channels.get(name);\n if (!channel) {\n throw new Error(\n `UnifiedSplatRenderer: source modifier reads undefined channel \"${name}\".`,\n );\n }\n return textureLoad(channel.texture, texel).r;\n },\n });\n const center = stack.offset === null ? localCenter : localCenter.add(stack.offset);\n const visible = stack.visible === null ? bool(true) : stack.visible;\n // Stable work slots: keep hidden splats in the gather/sort range, but\n // stamp drawable=0 into center.w so the draw material clips them.\n // Alpha-zero remains a secondary guard in the color channel.\n // Compacting hidden splats out instead was evaluated and rejected\n // (ROADMAP L3): the hidden set exists only on the GPU, so compaction\n // needs a count readback before the CPU-sized sort dispatch - see the\n const gatheredCenter = this.sourceMatrix.mul(vec4(center, 1.0)).xyz;\n output.element(target).assign(vec4(gatheredCenter, visible.select(float(1), float(0))));\n const resolvedColor = options.srgbOutput\n ? stack.color\n : asNode<'vec4'>(colorSpaceToWorking(stack.color, THREE.SRGBColorSpace));\n // `.rad` stores `alpha ÷ 2`; recover it here rather than in the draw\n // material, which is shared by every source and must see one alpha\n // convention. The work buffer is float storage, so `alpha > 1` (a\n // merged node) survives to the draw unclamped.\n const sourceAlpha = options.lodAlpha\n ? asNode<'float'>(resolvedColor.a.mul(2.0))\n : asNode<'float'>(resolvedColor.a);\n outputColor\n .element(target)\n .assign(vec4(resolvedColor.rgb, visible.select(sourceAlpha.mul(this.opacity), float(0))));\n const covariance =\n stack.rotation === null\n ? covarianceBase\n : stack.rotation.mul(covarianceBase).mul(stack.rotation.transpose());\n // Collapse in mesh-local space (Spark's min(scale) is local), then lift\n // to world. Applying isotropic on world Σ with a local normal inflated\n // λ and produced large soft blobs under mesh transforms.\n let localCovariance: THREE.Node<'mat3'> = asNode<'mat3'>(covariance);\n if (stack.isotropicCovarianceMix !== null) {\n const varianceScale =\n stack.isotropicVarianceScale ?? float(DEFAULT_ISOTROPIC_VARIANCE_SCALE);\n localCovariance = applyIsotropicCovarianceOverride(\n localCovariance,\n vec3(0, 0, 1),\n stack.isotropicCovarianceMix,\n varianceScale,\n );\n }\n const linear = this.sourceMatrix.toMat3();\n const worldCovarianceBase = linear.mul(localCovariance).mul(linear.transpose());\n const worldCovariance =\n stack.scaleSquared === null\n ? worldCovarianceBase\n : worldCovarianceBase.mul(stack.scaleSquared);\n const c0 = worldCovariance.mul(vec3(1, 0, 0));\n const c1 = worldCovariance.mul(vec3(0, 1, 0));\n const c2 = worldCovariance.mul(vec3(0, 0, 1));\n outputCovA.element(target).assign(vec4(c0.x, c0.y, c0.z, c1.y));\n outputCovB.element(target).assign(vec4(c1.z, c2.z, 0, 0));\n outputIsotropicMix\n .element(target)\n .assign(stack.isotropicCovarianceMix === null ? float(0) : stack.isotropicCovarianceMix);\n outputIsotropicScreenRadius\n .element(target)\n .assign(\n stack.isotropicCovarianceMix === null\n ? float(0)\n : (stack.isotropicScreenRadiusPx ?? float(0)),\n );\n });\n })().compute(sourceCapacity, [256]);\n }\n\n /** Gathers a source's active pool slots into a contiguous work-buffer range. */\n gather(\n renderer: THREE.WebGPURenderer,\n activeCount: number,\n targetOffset: number,\n sourceMatrix: THREE.Matrix4,\n cameraViewMatrixOrOpacity: THREE.Matrix4 | number = new THREE.Matrix4(),\n opacity = 1,\n ): void {\n if (activeCount < 0 || targetOffset < 0 || targetOffset + activeCount > this.capacity) {\n throw new Error('WorkBufferGather: requested range exceeds work-buffer capacity.');\n }\n this.activeCount.value = activeCount;\n this.targetOffset.value = targetOffset;\n this.sourceMatrix.value.copy(sourceMatrix);\n const cameraViewMatrix =\n typeof cameraViewMatrixOrOpacity === 'number'\n ? new THREE.Matrix4()\n : cameraViewMatrixOrOpacity;\n this.cameraViewMatrix.value.copy(cameraViewMatrix);\n this.opacity.value = Math.max(\n 0,\n typeof cameraViewMatrixOrOpacity === 'number' ? cameraViewMatrixOrOpacity : opacity,\n );\n this.pass.count = activeCount;\n renderer.compute(this.pass);\n // The dispatch just built this pass's bind group, which is what makes three\n // create the storage buffers - synchronously. Nothing reads their CPU side\n // back, so drop it. A `WorkBuffer` shared by several gathers releases on\n // whichever dispatches first; the rest find it settled.\n this.workBuffer.releaseCpuMirrors(renderer);\n }\n\n /**\n * Compiles this gather's compute pipeline without gathering anything.\n *\n * WebGPU compiles a compute pipeline lazily, at its first dispatch - not when\n * the node graph is built. Measured on a 13-marker scene, a source's *first*\n * `gather` took up to 2.0s while every later dispatch of the same pipeline\n * took under 0.4ms, and because `gather` runs inside `update` that compile\n * landed whole in one render frame. Running it through the async path here\n * compiles off the critical frame, so the first real gather finds it ready.\n *\n * The dispatch targets {@link WorkBuffer.SCRATCH_SLOTS} past the drawable\n * capacity rather than dispatching zero workgroups: zero also compiles, but\n * WebGPU warns about it once per source, and writing into a drawn slot could\n * leave junk behind when a cached gather is reused instead of re-run.\n *\n * Best-effort: a failure here costs only the stall it was meant to avoid, so\n * callers may ignore it.\n */\n async warmUp(renderer: THREE.WebGPURenderer): Promise<void> {\n this.activeCount.value = 1;\n this.targetOffset.value = this.capacity;\n this.pass.count = 1;\n await renderer.computeAsync(this.pass);\n // Leave nothing that a later reuse check could mistake for real state.\n this.activeCount.value = 0;\n this.targetOffset.value = 0;\n // Warm-up is often the first dispatch of all, so this is usually where the\n // mirrors actually go.\n this.workBuffer.releaseCpuMirrors(renderer);\n }\n\n /** Releases the gather pipeline. The owner releases the storage attribute. */\n dispose(): void {\n this.pass.dispose();\n }\n}\n","import * as THREE from 'three/webgpu';\nimport type { FloatUniform, Vec2Uniform } from './splat-mesh-material';\nimport {\n capProjectedEigenvaluesToScreenRadius,\n equalizeProjectedEigenvalues,\n} from './splat-mesh-material';\nimport { MAX_DOF_VARIANCE } from './depth-of-field';\nimport {\n Discard,\n Fn,\n cameraProjectionMatrix,\n float,\n instanceIndex,\n mat3,\n mix,\n modelViewMatrix,\n positionGeometry,\n screenUV,\n storage,\n texture as tslTexture,\n varying,\n vec2,\n vec3,\n vec4,\n} from 'three/tsl';\n\n/**\n * Creates the unified EWA draw material for gathered world-space splats.\n *\n * Its vertex stage reads both the sorter-written order and the gather-written\n * centers, covariance, and resolved color from storage buffers. It keeps the\n * verified ±3σ / exp(-4.5·|q|²) convention used by {@link SplatMesh}.\n */\nexport function createWorkBufferMaterial(options: {\n capacity: number;\n centers: THREE.StorageBufferAttribute;\n colors: THREE.StorageBufferAttribute;\n covarianceA: THREE.StorageBufferAttribute;\n covarianceB: THREE.StorageBufferAttribute;\n isotropicMix: THREE.StorageBufferAttribute;\n isotropicScreenRadius: THREE.StorageBufferAttribute;\n order: THREE.StorageInstancedBufferAttribute;\n focal: Vec2Uniform;\n viewport: Vec2Uniform;\n maxStdDev: FloatUniform;\n /** Screen-space minimum splat radius in px; `0` disables. */\n minSplatSizePx: FloatUniform;\n antialias: FloatUniform;\n /** Format-selected projected low-pass variance, in px². */\n projectedLowPassVariance: FloatUniform;\n /** LCC always preserves the low-pass integral; standard follows antialias. */\n compensateProjectedLowPass: FloatUniform;\n /** Core projected-2D DoF focus plane. Live; `0` aperture disables. */\n dofFocusDistance: FloatUniform;\n dofAperture: FloatUniform;\n /**\n * Proxy-mesh relight map (RGB = lit, A = coverage). Display only.\n * Host swaps the texture object and rebuilds when enabling/changing map.\n */\n relightMap: THREE.Texture;\n relightBlend: FloatUniform;\n relightBrightness: FloatUniform;\n relightBackground: FloatUniform;\n /** Coverage soft edge in screen pixels; `0` = hard mask. */\n relightSoftness: FloatUniform;\n}): THREE.NodeMaterial {\n const material = new THREE.NodeMaterial();\n const centers = storage(options.centers, 'vec4', options.capacity);\n const order = storage(options.order, 'float', options.capacity);\n const colors = storage(options.colors, 'vec4', options.capacity);\n const covarianceA = storage(options.covarianceA, 'vec4', options.capacity);\n const covarianceB = storage(options.covarianceB, 'vec4', options.capacity);\n const isotropicMix = storage(options.isotropicMix, 'float', options.capacity);\n const isotropicScreenRadius = storage(options.isotropicScreenRadius, 'float', options.capacity);\n const workColor = varying(vec4(1, 1, 1, 1), 'vWorkColor');\n const quadPosition = varying(positionGeometry.xy, 'vWorkQuadPosition');\n const opacityCompensation = varying(float(1), 'vWorkOpacityCompensation');\n // Spark LOD alpha, matching `SplatMesh`'s display graph. The gather has\n // already recovered `alpha ∈ [0,2]` for `.rad` sources, so `alpha > 1`\n // identifies a merged node here without a per-source shader variant: a\n // non-`.rad` source can only reach `alpha ≤ 1`, for which the branches below\n // collapse to the plain Gaussian this material has always drawn.\n const adjustedStdDev = varying(float(0), 'vWorkAdjustedStdDev');\n material.vertexNode = Fn(() => {\n const workIndex = order.element(instanceIndex).toInt();\n const centerSample = centers.element(workIndex);\n const center = centerSample.xyz;\n // Gather stamps drawable into center.w (1 = draw, 0 = modifier-hidden).\n const drawable = centerSample.w.greaterThan(0.5);\n workColor.assign(colors.element(workIndex));\n const viewCenter = modelViewMatrix.mul(vec4(center, 1.0)).toVar();\n const clipCenter = cameraProjectionMatrix.mul(viewCenter).toVar();\n const covA = covarianceA.element(workIndex);\n const covB = covarianceB.element(workIndex);\n const covariance = mat3(\n vec3(covA.x, covA.y, covA.z),\n vec3(covA.y, covA.w, covB.x),\n vec3(covA.z, covB.x, covB.y),\n );\n const invZ = float(1).div(viewCenter.z);\n const invZ2 = invZ.mul(invZ);\n const j1 = vec3(\n options.focal.x.mul(invZ),\n 0,\n options.focal.x.negate().mul(viewCenter.x).mul(invZ2),\n );\n const j2 = vec3(\n 0,\n options.focal.y.mul(invZ),\n options.focal.y.negate().mul(viewCenter.y).mul(invZ2),\n );\n const viewRotationT = modelViewMatrix.toMat3().transpose();\n const u1 = viewRotationT.mul(j1);\n const u2 = viewRotationT.mul(j2);\n const aRaw = u1.dot(covariance.mul(u1));\n const dRaw = u2.dot(covariance.mul(u2));\n const bRaw = u1.dot(covariance.mul(u2));\n\n const depth = viewCenter.z.negate().max(1e-4);\n const focus = options.dofFocusDistance.max(1e-4);\n // Aperture maps through the live focus plane (Spark host-helper parity), so\n // a near focus plane widens the angle and softens the whole scene.\n const halfApertureAngle = options.dofAperture.max(0).mul(0.5).div(focus).atan();\n // Spark's falloff (`/depth`). Unbounded near the camera; MAX_DOF_VARIANCE\n // below is the fill-rate guard, exactly as Spark's maxPixelRadius is.\n const focusBlur = depth.sub(focus).abs().div(depth);\n const apertureRadius = options.focal.x.mul(halfApertureAngle.tan());\n const cocRadiusPx = focusBlur.mul(apertureRadius);\n const cocVar = cocRadiusPx.mul(cocRadiusPx).min(float(MAX_DOF_VARIANCE));\n\n const a = aRaw.add(options.projectedLowPassVariance).add(cocVar);\n const d = dRaw.add(options.projectedLowPassVariance).add(cocVar);\n const b = bRaw;\n const isoMix = isotropicMix.element(workIndex);\n const detBlur = a.mul(d).sub(b.mul(b)).max(1e-9);\n // Classic LCC always compensates its 0.1 px² low-pass. Standard sources\n // retain their antialias-controlled 0.3 px² compatibility behavior.\n const detRaw = aRaw.mul(dRaw).sub(bRaw.mul(bRaw)).max(0);\n const detBase = aRaw\n .add(options.projectedLowPassVariance)\n .mul(dRaw.add(options.projectedLowPassVariance))\n .sub(bRaw.mul(bRaw))\n .max(1e-9);\n const mipFade = detRaw.div(detBlur).sqrt();\n const dofOnlyFade = detBase.div(detBlur).sqrt();\n const compensate = options.antialias.max(options.compensateProjectedLowPass);\n const fade = mix(dofOnlyFade, mipFade, compensate);\n // Screen-capped isotropic points must not inherit the mip-filter opacity\n // fade - it was derived from the pre-cap footprint and washes out dots.\n opacityCompensation.assign(mix(fade, float(1), isoMix));\n const mid = a.add(d).mul(0.5);\n const radius = vec2(a.sub(d).mul(0.5), b).length();\n let lambda1 = mid.add(radius);\n let lambda2 = mid.sub(radius).max(0);\n const equalized = equalizeProjectedEigenvalues(lambda1, lambda2, isoMix);\n lambda1 = equalized.lambda1;\n lambda2 = equalized.lambda2;\n const capped = capProjectedEigenvaluesToScreenRadius(\n lambda1,\n lambda2,\n isoMix,\n isotropicScreenRadius.element(workIndex),\n options.maxStdDev,\n );\n lambda1 = capped.lambda1;\n lambda2 = capped.lambda2;\n // A merged node (`alpha > 1`) grows the σ-cutoff `maxStdDev + 0.7·(remap−1)`\n // so one coarse splat covers the subtree it stands in for; the covariance is\n // untouched. A leaf keeps the base cutoff. `SplatMesh` does the same at\n // `applySplatMaterialGraph`'s `lodAlpha` branch.\n const remap = workColor.a.mul(4).sub(3).min(5);\n const stdDev = workColor.a\n .greaterThan(1)\n .select(options.maxStdDev.add(remap.sub(1).mul(0.7)), options.maxStdDev);\n adjustedStdDev.assign(stdDev);\n const eigenvector = vec2(b, lambda1.sub(a)).add(vec2(1e-6, 0)).normalize();\n // Screen-space minimum on each axis: a splat below the floor grows to it so\n // its Gaussian tiles with neighbours instead of leaving dark gaps between\n // sparse zoomed-out splats; already-large splats are untouched. Mirrors\n // `applySplatMaterialGraph`. `.max` after `.min(1024)` keeps the order valid.\n const minSplat = options.minSplatSizePx;\n const major = eigenvector.mul(lambda1.sqrt().mul(stdDev).min(1024).max(minSplat));\n const minor = vec2(eigenvector.y, eigenvector.x.negate()).mul(\n lambda2.sqrt().mul(stdDev).min(1024).max(minSplat),\n );\n const pixelOffset = major.mul(positionGeometry.x).add(minor.mul(positionGeometry.y));\n const ndcCenter = clipCenter.xy.div(clipCenter.w);\n const clipPosition = vec4(\n ndcCenter.add(pixelOffset.mul(2).div(options.viewport)),\n clipCenter.z.div(clipCenter.w),\n 1,\n );\n // Match SplatMesh's conservative center-frustum rejection. Without this,\n // a center behind the camera can produce a mirrored, screen-filling quad.\n // Modifier-hidden entries (drawable=0) share the clipped destination so\n // they generate no fragments while keeping a stable sort slot.\n const margin = clipCenter.w.mul(1.2);\n const inFrustum = clipCenter.z\n .greaterThan(margin.negate())\n .and(clipCenter.x.abs().lessThan(margin))\n .and(clipCenter.y.abs().lessThan(margin));\n return inFrustum.and(drawable).select(clipPosition, vec4(0, 0, 2, 1));\n })();\n material.fragmentNode = Fn(() => {\n const squaredDistance = quadPosition.dot(quadPosition);\n Discard(squaredDistance.greaterThan(1));\n // Spark's LOD falloff, mirroring `SplatMesh`'s display fragment.\n // `g = exp(-½·adjustedStdDev²·|q|²)` is the Gaussian at this fragment. A leaf\n // (`alpha ≤ 1`, and every non-`.rad` source) composites `g · alpha`. A merged\n // node uses the super-Gaussian plateau `1 − (1 − g)^a`, `a = exp((remap²−1)/e)`,\n // so a coarse splat fills its subtree's footprint without inflating Σ.\n const g = squaredDistance.mul(adjustedStdDev.mul(adjustedStdDev).mul(-0.5)).exp();\n const remap = workColor.a.mul(4).sub(3).min(5);\n const aExp = remap\n .mul(remap)\n .sub(1)\n .mul(1 / Math.E)\n .exp();\n const merged = g.oneMinus().pow(aExp).oneMinus();\n const opacity = workColor.a.greaterThan(1).select(merged, g.mul(workColor.a));\n const alpha = opacity.mul(opacityCompensation);\n const litCenter = tslTexture(options.relightMap, screenUV);\n const ox = options.relightSoftness.div(options.viewport.x.max(1));\n const oy = options.relightSoftness.div(options.viewport.y.max(1));\n const s0 = litCenter;\n const s1 = tslTexture(options.relightMap, screenUV.add(vec2(ox, 0)));\n const s2 = tslTexture(options.relightMap, screenUV.add(vec2(ox.negate(), 0)));\n const s3 = tslTexture(options.relightMap, screenUV.add(vec2(0, oy)));\n const s4 = tslTexture(options.relightMap, screenUV.add(vec2(0, oy.negate())));\n const wSum = s0.a.add(s1.a).add(s2.a).add(s3.a).add(s4.a).max(1e-4);\n const rgbSoft = s0.rgb\n .mul(s0.a)\n .add(s1.rgb.mul(s1.a))\n .add(s2.rgb.mul(s2.a))\n .add(s3.rgb.mul(s3.a))\n .add(s4.rgb.mul(s4.a))\n .div(wSum);\n const aSoft = wSum.mul(0.2);\n const litSoft = vec4(rgbSoft, aSoft);\n const lit = options.relightSoftness.greaterThan(0.5).select(litSoft, litCenter);\n const factor = mix(\n vec3(options.relightBackground),\n lit.rgb.mul(options.relightBrightness),\n lit.a,\n );\n const rgb = mix(workColor.rgb, workColor.rgb.mul(factor), options.relightBlend);\n return vec4(rgb.mul(alpha), alpha);\n })();\n material.transparent = true;\n material.depthTest = true;\n material.depthWrite = false;\n material.side = THREE.DoubleSide;\n material.blending = THREE.CustomBlending;\n material.blendSrc = THREE.OneFactor;\n material.blendDst = THREE.OneMinusSrcAlphaFactor;\n material.blendSrcAlpha = THREE.OneFactor;\n material.blendDstAlpha = THREE.OneMinusSrcAlphaFactor;\n material.toneMapped = false;\n return material;\n}\n","import type { WebGPURenderer } from 'three/webgpu';\n\n/**\n * WebGPU device limits hosts should request for VLAM beyond the spec defaults.\n *\n * The WebGPU default `maxStorageBufferBindingSize` is 128 MiB. Unified work\n * buffers and large streamed pools allocate storage attributes past that\n * (16 bytes per splat for an RGBA32F centers buffer). Desktop adapters often\n * advertise ~2 GiB, but only when those values are passed as `requiredLimits`\n * to `adapter.requestDevice()` / Three.js `WebGPURenderer`.\n *\n * `maxTextureDimension2D` is here for the same reason: the default is 8192,\n * which caps a pool at 8192 rows of {@link SPLAT_DATA_TEXTURE_WIDTH} (≈16.7 M\n * splats of *capacity*, so ≈11.2 M resident at the usual 1.5 slack). Desktop\n * adapters commonly advertise 16384 and double that ceiling for free.\n */\nexport interface WebGpuRequiredLimits {\n maxStorageBufferBindingSize: number;\n maxBufferSize: number;\n maxTextureDimension2D: number;\n // Assignable to the `Record<string, number>` Three.js WebGPURenderer takes\n // as `requiredLimits`, and open to further WebGPU limit names.\n [limit: string]: number;\n}\n\n/** Bytes per splat in the largest unified work-buffer attribute (RGBA32F centers). */\nexport const WORK_BUFFER_CENTERS_BYTES_PER_SPLAT = 16;\n\n/**\n * Bytes per work-buffer slot across *all* of its attributes: centers, colors,\n * covarianceA and covarianceB at RGBA32F (16 each), plus the isotropic mix and\n * screen-radius scalars (4 each).\n */\nexport const WORK_BUFFER_BYTES_PER_SLOT = 16 * 4 + 4 * 2;\n\n/**\n * WebGPU's default `maxStorageBufferBindingSize` when the host does not raise\n * it via `requiredLimits` (128 MiB).\n */\nexport const WEBGPU_DEFAULT_MAX_STORAGE_BUFFER_BINDING_SIZE = 134_217_728;\n\n/**\n * Copies the adapter's advertised buffer maxima for use as\n * `WebGPURenderer({ requiredLimits })`.\n *\n * Does not call `requestDevice` - the host owns device creation.\n */\nexport function recommendedWebGpuRequiredLimits(adapter: {\n limits: {\n maxStorageBufferBindingSize: number;\n maxBufferSize: number;\n maxTextureDimension2D?: number;\n };\n}): WebGpuRequiredLimits {\n return {\n maxStorageBufferBindingSize: adapter.limits.maxStorageBufferBindingSize,\n maxBufferSize: adapter.limits.maxBufferSize,\n // Spec minimum, so an adapter that somehow reports nothing still requests a\n // legal value rather than `undefined` (which requestDevice rejects).\n maxTextureDimension2D: adapter.limits.maxTextureDimension2D ?? 8192,\n };\n}\n\n/** Mirrors WebGPU `GPUPowerPreference` without requiring `@webgpu/types`. */\nexport type WebGpuPowerPreference = 'high-performance' | 'low-power';\n\ntype NavigatorPlatformProbe = Pick<Navigator, 'platform' | 'userAgent'> & {\n userAgentData?: { platform?: string };\n};\n\n/**\n * Whether the host should pass `powerPreference` to `navigator.gpu.requestAdapter`\n * / Three.js `WebGPURenderer`.\n *\n * Chrome on Windows ignores the option and logs a console warning when it is\n * present ([crbug.com/369219127](https://crbug.com/369219127)). Prefer\n * {@link webGpuPowerPreferenceOptions} so Windows hosts omit it automatically.\n */\nexport function supportsWebGpuPowerPreference(\n nav: NavigatorPlatformProbe | undefined = typeof navigator !== 'undefined'\n ? navigator\n : undefined,\n): boolean {\n if (!nav) return true;\n\n const uaDataPlatform = nav.userAgentData?.platform;\n if (typeof uaDataPlatform === 'string' && uaDataPlatform.length > 0) {\n return uaDataPlatform !== 'Windows';\n }\n\n // `navigator.platform` is \"Win32\" on both 32- and 64-bit Windows browsers.\n if (/^Win/i.test(nav.platform ?? '')) return false;\n return !/Windows/i.test(nav.userAgent ?? '');\n}\n\n/**\n * Options to spread into `requestAdapter(...)` and `WebGPURenderer({...})`.\n * Empty on Windows so Chrome does not warn about an ignored `powerPreference`.\n */\nexport function webGpuPowerPreferenceOptions(\n powerPreference: WebGpuPowerPreference = 'high-performance',\n nav?: NavigatorPlatformProbe,\n): { powerPreference: WebGpuPowerPreference } | Record<string, never> {\n return supportsWebGpuPowerPreference(nav) ? { powerPreference } : {};\n}\n\n/**\n * Byte size of the largest single storage-buffer bind VLAM allocates for a\n * given splat capacity (unified work-buffer centers: `capacity × 16`).\n *\n * Use with adapter/device limits to decide whether to raise `requiredLimits`\n * or lower the budget before constructing {@link UnifiedSplatRenderer}.\n */\nexport function estimateLargestStorageBufferBytes(capacity: number): number {\n if (!Number.isFinite(capacity) || capacity < 0) {\n throw new RangeError('Splat capacity must be a non-negative finite number.');\n }\n return Math.floor(capacity) * WORK_BUFFER_CENTERS_BYTES_PER_SPLAT;\n}\n\n/**\n * Estimates what a {@link UnifiedSplatRenderer}'s work buffer costs, in bytes,\n * once the scene has rendered a frame.\n *\n * This used to be double the figure below, and the doubling was not a detail.\n * Every `THREE.StorageBufferAttribute` is constructed around a JS typed array,\n * and three's WebGPU backend never releases it: the\n * `attribute.onUploadCallback()` call that would is missing from the backend's\n * buffer upload path. So a work buffer of N slots cost\n * N × {@link WORK_BUFFER_BYTES_PER_SLOT} on the GPU *and* the same again on the\n * JS heap, permanently, even though nothing ever reads the CPU copy back - the\n * gather pass writes these buffers entirely on the GPU.\n *\n * `WorkBuffer.releaseCpuMirrors` now drops those mirrors after the first\n * dispatch that uploads them (see `storage-attribute-mirror`), so steady state\n * is the GPU side alone. The *peak* is still double - the array has to exist\n * for the upload copy - which is what\n * {@link estimateUnifiedWorkBufferPeakBytes} reports.\n *\n * Either figure is invisible to `estimateSplatPoolBytes`, which prices the\n * splat *pool* and knows nothing about the unified renderer's scratch space. A\n * host sizing a scene for a memory-constrained device wants both.\n *\n * @param capacity - Work-buffer capacity in splats.\n * @returns Estimated steady-state bytes.\n * @throws {RangeError} if `capacity` is not a non-negative finite number.\n */\nexport function estimateUnifiedWorkBufferBytes(capacity: number): number {\n if (!Number.isFinite(capacity) || capacity < 0) {\n throw new RangeError('Splat capacity must be a non-negative finite number.');\n }\n return Math.floor(capacity) * WORK_BUFFER_BYTES_PER_SLOT;\n}\n\n/**\n * Peak bytes a {@link UnifiedSplatRenderer}'s work buffer occupies: the window\n * between construction and the first dispatch, while the GPU buffer and the JS\n * array three copied it from are both alive.\n *\n * Ask this one when the question is \"will allocating this scene succeed\" - an\n * out-of-memory failure happens against the peak. Ask\n * {@link estimateUnifiedWorkBufferBytes} when the question is \"what will this\n * scene hold once it is up\".\n *\n * @param capacity - Work-buffer capacity in splats.\n * @returns Estimated bytes, GPU plus the not-yet-released CPU mirror.\n * @throws {RangeError} if `capacity` is not a non-negative finite number.\n */\nexport function estimateUnifiedWorkBufferPeakBytes(capacity: number): number {\n return estimateUnifiedWorkBufferBytes(capacity) * 2;\n}\n\n/**\n * The renderer's WebGPU `maxStorageBufferBindingSize`, or 0 when it cannot be\n * read (WebGL2 fallback, or device not yet available). Callers skip the check\n * when 0 rather than falsely reject.\n */\nexport function deviceMaxStorageBufferBindingSize(renderer: WebGPURenderer): number {\n const backend = renderer.backend as\n | {\n isWebGPUBackend?: boolean;\n device?: { limits?: { maxStorageBufferBindingSize?: number } };\n }\n | undefined;\n if (backend?.isWebGPUBackend !== true) return 0;\n const limit = backend.device?.limits?.maxStorageBufferBindingSize;\n return typeof limit === 'number' && limit > 0 ? limit : 0;\n}\n\n/**\n * Throws when a planned storage buffer would exceed the device bind limit.\n * No-ops when the limit cannot be read (WebGL2 / uninitialized backend).\n */\nexport function assertStorageBufferFitsDevice(\n renderer: WebGPURenderer,\n byteSize: number,\n capacity: number,\n): void {\n const limit = deviceMaxStorageBufferBindingSize(renderer);\n if (limit <= 0 || byteSize <= limit) return;\n\n const needMiB = (byteSize / (1024 * 1024)).toFixed(1);\n const limitMiB = (limit / (1024 * 1024)).toFixed(1);\n const defaultMiB = (WEBGPU_DEFAULT_MAX_STORAGE_BUFFER_BINDING_SIZE / (1024 * 1024)).toFixed(0);\n throw new Error(\n `vlam: storage buffer needs ${needMiB} MiB for ${capacity.toLocaleString('en-US')} splats, ` +\n `but device maxStorageBufferBindingSize is ${limitMiB} MiB ` +\n `(WebGPU default is ${defaultMiB}). Pass recommendedWebGpuRequiredLimits(adapter) to ` +\n `WebGPURenderer, or lower the splat budget.`,\n );\n}\n","import * as THREE from 'three/webgpu';\nimport { uniform } from 'three/tsl';\nimport { ComputeSorter, releaseRendererAttributes } from './compute-sorter';\nimport { RadixSorter } from './radix-sorter';\nimport { clampDepthOfFieldSettings, type DepthOfFieldSettings } from './depth-of-field';\nimport {\n clampRelightingSettings,\n createPlaceholderRelightTexture,\n DEFAULT_RELIGHT_BACKGROUND,\n DEFAULT_RELIGHT_BRIGHTNESS,\n DEFAULT_RELIGHT_SOFTNESS,\n type RelightingSettings,\n type RelightingUniforms,\n} from './relighting';\nimport { SplatMesh } from './splat-mesh';\nimport type { SplatPickOptions, SplatPickResult, UnifiedSourceView } from './splat-mesh';\nimport { isFillConstrainedSplatDevice } from './splat-budget';\nimport { WebGpuSortScheduler } from './sort-scheduler';\nimport { WorkBuffer, WorkBufferGather } from './work-buffer-gather';\nimport { createWorkBufferMaterial } from './work-buffer-material';\nimport type { FloatUniform, Vec2Uniform } from './splat-mesh-material';\nimport { assertStorageBufferFitsDevice, estimateLargestStorageBufferBytes } from './webgpu-limits';\nimport { resolveXrView } from './xr-view';\nimport { StorageMirrorReleaser } from './storage-attribute-mirror';\nimport type { SplatSorter } from './sorter';\n\ninterface SourceRecord {\n source: SplatMesh;\n gather: WorkBufferGather;\n graphRevision: number;\n priority: number;\n opacity: number;\n visible: boolean;\n /** Whether modifier uniforms are host-invalidated instead of assumed live. */\n cacheModifiers: boolean;\n originalVisible: boolean;\n lastGather: {\n activeCount: number;\n contentRevision: number;\n offset: number;\n opacity: number;\n matrixWorld: THREE.Matrix4;\n /** Local camera position used by view-dependent SH evaluation. */\n localCameraPosition: THREE.Vector3;\n } | null;\n /** Per-prepare scratch: the source view snapshot for the current frame. */\n view: UnifiedSourceView | null;\n /** Per-prepare scratch: registration index for stable priority ordering. */\n registrationOrder: number;\n /** Per-prepare scratch: this frame's admitted work-buffer offset. */\n offset: number;\n}\n\n/** One admitted source's work-buffer ownership from a prepared frame. */\ninterface LayoutEntry {\n source: SplatMesh;\n offset: number;\n activeCount: number;\n}\n\n/** Registration settings for one source in a {@link UnifiedSplatRenderer}. */\nexport interface UnifiedSplatSourceOptions {\n /** Whole-source opacity applied after its modifier stack. Defaults to `1`. */\n opacity?: number;\n /** Whether this source contributes to the unified draw. Defaults to `true`. */\n visible?: boolean;\n /** Higher values survive a fixed work-buffer overflow first. Defaults to `0`. */\n priority?: number;\n /**\n * Cache a modifier-bearing source until {@link UnifiedSplatRenderer.invalidateSource}\n * is called. Defaults to `false`, which safely re-gathers live modifier\n * uniforms every frame.\n *\n * Use this for static modifiers, or when the host has a natural dirty signal\n * for every uniform/channel mutation. Forgetting to invalidate leaves the\n * unified work-buffer output stale.\n */\n cacheModifiers?: boolean;\n}\n\n/**\n * Result of a successful {@link UnifiedSplatRenderer.pick}: the frontmost hit\n * across every visible registered source, tagged with the source it landed on.\n */\nexport interface UnifiedSplatPickResult extends SplatPickResult {\n /** The registered source mesh whose splat produced the winning hit. */\n readonly source: SplatMesh;\n}\n\n/**\n * Construction settings shared by every source in a unified renderer.\n *\n * @experimental May change in a minor release.\n */\nexport interface UnifiedSplatRendererOptions {\n /** Composite source colors in display (sRGB) space. Defaults to `false`. */\n srgbOutput?: boolean;\n /**\n * Global depth-sort strategy. `'exact'` preserves every Float32 depth bit\n * through a stable radix sort of gathered world-space centers. `'counting'`\n * remains the lower-cost default for existing callers.\n *\n * @experimental Exact sorting trades two additional radix passes for better\n * ordering in large scenes with dense foliage or overlapping surfaces.\n */\n sortStrategy?: 'counting' | 'exact';\n}\n\n/**\n * Returns true when `renderer` can drive {@link UnifiedSplatRenderer}.\n * Heterogeneous gather/sort/draw is WebGPU-only; WebGL2 hosts keep standalone\n * `SplatMesh` draws or static {@link SplatScene}.\n */\nexport function supportsUnifiedSplatRenderer(renderer: object): boolean {\n return (\n (renderer as { backend?: { isWebGPUBackend?: boolean } }).backend?.isWebGPUBackend === true\n );\n}\n\n/**\n * One gather → global-sort → draw path for static and streamed splat sources.\n * Sources retain their own residency and update lifecycle; this mesh gathers\n * their current active pool ranges into one shared work buffer per frame.\n *\n * The unified mesh stays at identity (`matrixAutoUpdate` is off). Pose each\n * registered source mesh instead - sorter bounds and the draw material both\n * consume world-space centers written by gather.\n *\n * WebGPU only. Prefer {@link supportsUnifiedSplatRenderer} before construction.\n *\n * @experimental May change in a minor release.\n */\nexport class UnifiedSplatRenderer extends THREE.Mesh {\n private readonly workBuffer: WorkBuffer;\n private readonly workSourceIndex: THREE.StorageBufferAttribute;\n /** The sorted draw-order buffer; lives outside the geometry, freed on dispose. */\n private readonly orderAttribute: THREE.StorageInstancedBufferAttribute;\n /**\n * Frees the JS mirrors three keeps behind `workSourceIndex` and `order`.\n *\n * `workSourceIndex` is an identity ramp written once at construction; `order`\n * is written only by the sorter, on the GPU. Neither is ever read back, so\n * both mirrors are 4 B/slot of dead JS heap once uploaded.\n */\n private readonly mirrors: StorageMirrorReleaser;\n private readonly sorter: SplatSorter;\n private readonly renderer: THREE.WebGPURenderer;\n private readonly sources: SourceRecord[] = [];\n private previousLayout: LayoutEntry[] = [];\n /** Previous layout keyed by source; kept in step with {@link previousLayout}. */\n private readonly previousLayoutBySource = new Map<SplatMesh, LayoutEntry>();\n /** Per-prepare scratch lists, reused so a steady frame allocates nothing. */\n private readonly candidateScratch: SourceRecord[] = [];\n private readonly admittedScratch: SourceRecord[] = [];\n private readonly drawingBufferSize = new THREE.Vector2();\n /** Reused target dimensions for secondary views; avoids per-view garbage. */\n private readonly secondaryViewSize = new THREE.Vector2();\n /**\n * Camera gating for the global sorter, mirroring the standalone\n * `SplatMesh.requestSortIfNeeded` path: an unchanged work buffer under a\n * stationary camera skips the whole-buffer re-sort. Content changes\n * (regather, layout change) force the next sort through the scheduler.\n */\n private readonly sortScheduler: WebGpuSortScheduler;\n /** Model-view of the last accepted sort; starts unmatchable (zero scale). */\n private readonly lastSortedModelView = new THREE.Matrix4().makeScale(0, 0, 0);\n /**\n * Total admitted splats in the last prepared frame. `removeSource` filters\n * {@link previousLayout} directly, so a tail source's removal can leave the\n * remaining entries identical - only this total betrays the shrink, and a\n * shrunk draw range must re-sort (the old permutation indexes past it).\n */\n private previousAdmittedTotal = 0;\n private readonly bounds = new THREE.Sphere();\n private readonly focal: Vec2Uniform;\n private readonly viewport: Vec2Uniform;\n private readonly maxStdDev: FloatUniform;\n private readonly minSplatSizePx: FloatUniform;\n private readonly antialias: FloatUniform;\n private readonly projectedLowPassVariance: FloatUniform;\n private readonly compensateProjectedLowPass: FloatUniform;\n private readonly dofFocusDistance: FloatUniform;\n private readonly dofAperture: FloatUniform;\n private readonly relightPlaceholder: THREE.DataTexture;\n private relightMap: THREE.Texture;\n private readonly relightBlend: FloatUniform;\n private readonly relightBrightness: FloatUniform;\n private readonly relightBackground: FloatUniform;\n private readonly relightSoftness: FloatUniform;\n private readonly srgbOutput: boolean;\n private sourceMaxStdDev: number | null = null;\n private sourceAntialias: boolean | null = null;\n private sourceProjectedFilterProfile: 'default' | 'lcc' | null = null;\n private overflowedSourceCount = 0;\n private overflowedSplatCount = 0;\n private disposed = false;\n\n constructor(\n renderer: THREE.WebGPURenderer,\n capacity: number,\n options: UnifiedSplatRendererOptions = {},\n ) {\n if (!supportsUnifiedSplatRenderer(renderer)) {\n throw new Error(\n 'UnifiedSplatRenderer requires a WebGPU backend (renderer.backend.isWebGPUBackend). ' +\n 'On WebGL2 use standalone SplatMesh draws or static SplatScene.',\n );\n }\n // Fail before allocating StorageBufferAttributes that would trip a cryptic\n // CreateBindGroup validation error when the host left the 128 MiB default.\n assertStorageBufferFitsDevice(renderer, estimateLargestStorageBufferBytes(capacity), capacity);\n const workBuffer = new WorkBuffer(capacity);\n const focal = uniform(new THREE.Vector2());\n const viewport = uniform(new THREE.Vector2());\n const maxStdDev = uniform(3);\n const minSplatSizePx = uniform(0);\n const antialias = uniform(0);\n const projectedLowPassVariance = uniform(0.3);\n const compensateProjectedLowPass = uniform(0);\n const dofFocusDistance = uniform(10);\n const dofAperture = uniform(0);\n const relightPlaceholder = createPlaceholderRelightTexture();\n const relightBlend = uniform(0);\n const relightBrightness = uniform(DEFAULT_RELIGHT_BRIGHTNESS);\n const relightBackground = uniform(DEFAULT_RELIGHT_BACKGROUND);\n const relightSoftness = uniform(DEFAULT_RELIGHT_SOFTNESS);\n const order = new THREE.StorageInstancedBufferAttribute(new Float32Array(capacity), 1);\n const geometry = new THREE.InstancedBufferGeometry();\n geometry.setIndex([0, 1, 2, 0, 2, 3]);\n geometry.setAttribute(\n 'position',\n new THREE.BufferAttribute(new Float32Array([-1, -1, 0, 1, -1, 0, 1, 1, 0, -1, 1, 0]), 3),\n );\n geometry.instanceCount = 0;\n super(\n geometry,\n createWorkBufferMaterial({\n capacity,\n centers: workBuffer.centers,\n colors: workBuffer.colors,\n covarianceA: workBuffer.covarianceA,\n covarianceB: workBuffer.covarianceB,\n isotropicMix: workBuffer.isotropicMix,\n isotropicScreenRadius: workBuffer.isotropicScreenRadius,\n order,\n focal,\n viewport,\n maxStdDev,\n minSplatSizePx,\n antialias,\n projectedLowPassVariance,\n compensateProjectedLowPass,\n dofFocusDistance,\n dofAperture,\n relightMap: relightPlaceholder,\n relightBlend,\n relightBrightness,\n relightBackground,\n relightSoftness,\n }),\n );\n const indices = new Uint32Array(capacity);\n for (let i = 0; i < capacity; i++) indices[i] = i;\n this.workBuffer = workBuffer;\n this.focal = focal;\n this.viewport = viewport;\n this.maxStdDev = maxStdDev;\n this.minSplatSizePx = minSplatSizePx;\n this.antialias = antialias;\n this.projectedLowPassVariance = projectedLowPassVariance;\n this.compensateProjectedLowPass = compensateProjectedLowPass;\n this.dofFocusDistance = dofFocusDistance;\n this.dofAperture = dofAperture;\n this.relightPlaceholder = relightPlaceholder;\n this.relightMap = relightPlaceholder;\n this.relightBlend = relightBlend;\n this.relightBrightness = relightBrightness;\n this.relightBackground = relightBackground;\n this.relightSoftness = relightSoftness;\n this.srgbOutput = options.srgbOutput ?? false;\n this.sortScheduler = new WebGpuSortScheduler(undefined, isFillConstrainedSplatDevice());\n this.orderAttribute = order;\n this.workSourceIndex = new THREE.StorageBufferAttribute(indices, 1);\n this.mirrors = new StorageMirrorReleaser([this.workSourceIndex, this.orderAttribute]);\n this.renderer = renderer;\n const sortInputs = {\n renderer,\n capacity,\n centersBuffer: workBuffer.centers,\n splatIndexAttribute: order,\n sourceIndexAttribute: this.workSourceIndex,\n };\n this.sorter =\n options.sortStrategy === 'exact'\n ? new RadixSorter({ ...sortInputs, exactDepth: true })\n : new ComputeSorter(sortInputs);\n this.frustumCulled = false;\n this.matrixAutoUpdate = false;\n this.matrix.identity();\n this.matrixWorld.identity();\n }\n\n /** Fixed work-buffer splat capacity chosen at construction. */\n get capacity(): number {\n return this.workBuffer.capacity;\n }\n\n /** Registers a source. The caller keeps ownership and may still query it. */\n addSource(source: SplatMesh, options: UnifiedSplatSourceOptions = {}): void {\n this.assertNotDisposed('addSource');\n if (this.sources.some((record) => record.source === source)) return;\n const view = source.getUnifiedSourceView();\n const resolvedVisible = options.visible ?? source.visible;\n if (view.hasSourcePlacement) {\n // The gather pass resolves one matrix per source (the mesh's\n // `matrixWorld`); it knows nothing about a pool's *inner* per-source\n // placement, so a nested scene would draw every one of its sources at\n // its pool-local position. Reject rather than draw it wrong.\n throw new Error(\n 'UnifiedSplatRenderer: a SplatScene is already a unified pool with its own global ' +\n 'sort; add its sources individually, or draw the scene directly.',\n );\n }\n if (view.srgbOutput !== this.srgbOutput) {\n throw new Error(\n \"UnifiedSplatRenderer: every source must use the renderer's srgbOutput setting.\",\n );\n }\n if (this.sourceMaxStdDev === null) {\n this.sourceMaxStdDev = view.maxStdDev;\n this.maxStdDev.value = view.maxStdDev;\n this.minSplatSizePx.value = view.minSplatSizePx;\n } else if (this.sourceMaxStdDev !== view.maxStdDev) {\n throw new Error('UnifiedSplatRenderer: every source must use the same maxStdDev setting.');\n } else if (this.minSplatSizePx.value !== view.minSplatSizePx) {\n throw new Error(\n 'UnifiedSplatRenderer: every source must use the same minSplatSizePx setting.',\n );\n }\n if (this.sourceAntialias === null) {\n this.sourceAntialias = view.antialias;\n this.antialias.value = view.antialias ? 1 : 0;\n } else if (this.sourceAntialias !== view.antialias) {\n throw new Error('UnifiedSplatRenderer: every source must use the same antialias setting.');\n }\n if (this.sourceProjectedFilterProfile === null) {\n this.sourceProjectedFilterProfile = view.projectedFilterProfile;\n const lccProfile = view.projectedFilterProfile === 'lcc';\n this.projectedLowPassVariance.value = lccProfile ? 0.1 : 0.3;\n this.compensateProjectedLowPass.value = lccProfile ? 1 : 0;\n } else if (this.sourceProjectedFilterProfile !== view.projectedFilterProfile) {\n throw new Error(\n 'UnifiedSplatRenderer: every source must use the same projected filter profile.',\n );\n }\n this.sources.push({\n source,\n gather: this.createGather(view),\n graphRevision: view.graphRevision,\n priority: options.priority ?? 0,\n opacity: options.opacity ?? 1,\n visible: resolvedVisible,\n cacheModifiers: options.cacheModifiers ?? false,\n originalVisible: source.visible,\n lastGather: null,\n view: null,\n registrationOrder: 0,\n offset: 0,\n });\n source.visible = false;\n source.setUnifiedPickVisibility(resolvedVisible);\n }\n\n /**\n * Invalidates one registered source's cached gather output.\n *\n * Required after changing modifier uniforms or channels when that source was\n * registered with `cacheModifiers: true`. The following {@link update}\n * re-gathers the source and re-sorts because modifiers may move splats.\n * Returns `false` when the source is not registered.\n */\n invalidateSource(source: SplatMesh): boolean {\n this.assertNotDisposed('invalidateSource');\n const record = this.sources.find((candidate) => candidate.source === source);\n if (!record) return false;\n record.lastGather = null;\n return true;\n }\n\n /** Number of whole sources omitted in the most recent update due to capacity. */\n get droppedSourceCount(): number {\n return this.overflowedSourceCount;\n }\n\n /** Number of active splats omitted in the most recent update due to capacity. */\n get droppedSplatCount(): number {\n return this.overflowedSplatCount;\n }\n\n /** Updates a source's resolved opacity without rebuilding its gather pipeline. */\n setSourceOpacity(source: SplatMesh, opacity: number): void {\n this.assertNotDisposed('setSourceOpacity');\n const record = this.sources.find((entry) => entry.source === source);\n if (!record) throw new Error('UnifiedSplatRenderer: source is not registered.');\n record.opacity = opacity;\n }\n\n /** Shows or hides a source without changing its residency. */\n setSourceVisible(source: SplatMesh, visible: boolean): void {\n this.assertNotDisposed('setSourceVisible');\n const record = this.sources.find((entry) => entry.source === source);\n if (!record) throw new Error('UnifiedSplatRenderer: source is not registered.');\n record.visible = visible;\n record.source.setUnifiedPickVisibility(visible);\n }\n\n /**\n * Core projected-2D depth of field on the unified draw pass (not gather).\n * Live uniforms - no gather-cache invalidation. Pass `aperture: 0` to disable.\n */\n setDepthOfField(settings: Partial<DepthOfFieldSettings>): void {\n this.assertNotDisposed('setDepthOfField');\n const next = clampDepthOfFieldSettings(settings, this.getDepthOfField());\n this.dofFocusDistance.value = next.focusDistance;\n this.dofAperture.value = next.aperture;\n }\n\n /** Current core DoF uniforms (`aperture === 0` means off). */\n getDepthOfField(): DepthOfFieldSettings {\n return {\n focusDistance: this.dofFocusDistance.value,\n aperture: this.dofAperture.value,\n };\n }\n\n /**\n * PlayCanvas-style proxy-mesh relighting on the unified draw pass (not gather).\n * Live blend/brightness/background; map identity change rebuilds the draw\n * material. Does not invalidate gather caches. Pass `null` to disable.\n */\n setRelighting(options: RelightingSettings | null): void {\n this.assertNotDisposed('setRelighting');\n if (options === null) {\n this.relightBlend.value = 0;\n if (this.relightMap !== this.relightPlaceholder) {\n this.relightMap = this.relightPlaceholder;\n this.rebuildDrawMaterial();\n }\n return;\n }\n const next = clampRelightingSettings(options, this.getRelighting());\n this.relightBlend.value = next.blend;\n this.relightBrightness.value = next.brightness;\n this.relightBackground.value = next.background;\n this.relightSoftness.value = next.softness;\n if (options.map !== this.relightMap) {\n this.relightMap = options.map;\n this.rebuildDrawMaterial();\n }\n }\n\n /** Current relight numeric uniforms (`blend === 0` means off). */\n getRelighting(): RelightingUniforms {\n return {\n blend: this.relightBlend.value,\n brightness: this.relightBrightness.value,\n background: this.relightBackground.value,\n softness: this.relightSoftness.value,\n };\n }\n\n /**\n * Asynchronously picks the frontmost visible splat under an NDC coordinate\n * across every registered source.\n *\n * Semantics: each source that is\n * currently visible in this unified renderer runs its own depth-tested GPU\n * pick pass; the hit nearest the camera wins and the result names its source\n * mesh. Hidden sources (`setSourceVisible(source, false)`) never hit. A hit\n * whose source was removed, hidden, or disposed while its readback was in\n * flight is dropped rather than misattributed. Resolves `null` for misses,\n * an empty source list, and picks pending across {@link dispose}.\n *\n * Uses the construction renderer. Concurrent calls are safe: each source\n * serializes its own picks and shared renderer state is restored\n * synchronously before any await.\n */\n async pick(\n ndc: THREE.Vector2,\n camera: THREE.Camera,\n options?: SplatPickOptions,\n ): Promise<UnifiedSplatPickResult | null> {\n if (this.disposed) return null;\n const candidates = this.sources\n .filter((record) => record.visible)\n .map((record) => record.source);\n if (candidates.length === 0) return null;\n const hits = await Promise.all(\n candidates.map(async (source) => {\n const hit = await source.pick(ndc, camera, this.renderer, options);\n return hit === null ? null : { source, point: hit.point, distance: hit.distance };\n }),\n );\n if (this.disposed) return null;\n let best: UnifiedSplatPickResult | null = null;\n for (const hit of hits) {\n if (hit === null) continue;\n // Registration may have changed while the readbacks were in flight;\n // never attribute a hit to a source no longer picked by this renderer.\n const record = this.sources.find((entry) => entry.source === hit.source);\n if (record === undefined || !record.visible) continue;\n if (best === null || hit.distance < best.distance) best = hit;\n }\n return best;\n }\n\n /** Unregisters a source and restores its standalone draw visibility. */\n removeSource(source: SplatMesh): boolean {\n this.assertNotDisposed('removeSource');\n const index = this.sources.findIndex((entry) => entry.source === source);\n if (index < 0) return false;\n const [record] = this.sources.splice(index, 1);\n if (record) {\n record.gather.dispose();\n record.source.visible = record.originalVisible;\n record.source.setUnifiedPickVisibility(null);\n record.lastGather = null;\n }\n this.previousLayout = this.previousLayout.filter((entry) => entry.source !== source);\n this.previousLayoutBySource.delete(source);\n if (this.sources.length === 0) {\n this.sourceMaxStdDev = null;\n this.sourceAntialias = null;\n this.sourceProjectedFilterProfile = null;\n this.previousLayout = [];\n this.previousLayoutBySource.clear();\n }\n return true;\n }\n\n /** Updates source LODs, gathers their active splats, then globally sorts them. */\n update(camera: THREE.PerspectiveCamera): void {\n if (this.disposed) return;\n this.prepare(camera);\n }\n\n /**\n * Re-sorts and draws this unified source list for a secondary camera.\n * Mirrors and portals use this instead of rendering the hidden source meshes\n * individually, so their reflection keeps one global transparent order.\n */\n renderView(\n camera: THREE.PerspectiveCamera,\n renderer: THREE.WebGPURenderer,\n target: THREE.RenderTarget | null = null,\n ): void {\n if (this.disposed) return;\n if (renderer !== this.renderer) {\n throw new Error('UnifiedSplatRenderer: renderView must use its construction renderer.');\n }\n if (!supportsUnifiedSplatRenderer(renderer)) {\n throw new Error(\n 'UnifiedSplatRenderer requires a WebGPU backend (renderer.backend.isWebGPUBackend).',\n );\n }\n // A secondary view must sort for its own camera regardless of cadence, and\n // it leaves a foreign order in the shared buffer - invalidate afterwards so\n // the next primary update re-sorts even if its camera has not moved.\n const targetSize = target ? this.secondaryViewSize.set(target.width, target.height) : undefined;\n this.prepare(camera, targetSize, true);\n const previousTarget = renderer.getRenderTarget();\n try {\n renderer.setRenderTarget(target);\n renderer.render(this, camera);\n } finally {\n renderer.setRenderTarget(previousTarget);\n // `prepare` left the shared order buffer sorted for the secondary view.\n // Invalidate even when its draw throws, or a stationary primary view can\n // render through that foreign order after the host recovers.\n this.sortScheduler.invalidate();\n }\n }\n\n private prepare(\n camera: THREE.PerspectiveCamera,\n targetSize?: THREE.Vector2,\n forceSort = false,\n ): void {\n if (!supportsUnifiedSplatRenderer(this.renderer)) {\n throw new Error(\n 'UnifiedSplatRenderer requires a WebGPU backend (renderer.backend.isWebGPUBackend).',\n );\n }\n // Hosts must not pose this mesh; world centers already include source transforms.\n this.matrix.identity();\n this.matrixWorld.identity();\n // A secondary view (`renderView`) states its own size and camera; the\n // primary one follows the renderer, which in XR means a *per-eye* viewport\n // and a cyclopean head pose. Taking the drawing buffer there would be twice\n // the true width and halve every splat's horizontal size, and sorting from\n // the application camera would order the scene from wherever that camera\n // was left standing.\n const xrView = targetSize ? null : resolveXrView(camera, this.renderer);\n const projectionCamera: THREE.Camera = xrView?.eye ?? camera;\n // One global sort from the head serves both eyes (see `xr-view.ts`).\n const viewCamera: THREE.Camera = xrView?.head ?? camera;\n const viewport = this.drawingBufferSize;\n if (targetSize) viewport.copy(targetSize);\n else if (xrView) viewport.set(xrView.width, xrView.height);\n else this.renderer.getDrawingBufferSize(viewport);\n const projection = projectionCamera.projectionMatrix.elements;\n this.viewport.value.copy(viewport);\n const focalX = projection[0] ?? 0;\n const focalY = projection[5] ?? 0;\n this.focal.value.set((focalX * viewport.x) / 2, (focalY * viewport.y) / 2);\n const updated = this.candidateScratch;\n updated.length = 0;\n for (let i = 0; i < this.sources.length; i++) {\n const record = this.sources[i] as SourceRecord;\n // Children take the application camera: they resolve the XR view for\n // themselves, and a streamed source needs it for LOD scheduling.\n record.source.update(camera, this.renderer, { sort: false });\n record.view = record.source.getUnifiedSourceView();\n record.registrationOrder = i;\n updated.push(record);\n }\n // Overflow is deterministic and region-safe: preserve whole sources rather\n // than gathering a prefix of a streamed cut. Higher priority wins; matching\n // priorities retain registration order.\n updated.sort((a, b) => b.priority - a.priority || a.registrationOrder - b.registrationOrder);\n\n this.overflowedSourceCount = 0;\n this.overflowedSplatCount = 0;\n this.bounds.makeEmpty();\n\n const admitted = this.admittedScratch;\n admitted.length = 0;\n let offset = 0;\n for (const record of updated) {\n const view = record.view as UnifiedSourceView;\n if (record.graphRevision !== view.graphRevision) {\n // Build the replacement before disposing the old pipeline: a modifier\n // stack that throws at graph-build time must leave the record holding\n // a live gather (and an unchanged revision) so the next update can\n // retry after the caller fixes the stack.\n const rebuilt = this.createGather(view);\n record.gather.dispose();\n record.gather = rebuilt;\n record.graphRevision = view.graphRevision;\n record.lastGather = null;\n }\n if (!record.visible) {\n record.lastGather = null;\n continue;\n }\n if (view.activeCount === 0) {\n // An empty cut (e.g. a streamed source before its first chunk lands)\n // owns no work slice: skip the zero-splat gather dispatch and keep the\n // sorter bounds limited to sources that contribute splats.\n record.lastGather = null;\n continue;\n }\n if (offset + view.activeCount > this.workBuffer.capacity) {\n this.overflowedSourceCount++;\n this.overflowedSplatCount += view.activeCount;\n record.lastGather = null;\n continue;\n }\n record.offset = offset;\n admitted.push(record);\n this.bounds.union(view.worldBounds);\n offset += view.activeCount;\n }\n\n // Geometry under the draw order changed (layout, active cut, transform,\n // opacity, content revision). Camera-only SH / modifier color refreshes\n // re-gather the *same* world centers and must not force a sort.\n let geometryInvalidated = false;\n for (const record of admitted) {\n const view = record.view as UnifiedSourceView;\n const sliceOffset = record.offset;\n const previous = this.previousLayoutBySource.get(record.source);\n const ownedSameSlice =\n previous !== undefined &&\n previous.offset === sliceOffset &&\n previous.activeCount === view.activeCount;\n const last = record.lastGather;\n const geometryMatches =\n last !== null &&\n ownedSameSlice &&\n last.activeCount === view.activeCount &&\n last.contentRevision === view.contentRevision &&\n last.offset === sliceOffset &&\n last.opacity === record.opacity &&\n last.matrixWorld.equals(view.matrixWorld);\n const reusable =\n geometryMatches &&\n // SH depends on camera position, not orientation. A stationary camera\n // can reuse the resolved colors instead of re-running a full-source\n // gather every frame; camera motion invalidates only SH-bearing slices.\n (view.sh === null || last.localCameraPosition.equals(view.localCameraPosition.value)) &&\n (view.modifiers.length === 0 || record.cacheModifiers) &&\n // Belt-and-braces: a placed pool's splats move without its own\n // `matrixWorld` changing, so a cached gather would go stale silently.\n // `addSource` already rejects these.\n !view.hasSourcePlacement;\n if (!reusable) {\n record.gather.gather(\n this.renderer,\n view.activeCount,\n sliceOffset,\n view.matrixWorld,\n viewCamera.matrixWorldInverse,\n record.opacity,\n );\n if (!geometryMatches) geometryInvalidated = true;\n if (record.lastGather === null) {\n record.lastGather = {\n activeCount: view.activeCount,\n contentRevision: view.contentRevision,\n offset: sliceOffset,\n opacity: record.opacity,\n matrixWorld: view.matrixWorld.clone(),\n localCameraPosition: view.localCameraPosition.value.clone(),\n };\n } else {\n record.lastGather.activeCount = view.activeCount;\n record.lastGather.contentRevision = view.contentRevision;\n record.lastGather.offset = sliceOffset;\n record.lastGather.opacity = record.opacity;\n record.lastGather.matrixWorld.copy(view.matrixWorld);\n record.lastGather.localCameraPosition.copy(view.localCameraPosition.value);\n }\n }\n }\n\n // Commit ownership only after every gather for this preparation has run.\n let layoutChanged =\n admitted.length !== this.previousLayout.length || offset !== this.previousAdmittedTotal;\n this.previousAdmittedTotal = offset;\n if (!layoutChanged) {\n for (let i = 0; i < admitted.length; i++) {\n const record = admitted[i] as SourceRecord;\n const entry = this.previousLayout[i] as LayoutEntry;\n if (\n entry.source !== record.source ||\n entry.offset !== record.offset ||\n entry.activeCount !== (record.view as UnifiedSourceView).activeCount\n ) {\n layoutChanged = true;\n break;\n }\n }\n }\n if (layoutChanged) {\n this.previousLayout = admitted.map((record) => ({\n source: record.source,\n offset: record.offset,\n activeCount: (record.view as UnifiedSourceView).activeCount,\n }));\n this.previousLayoutBySource.clear();\n for (const entry of this.previousLayout) this.previousLayoutBySource.set(entry.source, entry);\n }\n\n // Work-buffer *geometry* moved underneath the current draw order: the next\n // sort must run whatever the camera did. Camera-driven SH/modifier color\n // refreshes re-gather without geometryInvalidated and deliberately skip\n // this. DoF is a live draw uniform and reaches neither branch.\n if (geometryInvalidated || layoutChanged) this.sortScheduler.invalidateContent();\n\n if (offset > 0) {\n const now = performance.now();\n const viewInverse = viewCamera.matrixWorldInverse;\n if (\n forceSort ||\n this.sortScheduler.shouldSubmit(viewInverse, this.lastSortedModelView, offset, now)\n ) {\n if (this.sorter.sort(viewInverse, offset, this.bounds)) {\n this.lastSortedModelView.copy(viewInverse);\n this.sortScheduler.markAccepted(now);\n }\n }\n }\n (this.geometry as THREE.InstancedBufferGeometry).instanceCount = offset;\n // Both are uploaded by the first frame's gather/sort dispatches, and neither\n // is ever read back - see the field comment. The work buffer's own mirrors\n // are released by `WorkBufferGather.gather`.\n if (!this.mirrors.settled) this.mirrors.release(this.renderer);\n }\n\n dispose(): void {\n if (this.disposed) return;\n this.disposed = true;\n for (const record of this.sources) {\n record.gather.dispose();\n record.source.visible = record.originalVisible;\n record.source.setUnifiedPickVisibility(null);\n record.lastGather = null;\n }\n this.sources.length = 0;\n this.previousLayout = [];\n this.previousLayoutBySource.clear();\n this.candidateScratch.length = 0;\n this.admittedScratch.length = 0;\n this.sourceMaxStdDev = null;\n this.sourceAntialias = null;\n this.sourceProjectedFilterProfile = null;\n this.sorter.dispose();\n this.geometry.dispose();\n (this.material as THREE.Material).dispose();\n this.relightPlaceholder.dispose();\n // The work buffer, its source-index list and the draw-order buffer are\n // storage attributes outside the geometry, so nothing above frees their\n // GPU buffers - ~72 B per work slot leaked per scene swap without this.\n releaseRendererAttributes(this.renderer, [\n this.workBuffer.centers,\n this.workBuffer.colors,\n this.workBuffer.covarianceA,\n this.workBuffer.covarianceB,\n this.workBuffer.isotropicMix,\n this.workBuffer.isotropicScreenRadius,\n this.workSourceIndex,\n this.orderAttribute,\n ]);\n }\n\n private assertNotDisposed(operation: string): void {\n if (this.disposed) {\n throw new Error(`UnifiedSplatRenderer: ${operation} called after dispose.`);\n }\n }\n\n /** Rebuilds the EWA draw material when the relight map identity changes. */\n private rebuildDrawMaterial(): void {\n const previous = this.material as THREE.Material;\n this.material = createWorkBufferMaterial({\n capacity: this.workBuffer.capacity,\n centers: this.workBuffer.centers,\n colors: this.workBuffer.colors,\n covarianceA: this.workBuffer.covarianceA,\n covarianceB: this.workBuffer.covarianceB,\n isotropicMix: this.workBuffer.isotropicMix,\n isotropicScreenRadius: this.workBuffer.isotropicScreenRadius,\n order: this.orderAttribute,\n focal: this.focal,\n viewport: this.viewport,\n maxStdDev: this.maxStdDev,\n minSplatSizePx: this.minSplatSizePx,\n antialias: this.antialias,\n projectedLowPassVariance: this.projectedLowPassVariance,\n compensateProjectedLowPass: this.compensateProjectedLowPass,\n dofFocusDistance: this.dofFocusDistance,\n dofAperture: this.dofAperture,\n relightMap: this.relightMap,\n relightBlend: this.relightBlend,\n relightBrightness: this.relightBrightness,\n relightBackground: this.relightBackground,\n relightSoftness: this.relightSoftness,\n });\n previous.dispose();\n }\n\n private createGather(view: ReturnType<SplatMesh['getUnifiedSourceView']>): WorkBufferGather {\n const gather = this.buildGather(view);\n // Compile off the critical frame. WebGPU defers compute-pipeline compilation\n // to the first dispatch, and that dispatch happens inside `update` - a\n // measured 2.0s frame on a marker-heavy scene. Warming here is best-effort:\n // if it fails or has not finished by the first real gather, the only cost is\n // the stall this avoids, so nothing waits on it.\n void gather.warmUp(this.renderer).catch(() => undefined);\n return gather;\n }\n\n private buildGather(view: ReturnType<SplatMesh['getUnifiedSourceView']>): WorkBufferGather {\n return new WorkBufferGather({\n capacity: this.workBuffer.capacity,\n sourceCapacity: view.capacity,\n sourceIndex: view.sourceIndex,\n centersTexture: view.centersTexture,\n colorsTexture: view.colorsTexture,\n covarianceATexture: view.covarianceATexture,\n covarianceBTexture: view.covarianceBTexture,\n dataTextureWidth: view.dataTextureWidth,\n sh: view.sh,\n modifiers: view.modifiers,\n channels: view.channels,\n localCameraPosition: view.localCameraPosition,\n srgbOutput: this.srgbOutput,\n // Per source, not a compatibility field: the gather normalizes `.rad`\n // half-alpha here so one shared draw material serves mixed scenes.\n lodAlpha: view.lodAlpha,\n workBuffer: this.workBuffer,\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 marker\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 markers 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 './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 (a marker 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`, marker `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 markers: what\n * makes a marker worth splats is that the camera is near it, and that changes\n * every frame. Splitting a pool evenly instead (`pool / N`) gives the marker\n * you fly up to a quarter of the budget it needs while three markers 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 markers 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 * `UnifiedSplatRenderer` 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 markers fight over the rest - e.g. `fixedWeight: 4`\n * against markers 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(markerA); // camera-weighted, priority 1\n * governor.register(markerB);\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 marker 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 * One-call WebGPU renderer construction for VLAM! scenes.\n *\n * Every host needs the same four things right - adapter, device, raised limits\n * and the platform-dependent `powerPreference` - and getting any of them wrong\n * fails *late*: past ~8.4 M unified splats, or with MSAA silently dropped.\n * {@link createSplatRenderer} is that block, once.\n *\n * The primitives it composes ({@link recommendedWebGpuRequiredLimits},\n * {@link webGpuPowerPreferenceOptions}) stay exported for hosts that own device\n * creation themselves - an XR session sharing a device, say.\n */\nimport { WebGPURenderer, type WebGPURendererParameters } from 'three/webgpu';\n\nimport { warn } from './logging';\nimport {\n recommendedWebGpuRequiredLimits,\n webGpuPowerPreferenceOptions,\n type WebGpuPowerPreference,\n} from './webgpu-limits';\n\n/*\n * The WebGPU surface is declared structurally, not via `navigator.gpu`.\n * `tsconfig.lib.json` compiles `src/lib` alone (`rootDir`, and an `include`\n * listing only the published entry points), so the ambient\n * `interface Navigator { gpu?: ... }` the viewer declares is not in scope for\n * the published build. A module here that reaches for `navigator.gpu`'s type\n * passes `npm run typecheck` and fails `npm run build:lib`.\n */\n\n/** The device handle `createSplatRenderer` hands to `WebGPURenderer`. */\nexport interface SplatRendererGpuDevice {\n readonly features: ReadonlySet<string>;\n}\n\n/** The adapter surface `createSplatRenderer` reads limits and features from. */\nexport interface SplatRendererGpuAdapter {\n readonly features: ReadonlySet<string>;\n readonly limits: {\n readonly maxStorageBufferBindingSize: number;\n readonly maxBufferSize: number;\n readonly maxTextureDimension2D?: number;\n };\n requestDevice(descriptor?: {\n requiredFeatures?: readonly string[];\n requiredLimits?: Record<string, number>;\n }): Promise<SplatRendererGpuDevice>;\n}\n\n/** The `navigator.gpu` surface `createSplatRenderer` probes. */\nexport interface SplatRendererGpu {\n requestAdapter(options?: {\n powerPreference?: WebGpuPowerPreference;\n }): Promise<SplatRendererGpuAdapter | null>;\n}\n\n/**\n * Options for {@link createSplatRenderer}.\n *\n * Everything `THREE.WebGPURenderer` accepts passes straight through -\n * `antialias`, `forceWebGL`, `trackTimestamp`, `canvas`, `alpha`, `samples`,\n * `getFallback`, … - except the three members the helper owns (`device`,\n * `requiredLimits`, and `powerPreference`, which is widened here to allow\n * `null`).\n */\nexport interface CreateSplatRendererOptions extends Omit<\n WebGPURendererParameters,\n 'device' | 'requiredLimits' | 'powerPreference'\n> {\n /**\n * Adapter/renderer power hint. Omitted automatically on Windows, where Chrome\n * ignores it and warns ([crbug.com/369219127](https://crbug.com/369219127)).\n * Pass `null` to never send it.\n *\n * @defaultValue `'high-performance'`\n */\n powerPreference?: WebGpuPowerPreference | null;\n /**\n * Throw instead of degrading to three's WebGL2 backend. Adapter and device\n * failures rethrow their original error, unwrapped.\n *\n * @defaultValue `false`\n */\n requireWebGpu?: boolean;\n /**\n * The WebGPU entry point to probe. Defaults to `navigator.gpu`; pass `null`\n * to skip the probe entirely (tests, non-DOM hosts).\n */\n gpu?: SplatRendererGpu | null;\n}\n\nfunction ambientGpu(): SplatRendererGpu | null {\n const nav = typeof navigator !== 'undefined' ? (navigator as { gpu?: unknown }) : undefined;\n const gpu = nav?.gpu;\n return gpu ? (gpu as SplatRendererGpu) : null;\n}\n\n/**\n * Creates a `THREE.WebGPURenderer` configured for splat rendering: the\n * adapter's advertised buffer/texture maxima as `requiredLimits`, every adapter\n * feature as `requiredFeatures`, and `powerPreference` only where the platform\n * honours it.\n *\n * Both raised values matter late rather than loudly. Without the limits, large\n * unified or streamed scenes throw past ~8.4 M splats. Without\n * `core-features-and-limits` among the requested features, three treats the\n * backend as compatibility-mode and **silently drops MSAA** - which is why the\n * device is requested here rather than left to `WebGPUBackend.init`. Owning the\n * request also keeps the real failure: three's `getFallback` hook logs a flat\n * \"WebGPU is not available\" and drops the cause.\n *\n * Degrades to three's WebGL2 backend exactly as an unconfigured\n * `new THREE.WebGPURenderer()` would, warning through\n * {@link setVlamLogHandler} when the cause was a fixable failure rather than a\n * browser without WebGPU. Pass `requireWebGpu: true` to throw instead.\n *\n * Does **not** call `setSize`, `renderer.init()`, or append the canvas - those\n * stay in host code - and does not touch `outputColorSpace`, whose three.js\n * default (sRGB) is already what the splat shader expects.\n *\n * @example\n * const renderer = await createSplatRenderer();\n * renderer.setSize(innerWidth, innerHeight);\n * document.body.appendChild(renderer.domElement);\n *\n * @throws {TypeError} if `requireWebGpu` and `forceWebGL` are both set.\n */\nexport async function createSplatRenderer(\n options: CreateSplatRendererOptions = {},\n): Promise<WebGPURenderer> {\n const {\n powerPreference = 'high-performance',\n requireWebGpu = false,\n gpu,\n ...rendererOptions\n } = options;\n const powerOptions =\n powerPreference === null ? {} : webGpuPowerPreferenceOptions(powerPreference);\n\n let adapter: SplatRendererGpuAdapter | null = null;\n let device: SplatRendererGpuDevice | null = null;\n\n if (rendererOptions.forceWebGL === true) {\n if (requireWebGpu) {\n throw new TypeError(\n 'vlam: createSplatRenderer cannot honour both requireWebGpu and forceWebGL.',\n );\n }\n } else {\n const entry = gpu === undefined ? ambientGpu() : gpu;\n if (!entry) {\n // Not a warning: a browser without WebGPU is not a fixable misconfiguration.\n if (requireWebGpu) {\n throw new Error('vlam: WebGPU is unavailable (no navigator.gpu) and requireWebGpu is set.');\n }\n } else {\n let adapterFailed = false;\n try {\n adapter = await entry.requestAdapter({ ...powerOptions });\n } catch (error) {\n if (requireWebGpu) throw error;\n // One warning per failure: an adapter that threw has already explained\n // itself, so the \"no adapter\" branch below must not warn again.\n adapterFailed = true;\n warn('WebGPU adapter request failed; falling back to WebGL2.', error);\n }\n if (!adapter && !adapterFailed) {\n if (requireWebGpu) {\n throw new Error('vlam: no WebGPU adapter was available and requireWebGpu is set.');\n }\n warn('No WebGPU adapter was available; falling back to WebGL2.');\n }\n if (adapter) {\n try {\n device = await adapter.requestDevice({\n // Every feature the adapter advertises. `core-features-and-limits`\n // is the one that matters: without it three treats the backend as\n // compatibility-mode and forces the sample count to 0.\n requiredFeatures: [...adapter.features],\n requiredLimits: recommendedWebGpuRequiredLimits(adapter),\n });\n } catch (error) {\n if (requireWebGpu) throw error;\n // The error object itself, not just a message: this is the value\n // three's `getFallback` swallows, and it distinguishes a fixable\n // failure (a GPU process out of memory) from an absent backend.\n warn('WebGPU device request failed; requesting limits without an owned device.', error);\n }\n }\n }\n }\n\n return new WebGPURenderer({\n antialias: true,\n ...rendererOptions,\n ...powerOptions,\n // Without a device, leave three to its own request - it fails the same way,\n // but the WebGL2 fallback behind it is still the right outcome.\n ...(device\n ? { device }\n : adapter\n ? { requiredLimits: recommendedWebGpuRequiredLimits(adapter) }\n : {}),\n });\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 marker 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 marker 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 marker, 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 * markers and a main at floor 1 consume a 16-slot pipe entirely, leaving the\n * focused marker 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: 'pagetable'` that cap is `min(2 GiB, this capture's decoded\n * size)` - a number sized for *one* streamed scene. A scene of streamed markers\n * therefore has no ceiling at all: thirteen markers 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 markers would reserve most of the\n * envelope for markers 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","import * as THREE from 'three/webgpu';\nimport type { SplatData } from './splat-data';\n\n/**\n * Volume selection over splat centers (CPU).\n *\n * A {@link SelectionVolume} answers one question - is this point inside the\n * volume? - so hosts can select a region of a loaded splat cloud with a placed\n * box, sphere or cylinder ({@link createSelectionVolume}), or with any custom\n * shape by implementing the interface directly.\n *\n * Selection runs over a `SplatData`'s positions ({@link selectInData}); the\n * split itself lives in `splat-partition.ts`. Everything here is pure array\n * math - no GPU, no mesh - so it is cheap to test and works the same for any\n * host. A one-shot selection gesture over a few million centers is a\n * brute-force scan by design: it costs milliseconds, needs no index to build\n * or invalidate, and supports arbitrarily transformed (including non-uniformly\n * scaled) volumes that a radius-based spatial grid cannot.\n */\nexport interface SelectionVolume {\n /**\n * Whether the point `(x, y, z)` lies inside the volume, in **the frame the\n * volume was built for** - for {@link createSelectionVolume} that is the\n * frame of the positions passed to {@link selectInData} (splat-local for a\n * `SplatData`, source-local for a collision mesh), fixed by the\n * `worldMatrix` argument.\n *\n * A splat is selected iff its *center* passes this test; a splat whose\n * center is outside but whose ellipsoid extent overlaps the volume is not\n * selected.\n */\n containsPoint(x: number, y: number, z: number): boolean;\n}\n\n/** The built-in selection shapes. Custom shapes implement {@link SelectionVolume}. */\nexport type SelectionVolumeKind = 'box' | 'sphere' | 'cylinder';\n\n/** Describes a built-in selection shape for {@link createSelectionVolume}. */\nexport interface SelectionVolumeOptions {\n kind: SelectionVolumeKind;\n /**\n * Places the shape: volume-local → world. Encodes the shape's center,\n * orientation and any (possibly non-uniform) scale. Default identity.\n * Must be invertible - a zero scale axis throws.\n */\n transform?: THREE.Matrix4;\n /**\n * Box half-extents (volume-local units). Required for `kind: 'box'`; every\n * component must be positive.\n */\n halfExtents?: readonly [number, number, number];\n /**\n * Sphere or cylinder radius (volume-local units). Required for\n * `kind: 'sphere'` and `kind: 'cylinder'`; must be positive.\n */\n radius?: number;\n /**\n * Cylinder full height along the volume-local Y axis. Required for\n * `kind: 'cylinder'`; must be positive.\n */\n height?: number;\n}\n\n/**\n * Builds a {@link SelectionVolume} for one of the built-in shapes.\n *\n * `worldMatrix` is the local → world matrix of the geometry being selected\n * (`mesh.matrixWorld`; omit for data not attached to a mesh). It fixes the\n * frame {@link SelectionVolume.containsPoint} expects: each point is mapped\n * through `transform⁻¹ · worldMatrix` into the shape's own frame, so a rotated\n * or non-uniformly scaled volume placement costs the same as an axis-aligned\n * one.\n *\n * @throws {Error} if the shape's dimensions are missing/non-positive, or if\n * the combined `transform⁻¹ · worldMatrix` is singular or non-finite (a\n * collapsed axis on either matrix would otherwise select silent garbage).\n */\nexport function createSelectionVolume(\n options: SelectionVolumeOptions,\n worldMatrix?: THREE.Matrix4,\n): SelectionVolume {\n // Guard both inputs before composing: `Matrix4.invert` reports a singular\n // matrix by silently returning zeros, which would map every point onto the\n // origin and \"select\" the whole cloud.\n if (options.transform) assertUsableMatrix(options.transform, '`transform`');\n if (worldMatrix) assertUsableMatrix(worldMatrix, '`worldMatrix`');\n\n const toVolume = new THREE.Matrix4();\n if (options.transform) toVolume.copy(options.transform).invert();\n if (worldMatrix) toVolume.multiply(worldMatrix);\n\n // Column-major linear part + translation, hoisted out of the per-point test:\n // the scan runs over millions of centers, so it must not touch the matrix\n // object, allocate, or call through a closure per point.\n const e = toVolume.elements;\n const m00 = e[0];\n const m10 = e[1];\n const m20 = e[2];\n const m01 = e[4];\n const m11 = e[5];\n const m21 = e[6];\n const m02 = e[8];\n const m12 = e[9];\n const m22 = e[10];\n const tx = e[12];\n const ty = e[13];\n const tz = e[14];\n\n switch (options.kind) {\n case 'box': {\n const he = options.halfExtents;\n if (!he || !(he[0] > 0) || !(he[1] > 0) || !(he[2] > 0)) {\n throw new Error(\n 'createSelectionVolume: a box requires halfExtents with three positive ' +\n `components (got ${he ? `[${he.join(', ')}]` : 'undefined'}).`,\n );\n }\n const [hx, hy, hz] = he;\n return {\n containsPoint: (x, y, z) =>\n Math.abs(m00 * x + m01 * y + m02 * z + tx) <= hx &&\n Math.abs(m10 * x + m11 * y + m12 * z + ty) <= hy &&\n Math.abs(m20 * x + m21 * y + m22 * z + tz) <= hz,\n };\n }\n case 'sphere': {\n const r = requirePositive(options.radius, 'sphere', 'radius');\n const r2 = r * r;\n return {\n containsPoint: (x, y, z) => {\n const vx = m00 * x + m01 * y + m02 * z + tx;\n const vy = m10 * x + m11 * y + m12 * z + ty;\n const vz = m20 * x + m21 * y + m22 * z + tz;\n return vx * vx + vy * vy + vz * vz <= r2;\n },\n };\n }\n case 'cylinder': {\n const r = requirePositive(options.radius, 'cylinder', 'radius');\n const halfHeight = requirePositive(options.height, 'cylinder', 'height') / 2;\n const r2 = r * r;\n return {\n containsPoint: (x, y, z) => {\n const vy = m10 * x + m11 * y + m12 * z + ty;\n if (Math.abs(vy) > halfHeight) return false;\n const vx = m00 * x + m01 * y + m02 * z + tx;\n const vz = m20 * x + m21 * y + m22 * z + tz;\n return vx * vx + vz * vz <= r2;\n },\n };\n }\n }\n}\n\n/**\n * The indices of every splat in `data` whose center is inside `volume`,\n * ascending. Pure read - `data` is untouched; feed the result (or the volume\n * directly) to `partitionSplatData` to split.\n */\nexport function selectInData(data: SplatData, volume: SelectionVolume): Uint32Array {\n const { positions, count } = data;\n // One growth-free pass: collect into a full-size buffer, then trim.\n const hits = new Uint32Array(count);\n let n = 0;\n for (let i = 0; i < count; i++) {\n if (\n volume.containsPoint(\n positions[i * 3] as number,\n positions[i * 3 + 1] as number,\n positions[i * 3 + 2] as number,\n )\n ) {\n hits[n++] = i;\n }\n }\n return hits.slice(0, n);\n}\n\n/**\n * How many splats in `data` fall inside `volume`, without materializing the\n * index list - for a live \"n selected\" readout while a host drags a volume\n * around, where {@link selectInData}'s `Uint32Array(count)` would allocate tens\n * of megabytes per pointer event on a large scene.\n */\nexport function countInData(data: SplatData, volume: SelectionVolume): number {\n const { positions, count } = data;\n let n = 0;\n for (let i = 0; i < count; i++) {\n if (\n volume.containsPoint(\n positions[i * 3] as number,\n positions[i * 3 + 1] as number,\n positions[i * 3 + 2] as number,\n )\n ) {\n n++;\n }\n }\n return n;\n}\n\nfunction requirePositive(value: number | undefined, kind: string, name: string): number {\n if (!(typeof value === 'number' && value > 0)) {\n throw new Error(\n `createSelectionVolume: a ${kind} requires a positive ${name} (got ${String(value)}).`,\n );\n }\n return value;\n}\n\n/** Rejects a non-finite or singular placement matrix before it is inverted. */\nfunction assertUsableMatrix(matrix: THREE.Matrix4, label: string): void {\n for (const value of matrix.elements) {\n if (!Number.isFinite(value)) {\n throw new Error(`createSelectionVolume: ${label} contains a non-finite value.`);\n }\n }\n if (matrix.determinant() === 0) {\n throw new Error(\n `createSelectionVolume: ${label} is not invertible (a zero scale axis collapses the volume).`,\n );\n }\n}\n","import type { SplatData } from './splat-data';\nimport { selectInData, type SelectionVolume } from './selection-volume';\n\n/**\n * Splits a {@link SplatData} into the part inside a {@link SelectionVolume}\n * and the rest, so the selected region can live as its own independently\n * transformed (and therefore independently animated) object.\n *\n * Both halves keep the source's local frame - separation does not bake any\n * transform. Register them as `SplatScene` or `UnifiedSplatRenderer` sources\n * for one global sort while they are posed independently. Separate\n * `SplatMesh` draw calls sort only within each mesh and can misblend where\n * their Gaussian footprints overlap.\n */\nexport interface SplatPartition {\n /** The splats whose centers passed the volume test. */\n readonly inside: SplatData;\n /** Everything else. */\n readonly outside: SplatData;\n /** Source indices of {@link inside}'s splats, ascending. */\n readonly insideIndices: Uint32Array;\n}\n\n/**\n * Partitions `data` by a selection.\n *\n * `selection` is either a volume (each splat center is tested) or an already\n * computed ascending index list, e.g. from {@link selectInData} - passing the\n * list avoids re-testing when a host previews a selection before separating.\n *\n * Per-splat arrays (positions, colors, covariances, SH) are gathered by index;\n * a SOG SH palette is shared by reference between the halves (it is immutable\n * and per-splat `labels` keep it valid for any subset). Streamed-only fields\n * (`radTree`, `frontierParent`, `radShCodebook`) do not survive a partition -\n * their index topology is meaningless on a subset - so they are dropped;\n * partitioning is for fully resident data. An empty or all-inclusive selection\n * is valid and yields a zero-count half.\n */\nexport function partitionSplatData(\n data: SplatData,\n selection: SelectionVolume | Uint32Array,\n): SplatPartition {\n // `ArrayBuffer.isView`, not `instanceof Uint32Array`: this library moves\n // typed arrays across worker realms (`load-worker.ts` transfers them), and a\n // foreign-realm array fails `instanceof` - it would fall through to the\n // volume branch and throw `containsPoint is not a function`.\n const insideIndices = ArrayBuffer.isView(selection)\n ? validateIndices(selection, data.count)\n : selectInData(data, selection);\n\n const insideMask = new Uint8Array(data.count);\n for (const i of insideIndices) insideMask[i] = 1;\n const outsideIndices = new Uint32Array(data.count - insideIndices.length);\n let n = 0;\n for (let i = 0; i < data.count; i++) {\n if (insideMask[i] === 0) outsideIndices[n++] = i;\n }\n\n return {\n inside: gatherSplatData(data, insideIndices),\n outside: gatherSplatData(data, outsideIndices),\n insideIndices,\n };\n}\n\n/** SH coefficients beyond DC for 1, 2 or 3 bands. */\nfunction shCoefficientCount(bands: number): number {\n return (bands + 1) * (bands + 1) - 1;\n}\n\n/**\n * A new {@link SplatData} holding `indices`' splats of `data`, in order.\n *\n * Internal: it assumes `indices` is already validated (see\n * {@link partitionSplatData}) and shares an immutable SOG palette by\n * reference, both of which are contracts a public helper would have to state.\n */\nfunction gatherSplatData(data: SplatData, indices: Uint32Array): SplatData {\n const count = indices.length;\n const positions = new Float32Array(count * 3);\n const colors = new Uint8Array(count * 4);\n const covariances = new Float32Array(count * 6);\n for (let k = 0; k < count; k++) {\n const i = indices[k] as number;\n positions[k * 3] = data.positions[i * 3] as number;\n positions[k * 3 + 1] = data.positions[i * 3 + 1] as number;\n positions[k * 3 + 2] = data.positions[i * 3 + 2] as number;\n colors[k * 4] = data.colors[i * 4] as number;\n colors[k * 4 + 1] = data.colors[i * 4 + 1] as number;\n colors[k * 4 + 2] = data.colors[i * 4 + 2] as number;\n colors[k * 4 + 3] = data.colors[i * 4 + 3] as number;\n for (let c = 0; c < 6; c++) {\n covariances[k * 6 + c] = data.covariances[i * 6 + c] as number;\n }\n }\n\n const result: {\n count: number;\n positions: Float32Array;\n colors: Uint8Array;\n covariances: Float32Array;\n sh?: SplatData['sh'];\n shPacked?: SplatData['shPacked'];\n antialias?: boolean;\n sourceFormat?: SplatData['sourceFormat'];\n } = { count, positions, colors, covariances };\n\n if (data.sh) {\n const labels = new Uint32Array(count);\n for (let k = 0; k < count; k++) labels[k] = data.sh.labels[indices[k] as number] as number;\n // The palette is immutable and label-addressed, so both halves share it.\n result.sh = { ...data.sh, labels };\n }\n if (data.shPacked) {\n const words = shCoefficientCount(data.shPacked.bands);\n const packed = new Uint32Array(count * words);\n for (let k = 0; k < count; k++) {\n const i = indices[k] as number;\n for (let w = 0; w < words; w++) {\n packed[k * words + w] = data.shPacked.packed[i * words + w] as number;\n }\n }\n result.shPacked = { ...data.shPacked, packed };\n }\n if (data.antialias !== undefined) result.antialias = data.antialias;\n if (data.sourceFormat !== undefined) result.sourceFormat = data.sourceFormat;\n return result;\n}\n\n/**\n * Validates an ascending, in-range, duplicate-free index list and returns a\n * private copy - the result is published on a `readonly` field, so a caller\n * reusing a scratch buffer must not be able to mutate the partition after the\n * fact.\n */\nfunction validateIndices(indices: Uint32Array, count: number): Uint32Array {\n let previous = -1;\n for (const i of indices) {\n if (i >= count) {\n throw new Error(`partitionSplatData: index ${i} is out of range (count ${count}).`);\n }\n if (i <= previous) {\n throw new Error('partitionSplatData: selection indices must be ascending and unique.');\n }\n previous = i;\n }\n return Uint32Array.from(indices);\n}\n","import type { Node } from 'three/webgpu';\n\n/**\n * Per-splat context a {@link SplatModifier} reads, evaluated in the vertex\n * stage. All fields are TSL nodes; see `docs/guide/effects-and-modifiers.md`.\n */\nexport interface SplatContext {\n // --- read-only inputs ---\n /** Pool splat index (stable identity for per-splat channels). */\n readonly index: Node<'int'>;\n /**\n * Splat center in mesh-local space (pre-displacement).\n *\n * Inside a `SplatScene` this is the splat's **placed** position\n * (`sourceMatrix · poolCenter`) - the placement is applied before the stack\n * runs, so effects stay anchored to the scene rather than travelling with a\n * moved source. Use {@link sourceCenter} for the pre-placement position.\n */\n readonly localCenter: Node<'vec3'>;\n /**\n * The splat's position in its own source's data frame. Identical to\n * {@link localCenter} on a plain mesh; inside a `SplatScene` it is the\n * **pre-placement** position, for effects that should travel *with* a moved\n * source instead of staying put in the scene.\n */\n readonly sourceCenter: Node<'vec3'>;\n /**\n * Linear transform from the source data frame to mesh-local space. This is\n * identity on a plain mesh; inside a {@link SplatScene} it is the linear part\n * of the source placement. Apply it to source-frame displacement vectors.\n * To express a source-frame rotation `R` as a mesh-local `rotation` output,\n * conjugate it: `sourceToLocal · R · sourceToLocal.inverse()`.\n */\n readonly sourceToLocal: Node<'mat3'>;\n /** `modelMatrix · localCenter` (world space). */\n readonly worldCenter: Node<'vec3'>;\n /** `modelViewMatrix · localCenter` (view space). */\n readonly viewCenter: Node<'vec3'>;\n /** Camera position in mesh-local space. */\n readonly cameraLocal: Node<'vec3'>;\n /** Color + opacity after SH - the stack's input color. */\n readonly baseColor: Node<'vec4'>;\n /** Approximate surface normal (least-variance axis of the splat's\n * covariance), mesh-local, oriented toward the camera. Built lazily -\n * only modifiers that read it pay for the eigen approximation. */\n readonly normal: Node<'vec3'>;\n /**\n * Reads a per-splat channel by name (see `SplatMesh.defineChannel` /\n * `writeChannel`). Float channels return their stored value; byte channels\n * return the value normalized to `[0, 1]`. Reading an undefined channel is\n * a build-time error (a missing mask is a bug, not a silent zero). The read\n * is memoized per name, so referencing the same channel twice costs one\n * texture fetch.\n */\n channel(name: string): Node<'float'>;\n\n // --- running transform (threaded through the fold) ---\n /** rgb + opacity. */\n readonly color: Node<'vec4'>;\n /** Local-space center displacement. */\n readonly offset: Node<'vec3'>;\n /** Uniform scale multiplier. */\n readonly scale: Node<'float'>;\n /** Rigid rotation. */\n readonly rotation: Node<'mat3'>;\n /** Visibility. */\n readonly visible: Node<'bool'>;\n}\n\n/** The transform fields a modifier changes; omitted fields pass through. */\nexport interface SplatOutputs {\n color?: Node<'vec4'>;\n offset?: Node<'vec3'>;\n scale?: Node<'float'>;\n rotation?: Node<'mat3'>;\n visible?: Node<'bool'>;\n /** Blend `Σ` toward σ²·I (0 = keep, 1 = full collapse). Screen λ equalized when > 0. */\n isotropicCovarianceMix?: Node<'float'>;\n /** Multiplier on max(n·Σ·n, ε); defaults to 0.35² when omitted. */\n isotropicVarianceScale?: Node<'float'>;\n /**\n * Optional isotropic screen-space σ radius in pixels. When set (> 0), caps\n * projected λ for zoom-stable dots. Omit for Spark-style world-space points\n * (size grows mildly on zoom-in).\n */\n isotropicScreenRadiusPx?: Node<'float'>;\n}\n\n/**\n * A splat effect hook. Reads the per-splat {@link SplatContext} and returns\n * the transform fields it wants to change. Pure TSL, so it compiles to WGSL\n * and GLSL and runs on both backends. Modifiers compose by folding over\n * `SplatMesh.modifiers` in order - each sees the running result of the ones\n * before it. Adding/removing/reordering modifiers recompiles the material;\n * changing a modifier's own uniforms or storage buffers never does.\n *\n * Constraints (see `docs/guide/effects-and-modifiers.md`): covariance is pre-baked,\n * so `scale` is uniform-only and `rotation` rigid-only; displaced splats\n * keep their pre-displacement depth-sort order.\n */\nexport type SplatModifier = (context: SplatContext) => SplatOutputs;\n\n/** Anything with a `modifiers` list - {@link ModifierSlots.apply} target. */\nexport interface ModifierStackTarget {\n modifiers: readonly SplatModifier[];\n}\n\n/**\n * Named, ordered modifier slots for hosts that stack several effects (reveal,\n * SDF, lighting, fog, opacity, …) on one mesh. The slot **order is fixed at\n * construction** - it defines the fold order of the compiled stack - while\n * each slot's occupant can change at runtime.\n *\n * The rebuild vs uniform-update contract (see\n * `docs/guide/effects-and-modifiers.md`, \"Multi-slot stacks\"):\n *\n * - An **empty slot costs nothing**: it is omitted from the compacted list\n * entirely, never compiled as a passthrough. All slots empty ⇒ the mesh\n * gets an empty list ⇒ the unhooked graph (and the unified renderer's\n * zero-modifier gather fast path stays eligible - except for a `SplatScene`,\n * which `UnifiedSplatRenderer` does not accept as a source at all).\n * - {@link apply} hands the mesh a **compacted array whose reference is\n * stable** while the occupancy is unchanged, so re-applying after\n * uniform-only changes is a guaranteed no-op on the mesh's identity diff -\n * no material rebuild, no `graphRevision` bump.\n * - Filling, clearing, or replacing a slot with a *different* function is a\n * structural change: the next {@link apply} rebuilds the material once.\n * Setting a slot to the function it already holds is a no-op.\n */\nexport class ModifierSlots {\n private readonly order: readonly string[];\n private readonly occupants = new Map<string, SplatModifier>();\n /** Compacted stack in slot order; rebuilt only on structural change so the\n * array reference itself is stable across uniform-only frames. */\n private compacted: readonly SplatModifier[] = [];\n\n /** @param order Slot names, in fold order. Must be non-empty and unique. */\n constructor(order: readonly string[]) {\n if (order.length === 0) {\n throw new Error('ModifierSlots: the slot order must name at least one slot.');\n }\n if (new Set(order).size !== order.length) {\n throw new Error(`ModifierSlots: duplicate slot name in order [${order.join(', ')}].`);\n }\n this.order = [...order];\n }\n\n /** The declared slot names, in fold order. */\n get slotNames(): readonly string[] {\n return this.order;\n }\n\n /**\n * Fills or clears a slot (`null` clears). Returns `true` when the change is\n * structural - i.e. the next {@link apply} will trigger a material rebuild -\n * and `false` when it was a no-op (same function, or clearing an already\n * empty slot). Unknown slot names throw: a typo must not silently drop an\n * effect.\n */\n set(name: string, modifier: SplatModifier | null): boolean {\n if (!this.order.includes(name)) {\n throw new Error(\n `ModifierSlots: unknown slot \"${name}\" (declared: ${this.order.join(', ')}).`,\n );\n }\n const current = this.occupants.get(name) ?? null;\n if (current === modifier) return false;\n if (modifier === null) this.occupants.delete(name);\n else this.occupants.set(name, modifier);\n const next: SplatModifier[] = [];\n for (const slot of this.order) {\n const occupant = this.occupants.get(slot);\n if (occupant) next.push(occupant);\n }\n this.compacted = next;\n return true;\n }\n\n /** The slot's current occupant, or `null` when empty. */\n get(name: string): SplatModifier | null {\n return this.occupants.get(name) ?? null;\n }\n\n /** Sugar for `set(name, null)`; returns whether the slot was occupied. */\n clear(name: string): boolean {\n return this.set(name, null);\n }\n\n /**\n * The compacted stack: occupied slots only, in slot order. The reference is\n * stable while occupancy is unchanged, so it is safe to assign every frame.\n */\n get modifiers(): readonly SplatModifier[] {\n return this.compacted;\n }\n\n /**\n * Assigns the compacted stack to `target.modifiers` (a `SplatMesh` or\n * anything with the same setter). The mesh's identity diff makes this free\n * when nothing structural changed, so calling it after every `set` - or\n * even every frame - is fine.\n */\n apply(target: ModifierStackTarget): void {\n target.modifiers = this.compacted;\n }\n}\n"],"names":["createYUpTransform","THREE","yUpTransformForFormat","format","foldSplatModifierStack","modifierList","localCameraPosition","inputs","cameraLocal","state","vec3","float","mat3","bool","used","isotropicCovarianceMix","isotropicVarianceScale","isotropicScreenRadiusPx","worldCenter","viewCenter","normal","channelNodes","context","modelWorldMatrix","vec4","modelViewMatrix","name","node","modifier","outputs","SPLAT_DATA_TEXTURE_WIDTH","allocateRowSpan","spans","rowCount","poolRows","bestIndex","i","span","start","releaseRowSpan","count","a","b","merged","last","SplatPool","options","__publicField","assertPoolRowsFitDevice","texelCount","floatType","centersImage","covarianceAImage","createDataTexture","data","createIntegerDataTexture","indices","index","texture","rows","fromRow","tenant","width","placements","heldRows","range","targetRow","from","to","length","group","height","type","deviceMaxTextureSize","renderer","backend","wgpu","_b","_a","gl","max","maxTextureSize","addMergedUpdateRange","attribute","mergedStart","mergedEnd","disjoint","rangeEnd","MAX_DOF_RADIUS_PX","MAX_DOF_VARIANCE","apertureAngleFromSize","aperture","focusDistance","size","focus","computeDofCocVariancePx2","depth","apertureAngle","focusBlur","apertureRadius","cocRadiusPx","variance","maxVariance","computeDofOpacityFade","detRaw","detBlur","clampDepthOfFieldSettings","settings","previous","MAX_SPLAT_RADIUS_PX","FRONTIER_ROOT_SIZE","DEFAULT_ISOTROPIC_VARIANCE_SCALE","applyIsotropicCovarianceOverride","covariance","_normal","mixFactor","varianceScale","e0","e1","e2","row0","row1","row2","r0","r1","r2","crossA","crossB","crossC","lenA","lenB","lenC","useA","useB","axis","thin","rThin","isoVar","isoCov","equalizeProjectedEigenvalues","lambda1","lambda2","circle","mix","capProjectedEigenvaluesToScreenRadius","screenRadiusPx","maxStdDev","targetVariance","capped1","capped2","enabled","vec3Uniform","uniform","sourceWorldTransform","columns","sourceId","localCenter","base","int","c0","c1","c2","c3","linear","shCoefficientCount","bands","shCoefficientReader","sh","textures","splatTexel","label","textureLoad","column","row","ivec2","groups","c","word","channels","uint","SH_C1","SH_C2","SH_C3","evaluateSplatSh","direction","x","y","z","coefficient","band1","xx","yy","zz","band2","applySplatMaterialGraph","material","mode","uniforms","pick","textureWidth","maxRadius","gaussianExponent","splatIndex","baseColor","poolCenter","placement","placed","shSum","colorAfterSh","makeNormal","covA","covB","poolSigma","inverse","toCamera","makeChannel","channel","stack","splatColor","varying","colorSpaceToWorking","quadPosition","positionGeometry","viewDepthVarying","opacityCompensation","vAdjustedStdDev","vAlpha2","Fn","center","clipCenter","cameraProjectionMatrix","clipPosition","frustumNdc","margin","inFrustum","isVisible","If","covarianceBase","placedCovariance","covariance3d","invZ","invZ2","j1","j2","viewRotationT","u1","u2","aQ","dQ","bQ","aRaw","dRaw","bRaw","halfApertureAngle","cocVar","lowPassVariance","d","mipFade","mid","radius","vec2","equalized","capped","eigenvector1","stdDev","alpha2","remap","majorLambda","minSplat","majorAxis","minorAxis","writePosition","pixelOffset","ndcCenter","notBlob","distance","ownSize","packedParent","isLeaf","absParent","parentSize","limitDist","ownCut","maxScreen","minScreen","projRadius","inBand","above","majorRadius","minorRadius","opacity","contributionOk","passes","squaredDistance","Discard","g","aExp","alpha","litCenter","tslTexture","screenUV","ox","oy","s0","s1","s2","s3","s4","wSum","rgbSoft","aSoft","litSoft","lit","factor","rgb","r","viewDepthRadius","modelView","m","hasUploaded","dropMirror","array","bytes","Ctor","StorageMirrorReleaser","attributes","freed","version","warn","_ComputeSorter","capacity","centersTexture","dataTextureWidth","centersBuffer","perSource","BUCKET_COUNT","BLOCK_SIZE","histogramAttribute","blockSumsAttribute","superBlockSumsAttribute","bucketsAttribute","histogram","storage","blockSums","superBlockSums","buckets","sourceIndex","order","workCenters","atomicStore","instanceIndex","poolIndex","texel","depthPoint","bucket","atomicAdd","runningTotal","Loop","slot","bucketCount","atomicLoad","blockTotal","scanSuperBlockSums","superBlockTotal","offset","destination","activeCount","rounded","bounds","viewRadius","near","far","blockCount","pass","releaseRendererAttributes","ComputeSorter","map","WorkerSorter","host","SortWorker","event","logError","init","_activeCount","_bounds","message","centers","sourceIds","current","sent","indexes","TWO_MILLION","FIVE_MILLION","EIGHT_MILLION","MODEL_VIEW_EPSILON","validateSortIntervalMs","value","automaticSortIntervalMs","isMobile","WebGpuSortScheduler","sortIntervalMs","lastAcceptedModelView","now","settled","moved","maximumElementDelta","interval","maximum","DEPTH_24_MAX","_ndc","_raycaster","_dirView","unpackNormalizedDepth","denormalizeViewDepth","normalized","unprojectViewDepth","ndcX","ndcY","viewDepth","camera","outPoint","t","_drawingBufferSize","resolveXrView","xr","head","eye","viewport","isXrArrayCamera","xrSessionInit","isWebGPU","required","optional","SplatPicker","ndc","run","px","pyBottom","pickTarget","pickProxy","pickCamera","mesh","previousTarget","previousScissorTest","previousClearAlpha","previousAutoClear","readback","rgba","error","point","target","source","projection","elements","xOffset","yOffset","rowW","DEFAULT_RELIGHT_BLEND","DEFAULT_RELIGHT_BRIGHTNESS","DEFAULT_RELIGHT_BACKGROUND","DEFAULT_RELIGHT_SOFTNESS","clampRelightingSettings","partial","blend","brightness","background","softness","createPlaceholderRelightTexture","MAX_DIM","MAX_CELLS","UniformGrid","poolIndices","lo","hi","v","nonZero","s","spanProduct","cell","chooseDims","cellEdge","cellCount","counts","cursor","p","ix","iy","visit","q","iz","rowBase","k","dx","dy","dz","d2","bestSq","UPLOAD_STAGING_CACHE_SIZE","_SplatMesh","detectSplatDeviceProfile","validateMaxStdDev","minSplatSizePx","validateMinSplatSizePx","isStatic","staticShWanted","resolveSplatPerformanceProfile","MAX_SH_BANDS","packedShBands","suppliedPool","makeOwnPool","shMismatch","ownsPool","pool","colorsTexture","covarianceATexture","covarianceBTexture","shPaletteTexture","shPackedTextures","geometry","splatIndexes","splatIndexAttribute","validateMaxSplatScreenRadius","validateFoveationTargetPx","validateFoveationDrawBudget","correction","profile","next","empty","startRow","handle","record","firstRow","lastRow","_appendBox","colors","covarianceA","covarianceB","positions","covariances","labels","frontierParent","p3","p6","wanted","requantize","packedRangesEqual","requantizeShWord","neutral","min","neutralShWordFor","method","active","prefix","previousCount","cached","visible","backing","fill","textureType","first","endRow","minPx","maxPx","cFrom","cTo","cLength","uploadStartedAt","xrView","projectionCamera","sortCamera","viewWidth","viewHeight","_viewSize","sortStartedAt","maxSize","worldPoint","_queryLocal","grid","gather","_queryWorld","ray","radiusAtUnitDistance","minimumRadius","bestDistance","maxDrop","gatherLocal","bestY","drop","sorted","out","_queryScale","cache","viewportX","viewportY","positionCamera","focalY","FOVEATION_ADAPT_MS","limit","_camLocal","_adaptView","_adaptMvp","cA","cB","slots","sampled","drawn","FOVEATION_ADAPT_STRIDE","cx","cy","cz","clipX","clipY","trace","estimatedDrawn","budget","FOVEATION_LIMIT_MAX_PX","identity","activeStart","activeLen","firstSlot","contiguous","dirtyStart","dirtyEnd","nextActiveCount","copyCount","movedPoolIndex","lastSlot","draw","centersTex","covATex","centersImg","covAImg","encodeFloat32ToHalf","components","entries","regions","region","key","encodeHalf","view","stagingData","half","staging","_uploadPosition","existing","buffer","oldestHeight","radixSorter","mod","SplatMesh","DEFAULT_FOVEATION_TARGET_PX","DEFAULT_FOVEATION_DRAW_BUDGET","APPEND_CAP","buildHoldSwapGroups","toAdd","buildSwapGroups","toRemove","coverageRuns","entry","touch","coverageGroup","byLeaf","leafKey","end","ensure","items","item","groupPriority","finest","isClassicLccSwapSet","compareClassicSwapGroups","describe","runs","aa","bb","classicFetchPhaseRank","phase","kindForClassicFetchPhase","classicFetchGroupKey","want","file","stampClassicFetchGroups","pending","lodBaseDistance","forceNearPriority","nearDisplayDistance","aggregates","prev","groupId","agg","groupClass","kind","_lodMultiplier","isWaitingOnFinest","classicFetchPhaseForDesired","lodMultiplier","classicFetchPhaseForCoverage","enqueueClassicFetch","level","inView","screenImportance","betterPhase","samePhaseNearer","samePhaseBetterView","compareClassicFetches","fileA","fileB","groupDistA","groupDistB","pendingA","pendingB","groupA","groupB","classA","classB","screenA","screenB","finestA","finestB","rankInGroup","sliceSplatData","chunk","shWordsPerSplat","chunkBytes","abortReason","signal","reason","validateAppendCap","cap","validateLodScale","scale","defaultCpuCacheBytes","resolveCpuCacheBytes","parseLodManifest","json","raw","directories","chunkUrls","directory","leaves","lodLevels","lods","levelKey","assertSaneRange","boxFromBound","fileCount","bound","buildSogScene","shBands","manifest","packShBands","chunkOptions","_","files","LodScheduler","computeSogPinnedFiles","computeSogMinimumCoverage","total","leaf","pinned","httpDatasetSource","manifestUrl","request","path","probeSize","url","response","toRequestInit","cancelBody","saneSize","MANIFESTS","n","createLocalDataset","found","basename","match","candidate","minDepth","SplatLoadError","names","root","urls","urlFor","normalize","shallowest","ONE_SHOT_FORMATS","WorkerClient","worker","createAbortError","deserializeSplatLoadError","detail","failure","onProgress","id","promise","resolve","reject","onAbort","cancel","ChunkLoader","LoadWorker","absoluteUrl","resolveSplatUrl","resolveFileFormat","withSourceFormat","requested","splatFormatForExtension","splatNameExtension","lcc","rad","sog","client","resolved","OneShotWorker","SOURCE_FORMATS","splatUrlExtension","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","splats","StreamedSplatMesh","FrontierWorkerCtor","neverRetireCoverageEarly","resolveSplatBudget","isPageTable","cacheCeilingBytes","_c","e","bootstrap","input","StaticLodSplatMesh","staticLodSplatMesh","lower","dataset","deviceProfile","deviceBudget","ceilingBudget","sourceOptions","radMaxStdDev","recommendedRadMaxStdDev","budgetLifts","buildRadScene","isAbortError","toSplatLoadError","buildLcc2Scene","buildLccScene","ceiling","liftBudgetToFinestLevel","residentCeiling","capacityFactor","capacityRows","resolvedFoveationMode","page","written","at","done","resident","msg","transfer","collision","controller","loadCollisionMeshTiles","abortListener","_resolve","_drawSize","lodCamera","performanceEvent","timings","uploadedCount","_paintLocal","edited","touchedFiles","fileEdits","touched","li","py","pz","_d","_e","_cameraWorldPos","_sphere","_cameraWorldQuat","startedAt","before","compactionCountBefore","_cameraLocal","_projScreen","_frustum","_cameraForward","pageTableTimestamp","scheduledRuns","holdingRuns","holding","desiredRuns","desired","desiredFiles","runKey","staged","classicLccGroups","pendingFetches","appended","addsPending","poolPressure","held","missing","holdForTarget","recoverable","stagedNow","nearestCandidates","homeGroup","seeds","nearLevel","seed","alt","neededRows","list","stagedSplats","totalSplats","readyGroups","groupRuns","groupReady","status","progress","totalGroups","critical","horizon","fallback","nearest","home","residentBefore","stagedBefore","appendedCount","removedCount","stagedCount","compacted","timestamp","sum","allowance","rowSplats","needed","slice","any","holdForFinest","covered","gapStart","nextCovered","gapEnd","holdingNearL0","ordered","worstFile","worstWant","activeFile","rowAligned","forwardLocal","frustum","sweepCap","f","plan","applyStartedAt","clamped","slicePlanRun","writeFinishedAt","degenerateStart","degenerateCount","residentFinishedAt","planTimings","ratio","tree","weight","classicWant","backoff","attempts","delay","candidates","j","loadScene","loader","loadSceneFile","BUDGET_GRANULARITY","CAMERA_RESCHEDULE_INTERVAL_MS","transferablesFor","transferables","normalizeBudget","buildInWorker","WorkerClass","abort","contentSplatCount","finestSplatCount","decodeReported","completed","result","immediate","MAX_SOURCES","SOURCE_ID_CHANNEL","SourceMatrixArray","maxSources","uniformArray","matrix","worldBoundsOf","sources","child","matrixMaxStretch","x0","y0","z0","x1","y1","z1","x2","y2","z2","g00","g11","g22","g01","g02","g12","SplatScene","sourceIdTexture","ids","box","sphere","live","RADIX_KEY_BITS","RADIX_EXACT_KEY_BITS","RADIX_BITS_PER_PASS","RADIX_KEY_MAX","radixPassCount","keyBits","WORKGROUP_SIZE","ELEMENTS_PER_THREAD","ELEMENTS_PER_WORKGROUP","DIGIT_COUNT","MASK_WORDS_PER_DIGIT","MASK_WORDS_PER_GROUP","SCAN_BLOCK_SIZE","RadixSorter","exactDepth","passCount","groupCount","histogramLength","scanBlockCount","keysAAttribute","keysBAttribute","valuesAAttribute","valuesBAttribute","scanSumsAttribute","masksAttribute","keysA","keysB","valuesA","valuesB","scanSums","masks","buildKeys","bits","floatBitsToUint","mask","inputKeys","outputKeys","inputValues","outputValues","bit","clearHistogram","buildHistogram","workgroupId","invocationLocalIndex","digit","scanBlocks","running","scanBlockSums","addBlockOffsets","block","digitOffsets","workgroupArray","rankedScatter","thread","maskBase","storageBarrier","workgroupBarrier","valid","bitInWord","maskIndex","atomicOr","digitBase","localRank","wordIndex","countOneBits","precedingMask","writeOrder","minimum","stage","_WorkBuffer","WorkBuffer","WorkBufferGather","sourceCapacity","modifiers","output","outputColor","outputCovA","outputCovB","outputIsotropicMix","outputIsotropicScreenRadius","inputA","inputB","gatheredCenter","resolvedColor","sourceAlpha","localCovariance","worldCovarianceBase","worldCovariance","targetOffset","sourceMatrix","cameraViewMatrixOrOpacity","cameraViewMatrix","createWorkBufferMaterial","isotropicMix","isotropicScreenRadius","workColor","adjustedStdDev","workIndex","centerSample","drawable","isoMix","detBase","dofOnlyFade","compensate","fade","eigenvector","major","minor","WORK_BUFFER_CENTERS_BYTES_PER_SPLAT","WORK_BUFFER_BYTES_PER_SLOT","WEBGPU_DEFAULT_MAX_STORAGE_BUFFER_BINDING_SIZE","recommendedWebGpuRequiredLimits","adapter","supportsWebGpuPowerPreference","nav","uaDataPlatform","webGpuPowerPreferenceOptions","powerPreference","estimateLargestStorageBufferBytes","estimateUnifiedWorkBufferBytes","estimateUnifiedWorkBufferPeakBytes","deviceMaxStorageBufferBindingSize","assertStorageBufferFitsDevice","byteSize","needMiB","limitMiB","defaultMiB","supportsUnifiedSplatRenderer","UnifiedSplatRenderer","workBuffer","focal","antialias","projectedLowPassVariance","compensateProjectedLowPass","dofFocusDistance","dofAperture","relightPlaceholder","relightBlend","relightBrightness","relightBackground","relightSoftness","isFillConstrainedSplatDevice","sortInputs","resolvedVisible","lccProfile","hits","hit","best","targetSize","forceSort","viewCamera","focalX","updated","admitted","rebuilt","geometryInvalidated","sliceOffset","ownedSameSlice","geometryMatches","layoutChanged","viewInverse","operation","SUSPENDED_BUDGET","BudgetGovernor","hysteresis","totalBudget","member","validateWeight","weights","changed","weightSum","cappedSpend","_cameraPos","_box","CameraBudgetGovernor","nonNegative","positive","priority","fixedWeight","initial","forced","significant","surface","projected","clamp","onScreen","applied","ambientGpu","gpu","createSplatRenderer","requireWebGpu","rendererOptions","powerOptions","device","adapterFailed","WebGPURenderer","DEFAULT_MAX_GLOBAL_INFLIGHT","DEFAULT_PER_MESH_FLOOR","ChunkFetchScheduler","normalizeWeight","participants","totalWeight","other","otherWeight","share","floored","reservedForOthers","bestWeight","DEFAULT_PER_MESH_FLOOR_BYTES","DEFAULT_MIN_INTERVAL_MS","DEFAULT_DEADBAND","ChunkCacheBudget","floor","unclamped","distributable","normalizeCeiling","createSelectionVolume","worldMatrix","assertUsableMatrix","toVolume","m00","m10","m20","m01","m11","m21","m02","m12","m22","tx","ty","tz","he","hx","hy","hz","requirePositive","vx","vy","vz","halfHeight","selectInData","volume","countInData","partitionSplatData","selection","insideIndices","validateIndices","insideMask","outsideIndices","gatherSplatData","words","packed","w","ModifierSlots","occupant"],"mappings":"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"}
|