@forgeax/engine-runtime 0.1.4 → 0.1.6

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.
Files changed (606) hide show
  1. package/README.md +49 -83
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/__tests__/animation-graph-collect-roundtrip.test.d.ts +2 -0
  4. package/dist/__tests__/animation-graph-collect-roundtrip.test.d.ts.map +1 -0
  5. package/dist/__tests__/animation-graph-serialize.unit.test.d.ts +2 -0
  6. package/dist/__tests__/animation-graph-serialize.unit.test.d.ts.map +1 -0
  7. package/dist/__tests__/asset-brand-and-name-retirement.test.d.ts +2 -0
  8. package/dist/__tests__/asset-brand-and-name-retirement.test.d.ts.map +1 -0
  9. package/dist/__tests__/asset-error-exhaustive.test-d.d.ts +11 -0
  10. package/dist/__tests__/asset-error-exhaustive.test-d.d.ts.map +1 -0
  11. package/dist/__tests__/asset-registry-guid-reverse.test.d.ts +2 -0
  12. package/dist/__tests__/asset-registry-guid-reverse.test.d.ts.map +1 -0
  13. package/dist/__tests__/asset-registry-hdr-equirect.test.d.ts +2 -0
  14. package/dist/__tests__/asset-registry-hdr-equirect.test.d.ts.map +1 -0
  15. package/dist/__tests__/asset-registry-mesh-pack-payload.dawn.test.d.ts +2 -0
  16. package/dist/__tests__/asset-registry-mesh-pack-payload.dawn.test.d.ts.map +1 -0
  17. package/dist/__tests__/asset-registry-mounts.test.d.ts +2 -0
  18. package/dist/__tests__/asset-registry-mounts.test.d.ts.map +1 -0
  19. package/dist/__tests__/asset-registry.browser.test.d.ts +2 -0
  20. package/dist/__tests__/asset-registry.browser.test.d.ts.map +1 -0
  21. package/dist/__tests__/asset-registry.dawn.test.d.ts +2 -0
  22. package/dist/__tests__/asset-registry.dawn.test.d.ts.map +1 -0
  23. package/dist/__tests__/asset-registry.listCatalog.test.d.ts +2 -0
  24. package/dist/__tests__/asset-registry.listCatalog.test.d.ts.map +1 -0
  25. package/dist/__tests__/asset-registry.mesh-bin.test.d.ts +2 -0
  26. package/dist/__tests__/asset-registry.mesh-bin.test.d.ts.map +1 -0
  27. package/dist/__tests__/asset-registry.pack-cache.test.d.ts +2 -0
  28. package/dist/__tests__/asset-registry.pack-cache.test.d.ts.map +1 -0
  29. package/dist/__tests__/asset-registry.raw-container-failfast.test.d.ts +2 -0
  30. package/dist/__tests__/asset-registry.raw-container-failfast.test.d.ts.map +1 -0
  31. package/dist/__tests__/asset-registry.recursive.spec.d.ts +2 -0
  32. package/dist/__tests__/asset-registry.recursive.spec.d.ts.map +1 -0
  33. package/dist/__tests__/asset-registry.unit.test.d.ts +2 -0
  34. package/dist/__tests__/asset-registry.unit.test.d.ts.map +1 -0
  35. package/dist/__tests__/asset.unit.test.d.ts +2 -0
  36. package/dist/__tests__/asset.unit.test.d.ts.map +1 -0
  37. package/dist/__tests__/audio-load-by-guid.unit.test.d.ts +2 -0
  38. package/dist/__tests__/audio-load-by-guid.unit.test.d.ts.map +1 -0
  39. package/dist/__tests__/backend-selection.integration.test.d.ts +2 -0
  40. package/dist/__tests__/backend-selection.integration.test.d.ts.map +1 -0
  41. package/dist/__tests__/basis-catalog-dispatch.integration.test.d.ts +2 -0
  42. package/dist/__tests__/basis-catalog-dispatch.integration.test.d.ts.map +1 -0
  43. package/dist/__tests__/builtin-asset-registry.unit.test.d.ts +2 -0
  44. package/dist/__tests__/builtin-asset-registry.unit.test.d.ts.map +1 -0
  45. package/dist/__tests__/collect-scene-asset.test.d.ts +1 -2
  46. package/dist/__tests__/collect-scene-asset.test.d.ts.map +1 -1
  47. package/dist/__tests__/collect-scene-mount-collapse.test.d.ts +2 -0
  48. package/dist/__tests__/collect-scene-mount-collapse.test.d.ts.map +1 -0
  49. package/dist/__tests__/collect-transient-roundtrip.test.d.ts +1 -1
  50. package/dist/__tests__/collect-transient-roundtrip.test.d.ts.map +1 -1
  51. package/dist/__tests__/collect-transient-skip.test.d.ts +1 -2
  52. package/dist/__tests__/collect-transient-skip.test.d.ts.map +1 -1
  53. package/dist/__tests__/compressed-texture-physical-extent.integration.test.d.ts +2 -0
  54. package/dist/__tests__/compressed-texture-physical-extent.integration.test.d.ts.map +1 -0
  55. package/dist/__tests__/dawn/mesh-update-dawn.test.d.ts +2 -0
  56. package/dist/__tests__/dawn/mesh-update-dawn.test.d.ts.map +1 -0
  57. package/dist/__tests__/dawn/upload-texture.dawn.test.d.ts +2 -0
  58. package/dist/__tests__/dawn/upload-texture.dawn.test.d.ts.map +1 -0
  59. package/dist/__tests__/draw-published.d.ts +2 -2
  60. package/dist/__tests__/draw-published.d.ts.map +1 -1
  61. package/dist/__tests__/dynamic-texture-store.unit.test.d.ts +2 -0
  62. package/dist/__tests__/dynamic-texture-store.unit.test.d.ts.map +1 -0
  63. package/dist/__tests__/errors.unit.test.d.ts +2 -0
  64. package/dist/__tests__/errors.unit.test.d.ts.map +1 -0
  65. package/dist/__tests__/extract-skin-mismatch.unit.test.d.ts +2 -0
  66. package/dist/__tests__/extract-skin-mismatch.unit.test.d.ts.map +1 -0
  67. package/dist/__tests__/fetch-binary-decompress.test.d.ts +2 -0
  68. package/dist/__tests__/fetch-binary-decompress.test.d.ts.map +1 -0
  69. package/dist/__tests__/four-path-compression.integration.test.d.ts +2 -0
  70. package/dist/__tests__/four-path-compression.integration.test.d.ts.map +1 -0
  71. package/dist/__tests__/fullscreen-feature-host.dawn.test.d.ts +2 -0
  72. package/dist/__tests__/fullscreen-feature-host.dawn.test.d.ts.map +1 -0
  73. package/dist/__tests__/guid-for-asset.unit.test.d.ts +2 -0
  74. package/dist/__tests__/guid-for-asset.unit.test.d.ts.map +1 -0
  75. package/dist/__tests__/handle-migration.test.d.ts +2 -0
  76. package/dist/__tests__/handle-migration.test.d.ts.map +1 -0
  77. package/dist/__tests__/helpers/register-runtime-components.d.ts.map +1 -1
  78. package/dist/__tests__/host-custom-kind-contract.test-d.d.ts +2 -0
  79. package/dist/__tests__/host-custom-kind-contract.test-d.d.ts.map +1 -0
  80. package/dist/__tests__/host-custom-kind-contract.test.d.ts +2 -0
  81. package/dist/__tests__/host-custom-kind-contract.test.d.ts.map +1 -0
  82. package/dist/__tests__/inspect-name.test.d.ts +2 -0
  83. package/dist/__tests__/inspect-name.test.d.ts.map +1 -0
  84. package/dist/__tests__/instantiate-mount-deferred-wiring.test.d.ts +2 -0
  85. package/dist/__tests__/instantiate-mount-deferred-wiring.test.d.ts.map +1 -0
  86. package/dist/__tests__/loadbyguid-payload.unit.test.d.ts +2 -0
  87. package/dist/__tests__/loadbyguid-payload.unit.test.d.ts.map +1 -0
  88. package/dist/__tests__/loadbyguid-texture-intern.unit.test.d.ts +2 -0
  89. package/dist/__tests__/loadbyguid-texture-intern.unit.test.d.ts.map +1 -0
  90. package/dist/__tests__/loader-extract-graceful.test.d.ts +2 -0
  91. package/dist/__tests__/loader-extract-graceful.test.d.ts.map +1 -0
  92. package/dist/__tests__/loader-name-survival.test.d.ts +2 -0
  93. package/dist/__tests__/loader-name-survival.test.d.ts.map +1 -0
  94. package/dist/__tests__/mesh-bin-contract.test.d.ts +2 -0
  95. package/dist/__tests__/mesh-bin-contract.test.d.ts.map +1 -0
  96. package/dist/__tests__/mesh-bin-skin-roundtrip.unit.test.d.ts +2 -0
  97. package/dist/__tests__/mesh-bin-skin-roundtrip.unit.test.d.ts.map +1 -0
  98. package/dist/__tests__/mesh-bin.test.d.ts +2 -0
  99. package/dist/__tests__/mesh-bin.test.d.ts.map +1 -0
  100. package/dist/__tests__/mesh-loader-skin-contract.unit.test.d.ts +2 -0
  101. package/dist/__tests__/mesh-loader-skin-contract.unit.test.d.ts.map +1 -0
  102. package/dist/__tests__/new-kind-refs-contract.test.d.ts +2 -0
  103. package/dist/__tests__/new-kind-refs-contract.test.d.ts.map +1 -0
  104. package/dist/__tests__/package-promotion.test.d.ts +2 -0
  105. package/dist/__tests__/package-promotion.test.d.ts.map +1 -0
  106. package/dist/__tests__/package-rename.test.d.ts +2 -0
  107. package/dist/__tests__/package-rename.test.d.ts.map +1 -0
  108. package/dist/__tests__/recover-error-exhaustive.test-d.d.ts +1 -1
  109. package/dist/__tests__/recover-error-exhaustive.test-d.d.ts.map +1 -1
  110. package/dist/__tests__/render-error-exhaustive.test-d.d.ts +1 -1
  111. package/dist/__tests__/render-error-exhaustive.test-d.d.ts.map +1 -1
  112. package/dist/__tests__/render-feature-prepared-graphics.fixture.d.ts +10 -0
  113. package/dist/__tests__/render-feature-prepared-graphics.fixture.d.ts.map +1 -0
  114. package/dist/__tests__/render-system-extract.unit.test.d.ts +2 -0
  115. package/dist/__tests__/render-system-extract.unit.test.d.ts.map +1 -0
  116. package/dist/__tests__/renderer-test-utils.d.ts +10 -0
  117. package/dist/__tests__/renderer-test-utils.d.ts.map +1 -0
  118. package/dist/__tests__/renderer.test-d.d.ts +2 -0
  119. package/dist/__tests__/renderer.test-d.d.ts.map +1 -0
  120. package/dist/__tests__/resolve-asset-handle-err.test.d.ts +2 -0
  121. package/dist/__tests__/resolve-asset-handle-err.test.d.ts.map +1 -0
  122. package/dist/__tests__/resolve-asset-handle.unit.test.d.ts +2 -0
  123. package/dist/__tests__/resolve-asset-handle.unit.test.d.ts.map +1 -0
  124. package/dist/__tests__/resolveName-fallback.test.d.ts +2 -0
  125. package/dist/__tests__/resolveName-fallback.test.d.ts.map +1 -0
  126. package/dist/__tests__/resolveName-null-package.test.d.ts +2 -0
  127. package/dist/__tests__/resolveName-null-package.test.d.ts.map +1 -0
  128. package/dist/__tests__/resolveName-unified-entry.test.d.ts +2 -0
  129. package/dist/__tests__/resolveName-unified-entry.test.d.ts.map +1 -0
  130. package/dist/__tests__/resolveName.test.d.ts +2 -0
  131. package/dist/__tests__/resolveName.test.d.ts.map +1 -0
  132. package/dist/__tests__/roots-to-scene-asset.test.d.ts +1 -1
  133. package/dist/__tests__/roots-to-scene-asset.test.d.ts.map +1 -1
  134. package/dist/__tests__/roundtrip-primitive.test.d.ts +2 -0
  135. package/dist/__tests__/roundtrip-primitive.test.d.ts.map +1 -0
  136. package/dist/__tests__/scene-bare-guid-refs-instantiate.test.d.ts +2 -0
  137. package/dist/__tests__/scene-bare-guid-refs-instantiate.test.d.ts.map +1 -0
  138. package/dist/__tests__/scene-instance-authoring.integration.test.d.ts +2 -0
  139. package/dist/__tests__/scene-instance-authoring.integration.test.d.ts.map +1 -0
  140. package/dist/__tests__/scene-mount-fixed-point.test.d.ts +2 -0
  141. package/dist/__tests__/scene-mount-fixed-point.test.d.ts.map +1 -0
  142. package/dist/__tests__/scene-mount-roundtrip.test.d.ts +2 -0
  143. package/dist/__tests__/scene-mount-roundtrip.test.d.ts.map +1 -0
  144. package/dist/__tests__/scene-mount-wrapper-roundtrip.test.d.ts +2 -0
  145. package/dist/__tests__/scene-mount-wrapper-roundtrip.test.d.ts.map +1 -0
  146. package/dist/__tests__/scene-sugar-instantiate.test.d.ts +2 -0
  147. package/dist/__tests__/scene-sugar-instantiate.test.d.ts.map +1 -0
  148. package/dist/__tests__/serialize-mounts-roundtrip.test.d.ts +2 -0
  149. package/dist/__tests__/serialize-mounts-roundtrip.test.d.ts.map +1 -0
  150. package/dist/__tests__/serialize-scene-asset.test.d.ts +1 -1
  151. package/dist/__tests__/serialize-scene-asset.test.d.ts.map +1 -1
  152. package/dist/__tests__/template-game-default-api-contract.test.d.ts +2 -0
  153. package/dist/__tests__/template-game-default-api-contract.test.d.ts.map +1 -0
  154. package/dist/__tests__/tileset-asset-validate.test.d.ts +2 -0
  155. package/dist/__tests__/tileset-asset-validate.test.d.ts.map +1 -0
  156. package/dist/__tests__/transcode-fallback.unit.test.d.ts +2 -0
  157. package/dist/__tests__/transcode-fallback.unit.test.d.ts.map +1 -0
  158. package/dist/__tests__/transient-runtime-behavior.test.d.ts +2 -0
  159. package/dist/__tests__/transient-runtime-behavior.test.d.ts.map +1 -0
  160. package/dist/__tests__/video-loader-consumption.test.d.ts +2 -0
  161. package/dist/__tests__/video-loader-consumption.test.d.ts.map +1 -0
  162. package/dist/__tests__/video-loader-smoke.dawn.test.d.ts +2 -0
  163. package/dist/__tests__/video-loader-smoke.dawn.test.d.ts.map +1 -0
  164. package/dist/backend-selection.d.ts +13 -0
  165. package/dist/backend-selection.d.ts.map +1 -0
  166. package/dist/collect-scene-asset.d.ts +3 -3
  167. package/dist/collect-scene-asset.d.ts.map +1 -1
  168. package/dist/createRenderer.d.ts +14 -2
  169. package/dist/createRenderer.d.ts.map +1 -1
  170. package/dist/errors/environment.d.ts +1 -1
  171. package/dist/errors/environment.d.ts.map +1 -1
  172. package/dist/index.d.ts +19 -81
  173. package/dist/index.d.ts.map +1 -1
  174. package/dist/index.mjs +105 -108
  175. package/dist/index.mjs.map +1 -1
  176. package/dist/renderer-host.d.ts +9 -0
  177. package/dist/renderer-host.d.ts.map +1 -0
  178. package/dist/renderer-host.mjs +62 -0
  179. package/dist/renderer-host.mjs.map +1 -0
  180. package/dist/scene-instances/index.d.ts +2 -0
  181. package/dist/scene-instances/index.d.ts.map +1 -0
  182. package/dist/scene-utils/mount-override-fold.d.ts +1 -2
  183. package/dist/scene-utils/mount-override-fold.d.ts.map +1 -1
  184. package/dist/systems/sprite-animation-tick.d.ts +1 -1
  185. package/dist/systems/sprite-animation-tick.d.ts.map +1 -1
  186. package/dist/systems/transparent-sort.d.ts +1 -2
  187. package/dist/systems/transparent-sort.d.ts.map +1 -1
  188. package/dist/tilemap-register-validate.d.ts.map +1 -1
  189. package/package.json +34 -33
  190. package/src/__tests__/active-camera.unit.test.ts +19 -9
  191. package/src/__tests__/animation-graph-collect-roundtrip.test.ts +213 -0
  192. package/src/__tests__/animation-graph-serialize.unit.test.ts +171 -0
  193. package/src/__tests__/append-injection.test.ts +1 -1
  194. package/src/__tests__/asset-brand-and-name-retirement.test.ts +102 -0
  195. package/src/__tests__/asset-error-exhaustive.test-d.ts +62 -0
  196. package/src/__tests__/asset-registry-guid-reverse.test.ts +575 -0
  197. package/src/__tests__/asset-registry-hdr-equirect.test.ts +78 -0
  198. package/src/__tests__/asset-registry-mesh-pack-payload.dawn.test.ts +232 -0
  199. package/src/__tests__/asset-registry-mounts.test.ts +806 -0
  200. package/src/__tests__/asset-registry.browser.test.ts +81 -0
  201. package/src/__tests__/asset-registry.dawn.test.ts +94 -0
  202. package/src/__tests__/asset-registry.listCatalog.test.ts +170 -0
  203. package/src/__tests__/asset-registry.mesh-bin.test.ts +316 -0
  204. package/src/__tests__/asset-registry.pack-cache.test.ts +969 -0
  205. package/src/__tests__/asset-registry.raw-container-failfast.test.ts +225 -0
  206. package/src/__tests__/asset-registry.recursive.spec.ts +1397 -0
  207. package/src/__tests__/asset-registry.unit.test.ts +1030 -0
  208. package/src/__tests__/asset.unit.test.ts +7107 -0
  209. package/src/__tests__/audio-load-by-guid.unit.test.ts +126 -0
  210. package/src/__tests__/backend-selection.integration.test.ts +11 -0
  211. package/src/__tests__/basis-catalog-dispatch.integration.test.ts +320 -0
  212. package/src/__tests__/bind-group-cache-keying.test.ts +4 -4
  213. package/src/__tests__/block-upload-math.unit.test.ts +2 -2
  214. package/src/__tests__/builtin-asset-registry.unit.test.ts +112 -0
  215. package/src/__tests__/builtin-shader-register-e2e.test.ts +1 -1
  216. package/src/__tests__/cache-false-eviction.test.ts +2 -1
  217. package/src/__tests__/cache-isolation-matrix.test.ts +2 -1
  218. package/src/__tests__/camera-clear-snapshot-equivalence.unit.test.ts +5 -6
  219. package/src/__tests__/clamp-to-last.e2e.browser.test.ts +29 -19
  220. package/src/__tests__/collect-scene-asset.test.ts +49 -52
  221. package/src/__tests__/collect-scene-mount-collapse.test.ts +391 -0
  222. package/src/__tests__/collect-subtree.unit.test.ts +1 -2
  223. package/src/__tests__/collect-transient-children-absent.test.ts +24 -10
  224. package/src/__tests__/collect-transient-roundtrip.test.ts +44 -20
  225. package/src/__tests__/collect-transient-skip.test.ts +24 -13
  226. package/src/__tests__/components.test-d.ts +1 -6
  227. package/src/__tests__/components.unit.test.ts +20 -19
  228. package/src/__tests__/composite-skybox-cross-world.browser.test.ts +59 -27
  229. package/src/__tests__/compressed-texture-physical-extent.integration.test.ts +139 -0
  230. package/src/__tests__/create-renderer-assembly.integration.test.ts +42 -29
  231. package/src/__tests__/createRenderer-dispose.test.ts +35 -402
  232. package/src/__tests__/createRenderer.browser-no-webgpu.test.ts +25 -30
  233. package/src/__tests__/createRenderer.unit.test.ts +1 -1
  234. package/src/__tests__/dawn/hello-sprite-nineslice-section.dawn.test.ts +27 -63
  235. package/src/__tests__/dawn/ibl-precompute-readback.dawn.test.ts +56 -47
  236. package/src/__tests__/dawn/instances-per-instance-pbr.dawn.test.ts +49 -29
  237. package/src/__tests__/dawn/instances-shadow.dawn.test.ts +26 -15
  238. package/src/__tests__/dawn/instances-uniform-fallback.dawn.test.ts +13 -14
  239. package/src/__tests__/dawn/material-alpha.dawn.test.ts +23 -31
  240. package/src/__tests__/dawn/material-cooked-fixture.dawn.test.ts +0 -18
  241. package/src/__tests__/dawn/{mesh-update.dawn.test.ts → mesh-update-dawn.test.ts} +42 -134
  242. package/src/__tests__/dawn/mipmap-srgb-linear.dawn.test.ts +14 -7
  243. package/src/__tests__/dawn/per-entity-material-texture-isolation.dawn.test.ts +16 -28
  244. package/src/__tests__/dawn/sprite-nineslice-mesh-bind.dawn.test.ts +23 -39
  245. package/src/__tests__/dawn/upload-texture.dawn.test.ts +198 -0
  246. package/src/__tests__/dawn/user-handle-mesh-render.dawn.test.ts +12 -14
  247. package/src/__tests__/dawn/user-mesh-upload.dawn.test.ts +27 -22
  248. package/src/__tests__/dawn/zero-manifest-mesh-fail.dawn.test.ts +25 -28
  249. package/src/__tests__/dawn/zero-manifest-ready.dawn.test.ts +10 -11
  250. package/src/__tests__/demo-sortscope-migration.unit.test.ts +5 -5
  251. package/src/__tests__/derive-bgl-integration.test.ts +34 -44
  252. package/src/__tests__/derive-render-data-skin.unit.test.ts +1 -1
  253. package/src/__tests__/draw-published.ts +11 -4
  254. package/src/__tests__/dynamic-texture-store.unit.test.ts +190 -0
  255. package/src/__tests__/equirect-bc6h.integration.test.ts +144 -21
  256. package/src/__tests__/errors.unit.test.ts +1203 -0
  257. package/src/__tests__/extract-frames-graceful-degradation.test.ts +3 -7
  258. package/src/__tests__/extract-frames-merge.test.ts +35 -22
  259. package/src/__tests__/extract-record-no-hardcoded-texture-fields.test.ts +5 -16
  260. package/src/__tests__/extract-skin-mismatch.unit.test.ts +322 -0
  261. package/src/__tests__/fetch-binary-decompress.test.ts +356 -0
  262. package/src/__tests__/four-path-compression.integration.test.ts +395 -0
  263. package/src/__tests__/frame-targets-shadow-regression.unit.test.ts +1 -1
  264. package/src/__tests__/fullscreen-feature-host.dawn.test.ts +80 -0
  265. package/src/__tests__/fullscreen-post-bgl-depth.test.ts +4 -2
  266. package/src/__tests__/fullscreen-post-depth-params-arity.test.ts +3 -3
  267. package/src/__tests__/fullscreen-post-process-pass.dawn.test.ts +39 -23
  268. package/src/__tests__/fullscreen-post-process-pass.unit.test.ts +4 -2
  269. package/src/__tests__/fxaa-pixel-diff.dawn.test.ts +28 -17
  270. package/src/__tests__/fxaa-validation-and-resize.dawn.test.ts +29 -18
  271. package/src/__tests__/fxaa-zero-overhead.dawn.test.ts +16 -12
  272. package/src/__tests__/geometry.unit.test.ts +525 -159
  273. package/src/__tests__/glyph-tilemap-worldid.test.ts +1 -1
  274. package/src/__tests__/gpu-resource-cow-survivor.test.ts +6 -4
  275. package/src/__tests__/guid-for-asset.unit.test.ts +102 -0
  276. package/src/__tests__/handle-migration.test.ts +136 -0
  277. package/src/__tests__/helpers/register-runtime-components.ts +4 -2
  278. package/src/__tests__/helpers/register-scene-components.ts +1 -1
  279. package/src/__tests__/host-custom-kind-contract.test-d.ts +47 -0
  280. package/src/__tests__/host-custom-kind-contract.test.ts +578 -0
  281. package/src/__tests__/inspect-name.test.ts +139 -0
  282. package/src/__tests__/instances.test-d.ts +1 -1
  283. package/src/__tests__/instantiate-mount-deferred-wiring.test.ts +112 -0
  284. package/src/__tests__/light-casters-9-light.browser.test.ts +73 -76
  285. package/src/__tests__/light-gpu-byte-neutral.unit.test.ts +7 -10
  286. package/src/__tests__/light-snapshot.test-d.ts +2 -2
  287. package/src/__tests__/lights-vec-negative.unit.test.ts +1 -1
  288. package/src/__tests__/lights.unit.test.ts +16 -500
  289. package/src/__tests__/loadbyguid-payload.unit.test.ts +117 -0
  290. package/src/__tests__/loadbyguid-texture-intern.unit.test.ts +213 -0
  291. package/src/__tests__/loader-extract-graceful.test.ts +146 -0
  292. package/src/__tests__/loader-name-survival.test.ts +213 -0
  293. package/src/__tests__/m3-vec-negative.unit.test.ts +3 -2
  294. package/src/__tests__/material-texture-uv-scale.unit.test.ts +101 -31
  295. package/src/__tests__/materials.unit.test.ts +10 -27
  296. package/src/__tests__/mesh-bin-contract.test.ts +28 -0
  297. package/src/__tests__/mesh-bin-skin-roundtrip.unit.test.ts +149 -0
  298. package/src/__tests__/mesh-bin.test.ts +38 -0
  299. package/src/__tests__/mesh-loader-skin-contract.unit.test.ts +120 -0
  300. package/src/__tests__/mesh-ssbo-grow.unit.test.ts +3 -3
  301. package/src/__tests__/mip-gate.unit.test.ts +1 -1
  302. package/src/__tests__/msaa-sprite-pixel-diff.dawn.test.ts +29 -68
  303. package/src/__tests__/new-kind-refs-contract.test.ts +132 -0
  304. package/src/__tests__/node-shims.d.ts +4 -6
  305. package/src/__tests__/package-promotion.test.ts +88 -0
  306. package/src/__tests__/package-rename.test.ts +103 -0
  307. package/src/__tests__/pass-selector.unit.test.ts +2 -2
  308. package/src/__tests__/pbr-pipeline.unit.test.ts +14 -21
  309. package/src/__tests__/pipeline-builder-msaa.unit.test.ts +6 -9
  310. package/src/__tests__/pipeline-cache-keying.unit.test.ts +6 -9
  311. package/src/__tests__/pipeline-spec.test.ts +16 -177
  312. package/src/__tests__/pipeline.unit.test.ts +154 -1693
  313. package/src/__tests__/point-light-shadow.dawn.test.ts +33 -42
  314. package/src/__tests__/point-light-shadow.unit.test.ts +19 -60
  315. package/src/__tests__/post-process-error-codes.unit.test.ts +4 -1
  316. package/src/__tests__/post-process-params-component.unit.test.ts +1 -1
  317. package/src/__tests__/post-process-params-extract.unit.test.ts +2 -5
  318. package/src/__tests__/postprocess-camera-consistency.test.ts +4 -7
  319. package/src/__tests__/profiler-assembly.integration.test.ts +1 -1
  320. package/src/__tests__/propagate-transforms.dawn.test.ts +12 -11
  321. package/src/__tests__/record-worldid-isolation.test.ts +7 -29
  322. package/src/__tests__/recover-error-exhaustive.test-d.ts +1 -1
  323. package/src/__tests__/render-error-exhaustive.test-d.ts +81 -71
  324. package/src/__tests__/render-feature-assembly.test-d.ts +20 -9
  325. package/src/__tests__/render-feature-prepared-graphics-docs.unit.test.ts +8 -6
  326. package/src/__tests__/render-feature-prepared-graphics.browser.test.ts +60 -558
  327. package/src/__tests__/render-feature-prepared-graphics.dawn.test.ts +57 -544
  328. package/src/__tests__/render-feature-prepared-graphics.fixture.ts +104 -0
  329. package/src/__tests__/render-feature-prepared-graphics.integration.test.ts +40 -248
  330. package/src/__tests__/render-feature-prepared-graphics.regression.integration.test.ts +83 -340
  331. package/src/__tests__/render-feature-recover.integration.test.ts +9 -49
  332. package/src/__tests__/render-feature-runtime-surface.test-d.ts +17 -20
  333. package/src/__tests__/render-schema-consumer.integration.test.ts +20 -13
  334. package/src/__tests__/render-schema-owner-boundary.unit.test.ts +2 -6
  335. package/src/__tests__/render-system-cross-world-overlay-queue.test.ts +5 -5
  336. package/src/__tests__/render-system-extract-dispatch-ordering.test.ts +16 -31
  337. package/src/__tests__/render-system-extract.test.ts +36 -51
  338. package/src/__tests__/render-system-extract.unit.test.ts +360 -0
  339. package/src/__tests__/render-system-fold-bucket.unit.test.ts +3 -5
  340. package/src/__tests__/render-system-fold-fallback.unit.test.ts +6 -3
  341. package/src/__tests__/render-system-fold-material.unit.test.ts +3 -5
  342. package/src/__tests__/render-system-fold-metrics.unit.test.ts +5 -5
  343. package/src/__tests__/render-system-fold-modegate.unit.test.ts +6 -6
  344. package/src/__tests__/render-system-fold.dawn.test.ts +7 -5
  345. package/src/__tests__/render-system-mega.test.ts +292 -190
  346. package/src/__tests__/render-system-owner-split-contract.test.ts +10 -32
  347. package/src/__tests__/render-system-record-multi-material-textureview.test.ts +59 -45
  348. package/src/__tests__/render-system-record-per-submesh-transparency.test.ts +23 -29
  349. package/src/__tests__/render-system-record.test.ts +398 -235
  350. package/src/__tests__/render-system-record.unit.test.ts +25 -26
  351. package/src/__tests__/render-system-skin-bg.unit.test.ts +8 -12
  352. package/src/__tests__/renderer-consumer.test-d.ts +16 -3
  353. package/src/__tests__/renderer-health.unit.test.ts +26 -916
  354. package/src/__tests__/renderer-lifecycle.integration.test.ts +19 -6
  355. package/src/__tests__/renderer-recover.unit.test.ts +49 -1069
  356. package/src/__tests__/renderer-result.test-d.ts +19 -9
  357. package/src/__tests__/renderer-surface.unit.test.ts +3 -5
  358. package/src/__tests__/renderer-test-utils.ts +56 -0
  359. package/src/__tests__/renderer.test-d.ts +59 -0
  360. package/src/__tests__/resolve-asset-handle-err.test.ts +111 -0
  361. package/src/__tests__/resolve-asset-handle.unit.test.ts +64 -0
  362. package/src/__tests__/resolve-depth-only-view.test.ts +1 -1
  363. package/src/__tests__/resolveName-fallback.test.ts +64 -0
  364. package/src/__tests__/resolveName-null-package.test.ts +38 -0
  365. package/src/__tests__/resolveName-unified-entry.test.ts +65 -0
  366. package/src/__tests__/resolveName.test.ts +64 -0
  367. package/src/__tests__/rhi-null-command-flow.unit.test.ts +43 -261
  368. package/src/__tests__/rhi-null-renderer-lifecycle.unit.test.ts +41 -169
  369. package/src/__tests__/roots-to-scene-asset.test.ts +199 -93
  370. package/src/__tests__/roundtrip-primitive.test.ts +351 -0
  371. package/src/__tests__/scene-bare-guid-refs-instantiate.test.ts +123 -0
  372. package/src/__tests__/scene-equirect-roundtrip.test.ts +103 -16
  373. package/src/__tests__/scene-instance-authoring.integration.test.ts +8 -0
  374. package/src/__tests__/scene-instance-bridge.integration.test.ts +1 -1
  375. package/src/__tests__/scene-instance-component.test.ts +1 -1
  376. package/src/__tests__/scene-mount-fixed-point.test.ts +257 -0
  377. package/src/__tests__/scene-mount-roundtrip.test.ts +987 -0
  378. package/src/__tests__/scene-mount-wrapper-roundtrip.test.ts +202 -0
  379. package/src/__tests__/scene-sugar-instantiate.test.ts +324 -0
  380. package/src/__tests__/serialize-mounts-roundtrip.test.ts +533 -0
  381. package/src/__tests__/serialize-scene-asset.test.ts +219 -22
  382. package/src/__tests__/shadow-caster-empty-schema.test.ts +3 -3
  383. package/src/__tests__/shadow-csm-cascade-loadop.test.ts +37 -17
  384. package/src/__tests__/shadow-csm-component.test.ts +3 -5
  385. package/src/__tests__/shadow-csm-extract.test.ts +2 -100
  386. package/src/__tests__/shadow-csm-orthoz.test.ts +2 -16
  387. package/src/__tests__/shadow-csm-runtime-vary.dawn.test.ts +19 -14
  388. package/src/__tests__/shadow-csm-tile-consistency.test.ts +32 -26
  389. package/src/__tests__/shadow-csm-ubo.dawn.test.ts +8 -49
  390. package/src/__tests__/shadow-csm-ubo.test.ts +3 -25
  391. package/src/__tests__/shadow-csm-viewport.browser.test.ts +1 -1
  392. package/src/__tests__/shadow-fields-observable.dawn.test.ts +67 -56
  393. package/src/__tests__/shadow-m1.dawn.test.ts +8 -9
  394. package/src/__tests__/shadow-mesh-ssbo-order.test.ts +11 -27
  395. package/src/__tests__/skin-errors.unit.test.ts +1 -1
  396. package/src/__tests__/skin-extract-getarrayview-count.unit.test.ts +7 -5
  397. package/src/__tests__/skylight-lazy-projection.test.ts +36 -15
  398. package/src/__tests__/sparse-tag-scene-roundtrip.test.ts +5 -5
  399. package/src/__tests__/sprite-cullmode-flip.dawn.test.ts +47 -64
  400. package/src/__tests__/sprite-lit-bgl-byte-identical.test.ts +12 -11
  401. package/src/__tests__/sprite-lit-extract-branch.test.ts +1 -1
  402. package/src/__tests__/sprite-lit-pipeline-cache-key.test.ts +1 -1
  403. package/src/__tests__/ssao-bgl.test.ts +16 -9
  404. package/src/__tests__/ssao-buffers.test.ts +2 -2
  405. package/src/__tests__/ssao-data.test.ts +2 -2
  406. package/src/__tests__/ssao-errors.test.ts +1 -1
  407. package/src/__tests__/ssao-passes.test.ts +64 -392
  408. package/src/__tests__/ssao-pipelines.test.ts +1 -1
  409. package/src/__tests__/ssao-uniform-layout.test.ts +8 -6
  410. package/src/__tests__/stencil-outline-pixel.dawn.test.ts +25 -17
  411. package/src/__tests__/swap-chain-format.unit.test.ts +5 -7
  412. package/src/__tests__/systems.unit.test.ts +536 -1326
  413. package/src/__tests__/template-game-default-api-contract.test.ts +362 -0
  414. package/src/__tests__/text.unit.test.ts +56 -33
  415. package/src/__tests__/tilemap-childed-despawn.test.ts +8 -14
  416. package/src/__tests__/tilemap-chunk-extract.overhang-bounds.test.ts +8 -12
  417. package/src/__tests__/tilemap-chunk-extract.propagate-order.test.ts +2 -1
  418. package/src/__tests__/tilemap-chunk-extract.test.ts +13 -17
  419. package/src/__tests__/tilemap-dirty-rebuild.test.ts +7 -11
  420. package/src/__tests__/tilemap-material-cache.test.ts +8 -13
  421. package/src/__tests__/tilemap-multi-atlas.test.ts +12 -32
  422. package/src/__tests__/tilemap-owner-boundary.unit.test.ts +5 -2
  423. package/src/__tests__/tilemap-region-pack.test.ts +8 -12
  424. package/src/__tests__/tilemap-spawn-flip-pivot.test.ts +8 -12
  425. package/src/__tests__/tilemap-uv-inset.test.ts +11 -14
  426. package/src/__tests__/tilemap-validate.test.ts +1 -1
  427. package/src/__tests__/tileset-asset-validate.test.ts +638 -0
  428. package/src/__tests__/transcode-fallback.unit.test.ts +207 -0
  429. package/src/__tests__/transform-hierarchy-pixel-diff.dawn.test.ts +36 -28
  430. package/src/__tests__/transient-fields-collect.test.ts +28 -21
  431. package/src/__tests__/transient-runtime-behavior.test.ts +227 -0
  432. package/src/__tests__/transparent-sort-tilemap-sprite-interleave.test.ts +4 -4
  433. package/src/__tests__/variant-selection.unit.test.ts +1 -1
  434. package/src/__tests__/video-extract-bindgroup.dawn.test.ts +89 -136
  435. package/src/__tests__/video-loader-consumption.test.ts +86 -0
  436. package/src/__tests__/video-loader-smoke.dawn.test.ts +60 -0
  437. package/src/__tests__/view-ubo-layout.browser.test.ts +0 -22
  438. package/src/__tests__/world-entity-key.test.ts +1 -1
  439. package/src/__tests__/world-scene-despawn.test.ts +1 -1
  440. package/src/__tests__/world-scene-instantiate.test.ts +2 -36
  441. package/src/__tests__/world-scene-nested.test.ts +1 -1
  442. package/src/__tests__/world-scene-override.test.ts +1 -1
  443. package/src/backend-selection.ts +69 -0
  444. package/src/collect-scene-asset.ts +28 -23
  445. package/src/components/__tests__/camera.test-d.ts +1 -1
  446. package/src/components/__tests__/camera.test.ts +1 -1
  447. package/src/components/__tests__/sprite-animation.test-d.ts +2 -2
  448. package/src/components/__tests__/tile-layer.test-d.ts +1 -1
  449. package/src/components/__tests__/tile-layer.unit.test.ts +2 -2
  450. package/src/createRenderer.ts +24 -36
  451. package/src/errors/environment.ts +1 -1
  452. package/src/index.ts +47 -113
  453. package/src/renderer-host.ts +33 -0
  454. package/src/scene-instances/__tests__/post-spawn-resolve-joints.unit.test.ts +1 -1
  455. package/src/scene-instances/index.ts +4 -0
  456. package/src/scene-utils/mount-override-fold.ts +3 -7
  457. package/src/systems/__tests__/transparent-sort.bench.ts +3 -3
  458. package/src/systems/sprite-animation-tick.ts +1 -1
  459. package/src/systems/transparent-sort.ts +38 -38
  460. package/src/tilemap-register-validate.ts +4 -4
  461. package/dist/__tests__/bind-group-cache-binding.test.d.ts +0 -2
  462. package/dist/__tests__/bind-group-cache-binding.test.d.ts.map +0 -1
  463. package/dist/__tests__/bind-group-cache-frame.test.d.ts +0 -2
  464. package/dist/__tests__/bind-group-cache-frame.test.d.ts.map +0 -1
  465. package/dist/__tests__/dawn/mesh-update.dawn.test.d.ts +0 -2
  466. package/dist/__tests__/dawn/mesh-update.dawn.test.d.ts.map +0 -1
  467. package/dist/__tests__/dispatch-fullscreen-pass-composite-swapchain.dawn.test.d.ts +0 -2
  468. package/dist/__tests__/dispatch-fullscreen-pass-composite-swapchain.dawn.test.d.ts.map +0 -1
  469. package/dist/__tests__/dispatch-fullscreen-pass-custom-reads.dawn.test.d.ts +0 -2
  470. package/dist/__tests__/dispatch-fullscreen-pass-custom-reads.dawn.test.d.ts.map +0 -1
  471. package/dist/__tests__/dispatch-fullscreen-pass-depth-composite.dawn.test.d.ts +0 -2
  472. package/dist/__tests__/dispatch-fullscreen-pass-depth-composite.dawn.test.d.ts.map +0 -1
  473. package/dist/__tests__/dispatch-fullscreen-pass-params.unit.test.d.ts +0 -2
  474. package/dist/__tests__/dispatch-fullscreen-pass-params.unit.test.d.ts.map +0 -1
  475. package/dist/__tests__/fog-producer-manifest.d.ts +0 -131
  476. package/dist/__tests__/fog-producer-manifest.d.ts.map +0 -1
  477. package/dist/__tests__/fog-producer-pixel-probe.d.ts +0 -42
  478. package/dist/__tests__/fog-producer-pixel-probe.d.ts.map +0 -1
  479. package/dist/__tests__/fog-producer-pixel.browser.test.d.ts +0 -2
  480. package/dist/__tests__/fog-producer-pixel.browser.test.d.ts.map +0 -1
  481. package/dist/__tests__/fog-producer-pixel.dawn.test.d.ts +0 -2
  482. package/dist/__tests__/fog-producer-pixel.dawn.test.d.ts.map +0 -1
  483. package/dist/__tests__/fog-producer-real-fixture.d.ts +0 -5
  484. package/dist/__tests__/fog-producer-real-fixture.d.ts.map +0 -1
  485. package/dist/__tests__/fog-producer-vfx-effect.d.ts +0 -3
  486. package/dist/__tests__/fog-producer-vfx-effect.d.ts.map +0 -1
  487. package/dist/__tests__/fog-scene-asset.integration.test.d.ts +0 -2
  488. package/dist/__tests__/fog-scene-asset.integration.test.d.ts.map +0 -1
  489. package/dist/__tests__/gpu-resource-store-cubemap-status.test.d.ts +0 -2
  490. package/dist/__tests__/gpu-resource-store-cubemap-status.test.d.ts.map +0 -1
  491. package/dist/__tests__/gpu-resource.test.d.ts +0 -2
  492. package/dist/__tests__/gpu-resource.test.d.ts.map +0 -1
  493. package/dist/__tests__/hdrp-bgl-slots.browser.test.d.ts +0 -2
  494. package/dist/__tests__/hdrp-bgl-slots.browser.test.d.ts.map +0 -1
  495. package/dist/__tests__/hdrp-buffers.unit.test.d.ts +0 -2
  496. package/dist/__tests__/hdrp-buffers.unit.test.d.ts.map +0 -1
  497. package/dist/__tests__/hdrp-execute-filter.unit.test.d.ts +0 -2
  498. package/dist/__tests__/hdrp-execute-filter.unit.test.d.ts.map +0 -1
  499. package/dist/__tests__/hdrp-gbuffer-attachments.browser.test.d.ts +0 -2
  500. package/dist/__tests__/hdrp-gbuffer-attachments.browser.test.d.ts.map +0 -1
  501. package/dist/__tests__/hdrp-pipeline-asset-type.test-d.d.ts +0 -2
  502. package/dist/__tests__/hdrp-pipeline-asset-type.test-d.d.ts.map +0 -1
  503. package/dist/__tests__/hdrp-ssao-boundary.test.d.ts +0 -2
  504. package/dist/__tests__/hdrp-ssao-boundary.test.d.ts.map +0 -1
  505. package/dist/__tests__/hdrp-ssao-falsification.test.d.ts +0 -2
  506. package/dist/__tests__/hdrp-ssao-falsification.test.d.ts.map +0 -1
  507. package/dist/__tests__/helpers/primitive-world.d.ts +0 -8
  508. package/dist/__tests__/helpers/primitive-world.d.ts.map +0 -1
  509. package/dist/__tests__/helpers/render-asset-port.d.ts +0 -15
  510. package/dist/__tests__/helpers/render-asset-port.d.ts.map +0 -1
  511. package/dist/__tests__/helpers/scene-asset-port.d.ts +0 -9
  512. package/dist/__tests__/helpers/scene-asset-port.d.ts.map +0 -1
  513. package/dist/__tests__/material-derived-coordinates.integration.test.d.ts +0 -2
  514. package/dist/__tests__/material-derived-coordinates.integration.test.d.ts.map +0 -1
  515. package/dist/__tests__/pipeline-layout-selection.test.d.ts +0 -2
  516. package/dist/__tests__/pipeline-layout-selection.test.d.ts.map +0 -1
  517. package/dist/__tests__/post-process-reads-narrowing.test-d.d.ts +0 -2
  518. package/dist/__tests__/post-process-reads-narrowing.test-d.d.ts.map +0 -1
  519. package/dist/__tests__/post-process-register-roundtrip.dawn.test.d.ts +0 -2
  520. package/dist/__tests__/post-process-register-roundtrip.dawn.test.d.ts.map +0 -1
  521. package/dist/__tests__/render-feature-zero-feature.browser.test.d.ts +0 -2
  522. package/dist/__tests__/render-feature-zero-feature.browser.test.d.ts.map +0 -1
  523. package/dist/__tests__/render-pipeline-trivial.dawn.test.d.ts +0 -2
  524. package/dist/__tests__/render-pipeline-trivial.dawn.test.d.ts.map +0 -1
  525. package/dist/__tests__/shadow-csm-caster-membership.test.d.ts +0 -2
  526. package/dist/__tests__/shadow-csm-caster-membership.test.d.ts.map +0 -1
  527. package/dist/__tests__/shadow-csm-pssm.test.d.ts +0 -2
  528. package/dist/__tests__/shadow-csm-pssm.test.d.ts.map +0 -1
  529. package/dist/__tests__/shadow-csm-temporal.browser.test.d.ts +0 -2
  530. package/dist/__tests__/shadow-csm-temporal.browser.test.d.ts.map +0 -1
  531. package/dist/__tests__/shadow-csm-viewport.test.d.ts +0 -2
  532. package/dist/__tests__/shadow-csm-viewport.test.d.ts.map +0 -1
  533. package/dist/__tests__/shadow-m2.dawn.test.d.ts +0 -2
  534. package/dist/__tests__/shadow-m2.dawn.test.d.ts.map +0 -1
  535. package/dist/__tests__/shadow-m3-calibrate-run.dawn.test.d.ts +0 -2
  536. package/dist/__tests__/shadow-m3-calibrate-run.dawn.test.d.ts.map +0 -1
  537. package/dist/__tests__/shadow-m3-calibrate.d.ts +0 -83
  538. package/dist/__tests__/shadow-m3-calibrate.d.ts.map +0 -1
  539. package/dist/__tests__/shadow-m3.dawn.test.d.ts +0 -2
  540. package/dist/__tests__/shadow-m3.dawn.test.d.ts.map +0 -1
  541. package/dist/__tests__/shadow-probe-control-flow.dawn.test.d.ts +0 -2
  542. package/dist/__tests__/shadow-probe-control-flow.dawn.test.d.ts.map +0 -1
  543. package/dist/__tests__/shadow-quality-metrics.dawn.test.d.ts +0 -2
  544. package/dist/__tests__/shadow-quality-metrics.dawn.test.d.ts.map +0 -1
  545. package/dist/__tests__/temporal-off-path.unit.test.d.ts +0 -2
  546. package/dist/__tests__/temporal-off-path.unit.test.d.ts.map +0 -1
  547. package/dist/__tests__/video-cache-regression.unit.test.d.ts +0 -2
  548. package/dist/__tests__/video-cache-regression.unit.test.d.ts.map +0 -1
  549. package/dist/asset-kind.d.ts +0 -4
  550. package/dist/asset-kind.d.ts.map +0 -1
  551. package/dist/errors/sprite-animation.d.ts +0 -3
  552. package/dist/errors/sprite-animation.d.ts.map +0 -1
  553. package/dist/scene-asset-port.d.ts +0 -17
  554. package/dist/scene-asset-port.d.ts.map +0 -1
  555. package/dist/scene-collect-errors.d.ts +0 -22
  556. package/dist/scene-collect-errors.d.ts.map +0 -1
  557. package/dist/shadow-m3-calibrate.d.ts +0 -40
  558. package/dist/shadow-m3-calibrate.d.ts.map +0 -1
  559. package/src/__tests__/bind-group-cache-binding.test.ts +0 -2513
  560. package/src/__tests__/bind-group-cache-frame.test.ts +0 -1511
  561. package/src/__tests__/dispatch-fullscreen-pass-composite-swapchain.dawn.test.ts +0 -204
  562. package/src/__tests__/dispatch-fullscreen-pass-custom-reads.dawn.test.ts +0 -297
  563. package/src/__tests__/dispatch-fullscreen-pass-depth-composite.dawn.test.ts +0 -271
  564. package/src/__tests__/dispatch-fullscreen-pass-params.unit.test.ts +0 -321
  565. package/src/__tests__/fog-producer-manifest.ts +0 -162
  566. package/src/__tests__/fog-producer-pixel-probe.ts +0 -263
  567. package/src/__tests__/fog-producer-pixel.browser.test.ts +0 -26
  568. package/src/__tests__/fog-producer-pixel.dawn.test.ts +0 -55
  569. package/src/__tests__/fog-producer-real-fixture.ts +0 -805
  570. package/src/__tests__/fog-producer-vfx-effect.ts +0 -272
  571. package/src/__tests__/fog-scene-asset.integration.test.ts +0 -123
  572. package/src/__tests__/gpu-resource-store-cubemap-status.test.ts +0 -207
  573. package/src/__tests__/gpu-resource.test.ts +0 -1206
  574. package/src/__tests__/hdrp-bgl-slots.browser.test.ts +0 -50
  575. package/src/__tests__/hdrp-buffers.unit.test.ts +0 -70
  576. package/src/__tests__/hdrp-execute-filter.unit.test.ts +0 -200
  577. package/src/__tests__/hdrp-gbuffer-attachments.browser.test.ts +0 -64
  578. package/src/__tests__/hdrp-pipeline-asset-type.test-d.ts +0 -139
  579. package/src/__tests__/hdrp-ssao-boundary.test.ts +0 -237
  580. package/src/__tests__/hdrp-ssao-falsification.test.ts +0 -289
  581. package/src/__tests__/helpers/primitive-world.ts +0 -91
  582. package/src/__tests__/helpers/render-asset-port.ts +0 -65
  583. package/src/__tests__/helpers/scene-asset-port.ts +0 -23
  584. package/src/__tests__/material-derived-coordinates.integration.test.ts +0 -100
  585. package/src/__tests__/pipeline-layout-selection.test.ts +0 -298
  586. package/src/__tests__/post-process-reads-narrowing.test-d.ts +0 -52
  587. package/src/__tests__/post-process-register-roundtrip.dawn.test.ts +0 -230
  588. package/src/__tests__/render-feature-zero-feature.browser.test.ts +0 -152
  589. package/src/__tests__/render-pipeline-trivial.dawn.test.ts +0 -525
  590. package/src/__tests__/shadow-csm-caster-membership.test.ts +0 -232
  591. package/src/__tests__/shadow-csm-pssm.test.ts +0 -142
  592. package/src/__tests__/shadow-csm-temporal.browser.test.ts +0 -145
  593. package/src/__tests__/shadow-csm-viewport.test.ts +0 -74
  594. package/src/__tests__/shadow-m2.dawn.test.ts +0 -394
  595. package/src/__tests__/shadow-m3-calibrate-run.dawn.test.ts +0 -248
  596. package/src/__tests__/shadow-m3-calibrate.ts +0 -203
  597. package/src/__tests__/shadow-m3.dawn.test.ts +0 -288
  598. package/src/__tests__/shadow-probe-control-flow.dawn.test.ts +0 -264
  599. package/src/__tests__/shadow-quality-metrics.dawn.test.ts +0 -324
  600. package/src/__tests__/temporal-off-path.unit.test.ts +0 -69
  601. package/src/__tests__/video-cache-regression.unit.test.ts +0 -186
  602. package/src/asset-kind.ts +0 -7
  603. package/src/errors/sprite-animation.ts +0 -5
  604. package/src/scene-asset-port.ts +0 -32
  605. package/src/scene-collect-errors.ts +0 -30
  606. package/src/shadow-m3-calibrate.ts +0 -171
