BabylonJS/Babylon.js · error
Cannot rebuild compound part "${proxy.name}": the retained c
Error message
Cannot rebuild compound part "${proxy.name}": the retained compound source data is not available. What it means
During compound rebuild, for each non-streaming proxy part the mesh tries to recover its retained source data via `_createRetainedPartSource(proxy)`. If that returns null the original splat data for the part is no longer available (e.g. released after merge), so rebuilding the buffers would silently corrupt the compound — hence the throw naming the proxy mesh.
Source
Thrown at packages/dev/core/src/Meshes/GaussianSplatting/gaussianSplattingMesh.pure.ts:1276
// scenario B, part 0 is itself a proxied part with no implicit "own" data.
for (let partIndex = 0; partIndex < this._partProxies.length; partIndex++) {
const proxy = this._partProxies[partIndex];
if (!proxy) {
continue;
}
// Streaming parts have no retained CPU source — their atlas rows (core + SH) are
// GPU-authoritative and preserved across the rebuild by the backup/restore hooks. Leave their
// rows at the fresh-array defaults (zero core, neutral SH) for the restore to overwrite; just
// advance the offset. (With a static part present _splatsData is non-null and these would
// otherwise reconstruct as a harmless zeros-slice; with only streaming parts it is null and
// _createRetainedPartSource returns null — so this skip is required, not just an optimization.)
if (this._streamingStates.some((s) => s.proxy === proxy)) {
rebuildOffset += proxy._vertexCount;
continue;
}
const source = this._createRetainedPartSource(proxy);
if (!source) {
throw new Error(`Cannot rebuild compound part "${proxy.name}": the retained compound source data is not available.`);
}
this._appendPartSourceToArrays(source, rebuildOffset, covA, covB, colorArray, sh, minimum, maximum);
rebuildOffset += source._vertexCount;
}
} else {
// No proxies yet: this is the very first addPart call on a mesh that loaded
// its own splat data (scenario A legacy path). Re-process that own data so
// it occupies the start of the new texture before the incoming part is appended.
// In the preferred scenario B (empty composer) splatCountA is 0 and this
// entire branch is skipped by the outer `if (splatCountA > 0)` guard.
if (this._splatsData) {
const uBufA = GaussianSplattingMeshBase._GetSplatDataBytes(this._splatsData);
const fBufA = GaussianSplattingMeshBase._GetSplatDataFloats(this._splatsData);
for (let i = 0; i < splatCountA; i++) {
this._makeSplat(i, fBufA, uBufA, covA, covB, colorArray, minimum, maximum, false);
}
if (sh && this._shData) {
for (let texIdx = 0; texIdx < sh.length; texIdx++) {
View on GitHub (pinned to 0592b347b8)
Solutions
- Keep the retained source data: don't dispose/release the original part `_splatsData` for meshes used as compound parts.
- Avoid streaming-proxy marking mismatches: ensure `_streamingStates` correctly includes streaming proxies so they are skipped, not rebuilt.
- Re-load the original part meshes from their source files before rebuilding the compound.
- Wrap rebuild in try/catch and fall back to re-creating the compound from freshly loaded sources.
Example fix
// before compound.dispose(false); // releases retained part sources // ... later, on context restore compound.rebuild(); // throws: source data gone // after // keep source splats data alive (do not dispose source meshes/data) compound.rebuild(); // _createRetainedPartSource succeeds
Defensive patterns
Strategy: try-catch
Validate before calling
for (const proxy of proxies) {
if (!compound._streamingStates.some(s => s.proxy === proxy) && !proxy._splatsData) {
// retained source missing: reload the original part from source files first
await reloadPartSource(proxy);
}
} Type guard
function canRebuildCompound(compound: GaussianSplattingMeshBase): boolean {
return compound._streamingStates.every(s =>
s.proxy._splatsData != null || s.proxy._isReservedEmpty);
} Try / catch
try {
compound.rebuild();
} catch (e) {
if (e instanceof Error && e.message.includes("retained compound source data")) {
const fresh = await reloadAllPartsFromFiles(); // re-decode original assets
return rebuildCompoundFrom(fresh);
}
throw e;
} Prevention
- Don't dispose or release _splatsData of meshes used as compound parts if rebuild is expected
- Keep original source files/URLs reachable so parts can be reloaded after context loss
- Ensure streaming proxies are registered in _streamingStates so rebuild skips them
- Prefer full re-creation of the compound over in-place rebuild when sources were freed
When it happens
Trigger: Rebuilding a compound (e.g. after context loss, `rebuild()`, or re-adding parts to a loaded compound) when a proxy part's retained source data was discarded — data retention disabled, `dispose` of sources, or memory-saving flags that skip keeping `_splatsData`.
Common situations: WebGL context restore on a compound whose part sources were freed; re-serializing or re-merging a compound loaded from a file without retained sources; enabling memory-optimization options that release source data after merge.
Related errors
- To call addPart()/addParts(), each source mesh must be fully
- To call addPart()/addParts(), each source mesh must not be a
- Cannot add part, as the maximum part count (${maxPartCount})
- Unable to create multi sampled framebuffer
- Unable to create Occlusion Query
AI-assisted analysis of BabylonJS/Babylon.js@0592b347b8 (2026-08-30).
Data as JSON: /api/errors/9aac57c7bd4e7366.
Report an issue: GitHub.