@forgeax/engine-runtime 0.1.23 → 0.1.24

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 (368) hide show
  1. package/README.md +15 -6
  2. package/dist/__tests__/advance-animation-player.cases.unit.test.d.ts +2 -0
  3. package/dist/__tests__/advance-animation-player.cases.unit.test.d.ts.map +1 -0
  4. package/dist/__tests__/asset-registry-aabb.cases.unit.test.d.ts +2 -0
  5. package/dist/__tests__/asset-registry-aabb.cases.unit.test.d.ts.map +1 -0
  6. package/dist/__tests__/asset-registry-bootstrap.cases.unit.test.d.ts +2 -0
  7. package/dist/__tests__/asset-registry-bootstrap.cases.unit.test.d.ts.map +1 -0
  8. package/dist/__tests__/asset-registry-builtin-nineslice.cases.unit.test.d.ts +2 -0
  9. package/dist/__tests__/asset-registry-builtin-nineslice.cases.unit.test.d.ts.map +1 -0
  10. package/dist/__tests__/asset-registry-d9-tile-sampler-soft-warn.cases.unit.test.d.ts +2 -0
  11. package/dist/__tests__/asset-registry-d9-tile-sampler-soft-warn.cases.unit.test.d.ts.map +1 -0
  12. package/dist/__tests__/asset-registry-guid.cases.unit.test.d.ts +2 -0
  13. package/dist/__tests__/asset-registry-guid.cases.unit.test.d.ts.map +1 -0
  14. package/dist/__tests__/asset-registry-material-validate.cases.unit.test.d.ts +2 -0
  15. package/dist/__tests__/asset-registry-material-validate.cases.unit.test.d.ts.map +1 -0
  16. package/dist/__tests__/asset-registry-mesh-fail-fast.cases.unit.test.d.ts +2 -0
  17. package/dist/__tests__/asset-registry-mesh-fail-fast.cases.unit.test.d.ts.map +1 -0
  18. package/dist/__tests__/asset-registry-scene.cases.unit.test.d.ts +2 -0
  19. package/dist/__tests__/asset-registry-scene.cases.unit.test.d.ts.map +1 -0
  20. package/dist/__tests__/asset-registry-sprite-slices-validate.cases.unit.test.d.ts +2 -0
  21. package/dist/__tests__/asset-registry-sprite-slices-validate.cases.unit.test.d.ts.map +1 -0
  22. package/dist/__tests__/asset-renderer-assembly.cases.unit.test.d.ts +2 -0
  23. package/dist/__tests__/asset-renderer-assembly.cases.unit.test.d.ts.map +1 -0
  24. package/dist/__tests__/asset.cases.unit.test.d.ts +2 -0
  25. package/dist/__tests__/asset.cases.unit.test.d.ts.map +1 -0
  26. package/dist/__tests__/bloom-gating.cases.unit.test.d.ts +2 -0
  27. package/dist/__tests__/bloom-gating.cases.unit.test.d.ts.map +1 -0
  28. package/dist/__tests__/builtin-guid-ssot.cases.unit.test.d.ts +2 -0
  29. package/dist/__tests__/builtin-guid-ssot.cases.unit.test.d.ts.map +1 -0
  30. package/dist/__tests__/builtin-pack.cases.unit.test.d.ts +2 -0
  31. package/dist/__tests__/builtin-pack.cases.unit.test.d.ts.map +1 -0
  32. package/dist/__tests__/camera-antialias.cases.unit.test.d.ts +2 -0
  33. package/dist/__tests__/camera-antialias.cases.unit.test.d.ts.map +1 -0
  34. package/dist/__tests__/camera-bloom.cases.unit.test.d.ts +2 -0
  35. package/dist/__tests__/camera-bloom.cases.unit.test.d.ts.map +1 -0
  36. package/dist/__tests__/camera-clear-schema.cases.unit.test.d.ts +2 -0
  37. package/dist/__tests__/camera-clear-schema.cases.unit.test.d.ts.map +1 -0
  38. package/dist/__tests__/camera-ortho.cases.unit.test.d.ts +2 -0
  39. package/dist/__tests__/camera-ortho.cases.unit.test.d.ts.map +1 -0
  40. package/dist/__tests__/cluster-binner.cases.unit.test.d.ts +2 -0
  41. package/dist/__tests__/cluster-binner.cases.unit.test.d.ts.map +1 -0
  42. package/dist/__tests__/create-renderer-fallback-shader-manifest.cases.unit.test.d.ts +2 -0
  43. package/dist/__tests__/create-renderer-fallback-shader-manifest.cases.unit.test.d.ts.map +1 -0
  44. package/dist/__tests__/create-renderer-uniform-fallback.cases.unit.test.d.ts +2 -0
  45. package/dist/__tests__/create-renderer-uniform-fallback.cases.unit.test.d.ts.map +1 -0
  46. package/dist/__tests__/cubemap-upload.cases.unit.test.d.ts +2 -0
  47. package/dist/__tests__/cubemap-upload.cases.unit.test.d.ts.map +1 -0
  48. package/dist/__tests__/dev-import-transport.cases.unit.test.d.ts +2 -0
  49. package/dist/__tests__/dev-import-transport.cases.unit.test.d.ts.map +1 -0
  50. package/dist/__tests__/engine-metrics.cases.unit.test.d.ts +2 -0
  51. package/dist/__tests__/engine-metrics.cases.unit.test.d.ts.map +1 -0
  52. package/dist/__tests__/equirect-projection-error.cases.unit.test.d.ts +2 -0
  53. package/dist/__tests__/equirect-projection-error.cases.unit.test.d.ts.map +1 -0
  54. package/dist/__tests__/font-asset-load.cases.unit.test.d.ts +2 -0
  55. package/dist/__tests__/font-asset-load.cases.unit.test.d.ts.map +1 -0
  56. package/dist/__tests__/fxaa-intermediate-texture.cases.unit.test.d.ts +2 -0
  57. package/dist/__tests__/fxaa-intermediate-texture.cases.unit.test.d.ts.map +1 -0
  58. package/dist/__tests__/fxaa-pipeline.cases.unit.test.d.ts +2 -0
  59. package/dist/__tests__/fxaa-pipeline.cases.unit.test.d.ts.map +1 -0
  60. package/dist/__tests__/gpu-residency.cases.unit.test.d.ts +2 -0
  61. package/dist/__tests__/gpu-residency.cases.unit.test.d.ts.map +1 -0
  62. package/dist/__tests__/gpu-resource-release.cases.unit.test.d.ts +2 -0
  63. package/dist/__tests__/gpu-resource-release.cases.unit.test.d.ts.map +1 -0
  64. package/dist/__tests__/handle-quad.cases.unit.test.d.ts +2 -0
  65. package/dist/__tests__/handle-quad.cases.unit.test.d.ts.map +1 -0
  66. package/dist/__tests__/hdrp-bgl-slots.cases.unit.test.d.ts +2 -0
  67. package/dist/__tests__/hdrp-bgl-slots.cases.unit.test.d.ts.map +1 -0
  68. package/dist/__tests__/hdrp-grid-invalid.cases.unit.test.d.ts +2 -0
  69. package/dist/__tests__/hdrp-grid-invalid.cases.unit.test.d.ts.map +1 -0
  70. package/dist/__tests__/hdrp-index-list-overflow-once.cases.unit.test.d.ts +2 -0
  71. package/dist/__tests__/hdrp-index-list-overflow-once.cases.unit.test.d.ts.map +1 -0
  72. package/dist/__tests__/hdrp-light-budget.cases.unit.test.d.ts +2 -0
  73. package/dist/__tests__/hdrp-light-budget.cases.unit.test.d.ts.map +1 -0
  74. package/dist/__tests__/hdrp-pipeline-asset-config.cases.unit.test.d.ts +2 -0
  75. package/dist/__tests__/hdrp-pipeline-asset-config.cases.unit.test.d.ts.map +1 -0
  76. package/dist/__tests__/helpers/renderer-device-loss-dawn.d.ts +25 -0
  77. package/dist/__tests__/helpers/renderer-device-loss-dawn.d.ts.map +1 -0
  78. package/dist/__tests__/helpers/standard-material-manifest.d.ts +0 -1
  79. package/dist/__tests__/helpers/standard-material-manifest.d.ts.map +1 -1
  80. package/dist/__tests__/ibl-pipeline-cache.cases.unit.test.d.ts +2 -0
  81. package/dist/__tests__/ibl-pipeline-cache.cases.unit.test.d.ts.map +1 -0
  82. package/dist/__tests__/lazy-catalog.cases.unit.test.d.ts +2 -0
  83. package/dist/__tests__/lazy-catalog.cases.unit.test.d.ts.map +1 -0
  84. package/dist/__tests__/load-by-guid-hdr.cases.unit.test.d.ts +2 -0
  85. package/dist/__tests__/load-by-guid-hdr.cases.unit.test.d.ts.map +1 -0
  86. package/dist/__tests__/load-by-guid-prod-material-parent.cases.unit.test.d.ts +2 -0
  87. package/dist/__tests__/load-by-guid-prod-material-parent.cases.unit.test.d.ts.map +1 -0
  88. package/dist/__tests__/load-by-guid-prod.cases.unit.test.d.ts +2 -0
  89. package/dist/__tests__/load-by-guid-prod.cases.unit.test.d.ts.map +1 -0
  90. package/dist/__tests__/loader-registry.cases.unit.test.d.ts +2 -0
  91. package/dist/__tests__/loader-registry.cases.unit.test.d.ts.map +1 -0
  92. package/dist/__tests__/m7-hdrp-demo-shape.cases.unit.test.d.ts +2 -0
  93. package/dist/__tests__/m7-hdrp-demo-shape.cases.unit.test.d.ts.map +1 -0
  94. package/dist/__tests__/mipmap-formula.cases.unit.test.d.ts +2 -0
  95. package/dist/__tests__/mipmap-formula.cases.unit.test.d.ts.map +1 -0
  96. package/dist/__tests__/mipmap-pipeline-cache.cases.unit.test.d.ts +2 -0
  97. package/dist/__tests__/mipmap-pipeline-cache.cases.unit.test.d.ts.map +1 -0
  98. package/dist/__tests__/parse-asset-payload-material.cases.unit.test.d.ts +2 -0
  99. package/dist/__tests__/parse-asset-payload-material.cases.unit.test.d.ts.map +1 -0
  100. package/dist/__tests__/parse-asset-payload-texture.cases.unit.test.d.ts +2 -0
  101. package/dist/__tests__/parse-asset-payload-texture.cases.unit.test.d.ts.map +1 -0
  102. package/dist/__tests__/parse-scene-payload-refs.cases.unit.test.d.ts +2 -0
  103. package/dist/__tests__/parse-scene-payload-refs.cases.unit.test.d.ts.map +1 -0
  104. package/dist/__tests__/pass-selector.cases.unit.test.d.ts +2 -0
  105. package/dist/__tests__/pass-selector.cases.unit.test.d.ts.map +1 -0
  106. package/dist/__tests__/pipeline-builder.cases.unit.test.d.ts +2 -0
  107. package/dist/__tests__/pipeline-builder.cases.unit.test.d.ts.map +1 -0
  108. package/dist/__tests__/pipeline-cache-key-topology.cases.unit.test.d.ts +2 -0
  109. package/dist/__tests__/pipeline-cache-key-topology.cases.unit.test.d.ts.map +1 -0
  110. package/dist/__tests__/pipeline-vertex-stride-branch.cases.unit.test.d.ts +2 -0
  111. package/dist/__tests__/pipeline-vertex-stride-branch.cases.unit.test.d.ts.map +1 -0
  112. package/dist/__tests__/pipeline.cases.unit.test.d.ts +2 -0
  113. package/dist/__tests__/pipeline.cases.unit.test.d.ts.map +1 -0
  114. package/dist/__tests__/post-process-register.cases.unit.test.d.ts +2 -0
  115. package/dist/__tests__/post-process-register.cases.unit.test.d.ts.map +1 -0
  116. package/dist/__tests__/propagate-transforms.cases.unit.test.d.ts +2 -0
  117. package/dist/__tests__/propagate-transforms.cases.unit.test.d.ts.map +1 -0
  118. package/dist/__tests__/record-all-topology.cases.unit.test.d.ts +2 -0
  119. package/dist/__tests__/record-all-topology.cases.unit.test.d.ts.map +1 -0
  120. package/dist/__tests__/record-draw-branch.cases.unit.test.d.ts +2 -0
  121. package/dist/__tests__/record-draw-branch.cases.unit.test.d.ts.map +1 -0
  122. package/dist/__tests__/record-strip-index-format.cases.unit.test.d.ts +2 -0
  123. package/dist/__tests__/record-strip-index-format.cases.unit.test.d.ts.map +1 -0
  124. package/dist/__tests__/register-with-guid-rgba16float.cases.unit.test.d.ts +2 -0
  125. package/dist/__tests__/register-with-guid-rgba16float.cases.unit.test.d.ts.map +1 -0
  126. package/dist/__tests__/render-data.cases.unit.test.d.ts +2 -0
  127. package/dist/__tests__/render-data.cases.unit.test.d.ts.map +1 -0
  128. package/dist/__tests__/render-error-exhaustive.test-d.d.ts.map +1 -1
  129. package/dist/__tests__/render-query-regression.cases.unit.test.d.ts +2 -0
  130. package/dist/__tests__/render-query-regression.cases.unit.test.d.ts.map +1 -0
  131. package/dist/__tests__/render-system-record-submesh.cases.unit.test.d.ts +2 -0
  132. package/dist/__tests__/render-system-record-submesh.cases.unit.test.d.ts.map +1 -0
  133. package/dist/__tests__/render-system-record-warn-once.cases.unit.test.d.ts +2 -0
  134. package/dist/__tests__/render-system-record-warn-once.cases.unit.test.d.ts.map +1 -0
  135. package/dist/__tests__/renderer-device-loss-multi-world.integration.test.d.ts +2 -0
  136. package/dist/__tests__/renderer-device-loss-multi-world.integration.test.d.ts.map +1 -0
  137. package/dist/__tests__/renderer-device-loss-provider-feasibility.dawn.test.d.ts +2 -0
  138. package/dist/__tests__/renderer-device-loss-provider-feasibility.dawn.test.d.ts.map +1 -0
  139. package/dist/__tests__/renderer-device-loss-recovery.dawn.test.d.ts +2 -0
  140. package/dist/__tests__/renderer-device-loss-recovery.dawn.test.d.ts.map +1 -0
  141. package/dist/__tests__/renderer-draw-world.cases.unit.test.d.ts +2 -0
  142. package/dist/__tests__/renderer-draw-world.cases.unit.test.d.ts.map +1 -0
  143. package/dist/__tests__/renderer-recovery-consumer.integration.test.d.ts +2 -0
  144. package/dist/__tests__/renderer-recovery-consumer.integration.test.d.ts.map +1 -0
  145. package/dist/__tests__/renderstate-pipeline-cache.cases.unit.test.d.ts +2 -0
  146. package/dist/__tests__/renderstate-pipeline-cache.cases.unit.test.d.ts.map +1 -0
  147. package/dist/__tests__/resolve-scene-guids.cases.unit.test.d.ts +2 -0
  148. package/dist/__tests__/resolve-scene-guids.cases.unit.test.d.ts.map +1 -0
  149. package/dist/__tests__/shadow-skip-non-triangle.cases.unit.test.d.ts +2 -0
  150. package/dist/__tests__/shadow-skip-non-triangle.cases.unit.test.d.ts.map +1 -0
  151. package/dist/__tests__/skin-errors-kebab-case.cases.unit.test.d.ts +2 -0
  152. package/dist/__tests__/skin-errors-kebab-case.cases.unit.test.d.ts.map +1 -0
  153. package/dist/__tests__/skin-instances-coexist.cases.unit.test.d.ts +2 -0
  154. package/dist/__tests__/skin-instances-coexist.cases.unit.test.d.ts.map +1 -0
  155. package/dist/__tests__/skin-m2-extract-happy.cases.unit.test.d.ts +2 -0
  156. package/dist/__tests__/skin-m2-extract-happy.cases.unit.test.d.ts.map +1 -0
  157. package/dist/__tests__/skin-m2-extract-joint-error.cases.unit.test.d.ts +2 -0
  158. package/dist/__tests__/skin-m2-extract-joint-error.cases.unit.test.d.ts.map +1 -0
  159. package/dist/__tests__/skin-m2-extract-null-assets.cases.unit.test.d.ts +2 -0
  160. package/dist/__tests__/skin-m2-extract-null-assets.cases.unit.test.d.ts.map +1 -0
  161. package/dist/__tests__/skin-m2-extract-skeleton-error.cases.unit.test.d.ts +2 -0
  162. package/dist/__tests__/skin-m2-extract-skeleton-error.cases.unit.test.d.ts.map +1 -0
  163. package/dist/__tests__/skin-m3-record-cache.cases.unit.test.d.ts +2 -0
  164. package/dist/__tests__/skin-m3-record-cache.cases.unit.test.d.ts.map +1 -0
  165. package/dist/__tests__/skin-palette-extract.cases.unit.test.d.ts +2 -0
  166. package/dist/__tests__/skin-palette-extract.cases.unit.test.d.ts.map +1 -0
  167. package/dist/__tests__/skin-pipeline-routing.cases.unit.test.d.ts +2 -0
  168. package/dist/__tests__/skin-pipeline-routing.cases.unit.test.d.ts.map +1 -0
  169. package/dist/__tests__/skin.cases.unit.test.d.ts +2 -0
  170. package/dist/__tests__/skin.cases.unit.test.d.ts.map +1 -0
  171. package/dist/__tests__/skybox-extract.cases.unit.test.d.ts +2 -0
  172. package/dist/__tests__/skybox-extract.cases.unit.test.d.ts.map +1 -0
  173. package/dist/__tests__/skybox-shader-compile.cases.unit.test.d.ts +2 -0
  174. package/dist/__tests__/skybox-shader-compile.cases.unit.test.d.ts.map +1 -0
  175. package/dist/__tests__/skylight-bind-group.cases.unit.test.d.ts +2 -0
  176. package/dist/__tests__/skylight-bind-group.cases.unit.test.d.ts.map +1 -0
  177. package/dist/__tests__/skylight-component.cases.unit.test.d.ts +2 -0
  178. package/dist/__tests__/skylight-component.cases.unit.test.d.ts.map +1 -0
  179. package/dist/__tests__/skylight-fallback-path.cases.unit.test.d.ts +2 -0
  180. package/dist/__tests__/skylight-fallback-path.cases.unit.test.d.ts.map +1 -0
  181. package/dist/__tests__/skylight-pipeline-layout.cases.unit.test.d.ts +2 -0
  182. package/dist/__tests__/skylight-pipeline-layout.cases.unit.test.d.ts.map +1 -0
  183. package/dist/__tests__/skylight-solid-color-extract.cases.unit.test.d.ts +2 -0
  184. package/dist/__tests__/skylight-solid-color-extract.cases.unit.test.d.ts.map +1 -0
  185. package/dist/__tests__/sprite-animation-tick-boundary.cases.unit.test.d.ts +2 -0
  186. package/dist/__tests__/sprite-animation-tick-boundary.cases.unit.test.d.ts.map +1 -0
  187. package/dist/__tests__/sprite-animation-tick-clamp.cases.unit.test.d.ts +2 -0
  188. package/dist/__tests__/sprite-animation-tick-clamp.cases.unit.test.d.ts.map +1 -0
  189. package/dist/__tests__/sprite-animation-tick-frame-duration-negative.cases.unit.test.d.ts +2 -0
  190. package/dist/__tests__/sprite-animation-tick-frame-duration-negative.cases.unit.test.d.ts.map +1 -0
  191. package/dist/__tests__/sprite-animation-tick-frame-duration-zero.cases.unit.test.d.ts +2 -0
  192. package/dist/__tests__/sprite-animation-tick-frame-duration-zero.cases.unit.test.d.ts.map +1 -0
  193. package/dist/__tests__/sprite-animation-tick-loop.cases.unit.test.d.ts +2 -0
  194. package/dist/__tests__/sprite-animation-tick-loop.cases.unit.test.d.ts.map +1 -0
  195. package/dist/__tests__/sprite-animation-tick-override-probe.cases.unit.test.d.ts +2 -0
  196. package/dist/__tests__/sprite-animation-tick-override-probe.cases.unit.test.d.ts.map +1 -0
  197. package/dist/__tests__/sprite-animation-tick-regions-mismatch.cases.unit.test.d.ts +2 -0
  198. package/dist/__tests__/sprite-animation-tick-regions-mismatch.cases.unit.test.d.ts.map +1 -0
  199. package/dist/__tests__/storage-buffer-caps.cases.unit.test.d.ts +2 -0
  200. package/dist/__tests__/storage-buffer-caps.cases.unit.test.d.ts.map +1 -0
  201. package/dist/__tests__/systems.cases.unit.test.d.ts +2 -0
  202. package/dist/__tests__/systems.cases.unit.test.d.ts.map +1 -0
  203. package/dist/__tests__/tonemap-hdr-target.cases.unit.test.d.ts +2 -0
  204. package/dist/__tests__/tonemap-hdr-target.cases.unit.test.d.ts.map +1 -0
  205. package/dist/__tests__/tonemap-pipeline-split.cases.unit.test.d.ts +2 -0
  206. package/dist/__tests__/tonemap-pipeline-split.cases.unit.test.d.ts.map +1 -0
  207. package/dist/__tests__/tonemap.cases.unit.test.d.ts +2 -0
  208. package/dist/__tests__/tonemap.cases.unit.test.d.ts.map +1 -0
  209. package/dist/__tests__/transparent-sort-config-get.cases.unit.test.d.ts +2 -0
  210. package/dist/__tests__/transparent-sort-config-get.cases.unit.test.d.ts.map +1 -0
  211. package/dist/__tests__/transparent-sort-config-set.cases.unit.test.d.ts +2 -0
  212. package/dist/__tests__/transparent-sort-config-set.cases.unit.test.d.ts.map +1 -0
  213. package/dist/__tests__/transparent-sort.cases.unit.test.d.ts +2 -0
  214. package/dist/__tests__/transparent-sort.cases.unit.test.d.ts.map +1 -0
  215. package/dist/__tests__/upload-texture-consistency.cases.unit.test.d.ts +2 -0
  216. package/dist/__tests__/upload-texture-consistency.cases.unit.test.d.ts.map +1 -0
  217. package/dist/__tests__/verify-revisions.cases.unit.test.d.ts +2 -0
  218. package/dist/__tests__/verify-revisions.cases.unit.test.d.ts.map +1 -0
  219. package/dist/__tests__/wire-default-loaders.cases.unit.test.d.ts +2 -0
  220. package/dist/__tests__/wire-default-loaders.cases.unit.test.d.ts.map +1 -0
  221. package/dist/__tests__/zero-camera-clear-fallback.cases.unit.test.d.ts +2 -0
  222. package/dist/__tests__/zero-camera-clear-fallback.cases.unit.test.d.ts.map +1 -0
  223. package/dist/dev-import-transport.d.ts.map +1 -1
  224. package/dist/index.mjs +1 -1
  225. package/dist/index.mjs.map +1 -1
  226. package/package.json +30 -30
  227. package/src/__tests__/advance-animation-player.cases.unit.test.ts +1666 -0
  228. package/src/__tests__/asset-registry-aabb.cases.unit.test.ts +676 -0
  229. package/src/__tests__/asset-registry-bootstrap.cases.unit.test.ts +472 -0
  230. package/src/__tests__/asset-registry-builtin-nineslice.cases.unit.test.ts +481 -0
  231. package/src/__tests__/asset-registry-d9-tile-sampler-soft-warn.cases.unit.test.ts +568 -0
  232. package/src/__tests__/asset-registry-guid.cases.unit.test.ts +500 -0
  233. package/src/__tests__/asset-registry-material-validate.cases.unit.test.ts +756 -0
  234. package/src/__tests__/asset-registry-mesh-fail-fast.cases.unit.test.ts +591 -0
  235. package/src/__tests__/asset-registry-scene.cases.unit.test.ts +501 -0
  236. package/src/__tests__/asset-registry-sprite-slices-validate.cases.unit.test.ts +536 -0
  237. package/src/__tests__/asset-renderer-assembly.cases.unit.test.ts +496 -0
  238. package/src/__tests__/asset.cases.unit.test.ts +439 -0
  239. package/src/__tests__/bind-group-cache-keying.test.ts +6 -2
  240. package/src/__tests__/bloom-gating.cases.unit.test.ts +428 -0
  241. package/src/__tests__/builtin-guid-ssot.cases.unit.test.ts +472 -0
  242. package/src/__tests__/builtin-pack.cases.unit.test.ts +639 -0
  243. package/src/__tests__/builtin-shader-register-e2e.test.ts +1 -1
  244. package/src/__tests__/camera-antialias.cases.unit.test.ts +372 -0
  245. package/src/__tests__/camera-bloom.cases.unit.test.ts +396 -0
  246. package/src/__tests__/camera-clear-schema.cases.unit.test.ts +391 -0
  247. package/src/__tests__/camera-ortho.cases.unit.test.ts +484 -0
  248. package/src/__tests__/cluster-binner.cases.unit.test.ts +817 -0
  249. package/src/__tests__/collect-scene-mount-collapse.test.ts +5 -1
  250. package/src/__tests__/create-renderer-fallback-shader-manifest.cases.unit.test.ts +345 -0
  251. package/src/__tests__/create-renderer-uniform-fallback.cases.unit.test.ts +445 -0
  252. package/src/__tests__/cubemap-upload.cases.unit.test.ts +593 -0
  253. package/src/__tests__/dawn/ibl-precompute-readback.dawn.test.ts +184 -17
  254. package/src/__tests__/dawn/material-alpha.dawn.test.ts +17 -4
  255. package/src/__tests__/derive-bgl-integration.test.ts +8 -8
  256. package/src/__tests__/dev-import-transport.cases.unit.test.ts +503 -0
  257. package/src/__tests__/engine-metrics.cases.unit.test.ts +371 -0
  258. package/src/__tests__/equirect-projection-error.cases.unit.test.ts +416 -0
  259. package/src/__tests__/font-asset-load.cases.unit.test.ts +510 -0
  260. package/src/__tests__/fxaa-intermediate-texture.cases.unit.test.ts +690 -0
  261. package/src/__tests__/fxaa-pipeline.cases.unit.test.ts +546 -0
  262. package/src/__tests__/gpu-residency.cases.unit.test.ts +479 -0
  263. package/src/__tests__/gpu-resource-release.cases.unit.test.ts +929 -0
  264. package/src/__tests__/handle-quad.cases.unit.test.ts +491 -0
  265. package/src/__tests__/hdrp-bgl-slots.cases.unit.test.ts +451 -0
  266. package/src/__tests__/hdrp-grid-invalid.cases.unit.test.ts +408 -0
  267. package/src/__tests__/hdrp-index-list-overflow-once.cases.unit.test.ts +405 -0
  268. package/src/__tests__/hdrp-light-budget.cases.unit.test.ts +483 -0
  269. package/src/__tests__/hdrp-pipeline-asset-config.cases.unit.test.ts +441 -0
  270. package/src/__tests__/helpers/renderer-device-loss-dawn.ts +69 -0
  271. package/src/__tests__/helpers/standard-material-manifest.ts +11 -15
  272. package/src/__tests__/ibl-pipeline-cache.cases.unit.test.ts +415 -0
  273. package/src/__tests__/lazy-catalog.cases.unit.test.ts +848 -0
  274. package/src/__tests__/light-gpu-byte-neutral.unit.test.ts +6 -5
  275. package/src/__tests__/load-by-guid-hdr.cases.unit.test.ts +582 -0
  276. package/src/__tests__/load-by-guid-prod-material-parent.cases.unit.test.ts +1514 -0
  277. package/src/__tests__/load-by-guid-prod.cases.unit.test.ts +557 -0
  278. package/src/__tests__/loader-registry.cases.unit.test.ts +767 -0
  279. package/src/__tests__/m7-hdrp-demo-shape.cases.unit.test.ts +361 -0
  280. package/src/__tests__/material-texture-uv-scale.unit.test.ts +2 -2
  281. package/src/__tests__/materials.unit.test.ts +29 -10
  282. package/src/__tests__/mipmap-formula.cases.unit.test.ts +434 -0
  283. package/src/__tests__/mipmap-pipeline-cache.cases.unit.test.ts +490 -0
  284. package/src/__tests__/parse-asset-payload-material.cases.unit.test.ts +565 -0
  285. package/src/__tests__/parse-asset-payload-texture.cases.unit.test.ts +720 -0
  286. package/src/__tests__/parse-scene-payload-refs.cases.unit.test.ts +767 -0
  287. package/src/__tests__/pass-selector.cases.unit.test.ts +376 -0
  288. package/src/__tests__/pbr-pipeline.unit.test.ts +23 -5
  289. package/src/__tests__/pipeline-builder.cases.unit.test.ts +624 -0
  290. package/src/__tests__/pipeline-cache-key-topology.cases.unit.test.ts +807 -0
  291. package/src/__tests__/pipeline-vertex-stride-branch.cases.unit.test.ts +379 -0
  292. package/src/__tests__/pipeline.cases.unit.test.ts +370 -0
  293. package/src/__tests__/post-process-register.cases.unit.test.ts +405 -0
  294. package/src/__tests__/propagate-transforms.cases.unit.test.ts +502 -0
  295. package/src/__tests__/propagate-transforms.dawn.test.ts +19 -2
  296. package/src/__tests__/record-all-topology.cases.unit.test.ts +608 -0
  297. package/src/__tests__/record-draw-branch.cases.unit.test.ts +621 -0
  298. package/src/__tests__/record-strip-index-format.cases.unit.test.ts +419 -0
  299. package/src/__tests__/register-with-guid-rgba16float.cases.unit.test.ts +469 -0
  300. package/src/__tests__/render-data.cases.unit.test.ts +553 -0
  301. package/src/__tests__/render-error-exhaustive.test-d.ts +1 -0
  302. package/src/__tests__/render-query-regression.cases.unit.test.ts +531 -0
  303. package/src/__tests__/render-system-record-submesh.cases.unit.test.ts +740 -0
  304. package/src/__tests__/render-system-record-warn-once.cases.unit.test.ts +418 -0
  305. package/src/__tests__/render-system-record.test.ts +15 -33
  306. package/src/__tests__/renderer-device-loss-multi-world.integration.test.ts +103 -0
  307. package/src/__tests__/renderer-device-loss-provider-feasibility.dawn.test.ts +124 -0
  308. package/src/__tests__/renderer-device-loss-recovery.dawn.test.ts +50 -0
  309. package/src/__tests__/renderer-draw-world.cases.unit.test.ts +514 -0
  310. package/src/__tests__/renderer-recovery-consumer.integration.test.ts +96 -0
  311. package/src/__tests__/renderstate-pipeline-cache.cases.unit.test.ts +631 -0
  312. package/src/__tests__/resolve-scene-guids.cases.unit.test.ts +1123 -0
  313. package/src/__tests__/shadow-caster-empty-schema.test.ts +1 -1
  314. package/src/__tests__/shadow-fields-observable.dawn.test.ts +2 -2
  315. package/src/__tests__/shadow-m1.dawn.test.ts +1 -1
  316. package/src/__tests__/shadow-skip-non-triangle.cases.unit.test.ts +681 -0
  317. package/src/__tests__/skin-errors-kebab-case.cases.unit.test.ts +475 -0
  318. package/src/__tests__/skin-extract-getarrayview-count.unit.test.ts +5 -4
  319. package/src/__tests__/skin-instances-coexist.cases.unit.test.ts +375 -0
  320. package/src/__tests__/skin-m2-extract-happy.cases.unit.test.ts +592 -0
  321. package/src/__tests__/skin-m2-extract-joint-error.cases.unit.test.ts +619 -0
  322. package/src/__tests__/skin-m2-extract-null-assets.cases.unit.test.ts +569 -0
  323. package/src/__tests__/skin-m2-extract-skeleton-error.cases.unit.test.ts +585 -0
  324. package/src/__tests__/skin-m3-record-cache.cases.unit.test.ts +457 -0
  325. package/src/__tests__/skin-palette-extract.cases.unit.test.ts +635 -0
  326. package/src/__tests__/skin-pipeline-routing.cases.unit.test.ts +411 -0
  327. package/src/__tests__/skin.cases.unit.test.ts +421 -0
  328. package/src/__tests__/skybox-extract.cases.unit.test.ts +511 -0
  329. package/src/__tests__/skybox-shader-compile.cases.unit.test.ts +437 -0
  330. package/src/__tests__/skylight-bind-group.cases.unit.test.ts +642 -0
  331. package/src/__tests__/skylight-component.cases.unit.test.ts +386 -0
  332. package/src/__tests__/skylight-fallback-path.cases.unit.test.ts +566 -0
  333. package/src/__tests__/skylight-lazy-projection.test.ts +35 -20
  334. package/src/__tests__/skylight-pipeline-layout.cases.unit.test.ts +506 -0
  335. package/src/__tests__/skylight-solid-color-extract.cases.unit.test.ts +399 -0
  336. package/src/__tests__/sprite-animation-tick-boundary.cases.unit.test.ts +521 -0
  337. package/src/__tests__/sprite-animation-tick-clamp.cases.unit.test.ts +453 -0
  338. package/src/__tests__/sprite-animation-tick-frame-duration-negative.cases.unit.test.ts +447 -0
  339. package/src/__tests__/sprite-animation-tick-frame-duration-zero.cases.unit.test.ts +395 -0
  340. package/src/__tests__/sprite-animation-tick-loop.cases.unit.test.ts +494 -0
  341. package/src/__tests__/sprite-animation-tick-override-probe.cases.unit.test.ts +439 -0
  342. package/src/__tests__/sprite-animation-tick-regions-mismatch.cases.unit.test.ts +466 -0
  343. package/src/__tests__/sprite-lit-bgl-byte-identical.test.ts +5 -5
  344. package/src/__tests__/sprite-lit-pipeline-cache-key.test.ts +1 -1
  345. package/src/__tests__/ssao-pipelines.test.ts +1 -1
  346. package/src/__tests__/storage-buffer-caps.cases.unit.test.ts +373 -0
  347. package/src/__tests__/systems.cases.unit.test.ts +425 -0
  348. package/src/__tests__/tilemap-chunk-extract.overhang-bounds.test.ts +1 -1
  349. package/src/__tests__/tonemap-hdr-target.cases.unit.test.ts +670 -0
  350. package/src/__tests__/tonemap-pipeline-split.cases.unit.test.ts +867 -0
  351. package/src/__tests__/tonemap.cases.unit.test.ts +446 -0
  352. package/src/__tests__/transparent-sort-config-get.cases.unit.test.ts +395 -0
  353. package/src/__tests__/transparent-sort-config-set.cases.unit.test.ts +424 -0
  354. package/src/__tests__/transparent-sort.cases.unit.test.ts +684 -0
  355. package/src/__tests__/upload-texture-consistency.cases.unit.test.ts +478 -0
  356. package/src/__tests__/verify-revisions.cases.unit.test.ts +619 -0
  357. package/src/__tests__/wire-default-loaders.cases.unit.test.ts +461 -0
  358. package/src/__tests__/zero-camera-clear-fallback.cases.unit.test.ts +374 -0
  359. package/src/dev-import-transport.ts +2 -1
  360. package/dist/__tests__/asset.unit.test.d.ts +0 -2
  361. package/dist/__tests__/asset.unit.test.d.ts.map +0 -1
  362. package/dist/__tests__/pipeline.unit.test.d.ts +0 -2
  363. package/dist/__tests__/pipeline.unit.test.d.ts.map +0 -1
  364. package/dist/__tests__/systems.unit.test.d.ts +0 -2
  365. package/dist/__tests__/systems.unit.test.d.ts.map +0 -1
  366. package/src/__tests__/asset.unit.test.ts +0 -7113
  367. package/src/__tests__/pipeline.unit.test.ts +0 -5279
  368. package/src/__tests__/systems.unit.test.ts +0 -9061
