{"record":{"id":"9699e14af7648d8a","repo":"BabylonJS/Babylon.js","slug":"writestreamingsplats-range-globaloffset-g","errorCode":null,"errorMessage":"_writeStreamingSplats: range [${globalOffset}, ${globalOffset + count}) is outside the atlas bounds [0, ${capacity})","messagePattern":"_writeStreamingSplats: range \\[(.+?), (.+?)\\) is outside the atlas bounds \\[0, (.+?)\\)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/dev/core/src/Meshes/GaussianSplatting/gaussianSplattingMeshBase.pure.ts","lineNumber":3168,"sourceCode":"     * (guaranteed after `reserveStreamingPart`). Bounds of the written centers are accumulated into `min`/`max`\n     * when provided (so the caller can grow the owning part's bounding info).\n     * @param globalOffset first atlas splat index to write\n     * @param count number of splats to write\n     * @param splatsData raw `.splat` bytes for `count` splats (stride 32)\n     * @param min optional running min accumulator for the written centers\n     * @param max optional running max accumulator for the written centers\n     */\n    protected _writeStreamingSplats(globalOffset: number, count: number, splatsData: ArrayBuffer | ArrayBufferView, min?: Vector3, max?: Vector3): void {\n        if (!this._splatPositions) {\n            return;\n        }\n        // Validate the range against the atlas and the input length (independent of GPU state): an out-of-range\n        // offset/count would write over another part's texels or allocate a huge transient (the CPU arrays are\n        // atlas-indexed up to `end`); a short input would read past its end.\n        const capacity = this._splatPositions.length / 4;\n        const uBuffer = GaussianSplattingMeshBase._GetSplatDataBytes(splatsData);\n        if (!Number.isInteger(globalOffset) || !Number.isInteger(count) || globalOffset < 0 || count < 0 || globalOffset + count > capacity) {\n            throw new Error(`_writeStreamingSplats: range [${globalOffset}, ${globalOffset + count}) is outside the atlas bounds [0, ${capacity})`);\n        }\n        if (uBuffer.length < count * 32) {\n            throw new Error(`_writeStreamingSplats: splatsData has ${uBuffer.length} bytes, need ${count * 32} for ${count} splats (stride 32)`);\n        }\n        if (count === 0 || !this._covariancesATexture) {\n            return;\n        }\n        const textureSize = this._getTextureSize(this._vertexCount);\n        const width = textureSize.x;\n        const covBSItemSize = this._useRGBACovariants ? 4 : 2;\n        const end = globalOffset + count;\n\n        const fBuffer = GaussianSplattingMeshBase._GetSplatDataFloats(splatsData);\n\n        // Transients are sized to `count`, not `end` (globalOffset + count): sizing by atlas position would allocate\n        // tens of MB for a small write near a large atlas's tail. dstIndex still addresses the atlas _splatPositions.\n        const covA = new Uint16Array(count * 4);\n        const covB = new Uint16Array(count * covBSItemSize);","sourceCodeStart":3150,"sourceCodeEnd":3186,"githubUrl":"https://github.com/BabylonJS/Babylon.js/blob/0592b347b8a4ee0236089ea86a749cacfdb266d8/packages/dev/core/src/Meshes/GaussianSplatting/gaussianSplattingMeshBase.pure.ts#L3150-L3186","documentation":"_writeStreamingSplats validates the requested global [globalOffset, globalOffset+count) range against the splat atlas capacity (this._splatPositions.length / 4) and throws when it is non-integral, negative, or exceeds bounds. This protects other parts' texels from being overwritten and avoids huge transient allocations.","triggerScenarios":"Calling _writeStreamingSplats (or a streaming write API) with an atlas-level offset beyond the mesh's current splat capacity, or with a count that runs past the end.","commonSituations":"Using a global splat index from a manifest that no longer matches the rebuilt atlas size; reserving less capacity than the streaming source contains.","solutions":["Clamp/validate globalOffset + count <= this._splatPositions.length / 4 before writing","Re-reserve the atlas to the needed capacity before streaming in more splats","Ensure offsets are integers derived from splat counts, not byte offsets"],"exampleFix":"// before\nbase._writeStreamingSplats(byteOffset / 4, count, data);\n// after\nconst capacity = base._splatPositions.length / 4;\nconst splatOffset = byteOffset / 32;\nif (splatOffset + count > capacity) throw new RangeError('exceeds atlas');\nbase._writeStreamingSplats(splatOffset, count, data);","handlingStrategy":"validation","validationCode":"const capacity = mesh._splatPositions.length / 4;\nif (!Number.isInteger(offset) || !Number.isInteger(count) || offset < 0 || count < 0 || offset + count > capacity) {\n  throw new RangeError('write range outside atlas');\n}","typeGuard":"const inAtlas = (o: number, c: number, capacity: number) =>\n  Number.isInteger(o) && Number.isInteger(c) && o >= 0 && c >= 0 && o + c <= capacity;","tryCatchPattern":"try {\n  base._writeStreamingSplats(offset, count, data);\n} catch (e) {\n  if (String(e).includes('outside the atlas bounds')) { reReserve(capacityNeeded); retry(); }\n  else throw e;\n}","preventionTips":["Track atlas capacity alongside streaming offsets","Convert byte offsets to splat indices consistently (divide by stride, not 4)","Split oversized writes across reservations"],"tags":["gaussian-splatting","bounds-check","streaming"],"backgroundTag":"range-out-of-bounds","analyzedSha":"0592b347b8a4ee0236089ea86a749cacfdb266d8","analyzedAt":"2026-08-30T15:11:20.442Z","schemaVersion":2},"datasetVersion":"2026-08-30T18:17:15.746Z"}