@forgeax/engine-runtime 0.0.0-dev.8d955ade1c79

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 (1017) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +165 -0
  3. package/dist/.tsbuildinfo +1 -0
  4. package/dist/__tests__/active-camera.unit.test.d.ts +2 -0
  5. package/dist/__tests__/active-camera.unit.test.d.ts.map +1 -0
  6. package/dist/__tests__/animation-graph-add.unit.test.d.ts +2 -0
  7. package/dist/__tests__/animation-graph-add.unit.test.d.ts.map +1 -0
  8. package/dist/__tests__/animation-graph-asset-union.test-d.d.ts +2 -0
  9. package/dist/__tests__/animation-graph-asset-union.test-d.d.ts.map +1 -0
  10. package/dist/__tests__/animation-graph-blend.unit.test.d.ts +2 -0
  11. package/dist/__tests__/animation-graph-blend.unit.test.d.ts.map +1 -0
  12. package/dist/__tests__/animation-graph-build.unit.test.d.ts +2 -0
  13. package/dist/__tests__/animation-graph-build.unit.test.d.ts.map +1 -0
  14. package/dist/__tests__/animation-graph-clip-missing.unit.test.d.ts +2 -0
  15. package/dist/__tests__/animation-graph-clip-missing.unit.test.d.ts.map +1 -0
  16. package/dist/__tests__/animation-graph-clip.unit.test.d.ts +2 -0
  17. package/dist/__tests__/animation-graph-clip.unit.test.d.ts.map +1 -0
  18. package/dist/__tests__/animation-graph-collect-roundtrip.test.d.ts +2 -0
  19. package/dist/__tests__/animation-graph-collect-roundtrip.test.d.ts.map +1 -0
  20. package/dist/__tests__/animation-graph-default-path.integration.test.d.ts +2 -0
  21. package/dist/__tests__/animation-graph-default-path.integration.test.d.ts.map +1 -0
  22. package/dist/__tests__/animation-graph-describe.unit.test.d.ts +2 -0
  23. package/dist/__tests__/animation-graph-describe.unit.test.d.ts.map +1 -0
  24. package/dist/__tests__/animation-graph-errors.unit.test.d.ts +2 -0
  25. package/dist/__tests__/animation-graph-errors.unit.test.d.ts.map +1 -0
  26. package/dist/__tests__/animation-graph-nesting.unit.test.d.ts +2 -0
  27. package/dist/__tests__/animation-graph-nesting.unit.test.d.ts.map +1 -0
  28. package/dist/__tests__/animation-graph-orthogonal-weight.unit.test.d.ts +2 -0
  29. package/dist/__tests__/animation-graph-orthogonal-weight.unit.test.d.ts.map +1 -0
  30. package/dist/__tests__/animation-graph-serialize.unit.test.d.ts +2 -0
  31. package/dist/__tests__/animation-graph-serialize.unit.test.d.ts.map +1 -0
  32. package/dist/__tests__/animation-graph-slot-fill.unit.test.d.ts +2 -0
  33. package/dist/__tests__/animation-graph-slot-fill.unit.test.d.ts.map +1 -0
  34. package/dist/__tests__/animation-nslot.unit.test.d.ts +2 -0
  35. package/dist/__tests__/animation-nslot.unit.test.d.ts.map +1 -0
  36. package/dist/__tests__/animation-slot-length-guard.unit.test.d.ts +2 -0
  37. package/dist/__tests__/animation-slot-length-guard.unit.test.d.ts.map +1 -0
  38. package/dist/__tests__/append-injection.test.d.ts +2 -0
  39. package/dist/__tests__/append-injection.test.d.ts.map +1 -0
  40. package/dist/__tests__/asi-world-baseline-export.unit.test.d.ts +2 -0
  41. package/dist/__tests__/asi-world-baseline-export.unit.test.d.ts.map +1 -0
  42. package/dist/__tests__/asi-world-ci-config.unit.test.d.ts +2 -0
  43. package/dist/__tests__/asi-world-ci-config.unit.test.d.ts.map +1 -0
  44. package/dist/__tests__/asi-world-smoke.unit.test.d.ts +2 -0
  45. package/dist/__tests__/asi-world-smoke.unit.test.d.ts.map +1 -0
  46. package/dist/__tests__/asset-brand-and-name-retirement.test.d.ts +2 -0
  47. package/dist/__tests__/asset-brand-and-name-retirement.test.d.ts.map +1 -0
  48. package/dist/__tests__/asset-error-exhaustive.test-d.d.ts +11 -0
  49. package/dist/__tests__/asset-error-exhaustive.test-d.d.ts.map +1 -0
  50. package/dist/__tests__/asset-registry-guid-reverse.test.d.ts +2 -0
  51. package/dist/__tests__/asset-registry-guid-reverse.test.d.ts.map +1 -0
  52. package/dist/__tests__/asset-registry-hdr-equirect.test.d.ts +2 -0
  53. package/dist/__tests__/asset-registry-hdr-equirect.test.d.ts.map +1 -0
  54. package/dist/__tests__/asset-registry-mesh-pack-payload.dawn.test.d.ts +2 -0
  55. package/dist/__tests__/asset-registry-mesh-pack-payload.dawn.test.d.ts.map +1 -0
  56. package/dist/__tests__/asset-registry-mounts.test.d.ts +2 -0
  57. package/dist/__tests__/asset-registry-mounts.test.d.ts.map +1 -0
  58. package/dist/__tests__/asset-registry-scene.test-d.d.ts +2 -0
  59. package/dist/__tests__/asset-registry-scene.test-d.d.ts.map +1 -0
  60. package/dist/__tests__/asset-registry.browser.test.d.ts +2 -0
  61. package/dist/__tests__/asset-registry.browser.test.d.ts.map +1 -0
  62. package/dist/__tests__/asset-registry.dawn.test.d.ts +2 -0
  63. package/dist/__tests__/asset-registry.dawn.test.d.ts.map +1 -0
  64. package/dist/__tests__/asset-registry.listCatalog.test.d.ts +2 -0
  65. package/dist/__tests__/asset-registry.listCatalog.test.d.ts.map +1 -0
  66. package/dist/__tests__/asset-registry.mesh-bin.test.d.ts +2 -0
  67. package/dist/__tests__/asset-registry.mesh-bin.test.d.ts.map +1 -0
  68. package/dist/__tests__/asset-registry.pack-cache.test.d.ts +2 -0
  69. package/dist/__tests__/asset-registry.pack-cache.test.d.ts.map +1 -0
  70. package/dist/__tests__/asset-registry.raw-container-failfast.test.d.ts +2 -0
  71. package/dist/__tests__/asset-registry.raw-container-failfast.test.d.ts.map +1 -0
  72. package/dist/__tests__/asset-registry.recursive.spec.d.ts +2 -0
  73. package/dist/__tests__/asset-registry.recursive.spec.d.ts.map +1 -0
  74. package/dist/__tests__/asset-registry.unit.test.d.ts +2 -0
  75. package/dist/__tests__/asset-registry.unit.test.d.ts.map +1 -0
  76. package/dist/__tests__/asset.unit.test.d.ts +2 -0
  77. package/dist/__tests__/asset.unit.test.d.ts.map +1 -0
  78. package/dist/__tests__/audio-load-by-guid.unit.test.d.ts +2 -0
  79. package/dist/__tests__/audio-load-by-guid.unit.test.d.ts.map +1 -0
  80. package/dist/__tests__/backend-selection.integration.test.d.ts +2 -0
  81. package/dist/__tests__/backend-selection.integration.test.d.ts.map +1 -0
  82. package/dist/__tests__/basis-catalog-dispatch.integration.test.d.ts +2 -0
  83. package/dist/__tests__/basis-catalog-dispatch.integration.test.d.ts.map +1 -0
  84. package/dist/__tests__/bind-group-cache-keying.test.d.ts +2 -0
  85. package/dist/__tests__/bind-group-cache-keying.test.d.ts.map +1 -0
  86. package/dist/__tests__/block-upload-math.unit.test.d.ts +2 -0
  87. package/dist/__tests__/block-upload-math.unit.test.d.ts.map +1 -0
  88. package/dist/__tests__/builtin-asset-registry.unit.test.d.ts +2 -0
  89. package/dist/__tests__/builtin-asset-registry.unit.test.d.ts.map +1 -0
  90. package/dist/__tests__/builtin-shader-register-e2e.test.d.ts +2 -0
  91. package/dist/__tests__/builtin-shader-register-e2e.test.d.ts.map +1 -0
  92. package/dist/__tests__/cache-false-eviction.test.d.ts +2 -0
  93. package/dist/__tests__/cache-false-eviction.test.d.ts.map +1 -0
  94. package/dist/__tests__/cache-isolation-matrix.test.d.ts +2 -0
  95. package/dist/__tests__/cache-isolation-matrix.test.d.ts.map +1 -0
  96. package/dist/__tests__/camera-clear-snapshot-equivalence.unit.test.d.ts +2 -0
  97. package/dist/__tests__/camera-clear-snapshot-equivalence.unit.test.d.ts.map +1 -0
  98. package/dist/__tests__/clamp-to-last.e2e.browser.test.d.ts +2 -0
  99. package/dist/__tests__/clamp-to-last.e2e.browser.test.d.ts.map +1 -0
  100. package/dist/__tests__/clamp-to-last.e2e.dawn.test.d.ts +2 -0
  101. package/dist/__tests__/clamp-to-last.e2e.dawn.test.d.ts.map +1 -0
  102. package/dist/__tests__/collect-scene-asset.test.d.ts +2 -0
  103. package/dist/__tests__/collect-scene-asset.test.d.ts.map +1 -0
  104. package/dist/__tests__/collect-scene-mount-collapse.test.d.ts +2 -0
  105. package/dist/__tests__/collect-scene-mount-collapse.test.d.ts.map +1 -0
  106. package/dist/__tests__/collect-subtree.unit.test.d.ts +2 -0
  107. package/dist/__tests__/collect-subtree.unit.test.d.ts.map +1 -0
  108. package/dist/__tests__/collect-transient-children-absent.test.d.ts +2 -0
  109. package/dist/__tests__/collect-transient-children-absent.test.d.ts.map +1 -0
  110. package/dist/__tests__/collect-transient-roundtrip.test.d.ts +2 -0
  111. package/dist/__tests__/collect-transient-roundtrip.test.d.ts.map +1 -0
  112. package/dist/__tests__/collect-transient-skip.test.d.ts +2 -0
  113. package/dist/__tests__/collect-transient-skip.test.d.ts.map +1 -0
  114. package/dist/__tests__/components.test-d.d.ts +2 -0
  115. package/dist/__tests__/components.test-d.d.ts.map +1 -0
  116. package/dist/__tests__/components.unit.test.d.ts +2 -0
  117. package/dist/__tests__/components.unit.test.d.ts.map +1 -0
  118. package/dist/__tests__/composite-skybox-cross-world.browser.test.d.ts +2 -0
  119. package/dist/__tests__/composite-skybox-cross-world.browser.test.d.ts.map +1 -0
  120. package/dist/__tests__/compressed-texture-physical-extent.integration.test.d.ts +2 -0
  121. package/dist/__tests__/compressed-texture-physical-extent.integration.test.d.ts.map +1 -0
  122. package/dist/__tests__/create-renderer-assembly.integration.test.d.ts +2 -0
  123. package/dist/__tests__/create-renderer-assembly.integration.test.d.ts.map +1 -0
  124. package/dist/__tests__/createRenderer-dispose.test.d.ts +2 -0
  125. package/dist/__tests__/createRenderer-dispose.test.d.ts.map +1 -0
  126. package/dist/__tests__/createRenderer-requiredFeatures.unit.test.d.ts +2 -0
  127. package/dist/__tests__/createRenderer-requiredFeatures.unit.test.d.ts.map +1 -0
  128. package/dist/__tests__/createRenderer.browser-no-webgpu.test.d.ts +2 -0
  129. package/dist/__tests__/createRenderer.browser-no-webgpu.test.d.ts.map +1 -0
  130. package/dist/__tests__/createRenderer.unit.test.d.ts +2 -0
  131. package/dist/__tests__/createRenderer.unit.test.d.ts.map +1 -0
  132. package/dist/__tests__/dawn/hello-sprite-nineslice-section.dawn.test.d.ts +2 -0
  133. package/dist/__tests__/dawn/hello-sprite-nineslice-section.dawn.test.d.ts.map +1 -0
  134. package/dist/__tests__/dawn/ibl-compose.dawn.test.d.ts +2 -0
  135. package/dist/__tests__/dawn/ibl-compose.dawn.test.d.ts.map +1 -0
  136. package/dist/__tests__/dawn/ibl-modules-compose.dawn.test.d.ts +2 -0
  137. package/dist/__tests__/dawn/ibl-modules-compose.dawn.test.d.ts.map +1 -0
  138. package/dist/__tests__/dawn/ibl-precompute-readback.dawn.test.d.ts +2 -0
  139. package/dist/__tests__/dawn/ibl-precompute-readback.dawn.test.d.ts.map +1 -0
  140. package/dist/__tests__/dawn/instances-per-instance-pbr.dawn.test.d.ts +2 -0
  141. package/dist/__tests__/dawn/instances-per-instance-pbr.dawn.test.d.ts.map +1 -0
  142. package/dist/__tests__/dawn/instances-shadow.dawn.test.d.ts +2 -0
  143. package/dist/__tests__/dawn/instances-shadow.dawn.test.d.ts.map +1 -0
  144. package/dist/__tests__/dawn/instances-uniform-fallback.dawn.test.d.ts +2 -0
  145. package/dist/__tests__/dawn/instances-uniform-fallback.dawn.test.d.ts.map +1 -0
  146. package/dist/__tests__/dawn/material-alpha.dawn.test.d.ts +2 -0
  147. package/dist/__tests__/dawn/material-alpha.dawn.test.d.ts.map +1 -0
  148. package/dist/__tests__/dawn/material-cooked-fixture.dawn.test.d.ts +2 -0
  149. package/dist/__tests__/dawn/material-cooked-fixture.dawn.test.d.ts.map +1 -0
  150. package/dist/__tests__/dawn/mesh-ssbo-normal-matrix.dawn.test.d.ts +2 -0
  151. package/dist/__tests__/dawn/mesh-ssbo-normal-matrix.dawn.test.d.ts.map +1 -0
  152. package/dist/__tests__/dawn/mesh-update-dawn.test.d.ts +2 -0
  153. package/dist/__tests__/dawn/mesh-update-dawn.test.d.ts.map +1 -0
  154. package/dist/__tests__/dawn/mipmap-srgb-linear.dawn.test.d.ts +2 -0
  155. package/dist/__tests__/dawn/mipmap-srgb-linear.dawn.test.d.ts.map +1 -0
  156. package/dist/__tests__/dawn/per-entity-material-texture-isolation.dawn.test.d.ts +2 -0
  157. package/dist/__tests__/dawn/per-entity-material-texture-isolation.dawn.test.d.ts.map +1 -0
  158. package/dist/__tests__/dawn/sprite-nineslice-mesh-bind.dawn.test.d.ts +2 -0
  159. package/dist/__tests__/dawn/sprite-nineslice-mesh-bind.dawn.test.d.ts.map +1 -0
  160. package/dist/__tests__/dawn/tonemap.dawn.test.d.ts +2 -0
  161. package/dist/__tests__/dawn/tonemap.dawn.test.d.ts.map +1 -0
  162. package/dist/__tests__/dawn/upload-texture.dawn.test.d.ts +2 -0
  163. package/dist/__tests__/dawn/upload-texture.dawn.test.d.ts.map +1 -0
  164. package/dist/__tests__/dawn/user-handle-mesh-render.dawn.test.d.ts +2 -0
  165. package/dist/__tests__/dawn/user-handle-mesh-render.dawn.test.d.ts.map +1 -0
  166. package/dist/__tests__/dawn/user-mesh-upload.dawn.test.d.ts +2 -0
  167. package/dist/__tests__/dawn/user-mesh-upload.dawn.test.d.ts.map +1 -0
  168. package/dist/__tests__/dawn/zero-manifest-mesh-fail.dawn.test.d.ts +2 -0
  169. package/dist/__tests__/dawn/zero-manifest-mesh-fail.dawn.test.d.ts.map +1 -0
  170. package/dist/__tests__/dawn/zero-manifest-ready.dawn.test.d.ts +2 -0
  171. package/dist/__tests__/dawn/zero-manifest-ready.dawn.test.d.ts.map +1 -0
  172. package/dist/__tests__/demo-sortscope-migration.unit.test.d.ts +2 -0
  173. package/dist/__tests__/demo-sortscope-migration.unit.test.d.ts.map +1 -0
  174. package/dist/__tests__/derive-bgl-integration.test.d.ts +2 -0
  175. package/dist/__tests__/derive-bgl-integration.test.d.ts.map +1 -0
  176. package/dist/__tests__/derive-render-data-skin.unit.test.d.ts +2 -0
  177. package/dist/__tests__/derive-render-data-skin.unit.test.d.ts.map +1 -0
  178. package/dist/__tests__/draw-published.d.ts +5 -0
  179. package/dist/__tests__/draw-published.d.ts.map +1 -0
  180. package/dist/__tests__/dynamic-texture-store.unit.test.d.ts +2 -0
  181. package/dist/__tests__/dynamic-texture-store.unit.test.d.ts.map +1 -0
  182. package/dist/__tests__/environment-error-exhaustive.test-d.d.ts +8 -0
  183. package/dist/__tests__/environment-error-exhaustive.test-d.d.ts.map +1 -0
  184. package/dist/__tests__/equirect-bc6h.integration.test.d.ts +2 -0
  185. package/dist/__tests__/equirect-bc6h.integration.test.d.ts.map +1 -0
  186. package/dist/__tests__/error-exhaustive.test.d.ts +2 -0
  187. package/dist/__tests__/error-exhaustive.test.d.ts.map +1 -0
  188. package/dist/__tests__/errors.unit.test.d.ts +2 -0
  189. package/dist/__tests__/errors.unit.test.d.ts.map +1 -0
  190. package/dist/__tests__/extract-frames-graceful-degradation.test.d.ts +2 -0
  191. package/dist/__tests__/extract-frames-graceful-degradation.test.d.ts.map +1 -0
  192. package/dist/__tests__/extract-frames-merge.test.d.ts +2 -0
  193. package/dist/__tests__/extract-frames-merge.test.d.ts.map +1 -0
  194. package/dist/__tests__/extract-frames-reset.test.d.ts +2 -0
  195. package/dist/__tests__/extract-frames-reset.test.d.ts.map +1 -0
  196. package/dist/__tests__/extract-record-no-hardcoded-texture-fields.test.d.ts +2 -0
  197. package/dist/__tests__/extract-record-no-hardcoded-texture-fields.test.d.ts.map +1 -0
  198. package/dist/__tests__/extract-skin-mismatch.unit.test.d.ts +2 -0
  199. package/dist/__tests__/extract-skin-mismatch.unit.test.d.ts.map +1 -0
  200. package/dist/__tests__/fetch-binary-decompress.test.d.ts +2 -0
  201. package/dist/__tests__/fetch-binary-decompress.test.d.ts.map +1 -0
  202. package/dist/__tests__/four-path-compression.integration.test.d.ts +2 -0
  203. package/dist/__tests__/four-path-compression.integration.test.d.ts.map +1 -0
  204. package/dist/__tests__/frame-targets-shadow-regression.unit.test.d.ts +2 -0
  205. package/dist/__tests__/frame-targets-shadow-regression.unit.test.d.ts.map +1 -0
  206. package/dist/__tests__/fullscreen-feature-host.dawn.test.d.ts +2 -0
  207. package/dist/__tests__/fullscreen-feature-host.dawn.test.d.ts.map +1 -0
  208. package/dist/__tests__/fullscreen-post-bgl-depth.test.d.ts +2 -0
  209. package/dist/__tests__/fullscreen-post-bgl-depth.test.d.ts.map +1 -0
  210. package/dist/__tests__/fullscreen-post-depth-params-arity.test.d.ts +2 -0
  211. package/dist/__tests__/fullscreen-post-depth-params-arity.test.d.ts.map +1 -0
  212. package/dist/__tests__/fullscreen-post-process-pass.dawn.test.d.ts +2 -0
  213. package/dist/__tests__/fullscreen-post-process-pass.dawn.test.d.ts.map +1 -0
  214. package/dist/__tests__/fullscreen-post-process-pass.unit.test.d.ts +2 -0
  215. package/dist/__tests__/fullscreen-post-process-pass.unit.test.d.ts.map +1 -0
  216. package/dist/__tests__/fxaa-pixel-diff.dawn.test.d.ts +2 -0
  217. package/dist/__tests__/fxaa-pixel-diff.dawn.test.d.ts.map +1 -0
  218. package/dist/__tests__/fxaa-validation-and-resize.dawn.test.d.ts +2 -0
  219. package/dist/__tests__/fxaa-validation-and-resize.dawn.test.d.ts.map +1 -0
  220. package/dist/__tests__/fxaa-zero-overhead.dawn.test.d.ts +2 -0
  221. package/dist/__tests__/fxaa-zero-overhead.dawn.test.d.ts.map +1 -0
  222. package/dist/__tests__/geometry.unit.test.d.ts +2 -0
  223. package/dist/__tests__/geometry.unit.test.d.ts.map +1 -0
  224. package/dist/__tests__/glyph-tilemap-worldid.test.d.ts +2 -0
  225. package/dist/__tests__/glyph-tilemap-worldid.test.d.ts.map +1 -0
  226. package/dist/__tests__/gpu-resource-cow-survivor.test.d.ts +2 -0
  227. package/dist/__tests__/gpu-resource-cow-survivor.test.d.ts.map +1 -0
  228. package/dist/__tests__/guid-for-asset.unit.test.d.ts +2 -0
  229. package/dist/__tests__/guid-for-asset.unit.test.d.ts.map +1 -0
  230. package/dist/__tests__/handle-migration.test.d.ts +2 -0
  231. package/dist/__tests__/handle-migration.test.d.ts.map +1 -0
  232. package/dist/__tests__/hello-tilemap-smoke-upgrade.unit.test.d.ts +2 -0
  233. package/dist/__tests__/hello-tilemap-smoke-upgrade.unit.test.d.ts.map +1 -0
  234. package/dist/__tests__/helpers/mock-shader-registry.d.ts +11 -0
  235. package/dist/__tests__/helpers/mock-shader-registry.d.ts.map +1 -0
  236. package/dist/__tests__/helpers/register-runtime-components.d.ts +4 -0
  237. package/dist/__tests__/helpers/register-runtime-components.d.ts.map +1 -0
  238. package/dist/__tests__/helpers/register-scene-components.d.ts +4 -0
  239. package/dist/__tests__/helpers/register-scene-components.d.ts.map +1 -0
  240. package/dist/__tests__/helpers/tilemap-assets.d.ts +6 -0
  241. package/dist/__tests__/helpers/tilemap-assets.d.ts.map +1 -0
  242. package/dist/__tests__/host-custom-kind-contract.test-d.d.ts +2 -0
  243. package/dist/__tests__/host-custom-kind-contract.test-d.d.ts.map +1 -0
  244. package/dist/__tests__/host-custom-kind-contract.test.d.ts +2 -0
  245. package/dist/__tests__/host-custom-kind-contract.test.d.ts.map +1 -0
  246. package/dist/__tests__/inspect-name.test.d.ts +2 -0
  247. package/dist/__tests__/inspect-name.test.d.ts.map +1 -0
  248. package/dist/__tests__/instances.test-d.d.ts +2 -0
  249. package/dist/__tests__/instances.test-d.d.ts.map +1 -0
  250. package/dist/__tests__/instantiate-mount-deferred-wiring.test.d.ts +2 -0
  251. package/dist/__tests__/instantiate-mount-deferred-wiring.test.d.ts.map +1 -0
  252. package/dist/__tests__/light-attenuation-formula.test.d.ts +2 -0
  253. package/dist/__tests__/light-attenuation-formula.test.d.ts.map +1 -0
  254. package/dist/__tests__/light-casters-9-light.browser.test.d.ts +2 -0
  255. package/dist/__tests__/light-casters-9-light.browser.test.d.ts.map +1 -0
  256. package/dist/__tests__/light-gpu-byte-neutral.unit.test.d.ts +2 -0
  257. package/dist/__tests__/light-gpu-byte-neutral.unit.test.d.ts.map +1 -0
  258. package/dist/__tests__/light-snapshot.test-d.d.ts +2 -0
  259. package/dist/__tests__/light-snapshot.test-d.d.ts.map +1 -0
  260. package/dist/__tests__/lights-vec-negative.unit.test.d.ts +2 -0
  261. package/dist/__tests__/lights-vec-negative.unit.test.d.ts.map +1 -0
  262. package/dist/__tests__/lights.unit.test.d.ts +2 -0
  263. package/dist/__tests__/lights.unit.test.d.ts.map +1 -0
  264. package/dist/__tests__/loadbyguid-payload.unit.test.d.ts +2 -0
  265. package/dist/__tests__/loadbyguid-payload.unit.test.d.ts.map +1 -0
  266. package/dist/__tests__/loadbyguid-texture-intern.unit.test.d.ts +2 -0
  267. package/dist/__tests__/loadbyguid-texture-intern.unit.test.d.ts.map +1 -0
  268. package/dist/__tests__/loader-extract-graceful.test.d.ts +2 -0
  269. package/dist/__tests__/loader-extract-graceful.test.d.ts.map +1 -0
  270. package/dist/__tests__/loader-name-survival.test.d.ts +2 -0
  271. package/dist/__tests__/loader-name-survival.test.d.ts.map +1 -0
  272. package/dist/__tests__/m3-vec-negative.unit.test.d.ts +2 -0
  273. package/dist/__tests__/m3-vec-negative.unit.test.d.ts.map +1 -0
  274. package/dist/__tests__/m5-browser-dawn-sweep.integration.test.d.ts +2 -0
  275. package/dist/__tests__/m5-browser-dawn-sweep.integration.test.d.ts.map +1 -0
  276. package/dist/__tests__/material-texture-uv-scale.unit.test.d.ts +2 -0
  277. package/dist/__tests__/material-texture-uv-scale.unit.test.d.ts.map +1 -0
  278. package/dist/__tests__/materials.unit.test.d.ts +2 -0
  279. package/dist/__tests__/materials.unit.test.d.ts.map +1 -0
  280. package/dist/__tests__/mesh-bin-contract.test.d.ts +2 -0
  281. package/dist/__tests__/mesh-bin-contract.test.d.ts.map +1 -0
  282. package/dist/__tests__/mesh-bin-skin-roundtrip.unit.test.d.ts +2 -0
  283. package/dist/__tests__/mesh-bin-skin-roundtrip.unit.test.d.ts.map +1 -0
  284. package/dist/__tests__/mesh-bin.test.d.ts +2 -0
  285. package/dist/__tests__/mesh-bin.test.d.ts.map +1 -0
  286. package/dist/__tests__/mesh-loader-skin-contract.unit.test.d.ts +2 -0
  287. package/dist/__tests__/mesh-loader-skin-contract.unit.test.d.ts.map +1 -0
  288. package/dist/__tests__/mesh-ssbo-grow.unit.test.d.ts +2 -0
  289. package/dist/__tests__/mesh-ssbo-grow.unit.test.d.ts.map +1 -0
  290. package/dist/__tests__/mip-gate.unit.test.d.ts +2 -0
  291. package/dist/__tests__/mip-gate.unit.test.d.ts.map +1 -0
  292. package/dist/__tests__/msaa-sprite-pixel-diff.dawn.test.d.ts +2 -0
  293. package/dist/__tests__/msaa-sprite-pixel-diff.dawn.test.d.ts.map +1 -0
  294. package/dist/__tests__/multi-uv-8set.e2e.dawn.test.d.ts +2 -0
  295. package/dist/__tests__/multi-uv-8set.e2e.dawn.test.d.ts.map +1 -0
  296. package/dist/__tests__/name.unit.test.d.ts +2 -0
  297. package/dist/__tests__/name.unit.test.d.ts.map +1 -0
  298. package/dist/__tests__/new-kind-refs-contract.test.d.ts +2 -0
  299. package/dist/__tests__/new-kind-refs-contract.test.d.ts.map +1 -0
  300. package/dist/__tests__/package-promotion.test.d.ts +2 -0
  301. package/dist/__tests__/package-promotion.test.d.ts.map +1 -0
  302. package/dist/__tests__/package-rename.test.d.ts +2 -0
  303. package/dist/__tests__/package-rename.test.d.ts.map +1 -0
  304. package/dist/__tests__/pass-selector.unit.test.d.ts +2 -0
  305. package/dist/__tests__/pass-selector.unit.test.d.ts.map +1 -0
  306. package/dist/__tests__/pbr-pipeline.unit.test.d.ts +2 -0
  307. package/dist/__tests__/pbr-pipeline.unit.test.d.ts.map +1 -0
  308. package/dist/__tests__/pipeline-builder-msaa.unit.test.d.ts +2 -0
  309. package/dist/__tests__/pipeline-builder-msaa.unit.test.d.ts.map +1 -0
  310. package/dist/__tests__/pipeline-cache-keying.unit.test.d.ts +2 -0
  311. package/dist/__tests__/pipeline-cache-keying.unit.test.d.ts.map +1 -0
  312. package/dist/__tests__/pipeline-spec.test.d.ts +2 -0
  313. package/dist/__tests__/pipeline-spec.test.d.ts.map +1 -0
  314. package/dist/__tests__/pipeline.unit.test.d.ts +2 -0
  315. package/dist/__tests__/pipeline.unit.test.d.ts.map +1 -0
  316. package/dist/__tests__/point-light-shadow.browser.test.d.ts +2 -0
  317. package/dist/__tests__/point-light-shadow.browser.test.d.ts.map +1 -0
  318. package/dist/__tests__/point-light-shadow.dawn.test.d.ts +2 -0
  319. package/dist/__tests__/point-light-shadow.dawn.test.d.ts.map +1 -0
  320. package/dist/__tests__/point-light-shadow.unit.test.d.ts +2 -0
  321. package/dist/__tests__/point-light-shadow.unit.test.d.ts.map +1 -0
  322. package/dist/__tests__/post-process-error-codes.unit.test.d.ts +2 -0
  323. package/dist/__tests__/post-process-error-codes.unit.test.d.ts.map +1 -0
  324. package/dist/__tests__/post-process-params-component.unit.test.d.ts +2 -0
  325. package/dist/__tests__/post-process-params-component.unit.test.d.ts.map +1 -0
  326. package/dist/__tests__/post-process-params-extract.unit.test.d.ts +2 -0
  327. package/dist/__tests__/post-process-params-extract.unit.test.d.ts.map +1 -0
  328. package/dist/__tests__/postprocess-camera-consistency.test.d.ts +2 -0
  329. package/dist/__tests__/postprocess-camera-consistency.test.d.ts.map +1 -0
  330. package/dist/__tests__/profiler-assembly.integration.test.d.ts +2 -0
  331. package/dist/__tests__/profiler-assembly.integration.test.d.ts.map +1 -0
  332. package/dist/__tests__/propagate-transforms.dawn.test.d.ts +2 -0
  333. package/dist/__tests__/propagate-transforms.dawn.test.d.ts.map +1 -0
  334. package/dist/__tests__/record-worldid-isolation.test.d.ts +2 -0
  335. package/dist/__tests__/record-worldid-isolation.test.d.ts.map +1 -0
  336. package/dist/__tests__/recover-error-exhaustive.test-d.d.ts +11 -0
  337. package/dist/__tests__/recover-error-exhaustive.test-d.d.ts.map +1 -0
  338. package/dist/__tests__/render-error-exhaustive.test-d.d.ts +11 -0
  339. package/dist/__tests__/render-error-exhaustive.test-d.d.ts.map +1 -0
  340. package/dist/__tests__/render-feature-assembly.test-d.d.ts +2 -0
  341. package/dist/__tests__/render-feature-assembly.test-d.d.ts.map +1 -0
  342. package/dist/__tests__/render-feature-prepared-graphics-docs.unit.test.d.ts +2 -0
  343. package/dist/__tests__/render-feature-prepared-graphics-docs.unit.test.d.ts.map +1 -0
  344. package/dist/__tests__/render-feature-prepared-graphics.browser.test.d.ts +2 -0
  345. package/dist/__tests__/render-feature-prepared-graphics.browser.test.d.ts.map +1 -0
  346. package/dist/__tests__/render-feature-prepared-graphics.dawn.test.d.ts +2 -0
  347. package/dist/__tests__/render-feature-prepared-graphics.dawn.test.d.ts.map +1 -0
  348. package/dist/__tests__/render-feature-prepared-graphics.fixture.d.ts +10 -0
  349. package/dist/__tests__/render-feature-prepared-graphics.fixture.d.ts.map +1 -0
  350. package/dist/__tests__/render-feature-prepared-graphics.integration.test.d.ts +2 -0
  351. package/dist/__tests__/render-feature-prepared-graphics.integration.test.d.ts.map +1 -0
  352. package/dist/__tests__/render-feature-prepared-graphics.regression.integration.test.d.ts +2 -0
  353. package/dist/__tests__/render-feature-prepared-graphics.regression.integration.test.d.ts.map +1 -0
  354. package/dist/__tests__/render-feature-recover.integration.test.d.ts +2 -0
  355. package/dist/__tests__/render-feature-recover.integration.test.d.ts.map +1 -0
  356. package/dist/__tests__/render-feature-runtime-surface.test-d.d.ts +2 -0
  357. package/dist/__tests__/render-feature-runtime-surface.test-d.d.ts.map +1 -0
  358. package/dist/__tests__/render-schema-consumer.integration.test.d.ts +2 -0
  359. package/dist/__tests__/render-schema-consumer.integration.test.d.ts.map +1 -0
  360. package/dist/__tests__/render-schema-owner-boundary.unit.test.d.ts +2 -0
  361. package/dist/__tests__/render-schema-owner-boundary.unit.test.d.ts.map +1 -0
  362. package/dist/__tests__/render-system-cross-world-overlay-queue.test.d.ts +2 -0
  363. package/dist/__tests__/render-system-cross-world-overlay-queue.test.d.ts.map +1 -0
  364. package/dist/__tests__/render-system-extract-dispatch-ordering.test.d.ts +2 -0
  365. package/dist/__tests__/render-system-extract-dispatch-ordering.test.d.ts.map +1 -0
  366. package/dist/__tests__/render-system-extract.test.d.ts +2 -0
  367. package/dist/__tests__/render-system-extract.test.d.ts.map +1 -0
  368. package/dist/__tests__/render-system-extract.unit.test.d.ts +2 -0
  369. package/dist/__tests__/render-system-extract.unit.test.d.ts.map +1 -0
  370. package/dist/__tests__/render-system-fold-bucket.unit.test.d.ts +2 -0
  371. package/dist/__tests__/render-system-fold-bucket.unit.test.d.ts.map +1 -0
  372. package/dist/__tests__/render-system-fold-fallback.unit.test.d.ts +2 -0
  373. package/dist/__tests__/render-system-fold-fallback.unit.test.d.ts.map +1 -0
  374. package/dist/__tests__/render-system-fold-material.unit.test.d.ts +2 -0
  375. package/dist/__tests__/render-system-fold-material.unit.test.d.ts.map +1 -0
  376. package/dist/__tests__/render-system-fold-metrics.unit.test.d.ts +2 -0
  377. package/dist/__tests__/render-system-fold-metrics.unit.test.d.ts.map +1 -0
  378. package/dist/__tests__/render-system-fold-modegate.unit.test.d.ts +2 -0
  379. package/dist/__tests__/render-system-fold-modegate.unit.test.d.ts.map +1 -0
  380. package/dist/__tests__/render-system-fold-scene-instances.unit.test.d.ts +2 -0
  381. package/dist/__tests__/render-system-fold-scene-instances.unit.test.d.ts.map +1 -0
  382. package/dist/__tests__/render-system-fold.dawn.test.d.ts +2 -0
  383. package/dist/__tests__/render-system-fold.dawn.test.d.ts.map +1 -0
  384. package/dist/__tests__/render-system-mega.test.d.ts +2 -0
  385. package/dist/__tests__/render-system-mega.test.d.ts.map +1 -0
  386. package/dist/__tests__/render-system-owner-split-contract.test.d.ts +2 -0
  387. package/dist/__tests__/render-system-owner-split-contract.test.d.ts.map +1 -0
  388. package/dist/__tests__/render-system-record-multi-material-textureview.test.d.ts +2 -0
  389. package/dist/__tests__/render-system-record-multi-material-textureview.test.d.ts.map +1 -0
  390. package/dist/__tests__/render-system-record-per-submesh-transparency.test.d.ts +2 -0
  391. package/dist/__tests__/render-system-record-per-submesh-transparency.test.d.ts.map +1 -0
  392. package/dist/__tests__/render-system-record.test.d.ts +2 -0
  393. package/dist/__tests__/render-system-record.test.d.ts.map +1 -0
  394. package/dist/__tests__/render-system-record.unit.test.d.ts +2 -0
  395. package/dist/__tests__/render-system-record.unit.test.d.ts.map +1 -0
  396. package/dist/__tests__/render-system-skin-bg.unit.test.d.ts +2 -0
  397. package/dist/__tests__/render-system-skin-bg.unit.test.d.ts.map +1 -0
  398. package/dist/__tests__/renderer-consumer.test-d.d.ts +2 -0
  399. package/dist/__tests__/renderer-consumer.test-d.d.ts.map +1 -0
  400. package/dist/__tests__/renderer-health.unit.test.d.ts +2 -0
  401. package/dist/__tests__/renderer-health.unit.test.d.ts.map +1 -0
  402. package/dist/__tests__/renderer-lifecycle.integration.test.d.ts +2 -0
  403. package/dist/__tests__/renderer-lifecycle.integration.test.d.ts.map +1 -0
  404. package/dist/__tests__/renderer-recover.unit.test.d.ts +2 -0
  405. package/dist/__tests__/renderer-recover.unit.test.d.ts.map +1 -0
  406. package/dist/__tests__/renderer-result.test-d.d.ts +2 -0
  407. package/dist/__tests__/renderer-result.test-d.d.ts.map +1 -0
  408. package/dist/__tests__/renderer-surface.unit.test.d.ts +2 -0
  409. package/dist/__tests__/renderer-surface.unit.test.d.ts.map +1 -0
  410. package/dist/__tests__/renderer-test-utils.d.ts +10 -0
  411. package/dist/__tests__/renderer-test-utils.d.ts.map +1 -0
  412. package/dist/__tests__/renderer.test-d.d.ts +2 -0
  413. package/dist/__tests__/renderer.test-d.d.ts.map +1 -0
  414. package/dist/__tests__/resolve-asset-handle-err.test.d.ts +2 -0
  415. package/dist/__tests__/resolve-asset-handle-err.test.d.ts.map +1 -0
  416. package/dist/__tests__/resolve-asset-handle.unit.test.d.ts +2 -0
  417. package/dist/__tests__/resolve-asset-handle.unit.test.d.ts.map +1 -0
  418. package/dist/__tests__/resolve-depth-only-view.test.d.ts +2 -0
  419. package/dist/__tests__/resolve-depth-only-view.test.d.ts.map +1 -0
  420. package/dist/__tests__/resolveName-fallback.test.d.ts +2 -0
  421. package/dist/__tests__/resolveName-fallback.test.d.ts.map +1 -0
  422. package/dist/__tests__/resolveName-null-package.test.d.ts +2 -0
  423. package/dist/__tests__/resolveName-null-package.test.d.ts.map +1 -0
  424. package/dist/__tests__/resolveName-unified-entry.test.d.ts +2 -0
  425. package/dist/__tests__/resolveName-unified-entry.test.d.ts.map +1 -0
  426. package/dist/__tests__/resolveName.test.d.ts +2 -0
  427. package/dist/__tests__/resolveName.test.d.ts.map +1 -0
  428. package/dist/__tests__/rhi-null-command-flow.unit.test.d.ts +2 -0
  429. package/dist/__tests__/rhi-null-command-flow.unit.test.d.ts.map +1 -0
  430. package/dist/__tests__/rhi-null-noop-behavior.unit.test.d.ts +2 -0
  431. package/dist/__tests__/rhi-null-noop-behavior.unit.test.d.ts.map +1 -0
  432. package/dist/__tests__/rhi-null-renderer-lifecycle.unit.test.d.ts +2 -0
  433. package/dist/__tests__/rhi-null-renderer-lifecycle.unit.test.d.ts.map +1 -0
  434. package/dist/__tests__/roots-to-scene-asset.test.d.ts +2 -0
  435. package/dist/__tests__/roots-to-scene-asset.test.d.ts.map +1 -0
  436. package/dist/__tests__/roundtrip-primitive.test.d.ts +2 -0
  437. package/dist/__tests__/roundtrip-primitive.test.d.ts.map +1 -0
  438. package/dist/__tests__/runtime-authority-scan.unit.test.d.ts +2 -0
  439. package/dist/__tests__/runtime-authority-scan.unit.test.d.ts.map +1 -0
  440. package/dist/__tests__/runtime-domain-residue.unit.test.d.ts +2 -0
  441. package/dist/__tests__/runtime-domain-residue.unit.test.d.ts.map +1 -0
  442. package/dist/__tests__/scene-bare-guid-refs-instantiate.test.d.ts +2 -0
  443. package/dist/__tests__/scene-bare-guid-refs-instantiate.test.d.ts.map +1 -0
  444. package/dist/__tests__/scene-equirect-roundtrip.test.d.ts +2 -0
  445. package/dist/__tests__/scene-equirect-roundtrip.test.d.ts.map +1 -0
  446. package/dist/__tests__/scene-instance-authoring.integration.test.d.ts +2 -0
  447. package/dist/__tests__/scene-instance-authoring.integration.test.d.ts.map +1 -0
  448. package/dist/__tests__/scene-instance-bridge.integration.test.d.ts +2 -0
  449. package/dist/__tests__/scene-instance-bridge.integration.test.d.ts.map +1 -0
  450. package/dist/__tests__/scene-instance-component.test.d.ts +2 -0
  451. package/dist/__tests__/scene-instance-component.test.d.ts.map +1 -0
  452. package/dist/__tests__/scene-instantiate-cycle-runtime.test.d.ts +2 -0
  453. package/dist/__tests__/scene-instantiate-cycle-runtime.test.d.ts.map +1 -0
  454. package/dist/__tests__/scene-mount-fixed-point.test.d.ts +2 -0
  455. package/dist/__tests__/scene-mount-fixed-point.test.d.ts.map +1 -0
  456. package/dist/__tests__/scene-mount-roundtrip.test.d.ts +2 -0
  457. package/dist/__tests__/scene-mount-roundtrip.test.d.ts.map +1 -0
  458. package/dist/__tests__/scene-mount-wrapper-roundtrip.test.d.ts +2 -0
  459. package/dist/__tests__/scene-mount-wrapper-roundtrip.test.d.ts.map +1 -0
  460. package/dist/__tests__/scene-sugar-instantiate.test.d.ts +2 -0
  461. package/dist/__tests__/scene-sugar-instantiate.test.d.ts.map +1 -0
  462. package/dist/__tests__/serialize-mounts-roundtrip.test.d.ts +2 -0
  463. package/dist/__tests__/serialize-mounts-roundtrip.test.d.ts.map +1 -0
  464. package/dist/__tests__/serialize-scene-asset.test.d.ts +2 -0
  465. package/dist/__tests__/serialize-scene-asset.test.d.ts.map +1 -0
  466. package/dist/__tests__/shadow-caster-empty-schema.test.d.ts +2 -0
  467. package/dist/__tests__/shadow-caster-empty-schema.test.d.ts.map +1 -0
  468. package/dist/__tests__/shadow-csm-atlas.test.d.ts +2 -0
  469. package/dist/__tests__/shadow-csm-atlas.test.d.ts.map +1 -0
  470. package/dist/__tests__/shadow-csm-blend.dawn.test.d.ts +2 -0
  471. package/dist/__tests__/shadow-csm-blend.dawn.test.d.ts.map +1 -0
  472. package/dist/__tests__/shadow-csm-cascade-loadop.test.d.ts +2 -0
  473. package/dist/__tests__/shadow-csm-cascade-loadop.test.d.ts.map +1 -0
  474. package/dist/__tests__/shadow-csm-component.test.d.ts +2 -0
  475. package/dist/__tests__/shadow-csm-component.test.d.ts.map +1 -0
  476. package/dist/__tests__/shadow-csm-extract.test.d.ts +2 -0
  477. package/dist/__tests__/shadow-csm-extract.test.d.ts.map +1 -0
  478. package/dist/__tests__/shadow-csm-orthoz.test.d.ts +2 -0
  479. package/dist/__tests__/shadow-csm-orthoz.test.d.ts.map +1 -0
  480. package/dist/__tests__/shadow-csm-runtime-vary.dawn.test.d.ts +2 -0
  481. package/dist/__tests__/shadow-csm-runtime-vary.dawn.test.d.ts.map +1 -0
  482. package/dist/__tests__/shadow-csm-shader.dawn.test.d.ts +2 -0
  483. package/dist/__tests__/shadow-csm-shader.dawn.test.d.ts.map +1 -0
  484. package/dist/__tests__/shadow-csm-tile-consistency.test.d.ts +2 -0
  485. package/dist/__tests__/shadow-csm-tile-consistency.test.d.ts.map +1 -0
  486. package/dist/__tests__/shadow-csm-ubo.dawn.test.d.ts +2 -0
  487. package/dist/__tests__/shadow-csm-ubo.dawn.test.d.ts.map +1 -0
  488. package/dist/__tests__/shadow-csm-ubo.test.d.ts +2 -0
  489. package/dist/__tests__/shadow-csm-ubo.test.d.ts.map +1 -0
  490. package/dist/__tests__/shadow-csm-viewport.browser.test.d.ts +2 -0
  491. package/dist/__tests__/shadow-csm-viewport.browser.test.d.ts.map +1 -0
  492. package/dist/__tests__/shadow-fields-observable.dawn.test.d.ts +2 -0
  493. package/dist/__tests__/shadow-fields-observable.dawn.test.d.ts.map +1 -0
  494. package/dist/__tests__/shadow-m1.dawn.test.d.ts +2 -0
  495. package/dist/__tests__/shadow-m1.dawn.test.d.ts.map +1 -0
  496. package/dist/__tests__/shadow-mesh-ssbo-order.test.d.ts +2 -0
  497. package/dist/__tests__/shadow-mesh-ssbo-order.test.d.ts.map +1 -0
  498. package/dist/__tests__/skin-error-exhaustive.test-d.d.ts +11 -0
  499. package/dist/__tests__/skin-error-exhaustive.test-d.d.ts.map +1 -0
  500. package/dist/__tests__/skin-errors.unit.test.d.ts +2 -0
  501. package/dist/__tests__/skin-errors.unit.test.d.ts.map +1 -0
  502. package/dist/__tests__/skin-extract-getarrayview-count.unit.test.d.ts +2 -0
  503. package/dist/__tests__/skin-extract-getarrayview-count.unit.test.d.ts.map +1 -0
  504. package/dist/__tests__/skin-parented-double-transform.test.d.ts +14 -0
  505. package/dist/__tests__/skin-parented-double-transform.test.d.ts.map +1 -0
  506. package/dist/__tests__/skylight-lazy-projection.test.d.ts +2 -0
  507. package/dist/__tests__/skylight-lazy-projection.test.d.ts.map +1 -0
  508. package/dist/__tests__/sparse-tag-scene-roundtrip.test.d.ts +2 -0
  509. package/dist/__tests__/sparse-tag-scene-roundtrip.test.d.ts.map +1 -0
  510. package/dist/__tests__/sprite-cullmode-flip.dawn.test.d.ts +2 -0
  511. package/dist/__tests__/sprite-cullmode-flip.dawn.test.d.ts.map +1 -0
  512. package/dist/__tests__/sprite-lit-bgl-byte-identical.test.d.ts +2 -0
  513. package/dist/__tests__/sprite-lit-bgl-byte-identical.test.d.ts.map +1 -0
  514. package/dist/__tests__/sprite-lit-extract-branch.test.d.ts +2 -0
  515. package/dist/__tests__/sprite-lit-extract-branch.test.d.ts.map +1 -0
  516. package/dist/__tests__/sprite-lit-pipeline-cache-key.test.d.ts +2 -0
  517. package/dist/__tests__/sprite-lit-pipeline-cache-key.test.d.ts.map +1 -0
  518. package/dist/__tests__/sprite-param-values.test-d.d.ts +2 -0
  519. package/dist/__tests__/sprite-param-values.test-d.d.ts.map +1 -0
  520. package/dist/__tests__/ssao-bgl.test.d.ts +2 -0
  521. package/dist/__tests__/ssao-bgl.test.d.ts.map +1 -0
  522. package/dist/__tests__/ssao-buffers.test.d.ts +2 -0
  523. package/dist/__tests__/ssao-buffers.test.d.ts.map +1 -0
  524. package/dist/__tests__/ssao-data.test.d.ts +2 -0
  525. package/dist/__tests__/ssao-data.test.d.ts.map +1 -0
  526. package/dist/__tests__/ssao-errors.test.d.ts +2 -0
  527. package/dist/__tests__/ssao-errors.test.d.ts.map +1 -0
  528. package/dist/__tests__/ssao-manifest.test.d.ts +2 -0
  529. package/dist/__tests__/ssao-manifest.test.d.ts.map +1 -0
  530. package/dist/__tests__/ssao-passes.test.d.ts +2 -0
  531. package/dist/__tests__/ssao-passes.test.d.ts.map +1 -0
  532. package/dist/__tests__/ssao-pipelines.test.d.ts +2 -0
  533. package/dist/__tests__/ssao-pipelines.test.d.ts.map +1 -0
  534. package/dist/__tests__/ssao-uniform-layout.test.d.ts +2 -0
  535. package/dist/__tests__/ssao-uniform-layout.test.d.ts.map +1 -0
  536. package/dist/__tests__/static-rig.integration.test.d.ts +2 -0
  537. package/dist/__tests__/static-rig.integration.test.d.ts.map +1 -0
  538. package/dist/__tests__/stencil-outline-pixel.dawn.test.d.ts +2 -0
  539. package/dist/__tests__/stencil-outline-pixel.dawn.test.d.ts.map +1 -0
  540. package/dist/__tests__/swap-chain-format.unit.test.d.ts +2 -0
  541. package/dist/__tests__/swap-chain-format.unit.test.d.ts.map +1 -0
  542. package/dist/__tests__/systems.unit.test.d.ts +2 -0
  543. package/dist/__tests__/systems.unit.test.d.ts.map +1 -0
  544. package/dist/__tests__/template-game-default-api-contract.test.d.ts +2 -0
  545. package/dist/__tests__/template-game-default-api-contract.test.d.ts.map +1 -0
  546. package/dist/__tests__/text.unit.test.d.ts +2 -0
  547. package/dist/__tests__/text.unit.test.d.ts.map +1 -0
  548. package/dist/__tests__/tilemap-childed-despawn.test.d.ts +2 -0
  549. package/dist/__tests__/tilemap-childed-despawn.test.d.ts.map +1 -0
  550. package/dist/__tests__/tilemap-chunk-extract.overhang-bounds.test.d.ts +2 -0
  551. package/dist/__tests__/tilemap-chunk-extract.overhang-bounds.test.d.ts.map +1 -0
  552. package/dist/__tests__/tilemap-chunk-extract.propagate-order.test.d.ts +2 -0
  553. package/dist/__tests__/tilemap-chunk-extract.propagate-order.test.d.ts.map +1 -0
  554. package/dist/__tests__/tilemap-chunk-extract.terrain-rewrite.unit.test.d.ts +2 -0
  555. package/dist/__tests__/tilemap-chunk-extract.terrain-rewrite.unit.test.d.ts.map +1 -0
  556. package/dist/__tests__/tilemap-chunk-extract.test.d.ts +2 -0
  557. package/dist/__tests__/tilemap-chunk-extract.test.d.ts.map +1 -0
  558. package/dist/__tests__/tilemap-chunk-extract.unit.test.d.ts +2 -0
  559. package/dist/__tests__/tilemap-chunk-extract.unit.test.d.ts.map +1 -0
  560. package/dist/__tests__/tilemap-chunk-y-sort-bench.unit.test.d.ts +2 -0
  561. package/dist/__tests__/tilemap-chunk-y-sort-bench.unit.test.d.ts.map +1 -0
  562. package/dist/__tests__/tilemap-dirty-rebuild.test.d.ts +2 -0
  563. package/dist/__tests__/tilemap-dirty-rebuild.test.d.ts.map +1 -0
  564. package/dist/__tests__/tilemap-material-cache.test.d.ts +2 -0
  565. package/dist/__tests__/tilemap-material-cache.test.d.ts.map +1 -0
  566. package/dist/__tests__/tilemap-multi-atlas.test.d.ts +2 -0
  567. package/dist/__tests__/tilemap-multi-atlas.test.d.ts.map +1 -0
  568. package/dist/__tests__/tilemap-owner-boundary.unit.test.d.ts +2 -0
  569. package/dist/__tests__/tilemap-owner-boundary.unit.test.d.ts.map +1 -0
  570. package/dist/__tests__/tilemap-region-pack.test.d.ts +2 -0
  571. package/dist/__tests__/tilemap-region-pack.test.d.ts.map +1 -0
  572. package/dist/__tests__/tilemap-spawn-flip-pivot.test.d.ts +2 -0
  573. package/dist/__tests__/tilemap-spawn-flip-pivot.test.d.ts.map +1 -0
  574. package/dist/__tests__/tilemap-uv-inset.test.d.ts +2 -0
  575. package/dist/__tests__/tilemap-uv-inset.test.d.ts.map +1 -0
  576. package/dist/__tests__/tilemap-validate.test.d.ts +2 -0
  577. package/dist/__tests__/tilemap-validate.test.d.ts.map +1 -0
  578. package/dist/__tests__/tileset-asset-validate.test.d.ts +2 -0
  579. package/dist/__tests__/tileset-asset-validate.test.d.ts.map +1 -0
  580. package/dist/__tests__/transcode-fallback.unit.test.d.ts +2 -0
  581. package/dist/__tests__/transcode-fallback.unit.test.d.ts.map +1 -0
  582. package/dist/__tests__/transform-hierarchy-pixel-diff.dawn.test.d.ts +2 -0
  583. package/dist/__tests__/transform-hierarchy-pixel-diff.dawn.test.d.ts.map +1 -0
  584. package/dist/__tests__/transient-fields-collect.test.d.ts +2 -0
  585. package/dist/__tests__/transient-fields-collect.test.d.ts.map +1 -0
  586. package/dist/__tests__/transient-runtime-behavior.test.d.ts +2 -0
  587. package/dist/__tests__/transient-runtime-behavior.test.d.ts.map +1 -0
  588. package/dist/__tests__/transitional-authoring.integration.test.d.ts +2 -0
  589. package/dist/__tests__/transitional-authoring.integration.test.d.ts.map +1 -0
  590. package/dist/__tests__/transparent-sort-tilemap-sprite-interleave.test.d.ts +2 -0
  591. package/dist/__tests__/transparent-sort-tilemap-sprite-interleave.test.d.ts.map +1 -0
  592. package/dist/__tests__/variant-selection.unit.test.d.ts +2 -0
  593. package/dist/__tests__/variant-selection.unit.test.d.ts.map +1 -0
  594. package/dist/__tests__/vertex-attribute-layout.test.d.ts +2 -0
  595. package/dist/__tests__/vertex-attribute-layout.test.d.ts.map +1 -0
  596. package/dist/__tests__/video-extract-bindgroup.dawn.test.d.ts +2 -0
  597. package/dist/__tests__/video-extract-bindgroup.dawn.test.d.ts.map +1 -0
  598. package/dist/__tests__/video-loader-consumption.test.d.ts +2 -0
  599. package/dist/__tests__/video-loader-consumption.test.d.ts.map +1 -0
  600. package/dist/__tests__/video-loader-smoke.dawn.test.d.ts +2 -0
  601. package/dist/__tests__/video-loader-smoke.dawn.test.d.ts.map +1 -0
  602. package/dist/__tests__/view-ubo-layout.browser.test.d.ts +2 -0
  603. package/dist/__tests__/view-ubo-layout.browser.test.d.ts.map +1 -0
  604. package/dist/__tests__/world-entity-key.test.d.ts +2 -0
  605. package/dist/__tests__/world-entity-key.test.d.ts.map +1 -0
  606. package/dist/__tests__/world-scene-despawn.test.d.ts +2 -0
  607. package/dist/__tests__/world-scene-despawn.test.d.ts.map +1 -0
  608. package/dist/__tests__/world-scene-instantiate.test.d.ts +2 -0
  609. package/dist/__tests__/world-scene-instantiate.test.d.ts.map +1 -0
  610. package/dist/__tests__/world-scene-nested.test.d.ts +2 -0
  611. package/dist/__tests__/world-scene-nested.test.d.ts.map +1 -0
  612. package/dist/__tests__/world-scene-override.test.d.ts +2 -0
  613. package/dist/__tests__/world-scene-override.test.d.ts.map +1 -0
  614. package/dist/__tests__/zero-compression-zero-load.integration.test.d.ts +2 -0
  615. package/dist/__tests__/zero-compression-zero-load.integration.test.d.ts.map +1 -0
  616. package/dist/asset-contributions.d.ts +3 -0
  617. package/dist/asset-contributions.d.ts.map +1 -0
  618. package/dist/backend-selection.d.ts +13 -0
  619. package/dist/backend-selection.d.ts.map +1 -0
  620. package/dist/collect-scene-asset.d.ts +19 -0
  621. package/dist/collect-scene-asset.d.ts.map +1 -0
  622. package/dist/components/__tests__/camera.test-d.d.ts +2 -0
  623. package/dist/components/__tests__/camera.test-d.d.ts.map +1 -0
  624. package/dist/components/__tests__/camera.test.d.ts +2 -0
  625. package/dist/components/__tests__/camera.test.d.ts.map +1 -0
  626. package/dist/components/__tests__/child-of.test.d.ts +2 -0
  627. package/dist/components/__tests__/child-of.test.d.ts.map +1 -0
  628. package/dist/components/__tests__/sprite-animation.test-d.d.ts +2 -0
  629. package/dist/components/__tests__/sprite-animation.test-d.d.ts.map +1 -0
  630. package/dist/components/__tests__/sprite-instances.unit.test.d.ts +2 -0
  631. package/dist/components/__tests__/sprite-instances.unit.test.d.ts.map +1 -0
  632. package/dist/components/__tests__/sprite-region-override.test-d.d.ts +2 -0
  633. package/dist/components/__tests__/sprite-region-override.test-d.d.ts.map +1 -0
  634. package/dist/components/__tests__/tile-layer.test-d.d.ts +2 -0
  635. package/dist/components/__tests__/tile-layer.test-d.d.ts.map +1 -0
  636. package/dist/components/__tests__/tile-layer.unit.test.d.ts +2 -0
  637. package/dist/components/__tests__/tile-layer.unit.test.d.ts.map +1 -0
  638. package/dist/components/__tests__/tilemap.test-d.d.ts +2 -0
  639. package/dist/components/__tests__/tilemap.test-d.d.ts.map +1 -0
  640. package/dist/components/__tests__/transform-vec-schema.test.d.ts +2 -0
  641. package/dist/components/__tests__/transform-vec-schema.test.d.ts.map +1 -0
  642. package/dist/create-renderer-env-classify.d.ts +21 -0
  643. package/dist/create-renderer-env-classify.d.ts.map +1 -0
  644. package/dist/createRenderer.d.ts +15 -0
  645. package/dist/createRenderer.d.ts.map +1 -0
  646. package/dist/dev-import-transport.d.ts +24 -0
  647. package/dist/dev-import-transport.d.ts.map +1 -0
  648. package/dist/errors/environment.d.ts +2 -0
  649. package/dist/errors/environment.d.ts.map +1 -0
  650. package/dist/index.d.ts +218 -0
  651. package/dist/index.d.ts.map +1 -0
  652. package/dist/index.mjs +813 -0
  653. package/dist/index.mjs.map +1 -0
  654. package/dist/renderer-host.d.ts +9 -0
  655. package/dist/renderer-host.d.ts.map +1 -0
  656. package/dist/renderer-host.mjs +62 -0
  657. package/dist/renderer-host.mjs.map +1 -0
  658. package/dist/scene-instances/__tests__/post-spawn-resolve-joints.unit.test.d.ts +2 -0
  659. package/dist/scene-instances/__tests__/post-spawn-resolve-joints.unit.test.d.ts.map +1 -0
  660. package/dist/scene-instances/index.d.ts +2 -0
  661. package/dist/scene-instances/index.d.ts.map +1 -0
  662. package/dist/scene-utils/mount-override-fold.d.ts +15 -0
  663. package/dist/scene-utils/mount-override-fold.d.ts.map +1 -0
  664. package/dist/sprite-param-values.d.ts +62 -0
  665. package/dist/sprite-param-values.d.ts.map +1 -0
  666. package/dist/systems/__tests__/_xorshift.d.ts +28 -0
  667. package/dist/systems/__tests__/_xorshift.d.ts.map +1 -0
  668. package/dist/systems/__tests__/argsort-in-place.test.d.ts +2 -0
  669. package/dist/systems/__tests__/argsort-in-place.test.d.ts.map +1 -0
  670. package/dist/systems/__tests__/transparent-sort.bench.d.ts +2 -0
  671. package/dist/systems/__tests__/transparent-sort.bench.d.ts.map +1 -0
  672. package/dist/systems/sprite-animation-tick.d.ts +50 -0
  673. package/dist/systems/sprite-animation-tick.d.ts.map +1 -0
  674. package/dist/systems/tonemap.d.ts +27 -0
  675. package/dist/systems/tonemap.d.ts.map +1 -0
  676. package/dist/systems/transparent-sort.d.ts +50 -0
  677. package/dist/systems/transparent-sort.d.ts.map +1 -0
  678. package/dist/tilemap-register-validate.d.ts +21 -0
  679. package/dist/tilemap-register-validate.d.ts.map +1 -0
  680. package/package.json +101 -0
  681. package/src/__tests__/active-camera.unit.test.ts +203 -0
  682. package/src/__tests__/animation-graph-add.unit.test.ts +67 -0
  683. package/src/__tests__/animation-graph-asset-union.test-d.ts +83 -0
  684. package/src/__tests__/animation-graph-blend.unit.test.ts +66 -0
  685. package/src/__tests__/animation-graph-build.unit.test.ts +100 -0
  686. package/src/__tests__/animation-graph-clip-missing.unit.test.ts +62 -0
  687. package/src/__tests__/animation-graph-clip.unit.test.ts +77 -0
  688. package/src/__tests__/animation-graph-collect-roundtrip.test.ts +213 -0
  689. package/src/__tests__/animation-graph-default-path.integration.test.ts +65 -0
  690. package/src/__tests__/animation-graph-describe.unit.test.ts +68 -0
  691. package/src/__tests__/animation-graph-errors.unit.test.ts +75 -0
  692. package/src/__tests__/animation-graph-nesting.unit.test.ts +84 -0
  693. package/src/__tests__/animation-graph-orthogonal-weight.unit.test.ts +98 -0
  694. package/src/__tests__/animation-graph-serialize.unit.test.ts +171 -0
  695. package/src/__tests__/animation-graph-slot-fill.unit.test.ts +95 -0
  696. package/src/__tests__/animation-nslot.unit.test.ts +63 -0
  697. package/src/__tests__/animation-slot-length-guard.unit.test.ts +81 -0
  698. package/src/__tests__/append-injection.test.ts +105 -0
  699. package/src/__tests__/asi-world-baseline-export.unit.test.ts +173 -0
  700. package/src/__tests__/asi-world-ci-config.unit.test.ts +186 -0
  701. package/src/__tests__/asi-world-smoke.unit.test.ts +192 -0
  702. package/src/__tests__/asset-brand-and-name-retirement.test.ts +102 -0
  703. package/src/__tests__/asset-error-exhaustive.test-d.ts +62 -0
  704. package/src/__tests__/asset-registry-guid-reverse.test.ts +608 -0
  705. package/src/__tests__/asset-registry-hdr-equirect.test.ts +78 -0
  706. package/src/__tests__/asset-registry-mesh-pack-payload.dawn.test.ts +232 -0
  707. package/src/__tests__/asset-registry-mounts.test.ts +806 -0
  708. package/src/__tests__/asset-registry-scene.test-d.ts +49 -0
  709. package/src/__tests__/asset-registry.browser.test.ts +81 -0
  710. package/src/__tests__/asset-registry.dawn.test.ts +94 -0
  711. package/src/__tests__/asset-registry.listCatalog.test.ts +170 -0
  712. package/src/__tests__/asset-registry.mesh-bin.test.ts +316 -0
  713. package/src/__tests__/asset-registry.pack-cache.test.ts +969 -0
  714. package/src/__tests__/asset-registry.raw-container-failfast.test.ts +225 -0
  715. package/src/__tests__/asset-registry.recursive.spec.ts +1397 -0
  716. package/src/__tests__/asset-registry.unit.test.ts +1030 -0
  717. package/src/__tests__/asset.unit.test.ts +7107 -0
  718. package/src/__tests__/audio-load-by-guid.unit.test.ts +126 -0
  719. package/src/__tests__/backend-selection.integration.test.ts +11 -0
  720. package/src/__tests__/basis-catalog-dispatch.integration.test.ts +320 -0
  721. package/src/__tests__/bind-group-cache-keying.test.ts +295 -0
  722. package/src/__tests__/block-upload-math.unit.test.ts +192 -0
  723. package/src/__tests__/builtin-asset-registry.unit.test.ts +112 -0
  724. package/src/__tests__/builtin-shader-register-e2e.test.ts +151 -0
  725. package/src/__tests__/cache-false-eviction.test.ts +127 -0
  726. package/src/__tests__/cache-isolation-matrix.test.ts +183 -0
  727. package/src/__tests__/camera-clear-snapshot-equivalence.unit.test.ts +75 -0
  728. package/src/__tests__/clamp-to-last.e2e.browser.test.ts +193 -0
  729. package/src/__tests__/clamp-to-last.e2e.dawn.test.ts +325 -0
  730. package/src/__tests__/collect-scene-asset.test.ts +873 -0
  731. package/src/__tests__/collect-scene-mount-collapse.test.ts +391 -0
  732. package/src/__tests__/collect-subtree.unit.test.ts +75 -0
  733. package/src/__tests__/collect-transient-children-absent.test.ts +168 -0
  734. package/src/__tests__/collect-transient-roundtrip.test.ts +269 -0
  735. package/src/__tests__/collect-transient-skip.test.ts +176 -0
  736. package/src/__tests__/components.test-d.ts +231 -0
  737. package/src/__tests__/components.unit.test.ts +2666 -0
  738. package/src/__tests__/composite-skybox-cross-world.browser.test.ts +410 -0
  739. package/src/__tests__/compressed-texture-physical-extent.integration.test.ts +139 -0
  740. package/src/__tests__/create-renderer-assembly.integration.test.ts +55 -0
  741. package/src/__tests__/createRenderer-dispose.test.ts +49 -0
  742. package/src/__tests__/createRenderer-requiredFeatures.unit.test.ts +134 -0
  743. package/src/__tests__/createRenderer.browser-no-webgpu.test.ts +54 -0
  744. package/src/__tests__/createRenderer.unit.test.ts +231 -0
  745. package/src/__tests__/dawn/hello-sprite-nineslice-section.dawn.test.ts +357 -0
  746. package/src/__tests__/dawn/ibl-compose.dawn.test.ts +151 -0
  747. package/src/__tests__/dawn/ibl-modules-compose.dawn.test.ts +223 -0
  748. package/src/__tests__/dawn/ibl-precompute-readback.dawn.test.ts +250 -0
  749. package/src/__tests__/dawn/instances-per-instance-pbr.dawn.test.ts +631 -0
  750. package/src/__tests__/dawn/instances-shadow.dawn.test.ts +454 -0
  751. package/src/__tests__/dawn/instances-uniform-fallback.dawn.test.ts +312 -0
  752. package/src/__tests__/dawn/material-alpha.dawn.test.ts +296 -0
  753. package/src/__tests__/dawn/material-cooked-fixture.dawn.test.ts +79 -0
  754. package/src/__tests__/dawn/mesh-ssbo-normal-matrix.dawn.test.ts +106 -0
  755. package/src/__tests__/dawn/mesh-update-dawn.test.ts +250 -0
  756. package/src/__tests__/dawn/mipmap-srgb-linear.dawn.test.ts +124 -0
  757. package/src/__tests__/dawn/per-entity-material-texture-isolation.dawn.test.ts +555 -0
  758. package/src/__tests__/dawn/sprite-nineslice-mesh-bind.dawn.test.ts +235 -0
  759. package/src/__tests__/dawn/tonemap.dawn.test.ts +107 -0
  760. package/src/__tests__/dawn/upload-texture.dawn.test.ts +198 -0
  761. package/src/__tests__/dawn/user-handle-mesh-render.dawn.test.ts +289 -0
  762. package/src/__tests__/dawn/user-mesh-upload.dawn.test.ts +209 -0
  763. package/src/__tests__/dawn/zero-manifest-mesh-fail.dawn.test.ts +119 -0
  764. package/src/__tests__/dawn/zero-manifest-ready.dawn.test.ts +98 -0
  765. package/src/__tests__/demo-sortscope-migration.unit.test.ts +115 -0
  766. package/src/__tests__/derive-bgl-integration.test.ts +388 -0
  767. package/src/__tests__/derive-render-data-skin.unit.test.ts +90 -0
  768. package/src/__tests__/draw-published.ts +17 -0
  769. package/src/__tests__/dynamic-texture-store.unit.test.ts +190 -0
  770. package/src/__tests__/environment-error-exhaustive.test-d.ts +28 -0
  771. package/src/__tests__/equirect-bc6h.integration.test.ts +158 -0
  772. package/src/__tests__/error-exhaustive.test.ts +220 -0
  773. package/src/__tests__/errors.unit.test.ts +1227 -0
  774. package/src/__tests__/extract-frames-graceful-degradation.test.ts +294 -0
  775. package/src/__tests__/extract-frames-merge.test.ts +656 -0
  776. package/src/__tests__/extract-frames-reset.test.ts +158 -0
  777. package/src/__tests__/extract-record-no-hardcoded-texture-fields.test.ts +81 -0
  778. package/src/__tests__/extract-skin-mismatch.unit.test.ts +322 -0
  779. package/src/__tests__/fetch-binary-decompress.test.ts +356 -0
  780. package/src/__tests__/four-path-compression.integration.test.ts +395 -0
  781. package/src/__tests__/frame-targets-shadow-regression.unit.test.ts +28 -0
  782. package/src/__tests__/fullscreen-feature-host.dawn.test.ts +80 -0
  783. package/src/__tests__/fullscreen-post-bgl-depth.test.ts +133 -0
  784. package/src/__tests__/fullscreen-post-depth-params-arity.test.ts +84 -0
  785. package/src/__tests__/fullscreen-post-process-pass.dawn.test.ts +336 -0
  786. package/src/__tests__/fullscreen-post-process-pass.unit.test.ts +109 -0
  787. package/src/__tests__/fxaa-pixel-diff.dawn.test.ts +303 -0
  788. package/src/__tests__/fxaa-validation-and-resize.dawn.test.ts +369 -0
  789. package/src/__tests__/fxaa-zero-overhead.dawn.test.ts +258 -0
  790. package/src/__tests__/geometry.unit.test.ts +2631 -0
  791. package/src/__tests__/glyph-tilemap-worldid.test.ts +146 -0
  792. package/src/__tests__/gpu-resource-cow-survivor.test.ts +390 -0
  793. package/src/__tests__/guid-for-asset.unit.test.ts +102 -0
  794. package/src/__tests__/handle-migration.test.ts +136 -0
  795. package/src/__tests__/hello-tilemap-smoke-upgrade.unit.test.ts +147 -0
  796. package/src/__tests__/helpers/mock-shader-registry.ts +139 -0
  797. package/src/__tests__/helpers/register-runtime-components.ts +71 -0
  798. package/src/__tests__/helpers/register-scene-components.ts +10 -0
  799. package/src/__tests__/helpers/tilemap-assets.ts +29 -0
  800. package/src/__tests__/host-custom-kind-contract.test-d.ts +47 -0
  801. package/src/__tests__/host-custom-kind-contract.test.ts +578 -0
  802. package/src/__tests__/inspect-name.test.ts +139 -0
  803. package/src/__tests__/instances.test-d.ts +61 -0
  804. package/src/__tests__/instantiate-mount-deferred-wiring.test.ts +112 -0
  805. package/src/__tests__/light-attenuation-formula.test.ts +171 -0
  806. package/src/__tests__/light-casters-9-light.browser.test.ts +877 -0
  807. package/src/__tests__/light-gpu-byte-neutral.unit.test.ts +160 -0
  808. package/src/__tests__/light-snapshot.test-d.ts +84 -0
  809. package/src/__tests__/lights-vec-negative.unit.test.ts +52 -0
  810. package/src/__tests__/lights.unit.test.ts +2456 -0
  811. package/src/__tests__/loadbyguid-payload.unit.test.ts +117 -0
  812. package/src/__tests__/loadbyguid-texture-intern.unit.test.ts +213 -0
  813. package/src/__tests__/loader-extract-graceful.test.ts +146 -0
  814. package/src/__tests__/loader-name-survival.test.ts +213 -0
  815. package/src/__tests__/m3-vec-negative.unit.test.ts +46 -0
  816. package/src/__tests__/m5-browser-dawn-sweep.integration.test.ts +8 -0
  817. package/src/__tests__/material-texture-uv-scale.unit.test.ts +119 -0
  818. package/src/__tests__/materials.unit.test.ts +254 -0
  819. package/src/__tests__/mesh-bin-contract.test.ts +28 -0
  820. package/src/__tests__/mesh-bin-skin-roundtrip.unit.test.ts +149 -0
  821. package/src/__tests__/mesh-bin.test.ts +38 -0
  822. package/src/__tests__/mesh-loader-skin-contract.unit.test.ts +120 -0
  823. package/src/__tests__/mesh-ssbo-grow.unit.test.ts +244 -0
  824. package/src/__tests__/mip-gate.unit.test.ts +79 -0
  825. package/src/__tests__/msaa-sprite-pixel-diff.dawn.test.ts +367 -0
  826. package/src/__tests__/multi-uv-8set.e2e.dawn.test.ts +296 -0
  827. package/src/__tests__/name.unit.test.ts +123 -0
  828. package/src/__tests__/new-kind-refs-contract.test.ts +132 -0
  829. package/src/__tests__/node-shims.d.ts +31 -0
  830. package/src/__tests__/package-promotion.test.ts +88 -0
  831. package/src/__tests__/package-rename.test.ts +103 -0
  832. package/src/__tests__/pass-selector.unit.test.ts +102 -0
  833. package/src/__tests__/pbr-pipeline.unit.test.ts +200 -0
  834. package/src/__tests__/pipeline-builder-msaa.unit.test.ts +221 -0
  835. package/src/__tests__/pipeline-cache-keying.unit.test.ts +313 -0
  836. package/src/__tests__/pipeline-spec.test.ts +900 -0
  837. package/src/__tests__/pipeline.unit.test.ts +5435 -0
  838. package/src/__tests__/point-light-shadow.browser.test.ts +278 -0
  839. package/src/__tests__/point-light-shadow.dawn.test.ts +813 -0
  840. package/src/__tests__/point-light-shadow.unit.test.ts +675 -0
  841. package/src/__tests__/post-process-error-codes.unit.test.ts +161 -0
  842. package/src/__tests__/post-process-params-component.unit.test.ts +153 -0
  843. package/src/__tests__/post-process-params-extract.unit.test.ts +97 -0
  844. package/src/__tests__/postprocess-camera-consistency.test.ts +375 -0
  845. package/src/__tests__/profiler-assembly.integration.test.ts +74 -0
  846. package/src/__tests__/propagate-transforms.dawn.test.ts +254 -0
  847. package/src/__tests__/record-worldid-isolation.test.ts +225 -0
  848. package/src/__tests__/recover-error-exhaustive.test-d.ts +35 -0
  849. package/src/__tests__/render-error-exhaustive.test-d.ts +291 -0
  850. package/src/__tests__/render-feature-assembly.test-d.ts +39 -0
  851. package/src/__tests__/render-feature-prepared-graphics-docs.unit.test.ts +126 -0
  852. package/src/__tests__/render-feature-prepared-graphics.browser.test.ts +88 -0
  853. package/src/__tests__/render-feature-prepared-graphics.dawn.test.ts +115 -0
  854. package/src/__tests__/render-feature-prepared-graphics.fixture.ts +104 -0
  855. package/src/__tests__/render-feature-prepared-graphics.integration.test.ts +56 -0
  856. package/src/__tests__/render-feature-prepared-graphics.regression.integration.test.ts +104 -0
  857. package/src/__tests__/render-feature-recover.integration.test.ts +102 -0
  858. package/src/__tests__/render-feature-runtime-surface.test-d.ts +42 -0
  859. package/src/__tests__/render-schema-consumer.integration.test.ts +32 -0
  860. package/src/__tests__/render-schema-owner-boundary.unit.test.ts +22 -0
  861. package/src/__tests__/render-system-cross-world-overlay-queue.test.ts +172 -0
  862. package/src/__tests__/render-system-extract-dispatch-ordering.test.ts +307 -0
  863. package/src/__tests__/render-system-extract.test.ts +1384 -0
  864. package/src/__tests__/render-system-extract.unit.test.ts +360 -0
  865. package/src/__tests__/render-system-fold-bucket.unit.test.ts +286 -0
  866. package/src/__tests__/render-system-fold-fallback.unit.test.ts +151 -0
  867. package/src/__tests__/render-system-fold-material.unit.test.ts +143 -0
  868. package/src/__tests__/render-system-fold-metrics.unit.test.ts +170 -0
  869. package/src/__tests__/render-system-fold-modegate.unit.test.ts +166 -0
  870. package/src/__tests__/render-system-fold-scene-instances.unit.test.ts +51 -0
  871. package/src/__tests__/render-system-fold.dawn.test.ts +308 -0
  872. package/src/__tests__/render-system-mega.test.ts +1680 -0
  873. package/src/__tests__/render-system-owner-split-contract.test.ts +178 -0
  874. package/src/__tests__/render-system-record-multi-material-textureview.test.ts +698 -0
  875. package/src/__tests__/render-system-record-per-submesh-transparency.test.ts +458 -0
  876. package/src/__tests__/render-system-record.test.ts +1668 -0
  877. package/src/__tests__/render-system-record.unit.test.ts +269 -0
  878. package/src/__tests__/render-system-skin-bg.unit.test.ts +143 -0
  879. package/src/__tests__/renderer-consumer.test-d.ts +18 -0
  880. package/src/__tests__/renderer-health.unit.test.ts +45 -0
  881. package/src/__tests__/renderer-lifecycle.integration.test.ts +42 -0
  882. package/src/__tests__/renderer-recover.unit.test.ts +55 -0
  883. package/src/__tests__/renderer-result.test-d.ts +36 -0
  884. package/src/__tests__/renderer-surface.unit.test.ts +414 -0
  885. package/src/__tests__/renderer-test-utils.ts +56 -0
  886. package/src/__tests__/renderer.test-d.ts +61 -0
  887. package/src/__tests__/resolve-asset-handle-err.test.ts +111 -0
  888. package/src/__tests__/resolve-asset-handle.unit.test.ts +64 -0
  889. package/src/__tests__/resolve-depth-only-view.test.ts +173 -0
  890. package/src/__tests__/resolveName-fallback.test.ts +64 -0
  891. package/src/__tests__/resolveName-null-package.test.ts +38 -0
  892. package/src/__tests__/resolveName-unified-entry.test.ts +65 -0
  893. package/src/__tests__/resolveName.test.ts +64 -0
  894. package/src/__tests__/rhi-null-command-flow.unit.test.ts +69 -0
  895. package/src/__tests__/rhi-null-noop-behavior.unit.test.ts +196 -0
  896. package/src/__tests__/rhi-null-renderer-lifecycle.unit.test.ts +54 -0
  897. package/src/__tests__/roots-to-scene-asset.test.ts +711 -0
  898. package/src/__tests__/roundtrip-primitive.test.ts +351 -0
  899. package/src/__tests__/runtime-authority-scan.unit.test.ts +19 -0
  900. package/src/__tests__/runtime-domain-residue.unit.test.ts +13 -0
  901. package/src/__tests__/scene-bare-guid-refs-instantiate.test.ts +123 -0
  902. package/src/__tests__/scene-equirect-roundtrip.test.ts +120 -0
  903. package/src/__tests__/scene-instance-authoring.integration.test.ts +8 -0
  904. package/src/__tests__/scene-instance-bridge.integration.test.ts +8 -0
  905. package/src/__tests__/scene-instance-component.test.ts +45 -0
  906. package/src/__tests__/scene-instantiate-cycle-runtime.test.ts +66 -0
  907. package/src/__tests__/scene-mount-fixed-point.test.ts +257 -0
  908. package/src/__tests__/scene-mount-roundtrip.test.ts +987 -0
  909. package/src/__tests__/scene-mount-wrapper-roundtrip.test.ts +202 -0
  910. package/src/__tests__/scene-sugar-instantiate.test.ts +324 -0
  911. package/src/__tests__/serialize-mounts-roundtrip.test.ts +533 -0
  912. package/src/__tests__/serialize-scene-asset.test.ts +533 -0
  913. package/src/__tests__/shadow-caster-empty-schema.test.ts +83 -0
  914. package/src/__tests__/shadow-csm-atlas.test.ts +178 -0
  915. package/src/__tests__/shadow-csm-blend.dawn.test.ts +230 -0
  916. package/src/__tests__/shadow-csm-cascade-loadop.test.ts +354 -0
  917. package/src/__tests__/shadow-csm-component.test.ts +284 -0
  918. package/src/__tests__/shadow-csm-extract.test.ts +236 -0
  919. package/src/__tests__/shadow-csm-orthoz.test.ts +137 -0
  920. package/src/__tests__/shadow-csm-runtime-vary.dawn.test.ts +203 -0
  921. package/src/__tests__/shadow-csm-shader.dawn.test.ts +358 -0
  922. package/src/__tests__/shadow-csm-tile-consistency.test.ts +425 -0
  923. package/src/__tests__/shadow-csm-ubo.dawn.test.ts +252 -0
  924. package/src/__tests__/shadow-csm-ubo.test.ts +278 -0
  925. package/src/__tests__/shadow-csm-viewport.browser.test.ts +85 -0
  926. package/src/__tests__/shadow-fields-observable.dawn.test.ts +626 -0
  927. package/src/__tests__/shadow-m1.dawn.test.ts +104 -0
  928. package/src/__tests__/shadow-mesh-ssbo-order.test.ts +48 -0
  929. package/src/__tests__/skin-error-exhaustive.test-d.ts +74 -0
  930. package/src/__tests__/skin-errors.unit.test.ts +10 -0
  931. package/src/__tests__/skin-extract-getarrayview-count.unit.test.ts +25 -0
  932. package/src/__tests__/skin-parented-double-transform.test.ts +268 -0
  933. package/src/__tests__/skylight-lazy-projection.test.ts +380 -0
  934. package/src/__tests__/sparse-tag-scene-roundtrip.test.ts +43 -0
  935. package/src/__tests__/sprite-cullmode-flip.dawn.test.ts +453 -0
  936. package/src/__tests__/sprite-lit-bgl-byte-identical.test.ts +142 -0
  937. package/src/__tests__/sprite-lit-extract-branch.test.ts +117 -0
  938. package/src/__tests__/sprite-lit-pipeline-cache-key.test.ts +123 -0
  939. package/src/__tests__/sprite-param-values.test-d.ts +104 -0
  940. package/src/__tests__/ssao-bgl.test.ts +354 -0
  941. package/src/__tests__/ssao-buffers.test.ts +211 -0
  942. package/src/__tests__/ssao-data.test.ts +121 -0
  943. package/src/__tests__/ssao-errors.test.ts +162 -0
  944. package/src/__tests__/ssao-manifest.test.ts +109 -0
  945. package/src/__tests__/ssao-passes.test.ts +905 -0
  946. package/src/__tests__/ssao-pipelines.test.ts +275 -0
  947. package/src/__tests__/ssao-uniform-layout.test.ts +180 -0
  948. package/src/__tests__/static-rig.integration.test.ts +9 -0
  949. package/src/__tests__/stencil-outline-pixel.dawn.test.ts +303 -0
  950. package/src/__tests__/swap-chain-format.unit.test.ts +115 -0
  951. package/src/__tests__/systems.unit.test.ts +9167 -0
  952. package/src/__tests__/template-game-default-api-contract.test.ts +362 -0
  953. package/src/__tests__/text.unit.test.ts +489 -0
  954. package/src/__tests__/tilemap-childed-despawn.test.ts +462 -0
  955. package/src/__tests__/tilemap-chunk-extract.overhang-bounds.test.ts +310 -0
  956. package/src/__tests__/tilemap-chunk-extract.propagate-order.test.ts +81 -0
  957. package/src/__tests__/tilemap-chunk-extract.terrain-rewrite.unit.test.ts +424 -0
  958. package/src/__tests__/tilemap-chunk-extract.test.ts +232 -0
  959. package/src/__tests__/tilemap-chunk-extract.unit.test.ts +383 -0
  960. package/src/__tests__/tilemap-chunk-y-sort-bench.unit.test.ts +109 -0
  961. package/src/__tests__/tilemap-dirty-rebuild.test.ts +296 -0
  962. package/src/__tests__/tilemap-material-cache.test.ts +132 -0
  963. package/src/__tests__/tilemap-multi-atlas.test.ts +217 -0
  964. package/src/__tests__/tilemap-owner-boundary.unit.test.ts +66 -0
  965. package/src/__tests__/tilemap-region-pack.test.ts +149 -0
  966. package/src/__tests__/tilemap-spawn-flip-pivot.test.ts +270 -0
  967. package/src/__tests__/tilemap-uv-inset.test.ts +201 -0
  968. package/src/__tests__/tilemap-validate.test.ts +201 -0
  969. package/src/__tests__/tileset-asset-validate.test.ts +638 -0
  970. package/src/__tests__/transcode-fallback.unit.test.ts +207 -0
  971. package/src/__tests__/transform-hierarchy-pixel-diff.dawn.test.ts +282 -0
  972. package/src/__tests__/transient-fields-collect.test.ts +210 -0
  973. package/src/__tests__/transient-runtime-behavior.test.ts +227 -0
  974. package/src/__tests__/transitional-authoring.integration.test.ts +7 -0
  975. package/src/__tests__/transparent-sort-tilemap-sprite-interleave.test.ts +227 -0
  976. package/src/__tests__/variant-selection.unit.test.ts +21 -0
  977. package/src/__tests__/vertex-attribute-layout.test.ts +324 -0
  978. package/src/__tests__/video-extract-bindgroup.dawn.test.ts +403 -0
  979. package/src/__tests__/video-loader-consumption.test.ts +86 -0
  980. package/src/__tests__/video-loader-smoke.dawn.test.ts +60 -0
  981. package/src/__tests__/view-ubo-layout.browser.test.ts +161 -0
  982. package/src/__tests__/world-entity-key.test.ts +92 -0
  983. package/src/__tests__/world-scene-despawn.test.ts +132 -0
  984. package/src/__tests__/world-scene-instantiate.test.ts +194 -0
  985. package/src/__tests__/world-scene-nested.test.ts +230 -0
  986. package/src/__tests__/world-scene-override.test.ts +199 -0
  987. package/src/__tests__/zero-compression-zero-load.integration.test.ts +107 -0
  988. package/src/asset-contributions.ts +48 -0
  989. package/src/backend-selection.ts +69 -0
  990. package/src/collect-scene-asset.ts +701 -0
  991. package/src/components/__tests__/camera.test-d.ts +57 -0
  992. package/src/components/__tests__/camera.test.ts +95 -0
  993. package/src/components/__tests__/child-of.test.ts +35 -0
  994. package/src/components/__tests__/sprite-animation.test-d.ts +160 -0
  995. package/src/components/__tests__/sprite-instances.unit.test.ts +108 -0
  996. package/src/components/__tests__/sprite-region-override.test-d.ts +101 -0
  997. package/src/components/__tests__/tile-layer.test-d.ts +33 -0
  998. package/src/components/__tests__/tile-layer.unit.test.ts +125 -0
  999. package/src/components/__tests__/tilemap.test-d.ts +35 -0
  1000. package/src/components/__tests__/transform-vec-schema.test.ts +121 -0
  1001. package/src/create-renderer-env-classify.ts +110 -0
  1002. package/src/createRenderer.ts +49 -0
  1003. package/src/dev-import-transport.ts +97 -0
  1004. package/src/errors/environment.ts +6 -0
  1005. package/src/index.ts +380 -0
  1006. package/src/renderer-host.ts +33 -0
  1007. package/src/scene-instances/__tests__/post-spawn-resolve-joints.unit.test.ts +183 -0
  1008. package/src/scene-instances/index.ts +4 -0
  1009. package/src/scene-utils/mount-override-fold.ts +213 -0
  1010. package/src/sprite-param-values.ts +61 -0
  1011. package/src/systems/__tests__/_xorshift.ts +83 -0
  1012. package/src/systems/__tests__/argsort-in-place.test.ts +155 -0
  1013. package/src/systems/__tests__/transparent-sort.bench.ts +218 -0
  1014. package/src/systems/sprite-animation-tick.ts +302 -0
  1015. package/src/systems/tonemap.ts +68 -0
  1016. package/src/systems/transparent-sort.ts +434 -0
  1017. package/src/tilemap-register-validate.ts +100 -0
