feat(scene): make removeFromScene symetric with addToScene#337
feat(scene): make removeFromScene symetric with addToScene#337RaananW wants to merge 12 commits into
Conversation
removeFromScene now accepts the same union as addToScene (Mesh | LightBase | Camera | ShadowGenerator | TransformNode | AssetContainer), dispatching by shape. Removing an AssetContainer undoes the add: recurses entities, clears the scene camera if it set it, unregisters animation groups, and splices the per-container before-render hook (previously leaked). Lights, cameras and shadow generators each get a removal path; all are idempotent. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
API ChangesAPI Extractor detected public API changes for Potentially breaking changes detected. Removed or changed public API lines:
API Extractor diffdiff --git a/home/vsts/work/1/s/test-results/api-report/target/temp/babylon-lite.api.md b/home/vsts/work/1/s/test-results/api-report/current/temp/babylon-lite.api.md
index 873e78f3..10f9b03e 100644
--- a/home/vsts/work/1/s/test-results/api-report/target/temp/babylon-lite.api.md
+++ b/home/vsts/work/1/s/test-results/api-report/current/temp/babylon-lite.api.md
@@ -4160,7 +4160,7 @@ export function removeBillboardSprite(handle: BillboardSpriteHandle): void;
export function removeBillboardSpriteIndex(system: BillboardSpriteSystem, index: number): void;
// @public
-export function removeFromScene(scene: SceneContext, mesh: Mesh): void;
+export function removeFromScene(scene: SceneContext, entity: Mesh | LightBase | Camera | ShadowGenerator | TransformNode | AssetContainer): void;
// @public
export function removeHierarchyInstance(pool: HierarchyInstancePool, index: number): void; |
There was a problem hiding this comment.
Pull request overview
Makes removeFromScene symmetric with addToScene by expanding it from “mesh-only removal” into a dispatcher that can remove lights, cameras, shadow generators, transform-node subtrees, and whole AssetContainers (including undoing addToScene(container)’s scene-side effects).
Changes:
- Expand
removeFromSceneto accept the same union asaddToSceneand add removal logic for non-mesh entities andAssetContainer. - Fix
addToScene(scene, container)to store the animation-group_beforeRenderhook on the container so it can be removed later. - Add docs + unit test and regenerate per-scene bundle manifests.
Reviewed changes
Copilot reviewed 49 out of 49 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/lite/unit/remove-from-scene.test.ts | Adds unit coverage for symmetry/idempotence scenarios (needs updates to match real shadow-generator shape). |
| packages/babylon-lite/src/scene/scene-remove.ts | Implements multi-entity removeFromScene dispatch and AssetContainer removal logic (has issues around shadow-task disposal + parent detachment). |
| packages/babylon-lite/src/scene/scene-core.ts | Stores the per-container _beforeRender hook so it can be removed during container unload. |
| packages/babylon-lite/src/asset-container.ts | Adds @internal _beforeRenderHook field to enable symmetric add/remove. |
| docs/lite/architecture/01-scene.md | Updates public API signatures to include Camera and documents removeFromScene. |
| lab/public/bundle/manifest/scene88.json | Bundle manifest regen for updated runtime output. |
| lab/public/bundle/manifest/scene86.json | Bundle manifest regen for updated runtime output. |
| lab/public/bundle/manifest/scene79.json | Bundle manifest regen for updated runtime output. |
| lab/public/bundle/manifest/scene75.json | Bundle manifest regen for updated runtime output. |
| lab/public/bundle/manifest/scene72.json | Bundle manifest regen for updated runtime output. |
| lab/public/bundle/manifest/scene71.json | Bundle manifest regen for updated runtime output. |
| lab/public/bundle/manifest/scene7.json | Bundle manifest regen for updated runtime output. |
| lab/public/bundle/manifest/scene59.json | Bundle manifest regen for updated runtime output. |
| lab/public/bundle/manifest/scene52.json | Bundle manifest regen for updated runtime output. |
| lab/public/bundle/manifest/scene48.json | Bundle manifest regen for updated runtime output. |
| lab/public/bundle/manifest/scene46.json | Bundle manifest regen for updated runtime output. |
| lab/public/bundle/manifest/scene43.json | Bundle manifest regen for updated runtime output. |
| lab/public/bundle/manifest/scene4.json | Bundle manifest regen for updated runtime output. |
| lab/public/bundle/manifest/scene33.json | Bundle manifest regen for updated runtime output. |
| lab/public/bundle/manifest/scene254.json | Bundle manifest regen for updated runtime output. |
| lab/public/bundle/manifest/scene253.json | Bundle manifest regen for updated runtime output. |
| lab/public/bundle/manifest/scene252.json | Bundle manifest regen for updated runtime output. |
| lab/public/bundle/manifest/scene251.json | Bundle manifest regen for updated runtime output. |
| lab/public/bundle/manifest/scene246.json | Bundle manifest regen for updated runtime output. |
| lab/public/bundle/manifest/scene245.json | Bundle manifest regen for updated runtime output. |
| lab/public/bundle/manifest/scene244.json | Bundle manifest regen for updated runtime output. |
| lab/public/bundle/manifest/scene240.json | Bundle manifest regen for updated runtime output. |
| lab/public/bundle/manifest/scene224.json | Bundle manifest regen for updated runtime output. |
| lab/public/bundle/manifest/scene223.json | Bundle manifest regen for updated runtime output. |
| lab/public/bundle/manifest/scene216.json | Bundle manifest regen for updated runtime output. |
| lab/public/bundle/manifest/scene201.json | Bundle manifest regen for updated runtime output. |
| lab/public/bundle/manifest/scene20.json | Bundle manifest regen for updated runtime output. |
| lab/public/bundle/manifest/scene2.json | Bundle manifest regen for updated runtime output. |
| lab/public/bundle/manifest/scene19.json | Bundle manifest regen for updated runtime output. |
| lab/public/bundle/manifest/scene177.json | Bundle manifest regen for updated runtime output. |
| lab/public/bundle/manifest/scene176.json | Bundle manifest regen for updated runtime output. |
| lab/public/bundle/manifest/scene173.json | Bundle manifest regen for updated runtime output. |
| lab/public/bundle/manifest/scene172.json | Bundle manifest regen for updated runtime output. |
| lab/public/bundle/manifest/scene158.json | Bundle manifest regen for updated runtime output. |
| lab/public/bundle/manifest/scene157.json | Bundle manifest regen for updated runtime output. |
| lab/public/bundle/manifest/scene151.json | Bundle manifest regen for updated runtime output. |
| lab/public/bundle/manifest/scene148.json | Bundle manifest regen for updated runtime output. |
| lab/public/bundle/manifest/scene147.json | Bundle manifest regen for updated runtime output. |
| lab/public/bundle/manifest/scene140.json | Bundle manifest regen for updated runtime output. |
| lab/public/bundle/manifest/scene129.json | Bundle manifest regen for updated runtime output. |
| lab/public/bundle/manifest/scene110.json | Bundle manifest regen for updated runtime output. |
| lab/public/bundle/manifest/scene106.json | Bundle manifest regen for updated runtime output. |
| lab/public/bundle/manifest/scene104.json | Bundle manifest regen for updated runtime output. |
| lab/public/bundle/manifest/scene100.json | Bundle manifest regen for updated runtime output. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Bundle Size ChangesIncreases
Sizes rounded to nearest KB. Run |
The API-report breaking-change gate diffs the generated api.md textually and only recognized type-alias union widening and trailing-optional-parameter additions as non-breaking. Widening a function PARAMETER's type to a union superset that still includes the original type (e.g. removeFromScene's 'mesh: Mesh' -> 'entity: Mesh | LightBase | Camera | ...') is also backward-compatible — existing callers still type-check — but was flagged as breaking by default. Add isNonBreakingParameterWidening, mirroring isNonBreakingUnionWidening: a signature whose only change is one or more parameters widening to a union superset (every prior top-level member still accepted) is additive. A genuine type replacement (string -> Color3) or a dropped member stays breaking, and a pure rename without widening stays breaking. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
API ChangesAPI Extractor detected public API changes for No removed public API lines were detected; this appears to be additive. API Extractor diffdiff --git a/home/vsts/work/1/s/test-results/api-report/target/temp/babylon-lite.api.md b/home/vsts/work/1/s/test-results/api-report/current/temp/babylon-lite.api.md
index 873e78f3..10f9b03e 100644
--- a/home/vsts/work/1/s/test-results/api-report/target/temp/babylon-lite.api.md
+++ b/home/vsts/work/1/s/test-results/api-report/current/temp/babylon-lite.api.md
@@ -4160,7 +4160,7 @@ export function removeBillboardSprite(handle: BillboardSpriteHandle): void;
export function removeBillboardSpriteIndex(system: BillboardSpriteSystem, index: number): void;
// @public
-export function removeFromScene(scene: SceneContext, mesh: Mesh): void;
+export function removeFromScene(scene: SceneContext, entity: Mesh | LightBase | Camera | ShadowGenerator | TransformNode | AssetContainer): void;
// @public
export function removeHierarchyInstance(pool: HierarchyInstancePool, index: number): void; |
sebavan
left a comment
There was a problem hiding this comment.
can we do without touching sizes ?
Bundle Size ChangesIncreases
Sizes rounded to nearest KB. Run |
Lab - Static SiteBuild 20260629.9 - merge @ 317e265 |
- Dispose the shadow generator's render task at sg._shadowTaskState._task (not a non-existent sg._task) and null the state so removal is idempotent. - Detach light/camera/scene-node parent links on removal so the push-based world-matrix child registry stops retaining/walking removed nodes (mirrors the mesh.parent = null already done for meshes). - Strengthen the unit test to stub the real _shadowTaskState._task shape, assert the task is disposed exactly once, and assert parent detachment. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
API ChangesAPI Extractor detected public API changes for No removed public API lines were detected; this appears to be additive. API Extractor diffdiff --git a/home/vsts/work/1/s/test-results/api-report/target/temp/babylon-lite.api.md b/home/vsts/work/1/s/test-results/api-report/current/temp/babylon-lite.api.md
index 873e78f3..10f9b03e 100644
--- a/home/vsts/work/1/s/test-results/api-report/target/temp/babylon-lite.api.md
+++ b/home/vsts/work/1/s/test-results/api-report/current/temp/babylon-lite.api.md
@@ -4160,7 +4160,7 @@ export function removeBillboardSprite(handle: BillboardSpriteHandle): void;
export function removeBillboardSpriteIndex(system: BillboardSpriteSystem, index: number): void;
// @public
-export function removeFromScene(scene: SceneContext, mesh: Mesh): void;
+export function removeFromScene(scene: SceneContext, entity: Mesh | LightBase | Camera | ShadowGenerator | TransformNode | AssetContainer): void;
// @public
export function removeHierarchyInstance(pool: HierarchyInstancePool, index: number): void; |
I was thinking about this as well. Right now, we basically have fixed code in scene-core.ts to deal with each entity type for add/remove. That means any time we add a new entity type, the code grows, even if you don't use that entity type. I wonder if a more "centralized dispatch" mechanism could make code smaller, where scene core would "dispatch" the add/remove operation to a callback per entity type, where the entity type itself is responsible for registering the dispatcher. Something like the first time an AssetContainer is constructed, statically register the dispatcher for what operation should be done for add/remove to/from scene. Something like that would not grow the bundle size with more entites added (or more functions like add/remove that need to operation on every entity type), but I'm not sure if the "centralized dispatch" code itself would be bigger than the current per-entity handling. |
Bundle Size ChangesIncreases
Decreases
Sizes rounded to nearest KB. Run |
…emove-from-scene # Conflicts: # lab/public/bundle/manifest/scene104.json # lab/public/bundle/manifest/scene129.json # lab/public/bundle/manifest/scene147.json # lab/public/bundle/manifest/scene148.json # lab/public/bundle/manifest/scene151.json # lab/public/bundle/manifest/scene158.json # lab/public/bundle/manifest/scene176.json # lab/public/bundle/manifest/scene2.json # lab/public/bundle/manifest/scene223.json # lab/public/bundle/manifest/scene240.json # lab/public/bundle/manifest/scene244.json # lab/public/bundle/manifest/scene245.json # lab/public/bundle/manifest/scene246.json # lab/public/bundle/manifest/scene251.json # lab/public/bundle/manifest/scene253.json # lab/public/bundle/manifest/scene254.json # lab/public/bundle/manifest/scene33.json # lab/public/bundle/manifest/scene7.json
Rebuild bundle-scene manifests so committed sizes reflect the merged code. All 205 bundle-size ceilings pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
API ChangesAPI Extractor detected public API changes for No removed public API lines were detected; this appears to be additive. API Extractor diffdiff --git a/home/vsts/work/1/s/test-results/api-report/target/temp/babylon-lite.api.md b/home/vsts/work/1/s/test-results/api-report/current/temp/babylon-lite.api.md
index e51fe551..743280fd 100644
--- a/home/vsts/work/1/s/test-results/api-report/target/temp/babylon-lite.api.md
+++ b/home/vsts/work/1/s/test-results/api-report/current/temp/babylon-lite.api.md
@@ -4194,7 +4194,7 @@ export function removeBillboardSprite(handle: BillboardSpriteHandle): void;
export function removeBillboardSpriteIndex(system: BillboardSpriteSystem, index: number): void;
// @public
-export function removeFromScene(scene: SceneContext, mesh: Mesh): void;
+export function removeFromScene(scene: SceneContext, entity: Mesh | LightBase | Camera | ShadowGenerator | TransformNode | AssetContainer): void;
// @public
export function removeHierarchyInstance(pool: HierarchyInstancePool, index: number): void; |
…flaky scene14/scene9) The merge-time manifest regeneration only introduced sub-1KB measurement jitter (identical content-hashed chunks) plus a flaky scene14 collapse (11MB remote GLB not finishing before readiness) and a scene9 collapse. Since this change alters no bundle composition, restore all per-scene manifests to master's stable values. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Bundle Size ChangesIncreases
Sizes rounded to nearest KB. Run |
… each) scene129 and scene173 are the only measured scenes that call removeFromScene, so the expanded dispatch logic legitimately ships in their (non-content-hashed) entry chunks: scene129 82.9->83.9KB, scene173 62.1->63.1KB. Verified reproducible across rebuilds; chunk composition unchanged (4 chunks each). Other scenes keep master values because the only other added code is a single addToScene assignment (<0.1KB, below manifest rounding). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
API ChangesAPI Extractor detected public API changes for No removed public API lines were detected; this appears to be additive. API Extractor diffdiff --git a/home/vsts/work/1/s/test-results/api-report/target/temp/babylon-lite.api.md b/home/vsts/work/1/s/test-results/api-report/current/temp/babylon-lite.api.md
index e51fe551..743280fd 100644
--- a/home/vsts/work/1/s/test-results/api-report/target/temp/babylon-lite.api.md
+++ b/home/vsts/work/1/s/test-results/api-report/current/temp/babylon-lite.api.md
@@ -4194,7 +4194,7 @@ export function removeBillboardSprite(handle: BillboardSpriteHandle): void;
export function removeBillboardSpriteIndex(system: BillboardSpriteSystem, index: number): void;
// @public
-export function removeFromScene(scene: SceneContext, mesh: Mesh): void;
+export function removeFromScene(scene: SceneContext, entity: Mesh | LightBase | Camera | ShadowGenerator | TransformNode | AssetContainer): void;
// @public
export function removeHierarchyInstance(pool: HierarchyInstancePool, index: number): void; |
1 similar comment
API ChangesAPI Extractor detected public API changes for No removed public API lines were detected; this appears to be additive. API Extractor diffdiff --git a/home/vsts/work/1/s/test-results/api-report/target/temp/babylon-lite.api.md b/home/vsts/work/1/s/test-results/api-report/current/temp/babylon-lite.api.md
index e51fe551..743280fd 100644
--- a/home/vsts/work/1/s/test-results/api-report/target/temp/babylon-lite.api.md
+++ b/home/vsts/work/1/s/test-results/api-report/current/temp/babylon-lite.api.md
@@ -4194,7 +4194,7 @@ export function removeBillboardSprite(handle: BillboardSpriteHandle): void;
export function removeBillboardSpriteIndex(system: BillboardSpriteSystem, index: number): void;
// @public
-export function removeFromScene(scene: SceneContext, mesh: Mesh): void;
+export function removeFromScene(scene: SceneContext, entity: Mesh | LightBase | Camera | ShadowGenerator | TransformNode | AssetContainer): void;
// @public
export function removeHierarchyInstance(pool: HierarchyInstancePool, index: number): void; |
Deduplicate the repeated indexOf/splice bookkeeping into a spliceOut helper, fold shadow-generator disposal into a shared disposeShadowGenerator, and share the detach-parent + unwind-children tail across the light/camera/shadow/node paths instead of repeating it per branch. Behaviour and idempotency are unchanged (unit tests green); scene129 83.9->83.7KB, scene173 63.1->62.9KB. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
API ChangesAPI Extractor detected public API changes for No removed public API lines were detected; this appears to be additive. API Extractor diffdiff --git a/home/vsts/work/1/s/test-results/api-report/target/temp/babylon-lite.api.md b/home/vsts/work/1/s/test-results/api-report/current/temp/babylon-lite.api.md
index e51fe551..743280fd 100644
--- a/home/vsts/work/1/s/test-results/api-report/target/temp/babylon-lite.api.md
+++ b/home/vsts/work/1/s/test-results/api-report/current/temp/babylon-lite.api.md
@@ -4194,7 +4194,7 @@ export function removeBillboardSprite(handle: BillboardSpriteHandle): void;
export function removeBillboardSpriteIndex(system: BillboardSpriteSystem, index: number): void;
// @public
-export function removeFromScene(scene: SceneContext, mesh: Mesh): void;
+export function removeFromScene(scene: SceneContext, entity: Mesh | LightBase | Camera | ShadowGenerator | TransformNode | AssetContainer): void;
// @public
export function removeHierarchyInstance(pool: HierarchyInstancePool, index: number): void; |
Bundle Size ChangesIncreases
Sizes rounded to nearest KB. Run |
API ChangesAPI Extractor detected public API changes for No removed public API lines were detected; this appears to be additive. API Extractor diffdiff --git a/home/vsts/work/1/s/test-results/api-report/target/temp/babylon-lite.api.md b/home/vsts/work/1/s/test-results/api-report/current/temp/babylon-lite.api.md
index e51fe551..743280fd 100644
--- a/home/vsts/work/1/s/test-results/api-report/target/temp/babylon-lite.api.md
+++ b/home/vsts/work/1/s/test-results/api-report/current/temp/babylon-lite.api.md
@@ -4194,7 +4194,7 @@ export function removeBillboardSprite(handle: BillboardSpriteHandle): void;
export function removeBillboardSpriteIndex(system: BillboardSpriteSystem, index: number): void;
// @public
-export function removeFromScene(scene: SceneContext, mesh: Mesh): void;
+export function removeFromScene(scene: SceneContext, entity: Mesh | LightBase | Camera | ShadowGenerator | TransformNode | AssetContainer): void;
// @public
export function removeHierarchyInstance(pool: HierarchyInstancePool, index: number): void; |
CI's fresh rebuild flagged 5 stale per-scene manifests. Investigation shows two distinct causes: - scene223 (+0.8KB raw, 64.0->64.8): REAL growth from this PR. scene223 uses gizmos, which import removeFromScene to clean up their meshes; the symmetric removeFromScene rewrite is larger and drags in disposeMeshGpu/shadow helpers that gizmo-only scenes didn't previously bundle. - scene105/157/227/45 (+0.1KB raw each): pre-existing master drift, NOT caused by this PR. A fresh build from master source already produces these values; the committed manifests were simply never regenerated after earlier library commits (same maintenance gap as the stale demos-manifest). The merge from master surfaced them via the full rebuild. All five values reproduce CI's fresh rebuild exactly. Committing them is required for validate-bundle-manifest to pass. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
4feb625 to
5f704e4
Compare
Bundle Size ChangesIncreases
Sizes rounded to nearest KB. Run |
API ChangesAPI Extractor detected public API changes for No removed public API lines were detected; this appears to be additive. API Extractor diffdiff --git a/home/vsts/work/1/s/test-results/api-report/target/temp/babylon-lite.api.md b/home/vsts/work/1/s/test-results/api-report/current/temp/babylon-lite.api.md
index e51fe551..743280fd 100644
--- a/home/vsts/work/1/s/test-results/api-report/target/temp/babylon-lite.api.md
+++ b/home/vsts/work/1/s/test-results/api-report/current/temp/babylon-lite.api.md
@@ -4194,7 +4194,7 @@ export function removeBillboardSprite(handle: BillboardSpriteHandle): void;
export function removeBillboardSpriteIndex(system: BillboardSpriteSystem, index: number): void;
// @public
-export function removeFromScene(scene: SceneContext, mesh: Mesh): void;
+export function removeFromScene(scene: SceneContext, entity: Mesh | LightBase | Camera | ShadowGenerator | TransformNode | AssetContainer): void;
// @public
export function removeHierarchyInstance(pool: HierarchyInstancePool, index: number): void; |
Bundle Size ChangesIncreases
Sizes rounded to nearest KB. Run |
Lab - Static SiteBuild 20260701.22 - merge @ a208b32 |
…emove-from-scene # Conflicts: # lab/public/bundle/manifest/scene129.json
Master's manifest regen (#) rebaselined all scenes with the smaller master removeFromScene. Re-measuring the 9 scenes that bundle removeFromScene (direct callers + gizmo scenes) on the merged tree shows 3 need the symmetric-rewrite delta reapplied on top of the new baseline: scene129: 83.7 -> 83.8 raw scene47: 36.8 -> 36.9 gzip scene49: 35.0 -> 35.1 gzip Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
API ChangesAPI Extractor detected public API changes for No removed public API lines were detected; this appears to be additive. API Extractor diffdiff --git a/home/vsts/work/1/s/test-results/api-report/target/temp/babylon-lite.api.md b/home/vsts/work/1/s/test-results/api-report/current/temp/babylon-lite.api.md
index b97f112d..244054de 100644
--- a/home/vsts/work/1/s/test-results/api-report/target/temp/babylon-lite.api.md
+++ b/home/vsts/work/1/s/test-results/api-report/current/temp/babylon-lite.api.md
@@ -4194,7 +4194,7 @@ export function removeBillboardSprite(handle: BillboardSpriteHandle): void;
export function removeBillboardSpriteIndex(system: BillboardSpriteSystem, index: number): void;
// @public
-export function removeFromScene(scene: SceneContext, mesh: Mesh): void;
+export function removeFromScene(scene: SceneContext, entity: Mesh | LightBase | Camera | ShadowGenerator | TransformNode | AssetContainer): void;
// @public
export function removeHierarchyInstance(pool: HierarchyInstancePool, index: number): void; |
Bundle Size ChangesIncreases
Sizes rounded to nearest KB. Run |
Lab - Static SiteBuild 20260702.3 - merge @ e6585ab |
Was partly discussed. No, we can't but this is not functionality that will influence time to first frame. this is functionality that can be deferred. the average increase is 0.8kb (0.2 gzipped). I am not too happy about adding size as well. |
|
Should we always defer it ? |
Won't help if we are measuring the way we do. not until we measure time-to-first-frame. I would prefer not to do that. We can cancel this PR if you feel like this is not the right approach |
Are we confirmed an approach like this won't help? |
I researched a bit (without implementing to be honest). it seems like the overhead of chunking the code will be bigger than the actual saving we have per entity. There are 5-6 lines of code per entity (excluding mesh, which is already there). I also can't see how we can implement this cleanly without making some function async, or, as i understand you suggest, asking the user to register what entities they might remove from the scene. It feels to me like a very cumbersome solution and a pain to the users. On the other hand, the functionality doesn't exist yet, so everything is possible. |
Pull request was converted to draft
Addresses the forum request: Make removeFromScene symmetric with addToScene.
Problem
addToSceneacceptsMesh | LightBase | Camera | ShadowGenerator | TransformNode | AssetContainer, butremoveFromSceneonly accepted a singleMesh. So an app could add a light/camera/loaded glTFAssetContainerbut had no public, symmetric way to remove it — and loading then unloading a GLB leaked.Changes
removeFromScenenow accepts the same union asaddToSceneand dispatches by entity shape. The original mesh-removal logic is preserved verbatim as an internal helper.entities, clearsscene.cameraif the container set it, unregistersanimationGroups, and splices the per-container_beforeRenderhook.addToScene(scene, container)pushed an anonymous_beforeRenderclosure that was never removable. It is now stored as an@internal AssetContainer._beforeRenderHookso removal can splice it back out.docs/lite/architecture/01-scene.mdand addedtests/lite/unit/remove-from-scene.test.ts.Validation
pnpm build:libclean; new unit test green.pnpm test:bundle-size— all 205 ceilings pass (+~0.8 KB only on scenes that importremoveFromScene; no ceiling changes).pnpm test:parity— 418 pass. The 2 borderline failures (scene116, scene144 at MAD 0.0700043 vs 0.07) also fail on clean master and touch no removal code — pre-existing and unrelated.Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com