@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,1505 @@
1
+ // Consolidated by feat-20260608-ci-time-cut M5 w13/w14: merges
2
+ // - render-system-record-pbr-ubo-stable.test.ts
3
+ // - render-system-record-scale-too-small.test.ts
4
+ // - render-system-record-sprite-region-override.test.ts
5
+ // - render-system-record-sprite-ubo-bytes.test.ts
6
+ // Each original file body sits in its own block-scope so helper names
7
+ // cannot collide; describe() inside still registers globally with vitest.
8
+
9
+ import { World } from '@forgeax/engine-ecs';
10
+ import {
11
+ SPRITE_PREMULTIPLIED_ALPHA_BLEND,
12
+ SpriteRegionOverride,
13
+ } from '@forgeax/engine-render/authoring';
14
+ import type { DispatchEntry } from '@forgeax/engine-render/internal';
15
+ import * as recordModule from '@forgeax/engine-render/internal';
16
+ import {
17
+ Camera,
18
+ createEngineMetrics,
19
+ detectNineSliceScaleTooSmall,
20
+ extractFrame,
21
+ isLitMaterialSnapshot,
22
+ type MaterialSnapshot,
23
+ MeshFilter,
24
+ MeshRenderer,
25
+ prepareExtractContext,
26
+ writeMaterialSnapshotPayload,
27
+ } from '@forgeax/engine-render/internal';
28
+ import { propagateTransforms, Transform } from '@forgeax/engine-scene';
29
+ import { ShaderRegistry, type ShaderRegistryDevice } from '@forgeax/engine-shader';
30
+ import type {
31
+ Handle,
32
+ MaterialAsset,
33
+ MaterialPass,
34
+ MeshAsset,
35
+ ParamSchemaEntry,
36
+ PassSelector,
37
+ } from '@forgeax/engine-types';
38
+ import { derive } from '@forgeax/engine-types';
39
+ import { describe, expect, it } from 'vitest';
40
+ import { createTestRenderAssetOwner } from './helpers/render-asset-port';
41
+
42
+ describe('derived material record stop-draw (w28)', () => {
43
+ it('returns a structured bounds error before a slot can be uploaded', () => {
44
+ const result = writeMaterialSnapshotPayload(
45
+ {
46
+ baseColor: new Float32Array([1, 1, 1]) as unknown as MaterialSnapshot['baseColor'],
47
+ metallic: 0,
48
+ roughness: 1,
49
+ materialParamSchema: [{ name: 'albedo', type: 'texture2d' }],
50
+ textureCoordinates: new Map([
51
+ [
52
+ 'albedo',
53
+ {
54
+ set: 0,
55
+ transform: { offset: [0, 0], scale: [1, 1], rotation: 0 },
56
+ physicalUvScale: [1, 1],
57
+ },
58
+ ],
59
+ ]),
60
+ },
61
+ 16,
62
+ );
63
+
64
+ expect(result).toMatchObject({
65
+ ok: false,
66
+ error: { code: 'material-payload-bounds', detail: { action: 'stop-draw' } },
67
+ });
68
+ });
69
+ });
70
+
71
+ // ─── from render-system-record-scale-too-small.test.ts ───
72
+ {
73
+ // render-system-record-scale-too-small — feat-20260527-sprite-nineslice M4 / w17 (a).
74
+ //
75
+ // AC-16 end-to-end: when a 9-slice sprite entity carries a Transform.scale
76
+ // below the four corner anchors, the detection path bumps
77
+ // `renderer.metrics.snapshot()['nineslice.scale-too-small']` once per offending
78
+ // renderableIndex per RenderSystem lifetime. AI users observe the breach
79
+ // through the EngineMetrics counter rather than parsing console.warn text
80
+ // (charter P3 machine-readable signals).
81
+ //
82
+ // The test exercises `detectNineSliceScaleTooSmall` (the pure helper extracted
83
+ // from recordFrame in w17 prep) so the assertion runs without a GPU device.
84
+ // recordFrame's inline call site forwards the same arguments to this helper,
85
+ // so the unit test reflects production behaviour byte-for-byte.
86
+ //
87
+ // Anchors: requirements §AC-16; plan-strategy §5.3 key tests #9; plan-strategy
88
+ // §D-5 EngineMetrics machine-readable counter.
89
+
90
+ function diagonalWorld(scaleX: number, scaleY: number): Float32Array {
91
+ // Column-major mat4 with diagonal scale, no rotation/translation.
92
+ // biome-ignore format: matrix layout
93
+ return new Float32Array([
94
+ scaleX, 0, 0, 0,
95
+ 0, scaleY, 0, 0,
96
+ 0, 0, 1, 0,
97
+ 0, 0, 0, 1,
98
+ ]);
99
+ }
100
+
101
+ describe('detectNineSliceScaleTooSmall (M4 / w17, AC-16 E2E)', () => {
102
+ it('single offending entity bumps the counter to 1', () => {
103
+ const metrics = createEngineMetrics();
104
+ const seen = new Set<number>();
105
+ // anchor budget: |0.3| + |0.3| = 0.6 horizontal / 0.6 vertical.
106
+ // scale 0.4 < 0.6 -> breach on both axes.
107
+ detectNineSliceScaleTooSmall(diagonalWorld(0.4, 0.4), [0.3, 0.3, 0.3, 0.3], 0, seen, metrics);
108
+ expect(metrics.snapshot()['nineslice.scale-too-small']).toBe(1);
109
+ });
110
+
111
+ it('repeat call on the SAME renderableIndex stays at 1 (warn-once anchor)', () => {
112
+ const metrics = createEngineMetrics();
113
+ const seen = new Set<number>();
114
+ for (let i = 0; i < 10; i++) {
115
+ detectNineSliceScaleTooSmall(
116
+ diagonalWorld(0.4, 0.4),
117
+ [0.3, 0.3, 0.3, 0.3],
118
+ 0,
119
+ seen,
120
+ metrics,
121
+ );
122
+ }
123
+ expect(metrics.snapshot()['nineslice.scale-too-small']).toBe(1);
124
+ });
125
+
126
+ it('N distinct offending entities yield counter === N', () => {
127
+ const metrics = createEngineMetrics();
128
+ const seen = new Set<number>();
129
+ for (let r = 0; r < 5; r++) {
130
+ detectNineSliceScaleTooSmall(
131
+ diagonalWorld(0.4, 0.4),
132
+ [0.3, 0.3, 0.3, 0.3],
133
+ r,
134
+ seen,
135
+ metrics,
136
+ );
137
+ }
138
+ expect(metrics.snapshot()['nineslice.scale-too-small']).toBe(5);
139
+ });
140
+
141
+ it('compliant scale (>= anchor) does NOT bump the counter', () => {
142
+ const metrics = createEngineMetrics();
143
+ const seen = new Set<number>();
144
+ detectNineSliceScaleTooSmall(diagonalWorld(1.0, 1.0), [0.3, 0.3, 0.3, 0.3], 0, seen, metrics);
145
+ expect(metrics.snapshot()['nineslice.scale-too-small']).toBeUndefined();
146
+ });
147
+
148
+ it('all-zero slices is a no-op (legacy quad path, no detection)', () => {
149
+ const metrics = createEngineMetrics();
150
+ const seen = new Set<number>();
151
+ // Even with a tiny scale the all-zero slices means "this is not a 9-slice"
152
+ detectNineSliceScaleTooSmall(diagonalWorld(0.01, 0.01), [0, 0, 0, 0], 0, seen, metrics);
153
+ expect(metrics.snapshot()['nineslice.scale-too-small']).toBeUndefined();
154
+ });
155
+
156
+ it('tile-mode sentinel (slices.w < 0) consumed via abs() — no false negative', () => {
157
+ const metrics = createEngineMetrics();
158
+ const seen = new Set<number>();
159
+ // sliceMode=1 negates slices.w; the helper takes abs() so the anchor budget
160
+ // still totals 0.6 on the vertical axis (|0.3| + |-0.3|).
161
+ detectNineSliceScaleTooSmall(
162
+ diagonalWorld(0.4, 0.4),
163
+ [0.3, 0.3, 0.3, -0.3],
164
+ 0,
165
+ seen,
166
+ metrics,
167
+ );
168
+ expect(metrics.snapshot()['nineslice.scale-too-small']).toBe(1);
169
+ });
170
+ });
171
+ }
172
+
173
+ // ─── from render-system-record-sprite-region-override.test.ts ───
174
+ {
175
+ // render-system-record-sprite-region-override — feat-20260527-sprite-nineslice M4 / w17 (b).
176
+ //
177
+ // AC-14: SpriteRegionOverride per-entity UV sub-rectangle composes with 9-slice.
178
+ // When the entity carries the override component, the extract stage replaces
179
+ // the asset-side `values.region` with the override's 4-float region
180
+ // before downstream snapshot construction; downstream 9-slice paths therefore
181
+ // measure slices against the override's region.zw, not the asset's. Two
182
+ // renderables sharing the same MaterialAsset can render with different
183
+ // effective regions when only one carries the override.
184
+ //
185
+ // Coverage:
186
+ // (1) entity WITHOUT SpriteRegionOverride uses the asset-side region as the
187
+ // effective region in the snapshot.
188
+ // (2) entity WITH SpriteRegionOverride uses the override's region in the
189
+ // snapshot — a half-region [0, 0, 0.5, 1] flows through verbatim,
190
+ // displacing the asset's [0, 0, 1, 1].
191
+ // (3) Two renderables sharing the same material asset diverge on effective
192
+ // region — the override-bearing entity sees the override; the other
193
+ // sees the asset's region. Slices=[0.25,0.25,0.25,0.25] flow through
194
+ // both unchanged (slices remain in the [0..1] local-UV unit; the
195
+ // effective region only governs UV sampling, not the slices unit).
196
+ //
197
+ // Anchors: requirements §AC-14; plan-strategy §5.3 key tests #8.
198
+
199
+ function makeShaderRegistryWithSprite(): ShaderRegistry {
200
+ const mockDevice: ShaderRegistryDevice = {
201
+ createShaderModule() {
202
+ return {
203
+ ok: true,
204
+ value: undefined,
205
+ unwrap: () => undefined,
206
+ unwrapOr: (d: unknown) => d,
207
+ } as unknown as ReturnType<ShaderRegistryDevice['createShaderModule']>;
208
+ },
209
+ };
210
+ const sr = new ShaderRegistry({ device: mockDevice, manifestUrl: undefined });
211
+ // feat-20260625-refactor-sprite-as-transparent-mesh M3 / w11+w12: the
212
+ // sprite paramSchema mirrors the WGSL UBO struct field set (4 vec4 +
213
+ // baseColorTexture). Legacy user inputs (slices/sliceMode/flipX/flipY/
214
+ // pivot) are still accepted by extract and folded into the UBO-aligned
215
+ // paramSnapshot vec4 entries (D-8).
216
+ sr.installMaterialArtifact('forgeax::sprite', {
217
+ source: 'fn main() {}',
218
+ paramSchema: [
219
+ { name: 'colorTint', type: 'vec4', default: [1.0, 1.0, 1.0, 1.0] },
220
+ { name: 'region', type: 'vec4', default: [0.0, 0.0, 1.0, 1.0] },
221
+ { name: 'pivotAndSize', type: 'vec4', default: [0.5, 0.5, 1.0, 1.0] },
222
+ { name: 'slicesAndMode', type: 'vec4', default: [0.0, 0.0, 0.0, 0.0] },
223
+ { name: 'baseColorTexture', type: 'texture2d' },
224
+ ],
225
+ });
226
+ return sr;
227
+ }
228
+
229
+ const SPRITE_PASS: MaterialPass = {
230
+ name: 'Sprite',
231
+ program: { module: 'forgeax::sprite' },
232
+ renderState: { queue: 3000 },
233
+ };
234
+
235
+ function identity() {
236
+ return {
237
+ pos: [0, 0, 0],
238
+ quat: [0, 0, 0, 1],
239
+ scale: [1, 1, 1],
240
+ };
241
+ }
242
+
243
+ function registerSpriteMesh(world: World): Handle<'MeshAsset', 'shared'> {
244
+ const positions = new Float32Array([0, 0, 0, 1, 0, 0, 0, 1, 0]);
245
+ return world.allocSharedRef<'MeshAsset', MeshAsset>('MeshAsset', {
246
+ kind: 'mesh',
247
+ vertices: new Float32Array([
248
+ 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0,
249
+ 1, 0, 0, 0, 0,
250
+ ]),
251
+ indices: new Uint16Array([0, 1, 2]),
252
+ attributes: { position: positions },
253
+ aabb: new Float32Array([0, 0, 0, 1, 1, 1]),
254
+ submeshes: [
255
+ {
256
+ indexOffset: 0,
257
+ indexCount: 3,
258
+ vertexCount: 36,
259
+ topology: 'triangle-list',
260
+ materialSlot: 0,
261
+ },
262
+ ],
263
+
264
+ materialSlots: [{ slotName: 'Default' }],
265
+ });
266
+ }
267
+
268
+ function spawnCamera(world: World): void {
269
+ world
270
+ .spawn(
271
+ { component: Transform, data: { ...identity(), pos: [0, 0, 5] } },
272
+ {
273
+ component: Camera,
274
+ data: {
275
+ fov: Math.PI / 4,
276
+ aspect: 1,
277
+ near: 0.1,
278
+ far: 100,
279
+ projection: 0,
280
+ left: -1,
281
+ right: 1,
282
+ bottom: -1,
283
+ top: 1,
284
+ },
285
+ },
286
+ )
287
+ .unwrap();
288
+ }
289
+
290
+ describe('SpriteRegionOverride compose 9-slice (M4 / w17b, AC-14)', () => {
291
+ it('(1) entity without override uses asset-side region', () => {
292
+ const world = new World();
293
+ const assets = createTestRenderAssetOwner(makeShaderRegistryWithSprite());
294
+ const mesh = registerSpriteMesh(world);
295
+ const matHandle = world.allocSharedRef<'MaterialAsset', MaterialAsset>('MaterialAsset', {
296
+ kind: 'material',
297
+ passes: [SPRITE_PASS],
298
+ values: { region: [0.1, 0.1, 0.8, 0.8] },
299
+ });
300
+ spawnCamera(world);
301
+ world
302
+ .spawn(
303
+ { component: Transform, data: identity() },
304
+ { component: MeshFilter, data: { assetHandle: mesh } },
305
+ { component: MeshRenderer, data: { materials: [matHandle] } },
306
+ )
307
+ .unwrap();
308
+ propagateTransforms(world);
309
+ const frame = extractFrame(world, prepareExtractContext(world, { assets }));
310
+ // feat-20260625 M3 / w12: sprite filter is now materialShaderId, not
311
+ // shadingModel; region lands on paramSnapshot.region (D-8 / AC-07).
312
+ const sprite = frame.renderables.find(
313
+ (r) => r.material.materialShaderId === 'forgeax::sprite',
314
+ );
315
+ expect(sprite).toBeDefined();
316
+ expect(sprite?.material.paramSnapshot?.region).toEqual([0.1, 0.1, 0.8, 0.8]);
317
+ });
318
+
319
+ it('(2) entity with override uses the override region in the snapshot', () => {
320
+ const world = new World();
321
+ const assets = createTestRenderAssetOwner(makeShaderRegistryWithSprite());
322
+ const mesh = registerSpriteMesh(world);
323
+ const matHandle = world.allocSharedRef<'MaterialAsset', MaterialAsset>('MaterialAsset', {
324
+ kind: 'material',
325
+ passes: [SPRITE_PASS],
326
+ values: { region: [0.0, 0.0, 1.0, 1.0] },
327
+ });
328
+ spawnCamera(world);
329
+ world
330
+ .spawn(
331
+ { component: Transform, data: identity() },
332
+ { component: MeshFilter, data: { assetHandle: mesh } },
333
+ { component: MeshRenderer, data: { materials: [matHandle] } },
334
+ {
335
+ component: SpriteRegionOverride,
336
+ data: { region: new Float32Array([0.0, 0.0, 0.5, 1.0]) },
337
+ },
338
+ )
339
+ .unwrap();
340
+ propagateTransforms(world);
341
+ const frame = extractFrame(world, prepareExtractContext(world, { assets }));
342
+ const sprite = frame.renderables.find(
343
+ (r) => r.material.materialShaderId === 'forgeax::sprite',
344
+ );
345
+ expect(sprite).toBeDefined();
346
+ // The override displaces the asset-side [0, 0, 1, 1] region with the
347
+ // half-width [0, 0, 0.5, 1] override, so downstream consumers (record
348
+ // stage UBO write, 9-slice anchor budget) see the override values via
349
+ // paramSnapshot.region (post-w12).
350
+ expect(sprite?.material.paramSnapshot?.region).toEqual([0, 0, 0.5, 1]);
351
+ });
352
+
353
+ it('(3) two entities sharing one material diverge on effective region (compose with 9-slice)', () => {
354
+ const world = new World();
355
+ const assets = createTestRenderAssetOwner(makeShaderRegistryWithSprite());
356
+ const mesh = registerSpriteMesh(world);
357
+ const matHandle = world.allocSharedRef<'MaterialAsset', MaterialAsset>('MaterialAsset', {
358
+ kind: 'material',
359
+ passes: [SPRITE_PASS],
360
+ values: {
361
+ region: [0.0, 0.0, 1.0, 1.0],
362
+ slicesAndMode: [0.25, 0.25, 0.25, 0.25],
363
+ },
364
+ });
365
+ spawnCamera(world);
366
+ // entity A: no override, sees the asset-side region.
367
+ world
368
+ .spawn(
369
+ { component: Transform, data: { ...identity(), pos: [-1, 0, 0] } },
370
+ { component: MeshFilter, data: { assetHandle: mesh } },
371
+ { component: MeshRenderer, data: { materials: [matHandle] } },
372
+ )
373
+ .unwrap();
374
+ // entity B: half-width override, sees the override region.
375
+ world
376
+ .spawn(
377
+ { component: Transform, data: { ...identity(), pos: [1, 0, 0] } },
378
+ { component: MeshFilter, data: { assetHandle: mesh } },
379
+ { component: MeshRenderer, data: { materials: [matHandle] } },
380
+ {
381
+ component: SpriteRegionOverride,
382
+ data: { region: new Float32Array([0.0, 0.0, 0.5, 1.0]) },
383
+ },
384
+ )
385
+ .unwrap();
386
+ propagateTransforms(world);
387
+ const frame = extractFrame(world, prepareExtractContext(world, { assets }));
388
+ const sprites = frame.renderables.filter(
389
+ (r) => r.material.materialShaderId === 'forgeax::sprite',
390
+ );
391
+ expect(sprites.length).toBe(2);
392
+ const regions = sprites.map((s) => s.material.paramSnapshot?.region);
393
+ // One renderable lands on the asset-side region; the other lands on the
394
+ // override. Order is archetype-walk order (we check the set, not which is
395
+ // which, so the test stays robust to archetype iteration order).
396
+ expect(regions).toContainEqual([0, 0, 1, 1]);
397
+ expect(regions).toContainEqual([0, 0, 0.5, 1]);
398
+ // Slices ride through unchanged on both renderables -- slicesAndMode
399
+ // stretch encoding (positive .w, plan-strategy D-3); effective region
400
+ // governs UV sampling, not the slices unit.
401
+ for (const s of sprites) {
402
+ expect(s.material.paramSnapshot?.slicesAndMode).toEqual([0.25, 0.25, 0.25, 0.25]);
403
+ }
404
+ });
405
+ });
406
+ }
407
+
408
+ // --- from render-system-record-sprite-ubo-bytes.test.ts ---
409
+ {
410
+ // feat-20260625-refactor-sprite-as-transparent-mesh M3 / w8 (TDD red).
411
+ //
412
+ // Sprite UBO bytes via the GENERIC paramSnapshot writer (M1 / w3
413
+ // applyParamSnapshotToUbo). The sprite path consumes the same
414
+ // derive(uboLayout).entries-driven overlay every other paramSchema-bound
415
+ // material uses, so the 4 vec4 sprite layout (colorTint / region /
416
+ // pivotAndSize / slicesAndMode) is produced by `applyParamSnapshotToUbo`
417
+ // walking a sprite-shaped paramSchema -- not by the legacy
418
+ // `buildSpriteMaterialUboPayload` POD helper.
419
+ //
420
+ // Plan anchors:
421
+ // - plan-strategy section 2 D-2 / D-6: generic std140 writer is the
422
+ // single UBO write path; sprite-specific builder is ablated in w13.
423
+ // - plan-strategy section 5.3 #1: 4-vec4 std140 layout byte correctness.
424
+ // - requirements AC-03: buildSpriteMaterialUboPayload export does not
425
+ // survive M3 (closed-loop reverse-grep gate).
426
+ //
427
+ // Red phase: the legacy helper still ships; we assert here that the new
428
+ // production path (generic writer + sprite-shaped paramSnapshot) produces
429
+ // the expected bytes AND that `buildSpriteMaterialUboPayload` is no longer
430
+ // exported. Both flip green when w13 lands.
431
+
432
+ const mod = recordModule as unknown as {
433
+ applyParamSnapshotToUbo?: (
434
+ payload: ArrayBuffer | Uint8Array,
435
+ paramSchema: readonly ParamSchemaEntry[] | undefined,
436
+ paramSnapshot:
437
+ | Readonly<Record<string, number | readonly number[] | string | undefined>>
438
+ | undefined,
439
+ ) => void;
440
+ buildSpriteMaterialUboPayload?: unknown;
441
+ };
442
+
443
+ // Sprite UBO layout (matches sprite.wgsl post-w11 unit-quad struct: four
444
+ // vec4 fields, total 64 B; the writer fills the leading 64 B of the
445
+ // STANDARD_PBR_UBO_SIZE-sized payload buffer the runtime reuses).
446
+ const SPRITE_UBO_SCHEMA: readonly ParamSchemaEntry[] = [
447
+ { name: 'colorTint', type: 'vec4', default: [1, 1, 1, 1] },
448
+ { name: 'region', type: 'vec4', default: [0, 0, 1, 1] },
449
+ { name: 'pivotAndSize', type: 'vec4', default: [0.5, 0.5, 1, 1] },
450
+ { name: 'slicesAndMode', type: 'vec4', default: [0, 0, 0, 0] },
451
+ ];
452
+
453
+ function makeSpriteSnapshot(opts: {
454
+ colorTintAlpha?: number;
455
+ region?: readonly [number, number, number, number];
456
+ pivot?: readonly [number, number];
457
+ slices?: readonly [number, number, number, number];
458
+ }): {
459
+ colorTint: readonly [number, number, number, number];
460
+ region: readonly [number, number, number, number];
461
+ pivotAndSize: readonly [number, number, number, number];
462
+ slicesAndMode: readonly [number, number, number, number];
463
+ } {
464
+ const pivot = opts.pivot ?? [0.5, 0.5];
465
+ return {
466
+ colorTint: [1, 1, 1, opts.colorTintAlpha ?? 1] as const,
467
+ region: opts.region ?? ([0, 0, 1, 1] as const),
468
+ // Unit quad: pivotAndSize.zw=(1,1) is the placeholder slot the shader
469
+ // no longer reads -- world scale flows entirely through worldFromLocal
470
+ // (plan-strategy section 2 D-6).
471
+ pivotAndSize: [pivot[0], pivot[1], 1, 1] as const,
472
+ slicesAndMode: (opts.slices ?? ([0, 0, 0, 0] as const)) as readonly [
473
+ number,
474
+ number,
475
+ number,
476
+ number,
477
+ ],
478
+ };
479
+ }
480
+
481
+ describe('sprite Material UBO bytes via generic writer (M3 / w8)', () => {
482
+ it('grep: buildSpriteMaterialUboPayload is no longer exported from render-system-record (AC-03)', () => {
483
+ expect(mod.buildSpriteMaterialUboPayload).toBeUndefined();
484
+ });
485
+
486
+ it('(1) no slices -> 64 B leading bytes; slot 3 = [0,0,0,0]; slots 0..2 = identity defaults', () => {
487
+ if (typeof mod.applyParamSnapshotToUbo !== 'function') {
488
+ throw new Error('applyParamSnapshotToUbo not exported');
489
+ }
490
+ // 80 B buffer mirrors the per-entity slot stride the runtime allocates
491
+ // (STANDARD_PBR_UBO_SIZE). The sprite writer only touches the leading
492
+ // 64 B; trailing bytes stay zero on a fresh ArrayBuffer.
493
+ const buf = new ArrayBuffer(80);
494
+ const snap = makeSpriteSnapshot({});
495
+ mod.applyParamSnapshotToUbo(buf, SPRITE_UBO_SCHEMA, snap);
496
+ const f32 = new Float32Array(buf);
497
+ // colorTint slot 0 - default identity (1,1,1,1).
498
+ expect(Array.from(f32.slice(0, 4))).toEqual([1, 1, 1, 1]);
499
+ // region slot 1 - identity (no flip applied at this layer).
500
+ expect(Array.from(f32.slice(4, 8))).toEqual([0, 0, 1, 1]);
501
+ // pivotAndSize slot 2 - pivot=(0.5,0.5); .zw=(1,1) dead-slot placeholder.
502
+ expect(Array.from(f32.slice(8, 12))).toEqual([0.5, 0.5, 1, 1]);
503
+ // slicesAndMode slot 3 - no slices, all zero.
504
+ expect(Array.from(f32.slice(12, 16))).toEqual([0, 0, 0, 0]);
505
+ // Trailing 16 B left untouched (overlay semantics, charter P4).
506
+ expect(Array.from(f32.slice(16, 20))).toEqual([0, 0, 0, 0]);
507
+ });
508
+
509
+ it('(2) stretch slices [.25,.25,.25,.25] -> slot 3 verbatim; slots 0..2 byte-stable vs baseline', () => {
510
+ if (typeof mod.applyParamSnapshotToUbo !== 'function') {
511
+ throw new Error('applyParamSnapshotToUbo not exported');
512
+ }
513
+ const baseBuf = new ArrayBuffer(80);
514
+ mod.applyParamSnapshotToUbo(baseBuf, SPRITE_UBO_SCHEMA, makeSpriteSnapshot({}));
515
+ const stretchBuf = new ArrayBuffer(80);
516
+ mod.applyParamSnapshotToUbo(
517
+ stretchBuf,
518
+ SPRITE_UBO_SCHEMA,
519
+ makeSpriteSnapshot({ slices: [0.25, 0.25, 0.25, 0.25] }),
520
+ );
521
+ const baseF32 = new Float32Array(baseBuf);
522
+ const stretchF32 = new Float32Array(stretchBuf);
523
+ // First 48 B (slots 0..2, 12 floats) byte-stable across slices presence
524
+ // (plan-strategy section D-7 isolation: 9-slice expressions live only in
525
+ // slot 3).
526
+ expect(Array.from(stretchF32.slice(0, 12))).toEqual(Array.from(baseF32.slice(0, 12)));
527
+ // Slot 3 verbatim copy of slicesAndMode (sliceMode=0 stretch).
528
+ expect(Array.from(stretchF32.slice(12, 16))).toEqual([0.25, 0.25, 0.25, 0.25]);
529
+ });
530
+
531
+ it('(3) tile slicesAndMode [.25,.25,.25,-.25] -> slot 3 carries sentinel (w negative)', () => {
532
+ if (typeof mod.applyParamSnapshotToUbo !== 'function') {
533
+ throw new Error('applyParamSnapshotToUbo not exported');
534
+ }
535
+ const buf = new ArrayBuffer(80);
536
+ // Extract folds sliceMode=1 into a negative slicesAndMode.w (plan-
537
+ // strategy D-3 sentinel); record writes the snapshot verbatim.
538
+ mod.applyParamSnapshotToUbo(
539
+ buf,
540
+ SPRITE_UBO_SCHEMA,
541
+ makeSpriteSnapshot({ slices: [0.25, 0.25, 0.25, -0.25] }),
542
+ );
543
+ const baseBuf = new ArrayBuffer(80);
544
+ mod.applyParamSnapshotToUbo(baseBuf, SPRITE_UBO_SCHEMA, makeSpriteSnapshot({}));
545
+ const f32 = new Float32Array(buf);
546
+ const baseF32 = new Float32Array(baseBuf);
547
+ // First 48 B still byte-identical to baseline across slot 3 changes.
548
+ expect(Array.from(f32.slice(0, 12))).toEqual(Array.from(baseF32.slice(0, 12)));
549
+ // Slot 3 carries the tile sentinel.
550
+ expect(f32[12]).toBe(0.25);
551
+ expect(f32[13]).toBe(0.25);
552
+ expect(f32[14]).toBe(0.25);
553
+ expect(f32[15]).toBe(-0.25);
554
+ });
555
+
556
+ it('flipped region [0.5,0,-0.5,1] -> slot 1 carries flip-folded coords verbatim (D-8)', () => {
557
+ if (typeof mod.applyParamSnapshotToUbo !== 'function') {
558
+ throw new Error('applyParamSnapshotToUbo not exported');
559
+ }
560
+ const buf = new ArrayBuffer(80);
561
+ // Extract folds flipX into region (region.x += region.z; region.z = -region.z).
562
+ // Starting from identity region [0,0,1,1], flipX yields [1,0,-1,1]; the
563
+ // snapshot reaches the writer with the fold already applied.
564
+ mod.applyParamSnapshotToUbo(
565
+ buf,
566
+ SPRITE_UBO_SCHEMA,
567
+ makeSpriteSnapshot({ region: [1, 0, -1, 1] }),
568
+ );
569
+ const f32 = new Float32Array(buf);
570
+ expect(Array.from(f32.slice(4, 8))).toEqual([1, 0, -1, 1]);
571
+ });
572
+ });
573
+ }
574
+
575
+ // ─── from feat-20260609 M2: dispatch selector filtering ───
576
+ {
577
+ // feat-20260609-pipeline-driven-pass-selector-shadowcaster-via-mat M2
578
+ // T-005: dispatch entry selector filtering unit tests.
579
+ //
580
+ // AC-03: selector { LightMode: ['Forward'] } filters to only Forward entries.
581
+ // AC-04: selector { LightMode: ['ShadowCaster'] } filters to only matching entity.
582
+ // AC-18: multiple ShadowCaster passes per entity -> filtered count = N.
583
+ //
584
+ // Anchors: plan-strategy section 5.3 key tests; requirements AC-03/AC-04/AC-18.
585
+
586
+ const filterDispatchBySelector = (
587
+ recordModule as unknown as {
588
+ filterDispatchBySelector?: (
589
+ dispatch: readonly DispatchEntry[],
590
+ selector: PassSelector,
591
+ ) => readonly DispatchEntry[];
592
+ }
593
+ ).filterDispatchBySelector;
594
+
595
+ function makeDispatchEntry(
596
+ renderableIndex: number,
597
+ passIndex: number,
598
+ tags: Record<string, string>,
599
+ ): DispatchEntry {
600
+ return {
601
+ entityIndex: renderableIndex,
602
+ materialHandle: 1,
603
+ renderableIndex,
604
+ passIndex,
605
+ queue: 2000,
606
+ layer: 0,
607
+ tags,
608
+ renderState: undefined,
609
+ defines: undefined,
610
+ vertexEntry: undefined,
611
+ fragmentEntry: undefined,
612
+ materialShaderId: 'forgeax::default-unlit',
613
+ paramSnapshot: undefined,
614
+ };
615
+ }
616
+
617
+ describe('dispatch entry selector filtering (M2, T-005)', () => {
618
+ it('AC-03: empty selector returns all dispatch entries unchanged', () => {
619
+ if (typeof filterDispatchBySelector !== 'function') {
620
+ throw new Error('filterDispatchBySelector not exported yet');
621
+ }
622
+ const dispatch: DispatchEntry[] = [
623
+ makeDispatchEntry(0, 0, { LightMode: 'Forward' }),
624
+ makeDispatchEntry(1, 0, { LightMode: 'ShadowCaster' }),
625
+ ];
626
+ const result = filterDispatchBySelector(dispatch, {});
627
+ expect(result.length).toBe(2);
628
+ });
629
+
630
+ it('AC-03: selector { LightMode: [Forward] } returns only Forward entries', () => {
631
+ if (typeof filterDispatchBySelector !== 'function') {
632
+ throw new Error('filterDispatchBySelector not exported yet');
633
+ }
634
+ const dispatch: DispatchEntry[] = [
635
+ makeDispatchEntry(0, 0, { LightMode: 'Forward' }),
636
+ makeDispatchEntry(1, 0, { LightMode: 'ShadowCaster' }),
637
+ makeDispatchEntry(2, 0, { LightMode: 'Forward' }),
638
+ ];
639
+ const result = filterDispatchBySelector(dispatch, { LightMode: ['Forward'] });
640
+ expect(result.length).toBe(2);
641
+ for (const e of result) {
642
+ expect(e.tags.LightMode).toBe('Forward');
643
+ }
644
+ });
645
+
646
+ it('AC-04: selector { LightMode: [ShadowCaster] } returns only ShadowCaster entries', () => {
647
+ if (typeof filterDispatchBySelector !== 'function') {
648
+ throw new Error('filterDispatchBySelector not exported yet');
649
+ }
650
+ const dispatch: DispatchEntry[] = [
651
+ makeDispatchEntry(0, 0, { LightMode: 'Forward' }),
652
+ makeDispatchEntry(1, 0, { LightMode: 'ShadowCaster' }),
653
+ makeDispatchEntry(2, 0, { LightMode: 'Forward' }),
654
+ ];
655
+ const result = filterDispatchBySelector(dispatch, { LightMode: ['ShadowCaster'] });
656
+ expect(result.length).toBe(1);
657
+ expect(result[0]?.tags.LightMode).toBe('ShadowCaster');
658
+ expect(result[0]?.renderableIndex).toBe(1);
659
+ });
660
+
661
+ it('AC-18: multiple ShadowCaster passes per entity -> filtered count equals N', () => {
662
+ if (typeof filterDispatchBySelector !== 'function') {
663
+ throw new Error('filterDispatchBySelector not exported yet');
664
+ }
665
+ // Entity at renderableIndex=0 has 3 ShadowCaster passes.
666
+ const dispatch: DispatchEntry[] = [
667
+ makeDispatchEntry(0, 0, { LightMode: 'ShadowCaster' }),
668
+ makeDispatchEntry(0, 1, { LightMode: 'ShadowCaster' }),
669
+ makeDispatchEntry(0, 2, { LightMode: 'ShadowCaster' }),
670
+ makeDispatchEntry(1, 0, { LightMode: 'Forward' }),
671
+ ];
672
+ const result = filterDispatchBySelector(dispatch, { LightMode: ['ShadowCaster'] });
673
+ expect(result.length).toBe(3);
674
+ for (const e of result) {
675
+ expect(e.tags.LightMode).toBe('ShadowCaster');
676
+ expect(e.renderableIndex).toBe(0);
677
+ }
678
+ // Each pass has a distinct passIndex.
679
+ expect(result[0]?.passIndex).toBe(0);
680
+ expect(result[1]?.passIndex).toBe(1);
681
+ expect(result[2]?.passIndex).toBe(2);
682
+ });
683
+
684
+ it('selector with non-matching key returns empty array', () => {
685
+ if (typeof filterDispatchBySelector !== 'function') {
686
+ throw new Error('filterDispatchBySelector not exported yet');
687
+ }
688
+ const dispatch: DispatchEntry[] = [makeDispatchEntry(0, 0, { LightMode: 'Forward' })];
689
+ const result = filterDispatchBySelector(dispatch, { RenderType: ['Opaque'] });
690
+ expect(result.length).toBe(0);
691
+ });
692
+
693
+ it('selector matching on multiple keys requires both to match', () => {
694
+ if (typeof filterDispatchBySelector !== 'function') {
695
+ throw new Error('filterDispatchBySelector not exported yet');
696
+ }
697
+ const dispatch: DispatchEntry[] = [
698
+ makeDispatchEntry(0, 0, { LightMode: 'Forward', RenderQueue: 'Geometry' }),
699
+ makeDispatchEntry(1, 0, { LightMode: 'Forward', RenderQueue: 'Transparent' }),
700
+ ];
701
+ const result = filterDispatchBySelector(dispatch, {
702
+ LightMode: ['Forward'],
703
+ RenderQueue: ['Geometry'],
704
+ });
705
+ expect(result.length).toBe(1);
706
+ expect(result[0]?.renderableIndex).toBe(0);
707
+ });
708
+
709
+ it('entry with empty tags is filtered out by a non-empty selector', () => {
710
+ if (typeof filterDispatchBySelector !== 'function') {
711
+ throw new Error('filterDispatchBySelector not exported yet');
712
+ }
713
+ const dispatch: DispatchEntry[] = [
714
+ makeDispatchEntry(0, 0, {}),
715
+ makeDispatchEntry(1, 0, { LightMode: 'Forward' }),
716
+ ];
717
+ const result = filterDispatchBySelector(dispatch, { LightMode: ['Forward'] });
718
+ expect(result.length).toBe(1);
719
+ expect(result[0]?.renderableIndex).toBe(1);
720
+ });
721
+ });
722
+ }
723
+
724
+ // --- from feat-20260625-refactor-sprite-as-transparent-mesh M1 / w1 ---
725
+ {
726
+ // Generic std140 UBO writer driven by derive(paramSchema).uboLayout.entries.
727
+ //
728
+ // Plan anchors:
729
+ // - plan-strategy section 2 D-2: the inline overlay at render-system-
730
+ // record.ts:4334-4374 is generalised to read every uboLayout.entries
731
+ // std140 offset and write paramSnapshot[name] (numeric / numeric[])
732
+ // into the payload buffer. standard-pbr stays byte-identical to the
733
+ // pre-feat buildPbrMaterialUboPayload output (covered by the w2 suite
734
+ // appended below); sprite-shaped layouts (4 x vec4 stride 16) are
735
+ // honoured per offset/size declared by derive.
736
+ // - requirements AC-03 sets buildSpriteMaterialUboPayload ablation as
737
+ // the M3 deliverable; M1 only establishes that the generic writer can
738
+ // produce sprite-shaped bytes from paramSnapshot alone (no asset get,
739
+ // gate R-H from plan-strategy section 5.6 prevents reading internals).
740
+ //
741
+ // The suite reads render-system-record dynamically so it sits RED until
742
+ // w3 lands. The expected helper is a pure function with the signature:
743
+ //
744
+ // applyParamSnapshotToUbo(
745
+ // payload: ArrayBuffer,
746
+ // paramSchema: readonly ParamSchemaEntry[] | undefined,
747
+ // paramSnapshot: { readonly [name: string]: number | readonly number[] }
748
+ // | undefined,
749
+ // ): void
750
+ //
751
+ // The helper is internal (export-for-test) and walks
752
+ // derive(paramSchema).uboLayout.entries to dispatch each field by its
753
+ // std140 offset. Vec / color entries pull numeric arrays from the
754
+ // snapshot; f32 / i32 / u32 entries pull scalars.
755
+
756
+ const mod = recordModule as unknown as {
757
+ applyParamSnapshotToUbo?: (
758
+ payload: ArrayBuffer | Uint8Array,
759
+ paramSchema: readonly ParamSchemaEntry[] | undefined,
760
+ paramSnapshot:
761
+ | Readonly<Record<string, number | readonly number[] | string | undefined>>
762
+ | undefined,
763
+ ) => void;
764
+ };
765
+
766
+ // Sprite-shaped paramSchema: four vec4 entries at std140 offsets
767
+ // 0 / 16 / 32 / 48; total = 64 bytes rounded to 16 (still 64).
768
+ // This mirrors the post-ablation sprite.wgsl UBO layout (M3 / w11) but
769
+ // the writer must already accept it in M1 so M3 only needs to wire it in.
770
+ const SPRITE_SHAPED_SCHEMA: readonly ParamSchemaEntry[] = [
771
+ { name: 'colorTint', type: 'vec4', default: [1, 1, 1, 1] },
772
+ { name: 'region', type: 'vec4', default: [0, 0, 1, 1] },
773
+ { name: 'pivotAndSize', type: 'vec4', default: [0.5, 0.5, 1, 1] },
774
+ { name: 'slicesAndMode', type: 'vec4', default: [0, 0, 0, 0] },
775
+ ];
776
+
777
+ describe('applyParamSnapshotToUbo: sprite-shaped 4x vec4 std140 (M1 / w1)', () => {
778
+ it('export: applyParamSnapshotToUbo helper is exported from render-system-record', () => {
779
+ expect(typeof mod.applyParamSnapshotToUbo).toBe('function');
780
+ });
781
+
782
+ it('derive(uboLayout).entries produces four vec4 entries at offsets 0/16/32/48', () => {
783
+ const { uboLayout } = derive(SPRITE_SHAPED_SCHEMA);
784
+ expect(uboLayout.entries.length).toBe(4);
785
+ expect(uboLayout.entries[0]).toMatchObject({
786
+ name: 'colorTint',
787
+ offset: 0,
788
+ size: 16,
789
+ type: 'vec4',
790
+ });
791
+ expect(uboLayout.entries[1]).toMatchObject({
792
+ name: 'region',
793
+ offset: 16,
794
+ size: 16,
795
+ type: 'vec4',
796
+ });
797
+ expect(uboLayout.entries[2]).toMatchObject({
798
+ name: 'pivotAndSize',
799
+ offset: 32,
800
+ size: 16,
801
+ type: 'vec4',
802
+ });
803
+ expect(uboLayout.entries[3]).toMatchObject({
804
+ name: 'slicesAndMode',
805
+ offset: 48,
806
+ size: 16,
807
+ type: 'vec4',
808
+ });
809
+ // std140 struct alignment rounds up to 16 -- 64 already aligns.
810
+ expect(uboLayout.totalBytes).toBe(64);
811
+ });
812
+
813
+ it('writes each vec4 at its std140 offset (byte-exact, no f32-slot heuristic)', () => {
814
+ if (typeof mod.applyParamSnapshotToUbo !== 'function') {
815
+ throw new Error('helper not exported yet (red phase)');
816
+ }
817
+ const buf = new ArrayBuffer(64);
818
+ // Values chosen to round-trip exactly in IEEE 754 f32 (powers of 2 /
819
+ // sums thereof) so we can assert with toEqual; the non-exact-encoding
820
+ // values (0.1 / 0.2 / 0.3) would lose the trailing bit on f32 storage
821
+ // and force a toBeCloseTo, weakening the byte-level signal.
822
+ const snapshot = {
823
+ colorTint: [0.25, 0.5, 0.75, 1] as readonly number[],
824
+ region: [0.125, 0.25, 0.5, 0.75] as readonly number[],
825
+ pivotAndSize: [0.5, 0.5, 2, 4] as readonly number[],
826
+ slicesAndMode: [0.125, 0.125, 0.125, -0.125] as readonly number[],
827
+ };
828
+ mod.applyParamSnapshotToUbo(buf, SPRITE_SHAPED_SCHEMA, snapshot);
829
+ const f32 = new Float32Array(buf);
830
+ // colorTint at offset 0
831
+ expect(Array.from(f32.slice(0, 4))).toEqual([0.25, 0.5, 0.75, 1]);
832
+ // region at offset 16 (f32 index 4)
833
+ expect(Array.from(f32.slice(4, 8))).toEqual([0.125, 0.25, 0.5, 0.75]);
834
+ // pivotAndSize at offset 32 (f32 index 8)
835
+ expect(Array.from(f32.slice(8, 12))).toEqual([0.5, 0.5, 2, 4]);
836
+ // slicesAndMode at offset 48 (f32 index 12) -- carries 9-slice tile
837
+ // sentinel via negative w (verbatim from snapshot, charter P3 explicit
838
+ // signal vs the legacy spriteFields.sliceMode side channel).
839
+ expect(Array.from(f32.slice(12, 16))).toEqual([0.125, 0.125, 0.125, -0.125]);
840
+ });
841
+
842
+ it('missing snapshot fields leave existing bytes untouched (overlay semantics)', () => {
843
+ if (typeof mod.applyParamSnapshotToUbo !== 'function') {
844
+ throw new Error('helper not exported yet (red phase)');
845
+ }
846
+ const buf = new ArrayBuffer(64);
847
+ const pre = new Float32Array(buf);
848
+ // Seed the buffer with sentinel pre-values; the writer must only
849
+ // touch offsets it has snapshot values for (charter P4 -- the writer
850
+ // is an overlay, not a clear-and-fill stamp).
851
+ pre[0] = 9;
852
+ pre[1] = 9;
853
+ pre[2] = 9;
854
+ pre[3] = 9;
855
+ pre[12] = 7;
856
+ pre[13] = 7;
857
+ pre[14] = 7;
858
+ pre[15] = 7;
859
+ const snapshot = {
860
+ region: [0.125, 0.25, 0.5, 0.75] as readonly number[],
861
+ pivotAndSize: [0.5, 0.5, 2, 4] as readonly number[],
862
+ };
863
+ mod.applyParamSnapshotToUbo(buf, SPRITE_SHAPED_SCHEMA, snapshot);
864
+ const f32 = new Float32Array(buf);
865
+ // colorTint (no snapshot value) preserved
866
+ expect(Array.from(f32.slice(0, 4))).toEqual([9, 9, 9, 9]);
867
+ // region written
868
+ expect(Array.from(f32.slice(4, 8))).toEqual([0.125, 0.25, 0.5, 0.75]);
869
+ // pivotAndSize written
870
+ expect(Array.from(f32.slice(8, 12))).toEqual([0.5, 0.5, 2, 4]);
871
+ // slicesAndMode (no snapshot value) preserved
872
+ expect(Array.from(f32.slice(12, 16))).toEqual([7, 7, 7, 7]);
873
+ });
874
+
875
+ it('no-op when paramSchema or paramSnapshot is undefined', () => {
876
+ if (typeof mod.applyParamSnapshotToUbo !== 'function') {
877
+ throw new Error('helper not exported yet (red phase)');
878
+ }
879
+ const buf = new ArrayBuffer(64);
880
+ const pre = new Float32Array(buf);
881
+ for (let i = 0; i < 16; i++) pre[i] = i + 1;
882
+ const snapshot = { colorTint: [0.5, 0.5, 0.5, 0.5] as readonly number[] };
883
+ mod.applyParamSnapshotToUbo(buf, undefined, snapshot);
884
+ // Schema undefined -> no writes.
885
+ for (let i = 0; i < 16; i++) expect(pre[i]).toBe(i + 1);
886
+ mod.applyParamSnapshotToUbo(buf, SPRITE_SHAPED_SCHEMA, undefined);
887
+ // Snapshot undefined -> no writes.
888
+ for (let i = 0; i < 16; i++) expect(pre[i]).toBe(i + 1);
889
+ });
890
+ });
891
+ }
892
+
893
+ // --- from feat-20260625-refactor-sprite-as-transparent-mesh M2 / w4 ---
894
+ {
895
+ // AC-05: a MaterialSnapshot carrying transparent:true on its source pass
896
+ // (recorded as MaterialSnapshot.transparent === true) must drive the LDR
897
+ // split-pass decision and the blend-state lookup INDEPENDENTLY of the
898
+ // shader id. The shader here is a generic 'forgeax::unlit-test' stub -- if
899
+ // splitLdrSprite or the blend-state helper still keys on
900
+ // 'forgeax::sprite', this suite stays red.
901
+ //
902
+ // Plan anchors:
903
+ // - requirements AC-05 (transparent decoupled from sprite shader)
904
+ // - plan-strategy section 2 D-3 (MaterialSnapshot.transparent
905
+ // transparent passthrough; split reads snapshot, not pass)
906
+ // - plan-strategy section 5.4 falsification check (the inline comment
907
+ // below records the variant: flipping blend src=one/dst=zero turns
908
+ // premultiplied alpha into hard-edge composition and fails the dawn
909
+ // msaa-sprite-pixel-diff baseline -- documenting that the pixel
910
+ // diff smoke is sensitive to blend factor correctness).
911
+ //
912
+ // The `computeSplitLdrSprite` helper is added by w7. The dynamic-import
913
+ // sentinel keeps this suite red until it lands.
914
+ //
915
+ // feat-20260626-sprite-transparent-collapse M2 / M4: the sibling
916
+ // `resolveTransparentBlendState` helper has been removed — premultiplied-
917
+ // alpha blend factors are now pinned to the exported
918
+ // `SPRITE_PREMULTIPLIED_ALPHA_BLEND` constant (the SSOT for asset-side
919
+ // `renderState.blend`); the tests that exercised the resolver are deleted
920
+ // and the falsification check now lives on the dawn pixel-diff
921
+ // baseline + the asset-side constant export check.
922
+
923
+ type DispatchEntryLike = {
924
+ readonly source: {
925
+ readonly material: MaterialSnapshot;
926
+ readonly materials: readonly MaterialSnapshot[];
927
+ };
928
+ };
929
+
930
+ const mod = recordModule as unknown as {
931
+ computeSplitLdrSprite?: (
932
+ validatedOrdered: readonly (DispatchEntryLike | undefined)[],
933
+ tonemapActive: boolean,
934
+ ) => boolean;
935
+ isEntityFullyTransparent?: (source: {
936
+ material: MaterialSnapshot;
937
+ materials?: readonly MaterialSnapshot[];
938
+ }) => boolean;
939
+ entityHasTransparentSubmesh?: (source: {
940
+ material: MaterialSnapshot;
941
+ materials?: readonly MaterialSnapshot[];
942
+ }) => boolean;
943
+ };
944
+
945
+ function makeTransparentSnap(opts: {
946
+ transparent?: boolean;
947
+ materialShaderId?: string;
948
+ }): MaterialSnapshot {
949
+ return {
950
+ baseColor: [1, 1, 1, 1] as const,
951
+ metallic: 0,
952
+ roughness: 1,
953
+ materialShaderId: opts.materialShaderId ?? 'forgeax::unlit-test',
954
+ paramSnapshot: {},
955
+ ...(opts.transparent === true && { transparent: true }),
956
+ } as unknown as MaterialSnapshot;
957
+ }
958
+
959
+ function makeValidatedEntry(material: MaterialSnapshot): DispatchEntryLike {
960
+ return { source: { material, materials: [material] } };
961
+ }
962
+
963
+ describe('transparent decouples from sprite shader (M2 / w4, AC-05)', () => {
964
+ it('export: computeSplitLdrSprite helper is exported from render-system-record', () => {
965
+ expect(typeof mod.computeSplitLdrSprite).toBe('function');
966
+ });
967
+
968
+ it('non-sprite shader with transparent:true triggers LDR split', () => {
969
+ if (typeof mod.computeSplitLdrSprite !== 'function') {
970
+ throw new Error('computeSplitLdrSprite helper not exported yet (red phase)');
971
+ }
972
+ const transparentSnap = makeTransparentSnap({
973
+ transparent: true,
974
+ materialShaderId: 'forgeax::unlit-test',
975
+ });
976
+ const opaqueSnap = makeTransparentSnap({
977
+ transparent: false,
978
+ materialShaderId: 'forgeax::unlit-test',
979
+ });
980
+ // tonemapActive=false (LDR path) + a transparent entry in the
981
+ // validated list -- the split decision must trip even though the
982
+ // shader is not forgeax::sprite (AC-05 proves the decoupling).
983
+ const split = mod.computeSplitLdrSprite(
984
+ [makeValidatedEntry(opaqueSnap), makeValidatedEntry(transparentSnap)],
985
+ false,
986
+ );
987
+ expect(split).toBe(true);
988
+ });
989
+
990
+ it('LDR split stays false on pure-opaque non-sprite list', () => {
991
+ if (typeof mod.computeSplitLdrSprite !== 'function') {
992
+ throw new Error('computeSplitLdrSprite helper not exported yet (red phase)');
993
+ }
994
+ const opaqueA = makeTransparentSnap({
995
+ transparent: false,
996
+ materialShaderId: 'forgeax::unlit-test',
997
+ });
998
+ const opaqueB = makeTransparentSnap({
999
+ transparent: false,
1000
+ materialShaderId: 'forgeax::default-standard-pbr',
1001
+ });
1002
+ const split = mod.computeSplitLdrSprite(
1003
+ [makeValidatedEntry(opaqueA), makeValidatedEntry(opaqueB)],
1004
+ false,
1005
+ );
1006
+ expect(split).toBe(false);
1007
+ });
1008
+
1009
+ it('HDR path (tonemapActive=true) suppresses split even with transparent entries', () => {
1010
+ if (typeof mod.computeSplitLdrSprite !== 'function') {
1011
+ throw new Error('computeSplitLdrSprite helper not exported yet (red phase)');
1012
+ }
1013
+ const transparentSnap = makeTransparentSnap({
1014
+ transparent: true,
1015
+ materialShaderId: 'forgeax::unlit-test',
1016
+ });
1017
+ const split = mod.computeSplitLdrSprite([makeValidatedEntry(transparentSnap)], true);
1018
+ expect(split).toBe(false);
1019
+ });
1020
+
1021
+ it('M3 / w13: legacy sprite material without transparent:true does NOT trigger split (union arm deleted)', () => {
1022
+ if (typeof mod.computeSplitLdrSprite !== 'function') {
1023
+ throw new Error('computeSplitLdrSprite helper not exported yet (red phase)');
1024
+ }
1025
+ // feat-20260625-refactor-sprite-as-transparent-mesh M3 / w13 (D-3):
1026
+ // the M2 union (`transparent || shadingModel === 'sprite'`) is gone
1027
+ // — transparent is the single SSOT. A legacy snapshot carrying
1028
+ // `materialShaderId: 'forgeax::sprite'` but no `transparent: true`
1029
+ // declaration MUST NOT trigger the LDR split (forces explicit
1030
+ // transparent attribution on the asset side; AC-04 / AC-05).
1031
+ const legacySpriteNoTransparent = {
1032
+ baseColor: [1, 1, 1, 1] as const,
1033
+ metallic: 0,
1034
+ roughness: 1,
1035
+ materialShaderId: 'forgeax::sprite',
1036
+ paramSnapshot: {},
1037
+ } as unknown as MaterialSnapshot;
1038
+ const splitWithoutTransparent = mod.computeSplitLdrSprite(
1039
+ [makeValidatedEntry(legacySpriteNoTransparent)],
1040
+ false,
1041
+ );
1042
+ expect(splitWithoutTransparent).toBe(false);
1043
+
1044
+ // Same snapshot with `transparent: true` declared on the pass DOES
1045
+ // trigger the split (the only path that should).
1046
+ const spriteTransparent = {
1047
+ ...legacySpriteNoTransparent,
1048
+ transparent: true,
1049
+ } as unknown as MaterialSnapshot;
1050
+ const splitWithTransparent = mod.computeSplitLdrSprite(
1051
+ [makeValidatedEntry(spriteTransparent)],
1052
+ false,
1053
+ );
1054
+ expect(splitWithTransparent).toBe(true);
1055
+ });
1056
+
1057
+ it('transparent:true resolves premultiplied-alpha blend state (src=one/dst=one-minus-src-alpha)', () => {
1058
+ // feat-20260626-sprite-transparent-collapse M2 / M4: the
1059
+ // `resolveTransparentBlendState` resolver is gone — premultiplied-alpha
1060
+ // factors are pinned to the exported `SPRITE_PREMULTIPLIED_ALPHA_BLEND`
1061
+ // constant (the asset-side SSOT for `renderState.blend`). This test
1062
+ // pins the constant's shape so a future maintainer flipping the color
1063
+ // blend to {src:'one', dst:'zero'} (opaque copy) still trips a unit
1064
+ // gate before the dawn msaa-sprite-pixel-diff smoke catches the
1065
+ // regression. AC-10 reaches the human arbiter through both paths.
1066
+ expect(SPRITE_PREMULTIPLIED_ALPHA_BLEND.color.srcFactor).toBe('one');
1067
+ expect(SPRITE_PREMULTIPLIED_ALPHA_BLEND.color.dstFactor).toBe('one-minus-src-alpha');
1068
+ expect(SPRITE_PREMULTIPLIED_ALPHA_BLEND.alpha.srcFactor).toBe('one');
1069
+ expect(SPRITE_PREMULTIPLIED_ALPHA_BLEND.alpha.dstFactor).toBe('one-minus-src-alpha');
1070
+ });
1071
+
1072
+ // feat-city-glb Bug 5 (per-submesh transparency): the split must trip when
1073
+ // a NON-zero submesh material is transparent even though the entity-level
1074
+ // material[0] is opaque (the crosswalk case: opaque road submesh[0] +
1075
+ // BLEND decal submesh[1] on one multi-material mesh).
1076
+ it('per-submesh: split trips when only a non-zero submesh is transparent', () => {
1077
+ if (typeof mod.computeSplitLdrSprite !== 'function') {
1078
+ throw new Error('computeSplitLdrSprite helper not exported yet');
1079
+ }
1080
+ const opaque0 = makeTransparentSnap({
1081
+ transparent: false,
1082
+ materialShaderId: 'forgeax::default-standard-pbr',
1083
+ });
1084
+ const transparent1 = makeTransparentSnap({
1085
+ transparent: true,
1086
+ materialShaderId: 'forgeax::default-standard-pbr',
1087
+ });
1088
+ // Entity-level material (=materials[0]) is opaque; materials[1] transparent.
1089
+ const mixedEntry: DispatchEntryLike = {
1090
+ source: { material: opaque0, materials: [opaque0, transparent1] },
1091
+ };
1092
+ expect(mod.computeSplitLdrSprite([mixedEntry], false)).toBe(true);
1093
+ });
1094
+
1095
+ it('per-submesh: split stays false when every submesh material is opaque', () => {
1096
+ if (typeof mod.computeSplitLdrSprite !== 'function') {
1097
+ throw new Error('computeSplitLdrSprite helper not exported yet');
1098
+ }
1099
+ const opaque0 = makeTransparentSnap({
1100
+ transparent: false,
1101
+ materialShaderId: 'forgeax::default-standard-pbr',
1102
+ });
1103
+ const opaque1 = makeTransparentSnap({
1104
+ transparent: false,
1105
+ materialShaderId: 'forgeax::default-standard-pbr',
1106
+ });
1107
+ const opaqueMixed: DispatchEntryLike = {
1108
+ source: { material: opaque0, materials: [opaque0, opaque1] },
1109
+ };
1110
+ expect(mod.computeSplitLdrSprite([opaqueMixed], false)).toBe(false);
1111
+ });
1112
+
1113
+ it('isEntityFullyTransparent / entityHasTransparentSubmesh classify mixed meshes', () => {
1114
+ if (
1115
+ typeof mod.isEntityFullyTransparent !== 'function' ||
1116
+ typeof mod.entityHasTransparentSubmesh !== 'function'
1117
+ ) {
1118
+ throw new Error('per-submesh transparency helpers not exported yet');
1119
+ }
1120
+ const opaque = makeTransparentSnap({ transparent: false });
1121
+ const transp = makeTransparentSnap({ transparent: true });
1122
+ // Mixed: opaque[0] + transparent[1] -> has a transparent submesh but not
1123
+ // fully transparent (must draw in BOTH geometry pass and blend sub-pass).
1124
+ const mixed = { material: opaque, materials: [opaque, transp] };
1125
+ expect(mod.entityHasTransparentSubmesh(mixed)).toBe(true);
1126
+ expect(mod.isEntityFullyTransparent(mixed)).toBe(false);
1127
+ // Fully transparent (e.g. a sprite / 4.3 window quad).
1128
+ const full = { material: transp, materials: [transp] };
1129
+ expect(mod.entityHasTransparentSubmesh(full)).toBe(true);
1130
+ expect(mod.isEntityFullyTransparent(full)).toBe(true);
1131
+ // Fully opaque.
1132
+ const none = { material: opaque, materials: [opaque, opaque] };
1133
+ expect(mod.entityHasTransparentSubmesh(none)).toBe(false);
1134
+ expect(mod.isEntityFullyTransparent(none)).toBe(false);
1135
+ });
1136
+ });
1137
+ }
1138
+
1139
+ // --- from feat-20260625-refactor-sprite-as-transparent-mesh M2 / w5 ---
1140
+ {
1141
+ // AC-14: a transparent material whose target shader pipeline is not yet
1142
+ // cached must surface a structured RhiError (.code / .expected / .hint),
1143
+ // NOT silently fall back to the debug-pink placeholder. The generic
1144
+ // materialShaderId path is the only resolver -- post feat-20260625 M3 / w14
1145
+ // the dedicated sprite PSO fields are gone; sprite uses the same generic
1146
+ // getMaterialShaderPipeline path every transparent material consumes.
1147
+ //
1148
+ // The helper signature added by w7:
1149
+ //
1150
+ // selectMaterialPipelineForRender({
1151
+ // materialShaderId, isHdr, renderState, topology, indexFormat,
1152
+ // variantSet, sampleCount,
1153
+ // getMaterialShaderPipeline, // injected at call site (runtime.getMaterialShaderPipeline)
1154
+ // }): Result<RenderPipeline, RhiError>
1155
+ //
1156
+ // Plan anchors:
1157
+ // - requirements AC-14 (structured RhiError, no debug-pink fall-through)
1158
+ // - plan-strategy section 8.3 (error info AI users consume via property
1159
+ // access: .code / .expected / .hint; charter P3 explicit failure)
1160
+ // - plan-strategy section 2 D-3 (transparent flows through the generic
1161
+ // materialShaderId path; the resolver therefore never branches on
1162
+ // 'forgeax::sprite' to recover)
1163
+
1164
+ type SelectArgs = {
1165
+ readonly materialShaderId: string;
1166
+ readonly isHdr: boolean;
1167
+ readonly renderState: {
1168
+ readonly blend?: {
1169
+ readonly color: { readonly srcFactor: string; readonly dstFactor: string };
1170
+ readonly alpha: { readonly srcFactor: string; readonly dstFactor: string };
1171
+ };
1172
+ };
1173
+ readonly topology: 'triangle-list' | 'triangle-strip';
1174
+ readonly indexFormat: 'uint16' | 'uint32';
1175
+ readonly variantSet: string;
1176
+ readonly sampleCount: number;
1177
+ readonly getMaterialShaderPipeline: (
1178
+ materialShaderId: string,
1179
+ isHdr: boolean,
1180
+ ) => unknown | null;
1181
+ };
1182
+
1183
+ const mod = recordModule as unknown as {
1184
+ selectMaterialPipelineForRender?: (
1185
+ args: SelectArgs,
1186
+ ) =>
1187
+ | { readonly ok: true; readonly value: unknown }
1188
+ | { readonly ok: false; readonly error: { code: string; expected: string; hint: string } };
1189
+ };
1190
+
1191
+ function makeSelectArgs(overrides: {
1192
+ materialShaderId?: string;
1193
+ getter?: SelectArgs['getMaterialShaderPipeline'];
1194
+ }): SelectArgs {
1195
+ const pipelineStub = { __pipeline: true };
1196
+ return {
1197
+ materialShaderId: overrides.materialShaderId ?? 'forgeax::unlit-test',
1198
+ isHdr: false,
1199
+ renderState: {
1200
+ blend: {
1201
+ color: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha' },
1202
+ alpha: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha' },
1203
+ },
1204
+ },
1205
+ topology: 'triangle-list',
1206
+ indexFormat: 'uint16',
1207
+ variantSet: '',
1208
+ sampleCount: 1,
1209
+ getMaterialShaderPipeline: overrides.getter ?? (() => pipelineStub),
1210
+ };
1211
+ }
1212
+
1213
+ describe('selectMaterialPipelineForRender pipeline-miss surfaces RhiError (M2 / w5, AC-14)', () => {
1214
+ it('export: selectMaterialPipelineForRender helper is exported from render-system-record', () => {
1215
+ expect(typeof mod.selectMaterialPipelineForRender).toBe('function');
1216
+ });
1217
+
1218
+ it('cache miss returns ok=false with structured RhiError (.code/.expected/.hint)', () => {
1219
+ if (typeof mod.selectMaterialPipelineForRender !== 'function') {
1220
+ throw new Error('selectMaterialPipelineForRender helper not exported yet (red phase)');
1221
+ }
1222
+ const result = mod.selectMaterialPipelineForRender(
1223
+ makeSelectArgs({
1224
+ materialShaderId: 'forgeax::unlit-test',
1225
+ getter: () => null, // simulate cache miss / async build pending
1226
+ }),
1227
+ );
1228
+ expect(result.ok).toBe(false);
1229
+ if (result.ok) throw new Error('unreachable -- result.ok asserted false above');
1230
+ const err = result.error;
1231
+ // charter P3 -- AI users branch on .code (string union member from
1232
+ // RhiErrorCode) and consume .expected / .hint as property reads;
1233
+ // they do NOT parse the human-facing .message string.
1234
+ expect(typeof err.code).toBe('string');
1235
+ expect(err.code.length).toBeGreaterThan(0);
1236
+ expect(typeof err.expected).toBe('string');
1237
+ expect(err.expected.length).toBeGreaterThan(0);
1238
+ expect(typeof err.hint).toBe('string');
1239
+ expect(err.hint.length).toBeGreaterThan(0);
1240
+ });
1241
+
1242
+ it('cache miss .hint references the missing materialShaderId (AI users locate the cache key)', () => {
1243
+ if (typeof mod.selectMaterialPipelineForRender !== 'function') {
1244
+ throw new Error('selectMaterialPipelineForRender helper not exported yet (red phase)');
1245
+ }
1246
+ const result = mod.selectMaterialPipelineForRender(
1247
+ makeSelectArgs({
1248
+ materialShaderId: 'forgeax::missing-shader-id',
1249
+ getter: () => null,
1250
+ }),
1251
+ );
1252
+ expect(result.ok).toBe(false);
1253
+ if (result.ok) throw new Error('unreachable -- result.ok asserted false above');
1254
+ // The shader id must appear in .hint (or .expected) so AI users can
1255
+ // grep the registry / pipeline cache straight from the structured
1256
+ // error -- not from a human-prose substring of .message.
1257
+ const exposed = `${result.error.hint} ${result.error.expected}`;
1258
+ expect(exposed).toContain('forgeax::missing-shader-id');
1259
+ });
1260
+
1261
+ it('cache hit returns ok=true with the pipeline (no error path on success)', () => {
1262
+ if (typeof mod.selectMaterialPipelineForRender !== 'function') {
1263
+ throw new Error('selectMaterialPipelineForRender helper not exported yet (red phase)');
1264
+ }
1265
+ const pipeline = { __pipeline: 'forgeax::unlit-test-pso' };
1266
+ const result = mod.selectMaterialPipelineForRender(
1267
+ makeSelectArgs({
1268
+ materialShaderId: 'forgeax::unlit-test',
1269
+ getter: () => pipeline,
1270
+ }),
1271
+ );
1272
+ expect(result.ok).toBe(true);
1273
+ if (!result.ok) throw new Error('unreachable -- result.ok asserted true above');
1274
+ expect(result.value).toBe(pipeline);
1275
+ });
1276
+
1277
+ it('cache miss does NOT silently substitute a debug-pink fallback pipeline', () => {
1278
+ if (typeof mod.selectMaterialPipelineForRender !== 'function') {
1279
+ throw new Error('selectMaterialPipelineForRender helper not exported yet (red phase)');
1280
+ }
1281
+ // Probe: the only branch the getter ever returns is the one passed in.
1282
+ // A cache miss must surface ok=false, NOT a synthesized pipeline value
1283
+ // that hides the miss (charter P3 -- the pre-M6 silent fallback to
1284
+ // pipelineState.standardPipeline* was exactly this anti-pattern, see
1285
+ // render-system-record.ts:5188-5198 commentary).
1286
+ let getterCallCount = 0;
1287
+ const result = mod.selectMaterialPipelineForRender(
1288
+ makeSelectArgs({
1289
+ materialShaderId: 'forgeax::unlit-test',
1290
+ getter: () => {
1291
+ getterCallCount += 1;
1292
+ return null;
1293
+ },
1294
+ }),
1295
+ );
1296
+ expect(getterCallCount).toBeGreaterThanOrEqual(1);
1297
+ expect(result.ok).toBe(false);
1298
+ });
1299
+ });
1300
+ }
1301
+
1302
+ // ─── bug-20260622-tilemap-ysort-transparent-sort-modes-followup M2 m2-1 ───
1303
+ //
1304
+ // AC-04 (error signal SSOT) + AC-05 (LAYER_Y footY ordering) + R-2
1305
+ // (mode=DISTANCE without a cameraPos falls back to the original list).
1306
+ // `sortTransparentDispatch` itself is render-system.ts-private (closure-
1307
+ // scoped helper invoked once per draw); the public-surface contract sits on
1308
+ // `transparentSortEntries` (same primary `layer ASC` + secondary mode-formula
1309
+ // + identical fallback semantics). Asserting against the exported helper
1310
+ // gives byte-exact coverage of the 4-mode dispatch SSOT without instantiating
1311
+ // the full RenderSystem (which would need a real GPU device + canvas, far
1312
+ // outside unit-test scope; plan-strategy R-2 mitigation).
1313
+ // biome-ignore lint/complexity/noUselessLoneBlockStatements: mirrors the per-test-file block-scope idiom this consolidated test file already uses (lines 31/159/261/501/633 -- each ported slab from a pre-consolidation file lives in its own block so helper names cannot collide).
1314
+ {
1315
+ // --- AC-04: setTransparentSortConfig mode=99 -> Result.err with 4 SSOT fields ---
1316
+ describe('AC-04 setTransparentSortConfig mode=99 returns Result.err (KV untouched)', () => {
1317
+ it('Result.err carries code/expected/hint/detail + KV resource is NOT inserted', async () => {
1318
+ const { setTransparentSortConfig, TRANSPARENT_SORT_CONFIG_KEY } = await import(
1319
+ '@forgeax/engine-render/internal'
1320
+ );
1321
+ const world = new World();
1322
+ // Pre-check: resource MUST be absent before the rejected call.
1323
+ expect(world.hasResource(TRANSPARENT_SORT_CONFIG_KEY)).toBe(false);
1324
+
1325
+ const r = setTransparentSortConfig(world, { mode: 99, yzAlpha: 1.0 });
1326
+ expect(r.ok).toBe(false);
1327
+ if (r.ok) return;
1328
+ // The 4 SSOT fields locked by plan-strategy D-4. The math symbol
1329
+ // U+2208 ("is element of") goes through the ASCII `∈` escape
1330
+ // so this source file stays English-only per AGENTS.md §Conventions
1331
+ // (mirrors EXPECTED_EXPECTED in systems.unit.test.ts).
1332
+ expect(r.error.code).toBe('resource-invalid-value');
1333
+ expect(r.error.expected).toBe('mode ∈ {0, 1, 2, 3}');
1334
+ expect(r.error.hint).toBe('0=layer-z, 1=layer-y, 2=layer-yz, 3=distance');
1335
+ expect((r.error.detail as { receivedMode: number }).receivedMode).toBe(99);
1336
+ // The KV resource MUST stay un-inserted after a rejected write
1337
+ // (charter P3 -- structured failure, never silently coerce).
1338
+ expect(world.hasResource(TRANSPARENT_SORT_CONFIG_KEY)).toBe(false);
1339
+ });
1340
+ });
1341
+
1342
+ // --- AC-05: mode=LAYER_Y, 3 entities footY=10/20/30 same layer -> 30/20/10 ---
1343
+ describe('AC-05 LAYER_Y footY ordering (same layer, deeper foot draws later)', () => {
1344
+ it('footY=10/20/30 same layer -> output order footY=30/20/10 (back-to-front)', async () => {
1345
+ const { setTransparentSortConfig, TRANSPARENT_SORT_MODE_LAYER_Y } = await import(
1346
+ '@forgeax/engine-render/internal'
1347
+ );
1348
+ const { transparentSortEntries } = await import('../systems/transparent-sort');
1349
+ const world = new World();
1350
+ setTransparentSortConfig(world, {
1351
+ mode: TRANSPARENT_SORT_MODE_LAYER_Y,
1352
+ yzAlpha: 1.0,
1353
+ }).unwrap();
1354
+
1355
+ // footY = posY - pivotY * sizeY. Pin pivotY=0 + sizeY=1 so footY === posY
1356
+ // -- the 10/20/30 numbers land verbatim, no algebra to second-guess.
1357
+ // mode=1 sortValue = -footY; ASC over [-10, -20, -30] yields entries
1358
+ // ordered footY=30, 20, 10 (deepest foot draws last = back-to-front).
1359
+ const entries = [
1360
+ {
1361
+ entityIndex: 0,
1362
+ materialHandle: 0,
1363
+ layer: 0,
1364
+ posX: 0,
1365
+ posY: 10,
1366
+ posZ: 0,
1367
+ pivotY: 0,
1368
+ sizeY: 1,
1369
+ },
1370
+ {
1371
+ entityIndex: 1,
1372
+ materialHandle: 0,
1373
+ layer: 0,
1374
+ posX: 0,
1375
+ posY: 20,
1376
+ posZ: 0,
1377
+ pivotY: 0,
1378
+ sizeY: 1,
1379
+ },
1380
+ {
1381
+ entityIndex: 2,
1382
+ materialHandle: 0,
1383
+ layer: 0,
1384
+ posX: 0,
1385
+ posY: 30,
1386
+ posZ: 0,
1387
+ pivotY: 0,
1388
+ sizeY: 1,
1389
+ },
1390
+ ];
1391
+ const sorted = transparentSortEntries(entries, world);
1392
+ expect(sorted.map((e) => e.posY)).toEqual([30, 20, 10]);
1393
+ });
1394
+ });
1395
+
1396
+ // --- R-2: mode=DISTANCE without a cameraPos returns entries unchanged ---
1397
+ // plan-strategy R-2 risk mitigation: sortTransparentDispatch mode=3 +
1398
+ // cameras[0] missing must keep the original dispatch list (PR #401
1399
+ // baseline). transparentSortEntries surfaces the same fallback through
1400
+ // the public helper -- the 2-arg call (no cameraPos) falls through to
1401
+ // the mode=0 posZ formula; with posZ pinned constant the sort becomes
1402
+ // a no-op preserving insertion order (charter P3 deterministic output).
1403
+ describe('R-2 mode=DISTANCE + cameraPos absent preserves insertion order', () => {
1404
+ it('transparentSortEntries(entries, world) with mode=3 + no cameraPos = insertion order', async () => {
1405
+ const { setTransparentSortConfig, TRANSPARENT_SORT_MODE_DISTANCE } = await import(
1406
+ '@forgeax/engine-render/internal'
1407
+ );
1408
+ const { transparentSortEntries } = await import('../systems/transparent-sort');
1409
+ const world = new World();
1410
+ setTransparentSortConfig(world, {
1411
+ mode: TRANSPARENT_SORT_MODE_DISTANCE,
1412
+ yzAlpha: 1.0,
1413
+ }).unwrap();
1414
+
1415
+ // posZ pinned identical so the fallback (posZ ASC) is a no-op; the
1416
+ // assertion guards both R-2 (no crash, no reorder) and "fallback is
1417
+ // deterministic" together.
1418
+ const entries = [
1419
+ {
1420
+ entityIndex: 7,
1421
+ materialHandle: 0,
1422
+ layer: 0,
1423
+ posX: 1,
1424
+ posY: 0,
1425
+ posZ: 0,
1426
+ pivotY: 0.5,
1427
+ sizeY: 1,
1428
+ },
1429
+ {
1430
+ entityIndex: 8,
1431
+ materialHandle: 0,
1432
+ layer: 0,
1433
+ posX: 2,
1434
+ posY: 0,
1435
+ posZ: 0,
1436
+ pivotY: 0.5,
1437
+ sizeY: 1,
1438
+ },
1439
+ {
1440
+ entityIndex: 9,
1441
+ materialHandle: 0,
1442
+ layer: 0,
1443
+ posX: 3,
1444
+ posY: 0,
1445
+ posZ: 0,
1446
+ pivotY: 0.5,
1447
+ sizeY: 1,
1448
+ },
1449
+ ];
1450
+ const sorted = transparentSortEntries(entries, world);
1451
+ expect(sorted.map((e) => e.entityIndex)).toEqual([7, 8, 9]);
1452
+ });
1453
+ });
1454
+ }
1455
+
1456
+ // --- tweak-20260701 M2 AC-02: zero-light warning polarity ---
1457
+ {
1458
+ // AC-02: the zero-light black-screen warning must still fire for
1459
+ // builtin PBR/standard materials and remain silent for custom, unlit /
1460
+ // sprite / default materials. Custom shader lighting contracts are authored
1461
+ // by the shader and cannot be inferred by this generic diagnostic.
1462
+ //
1463
+ // The test imports `isLitMaterialSnapshot` from the production
1464
+ // render-system-record module — the same function that recordFrame's
1465
+ // zero-light warning gate (line ~2122) calls to compute
1466
+ // hasStandardMaterial. A test-local copy would not anchor to the
1467
+ // production path.
1468
+
1469
+ function makeSnap(materialShaderId: string | undefined): MaterialSnapshot {
1470
+ return {
1471
+ baseColor: [1, 1, 1, 1] as const,
1472
+ metallic: 0,
1473
+ roughness: 1,
1474
+ materialShaderId,
1475
+ paramSnapshot: undefined,
1476
+ } as unknown as MaterialSnapshot;
1477
+ }
1478
+
1479
+ describe('AC-02: zero-light warning polarity', () => {
1480
+ it('standard / PBR material (materialShaderId !== undefined, !== default-unlit) -> lit', () => {
1481
+ const snap = makeSnap('forgeax::default-standard-pbr');
1482
+ expect(isLitMaterialSnapshot(snap)).toBe(true);
1483
+ });
1484
+
1485
+ it('custom shader material -> not lit (custom lighting contract)', () => {
1486
+ const snap = makeSnap('my-custom-shader');
1487
+ expect(isLitMaterialSnapshot(snap)).toBe(false);
1488
+ });
1489
+
1490
+ it('unlit material (materialShaderId === forgeax::default-unlit) -> not lit (no warn)', () => {
1491
+ const snap = makeSnap('forgeax::default-unlit');
1492
+ expect(isLitMaterialSnapshot(snap)).toBe(false);
1493
+ });
1494
+
1495
+ it('sprite material (materialShaderId === forgeax::sprite) -> not lit (no warn)', () => {
1496
+ const snap = makeSnap('forgeax::sprite');
1497
+ expect(isLitMaterialSnapshot(snap)).toBe(false);
1498
+ });
1499
+
1500
+ it('default material (materialShaderId === undefined) -> not lit (no warn)', () => {
1501
+ const snap = makeSnap(undefined);
1502
+ expect(isLitMaterialSnapshot(snap)).toBe(false);
1503
+ });
1504
+ });
1505
+ }