@@ -0,0 +1,2666 @@
1
+ // biome-ignore-all lint/complexity/noUselessLoneBlockStatements: block-scope isolation between merged source files (consolidation paradigm)
2
+ // Consolidated by feat-20260609-test-pool-startup-reduction-merge-tiny-test-files
3
+ //
4
+ // Source files (N=21):
5
+ // - packages/runtime/src/__tests__/children.test.ts
6
+ // - packages/runtime/src/__tests__/components.test.ts
7
+ // - packages/runtime/src/__tests__/hierarchy-components.test.ts
8
+ // - packages/runtime/src/__tests__/inspector-frustum-stats.test.ts
9
+ // - packages/runtime/src/__tests__/layer-component.test.ts
10
+ // - packages/runtime/src/__tests__/mesh-renderer-pickable.test.ts
11
+ // - packages/runtime/src/__tests__/pick.test.ts
12
+ // - packages/runtime/src/__tests__/register-inspector.test.ts
13
+ // - packages/runtime/src/__tests__/relationship-migration-regression.test.ts
14
+ // - packages/runtime/src/__tests__/scene-defaults.test.ts
15
+ // - packages/runtime/src/__tests__/sort-key-component.test.ts
16
+ // - packages/runtime/src/components/__tests__/animation-player.test.ts
17
+ // - packages/runtime/src/components/__tests__/camera.test.ts
18
+ // - packages/runtime/src/components/__tests__/layer.test.ts
19
+ // - packages/runtime/src/components/__tests__/mesh-renderer.test.ts
20
+ // - packages/runtime/src/components/__tests__/skybox-background.test.ts
21
+ // - packages/runtime/src/components/__tests__/sort-key.test.ts
22
+ // - packages/runtime/src/components/__tests__/sprite-components-schema.test.ts
23
+ // - packages/runtime/src/components/__tests__/sprite-playback-mode.test.ts
24
+ // - packages/runtime/src/components/__tests__/transform.test.ts
25
+ // - packages/runtime/src/__tests__/mesh-renderer-multi-material.test.ts
26
+ //
27
+ // Paradigm: each block-scoped describe('<source-filename>.test.ts', ...) preserves
28
+ // source as ancestorTitles[0]. Top-level imports merged + deduped.
29
+
30
+ import { AnimationPlayer } from '@forgeax/engine-animation';
31
+ import { AssetRegistry } from '@forgeax/engine-assets-runtime';
32
+ import {
33
+ type Component,
34
+ defineComponent,
35
+ ENTITY_NULL_RAW,
36
+ type EntityHandle,
37
+ World,
38
+ } from '@forgeax/engine-ecs';
39
+ import { componentDefinition, componentId, componentSchema } from '@forgeax/engine-ecs/internal';
40
+ import {
41
+ ANTIALIAS_NONE,
42
+ BLOOM_DISABLED,
43
+ CAMERA_PROJECTION_ORTHOGRAPHIC,
44
+ CAMERA_PROJECTION_PERSPECTIVE,
45
+ Camera,
46
+ DirectionalLight,
47
+ Layer,
48
+ MeshFilter,
49
+ MeshRenderer,
50
+ orthographic,
51
+ perspective,
52
+ SceneInstance,
53
+ SKYBOX_MODE_CUBEMAP,
54
+ SkyboxBackground,
55
+ type SkyboxMode,
56
+ SortKey,
57
+ TONEMAP_NONE,
58
+ TONEMAP_REINHARD,
59
+ TONEMAP_REINHARD_EXTENDED,
60
+ } from '@forgeax/engine-render';
61
+ import {
62
+ GlyphText,
63
+ SpriteAnimation,
64
+ SpriteRegionOverride,
65
+ Tilemap,
66
+ } from '@forgeax/engine-render/authoring';
67
+ import * as SceneOwner from '@forgeax/engine-scene';
68
+ import { ChildOf, Children, propagateTransforms, Transform } from '@forgeax/engine-scene';
69
+ import type { Handle, LocalEntityId, SceneAsset, SceneEntity } from '@forgeax/engine-types';
70
+ import { toShared } from '@forgeax/engine-types';
71
+ import { describe, expect, expectTypeOf, it } from 'vitest';
72
+ import { tonemapFromF32 } from '../../../render/src/components/camera';
73
+ import { skyboxModeFromF32 } from '../../../render/src/components/skybox-background';
74
+ import {
75
+ SPRITE_PLAYBACK_MODE_CLAMP,
76
+ SPRITE_PLAYBACK_MODE_LOOP,
77
+ type SpritePlaybackMode,
78
+ spritePlaybackModeFromU32,
79
+ } from '../../../render/src/components/sprite-playback-mode';
80
+ import { extractFrame, prepareExtractContext } from '../../../render/src/render-system-extract';
81
+
82
+ {
83
+ // --- from children.test.ts ---
84
+ // w13 - Children component schema migration to `array<entity>` (M3).
85
+ //
86
+ // Locks AC-05 (requirements.md): the `entities: 'array<entity>'` field
87
+ // resolves to a fresh `Uint32Array` snapshot at the get-site, and the
88
+ // snapshot length reflects the live element count after
89
+ // `world.push(parent, Children, 'entities', child)` /
90
+ // `world.pop(parent, Children, 'entities')` mutation routes through the
91
+ // M0/M1/M2 BufferPool slot + sidecar count column infrastructure.
92
+ //
93
+ // feat-20260515-buffer-array-vocab-collapse M3 / w17: rewritten for the
94
+ // collapsed-vocab API surface -- the `VarArrayView<Entity>` wrapper was
95
+ // retired (M2 / w10). AI users mutate via the three `world` commands and
96
+ // read through the read-only `Uint32Array` snapshot; `snap.length` is the
97
+ // live count, `snap[i]` is the packed Entity u32.
98
+ //
99
+ // Test path note: project convention places runtime tests under
100
+ // `src/__tests__/` (TS rootDir = `./src`). The plan-tasks.json target path
101
+ // `packages/runtime/src/components/__tests__/children.test.ts` lives outside
102
+ // rootDir and would not be picked up by `tsc -b`. The test file therefore
103
+ // lands under `src/__tests__/` to match the existing
104
+ // `hierarchy-components.test.ts` convention.
105
+
106
+ describe('w13 - Children { entities: array<entity> } schema (AC-05)', () => {
107
+ it('componentSchema(Children).entities is the array<entity> keyword (no legacy `count` field)', () => {
108
+ expect(Children.name).toBe('Children');
109
+ expect(Object.keys(componentSchema(Children)).length).toBe(1);
110
+ expect((componentSchema(Children) as Record<string, unknown>).entities).toBe('array<entity>');
111
+ expect((componentSchema(Children) as Record<string, unknown>).count).toBeUndefined();
112
+ });
113
+
114
+ it('world.get(e, Children).entities is a Uint32Array snapshot with length reflecting initial payload', () => {
115
+ const world = new World();
116
+ world.components.register(ChildOf).unwrap();
117
+ world.components.register(Children).unwrap();
118
+ const parent = world.spawn().unwrap();
119
+ const a = world.spawn({ component: ChildOf, data: { parent } }).unwrap();
120
+ const b = world.spawn({ component: ChildOf, data: { parent } }).unwrap();
121
+ const got = world.get(parent, Children).unwrap();
122
+ // `Children` is an engine-maintained materialized target. Its snapshot
123
+ // is read-only; source-side ChildOf writes are the only mutation route.
124
+ expect(got.entities.length).toBe(2);
125
+ expect(got.entities[0]).toBe(a);
126
+ expect(got.entities[1]).toBe(b);
127
+ });
128
+ });
129
+ }
130
+
131
+ {
132
+ // --- from components.test.ts ---
133
+ // w7 - 5 component schema runtime registration + multi-component spawn (TDD red).
134
+ //
135
+ // Locks plan-strategy 7.2 naming + requirements IN-1 schema field set:
136
+ // Transform: pos:[f32x3] + quat:[f32x4] + scale:[f32x3] = 10 f32
137
+ // MeshFilter: assetHandle:'shared<MeshAsset>' (u32)
138
+ // MeshRenderer: materials:'array<shared<MaterialAsset>>' (u32)
139
+ // Camera: fov:f32 + aspect:f32 + near:f32 + far:f32 = 4 f32
140
+ // DirectionalLight: direction:[f32x3] + color:[f32x3] + intensity:f32 = 7 f32
141
+ //
142
+ // Storage shape: forgeax ECS columns store scalar fields as flat scalars
143
+ // (`Float32Array` / `Uint32Array`) and vector / quaternion fields as inline
144
+ // fixed-length array columns (`array<f32, N>`, e.g. `pos: [x, y, z]`).
145
+ // The 5-component schema picks names that AI users see in `world.get(e, T)`
146
+ // and `data: {...}` shapes.
147
+ //
148
+ // charter mapping: proposition 1 (single import + LSP hover discoverability) +
149
+ // proposition 3 (machine-readable schema > prose) + proposition 5 (consistent
150
+ // abstraction: components are flat-scalar SoA columns, not nested objects).
151
+
152
+ describe('w7 - 5 component schemas register through defineComponent', () => {
153
+ it('Transform has pos/quat/scale inline array fields + world array<f32,16>', () => {
154
+ expect(Transform.name).toBe('Transform');
155
+ expect(Object.keys(componentSchema(Transform)).length).toBe(4);
156
+ // pos: inline [x, y, z]
157
+ expect(componentSchema(Transform).pos).toBe('array<f32, 3>');
158
+ // quat: inline [x, y, z, w] quaternion
159
+ expect(componentSchema(Transform).quat).toBe('array<f32, 4>');
160
+ // scale: inline [x, y, z]
161
+ expect(componentSchema(Transform).scale).toBe('array<f32, 3>');
162
+ // world: resolved mat4 (column-major 16 floats)
163
+ expect(componentSchema(Transform).world).toBe('array<f32, 16>');
164
+ });
165
+
166
+ it('MeshFilter has 1 shared<MeshAsset> field (assetHandle; M5 / w14)', () => {
167
+ expect(MeshFilter.name).toBe('MeshFilter');
168
+ expect(Object.keys(componentSchema(MeshFilter)).length).toBe(1);
169
+ expect(componentSchema(MeshFilter).assetHandle).toBe('shared<MeshAsset>');
170
+ });
171
+
172
+ it('MeshRenderer has 1 field (materials; feat-20260608 M2 / w7 multi-material array)', () => {
173
+ expect(MeshRenderer.name).toBe('MeshRenderer');
174
+ expect(Object.keys(componentSchema(MeshRenderer)).length).toBe(1);
175
+ const schemaRecord = componentSchema(MeshRenderer) as Record<string, string>;
176
+ expect(schemaRecord.materials).toBe('array<shared<MaterialAsset>>');
177
+ });
178
+
179
+ it('Camera has 20 fields (17 f32 + historyVersion u32 + clearColor array<f32,4> + autoAspect bool)', () => {
180
+ expect(Camera.name).toBe('Camera');
181
+ expect(Object.keys(componentSchema(Camera)).length).toBe(20);
182
+ expect(componentSchema(Camera).fov).toBe('f32');
183
+ expect(componentSchema(Camera).aspect).toBe('f32');
184
+ expect(componentSchema(Camera).near).toBe('f32');
185
+ expect(componentSchema(Camera).far).toBe('f32');
186
+ expect(componentSchema(Camera).projection).toBe('f32');
187
+ expect(componentSchema(Camera).left).toBe('f32');
188
+ expect(componentSchema(Camera).right).toBe('f32');
189
+ expect(componentSchema(Camera).bottom).toBe('f32');
190
+ expect(componentSchema(Camera).top).toBe('f32');
191
+ expect(componentSchema(Camera).historyVersion).toBe('u32');
192
+ // feat-20260519-tonemap-reinhard-mvp / M1 / T-M1.2: AC-01 + D-1.
193
+ expect(componentSchema(Camera).tonemap).toBe('f32');
194
+ expect(componentSchema(Camera).exposure).toBe('f32');
195
+ expect(componentSchema(Camera).whitePoint).toBe('f32');
196
+ expect(componentSchema(Camera).antialias).toBe('f32');
197
+ // feat-20260531-bloom-first-declarative-render-graph-pass / w2.
198
+ expect(componentSchema(Camera).bloom).toBe('f32');
199
+ expect(componentSchema(Camera).bloomThreshold).toBe('f32');
200
+ expect(componentSchema(Camera).bloomIntensity).toBe('f32');
201
+ expect(componentSchema(Camera).bloomBlurRadius).toBe('f32');
202
+ // feat-20260709 M3: clear-color quartet collapsed into one inline
203
+ // array<f32,4> column (clearColor); per-axis scalars are gone.
204
+ expect(componentSchema(Camera).clearColor).toBe('array<f32, 4>');
205
+ expect('clearR' in componentSchema(Camera)).toBe(false);
206
+ expect('clearG' in componentSchema(Camera)).toBe(false);
207
+ expect('clearB' in componentSchema(Camera)).toBe(false);
208
+ expect('clearA' in componentSchema(Camera)).toBe(false);
209
+ // feat-20260617-host-engine-contract-and-video-cutscene / M3: aspect-sync
210
+ // opt-out flag (bool column tier, not f32).
211
+ expect(componentSchema(Camera).autoAspect).toBe('bool');
212
+ });
213
+
214
+ it('DirectionalLight has 12 fields: 3 light + castShadow bool + 8 merged shadow f32', () => {
215
+ // feat-20260621: DirectionalLightShadow merged into DirectionalLight via castShadow toggle.
216
+ // shadowDistance replaced the nearPlane/farPlane pair (near derives from camera).
217
+ // feat-20260709 M2: direction/color collapsed from 6 per-axis scalars to
218
+ // two array<f32,3> columns (3 light fields: direction + color + intensity).
219
+ expect(DirectionalLight.name).toBe('DirectionalLight');
220
+ expect(Object.keys(componentSchema(DirectionalLight)).length).toBe(12);
221
+ // 3 light fields
222
+ expect(componentSchema(DirectionalLight).direction).toBe('array<f32, 3>');
223
+ expect(componentSchema(DirectionalLight).color).toBe('array<f32, 3>');
224
+ expect(componentSchema(DirectionalLight).intensity).toBe('f32');
225
+ // shadow gate + 8 merged shadow fields
226
+ expect(componentSchema(DirectionalLight).castShadow).toBe('bool');
227
+ expect(componentSchema(DirectionalLight).mapSize).toBe('f32');
228
+ expect(componentSchema(DirectionalLight).cascadeCount).toBe('f32');
229
+ expect(componentSchema(DirectionalLight).splitLambda).toBe('f32');
230
+ expect(componentSchema(DirectionalLight).cascadeBlend).toBe('f32');
231
+ expect(componentSchema(DirectionalLight).depthBias).toBe('f32');
232
+ expect(componentSchema(DirectionalLight).normalBias).toBe('f32');
233
+ expect(componentSchema(DirectionalLight).shadowDistance).toBe('f32');
234
+ expect(componentSchema(DirectionalLight).pcfKernelSize).toBe('f32');
235
+ });
236
+
237
+ it('all 5 components are frozen tokens with owner-assigned identities', () => {
238
+ expect(Object.isFrozen(Transform)).toBe(true);
239
+ expect(Object.isFrozen(MeshFilter)).toBe(true);
240
+ expect(Object.isFrozen(MeshRenderer)).toBe(true);
241
+ expect(Object.isFrozen(Camera)).toBe(true);
242
+ expect(Object.isFrozen(DirectionalLight)).toBe(true);
243
+ for (const t of [Transform, MeshFilter, MeshRenderer, Camera, DirectionalLight]) {
244
+ expect(typeof componentId(t)).toBe('number');
245
+ expect(componentId(t)).toBeGreaterThanOrEqual(0);
246
+ }
247
+ });
248
+ });
249
+
250
+ describe('w7 - world.spawn({ component, data }) accepts each of the 5 components', () => {
251
+ it('spawn Transform succeeds and stores the pos/quat/scale values', () => {
252
+ const world = new World();
253
+ const e = world
254
+ .spawn({
255
+ component: Transform,
256
+ data: { pos: [1, 2, 3], quat: [0, 0, 0, 1], scale: [1, 1, 1] },
257
+ })
258
+ .unwrap();
259
+ const r = world.get(e, Transform).unwrap();
260
+ expect(Array.from(r.pos)).toEqual([1, 2, 3]);
261
+ expect(Array.from(r.quat)).toEqual([0, 0, 0, 1]);
262
+ expect(Array.from(r.scale)).toEqual([1, 1, 1]);
263
+ });
264
+
265
+ it('spawn MeshFilter with HANDLE_CUBE-style u32 succeeds', () => {
266
+ const world = new World();
267
+ const e = world
268
+ .spawn({
269
+ component: MeshFilter,
270
+ data: { assetHandle: 1 as Handle<'MeshAsset', 'shared'> },
271
+ })
272
+ .unwrap();
273
+ const r = world.get(e, MeshFilter).unwrap();
274
+ expect(r.assetHandle).toBe(1);
275
+ });
276
+
277
+ it('spawn MeshRenderer with a branded Handle<MaterialAsset> succeeds', () => {
278
+ const world = new World();
279
+ const e = world
280
+ .spawn({
281
+ component: MeshRenderer,
282
+ data: {
283
+ materials: [7 as Handle<'MaterialAsset', 'shared'>],
284
+ },
285
+ })
286
+ .unwrap();
287
+ const r = world.get(e, MeshRenderer).unwrap();
288
+ expect(r.materials[0]).toBe(7);
289
+ });
290
+
291
+ it('spawn Camera with perspective parameters succeeds', () => {
292
+ const world = new World();
293
+ const e = world
294
+ .spawn({
295
+ component: Camera,
296
+ data: {
297
+ fov: Math.PI / 4,
298
+ aspect: 16 / 9,
299
+ near: 0.1,
300
+ far: 100,
301
+ projection: 0,
302
+ left: -1,
303
+ right: 1,
304
+ bottom: -1,
305
+ top: 1,
306
+ },
307
+ })
308
+ .unwrap();
309
+ const r = world.get(e, Camera).unwrap();
310
+ expect(r.fov).toBeCloseTo(Math.PI / 4, 5);
311
+ expect(r.far).toBe(100);
312
+ });
313
+
314
+ it('spawn DirectionalLight succeeds', () => {
315
+ const world = new World();
316
+ const e = world
317
+ .spawn({
318
+ component: DirectionalLight,
319
+ data: {
320
+ direction: [-0.5, -1, -0.3],
321
+ color: [1, 1, 1],
322
+ intensity: 1,
323
+ },
324
+ })
325
+ .unwrap();
326
+ const r = world.get(e, DirectionalLight).unwrap();
327
+ expect(r.intensity).toBe(1);
328
+ expect(r.color[0]).toBe(1);
329
+ });
330
+
331
+ it('multi-component spawn (Transform + MeshFilter + MeshRenderer + Camera) targets a single archetype', () => {
332
+ const world = new World();
333
+ const e = world
334
+ .spawn(
335
+ {
336
+ component: Transform,
337
+ data: {
338
+ pos: [0, 0, 0],
339
+ quat: [0, 0, 0, 1],
340
+ scale: [1, 1, 1],
341
+ },
342
+ },
343
+ { component: MeshFilter, data: { assetHandle: 1 as Handle<'MeshAsset', 'shared'> } },
344
+ {
345
+ component: MeshRenderer,
346
+ data: {
347
+ materials: [5 as Handle<'MaterialAsset', 'shared'>],
348
+ },
349
+ },
350
+ {
351
+ component: Camera,
352
+ data: {
353
+ fov: Math.PI / 4,
354
+ aspect: 1,
355
+ near: 0.1,
356
+ far: 100,
357
+ projection: 0,
358
+ left: -1,
359
+ right: 1,
360
+ bottom: -1,
361
+ top: 1,
362
+ },
363
+ },
364
+ )
365
+ .unwrap();
366
+ expect(world.get(e, Transform).unwrap().pos[0]).toBe(0);
367
+ expect(world.get(e, MeshFilter).unwrap().assetHandle).toBe(1);
368
+ expect(world.get(e, MeshRenderer).unwrap().materials[0]).toBe(5);
369
+ expect(world.get(e, Camera).unwrap().far).toBe(100);
370
+ });
371
+ });
372
+
373
+ describe('w7 - 5 component schemas remain unique tokens (no double registration / no name collision)', () => {
374
+ it('5 components have 5 distinct componentId values', () => {
375
+ const ids = new Set([
376
+ componentId(Transform),
377
+ componentId(MeshFilter),
378
+ componentId(MeshRenderer),
379
+ componentId(Camera),
380
+ componentId(DirectionalLight),
381
+ ]);
382
+ expect(ids.size).toBe(5);
383
+ });
384
+
385
+ it('user-defined component with the same name shape compiles independently', () => {
386
+ // Using the same user-style schema to confirm the 5 engine tokens do not
387
+ // collide with downstream user code at the registry level. The user token
388
+ // is a fresh allocation; it does not interfere with the 5 engine tokens.
389
+ const UserPos = defineComponent('UserPos', { x: { type: 'f32' } });
390
+ expect(componentId(UserPos)).not.toBe(componentId(Transform));
391
+ });
392
+ });
393
+ }
394
+
395
+ {
396
+ // --- from hierarchy-components.test.ts ---
397
+ // w5 - ChildOf / Children hierarchy component schema tests.
398
+ //
399
+ // Locks plan-strategy §D-P2 + requirements AC-12 schema field set:
400
+ // ChildOf: { parent: 'entity' }
401
+ // (M5 / w18 - migrated from raw 'ref' to schema-vocab
402
+ // 'entity' keyword; the parent column carries the encoded
403
+ // Entity and the ECS does not bottom out dangling refs on
404
+ // read - consumers check liveness themselves)
405
+ // Children: { entities: 'array<entity>' } = variable-length
406
+ // forward-list of child entity u32s (M3 / w13 of
407
+ // feat-20260514-ecs-children-instances-managed-buffer-array
408
+ // migrated from the legacy `count: 'u32'` advisory marker
409
+ // to the real ECS-managed array storage path; AC-05).
410
+ // The dedicated assertion lives in children.test.ts; this
411
+ // file keeps an integration-shape spawn smoke for the
412
+ // archetype-with-Children path.
413
+ //
414
+ // feat-20260601 M4: GlobalTransform is retired. The resolved world transform
415
+ // is the `Transform.world` mat4 column; propagate + hierarchy compose coverage
416
+ // lives in render-system-extract.test.ts (Transform.world parent x child).
417
+ //
418
+ // charter mapping: proposition 2 (Bevy ChildOf/Children industry analog) +
419
+ // proposition 3 (machine-readable schema > prose: grep componentSchema(ChildOf) /
420
+ // componentSchema(Children) recovers shape).
421
+
422
+ describe('w5 - ChildOf / Children register through defineComponent', () => {
423
+ it('ChildOf has 1 entity field (parent) (M5 / w18)', () => {
424
+ expect(ChildOf.name).toBe('ChildOf');
425
+ expect(Object.keys(componentSchema(ChildOf)).length).toBe(1);
426
+ expect(componentSchema(ChildOf).parent).toBe('entity');
427
+ });
428
+
429
+ it('Children has 1 array<entity> field (entities; M3 / w13 migration from legacy `count: u32`)', () => {
430
+ expect(Children.name).toBe('Children');
431
+ expect(Object.keys(componentSchema(Children)).length).toBe(1);
432
+ expect((componentSchema(Children) as Record<string, unknown>).entities).toBe('array<entity>');
433
+ });
434
+
435
+ it('Transform carries the resolved world mat4 column (array<f32, 16>)', () => {
436
+ // feat-20260601 M4: the world transform lives on Transform.world, not a
437
+ // separate GlobalTransform component. The 3 local-TRS inline array
438
+ // columns (pos/quat/scale) plus the `world: array<f32, 16>` resolved
439
+ // mat4 are the full shape.
440
+ const tKeys = Object.keys(componentSchema(Transform));
441
+ expect(tKeys).toContain('world');
442
+ expect(componentSchema(Transform).world).toBe('array<f32, 16>');
443
+ expect(componentSchema(Transform).pos).toBe('array<f32, 3>');
444
+ expect(componentSchema(Transform).quat).toBe('array<f32, 4>');
445
+ expect(componentSchema(Transform).scale).toBe('array<f32, 3>');
446
+ });
447
+
448
+ it('ChildOf / Children frozen tokens (schema immutable)', () => {
449
+ expect(Object.isFrozen(componentSchema(ChildOf))).toBe(true);
450
+ expect(Object.isFrozen(componentSchema(Children))).toBe(true);
451
+ });
452
+
453
+ it('spawn with ChildOf carries encoded Entity u32 through ref field round-trip', () => {
454
+ const world = new World();
455
+ const root = world
456
+ .spawn({
457
+ component: Transform,
458
+ data: {
459
+ pos: [0, 0, 0],
460
+ quat: [0, 0, 0, 1],
461
+ scale: [1, 1, 1],
462
+ },
463
+ })
464
+ .unwrap();
465
+ const child = world
466
+ .spawn(
467
+ {
468
+ component: Transform,
469
+ data: {
470
+ pos: [1, 0, 0],
471
+ quat: [0, 0, 0, 1],
472
+ scale: [1, 1, 1],
473
+ },
474
+ },
475
+ { component: ChildOf, data: { parent: root } },
476
+ )
477
+ .unwrap();
478
+ const r = world.get(child, ChildOf);
479
+ expect(r.ok).toBe(true);
480
+ if (!r.ok) return;
481
+ expect(r.value.parent).toBe(root);
482
+ });
483
+
484
+ it('spawn with Transform + Children creates archetype with both components', () => {
485
+ const world = new World();
486
+ const root = world
487
+ .spawn(
488
+ {
489
+ component: Transform,
490
+ data: {
491
+ pos: [0, 0, 0],
492
+ quat: [0, 0, 0, 1],
493
+ scale: [1, 1, 1],
494
+ },
495
+ },
496
+ { component: Children, data: { entities: new Uint32Array([]) as never } },
497
+ )
498
+ .unwrap();
499
+ const rt = world.get(root, Transform);
500
+ expect(rt.ok).toBe(true);
501
+ if (!rt.ok) return;
502
+ expect(rt.value.quat[3]).toBe(1);
503
+ expect(rt.value.scale[0]).toBe(1);
504
+ const rc = world.get(root, Children);
505
+ expect(rc.ok).toBe(true);
506
+ if (!rc.ok) return;
507
+ // The variable-length entities snapshot starts empty for a fresh spawn
508
+ // with a zero-length payload; the snapshot length equals the live count.
509
+ // A dedicated push/pop suite lives in children.test.ts.
510
+ expect(rc.value.entities.length).toBe(0);
511
+ });
512
+
513
+ // Silence unused-import lint if defineComponent elsewhere; keep the symbol
514
+ // reference so the test file double-checks barrel wiring side-effects.
515
+ it('defineComponent type is re-exported through @forgeax/engine-ecs', () => {
516
+ expect(typeof defineComponent).toBe('function');
517
+ });
518
+ });
519
+ }
520
+
521
+ {
522
+ // --- from layer-component.test.ts ---
523
+ // w07 - Layer component schema spawn (TDD red).
524
+ //
525
+ // feat-20260520-2d-sprite-layer-mvp M-2 w07 / requirements AC-06 + AC-18 path (1).
526
+ //
527
+ // Coverage:
528
+ // - 4 explicit Layer values: {-100, 0, 100, 1000} (background / default /
529
+ // foreground / UI). i32 schema must preserve negative values via two's
530
+ // complement (no schema-layer mutate). plan-strategy §7 M-2 acceptance
531
+ // anchor AC-06.
532
+ // - 1 fallback case: entity spawned without Layer must round-trip via the
533
+ // existing 4-layer spawn fallback chain (feat-20260517-spawn-default-
534
+ // fallback) — value defaults to 0 (i32 scalar default), no fifth fallback
535
+ // layer introduced (AC-18 path (1)).
536
+ //
537
+ // charter mapping: F1 (single-import barrel discovery) + P3 (explicit defaults
538
+ // — i32 zero default surfaces as a read-back value, not undefined) +
539
+ // P4 (consistent abstraction — Layer is a generic ECS render component, not a
540
+ // 2D-only special; 3D entities may also carry Layer).
541
+ //
542
+ // TDD red: imports Layer from the runtime barrel; w11 implements the
543
+ // component file + barrel re-export to turn this green.
544
+
545
+ describe('w07 - Layer = defineComponent("Layer", { value: "i32" })', () => {
546
+ it('has schema { value: "i32" } (1 i32 field)', () => {
547
+ expect(Layer.name).toBe('Layer');
548
+ expect(Object.keys(componentSchema(Layer)).length).toBe(1);
549
+ expect(componentSchema(Layer).value).toBe('i32');
550
+ });
551
+
552
+ it("spawn Layer { value: -100 } (background) round-trips with two's complement", () => {
553
+ const world = new World();
554
+ const e = world.spawn({ component: Layer, data: { value: -100 } }).unwrap();
555
+ const r = world.get(e, Layer).unwrap();
556
+ expect(r.value).toBe(-100);
557
+ });
558
+
559
+ it('spawn Layer { value: 0 } (default / game layer) round-trips', () => {
560
+ const world = new World();
561
+ const e = world.spawn({ component: Layer, data: { value: 0 } }).unwrap();
562
+ const r = world.get(e, Layer).unwrap();
563
+ expect(r.value).toBe(0);
564
+ });
565
+
566
+ it('spawn Layer { value: 100 } (foreground) round-trips', () => {
567
+ const world = new World();
568
+ const e = world.spawn({ component: Layer, data: { value: 100 } }).unwrap();
569
+ const r = world.get(e, Layer).unwrap();
570
+ expect(r.value).toBe(100);
571
+ });
572
+
573
+ it('spawn Layer { value: 1000 } (UI) round-trips', () => {
574
+ const world = new World();
575
+ const e = world.spawn({ component: Layer, data: { value: 1000 } }).unwrap();
576
+ const r = world.get(e, Layer).unwrap();
577
+ expect(r.value).toBe(1000);
578
+ });
579
+
580
+ it('spawn payload omitting value falls through 4-layer chain to schema default 0 (AC-18 path 1)', () => {
581
+ // Entity spawned without Layer payload — the existing 4-layer spawn
582
+ // fallback chain (feat-20260517-spawn-default-fallback) fills i32
583
+ // scalar default 0. No fifth fallback layer is introduced for this
584
+ // feat (AC-18 path 1). The call below uses an empty `data: {}` payload
585
+ // (no value field). The spawn must succeed (Result.ok) and the
586
+ // round-tripped value must be exactly 0.
587
+ const world = new World();
588
+ const spawnResult = world.spawn({ component: Layer, data: {} });
589
+ expect(spawnResult.ok).toBe(true);
590
+ const e = spawnResult.unwrap();
591
+ const r = world.get(e, Layer).unwrap();
592
+ expect(r.value).toBe(0);
593
+ });
594
+ });
595
+ }
596
+
597
+ {
598
+ // --- from relationship-migration-regression.test.ts ---
599
+ // relationship-migration-regression.test.ts -- unit project (t21, M4).
600
+ // Zero-regression + auto-sync verification for the ChildOf/Children
601
+ // relationship migration (t20).
602
+ //
603
+ // Two orthogonal guarantees after ChildOf gained its `relationship` block:
604
+ // AC-25 reader path unchanged -- propagateTransforms still walks ChildOf.parent
605
+ // upward and composes child.Transform.world = parent.world x
606
+ // compose(child local). The migration must NOT alter this output.
607
+ // AC-25 mirror auto-maintained -- spawning / removing / reparenting ChildOf
608
+ // now auto-updates the parent's Children.entities list (the OOS-10 manual-
609
+ // sync contract is retired). This is the new behaviour the migration adds.
610
+ //
611
+ // CPU-only (no GPU access): propagateTransforms is pure matrix math and the
612
+ // bidirectional sync is pure ECS bookkeeping, so this runs in the unit layer
613
+ // alongside the pixel-readback propagate coverage in the dawn layer (AC-04).
614
+
615
+ function transformData(x: number, y: number, z: number) {
616
+ return { pos: [x, y, z], quat: [0, 0, 0, 1], scale: [1, 1, 1] };
617
+ }
618
+
619
+ describe('relationship-migration-regression (t21 / AC-25 / AC-26)', () => {
620
+ it('ChildOf remains the writable relationship source token', () => {
621
+ expect(ChildOf.name).toBe('ChildOf');
622
+ });
623
+
624
+ it('propagateTransforms reader path unchanged: child composes parent x local translation', () => {
625
+ const world = new World();
626
+ const parent = world.spawn({ component: Transform, data: transformData(10, 0, 0) }).unwrap();
627
+ const child = world
628
+ .spawn(
629
+ { component: Transform, data: transformData(1, 2, 3) },
630
+ { component: ChildOf, data: { parent } },
631
+ )
632
+ .unwrap();
633
+
634
+ const r = propagateTransforms(world);
635
+ expect(r.ok).toBe(true);
636
+
637
+ // feat-20260601: the resolved world transform lives on Transform.world
638
+ // (a 16-float column-major mat4); the translation column is m[12,13,14].
639
+ const t = world.get(child, Transform);
640
+ expect(t.ok).toBe(true);
641
+ if (!t.ok) return;
642
+ const w = t.value.world;
643
+ // parent translates +10 X, child local +1/+2/+3 -> world 11/2/3.
644
+ expect(w[12]).toBeCloseTo(11, 5);
645
+ expect(w[13]).toBeCloseTo(2, 5);
646
+ expect(w[14]).toBeCloseTo(3, 5);
647
+ });
648
+
649
+ it('spawning ChildOf auto-appends the child to parent.Children.entities (OOS-10 retired)', () => {
650
+ const world = new World();
651
+ // Mirror must be registered before the holder is used (M2 contract:
652
+ // the relationship hook resolves Children by name from the registry).
653
+ const parent = world.spawn({ component: Transform, data: transformData(0, 0, 0) }).unwrap();
654
+ const a = world
655
+ .spawn(
656
+ { component: Transform, data: transformData(0, 0, 0) },
657
+ { component: ChildOf, data: { parent } },
658
+ )
659
+ .unwrap();
660
+ const b = world
661
+ .spawn(
662
+ { component: Transform, data: transformData(0, 0, 0) },
663
+ { component: ChildOf, data: { parent } },
664
+ )
665
+ .unwrap();
666
+
667
+ const snap = world.get(parent, Children);
668
+ expect(snap.ok).toBe(true);
669
+ if (!snap.ok) return;
670
+ expect(Array.from(snap.value.entities)).toEqual([a, b]);
671
+ });
672
+
673
+ it('removing ChildOf prunes the child from parent.Children.entities', () => {
674
+ const world = new World();
675
+ const parent = world.spawn({ component: Transform, data: transformData(0, 0, 0) }).unwrap();
676
+ const a = world
677
+ .spawn(
678
+ { component: Transform, data: transformData(0, 0, 0) },
679
+ { component: ChildOf, data: { parent } },
680
+ )
681
+ .unwrap();
682
+ const b = world
683
+ .spawn(
684
+ { component: Transform, data: transformData(0, 0, 0) },
685
+ { component: ChildOf, data: { parent } },
686
+ )
687
+ .unwrap();
688
+
689
+ world.removeComponent(a, ChildOf).unwrap();
690
+
691
+ const snap = world.get(parent, Children);
692
+ expect(snap.ok).toBe(true);
693
+ if (!snap.ok) return;
694
+ expect(Array.from(snap.value.entities)).toEqual([b]);
695
+ });
696
+
697
+ it('reparent (exclusive re-add) moves the child between parent Children lists', () => {
698
+ const world = new World();
699
+ const oldParent = world
700
+ .spawn({ component: Transform, data: transformData(0, 0, 0) })
701
+ .unwrap();
702
+ const newParent = world
703
+ .spawn({ component: Transform, data: transformData(0, 0, 0) })
704
+ .unwrap();
705
+ const child = world
706
+ .spawn(
707
+ { component: Transform, data: transformData(0, 0, 0) },
708
+ { component: ChildOf, data: { parent: oldParent } },
709
+ )
710
+ .unwrap();
711
+
712
+ // Atomic detach-then-attach reparent via the M3 Commands surface.
713
+ world.reparent(child, newParent, ChildOf, { parent: newParent }).unwrap();
714
+
715
+ const oldSnap = world.get(oldParent, Children);
716
+ const newSnap = world.get(newParent, Children);
717
+ expect(oldSnap.ok && newSnap.ok).toBe(true);
718
+ if (!oldSnap.ok || !newSnap.ok) return;
719
+ expect(Array.from(oldSnap.value.entities)).toEqual([]);
720
+ expect(Array.from(newSnap.value.entities)).toEqual([child]);
721
+
722
+ // Reader path: ChildOf.parent now points at newParent.
723
+ const co = world.get(child, ChildOf);
724
+ expect(co.ok).toBe(true);
725
+ if (!co.ok) return;
726
+ expect(co.value.parent).toBe(newParent);
727
+ });
728
+ });
729
+
730
+ // feat-20260602 M2 / w7: relationship-mirror validation moved into
731
+ // `defineComponent` (define-time fail-fast). The runtime ChildOf holder
732
+ // declares `mirror: 'Children'`, so the component barrel must export Children
733
+ // (the mirror) before ChildOf (the holder) -- otherwise `defineComponent`
734
+ // throws RelationshipMirrorComponentNotRegisteredError while the package is
735
+ // being evaluated. This block is the load-bearing guard for that barrel
736
+ // ordering: a clean dynamic import proves the module-evaluation order
737
+ // satisfies the mirror-before-holder define-time contract.
738
+ describe('relationship-migration-regression (feat-20260602 M2 / w7 barrel order)', () => {
739
+ it('imports @forgeax/engine-runtime without throwing on module evaluation', async () => {
740
+ const mod = await import('@forgeax/engine-scene');
741
+ expect(mod.Children).toBeDefined();
742
+ expect(mod.ChildOf).toBeDefined();
743
+ });
744
+
745
+ it('runtime scene barrel exposes both relationship tokens', async () => {
746
+ const { ChildOf, Children } = await import('@forgeax/engine-scene');
747
+ expect(ChildOf.name).toBe('ChildOf');
748
+ expect(Children.name).toBe('Children');
749
+ expect((componentSchema(Children) as Record<string, string>).entities).toBe('array<entity>');
750
+ });
751
+ });
752
+ }
753
+
754
+ {
755
+ // --- from scene-defaults.test.ts ---
756
+ // scene-defaults.test - default-value 4-layer fallback (w20 TDD red).
757
+ //
758
+ // Coverage map (anchored to requirements §AC-07 / §AC-11 / §AC-12 +
759
+ // §default-value mechanism + plan-strategy §D-P3 / §D-P4):
760
+ //
761
+ // (a) AC-07 layer 1 explicit Scene value:
762
+ // SceneEntity.components.Transform.pos = [1.5, 0, 0] -> instantiate ->
763
+ // pos === [1.5, 0, 0]. Explicit Scene value beats every layer 2/3
764
+ // default (no setOverride written, so overrides() stays empty).
765
+ //
766
+ // (b) AC-11 layer 2 component-level defaults (D-P3 add-only minor):
767
+ // defineComponent('Transform',
768
+ // { pos: { type: 'array<f32, 3>', default: new Float32Array([0, 7, 0]) } });
769
+ // SceneEntity does NOT write pos -> instantiate -> pos === [0, 7, 0].
770
+ //
771
+ // (c) AC-12 layer 3 TS type defaults + NULL_ENTITY sentinel:
772
+ // Three layers all silent on pos -> pos === [0, 0, 0] (TS array<f32, N>
773
+ // zero-fill default).
774
+ // 'entity' typed field with no source value -> NULL_ENTITY sentinel
775
+ // (raw u32 = 0xffffffff per ENTITY_NULL_RAW SSOT). Layer 3 silent --
776
+ // MUST NOT throw 'scene-default-missing'; charter tension explicitly
777
+ // captured in requirements + plan-strategy.
778
+ //
779
+ // (d) layer-boundary discipline:
780
+ // Known schema field name + missing value -> walks layer 1 -> 2 -> 3
781
+ // chain (this test file).
782
+ // Unknown field name (typo 'pozX') -> ajv fail-fast rejects upstream
783
+ // at pack-schema validate, never reaching instantiate -- that path is
784
+ // owned by AC-08 in packages/pack scene-schema tests (M1 / w4 covers
785
+ // it). The boundary is "known name + missing value = silent fallback;
786
+ // unknown name = fail-fast" -- this file asserts the silent half so
787
+ // the contract is bidirectionally pinned.
788
+ //
789
+ // w22 (ImplementerAgent M4) lands the impl in
790
+ // packages/ecs/src/scene-instance-container.ts; this file is the red phase.
791
+
792
+ // M3 ECS-fication: scene-instance container API replaced; tests use
793
+ // world.instantiateScene + registerSceneAsset (allocUniqueRef + toShared)
794
+ // and read mapping via the SceneInstance component on the synthetic root.
795
+ // SceneInstance schema must be locally registered so resolveComponent finds
796
+ // it during instantiateScene (matches the runtime schema definition).
797
+ defineComponent('SceneInstance', {
798
+ source: { type: 'shared<SceneAsset>' },
799
+ mapping: { type: 'array<entity>' },
800
+ state: { type: 'unique<SceneInstanceState>' },
801
+ });
802
+
803
+ function localId(n: number): LocalEntityId {
804
+ return n as LocalEntityId;
805
+ }
806
+
807
+ function buildScene(nodes: readonly SceneEntity[]): SceneAsset {
808
+ return { kind: 'scene', entities: nodes };
809
+ }
810
+
811
+ function registerSceneAsset(
812
+ world: World,
813
+ asset: SceneAsset,
814
+ components: readonly Component[] = [Transform],
815
+ ): Handle<'SceneAsset', 'shared'> {
816
+ for (const component of [SceneInstance, ChildOf, Children, ...components]) {
817
+ world.components.register(component).unwrap();
818
+ }
819
+ return world.allocSharedRef('SceneAsset', asset);
820
+ }
821
+
822
+ function firstNodeEntity(world: World, root: EntityHandle): EntityHandle {
823
+ // entityToLocalId.keys() iterates in topo-sort spawn order; first key is
824
+ // localId 0's live Entity. Robust against the mapping[0]===0 encoding
825
+ // (gen=0+idx=0 produces raw u32 0, which is a valid Entity not "empty").
826
+ const stateRes = SceneOwner.worldGetSceneInstanceState(world, root);
827
+ if (!stateRes.ok) throw new Error('SceneInstance state lookup failed');
828
+ const it = stateRes.value.entityToLocalId.keys();
829
+ const first = it.next();
830
+ if (first.done) throw new Error('entityToLocalId empty');
831
+ return first.value;
832
+ }
833
+
834
+ describe('default-value 4-layer fallback (w20 / AC-07 + AC-11 + AC-12)', () => {
835
+ it('AC-07 layer 1: explicit Scene value beats every default', () => {
836
+ const Transform = defineComponent('Transform', {
837
+ pos: { type: 'array<f32, 3>', default: new Float32Array([99, 99, 99]) },
838
+ });
839
+ const world = new World();
840
+ world.components.register(Transform).unwrap();
841
+
842
+ const nodes: SceneEntity[] = [
843
+ {
844
+ localId: localId(0),
845
+ components: { Transform: { pos: [1.5, 0, 0] } },
846
+ },
847
+ ];
848
+ const handle = registerSceneAsset(world, buildScene(nodes), [Transform]);
849
+ const r = SceneOwner.worldInstantiateScene(world, handle);
850
+ expect(r.ok).toBe(true);
851
+ if (!r.ok) return;
852
+ const e = firstNodeEntity(world, r.value.root);
853
+ const t = world.get(e, Transform).unwrap();
854
+ expect(Array.from(t.pos)).toEqual([1.5, 0, 0]);
855
+ // overrides stay empty - no setSceneOverride was called. M3 reads them
856
+ // from the SceneInstanceState payload via getSceneInstanceState.
857
+ const stateRes = SceneOwner.worldGetSceneInstanceState(world, r.value.root);
858
+ expect(stateRes.ok).toBe(true);
859
+ if (!stateRes.ok) return;
860
+ expect(stateRes.value.overrides.size).toBe(0);
861
+ });
862
+
863
+ it('AC-11 layer 2: component-level defaults fill missing fields (D-P3)', () => {
864
+ const Transform = defineComponent('Transform', {
865
+ pos: { type: 'array<f32, 3>', default: new Float32Array([0, 7, 0]) },
866
+ });
867
+ const world = new World();
868
+ world.components.register(Transform).unwrap();
869
+
870
+ // Node omits pos entirely -> layer 2 fills the whole array column with
871
+ // the field-descriptor default [0, 7, 0] (beats the layer-3 zero-fill,
872
+ // asserted in the next test).
873
+ const nodes: SceneEntity[] = [
874
+ {
875
+ localId: localId(0),
876
+ components: { Transform: {} },
877
+ },
878
+ ];
879
+ const handle = registerSceneAsset(world, buildScene(nodes), [Transform]);
880
+ const r = SceneOwner.worldInstantiateScene(world, handle);
881
+ expect(r.ok).toBe(true);
882
+ if (!r.ok) return;
883
+ const e = firstNodeEntity(world, r.value.root);
884
+ const t = world.get(e, Transform).unwrap();
885
+ expect(Array.from(t.pos)).toEqual([0, 7, 0]);
886
+ });
887
+
888
+ it('AC-12 layer 3: TS type defaults silently fill three-layer-empty fields', () => {
889
+ const Transform = defineComponent('Transform', {
890
+ pos: 'array<f32, 3>',
891
+ flag: 'bool',
892
+ kind: 'u32',
893
+ });
894
+ const world = new World();
895
+ world.components.register(Transform).unwrap();
896
+
897
+ // No defaults at the component level; node writes nothing. Three-layer
898
+ // chain leaves pos / flag / kind empty -> layer 3 fills with TS type
899
+ // defaults. AC-12 mandates this path is SILENT (no error code).
900
+ const nodes: SceneEntity[] = [
901
+ {
902
+ localId: localId(0),
903
+ components: { Transform: {} },
904
+ },
905
+ ];
906
+ const handle = registerSceneAsset(world, buildScene(nodes), [Transform]);
907
+ const r = SceneOwner.worldInstantiateScene(world, handle);
908
+ expect(r.ok).toBe(true);
909
+ if (!r.ok) return;
910
+ const e = firstNodeEntity(world, r.value.root);
911
+ const t = world.get(e, Transform).unwrap();
912
+ expect(Array.from(t.pos)).toEqual([0, 0, 0]); // array<f32, 3> -> zero-fill
913
+ expect(t.flag).toBe(false); // bool -> false (read back as 0/1)
914
+ expect(t.kind).toBe(0); // u32 -> 0
915
+ });
916
+
917
+ it('AC-12 layer 3: entity-typed field falls back to NULL_ENTITY sentinel', () => {
918
+ const TargetSlot = defineComponent('TargetSlot', { target: 'entity' });
919
+ const world = new World();
920
+ world.components.register(TargetSlot).unwrap();
921
+
922
+ // Node declares TargetSlot with NO target value -> three layers silent
923
+ // -> layer 3 must store ENTITY_NULL_RAW (0xffffffff) sentinel in the u32
924
+ // column. On read, readRow decodes ENTITY_NULL_RAW back to JS null (the
925
+ // null-sentinel mapping is a storage-level convention, not a liveness check).
926
+ // Both shapes are pinned here so a regression in either direction trips
927
+ // this test:
928
+ // - storage layer: ENTITY_NULL_RAW is the sentinel SSOT
929
+ // - read layer: null is what the AI user observes after world.get
930
+ const nodes: SceneEntity[] = [
931
+ {
932
+ localId: localId(0),
933
+ components: { TargetSlot: {} },
934
+ },
935
+ ];
936
+ const handle = registerSceneAsset(world, buildScene(nodes), [TargetSlot]);
937
+ const r = SceneOwner.worldInstantiateScene(world, handle);
938
+ expect(r.ok).toBe(true);
939
+ if (!r.ok) return;
940
+ const e = firstNodeEntity(world, r.value.root);
941
+ const slot = world.get(e, TargetSlot).unwrap();
942
+ // Read-side: ENTITY_NULL_RAW storage maps back to null; layer 3 silent
943
+ // path must NOT emit any error code.
944
+ expect(slot.target).toBe(null);
945
+ // SSOT pin: verify ENTITY_NULL_RAW is the agreed sentinel (so the
946
+ // contract between layer 3 and the storage column stays bidirectional).
947
+ expect(ENTITY_NULL_RAW).toBe(0xffffffff);
948
+ });
949
+
950
+ it('layer chain order: layer 1 > layer 2 > layer 3 priority discipline', () => {
951
+ // Compose all three layers in one node so the priority chain is
952
+ // observable in a single instantiate roundtrip (one field per layer;
953
+ // array columns fill whole-field, not per-lane):
954
+ // pos: layer 1 wins (explicit Scene value)
955
+ // quat: layer 2 wins (component default)
956
+ // scale: layer 3 wins (silent TS default)
957
+ const Transform = defineComponent('Transform', {
958
+ pos: { type: 'array<f32, 3>', default: new Float32Array([99, 99, 99]) },
959
+ quat: { type: 'array<f32, 4>', default: new Float32Array([0, 7, 0, 1]) },
960
+ scale: 'array<f32, 3>',
961
+ });
962
+ const world = new World();
963
+ world.components.register(Transform).unwrap();
964
+
965
+ const nodes: SceneEntity[] = [
966
+ {
967
+ localId: localId(0),
968
+ components: { Transform: { pos: [1.5, 0, 0] } },
969
+ },
970
+ ];
971
+ const handle = registerSceneAsset(world, buildScene(nodes), [Transform]);
972
+ const r = SceneOwner.worldInstantiateScene(world, handle);
973
+ expect(r.ok).toBe(true);
974
+ if (!r.ok) return;
975
+ const e = firstNodeEntity(world, r.value.root);
976
+ const t = world.get(e, Transform).unwrap();
977
+ expect(Array.from(t.pos)).toEqual([1.5, 0, 0]); // layer 1 wins (explicit beats default 99)
978
+ expect(Array.from(t.quat)).toEqual([0, 7, 0, 1]); // layer 2 wins (component default beats type 0)
979
+ expect(Array.from(t.scale)).toEqual([0, 0, 0]); // layer 3 wins (TS array<f32, N> zero-fill)
980
+ });
981
+
982
+ it('layer 3 silent path: no error code surface on three-layer-empty schema field', () => {
983
+ // Charter tension declared in requirements §default-value mechanism +
984
+ // plan-strategy §3.3 §error-model: layer 3 MUST be silent. This test
985
+ // pins the negative -- the Result must be ok and no error-shaped value
986
+ // surfaces to the caller.
987
+ const Transform = defineComponent('Transform', {
988
+ pos: 'array<f32, 3>',
989
+ });
990
+ const world = new World();
991
+ world.components.register(Transform).unwrap();
992
+
993
+ const nodes: SceneEntity[] = [{ localId: localId(0), components: { Transform: {} } }];
994
+ const handle = registerSceneAsset(world, buildScene(nodes), [Transform]);
995
+ const r = SceneOwner.worldInstantiateScene(world, handle);
996
+ expect(r.ok).toBe(true);
997
+ // No 'scene-default-missing' / similar code surfaces. Asserted at the
998
+ // Result envelope -- if w22 ever introduces such a code in the silent
999
+ // path this assertion flips red.
1000
+ });
1001
+ });
1002
+ }
1003
+
1004
+ {
1005
+ // --- from sort-key-component.test.ts ---
1006
+ // w08 - SortKey component schema spawn (TDD red).
1007
+ //
1008
+ // feat-20260520-2d-sprite-layer-mvp M-2 w08 / requirements AC-07 + AC-19 (4).
1009
+ //
1010
+ // Coverage:
1011
+ // - SortKey { value: 1.5 } round-trips with f32 precision.
1012
+ // - spawn without SortKey reads back default 0.0 (f32 scalar default
1013
+ // via the 4-layer fallback chain — feat-20260517-spawn-default-fallback).
1014
+ // - Layer { value: 100 } + SortKey { value: -2.0 } co-exist on one entity
1015
+ // (multi-component spawn targets a single archetype).
1016
+ //
1017
+ // The downstream "SortKey overrides TransparentSortConfig mode formula"
1018
+ // behaviour is verified by w16 inside `transparent-sort.test.ts` — this
1019
+ // task only validates that SortKey is a registerable scalar f32 component
1020
+ // with the expected read-back path. charter mapping: F1 + P3 + P4 (Layer +
1021
+ // SortKey are generic ECS renderer components, not 2D-only specials).
1022
+ //
1023
+ // TDD red: w12 implements the component file + barrel re-export to turn
1024
+ // this green.
1025
+
1026
+ describe('w08 - SortKey = defineComponent("SortKey", { value: "f32" })', () => {
1027
+ it('has schema { value: "f32" } (1 f32 field)', () => {
1028
+ expect(SortKey.name).toBe('SortKey');
1029
+ expect(Object.keys(componentSchema(SortKey)).length).toBe(1);
1030
+ expect(componentSchema(SortKey).value).toBe('f32');
1031
+ });
1032
+
1033
+ it('spawn SortKey { value: 1.5 } round-trips within f32 precision', () => {
1034
+ const world = new World();
1035
+ const e = world.spawn({ component: SortKey, data: { value: 1.5 } }).unwrap();
1036
+ const r = world.get(e, SortKey).unwrap();
1037
+ expect(r.value).toBeCloseTo(1.5, 5);
1038
+ });
1039
+
1040
+ it('spawn payload omitting value falls through 4-layer chain to f32 default 0', () => {
1041
+ const world = new World();
1042
+ const spawnResult = world.spawn({ component: SortKey, data: {} });
1043
+ expect(spawnResult.ok).toBe(true);
1044
+ const e = spawnResult.unwrap();
1045
+ const r = world.get(e, SortKey).unwrap();
1046
+ expect(r.value).toBe(0);
1047
+ });
1048
+
1049
+ it('Layer + SortKey co-exist on a single entity (multi-component spawn)', () => {
1050
+ const world = new World();
1051
+ const e = world
1052
+ .spawn(
1053
+ { component: Layer, data: { value: 100 } },
1054
+ { component: SortKey, data: { value: -2.0 } },
1055
+ )
1056
+ .unwrap();
1057
+ const layerRead = world.get(e, Layer).unwrap();
1058
+ const sortKeyRead = world.get(e, SortKey).unwrap();
1059
+ expect(layerRead.value).toBe(100);
1060
+ expect(sortKeyRead.value).toBeCloseTo(-2.0, 5);
1061
+ });
1062
+ });
1063
+ }
1064
+
1065
+ {
1066
+ // --- from animation-player.test.ts ---
1067
+ // feat-20260615-animation-player-crossfade-simple-transition M1 / w1 —
1068
+ // AnimationPlayer SoA schema lock test.
1069
+ //
1070
+ // Old 5-field schema { clip, time, speed, paused, looping } replaced
1071
+ // by 6-field SoA variable arrays (feat-20260713 M1 / w4 dropped the fixed
1072
+ // 4-slot cap; the columns are now variable `array<T>`), then extended to
1073
+ // 10 fields by feat-20260713 M3 / w24 (plan D-3: a single component surface
1074
+ // hosts both direct-write and graph-driven playback — no second peer
1075
+ // component):
1076
+ // clips: 'array<shared<AnimationClip>>' (default empty — no slot active)
1077
+ // times: 'array<f32>' (default empty Float32Array)
1078
+ // weights: 'array<f32>' (default empty Float32Array)
1079
+ // speeds: 'array<f32>' (default empty — consumers write
1080
+ // all four columns length-synced)
1081
+ // graph: 'shared<AnimationGraph>' (default 0 — no graph attached)
1082
+ // nodeWeights: 'array<f32>' (default empty — per-node runtime knob)
1083
+ // nodeTimes: 'array<f32>' (default empty — per-node seek time)
1084
+ // nodeSpeeds: 'array<f32>' (default empty — per-node speed)
1085
+ // paused: 'bool' (default false)
1086
+ // looping: 'bool' (default true)
1087
+ //
1088
+ // Anchors: requirements AC-01 (variable N-slot direct-write field set) + AC-02
1089
+ // (type-level error on old shape) + IS-1 (SoA arrays); feat-20260713 M1
1090
+ // plan D-1/D-6 (variable columns, speeds default []); M3 plan D-3 (graph
1091
+ // handle + per-node runtime knobs extend the same component);
1092
+ // plan-tasks.json w3/w4/w24.
1093
+
1094
+ describe('AnimationPlayer — SoA 10-field schema lock (M1 / w3 + M3 / w24)', () => {
1095
+ it('AnimationPlayer is a registered component with name "AnimationPlayer" and 10 SoA schema fields', () => {
1096
+ expect(AnimationPlayer.name).toBe('AnimationPlayer');
1097
+ const schema = componentSchema(AnimationPlayer) as Record<string, unknown>;
1098
+ expect(Object.keys(schema).length).toBe(10);
1099
+ expect(schema).toEqual({
1100
+ clips: 'array<shared<AnimationClip>>',
1101
+ times: 'array<f32>',
1102
+ weights: 'array<f32>',
1103
+ speeds: 'array<f32>',
1104
+ graph: 'shared<AnimationGraph>',
1105
+ nodeWeights: 'array<f32>',
1106
+ nodeTimes: 'array<f32>',
1107
+ nodeSpeeds: 'array<f32>',
1108
+ paused: 'bool',
1109
+ looping: 'bool',
1110
+ });
1111
+ });
1112
+
1113
+ it('componentSchema(AnimationPlayer).clips is variable array<shared<AnimationClip>> (SoA keyword)', () => {
1114
+ expect((componentSchema(AnimationPlayer) as Record<string, unknown>).clips).toBe(
1115
+ 'array<shared<AnimationClip>>',
1116
+ );
1117
+ });
1118
+
1119
+ it('componentSchema(AnimationPlayer).times is variable array<f32>', () => {
1120
+ expect((componentSchema(AnimationPlayer) as Record<string, unknown>).times).toBe(
1121
+ 'array<f32>',
1122
+ );
1123
+ });
1124
+
1125
+ it('componentSchema(AnimationPlayer).weights is variable array<f32>', () => {
1126
+ expect((componentSchema(AnimationPlayer) as Record<string, unknown>).weights).toBe(
1127
+ 'array<f32>',
1128
+ );
1129
+ });
1130
+
1131
+ it('componentSchema(AnimationPlayer).speeds is variable array<f32>', () => {
1132
+ expect((componentSchema(AnimationPlayer) as Record<string, unknown>).speeds).toBe(
1133
+ 'array<f32>',
1134
+ );
1135
+ });
1136
+
1137
+ it('componentSchema(AnimationPlayer).paused is bool', () => {
1138
+ expect((componentSchema(AnimationPlayer) as Record<string, unknown>).paused).toBe('bool');
1139
+ });
1140
+
1141
+ it('componentSchema(AnimationPlayer).looping is bool', () => {
1142
+ expect((componentSchema(AnimationPlayer) as Record<string, unknown>).looping).toBe('bool');
1143
+ });
1144
+
1145
+ it('componentSchema(AnimationPlayer).graph is shared<AnimationGraph> (M3 / w24 graph handle)', () => {
1146
+ expect((componentSchema(AnimationPlayer) as Record<string, unknown>).graph).toBe(
1147
+ 'shared<AnimationGraph>',
1148
+ );
1149
+ });
1150
+
1151
+ it('componentSchema(AnimationPlayer).nodeWeights is variable array<f32> (M3 / w24 per-node knob)', () => {
1152
+ expect((componentSchema(AnimationPlayer) as Record<string, unknown>).nodeWeights).toBe(
1153
+ 'array<f32>',
1154
+ );
1155
+ });
1156
+
1157
+ it('componentSchema(AnimationPlayer).nodeTimes is variable array<f32> (M3 / w24 per-node seek time)', () => {
1158
+ expect((componentSchema(AnimationPlayer) as Record<string, unknown>).nodeTimes).toBe(
1159
+ 'array<f32>',
1160
+ );
1161
+ });
1162
+
1163
+ it('componentSchema(AnimationPlayer).nodeSpeeds is variable array<f32> (M3 / w24 per-node speed)', () => {
1164
+ expect((componentSchema(AnimationPlayer) as Record<string, unknown>).nodeSpeeds).toBe(
1165
+ 'array<f32>',
1166
+ );
1167
+ });
1168
+
1169
+ it('old field clip is absent from schema (AC-02 type-level error)', () => {
1170
+ expect(componentSchema(AnimationPlayer)).not.toHaveProperty('clip');
1171
+ });
1172
+
1173
+ it('old field time is absent from schema (AC-02 type-level error)', () => {
1174
+ expect(componentSchema(AnimationPlayer)).not.toHaveProperty('time');
1175
+ });
1176
+
1177
+ it('old field speed is absent from schema (AC-02 type-level error)', () => {
1178
+ expect(componentSchema(AnimationPlayer)).not.toHaveProperty('speed');
1179
+ });
1180
+
1181
+ it('AnimationPlayer spawn yields variable SoA defaults: clips/times/weights/speeds all empty, paused=false, looping=true (M1 / w4 variable columns, speeds default [])', () => {
1182
+ const world = new World();
1183
+ const e = world
1184
+ .spawn({
1185
+ component: AnimationPlayer,
1186
+ data: {},
1187
+ })
1188
+ .unwrap();
1189
+ const ap = world.get(e, AnimationPlayer).unwrap() as unknown as {
1190
+ clips: Uint32Array;
1191
+ times: Float32Array;
1192
+ weights: Float32Array;
1193
+ speeds: Float32Array;
1194
+ paused: boolean;
1195
+ looping: boolean;
1196
+ };
1197
+ // Variable columns default to empty (no slot active). Consumers write all
1198
+ // four columns length-synced when spawning an active player (M1 / w7).
1199
+ expect(ap.clips).toBeInstanceOf(Uint32Array);
1200
+ expect(ap.clips.length).toBe(0);
1201
+ expect(ap.times).toBeInstanceOf(Float32Array);
1202
+ expect(ap.times.length).toBe(0);
1203
+ expect(ap.weights).toBeInstanceOf(Float32Array);
1204
+ expect(ap.weights.length).toBe(0);
1205
+ // speeds layer-2 default retired to [] (was [1,1,1,1]); a variable column
1206
+ // cannot carry a fixed-width "play at 1x" default without desyncing from
1207
+ // an empty clips column.
1208
+ expect(ap.speeds).toBeInstanceOf(Float32Array);
1209
+ expect(ap.speeds.length).toBe(0);
1210
+ expect(ap.paused).toBe(false);
1211
+ expect(ap.looping).toBe(true);
1212
+ });
1213
+
1214
+ it('AnimationPlayer spawn with explicit SoA data overrides defaults', () => {
1215
+ const world = new World();
1216
+ const e = world
1217
+ .spawn({
1218
+ component: AnimationPlayer,
1219
+ data: {
1220
+ clips: [
1221
+ toShared<'AnimationClip'>(7),
1222
+ toShared<'AnimationClip'>(3),
1223
+ 0 as Handle<'AnimationClip', 'shared'>,
1224
+ 0 as Handle<'AnimationClip', 'shared'>,
1225
+ ],
1226
+ times: new Float32Array([0, 1.5, 0, 0]),
1227
+ weights: new Float32Array([0.7, 0.3, 0, 0]),
1228
+ speeds: new Float32Array([1, 0.5, 0, 0]),
1229
+ paused: true,
1230
+ looping: false,
1231
+ },
1232
+ })
1233
+ .unwrap();
1234
+ const ap = world.get(e, AnimationPlayer).unwrap() as unknown as {
1235
+ clips: Uint32Array;
1236
+ times: Float32Array;
1237
+ weights: Float32Array;
1238
+ speeds: Float32Array;
1239
+ paused: boolean;
1240
+ looping: boolean;
1241
+ };
1242
+ expect(ap.clips[0]).toBe(toShared<'AnimationClip'>(7));
1243
+ expect(ap.clips[1]).toBe(toShared<'AnimationClip'>(3));
1244
+ expect(ap.clips[2]).toBe(0);
1245
+ expect(ap.clips[3]).toBe(0);
1246
+ expect(ap.times[0]).toBe(0);
1247
+ expect(ap.times[1]).toBeCloseTo(1.5, 5);
1248
+ expect(ap.weights[0]).toBeCloseTo(0.7, 5);
1249
+ expect(ap.weights[1]).toBeCloseTo(0.3, 5);
1250
+ expect(ap.speeds[0]).toBeCloseTo(1, 5);
1251
+ expect(ap.speeds[1]).toBeCloseTo(0.5, 5);
1252
+ expect(ap.paused).toBe(true);
1253
+ expect(ap.looping).toBe(false);
1254
+ });
1255
+
1256
+ it('AnimationPlayer set(times) updates the times array column', () => {
1257
+ const world = new World();
1258
+ const e = world
1259
+ .spawn({
1260
+ component: AnimationPlayer,
1261
+ data: {},
1262
+ })
1263
+ .unwrap();
1264
+ world.set(e, AnimationPlayer, { times: new Float32Array([2.0, 0, 0, 0]) });
1265
+ const ap = world.get(e, AnimationPlayer).unwrap() as unknown as { times: Float32Array };
1266
+ expect(ap.times[0]).toBeCloseTo(2.0, 5);
1267
+ });
1268
+ });
1269
+ }
1270
+
1271
+ {
1272
+ // --- from camera.test.ts ---
1273
+ // feat-20260602-ecs-component-type-field-reflection-metadata-base / M5 / w14.
1274
+ //
1275
+ // Camera SSOT invariant guards (AC-07). Two concerns:
1276
+ //
1277
+ // (a) Full 21-field snapshot tests for perspective() / orthographic() factory
1278
+ // functions — every field value is asserted to match the pre-migration
1279
+ // reference. These guards ensure the w13 SSOT refactoring (deleting
1280
+ // CameraDataPod, deriving factory base from Camera token defaults) does
1281
+ // not change any field-level behavior.
1282
+ //
1283
+ // (b) Camera.fields reflection guard: 17 f32 fields, each with type:'f32'.
1284
+ // After w13 deletes CameraDataPod, the factory return type is also verified
1285
+ // to be derivable from the Camera token (no standalone interface).
1286
+ //
1287
+ // Anchors: requirements AC-07 (camera SSOT guard), plan-strategy D-A4
1288
+ // (keep factories, delete CameraDataPod), plan-tasks.json w14/w13.
1289
+
1290
+ // ────────────────────────────────────────────────────────────────────────────
1291
+ // w14-a: 21-field value snapshot (AC-07 invariant guard for w13 SSOT
1292
+ // refactoring — every field value must stay byte-identical)
1293
+ // ────────────────────────────────────────────────────────────────────────────
1294
+
1295
+ describe('camera factory 20-field snapshot (w14 AC-07 invariant)', () => {
1296
+ it('perspective({ fov: Math.PI/3, aspect: 16/9 }) — all 20 fields match reference', () => {
1297
+ const pod = perspective({ fov: Math.PI / 3, aspect: 16 / 9 });
1298
+ // Perspective quartet — caller-supplied
1299
+ expect(pod.fov).toBeCloseTo(Math.PI / 3, 6);
1300
+ expect(pod.aspect).toBeCloseTo(16 / 9, 6);
1301
+ expect(pod.near).toBeCloseTo(0.1, 6);
1302
+ expect(pod.far).toBe(100);
1303
+ // Projection discriminator
1304
+ expect(pod.projection).toBe(CAMERA_PROJECTION_PERSPECTIVE);
1305
+ // Ortho quartet defaults
1306
+ expect(pod.left).toBe(-1);
1307
+ expect(pod.right).toBe(1);
1308
+ expect(pod.bottom).toBe(-1);
1309
+ expect(pod.top).toBe(1);
1310
+ // Tonemap trio defaults
1311
+ expect(pod.tonemap).toBe(TONEMAP_NONE);
1312
+ expect(pod.exposure).toBeCloseTo(1.0, 6);
1313
+ expect(pod.whitePoint).toBeCloseTo(4.0, 6);
1314
+ // Post-processing defaults
1315
+ expect(pod.antialias).toBe(ANTIALIAS_NONE);
1316
+ expect(pod.historyVersion).toBe(0);
1317
+ expect(pod.bloom).toBe(BLOOM_DISABLED);
1318
+ expect(pod.bloomThreshold).toBeCloseTo(1.0, 6);
1319
+ expect(pod.bloomIntensity).toBeCloseTo(1.0, 6);
1320
+ expect(pod.bloomBlurRadius).toBeCloseTo(4.0, 6);
1321
+ // Clear-color default: transparent black array [0,0,0,0] (feat-20260709 M3;
1322
+ // E9 factory-pathway carries the new array default via
1323
+ // cameraPodFromDefaults()).
1324
+ expect(Array.from(pod.clearColor)).toEqual([0, 0, 0, 0]);
1325
+ // aspect-sync opt-out default (feat-20260617 / M3)
1326
+ expect(pod.autoAspect).toBe(true);
1327
+ });
1328
+
1329
+ it('perspective({ fov: 45, aspect: 4/3, near: 0.01, far: 1000 }) — explicit overrides, rest defaults', () => {
1330
+ const pod = perspective({ fov: 45, aspect: 4 / 3, near: 0.01, far: 1000 });
1331
+ expect(pod.fov).toBe(45);
1332
+ expect(pod.aspect).toBeCloseTo(4 / 3, 6);
1333
+ expect(pod.near).toBeCloseTo(0.01, 6);
1334
+ expect(pod.far).toBe(1000);
1335
+ expect(pod.projection).toBe(CAMERA_PROJECTION_PERSPECTIVE);
1336
+ expect(pod.left).toBe(-1);
1337
+ expect(pod.right).toBe(1);
1338
+ expect(pod.bottom).toBe(-1);
1339
+ expect(pod.top).toBe(1);
1340
+ expect(pod.tonemap).toBe(TONEMAP_NONE);
1341
+ expect(pod.exposure).toBeCloseTo(1.0, 6);
1342
+ expect(pod.whitePoint).toBeCloseTo(4.0, 6);
1343
+ expect(pod.antialias).toBe(ANTIALIAS_NONE);
1344
+ expect(pod.bloom).toBe(BLOOM_DISABLED);
1345
+ expect(pod.bloomThreshold).toBeCloseTo(1.0, 6);
1346
+ expect(pod.bloomIntensity).toBeCloseTo(1.0, 6);
1347
+ expect(pod.bloomBlurRadius).toBeCloseTo(4.0, 6);
1348
+ });
1349
+
1350
+ it('orthographic({ left: -10, right: 10, bottom: -10, top: 10 }) — all 20 fields match reference', () => {
1351
+ const pod = orthographic({ left: -10, right: 10, bottom: -10, top: 10 });
1352
+ // Ortho bounds — caller-supplied
1353
+ expect(pod.left).toBe(-10);
1354
+ expect(pod.right).toBe(10);
1355
+ expect(pod.bottom).toBe(-10);
1356
+ expect(pod.top).toBe(10);
1357
+ expect(pod.near).toBeCloseTo(0.1, 6);
1358
+ expect(pod.far).toBe(100);
1359
+ // Perspective fields sentinel
1360
+ expect(pod.fov).toBe(0);
1361
+ expect(pod.aspect).toBe(1);
1362
+ expect(pod.projection).toBe(CAMERA_PROJECTION_ORTHOGRAPHIC);
1363
+ // Tonemap trio defaults
1364
+ expect(pod.tonemap).toBe(TONEMAP_NONE);
1365
+ expect(pod.exposure).toBeCloseTo(1.0, 6);
1366
+ expect(pod.whitePoint).toBeCloseTo(4.0, 6);
1367
+ // Post-processing defaults
1368
+ expect(pod.antialias).toBe(ANTIALIAS_NONE);
1369
+ expect(pod.historyVersion).toBe(0);
1370
+ expect(pod.bloom).toBe(BLOOM_DISABLED);
1371
+ expect(pod.bloomThreshold).toBeCloseTo(1.0, 6);
1372
+ expect(pod.bloomIntensity).toBeCloseTo(1.0, 6);
1373
+ expect(pod.bloomBlurRadius).toBeCloseTo(4.0, 6);
1374
+ // Clear-color default: transparent black array [0,0,0,0] (feat-20260709 M3).
1375
+ expect(Array.from(pod.clearColor)).toEqual([0, 0, 0, 0]);
1376
+ // aspect-sync opt-out default (feat-20260617 / M3): the sidecar only
1377
+ // touches perspective cameras, but the column default is shared.
1378
+ expect(pod.autoAspect).toBe(true);
1379
+ });
1380
+
1381
+ it('orthographic({ ..., near: -1, far: 1 }) — explicit near/far overrides', () => {
1382
+ const pod = orthographic({ left: 0, right: 800, bottom: 600, top: 0, near: -1, far: 1 });
1383
+ expect(pod.left).toBe(0);
1384
+ expect(pod.right).toBe(800);
1385
+ expect(pod.bottom).toBe(600);
1386
+ expect(pod.top).toBe(0);
1387
+ expect(pod.near).toBe(-1);
1388
+ expect(pod.far).toBe(1);
1389
+ expect(pod.projection).toBe(CAMERA_PROJECTION_ORTHOGRAPHIC);
1390
+ expect(pod.fov).toBe(0);
1391
+ expect(pod.aspect).toBe(1);
1392
+ expect(pod.tonemap).toBe(TONEMAP_NONE);
1393
+ });
1394
+
1395
+ it('perspective + orthographic 20-field counts (20 Camera columns)', () => {
1396
+ const p = perspective({ fov: 60, aspect: 4 / 3 });
1397
+ const o = orthographic({ left: -1, right: 1, bottom: -1, top: 1 });
1398
+ // Both return exactly 20 fields (17 f32 + historyVersion u32 + one clearColor array +
1399
+ // autoAspect bool column; feat-20260709 M3 collapsed the 4-scalar
1400
+ // clear-color quartet into one inline array<f32,4>).
1401
+ expect(Object.keys(p).length).toBe(20);
1402
+ expect(Object.keys(o).length).toBe(20);
1403
+ });
1404
+ });
1405
+
1406
+ // ────────────────────────────────────────────────────────────────────────────
1407
+ // w14-b: Camera.fields reflection guard (AC-07 SSOT — 17 f32 fields)
1408
+ // ────────────────────────────────────────────────────────────────────────────
1409
+
1410
+ describe('Camera.fields reflection (w14 AC-07 SSOT)', () => {
1411
+ it('Camera.fields has exactly 20 keys matching the Camera column set', () => {
1412
+ const keys = Object.keys(Camera.fields).sort();
1413
+ expect(keys).toEqual([
1414
+ 'antialias',
1415
+ 'aspect',
1416
+ 'autoAspect',
1417
+ 'bloom',
1418
+ 'bloomBlurRadius',
1419
+ 'bloomIntensity',
1420
+ 'bloomThreshold',
1421
+ 'bottom',
1422
+ 'clearColor',
1423
+ 'exposure',
1424
+ 'far',
1425
+ 'fov',
1426
+ 'historyVersion',
1427
+ 'left',
1428
+ 'near',
1429
+ 'projection',
1430
+ 'right',
1431
+ 'tonemap',
1432
+ 'top',
1433
+ 'whitePoint',
1434
+ ]);
1435
+ });
1436
+
1437
+ it('every Camera.fields entry has the declared numeric, bool, or array storage kind', () => {
1438
+ for (const key of Object.keys(Camera.fields) as Array<keyof typeof Camera.fields>) {
1439
+ const expected =
1440
+ key === 'autoAspect'
1441
+ ? 'bool'
1442
+ : key === 'clearColor'
1443
+ ? 'array<f32, 4>'
1444
+ : key === 'historyVersion'
1445
+ ? 'u32'
1446
+ : 'f32';
1447
+ expect(Camera.fields[key].type).toBe(expected);
1448
+ }
1449
+ });
1450
+
1451
+ it('Camera.fields is frozen', () => {
1452
+ expect(Object.isFrozen(Camera.fields)).toBe(true);
1453
+ });
1454
+
1455
+ it("Camera.fields defaults match factory reference values (confirmed by w14-a's snapshot)", () => {
1456
+ // The per-field defaults stored in Camera.fields after M3 migration
1457
+ // should align with what perspective() / orthographic() use as
1458
+ // their base values. Note: fov / aspect / near / far have no default
1459
+ // (perspective quartet is explicit-only per OOS-5).
1460
+ const d = Camera.fields;
1461
+ expect(d.projection.default).toBe(0);
1462
+ expect(d.left.default).toBe(-1);
1463
+ expect(d.right.default).toBe(1);
1464
+ expect(d.bottom.default).toBe(-1);
1465
+ expect(d.top.default).toBe(1);
1466
+ expect(d.tonemap.default).toBe(0);
1467
+ expect(d.exposure.default).toBeCloseTo(1.0, 6);
1468
+ expect(d.whitePoint.default).toBeCloseTo(4.0, 6);
1469
+ expect(d.antialias.default).toBe(0);
1470
+ expect(d.historyVersion.default).toBe(0);
1471
+ expect(d.bloom.default).toBe(0);
1472
+ expect(d.bloomThreshold.default).toBeCloseTo(1.0, 6);
1473
+ expect(d.bloomIntensity.default).toBeCloseTo(1.0, 6);
1474
+ expect(d.bloomBlurRadius.default).toBeCloseTo(4.0, 6);
1475
+ // Clear-color default: explicit layer-2 transparent black array (feat-20260709
1476
+ // M3; array layer-3 fallback is all-zero so the default MUST be explicit,
1477
+ // D-5).
1478
+ expect(Array.from(d.clearColor.default as Float32Array)).toEqual([0, 0, 0, 0]);
1479
+ // aspect-sync opt-out default (feat-20260617 / M3).
1480
+ expect(d.autoAspect.default).toBe(true);
1481
+ // Perspective quartet defaults intentionally absent (OOS-5).
1482
+ expect(d.fov.default).toBeUndefined();
1483
+ expect(d.aspect.default).toBeUndefined();
1484
+ expect(d.near.default).toBeUndefined();
1485
+ expect(d.far.default).toBeUndefined();
1486
+ });
1487
+ });
1488
+
1489
+ describe('componentDefinition(Camera).defaults — frozen token defaults map (AC-07 + feat-20260528-fxaa-post-processing + feat-20260531-bloom + feat-20260608-clear-color)', () => {
1490
+ it('componentDefinition(Camera).defaults equals { projection: 0, left: -1, right: 1, bottom: -1, top: 1, tonemap: 0, exposure: 1.0, whitePoint: 4.0, antialias: 0, bloom: 0, bloomThreshold: 1.0, bloomIntensity: 1.0, bloomBlurRadius: 4.0, clearColor: [0,0,0,0], autoAspect: true }', () => {
1491
+ expect(componentDefinition(Camera).defaults).toEqual({
1492
+ projection: 0,
1493
+ left: -1,
1494
+ right: 1,
1495
+ bottom: -1,
1496
+ top: 1,
1497
+ tonemap: 0,
1498
+ exposure: 1.0,
1499
+ whitePoint: 4.0,
1500
+ antialias: 0,
1501
+ historyVersion: 0,
1502
+ bloom: 0,
1503
+ bloomThreshold: 1.0,
1504
+ bloomIntensity: 1.0,
1505
+ bloomBlurRadius: 4.0,
1506
+ clearColor: new Float32Array([0, 0, 0, 0]),
1507
+ autoAspect: true,
1508
+ });
1509
+ });
1510
+
1511
+ it('componentDefinition(Camera).defaults is deep-frozen (Object.isFrozen returns true)', () => {
1512
+ expect(componentDefinition(Camera).defaults).toBeDefined();
1513
+ // Object.isFrozen is true for any frozen object; defineComponent
1514
+ // freezes the per-component defaults map at registration time.
1515
+ expect(Object.isFrozen(componentDefinition(Camera).defaults)).toBe(true);
1516
+ });
1517
+
1518
+ it('componentDefinition(Camera).defaults does NOT carry fov / aspect / near / far (perspective quartet stays explicit per OOS-5)', () => {
1519
+ const d = componentDefinition(Camera).defaults as
1520
+ | Readonly<Record<string, unknown>>
1521
+ | undefined;
1522
+ expect(d).toBeDefined();
1523
+ if (d === undefined) return;
1524
+ expect('fov' in d).toBe(false);
1525
+ expect('aspect' in d).toBe(false);
1526
+ expect('near' in d).toBe(false);
1527
+ expect('far' in d).toBe(false);
1528
+ });
1529
+ });
1530
+
1531
+ describe('Camera 4-field perspective spawn — token defaults fill ortho quartet (AC-07 runtime)', () => {
1532
+ it('world.spawn({ component: Camera, data: { fov, aspect, near, far } }) yields projection === 0 + ortho defaults', () => {
1533
+ const world = new World();
1534
+ const e = world
1535
+ .spawn({
1536
+ component: Camera,
1537
+ data: {
1538
+ fov: Math.PI / 4,
1539
+ aspect: 16 / 9,
1540
+ near: 0.1,
1541
+ far: 100,
1542
+ },
1543
+ })
1544
+ .unwrap();
1545
+ const row = world.get(e, Camera).unwrap();
1546
+
1547
+ // Layer-1: explicit values pass through (f32 round on 16 / 9).
1548
+ expect(row.fov).toBeCloseTo(Math.PI / 4, 6);
1549
+ expect(row.aspect).toBeCloseTo(16 / 9, 6);
1550
+ expect(row.near).toBeCloseTo(0.1, 6);
1551
+ expect(row.far).toBe(100);
1552
+
1553
+ // Layer-2 (token defaults): projection + 4 ortho fields + 3 tonemap fields.
1554
+ expect(row.projection).toBe(CAMERA_PROJECTION_PERSPECTIVE);
1555
+ expect(row.left).toBe(-1);
1556
+ expect(row.right).toBe(1);
1557
+ expect(row.bottom).toBe(-1);
1558
+ expect(row.top).toBe(1);
1559
+ expect(row.tonemap).toBe(TONEMAP_NONE);
1560
+ expect(row.exposure).toBeCloseTo(1.0, 6);
1561
+ expect(row.whitePoint).toBeCloseTo(4.0, 6);
1562
+ });
1563
+
1564
+ it('explicit ortho override path: caller passes projection + 4 ortho fields, defaults yield', () => {
1565
+ // Token defaults must NOT clobber explicit caller input -- this is
1566
+ // the layer-1 wins clause from research section F1.
1567
+ const world = new World();
1568
+ const e = world
1569
+ .spawn({
1570
+ component: Camera,
1571
+ data: {
1572
+ fov: 0,
1573
+ aspect: 1,
1574
+ near: 0.1,
1575
+ far: 100,
1576
+ projection: 1,
1577
+ left: -10,
1578
+ right: 10,
1579
+ bottom: -10,
1580
+ top: 10,
1581
+ },
1582
+ })
1583
+ .unwrap();
1584
+ const row = world.get(e, Camera).unwrap();
1585
+ expect(row.projection).toBe(1);
1586
+ expect(row.left).toBe(-10);
1587
+ expect(row.right).toBe(10);
1588
+ expect(row.bottom).toBe(-10);
1589
+ expect(row.top).toBe(10);
1590
+ });
1591
+ });
1592
+
1593
+ // feat-20260519-tonemap-reinhard-mvp / M1 / T-M1.1 + T-M1.2.
1594
+ //
1595
+ // Camera tonemap field surface (AC-01 + AC-04 + AC-05 + AC-06). Three
1596
+ // schema columns (tonemap / exposure / whitePoint) plus a string-literal
1597
+ // closed union map (TONEMAP_NONE / TONEMAP_REINHARD_EXTENDED +
1598
+ // tonemapFromF32) following the same shape as projection +
1599
+ // cameraProjectionFromF32. Defaults: { tonemap: 0, exposure: 1.0,
1600
+ // whitePoint: 4.0 } belong to the layer-2 token defaults map (D-1 + D-7
1601
+ // in plan-strategy section 2.3). Spawn-time numeric range fail-fast is
1602
+ // out of scope per O1 (plan-decisions L-O1) — shader floor max(Y, 1e-5)
1603
+ // guards exposure / whitePoint == 0 from NaN.
1604
+ describe('Camera tonemap mapping (AC-01)', () => {
1605
+ it('TONEMAP_NONE === 0 + TONEMAP_REINHARD_EXTENDED === 1 (numeric encoding)', () => {
1606
+ expect(TONEMAP_NONE).toBe(0);
1607
+ expect(TONEMAP_REINHARD_EXTENDED).toBe(1);
1608
+ });
1609
+
1610
+ it('tonemapFromF32(0) === "none" + tonemapFromF32(1) === "reinhard-extended"', () => {
1611
+ expect(tonemapFromF32(0)).toBe('none');
1612
+ expect(tonemapFromF32(1)).toBe('reinhard-extended');
1613
+ });
1614
+
1615
+ it('tonemapFromF32 maps all 8 modes correctly', () => {
1616
+ expect(tonemapFromF32(2)).toBe('linear');
1617
+ expect(tonemapFromF32(3)).toBe('cineon');
1618
+ expect(tonemapFromF32(4)).toBe('aces-filmic');
1619
+ expect(tonemapFromF32(5)).toBe('agx');
1620
+ expect(tonemapFromF32(6)).toBe('neutral');
1621
+ expect(TONEMAP_REINHARD).toBe(7);
1622
+ expect(tonemapFromF32(TONEMAP_REINHARD)).toBe('reinhard');
1623
+ });
1624
+
1625
+ it('tonemapFromF32 falls back to "none" for out-of-range numeric (defensive)', () => {
1626
+ expect(tonemapFromF32(-1)).toBe('none');
1627
+ expect(tonemapFromF32(8)).toBe('none');
1628
+ expect(tonemapFromF32(NaN)).toBe('none');
1629
+ });
1630
+ });
1631
+
1632
+ describe('Camera 7-field perspective + opt-in tonemap spawn (AC-01 runtime)', () => {
1633
+ it('spawn with tonemap "reinhard-extended" path keeps numeric encoding 1', () => {
1634
+ const world = new World();
1635
+ const e = world
1636
+ .spawn({
1637
+ component: Camera,
1638
+ data: {
1639
+ fov: Math.PI / 4,
1640
+ aspect: 16 / 9,
1641
+ near: 0.1,
1642
+ far: 100,
1643
+ tonemap: TONEMAP_REINHARD_EXTENDED,
1644
+ exposure: 1.0,
1645
+ whitePoint: 4.0,
1646
+ },
1647
+ })
1648
+ .unwrap();
1649
+ const row = world.get(e, Camera).unwrap();
1650
+ expect(row.tonemap).toBe(TONEMAP_REINHARD_EXTENDED);
1651
+ expect(row.exposure).toBeCloseTo(1.0, 6);
1652
+ expect(row.whitePoint).toBeCloseTo(4.0, 6);
1653
+ // Layer-2: ortho quartet still defaulted.
1654
+ expect(row.left).toBe(-1);
1655
+ expect(row.right).toBe(1);
1656
+ });
1657
+
1658
+ it('explicit non-default exposure + whitePoint pass through layer-1 wins', () => {
1659
+ const world = new World();
1660
+ const e = world
1661
+ .spawn({
1662
+ component: Camera,
1663
+ data: {
1664
+ fov: Math.PI / 4,
1665
+ aspect: 16 / 9,
1666
+ near: 0.1,
1667
+ far: 100,
1668
+ tonemap: TONEMAP_REINHARD_EXTENDED,
1669
+ exposure: 2.5,
1670
+ whitePoint: 8.0,
1671
+ },
1672
+ })
1673
+ .unwrap();
1674
+ const row = world.get(e, Camera).unwrap();
1675
+ expect(row.exposure).toBeCloseTo(2.5, 6);
1676
+ expect(row.whitePoint).toBeCloseTo(8.0, 6);
1677
+ });
1678
+ });
1679
+
1680
+ // M2 / w9: Camera.perspective / Camera.orthographic static factory tests
1681
+ // (feat-20260525-boilerplate-reduction-pod-defaults-factories)
1682
+ //
1683
+ // Covers AC-05 + AC-06 (perspective + orthographic returns with defaults).
1684
+ // Plan-strategy section 5.2 unit-test requirement + section 5.3 testing points.
1685
+ describe('perspective factory', () => {
1686
+ it('perspective({ fov: 60, aspect: 4/3 }) returns POD with projection=0, near=0.1, far=100', () => {
1687
+ const pod = perspective({ fov: 60, aspect: 4 / 3 });
1688
+ expect(pod.fov).toBe(60);
1689
+ expect(pod.aspect).toBeCloseTo(4 / 3, 6);
1690
+ expect(pod.near).toBeCloseTo(0.1, 6);
1691
+ expect(pod.far).toBe(100);
1692
+ expect(pod.projection).toBe(CAMERA_PROJECTION_PERSPECTIVE);
1693
+ expect(pod.left).toBe(-1);
1694
+ expect(pod.right).toBe(1);
1695
+ expect(pod.bottom).toBe(-1);
1696
+ expect(pod.top).toBe(1);
1697
+ expect(pod.tonemap).toBe(TONEMAP_NONE);
1698
+ expect(pod.exposure).toBeCloseTo(1.0, 6);
1699
+ expect(pod.whitePoint).toBeCloseTo(4.0, 6);
1700
+ });
1701
+
1702
+ it('perspective({ fov: 45, aspect: 16/9, near: 0.01, far: 1000 }) returns explicit overrides', () => {
1703
+ const pod = perspective({ fov: 45, aspect: 16 / 9, near: 0.01, far: 1000 });
1704
+ expect(pod.fov).toBe(45);
1705
+ expect(pod.aspect).toBeCloseTo(16 / 9, 6);
1706
+ expect(pod.near).toBeCloseTo(0.01, 6);
1707
+ expect(pod.far).toBe(1000);
1708
+ expect(pod.projection).toBe(CAMERA_PROJECTION_PERSPECTIVE);
1709
+ // Ortho quartet + tonemap get defaults.
1710
+ expect(pod.left).toBe(-1);
1711
+ expect(pod.right).toBe(1);
1712
+ expect(pod.tonemap).toBe(TONEMAP_NONE);
1713
+ expect(pod.exposure).toBeCloseTo(1.0, 6);
1714
+ });
1715
+
1716
+ it('perspective return value has all 20 Camera fields', () => {
1717
+ const pod = perspective({ fov: 60, aspect: 4 / 3 });
1718
+ const keys = Object.keys(pod).sort();
1719
+ expect(keys).toEqual([
1720
+ 'antialias',
1721
+ 'aspect',
1722
+ 'autoAspect',
1723
+ 'bloom',
1724
+ 'bloomBlurRadius',
1725
+ 'bloomIntensity',
1726
+ 'bloomThreshold',
1727
+ 'bottom',
1728
+ 'clearColor',
1729
+ 'exposure',
1730
+ 'far',
1731
+ 'fov',
1732
+ 'historyVersion',
1733
+ 'left',
1734
+ 'near',
1735
+ 'projection',
1736
+ 'right',
1737
+ 'tonemap',
1738
+ 'top',
1739
+ 'whitePoint',
1740
+ ]);
1741
+ });
1742
+ });
1743
+
1744
+ describe('orthographic factory', () => {
1745
+ it('orthographic({ left: -10, right: 10, bottom: -10, top: 10 }) returns POD with projection=1, near=0.1, far=100', () => {
1746
+ const pod = orthographic({ left: -10, right: 10, bottom: -10, top: 10 });
1747
+ expect(pod.projection).toBe(CAMERA_PROJECTION_ORTHOGRAPHIC);
1748
+ expect(pod.left).toBe(-10);
1749
+ expect(pod.right).toBe(10);
1750
+ expect(pod.bottom).toBe(-10);
1751
+ expect(pod.top).toBe(10);
1752
+ expect(pod.near).toBeCloseTo(0.1, 6);
1753
+ expect(pod.far).toBe(100);
1754
+ // Perspective fields get sensible defaults (fov=0, aspect=1 for ortho).
1755
+ expect(pod.fov).toBe(0);
1756
+ expect(pod.aspect).toBe(1);
1757
+ expect(pod.tonemap).toBe(TONEMAP_NONE);
1758
+ expect(pod.exposure).toBeCloseTo(1.0, 6);
1759
+ expect(pod.whitePoint).toBeCloseTo(4.0, 6);
1760
+ });
1761
+
1762
+ it('orthographic({ left: 0, right: 800, bottom: 600, top: 0, near: -1, far: 1 }) returns explicit overrides', () => {
1763
+ const pod = orthographic({
1764
+ left: 0,
1765
+ right: 800,
1766
+ bottom: 600,
1767
+ top: 0,
1768
+ near: -1,
1769
+ far: 1,
1770
+ });
1771
+ expect(pod.left).toBe(0);
1772
+ expect(pod.right).toBe(800);
1773
+ expect(pod.bottom).toBe(600);
1774
+ expect(pod.top).toBe(0);
1775
+ expect(pod.near).toBe(-1);
1776
+ expect(pod.far).toBe(1);
1777
+ expect(pod.projection).toBe(CAMERA_PROJECTION_ORTHOGRAPHIC);
1778
+ });
1779
+ });
1780
+ }
1781
+
1782
+ {
1783
+ // --- from layer.test.ts ---
1784
+ // feat-20260525-boilerplate-reduction-pod-defaults-factories / M1 / w3b.
1785
+ //
1786
+ // Layer defaults fill regression barrier (AC-04 sweep table #14).
1787
+ // Three concerns:
1788
+ //
1789
+ // (a) `componentDefinition(Layer).defaults` is a frozen map asserting `{ value: 0 }`
1790
+ // (default game layer, charter P1 progressive disclosure).
1791
+ //
1792
+ // (b) `world.spawn({ component: Layer, data: {} })` yields `value === 0`
1793
+ // (the default game layer convention).
1794
+ //
1795
+ // (c) `world.spawn` with an explicit `value: 100` preserves the explicit
1796
+ // value -- layer-2 defaults do NOT overwrite layer-1 explicit input.
1797
+ //
1798
+ // Anchors: requirements AC-04 (sweep table #14 Layer defaults-added);
1799
+ // plan-strategy section 2 sweep pre-judgment table.
1800
+
1801
+ describe('componentDefinition(Layer).defaults -- frozen map assertion (AC-04 #14)', () => {
1802
+ it('componentDefinition(Layer).defaults equals { value: 0 }', () => {
1803
+ expect(componentDefinition(Layer).defaults).toEqual({ value: 0 });
1804
+ });
1805
+
1806
+ it('componentDefinition(Layer).defaults is deep-frozen (Object.isFrozen returns true)', () => {
1807
+ expect(componentDefinition(Layer).defaults).toBeDefined();
1808
+ expect(Object.isFrozen(componentDefinition(Layer).defaults as object)).toBe(true);
1809
+ });
1810
+ });
1811
+
1812
+ describe('Layer spawn with data: {} -- default game layer (AC-04)', () => {
1813
+ it('spawn Layer with data: {} yields value === 0', () => {
1814
+ const world = new World();
1815
+ const e = world.spawn({ component: Layer, data: {} }).unwrap();
1816
+ const row = world.get(e, Layer).unwrap();
1817
+
1818
+ expect(row.value).toBe(0);
1819
+ });
1820
+ });
1821
+
1822
+ describe('Layer spawn with explicit value -- layer-1 wins (AC-04)', () => {
1823
+ it('spawn Layer with explicit value: 100 preserves the explicit value', () => {
1824
+ const world = new World();
1825
+ const e = world.spawn({ component: Layer, data: { value: 100 } }).unwrap();
1826
+ const row = world.get(e, Layer).unwrap();
1827
+
1828
+ expect(row.value).toBe(100);
1829
+ });
1830
+ });
1831
+ }
1832
+
1833
+ {
1834
+ // --- from mesh-renderer.test.ts ---
1835
+ // feat-20260608-mesh-multi-section-primitive-multi-material-slot M2 / w8.
1836
+ //
1837
+ // MeshRenderer schema migration barrier: material field removed, materials
1838
+ // array added.
1839
+ //
1840
+ // (a) `componentDefinition(MeshRenderer).defaults` is a frozen map asserting
1841
+ // `{ materials: [] }` (empty array routes to D-Q7 case B default
1842
+ // material path).
1843
+ //
1844
+ // (b) `world.spawn({ component: MeshRenderer, data: {} })` produces a row
1845
+ // with `materials === []` (the D-Q7 case B path, mid-grey default).
1846
+ //
1847
+ // (c) `world.spawn` with an explicit materials array round-trips: write
1848
+ // and read-back the materials array.
1849
+ //
1850
+ // (d) `world.spawn` with the old `material` field (singular) is a TS
1851
+ // compile-time error (verified via test-d.ts).
1852
+
1853
+ describe('componentDefinition(MeshRenderer).defaults — frozen map assertion (w8)', () => {
1854
+ it('componentDefinition(MeshRenderer).defaults equals { materials: [] }', () => {
1855
+ expect(componentDefinition(MeshRenderer).defaults).toEqual({ materials: [] });
1856
+ });
1857
+
1858
+ it('componentDefinition(MeshRenderer).defaults is deep-frozen (Object.isFrozen returns true)', () => {
1859
+ expect(componentDefinition(MeshRenderer).defaults).toBeDefined();
1860
+ expect(Object.isFrozen(componentDefinition(MeshRenderer).defaults as object)).toBe(true);
1861
+ });
1862
+ });
1863
+
1864
+ describe('MeshRenderer spawn with data: {} — D-Q7 case B path (w8)', () => {
1865
+ it('spawn MeshRenderer with data: {} produces materials column === empty Uint32Array', () => {
1866
+ const world = new World();
1867
+ const e = world.spawn({ component: MeshRenderer, data: {} }).unwrap();
1868
+ const row = world.get(e, MeshRenderer).unwrap();
1869
+
1870
+ // materials column defaults to empty array for D-Q7 case B;
1871
+ // runtime type is Uint32Array (handles stored as u32).
1872
+ expect(row.materials).toBeInstanceOf(Uint32Array);
1873
+ expect((row.materials as unknown as Uint32Array).length).toBe(0);
1874
+ });
1875
+ });
1876
+
1877
+ describe('MeshRenderer spawn with explicit materials array — round-trip (w8)', () => {
1878
+ it('spawn with explicit materials array preserves the handle values as Uint32Array', () => {
1879
+ const world = new World();
1880
+
1881
+ const matHandle = world.allocSharedRef('MaterialAsset', {
1882
+ kind: 'material',
1883
+ baseColor: [1, 0, 0, 1],
1884
+ passes: [{ name: 'forward', program: { module: 'project::standard' } }],
1885
+ } as never);
1886
+
1887
+ expect(matHandle).toBeGreaterThan(0);
1888
+
1889
+ const e = world.spawn({ component: MeshRenderer, data: { materials: [matHandle] } }).unwrap();
1890
+ const row = world.get(e, MeshRenderer).unwrap();
1891
+
1892
+ // Runtime: Uint32Array with the handle value
1893
+ expect(row.materials).toBeInstanceOf(Uint32Array);
1894
+ expect((row.materials as unknown as Uint32Array)[0]).toBe(matHandle);
1895
+ });
1896
+
1897
+ it('spawn with multiple materials round-trips Uint32Array', () => {
1898
+ const world = new World();
1899
+
1900
+ const m0 = world.allocSharedRef('MaterialAsset', {
1901
+ kind: 'material',
1902
+ baseColor: [1, 0, 0, 1],
1903
+ } as never);
1904
+ const m1 = world.allocSharedRef('MaterialAsset', {
1905
+ kind: 'material',
1906
+ baseColor: [0, 1, 0, 1],
1907
+ } as never);
1908
+
1909
+ const e = world.spawn({ component: MeshRenderer, data: { materials: [m0, m1] } }).unwrap();
1910
+ const row = world.get(e, MeshRenderer).unwrap();
1911
+
1912
+ expect(row.materials).toBeInstanceOf(Uint32Array);
1913
+ expect((row.materials as unknown as Uint32Array).length).toBe(2);
1914
+ expect((row.materials as unknown as Uint32Array)[0]).toBe(m0);
1915
+ expect((row.materials as unknown as Uint32Array)[1]).toBe(m1);
1916
+ });
1917
+ });
1918
+ }
1919
+
1920
+ {
1921
+ // --- from skybox-background.test.ts ---
1922
+ // feat-20260531-skybox-env-background / M1 / w2.
1923
+ //
1924
+ // SkyboxBackground component unit tests (TDD red phase -- the component
1925
+ // source file does not exist yet; this test will turn green after w1
1926
+ // creates skybox-background.ts, barrel, and top-level re-export).
1927
+ //
1928
+ // Covers:
1929
+ // (a) Spawning an entity with SkyboxBackground can be queried via
1930
+ // world.query row iteration (AC-01).
1931
+ // (b) skyboxModeFromF32(0) returns 'cubemap' with TS narrow type
1932
+ // (no `as` cast, AC-01).
1933
+ // (c) SKYBOX_MODE_CUBEMAP === 0 invariant (non-zero sentinel guard,
1934
+ // AC-01).
1935
+ // (d) defaults: { mode: SKYBOX_MODE_CUBEMAP } takes effect -- spawn
1936
+ // without `mode` yields mode === 0 (AC-01).
1937
+ // (e) The mapper function uses exhaustive switch (no `default` branch,
1938
+ // AC-02).
1939
+ //
1940
+ // Anchors: requirements AC-01 / AC-02; plan-strategy D-5 (f32 enum
1941
+ // column + mapper); research Finding 6 (camera.ts:51,105,145,221-237
1942
+ // cameraProjectionFromF32 pattern); plan-tasks.json w2 acceptanceCheck.
1943
+
1944
+ describe('SkyboxBackground — component schema (AC-01)', () => {
1945
+ it('SKYBOX_MODE_CUBEMAP equals 0 (non-zero guard)', () => {
1946
+ expect(SKYBOX_MODE_CUBEMAP).toBe(0);
1947
+ });
1948
+
1949
+ it('SkyboxBackground is a defineComponent token', () => {
1950
+ expect(SkyboxBackground).toBeDefined();
1951
+ expect(typeof SkyboxBackground.name).toBe('string');
1952
+ });
1953
+
1954
+ it('defaults: { mode: SKYBOX_MODE_CUBEMAP } yields mode===0 on spawn without mode', () => {
1955
+ const world = new World();
1956
+ const e = world
1957
+ .spawn({
1958
+ component: SkyboxBackground,
1959
+ data: {
1960
+ equirect: 42 as unknown as never, // Handle<EquirectAsset> stored as u32
1961
+ },
1962
+ })
1963
+ .unwrap();
1964
+ const row = world.get(e, SkyboxBackground).unwrap();
1965
+ expect(row.mode).toBe(SKYBOX_MODE_CUBEMAP);
1966
+ });
1967
+
1968
+ it('entity with SkyboxBackground is hit by a single-component query', () => {
1969
+ const world = new World();
1970
+ world.spawn({
1971
+ component: SkyboxBackground,
1972
+ data: {
1973
+ equirect: 7 as unknown as never,
1974
+ mode: SKYBOX_MODE_CUBEMAP,
1975
+ },
1976
+ });
1977
+
1978
+ const query = world.query({ with: [SkyboxBackground] }).unwrap();
1979
+ expect([...query].length).toBe(1);
1980
+ });
1981
+
1982
+ it('world.get returns the equirect handle (u32-stored)', () => {
1983
+ const world = new World();
1984
+ const handle = 99;
1985
+ const e = world
1986
+ .spawn({
1987
+ component: SkyboxBackground,
1988
+ data: { equirect: handle as unknown as never, mode: 0 },
1989
+ })
1990
+ .unwrap();
1991
+ const row = world.get(e, SkyboxBackground).unwrap();
1992
+ expect(row.equirect).toBe(handle);
1993
+ });
1994
+ });
1995
+
1996
+ describe('skyboxModeFromF32 — mapper (AC-01 / AC-02)', () => {
1997
+ it('skyboxModeFromF32(0) returns "cubemap" (string literal, not widened to string)', () => {
1998
+ const result = skyboxModeFromF32(0);
1999
+ expect(result).toBe('cubemap');
2000
+ // TS narrow: type is literal 'cubemap', not widened `string`.
2001
+ expectTypeOf(result).toEqualTypeOf<'cubemap'>();
2002
+ });
2003
+
2004
+ it('skyboxModeFromF32(unknown) also returns "cubemap" (only mode exists)', () => {
2005
+ // Currently only one mode exists; any value maps to 'cubemap'.
2006
+ const result = skyboxModeFromF32(99);
2007
+ expect(result).toBe('cubemap');
2008
+ expectTypeOf(result).toEqualTypeOf<'cubemap'>();
2009
+ });
2010
+
2011
+ it('switch on SkyboxMode is exhaustive (no `as` cast in test)', () => {
2012
+ // This demonstrates that the consumer-side switch on SkyboxMode is
2013
+ // exhaustive -- when a future mode is added, TS will catch the missing
2014
+ // branch at compile time.
2015
+ const mode: SkyboxMode = skyboxModeFromF32(0);
2016
+ let hit = false;
2017
+ switch (mode) {
2018
+ case 'cubemap':
2019
+ hit = true;
2020
+ break;
2021
+ // No `default` branch -- TS enforces exhaustiveness when new
2022
+ // members are added to SkyboxMode.
2023
+ }
2024
+ expect(hit).toBe(true);
2025
+ });
2026
+ });
2027
+ }
2028
+
2029
+ {
2030
+ // --- from sort-key.test.ts ---
2031
+ // feat-20260525-boilerplate-reduction-pod-defaults-factories / M1 / w3c.
2032
+ //
2033
+ // SortKey defaults fill regression barrier (AC-04 sweep table #15).
2034
+ // Three concerns:
2035
+ //
2036
+ // (a) `componentDefinition(SortKey).defaults` is a frozen map asserting `{ value: 0 }`
2037
+ // (value 0 means 'no override, use mode formula' per transparent-sort
2038
+ // algorithm semantics, charter P1 progressive disclosure).
2039
+ //
2040
+ // (b) `world.spawn({ component: SortKey, data: {} })` yields `value === 0`
2041
+ // (the 'no override' sentinel).
2042
+ //
2043
+ // (c) `world.spawn` with an explicit `value: -100` preserves the explicit
2044
+ // value -- layer-2 defaults do NOT overwrite layer-1 explicit input.
2045
+ //
2046
+ // Anchors: requirements AC-04 (sweep table #15 SortKey defaults-added);
2047
+ // plan-strategy section 2 sweep pre-judgment table.
2048
+
2049
+ describe('componentDefinition(SortKey).defaults -- frozen map assertion (AC-04 #15)', () => {
2050
+ it('componentDefinition(SortKey).defaults equals { value: 0 }', () => {
2051
+ expect(componentDefinition(SortKey).defaults).toEqual({ value: 0 });
2052
+ });
2053
+
2054
+ it('componentDefinition(SortKey).defaults is deep-frozen (Object.isFrozen returns true)', () => {
2055
+ expect(componentDefinition(SortKey).defaults).toBeDefined();
2056
+ expect(Object.isFrozen(componentDefinition(SortKey).defaults as object)).toBe(true);
2057
+ });
2058
+ });
2059
+
2060
+ describe('SortKey spawn with data: {} -- no-override sentinel (AC-04)', () => {
2061
+ it('spawn SortKey with data: {} yields value === 0', () => {
2062
+ const world = new World();
2063
+ const e = world.spawn({ component: SortKey, data: {} }).unwrap();
2064
+ const row = world.get(e, SortKey).unwrap();
2065
+
2066
+ expect(row.value).toBe(0);
2067
+ });
2068
+ });
2069
+
2070
+ describe('SortKey spawn with explicit value -- layer-1 wins (AC-04)', () => {
2071
+ it('spawn SortKey with explicit value: -100 preserves the explicit value', () => {
2072
+ const world = new World();
2073
+ const e = world.spawn({ component: SortKey, data: { value: -100 } }).unwrap();
2074
+ const row = world.get(e, SortKey).unwrap();
2075
+
2076
+ expect(row.value).toBe(-100);
2077
+ });
2078
+ });
2079
+ }
2080
+
2081
+ {
2082
+ // --- from sprite-components-schema.test.ts ---
2083
+ // feat-20260521-sprite-atlas-animation / M2 / T-10.
2084
+ //
2085
+ // TDD red phase: this runtime suite exercises `world.spawn` + `world.get`
2086
+ // round-trips for the two new sprite-only ECS components — both of which
2087
+ // land in T-11 (SpriteRegionOverride) + T-12 (SpriteAnimation). Before
2088
+ // those impl tasks land the imports `../sprite-region-override` /
2089
+ // `../sprite-animation` fail to resolve and the suite stays red. After
2090
+ // T-11 + T-12 the imports resolve, `defineComponent` accepts the schema
2091
+ // keywords (no `SchemaUnsupportedFieldError` / `ManagedArrayElementType-
2092
+ // NotAllowedError`), and the spawn / get assertions turn green.
2093
+ //
2094
+ // Why a runtime test on top of the T-08 / T-09 type-d coverage?
2095
+ // Plan-strategy section 4 risk R-SCHEMA-1 + R-SCHEMA-2 explicitly cite the
2096
+ // ECS schema whitelist (`SchemaFieldType`) as the structural failure mode:
2097
+ // the type-d files prove the literals look right at TS edge, but the
2098
+ // runtime `defineComponent` call still has to walk the schema and accept
2099
+ // `'array<f32, 4>'` (fixed-length) + `'array<f32>'` (variable) + the
2100
+ // scalar `'u32'` / `'f32'` quartet without throwing. T-10 is the
2101
+ // behavioural witness for those two risks.
2102
+ //
2103
+ // Default-value coverage (component-default-fallback layer-3, AC-02 +
2104
+ // requirements section 2.3 defaults column):
2105
+ // - `currentFrame: 'u32'` -> 0 (scalar layer-3 fallback)
2106
+ // - `accumDt: 'f32'` -> 0 (scalar layer-3 fallback)
2107
+ // - `playbackMode: 'u32'` -> 0 (= SPRITE_PLAYBACK_MODE_LOOP, the
2108
+ // default playbackMode per AC-02 +
2109
+ // requirements section 2.3 footnote
2110
+ // "default 'loop'")
2111
+ // The spawn payload thus only needs to carry frameCount / frameDuration /
2112
+ // regions for an entity to be observable; AI users get a meaningful
2113
+ // 4-step minimal walk-cycle by passing one Float32Array of length
2114
+ // frameCount * 4.
2115
+ //
2116
+ // Anchors: plan-tasks.json T-10 (acceptanceCheck: T-11 + T-12 land then
2117
+ // region round-trip + 6-field default-value + length round-trip + no
2118
+ // SchemaUnsupportedFieldError); plan-strategy section 4 R-SCHEMA-1 + R-
2119
+ // SCHEMA-2 reaction; research F-5; requirements section AC-01 + AC-02 +
2120
+ // section 2.3 (defaults column) + section 2.4; charter F1 + P3.
2121
+
2122
+ describe('SpriteRegionOverride — defineComponent does not throw (R-SCHEMA-2)', () => {
2123
+ it("name + schema lock match the M2 D-6 contract (region: 'array<f32, 4>')", () => {
2124
+ expect(SpriteRegionOverride.name).toBe('SpriteRegionOverride');
2125
+ expect(Object.keys(componentSchema(SpriteRegionOverride)).length).toBe(1);
2126
+ expect((componentSchema(SpriteRegionOverride) as Record<string, unknown>).region).toBe(
2127
+ 'array<f32, 4>',
2128
+ );
2129
+ });
2130
+ });
2131
+
2132
+ describe('SpriteRegionOverride — spawn + get round-trip (AC-01 + AC-03 producer)', () => {
2133
+ it('region [0.5, 0, 0.5, 1] round-trips byte-for-byte', () => {
2134
+ const world = new World();
2135
+ const region = new Float32Array([0.5, 0, 0.5, 1]);
2136
+ const e = world.spawn({ component: SpriteRegionOverride, data: { region } }).unwrap();
2137
+ const snap = world.get(e, SpriteRegionOverride).unwrap();
2138
+ expect(snap.region).toBeInstanceOf(Float32Array);
2139
+ expect(snap.region.length).toBe(4);
2140
+ expect(snap.region[0]).toBeCloseTo(0.5, 6);
2141
+ expect(snap.region[1]).toBeCloseTo(0, 6);
2142
+ expect(snap.region[2]).toBeCloseTo(0.5, 6);
2143
+ expect(snap.region[3]).toBeCloseTo(1, 6);
2144
+ });
2145
+
2146
+ it('region overwrite via world.set updates the snapshot', () => {
2147
+ const world = new World();
2148
+ const e = world
2149
+ .spawn({
2150
+ component: SpriteRegionOverride,
2151
+ data: { region: new Float32Array([0, 0, 1, 1]) },
2152
+ })
2153
+ .unwrap();
2154
+
2155
+ world
2156
+ .set(e, SpriteRegionOverride, { region: new Float32Array([0.25, 0.25, 0.5, 0.5]) })
2157
+ .unwrap();
2158
+
2159
+ const snap = world.get(e, SpriteRegionOverride).unwrap();
2160
+ expect(snap.region.length).toBe(4);
2161
+ expect(snap.region[0]).toBeCloseTo(0.25, 6);
2162
+ expect(snap.region[2]).toBeCloseTo(0.5, 6);
2163
+ });
2164
+ });
2165
+
2166
+ describe('SpriteAnimation — defineComponent does not throw (R-SCHEMA-1)', () => {
2167
+ it('name + schema lock match the M2 D-5 + D-6 contract (6 fields)', () => {
2168
+ expect(SpriteAnimation.name).toBe('SpriteAnimation');
2169
+
2170
+ const schema = componentSchema(SpriteAnimation) as Record<string, unknown>;
2171
+ expect(Object.keys(schema).length).toBe(6);
2172
+ expect(schema.frameCount).toBe('u32');
2173
+ expect(schema.frameDuration).toBe('f32');
2174
+ expect(schema.currentFrame).toBe('u32');
2175
+ expect(schema.accumDt).toBe('f32');
2176
+ expect(schema.regions).toBe('array<f32>');
2177
+ expect(schema.playbackMode).toBe('u32');
2178
+ });
2179
+ });
2180
+
2181
+ describe('SpriteAnimation — spawn + get round-trip (AC-02 + section 2.3)', () => {
2182
+ it('4-frame walk cycle round-trips through ECS columns', () => {
2183
+ const world = new World();
2184
+ const frameCount = 4;
2185
+ const regions = new Float32Array(frameCount * 4);
2186
+ for (let i = 0; i < frameCount; i++) {
2187
+ regions[i * 4 + 0] = i * 0.25;
2188
+ regions[i * 4 + 1] = 0;
2189
+ regions[i * 4 + 2] = 0.25;
2190
+ regions[i * 4 + 3] = 1;
2191
+ }
2192
+ const e = world
2193
+ .spawn({
2194
+ component: SpriteAnimation,
2195
+ data: {
2196
+ frameCount,
2197
+ frameDuration: 0.1,
2198
+ currentFrame: 0,
2199
+ accumDt: 0,
2200
+ regions,
2201
+ playbackMode: SPRITE_PLAYBACK_MODE_LOOP,
2202
+ },
2203
+ })
2204
+ .unwrap();
2205
+ const snap = world.get(e, SpriteAnimation).unwrap();
2206
+ expect(snap.frameCount).toBe(frameCount);
2207
+ expect(snap.frameDuration).toBeCloseTo(0.1, 6);
2208
+ expect(snap.currentFrame).toBe(0);
2209
+ expect(snap.accumDt).toBeCloseTo(0, 6);
2210
+ expect(snap.regions).toBeInstanceOf(Float32Array);
2211
+ expect(snap.regions.length).toBe(frameCount * 4);
2212
+ expect(snap.regions[2 * 4 + 0]).toBeCloseTo(0.5, 6);
2213
+ expect(snap.playbackMode).toBe(SPRITE_PLAYBACK_MODE_LOOP);
2214
+ });
2215
+
2216
+ it('clamp playback mode encodes as numeric 1 in the column', () => {
2217
+ const world = new World();
2218
+ const e = world
2219
+ .spawn({
2220
+ component: SpriteAnimation,
2221
+ data: {
2222
+ frameCount: 3,
2223
+ frameDuration: 0.1,
2224
+ regions: new Float32Array(3 * 4),
2225
+ playbackMode: SPRITE_PLAYBACK_MODE_CLAMP,
2226
+ },
2227
+ })
2228
+ .unwrap();
2229
+ const snap = world.get(e, SpriteAnimation).unwrap();
2230
+ expect(snap.playbackMode).toBe(1);
2231
+ expect(snap.playbackMode).toBe(SPRITE_PLAYBACK_MODE_CLAMP);
2232
+ });
2233
+
2234
+ it('layer-3 defaults fill currentFrame=0 / accumDt=0 / playbackMode=0 when omitted', () => {
2235
+ const world = new World();
2236
+ const e = world
2237
+ .spawn({
2238
+ component: SpriteAnimation,
2239
+ data: {
2240
+ frameCount: 2,
2241
+ frameDuration: 0.05,
2242
+ regions: new Float32Array([0, 0, 0.5, 1, 0.5, 0, 0.5, 1]),
2243
+ },
2244
+ })
2245
+ .unwrap();
2246
+ const snap = world.get(e, SpriteAnimation).unwrap();
2247
+ expect(snap.currentFrame).toBe(0);
2248
+ expect(snap.accumDt).toBeCloseTo(0, 6);
2249
+ expect(snap.playbackMode).toBe(SPRITE_PLAYBACK_MODE_LOOP);
2250
+ expect(snap.regions.length).toBe(2 * 4);
2251
+ });
2252
+
2253
+ it('variable regions length round-trip — frameCount * 4 invariant is data not schema', () => {
2254
+ // Schema does not constrain regions.length; the M4 sprite-animation-tick
2255
+ // system enforces `regions.length === frameCount * 4` at first
2256
+ // observation (D-1 fail-fast path). This test only locks the bytes
2257
+ // round-trip — feeding 12 floats with frameCount === 3 returns 12 floats.
2258
+ const world = new World();
2259
+ const regions = new Float32Array([0, 0, 0.33, 1, 0.33, 0, 0.33, 1, 0.66, 0, 0.34, 1]);
2260
+ const e = world
2261
+ .spawn({
2262
+ component: SpriteAnimation,
2263
+ data: {
2264
+ frameCount: 3,
2265
+ frameDuration: 0.2,
2266
+ regions,
2267
+ },
2268
+ })
2269
+ .unwrap();
2270
+ const snap = world.get(e, SpriteAnimation).unwrap();
2271
+ expect(snap.regions.length).toBe(12);
2272
+ expect(snap.regions[4]).toBeCloseTo(0.33, 6);
2273
+ });
2274
+ });
2275
+ }
2276
+
2277
+ {
2278
+ // --- from sprite-playback-mode.test.ts ---
2279
+ // feat-20260521-sprite-atlas-animation / M1 / T-06.
2280
+ //
2281
+ // TDD red phase: packages/runtime/src/components/sprite-playback-mode.ts
2282
+ // does not yet exist — these constant + mapper assertions stay red until
2283
+ // T-07 lands the SSOT (plan-strategy section 2 D-5 / requirements
2284
+ // section AC-02 + section 2.3).
2285
+ //
2286
+ // The shape mirrors `packages/render/src/components/camera.ts:72-90`
2287
+ // Tonemap block (TONEMAP_NONE = 0 / TONEMAP_REINHARD_EXTENDED = 1 +
2288
+ // `type Tonemap = 'none' | 'reinhard-extended'` + `tonemapFromF32`)
2289
+ // because ECS schema whitelist SchemaFieldType does not accept string-
2290
+ // literal unions for `playbackMode` (research F-2 + F-5). The mapper
2291
+ // pattern lets the ECS column stay `'u32'` while AI users still consume
2292
+ // `'loop' | 'clamp'` literal-union narrowing in TS land.
2293
+ //
2294
+ // Anchors: plan-strategy section 2 D-5 + section 3.1 PR block SPM (sprite-
2295
+ // playback-mode) + section 4 risk R-SCHEMA-1 reaction; research
2296
+ // F-2 + F-5; requirements section AC-02 + section 2.3 playbackMode
2297
+ // row; charter P4 consistent abstraction (same shape as M1
2298
+ // tonemap-encoding so AI users keep one mental model).
2299
+
2300
+ describe('sprite-playback-mode SSOT — constants', () => {
2301
+ it('SPRITE_PLAYBACK_MODE_LOOP === 0 (u32 column encoding)', () => {
2302
+ expect(SPRITE_PLAYBACK_MODE_LOOP).toBe(0);
2303
+ });
2304
+
2305
+ it('SPRITE_PLAYBACK_MODE_CLAMP === 1 (u32 column encoding)', () => {
2306
+ expect(SPRITE_PLAYBACK_MODE_CLAMP).toBe(1);
2307
+ });
2308
+
2309
+ it('constants are typed as numeric literals (not widened to number)', () => {
2310
+ expectTypeOf(SPRITE_PLAYBACK_MODE_LOOP).toEqualTypeOf<0>();
2311
+ expectTypeOf(SPRITE_PLAYBACK_MODE_CLAMP).toEqualTypeOf<1>();
2312
+ });
2313
+ });
2314
+
2315
+ describe('sprite-playback-mode SSOT — SpritePlaybackMode type', () => {
2316
+ it("SpritePlaybackMode is exactly 'loop' | 'clamp'", () => {
2317
+ expectTypeOf<SpritePlaybackMode>().toEqualTypeOf<'loop' | 'clamp'>();
2318
+ });
2319
+
2320
+ it("'loop' is assignable to SpritePlaybackMode", () => {
2321
+ const mode: SpritePlaybackMode = 'loop';
2322
+ expect(mode).toBe('loop');
2323
+ });
2324
+
2325
+ it("'clamp' is assignable to SpritePlaybackMode", () => {
2326
+ const mode: SpritePlaybackMode = 'clamp';
2327
+ expect(mode).toBe('clamp');
2328
+ });
2329
+ });
2330
+
2331
+ describe('spritePlaybackModeFromU32 — u32 to string-literal translator', () => {
2332
+ it('spritePlaybackModeFromU32(0) === "loop"', () => {
2333
+ expect(spritePlaybackModeFromU32(0)).toBe('loop');
2334
+ });
2335
+
2336
+ it('spritePlaybackModeFromU32(1) === "clamp"', () => {
2337
+ expect(spritePlaybackModeFromU32(1)).toBe('clamp');
2338
+ });
2339
+
2340
+ it('out-of-range numerics fall back to "loop" (charter P4 no silent exception)', () => {
2341
+ // Mirror cameraProjectionFromF32 / tonemapFromF32: defensive default
2342
+ // for uninitialised / stale numeric values. The schema layer already
2343
+ // guarantees the column carries a u32, but AI users may pass through a
2344
+ // hand-set numeric that escapes the [0, 1] range.
2345
+ expect(spritePlaybackModeFromU32(2)).toBe('loop');
2346
+ expect(spritePlaybackModeFromU32(-1)).toBe('loop');
2347
+ expect(spritePlaybackModeFromU32(NaN)).toBe('loop');
2348
+ });
2349
+
2350
+ it('return type narrows to SpritePlaybackMode (string-literal union)', () => {
2351
+ expectTypeOf(spritePlaybackModeFromU32(0)).toEqualTypeOf<SpritePlaybackMode>();
2352
+ });
2353
+
2354
+ it('round-trip: SPRITE_PLAYBACK_MODE_LOOP -> "loop" / SPRITE_PLAYBACK_MODE_CLAMP -> "clamp"', () => {
2355
+ expect(spritePlaybackModeFromU32(SPRITE_PLAYBACK_MODE_LOOP)).toBe('loop');
2356
+ expect(spritePlaybackModeFromU32(SPRITE_PLAYBACK_MODE_CLAMP)).toBe('clamp');
2357
+ });
2358
+ });
2359
+ }
2360
+
2361
+ {
2362
+ // --- from transform.test.ts ---
2363
+ // feat-20260525-boilerplate-reduction-pod-defaults-factories / M1 / w1.
2364
+ //
2365
+ // Transform defaults fill regression barrier (AC-02 + AC-03). Three
2366
+ // concerns:
2367
+ //
2368
+ // (a) `world.spawn({ component: Transform, data: {} })` returns identity
2369
+ // transform (pos/quat/scale all identity values), verifying that the
2370
+ // layer-2 defaults map fills every schema field.
2371
+ //
2372
+ // (b) `world.spawn({ component: Transform, data: { pos: [0, 0, 2] } })`
2373
+ // returns pos=[0,0,2] with remaining fields at identity defaults,
2374
+ // verifying that layer-1 explicit values take priority over layer-2
2375
+ // defaults.
2376
+ //
2377
+ // (c) `world.set` partial patch on Transform does not affect unfilled
2378
+ // columns, verifying the column-wise set semantics.
2379
+ //
2380
+ // Anchors: requirements AC-02 / AC-03; plan-strategy section 5.1 TDD
2381
+ // red-green-refactor + section 5.2 unit test requirements.
2382
+
2383
+ describe('Transform defaults fill — identity spawn (AC-02)', () => {
2384
+ it('world.spawn({ component: Transform, data: {} }) returns identity transform', () => {
2385
+ const world = new World();
2386
+ const e = world.spawn({ component: Transform, data: {} }).unwrap();
2387
+ const row = world.get(e, Transform).unwrap();
2388
+
2389
+ expect(Array.from(row.pos)).toEqual([0, 0, 0]);
2390
+ expect(Array.from(row.quat)).toEqual([0, 0, 0, 1]);
2391
+ expect(Array.from(row.scale)).toEqual([1, 1, 1]);
2392
+ });
2393
+ });
2394
+
2395
+ describe('Transform partial override — layer-1 wins over layer-2 defaults (AC-03)', () => {
2396
+ it('world.spawn with only pos=[0,0,2] yields identity on all other fields', () => {
2397
+ const world = new World();
2398
+ const e = world.spawn({ component: Transform, data: { pos: [0, 0, 2] } }).unwrap();
2399
+ const row = world.get(e, Transform).unwrap();
2400
+
2401
+ expect(Array.from(row.pos)).toEqual([0, 0, 2]);
2402
+ expect(Array.from(row.quat)).toEqual([0, 0, 0, 1]);
2403
+ expect(Array.from(row.scale)).toEqual([1, 1, 1]);
2404
+ });
2405
+ });
2406
+
2407
+ describe('Transform new schema — local pos/quat/scale array cols + world array<f32,16> (AC-01)', () => {
2408
+ it('spawn data:{} yields identity local TRS + identity world mat4 (16 contiguous f32)', () => {
2409
+ const world = new World();
2410
+ const e = world.spawn({ component: Transform, data: {} }).unwrap();
2411
+ const row = world.get(e, Transform).unwrap();
2412
+
2413
+ // local pos/quat/scale inline array columns at identity.
2414
+ expect(Array.from(row.pos)).toEqual([0, 0, 0]);
2415
+ expect(Array.from(row.quat)).toEqual([0, 0, 0, 1]);
2416
+ expect(Array.from(row.scale)).toEqual([1, 1, 1]);
2417
+
2418
+ // world array<f32,16> resolves to a Float32Array view of 16 contiguous
2419
+ // floats. Default fill is the identity mat4 (column-major).
2420
+ const w = row.world as Float32Array;
2421
+ expect(w).toBeInstanceOf(Float32Array);
2422
+ expect(w.length).toBe(16);
2423
+ const identity = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
2424
+ for (let i = 0; i < 16; i++) {
2425
+ expect(w[i]).toBeCloseTo(identity[i] as number, 5);
2426
+ }
2427
+ });
2428
+ });
2429
+
2430
+ describe('Transform world.set — partial patch does not affect unfilled columns', () => {
2431
+ it('world.set partial patch on Transform leaves unreferenced columns unchanged', () => {
2432
+ const world = new World();
2433
+ const e = world.spawn({ component: Transform, data: { pos: [5, 3, 10] } }).unwrap();
2434
+
2435
+ // Partial set: only the pos column (the patch unit is the whole inline
2436
+ // array field; quat/scale columns are not referenced).
2437
+ world.set(e, Transform, { pos: [25, 3, 10] }).unwrap();
2438
+
2439
+ const after = world.get(e, Transform).unwrap();
2440
+ expect(Array.from(after.pos)).toEqual([25, 3, 10]);
2441
+ // Identity defaults still in place for unset columns.
2442
+ expect(Array.from(after.quat)).toEqual([0, 0, 0, 1]);
2443
+ expect(Array.from(after.scale)).toEqual([1, 1, 1]);
2444
+ });
2445
+ });
2446
+ }
2447
+
2448
+ {
2449
+ // --- from mesh-renderer-multi-material.test.ts ---
2450
+ // mesh-renderer-multi-material.test.ts — unit tests for render-system-extract
2451
+ // count-mismatch validation (feat-20260608-mesh-multi-section-primitive-multi-material-slot
2452
+ // M2 / w12).
2453
+ //
2454
+ // Anchors: requirements AC-05 (a); plan-strategy §2 D-3 (read-side interception);
2455
+ // plan-strategy §5.3 key test points "AC-03/AC-05 three triggers".
2456
+
2457
+ describe('render-system-extract count-mismatch (w12, AC-05 a)', () => {
2458
+ it('single mesh single material: extract succeeds', () => {
2459
+ const world = new World();
2460
+ const assets = new AssetRegistry(
2461
+ // biome-ignore lint/suspicious/noExplicitAny: mock ShaderRegistry
2462
+ { findMaterialArtifact: () => ({ ok: false }) } as any,
2463
+ );
2464
+
2465
+ const matHandle = world.allocSharedRef('MaterialAsset', {
2466
+ kind: 'material',
2467
+ baseColor: [1, 0, 0, 1],
2468
+ passes: [{ name: 'forward', program: { module: 'project::standard' } }],
2469
+ } as never);
2470
+
2471
+ const meshHandle = world.allocSharedRef('MeshAsset', {
2472
+ kind: 'mesh',
2473
+ vertices: new Float32Array(4 * 12),
2474
+ indices: new Uint16Array([0, 1, 2, 0, 2, 3]),
2475
+ attributes: { position: new Float32Array(4 * 3) },
2476
+ materialSlots: [{ slotName: 'Default' }],
2477
+ submeshes: [
2478
+ {
2479
+ indexOffset: 0,
2480
+ indexCount: 6,
2481
+ vertexCount: 4,
2482
+ materialSlot: 0,
2483
+ topology: 'triangle-list' as const,
2484
+ },
2485
+ ],
2486
+ } as never);
2487
+
2488
+ world.spawn(
2489
+ { component: Transform, data: {} },
2490
+ { component: MeshFilter, data: { assetHandle: meshHandle } },
2491
+ { component: MeshRenderer, data: { materials: [matHandle] } },
2492
+ );
2493
+
2494
+ // Extract should not crash; count matches (1 submesh, 1 material).
2495
+ // The renderable may be empty (material resolves to 0 passes with mock
2496
+ // ShaderRegistry) but the key assertion: no crash and count-mismatch
2497
+ // error is NOT triggered.
2498
+ const frame = extractFrame(world, prepareExtractContext(world, { assets }));
2499
+ expect(frame.renderables).toBeDefined();
2500
+ });
2501
+
2502
+ it('materials: [] with submeshes: [1] routes through D-Q7 case B mid-grey default (preserves legacy single-mesh-no-material path)', () => {
2503
+ const world = new World();
2504
+
2505
+ const assets = new AssetRegistry(
2506
+ // biome-ignore lint/suspicious/noExplicitAny: mock ShaderRegistry
2507
+ { findMaterialArtifact: () => ({ ok: false }) } as any,
2508
+ );
2509
+
2510
+ const meshHandle = world.allocSharedRef('MeshAsset', {
2511
+ kind: 'mesh',
2512
+ vertices: new Float32Array(4 * 12),
2513
+ indices: new Uint16Array([0, 1, 2, 0, 2, 3]),
2514
+ attributes: { position: new Float32Array(4 * 3) },
2515
+ materialSlots: [{ slotName: 'Default' }],
2516
+ submeshes: [
2517
+ {
2518
+ indexOffset: 0,
2519
+ indexCount: 6,
2520
+ vertexCount: 4,
2521
+ materialSlot: 0,
2522
+ topology: 'triangle-list' as const,
2523
+ },
2524
+ ],
2525
+ } as never);
2526
+
2527
+ world.spawn(
2528
+ { component: Transform, data: {} },
2529
+ { component: MeshFilter, data: { assetHandle: meshHandle } },
2530
+ { component: MeshRenderer, data: { materials: [] } },
2531
+ );
2532
+
2533
+ // materials.length=0 routes through D-Q7 case B (defaultMaterialSnapshot
2534
+ // mid-grey unlit) without triggering mesh-renderer-material-count-mismatch
2535
+ // -- count-mismatch only applies to non-empty materials whose length
2536
+ // disagrees with submeshes.length. Backward-compat preserved for legacy
2537
+ // `data: {}` spawn shape (charter P5 consistent abstraction).
2538
+ const frame = extractFrame(world, prepareExtractContext(world, { assets }));
2539
+ expect(frame.renderables.length).toBe(1);
2540
+ });
2541
+
2542
+ it('materials overflow emits a diagnostic and ignores the extra override', () => {
2543
+ const world = new World();
2544
+
2545
+ const assets = new AssetRegistry(
2546
+ // biome-ignore lint/suspicious/noExplicitAny: mock ShaderRegistry
2547
+ { findMaterialArtifact: () => ({ ok: false }) } as any,
2548
+ );
2549
+
2550
+ const matHandle = world.allocSharedRef('MaterialAsset', {
2551
+ kind: 'material',
2552
+ baseColor: [1, 0, 0, 1],
2553
+ passes: [{ name: 'forward', program: { module: 'project::standard' } }],
2554
+ } as never);
2555
+
2556
+ const meshHandle = world.allocSharedRef('MeshAsset', {
2557
+ kind: 'mesh',
2558
+ vertices: new Float32Array(4 * 12),
2559
+ indices: new Uint16Array([0, 1, 2, 0, 2, 3]),
2560
+ attributes: { position: new Float32Array(4 * 3) },
2561
+ materialSlots: [{ slotName: 'Default' }],
2562
+ submeshes: [
2563
+ {
2564
+ indexOffset: 0,
2565
+ indexCount: 6,
2566
+ vertexCount: 4,
2567
+ materialSlot: 0,
2568
+ topology: 'triangle-list' as const,
2569
+ },
2570
+ ],
2571
+ } as never);
2572
+
2573
+ world.spawn(
2574
+ { component: Transform, data: {} },
2575
+ { component: MeshFilter, data: { assetHandle: meshHandle } },
2576
+ { component: MeshRenderer, data: { materials: [matHandle, matHandle] } },
2577
+ );
2578
+
2579
+ const frame = extractFrame(world, prepareExtractContext(world, { assets }));
2580
+ // Owner validation is diagnostic and non-fatal: the malformed extra
2581
+ // override is ignored while the valid first material still renders.
2582
+ expect(frame.renderables).toHaveLength(1);
2583
+ });
2584
+ });
2585
+
2586
+ // ────────────────────────────────────────────────────────────────────────
2587
+ // feat-20260709 M3 / w11: Camera clearColor + GlyphText color + Tilemap
2588
+ // tileSize vec collapse -- schema shape, explicit layer-2 defaults, and
2589
+ // spawn-omit equivalence (AC-01 / E1 / E9). D-5 (non-zero array defaults
2590
+ // declared explicitly at layer 2, since the array layer-3 fallback is
2591
+ // all-zero).
2592
+ // ────────────────────────────────────────────────────────────────────────
2593
+ describe('M3 vec-collapse schema + defaults (w11 AC-01 / E1 / E9)', () => {
2594
+ it('Camera.clearColor is array<f32,4> with explicit layer-2 default [0,0,0,0]', () => {
2595
+ expect(componentSchema(Camera).clearColor).toBe('array<f32, 4>');
2596
+ expect(Array.from(Camera.fields.clearColor.default as Float32Array)).toEqual([0, 0, 0, 0]);
2597
+ });
2598
+
2599
+ it('GlyphText.color is array<f32,4> with explicit layer-2 default [1,1,1,1]; per-axis scalars gone', () => {
2600
+ expect(componentSchema(GlyphText).color).toBe('array<f32, 4>');
2601
+ expect('colorR' in componentSchema(GlyphText)).toBe(false);
2602
+ expect('colorG' in componentSchema(GlyphText)).toBe(false);
2603
+ expect('colorB' in componentSchema(GlyphText)).toBe(false);
2604
+ expect('colorA' in componentSchema(GlyphText)).toBe(false);
2605
+ expect(Array.from(GlyphText.fields.color.default as Float32Array)).toEqual([1, 1, 1, 1]);
2606
+ });
2607
+
2608
+ it('Tilemap.tileSize is array<f32,2> with explicit layer-2 default [1,1]; per-axis scalars gone', () => {
2609
+ expect(componentSchema(Tilemap).tileSize).toBe('array<f32, 2>');
2610
+ expect('tileSizeX' in componentSchema(Tilemap)).toBe(false);
2611
+ expect('tileSizeY' in componentSchema(Tilemap)).toBe(false);
2612
+ expect(Array.from(Tilemap.fields.tileSize.default as Float32Array)).toEqual([1, 1]);
2613
+ });
2614
+
2615
+ it('E1: Camera spawned with clearColor omitted resolves to [0,0,0,0]', () => {
2616
+ const world = new World();
2617
+ const e = world
2618
+ .spawn({ component: Camera, data: { fov: Math.PI / 4, aspect: 1, near: 0.1, far: 100 } })
2619
+ .unwrap();
2620
+ const row = world.get(e, Camera).unwrap();
2621
+ expect(Array.from(row.clearColor)).toEqual([0, 0, 0, 0]);
2622
+ });
2623
+
2624
+ it('E1: GlyphText spawned with color omitted resolves to [1,1,1,1] (equal to old scalar default)', () => {
2625
+ const world = new World();
2626
+ const e = world
2627
+ .spawn({ component: GlyphText, data: { fontHandle: 0 as never, text: 'x', fontSize: 16 } })
2628
+ .unwrap();
2629
+ const row = world.get(e, GlyphText).unwrap();
2630
+ expect(Array.from(row.color)).toEqual([1, 1, 1, 1]);
2631
+ });
2632
+
2633
+ it('E1: Tilemap spawned with tileSize omitted resolves to [1,1] (equal to old scalar default)', () => {
2634
+ const world = new World();
2635
+ const e = world.spawn({ component: Tilemap, data: { cols: 4, rows: 4 } }).unwrap();
2636
+ const row = world.get(e, Tilemap).unwrap();
2637
+ expect(Array.from(row.tileSize)).toEqual([1, 1]);
2638
+ });
2639
+
2640
+ it('E9: perspective() / orthographic() factories flow the new clearColor array default through cameraPodFromDefaults() (zero signature change)', () => {
2641
+ const p = perspective({ fov: Math.PI / 3, aspect: 4 / 3 });
2642
+ const o = orthographic({ left: -1, right: 1, bottom: -1, top: 1 });
2643
+ // The factory output carries the array-shaped clearColor field verbatim
2644
+ // from Camera.fields defaults -- no per-axis scalar keys survive.
2645
+ expect(Array.from(p.clearColor)).toEqual([0, 0, 0, 0]);
2646
+ expect(Array.from(o.clearColor)).toEqual([0, 0, 0, 0]);
2647
+ expect('clearR' in p).toBe(false);
2648
+ expect('clearR' in o).toBe(false);
2649
+ });
2650
+
2651
+ it('E9: factory spread + clearColor override lands the authored color', () => {
2652
+ const world = new World();
2653
+ const e = world
2654
+ .spawn({
2655
+ component: Camera,
2656
+ data: {
2657
+ ...perspective({ fov: Math.PI / 3, aspect: 4 / 3 }),
2658
+ clearColor: [0.4, 0.6, 1, 1],
2659
+ },
2660
+ })
2661
+ .unwrap();
2662
+ const row = world.get(e, Camera).unwrap();
2663
+ expect(Array.from(row.clearColor)).toEqual([Math.fround(0.4), Math.fround(0.6), 1, 1]);
2664
+ });
2665
+ });
2666
+ }