@forgeax/engine-runtime 0.1.2

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