@@ -0,0 +1,807 @@
1
+ // @ts-nocheck — merged file: cross-source node:fs/node:path imports outside @types/node coverage in runtime tsconfig
2
+ // Consolidated by feat-20260609-test-pool-startup-reduction-merge-tiny-test-files
3
+ // biome-ignore-all lint/complexity/noUselessLoneBlockStatements: block-scope isolation between merged source files (consolidation paradigm)
4
+ //
5
+ // Source files (N=33):
6
+ // - packages/runtime/__tests__/create-renderer-fallback.test.ts
7
+ // - packages/runtime/src/__tests__/cluster-binner.test.ts
8
+ // - packages/runtime/src/__tests__/create-renderer-fallback-shader-manifest.test.ts
9
+ // - packages/runtime/src/__tests__/create-renderer-uniform-fallback.test.ts
10
+ // - packages/runtime/src/__tests__/createRenderer.test.ts
11
+ // - packages/runtime/src/__tests__/dispatch-sort.test.ts
12
+ // - packages/runtime/src/__tests__/engine-metrics.test.ts
13
+ // - packages/runtime/src/__tests__/device/gpu-residency-caps-guard.test.ts
14
+ // - packages/runtime/src/__tests__/pass-selector.test.ts
15
+ // - packages/runtime/src/__tests__/pipeline-builder.test.ts
16
+ // - packages/runtime/src/__tests__/pipeline-cache-key-topology.test.ts
17
+ // - packages/runtime/src/__tests__/pipeline-rename-grep-gate.test.ts
18
+ // - packages/runtime/src/__tests__/pipeline-vertex-stride-branch.test.ts
19
+ // - packages/runtime/src/__tests__/post-process-register.test.ts
20
+ // - packages/runtime/src/__tests__/record-all-topology.test.ts
21
+ // - packages/runtime/src/__tests__/record-draw-branch.test.ts
22
+ // - packages/runtime/src/__tests__/record-strip-index-format.test.ts
23
+ // - packages/runtime/src/__tests__/render-query-regression.test.ts
24
+ // - packages/runtime/src/__tests__/renderer-draw-world.test.ts
25
+ // - packages/runtime/src/__tests__/renderer-input-snapshot.test.ts
26
+ // - packages/runtime/src/__tests__/renderer-read-pixels.test.ts
27
+ // - packages/runtime/src/__tests__/renderer-ready.test.ts
28
+ // - packages/runtime/src/__tests__/renderstate-pipeline-cache.test.ts
29
+ // - packages/runtime/src/__tests__/storage-buffer-caps.test.ts
30
+ // - packages/runtime/src/__tests__/device/gpu-residency.test.ts
31
+ // - packages/runtime/src/__tests__/render-data.test.ts
32
+ // - packages/runtime/src/__tests__/hdrp-bgl-slots.test.ts
33
+ // - packages/runtime/src/__tests__/hdrp-caps-gate.test.ts
34
+ // - packages/runtime/src/__tests__/hdrp-grid-invalid.test.ts
35
+ // - packages/runtime/src/__tests__/hdrp-index-list-overflow-once.test.ts
36
+ // - packages/runtime/src/__tests__/hdrp-light-budget.test.ts
37
+ // - packages/runtime/src/__tests__/hdrp-pipeline-asset-config.test.ts
38
+ // - packages/runtime/src/__tests__/m7-hdrp-demo-shape.test.ts
39
+ //
40
+ // Paradigm: each block-scope wraps a source file. ancestorTitles[0] is the
41
+ // source-preserved inner describe (NOT the source filename for these 3 files
42
+ // — recovery path: vitest report ancestorTitles -> grep this file -> upstream
43
+ // `// ─── from <name>.test.ts ───` block separator -> source filename).
44
+ // Top-level imports merged + deduped.
45
+
46
+ import { existsSync, readFileSync } from 'node:fs';
47
+ import { dirname, resolve } from 'node:path';
48
+ import { fileURLToPath } from 'node:url';
49
+ import { HANDLE_CUBE, HANDLE_TRIANGLE, resolveAssetHandle } from '@forgeax/engine-assets-runtime';
50
+ import type { World as WorldType } from '@forgeax/engine-ecs';
51
+ import { defineComponent, World } from '@forgeax/engine-ecs';
52
+ import {
53
+ buildMeshAttributeMapForUvSets,
54
+ createBoxGeometry,
55
+ createConeGeometry,
56
+ createCylinderGeometry,
57
+ createPlaneGeometry,
58
+ createSphereGeometry,
59
+ createTorusGeometry,
60
+ PROCEDURAL_FLOATS_PER_VERTEX,
61
+ } from '@forgeax/engine-geometry';
62
+ import { type Mat4, mat4, type Vec3, vec3 } from '@forgeax/engine-math';
63
+ import type { Renderer as RendererType } from '@forgeax/engine-render';
64
+ import { Camera, MeshFilter, MeshRenderer } from '@forgeax/engine-render';
65
+ import {
66
+ err,
67
+ ok,
68
+ type PipelineLayout,
69
+ type RenderPipeline,
70
+ type Result,
71
+ type RhiCaps,
72
+ type RhiDevice,
73
+ RhiError,
74
+ ok as rhiOk,
75
+ type ShaderModule,
76
+ } from '@forgeax/engine-rhi';
77
+ import {
78
+ acquireCanvasContext as acquireNullCanvasContext,
79
+ createShaderModule as createNullShaderModule,
80
+ RhiNullAdapter,
81
+ } from '@forgeax/engine-rhi-null';
82
+ import { ChildOf, Transform } from '@forgeax/engine-scene';
83
+ import {
84
+ findVariantByKey,
85
+ type MaterialShaderEntry,
86
+ type MaterialShaderManifestEntry,
87
+ type MaterialShaderManifestVariant,
88
+ } from '@forgeax/engine-shader';
89
+ import {
90
+ type AssetError,
91
+ type EquirectAsset,
92
+ type Handle,
93
+ type MaterialRenderState,
94
+ type MeshAsset,
95
+ type PassSelector,
96
+ type PrimitiveTopology,
97
+ type RenderPipelineAsset,
98
+ type TextureAsset,
99
+ toShared,
100
+ } from '@forgeax/engine-types';
101
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
102
+ import {
103
+ bin,
104
+ type ClusterBinError,
105
+ calculateSphereClusterBounds,
106
+ clusterSpaceObjectAabb,
107
+ deriveCullingRadius,
108
+ ndcPositionToCluster,
109
+ viewZToZSlice,
110
+ } from '../../../render/src/cluster-binner';
111
+ import { GpuResidencyCache } from '../../../render/src/device/gpu-residency';
112
+ import { createEngineMetrics } from '../../../render/src/engine-metrics';
113
+ import { assertStorageBufferCap } from '../../../render/src/light-buffer-layout';
114
+ import {
115
+ buildPbrPipelineLayouts,
116
+ buildPbrViewBglEntries,
117
+ type PbrCaps,
118
+ } from '../../../render/src/pbr-pipeline';
119
+ import {
120
+ HdrpInstallError,
121
+ validateClusterGrid,
122
+ } from '../../../render/src/pipeline/standard-pipeline';
123
+ import {
124
+ buildPipelineForMaterialShader,
125
+ type PipelineBuilderContext,
126
+ type PipelineBuilderShaderModuleFactory,
127
+ } from '../../../render/src/pipeline-builder';
128
+ import {
129
+ cacheKeyOf,
130
+ createHdrpBindGroupLayoutDescriptor,
131
+ type PipelineSpec,
132
+ } from '../../../render/src/pipeline-spec';
133
+ import {
134
+ deriveRenderDataCubemap,
135
+ deriveRenderDataMesh,
136
+ deriveRenderDataTexture,
137
+ } from '../../../render/src/render-data';
138
+ import { extractFrame, prepareExtractContext } from '../../../render/src/render-system-extract';
139
+ import { matchPass } from '../../../render/src/systems/pass-selector';
140
+ import { makeMockShaderRegistry } from './helpers/mock-shader-registry';
141
+ import { standardMaterialShaderVariants } from './helpers/standard-material-manifest';
142
+
143
+ function makeExplicitNullRhi(spies: {
144
+ setIndexBuffer: ReturnType<typeof vi.fn>;
145
+ setVertexBuffer: ReturnType<typeof vi.fn>;
146
+ draw: ReturnType<typeof vi.fn>;
147
+ drawIndexed: ReturnType<typeof vi.fn>;
148
+ }): unknown {
149
+ const adapter = new RhiNullAdapter();
150
+ const requestDevice = adapter.requestDevice.bind(adapter);
151
+ adapter.requestDevice = async () => {
152
+ const result = await requestDevice();
153
+ if (result.ok) {
154
+ const device = result.value;
155
+ const createCommandEncoder = device.createCommandEncoder.bind(device);
156
+ device.createCommandEncoder = (desc) => {
157
+ const encoderResult = createCommandEncoder(desc);
158
+ if (!encoderResult.ok) return encoderResult;
159
+ const encoder = encoderResult.value;
160
+ const beginRenderPass = encoder.beginRenderPass.bind(encoder);
161
+ encoder.beginRenderPass = (passDesc) => {
162
+ const pass = beginRenderPass(passDesc);
163
+ const draw = pass.draw.bind(pass);
164
+ const drawIndexed = pass.drawIndexed.bind(pass);
165
+ const setIndexBuffer = pass.setIndexBuffer.bind(pass);
166
+ const setVertexBuffer = pass.setVertexBuffer.bind(pass);
167
+ pass.draw = (...args) => {
168
+ spies.draw(...args);
169
+ draw(...args);
170
+ };
171
+ pass.drawIndexed = (...args) => {
172
+ spies.drawIndexed(...args);
173
+ drawIndexed(...args);
174
+ };
175
+ pass.setIndexBuffer = (...args) => {
176
+ spies.setIndexBuffer(...args);
177
+ setIndexBuffer(...args);
178
+ };
179
+ pass.setVertexBuffer = (...args) => {
180
+ spies.setVertexBuffer(...args);
181
+ setVertexBuffer(...args);
182
+ };
183
+ return pass;
184
+ };
185
+ return { ok: true, value: encoder };
186
+ };
187
+ }
188
+ return result;
189
+ };
190
+ return {
191
+ requestAdapter: async () => ({ ok: true, value: adapter }),
192
+ acquireCanvasContext: acquireNullCanvasContext,
193
+ createShaderModule: (device: unknown, desc: { code: string; label?: string }) =>
194
+ createNullShaderModule(device as never, desc),
195
+ };
196
+ }
197
+
198
+ vi.mock('@forgeax/engine-rhi-wgpu', () => {
199
+ return {
200
+ rhi: {
201
+ requestAdapter: async () => ({
202
+ ok: false,
203
+ error: {
204
+ code: 'adapter-unavailable',
205
+ expected: 'adapter available',
206
+ hint: 'default mock fail',
207
+ },
208
+ }),
209
+ getPreferredCanvasFormat: () => 'bgra8unorm',
210
+ acquireCanvasContext: () => ({
211
+ ok: false,
212
+ error: {
213
+ code: 'webgpu-runtime-error',
214
+ expected: 'context available',
215
+ hint: 'default mock fail',
216
+ },
217
+ }),
218
+ },
219
+ ensureReady: async () => undefined,
220
+ };
221
+ });
222
+
223
+ void [
224
+ Camera,
225
+ ChildOf,
226
+ GpuResidencyCache,
227
+ HANDLE_CUBE,
228
+ HANDLE_TRIANGLE,
229
+ HdrpInstallError,
230
+ MeshFilter,
231
+ MeshRenderer,
232
+ PROCEDURAL_FLOATS_PER_VERTEX,
233
+ RhiError,
234
+ RhiNullAdapter,
235
+ Transform,
236
+ World,
237
+ acquireNullCanvasContext,
238
+ afterEach,
239
+ assertStorageBufferCap,
240
+ beforeEach,
241
+ bin,
242
+ buildMeshAttributeMapForUvSets,
243
+ buildPbrPipelineLayouts,
244
+ buildPbrViewBglEntries,
245
+ buildPipelineForMaterialShader,
246
+ cacheKeyOf,
247
+ calculateSphereClusterBounds,
248
+ clusterSpaceObjectAabb,
249
+ createBoxGeometry,
250
+ createConeGeometry,
251
+ createCylinderGeometry,
252
+ createEngineMetrics,
253
+ createHdrpBindGroupLayoutDescriptor,
254
+ createNullShaderModule,
255
+ createPlaneGeometry,
256
+ createSphereGeometry,
257
+ createTorusGeometry,
258
+ defineComponent,
259
+ deriveCullingRadius,
260
+ deriveRenderDataCubemap,
261
+ deriveRenderDataMesh,
262
+ deriveRenderDataTexture,
263
+ describe,
264
+ dirname,
265
+ err,
266
+ existsSync,
267
+ expect,
268
+ extractFrame,
269
+ fileURLToPath,
270
+ findVariantByKey,
271
+ it,
272
+ makeExplicitNullRhi,
273
+ makeMockShaderRegistry,
274
+ mat4,
275
+ matchPass,
276
+ ndcPositionToCluster,
277
+ ok,
278
+ prepareExtractContext,
279
+ readFileSync,
280
+ resolve,
281
+ resolveAssetHandle,
282
+ rhiOk,
283
+ standardMaterialShaderVariants,
284
+ toShared,
285
+ validateClusterGrid,
286
+ vec3,
287
+ vi,
288
+ viewZToZSlice,
289
+ ];
290
+ type __MergedKeep =
291
+ | AssetError
292
+ | ClusterBinError
293
+ | EquirectAsset
294
+ | Handle
295
+ | Mat4
296
+ | MaterialRenderState
297
+ | MaterialShaderEntry
298
+ | MaterialShaderManifestEntry
299
+ | MaterialShaderManifestVariant
300
+ | MeshAsset
301
+ | PassSelector
302
+ | PbrCaps
303
+ | PipelineBuilderContext
304
+ | PipelineBuilderShaderModuleFactory
305
+ | PipelineLayout
306
+ | PipelineSpec
307
+ | PrimitiveTopology
308
+ | RenderPipeline
309
+ | RenderPipelineAsset
310
+ | RendererType
311
+ | Result
312
+ | RhiCaps
313
+ | RhiDevice
314
+ | ShaderModule
315
+ | TextureAsset
316
+ | Vec3
317
+ | WorldType;
318
+
319
+ {
320
+ // --- from pipeline-cache-key-topology.test.ts ---
321
+ const ID = 'my-game::pulse-material';
322
+
323
+ // Helper: construct PipelineSpec for cache-key unit tests (M2-T2 migration).
324
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
325
+ function mkSpec(
326
+ id: string,
327
+ isHdr: boolean,
328
+ renderState?: MaterialRenderState,
329
+ topology?: PrimitiveTopology,
330
+ indexFormat?: 'uint16' | 'uint32',
331
+ variantSet?: string,
332
+ passKind: string = 'forward',
333
+ sampleCount: 1 | 4 = 1,
334
+ ): PipelineSpec {
335
+ const colorFormat: GPUTextureFormat = isHdr
336
+ ? ('rgba16float' as unknown as GPUTextureFormat)
337
+ : ('bgra8unorm-srgb' as unknown as GPUTextureFormat);
338
+ return {
339
+ shader: { id, passKind, variantSet },
340
+ attachments: {
341
+ colorFormats: passKind === 'shadow-caster' ? [] : [colorFormat],
342
+ depthFormat:
343
+ passKind === 'shadow-caster'
344
+ ? ('depth32float' as unknown as GPUTextureFormat)
345
+ : ('depth24plus-stencil8' as unknown as GPUTextureFormat),
346
+ sampleCount,
347
+ },
348
+ geometry: {
349
+ topology: topology ?? 'triangle-list',
350
+ stripIndexFormat: indexFormat,
351
+ vertexLayout: {
352
+ position: new Float32Array(0),
353
+ normal: new Float32Array(0),
354
+ uv: new Float32Array(0),
355
+ tangent: new Float32Array(0),
356
+ },
357
+ },
358
+ renderState,
359
+ };
360
+ }
361
+
362
+ describe('per-spec pipeline cache key — topology dimension (AC-03/05/06)', () => {
363
+ it('(a) AC-05: different topology -> different cache key (distinct PSO slots)', () => {
364
+ const line = cacheKeyOf(mkSpec(ID, false, undefined, 'line-list'));
365
+ const tri = cacheKeyOf(mkSpec(ID, false, undefined, 'triangle-list'));
366
+
367
+ expect(line).not.toBe(tri);
368
+ });
369
+
370
+ it('(a) AC-05: topology difference holds with a renderState present', () => {
371
+ const rs: MaterialRenderState = { cullMode: 'none' };
372
+ const line = cacheKeyOf(mkSpec(ID, false, rs, 'line-list'));
373
+ const tri = cacheKeyOf(mkSpec(ID, false, rs, 'triangle-list'));
374
+
375
+ expect(line).not.toBe(tri);
376
+ });
377
+
378
+ it('(b) AC-03: omitted topology == explicit triangle-list (byte-identical key)', () => {
379
+ const omitted = cacheKeyOf(mkSpec(ID, false, undefined, undefined));
380
+ const explicit = cacheKeyOf(mkSpec(ID, false, undefined, 'triangle-list'));
381
+
382
+ expect(omitted).toBe(explicit);
383
+ });
384
+
385
+ it('(b) AC-03: omitted topology key contains the :triangle-list segment', () => {
386
+ const omitted = cacheKeyOf(mkSpec(ID, false, undefined, undefined));
387
+
388
+ // M2-T4: key format is id:passKind:variantSet:colorFormat:depthFormat:
389
+ // sampleCount:topology[:stripSegment]:vl:digest[:renderStateHash].
390
+ // Strip segment is '' for non-strip topo → topology: followed by :vl:.
391
+ expect(omitted.includes(':triangle-list:')).toBe(true);
392
+ expect(omitted.includes(':vl:')).toBe(true);
393
+ });
394
+
395
+ it('(b) AC-03: omitted-topology + undefined-renderState key contains legacy shape prefix', () => {
396
+ // M2-T4: key format changed — passKind:variantSet:colorFormats replaces
397
+ // the pre-M2 :ldr segment. The prefix carries the full 4-axis structure;
398
+ // vl:<hash> follows the topology segment.
399
+ // bug-20260708 M2 (b): variantSet=undefined now serializes as sentinel
400
+ // `~` (was empty segment `::`) to decouple undefined from canonical `''`.
401
+ const key = cacheKeyOf(mkSpec('forgeax::default-pbr', false, undefined, undefined));
402
+
403
+ expect(
404
+ key.startsWith(
405
+ 'forgeax::default-pbr:forward:~:bgra8unorm-srgb:depth24plus-stencil8:1:triangle-list',
406
+ ),
407
+ ).toBe(true);
408
+ // vl:<digest> segment present (vertex layout hash); may have trailing : from
409
+ // renderStateHash('') at end of join
410
+ expect(key).toMatch(/:vl:[^:]+/);
411
+ });
412
+
413
+ it('(c) AC-06: same tuple -> identical key (idempotent, cache hit)', () => {
414
+ const rs: MaterialRenderState = { cullMode: 'front' };
415
+ const k1 = cacheKeyOf(mkSpec(ID, true, rs, 'line-list'));
416
+ const k2 = cacheKeyOf(mkSpec(ID, true, { ...rs }, 'line-list'));
417
+
418
+ expect(k1).toBe(k2);
419
+ });
420
+
421
+ it('(d) topology is an independent segment, not folded into the renderState hash', () => {
422
+ // Moving topology while holding renderState fixed must change the key, and
423
+ // moving renderState while holding topology fixed must also change it --
424
+ // proving the two dimensions are orthogonal (D-2 / D-A3).
425
+ const base = cacheKeyOf(mkSpec(ID, false, { cullMode: 'none' }, 'triangle-list'));
426
+ const topoMoved = cacheKeyOf(mkSpec(ID, false, { cullMode: 'none' }, 'line-list'));
427
+ const rsMoved = cacheKeyOf(mkSpec(ID, false, { cullMode: 'front' }, 'triangle-list'));
428
+
429
+ expect(base).not.toBe(topoMoved);
430
+ expect(base).not.toBe(rsMoved);
431
+ expect(topoMoved).not.toBe(rsMoved);
432
+ });
433
+
434
+ it('(d) every strip / list topology yields a distinct key segment', () => {
435
+ const topologies: PrimitiveTopology[] = [
436
+ 'point-list',
437
+ 'line-list',
438
+ 'line-strip',
439
+ 'triangle-list',
440
+ 'triangle-strip',
441
+ ];
442
+ const keys = topologies.map((t) => cacheKeyOf(mkSpec(ID, false, undefined, t)));
443
+
444
+ expect(new Set(keys).size).toBe(topologies.length);
445
+ });
446
+ });
447
+ // ============================================================================
448
+ // feat-20260609-hdrp-cluster-fragment-ggx M1 / w2
449
+ // PSO cache key -- variantSet dimension (TDD red phase).
450
+ // ============================================================================
451
+
452
+ describe('per-spec pipeline cache key -- variantSet dimension (feat-20260609 M1)', () => {
453
+ it('(a) different variantSet -> different cache key (distinct PSO slots)', () => {
454
+ const k1 = cacheKeyOf(
455
+ mkSpec(ID, false, undefined, 'triangle-list', undefined, 'STORAGE_BUFFER_AVAILABLE=true'),
456
+ );
457
+ const k2 = cacheKeyOf(
458
+ mkSpec(
459
+ ID,
460
+ false,
461
+ undefined,
462
+ 'triangle-list',
463
+ undefined,
464
+ 'CLUSTER_FORWARD_AVAILABLE=true+STORAGE_BUFFER_AVAILABLE=true',
465
+ ),
466
+ );
467
+ expect(k1).not.toBe(k2);
468
+ });
469
+
470
+ it('(b) same variantSet -> identical cache key (idempotent, cache hit)', () => {
471
+ const vs = 'CLUSTER_FORWARD_AVAILABLE=true+STORAGE_BUFFER_AVAILABLE=true';
472
+ const k1 = cacheKeyOf(mkSpec(ID, false, undefined, 'triangle-list', undefined, vs));
473
+ const k2 = cacheKeyOf(mkSpec(ID, false, undefined, 'triangle-list', undefined, vs));
474
+ expect(k1).toBe(k2);
475
+ });
476
+
477
+ it("(c) bug-20260708 M2 (b): variantSet '' (canonical all-true) and undefined produce DISTINCT keys via sentinel", () => {
478
+ // bug-20260708 M2 (b) REVERSES the prior M4.5 D-11 normalize-to-same
479
+ // behavior. `cacheKeyOf` now uses sentinel `~` for `variantSet=undefined`
480
+ // and preserves `''` verbatim, so undefined vs canonical `''` land in
481
+ // separate cache slots. This decoupling closes the R-11 collision that
482
+ // let boot-seeded PIR=true modules leak into character sprite pipeline
483
+ // requests (variantSet=undefined). See sentinel gate in
484
+ // pipeline-cache-keying.unit.test.ts §'variantSet sentinel' as SSOT.
485
+ const withUndefined = cacheKeyOf(
486
+ mkSpec(ID, false, undefined, 'triangle-list', undefined, undefined),
487
+ );
488
+ const withEmpty = cacheKeyOf(mkSpec(ID, false, undefined, 'triangle-list', undefined, ''));
489
+ expect(withUndefined).not.toBe(withEmpty);
490
+ // Undefined key contains the `~` sentinel segment (position: after
491
+ // shader.id + passKind, before colorFormats).
492
+ expect(withUndefined).toContain(':~:');
493
+ // Canonical `''` key contains an empty variantSet segment `::`.
494
+ expect(withEmpty).toContain('::');
495
+ expect(withUndefined.length).toBeGreaterThan(0);
496
+ expect(withEmpty.length).toBeGreaterThan(0);
497
+ });
498
+
499
+ it("(e) M4.5 / D-11: canonical '' and expanded 'AXIS=true+...' all-true produce DIFFERENT keys", () => {
500
+ const withEmpty = cacheKeyOf(mkSpec(ID, false, undefined, 'triangle-list', undefined, ''));
501
+ const withExpandedAllTrue = cacheKeyOf(
502
+ mkSpec(
503
+ ID,
504
+ false,
505
+ undefined,
506
+ 'triangle-list',
507
+ undefined,
508
+ 'CLUSTER_FORWARD_AVAILABLE=true+STORAGE_BUFFER_AVAILABLE=true',
509
+ ),
510
+ );
511
+ expect(withEmpty).not.toBe(withExpandedAllTrue);
512
+ });
513
+
514
+ it('(d) variantSet is orthogonal to topology dimension', () => {
515
+ const base = cacheKeyOf(
516
+ mkSpec(ID, false, undefined, 'triangle-list', undefined, 'CLUSTER_FORWARD_AVAILABLE=true'),
517
+ );
518
+ const topoMoved = cacheKeyOf(
519
+ mkSpec(ID, false, undefined, 'line-list', undefined, 'CLUSTER_FORWARD_AVAILABLE=true'),
520
+ );
521
+ const variantMoved = cacheKeyOf(
522
+ mkSpec(ID, false, undefined, 'triangle-list', undefined, 'STORAGE_BUFFER_AVAILABLE=true'),
523
+ );
524
+ expect(base).not.toBe(topoMoved);
525
+ expect(base).not.toBe(variantMoved);
526
+ expect(topoMoved).not.toBe(variantMoved);
527
+ });
528
+ });
529
+
530
+ // ============================================================================
531
+ // feat-20260609-hdrp-cluster-fragment-ggx M4 / w30
532
+ // runtime variant WGSL resolution -- variantSet -> correct variant WGSL (TDD red phase).
533
+ // ============================================================================
534
+
535
+ describe('runtime variant WGSL resolution from manifest (feat-20260609 M4 / w30)', () => {
536
+ function makeEntry(
537
+ identifier: string,
538
+ defaultBindings: string,
539
+ variants: readonly { definesKey: string; source: string; bindings: string }[],
540
+ ): MaterialShaderManifestEntry {
541
+ // M3 / w13: the binding-layout sidecar is gone from MaterialShader{Entry,ManifestEntry}
542
+ // — paramSchema is the SSOT. The fixture below sets the per-variant
543
+ // BGL hint into the composedWgsl as a comment marker so the
544
+ // variant-resolution assertions can still distinguish HDRP-vs-URP
545
+ // variants without the deleted binding-layout JSON-string.
546
+ return {
547
+ identifier,
548
+ sourcePath: `${identifier}.wgsl`,
549
+ composedWgsl: `// default wgsl for ${identifier} ${defaultBindings}`,
550
+ paramSchema: '[]',
551
+ variants: variants.map((v) => ({
552
+ definesKey: v.definesKey,
553
+ defines: Object.fromEntries(
554
+ v.definesKey
555
+ ? v.definesKey.split('+').map((kv) => {
556
+ const [k, val] = kv.split('=');
557
+ return [k, val === 'true'] as [string, boolean];
558
+ })
559
+ : [],
560
+ ),
561
+ composedWgsl: `${v.source} ${v.bindings}`,
562
+ })) as readonly MaterialShaderManifestVariant[],
563
+ };
564
+ }
565
+
566
+ const BGL_WITH_CLUSTER = JSON.stringify([
567
+ { entries: [{ binding: 0, buffer: { hasDynamicOffset: true } }] },
568
+ { entries: [] },
569
+ {
570
+ entries: [
571
+ { binding: 0, buffer: { hasDynamicOffset: true } },
572
+ { binding: 3, buffer: { type: 'read-only-storage' } },
573
+ { binding: 4, buffer: { type: 'read-only-storage' } },
574
+ { binding: 5, buffer: { type: 'read-only-storage' } },
575
+ { binding: 6, buffer: { type: 'uniform' } },
576
+ ],
577
+ },
578
+ ]);
579
+
580
+ const BGL_WITHOUT_CLUSTER = JSON.stringify([
581
+ { entries: [{ binding: 0, buffer: { hasDynamicOffset: true } }] },
582
+ { entries: [] },
583
+ {
584
+ entries: [{ binding: 0, buffer: { hasDynamicOffset: true } }],
585
+ },
586
+ ]);
587
+
588
+ const PBR_ID = 'forgeax::default-standard-pbr';
589
+ const HDRP_DSK = 'CLUSTER_FORWARD_AVAILABLE=true+STORAGE_BUFFER_AVAILABLE=true';
590
+ const URP_DSK = 'STORAGE_BUFFER_AVAILABLE=true';
591
+
592
+ // Helper to extract the per-variant BGL JSON the fixture appended to
593
+ // composedWgsl. The test fixture (makeEntry) embeds the BGL JSON
594
+ // string after the WGSL source body so variant resolution can be
595
+ // asserted without the deleted MaterialShaderManifestVariant binding-layout sidecar
596
+ // sidecar field (M3 / w13 grep gate).
597
+ const extractBglJson = (composedWgsl: string | undefined): string | undefined => {
598
+ if (composedWgsl === undefined) return undefined;
599
+ const idx = composedWgsl.indexOf('[');
600
+ return idx >= 0 ? composedWgsl.slice(idx) : undefined;
601
+ };
602
+
603
+ it('(a) HDRP variant resolves to WGSL with cluster bindings (binding 3..6 present)', () => {
604
+ const entry = makeEntry(PBR_ID, BGL_WITHOUT_CLUSTER, [
605
+ { definesKey: HDRP_DSK, source: '// HDRP variant WGSL', bindings: BGL_WITH_CLUSTER },
606
+ { definesKey: URP_DSK, source: '// URP variant WGSL', bindings: BGL_WITHOUT_CLUSTER },
607
+ ]);
608
+
609
+ const variant = findVariantByKey(entry, HDRP_DSK);
610
+ expect(variant).toBeDefined();
611
+ expect(variant?.composedWgsl).toContain('// HDRP variant WGSL');
612
+
613
+ const bglJson = extractBglJson(variant?.composedWgsl);
614
+ expect(bglJson).toBeDefined();
615
+ const bgl = JSON.parse(bglJson as string) as ReadonlyArray<{
616
+ entries: ReadonlyArray<{ binding: number }>;
617
+ }>;
618
+ const group2 = bgl[2];
619
+ expect(group2).toBeDefined();
620
+ const bindings = group2?.entries.map((e) => e.binding);
621
+ expect(bindings).toContain(3);
622
+ expect(bindings).toContain(4);
623
+ expect(bindings).toContain(5);
624
+ expect(bindings).toContain(6);
625
+ });
626
+
627
+ it('(b) URP variant resolves to WGSL without cluster bindings (binding 3..6 absent)', () => {
628
+ const entry = makeEntry(PBR_ID, BGL_WITH_CLUSTER, [
629
+ { definesKey: HDRP_DSK, source: '// HDRP variant WGSL', bindings: BGL_WITH_CLUSTER },
630
+ { definesKey: URP_DSK, source: '// URP variant WGSL', bindings: BGL_WITHOUT_CLUSTER },
631
+ ]);
632
+
633
+ const variant = findVariantByKey(entry, URP_DSK);
634
+ expect(variant).toBeDefined();
635
+ expect(variant?.composedWgsl).toContain('// URP variant WGSL');
636
+
637
+ const bglJson = extractBglJson(variant?.composedWgsl);
638
+ expect(bglJson).toBeDefined();
639
+ const bgl = JSON.parse(bglJson as string) as ReadonlyArray<{
640
+ entries: ReadonlyArray<{ binding: number }>;
641
+ }>;
642
+ const group2 = bgl[2];
643
+ expect(group2).toBeDefined();
644
+ const bindings = group2?.entries.map((e) => e.binding);
645
+ expect(bindings).not.toContain(3);
646
+ expect(bindings).not.toContain(4);
647
+ expect(bindings).not.toContain(5);
648
+ expect(bindings).not.toContain(6);
649
+ });
650
+
651
+ it('(c) empty variantSet (definesKey="") falls back to entry default composedWgsl', () => {
652
+ const entry = makeEntry(PBR_ID, BGL_WITHOUT_CLUSTER, [
653
+ { definesKey: HDRP_DSK, source: '// HDRP variant WGSL', bindings: BGL_WITH_CLUSTER },
654
+ { definesKey: URP_DSK, source: '// URP variant WGSL', bindings: BGL_WITHOUT_CLUSTER },
655
+ ]);
656
+
657
+ const variant = findVariantByKey(entry, '');
658
+ expect(variant).toBeUndefined();
659
+ });
660
+
661
+ it('(d) unknown variantSet returns undefined (fail-soft)', () => {
662
+ const entry = makeEntry(PBR_ID, BGL_WITHOUT_CLUSTER, [
663
+ { definesKey: URP_DSK, source: '// URP variant WGSL', bindings: BGL_WITHOUT_CLUSTER },
664
+ ]);
665
+
666
+ const variant4 = findVariantByKey(entry, 'NONEXISTENT_KEY=true');
667
+ expect(variant4).toBeUndefined();
668
+ });
669
+ });
670
+
671
+ // ============================================================================
672
+ // bug-20260708 M2 (a) AC-03 / AC-04: sprite boot variant resolution.
673
+ // Simulates the createRenderer boot-time material-shader registration
674
+ // pattern (extended at line ~3687 to include PER_INSTANCE_REGION=false in
675
+ // variantDefines). Asserts that a sprite-like manifest entry (with the PIR
676
+ // axis) resolves to the PIR=false variant at boot, so per-entity spritePH
677
+ // path (variantSet=undefined) sees PIR=false shader source via
678
+ // installMaterialArtifact → lookup.value.source fallback.
679
+ // ============================================================================
680
+ describe('sprite boot variant resolution (bug-20260708 AC-03/AC-04)', () => {
681
+ const SPRITE_ID = 'forgeax::sprite';
682
+
683
+ function makeSpriteEntry(): MaterialShaderManifestEntry {
684
+ // Sprite manifest emits a 2-axis Cartesian: PER_INSTANCE_REGION x
685
+ // STORAGE_BUFFER_AVAILABLE. Canonical all-true `''` = PIR=true+SBA=true;
686
+ // other 3 combinations get sorted-key definesKey strings.
687
+ return {
688
+ identifier: SPRITE_ID,
689
+ sourcePath: 'sprite.wgsl',
690
+ composedWgsl: '// sprite all-true (PIR=true+SBA=true, 5653B) default composedWgsl',
691
+ paramSchema: '[]',
692
+ variants: [
693
+ {
694
+ definesKey: '',
695
+ defines: { PER_INSTANCE_REGION: true, STORAGE_BUFFER_AVAILABLE: true },
696
+ composedWgsl: '// sprite PIR=true SBA=true 5653B instances[idx].region',
697
+ },
698
+ {
699
+ definesKey: 'PER_INSTANCE_REGION=false+STORAGE_BUFFER_AVAILABLE=true',
700
+ defines: { PER_INSTANCE_REGION: false, STORAGE_BUFFER_AVAILABLE: true },
701
+ composedWgsl: '// sprite PIR=false SBA=true 5575B material.region',
702
+ },
703
+ {
704
+ definesKey: 'PER_INSTANCE_REGION=true+STORAGE_BUFFER_AVAILABLE=false',
705
+ defines: { PER_INSTANCE_REGION: true, STORAGE_BUFFER_AVAILABLE: false },
706
+ composedWgsl:
707
+ '// sprite PIR=true SBA=false 5653B (WebGL2 fallback) instances[idx].region',
708
+ },
709
+ {
710
+ definesKey: 'PER_INSTANCE_REGION=false+STORAGE_BUFFER_AVAILABLE=false',
711
+ defines: { PER_INSTANCE_REGION: false, STORAGE_BUFFER_AVAILABLE: false },
712
+ composedWgsl: '// sprite PIR=false SBA=false 5575B (WebGL2 fallback) material.region',
713
+ },
714
+ ] as readonly MaterialShaderManifestVariant[],
715
+ };
716
+ }
717
+
718
+ // Mirrors the createRenderer boot resolution at line ~3680-3696 (with the
719
+ // bug-20260708 M2 (a) PER_INSTANCE_REGION=false extension). Extracted here
720
+ // for testability — the actual createRenderer implementation is inline.
721
+ // AC-03/AC-04 depend on this logic picking the PIR=false variant for
722
+ // sprite; the test locks the outcome so an inline-logic drift is caught.
723
+ function resolveBootDefinesKey(
724
+ msEntry: MaterialShaderManifestEntry,
725
+ opts: { readonly storageBufferCapable: boolean; readonly isHdrpActive: boolean },
726
+ ): string {
727
+ const variantDefines: Record<string, boolean> = {
728
+ STORAGE_BUFFER_AVAILABLE: opts.storageBufferCapable,
729
+ };
730
+ if (msEntry.variants.some((v) => 'CLUSTER_FORWARD_AVAILABLE' in v.defines)) {
731
+ variantDefines.CLUSTER_FORWARD_AVAILABLE = opts.isHdrpActive;
732
+ }
733
+ if (msEntry.variants.some((v) => 'PER_INSTANCE_REGION' in v.defines)) {
734
+ variantDefines.PER_INSTANCE_REGION = false;
735
+ }
736
+ const sortedEntries = Object.entries(variantDefines).sort(([a], [b]) =>
737
+ a < b ? -1 : a > b ? 1 : 0,
738
+ );
739
+ return sortedEntries.every(([, v]) => v === true)
740
+ ? ''
741
+ : sortedEntries.map(([k, v]) => `${k}=${v}`).join('+');
742
+ }
743
+
744
+ it('(a) boot resolves sprite to PIR=false variant when storageBufferCapable=true (AC-03)', () => {
745
+ const spriteEntry = makeSpriteEntry();
746
+ const definesKey = resolveBootDefinesKey(spriteEntry, {
747
+ storageBufferCapable: true,
748
+ isHdrpActive: false,
749
+ });
750
+ expect(definesKey).toBe('PER_INSTANCE_REGION=false+STORAGE_BUFFER_AVAILABLE=true');
751
+ const variant = findVariantByKey(spriteEntry, definesKey);
752
+ expect(variant).toBeDefined();
753
+ expect(variant?.defines.PER_INSTANCE_REGION).toBe(false);
754
+ expect(variant?.defines.STORAGE_BUFFER_AVAILABLE).toBe(true);
755
+ // The variant WGSL corresponds to the PIR=false 5575B variant that
756
+ // reads region from material.region (M1 anchor).
757
+ expect(variant?.composedWgsl).toContain('material.region');
758
+ expect(variant?.composedWgsl).not.toContain('instances[idx].region');
759
+ });
760
+
761
+ it('(b) boot resolves sprite to PIR=false+SBA=false variant on WebGL2 fallback (AC-03)', () => {
762
+ const spriteEntry = makeSpriteEntry();
763
+ const definesKey = resolveBootDefinesKey(spriteEntry, {
764
+ storageBufferCapable: false,
765
+ isHdrpActive: false,
766
+ });
767
+ expect(definesKey).toBe('PER_INSTANCE_REGION=false+STORAGE_BUFFER_AVAILABLE=false');
768
+ const variant = findVariantByKey(spriteEntry, definesKey);
769
+ expect(variant).toBeDefined();
770
+ expect(variant?.defines.PER_INSTANCE_REGION).toBe(false);
771
+ expect(variant?.defines.STORAGE_BUFFER_AVAILABLE).toBe(false);
772
+ });
773
+
774
+ it('(c) SpriteInstances canonical PIR=true variant resolvable via findVariantByKey("") (AC-04)', () => {
775
+ // SpriteInstances batches request `variantSet=''` via
776
+ // SPRITE_PASS_PER_INSTANCE_REGION_VARIANT_SET (bug-20260708 M2 (c)).
777
+ // The runtime substitution branch in `getMaterialShaderPipeline`
778
+ // (`createRenderer.ts:1730`) resolves this via findVariantByKey with
779
+ // the canonical empty key — returning the PIR=true+SBA=true variant.
780
+ const spriteEntry = makeSpriteEntry();
781
+ const variant = findVariantByKey(spriteEntry, '');
782
+ expect(variant).toBeDefined();
783
+ expect(variant?.defines.PER_INSTANCE_REGION).toBe(true);
784
+ expect(variant?.defines.STORAGE_BUFFER_AVAILABLE).toBe(true);
785
+ // The variant WGSL corresponds to the PIR=true 5653B variant that
786
+ // reads region from instances[idx].region (M1 anchor).
787
+ expect(variant?.composedWgsl).toContain('instances[idx].region');
788
+ });
789
+
790
+ it('(d) boot PIR=false variant and canonical `""` PIR=true variant have distinct source (AC-04 module identity)', () => {
791
+ // AC-04: sprite entities with no SpriteInstances (variantSet=undefined,
792
+ // resolves via lookup.value to PIR=false source) and SpriteInstances
793
+ // batches (variantSet='', resolves via findVariantByKey to PIR=true
794
+ // source) MUST get different shader source strings — which in turn
795
+ // produce different compiled shader modules (5575B vs 5653B). Same
796
+ // materialShaderId, distinct module handles.
797
+ const spriteEntry = makeSpriteEntry();
798
+ const bootDefinesKey = resolveBootDefinesKey(spriteEntry, {
799
+ storageBufferCapable: true,
800
+ isHdrpActive: false,
801
+ });
802
+ const bootVariant = findVariantByKey(spriteEntry, bootDefinesKey);
803
+ const spriteInstancesVariant = findVariantByKey(spriteEntry, '');
804
+ expect(bootVariant?.composedWgsl).not.toBe(spriteInstancesVariant?.composedWgsl);
805
+ });
806
+ });
807
+ }