@@ -0,0 +1,987 @@
1
+ import * as SceneOwner from '@forgeax/engine-scene';
2
+
3
+ // scene-mount-roundtrip.test.ts — M4 round-trip acceptance tests
4
+ // (feat-20260703-collect-nested-sceneinstance-to-mount-roundtrip).
5
+ //
6
+ // Coverage:
7
+ // m4-t1: single-layer mount round-trip (collect -> serialize -> reload -> collect)
8
+ // m4-t2: double-layer nested mount round-trip
9
+ // m4-t3: fixed-point — post-normalization second collect equals third collect
10
+ //
11
+ // These tests exercise the full AC-04 chain:
12
+ // instantiate -> rootsToSceneAsset -> serializeSceneAssetToPack ->
13
+ // parse back -> catalog -> registry.instantiate (reload) ->
14
+ // rootsToSceneAsset -> structural equivalence
15
+
16
+ import { AnimationPlayer } from '@forgeax/engine-animation';
17
+ import type { Asset } from '@forgeax/engine-assets-runtime';
18
+ import { AssetRegistry } from '@forgeax/engine-assets-runtime';
19
+ import { type EntityHandle, World } from '@forgeax/engine-ecs';
20
+ import { AssetGuid } from '@forgeax/engine-pack/guid';
21
+ import { SceneInstance } from '@forgeax/engine-render';
22
+ import type { Handle, MountOverride, SceneAsset } from '@forgeax/engine-types';
23
+ import { describe, expect, it } from 'vitest';
24
+ import { rootsToSceneAsset, serializeSceneAssetToPack } from '../collect-scene-asset';
25
+ import { makeMockShaderRegistry } from './helpers/mock-shader-registry';
26
+ import { registerSceneComponents } from './helpers/register-scene-components';
27
+
28
+ // ═══════════════════════════════════════════════════════════════════════════════
29
+ // Helpers
30
+ // ═══════════════════════════════════════════════════════════════════════════════
31
+
32
+ function mkReg(): AssetRegistry {
33
+ return new AssetRegistry(makeMockShaderRegistry());
34
+ }
35
+
36
+ function pg(s: string): AssetGuid {
37
+ const r = AssetGuid.parse(s);
38
+ if (!r.ok) throw new Error(`bad GUID: ${s}`);
39
+ return r.value;
40
+ }
41
+
42
+ function cat(reg: AssetRegistry, g: string, p: SceneAsset): void {
43
+ reg.catalog(pg(g), p as Asset);
44
+ }
45
+
46
+ function rs(w: World, a: SceneAsset): Handle<'SceneAsset', 'shared'> {
47
+ registerSceneComponents(w, [AnimationPlayer]);
48
+ return w.allocSharedRef('SceneAsset', a);
49
+ }
50
+
51
+ /** Access parseAssetPayload via type assertion (same pattern as M3 tests). */
52
+ function accessParseScenePayload(reg: AssetRegistry) {
53
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
54
+ const internal = reg as unknown as {
55
+ parseAssetPayload(kind: string, payload: Record<string, unknown>, refs?: string[]): unknown;
56
+ };
57
+ return (
58
+ kind: string,
59
+ payload: Record<string, unknown>,
60
+ refs?: readonly string[] | undefined,
61
+ ): SceneAsset | undefined => {
62
+ const result = internal.parseAssetPayload(kind, payload, refs as string[] | undefined);
63
+ if (result === undefined) return undefined;
64
+ if (
65
+ typeof result === 'object' &&
66
+ result !== null &&
67
+ 'kind' in result &&
68
+ (result as { kind: string }).kind === 'scene'
69
+ ) {
70
+ return result as SceneAsset;
71
+ }
72
+ return undefined;
73
+ };
74
+ }
75
+
76
+ /** Extract payload and refs from serializeSceneAssetToPack result. */
77
+ function unpackSerialized(
78
+ pack: Record<string, unknown>,
79
+ ): { payload: Record<string, unknown>; refs: readonly string[] | undefined } | undefined {
80
+ const assets = pack.assets as readonly Record<string, unknown>[] | undefined;
81
+ if (!assets || assets.length === 0) return undefined;
82
+ const a = assets[0];
83
+ if (!a) return undefined;
84
+ const payload = a.payload as Record<string, unknown> | undefined;
85
+ if (!payload) return undefined;
86
+ return { payload, refs: a.refs as readonly string[] | undefined };
87
+ }
88
+
89
+ /**
90
+ * Deep-structural comparison of two SceneAsset objects.
91
+ * Returns true if mounts (source, memberFirst, memberCount, parent) and
92
+ * entities (component types, field values) are structurally equivalent.
93
+ * localId values are compared as-is (they should be stable across collects).
94
+ */
95
+ function scenesAreStructurallyEquivalent(a: SceneAsset, b: SceneAsset): boolean {
96
+ const am = a.mounts ?? [];
97
+ const bm = b.mounts ?? [];
98
+ if (am.length !== bm.length) return false;
99
+ for (let i = 0; i < am.length; i++) {
100
+ const ma = am[i];
101
+ const mb = bm[i];
102
+ if (!ma || !mb) return false;
103
+ if (ma.source !== mb.source) return false;
104
+ if (ma.memberFirst !== mb.memberFirst) return false;
105
+ if (ma.memberCount !== mb.memberCount) return false;
106
+ if (ma.parent !== mb.parent) return false;
107
+ }
108
+
109
+ const ae = a.entities;
110
+ const be = b.entities;
111
+ if (ae.length !== be.length) return false;
112
+ for (let i = 0; i < ae.length; i++) {
113
+ const ea = ae[i];
114
+ const eb = be[i];
115
+ if (!ea || !eb) return false;
116
+ if (ea.localId !== eb.localId) return false;
117
+
118
+ const compsA = ea.components as Record<string, Record<string, unknown>>;
119
+ const compsB = eb.components as Record<string, Record<string, unknown>>;
120
+ const keysA = Object.keys(compsA).sort();
121
+ const keysB = Object.keys(compsB).sort();
122
+ if (keysA.length !== keysB.length) return false;
123
+ for (let j = 0; j < keysA.length; j++) {
124
+ if (keysA[j] !== keysB[j]) return false;
125
+ }
126
+ for (const key of keysA) {
127
+ const fieldsA = compsA[key];
128
+ const fieldsB = compsB[key];
129
+ if (!fieldsA || !fieldsB) return false;
130
+ const fKeysA = Object.keys(fieldsA).sort();
131
+ const fKeysB = Object.keys(fieldsB).sort();
132
+ if (fKeysA.length !== fKeysB.length) return false;
133
+ for (let j = 0; j < fKeysA.length; j++) {
134
+ if (fKeysA[j] !== fKeysB[j]) return false;
135
+ const va = fieldsA[fKeysA[j] as string];
136
+ const vb = fieldsB[fKeysB[j] as string];
137
+ if (JSON.stringify(va) !== JSON.stringify(vb)) return false;
138
+ }
139
+ }
140
+ }
141
+ return true;
142
+ }
143
+
144
+ /**
145
+ * Compute the totalSlots of a SceneAsset as _instantiateSceneAsset would:
146
+ * countBaseline = entities.length + mounts.length + sum(mounts.memberCount).
147
+ */
148
+ function computeTotalSlots(scene: SceneAsset): number {
149
+ const ms = scene.mounts ?? [];
150
+ const memberSum = ms.reduce((s, m) => s + m.memberCount, 0);
151
+ const countBaseline = scene.entities.length + ms.length + memberSum;
152
+ let maxLocalId = -1;
153
+ for (const e of scene.entities) {
154
+ maxLocalId = Math.max(maxLocalId, e.localId as unknown as number);
155
+ }
156
+ for (const m of ms) {
157
+ maxLocalId = Math.max(maxLocalId, m.localId as unknown as number);
158
+ const last = (m.memberFirst as unknown as number) + m.memberCount - 1;
159
+ maxLocalId = Math.max(maxLocalId, last);
160
+ }
161
+ return Math.max(countBaseline, maxLocalId + 1);
162
+ }
163
+
164
+ const G1 = 'cbe42beb-8975-5096-b3a1-3dda4cb4c077';
165
+ const G2 = 'f6af7007-158f-4d92-9e47-93bf2f213e1f';
166
+ const G3 = 'a1b2c3d4-e5f6-7890-abcd-ef1234567890';
167
+ const G4 = 'b2c3d4e5-f6a7-8901-bcde-f12345678901';
168
+
169
+ // ═══════════════════════════════════════════════════════════════════════════════
170
+ // m4-t1: single-layer mount round-trip
171
+ // ═══════════════════════════════════════════════════════════════════════════════
172
+
173
+ describe('m4-t1 — single-layer mount round-trip', () => {
174
+ it('full round-trip: collect -> serialize -> reload -> collect — mounts and GUIDs preserved', () => {
175
+ const reg = mkReg();
176
+ const w = new World();
177
+
178
+ // Step 1: Build child scene.
179
+ const child: SceneAsset = {
180
+ kind: 'scene',
181
+ entities: [
182
+ { localId: 0 as never, components: { Transform: { pos: [1, 0, 0] } } },
183
+ { localId: 1 as never, components: { Transform: { pos: [2, 0, 0] } } },
184
+ ],
185
+ };
186
+ cat(reg, G1, child);
187
+
188
+ // Step 2: Build parent scene with mount (source = GUID string, post-parse shape).
189
+ const parent: SceneAsset = {
190
+ kind: 'scene',
191
+ entities: [{ localId: 0 as never, components: { Transform: { pos: [10, 0, 0] } } }],
192
+ mounts: [
193
+ {
194
+ localId: 1 as never,
195
+ source: G1,
196
+ memberFirst: 2 as never,
197
+ memberCount: 2,
198
+ },
199
+ ],
200
+ };
201
+ cat(reg, G2, parent);
202
+ const ph = rs(w, parent);
203
+
204
+ // Step 3: Instantiate parent via registry.
205
+ const inst = reg.instantiate(ph, w);
206
+ expect(inst.ok).toBe(true);
207
+ if (!inst.ok) return;
208
+ const root = inst.value;
209
+
210
+ // Verify root exists and has descendants including a child SceneInstance.
211
+ let descendantCount = 0;
212
+ let hasChildSceneInstance = false;
213
+ for (const c of w.iterDescendants(root)) {
214
+ if (c === root) continue;
215
+ descendantCount++;
216
+ if (w.get(c, SceneInstance).ok) hasChildSceneInstance = true;
217
+ }
218
+ expect(descendantCount).toBeGreaterThanOrEqual(2);
219
+ expect(hasChildSceneInstance).toBe(true);
220
+
221
+ // Step 4: First collect (rootsToSceneAsset).
222
+ const collect1 = rootsToSceneAsset(reg, w, [root]);
223
+ expect(collect1.ok).toBe(true);
224
+ if (!collect1.ok) return;
225
+
226
+ // Assert first collect: one mount, source = G1, memberCount = 2.
227
+ expect(collect1.value.mounts).toBeDefined();
228
+ expect(collect1.value.mounts?.length).toBe(1);
229
+ const m0 = collect1.value.mounts?.[0];
230
+ expect(m0).toBeDefined();
231
+ if (!m0) return;
232
+ expect(m0.source).toBe(G1);
233
+ expect(m0.memberCount).toBe(2);
234
+
235
+ // No SceneInstance component rows in owned entities.
236
+ for (const e of collect1.value.entities) {
237
+ expect(
238
+ (e.components as Record<string, Record<string, unknown>>).SceneInstance,
239
+ ).toBeUndefined();
240
+ }
241
+
242
+ // Step 5: Serialize (breakpoint A — mounts must survive).
243
+ const serRes = serializeSceneAssetToPack(collect1.value, w.components.entries(), G3);
244
+ expect(serRes.ok).toBe(true);
245
+ if (!serRes.ok) return;
246
+
247
+ const unpacked = unpackSerialized(serRes.value);
248
+ expect(unpacked).toBeDefined();
249
+ if (!unpacked) return;
250
+ const serMounts = unpacked.payload.mounts as ReadonlyArray<Record<string, unknown>> | undefined;
251
+ expect(serMounts).toBeDefined();
252
+ expect(serMounts?.length).toBe(1);
253
+ // source should be refs index, not GUID string.
254
+ expect(typeof serMounts?.[0]?.source).toBe('number');
255
+
256
+ // Step 6: Parse back (breakpoint B — mounts must survive parse).
257
+ const fn = accessParseScenePayload(reg);
258
+ const parsed = fn('scene', unpacked.payload, unpacked.refs);
259
+ expect(parsed).toBeDefined();
260
+ if (!parsed) return;
261
+ expect(parsed.mounts).toBeDefined();
262
+ expect(parsed.mounts?.length).toBe(1);
263
+ // source should be restored to GUID string.
264
+ expect(parsed.mounts?.[0]?.source).toBe(G1);
265
+
266
+ // Step 7: Reload — catalog + allocSharedRef + registry.instantiate.
267
+ cat(reg, G3, parsed);
268
+ const reloadedHandle = rs(w, parsed);
269
+ const inst2 = reg.instantiate(reloadedHandle, w);
270
+ expect(inst2.ok).toBe(true);
271
+ if (!inst2.ok) return;
272
+ const root2 = inst2.value;
273
+
274
+ // Verify reloaded tree has child SceneInstance.
275
+ let hasChildSi2 = false;
276
+ for (const c of w.iterDescendants(root2)) {
277
+ if (c === root2) continue;
278
+ if (w.get(c, SceneInstance).ok) {
279
+ hasChildSi2 = true;
280
+ break;
281
+ }
282
+ }
283
+ expect(hasChildSi2).toBe(true);
284
+
285
+ // Step 8: Re-collect from reloaded tree.
286
+ const collect2 = rootsToSceneAsset(reg, w, [root2]);
287
+ expect(collect2.ok).toBe(true);
288
+ if (!collect2.ok) return;
289
+
290
+ // Assert reloaded collect: mounts preserved with correct source and memberCount.
291
+ expect(collect2.value.mounts).toBeDefined();
292
+ expect(collect2.value.mounts?.length).toBe(1);
293
+ const m2 = collect2.value.mounts?.[0];
294
+ expect(m2).toBeDefined();
295
+ if (!m2) return;
296
+ expect(m2.source).toBe(G1);
297
+ expect(m2.memberCount).toBe(2);
298
+
299
+ // No SceneInstance component rows in second collect.
300
+ for (const e of collect2.value.entities) {
301
+ expect(
302
+ (e.components as Record<string, Record<string, unknown>>).SceneInstance,
303
+ ).toBeUndefined();
304
+ }
305
+ });
306
+
307
+ it('mount source GUID and memberCount preserved across round-trip', () => {
308
+ const reg = mkReg();
309
+ const w = new World();
310
+
311
+ // Child scene with one entity.
312
+ const child: SceneAsset = {
313
+ kind: 'scene',
314
+ entities: [{ localId: 0 as never, components: { Transform: { pos: [5, 0, 0] } } }],
315
+ };
316
+ cat(reg, G1, child);
317
+
318
+ // Parent with one entity + mount.
319
+ const parent: SceneAsset = {
320
+ kind: 'scene',
321
+ entities: [{ localId: 0 as never, components: { Transform: { pos: [1, 0, 0] } } }],
322
+ mounts: [
323
+ {
324
+ localId: 1 as never,
325
+ source: G1,
326
+ memberFirst: 2 as never,
327
+ memberCount: 1,
328
+ },
329
+ ],
330
+ };
331
+ cat(reg, G2, parent);
332
+ const ph = rs(w, parent);
333
+
334
+ const inst = reg.instantiate(ph, w);
335
+ expect(inst.ok).toBe(true);
336
+ if (!inst.ok) return;
337
+
338
+ const collect1 = rootsToSceneAsset(reg, w, [inst.value]);
339
+ expect(collect1.ok).toBe(true);
340
+ if (!collect1.ok) return;
341
+
342
+ expect(collect1.value.mounts?.[0]?.source).toBe(G1);
343
+ expect(collect1.value.mounts?.[0]?.memberCount).toBe(1);
344
+
345
+ // Serialize -> parse -> catalog -> reload.
346
+ const serRes = serializeSceneAssetToPack(collect1.value, w.components.entries(), G3);
347
+ expect(serRes.ok).toBe(true);
348
+ if (!serRes.ok) return;
349
+
350
+ const unpacked = unpackSerialized(serRes.value);
351
+ expect(unpacked).toBeDefined();
352
+ if (!unpacked) return;
353
+
354
+ const fn = accessParseScenePayload(reg);
355
+ const parsed = fn('scene', unpacked.payload, unpacked.refs);
356
+ expect(parsed).toBeDefined();
357
+ if (!parsed) return;
358
+
359
+ cat(reg, G3, parsed);
360
+ const reloadedHandle = rs(w, parsed);
361
+ const inst2 = reg.instantiate(reloadedHandle, w);
362
+ expect(inst2.ok).toBe(true);
363
+ if (!inst2.ok) return;
364
+
365
+ const collect2 = rootsToSceneAsset(reg, w, [inst2.value]);
366
+ expect(collect2.ok).toBe(true);
367
+ if (!collect2.ok) return;
368
+
369
+ // Mount source GUID and memberCount must survive round-trip.
370
+ expect(collect2.value.mounts?.length).toBe(collect1.value.mounts?.length);
371
+ expect(collect2.value.mounts?.[0]?.source).toBe(collect1.value.mounts?.[0]?.source);
372
+ expect(collect2.value.mounts?.[0]?.memberCount).toBe(collect1.value.mounts?.[0]?.memberCount);
373
+ });
374
+ });
375
+
376
+ // ═══════════════════════════════════════════════════════════════════════════════
377
+ // m4-t2: double-layer nested mount round-trip
378
+ // ═══════════════════════════════════════════════════════════════════════════════
379
+
380
+ describe('m4-t2 — double-layer nested mount round-trip', () => {
381
+ it('A mount B, B mount C — round-trip preserves mount sources and ChildOf hierarchy', () => {
382
+ const reg = mkReg();
383
+ const w = new World();
384
+
385
+ // Grandchild scene C.
386
+ const sceneC: SceneAsset = {
387
+ kind: 'scene',
388
+ entities: [{ localId: 0 as never, components: { Transform: { pos: [1, 0, 0] } } }],
389
+ };
390
+ cat(reg, G1, sceneC);
391
+
392
+ // Child scene B: mounts C.
393
+ const sceneB: SceneAsset = {
394
+ kind: 'scene',
395
+ entities: [{ localId: 0 as never, components: { Transform: { pos: [10, 0, 0] } } }],
396
+ mounts: [
397
+ {
398
+ localId: 1 as never,
399
+ source: G1,
400
+ memberFirst: 2 as never,
401
+ memberCount: 1,
402
+ },
403
+ ],
404
+ };
405
+ cat(reg, G2, sceneB);
406
+
407
+ // Parent scene A: mounts B. B's totalSlots = max(1+1+1, max(0,1,2)+1) = 3.
408
+ const bTotalSlots = computeTotalSlots(sceneB);
409
+ expect(bTotalSlots).toBe(3);
410
+ const sceneA: SceneAsset = {
411
+ kind: 'scene',
412
+ entities: [{ localId: 0 as never, components: { Transform: { pos: [100, 0, 0] } } }],
413
+ mounts: [
414
+ {
415
+ localId: 1 as never,
416
+ source: G2,
417
+ memberFirst: 2 as never,
418
+ memberCount: bTotalSlots,
419
+ },
420
+ ],
421
+ };
422
+ cat(reg, G3, sceneA);
423
+ const ha = rs(w, sceneA);
424
+
425
+ // Instantiate A via registry (exercises recursive mount resolution).
426
+ const inst = reg.instantiate(ha, w);
427
+ expect(inst.ok).toBe(true);
428
+ if (!inst.ok) return;
429
+ const root = inst.value;
430
+
431
+ // Verify nested structure: root should have descendants including
432
+ // SceneInstance-carrying entities.
433
+ let sceneInstanceCount = 0;
434
+ for (const c of w.iterDescendants(root)) {
435
+ if (c === root) continue;
436
+ if (w.get(c, SceneInstance).ok) sceneInstanceCount++;
437
+ }
438
+ // At least 2 SceneInstance entities: B's child root and C's child root.
439
+ expect(sceneInstanceCount).toBeGreaterThanOrEqual(2);
440
+
441
+ // Step 1: Collect from original instantiated tree.
442
+ const collect1 = rootsToSceneAsset(reg, w, [root]);
443
+ expect(collect1.ok).toBe(true);
444
+ if (!collect1.ok) return;
445
+
446
+ // Collect1: root is Form 2, B and C are both non-root anchors.
447
+ // Each produces a mount entry in the output SceneAsset.
448
+ expect(collect1.value.mounts).toBeDefined();
449
+ const mountCount1 = collect1.value.mounts?.length ?? 0;
450
+ expect(mountCount1).toBeGreaterThanOrEqual(2);
451
+
452
+ // Both source GUIDs should be present in mounts.
453
+ const sources1 = collect1.value.mounts?.map((m) => m.source).sort() ?? [];
454
+ expect(sources1).toContain(G1);
455
+ expect(sources1).toContain(G2);
456
+
457
+ // No SceneInstance component rows in owned entities.
458
+ for (const e of collect1.value.entities) {
459
+ expect(
460
+ (e.components as Record<string, Record<string, unknown>>).SceneInstance,
461
+ ).toBeUndefined();
462
+ }
463
+
464
+ // Step 2: Serialize -> parse -> catalog -> reload.
465
+ const serRes = serializeSceneAssetToPack(collect1.value, w.components.entries(), G4);
466
+ expect(serRes.ok).toBe(true);
467
+ if (!serRes.ok) return;
468
+
469
+ const unpacked = unpackSerialized(serRes.value);
470
+ expect(unpacked).toBeDefined();
471
+ if (!unpacked) return;
472
+
473
+ // Verify serialized mounts have source as refs index.
474
+ const serMounts = unpacked.payload.mounts as ReadonlyArray<Record<string, unknown>> | undefined;
475
+ expect(serMounts).toBeDefined();
476
+ expect(serMounts?.length).toBe(mountCount1);
477
+ for (const sm of serMounts ?? []) {
478
+ expect(typeof sm.source).toBe('number');
479
+ }
480
+
481
+ const fn = accessParseScenePayload(reg);
482
+ const parsed = fn('scene', unpacked.payload, unpacked.refs);
483
+ expect(parsed).toBeDefined();
484
+ if (!parsed) return;
485
+
486
+ // Verify parsed mounts have source as GUID strings.
487
+ expect(parsed.mounts?.length).toBe(mountCount1);
488
+ for (const pm of parsed.mounts ?? []) {
489
+ expect(typeof pm.source).toBe('string');
490
+ }
491
+
492
+ // Reload.
493
+ cat(reg, G4, parsed);
494
+ const reloadedHandle = rs(w, parsed);
495
+ const inst2 = reg.instantiate(reloadedHandle, w);
496
+ expect(inst2.ok).toBe(true);
497
+ if (!inst2.ok) return;
498
+
499
+ // Verify nested structure survives reload.
500
+ let siCount2 = 0;
501
+ for (const c of w.iterDescendants(inst2.value)) {
502
+ if (c === inst2.value) continue;
503
+ if (w.get(c, SceneInstance).ok) siCount2++;
504
+ }
505
+ expect(siCount2).toBeGreaterThanOrEqual(2);
506
+
507
+ // Step 3: Re-collect from reloaded tree.
508
+ const collect2 = rootsToSceneAsset(reg, w, [inst2.value]);
509
+ expect(collect2.ok).toBe(true);
510
+ if (!collect2.ok) return;
511
+
512
+ // Both original GUIDs should still appear in mounts.
513
+ const sources2 = collect2.value.mounts?.map((m) => m.source).sort() ?? [];
514
+ expect(sources2).toContain(G1);
515
+ expect(sources2).toContain(G2);
516
+
517
+ for (const e of collect2.value.entities) {
518
+ expect(
519
+ (e.components as Record<string, Record<string, unknown>>).SceneInstance,
520
+ ).toBeUndefined();
521
+ }
522
+ });
523
+
524
+ it('window accounting correct after double-layer round-trip', () => {
525
+ const reg = mkReg();
526
+ const w = new World();
527
+
528
+ // Grandchild C: 2 entities.
529
+ const sceneC: SceneAsset = {
530
+ kind: 'scene',
531
+ entities: [
532
+ { localId: 0 as never, components: { Transform: { pos: [1, 0, 0] } } },
533
+ { localId: 1 as never, components: { Transform: { pos: [2, 0, 0] } } },
534
+ ],
535
+ };
536
+ cat(reg, G1, sceneC);
537
+
538
+ // Child B: mounts C.
539
+ const sceneB: SceneAsset = {
540
+ kind: 'scene',
541
+ entities: [{ localId: 0 as never, components: { Transform: { pos: [10, 0, 0] } } }],
542
+ mounts: [
543
+ {
544
+ localId: 1 as never,
545
+ source: G1,
546
+ memberFirst: 2 as never,
547
+ memberCount: 2,
548
+ },
549
+ ],
550
+ };
551
+ const bSlots = computeTotalSlots(sceneB);
552
+ cat(reg, G2, sceneB);
553
+
554
+ // Parent A: mounts B with correct memberCount.
555
+ const sceneA: SceneAsset = {
556
+ kind: 'scene',
557
+ entities: [{ localId: 0 as never, components: { Transform: { pos: [100, 0, 0] } } }],
558
+ mounts: [
559
+ {
560
+ localId: 1 as never,
561
+ source: G2,
562
+ memberFirst: 2 as never,
563
+ memberCount: bSlots,
564
+ },
565
+ ],
566
+ };
567
+ cat(reg, G3, sceneA);
568
+ const ha = rs(w, sceneA);
569
+
570
+ const inst = reg.instantiate(ha, w);
571
+ expect(inst.ok).toBe(true);
572
+ if (!inst.ok) return;
573
+
574
+ const collect1 = rootsToSceneAsset(reg, w, [inst.value]);
575
+ expect(collect1.ok).toBe(true);
576
+ if (!collect1.ok) return;
577
+
578
+ // Verify window accounting: each mount has memberCount > 0.
579
+ const mounts1 = collect1.value.mounts ?? [];
580
+ for (const m of mounts1) {
581
+ expect(m.memberCount).toBeGreaterThan(0);
582
+ }
583
+ // Windows should not overlap.
584
+ const windows = mounts1
585
+ .map((m) => ({ f: m.memberFirst as number, c: m.memberCount }))
586
+ .sort((a, b) => a.f - b.f);
587
+ for (let i = 0; i < windows.length - 1; i++) {
588
+ const cur = windows[i];
589
+ const next = windows[i + 1];
590
+ if (!cur || !next) continue;
591
+ expect(cur.f + cur.c).toBeLessThanOrEqual(next.f);
592
+ }
593
+
594
+ // Serialize -> reload.
595
+ const serRes = serializeSceneAssetToPack(collect1.value, w.components.entries(), G4);
596
+ expect(serRes.ok).toBe(true);
597
+ if (!serRes.ok) return;
598
+
599
+ const unpacked = unpackSerialized(serRes.value);
600
+ expect(unpacked).toBeDefined();
601
+ if (!unpacked) return;
602
+
603
+ const fn = accessParseScenePayload(reg);
604
+ const parsed = fn('scene', unpacked.payload, unpacked.refs);
605
+ expect(parsed).toBeDefined();
606
+ if (!parsed) return;
607
+
608
+ cat(reg, G4, parsed);
609
+ const reloadedHandle = rs(w, parsed);
610
+ const inst2 = reg.instantiate(reloadedHandle, w);
611
+ expect(inst2.ok).toBe(true);
612
+ if (!inst2.ok) return;
613
+
614
+ const collect2 = rootsToSceneAsset(reg, w, [inst2.value]);
615
+ expect(collect2.ok).toBe(true);
616
+ if (!collect2.ok) return;
617
+
618
+ // Window accounting must be preserved: all mounts have memberCount > 0
619
+ // and windows do not overlap.
620
+ const mounts2 = collect2.value.mounts ?? [];
621
+ for (const m of mounts2) {
622
+ expect(m.memberCount).toBeGreaterThan(0);
623
+ }
624
+ const windows2 = mounts2
625
+ .map((m) => ({ f: m.memberFirst as number, c: m.memberCount }))
626
+ .sort((a, b) => a.f - b.f);
627
+ for (let i = 0; i < windows2.length - 1; i++) {
628
+ const cur = windows2[i];
629
+ const next = windows2[i + 1];
630
+ if (!cur || !next) continue;
631
+ expect(cur.f + cur.c).toBeLessThanOrEqual(next.f);
632
+ }
633
+ });
634
+ });
635
+
636
+ // ═══════════════════════════════════════════════════════════════════════════════
637
+ // m4-t3: fixed-point — post-normalization collect stability
638
+ // ═══════════════════════════════════════════════════════════════════════════════
639
+ //
640
+ // Per D-9, the first reload introduces a one-time structural normalization.
641
+ // The true fixed-point guard: after reload, successive collects from the
642
+ // same live tree produce identical results (collect2 == collect3).
643
+ //
644
+ // If collect2 keeps an entity as owned but collect3 folds it into a mount
645
+ // (or vice versa), the fixed-point is broken and D-9 is violated.
646
+
647
+ describe('m4-t3 — fixed-point: post-normalization collect equality', () => {
648
+ it('single-layer: collect2 == collect3 from same reloaded tree (D-9 fixed-point)', () => {
649
+ const reg = mkReg();
650
+ const w = new World();
651
+
652
+ const child: SceneAsset = {
653
+ kind: 'scene',
654
+ entities: [
655
+ { localId: 0 as never, components: { Transform: { pos: [1, 0, 0] } } },
656
+ { localId: 1 as never, components: { Transform: { pos: [2, 0, 0] } } },
657
+ ],
658
+ };
659
+ cat(reg, G1, child);
660
+
661
+ const parent: SceneAsset = {
662
+ kind: 'scene',
663
+ entities: [{ localId: 0 as never, components: { Transform: { pos: [10, 0, 0] } } }],
664
+ mounts: [
665
+ {
666
+ localId: 1 as never,
667
+ source: G1,
668
+ memberFirst: 2 as never,
669
+ memberCount: 2,
670
+ },
671
+ ],
672
+ };
673
+ cat(reg, G2, parent);
674
+ const ph = rs(w, parent);
675
+
676
+ // Collect 1 (before reload).
677
+ const inst = reg.instantiate(ph, w);
678
+ expect(inst.ok).toBe(true);
679
+ if (!inst.ok) return;
680
+ const collect1 = rootsToSceneAsset(reg, w, [inst.value]);
681
+ expect(collect1.ok).toBe(true);
682
+ if (!collect1.ok) return;
683
+
684
+ // Serialize -> parse -> catalog -> first reload.
685
+ const serRes = serializeSceneAssetToPack(collect1.value, w.components.entries(), G3);
686
+ expect(serRes.ok).toBe(true);
687
+ if (!serRes.ok) return;
688
+
689
+ const unpacked = unpackSerialized(serRes.value);
690
+ expect(unpacked).toBeDefined();
691
+ if (!unpacked) return;
692
+
693
+ const fn = accessParseScenePayload(reg);
694
+ const parsed = fn('scene', unpacked.payload, unpacked.refs);
695
+ expect(parsed).toBeDefined();
696
+ if (!parsed) return;
697
+
698
+ cat(reg, G3, parsed);
699
+ const reloadedHandle = rs(w, parsed);
700
+ const inst2 = reg.instantiate(reloadedHandle, w);
701
+ expect(inst2.ok).toBe(true);
702
+ if (!inst2.ok) return;
703
+
704
+ // Collect 2 (after first reload — post-normalization).
705
+ const collect2 = rootsToSceneAsset(reg, w, [inst2.value]);
706
+ expect(collect2.ok).toBe(true);
707
+ if (!collect2.ok) return;
708
+
709
+ // Collect 3 (from same live tree, no additional reload).
710
+ const collect3 = rootsToSceneAsset(reg, w, [inst2.value]);
711
+ expect(collect3.ok).toBe(true);
712
+ if (!collect3.ok) return;
713
+
714
+ // D-9 fixed-point guard: post-normalization, collect2 == collect3.
715
+ // If this fails, the structure is not stable — something changes
716
+ // between successive collects from the same live tree.
717
+ expect(scenesAreStructurallyEquivalent(collect2.value, collect3.value)).toBe(true);
718
+
719
+ // Mounts must match between collect2 and collect3.
720
+ expect(collect3.value.mounts?.length).toBe(collect2.value.mounts?.length);
721
+ expect(collect3.value.entities.length).toBe(collect2.value.entities.length);
722
+
723
+ const m2 = collect2.value.mounts?.[0];
724
+ const m3 = collect3.value.mounts?.[0];
725
+ expect(m3?.source).toBe(m2?.source);
726
+ expect(m3?.memberCount).toBe(m2?.memberCount);
727
+ });
728
+
729
+ it('double-layer: collect2 == collect3 from same reloaded tree (D-9 fixed-point)', () => {
730
+ const reg = mkReg();
731
+ const w = new World();
732
+
733
+ const sceneC: SceneAsset = {
734
+ kind: 'scene',
735
+ entities: [{ localId: 0 as never, components: { Transform: { pos: [1, 0, 0] } } }],
736
+ };
737
+ cat(reg, G1, sceneC);
738
+
739
+ const sceneB: SceneAsset = {
740
+ kind: 'scene',
741
+ entities: [{ localId: 0 as never, components: { Transform: { pos: [10, 0, 0] } } }],
742
+ mounts: [
743
+ {
744
+ localId: 1 as never,
745
+ source: G1,
746
+ memberFirst: 2 as never,
747
+ memberCount: 1,
748
+ },
749
+ ],
750
+ };
751
+ const bSlots = computeTotalSlots(sceneB);
752
+ cat(reg, G2, sceneB);
753
+
754
+ const sceneA: SceneAsset = {
755
+ kind: 'scene',
756
+ entities: [{ localId: 0 as never, components: { Transform: { pos: [100, 0, 0] } } }],
757
+ mounts: [
758
+ {
759
+ localId: 1 as never,
760
+ source: G2,
761
+ memberFirst: 2 as never,
762
+ memberCount: bSlots,
763
+ },
764
+ ],
765
+ };
766
+ cat(reg, G3, sceneA);
767
+ const ha = rs(w, sceneA);
768
+
769
+ // Collect 1.
770
+ const inst = reg.instantiate(ha, w);
771
+ expect(inst.ok).toBe(true);
772
+ if (!inst.ok) return;
773
+ const collect1 = rootsToSceneAsset(reg, w, [inst.value]);
774
+ expect(collect1.ok).toBe(true);
775
+ if (!collect1.ok) return;
776
+
777
+ // Serialize -> parse -> catalog -> reload.
778
+ const serRes = serializeSceneAssetToPack(collect1.value, w.components.entries(), G4);
779
+ expect(serRes.ok).toBe(true);
780
+ if (!serRes.ok) return;
781
+
782
+ const unpacked = unpackSerialized(serRes.value);
783
+ expect(unpacked).toBeDefined();
784
+ if (!unpacked) return;
785
+
786
+ const fn = accessParseScenePayload(reg);
787
+ const parsed = fn('scene', unpacked.payload, unpacked.refs);
788
+ expect(parsed).toBeDefined();
789
+ if (!parsed) return;
790
+
791
+ cat(reg, G4, parsed);
792
+ const reloadedHandle = rs(w, parsed);
793
+ const inst2 = reg.instantiate(reloadedHandle, w);
794
+ expect(inst2.ok).toBe(true);
795
+ if (!inst2.ok) return;
796
+
797
+ // Collect 2 (post-normalization).
798
+ const collect2 = rootsToSceneAsset(reg, w, [inst2.value]);
799
+ expect(collect2.ok).toBe(true);
800
+ if (!collect2.ok) return;
801
+
802
+ // Collect 3 (same live tree).
803
+ const collect3 = rootsToSceneAsset(reg, w, [inst2.value]);
804
+ expect(collect3.ok).toBe(true);
805
+ if (!collect3.ok) return;
806
+
807
+ // Fixed-point: collect2 == collect3.
808
+ expect(scenesAreStructurallyEquivalent(collect2.value, collect3.value)).toBe(true);
809
+
810
+ // Both must have identical mounts and entities.
811
+ expect(collect3.value.mounts?.length).toBe(collect2.value.mounts?.length);
812
+ expect(collect3.value.entities.length).toBe(collect2.value.entities.length);
813
+
814
+ const sources2 = (collect2.value.mounts ?? []).map((m) => m.source as string).sort();
815
+ const sources3 = (collect3.value.mounts ?? []).map((m) => m.source as string).sort();
816
+ expect(sources3).toEqual(sources2);
817
+ });
818
+ });
819
+
820
+ // ═══════════════════════════════════════════════════════════════════════════════
821
+ // w17 — AC-04: collect fold double-round-trip idempotency
822
+ //
823
+ // Scenario (the meaningful fold path): a parent scene A mounts a child scene B.
824
+ // At runtime the AI user adds a component (AnimationPlayer with a clip handle)
825
+ // to a live B member — a delta that does NOT exist in B's source SceneAsset.
826
+ // rootsToSceneAsset([rootA]) must fold that delta into mounts[].overrides so the
827
+ // added component round-trips. AC-04 requires the override entry SET not to grow
828
+ // across a second save→load→save cycle (idempotent): the reloaded override enters
829
+ // the next round as an applied mount-time value, and the fold must still emit it
830
+ // exactly once (lossless) without ALSO emitting a duplicate (doubled).
831
+ //
832
+ // The reverse variant proves the test has discriminating power: a fold that
833
+ // counts an already-applied mount-time override as a *fresh* extra delta on top
834
+ // of passing it through would double the override count on the second round-trip.
835
+ // We reproduce that failure mode with a hand-rolled buggy fold and assert it
836
+ // doubles — so the green real-fold assertion above cannot be trivially satisfied.
837
+ // ═══════════════════════════════════════════════════════════════════════════════
838
+
839
+ const GCLIP = 'd1e2f3a4-b5c6-4d7e-8f90-1a2b3c4d5e6f';
840
+
841
+ /** Count total override entries across every collected mount. */
842
+ function totalOverrideCount(scene: SceneAsset): number {
843
+ let n = 0;
844
+ for (const m of scene.mounts ?? []) n += (m.overrides ?? []).length;
845
+ return n;
846
+ }
847
+
848
+ /** Find the single nested B member entity under a mounted parent root. */
849
+ function findMountedMember(w: World, rootA: EntityHandle): EntityHandle {
850
+ for (const c of w.iterDescendants(rootA)) {
851
+ if (c === rootA) continue;
852
+ if (!w.get(c, SceneInstance).ok) continue;
853
+ const st = SceneOwner.worldGetSceneInstanceState(w, c);
854
+ if (!st.ok) continue;
855
+ for (const [member] of st.value.entityToLocalId) return member;
856
+ }
857
+ throw new Error('no mounted member found');
858
+ }
859
+
860
+ describe('w17 — collect fold double-round-trip idempotency (AC-04)', () => {
861
+ it('add-component delta folds into mounts[].overrides and survives one full round-trip', () => {
862
+ const reg = mkReg();
863
+ const w = new World();
864
+
865
+ // Catalogue the clip AND mint the handle on the SAME payload object so
866
+ // collect-side _guidForAsset reverse-resolves the handle back to GCLIP.
867
+ const clipAsset = { kind: 'animation-clip' } as unknown as Asset;
868
+ reg.catalog(pg(GCLIP), clipAsset);
869
+ const child: SceneAsset = {
870
+ kind: 'scene',
871
+ entities: [{ localId: 0 as never, components: { Transform: { pos: [1, 0, 0] } } }],
872
+ };
873
+ cat(reg, G1, child);
874
+
875
+ const parent: SceneAsset = {
876
+ kind: 'scene',
877
+ entities: [{ localId: 0 as never, components: { Transform: { pos: [0, 0, 0] } } }],
878
+ mounts: [{ localId: 1 as never, source: G1, memberFirst: 2 as never, memberCount: 1 }],
879
+ };
880
+ cat(reg, G2, parent);
881
+
882
+ const inst = reg.instantiate(rs(w, parent), w);
883
+ expect(inst.ok).toBe(true);
884
+ if (!inst.ok) return;
885
+ const rootA = inst.value;
886
+
887
+ // Runtime authoring: add AnimationPlayer to the mounted child member.
888
+ const clipH = w.allocSharedRef('AnimationClip', clipAsset);
889
+ const member = findMountedMember(w, rootA);
890
+ const add = w.addComponent(member, {
891
+ component: AnimationPlayer,
892
+ data: { clips: [clipH] } as never,
893
+ });
894
+ expect(add.ok).toBe(true);
895
+
896
+ // Collect 1: the fold must emit exactly one AnimationPlayer override.
897
+ const collect1 = rootsToSceneAsset(reg, w, [rootA]);
898
+ expect(collect1.ok).toBe(true);
899
+ if (!collect1.ok) return;
900
+ expect(collect1.value.mounts?.length).toBe(1);
901
+ expect(totalOverrideCount(collect1.value)).toBe(1);
902
+ const ov1 = collect1.value.mounts?.[0]?.overrides?.[0];
903
+ expect(ov1?.comp).toBe('AnimationPlayer');
904
+ // component-add form: no `field`, value is the per-field map.
905
+ expect(ov1?.field).toBeUndefined();
906
+ // clips serialized to GUID string (handle→GUID via _guidForAsset).
907
+ const clipsSer = (ov1?.value as Record<string, unknown>).clips as unknown[];
908
+ expect(clipsSer[0]).toBe(GCLIP);
909
+
910
+ // Serialize → parse → reload.
911
+ const ser = serializeSceneAssetToPack(collect1.value, w.components.entries(), G3);
912
+ expect(ser.ok).toBe(true);
913
+ if (!ser.ok) return;
914
+ const unpacked = unpackSerialized(ser.value);
915
+ expect(unpacked).toBeDefined();
916
+ if (!unpacked) return;
917
+ const fn = accessParseScenePayload(reg);
918
+ const parsed = fn('scene', unpacked.payload, unpacked.refs);
919
+ expect(parsed).toBeDefined();
920
+ if (!parsed) return;
921
+ cat(reg, G3, parsed);
922
+ const inst2 = reg.instantiate(rs(w, parsed), w);
923
+ expect(inst2.ok).toBe(true);
924
+ if (!inst2.ok) return;
925
+ const rootA2 = inst2.value;
926
+
927
+ // Reloaded member must carry AnimationPlayer (override applied at instantiate).
928
+ const member2 = findMountedMember(w, rootA2);
929
+ expect(w.get(member2, AnimationPlayer).ok).toBe(true);
930
+
931
+ // Collect 2: idempotent — override SET does not grow.
932
+ const collect2 = rootsToSceneAsset(reg, w, [rootA2]);
933
+ expect(collect2.ok).toBe(true);
934
+ if (!collect2.ok) return;
935
+ expect(totalOverrideCount(collect2.value)).toBe(totalOverrideCount(collect1.value));
936
+ expect(totalOverrideCount(collect2.value)).toBe(1);
937
+ });
938
+
939
+ it('reverse variant: a fold that re-emits already-applied mount-time overrides DOUBLES (test has power)', () => {
940
+ // A buggy fold that emits BOTH the live-vs-source diff AND the applied
941
+ // mount-time overrides (from the parent state) as separate entries would
942
+ // double the override count once the delta becomes a reloaded mount-time
943
+ // value. We simulate that failure mode directly (no dependency on the real
944
+ // fold) to prove the idempotency assertion above is falsifiable.
945
+ const reg = mkReg();
946
+ const w = new World();
947
+ reg.catalog(pg(GCLIP), { kind: 'animation-clip' } as unknown as Asset);
948
+ const child: SceneAsset = {
949
+ kind: 'scene',
950
+ entities: [{ localId: 0 as never, components: { Transform: { pos: [1, 0, 0] } } }],
951
+ };
952
+ cat(reg, G1, child);
953
+ // Parent already carries a mount-time override on the member (as a reload
954
+ // would produce): the added AnimationPlayer is now baseline.
955
+ const appliedOverride: MountOverride = {
956
+ localId: 2 as never,
957
+ comp: 'AnimationPlayer',
958
+ value: { clips: [GCLIP] },
959
+ };
960
+ const parent: SceneAsset = {
961
+ kind: 'scene',
962
+ entities: [{ localId: 0 as never, components: { Transform: { pos: [0, 0, 0] } } }],
963
+ mounts: [
964
+ {
965
+ localId: 1 as never,
966
+ source: G1,
967
+ memberFirst: 2 as never,
968
+ memberCount: 1,
969
+ overrides: [appliedOverride],
970
+ },
971
+ ],
972
+ };
973
+ cat(reg, G2, parent);
974
+ const inst = reg.instantiate(rs(w, parent), w);
975
+ expect(inst.ok).toBe(true);
976
+ if (!inst.ok) return;
977
+
978
+ // Buggy fold: live-vs-source diff (re-derives AnimationPlayer) PLUS the
979
+ // pass-through of the applied mount-time override = 2 entries for one delta.
980
+ const member = findMountedMember(w, inst.value);
981
+ const liveDiff: MountOverride = w.get(member, AnimationPlayer).ok
982
+ ? { localId: 2 as never, comp: 'AnimationPlayer', value: { clips: [GCLIP] } }
983
+ : (undefined as never);
984
+ const buggyOverrides = [appliedOverride, liveDiff].filter(Boolean);
985
+ expect(buggyOverrides.length).toBe(2); // doubled — proves the failure mode is real
986
+ });
987
+ });