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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (1017) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +165 -0
  3. package/dist/.tsbuildinfo +1 -0
  4. package/dist/__tests__/active-camera.unit.test.d.ts +2 -0
  5. package/dist/__tests__/active-camera.unit.test.d.ts.map +1 -0
  6. package/dist/__tests__/animation-graph-add.unit.test.d.ts +2 -0
  7. package/dist/__tests__/animation-graph-add.unit.test.d.ts.map +1 -0
  8. package/dist/__tests__/animation-graph-asset-union.test-d.d.ts +2 -0
  9. package/dist/__tests__/animation-graph-asset-union.test-d.d.ts.map +1 -0
  10. package/dist/__tests__/animation-graph-blend.unit.test.d.ts +2 -0
  11. package/dist/__tests__/animation-graph-blend.unit.test.d.ts.map +1 -0
  12. package/dist/__tests__/animation-graph-build.unit.test.d.ts +2 -0
  13. package/dist/__tests__/animation-graph-build.unit.test.d.ts.map +1 -0
  14. package/dist/__tests__/animation-graph-clip-missing.unit.test.d.ts +2 -0
  15. package/dist/__tests__/animation-graph-clip-missing.unit.test.d.ts.map +1 -0
  16. package/dist/__tests__/animation-graph-clip.unit.test.d.ts +2 -0
  17. package/dist/__tests__/animation-graph-clip.unit.test.d.ts.map +1 -0
  18. package/dist/__tests__/animation-graph-collect-roundtrip.test.d.ts +2 -0
  19. package/dist/__tests__/animation-graph-collect-roundtrip.test.d.ts.map +1 -0
  20. package/dist/__tests__/animation-graph-default-path.integration.test.d.ts +2 -0
  21. package/dist/__tests__/animation-graph-default-path.integration.test.d.ts.map +1 -0
  22. package/dist/__tests__/animation-graph-describe.unit.test.d.ts +2 -0
  23. package/dist/__tests__/animation-graph-describe.unit.test.d.ts.map +1 -0
  24. package/dist/__tests__/animation-graph-errors.unit.test.d.ts +2 -0
  25. package/dist/__tests__/animation-graph-errors.unit.test.d.ts.map +1 -0
  26. package/dist/__tests__/animation-graph-nesting.unit.test.d.ts +2 -0
  27. package/dist/__tests__/animation-graph-nesting.unit.test.d.ts.map +1 -0
  28. package/dist/__tests__/animation-graph-orthogonal-weight.unit.test.d.ts +2 -0
  29. package/dist/__tests__/animation-graph-orthogonal-weight.unit.test.d.ts.map +1 -0
  30. package/dist/__tests__/animation-graph-serialize.unit.test.d.ts +2 -0
  31. package/dist/__tests__/animation-graph-serialize.unit.test.d.ts.map +1 -0
  32. package/dist/__tests__/animation-graph-slot-fill.unit.test.d.ts +2 -0
  33. package/dist/__tests__/animation-graph-slot-fill.unit.test.d.ts.map +1 -0
  34. package/dist/__tests__/animation-nslot.unit.test.d.ts +2 -0
  35. package/dist/__tests__/animation-nslot.unit.test.d.ts.map +1 -0
  36. package/dist/__tests__/animation-slot-length-guard.unit.test.d.ts +2 -0
  37. package/dist/__tests__/animation-slot-length-guard.unit.test.d.ts.map +1 -0
  38. package/dist/__tests__/append-injection.test.d.ts +2 -0
  39. package/dist/__tests__/append-injection.test.d.ts.map +1 -0
  40. package/dist/__tests__/asi-world-baseline-export.unit.test.d.ts +2 -0
  41. package/dist/__tests__/asi-world-baseline-export.unit.test.d.ts.map +1 -0
  42. package/dist/__tests__/asi-world-ci-config.unit.test.d.ts +2 -0
  43. package/dist/__tests__/asi-world-ci-config.unit.test.d.ts.map +1 -0
  44. package/dist/__tests__/asi-world-smoke.unit.test.d.ts +2 -0
  45. package/dist/__tests__/asi-world-smoke.unit.test.d.ts.map +1 -0
  46. package/dist/__tests__/asset-brand-and-name-retirement.test.d.ts +2 -0
  47. package/dist/__tests__/asset-brand-and-name-retirement.test.d.ts.map +1 -0
  48. package/dist/__tests__/asset-error-exhaustive.test-d.d.ts +11 -0
  49. package/dist/__tests__/asset-error-exhaustive.test-d.d.ts.map +1 -0
  50. package/dist/__tests__/asset-registry-guid-reverse.test.d.ts +2 -0
  51. package/dist/__tests__/asset-registry-guid-reverse.test.d.ts.map +1 -0
  52. package/dist/__tests__/asset-registry-hdr-equirect.test.d.ts +2 -0
  53. package/dist/__tests__/asset-registry-hdr-equirect.test.d.ts.map +1 -0
  54. package/dist/__tests__/asset-registry-mesh-pack-payload.dawn.test.d.ts +2 -0
  55. package/dist/__tests__/asset-registry-mesh-pack-payload.dawn.test.d.ts.map +1 -0
  56. package/dist/__tests__/asset-registry-mounts.test.d.ts +2 -0
  57. package/dist/__tests__/asset-registry-mounts.test.d.ts.map +1 -0
  58. package/dist/__tests__/asset-registry-scene.test-d.d.ts +2 -0
  59. package/dist/__tests__/asset-registry-scene.test-d.d.ts.map +1 -0
  60. package/dist/__tests__/asset-registry.browser.test.d.ts +2 -0
  61. package/dist/__tests__/asset-registry.browser.test.d.ts.map +1 -0
  62. package/dist/__tests__/asset-registry.dawn.test.d.ts +2 -0
  63. package/dist/__tests__/asset-registry.dawn.test.d.ts.map +1 -0
  64. package/dist/__tests__/asset-registry.listCatalog.test.d.ts +2 -0
  65. package/dist/__tests__/asset-registry.listCatalog.test.d.ts.map +1 -0
  66. package/dist/__tests__/asset-registry.mesh-bin.test.d.ts +2 -0
  67. package/dist/__tests__/asset-registry.mesh-bin.test.d.ts.map +1 -0
  68. package/dist/__tests__/asset-registry.pack-cache.test.d.ts +2 -0
  69. package/dist/__tests__/asset-registry.pack-cache.test.d.ts.map +1 -0
  70. package/dist/__tests__/asset-registry.raw-container-failfast.test.d.ts +2 -0
  71. package/dist/__tests__/asset-registry.raw-container-failfast.test.d.ts.map +1 -0
  72. package/dist/__tests__/asset-registry.recursive.spec.d.ts +2 -0
  73. package/dist/__tests__/asset-registry.recursive.spec.d.ts.map +1 -0
  74. package/dist/__tests__/asset-registry.unit.test.d.ts +2 -0
  75. package/dist/__tests__/asset-registry.unit.test.d.ts.map +1 -0
  76. package/dist/__tests__/asset.unit.test.d.ts +2 -0
  77. package/dist/__tests__/asset.unit.test.d.ts.map +1 -0
  78. package/dist/__tests__/audio-load-by-guid.unit.test.d.ts +2 -0
  79. package/dist/__tests__/audio-load-by-guid.unit.test.d.ts.map +1 -0
  80. package/dist/__tests__/backend-selection.integration.test.d.ts +2 -0
  81. package/dist/__tests__/backend-selection.integration.test.d.ts.map +1 -0
  82. package/dist/__tests__/basis-catalog-dispatch.integration.test.d.ts +2 -0
  83. package/dist/__tests__/basis-catalog-dispatch.integration.test.d.ts.map +1 -0
  84. package/dist/__tests__/bind-group-cache-keying.test.d.ts +2 -0
  85. package/dist/__tests__/bind-group-cache-keying.test.d.ts.map +1 -0
  86. package/dist/__tests__/block-upload-math.unit.test.d.ts +2 -0
  87. package/dist/__tests__/block-upload-math.unit.test.d.ts.map +1 -0
  88. package/dist/__tests__/builtin-asset-registry.unit.test.d.ts +2 -0
  89. package/dist/__tests__/builtin-asset-registry.unit.test.d.ts.map +1 -0
  90. package/dist/__tests__/builtin-shader-register-e2e.test.d.ts +2 -0
  91. package/dist/__tests__/builtin-shader-register-e2e.test.d.ts.map +1 -0
  92. package/dist/__tests__/cache-false-eviction.test.d.ts +2 -0
  93. package/dist/__tests__/cache-false-eviction.test.d.ts.map +1 -0
  94. package/dist/__tests__/cache-isolation-matrix.test.d.ts +2 -0
  95. package/dist/__tests__/cache-isolation-matrix.test.d.ts.map +1 -0
  96. package/dist/__tests__/camera-clear-snapshot-equivalence.unit.test.d.ts +2 -0
  97. package/dist/__tests__/camera-clear-snapshot-equivalence.unit.test.d.ts.map +1 -0
  98. package/dist/__tests__/clamp-to-last.e2e.browser.test.d.ts +2 -0
  99. package/dist/__tests__/clamp-to-last.e2e.browser.test.d.ts.map +1 -0
  100. package/dist/__tests__/clamp-to-last.e2e.dawn.test.d.ts +2 -0
  101. package/dist/__tests__/clamp-to-last.e2e.dawn.test.d.ts.map +1 -0
  102. package/dist/__tests__/collect-scene-asset.test.d.ts +2 -0
  103. package/dist/__tests__/collect-scene-asset.test.d.ts.map +1 -0
  104. package/dist/__tests__/collect-scene-mount-collapse.test.d.ts +2 -0
  105. package/dist/__tests__/collect-scene-mount-collapse.test.d.ts.map +1 -0
  106. package/dist/__tests__/collect-subtree.unit.test.d.ts +2 -0
  107. package/dist/__tests__/collect-subtree.unit.test.d.ts.map +1 -0
  108. package/dist/__tests__/collect-transient-children-absent.test.d.ts +2 -0
  109. package/dist/__tests__/collect-transient-children-absent.test.d.ts.map +1 -0
  110. package/dist/__tests__/collect-transient-roundtrip.test.d.ts +2 -0
  111. package/dist/__tests__/collect-transient-roundtrip.test.d.ts.map +1 -0
  112. package/dist/__tests__/collect-transient-skip.test.d.ts +2 -0
  113. package/dist/__tests__/collect-transient-skip.test.d.ts.map +1 -0
  114. package/dist/__tests__/components.test-d.d.ts +2 -0
  115. package/dist/__tests__/components.test-d.d.ts.map +1 -0
  116. package/dist/__tests__/components.unit.test.d.ts +2 -0
  117. package/dist/__tests__/components.unit.test.d.ts.map +1 -0
  118. package/dist/__tests__/composite-skybox-cross-world.browser.test.d.ts +2 -0
  119. package/dist/__tests__/composite-skybox-cross-world.browser.test.d.ts.map +1 -0
  120. package/dist/__tests__/compressed-texture-physical-extent.integration.test.d.ts +2 -0
  121. package/dist/__tests__/compressed-texture-physical-extent.integration.test.d.ts.map +1 -0
  122. package/dist/__tests__/create-renderer-assembly.integration.test.d.ts +2 -0
  123. package/dist/__tests__/create-renderer-assembly.integration.test.d.ts.map +1 -0
  124. package/dist/__tests__/createRenderer-dispose.test.d.ts +2 -0
  125. package/dist/__tests__/createRenderer-dispose.test.d.ts.map +1 -0
  126. package/dist/__tests__/createRenderer-requiredFeatures.unit.test.d.ts +2 -0
  127. package/dist/__tests__/createRenderer-requiredFeatures.unit.test.d.ts.map +1 -0
  128. package/dist/__tests__/createRenderer.browser-no-webgpu.test.d.ts +2 -0
  129. package/dist/__tests__/createRenderer.browser-no-webgpu.test.d.ts.map +1 -0
  130. package/dist/__tests__/createRenderer.unit.test.d.ts +2 -0
  131. package/dist/__tests__/createRenderer.unit.test.d.ts.map +1 -0
  132. package/dist/__tests__/dawn/hello-sprite-nineslice-section.dawn.test.d.ts +2 -0
  133. package/dist/__tests__/dawn/hello-sprite-nineslice-section.dawn.test.d.ts.map +1 -0
  134. package/dist/__tests__/dawn/ibl-compose.dawn.test.d.ts +2 -0
  135. package/dist/__tests__/dawn/ibl-compose.dawn.test.d.ts.map +1 -0
  136. package/dist/__tests__/dawn/ibl-modules-compose.dawn.test.d.ts +2 -0
  137. package/dist/__tests__/dawn/ibl-modules-compose.dawn.test.d.ts.map +1 -0
  138. package/dist/__tests__/dawn/ibl-precompute-readback.dawn.test.d.ts +2 -0
  139. package/dist/__tests__/dawn/ibl-precompute-readback.dawn.test.d.ts.map +1 -0
  140. package/dist/__tests__/dawn/instances-per-instance-pbr.dawn.test.d.ts +2 -0
  141. package/dist/__tests__/dawn/instances-per-instance-pbr.dawn.test.d.ts.map +1 -0
  142. package/dist/__tests__/dawn/instances-shadow.dawn.test.d.ts +2 -0
  143. package/dist/__tests__/dawn/instances-shadow.dawn.test.d.ts.map +1 -0
  144. package/dist/__tests__/dawn/instances-uniform-fallback.dawn.test.d.ts +2 -0
  145. package/dist/__tests__/dawn/instances-uniform-fallback.dawn.test.d.ts.map +1 -0
  146. package/dist/__tests__/dawn/material-alpha.dawn.test.d.ts +2 -0
  147. package/dist/__tests__/dawn/material-alpha.dawn.test.d.ts.map +1 -0
  148. package/dist/__tests__/dawn/material-cooked-fixture.dawn.test.d.ts +2 -0
  149. package/dist/__tests__/dawn/material-cooked-fixture.dawn.test.d.ts.map +1 -0
  150. package/dist/__tests__/dawn/mesh-ssbo-normal-matrix.dawn.test.d.ts +2 -0
  151. package/dist/__tests__/dawn/mesh-ssbo-normal-matrix.dawn.test.d.ts.map +1 -0
  152. package/dist/__tests__/dawn/mesh-update-dawn.test.d.ts +2 -0
  153. package/dist/__tests__/dawn/mesh-update-dawn.test.d.ts.map +1 -0
  154. package/dist/__tests__/dawn/mipmap-srgb-linear.dawn.test.d.ts +2 -0
  155. package/dist/__tests__/dawn/mipmap-srgb-linear.dawn.test.d.ts.map +1 -0
  156. package/dist/__tests__/dawn/per-entity-material-texture-isolation.dawn.test.d.ts +2 -0
  157. package/dist/__tests__/dawn/per-entity-material-texture-isolation.dawn.test.d.ts.map +1 -0
  158. package/dist/__tests__/dawn/sprite-nineslice-mesh-bind.dawn.test.d.ts +2 -0
  159. package/dist/__tests__/dawn/sprite-nineslice-mesh-bind.dawn.test.d.ts.map +1 -0
  160. package/dist/__tests__/dawn/tonemap.dawn.test.d.ts +2 -0
  161. package/dist/__tests__/dawn/tonemap.dawn.test.d.ts.map +1 -0
  162. package/dist/__tests__/dawn/upload-texture.dawn.test.d.ts +2 -0
  163. package/dist/__tests__/dawn/upload-texture.dawn.test.d.ts.map +1 -0
  164. package/dist/__tests__/dawn/user-handle-mesh-render.dawn.test.d.ts +2 -0
  165. package/dist/__tests__/dawn/user-handle-mesh-render.dawn.test.d.ts.map +1 -0
  166. package/dist/__tests__/dawn/user-mesh-upload.dawn.test.d.ts +2 -0
  167. package/dist/__tests__/dawn/user-mesh-upload.dawn.test.d.ts.map +1 -0
  168. package/dist/__tests__/dawn/zero-manifest-mesh-fail.dawn.test.d.ts +2 -0
  169. package/dist/__tests__/dawn/zero-manifest-mesh-fail.dawn.test.d.ts.map +1 -0
  170. package/dist/__tests__/dawn/zero-manifest-ready.dawn.test.d.ts +2 -0
  171. package/dist/__tests__/dawn/zero-manifest-ready.dawn.test.d.ts.map +1 -0
  172. package/dist/__tests__/demo-sortscope-migration.unit.test.d.ts +2 -0
  173. package/dist/__tests__/demo-sortscope-migration.unit.test.d.ts.map +1 -0
  174. package/dist/__tests__/derive-bgl-integration.test.d.ts +2 -0
  175. package/dist/__tests__/derive-bgl-integration.test.d.ts.map +1 -0
  176. package/dist/__tests__/derive-render-data-skin.unit.test.d.ts +2 -0
  177. package/dist/__tests__/derive-render-data-skin.unit.test.d.ts.map +1 -0
  178. package/dist/__tests__/draw-published.d.ts +5 -0
  179. package/dist/__tests__/draw-published.d.ts.map +1 -0
  180. package/dist/__tests__/dynamic-texture-store.unit.test.d.ts +2 -0
  181. package/dist/__tests__/dynamic-texture-store.unit.test.d.ts.map +1 -0
  182. package/dist/__tests__/environment-error-exhaustive.test-d.d.ts +8 -0
  183. package/dist/__tests__/environment-error-exhaustive.test-d.d.ts.map +1 -0
  184. package/dist/__tests__/equirect-bc6h.integration.test.d.ts +2 -0
  185. package/dist/__tests__/equirect-bc6h.integration.test.d.ts.map +1 -0
  186. package/dist/__tests__/error-exhaustive.test.d.ts +2 -0
  187. package/dist/__tests__/error-exhaustive.test.d.ts.map +1 -0
  188. package/dist/__tests__/errors.unit.test.d.ts +2 -0
  189. package/dist/__tests__/errors.unit.test.d.ts.map +1 -0
  190. package/dist/__tests__/extract-frames-graceful-degradation.test.d.ts +2 -0
  191. package/dist/__tests__/extract-frames-graceful-degradation.test.d.ts.map +1 -0
  192. package/dist/__tests__/extract-frames-merge.test.d.ts +2 -0
  193. package/dist/__tests__/extract-frames-merge.test.d.ts.map +1 -0
  194. package/dist/__tests__/extract-frames-reset.test.d.ts +2 -0
  195. package/dist/__tests__/extract-frames-reset.test.d.ts.map +1 -0
  196. package/dist/__tests__/extract-record-no-hardcoded-texture-fields.test.d.ts +2 -0
  197. package/dist/__tests__/extract-record-no-hardcoded-texture-fields.test.d.ts.map +1 -0
  198. package/dist/__tests__/extract-skin-mismatch.unit.test.d.ts +2 -0
  199. package/dist/__tests__/extract-skin-mismatch.unit.test.d.ts.map +1 -0
  200. package/dist/__tests__/fetch-binary-decompress.test.d.ts +2 -0
  201. package/dist/__tests__/fetch-binary-decompress.test.d.ts.map +1 -0
  202. package/dist/__tests__/four-path-compression.integration.test.d.ts +2 -0
  203. package/dist/__tests__/four-path-compression.integration.test.d.ts.map +1 -0
  204. package/dist/__tests__/frame-targets-shadow-regression.unit.test.d.ts +2 -0
  205. package/dist/__tests__/frame-targets-shadow-regression.unit.test.d.ts.map +1 -0
  206. package/dist/__tests__/fullscreen-feature-host.dawn.test.d.ts +2 -0
  207. package/dist/__tests__/fullscreen-feature-host.dawn.test.d.ts.map +1 -0
  208. package/dist/__tests__/fullscreen-post-bgl-depth.test.d.ts +2 -0
  209. package/dist/__tests__/fullscreen-post-bgl-depth.test.d.ts.map +1 -0
  210. package/dist/__tests__/fullscreen-post-depth-params-arity.test.d.ts +2 -0
  211. package/dist/__tests__/fullscreen-post-depth-params-arity.test.d.ts.map +1 -0
  212. package/dist/__tests__/fullscreen-post-process-pass.dawn.test.d.ts +2 -0
  213. package/dist/__tests__/fullscreen-post-process-pass.dawn.test.d.ts.map +1 -0
  214. package/dist/__tests__/fullscreen-post-process-pass.unit.test.d.ts +2 -0
  215. package/dist/__tests__/fullscreen-post-process-pass.unit.test.d.ts.map +1 -0
  216. package/dist/__tests__/fxaa-pixel-diff.dawn.test.d.ts +2 -0
  217. package/dist/__tests__/fxaa-pixel-diff.dawn.test.d.ts.map +1 -0
  218. package/dist/__tests__/fxaa-validation-and-resize.dawn.test.d.ts +2 -0
  219. package/dist/__tests__/fxaa-validation-and-resize.dawn.test.d.ts.map +1 -0
  220. package/dist/__tests__/fxaa-zero-overhead.dawn.test.d.ts +2 -0
  221. package/dist/__tests__/fxaa-zero-overhead.dawn.test.d.ts.map +1 -0
  222. package/dist/__tests__/geometry.unit.test.d.ts +2 -0
  223. package/dist/__tests__/geometry.unit.test.d.ts.map +1 -0
  224. package/dist/__tests__/glyph-tilemap-worldid.test.d.ts +2 -0
  225. package/dist/__tests__/glyph-tilemap-worldid.test.d.ts.map +1 -0
  226. package/dist/__tests__/gpu-resource-cow-survivor.test.d.ts +2 -0
  227. package/dist/__tests__/gpu-resource-cow-survivor.test.d.ts.map +1 -0
  228. package/dist/__tests__/guid-for-asset.unit.test.d.ts +2 -0
  229. package/dist/__tests__/guid-for-asset.unit.test.d.ts.map +1 -0
  230. package/dist/__tests__/handle-migration.test.d.ts +2 -0
  231. package/dist/__tests__/handle-migration.test.d.ts.map +1 -0
  232. package/dist/__tests__/hello-tilemap-smoke-upgrade.unit.test.d.ts +2 -0
  233. package/dist/__tests__/hello-tilemap-smoke-upgrade.unit.test.d.ts.map +1 -0
  234. package/dist/__tests__/helpers/mock-shader-registry.d.ts +11 -0
  235. package/dist/__tests__/helpers/mock-shader-registry.d.ts.map +1 -0
  236. package/dist/__tests__/helpers/register-runtime-components.d.ts +4 -0
  237. package/dist/__tests__/helpers/register-runtime-components.d.ts.map +1 -0
  238. package/dist/__tests__/helpers/register-scene-components.d.ts +4 -0
  239. package/dist/__tests__/helpers/register-scene-components.d.ts.map +1 -0
  240. package/dist/__tests__/helpers/tilemap-assets.d.ts +6 -0
  241. package/dist/__tests__/helpers/tilemap-assets.d.ts.map +1 -0
  242. package/dist/__tests__/host-custom-kind-contract.test-d.d.ts +2 -0
  243. package/dist/__tests__/host-custom-kind-contract.test-d.d.ts.map +1 -0
  244. package/dist/__tests__/host-custom-kind-contract.test.d.ts +2 -0
  245. package/dist/__tests__/host-custom-kind-contract.test.d.ts.map +1 -0
  246. package/dist/__tests__/inspect-name.test.d.ts +2 -0
  247. package/dist/__tests__/inspect-name.test.d.ts.map +1 -0
  248. package/dist/__tests__/instances.test-d.d.ts +2 -0
  249. package/dist/__tests__/instances.test-d.d.ts.map +1 -0
  250. package/dist/__tests__/instantiate-mount-deferred-wiring.test.d.ts +2 -0
  251. package/dist/__tests__/instantiate-mount-deferred-wiring.test.d.ts.map +1 -0
  252. package/dist/__tests__/light-attenuation-formula.test.d.ts +2 -0
  253. package/dist/__tests__/light-attenuation-formula.test.d.ts.map +1 -0
  254. package/dist/__tests__/light-casters-9-light.browser.test.d.ts +2 -0
  255. package/dist/__tests__/light-casters-9-light.browser.test.d.ts.map +1 -0
  256. package/dist/__tests__/light-gpu-byte-neutral.unit.test.d.ts +2 -0
  257. package/dist/__tests__/light-gpu-byte-neutral.unit.test.d.ts.map +1 -0
  258. package/dist/__tests__/light-snapshot.test-d.d.ts +2 -0
  259. package/dist/__tests__/light-snapshot.test-d.d.ts.map +1 -0
  260. package/dist/__tests__/lights-vec-negative.unit.test.d.ts +2 -0
  261. package/dist/__tests__/lights-vec-negative.unit.test.d.ts.map +1 -0
  262. package/dist/__tests__/lights.unit.test.d.ts +2 -0
  263. package/dist/__tests__/lights.unit.test.d.ts.map +1 -0
  264. package/dist/__tests__/loadbyguid-payload.unit.test.d.ts +2 -0
  265. package/dist/__tests__/loadbyguid-payload.unit.test.d.ts.map +1 -0
  266. package/dist/__tests__/loadbyguid-texture-intern.unit.test.d.ts +2 -0
  267. package/dist/__tests__/loadbyguid-texture-intern.unit.test.d.ts.map +1 -0
  268. package/dist/__tests__/loader-extract-graceful.test.d.ts +2 -0
  269. package/dist/__tests__/loader-extract-graceful.test.d.ts.map +1 -0
  270. package/dist/__tests__/loader-name-survival.test.d.ts +2 -0
  271. package/dist/__tests__/loader-name-survival.test.d.ts.map +1 -0
  272. package/dist/__tests__/m3-vec-negative.unit.test.d.ts +2 -0
  273. package/dist/__tests__/m3-vec-negative.unit.test.d.ts.map +1 -0
  274. package/dist/__tests__/m5-browser-dawn-sweep.integration.test.d.ts +2 -0
  275. package/dist/__tests__/m5-browser-dawn-sweep.integration.test.d.ts.map +1 -0
  276. package/dist/__tests__/material-texture-uv-scale.unit.test.d.ts +2 -0
  277. package/dist/__tests__/material-texture-uv-scale.unit.test.d.ts.map +1 -0
  278. package/dist/__tests__/materials.unit.test.d.ts +2 -0
  279. package/dist/__tests__/materials.unit.test.d.ts.map +1 -0
  280. package/dist/__tests__/mesh-bin-contract.test.d.ts +2 -0
  281. package/dist/__tests__/mesh-bin-contract.test.d.ts.map +1 -0
  282. package/dist/__tests__/mesh-bin-skin-roundtrip.unit.test.d.ts +2 -0
  283. package/dist/__tests__/mesh-bin-skin-roundtrip.unit.test.d.ts.map +1 -0
  284. package/dist/__tests__/mesh-bin.test.d.ts +2 -0
  285. package/dist/__tests__/mesh-bin.test.d.ts.map +1 -0
  286. package/dist/__tests__/mesh-loader-skin-contract.unit.test.d.ts +2 -0
  287. package/dist/__tests__/mesh-loader-skin-contract.unit.test.d.ts.map +1 -0
  288. package/dist/__tests__/mesh-ssbo-grow.unit.test.d.ts +2 -0
  289. package/dist/__tests__/mesh-ssbo-grow.unit.test.d.ts.map +1 -0
  290. package/dist/__tests__/mip-gate.unit.test.d.ts +2 -0
  291. package/dist/__tests__/mip-gate.unit.test.d.ts.map +1 -0
  292. package/dist/__tests__/msaa-sprite-pixel-diff.dawn.test.d.ts +2 -0
  293. package/dist/__tests__/msaa-sprite-pixel-diff.dawn.test.d.ts.map +1 -0
  294. package/dist/__tests__/multi-uv-8set.e2e.dawn.test.d.ts +2 -0
  295. package/dist/__tests__/multi-uv-8set.e2e.dawn.test.d.ts.map +1 -0
  296. package/dist/__tests__/name.unit.test.d.ts +2 -0
  297. package/dist/__tests__/name.unit.test.d.ts.map +1 -0
  298. package/dist/__tests__/new-kind-refs-contract.test.d.ts +2 -0
  299. package/dist/__tests__/new-kind-refs-contract.test.d.ts.map +1 -0
  300. package/dist/__tests__/package-promotion.test.d.ts +2 -0
  301. package/dist/__tests__/package-promotion.test.d.ts.map +1 -0
  302. package/dist/__tests__/package-rename.test.d.ts +2 -0
  303. package/dist/__tests__/package-rename.test.d.ts.map +1 -0
  304. package/dist/__tests__/pass-selector.unit.test.d.ts +2 -0
  305. package/dist/__tests__/pass-selector.unit.test.d.ts.map +1 -0
  306. package/dist/__tests__/pbr-pipeline.unit.test.d.ts +2 -0
  307. package/dist/__tests__/pbr-pipeline.unit.test.d.ts.map +1 -0
  308. package/dist/__tests__/pipeline-builder-msaa.unit.test.d.ts +2 -0
  309. package/dist/__tests__/pipeline-builder-msaa.unit.test.d.ts.map +1 -0
  310. package/dist/__tests__/pipeline-cache-keying.unit.test.d.ts +2 -0
  311. package/dist/__tests__/pipeline-cache-keying.unit.test.d.ts.map +1 -0
  312. package/dist/__tests__/pipeline-spec.test.d.ts +2 -0
  313. package/dist/__tests__/pipeline-spec.test.d.ts.map +1 -0
  314. package/dist/__tests__/pipeline.unit.test.d.ts +2 -0
  315. package/dist/__tests__/pipeline.unit.test.d.ts.map +1 -0
  316. package/dist/__tests__/point-light-shadow.browser.test.d.ts +2 -0
  317. package/dist/__tests__/point-light-shadow.browser.test.d.ts.map +1 -0
  318. package/dist/__tests__/point-light-shadow.dawn.test.d.ts +2 -0
  319. package/dist/__tests__/point-light-shadow.dawn.test.d.ts.map +1 -0
  320. package/dist/__tests__/point-light-shadow.unit.test.d.ts +2 -0
  321. package/dist/__tests__/point-light-shadow.unit.test.d.ts.map +1 -0
  322. package/dist/__tests__/post-process-error-codes.unit.test.d.ts +2 -0
  323. package/dist/__tests__/post-process-error-codes.unit.test.d.ts.map +1 -0
  324. package/dist/__tests__/post-process-params-component.unit.test.d.ts +2 -0
  325. package/dist/__tests__/post-process-params-component.unit.test.d.ts.map +1 -0
  326. package/dist/__tests__/post-process-params-extract.unit.test.d.ts +2 -0
  327. package/dist/__tests__/post-process-params-extract.unit.test.d.ts.map +1 -0
  328. package/dist/__tests__/postprocess-camera-consistency.test.d.ts +2 -0
  329. package/dist/__tests__/postprocess-camera-consistency.test.d.ts.map +1 -0
  330. package/dist/__tests__/profiler-assembly.integration.test.d.ts +2 -0
  331. package/dist/__tests__/profiler-assembly.integration.test.d.ts.map +1 -0
  332. package/dist/__tests__/propagate-transforms.dawn.test.d.ts +2 -0
  333. package/dist/__tests__/propagate-transforms.dawn.test.d.ts.map +1 -0
  334. package/dist/__tests__/record-worldid-isolation.test.d.ts +2 -0
  335. package/dist/__tests__/record-worldid-isolation.test.d.ts.map +1 -0
  336. package/dist/__tests__/recover-error-exhaustive.test-d.d.ts +11 -0
  337. package/dist/__tests__/recover-error-exhaustive.test-d.d.ts.map +1 -0
  338. package/dist/__tests__/render-error-exhaustive.test-d.d.ts +11 -0
  339. package/dist/__tests__/render-error-exhaustive.test-d.d.ts.map +1 -0
  340. package/dist/__tests__/render-feature-assembly.test-d.d.ts +2 -0
  341. package/dist/__tests__/render-feature-assembly.test-d.d.ts.map +1 -0
  342. package/dist/__tests__/render-feature-prepared-graphics-docs.unit.test.d.ts +2 -0
  343. package/dist/__tests__/render-feature-prepared-graphics-docs.unit.test.d.ts.map +1 -0
  344. package/dist/__tests__/render-feature-prepared-graphics.browser.test.d.ts +2 -0
  345. package/dist/__tests__/render-feature-prepared-graphics.browser.test.d.ts.map +1 -0
  346. package/dist/__tests__/render-feature-prepared-graphics.dawn.test.d.ts +2 -0
  347. package/dist/__tests__/render-feature-prepared-graphics.dawn.test.d.ts.map +1 -0
  348. package/dist/__tests__/render-feature-prepared-graphics.fixture.d.ts +10 -0
  349. package/dist/__tests__/render-feature-prepared-graphics.fixture.d.ts.map +1 -0
  350. package/dist/__tests__/render-feature-prepared-graphics.integration.test.d.ts +2 -0
  351. package/dist/__tests__/render-feature-prepared-graphics.integration.test.d.ts.map +1 -0
  352. package/dist/__tests__/render-feature-prepared-graphics.regression.integration.test.d.ts +2 -0
  353. package/dist/__tests__/render-feature-prepared-graphics.regression.integration.test.d.ts.map +1 -0
  354. package/dist/__tests__/render-feature-recover.integration.test.d.ts +2 -0
  355. package/dist/__tests__/render-feature-recover.integration.test.d.ts.map +1 -0
  356. package/dist/__tests__/render-feature-runtime-surface.test-d.d.ts +2 -0
  357. package/dist/__tests__/render-feature-runtime-surface.test-d.d.ts.map +1 -0
  358. package/dist/__tests__/render-schema-consumer.integration.test.d.ts +2 -0
  359. package/dist/__tests__/render-schema-consumer.integration.test.d.ts.map +1 -0
  360. package/dist/__tests__/render-schema-owner-boundary.unit.test.d.ts +2 -0
  361. package/dist/__tests__/render-schema-owner-boundary.unit.test.d.ts.map +1 -0
  362. package/dist/__tests__/render-system-cross-world-overlay-queue.test.d.ts +2 -0
  363. package/dist/__tests__/render-system-cross-world-overlay-queue.test.d.ts.map +1 -0
  364. package/dist/__tests__/render-system-extract-dispatch-ordering.test.d.ts +2 -0
  365. package/dist/__tests__/render-system-extract-dispatch-ordering.test.d.ts.map +1 -0
  366. package/dist/__tests__/render-system-extract.test.d.ts +2 -0
  367. package/dist/__tests__/render-system-extract.test.d.ts.map +1 -0
  368. package/dist/__tests__/render-system-extract.unit.test.d.ts +2 -0
  369. package/dist/__tests__/render-system-extract.unit.test.d.ts.map +1 -0
  370. package/dist/__tests__/render-system-fold-bucket.unit.test.d.ts +2 -0
  371. package/dist/__tests__/render-system-fold-bucket.unit.test.d.ts.map +1 -0
  372. package/dist/__tests__/render-system-fold-fallback.unit.test.d.ts +2 -0
  373. package/dist/__tests__/render-system-fold-fallback.unit.test.d.ts.map +1 -0
  374. package/dist/__tests__/render-system-fold-material.unit.test.d.ts +2 -0
  375. package/dist/__tests__/render-system-fold-material.unit.test.d.ts.map +1 -0
  376. package/dist/__tests__/render-system-fold-metrics.unit.test.d.ts +2 -0
  377. package/dist/__tests__/render-system-fold-metrics.unit.test.d.ts.map +1 -0
  378. package/dist/__tests__/render-system-fold-modegate.unit.test.d.ts +2 -0
  379. package/dist/__tests__/render-system-fold-modegate.unit.test.d.ts.map +1 -0
  380. package/dist/__tests__/render-system-fold-scene-instances.unit.test.d.ts +2 -0
  381. package/dist/__tests__/render-system-fold-scene-instances.unit.test.d.ts.map +1 -0
  382. package/dist/__tests__/render-system-fold.dawn.test.d.ts +2 -0
  383. package/dist/__tests__/render-system-fold.dawn.test.d.ts.map +1 -0
  384. package/dist/__tests__/render-system-mega.test.d.ts +2 -0
  385. package/dist/__tests__/render-system-mega.test.d.ts.map +1 -0
  386. package/dist/__tests__/render-system-owner-split-contract.test.d.ts +2 -0
  387. package/dist/__tests__/render-system-owner-split-contract.test.d.ts.map +1 -0
  388. package/dist/__tests__/render-system-record-multi-material-textureview.test.d.ts +2 -0
  389. package/dist/__tests__/render-system-record-multi-material-textureview.test.d.ts.map +1 -0
  390. package/dist/__tests__/render-system-record-per-submesh-transparency.test.d.ts +2 -0
  391. package/dist/__tests__/render-system-record-per-submesh-transparency.test.d.ts.map +1 -0
  392. package/dist/__tests__/render-system-record.test.d.ts +2 -0
  393. package/dist/__tests__/render-system-record.test.d.ts.map +1 -0
  394. package/dist/__tests__/render-system-record.unit.test.d.ts +2 -0
  395. package/dist/__tests__/render-system-record.unit.test.d.ts.map +1 -0
  396. package/dist/__tests__/render-system-skin-bg.unit.test.d.ts +2 -0
  397. package/dist/__tests__/render-system-skin-bg.unit.test.d.ts.map +1 -0
  398. package/dist/__tests__/renderer-consumer.test-d.d.ts +2 -0
  399. package/dist/__tests__/renderer-consumer.test-d.d.ts.map +1 -0
  400. package/dist/__tests__/renderer-health.unit.test.d.ts +2 -0
  401. package/dist/__tests__/renderer-health.unit.test.d.ts.map +1 -0
  402. package/dist/__tests__/renderer-lifecycle.integration.test.d.ts +2 -0
  403. package/dist/__tests__/renderer-lifecycle.integration.test.d.ts.map +1 -0
  404. package/dist/__tests__/renderer-recover.unit.test.d.ts +2 -0
  405. package/dist/__tests__/renderer-recover.unit.test.d.ts.map +1 -0
  406. package/dist/__tests__/renderer-result.test-d.d.ts +2 -0
  407. package/dist/__tests__/renderer-result.test-d.d.ts.map +1 -0
  408. package/dist/__tests__/renderer-surface.unit.test.d.ts +2 -0
  409. package/dist/__tests__/renderer-surface.unit.test.d.ts.map +1 -0
  410. package/dist/__tests__/renderer-test-utils.d.ts +10 -0
  411. package/dist/__tests__/renderer-test-utils.d.ts.map +1 -0
  412. package/dist/__tests__/renderer.test-d.d.ts +2 -0
  413. package/dist/__tests__/renderer.test-d.d.ts.map +1 -0
  414. package/dist/__tests__/resolve-asset-handle-err.test.d.ts +2 -0
  415. package/dist/__tests__/resolve-asset-handle-err.test.d.ts.map +1 -0
  416. package/dist/__tests__/resolve-asset-handle.unit.test.d.ts +2 -0
  417. package/dist/__tests__/resolve-asset-handle.unit.test.d.ts.map +1 -0
  418. package/dist/__tests__/resolve-depth-only-view.test.d.ts +2 -0
  419. package/dist/__tests__/resolve-depth-only-view.test.d.ts.map +1 -0
  420. package/dist/__tests__/resolveName-fallback.test.d.ts +2 -0
  421. package/dist/__tests__/resolveName-fallback.test.d.ts.map +1 -0
  422. package/dist/__tests__/resolveName-null-package.test.d.ts +2 -0
  423. package/dist/__tests__/resolveName-null-package.test.d.ts.map +1 -0
  424. package/dist/__tests__/resolveName-unified-entry.test.d.ts +2 -0
  425. package/dist/__tests__/resolveName-unified-entry.test.d.ts.map +1 -0
  426. package/dist/__tests__/resolveName.test.d.ts +2 -0
  427. package/dist/__tests__/resolveName.test.d.ts.map +1 -0
  428. package/dist/__tests__/rhi-null-command-flow.unit.test.d.ts +2 -0
  429. package/dist/__tests__/rhi-null-command-flow.unit.test.d.ts.map +1 -0
  430. package/dist/__tests__/rhi-null-noop-behavior.unit.test.d.ts +2 -0
  431. package/dist/__tests__/rhi-null-noop-behavior.unit.test.d.ts.map +1 -0
  432. package/dist/__tests__/rhi-null-renderer-lifecycle.unit.test.d.ts +2 -0
  433. package/dist/__tests__/rhi-null-renderer-lifecycle.unit.test.d.ts.map +1 -0
  434. package/dist/__tests__/roots-to-scene-asset.test.d.ts +2 -0
  435. package/dist/__tests__/roots-to-scene-asset.test.d.ts.map +1 -0
  436. package/dist/__tests__/roundtrip-primitive.test.d.ts +2 -0
  437. package/dist/__tests__/roundtrip-primitive.test.d.ts.map +1 -0
  438. package/dist/__tests__/runtime-authority-scan.unit.test.d.ts +2 -0
  439. package/dist/__tests__/runtime-authority-scan.unit.test.d.ts.map +1 -0
  440. package/dist/__tests__/runtime-domain-residue.unit.test.d.ts +2 -0
  441. package/dist/__tests__/runtime-domain-residue.unit.test.d.ts.map +1 -0
  442. package/dist/__tests__/scene-bare-guid-refs-instantiate.test.d.ts +2 -0
  443. package/dist/__tests__/scene-bare-guid-refs-instantiate.test.d.ts.map +1 -0
  444. package/dist/__tests__/scene-equirect-roundtrip.test.d.ts +2 -0
  445. package/dist/__tests__/scene-equirect-roundtrip.test.d.ts.map +1 -0
  446. package/dist/__tests__/scene-instance-authoring.integration.test.d.ts +2 -0
  447. package/dist/__tests__/scene-instance-authoring.integration.test.d.ts.map +1 -0
  448. package/dist/__tests__/scene-instance-bridge.integration.test.d.ts +2 -0
  449. package/dist/__tests__/scene-instance-bridge.integration.test.d.ts.map +1 -0
  450. package/dist/__tests__/scene-instance-component.test.d.ts +2 -0
  451. package/dist/__tests__/scene-instance-component.test.d.ts.map +1 -0
  452. package/dist/__tests__/scene-instantiate-cycle-runtime.test.d.ts +2 -0
  453. package/dist/__tests__/scene-instantiate-cycle-runtime.test.d.ts.map +1 -0
  454. package/dist/__tests__/scene-mount-fixed-point.test.d.ts +2 -0
  455. package/dist/__tests__/scene-mount-fixed-point.test.d.ts.map +1 -0
  456. package/dist/__tests__/scene-mount-roundtrip.test.d.ts +2 -0
  457. package/dist/__tests__/scene-mount-roundtrip.test.d.ts.map +1 -0
  458. package/dist/__tests__/scene-mount-wrapper-roundtrip.test.d.ts +2 -0
  459. package/dist/__tests__/scene-mount-wrapper-roundtrip.test.d.ts.map +1 -0
  460. package/dist/__tests__/scene-sugar-instantiate.test.d.ts +2 -0
  461. package/dist/__tests__/scene-sugar-instantiate.test.d.ts.map +1 -0
  462. package/dist/__tests__/serialize-mounts-roundtrip.test.d.ts +2 -0
  463. package/dist/__tests__/serialize-mounts-roundtrip.test.d.ts.map +1 -0
  464. package/dist/__tests__/serialize-scene-asset.test.d.ts +2 -0
  465. package/dist/__tests__/serialize-scene-asset.test.d.ts.map +1 -0
  466. package/dist/__tests__/shadow-caster-empty-schema.test.d.ts +2 -0
  467. package/dist/__tests__/shadow-caster-empty-schema.test.d.ts.map +1 -0
  468. package/dist/__tests__/shadow-csm-atlas.test.d.ts +2 -0
  469. package/dist/__tests__/shadow-csm-atlas.test.d.ts.map +1 -0
  470. package/dist/__tests__/shadow-csm-blend.dawn.test.d.ts +2 -0
  471. package/dist/__tests__/shadow-csm-blend.dawn.test.d.ts.map +1 -0
  472. package/dist/__tests__/shadow-csm-cascade-loadop.test.d.ts +2 -0
  473. package/dist/__tests__/shadow-csm-cascade-loadop.test.d.ts.map +1 -0
  474. package/dist/__tests__/shadow-csm-component.test.d.ts +2 -0
  475. package/dist/__tests__/shadow-csm-component.test.d.ts.map +1 -0
  476. package/dist/__tests__/shadow-csm-extract.test.d.ts +2 -0
  477. package/dist/__tests__/shadow-csm-extract.test.d.ts.map +1 -0
  478. package/dist/__tests__/shadow-csm-orthoz.test.d.ts +2 -0
  479. package/dist/__tests__/shadow-csm-orthoz.test.d.ts.map +1 -0
  480. package/dist/__tests__/shadow-csm-runtime-vary.dawn.test.d.ts +2 -0
  481. package/dist/__tests__/shadow-csm-runtime-vary.dawn.test.d.ts.map +1 -0
  482. package/dist/__tests__/shadow-csm-shader.dawn.test.d.ts +2 -0
  483. package/dist/__tests__/shadow-csm-shader.dawn.test.d.ts.map +1 -0
  484. package/dist/__tests__/shadow-csm-tile-consistency.test.d.ts +2 -0
  485. package/dist/__tests__/shadow-csm-tile-consistency.test.d.ts.map +1 -0
  486. package/dist/__tests__/shadow-csm-ubo.dawn.test.d.ts +2 -0
  487. package/dist/__tests__/shadow-csm-ubo.dawn.test.d.ts.map +1 -0
  488. package/dist/__tests__/shadow-csm-ubo.test.d.ts +2 -0
  489. package/dist/__tests__/shadow-csm-ubo.test.d.ts.map +1 -0
  490. package/dist/__tests__/shadow-csm-viewport.browser.test.d.ts +2 -0
  491. package/dist/__tests__/shadow-csm-viewport.browser.test.d.ts.map +1 -0
  492. package/dist/__tests__/shadow-fields-observable.dawn.test.d.ts +2 -0
  493. package/dist/__tests__/shadow-fields-observable.dawn.test.d.ts.map +1 -0
  494. package/dist/__tests__/shadow-m1.dawn.test.d.ts +2 -0
  495. package/dist/__tests__/shadow-m1.dawn.test.d.ts.map +1 -0
  496. package/dist/__tests__/shadow-mesh-ssbo-order.test.d.ts +2 -0
  497. package/dist/__tests__/shadow-mesh-ssbo-order.test.d.ts.map +1 -0
  498. package/dist/__tests__/skin-error-exhaustive.test-d.d.ts +11 -0
  499. package/dist/__tests__/skin-error-exhaustive.test-d.d.ts.map +1 -0
  500. package/dist/__tests__/skin-errors.unit.test.d.ts +2 -0
  501. package/dist/__tests__/skin-errors.unit.test.d.ts.map +1 -0
  502. package/dist/__tests__/skin-extract-getarrayview-count.unit.test.d.ts +2 -0
  503. package/dist/__tests__/skin-extract-getarrayview-count.unit.test.d.ts.map +1 -0
  504. package/dist/__tests__/skin-parented-double-transform.test.d.ts +14 -0
  505. package/dist/__tests__/skin-parented-double-transform.test.d.ts.map +1 -0
  506. package/dist/__tests__/skylight-lazy-projection.test.d.ts +2 -0
  507. package/dist/__tests__/skylight-lazy-projection.test.d.ts.map +1 -0
  508. package/dist/__tests__/sparse-tag-scene-roundtrip.test.d.ts +2 -0
  509. package/dist/__tests__/sparse-tag-scene-roundtrip.test.d.ts.map +1 -0
  510. package/dist/__tests__/sprite-cullmode-flip.dawn.test.d.ts +2 -0
  511. package/dist/__tests__/sprite-cullmode-flip.dawn.test.d.ts.map +1 -0
  512. package/dist/__tests__/sprite-lit-bgl-byte-identical.test.d.ts +2 -0
  513. package/dist/__tests__/sprite-lit-bgl-byte-identical.test.d.ts.map +1 -0
  514. package/dist/__tests__/sprite-lit-extract-branch.test.d.ts +2 -0
  515. package/dist/__tests__/sprite-lit-extract-branch.test.d.ts.map +1 -0
  516. package/dist/__tests__/sprite-lit-pipeline-cache-key.test.d.ts +2 -0
  517. package/dist/__tests__/sprite-lit-pipeline-cache-key.test.d.ts.map +1 -0
  518. package/dist/__tests__/sprite-param-values.test-d.d.ts +2 -0
  519. package/dist/__tests__/sprite-param-values.test-d.d.ts.map +1 -0
  520. package/dist/__tests__/ssao-bgl.test.d.ts +2 -0
  521. package/dist/__tests__/ssao-bgl.test.d.ts.map +1 -0
  522. package/dist/__tests__/ssao-buffers.test.d.ts +2 -0
  523. package/dist/__tests__/ssao-buffers.test.d.ts.map +1 -0
  524. package/dist/__tests__/ssao-data.test.d.ts +2 -0
  525. package/dist/__tests__/ssao-data.test.d.ts.map +1 -0
  526. package/dist/__tests__/ssao-errors.test.d.ts +2 -0
  527. package/dist/__tests__/ssao-errors.test.d.ts.map +1 -0
  528. package/dist/__tests__/ssao-manifest.test.d.ts +2 -0
  529. package/dist/__tests__/ssao-manifest.test.d.ts.map +1 -0
  530. package/dist/__tests__/ssao-passes.test.d.ts +2 -0
  531. package/dist/__tests__/ssao-passes.test.d.ts.map +1 -0
  532. package/dist/__tests__/ssao-pipelines.test.d.ts +2 -0
  533. package/dist/__tests__/ssao-pipelines.test.d.ts.map +1 -0
  534. package/dist/__tests__/ssao-uniform-layout.test.d.ts +2 -0
  535. package/dist/__tests__/ssao-uniform-layout.test.d.ts.map +1 -0
  536. package/dist/__tests__/static-rig.integration.test.d.ts +2 -0
  537. package/dist/__tests__/static-rig.integration.test.d.ts.map +1 -0
  538. package/dist/__tests__/stencil-outline-pixel.dawn.test.d.ts +2 -0
  539. package/dist/__tests__/stencil-outline-pixel.dawn.test.d.ts.map +1 -0
  540. package/dist/__tests__/swap-chain-format.unit.test.d.ts +2 -0
  541. package/dist/__tests__/swap-chain-format.unit.test.d.ts.map +1 -0
  542. package/dist/__tests__/systems.unit.test.d.ts +2 -0
  543. package/dist/__tests__/systems.unit.test.d.ts.map +1 -0
  544. package/dist/__tests__/template-game-default-api-contract.test.d.ts +2 -0
  545. package/dist/__tests__/template-game-default-api-contract.test.d.ts.map +1 -0
  546. package/dist/__tests__/text.unit.test.d.ts +2 -0
  547. package/dist/__tests__/text.unit.test.d.ts.map +1 -0
  548. package/dist/__tests__/tilemap-childed-despawn.test.d.ts +2 -0
  549. package/dist/__tests__/tilemap-childed-despawn.test.d.ts.map +1 -0
  550. package/dist/__tests__/tilemap-chunk-extract.overhang-bounds.test.d.ts +2 -0
  551. package/dist/__tests__/tilemap-chunk-extract.overhang-bounds.test.d.ts.map +1 -0
  552. package/dist/__tests__/tilemap-chunk-extract.propagate-order.test.d.ts +2 -0
  553. package/dist/__tests__/tilemap-chunk-extract.propagate-order.test.d.ts.map +1 -0
  554. package/dist/__tests__/tilemap-chunk-extract.terrain-rewrite.unit.test.d.ts +2 -0
  555. package/dist/__tests__/tilemap-chunk-extract.terrain-rewrite.unit.test.d.ts.map +1 -0
  556. package/dist/__tests__/tilemap-chunk-extract.test.d.ts +2 -0
  557. package/dist/__tests__/tilemap-chunk-extract.test.d.ts.map +1 -0
  558. package/dist/__tests__/tilemap-chunk-extract.unit.test.d.ts +2 -0
  559. package/dist/__tests__/tilemap-chunk-extract.unit.test.d.ts.map +1 -0
  560. package/dist/__tests__/tilemap-chunk-y-sort-bench.unit.test.d.ts +2 -0
  561. package/dist/__tests__/tilemap-chunk-y-sort-bench.unit.test.d.ts.map +1 -0
  562. package/dist/__tests__/tilemap-dirty-rebuild.test.d.ts +2 -0
  563. package/dist/__tests__/tilemap-dirty-rebuild.test.d.ts.map +1 -0
  564. package/dist/__tests__/tilemap-material-cache.test.d.ts +2 -0
  565. package/dist/__tests__/tilemap-material-cache.test.d.ts.map +1 -0
  566. package/dist/__tests__/tilemap-multi-atlas.test.d.ts +2 -0
  567. package/dist/__tests__/tilemap-multi-atlas.test.d.ts.map +1 -0
  568. package/dist/__tests__/tilemap-owner-boundary.unit.test.d.ts +2 -0
  569. package/dist/__tests__/tilemap-owner-boundary.unit.test.d.ts.map +1 -0
  570. package/dist/__tests__/tilemap-region-pack.test.d.ts +2 -0
  571. package/dist/__tests__/tilemap-region-pack.test.d.ts.map +1 -0
  572. package/dist/__tests__/tilemap-spawn-flip-pivot.test.d.ts +2 -0
  573. package/dist/__tests__/tilemap-spawn-flip-pivot.test.d.ts.map +1 -0
  574. package/dist/__tests__/tilemap-uv-inset.test.d.ts +2 -0
  575. package/dist/__tests__/tilemap-uv-inset.test.d.ts.map +1 -0
  576. package/dist/__tests__/tilemap-validate.test.d.ts +2 -0
  577. package/dist/__tests__/tilemap-validate.test.d.ts.map +1 -0
  578. package/dist/__tests__/tileset-asset-validate.test.d.ts +2 -0
  579. package/dist/__tests__/tileset-asset-validate.test.d.ts.map +1 -0
  580. package/dist/__tests__/transcode-fallback.unit.test.d.ts +2 -0
  581. package/dist/__tests__/transcode-fallback.unit.test.d.ts.map +1 -0
  582. package/dist/__tests__/transform-hierarchy-pixel-diff.dawn.test.d.ts +2 -0
  583. package/dist/__tests__/transform-hierarchy-pixel-diff.dawn.test.d.ts.map +1 -0
  584. package/dist/__tests__/transient-fields-collect.test.d.ts +2 -0
  585. package/dist/__tests__/transient-fields-collect.test.d.ts.map +1 -0
  586. package/dist/__tests__/transient-runtime-behavior.test.d.ts +2 -0
  587. package/dist/__tests__/transient-runtime-behavior.test.d.ts.map +1 -0
  588. package/dist/__tests__/transitional-authoring.integration.test.d.ts +2 -0
  589. package/dist/__tests__/transitional-authoring.integration.test.d.ts.map +1 -0
  590. package/dist/__tests__/transparent-sort-tilemap-sprite-interleave.test.d.ts +2 -0
  591. package/dist/__tests__/transparent-sort-tilemap-sprite-interleave.test.d.ts.map +1 -0
  592. package/dist/__tests__/variant-selection.unit.test.d.ts +2 -0
  593. package/dist/__tests__/variant-selection.unit.test.d.ts.map +1 -0
  594. package/dist/__tests__/vertex-attribute-layout.test.d.ts +2 -0
  595. package/dist/__tests__/vertex-attribute-layout.test.d.ts.map +1 -0
  596. package/dist/__tests__/video-extract-bindgroup.dawn.test.d.ts +2 -0
  597. package/dist/__tests__/video-extract-bindgroup.dawn.test.d.ts.map +1 -0
  598. package/dist/__tests__/video-loader-consumption.test.d.ts +2 -0
  599. package/dist/__tests__/video-loader-consumption.test.d.ts.map +1 -0
  600. package/dist/__tests__/video-loader-smoke.dawn.test.d.ts +2 -0
  601. package/dist/__tests__/video-loader-smoke.dawn.test.d.ts.map +1 -0
  602. package/dist/__tests__/view-ubo-layout.browser.test.d.ts +2 -0
  603. package/dist/__tests__/view-ubo-layout.browser.test.d.ts.map +1 -0
  604. package/dist/__tests__/world-entity-key.test.d.ts +2 -0
  605. package/dist/__tests__/world-entity-key.test.d.ts.map +1 -0
  606. package/dist/__tests__/world-scene-despawn.test.d.ts +2 -0
  607. package/dist/__tests__/world-scene-despawn.test.d.ts.map +1 -0
  608. package/dist/__tests__/world-scene-instantiate.test.d.ts +2 -0
  609. package/dist/__tests__/world-scene-instantiate.test.d.ts.map +1 -0
  610. package/dist/__tests__/world-scene-nested.test.d.ts +2 -0
  611. package/dist/__tests__/world-scene-nested.test.d.ts.map +1 -0
  612. package/dist/__tests__/world-scene-override.test.d.ts +2 -0
  613. package/dist/__tests__/world-scene-override.test.d.ts.map +1 -0
  614. package/dist/__tests__/zero-compression-zero-load.integration.test.d.ts +2 -0
  615. package/dist/__tests__/zero-compression-zero-load.integration.test.d.ts.map +1 -0
  616. package/dist/asset-contributions.d.ts +3 -0
  617. package/dist/asset-contributions.d.ts.map +1 -0
  618. package/dist/backend-selection.d.ts +13 -0
  619. package/dist/backend-selection.d.ts.map +1 -0
  620. package/dist/collect-scene-asset.d.ts +19 -0
  621. package/dist/collect-scene-asset.d.ts.map +1 -0
  622. package/dist/components/__tests__/camera.test-d.d.ts +2 -0
  623. package/dist/components/__tests__/camera.test-d.d.ts.map +1 -0
  624. package/dist/components/__tests__/camera.test.d.ts +2 -0
  625. package/dist/components/__tests__/camera.test.d.ts.map +1 -0
  626. package/dist/components/__tests__/child-of.test.d.ts +2 -0
  627. package/dist/components/__tests__/child-of.test.d.ts.map +1 -0
  628. package/dist/components/__tests__/sprite-animation.test-d.d.ts +2 -0
  629. package/dist/components/__tests__/sprite-animation.test-d.d.ts.map +1 -0
  630. package/dist/components/__tests__/sprite-instances.unit.test.d.ts +2 -0
  631. package/dist/components/__tests__/sprite-instances.unit.test.d.ts.map +1 -0
  632. package/dist/components/__tests__/sprite-region-override.test-d.d.ts +2 -0
  633. package/dist/components/__tests__/sprite-region-override.test-d.d.ts.map +1 -0
  634. package/dist/components/__tests__/tile-layer.test-d.d.ts +2 -0
  635. package/dist/components/__tests__/tile-layer.test-d.d.ts.map +1 -0
  636. package/dist/components/__tests__/tile-layer.unit.test.d.ts +2 -0
  637. package/dist/components/__tests__/tile-layer.unit.test.d.ts.map +1 -0
  638. package/dist/components/__tests__/tilemap.test-d.d.ts +2 -0
  639. package/dist/components/__tests__/tilemap.test-d.d.ts.map +1 -0
  640. package/dist/components/__tests__/transform-vec-schema.test.d.ts +2 -0
  641. package/dist/components/__tests__/transform-vec-schema.test.d.ts.map +1 -0
  642. package/dist/create-renderer-env-classify.d.ts +21 -0
  643. package/dist/create-renderer-env-classify.d.ts.map +1 -0
  644. package/dist/createRenderer.d.ts +15 -0
  645. package/dist/createRenderer.d.ts.map +1 -0
  646. package/dist/dev-import-transport.d.ts +24 -0
  647. package/dist/dev-import-transport.d.ts.map +1 -0
  648. package/dist/errors/environment.d.ts +2 -0
  649. package/dist/errors/environment.d.ts.map +1 -0
  650. package/dist/index.d.ts +218 -0
  651. package/dist/index.d.ts.map +1 -0
  652. package/dist/index.mjs +813 -0
  653. package/dist/index.mjs.map +1 -0
  654. package/dist/renderer-host.d.ts +9 -0
  655. package/dist/renderer-host.d.ts.map +1 -0
  656. package/dist/renderer-host.mjs +62 -0
  657. package/dist/renderer-host.mjs.map +1 -0
  658. package/dist/scene-instances/__tests__/post-spawn-resolve-joints.unit.test.d.ts +2 -0
  659. package/dist/scene-instances/__tests__/post-spawn-resolve-joints.unit.test.d.ts.map +1 -0
  660. package/dist/scene-instances/index.d.ts +2 -0
  661. package/dist/scene-instances/index.d.ts.map +1 -0
  662. package/dist/scene-utils/mount-override-fold.d.ts +15 -0
  663. package/dist/scene-utils/mount-override-fold.d.ts.map +1 -0
  664. package/dist/sprite-param-values.d.ts +62 -0
  665. package/dist/sprite-param-values.d.ts.map +1 -0
  666. package/dist/systems/__tests__/_xorshift.d.ts +28 -0
  667. package/dist/systems/__tests__/_xorshift.d.ts.map +1 -0
  668. package/dist/systems/__tests__/argsort-in-place.test.d.ts +2 -0
  669. package/dist/systems/__tests__/argsort-in-place.test.d.ts.map +1 -0
  670. package/dist/systems/__tests__/transparent-sort.bench.d.ts +2 -0
  671. package/dist/systems/__tests__/transparent-sort.bench.d.ts.map +1 -0
  672. package/dist/systems/sprite-animation-tick.d.ts +50 -0
  673. package/dist/systems/sprite-animation-tick.d.ts.map +1 -0
  674. package/dist/systems/tonemap.d.ts +27 -0
  675. package/dist/systems/tonemap.d.ts.map +1 -0
  676. package/dist/systems/transparent-sort.d.ts +50 -0
  677. package/dist/systems/transparent-sort.d.ts.map +1 -0
  678. package/dist/tilemap-register-validate.d.ts +21 -0
  679. package/dist/tilemap-register-validate.d.ts.map +1 -0
  680. package/package.json +101 -0
  681. package/src/__tests__/active-camera.unit.test.ts +203 -0
  682. package/src/__tests__/animation-graph-add.unit.test.ts +67 -0
  683. package/src/__tests__/animation-graph-asset-union.test-d.ts +83 -0
  684. package/src/__tests__/animation-graph-blend.unit.test.ts +66 -0
  685. package/src/__tests__/animation-graph-build.unit.test.ts +100 -0
  686. package/src/__tests__/animation-graph-clip-missing.unit.test.ts +62 -0
  687. package/src/__tests__/animation-graph-clip.unit.test.ts +77 -0
  688. package/src/__tests__/animation-graph-collect-roundtrip.test.ts +213 -0
  689. package/src/__tests__/animation-graph-default-path.integration.test.ts +65 -0
  690. package/src/__tests__/animation-graph-describe.unit.test.ts +68 -0
  691. package/src/__tests__/animation-graph-errors.unit.test.ts +75 -0
  692. package/src/__tests__/animation-graph-nesting.unit.test.ts +84 -0
  693. package/src/__tests__/animation-graph-orthogonal-weight.unit.test.ts +98 -0
  694. package/src/__tests__/animation-graph-serialize.unit.test.ts +171 -0
  695. package/src/__tests__/animation-graph-slot-fill.unit.test.ts +95 -0
  696. package/src/__tests__/animation-nslot.unit.test.ts +63 -0
  697. package/src/__tests__/animation-slot-length-guard.unit.test.ts +81 -0
  698. package/src/__tests__/append-injection.test.ts +105 -0
  699. package/src/__tests__/asi-world-baseline-export.unit.test.ts +173 -0
  700. package/src/__tests__/asi-world-ci-config.unit.test.ts +186 -0
  701. package/src/__tests__/asi-world-smoke.unit.test.ts +192 -0
  702. package/src/__tests__/asset-brand-and-name-retirement.test.ts +102 -0
  703. package/src/__tests__/asset-error-exhaustive.test-d.ts +62 -0
  704. package/src/__tests__/asset-registry-guid-reverse.test.ts +608 -0
  705. package/src/__tests__/asset-registry-hdr-equirect.test.ts +78 -0
  706. package/src/__tests__/asset-registry-mesh-pack-payload.dawn.test.ts +232 -0
  707. package/src/__tests__/asset-registry-mounts.test.ts +806 -0
  708. package/src/__tests__/asset-registry-scene.test-d.ts +49 -0
  709. package/src/__tests__/asset-registry.browser.test.ts +81 -0
  710. package/src/__tests__/asset-registry.dawn.test.ts +94 -0
  711. package/src/__tests__/asset-registry.listCatalog.test.ts +170 -0
  712. package/src/__tests__/asset-registry.mesh-bin.test.ts +316 -0
  713. package/src/__tests__/asset-registry.pack-cache.test.ts +969 -0
  714. package/src/__tests__/asset-registry.raw-container-failfast.test.ts +225 -0
  715. package/src/__tests__/asset-registry.recursive.spec.ts +1397 -0
  716. package/src/__tests__/asset-registry.unit.test.ts +1030 -0
  717. package/src/__tests__/asset.unit.test.ts +7107 -0
  718. package/src/__tests__/audio-load-by-guid.unit.test.ts +126 -0
  719. package/src/__tests__/backend-selection.integration.test.ts +11 -0
  720. package/src/__tests__/basis-catalog-dispatch.integration.test.ts +320 -0
  721. package/src/__tests__/bind-group-cache-keying.test.ts +295 -0
  722. package/src/__tests__/block-upload-math.unit.test.ts +192 -0
  723. package/src/__tests__/builtin-asset-registry.unit.test.ts +112 -0
  724. package/src/__tests__/builtin-shader-register-e2e.test.ts +151 -0
  725. package/src/__tests__/cache-false-eviction.test.ts +127 -0
  726. package/src/__tests__/cache-isolation-matrix.test.ts +183 -0
  727. package/src/__tests__/camera-clear-snapshot-equivalence.unit.test.ts +75 -0
  728. package/src/__tests__/clamp-to-last.e2e.browser.test.ts +193 -0
  729. package/src/__tests__/clamp-to-last.e2e.dawn.test.ts +325 -0
  730. package/src/__tests__/collect-scene-asset.test.ts +873 -0
  731. package/src/__tests__/collect-scene-mount-collapse.test.ts +391 -0
  732. package/src/__tests__/collect-subtree.unit.test.ts +75 -0
  733. package/src/__tests__/collect-transient-children-absent.test.ts +168 -0
  734. package/src/__tests__/collect-transient-roundtrip.test.ts +269 -0
  735. package/src/__tests__/collect-transient-skip.test.ts +176 -0
  736. package/src/__tests__/components.test-d.ts +231 -0
  737. package/src/__tests__/components.unit.test.ts +2666 -0
  738. package/src/__tests__/composite-skybox-cross-world.browser.test.ts +410 -0
  739. package/src/__tests__/compressed-texture-physical-extent.integration.test.ts +139 -0
  740. package/src/__tests__/create-renderer-assembly.integration.test.ts +55 -0
  741. package/src/__tests__/createRenderer-dispose.test.ts +49 -0
  742. package/src/__tests__/createRenderer-requiredFeatures.unit.test.ts +134 -0
  743. package/src/__tests__/createRenderer.browser-no-webgpu.test.ts +54 -0
  744. package/src/__tests__/createRenderer.unit.test.ts +231 -0
  745. package/src/__tests__/dawn/hello-sprite-nineslice-section.dawn.test.ts +357 -0
  746. package/src/__tests__/dawn/ibl-compose.dawn.test.ts +151 -0
  747. package/src/__tests__/dawn/ibl-modules-compose.dawn.test.ts +223 -0
  748. package/src/__tests__/dawn/ibl-precompute-readback.dawn.test.ts +250 -0
  749. package/src/__tests__/dawn/instances-per-instance-pbr.dawn.test.ts +631 -0
  750. package/src/__tests__/dawn/instances-shadow.dawn.test.ts +454 -0
  751. package/src/__tests__/dawn/instances-uniform-fallback.dawn.test.ts +312 -0
  752. package/src/__tests__/dawn/material-alpha.dawn.test.ts +296 -0
  753. package/src/__tests__/dawn/material-cooked-fixture.dawn.test.ts +79 -0
  754. package/src/__tests__/dawn/mesh-ssbo-normal-matrix.dawn.test.ts +106 -0
  755. package/src/__tests__/dawn/mesh-update-dawn.test.ts +250 -0
  756. package/src/__tests__/dawn/mipmap-srgb-linear.dawn.test.ts +124 -0
  757. package/src/__tests__/dawn/per-entity-material-texture-isolation.dawn.test.ts +555 -0
  758. package/src/__tests__/dawn/sprite-nineslice-mesh-bind.dawn.test.ts +235 -0
  759. package/src/__tests__/dawn/tonemap.dawn.test.ts +107 -0
  760. package/src/__tests__/dawn/upload-texture.dawn.test.ts +198 -0
  761. package/src/__tests__/dawn/user-handle-mesh-render.dawn.test.ts +289 -0
  762. package/src/__tests__/dawn/user-mesh-upload.dawn.test.ts +209 -0
  763. package/src/__tests__/dawn/zero-manifest-mesh-fail.dawn.test.ts +119 -0
  764. package/src/__tests__/dawn/zero-manifest-ready.dawn.test.ts +98 -0
  765. package/src/__tests__/demo-sortscope-migration.unit.test.ts +115 -0
  766. package/src/__tests__/derive-bgl-integration.test.ts +388 -0
  767. package/src/__tests__/derive-render-data-skin.unit.test.ts +90 -0
  768. package/src/__tests__/draw-published.ts +17 -0
  769. package/src/__tests__/dynamic-texture-store.unit.test.ts +190 -0
  770. package/src/__tests__/environment-error-exhaustive.test-d.ts +28 -0
  771. package/src/__tests__/equirect-bc6h.integration.test.ts +158 -0
  772. package/src/__tests__/error-exhaustive.test.ts +220 -0
  773. package/src/__tests__/errors.unit.test.ts +1227 -0
  774. package/src/__tests__/extract-frames-graceful-degradation.test.ts +294 -0
  775. package/src/__tests__/extract-frames-merge.test.ts +656 -0
  776. package/src/__tests__/extract-frames-reset.test.ts +158 -0
  777. package/src/__tests__/extract-record-no-hardcoded-texture-fields.test.ts +81 -0
  778. package/src/__tests__/extract-skin-mismatch.unit.test.ts +322 -0
  779. package/src/__tests__/fetch-binary-decompress.test.ts +356 -0
  780. package/src/__tests__/four-path-compression.integration.test.ts +395 -0
  781. package/src/__tests__/frame-targets-shadow-regression.unit.test.ts +28 -0
  782. package/src/__tests__/fullscreen-feature-host.dawn.test.ts +80 -0
  783. package/src/__tests__/fullscreen-post-bgl-depth.test.ts +133 -0
  784. package/src/__tests__/fullscreen-post-depth-params-arity.test.ts +84 -0
  785. package/src/__tests__/fullscreen-post-process-pass.dawn.test.ts +336 -0
  786. package/src/__tests__/fullscreen-post-process-pass.unit.test.ts +109 -0
  787. package/src/__tests__/fxaa-pixel-diff.dawn.test.ts +303 -0
  788. package/src/__tests__/fxaa-validation-and-resize.dawn.test.ts +369 -0
  789. package/src/__tests__/fxaa-zero-overhead.dawn.test.ts +258 -0
  790. package/src/__tests__/geometry.unit.test.ts +2631 -0
  791. package/src/__tests__/glyph-tilemap-worldid.test.ts +146 -0
  792. package/src/__tests__/gpu-resource-cow-survivor.test.ts +390 -0
  793. package/src/__tests__/guid-for-asset.unit.test.ts +102 -0
  794. package/src/__tests__/handle-migration.test.ts +136 -0
  795. package/src/__tests__/hello-tilemap-smoke-upgrade.unit.test.ts +147 -0
  796. package/src/__tests__/helpers/mock-shader-registry.ts +139 -0
  797. package/src/__tests__/helpers/register-runtime-components.ts +71 -0
  798. package/src/__tests__/helpers/register-scene-components.ts +10 -0
  799. package/src/__tests__/helpers/tilemap-assets.ts +29 -0
  800. package/src/__tests__/host-custom-kind-contract.test-d.ts +47 -0
  801. package/src/__tests__/host-custom-kind-contract.test.ts +578 -0
  802. package/src/__tests__/inspect-name.test.ts +139 -0
  803. package/src/__tests__/instances.test-d.ts +61 -0
  804. package/src/__tests__/instantiate-mount-deferred-wiring.test.ts +112 -0
  805. package/src/__tests__/light-attenuation-formula.test.ts +171 -0
  806. package/src/__tests__/light-casters-9-light.browser.test.ts +877 -0
  807. package/src/__tests__/light-gpu-byte-neutral.unit.test.ts +160 -0
  808. package/src/__tests__/light-snapshot.test-d.ts +84 -0
  809. package/src/__tests__/lights-vec-negative.unit.test.ts +52 -0
  810. package/src/__tests__/lights.unit.test.ts +2456 -0
  811. package/src/__tests__/loadbyguid-payload.unit.test.ts +117 -0
  812. package/src/__tests__/loadbyguid-texture-intern.unit.test.ts +213 -0
  813. package/src/__tests__/loader-extract-graceful.test.ts +146 -0
  814. package/src/__tests__/loader-name-survival.test.ts +213 -0
  815. package/src/__tests__/m3-vec-negative.unit.test.ts +46 -0
  816. package/src/__tests__/m5-browser-dawn-sweep.integration.test.ts +8 -0
  817. package/src/__tests__/material-texture-uv-scale.unit.test.ts +119 -0
  818. package/src/__tests__/materials.unit.test.ts +254 -0
  819. package/src/__tests__/mesh-bin-contract.test.ts +28 -0
  820. package/src/__tests__/mesh-bin-skin-roundtrip.unit.test.ts +149 -0
  821. package/src/__tests__/mesh-bin.test.ts +38 -0
  822. package/src/__tests__/mesh-loader-skin-contract.unit.test.ts +120 -0
  823. package/src/__tests__/mesh-ssbo-grow.unit.test.ts +244 -0
  824. package/src/__tests__/mip-gate.unit.test.ts +79 -0
  825. package/src/__tests__/msaa-sprite-pixel-diff.dawn.test.ts +367 -0
  826. package/src/__tests__/multi-uv-8set.e2e.dawn.test.ts +296 -0
  827. package/src/__tests__/name.unit.test.ts +123 -0
  828. package/src/__tests__/new-kind-refs-contract.test.ts +132 -0
  829. package/src/__tests__/node-shims.d.ts +31 -0
  830. package/src/__tests__/package-promotion.test.ts +88 -0
  831. package/src/__tests__/package-rename.test.ts +103 -0
  832. package/src/__tests__/pass-selector.unit.test.ts +102 -0
  833. package/src/__tests__/pbr-pipeline.unit.test.ts +200 -0
  834. package/src/__tests__/pipeline-builder-msaa.unit.test.ts +221 -0
  835. package/src/__tests__/pipeline-cache-keying.unit.test.ts +313 -0
  836. package/src/__tests__/pipeline-spec.test.ts +900 -0
  837. package/src/__tests__/pipeline.unit.test.ts +5435 -0
  838. package/src/__tests__/point-light-shadow.browser.test.ts +278 -0
  839. package/src/__tests__/point-light-shadow.dawn.test.ts +813 -0
  840. package/src/__tests__/point-light-shadow.unit.test.ts +675 -0
  841. package/src/__tests__/post-process-error-codes.unit.test.ts +161 -0
  842. package/src/__tests__/post-process-params-component.unit.test.ts +153 -0
  843. package/src/__tests__/post-process-params-extract.unit.test.ts +97 -0
  844. package/src/__tests__/postprocess-camera-consistency.test.ts +375 -0
  845. package/src/__tests__/profiler-assembly.integration.test.ts +74 -0
  846. package/src/__tests__/propagate-transforms.dawn.test.ts +254 -0
  847. package/src/__tests__/record-worldid-isolation.test.ts +225 -0
  848. package/src/__tests__/recover-error-exhaustive.test-d.ts +35 -0
  849. package/src/__tests__/render-error-exhaustive.test-d.ts +291 -0
  850. package/src/__tests__/render-feature-assembly.test-d.ts +39 -0
  851. package/src/__tests__/render-feature-prepared-graphics-docs.unit.test.ts +126 -0
  852. package/src/__tests__/render-feature-prepared-graphics.browser.test.ts +88 -0
  853. package/src/__tests__/render-feature-prepared-graphics.dawn.test.ts +115 -0
  854. package/src/__tests__/render-feature-prepared-graphics.fixture.ts +104 -0
  855. package/src/__tests__/render-feature-prepared-graphics.integration.test.ts +56 -0
  856. package/src/__tests__/render-feature-prepared-graphics.regression.integration.test.ts +104 -0
  857. package/src/__tests__/render-feature-recover.integration.test.ts +102 -0
  858. package/src/__tests__/render-feature-runtime-surface.test-d.ts +42 -0
  859. package/src/__tests__/render-schema-consumer.integration.test.ts +32 -0
  860. package/src/__tests__/render-schema-owner-boundary.unit.test.ts +22 -0
  861. package/src/__tests__/render-system-cross-world-overlay-queue.test.ts +172 -0
  862. package/src/__tests__/render-system-extract-dispatch-ordering.test.ts +307 -0
  863. package/src/__tests__/render-system-extract.test.ts +1384 -0
  864. package/src/__tests__/render-system-extract.unit.test.ts +360 -0
  865. package/src/__tests__/render-system-fold-bucket.unit.test.ts +286 -0
  866. package/src/__tests__/render-system-fold-fallback.unit.test.ts +151 -0
  867. package/src/__tests__/render-system-fold-material.unit.test.ts +143 -0
  868. package/src/__tests__/render-system-fold-metrics.unit.test.ts +170 -0
  869. package/src/__tests__/render-system-fold-modegate.unit.test.ts +166 -0
  870. package/src/__tests__/render-system-fold-scene-instances.unit.test.ts +51 -0
  871. package/src/__tests__/render-system-fold.dawn.test.ts +308 -0
  872. package/src/__tests__/render-system-mega.test.ts +1680 -0
  873. package/src/__tests__/render-system-owner-split-contract.test.ts +178 -0
  874. package/src/__tests__/render-system-record-multi-material-textureview.test.ts +698 -0
  875. package/src/__tests__/render-system-record-per-submesh-transparency.test.ts +458 -0
  876. package/src/__tests__/render-system-record.test.ts +1668 -0
  877. package/src/__tests__/render-system-record.unit.test.ts +269 -0
  878. package/src/__tests__/render-system-skin-bg.unit.test.ts +143 -0
  879. package/src/__tests__/renderer-consumer.test-d.ts +18 -0
  880. package/src/__tests__/renderer-health.unit.test.ts +45 -0
  881. package/src/__tests__/renderer-lifecycle.integration.test.ts +42 -0
  882. package/src/__tests__/renderer-recover.unit.test.ts +55 -0
  883. package/src/__tests__/renderer-result.test-d.ts +36 -0
  884. package/src/__tests__/renderer-surface.unit.test.ts +414 -0
  885. package/src/__tests__/renderer-test-utils.ts +56 -0
  886. package/src/__tests__/renderer.test-d.ts +61 -0
  887. package/src/__tests__/resolve-asset-handle-err.test.ts +111 -0
  888. package/src/__tests__/resolve-asset-handle.unit.test.ts +64 -0
  889. package/src/__tests__/resolve-depth-only-view.test.ts +173 -0
  890. package/src/__tests__/resolveName-fallback.test.ts +64 -0
  891. package/src/__tests__/resolveName-null-package.test.ts +38 -0
  892. package/src/__tests__/resolveName-unified-entry.test.ts +65 -0
  893. package/src/__tests__/resolveName.test.ts +64 -0
  894. package/src/__tests__/rhi-null-command-flow.unit.test.ts +69 -0
  895. package/src/__tests__/rhi-null-noop-behavior.unit.test.ts +196 -0
  896. package/src/__tests__/rhi-null-renderer-lifecycle.unit.test.ts +54 -0
  897. package/src/__tests__/roots-to-scene-asset.test.ts +711 -0
  898. package/src/__tests__/roundtrip-primitive.test.ts +351 -0
  899. package/src/__tests__/runtime-authority-scan.unit.test.ts +19 -0
  900. package/src/__tests__/runtime-domain-residue.unit.test.ts +13 -0
  901. package/src/__tests__/scene-bare-guid-refs-instantiate.test.ts +123 -0
  902. package/src/__tests__/scene-equirect-roundtrip.test.ts +120 -0
  903. package/src/__tests__/scene-instance-authoring.integration.test.ts +8 -0
  904. package/src/__tests__/scene-instance-bridge.integration.test.ts +8 -0
  905. package/src/__tests__/scene-instance-component.test.ts +45 -0
  906. package/src/__tests__/scene-instantiate-cycle-runtime.test.ts +66 -0
  907. package/src/__tests__/scene-mount-fixed-point.test.ts +257 -0
  908. package/src/__tests__/scene-mount-roundtrip.test.ts +987 -0
  909. package/src/__tests__/scene-mount-wrapper-roundtrip.test.ts +202 -0
  910. package/src/__tests__/scene-sugar-instantiate.test.ts +324 -0
  911. package/src/__tests__/serialize-mounts-roundtrip.test.ts +533 -0
  912. package/src/__tests__/serialize-scene-asset.test.ts +533 -0
  913. package/src/__tests__/shadow-caster-empty-schema.test.ts +83 -0
  914. package/src/__tests__/shadow-csm-atlas.test.ts +178 -0
  915. package/src/__tests__/shadow-csm-blend.dawn.test.ts +230 -0
  916. package/src/__tests__/shadow-csm-cascade-loadop.test.ts +354 -0
  917. package/src/__tests__/shadow-csm-component.test.ts +284 -0
  918. package/src/__tests__/shadow-csm-extract.test.ts +236 -0
  919. package/src/__tests__/shadow-csm-orthoz.test.ts +137 -0
  920. package/src/__tests__/shadow-csm-runtime-vary.dawn.test.ts +203 -0
  921. package/src/__tests__/shadow-csm-shader.dawn.test.ts +358 -0
  922. package/src/__tests__/shadow-csm-tile-consistency.test.ts +425 -0
  923. package/src/__tests__/shadow-csm-ubo.dawn.test.ts +252 -0
  924. package/src/__tests__/shadow-csm-ubo.test.ts +278 -0
  925. package/src/__tests__/shadow-csm-viewport.browser.test.ts +85 -0
  926. package/src/__tests__/shadow-fields-observable.dawn.test.ts +626 -0
  927. package/src/__tests__/shadow-m1.dawn.test.ts +104 -0
  928. package/src/__tests__/shadow-mesh-ssbo-order.test.ts +48 -0
  929. package/src/__tests__/skin-error-exhaustive.test-d.ts +74 -0
  930. package/src/__tests__/skin-errors.unit.test.ts +10 -0
  931. package/src/__tests__/skin-extract-getarrayview-count.unit.test.ts +25 -0
  932. package/src/__tests__/skin-parented-double-transform.test.ts +268 -0
  933. package/src/__tests__/skylight-lazy-projection.test.ts +380 -0
  934. package/src/__tests__/sparse-tag-scene-roundtrip.test.ts +43 -0
  935. package/src/__tests__/sprite-cullmode-flip.dawn.test.ts +453 -0
  936. package/src/__tests__/sprite-lit-bgl-byte-identical.test.ts +142 -0
  937. package/src/__tests__/sprite-lit-extract-branch.test.ts +117 -0
  938. package/src/__tests__/sprite-lit-pipeline-cache-key.test.ts +123 -0
  939. package/src/__tests__/sprite-param-values.test-d.ts +104 -0
  940. package/src/__tests__/ssao-bgl.test.ts +354 -0
  941. package/src/__tests__/ssao-buffers.test.ts +211 -0
  942. package/src/__tests__/ssao-data.test.ts +121 -0
  943. package/src/__tests__/ssao-errors.test.ts +162 -0
  944. package/src/__tests__/ssao-manifest.test.ts +109 -0
  945. package/src/__tests__/ssao-passes.test.ts +905 -0
  946. package/src/__tests__/ssao-pipelines.test.ts +275 -0
  947. package/src/__tests__/ssao-uniform-layout.test.ts +180 -0
  948. package/src/__tests__/static-rig.integration.test.ts +9 -0
  949. package/src/__tests__/stencil-outline-pixel.dawn.test.ts +303 -0
  950. package/src/__tests__/swap-chain-format.unit.test.ts +115 -0
  951. package/src/__tests__/systems.unit.test.ts +9167 -0
  952. package/src/__tests__/template-game-default-api-contract.test.ts +362 -0
  953. package/src/__tests__/text.unit.test.ts +489 -0
  954. package/src/__tests__/tilemap-childed-despawn.test.ts +462 -0
  955. package/src/__tests__/tilemap-chunk-extract.overhang-bounds.test.ts +310 -0
  956. package/src/__tests__/tilemap-chunk-extract.propagate-order.test.ts +81 -0
  957. package/src/__tests__/tilemap-chunk-extract.terrain-rewrite.unit.test.ts +424 -0
  958. package/src/__tests__/tilemap-chunk-extract.test.ts +232 -0
  959. package/src/__tests__/tilemap-chunk-extract.unit.test.ts +383 -0
  960. package/src/__tests__/tilemap-chunk-y-sort-bench.unit.test.ts +109 -0
  961. package/src/__tests__/tilemap-dirty-rebuild.test.ts +296 -0
  962. package/src/__tests__/tilemap-material-cache.test.ts +132 -0
  963. package/src/__tests__/tilemap-multi-atlas.test.ts +217 -0
  964. package/src/__tests__/tilemap-owner-boundary.unit.test.ts +66 -0
  965. package/src/__tests__/tilemap-region-pack.test.ts +149 -0
  966. package/src/__tests__/tilemap-spawn-flip-pivot.test.ts +270 -0
  967. package/src/__tests__/tilemap-uv-inset.test.ts +201 -0
  968. package/src/__tests__/tilemap-validate.test.ts +201 -0
  969. package/src/__tests__/tileset-asset-validate.test.ts +638 -0
  970. package/src/__tests__/transcode-fallback.unit.test.ts +207 -0
  971. package/src/__tests__/transform-hierarchy-pixel-diff.dawn.test.ts +282 -0
  972. package/src/__tests__/transient-fields-collect.test.ts +210 -0
  973. package/src/__tests__/transient-runtime-behavior.test.ts +227 -0
  974. package/src/__tests__/transitional-authoring.integration.test.ts +7 -0
  975. package/src/__tests__/transparent-sort-tilemap-sprite-interleave.test.ts +227 -0
  976. package/src/__tests__/variant-selection.unit.test.ts +21 -0
  977. package/src/__tests__/vertex-attribute-layout.test.ts +324 -0
  978. package/src/__tests__/video-extract-bindgroup.dawn.test.ts +403 -0
  979. package/src/__tests__/video-loader-consumption.test.ts +86 -0
  980. package/src/__tests__/video-loader-smoke.dawn.test.ts +60 -0
  981. package/src/__tests__/view-ubo-layout.browser.test.ts +161 -0
  982. package/src/__tests__/world-entity-key.test.ts +92 -0
  983. package/src/__tests__/world-scene-despawn.test.ts +132 -0
  984. package/src/__tests__/world-scene-instantiate.test.ts +194 -0
  985. package/src/__tests__/world-scene-nested.test.ts +230 -0
  986. package/src/__tests__/world-scene-override.test.ts +199 -0
  987. package/src/__tests__/zero-compression-zero-load.integration.test.ts +107 -0
  988. package/src/asset-contributions.ts +48 -0
  989. package/src/backend-selection.ts +69 -0
  990. package/src/collect-scene-asset.ts +701 -0
  991. package/src/components/__tests__/camera.test-d.ts +57 -0
  992. package/src/components/__tests__/camera.test.ts +95 -0
  993. package/src/components/__tests__/child-of.test.ts +35 -0
  994. package/src/components/__tests__/sprite-animation.test-d.ts +160 -0
  995. package/src/components/__tests__/sprite-instances.unit.test.ts +108 -0
  996. package/src/components/__tests__/sprite-region-override.test-d.ts +101 -0
  997. package/src/components/__tests__/tile-layer.test-d.ts +33 -0
  998. package/src/components/__tests__/tile-layer.unit.test.ts +125 -0
  999. package/src/components/__tests__/tilemap.test-d.ts +35 -0
  1000. package/src/components/__tests__/transform-vec-schema.test.ts +121 -0
  1001. package/src/create-renderer-env-classify.ts +110 -0
  1002. package/src/createRenderer.ts +49 -0
  1003. package/src/dev-import-transport.ts +97 -0
  1004. package/src/errors/environment.ts +6 -0
  1005. package/src/index.ts +380 -0
  1006. package/src/renderer-host.ts +33 -0
  1007. package/src/scene-instances/__tests__/post-spawn-resolve-joints.unit.test.ts +183 -0
  1008. package/src/scene-instances/index.ts +4 -0
  1009. package/src/scene-utils/mount-override-fold.ts +213 -0
  1010. package/src/sprite-param-values.ts +61 -0
  1011. package/src/systems/__tests__/_xorshift.ts +83 -0
  1012. package/src/systems/__tests__/argsort-in-place.test.ts +155 -0
  1013. package/src/systems/__tests__/transparent-sort.bench.ts +218 -0
  1014. package/src/systems/sprite-animation-tick.ts +302 -0
  1015. package/src/systems/tonemap.ts +68 -0
  1016. package/src/systems/transparent-sort.ts +434 -0
  1017. package/src/tilemap-register-validate.ts +100 -0
