diff --git a/docs/lite/architecture/01-scene.md b/docs/lite/architecture/01-scene.md index 30fae8f0ec..7c92ea0c96 100644 --- a/docs/lite/architecture/01-scene.md +++ b/docs/lite/architecture/01-scene.md @@ -60,7 +60,10 @@ export interface SceneContext { } /** Add an entity or asset container to the scene. Auto-routes by type. */ -export function addToScene(scene: SceneContext, entity: Mesh | LightBase | ShadowGenerator | TransformNode | AssetContainer): void; +export function addToScene(scene: SceneContext, entity: Mesh | LightBase | Camera | ShadowGenerator | TransformNode | AssetContainer): void; + +/** Remove an entity or asset container from the scene, undoing addToScene. Idempotent. */ +export function removeFromScene(scene: SceneContext, entity: Mesh | LightBase | Camera | ShadowGenerator | TransformNode | AssetContainer): void; /** Register a callback to run before each rendered frame. */ export function onBeforeRender(scene: SceneContext, cb: (deltaMs: number) => void): void; diff --git a/lab/public/bundle/manifest/scene1.json b/lab/public/bundle/manifest/scene1.json index f92c25cf71..ba2a616826 100644 --- a/lab/public/bundle/manifest/scene1.json +++ b/lab/public/bundle/manifest/scene1.json @@ -1,6 +1,6 @@ { "rawKB": 88.2, - "gzipKB": 36.9, + "gzipKB": 37, "ignoredRawKB": 0, "runtimeChunks": [ "scene1-background-dds-skybox-BU8dLhLa.js", diff --git a/lab/public/bundle/manifest/scene101.json b/lab/public/bundle/manifest/scene101.json index 40e883134b..75e173b341 100644 --- a/lab/public/bundle/manifest/scene101.json +++ b/lab/public/bundle/manifest/scene101.json @@ -1,5 +1,5 @@ { - "rawKB": 53.6, + "rawKB": 53.7, "gzipKB": 21.4, "ignoredRawKB": 0, "runtimeChunks": [ diff --git a/lab/public/bundle/manifest/scene105.json b/lab/public/bundle/manifest/scene105.json index 1c0eb604ba..28ffd11189 100644 --- a/lab/public/bundle/manifest/scene105.json +++ b/lab/public/bundle/manifest/scene105.json @@ -1,5 +1,5 @@ { - "rawKB": 102.2, + "rawKB": 102.3, "gzipKB": 39.5, "ignoredRawKB": 0, "runtimeChunks": [ diff --git a/lab/public/bundle/manifest/scene111.json b/lab/public/bundle/manifest/scene111.json index d3a437d163..48fc5fb196 100644 --- a/lab/public/bundle/manifest/scene111.json +++ b/lab/public/bundle/manifest/scene111.json @@ -1,6 +1,6 @@ { "rawKB": 133.6, - "gzipKB": 54.3, + "gzipKB": 54.4, "ignoredRawKB": 6.4, "runtimeChunks": [ "scene111-_math-factory-DFVyS8gB.js", diff --git a/lab/public/bundle/manifest/scene112.json b/lab/public/bundle/manifest/scene112.json index b33355d8f6..665d103b95 100644 --- a/lab/public/bundle/manifest/scene112.json +++ b/lab/public/bundle/manifest/scene112.json @@ -1,6 +1,6 @@ { "rawKB": 120.1, - "gzipKB": 48.9, + "gzipKB": 49, "ignoredRawKB": 0, "runtimeChunks": [ "scene112-background-dds-skybox-QAvt-jgW.js", diff --git a/lab/public/bundle/manifest/scene113.json b/lab/public/bundle/manifest/scene113.json index 0d1e5c620b..03ce4b253c 100644 --- a/lab/public/bundle/manifest/scene113.json +++ b/lab/public/bundle/manifest/scene113.json @@ -1,5 +1,5 @@ { - "rawKB": 63.4, + "rawKB": 63.5, "gzipKB": 24.4, "ignoredRawKB": 0, "runtimeChunks": [ diff --git a/lab/public/bundle/manifest/scene114.json b/lab/public/bundle/manifest/scene114.json index 7cb2a358d6..6afc6d5592 100644 --- a/lab/public/bundle/manifest/scene114.json +++ b/lab/public/bundle/manifest/scene114.json @@ -1,5 +1,5 @@ { - "rawKB": 81.2, + "rawKB": 81.3, "gzipKB": 31.7, "ignoredRawKB": 0, "runtimeChunks": [ diff --git a/lab/public/bundle/manifest/scene115.json b/lab/public/bundle/manifest/scene115.json index d5affc6762..670484d383 100644 --- a/lab/public/bundle/manifest/scene115.json +++ b/lab/public/bundle/manifest/scene115.json @@ -1,5 +1,5 @@ { - "rawKB": 126.9, + "rawKB": 127, "gzipKB": 52.4, "ignoredRawKB": 0, "runtimeChunks": [ diff --git a/lab/public/bundle/manifest/scene116.json b/lab/public/bundle/manifest/scene116.json index 1fc11ffe15..697ce10700 100644 --- a/lab/public/bundle/manifest/scene116.json +++ b/lab/public/bundle/manifest/scene116.json @@ -1,5 +1,5 @@ { - "rawKB": 76.3, + "rawKB": 76.4, "gzipKB": 31.3, "ignoredRawKB": 0, "runtimeChunks": [ diff --git a/lab/public/bundle/manifest/scene127.json b/lab/public/bundle/manifest/scene127.json index 4ba46c2314..565f79e8c8 100644 --- a/lab/public/bundle/manifest/scene127.json +++ b/lab/public/bundle/manifest/scene127.json @@ -1,5 +1,5 @@ { - "rawKB": 61.1, + "rawKB": 61.2, "gzipKB": 23.5, "ignoredRawKB": 0, "runtimeChunks": [ diff --git a/lab/public/bundle/manifest/scene129.json b/lab/public/bundle/manifest/scene129.json index dbb0aa51ef..389fdf1c40 100644 --- a/lab/public/bundle/manifest/scene129.json +++ b/lab/public/bundle/manifest/scene129.json @@ -1,6 +1,6 @@ { - "rawKB": 85.1, - "gzipKB": 32.4, + "rawKB": 85.9, + "gzipKB": 32.6, "ignoredRawKB": 0, "runtimeChunks": [ "scene129-gs-picking-pipeline-RhdQBaXc.js", diff --git a/lab/public/bundle/manifest/scene140.json b/lab/public/bundle/manifest/scene140.json index 9a5f952920..669d448218 100644 --- a/lab/public/bundle/manifest/scene140.json +++ b/lab/public/bundle/manifest/scene140.json @@ -1,6 +1,6 @@ { - "rawKB": 89.5, - "gzipKB": 41.5, + "rawKB": 89.6, + "gzipKB": 41.6, "ignoredRawKB": 6.6, "runtimeChunks": [ "scene140-_math-factory-DFVyS8gB.js", diff --git a/lab/public/bundle/manifest/scene146.json b/lab/public/bundle/manifest/scene146.json index 37e760a4b4..b14238ae70 100644 --- a/lab/public/bundle/manifest/scene146.json +++ b/lab/public/bundle/manifest/scene146.json @@ -1,6 +1,6 @@ { "rawKB": 111.1, - "gzipKB": 45.5, + "gzipKB": 45.6, "ignoredRawKB": 0, "runtimeChunks": [ "scene146-alpha-test-fragment-C4ev6MZQ.js", diff --git a/lab/public/bundle/manifest/scene15.json b/lab/public/bundle/manifest/scene15.json index 81cf82fa81..0fcee2e492 100644 --- a/lab/public/bundle/manifest/scene15.json +++ b/lab/public/bundle/manifest/scene15.json @@ -1,6 +1,6 @@ { "rawKB": 47.3, - "gzipKB": 18.8, + "gzipKB": 18.9, "ignoredRawKB": 0, "runtimeChunks": [ "scene15-standard-renderable-cgZZ-rj3.js", diff --git a/lab/public/bundle/manifest/scene150.json b/lab/public/bundle/manifest/scene150.json index 849cc2df3b..350ff871da 100644 --- a/lab/public/bundle/manifest/scene150.json +++ b/lab/public/bundle/manifest/scene150.json @@ -1,5 +1,5 @@ { - "rawKB": 55.1, + "rawKB": 55.2, "gzipKB": 21.5, "ignoredRawKB": 0, "runtimeChunks": [ diff --git a/lab/public/bundle/manifest/scene155.json b/lab/public/bundle/manifest/scene155.json index 9a2bd19de5..4a4d6e2181 100644 --- a/lab/public/bundle/manifest/scene155.json +++ b/lab/public/bundle/manifest/scene155.json @@ -1,5 +1,5 @@ { - "rawKB": 57.7, + "rawKB": 57.8, "gzipKB": 22.4, "ignoredRawKB": 0, "runtimeChunks": [ diff --git a/lab/public/bundle/manifest/scene157.json b/lab/public/bundle/manifest/scene157.json index 0802fce1b7..ccd6ed952b 100644 --- a/lab/public/bundle/manifest/scene157.json +++ b/lab/public/bundle/manifest/scene157.json @@ -1,6 +1,6 @@ { "rawKB": 95.8, - "gzipKB": 39, + "gzipKB": 39.1, "ignoredRawKB": 0, "runtimeChunks": [ "scene157-create-skeleton-CSKHrTlw.js", diff --git a/lab/public/bundle/manifest/scene160.json b/lab/public/bundle/manifest/scene160.json index 3e6f7c6673..c40584b3cf 100644 --- a/lab/public/bundle/manifest/scene160.json +++ b/lab/public/bundle/manifest/scene160.json @@ -1,5 +1,5 @@ { - "rawKB": 41.7, + "rawKB": 41.8, "gzipKB": 16.3, "ignoredRawKB": 0, "runtimeChunks": [ diff --git a/lab/public/bundle/manifest/scene161.json b/lab/public/bundle/manifest/scene161.json index 5114fcc9a8..7ffcd74c33 100644 --- a/lab/public/bundle/manifest/scene161.json +++ b/lab/public/bundle/manifest/scene161.json @@ -1,5 +1,5 @@ { - "rawKB": 40.3, + "rawKB": 40.4, "gzipKB": 15.8, "ignoredRawKB": 0, "runtimeChunks": [ diff --git a/lab/public/bundle/manifest/scene172.json b/lab/public/bundle/manifest/scene172.json index b446d52a4e..043f76c2ad 100644 --- a/lab/public/bundle/manifest/scene172.json +++ b/lab/public/bundle/manifest/scene172.json @@ -1,5 +1,5 @@ { - "rawKB": 59.7, + "rawKB": 59.8, "gzipKB": 23.4, "ignoredRawKB": 1485.2, "runtimeChunks": [ diff --git a/lab/public/bundle/manifest/scene173.json b/lab/public/bundle/manifest/scene173.json index 1b2d42c31a..1d08180458 100644 --- a/lab/public/bundle/manifest/scene173.json +++ b/lab/public/bundle/manifest/scene173.json @@ -1,6 +1,6 @@ { - "rawKB": 62.7, - "gzipKB": 24.3, + "rawKB": 63.4, + "gzipKB": 24.6, "ignoredRawKB": 1485.2, "runtimeChunks": [ "scene173-recast-navigation-CYBQI-zY-Dry_z7x_.js", diff --git a/lab/public/bundle/manifest/scene178.json b/lab/public/bundle/manifest/scene178.json index 173097075c..eb39a8b177 100644 --- a/lab/public/bundle/manifest/scene178.json +++ b/lab/public/bundle/manifest/scene178.json @@ -1,5 +1,5 @@ { - "rawKB": 87.9, + "rawKB": 88, "gzipKB": 35.9, "ignoredRawKB": 0, "runtimeChunks": [ diff --git a/lab/public/bundle/manifest/scene2.json b/lab/public/bundle/manifest/scene2.json index a0fa40dcc7..01cdfdde01 100644 --- a/lab/public/bundle/manifest/scene2.json +++ b/lab/public/bundle/manifest/scene2.json @@ -1,5 +1,5 @@ { - "rawKB": 46.9, + "rawKB": 47, "gzipKB": 18.7, "ignoredRawKB": 0, "runtimeChunks": [ diff --git a/lab/public/bundle/manifest/scene202.json b/lab/public/bundle/manifest/scene202.json index 189ead3b1f..e0520f9394 100644 --- a/lab/public/bundle/manifest/scene202.json +++ b/lab/public/bundle/manifest/scene202.json @@ -1,5 +1,5 @@ { - "rawKB": 49.3, + "rawKB": 49.4, "gzipKB": 20, "ignoredRawKB": 0, "runtimeChunks": [ diff --git a/lab/public/bundle/manifest/scene205.json b/lab/public/bundle/manifest/scene205.json index e166da8ae3..1cddf0e960 100644 --- a/lab/public/bundle/manifest/scene205.json +++ b/lab/public/bundle/manifest/scene205.json @@ -1,5 +1,5 @@ { - "rawKB": 62.1, + "rawKB": 62.2, "gzipKB": 25.5, "ignoredRawKB": 0, "runtimeChunks": [ diff --git a/lab/public/bundle/manifest/scene207.json b/lab/public/bundle/manifest/scene207.json index e496076ea5..6a16239672 100644 --- a/lab/public/bundle/manifest/scene207.json +++ b/lab/public/bundle/manifest/scene207.json @@ -1,6 +1,6 @@ { "rawKB": 59.7, - "gzipKB": 23.9, + "gzipKB": 24, "ignoredRawKB": 0, "runtimeChunks": [ "scene207-_mat4-storage-f64-B_0HYOAx.js", diff --git a/lab/public/bundle/manifest/scene210.json b/lab/public/bundle/manifest/scene210.json index b5d7a1b366..e2ae5cb0f8 100644 --- a/lab/public/bundle/manifest/scene210.json +++ b/lab/public/bundle/manifest/scene210.json @@ -1,5 +1,5 @@ { - "rawKB": 77, + "rawKB": 77.1, "gzipKB": 32, "ignoredRawKB": 0, "runtimeChunks": [ diff --git a/lab/public/bundle/manifest/scene211.json b/lab/public/bundle/manifest/scene211.json index 7e8e3dd8d4..8514cac8c5 100644 --- a/lab/public/bundle/manifest/scene211.json +++ b/lab/public/bundle/manifest/scene211.json @@ -1,5 +1,5 @@ { - "rawKB": 90.9, + "rawKB": 91, "gzipKB": 38.4, "ignoredRawKB": 0, "runtimeChunks": [ diff --git a/lab/public/bundle/manifest/scene212.json b/lab/public/bundle/manifest/scene212.json index ea797dc934..dd517e5476 100644 --- a/lab/public/bundle/manifest/scene212.json +++ b/lab/public/bundle/manifest/scene212.json @@ -1,6 +1,6 @@ { "rawKB": 104.2, - "gzipKB": 41.8, + "gzipKB": 41.9, "ignoredRawKB": 0, "runtimeChunks": [ "scene212-generate-mipmaps-ffZ1zox-.js", diff --git a/lab/public/bundle/manifest/scene213.json b/lab/public/bundle/manifest/scene213.json index 7530b26aab..e53599dd5a 100644 --- a/lab/public/bundle/manifest/scene213.json +++ b/lab/public/bundle/manifest/scene213.json @@ -1,5 +1,5 @@ { - "rawKB": 50, + "rawKB": 50.1, "gzipKB": 19.4, "ignoredRawKB": 0, "runtimeChunks": [ diff --git a/lab/public/bundle/manifest/scene214.json b/lab/public/bundle/manifest/scene214.json index a3b11f71cd..aecd836027 100644 --- a/lab/public/bundle/manifest/scene214.json +++ b/lab/public/bundle/manifest/scene214.json @@ -1,6 +1,6 @@ { "rawKB": 68.5, - "gzipKB": 27.1, + "gzipKB": 27.2, "ignoredRawKB": 0, "runtimeChunks": [ "scene214-material-view-Dua1bl7W.js", diff --git a/lab/public/bundle/manifest/scene216.json b/lab/public/bundle/manifest/scene216.json index dbe2b0117a..43784fb454 100644 --- a/lab/public/bundle/manifest/scene216.json +++ b/lab/public/bundle/manifest/scene216.json @@ -1,6 +1,6 @@ { "rawKB": 54.1, - "gzipKB": 22.2, + "gzipKB": 22.3, "ignoredRawKB": 0, "runtimeChunks": [ "scene216-pbr-fog-wgsl-BuLKZvgv.js", diff --git a/lab/public/bundle/manifest/scene218.json b/lab/public/bundle/manifest/scene218.json index 9eb0c833e3..2d05e9a178 100644 --- a/lab/public/bundle/manifest/scene218.json +++ b/lab/public/bundle/manifest/scene218.json @@ -1,5 +1,5 @@ { - "rawKB": 94.8, + "rawKB": 94.9, "gzipKB": 39.3, "ignoredRawKB": 0, "runtimeChunks": [ diff --git a/lab/public/bundle/manifest/scene22.json b/lab/public/bundle/manifest/scene22.json index 3a0527ed63..618df34dee 100644 --- a/lab/public/bundle/manifest/scene22.json +++ b/lab/public/bundle/manifest/scene22.json @@ -1,6 +1,6 @@ { "rawKB": 97.8, - "gzipKB": 39.5, + "gzipKB": 39.6, "ignoredRawKB": 0, "runtimeChunks": [ "scene22-esm-shadow-view-BpdnPOo5.js", diff --git a/lab/public/bundle/manifest/scene223.json b/lab/public/bundle/manifest/scene223.json index 1c4ca0d158..5347680e78 100644 --- a/lab/public/bundle/manifest/scene223.json +++ b/lab/public/bundle/manifest/scene223.json @@ -1,6 +1,6 @@ { - "rawKB": 64.4, - "gzipKB": 24.4, + "rawKB": 65.2, + "gzipKB": 24.6, "ignoredRawKB": 0, "runtimeChunks": [ "scene223-standard-renderable-W0J5Mh6Q.js", diff --git a/lab/public/bundle/manifest/scene224.json b/lab/public/bundle/manifest/scene224.json index 22d77ee1d7..8bbed76358 100644 --- a/lab/public/bundle/manifest/scene224.json +++ b/lab/public/bundle/manifest/scene224.json @@ -1,6 +1,6 @@ { "rawKB": 82.2, - "gzipKB": 30.4, + "gzipKB": 30.5, "ignoredRawKB": 0, "runtimeChunks": [ "scene224-standard-renderable-I6lj5uUi.js", diff --git a/lab/public/bundle/manifest/scene225.json b/lab/public/bundle/manifest/scene225.json index 4844cfb920..d033151a94 100644 --- a/lab/public/bundle/manifest/scene225.json +++ b/lab/public/bundle/manifest/scene225.json @@ -1,5 +1,5 @@ { - "rawKB": 58, + "rawKB": 58.1, "gzipKB": 23.3, "ignoredRawKB": 0, "runtimeChunks": [ diff --git a/lab/public/bundle/manifest/scene227.json b/lab/public/bundle/manifest/scene227.json index 42c71708ef..616d90e8a9 100644 --- a/lab/public/bundle/manifest/scene227.json +++ b/lab/public/bundle/manifest/scene227.json @@ -1,5 +1,5 @@ { - "rawKB": 48.2, + "rawKB": 48.3, "gzipKB": 19.1, "ignoredRawKB": 0, "runtimeChunks": [ diff --git a/lab/public/bundle/manifest/scene242.json b/lab/public/bundle/manifest/scene242.json index 05ed3435f5..3fa3b34745 100644 --- a/lab/public/bundle/manifest/scene242.json +++ b/lab/public/bundle/manifest/scene242.json @@ -1,5 +1,5 @@ { - "rawKB": 97.5, + "rawKB": 97.6, "gzipKB": 40.4, "ignoredRawKB": 0, "runtimeChunks": [ diff --git a/lab/public/bundle/manifest/scene245.json b/lab/public/bundle/manifest/scene245.json index c045f23863..788688b8b7 100644 --- a/lab/public/bundle/manifest/scene245.json +++ b/lab/public/bundle/manifest/scene245.json @@ -1,5 +1,5 @@ { - "rawKB": 101, + "rawKB": 101.1, "gzipKB": 42.9, "ignoredRawKB": 0, "runtimeChunks": [ diff --git a/lab/public/bundle/manifest/scene25.json b/lab/public/bundle/manifest/scene25.json index 517ec59a8b..af544c56ba 100644 --- a/lab/public/bundle/manifest/scene25.json +++ b/lab/public/bundle/manifest/scene25.json @@ -1,5 +1,5 @@ { - "rawKB": 51.9, + "rawKB": 52, "gzipKB": 20.6, "ignoredRawKB": 0, "runtimeChunks": [ diff --git a/lab/public/bundle/manifest/scene251.json b/lab/public/bundle/manifest/scene251.json index 781c47224f..6f3802978e 100644 --- a/lab/public/bundle/manifest/scene251.json +++ b/lab/public/bundle/manifest/scene251.json @@ -1,5 +1,5 @@ { - "rawKB": 89.9, + "rawKB": 90, "gzipKB": 37.2, "ignoredRawKB": 0, "runtimeChunks": [ diff --git a/lab/public/bundle/manifest/scene3.json b/lab/public/bundle/manifest/scene3.json index 0b530eef81..b6ecb223dc 100644 --- a/lab/public/bundle/manifest/scene3.json +++ b/lab/public/bundle/manifest/scene3.json @@ -1,5 +1,5 @@ { - "rawKB": 53.7, + "rawKB": 53.8, "gzipKB": 21.5, "ignoredRawKB": 0, "runtimeChunks": [ diff --git a/lab/public/bundle/manifest/scene38.json b/lab/public/bundle/manifest/scene38.json index 7112d29461..5f1410504d 100644 --- a/lab/public/bundle/manifest/scene38.json +++ b/lab/public/bundle/manifest/scene38.json @@ -1,5 +1,5 @@ { - "rawKB": 61.1, + "rawKB": 61.2, "gzipKB": 24.1, "ignoredRawKB": 0, "runtimeChunks": [ diff --git a/lab/public/bundle/manifest/scene4.json b/lab/public/bundle/manifest/scene4.json index dcb0b97a10..0e527a3e10 100644 --- a/lab/public/bundle/manifest/scene4.json +++ b/lab/public/bundle/manifest/scene4.json @@ -1,5 +1,5 @@ { - "rawKB": 72.5, + "rawKB": 72.6, "gzipKB": 28.5, "ignoredRawKB": 0, "runtimeChunks": [ diff --git a/lab/public/bundle/manifest/scene40.json b/lab/public/bundle/manifest/scene40.json index a8106ca316..c7f4f75231 100644 --- a/lab/public/bundle/manifest/scene40.json +++ b/lab/public/bundle/manifest/scene40.json @@ -1,5 +1,5 @@ { - "rawKB": 49.6, + "rawKB": 49.7, "gzipKB": 19.9, "ignoredRawKB": 0, "runtimeChunks": [ diff --git a/lab/public/bundle/manifest/scene42.json b/lab/public/bundle/manifest/scene42.json index 34659684b4..8303dd50cd 100644 --- a/lab/public/bundle/manifest/scene42.json +++ b/lab/public/bundle/manifest/scene42.json @@ -1,5 +1,5 @@ { - "rawKB": 51.2, + "rawKB": 51.3, "gzipKB": 20.3, "ignoredRawKB": 0, "runtimeChunks": [ diff --git a/lab/public/bundle/manifest/scene48.json b/lab/public/bundle/manifest/scene48.json index d3655ba065..ce41ea0d50 100644 --- a/lab/public/bundle/manifest/scene48.json +++ b/lab/public/bundle/manifest/scene48.json @@ -1,6 +1,6 @@ { "rawKB": 54.4, - "gzipKB": 21.5, + "gzipKB": 21.6, "ignoredRawKB": 0, "runtimeChunks": [ "scene48-standard-renderable-B82OV5q4.js", diff --git a/lab/public/bundle/manifest/scene6.json b/lab/public/bundle/manifest/scene6.json index 003ca23ebc..baec651d18 100644 --- a/lab/public/bundle/manifest/scene6.json +++ b/lab/public/bundle/manifest/scene6.json @@ -1,5 +1,5 @@ { - "rawKB": 72.5, + "rawKB": 72.6, "gzipKB": 30.3, "ignoredRawKB": 0, "runtimeChunks": [ diff --git a/lab/public/bundle/manifest/scene66.json b/lab/public/bundle/manifest/scene66.json index 101ae1de6a..69d8954b50 100644 --- a/lab/public/bundle/manifest/scene66.json +++ b/lab/public/bundle/manifest/scene66.json @@ -1,6 +1,6 @@ { "rawKB": 89.1, - "gzipKB": 41, + "gzipKB": 41.1, "ignoredRawKB": 5.5, "runtimeChunks": [ "scene66-_math-factory-DFVyS8gB.js", diff --git a/lab/public/bundle/manifest/scene70.json b/lab/public/bundle/manifest/scene70.json index 98251a04f7..90d7f931cd 100644 --- a/lab/public/bundle/manifest/scene70.json +++ b/lab/public/bundle/manifest/scene70.json @@ -1,5 +1,5 @@ { - "rawKB": 90.7, + "rawKB": 90.8, "gzipKB": 36.7, "ignoredRawKB": 14.9, "runtimeChunks": [ diff --git a/lab/public/bundle/manifest/scene72.json b/lab/public/bundle/manifest/scene72.json index 3cf6025c65..156a6ba5e5 100644 --- a/lab/public/bundle/manifest/scene72.json +++ b/lab/public/bundle/manifest/scene72.json @@ -1,6 +1,6 @@ { "rawKB": 108.3, - "gzipKB": 43.9, + "gzipKB": 44, "ignoredRawKB": 3.3, "runtimeChunks": [ "scene72-_math-factory-DFVyS8gB.js", diff --git a/lab/public/bundle/manifest/scene75.json b/lab/public/bundle/manifest/scene75.json index 9f01a71c38..bd37502dee 100644 --- a/lab/public/bundle/manifest/scene75.json +++ b/lab/public/bundle/manifest/scene75.json @@ -1,5 +1,5 @@ { - "rawKB": 50.2, + "rawKB": 50.3, "gzipKB": 20, "ignoredRawKB": 0, "runtimeChunks": [ diff --git a/lab/public/bundle/manifest/scene79.json b/lab/public/bundle/manifest/scene79.json index 626faec75a..3121a63231 100644 --- a/lab/public/bundle/manifest/scene79.json +++ b/lab/public/bundle/manifest/scene79.json @@ -1,5 +1,5 @@ { - "rawKB": 61.3, + "rawKB": 61.4, "gzipKB": 27.6, "ignoredRawKB": 8.1, "runtimeChunks": [ diff --git a/lab/public/bundle/manifest/scene85.json b/lab/public/bundle/manifest/scene85.json index cb34e91d2c..65ed3648f8 100644 --- a/lab/public/bundle/manifest/scene85.json +++ b/lab/public/bundle/manifest/scene85.json @@ -1,6 +1,6 @@ { "rawKB": 60.5, - "gzipKB": 26.3, + "gzipKB": 26.4, "ignoredRawKB": 6.5, "runtimeChunks": [ "scene85-_math-factory-DFVyS8gB.js", diff --git a/lab/public/bundle/manifest/scene88.json b/lab/public/bundle/manifest/scene88.json index 8e9b8a059d..2ec84a621c 100644 --- a/lab/public/bundle/manifest/scene88.json +++ b/lab/public/bundle/manifest/scene88.json @@ -1,5 +1,5 @@ { - "rawKB": 60.6, + "rawKB": 60.7, "gzipKB": 26.9, "ignoredRawKB": 6.4, "runtimeChunks": [ diff --git a/lab/public/bundle/manifest/scene98.json b/lab/public/bundle/manifest/scene98.json index fcebe44360..4283f2b9ba 100644 --- a/lab/public/bundle/manifest/scene98.json +++ b/lab/public/bundle/manifest/scene98.json @@ -1,5 +1,5 @@ { - "rawKB": 60.2, + "rawKB": 60.3, "gzipKB": 24.4, "ignoredRawKB": 0, "runtimeChunks": [ diff --git a/packages/babylon-lite/src/asset-container.ts b/packages/babylon-lite/src/asset-container.ts index 524c027299..a30aa8fd94 100644 --- a/packages/babylon-lite/src/asset-container.ts +++ b/packages/babylon-lite/src/asset-container.ts @@ -31,6 +31,10 @@ export interface AssetContainer { * `enableBoneControl()` was called before loading; otherwise `undefined`. * Drive bones via `getBoneByName()` + the `setBone*` functions. */ skeletons?: Skeleton[]; + /** @internal Per-frame animation tick closure pushed onto `scene._beforeRender` by + * `addToScene(scene, container)`. Stored so `removeFromScene(scene, container)` can + * splice it back out, keeping the two calls symmetric. */ + _beforeRenderHook?: (deltaMs: number) => void; /** Deferred scene-wiring hook contributed by a loader feature that needs the * target `SceneContext` (which the loader itself never sees). `addToScene()` * invokes it once, synchronously, while processing the container. Used by diff --git a/packages/babylon-lite/src/scene/scene-core.ts b/packages/babylon-lite/src/scene/scene-core.ts index e19ed05c95..2ee8fbae63 100644 --- a/packages/babylon-lite/src/scene/scene-core.ts +++ b/packages/babylon-lite/src/scene/scene-core.ts @@ -326,11 +326,13 @@ export function addToScene(scene: SceneContext, entity: Mesh | LightBase | Camer const engine = ctx.surface.engine; const groups = result.animationGroups; ctx.animationGroups.push(...groups); - ctx._beforeRender.push((deltaMs: number) => { + const hook = (deltaMs: number): void => { for (const g of groups) { tickAnimation(g, deltaMs, engine); } - }); + }; + result._beforeRenderHook = hook; + ctx._beforeRender.push(hook); } // Feature-owned scene wiring (e.g. EXT_lights_image_based installs its IBL // environment). Runs synchronously so the environment is registered before diff --git a/packages/babylon-lite/src/scene/scene-remove.ts b/packages/babylon-lite/src/scene/scene-remove.ts index 01eca59ef7..dce54bd727 100644 --- a/packages/babylon-lite/src/scene/scene-remove.ts +++ b/packages/babylon-lite/src/scene/scene-remove.ts @@ -1,13 +1,121 @@ -import type { SceneContext } from "./scene-core.js"; +import type { addToScene, SceneContext } from "./scene-core.js"; import { unregisterMeshScene } from "./mesh-scene-registry.js"; import type { Mesh } from "../mesh/mesh.js"; +import type { LightBase } from "../light/types.js"; +import type { Camera } from "../camera/camera.js"; +import type { ShadowGenerator } from "../shadow/shadow-generator.js"; +import type { TransformNode } from "./transform-node.js"; +import type { SceneNode } from "./scene-node.js"; +import type { AssetContainer } from "../asset-container.js"; import { disposeMeshGpu } from "../mesh/mesh-dispose.js"; import { removeMeshFromTask } from "../frame-graph/render-task.js"; import type { RenderTask } from "../frame-graph/render-task.js"; -/** Remove a mesh from the scene and destroy its GPU resources. +/** Remove an entity from the scene, undoing what `addToScene` did. Accepts the same + * union as {@link addToScene}: a Mesh, light, camera, shadow generator, transform node, + * or a whole AssetContainer. Safe to call more than once (idempotent). + * * Standalone function for tree-shaking — only included when actually used. */ -export function removeFromScene(scene: SceneContext, mesh: Mesh): void { +export function removeFromScene(scene: SceneContext, entity: Mesh | LightBase | Camera | ShadowGenerator | TransformNode | AssetContainer): void { + // AssetContainer — undo addToScene(scene, container) field by field. + if ("entities" in entity) { + const container = entity as AssetContainer; + for (const e of container.entities) { + removeFromScene(scene, e as Mesh | LightBase | TransformNode); + } + if (container.camera && scene.camera === container.camera) { + scene.camera = null; + } + const groups = container.animationGroups; + if (groups?.length) { + for (const g of groups) { + spliceOut(scene.animationGroups, g); + } + } + const hook = container._beforeRenderHook; + if (hook) { + spliceOut(scene._beforeRender, hook); + container._beforeRenderHook = undefined; + } + return; + } + // Mesh — carries GPU geometry + material. Owns the only heavy removal path. + if ("_gpu" in entity && "material" in entity) { + removeMeshFromScene(scene, entity as unknown as Mesh); + removeChildren(scene, entity as unknown as SceneNode); + return; + } + // Non-mesh scene nodes (light, camera, shadow generator, transform node) share a + // detach-parent + unwind-children tail; only the scene-list bookkeeping differs. + if ("lightType" in entity) { + // Light — drop from the scene list and remove its shadow generator with it. + spliceOut(scene.lights, entity as LightBase); + const sg = (entity as LightBase).shadowGenerator; + if (sg) { + disposeShadowGenerator(scene, sg); + } + } else if ("fov" in entity && "nearPlane" in entity) { + // Camera — clear the scene reference if this camera is the active one. + if (scene.camera === (entity as Camera)) { + scene.camera = null; + } + } else if ("_shadowType" in entity && "_light" in entity) { + // Shadow generator removed on its own (not via its light). + disposeShadowGenerator(scene, entity as ShadowGenerator); + } + // TransformNode / any other scene-graph node needs no bookkeeping of its own. + detachParent(entity); + removeChildren(scene, entity as unknown as SceneNode); +} + +// Compile-time symmetry guard (zero runtime cost, not part of the public API): +// removeFromScene must accept exactly the same arguments as addToScene so the two +// stay mirror images. If either signature drifts, `_ParamsEqual` resolves to `false`, +// `_AssertTrue` violates its `extends true` constraint, and the build fails. +// The `declare const` is ambient — it emits no JavaScript and is never exported. +type _ParamsEqual = [A] extends [B] ? ([B] extends [A] ? true : false) : false; +type _AssertTrue = T; +declare const _removeMatchesAdd: _AssertTrue<_ParamsEqual, Parameters>>; + +/** Drop a shadow generator from the scene and dispose its task resources exactly once. + * The disposable render task lives on the lazily-created task state, not the generator + * itself; nulling the state afterwards keeps repeat removals a safe no-op. */ +function disposeShadowGenerator(scene: SceneContext, sg: ShadowGenerator): void { + spliceOut(scene.shadowGenerators, sg); + if (sg._shadowTaskState) { + sg._shadowTaskState._task.dispose(); + sg._shadowTaskState = undefined; + } +} + +/** Remove the first occurrence of `item` from `arr` if present. */ +function spliceOut(arr: T[], item: T): void { + const i = arr.indexOf(item); + if (i >= 0) { + arr.splice(i, 1); + } +} + +/** Clear an entity's `parent` link when it has one, so the world-matrix child registry + * stops retaining/walking a removed node during parent invalidation. */ +function detachParent(node: unknown): void { + if (node && typeof node === "object" && "parent" in node) { + (node as { parent: unknown }).parent = null; + } +} + +function removeChildren(scene: SceneContext, node: SceneNode): void { + const kids = node.children; + if (kids?.length) { + for (const child of [...kids]) { + removeFromScene(scene, child as Mesh); + } + } +} + +/** Remove a mesh from the scene and destroy its GPU resources. + * Internal helper — `removeFromScene` dispatches here for the Mesh case. */ +function removeMeshFromScene(scene: SceneContext, mesh: Mesh): void { const fns = scene._meshDisposables.get(mesh); // Whether this call actually mutated scene state — used to gate the renderable // version bump so a no-op removal (mesh never registered) doesn't needlessly diff --git a/reference/lite/scene164-device-lost-recovery/babylon-ref-golden.png b/reference/lite/scene164-device-lost-recovery/babylon-ref-golden.png index 18259e6972..17f1edeec2 100644 Binary files a/reference/lite/scene164-device-lost-recovery/babylon-ref-golden.png and b/reference/lite/scene164-device-lost-recovery/babylon-ref-golden.png differ diff --git a/reference/lite/scene212-gltf-dispersion/babylon-ref-golden.png b/reference/lite/scene212-gltf-dispersion/babylon-ref-golden.png index 826556f362..a5cfc79c76 100644 Binary files a/reference/lite/scene212-gltf-dispersion/babylon-ref-golden.png and b/reference/lite/scene212-gltf-dispersion/babylon-ref-golden.png differ diff --git a/reference/lite/scene214-cascaded-shadows/babylon-ref-golden.png b/reference/lite/scene214-cascaded-shadows/babylon-ref-golden.png index fbd70b3423..d6ca3233fb 100644 Binary files a/reference/lite/scene214-cascaded-shadows/babylon-ref-golden.png and b/reference/lite/scene214-cascaded-shadows/babylon-ref-golden.png differ diff --git a/reference/lite/scene215-cascaded-shadows-pbr/babylon-ref-golden.png b/reference/lite/scene215-cascaded-shadows-pbr/babylon-ref-golden.png index 541374006e..7ba6d8fbb6 100644 Binary files a/reference/lite/scene215-cascaded-shadows-pbr/babylon-ref-golden.png and b/reference/lite/scene215-cascaded-shadows-pbr/babylon-ref-golden.png differ diff --git a/reference/lite/scene225-geospatial-camera/babylon-ref-golden.png b/reference/lite/scene225-geospatial-camera/babylon-ref-golden.png index e6693dece7..d38e906080 100644 Binary files a/reference/lite/scene225-geospatial-camera/babylon-ref-golden.png and b/reference/lite/scene225-geospatial-camera/babylon-ref-golden.png differ diff --git a/reference/lite/scene73-wheel-nme-viewport/babylon-ref-golden.png b/reference/lite/scene73-wheel-nme-viewport/babylon-ref-golden.png index ff244cec7c..4aed424079 100644 Binary files a/reference/lite/scene73-wheel-nme-viewport/babylon-ref-golden.png and b/reference/lite/scene73-wheel-nme-viewport/babylon-ref-golden.png differ diff --git a/reference/lite/scene79-nme-modes/babylon-ref-golden.png b/reference/lite/scene79-nme-modes/babylon-ref-golden.png index aacb348178..fd762ba242 100644 Binary files a/reference/lite/scene79-nme-modes/babylon-ref-golden.png and b/reference/lite/scene79-nme-modes/babylon-ref-golden.png differ diff --git a/scripts/report-api-changes.ts b/scripts/report-api-changes.ts index d327498618..827d1b1f80 100644 --- a/scripts/report-api-changes.ts +++ b/scripts/report-api-changes.ts @@ -377,78 +377,6 @@ function isNonBreakingOptionalParameterExpansion(removedLine: string, addedLine: return addedSignature.parameters.slice(removedSignature.parameters.length).every(isOptionalParameter); } -/** - * Split a parameter (as rendered in an `.api.md` signature) into its optional/rest markers - * and its type. `url: string` → `{ prefix: "url", optional: false, rest: false, type: "string" }`; - * `options?: MeshOptions` → optional; `...defines: string[]` → rest. Returns `undefined` for a - * parameter with no type annotation (which keeps the change classified as breaking by default). - */ -function parseParameterType(parameter: string): { optional: boolean; rest: boolean; type: string } | undefined { - const rest = parameter.startsWith("..."); - const body = rest ? parameter.slice(3) : parameter; - // Find the first top-level `:` that separates the (identifier) name from the type. Parameter - // names cannot contain `<`/`(`/`[`/`{`, so any such bracket means we've entered the type. - let colon = -1; - for (let i = 0; i < body.length; i += 1) { - const ch = body[i]!; - if (ch === ":") { - colon = i; - break; - } - if (ch === "<" || ch === "(" || ch === "[" || ch === "{") { - break; - } - } - if (colon === -1) { - return undefined; - } - const name = body.slice(0, colon); - const optional = name.trimEnd().endsWith("?"); - return { optional, rest, type: body.slice(colon + 1).trim() }; -} - -/** - * Treat a parameter whose type UNION GAINED members (none removed) as non-breaking — e.g. an - * overload set collapsed into a single implementation signature - * (`f(x: string)` + `f(x: ArrayBuffer | Blob)` → `f(x: string | ArrayBuffer | Blob)`), or a lone - * signature whose parameter type was widened (`f(x: string)` → `f(x: string | Color3)`). Widening - * an accepted parameter type is additive: every prior caller still type-checks. Because - * `breakingApiLines` tests each removed line against ALL added lines, this classifies every removed - * overload as non-breaking against the single widened signature that supersedes it. - * - * Conservative by construction — the name/generics prefix and return-type suffix must be identical, - * the parameter count must match, optional/rest markers must match position-for-position, and every - * removed parameter's union members must be a SUBSET of the corresponding added parameter's members. - * A narrowed or re-spelled parameter type, a changed return type, or a changed arity all fall - * through to breaking. - */ -function isNonBreakingParameterTypeWidening(removedLine: string, addedLine: string): boolean { - const removedSignature = parseCallableSignature(removedLine); - const addedSignature = parseCallableSignature(addedLine); - - if (!removedSignature || !addedSignature) { - return false; - } - - if (removedSignature.prefix !== addedSignature.prefix || removedSignature.suffix !== addedSignature.suffix) { - return false; - } - - if (removedSignature.parameters.length !== addedSignature.parameters.length) { - return false; - } - - return removedSignature.parameters.every((removedParameter, index) => { - const removed = parseParameterType(removedParameter); - const added = parseParameterType(addedSignature.parameters[index]!); - if (!removed || !added || removed.optional !== added.optional || removed.rest !== added.rest) { - return false; - } - const addedMembers = new Set(splitUnionMembers(added.type)); - return splitUnionMembers(removed.type).every((member) => addedMembers.has(member)); - }); -} - const CONST_LITERAL_PATTERN = /^export (?:declare )?const ([A-Za-z_$][\w$]*) = (.+);$/; const CONST_TYPED_PATTERN = /^export (?:declare )?const ([A-Za-z_$][\w$]*): (.+);$/; @@ -540,6 +468,66 @@ function isNonBreakingUnionWidening(removedLine: string, addedLine: string): boo return removedMembers.every((member) => addedMembers.has(member)); } +/** Split a single parameter declaration into its optional flag and type, ignoring the + * parameter name. Returns `undefined` for rest params (`...x: T[]`) and anything that + * doesn't look like `name: Type` — those are left to other classifiers / stay breaking. */ +function splitParameterType(parameter: string): { optional: boolean; type: string } | undefined { + if (parameter.startsWith("...")) { + return undefined; + } + const match = /^[A-Za-z_$][\w$]*(\?)?\s*:\s*([\s\S]+)$/.exec(parameter); + if (!match) { + return undefined; + } + return { optional: match[1] === "?", type: match[2]!.trim() }; +} + +/** + * Treat a function/method whose only change is one or more parameters WIDENING their + * type to a union superset — every previous top-level union member is still accepted — + * as non-breaking. Widening an input parameter is backward-compatible: existing callers + * that passed the old type still type-check (TypeScript parameter bivariance/contravariance). + * + * Example: `removeFromScene(scene: SceneContext, mesh: Mesh)` → + * `removeFromScene(scene: SceneContext, entity: Mesh | LightBase | Camera)` — `Mesh` is + * still in the accepted set, so old calls keep compiling. The parameter NAME may change + * (names are not part of a positional call contract). A genuine type REPLACEMENT + * (`string` → `Color3`, where `string` is not a member of the new type) keeps the old + * member out of the new set and so stays breaking. Mirrors {@link isNonBreakingUnionWidening} + * for type aliases. + */ +function isNonBreakingParameterWidening(removedLine: string, addedLine: string): boolean { + const removedSignature = parseCallableSignature(removedLine); + const addedSignature = parseCallableSignature(addedLine); + if (!removedSignature || !addedSignature) { + return false; + } + if (removedSignature.prefix !== addedSignature.prefix || removedSignature.suffix !== addedSignature.suffix) { + return false; + } + if (removedSignature.parameters.length === 0 || removedSignature.parameters.length !== addedSignature.parameters.length) { + return false; + } + let widenedAtLeastOne = false; + for (let index = 0; index < removedSignature.parameters.length; index += 1) { + const removedParam = splitParameterType(removedSignature.parameters[index]!); + const addedParam = splitParameterType(addedSignature.parameters[index]!); + if (!removedParam || !addedParam || removedParam.optional !== addedParam.optional) { + return false; + } + if (removedParam.type === addedParam.type) { + continue; + } + const addedMembers = new Set(splitUnionMembers(addedParam.type)); + if (!splitUnionMembers(removedParam.type).every((member) => addedMembers.has(member))) { + return false; // a member was dropped/replaced → genuine breaking type change + } + widenedAtLeastOne = true; + } + // Require an actual widening so a pure parameter rename isn't silently reclassified. + return widenedAtLeastOne; +} + /** * The TypedArray / buffer-view types that TypeScript 5.7 made generic over their * backing buffer (`Float32Array` → `Float32Array`). @@ -600,7 +588,7 @@ export function breakingApiLines(diff: string): string[] { !addedLines.some( (addedLine) => isNonBreakingOptionalParameterExpansion(removedLine, addedLine) || - isNonBreakingParameterTypeWidening(removedLine, addedLine) || + isNonBreakingParameterWidening(removedLine, addedLine) || isNonBreakingConstLiteralWidening(removedLine, addedLine) || isNonBreakingUnionWidening(removedLine, addedLine) || isNonBreakingTypedArrayGenericWidening(removedLine, addedLine) diff --git a/tests/lite/unit/remove-from-scene.test.ts b/tests/lite/unit/remove-from-scene.test.ts new file mode 100644 index 0000000000..7840d1ca9b --- /dev/null +++ b/tests/lite/unit/remove-from-scene.test.ts @@ -0,0 +1,86 @@ +import { describe, expect, it } from "vitest"; + +import { removeFromScene } from "../../../packages/babylon-lite/src/scene/scene-remove"; +import { addToScene } from "../../../packages/babylon-lite/src/scene/scene-core"; +import type { SceneContext } from "../../../packages/babylon-lite/src/scene/scene-core"; +import type { AssetContainer } from "../../../packages/babylon-lite/src/asset-container"; + +function fakeScene(): SceneContext { + return { + surface: { engine: {} }, + camera: null, + lights: [], + meshes: [], + animationGroups: [], + shadowGenerators: [], + _beforeRender: [], + _renderables: [], + _materialSwapQueue: [], + _groups: new Map(), + _meshDisposables: new Map(), + _frameGraph: { _tasks: [] }, + } as unknown as SceneContext; +} + +describe("removeFromScene symmetry", () => { + it("removes a light, clears its shadow generator, disposes its task and detaches parent", () => { + const scene = fakeScene(); + let disposed = 0; + // Real ShadowGenerator stores the disposable render task under _shadowTaskState._task. + const sg = { _shadowType: "esm", _light: {}, _shadowTaskState: { _task: { dispose: () => disposed++ } } }; + const light = { lightType: "point", children: [], shadowGenerator: sg, parent: {} }; + scene.lights.push(light as never); + scene.shadowGenerators.push(sg as never); + + removeFromScene(scene, light as never); + expect(scene.lights).toHaveLength(0); + expect(scene.shadowGenerators).toHaveLength(0); + expect(disposed).toBe(1); + expect(light.parent).toBeNull(); + // idempotent + removeFromScene(scene, light as never); + expect(scene.lights).toHaveLength(0); + expect(disposed).toBe(1); + }); + + it("clears the scene camera only when it matches, detaching its parent", () => { + const scene = fakeScene(); + const cam = { fov: 0.8, nearPlane: 0.1, children: [], parent: {} }; + scene.camera = cam as never; + removeFromScene(scene, cam as never); + expect(scene.camera).toBeNull(); + expect(cam.parent).toBeNull(); + const other = { fov: 1, nearPlane: 0.1, children: [] }; + scene.camera = cam as never; + removeFromScene(scene, other as never); + expect(scene.camera).toBe(cam); + }); + + it("undoes addToScene(container): lights, camera, anim groups and beforeRender hook", () => { + const scene = fakeScene(); + const light = { lightType: "point", children: [] }; + const cam = { fov: 0.8, nearPlane: 0.1, children: [] }; + const group = { _stopped: false, _ctrl: { tick: () => {} } }; + const container: AssetContainer = { + entities: [light as never], + camera: cam as never, + animationGroups: [group as never], + }; + + addToScene(scene, container); + expect(scene.lights).toHaveLength(1); + expect(scene.camera).toBe(cam); + expect(scene.animationGroups).toHaveLength(1); + expect(scene._beforeRender).toHaveLength(1); + + removeFromScene(scene, container); + expect(scene.lights).toHaveLength(0); + expect(scene.camera).toBeNull(); + expect(scene.animationGroups).toHaveLength(0); + expect(scene._beforeRender).toHaveLength(0); + expect(container._beforeRenderHook).toBeUndefined(); + // safe to call twice + removeFromScene(scene, container); + expect(scene._beforeRender).toHaveLength(0); + }); +}); diff --git a/tests/lite/unit/report-api-changes.test.ts b/tests/lite/unit/report-api-changes.test.ts index ad1ff9c32e..88dc60f773 100644 --- a/tests/lite/unit/report-api-changes.test.ts +++ b/tests/lite/unit/report-api-changes.test.ts @@ -30,6 +30,33 @@ describe("API report breaking-change classifier", () => { expect(breakingApiLines(diff)).toEqual(["export declare function setColor(color: string): void;"]); }); + it("treats a parameter widening to a union superset as additive", () => { + const diff = apiDiff( + "export declare function removeFromScene(scene: SceneContext, mesh: Mesh): void;", + "export declare function removeFromScene(scene: SceneContext, entity: Mesh | LightBase | Camera): void;" + ); + + expect(breakingApiLines(diff)).toEqual([]); + }); + + it("treats a non-union parameter widening into a union as additive", () => { + const diff = apiDiff("export declare function add(entity: Mesh): void;", "export declare function add(entity: Mesh | LightBase): void;"); + + expect(breakingApiLines(diff)).toEqual([]); + }); + + it("flags a parameter union that drops the original type as breaking", () => { + const diff = apiDiff("export declare function add(entity: Mesh): void;", "export declare function add(entity: LightBase | Camera): void;"); + + expect(breakingApiLines(diff)).toEqual(["export declare function add(entity: Mesh): void;"]); + }); + + it("flags a pure parameter rename (no widening) as breaking", () => { + const diff = apiDiff("export declare function add(mesh: Mesh): void;", "export declare function add(entity: Mesh): void;"); + + expect(breakingApiLines(diff)).toEqual(["export declare function add(mesh: Mesh): void;"]); + }); + it("flags return type changes as breaking", () => { const diff = apiDiff("export declare function createMesh(name: string): Mesh;", "export declare function createMesh(name: string): Promise;");