@@ -0,0 +1,1668 @@
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 { AssetRegistry } from '@forgeax/engine-assets-runtime';
10
+ import { World } from '@forgeax/engine-ecs';
11
+ import { Camera, MeshFilter, MeshRenderer } from '@forgeax/engine-render';
12
+ import {
13
+ SPRITE_PREMULTIPLIED_ALPHA_BLEND,
14
+ SpriteRegionOverride,
15
+ } from '@forgeax/engine-render/authoring';
16
+ import { propagateTransforms, Transform } from '@forgeax/engine-scene';
17
+ import { ShaderRegistry, type ShaderRegistryDevice } from '@forgeax/engine-shader';
18
+ import type {
19
+ Handle,
20
+ MaterialAsset,
21
+ MaterialPass,
22
+ MeshAsset,
23
+ ParamSchemaEntry,
24
+ } from '@forgeax/engine-types';
25
+ import { derive } from '@forgeax/engine-types';
26
+ import { describe, expect, it } from 'vitest';
27
+ import { createEngineMetrics } from '../../../render/src/engine-metrics';
28
+ import { isLitMaterialSnapshot } from '../../../render/src/record/helpers';
29
+ import * as recordModule from '../../../render/src/record/main-pass-material';
30
+ import { detectNineSliceScaleTooSmall } from '../../../render/src/record/main-pass-material';
31
+ import * as spriteRecordModule from '../../../render/src/record/main-pass-sprite-draws';
32
+ import { filterDispatchBySelector } from '../../../render/src/record/shadow-pass';
33
+ import type { DispatchEntry, MaterialSnapshot } from '../../../render/src/render-system-extract';
34
+ import { extractFrame, prepareExtractContext } from '../../../render/src/render-system-extract';
35
+
36
+ // ─── from render-system-record-pbr-ubo-stable.test.ts ───
37
+ {
38
+ // render-system-record-pbr-ubo-stable - feat-20260527-sprite-nineslice M2 / w6.
39
+ //
40
+ // PBR Material UBO byte-sequence regression net (D-7 isolation):
41
+ // any deviation in the PBR write path's byte sequence (even 1 byte) caused
42
+ // by the sprite-branch schema-driven UBO extension (w11) MUST fail this test
43
+ // — that triggers R-8 fallback (revert D-7 to physically isolated sprite
44
+ // write function).
45
+ //
46
+ // w11 will extract the inline PBR UBO write path
47
+ // (render-system-record.ts:2194-2277 -- baseColor, metallic, roughness,
48
+ // channelMap u32 packing, emissive, occlusionStrength, paramSnapshot
49
+ // schema-driven overlay) into a pure helper
50
+ // `buildPbrMaterialUboPayload(material) -> Uint8Array(288B)` so this
51
+ // regression test can exercise the PBR write byte-for-byte.
52
+ //
53
+ // The helper MUST produce the exact byte sequence the inline path produced
54
+ // pre-feat (no slices-related side effects on the PBR layout). This test
55
+ // asserts the PBR baseline against literal byte values.
56
+ //
57
+ // Coverage:
58
+ // - 304 B payload (STANDARD_PBR_UBO_SIZE)
59
+ // - slot 0 = baseColor.rgb + 1 (alpha hardcoded)
60
+ // - slot 1 first 8 B = metallic, roughness (f32x2)
61
+ // - slot 1 last 4 u32 (channelMap) = [2, 1, 0, 0]
62
+ // - slot 2 = emissive.rgb + emissiveIntensity
63
+ // - slot 3 first 4 B = occlusionStrength; rest stays 0 (no schema overlay
64
+ // in default-standard-pbr path; paramSnapshot reuses slot 0/1/2 by
65
+ // position).
66
+ //
67
+ // Anchors: plan-strategy §R-8, §5.3 key tests #2; AGENTS.md §Error model
68
+ // (closed-union AssetErrorCode unchanged, baseline test does not depend on
69
+ // new error members).
70
+
71
+ const mod = recordModule as unknown as {
72
+ buildPbrMaterialUboPayload?: (material: MaterialSnapshot) => Uint8Array;
73
+ applyMaterialTextureUvScales?: (
74
+ payload: ArrayBuffer | Uint8Array,
75
+ material: MaterialSnapshot,
76
+ world: World,
77
+ ) => void;
78
+ };
79
+
80
+ function makePbrSnapshot(opts: {
81
+ baseColor?: readonly [number, number, number, number];
82
+ metallic?: number;
83
+ roughness?: number;
84
+ emissive?: readonly [number, number, number];
85
+ emissiveIntensity?: number;
86
+ occlusionStrength?: number;
87
+ clearcoat?: number;
88
+ clearcoatRoughness?: number;
89
+ specularTint?: readonly [number, number, number];
90
+ }): MaterialSnapshot {
91
+ return {
92
+ baseColor: opts.baseColor ?? [0.5, 0.6, 0.7, 1],
93
+ metallic: opts.metallic ?? 0.1,
94
+ roughness: opts.roughness ?? 0.4,
95
+ materialShaderId: 'forgeax::default-standard-pbr',
96
+ paramSnapshot: undefined,
97
+ ...(opts.emissive !== undefined && { emissive: opts.emissive }),
98
+ ...(opts.emissiveIntensity !== undefined && { emissiveIntensity: opts.emissiveIntensity }),
99
+ ...(opts.occlusionStrength !== undefined && { occlusionStrength: opts.occlusionStrength }),
100
+ ...(opts.clearcoat !== undefined && { clearcoat: opts.clearcoat }),
101
+ ...(opts.clearcoatRoughness !== undefined && { clearcoatRoughness: opts.clearcoatRoughness }),
102
+ ...(opts.specularTint !== undefined && { specularTint: opts.specularTint }),
103
+ } as unknown as MaterialSnapshot;
104
+ }
105
+
106
+ describe('PBR Material UBO byte sequence regression net (M2 / w6, D-7 isolation)', () => {
107
+ it('export: buildPbrMaterialUboPayload helper is exported from render-system-record', () => {
108
+ expect(typeof mod.buildPbrMaterialUboPayload).toBe('function');
109
+ });
110
+
111
+ it('304 B payload size + standard PBR slot layout (baseline)', () => {
112
+ if (typeof mod.buildPbrMaterialUboPayload !== 'function') {
113
+ throw new Error('helper not exported yet');
114
+ }
115
+ const snap = makePbrSnapshot({
116
+ baseColor: [0.5, 0.6, 0.7, 1],
117
+ metallic: 0.1,
118
+ roughness: 0.4,
119
+ emissive: [0, 0, 0],
120
+ emissiveIntensity: 0,
121
+ occlusionStrength: 1,
122
+ clearcoat: 0.85,
123
+ clearcoatRoughness: 0.12,
124
+ specularTint: [0.11, 0.22, 0.33],
125
+ });
126
+ const buf = mod.buildPbrMaterialUboPayload(snap);
127
+ expect(buf.byteLength).toBe(304);
128
+ const f32 = new Float32Array(buf.buffer, buf.byteOffset, buf.byteLength / 4);
129
+ // feat-20260613 fix-issue-1 (D-8 channelMap split): the 4 channelMap
130
+ // u32 slots collapse into 4 independent f32 channel selectors at
131
+ // f32[6..9] (metallicChannel/roughnessChannel/aoChannel/extraChannel).
132
+ // baseColor.rgb + alpha=1 (alpha hardcoded in PBR write path).
133
+ expect(f32[0]).toBeCloseTo(0.5);
134
+ expect(f32[1]).toBeCloseTo(0.6);
135
+ expect(f32[2]).toBeCloseTo(0.7);
136
+ expect(f32[3]).toBe(1);
137
+ // metallic + roughness (first 8 B of slot 1).
138
+ expect(f32[4]).toBeCloseTo(0.1);
139
+ expect(f32[5]).toBeCloseTo(0.4);
140
+ // 4 channel selectors as f32 (default = B/G/R/_ glTF packing).
141
+ expect(f32[6]).toBe(2); // metallicChannel <- B
142
+ expect(f32[7]).toBe(1); // roughnessChannel <- G
143
+ expect(f32[8]).toBe(0); // aoChannel <- R
144
+ expect(f32[9]).toBe(0); // extraChannel <- reserved
145
+ // offsets 40..47 implicit pad (vec3 align=16 before emissive).
146
+ expect(f32[10]).toBe(0);
147
+ expect(f32[11]).toBe(0);
148
+ // emissive.rgb + emissiveIntensity (offset 48..63).
149
+ expect(f32[12]).toBe(0);
150
+ expect(f32[13]).toBe(0);
151
+ expect(f32[14]).toBe(0);
152
+ expect(f32[15]).toBe(0);
153
+ // occlusionStrength (offset 64..67).
154
+ expect(f32[16]).toBe(1);
155
+ // alphaCutoff (offset 68).
156
+ expect(f32[17]).toBe(0);
157
+ // clearcoat layer (offsets 72..79).
158
+ expect(f32[18]).toBeCloseTo(0.85);
159
+ expect(f32[19]).toBeCloseTo(0.12);
160
+ // specularTint vec3 (offsets 80..92).
161
+ expect(f32[20]).toBeCloseTo(0.11);
162
+ expect(f32[21]).toBeCloseTo(0.22);
163
+ expect(f32[22]).toBeCloseTo(0.33);
164
+ expect(f32[72]).toBe(1);
165
+ });
166
+
167
+ it('writes schema-carried alphaCutoff into the baseline PBR payload', () => {
168
+ if (typeof mod.buildPbrMaterialUboPayload !== 'function') {
169
+ throw new Error('helper not exported yet');
170
+ }
171
+ const payload = mod.buildPbrMaterialUboPayload({
172
+ ...makePbrSnapshot({}),
173
+ paramSnapshot: { alphaCutoff: 0.5 },
174
+ } as MaterialSnapshot);
175
+ const f32 = new Float32Array(payload.buffer, payload.byteOffset, payload.byteLength / 4);
176
+ expect(f32[17]).toBeCloseTo(0.5);
177
+ });
178
+
179
+ it('keeps engine-owned texture UV tails aligned across PBR and sprite layouts', () => {
180
+ const apply = mod.applyMaterialTextureUvScales;
181
+ if (typeof apply !== 'function') throw new Error('helper not exported yet');
182
+ const world = new World();
183
+ const pbrPayload = new ArrayBuffer(304);
184
+ apply(pbrPayload, makePbrSnapshot({}), world);
185
+ const pbrF32 = new Float32Array(pbrPayload);
186
+ expect(pbrF32[26]).toBe(1);
187
+ expect(pbrF32[34]).toBe(1);
188
+ expect(pbrF32[42]).toBe(1);
189
+ expect(pbrF32[50]).toBe(1);
190
+ expect(pbrF32[58]).toBe(1);
191
+ expect(pbrF32[66]).toBe(1);
192
+
193
+ const spritePayload = new ArrayBuffer(128);
194
+ apply(
195
+ spritePayload,
196
+ { ...makePbrSnapshot({}), materialShaderId: 'forgeax::sprite' } as MaterialSnapshot,
197
+ world,
198
+ );
199
+ const spriteF32 = new Float32Array(spritePayload);
200
+ expect(Array.from(spriteF32.slice(16, 24))).toEqual([0, 0, 1, 1, 0, 0, 1, 1]);
201
+ });
202
+
203
+ it('byte-stable across two equivalent PBR snapshots (idempotent write)', () => {
204
+ if (typeof mod.buildPbrMaterialUboPayload !== 'function') {
205
+ throw new Error('helper not exported yet');
206
+ }
207
+ const snap = makePbrSnapshot({
208
+ baseColor: [0.2, 0.3, 0.4, 1],
209
+ metallic: 0.5,
210
+ roughness: 0.5,
211
+ });
212
+ const a = mod.buildPbrMaterialUboPayload(snap);
213
+ const b = mod.buildPbrMaterialUboPayload(snap);
214
+ expect(new Uint8Array(a)).toEqual(new Uint8Array(b));
215
+ });
216
+ });
217
+ }
218
+
219
+ // ─── from render-system-record-scale-too-small.test.ts ───
220
+ {
221
+ // render-system-record-scale-too-small — feat-20260527-sprite-nineslice M4 / w17 (a).
222
+ //
223
+ // AC-16 end-to-end: when a 9-slice sprite entity carries a Transform.scale
224
+ // below the four corner anchors, the detection path bumps
225
+ // the owner metrics snapshot's `nineslice.scale-too-small` key once per offending
226
+ // renderableIndex per RenderSystem lifetime. AI users observe the breach
227
+ // through the EngineMetrics counter rather than parsing console.warn text
228
+ // (charter P3 machine-readable signals).
229
+ //
230
+ // The test exercises `detectNineSliceScaleTooSmall` (the pure helper extracted
231
+ // from recordFrame in w17 prep) so the assertion runs without a GPU device.
232
+ // recordFrame's inline call site forwards the same arguments to this helper,
233
+ // so the unit test reflects production behaviour byte-for-byte.
234
+ //
235
+ // Anchors: requirements §AC-16; plan-strategy §5.3 key tests #9; plan-strategy
236
+ // §D-5 EngineMetrics machine-readable counter.
237
+
238
+ function diagonalWorld(scaleX: number, scaleY: number): Float32Array {
239
+ // Column-major mat4 with diagonal scale, no rotation/translation.
240
+ // biome-ignore format: matrix layout
241
+ return new Float32Array([
242
+ scaleX, 0, 0, 0,
243
+ 0, scaleY, 0, 0,
244
+ 0, 0, 1, 0,
245
+ 0, 0, 0, 1,
246
+ ]);
247
+ }
248
+
249
+ describe('detectNineSliceScaleTooSmall (M4 / w17, AC-16 E2E)', () => {
250
+ it('single offending entity bumps the counter to 1', () => {
251
+ const metrics = createEngineMetrics();
252
+ const seen = new Set<number>();
253
+ // anchor budget: |0.3| + |0.3| = 0.6 horizontal / 0.6 vertical.
254
+ // scale 0.4 < 0.6 -> breach on both axes.
255
+ detectNineSliceScaleTooSmall(diagonalWorld(0.4, 0.4), [0.3, 0.3, 0.3, 0.3], 0, seen, metrics);
256
+ expect(metrics.snapshot()['nineslice.scale-too-small']).toBe(1);
257
+ });
258
+
259
+ it('repeat call on the SAME renderableIndex stays at 1 (warn-once anchor)', () => {
260
+ const metrics = createEngineMetrics();
261
+ const seen = new Set<number>();
262
+ for (let i = 0; i < 10; i++) {
263
+ detectNineSliceScaleTooSmall(
264
+ diagonalWorld(0.4, 0.4),
265
+ [0.3, 0.3, 0.3, 0.3],
266
+ 0,
267
+ seen,
268
+ metrics,
269
+ );
270
+ }
271
+ expect(metrics.snapshot()['nineslice.scale-too-small']).toBe(1);
272
+ });
273
+
274
+ it('N distinct offending entities yield counter === N', () => {
275
+ const metrics = createEngineMetrics();
276
+ const seen = new Set<number>();
277
+ for (let r = 0; r < 5; r++) {
278
+ detectNineSliceScaleTooSmall(
279
+ diagonalWorld(0.4, 0.4),
280
+ [0.3, 0.3, 0.3, 0.3],
281
+ r,
282
+ seen,
283
+ metrics,
284
+ );
285
+ }
286
+ expect(metrics.snapshot()['nineslice.scale-too-small']).toBe(5);
287
+ });
288
+
289
+ it('compliant scale (>= anchor) does NOT bump the counter', () => {
290
+ const metrics = createEngineMetrics();
291
+ const seen = new Set<number>();
292
+ detectNineSliceScaleTooSmall(diagonalWorld(1.0, 1.0), [0.3, 0.3, 0.3, 0.3], 0, seen, metrics);
293
+ expect(metrics.snapshot()['nineslice.scale-too-small']).toBeUndefined();
294
+ });
295
+
296
+ it('all-zero slices is a no-op (legacy quad path, no detection)', () => {
297
+ const metrics = createEngineMetrics();
298
+ const seen = new Set<number>();
299
+ // Even with a tiny scale the all-zero slices means "this is not a 9-slice"
300
+ detectNineSliceScaleTooSmall(diagonalWorld(0.01, 0.01), [0, 0, 0, 0], 0, seen, metrics);
301
+ expect(metrics.snapshot()['nineslice.scale-too-small']).toBeUndefined();
302
+ });
303
+
304
+ it('tile-mode sentinel (slices.w < 0) consumed via abs() — no false negative', () => {
305
+ const metrics = createEngineMetrics();
306
+ const seen = new Set<number>();
307
+ // sliceMode=1 negates slices.w; the helper takes abs() so the anchor budget
308
+ // still totals 0.6 on the vertical axis (|0.3| + |-0.3|).
309
+ detectNineSliceScaleTooSmall(
310
+ diagonalWorld(0.4, 0.4),
311
+ [0.3, 0.3, 0.3, -0.3],
312
+ 0,
313
+ seen,
314
+ metrics,
315
+ );
316
+ expect(metrics.snapshot()['nineslice.scale-too-small']).toBe(1);
317
+ });
318
+ });
319
+ }
320
+
321
+ // ─── from render-system-record-sprite-region-override.test.ts ───
322
+ {
323
+ // render-system-record-sprite-region-override — feat-20260527-sprite-nineslice M4 / w17 (b).
324
+ //
325
+ // AC-14: SpriteRegionOverride per-entity UV sub-rectangle composes with 9-slice.
326
+ // When the entity carries the override component, the extract stage replaces
327
+ // the asset-side `values.region` with the override's 4-float region
328
+ // before downstream snapshot construction; downstream 9-slice paths therefore
329
+ // measure slices against the override's region.zw, not the asset's. Two
330
+ // renderables sharing the same MaterialAsset can render with different
331
+ // effective regions when only one carries the override.
332
+ //
333
+ // Coverage:
334
+ // (1) entity WITHOUT SpriteRegionOverride uses the asset-side region as the
335
+ // effective region in the snapshot.
336
+ // (2) entity WITH SpriteRegionOverride uses the override's region in the
337
+ // snapshot — a half-region [0, 0, 0.5, 1] flows through verbatim,
338
+ // displacing the asset's [0, 0, 1, 1].
339
+ // (3) Two renderables sharing the same material asset diverge on effective
340
+ // region — the override-bearing entity sees the override; the other
341
+ // sees the asset's region. Slices=[0.25,0.25,0.25,0.25] flow through
342
+ // both unchanged (slices remain in the [0..1] local-UV unit; the
343
+ // effective region only governs UV sampling, not the slices unit).
344
+ //
345
+ // Anchors: requirements §AC-14; plan-strategy §5.3 key tests #8.
346
+
347
+ function makeShaderRegistryWithSprite(): ShaderRegistry {
348
+ const mockDevice: ShaderRegistryDevice = {
349
+ createShaderModule() {
350
+ return {
351
+ ok: true,
352
+ value: undefined,
353
+ unwrap: () => undefined,
354
+ unwrapOr: (d: unknown) => d,
355
+ } as unknown as ReturnType<ShaderRegistryDevice['createShaderModule']>;
356
+ },
357
+ };
358
+ const sr = new ShaderRegistry({ device: mockDevice, manifestUrl: undefined });
359
+ // feat-20260625-refactor-sprite-as-transparent-mesh M3 / w11+w12: the
360
+ // sprite paramSchema mirrors the WGSL UBO struct field set (4 vec4 +
361
+ // baseColorTexture). Legacy user inputs (slices/sliceMode/flipX/flipY/
362
+ // pivot) are still accepted by extract and folded into the UBO-aligned
363
+ // paramSnapshot vec4 entries (D-8).
364
+ sr.installMaterialArtifact('forgeax::sprite', {
365
+ source: 'fn main() {}',
366
+ paramSchema: [
367
+ { name: 'colorTint', type: 'vec4', default: [1.0, 1.0, 1.0, 1.0] },
368
+ { name: 'region', type: 'vec4', default: [0.0, 0.0, 1.0, 1.0] },
369
+ { name: 'pivotAndSize', type: 'vec4', default: [0.5, 0.5, 1.0, 1.0] },
370
+ { name: 'slicesAndMode', type: 'vec4', default: [0.0, 0.0, 0.0, 0.0] },
371
+ { name: 'baseColorTexture', type: 'texture2d' },
372
+ ],
373
+ });
374
+ return sr;
375
+ }
376
+
377
+ const SPRITE_PASS: MaterialPass = {
378
+ name: 'Sprite',
379
+ program: { module: 'forgeax::sprite' },
380
+ renderState: { queue: 3000 },
381
+ };
382
+
383
+ function identity() {
384
+ return {
385
+ pos: [0, 0, 0],
386
+ quat: [0, 0, 0, 1],
387
+ scale: [1, 1, 1],
388
+ };
389
+ }
390
+
391
+ function registerSpriteMesh(world: World): Handle<'MeshAsset', 'shared'> {
392
+ const positions = new Float32Array([0, 0, 0, 1, 0, 0, 0, 1, 0]);
393
+ return world.allocSharedRef<'MeshAsset', MeshAsset>('MeshAsset', {
394
+ kind: 'mesh',
395
+ vertices: new Float32Array([
396
+ 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,
397
+ 1, 0, 0, 0, 0,
398
+ ]),
399
+ indices: new Uint16Array([0, 1, 2]),
400
+ attributes: { position: positions },
401
+ aabb: new Float32Array([0, 0, 0, 1, 1, 1]),
402
+ submeshes: [
403
+ {
404
+ indexOffset: 0,
405
+ indexCount: 3,
406
+ vertexCount: 36,
407
+ topology: 'triangle-list',
408
+ materialSlot: 0,
409
+ },
410
+ ],
411
+
412
+ materialSlots: [{ slotName: 'Default' }],
413
+ });
414
+ }
415
+
416
+ function spawnCamera(world: World): void {
417
+ world
418
+ .spawn(
419
+ { component: Transform, data: { ...identity(), pos: [0, 0, 5] } },
420
+ {
421
+ component: Camera,
422
+ data: {
423
+ fov: Math.PI / 4,
424
+ aspect: 1,
425
+ near: 0.1,
426
+ far: 100,
427
+ projection: 0,
428
+ left: -1,
429
+ right: 1,
430
+ bottom: -1,
431
+ top: 1,
432
+ },
433
+ },
434
+ )
435
+ .unwrap();
436
+ }
437
+
438
+ describe('SpriteRegionOverride compose 9-slice (M4 / w17b, AC-14)', () => {
439
+ it('(1) entity without override uses asset-side region', () => {
440
+ const world = new World();
441
+ const assets = new AssetRegistry(makeShaderRegistryWithSprite());
442
+ const mesh = registerSpriteMesh(world);
443
+ const matHandle = world.allocSharedRef<'MaterialAsset', MaterialAsset>('MaterialAsset', {
444
+ kind: 'material',
445
+ passes: [SPRITE_PASS],
446
+ values: { region: [0.1, 0.1, 0.8, 0.8] },
447
+ });
448
+ spawnCamera(world);
449
+ world
450
+ .spawn(
451
+ { component: Transform, data: identity() },
452
+ { component: MeshFilter, data: { assetHandle: mesh } },
453
+ { component: MeshRenderer, data: { materials: [matHandle] } },
454
+ )
455
+ .unwrap();
456
+ propagateTransforms(world);
457
+ const frame = extractFrame(world, prepareExtractContext(world, { assets }));
458
+ // feat-20260625 M3 / w12: sprite filter is now materialShaderId, not
459
+ // shadingModel; region lands on paramSnapshot.region (D-8 / AC-07).
460
+ const sprite = frame.renderables.find(
461
+ (r) => r.material.materialShaderId === 'forgeax::sprite',
462
+ );
463
+ expect(sprite).toBeDefined();
464
+ expect(sprite?.material.paramSnapshot?.region).toEqual([0.1, 0.1, 0.8, 0.8]);
465
+ });
466
+
467
+ it('(2) entity with override uses the override region in the snapshot', () => {
468
+ const world = new World();
469
+ const assets = new AssetRegistry(makeShaderRegistryWithSprite());
470
+ const mesh = registerSpriteMesh(world);
471
+ const matHandle = world.allocSharedRef<'MaterialAsset', MaterialAsset>('MaterialAsset', {
472
+ kind: 'material',
473
+ passes: [SPRITE_PASS],
474
+ values: { region: [0.0, 0.0, 1.0, 1.0] },
475
+ });
476
+ spawnCamera(world);
477
+ world
478
+ .spawn(
479
+ { component: Transform, data: identity() },
480
+ { component: MeshFilter, data: { assetHandle: mesh } },
481
+ { component: MeshRenderer, data: { materials: [matHandle] } },
482
+ {
483
+ component: SpriteRegionOverride,
484
+ data: { region: new Float32Array([0.0, 0.0, 0.5, 1.0]) },
485
+ },
486
+ )
487
+ .unwrap();
488
+ propagateTransforms(world);
489
+ const frame = extractFrame(world, prepareExtractContext(world, { assets }));
490
+ const sprite = frame.renderables.find(
491
+ (r) => r.material.materialShaderId === 'forgeax::sprite',
492
+ );
493
+ expect(sprite).toBeDefined();
494
+ // The override displaces the asset-side [0, 0, 1, 1] region with the
495
+ // half-width [0, 0, 0.5, 1] override, so downstream consumers (record
496
+ // stage UBO write, 9-slice anchor budget) see the override values via
497
+ // paramSnapshot.region (post-w12).
498
+ expect(sprite?.material.paramSnapshot?.region).toEqual([0, 0, 0.5, 1]);
499
+ });
500
+
501
+ it('(3) two entities sharing one material diverge on effective region (compose with 9-slice)', () => {
502
+ const world = new World();
503
+ const assets = new AssetRegistry(makeShaderRegistryWithSprite());
504
+ const mesh = registerSpriteMesh(world);
505
+ const matHandle = world.allocSharedRef<'MaterialAsset', MaterialAsset>('MaterialAsset', {
506
+ kind: 'material',
507
+ passes: [SPRITE_PASS],
508
+ values: {
509
+ region: [0.0, 0.0, 1.0, 1.0],
510
+ slicesAndMode: [0.25, 0.25, 0.25, 0.25],
511
+ },
512
+ });
513
+ spawnCamera(world);
514
+ // entity A: no override, sees the asset-side region.
515
+ world
516
+ .spawn(
517
+ { component: Transform, data: { ...identity(), pos: [-1, 0, 0] } },
518
+ { component: MeshFilter, data: { assetHandle: mesh } },
519
+ { component: MeshRenderer, data: { materials: [matHandle] } },
520
+ )
521
+ .unwrap();
522
+ // entity B: half-width override, sees the override region.
523
+ world
524
+ .spawn(
525
+ { component: Transform, data: { ...identity(), pos: [1, 0, 0] } },
526
+ { component: MeshFilter, data: { assetHandle: mesh } },
527
+ { component: MeshRenderer, data: { materials: [matHandle] } },
528
+ {
529
+ component: SpriteRegionOverride,
530
+ data: { region: new Float32Array([0.0, 0.0, 0.5, 1.0]) },
531
+ },
532
+ )
533
+ .unwrap();
534
+ propagateTransforms(world);
535
+ const frame = extractFrame(world, prepareExtractContext(world, { assets }));
536
+ const sprites = frame.renderables.filter(
537
+ (r) => r.material.materialShaderId === 'forgeax::sprite',
538
+ );
539
+ expect(sprites.length).toBe(2);
540
+ const regions = sprites.map((s) => s.material.paramSnapshot?.region);
541
+ // One renderable lands on the asset-side region; the other lands on the
542
+ // override. Order is archetype-walk order (we check the set, not which is
543
+ // which, so the test stays robust to archetype iteration order).
544
+ expect(regions).toContainEqual([0, 0, 1, 1]);
545
+ expect(regions).toContainEqual([0, 0, 0.5, 1]);
546
+ // Slices ride through unchanged on both renderables -- slicesAndMode
547
+ // stretch encoding (positive .w, plan-strategy D-3); effective region
548
+ // governs UV sampling, not the slices unit.
549
+ for (const s of sprites) {
550
+ expect(s.material.paramSnapshot?.slicesAndMode).toEqual([0.25, 0.25, 0.25, 0.25]);
551
+ }
552
+ });
553
+ });
554
+ }
555
+
556
+ // --- from render-system-record-sprite-ubo-bytes.test.ts ---
557
+ {
558
+ // feat-20260625-refactor-sprite-as-transparent-mesh M3 / w8 (TDD red).
559
+ //
560
+ // Sprite UBO bytes via the GENERIC paramSnapshot writer (M1 / w3
561
+ // applyParamSnapshotToUbo). The sprite path consumes the same
562
+ // derive(uboLayout).entries-driven overlay every other paramSchema-bound
563
+ // material uses, so the 4 vec4 sprite layout (colorTint / region /
564
+ // pivotAndSize / slicesAndMode) is produced by `applyParamSnapshotToUbo`
565
+ // walking a sprite-shaped paramSchema -- not by the legacy
566
+ // `buildSpriteMaterialUboPayload` POD helper.
567
+ //
568
+ // Plan anchors:
569
+ // - plan-strategy section 2 D-2 / D-6: generic std140 writer is the
570
+ // single UBO write path; sprite-specific builder is ablated in w13.
571
+ // - plan-strategy section 5.3 #1: 4-vec4 std140 layout byte correctness.
572
+ // - requirements AC-03: buildSpriteMaterialUboPayload export does not
573
+ // survive M3 (closed-loop reverse-grep gate).
574
+ //
575
+ // Red phase: the legacy helper still ships; we assert here that the new
576
+ // production path (generic writer + sprite-shaped paramSnapshot) produces
577
+ // the expected bytes AND that `buildSpriteMaterialUboPayload` is no longer
578
+ // exported. Both flip green when w13 lands.
579
+
580
+ const mod = recordModule as unknown as {
581
+ applyParamSnapshotToUbo?: (
582
+ payload: ArrayBuffer | Uint8Array,
583
+ paramSchema: readonly ParamSchemaEntry[] | undefined,
584
+ paramSnapshot:
585
+ | Readonly<Record<string, number | readonly number[] | string | undefined>>
586
+ | undefined,
587
+ ) => void;
588
+ buildSpriteMaterialUboPayload?: unknown;
589
+ };
590
+
591
+ // Sprite UBO layout (matches sprite.wgsl post-w11 unit-quad struct: four
592
+ // vec4 fields, total 64 B; the writer fills the leading 64 B of the
593
+ // STANDARD_PBR_UBO_SIZE-sized payload buffer the runtime reuses).
594
+ const SPRITE_UBO_SCHEMA: readonly ParamSchemaEntry[] = [
595
+ { name: 'colorTint', type: 'vec4', default: [1, 1, 1, 1] },
596
+ { name: 'region', type: 'vec4', default: [0, 0, 1, 1] },
597
+ { name: 'pivotAndSize', type: 'vec4', default: [0.5, 0.5, 1, 1] },
598
+ { name: 'slicesAndMode', type: 'vec4', default: [0, 0, 0, 0] },
599
+ ];
600
+
601
+ function makeSpriteSnapshot(opts: {
602
+ colorTintAlpha?: number;
603
+ region?: readonly [number, number, number, number];
604
+ pivot?: readonly [number, number];
605
+ slices?: readonly [number, number, number, number];
606
+ }): {
607
+ colorTint: readonly [number, number, number, number];
608
+ region: readonly [number, number, number, number];
609
+ pivotAndSize: readonly [number, number, number, number];
610
+ slicesAndMode: readonly [number, number, number, number];
611
+ } {
612
+ const pivot = opts.pivot ?? [0.5, 0.5];
613
+ return {
614
+ colorTint: [1, 1, 1, opts.colorTintAlpha ?? 1] as const,
615
+ region: opts.region ?? ([0, 0, 1, 1] as const),
616
+ // Unit quad: pivotAndSize.zw=(1,1) is the placeholder slot the shader
617
+ // no longer reads -- world scale flows entirely through worldFromLocal
618
+ // (plan-strategy section 2 D-6).
619
+ pivotAndSize: [pivot[0], pivot[1], 1, 1] as const,
620
+ slicesAndMode: (opts.slices ?? ([0, 0, 0, 0] as const)) as readonly [
621
+ number,
622
+ number,
623
+ number,
624
+ number,
625
+ ],
626
+ };
627
+ }
628
+
629
+ describe('sprite Material UBO bytes via generic writer (M3 / w8)', () => {
630
+ it('grep: buildSpriteMaterialUboPayload is no longer exported from render-system-record (AC-03)', () => {
631
+ expect(mod.buildSpriteMaterialUboPayload).toBeUndefined();
632
+ });
633
+
634
+ it('(1) no slices -> 64 B leading bytes; slot 3 = [0,0,0,0]; slots 0..2 = identity defaults', () => {
635
+ if (typeof mod.applyParamSnapshotToUbo !== 'function') {
636
+ throw new Error('applyParamSnapshotToUbo not exported');
637
+ }
638
+ // 80 B buffer mirrors the per-entity slot stride the runtime allocates
639
+ // (STANDARD_PBR_UBO_SIZE). The sprite writer only touches the leading
640
+ // 64 B; trailing bytes stay zero on a fresh ArrayBuffer.
641
+ const buf = new ArrayBuffer(80);
642
+ const snap = makeSpriteSnapshot({});
643
+ mod.applyParamSnapshotToUbo(buf, SPRITE_UBO_SCHEMA, snap);
644
+ const f32 = new Float32Array(buf);
645
+ // colorTint slot 0 - default identity (1,1,1,1).
646
+ expect(Array.from(f32.slice(0, 4))).toEqual([1, 1, 1, 1]);
647
+ // region slot 1 - identity (no flip applied at this layer).
648
+ expect(Array.from(f32.slice(4, 8))).toEqual([0, 0, 1, 1]);
649
+ // pivotAndSize slot 2 - pivot=(0.5,0.5); .zw=(1,1) dead-slot placeholder.
650
+ expect(Array.from(f32.slice(8, 12))).toEqual([0.5, 0.5, 1, 1]);
651
+ // slicesAndMode slot 3 - no slices, all zero.
652
+ expect(Array.from(f32.slice(12, 16))).toEqual([0, 0, 0, 0]);
653
+ // Trailing 16 B left untouched (overlay semantics, charter P4).
654
+ expect(Array.from(f32.slice(16, 20))).toEqual([0, 0, 0, 0]);
655
+ });
656
+
657
+ it('(2) stretch slices [.25,.25,.25,.25] -> slot 3 verbatim; slots 0..2 byte-stable vs baseline', () => {
658
+ if (typeof mod.applyParamSnapshotToUbo !== 'function') {
659
+ throw new Error('applyParamSnapshotToUbo not exported');
660
+ }
661
+ const baseBuf = new ArrayBuffer(80);
662
+ mod.applyParamSnapshotToUbo(baseBuf, SPRITE_UBO_SCHEMA, makeSpriteSnapshot({}));
663
+ const stretchBuf = new ArrayBuffer(80);
664
+ mod.applyParamSnapshotToUbo(
665
+ stretchBuf,
666
+ SPRITE_UBO_SCHEMA,
667
+ makeSpriteSnapshot({ slices: [0.25, 0.25, 0.25, 0.25] }),
668
+ );
669
+ const baseF32 = new Float32Array(baseBuf);
670
+ const stretchF32 = new Float32Array(stretchBuf);
671
+ // First 48 B (slots 0..2, 12 floats) byte-stable across slices presence
672
+ // (plan-strategy section D-7 isolation: 9-slice expressions live only in
673
+ // slot 3).
674
+ expect(Array.from(stretchF32.slice(0, 12))).toEqual(Array.from(baseF32.slice(0, 12)));
675
+ // Slot 3 verbatim copy of slicesAndMode (sliceMode=0 stretch).
676
+ expect(Array.from(stretchF32.slice(12, 16))).toEqual([0.25, 0.25, 0.25, 0.25]);
677
+ });
678
+
679
+ it('(3) tile slicesAndMode [.25,.25,.25,-.25] -> slot 3 carries sentinel (w negative)', () => {
680
+ if (typeof mod.applyParamSnapshotToUbo !== 'function') {
681
+ throw new Error('applyParamSnapshotToUbo not exported');
682
+ }
683
+ const buf = new ArrayBuffer(80);
684
+ // Extract folds sliceMode=1 into a negative slicesAndMode.w (plan-
685
+ // strategy D-3 sentinel); record writes the snapshot verbatim.
686
+ mod.applyParamSnapshotToUbo(
687
+ buf,
688
+ SPRITE_UBO_SCHEMA,
689
+ makeSpriteSnapshot({ slices: [0.25, 0.25, 0.25, -0.25] }),
690
+ );
691
+ const baseBuf = new ArrayBuffer(80);
692
+ mod.applyParamSnapshotToUbo(baseBuf, SPRITE_UBO_SCHEMA, makeSpriteSnapshot({}));
693
+ const f32 = new Float32Array(buf);
694
+ const baseF32 = new Float32Array(baseBuf);
695
+ // First 48 B still byte-identical to baseline across slot 3 changes.
696
+ expect(Array.from(f32.slice(0, 12))).toEqual(Array.from(baseF32.slice(0, 12)));
697
+ // Slot 3 carries the tile sentinel.
698
+ expect(f32[12]).toBe(0.25);
699
+ expect(f32[13]).toBe(0.25);
700
+ expect(f32[14]).toBe(0.25);
701
+ expect(f32[15]).toBe(-0.25);
702
+ });
703
+
704
+ it('flipped region [0.5,0,-0.5,1] -> slot 1 carries flip-folded coords verbatim (D-8)', () => {
705
+ if (typeof mod.applyParamSnapshotToUbo !== 'function') {
706
+ throw new Error('applyParamSnapshotToUbo not exported');
707
+ }
708
+ const buf = new ArrayBuffer(80);
709
+ // Extract folds flipX into region (region.x += region.z; region.z = -region.z).
710
+ // Starting from identity region [0,0,1,1], flipX yields [1,0,-1,1]; the
711
+ // snapshot reaches the writer with the fold already applied.
712
+ mod.applyParamSnapshotToUbo(
713
+ buf,
714
+ SPRITE_UBO_SCHEMA,
715
+ makeSpriteSnapshot({ region: [1, 0, -1, 1] }),
716
+ );
717
+ const f32 = new Float32Array(buf);
718
+ expect(Array.from(f32.slice(4, 8))).toEqual([1, 0, -1, 1]);
719
+ });
720
+ });
721
+ }
722
+
723
+ // ─── from feat-20260609 M2: dispatch selector filtering ───
724
+ {
725
+ // feat-20260609-pipeline-driven-pass-selector-shadowcaster-via-mat M2
726
+ // T-005: dispatch entry selector filtering unit tests.
727
+ //
728
+ // AC-03: selector { LightMode: ['Forward'] } filters to only Forward entries.
729
+ // AC-04: selector { LightMode: ['ShadowCaster'] } filters to only matching entity.
730
+ // AC-18: multiple ShadowCaster passes per entity -> filtered count = N.
731
+ //
732
+ // Anchors: plan-strategy section 5.3 key tests; requirements AC-03/AC-04/AC-18.
733
+
734
+ function makeDispatchEntry(
735
+ renderableIndex: number,
736
+ passIndex: number,
737
+ tags: Record<string, string>,
738
+ ): DispatchEntry {
739
+ return {
740
+ entityIndex: renderableIndex,
741
+ materialHandle: 1,
742
+ renderableIndex,
743
+ passIndex,
744
+ queue: 2000,
745
+ layer: 0,
746
+ tags,
747
+ renderState: undefined,
748
+ defines: undefined,
749
+ vertexEntry: undefined,
750
+ fragmentEntry: undefined,
751
+ materialShaderId: 'forgeax::default-unlit',
752
+ paramSnapshot: undefined,
753
+ };
754
+ }
755
+
756
+ describe('dispatch entry selector filtering (M2, T-005)', () => {
757
+ it('AC-03: empty selector returns all dispatch entries unchanged', () => {
758
+ if (typeof filterDispatchBySelector !== 'function') {
759
+ throw new Error('filterDispatchBySelector not exported yet');
760
+ }
761
+ const dispatch: DispatchEntry[] = [
762
+ makeDispatchEntry(0, 0, { LightMode: 'Forward' }),
763
+ makeDispatchEntry(1, 0, { LightMode: 'ShadowCaster' }),
764
+ ];
765
+ const result = filterDispatchBySelector(dispatch, {});
766
+ expect(result.length).toBe(2);
767
+ });
768
+
769
+ it('AC-03: selector { LightMode: [Forward] } returns only Forward entries', () => {
770
+ if (typeof filterDispatchBySelector !== 'function') {
771
+ throw new Error('filterDispatchBySelector not exported yet');
772
+ }
773
+ const dispatch: DispatchEntry[] = [
774
+ makeDispatchEntry(0, 0, { LightMode: 'Forward' }),
775
+ makeDispatchEntry(1, 0, { LightMode: 'ShadowCaster' }),
776
+ makeDispatchEntry(2, 0, { LightMode: 'Forward' }),
777
+ ];
778
+ const result = filterDispatchBySelector(dispatch, { LightMode: ['Forward'] });
779
+ expect(result.length).toBe(2);
780
+ for (const e of result) {
781
+ expect(e.tags.LightMode).toBe('Forward');
782
+ }
783
+ });
784
+
785
+ it('AC-04: selector { LightMode: [ShadowCaster] } returns only ShadowCaster entries', () => {
786
+ if (typeof filterDispatchBySelector !== 'function') {
787
+ throw new Error('filterDispatchBySelector not exported yet');
788
+ }
789
+ const dispatch: DispatchEntry[] = [
790
+ makeDispatchEntry(0, 0, { LightMode: 'Forward' }),
791
+ makeDispatchEntry(1, 0, { LightMode: 'ShadowCaster' }),
792
+ makeDispatchEntry(2, 0, { LightMode: 'Forward' }),
793
+ ];
794
+ const result = filterDispatchBySelector(dispatch, { LightMode: ['ShadowCaster'] });
795
+ expect(result.length).toBe(1);
796
+ expect(result[0]?.tags.LightMode).toBe('ShadowCaster');
797
+ expect(result[0]?.renderableIndex).toBe(1);
798
+ });
799
+
800
+ it('AC-18: multiple ShadowCaster passes per entity -> filtered count equals N', () => {
801
+ if (typeof filterDispatchBySelector !== 'function') {
802
+ throw new Error('filterDispatchBySelector not exported yet');
803
+ }
804
+ // Entity at renderableIndex=0 has 3 ShadowCaster passes.
805
+ const dispatch: DispatchEntry[] = [
806
+ makeDispatchEntry(0, 0, { LightMode: 'ShadowCaster' }),
807
+ makeDispatchEntry(0, 1, { LightMode: 'ShadowCaster' }),
808
+ makeDispatchEntry(0, 2, { LightMode: 'ShadowCaster' }),
809
+ makeDispatchEntry(1, 0, { LightMode: 'Forward' }),
810
+ ];
811
+ const result = filterDispatchBySelector(dispatch, { LightMode: ['ShadowCaster'] });
812
+ expect(result.length).toBe(3);
813
+ for (const e of result) {
814
+ expect(e.tags.LightMode).toBe('ShadowCaster');
815
+ expect(e.renderableIndex).toBe(0);
816
+ }
817
+ // Each pass has a distinct passIndex.
818
+ expect(result[0]?.passIndex).toBe(0);
819
+ expect(result[1]?.passIndex).toBe(1);
820
+ expect(result[2]?.passIndex).toBe(2);
821
+ });
822
+
823
+ it('selector with non-matching key returns empty array', () => {
824
+ if (typeof filterDispatchBySelector !== 'function') {
825
+ throw new Error('filterDispatchBySelector not exported yet');
826
+ }
827
+ const dispatch: DispatchEntry[] = [makeDispatchEntry(0, 0, { LightMode: 'Forward' })];
828
+ const result = filterDispatchBySelector(dispatch, { RenderType: ['Opaque'] });
829
+ expect(result.length).toBe(0);
830
+ });
831
+
832
+ it('selector matching on multiple keys requires both to match', () => {
833
+ if (typeof filterDispatchBySelector !== 'function') {
834
+ throw new Error('filterDispatchBySelector not exported yet');
835
+ }
836
+ const dispatch: DispatchEntry[] = [
837
+ makeDispatchEntry(0, 0, { LightMode: 'Forward', RenderQueue: 'Geometry' }),
838
+ makeDispatchEntry(1, 0, { LightMode: 'Forward', RenderQueue: 'Transparent' }),
839
+ ];
840
+ const result = filterDispatchBySelector(dispatch, {
841
+ LightMode: ['Forward'],
842
+ RenderQueue: ['Geometry'],
843
+ });
844
+ expect(result.length).toBe(1);
845
+ expect(result[0]?.renderableIndex).toBe(0);
846
+ });
847
+
848
+ it('entry with empty tags is filtered out by a non-empty selector', () => {
849
+ if (typeof filterDispatchBySelector !== 'function') {
850
+ throw new Error('filterDispatchBySelector not exported yet');
851
+ }
852
+ const dispatch: DispatchEntry[] = [
853
+ makeDispatchEntry(0, 0, {}),
854
+ makeDispatchEntry(1, 0, { LightMode: 'Forward' }),
855
+ ];
856
+ const result = filterDispatchBySelector(dispatch, { LightMode: ['Forward'] });
857
+ expect(result.length).toBe(1);
858
+ expect(result[0]?.renderableIndex).toBe(1);
859
+ });
860
+ });
861
+ }
862
+
863
+ // --- from feat-20260625-refactor-sprite-as-transparent-mesh M1 / w1 ---
864
+ {
865
+ // Generic std140 UBO writer driven by derive(paramSchema).uboLayout.entries.
866
+ //
867
+ // Plan anchors:
868
+ // - plan-strategy section 2 D-2: the inline overlay at render-system-
869
+ // record.ts:4334-4374 is generalised to read every uboLayout.entries
870
+ // std140 offset and write paramSnapshot[name] (numeric / numeric[])
871
+ // into the payload buffer. standard-pbr stays byte-identical to the
872
+ // pre-feat buildPbrMaterialUboPayload output (covered by the w2 suite
873
+ // appended below); sprite-shaped layouts (4 x vec4 stride 16) are
874
+ // honoured per offset/size declared by derive.
875
+ // - requirements AC-03 sets buildSpriteMaterialUboPayload ablation as
876
+ // the M3 deliverable; M1 only establishes that the generic writer can
877
+ // produce sprite-shaped bytes from paramSnapshot alone (no asset get,
878
+ // gate R-H from plan-strategy section 5.6 prevents reading internals).
879
+ //
880
+ // The suite reads render-system-record dynamically so it sits RED until
881
+ // w3 lands. The expected helper is a pure function with the signature:
882
+ //
883
+ // applyParamSnapshotToUbo(
884
+ // payload: ArrayBuffer,
885
+ // paramSchema: readonly ParamSchemaEntry[] | undefined,
886
+ // paramSnapshot: { readonly [name: string]: number | readonly number[] }
887
+ // | undefined,
888
+ // ): void
889
+ //
890
+ // The helper is internal (export-for-test) and walks
891
+ // derive(paramSchema).uboLayout.entries to dispatch each field by its
892
+ // std140 offset. Vec / color entries pull numeric arrays from the
893
+ // snapshot; f32 / i32 / u32 entries pull scalars.
894
+
895
+ const mod = recordModule as unknown as {
896
+ applyParamSnapshotToUbo?: (
897
+ payload: ArrayBuffer | Uint8Array,
898
+ paramSchema: readonly ParamSchemaEntry[] | undefined,
899
+ paramSnapshot:
900
+ | Readonly<Record<string, number | readonly number[] | string | undefined>>
901
+ | undefined,
902
+ ) => void;
903
+ };
904
+
905
+ // Sprite-shaped paramSchema: four vec4 entries at std140 offsets
906
+ // 0 / 16 / 32 / 48; total = 64 bytes rounded to 16 (still 64).
907
+ // This mirrors the post-ablation sprite.wgsl UBO layout (M3 / w11) but
908
+ // the writer must already accept it in M1 so M3 only needs to wire it in.
909
+ const SPRITE_SHAPED_SCHEMA: readonly ParamSchemaEntry[] = [
910
+ { name: 'colorTint', type: 'vec4', default: [1, 1, 1, 1] },
911
+ { name: 'region', type: 'vec4', default: [0, 0, 1, 1] },
912
+ { name: 'pivotAndSize', type: 'vec4', default: [0.5, 0.5, 1, 1] },
913
+ { name: 'slicesAndMode', type: 'vec4', default: [0, 0, 0, 0] },
914
+ ];
915
+
916
+ describe('applyParamSnapshotToUbo: sprite-shaped 4x vec4 std140 (M1 / w1)', () => {
917
+ it('export: applyParamSnapshotToUbo helper is exported from render-system-record', () => {
918
+ expect(typeof mod.applyParamSnapshotToUbo).toBe('function');
919
+ });
920
+
921
+ it('derive(uboLayout).entries produces four vec4 entries at offsets 0/16/32/48', () => {
922
+ const { uboLayout } = derive(SPRITE_SHAPED_SCHEMA);
923
+ expect(uboLayout.entries.length).toBe(4);
924
+ expect(uboLayout.entries[0]).toMatchObject({
925
+ name: 'colorTint',
926
+ offset: 0,
927
+ size: 16,
928
+ type: 'vec4',
929
+ });
930
+ expect(uboLayout.entries[1]).toMatchObject({
931
+ name: 'region',
932
+ offset: 16,
933
+ size: 16,
934
+ type: 'vec4',
935
+ });
936
+ expect(uboLayout.entries[2]).toMatchObject({
937
+ name: 'pivotAndSize',
938
+ offset: 32,
939
+ size: 16,
940
+ type: 'vec4',
941
+ });
942
+ expect(uboLayout.entries[3]).toMatchObject({
943
+ name: 'slicesAndMode',
944
+ offset: 48,
945
+ size: 16,
946
+ type: 'vec4',
947
+ });
948
+ // std140 struct alignment rounds up to 16 -- 64 already aligns.
949
+ expect(uboLayout.totalBytes).toBe(64);
950
+ });
951
+
952
+ it('writes each vec4 at its std140 offset (byte-exact, no f32-slot heuristic)', () => {
953
+ if (typeof mod.applyParamSnapshotToUbo !== 'function') {
954
+ throw new Error('helper not exported yet (red phase)');
955
+ }
956
+ const buf = new ArrayBuffer(64);
957
+ // Values chosen to round-trip exactly in IEEE 754 f32 (powers of 2 /
958
+ // sums thereof) so we can assert with toEqual; the non-exact-encoding
959
+ // values (0.1 / 0.2 / 0.3) would lose the trailing bit on f32 storage
960
+ // and force a toBeCloseTo, weakening the byte-level signal.
961
+ const snapshot = {
962
+ colorTint: [0.25, 0.5, 0.75, 1] as readonly number[],
963
+ region: [0.125, 0.25, 0.5, 0.75] as readonly number[],
964
+ pivotAndSize: [0.5, 0.5, 2, 4] as readonly number[],
965
+ slicesAndMode: [0.125, 0.125, 0.125, -0.125] as readonly number[],
966
+ };
967
+ mod.applyParamSnapshotToUbo(buf, SPRITE_SHAPED_SCHEMA, snapshot);
968
+ const f32 = new Float32Array(buf);
969
+ // colorTint at offset 0
970
+ expect(Array.from(f32.slice(0, 4))).toEqual([0.25, 0.5, 0.75, 1]);
971
+ // region at offset 16 (f32 index 4)
972
+ expect(Array.from(f32.slice(4, 8))).toEqual([0.125, 0.25, 0.5, 0.75]);
973
+ // pivotAndSize at offset 32 (f32 index 8)
974
+ expect(Array.from(f32.slice(8, 12))).toEqual([0.5, 0.5, 2, 4]);
975
+ // slicesAndMode at offset 48 (f32 index 12) -- carries 9-slice tile
976
+ // sentinel via negative w (verbatim from snapshot, charter P3 explicit
977
+ // signal vs the legacy spriteFields.sliceMode side channel).
978
+ expect(Array.from(f32.slice(12, 16))).toEqual([0.125, 0.125, 0.125, -0.125]);
979
+ });
980
+
981
+ it('missing snapshot fields leave existing bytes untouched (overlay semantics)', () => {
982
+ if (typeof mod.applyParamSnapshotToUbo !== 'function') {
983
+ throw new Error('helper not exported yet (red phase)');
984
+ }
985
+ const buf = new ArrayBuffer(64);
986
+ const pre = new Float32Array(buf);
987
+ // Seed the buffer with sentinel pre-values; the writer must only
988
+ // touch offsets it has snapshot values for (charter P4 -- the writer
989
+ // is an overlay, not a clear-and-fill stamp).
990
+ pre[0] = 9;
991
+ pre[1] = 9;
992
+ pre[2] = 9;
993
+ pre[3] = 9;
994
+ pre[12] = 7;
995
+ pre[13] = 7;
996
+ pre[14] = 7;
997
+ pre[15] = 7;
998
+ const snapshot = {
999
+ region: [0.125, 0.25, 0.5, 0.75] as readonly number[],
1000
+ pivotAndSize: [0.5, 0.5, 2, 4] as readonly number[],
1001
+ };
1002
+ mod.applyParamSnapshotToUbo(buf, SPRITE_SHAPED_SCHEMA, snapshot);
1003
+ const f32 = new Float32Array(buf);
1004
+ // colorTint (no snapshot value) preserved
1005
+ expect(Array.from(f32.slice(0, 4))).toEqual([9, 9, 9, 9]);
1006
+ // region written
1007
+ expect(Array.from(f32.slice(4, 8))).toEqual([0.125, 0.25, 0.5, 0.75]);
1008
+ // pivotAndSize written
1009
+ expect(Array.from(f32.slice(8, 12))).toEqual([0.5, 0.5, 2, 4]);
1010
+ // slicesAndMode (no snapshot value) preserved
1011
+ expect(Array.from(f32.slice(12, 16))).toEqual([7, 7, 7, 7]);
1012
+ });
1013
+
1014
+ it('no-op when paramSchema or paramSnapshot is undefined', () => {
1015
+ if (typeof mod.applyParamSnapshotToUbo !== 'function') {
1016
+ throw new Error('helper not exported yet (red phase)');
1017
+ }
1018
+ const buf = new ArrayBuffer(64);
1019
+ const pre = new Float32Array(buf);
1020
+ for (let i = 0; i < 16; i++) pre[i] = i + 1;
1021
+ const snapshot = { colorTint: [0.5, 0.5, 0.5, 0.5] as readonly number[] };
1022
+ mod.applyParamSnapshotToUbo(buf, undefined, snapshot);
1023
+ // Schema undefined -> no writes.
1024
+ for (let i = 0; i < 16; i++) expect(pre[i]).toBe(i + 1);
1025
+ mod.applyParamSnapshotToUbo(buf, SPRITE_SHAPED_SCHEMA, undefined);
1026
+ // Snapshot undefined -> no writes.
1027
+ for (let i = 0; i < 16; i++) expect(pre[i]).toBe(i + 1);
1028
+ });
1029
+ });
1030
+ }
1031
+
1032
+ // --- from feat-20260625-refactor-sprite-as-transparent-mesh M1 / w2 ---
1033
+ {
1034
+ // standard-pbr regression: the generic writer over the engine-shipped
1035
+ // default-standard-pbr.material.json paramSchema (15 numeric entries
1036
+ // std140-packed into 96 B) must produce byte-identical output to
1037
+ // buildPbrMaterialUboPayload. This is the gate plan-strategy section 2
1038
+ // D-2 sets so the generic writer can replace the inline overlay without
1039
+ // perturbing PBR rendering. RED until w3 lands the helper.
1040
+
1041
+ const mod = recordModule as unknown as {
1042
+ applyParamSnapshotToUbo?: (
1043
+ payload: ArrayBuffer | Uint8Array,
1044
+ paramSchema: readonly ParamSchemaEntry[] | undefined,
1045
+ paramSnapshot:
1046
+ | Readonly<Record<string, number | readonly number[] | string | undefined>>
1047
+ | undefined,
1048
+ ) => void;
1049
+ buildPbrMaterialUboPayload?: (material: MaterialSnapshot) => Uint8Array;
1050
+ };
1051
+
1052
+ // Mirror the engine-shipped default-standard-pbr.material.json schema
1053
+ // (the on-disk SSOT; inline here to keep the unit suite free of asset
1054
+ // loads).
1055
+ const STANDARD_PBR_SCHEMA: readonly ParamSchemaEntry[] = [
1056
+ { name: 'baseColor', type: 'color', default: [1, 1, 1, 1] },
1057
+ { name: 'metallic', type: 'f32', default: 0 },
1058
+ { name: 'roughness', type: 'f32', default: 0.5 },
1059
+ { name: 'metallicChannel', type: 'f32', default: 2 },
1060
+ { name: 'roughnessChannel', type: 'f32', default: 1 },
1061
+ { name: 'aoChannel', type: 'f32', default: 0 },
1062
+ { name: 'extraChannel', type: 'f32', default: 0 },
1063
+ { name: 'emissive', type: 'vec3', default: [0, 0, 0] },
1064
+ { name: 'emissiveIntensity', type: 'f32', default: 0 },
1065
+ { name: 'occlusionStrength', type: 'f32', default: 1 },
1066
+ { name: 'alphaCutoff', type: 'f32', default: 0 },
1067
+ { name: 'clearcoat', type: 'f32', default: 0 },
1068
+ { name: 'clearcoatRoughness', type: 'f32', default: 0.5 },
1069
+ { name: 'specularTint', type: 'vec3', default: [1, 1, 1] },
1070
+ ];
1071
+
1072
+ describe('applyParamSnapshotToUbo: standard-pbr byte-identical via derive (M1 / w2)', () => {
1073
+ it('derive(standard-pbr).uboLayout matches the 96 B authored numeric layout', () => {
1074
+ const { uboLayout } = derive(STANDARD_PBR_SCHEMA);
1075
+ expect(uboLayout.totalBytes).toBe(96);
1076
+ const byName = new Map(uboLayout.entries.map((e) => [e.name, e]));
1077
+ expect(byName.get('baseColor')?.offset).toBe(0);
1078
+ expect(byName.get('metallic')?.offset).toBe(16);
1079
+ expect(byName.get('roughness')?.offset).toBe(20);
1080
+ expect(byName.get('metallicChannel')?.offset).toBe(24);
1081
+ expect(byName.get('roughnessChannel')?.offset).toBe(28);
1082
+ expect(byName.get('aoChannel')?.offset).toBe(32);
1083
+ expect(byName.get('extraChannel')?.offset).toBe(36);
1084
+ // vec3 aligns to 16: cursor at 40 rounds up to 48.
1085
+ expect(byName.get('emissive')?.offset).toBe(48);
1086
+ expect(byName.get('emissiveIntensity')?.offset).toBe(60);
1087
+ expect(byName.get('occlusionStrength')?.offset).toBe(64);
1088
+ expect(byName.get('alphaCutoff')?.offset).toBe(68);
1089
+ expect(byName.get('clearcoat')?.offset).toBe(72);
1090
+ expect(byName.get('clearcoatRoughness')?.offset).toBe(76);
1091
+ expect(byName.get('specularTint')?.offset).toBe(80);
1092
+ });
1093
+
1094
+ it('generic writer over standard-pbr snapshot equals buildPbrMaterialUboPayload bytes', () => {
1095
+ if (typeof mod.applyParamSnapshotToUbo !== 'function') {
1096
+ throw new Error('helper not exported yet (red phase)');
1097
+ }
1098
+ if (typeof mod.buildPbrMaterialUboPayload !== 'function') {
1099
+ throw new Error('buildPbrMaterialUboPayload missing -- baseline broken');
1100
+ }
1101
+ // Construct a snapshot equivalent to the explicit material fields the
1102
+ // legacy helper consumes (baseColor / metallic / roughness / emissive /
1103
+ // emissiveIntensity / occlusionStrength + the channelMap defaults the
1104
+ // helper hard-codes).
1105
+ const baseColor = [0.5, 0.6, 0.7, 1] as readonly number[];
1106
+ const metallic = 0.1;
1107
+ const roughness = 0.4;
1108
+ const emissive = [0, 0, 0] as readonly number[];
1109
+ const emissiveIntensity = 0;
1110
+ const occlusionStrength = 1;
1111
+ const material = {
1112
+ baseColor,
1113
+ metallic,
1114
+ roughness,
1115
+ materialShaderId: 'forgeax::default-standard-pbr',
1116
+ paramSnapshot: undefined,
1117
+ emissive,
1118
+ emissiveIntensity,
1119
+ occlusionStrength,
1120
+ alphaCutoff: 0,
1121
+ clearcoat: 0,
1122
+ clearcoatRoughness: 0.5,
1123
+ specularTint: [1, 1, 1],
1124
+ } as unknown as MaterialSnapshot;
1125
+ const baseline = mod.buildPbrMaterialUboPayload(material);
1126
+ // Construct the generic-writer output: start from the same explicit
1127
+ // PBR baseline, then apply the generic overlay over a paramSnapshot
1128
+ // carrying the same values. The overlay must not perturb any byte
1129
+ // the explicit baseline already wrote (charter P4 -- one writer, one
1130
+ // byte layout).
1131
+ const candidate = mod.buildPbrMaterialUboPayload(material);
1132
+ const snapshot = {
1133
+ baseColor,
1134
+ metallic,
1135
+ roughness,
1136
+ metallicChannel: 2,
1137
+ roughnessChannel: 1,
1138
+ aoChannel: 0,
1139
+ extraChannel: 0,
1140
+ emissive,
1141
+ emissiveIntensity,
1142
+ occlusionStrength,
1143
+ specularTint: [1, 1, 1],
1144
+ };
1145
+ mod.applyParamSnapshotToUbo(candidate, STANDARD_PBR_SCHEMA, snapshot);
1146
+ expect(new Uint8Array(candidate)).toEqual(new Uint8Array(baseline));
1147
+ });
1148
+
1149
+ it('overlays non-opaque standard-PBR baseColor alpha over the opaque baseline', () => {
1150
+ if (typeof mod.applyParamSnapshotToUbo !== 'function') {
1151
+ throw new Error('helper not exported yet (red phase)');
1152
+ }
1153
+ if (typeof mod.buildPbrMaterialUboPayload !== 'function') {
1154
+ throw new Error('buildPbrMaterialUboPayload missing -- baseline broken');
1155
+ }
1156
+ const material = {
1157
+ baseColor: [0.5, 0.6, 0.7, 0.5],
1158
+ metallic: 0,
1159
+ roughness: 0.5,
1160
+ materialShaderId: 'forgeax::default-standard-pbr',
1161
+ paramSnapshot: undefined,
1162
+ } as unknown as MaterialSnapshot;
1163
+ const payload = mod.buildPbrMaterialUboPayload(material);
1164
+ // The explicit baseline is intentionally opaque; importer-supplied
1165
+ // paramSnapshot values must overwrite all four baseColor components.
1166
+ expect(new Float32Array(payload.buffer, payload.byteOffset, payload.byteLength / 4)[3]).toBe(
1167
+ 1,
1168
+ );
1169
+ mod.applyParamSnapshotToUbo(payload, STANDARD_PBR_SCHEMA, {
1170
+ baseColor: [0.5, 0.6, 0.7, 0.5],
1171
+ });
1172
+ expect(new Float32Array(payload.buffer, payload.byteOffset, payload.byteLength / 4)[3]).toBe(
1173
+ 0.5,
1174
+ );
1175
+ });
1176
+
1177
+ it('honours f32 slot offsets independently (slot 1 width = 4 floats from metallic..extraChannel)', () => {
1178
+ if (typeof mod.applyParamSnapshotToUbo !== 'function') {
1179
+ throw new Error('helper not exported yet (red phase)');
1180
+ }
1181
+ const buf = new ArrayBuffer(80);
1182
+ // f32-exact values (powers of 2 / sums) so we can assert toEqual on
1183
+ // every slot; toBeCloseTo would let off-by-one-bit drifts hide.
1184
+ const snapshot = {
1185
+ baseColor: [0.125, 0.25, 0.5, 1] as readonly number[],
1186
+ metallic: 0.5,
1187
+ roughness: 0.25,
1188
+ metallicChannel: 2,
1189
+ roughnessChannel: 1,
1190
+ aoChannel: 0,
1191
+ extraChannel: 0,
1192
+ emissive: [0.5, 0.5, 0.5] as readonly number[],
1193
+ emissiveIntensity: 1.5,
1194
+ occlusionStrength: 0.75,
1195
+ };
1196
+ mod.applyParamSnapshotToUbo(buf, STANDARD_PBR_SCHEMA, snapshot);
1197
+ const f32 = new Float32Array(buf);
1198
+ // baseColor occupies slot 0 (offset 0).
1199
+ expect(Array.from(f32.slice(0, 4))).toEqual([0.125, 0.25, 0.5, 1]);
1200
+ // 4 f32 channels packed at offset 16..36 -- distinct slots, not folded
1201
+ // into one vec4 (regression against legacy "first two f32 only"
1202
+ // overlay at render-system-record.ts:4334-4374).
1203
+ expect(f32[4]).toBe(0.5);
1204
+ expect(f32[5]).toBe(0.25);
1205
+ expect(f32[6]).toBe(2);
1206
+ expect(f32[7]).toBe(1);
1207
+ expect(f32[8]).toBe(0);
1208
+ expect(f32[9]).toBe(0);
1209
+ // emissive vec3 at offset 48 (f32 index 12).
1210
+ expect(Array.from(f32.slice(12, 15))).toEqual([0.5, 0.5, 0.5]);
1211
+ expect(f32[15]).toBe(1.5);
1212
+ expect(f32[16]).toBe(0.75);
1213
+ });
1214
+ });
1215
+ }
1216
+
1217
+ // --- from feat-20260625-refactor-sprite-as-transparent-mesh M2 / w4 ---
1218
+ {
1219
+ // AC-05: a MaterialSnapshot carrying transparent:true on its source pass
1220
+ // (recorded as MaterialSnapshot.transparent === true) must drive the LDR
1221
+ // split-pass decision and the blend-state lookup INDEPENDENTLY of the
1222
+ // shader id. The shader here is a generic 'forgeax::unlit-test' stub -- if
1223
+ // splitLdrSprite or the blend-state helper still keys on
1224
+ // 'forgeax::sprite', this suite stays red.
1225
+ //
1226
+ // Plan anchors:
1227
+ // - requirements AC-05 (transparent decoupled from sprite shader)
1228
+ // - plan-strategy section 2 D-3 (MaterialSnapshot.transparent
1229
+ // transparent passthrough; split reads snapshot, not pass)
1230
+ // - plan-strategy section 5.4 falsification check (the inline comment
1231
+ // below records the variant: flipping blend src=one/dst=zero turns
1232
+ // premultiplied alpha into hard-edge composition and fails the dawn
1233
+ // msaa-sprite-pixel-diff baseline -- documenting that the pixel
1234
+ // diff smoke is sensitive to blend factor correctness).
1235
+ //
1236
+ // The `computeSplitLdrSprite` helper is added by w7. The dynamic-import
1237
+ // sentinel keeps this suite red until it lands.
1238
+ //
1239
+ // feat-20260626-sprite-transparent-collapse M2 / M4: the sibling
1240
+ // `resolveTransparentBlendState` helper has been removed — premultiplied-
1241
+ // alpha blend factors are now pinned to the exported
1242
+ // `SPRITE_PREMULTIPLIED_ALPHA_BLEND` constant (the SSOT for asset-side
1243
+ // `renderState.blend`); the tests that exercised the resolver are deleted
1244
+ // and the falsification check now lives on the dawn pixel-diff
1245
+ // baseline + the asset-side constant export check.
1246
+
1247
+ type DispatchEntryLike = {
1248
+ readonly source: {
1249
+ readonly material: MaterialSnapshot;
1250
+ readonly materials: readonly MaterialSnapshot[];
1251
+ };
1252
+ };
1253
+
1254
+ const spriteMod = spriteRecordModule as unknown as {
1255
+ computeSplitLdrSprite?: (
1256
+ validatedOrdered: readonly (DispatchEntryLike | undefined)[],
1257
+ tonemapActive: boolean,
1258
+ ) => boolean;
1259
+ };
1260
+ const mod = recordModule as unknown as {
1261
+ isEntityFullyTransparent?: (source: {
1262
+ material: MaterialSnapshot;
1263
+ materials?: readonly MaterialSnapshot[];
1264
+ }) => boolean;
1265
+ entityHasTransparentSubmesh?: (source: {
1266
+ material: MaterialSnapshot;
1267
+ materials?: readonly MaterialSnapshot[];
1268
+ }) => boolean;
1269
+ };
1270
+
1271
+ function makeTransparentSnap(opts: {
1272
+ transparent?: boolean;
1273
+ materialShaderId?: string;
1274
+ }): MaterialSnapshot {
1275
+ return {
1276
+ baseColor: [1, 1, 1, 1] as const,
1277
+ metallic: 0,
1278
+ roughness: 1,
1279
+ materialShaderId: opts.materialShaderId ?? 'forgeax::unlit-test',
1280
+ paramSnapshot: {},
1281
+ ...(opts.transparent === true && { transparent: true }),
1282
+ } as unknown as MaterialSnapshot;
1283
+ }
1284
+
1285
+ function makeValidatedEntry(material: MaterialSnapshot): DispatchEntryLike {
1286
+ return { source: { material, materials: [material] } };
1287
+ }
1288
+
1289
+ describe('transparent decouples from sprite shader (M2 / w4, AC-05)', () => {
1290
+ it('export: computeSplitLdrSprite helper is exported from render-system-record', () => {
1291
+ expect(typeof spriteMod.computeSplitLdrSprite).toBe('function');
1292
+ });
1293
+
1294
+ it('non-sprite shader with transparent:true triggers LDR split', () => {
1295
+ if (typeof spriteMod.computeSplitLdrSprite !== 'function') {
1296
+ throw new Error('computeSplitLdrSprite helper not exported yet (red phase)');
1297
+ }
1298
+ const transparentSnap = makeTransparentSnap({
1299
+ transparent: true,
1300
+ materialShaderId: 'forgeax::unlit-test',
1301
+ });
1302
+ const opaqueSnap = makeTransparentSnap({
1303
+ transparent: false,
1304
+ materialShaderId: 'forgeax::unlit-test',
1305
+ });
1306
+ // tonemapActive=false (LDR path) + a transparent entry in the
1307
+ // validated list -- the split decision must trip even though the
1308
+ // shader is not forgeax::sprite (AC-05 proves the decoupling).
1309
+ const split = spriteMod.computeSplitLdrSprite(
1310
+ [makeValidatedEntry(opaqueSnap), makeValidatedEntry(transparentSnap)],
1311
+ false,
1312
+ );
1313
+ expect(split).toBe(true);
1314
+ });
1315
+
1316
+ it('LDR split stays false on pure-opaque non-sprite list', () => {
1317
+ if (typeof spriteMod.computeSplitLdrSprite !== 'function') {
1318
+ throw new Error('computeSplitLdrSprite helper not exported yet (red phase)');
1319
+ }
1320
+ const opaqueA = makeTransparentSnap({
1321
+ transparent: false,
1322
+ materialShaderId: 'forgeax::unlit-test',
1323
+ });
1324
+ const opaqueB = makeTransparentSnap({
1325
+ transparent: false,
1326
+ materialShaderId: 'forgeax::default-standard-pbr',
1327
+ });
1328
+ const split = spriteMod.computeSplitLdrSprite(
1329
+ [makeValidatedEntry(opaqueA), makeValidatedEntry(opaqueB)],
1330
+ false,
1331
+ );
1332
+ expect(split).toBe(false);
1333
+ });
1334
+
1335
+ it('HDR path (tonemapActive=true) suppresses split even with transparent entries', () => {
1336
+ if (typeof spriteMod.computeSplitLdrSprite !== 'function') {
1337
+ throw new Error('computeSplitLdrSprite helper not exported yet (red phase)');
1338
+ }
1339
+ const transparentSnap = makeTransparentSnap({
1340
+ transparent: true,
1341
+ materialShaderId: 'forgeax::unlit-test',
1342
+ });
1343
+ const split = spriteMod.computeSplitLdrSprite([makeValidatedEntry(transparentSnap)], true);
1344
+ expect(split).toBe(false);
1345
+ });
1346
+
1347
+ it('M3 / w13: legacy sprite material without transparent:true does NOT trigger split (union arm deleted)', () => {
1348
+ if (typeof spriteMod.computeSplitLdrSprite !== 'function') {
1349
+ throw new Error('computeSplitLdrSprite helper not exported yet (red phase)');
1350
+ }
1351
+ // feat-20260625-refactor-sprite-as-transparent-mesh M3 / w13 (D-3):
1352
+ // the M2 union (`transparent || shadingModel === 'sprite'`) is gone
1353
+ // — transparent is the single SSOT. A legacy snapshot carrying
1354
+ // `materialShaderId: 'forgeax::sprite'` but no `transparent: true`
1355
+ // declaration MUST NOT trigger the LDR split (forces explicit
1356
+ // transparent attribution on the asset side; AC-04 / AC-05).
1357
+ const legacySpriteNoTransparent = {
1358
+ baseColor: [1, 1, 1, 1] as const,
1359
+ metallic: 0,
1360
+ roughness: 1,
1361
+ materialShaderId: 'forgeax::sprite',
1362
+ paramSnapshot: {},
1363
+ } as unknown as MaterialSnapshot;
1364
+ const splitWithoutTransparent = spriteMod.computeSplitLdrSprite(
1365
+ [makeValidatedEntry(legacySpriteNoTransparent)],
1366
+ false,
1367
+ );
1368
+ expect(splitWithoutTransparent).toBe(false);
1369
+
1370
+ // Same snapshot with `transparent: true` declared on the pass DOES
1371
+ // trigger the split (the only path that should).
1372
+ const spriteTransparent = {
1373
+ ...legacySpriteNoTransparent,
1374
+ transparent: true,
1375
+ } as unknown as MaterialSnapshot;
1376
+ const splitWithTransparent = spriteMod.computeSplitLdrSprite(
1377
+ [makeValidatedEntry(spriteTransparent)],
1378
+ false,
1379
+ );
1380
+ expect(splitWithTransparent).toBe(true);
1381
+ });
1382
+
1383
+ it('transparent:true resolves premultiplied-alpha blend state (src=one/dst=one-minus-src-alpha)', () => {
1384
+ // feat-20260626-sprite-transparent-collapse M2 / M4: the
1385
+ // `resolveTransparentBlendState` resolver is gone — premultiplied-alpha
1386
+ // factors are pinned to the exported `SPRITE_PREMULTIPLIED_ALPHA_BLEND`
1387
+ // constant (the asset-side SSOT for `renderState.blend`). This test
1388
+ // pins the constant's shape so a future maintainer flipping the color
1389
+ // blend to {src:'one', dst:'zero'} (opaque copy) still trips a unit
1390
+ // gate before the dawn msaa-sprite-pixel-diff smoke catches the
1391
+ // regression. AC-10 reaches the human arbiter through both paths.
1392
+ expect(SPRITE_PREMULTIPLIED_ALPHA_BLEND.color.srcFactor).toBe('one');
1393
+ expect(SPRITE_PREMULTIPLIED_ALPHA_BLEND.color.dstFactor).toBe('one-minus-src-alpha');
1394
+ expect(SPRITE_PREMULTIPLIED_ALPHA_BLEND.alpha.srcFactor).toBe('one');
1395
+ expect(SPRITE_PREMULTIPLIED_ALPHA_BLEND.alpha.dstFactor).toBe('one-minus-src-alpha');
1396
+ });
1397
+
1398
+ // feat-city-glb Bug 5 (per-submesh transparency): the split must trip when
1399
+ // a NON-zero submesh material is transparent even though the entity-level
1400
+ // material[0] is opaque (the crosswalk case: opaque road submesh[0] +
1401
+ // BLEND decal submesh[1] on one multi-material mesh).
1402
+ it('per-submesh: split trips when only a non-zero submesh is transparent', () => {
1403
+ if (typeof spriteMod.computeSplitLdrSprite !== 'function') {
1404
+ throw new Error('computeSplitLdrSprite helper not exported yet');
1405
+ }
1406
+ const opaque0 = makeTransparentSnap({
1407
+ transparent: false,
1408
+ materialShaderId: 'forgeax::default-standard-pbr',
1409
+ });
1410
+ const transparent1 = makeTransparentSnap({
1411
+ transparent: true,
1412
+ materialShaderId: 'forgeax::default-standard-pbr',
1413
+ });
1414
+ // Entity-level material (=materials[0]) is opaque; materials[1] transparent.
1415
+ const mixedEntry: DispatchEntryLike = {
1416
+ source: { material: opaque0, materials: [opaque0, transparent1] },
1417
+ };
1418
+ expect(spriteMod.computeSplitLdrSprite([mixedEntry], false)).toBe(true);
1419
+ });
1420
+
1421
+ it('per-submesh: split stays false when every submesh material is opaque', () => {
1422
+ if (typeof spriteMod.computeSplitLdrSprite !== 'function') {
1423
+ throw new Error('computeSplitLdrSprite helper not exported yet');
1424
+ }
1425
+ const opaque0 = makeTransparentSnap({
1426
+ transparent: false,
1427
+ materialShaderId: 'forgeax::default-standard-pbr',
1428
+ });
1429
+ const opaque1 = makeTransparentSnap({
1430
+ transparent: false,
1431
+ materialShaderId: 'forgeax::default-standard-pbr',
1432
+ });
1433
+ const opaqueMixed: DispatchEntryLike = {
1434
+ source: { material: opaque0, materials: [opaque0, opaque1] },
1435
+ };
1436
+ expect(spriteMod.computeSplitLdrSprite([opaqueMixed], false)).toBe(false);
1437
+ });
1438
+
1439
+ it('isEntityFullyTransparent / entityHasTransparentSubmesh classify mixed meshes', () => {
1440
+ if (
1441
+ typeof mod.isEntityFullyTransparent !== 'function' ||
1442
+ typeof mod.entityHasTransparentSubmesh !== 'function'
1443
+ ) {
1444
+ throw new Error('per-submesh transparency helpers not exported yet');
1445
+ }
1446
+ const opaque = makeTransparentSnap({ transparent: false });
1447
+ const transp = makeTransparentSnap({ transparent: true });
1448
+ // Mixed: opaque[0] + transparent[1] -> has a transparent submesh but not
1449
+ // fully transparent (must draw in BOTH geometry pass and blend sub-pass).
1450
+ const mixed = { material: opaque, materials: [opaque, transp] };
1451
+ expect(mod.entityHasTransparentSubmesh(mixed)).toBe(true);
1452
+ expect(mod.isEntityFullyTransparent(mixed)).toBe(false);
1453
+ // Fully transparent (e.g. a sprite / 4.3 window quad).
1454
+ const full = { material: transp, materials: [transp] };
1455
+ expect(mod.entityHasTransparentSubmesh(full)).toBe(true);
1456
+ expect(mod.isEntityFullyTransparent(full)).toBe(true);
1457
+ // Fully opaque.
1458
+ const none = { material: opaque, materials: [opaque, opaque] };
1459
+ expect(mod.entityHasTransparentSubmesh(none)).toBe(false);
1460
+ expect(mod.isEntityFullyTransparent(none)).toBe(false);
1461
+ });
1462
+ });
1463
+ }
1464
+
1465
+ // ─── bug-20260622-tilemap-ysort-transparent-sort-modes-followup M2 m2-1 ───
1466
+ //
1467
+ // AC-04 (error signal SSOT) + AC-05 (LAYER_Y footY ordering) + R-2
1468
+ // (mode=DISTANCE without a cameraPos falls back to the original list).
1469
+ // `sortTransparentDispatch` itself is render-system.ts-private (closure-
1470
+ // scoped helper invoked once per draw); the public-surface contract sits on
1471
+ // `transparentSortEntries` (same primary `layer ASC` + secondary mode-formula
1472
+ // + identical fallback semantics). Asserting against the exported helper
1473
+ // gives byte-exact coverage of the 4-mode dispatch SSOT without instantiating
1474
+ // the full RenderSystem (which would need a real GPU device + canvas, far
1475
+ // outside unit-test scope; plan-strategy R-2 mitigation).
1476
+ // 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).
1477
+ {
1478
+ // --- AC-04: setTransparentSortConfig mode=99 -> Result.err with 4 SSOT fields ---
1479
+ describe('AC-04 setTransparentSortConfig mode=99 returns Result.err (KV untouched)', () => {
1480
+ it('Result.err carries code/expected/hint/detail + KV resource is NOT inserted', async () => {
1481
+ const { setTransparentSortConfig, TRANSPARENT_SORT_CONFIG_KEY } = await import(
1482
+ '../../../render/src/systems/transparent-sort-config'
1483
+ );
1484
+ const world = new World();
1485
+ // Pre-check: resource MUST be absent before the rejected call.
1486
+ expect(world.hasResource(TRANSPARENT_SORT_CONFIG_KEY)).toBe(false);
1487
+
1488
+ const r = setTransparentSortConfig(world, { mode: 99, yzAlpha: 1.0 });
1489
+ expect(r.ok).toBe(false);
1490
+ if (r.ok) return;
1491
+ // The 4 SSOT fields locked by plan-strategy D-4. The math symbol
1492
+ // U+2208 ("is element of") goes through the ASCII `∈` escape
1493
+ // so this source file stays English-only per AGENTS.md §Conventions
1494
+ // (mirrors EXPECTED_EXPECTED in systems.unit.test.ts).
1495
+ expect(r.error.code).toBe('resource-invalid-value');
1496
+ expect(r.error.expected).toBe('mode ∈ {0, 1, 2, 3}');
1497
+ expect(r.error.hint).toBe('0=layer-z, 1=layer-y, 2=layer-yz, 3=distance');
1498
+ expect((r.error.detail as { receivedMode: number }).receivedMode).toBe(99);
1499
+ // The KV resource MUST stay un-inserted after a rejected write
1500
+ // (charter P3 -- structured failure, never silently coerce).
1501
+ expect(world.hasResource(TRANSPARENT_SORT_CONFIG_KEY)).toBe(false);
1502
+ });
1503
+ });
1504
+
1505
+ // --- AC-05: mode=LAYER_Y, 3 entities footY=10/20/30 same layer -> 30/20/10 ---
1506
+ describe('AC-05 LAYER_Y footY ordering (same layer, deeper foot draws later)', () => {
1507
+ it('footY=10/20/30 same layer -> output order footY=30/20/10 (back-to-front)', async () => {
1508
+ const { setTransparentSortConfig, TRANSPARENT_SORT_MODE_LAYER_Y } = await import(
1509
+ '../../../render/src/systems/transparent-sort-config'
1510
+ );
1511
+ const { transparentSortEntries } = await import('../systems/transparent-sort');
1512
+ const world = new World();
1513
+ setTransparentSortConfig(world, {
1514
+ mode: TRANSPARENT_SORT_MODE_LAYER_Y,
1515
+ yzAlpha: 1.0,
1516
+ }).unwrap();
1517
+
1518
+ // footY = posY - pivotY * sizeY. Pin pivotY=0 + sizeY=1 so footY === posY
1519
+ // -- the 10/20/30 numbers land verbatim, no algebra to second-guess.
1520
+ // mode=1 sortValue = -footY; ASC over [-10, -20, -30] yields entries
1521
+ // ordered footY=30, 20, 10 (deepest foot draws last = back-to-front).
1522
+ const entries = [
1523
+ {
1524
+ entityIndex: 0,
1525
+ materialHandle: 0,
1526
+ layer: 0,
1527
+ posX: 0,
1528
+ posY: 10,
1529
+ posZ: 0,
1530
+ pivotY: 0,
1531
+ sizeY: 1,
1532
+ },
1533
+ {
1534
+ entityIndex: 1,
1535
+ materialHandle: 0,
1536
+ layer: 0,
1537
+ posX: 0,
1538
+ posY: 20,
1539
+ posZ: 0,
1540
+ pivotY: 0,
1541
+ sizeY: 1,
1542
+ },
1543
+ {
1544
+ entityIndex: 2,
1545
+ materialHandle: 0,
1546
+ layer: 0,
1547
+ posX: 0,
1548
+ posY: 30,
1549
+ posZ: 0,
1550
+ pivotY: 0,
1551
+ sizeY: 1,
1552
+ },
1553
+ ];
1554
+ const sorted = transparentSortEntries(entries, world);
1555
+ expect(sorted.map((e) => e.posY)).toEqual([30, 20, 10]);
1556
+ });
1557
+ });
1558
+
1559
+ // --- R-2: mode=DISTANCE without a cameraPos returns entries unchanged ---
1560
+ // plan-strategy R-2 risk mitigation: sortTransparentDispatch mode=3 +
1561
+ // cameras[0] missing must keep the original dispatch list (PR #401
1562
+ // baseline). transparentSortEntries surfaces the same fallback through
1563
+ // the public helper -- the 2-arg call (no cameraPos) falls through to
1564
+ // the mode=0 posZ formula; with posZ pinned constant the sort becomes
1565
+ // a no-op preserving insertion order (charter P3 deterministic output).
1566
+ describe('R-2 mode=DISTANCE + cameraPos absent preserves insertion order', () => {
1567
+ it('transparentSortEntries(entries, world) with mode=3 + no cameraPos = insertion order', async () => {
1568
+ const { setTransparentSortConfig, TRANSPARENT_SORT_MODE_DISTANCE } = await import(
1569
+ '../../../render/src/systems/transparent-sort-config'
1570
+ );
1571
+ const { transparentSortEntries } = await import('../systems/transparent-sort');
1572
+ const world = new World();
1573
+ setTransparentSortConfig(world, {
1574
+ mode: TRANSPARENT_SORT_MODE_DISTANCE,
1575
+ yzAlpha: 1.0,
1576
+ }).unwrap();
1577
+
1578
+ // posZ pinned identical so the fallback (posZ ASC) is a no-op; the
1579
+ // assertion guards both R-2 (no crash, no reorder) and "fallback is
1580
+ // deterministic" together.
1581
+ const entries = [
1582
+ {
1583
+ entityIndex: 7,
1584
+ materialHandle: 0,
1585
+ layer: 0,
1586
+ posX: 1,
1587
+ posY: 0,
1588
+ posZ: 0,
1589
+ pivotY: 0.5,
1590
+ sizeY: 1,
1591
+ },
1592
+ {
1593
+ entityIndex: 8,
1594
+ materialHandle: 0,
1595
+ layer: 0,
1596
+ posX: 2,
1597
+ posY: 0,
1598
+ posZ: 0,
1599
+ pivotY: 0.5,
1600
+ sizeY: 1,
1601
+ },
1602
+ {
1603
+ entityIndex: 9,
1604
+ materialHandle: 0,
1605
+ layer: 0,
1606
+ posX: 3,
1607
+ posY: 0,
1608
+ posZ: 0,
1609
+ pivotY: 0.5,
1610
+ sizeY: 1,
1611
+ },
1612
+ ];
1613
+ const sorted = transparentSortEntries(entries, world);
1614
+ expect(sorted.map((e) => e.entityIndex)).toEqual([7, 8, 9]);
1615
+ });
1616
+ });
1617
+ }
1618
+
1619
+ // --- tweak-20260701 M2 AC-02: zero-light warning polarity ---
1620
+ {
1621
+ // AC-02: the zero-light black-screen warning must still fire for
1622
+ // builtin PBR/standard materials and remain silent for custom, unlit /
1623
+ // sprite / default materials. Custom shader lighting contracts are authored
1624
+ // by the shader and cannot be inferred by this generic diagnostic.
1625
+ //
1626
+ // The test imports `isLitMaterialSnapshot` from the production
1627
+ // render-system-record module — the same function that recordFrame's
1628
+ // zero-light warning gate (line ~2122) calls to compute
1629
+ // hasStandardMaterial. A test-local copy would not anchor to the
1630
+ // production path.
1631
+
1632
+ function makeSnap(materialShaderId: string | undefined): MaterialSnapshot {
1633
+ return {
1634
+ baseColor: [1, 1, 1, 1] as const,
1635
+ metallic: 0,
1636
+ roughness: 1,
1637
+ materialShaderId,
1638
+ paramSnapshot: undefined,
1639
+ } as unknown as MaterialSnapshot;
1640
+ }
1641
+
1642
+ describe('AC-02: zero-light warning polarity', () => {
1643
+ it('standard / PBR material (materialShaderId !== undefined, !== default-unlit) -> lit', () => {
1644
+ const snap = makeSnap('forgeax::default-standard-pbr');
1645
+ expect(isLitMaterialSnapshot(snap)).toBe(true);
1646
+ });
1647
+
1648
+ it('custom shader material -> not lit (custom lighting contract)', () => {
1649
+ const snap = makeSnap('my-custom-shader');
1650
+ expect(isLitMaterialSnapshot(snap)).toBe(false);
1651
+ });
1652
+
1653
+ it('unlit material (materialShaderId === forgeax::default-unlit) -> not lit (no warn)', () => {
1654
+ const snap = makeSnap('forgeax::default-unlit');
1655
+ expect(isLitMaterialSnapshot(snap)).toBe(false);
1656
+ });
1657
+
1658
+ it('sprite material (materialShaderId === forgeax::sprite) -> not lit (no warn)', () => {
1659
+ const snap = makeSnap('forgeax::sprite');
1660
+ expect(isLitMaterialSnapshot(snap)).toBe(false);
1661
+ });
1662
+
1663
+ it('default material (materialShaderId === undefined) -> not lit (no warn)', () => {
1664
+ const snap = makeSnap(undefined);
1665
+ expect(isLitMaterialSnapshot(snap)).toBe(false);
1666
+ });
1667
+ });
1668
+ }