@forgeax/engine-runtime 0.1.21 → 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 (410) hide show
  1. package/README.md +18 -9
  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/register-runtime-components.d.ts.map +1 -1
  77. package/dist/__tests__/helpers/register-scene-components.d.ts.map +1 -1
  78. package/dist/__tests__/helpers/renderer-device-loss-dawn.d.ts +25 -0
  79. package/dist/__tests__/helpers/renderer-device-loss-dawn.d.ts.map +1 -0
  80. package/dist/__tests__/helpers/standard-material-manifest.d.ts +0 -1
  81. package/dist/__tests__/helpers/standard-material-manifest.d.ts.map +1 -1
  82. package/dist/__tests__/ibl-pipeline-cache.cases.unit.test.d.ts +2 -0
  83. package/dist/__tests__/ibl-pipeline-cache.cases.unit.test.d.ts.map +1 -0
  84. package/dist/__tests__/lazy-catalog.cases.unit.test.d.ts +2 -0
  85. package/dist/__tests__/lazy-catalog.cases.unit.test.d.ts.map +1 -0
  86. package/dist/__tests__/load-by-guid-hdr.cases.unit.test.d.ts +2 -0
  87. package/dist/__tests__/load-by-guid-hdr.cases.unit.test.d.ts.map +1 -0
  88. package/dist/__tests__/load-by-guid-prod-material-parent.cases.unit.test.d.ts +2 -0
  89. package/dist/__tests__/load-by-guid-prod-material-parent.cases.unit.test.d.ts.map +1 -0
  90. package/dist/__tests__/load-by-guid-prod.cases.unit.test.d.ts +2 -0
  91. package/dist/__tests__/load-by-guid-prod.cases.unit.test.d.ts.map +1 -0
  92. package/dist/__tests__/loader-registry.cases.unit.test.d.ts +2 -0
  93. package/dist/__tests__/loader-registry.cases.unit.test.d.ts.map +1 -0
  94. package/dist/__tests__/m7-hdrp-demo-shape.cases.unit.test.d.ts +2 -0
  95. package/dist/__tests__/m7-hdrp-demo-shape.cases.unit.test.d.ts.map +1 -0
  96. package/dist/__tests__/mipmap-formula.cases.unit.test.d.ts +2 -0
  97. package/dist/__tests__/mipmap-formula.cases.unit.test.d.ts.map +1 -0
  98. package/dist/__tests__/mipmap-pipeline-cache.cases.unit.test.d.ts +2 -0
  99. package/dist/__tests__/mipmap-pipeline-cache.cases.unit.test.d.ts.map +1 -0
  100. package/dist/__tests__/parse-asset-payload-material.cases.unit.test.d.ts +2 -0
  101. package/dist/__tests__/parse-asset-payload-material.cases.unit.test.d.ts.map +1 -0
  102. package/dist/__tests__/parse-asset-payload-texture.cases.unit.test.d.ts +2 -0
  103. package/dist/__tests__/parse-asset-payload-texture.cases.unit.test.d.ts.map +1 -0
  104. package/dist/__tests__/parse-scene-payload-refs.cases.unit.test.d.ts +2 -0
  105. package/dist/__tests__/parse-scene-payload-refs.cases.unit.test.d.ts.map +1 -0
  106. package/dist/__tests__/pass-selector.cases.unit.test.d.ts +2 -0
  107. package/dist/__tests__/pass-selector.cases.unit.test.d.ts.map +1 -0
  108. package/dist/__tests__/pipeline-builder.cases.unit.test.d.ts +2 -0
  109. package/dist/__tests__/pipeline-builder.cases.unit.test.d.ts.map +1 -0
  110. package/dist/__tests__/pipeline-cache-key-topology.cases.unit.test.d.ts +2 -0
  111. package/dist/__tests__/pipeline-cache-key-topology.cases.unit.test.d.ts.map +1 -0
  112. package/dist/__tests__/pipeline-vertex-stride-branch.cases.unit.test.d.ts +2 -0
  113. package/dist/__tests__/pipeline-vertex-stride-branch.cases.unit.test.d.ts.map +1 -0
  114. package/dist/__tests__/pipeline.cases.unit.test.d.ts +2 -0
  115. package/dist/__tests__/pipeline.cases.unit.test.d.ts.map +1 -0
  116. package/dist/__tests__/post-process-register.cases.unit.test.d.ts +2 -0
  117. package/dist/__tests__/post-process-register.cases.unit.test.d.ts.map +1 -0
  118. package/dist/__tests__/propagate-transforms.cases.unit.test.d.ts +2 -0
  119. package/dist/__tests__/propagate-transforms.cases.unit.test.d.ts.map +1 -0
  120. package/dist/__tests__/record-all-topology.cases.unit.test.d.ts +2 -0
  121. package/dist/__tests__/record-all-topology.cases.unit.test.d.ts.map +1 -0
  122. package/dist/__tests__/record-draw-branch.cases.unit.test.d.ts +2 -0
  123. package/dist/__tests__/record-draw-branch.cases.unit.test.d.ts.map +1 -0
  124. package/dist/__tests__/record-strip-index-format.cases.unit.test.d.ts +2 -0
  125. package/dist/__tests__/record-strip-index-format.cases.unit.test.d.ts.map +1 -0
  126. package/dist/__tests__/register-with-guid-rgba16float.cases.unit.test.d.ts +2 -0
  127. package/dist/__tests__/register-with-guid-rgba16float.cases.unit.test.d.ts.map +1 -0
  128. package/dist/__tests__/render-data.cases.unit.test.d.ts +2 -0
  129. package/dist/__tests__/render-data.cases.unit.test.d.ts.map +1 -0
  130. package/dist/__tests__/render-error-exhaustive.test-d.d.ts.map +1 -1
  131. package/dist/__tests__/render-query-regression.cases.unit.test.d.ts +2 -0
  132. package/dist/__tests__/render-query-regression.cases.unit.test.d.ts.map +1 -0
  133. package/dist/__tests__/render-system-record-submesh.cases.unit.test.d.ts +2 -0
  134. package/dist/__tests__/render-system-record-submesh.cases.unit.test.d.ts.map +1 -0
  135. package/dist/__tests__/render-system-record-warn-once.cases.unit.test.d.ts +2 -0
  136. package/dist/__tests__/render-system-record-warn-once.cases.unit.test.d.ts.map +1 -0
  137. package/dist/__tests__/renderer-device-loss-multi-world.integration.test.d.ts +2 -0
  138. package/dist/__tests__/renderer-device-loss-multi-world.integration.test.d.ts.map +1 -0
  139. package/dist/__tests__/renderer-device-loss-provider-feasibility.dawn.test.d.ts +2 -0
  140. package/dist/__tests__/renderer-device-loss-provider-feasibility.dawn.test.d.ts.map +1 -0
  141. package/dist/__tests__/renderer-device-loss-recovery.dawn.test.d.ts +2 -0
  142. package/dist/__tests__/renderer-device-loss-recovery.dawn.test.d.ts.map +1 -0
  143. package/dist/__tests__/renderer-draw-world.cases.unit.test.d.ts +2 -0
  144. package/dist/__tests__/renderer-draw-world.cases.unit.test.d.ts.map +1 -0
  145. package/dist/__tests__/renderer-recovery-consumer.integration.test.d.ts +2 -0
  146. package/dist/__tests__/renderer-recovery-consumer.integration.test.d.ts.map +1 -0
  147. package/dist/__tests__/renderstate-pipeline-cache.cases.unit.test.d.ts +2 -0
  148. package/dist/__tests__/renderstate-pipeline-cache.cases.unit.test.d.ts.map +1 -0
  149. package/dist/__tests__/resolve-scene-guids.cases.unit.test.d.ts +2 -0
  150. package/dist/__tests__/resolve-scene-guids.cases.unit.test.d.ts.map +1 -0
  151. package/dist/__tests__/shadow-skip-non-triangle.cases.unit.test.d.ts +2 -0
  152. package/dist/__tests__/shadow-skip-non-triangle.cases.unit.test.d.ts.map +1 -0
  153. package/dist/__tests__/skin-errors-kebab-case.cases.unit.test.d.ts +2 -0
  154. package/dist/__tests__/skin-errors-kebab-case.cases.unit.test.d.ts.map +1 -0
  155. package/dist/__tests__/skin-instances-coexist.cases.unit.test.d.ts +2 -0
  156. package/dist/__tests__/skin-instances-coexist.cases.unit.test.d.ts.map +1 -0
  157. package/dist/__tests__/skin-m2-extract-happy.cases.unit.test.d.ts +2 -0
  158. package/dist/__tests__/skin-m2-extract-happy.cases.unit.test.d.ts.map +1 -0
  159. package/dist/__tests__/skin-m2-extract-joint-error.cases.unit.test.d.ts +2 -0
  160. package/dist/__tests__/skin-m2-extract-joint-error.cases.unit.test.d.ts.map +1 -0
  161. package/dist/__tests__/skin-m2-extract-null-assets.cases.unit.test.d.ts +2 -0
  162. package/dist/__tests__/skin-m2-extract-null-assets.cases.unit.test.d.ts.map +1 -0
  163. package/dist/__tests__/skin-m2-extract-skeleton-error.cases.unit.test.d.ts +2 -0
  164. package/dist/__tests__/skin-m2-extract-skeleton-error.cases.unit.test.d.ts.map +1 -0
  165. package/dist/__tests__/skin-m3-record-cache.cases.unit.test.d.ts +2 -0
  166. package/dist/__tests__/skin-m3-record-cache.cases.unit.test.d.ts.map +1 -0
  167. package/dist/__tests__/skin-palette-extract.cases.unit.test.d.ts +2 -0
  168. package/dist/__tests__/skin-palette-extract.cases.unit.test.d.ts.map +1 -0
  169. package/dist/__tests__/skin-pipeline-routing.cases.unit.test.d.ts +2 -0
  170. package/dist/__tests__/skin-pipeline-routing.cases.unit.test.d.ts.map +1 -0
  171. package/dist/__tests__/skin.cases.unit.test.d.ts +2 -0
  172. package/dist/__tests__/skin.cases.unit.test.d.ts.map +1 -0
  173. package/dist/__tests__/skybox-extract.cases.unit.test.d.ts +2 -0
  174. package/dist/__tests__/skybox-extract.cases.unit.test.d.ts.map +1 -0
  175. package/dist/__tests__/skybox-shader-compile.cases.unit.test.d.ts +2 -0
  176. package/dist/__tests__/skybox-shader-compile.cases.unit.test.d.ts.map +1 -0
  177. package/dist/__tests__/skylight-bind-group.cases.unit.test.d.ts +2 -0
  178. package/dist/__tests__/skylight-bind-group.cases.unit.test.d.ts.map +1 -0
  179. package/dist/__tests__/skylight-component.cases.unit.test.d.ts +2 -0
  180. package/dist/__tests__/skylight-component.cases.unit.test.d.ts.map +1 -0
  181. package/dist/__tests__/skylight-fallback-path.cases.unit.test.d.ts +2 -0
  182. package/dist/__tests__/skylight-fallback-path.cases.unit.test.d.ts.map +1 -0
  183. package/dist/__tests__/skylight-pipeline-layout.cases.unit.test.d.ts +2 -0
  184. package/dist/__tests__/skylight-pipeline-layout.cases.unit.test.d.ts.map +1 -0
  185. package/dist/__tests__/skylight-solid-color-extract.cases.unit.test.d.ts +2 -0
  186. package/dist/__tests__/skylight-solid-color-extract.cases.unit.test.d.ts.map +1 -0
  187. package/dist/__tests__/sprite-animation-tick-boundary.cases.unit.test.d.ts +2 -0
  188. package/dist/__tests__/sprite-animation-tick-boundary.cases.unit.test.d.ts.map +1 -0
  189. package/dist/__tests__/sprite-animation-tick-clamp.cases.unit.test.d.ts +2 -0
  190. package/dist/__tests__/sprite-animation-tick-clamp.cases.unit.test.d.ts.map +1 -0
  191. package/dist/__tests__/sprite-animation-tick-frame-duration-negative.cases.unit.test.d.ts +2 -0
  192. package/dist/__tests__/sprite-animation-tick-frame-duration-negative.cases.unit.test.d.ts.map +1 -0
  193. package/dist/__tests__/sprite-animation-tick-frame-duration-zero.cases.unit.test.d.ts +2 -0
  194. package/dist/__tests__/sprite-animation-tick-frame-duration-zero.cases.unit.test.d.ts.map +1 -0
  195. package/dist/__tests__/sprite-animation-tick-loop.cases.unit.test.d.ts +2 -0
  196. package/dist/__tests__/sprite-animation-tick-loop.cases.unit.test.d.ts.map +1 -0
  197. package/dist/__tests__/sprite-animation-tick-override-probe.cases.unit.test.d.ts +2 -0
  198. package/dist/__tests__/sprite-animation-tick-override-probe.cases.unit.test.d.ts.map +1 -0
  199. package/dist/__tests__/sprite-animation-tick-regions-mismatch.cases.unit.test.d.ts +2 -0
  200. package/dist/__tests__/sprite-animation-tick-regions-mismatch.cases.unit.test.d.ts.map +1 -0
  201. package/dist/__tests__/storage-buffer-caps.cases.unit.test.d.ts +2 -0
  202. package/dist/__tests__/storage-buffer-caps.cases.unit.test.d.ts.map +1 -0
  203. package/dist/__tests__/systems.cases.unit.test.d.ts +2 -0
  204. package/dist/__tests__/systems.cases.unit.test.d.ts.map +1 -0
  205. package/dist/__tests__/tonemap-hdr-target.cases.unit.test.d.ts +2 -0
  206. package/dist/__tests__/tonemap-hdr-target.cases.unit.test.d.ts.map +1 -0
  207. package/dist/__tests__/tonemap-pipeline-split.cases.unit.test.d.ts +2 -0
  208. package/dist/__tests__/tonemap-pipeline-split.cases.unit.test.d.ts.map +1 -0
  209. package/dist/__tests__/tonemap.cases.unit.test.d.ts +2 -0
  210. package/dist/__tests__/tonemap.cases.unit.test.d.ts.map +1 -0
  211. package/dist/__tests__/transparent-sort-config-get.cases.unit.test.d.ts +2 -0
  212. package/dist/__tests__/transparent-sort-config-get.cases.unit.test.d.ts.map +1 -0
  213. package/dist/__tests__/transparent-sort-config-set.cases.unit.test.d.ts +2 -0
  214. package/dist/__tests__/transparent-sort-config-set.cases.unit.test.d.ts.map +1 -0
  215. package/dist/__tests__/transparent-sort.cases.unit.test.d.ts +2 -0
  216. package/dist/__tests__/transparent-sort.cases.unit.test.d.ts.map +1 -0
  217. package/dist/__tests__/upload-texture-consistency.cases.unit.test.d.ts +2 -0
  218. package/dist/__tests__/upload-texture-consistency.cases.unit.test.d.ts.map +1 -0
  219. package/dist/__tests__/verify-revisions.cases.unit.test.d.ts +2 -0
  220. package/dist/__tests__/verify-revisions.cases.unit.test.d.ts.map +1 -0
  221. package/dist/__tests__/wire-default-loaders.cases.unit.test.d.ts +2 -0
  222. package/dist/__tests__/wire-default-loaders.cases.unit.test.d.ts.map +1 -0
  223. package/dist/__tests__/zero-camera-clear-fallback.cases.unit.test.d.ts +2 -0
  224. package/dist/__tests__/zero-camera-clear-fallback.cases.unit.test.d.ts.map +1 -0
  225. package/dist/createRenderer.d.ts.map +1 -1
  226. package/dist/dev-import-transport.d.ts.map +1 -1
  227. package/dist/index.mjs +4 -3
  228. package/dist/index.mjs.map +1 -1
  229. package/dist/sprite-param-values.d.ts +1 -1
  230. package/package.json +30 -30
  231. package/src/__tests__/advance-animation-player.cases.unit.test.ts +1666 -0
  232. package/src/__tests__/animation-graph-asset-union.test-d.ts +2 -0
  233. package/src/__tests__/asset-brand-and-name-retirement.test.ts +4 -3
  234. package/src/__tests__/asset-registry-aabb.cases.unit.test.ts +676 -0
  235. package/src/__tests__/asset-registry-bootstrap.cases.unit.test.ts +472 -0
  236. package/src/__tests__/asset-registry-builtin-nineslice.cases.unit.test.ts +481 -0
  237. package/src/__tests__/asset-registry-d9-tile-sampler-soft-warn.cases.unit.test.ts +568 -0
  238. package/src/__tests__/asset-registry-guid.cases.unit.test.ts +500 -0
  239. package/src/__tests__/asset-registry-material-validate.cases.unit.test.ts +756 -0
  240. package/src/__tests__/asset-registry-mesh-fail-fast.cases.unit.test.ts +591 -0
  241. package/src/__tests__/asset-registry-scene.cases.unit.test.ts +501 -0
  242. package/src/__tests__/asset-registry-scene.test-d.ts +1 -0
  243. package/src/__tests__/asset-registry-sprite-slices-validate.cases.unit.test.ts +536 -0
  244. package/src/__tests__/asset-renderer-assembly.cases.unit.test.ts +496 -0
  245. package/src/__tests__/asset.cases.unit.test.ts +439 -0
  246. package/src/__tests__/bind-group-cache-keying.test.ts +6 -2
  247. package/src/__tests__/bloom-gating.cases.unit.test.ts +428 -0
  248. package/src/__tests__/builtin-guid-ssot.cases.unit.test.ts +472 -0
  249. package/src/__tests__/builtin-pack.cases.unit.test.ts +639 -0
  250. package/src/__tests__/builtin-shader-register-e2e.test.ts +1 -1
  251. package/src/__tests__/camera-antialias.cases.unit.test.ts +372 -0
  252. package/src/__tests__/camera-bloom.cases.unit.test.ts +396 -0
  253. package/src/__tests__/camera-clear-schema.cases.unit.test.ts +391 -0
  254. package/src/__tests__/camera-ortho.cases.unit.test.ts +484 -0
  255. package/src/__tests__/cluster-binner.cases.unit.test.ts +817 -0
  256. package/src/__tests__/collect-scene-mount-collapse.test.ts +5 -1
  257. package/src/__tests__/collect-transient-roundtrip.test.ts +14 -3
  258. package/src/__tests__/components.test-d.ts +9 -6
  259. package/src/__tests__/components.unit.test.ts +23 -19
  260. package/src/__tests__/create-renderer-fallback-shader-manifest.cases.unit.test.ts +345 -0
  261. package/src/__tests__/create-renderer-uniform-fallback.cases.unit.test.ts +445 -0
  262. package/src/__tests__/cubemap-upload.cases.unit.test.ts +593 -0
  263. package/src/__tests__/dawn/ibl-precompute-readback.dawn.test.ts +184 -17
  264. package/src/__tests__/dawn/material-alpha.dawn.test.ts +17 -4
  265. package/src/__tests__/derive-bgl-integration.test.ts +8 -8
  266. package/src/__tests__/dev-import-transport.cases.unit.test.ts +503 -0
  267. package/src/__tests__/engine-metrics.cases.unit.test.ts +371 -0
  268. package/src/__tests__/equirect-projection-error.cases.unit.test.ts +416 -0
  269. package/src/__tests__/errors.unit.test.ts +3 -0
  270. package/src/__tests__/extract-frames-merge.test.ts +3 -0
  271. package/src/__tests__/font-asset-load.cases.unit.test.ts +510 -0
  272. package/src/__tests__/fxaa-intermediate-texture.cases.unit.test.ts +690 -0
  273. package/src/__tests__/fxaa-pipeline.cases.unit.test.ts +546 -0
  274. package/src/__tests__/gpu-residency.cases.unit.test.ts +479 -0
  275. package/src/__tests__/gpu-resource-release.cases.unit.test.ts +929 -0
  276. package/src/__tests__/handle-quad.cases.unit.test.ts +491 -0
  277. package/src/__tests__/hdrp-bgl-slots.cases.unit.test.ts +451 -0
  278. package/src/__tests__/hdrp-grid-invalid.cases.unit.test.ts +408 -0
  279. package/src/__tests__/hdrp-index-list-overflow-once.cases.unit.test.ts +405 -0
  280. package/src/__tests__/hdrp-light-budget.cases.unit.test.ts +483 -0
  281. package/src/__tests__/hdrp-pipeline-asset-config.cases.unit.test.ts +441 -0
  282. package/src/__tests__/helpers/register-runtime-components.ts +9 -1
  283. package/src/__tests__/helpers/register-scene-components.ts +9 -2
  284. package/src/__tests__/helpers/renderer-device-loss-dawn.ts +69 -0
  285. package/src/__tests__/helpers/standard-material-manifest.ts +11 -15
  286. package/src/__tests__/ibl-pipeline-cache.cases.unit.test.ts +415 -0
  287. package/src/__tests__/lazy-catalog.cases.unit.test.ts +848 -0
  288. package/src/__tests__/light-gpu-byte-neutral.unit.test.ts +14 -15
  289. package/src/__tests__/light-snapshot.test-d.ts +27 -1
  290. package/src/__tests__/lights-preservation.fixup.test.ts +19 -21
  291. package/src/__tests__/lights.unit.test.ts +2076 -1
  292. package/src/__tests__/load-by-guid-hdr.cases.unit.test.ts +582 -0
  293. package/src/__tests__/load-by-guid-prod-material-parent.cases.unit.test.ts +1514 -0
  294. package/src/__tests__/load-by-guid-prod.cases.unit.test.ts +557 -0
  295. package/src/__tests__/loader-name-survival.test.ts +8 -9
  296. package/src/__tests__/loader-registry.cases.unit.test.ts +767 -0
  297. package/src/__tests__/m7-hdrp-demo-shape.cases.unit.test.ts +361 -0
  298. package/src/__tests__/material-texture-uv-scale.unit.test.ts +2 -2
  299. package/src/__tests__/materials.unit.test.ts +29 -10
  300. package/src/__tests__/mipmap-formula.cases.unit.test.ts +434 -0
  301. package/src/__tests__/mipmap-pipeline-cache.cases.unit.test.ts +490 -0
  302. package/src/__tests__/package-promotion.test.ts +7 -11
  303. package/src/__tests__/parse-asset-payload-material.cases.unit.test.ts +565 -0
  304. package/src/__tests__/parse-asset-payload-texture.cases.unit.test.ts +720 -0
  305. package/src/__tests__/parse-scene-payload-refs.cases.unit.test.ts +767 -0
  306. package/src/__tests__/pass-selector.cases.unit.test.ts +376 -0
  307. package/src/__tests__/pbr-pipeline.unit.test.ts +39 -21
  308. package/src/__tests__/pipeline-builder.cases.unit.test.ts +624 -0
  309. package/src/__tests__/pipeline-cache-key-topology.cases.unit.test.ts +807 -0
  310. package/src/__tests__/pipeline-vertex-stride-branch.cases.unit.test.ts +379 -0
  311. package/src/__tests__/pipeline.cases.unit.test.ts +370 -0
  312. package/src/__tests__/point-light-shadow.browser.test.ts +1 -0
  313. package/src/__tests__/point-light-shadow.unit.test.ts +18 -98
  314. package/src/__tests__/post-process-register.cases.unit.test.ts +405 -0
  315. package/src/__tests__/propagate-transforms.cases.unit.test.ts +502 -0
  316. package/src/__tests__/propagate-transforms.dawn.test.ts +34 -18
  317. package/src/__tests__/record-all-topology.cases.unit.test.ts +608 -0
  318. package/src/__tests__/record-draw-branch.cases.unit.test.ts +621 -0
  319. package/src/__tests__/record-strip-index-format.cases.unit.test.ts +419 -0
  320. package/src/__tests__/record-worldid-isolation.test.ts +16 -32
  321. package/src/__tests__/register-with-guid-rgba16float.cases.unit.test.ts +469 -0
  322. package/src/__tests__/render-data.cases.unit.test.ts +553 -0
  323. package/src/__tests__/render-error-exhaustive.test-d.ts +11 -0
  324. package/src/__tests__/render-query-regression.cases.unit.test.ts +531 -0
  325. package/src/__tests__/render-system-extract.test.ts +5 -5
  326. package/src/__tests__/render-system-record-multi-material-textureview.test.ts +1 -0
  327. package/src/__tests__/render-system-record-per-submesh-transparency.test.ts +1 -0
  328. package/src/__tests__/render-system-record-submesh.cases.unit.test.ts +740 -0
  329. package/src/__tests__/render-system-record-warn-once.cases.unit.test.ts +418 -0
  330. package/src/__tests__/render-system-record.test.ts +15 -33
  331. package/src/__tests__/renderer-device-loss-multi-world.integration.test.ts +103 -0
  332. package/src/__tests__/renderer-device-loss-provider-feasibility.dawn.test.ts +124 -0
  333. package/src/__tests__/renderer-device-loss-recovery.dawn.test.ts +50 -0
  334. package/src/__tests__/renderer-draw-world.cases.unit.test.ts +514 -0
  335. package/src/__tests__/renderer-recovery-consumer.integration.test.ts +96 -0
  336. package/src/__tests__/renderer-surface.unit.test.ts +0 -2
  337. package/src/__tests__/renderstate-pipeline-cache.cases.unit.test.ts +631 -0
  338. package/src/__tests__/resolve-scene-guids.cases.unit.test.ts +1123 -0
  339. package/src/__tests__/resolveName-unified-entry.test.ts +6 -6
  340. package/src/__tests__/resolveName.test.ts +14 -2
  341. package/src/__tests__/shadow-caster-empty-schema.test.ts +1 -1
  342. package/src/__tests__/shadow-csm-cascade-loadop.test.ts +1 -0
  343. package/src/__tests__/shadow-csm-tile-consistency.test.ts +1 -0
  344. package/src/__tests__/shadow-csm-ubo.test.ts +3 -1
  345. package/src/__tests__/shadow-fields-observable.dawn.test.ts +15 -4
  346. package/src/__tests__/shadow-m1.dawn.test.ts +1 -1
  347. package/src/__tests__/shadow-skip-non-triangle.cases.unit.test.ts +681 -0
  348. package/src/__tests__/skin-errors-kebab-case.cases.unit.test.ts +475 -0
  349. package/src/__tests__/skin-extract-getarrayview-count.unit.test.ts +5 -4
  350. package/src/__tests__/skin-instances-coexist.cases.unit.test.ts +375 -0
  351. package/src/__tests__/skin-m2-extract-happy.cases.unit.test.ts +592 -0
  352. package/src/__tests__/skin-m2-extract-joint-error.cases.unit.test.ts +619 -0
  353. package/src/__tests__/skin-m2-extract-null-assets.cases.unit.test.ts +569 -0
  354. package/src/__tests__/skin-m2-extract-skeleton-error.cases.unit.test.ts +585 -0
  355. package/src/__tests__/skin-m3-record-cache.cases.unit.test.ts +457 -0
  356. package/src/__tests__/skin-palette-extract.cases.unit.test.ts +635 -0
  357. package/src/__tests__/skin-parented-double-transform.test.ts +5 -5
  358. package/src/__tests__/skin-pipeline-routing.cases.unit.test.ts +411 -0
  359. package/src/__tests__/skin.cases.unit.test.ts +421 -0
  360. package/src/__tests__/skinned-shadow-mixed.dawn.test.ts +1 -6
  361. package/src/__tests__/skybox-extract.cases.unit.test.ts +511 -0
  362. package/src/__tests__/skybox-shader-compile.cases.unit.test.ts +437 -0
  363. package/src/__tests__/skylight-bind-group.cases.unit.test.ts +642 -0
  364. package/src/__tests__/skylight-component.cases.unit.test.ts +386 -0
  365. package/src/__tests__/skylight-fallback-path.cases.unit.test.ts +566 -0
  366. package/src/__tests__/skylight-lazy-projection.test.ts +35 -20
  367. package/src/__tests__/skylight-pipeline-layout.cases.unit.test.ts +506 -0
  368. package/src/__tests__/skylight-solid-color-extract.cases.unit.test.ts +399 -0
  369. package/src/__tests__/sprite-animation-tick-boundary.cases.unit.test.ts +521 -0
  370. package/src/__tests__/sprite-animation-tick-clamp.cases.unit.test.ts +453 -0
  371. package/src/__tests__/sprite-animation-tick-frame-duration-negative.cases.unit.test.ts +447 -0
  372. package/src/__tests__/sprite-animation-tick-frame-duration-zero.cases.unit.test.ts +395 -0
  373. package/src/__tests__/sprite-animation-tick-loop.cases.unit.test.ts +494 -0
  374. package/src/__tests__/sprite-animation-tick-override-probe.cases.unit.test.ts +439 -0
  375. package/src/__tests__/sprite-animation-tick-regions-mismatch.cases.unit.test.ts +466 -0
  376. package/src/__tests__/sprite-lit-bgl-byte-identical.test.ts +16 -18
  377. package/src/__tests__/sprite-lit-pipeline-cache-key.test.ts +1 -1
  378. package/src/__tests__/ssao-bgl.test.ts +2 -1
  379. package/src/__tests__/ssao-pipelines.test.ts +1 -1
  380. package/src/__tests__/storage-buffer-caps.cases.unit.test.ts +373 -0
  381. package/src/__tests__/systems.cases.unit.test.ts +425 -0
  382. package/src/__tests__/template-game-default-api-contract.test.ts +45 -23
  383. package/src/__tests__/tilemap-chunk-extract.overhang-bounds.test.ts +1 -1
  384. package/src/__tests__/tilemap-chunk-extract.propagate-order.test.ts +10 -4
  385. package/src/__tests__/tonemap-hdr-target.cases.unit.test.ts +670 -0
  386. package/src/__tests__/tonemap-pipeline-split.cases.unit.test.ts +867 -0
  387. package/src/__tests__/tonemap.cases.unit.test.ts +446 -0
  388. package/src/__tests__/transform-hierarchy-pixel-diff.dawn.test.ts +2 -2
  389. package/src/__tests__/transparent-sort-config-get.cases.unit.test.ts +395 -0
  390. package/src/__tests__/transparent-sort-config-set.cases.unit.test.ts +424 -0
  391. package/src/__tests__/transparent-sort.cases.unit.test.ts +684 -0
  392. package/src/__tests__/upload-texture-consistency.cases.unit.test.ts +478 -0
  393. package/src/__tests__/verify-revisions.cases.unit.test.ts +619 -0
  394. package/src/__tests__/wire-default-loaders.cases.unit.test.ts +461 -0
  395. package/src/__tests__/zero-camera-clear-fallback.cases.unit.test.ts +374 -0
  396. package/src/collect-scene-asset.ts +1 -1
  397. package/src/components/__tests__/transform-vec-schema.test.ts +2 -2
  398. package/src/createRenderer.ts +1 -0
  399. package/src/dev-import-transport.ts +2 -1
  400. package/src/sprite-param-values.ts +1 -1
  401. package/dist/.tsbuildinfo +0 -1
  402. package/dist/__tests__/asset.unit.test.d.ts +0 -2
  403. package/dist/__tests__/asset.unit.test.d.ts.map +0 -1
  404. package/dist/__tests__/pipeline.unit.test.d.ts +0 -2
  405. package/dist/__tests__/pipeline.unit.test.d.ts.map +0 -1
  406. package/dist/__tests__/systems.unit.test.d.ts +0 -2
  407. package/dist/__tests__/systems.unit.test.d.ts.map +0 -1
  408. package/src/__tests__/asset.unit.test.ts +0 -7110
  409. package/src/__tests__/pipeline.unit.test.ts +0 -5283
  410. package/src/__tests__/systems.unit.test.ts +0 -9210
@@ -0,0 +1,1123 @@
1
+ // @ts-nocheck — merged file: cross-source type narrowing failures from blocks originally outside src/ rootDir
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__/asset-registry.test.ts
7
+ // - packages/runtime/__tests__/loader-registry.test.ts
8
+ // - packages/runtime/__tests__/wire-default-loaders.test.ts
9
+ // - packages/runtime/src/__tests__/asset-registry-aabb.test.ts
10
+ // - packages/runtime/src/__tests__/asset-registry-builtin-nineslice.test.ts
11
+ // - packages/runtime/src/__tests__/asset-registry-d9-tile-sampler-soft-warn.test.ts
12
+ // - packages/runtime/src/__tests__/asset-registry-guid.test.ts
13
+ // - packages/runtime/src/__tests__/asset-registry-material-validate.test.ts
14
+ // - packages/runtime/src/__tests__/asset-registry-mesh-fail-fast.test.ts
15
+ // - packages/runtime/src/__tests__/asset-registry-scene.test.ts
16
+ // - packages/runtime/src/__tests__/asset-registry-sprite-slices-validate.test.ts
17
+ // - packages/runtime/src/__tests__/auto-select.test.ts
18
+ // - packages/runtime/src/__tests__/bindgroup-resize-invalidation.test.ts
19
+ // - packages/runtime/src/__tests__/builtin-guid-ssot.test.ts
20
+ // - packages/runtime/src/__tests__/builtin-pack.test.ts
21
+ // - packages/runtime/src/__tests__/cube-texture-narrowing.test.ts
22
+ // - packages/runtime/src/__tests__/cubemap-upload.test.ts
23
+ // - packages/runtime/src/__tests__/dev-import-transport.test.ts
24
+ // - packages/runtime/src/__tests__/font-asset-load.test.ts
25
+ // - packages/runtime/src/__tests__/handle-quad.test.ts
26
+ // - packages/runtime/src/__tests__/lazy-catalog.test.ts
27
+ // - packages/runtime/src/__tests__/load-by-guid-hdr.test.ts
28
+ // - packages/runtime/src/__tests__/load-by-guid-prod-material-parent.test.ts
29
+ // - packages/runtime/src/__tests__/load-by-guid-prod.test.ts
30
+ // - packages/runtime/src/__tests__/mipmap-formula.test.ts
31
+ // - packages/runtime/src/__tests__/mipmap-pipeline-cache.test.ts
32
+ // - packages/runtime/src/__tests__/parse-asset-payload-material.test.ts
33
+ // - packages/runtime/src/__tests__/parse-asset-payload-texture.test.ts
34
+ // - packages/runtime/src/__tests__/parse-scene-payload-refs.test.ts
35
+ // - packages/runtime/src/__tests__/register-with-guid-rgba16float.test.ts
36
+ // - packages/runtime/src/__tests__/resolve-scene-guids.test.ts
37
+ // - packages/runtime/src/__tests__/upload-texture-consistency.test.ts
38
+ // - packages/runtime/src/__tests__/verify-revisions.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 { readFileSync } from 'node:fs';
47
+ import { fileURLToPath } from 'node:url';
48
+ import * as assetRegistryModule from '@forgeax/engine-assets-runtime';
49
+ import {
50
+ AssetRegistry,
51
+ animationClipLoader,
52
+ buildSceneChildContext,
53
+ fontLoader,
54
+ getOrCreateMipmapPipeline,
55
+ HANDLE_CUBE,
56
+ HANDLE_CYLINDER,
57
+ HANDLE_QUAD,
58
+ HANDLE_SPHERE,
59
+ HANDLE_TRIANGLE,
60
+ INLINE_PACK_LOADERS,
61
+ LoaderRegistry,
62
+ materialLoader,
63
+ meshLoader,
64
+ mipmapCacheSize,
65
+ numMipLevels,
66
+ PACK_ARTIFACT_LOADERS,
67
+ resolveAssetHandle,
68
+ sceneLoader,
69
+ skeletonLoader,
70
+ skinLoader,
71
+ textureLoader,
72
+ walkMaterialPassesOverSharedRefs,
73
+ wireDefaultLoaders,
74
+ } from '@forgeax/engine-assets-runtime';
75
+ import { audioLoader } from '@forgeax/engine-audio-webaudio';
76
+ import { defineComponent, World } from '@forgeax/engine-ecs';
77
+ import {
78
+ createBoxGeometry,
79
+ createPlaneGeometry,
80
+ meshFromInterleaved,
81
+ PROCEDURAL_FLOATS_PER_VERTEX,
82
+ } from '@forgeax/engine-geometry';
83
+ import { AssetGuid } from '@forgeax/engine-pack/guid';
84
+ import { ok, ok as rhiOk } from '@forgeax/engine-rhi';
85
+ import { rhi } from '@forgeax/engine-rhi-null';
86
+ import { ShaderRegistry } from '@forgeax/engine-shader';
87
+ import type {
88
+ EquirectAsset,
89
+ Handle,
90
+ MaterialAsset,
91
+ MaterialPass,
92
+ SamplerAsset,
93
+ } from '@forgeax/engine-types';
94
+ import { AssetError, toShared, toUnique, unwrapHandle } from '@forgeax/engine-types';
95
+ import { afterEach, beforeEach, describe, expect, expectTypeOf, it, vi } from 'vitest';
96
+ import { deriveBuiltin } from '../../../pack/src/builtin';
97
+ import { DeviceScope } from '../../../render/src/device/device-scope';
98
+ import { GpuResidencyCache } from '../../../render/src/device/gpu-residency';
99
+ import { createEngineMetrics } from '../../../render/src/engine-metrics';
100
+ import { createRenderer as createRuntimeRenderer } from '../createRenderer';
101
+ import { createDevImportTransport, type ImportTransport } from '../dev-import-transport';
102
+ import { EngineEnvironmentError } from '../errors/environment';
103
+ import { makeMockShaderRegistry } from './helpers/mock-shader-registry';
104
+ import { requireRenderer } from './renderer-test-utils';
105
+
106
+ vi.mock('@forgeax/engine-rhi-webgpu', async () => {
107
+ spies.webgpuImportCount += 1;
108
+ const actualRhi =
109
+ await vi.importActual<typeof import('@forgeax/engine-rhi')>('@forgeax/engine-rhi');
110
+ spies.rhiErrorCtor = actualRhi.RhiError;
111
+ const fakeAdapter = {
112
+ features: new Set<string>(),
113
+ limits: {} as Readonly<Record<string, number>>,
114
+ async requestDevice() {
115
+ return actualRhi.ok(makeFakeRhiDevice());
116
+ },
117
+ };
118
+ return {
119
+ rhi: {
120
+ requestAdapter: async () => {
121
+ if (spies.rhiWebgpuRequestAdapterShould === 'reject-rhi-not-available') {
122
+ return actualRhi.err(
123
+ new actualRhi.RhiError({
124
+ code: 'rhi-not-available',
125
+ expected: 'navigator.gpu available',
126
+ hint: 'unit-test: forced rhi-not-available',
127
+ }),
128
+ );
129
+ }
130
+ if (spies.rhiWebgpuRequestAdapterShould === 'reject-adapter-null') {
131
+ return actualRhi.err(
132
+ new actualRhi.RhiError({
133
+ code: 'adapter-unavailable',
134
+ expected: 'requestAdapter returns non-null',
135
+ hint: 'unit-test: forced adapter-unavailable',
136
+ }),
137
+ );
138
+ }
139
+ return actualRhi.ok(fakeAdapter);
140
+ },
141
+ acquireCanvasContext: (_canvas: unknown) => {
142
+ return actualRhi.ok({
143
+ configure: () => actualRhi.ok(undefined),
144
+ unconfigure: () => actualRhi.ok(undefined),
145
+ getCurrentTexture: () => actualRhi.ok({ __brand: 'TextureView' }),
146
+ });
147
+ },
148
+ },
149
+ createShaderModule: async () => actualRhi.ok({ __brand: 'ShaderModule' } as unknown as object),
150
+ translateErrorEventToRhiError: () =>
151
+ actualRhi.err(
152
+ new actualRhi.RhiError({
153
+ code: 'webgpu-runtime-error',
154
+ expected: 'GPU error event translates to RhiError',
155
+ hint: 'unit-test mock event translation',
156
+ }),
157
+ ),
158
+ _internal_getRawDevice: () => undefined,
159
+ };
160
+ });
161
+ vi.mock('@forgeax/engine-rhi-wgpu', async () => {
162
+ spies.wgpuImportCount += 1;
163
+ const actualRhi =
164
+ await vi.importActual<typeof import('@forgeax/engine-rhi')>('@forgeax/engine-rhi');
165
+ const fakeAdapter = {
166
+ features: new Set<string>(),
167
+ limits: {} as Readonly<Record<string, number>>,
168
+ async requestDevice() {
169
+ return actualRhi.ok(makeFakeRhiDevice());
170
+ },
171
+ };
172
+ return {
173
+ rhi: {
174
+ requestAdapter: async () => {
175
+ if (spies.rhiWgpuRequestAdapterShould === 'reject-rhi-not-available') {
176
+ return actualRhi.err(
177
+ new actualRhi.RhiError({
178
+ code: 'rhi-not-available',
179
+ expected: 'wgpu webgl backend available',
180
+ hint: 'unit-test: forced rhi-not-available on rhi-wgpu',
181
+ }),
182
+ );
183
+ }
184
+ return actualRhi.ok(fakeAdapter);
185
+ },
186
+ acquireCanvasContext: (_canvas: unknown) => {
187
+ return actualRhi.ok({
188
+ configure: () => actualRhi.ok(undefined),
189
+ unconfigure: () => actualRhi.ok(undefined),
190
+ getCurrentTexture: () => actualRhi.ok({ __brand: 'TextureView' }),
191
+ });
192
+ },
193
+ },
194
+ ensureReady: async () => {
195
+ spies.ensureReadyCount += 1;
196
+ if (spies.rhiWgpuEnsureReadyShould === 'reject-load-failed') {
197
+ throw new Error('unit-test: forced rhi-wgpu wasm load failure');
198
+ }
199
+ return undefined;
200
+ },
201
+ createShaderModule: async () => actualRhi.ok({ __brand: 'ShaderModule' } as unknown as object),
202
+ translateErrorEventToRhiError: () =>
203
+ actualRhi.err(
204
+ new actualRhi.RhiError({
205
+ code: 'webgpu-runtime-error',
206
+ expected: 'GPU error event translates to RhiError',
207
+ hint: 'unit-test mock event translation',
208
+ }),
209
+ ),
210
+ _internal_getRawDevice: () => undefined,
211
+ };
212
+ });
213
+ const spies = vi.hoisted(() => ({
214
+ webgpuImportCount: 0,
215
+ wgpuImportCount: 0,
216
+ ensureReadyCount: 0,
217
+ // Failure toggles set by individual tests before invoking createRenderer.
218
+ rhiWebgpuRequestAdapterShould: 'success' as
219
+ | 'success'
220
+ | 'reject-rhi-not-available'
221
+ | 'reject-adapter-null',
222
+ rhiWgpuEnsureReadyShould: 'success' as 'success' | 'reject-load-failed',
223
+ rhiWgpuRequestAdapterShould: 'success' as 'success' | 'reject-rhi-not-available',
224
+ rhiErrorCtor: undefined as undefined | (new (...args: never[]) => Error),
225
+ reset(): void {
226
+ this.webgpuImportCount = 0;
227
+ this.wgpuImportCount = 0;
228
+ this.ensureReadyCount = 0;
229
+ this.rhiWebgpuRequestAdapterShould = 'success';
230
+ this.rhiWgpuEnsureReadyShould = 'success';
231
+ this.rhiWgpuRequestAdapterShould = 'success';
232
+ },
233
+ }));
234
+ function makeFakeRhiDevice(): Record<string, unknown> {
235
+ let resolveLost!: (info: unknown) => void;
236
+ const lost = new Promise<unknown>((res) => {
237
+ resolveLost = res;
238
+ });
239
+ void resolveLost;
240
+ return {
241
+ __brand: 'RhiDevice',
242
+ lost,
243
+ features: new Set<string>(),
244
+ limits: {},
245
+ // feat-20260707 M5 / w33: createRenderer projects RhiCaps.textureCompression*
246
+ // into TranscodeCaps right after building the registry, so the fake device
247
+ // must expose a caps object (the real RhiDevice always carries one).
248
+ caps: {
249
+ textureCompressionBc: false,
250
+ textureCompressionEtc2: false,
251
+ textureCompressionAstc: false,
252
+ },
253
+ queue: {
254
+ submit: () => undefined,
255
+ writeBuffer: () => undefined,
256
+ },
257
+ createBuffer: () => ({ ok: true, value: { __brand: 'Buffer' } }),
258
+ createTexture: () => ({ ok: true, value: { __brand: 'Texture' } }),
259
+ createBindGroupLayout: () => ({ ok: true, value: { __brand: 'BindGroupLayout' } }),
260
+ createBindGroup: () => ({ ok: true, value: { __brand: 'BindGroup' } }),
261
+ createPipelineLayout: () => ({ ok: true, value: { __brand: 'PipelineLayout' } }),
262
+ createRenderPipeline: () => ({ ok: true, value: { __brand: 'RenderPipeline' } }),
263
+ createSampler: () => ({ ok: true, value: { __brand: 'Sampler' } }),
264
+ createShaderModule: () => ({
265
+ ok: true,
266
+ value: { __brand: 'ShaderModule' },
267
+ }),
268
+ createTextureView: () => ({ ok: true, value: { __brand: 'TextureView' } }),
269
+ createCommandEncoder: () => ({
270
+ ok: true,
271
+ value: {
272
+ beginRenderPass: () => ({
273
+ setPipeline: () => undefined,
274
+ setBindGroup: () => undefined,
275
+ setVertexBuffer: () => undefined,
276
+ setIndexBuffer: () => undefined,
277
+ draw: () => undefined,
278
+ drawIndexed: () => undefined,
279
+ end: () => undefined,
280
+ }),
281
+ finish: () => ({ __brand: 'CommandBuffer' }),
282
+ },
283
+ }),
284
+ };
285
+ }
286
+ function makeMockCanvas(opts: { webgpu?: 'context' | 'null'; webgl2?: 'context' | 'null' }) {
287
+ return {
288
+ width: 800,
289
+ height: 600,
290
+ getContext(kind: string): unknown {
291
+ if (kind === 'webgpu') {
292
+ if (opts.webgpu === 'context') {
293
+ return {
294
+ __mockTag: 'webgpu-canvas-context',
295
+ configure: () => undefined,
296
+ unconfigure: () => undefined,
297
+ getCurrentTexture: () => ({ createView: () => ({}) }),
298
+ };
299
+ }
300
+ return null;
301
+ }
302
+ if (kind === 'webgl2') {
303
+ if (opts.webgl2 === 'context') {
304
+ return {
305
+ __mockTag: 'webgl2',
306
+ getExtension: () => null,
307
+ getParameter: () => 1,
308
+ isContextLost: () => false,
309
+ };
310
+ }
311
+ return null;
312
+ }
313
+ return null;
314
+ },
315
+ addEventListener() {},
316
+ removeEventListener() {},
317
+ } as unknown as HTMLCanvasElement;
318
+ }
319
+ function _makeStubGPU(): unknown {
320
+ return {
321
+ requestAdapter: async () => ({
322
+ features: new Set<string>(),
323
+ limits: {},
324
+ requestDevice: async () => makeFakeRhiDevice(),
325
+ }),
326
+ getPreferredCanvasFormat: () => 'bgra8unorm',
327
+ };
328
+ }
329
+
330
+ void [
331
+ AssetError,
332
+ AssetGuid,
333
+ AssetRegistry,
334
+ DeviceScope,
335
+ EngineEnvironmentError,
336
+ GpuResidencyCache,
337
+ HANDLE_CUBE,
338
+ HANDLE_CYLINDER,
339
+ HANDLE_QUAD,
340
+ HANDLE_SPHERE,
341
+ HANDLE_TRIANGLE,
342
+ INLINE_PACK_LOADERS,
343
+ LoaderRegistry,
344
+ PACK_ARTIFACT_LOADERS,
345
+ PROCEDURAL_FLOATS_PER_VERTEX,
346
+ ShaderRegistry,
347
+ World,
348
+ _makeStubGPU,
349
+ afterEach,
350
+ animationClipLoader,
351
+ assetRegistryModule,
352
+ audioLoader,
353
+ beforeEach,
354
+ buildSceneChildContext,
355
+ createBoxGeometry,
356
+ createDevImportTransport,
357
+ createEngineMetrics,
358
+ createPlaneGeometry,
359
+ createRuntimeRenderer,
360
+ defineComponent,
361
+ deriveBuiltin,
362
+ describe,
363
+ expect,
364
+ expectTypeOf,
365
+ fileURLToPath,
366
+ fontLoader,
367
+ getOrCreateMipmapPipeline,
368
+ it,
369
+ makeFakeRhiDevice,
370
+ makeMockCanvas,
371
+ makeMockShaderRegistry,
372
+ materialLoader,
373
+ meshFromInterleaved,
374
+ meshLoader,
375
+ mipmapCacheSize,
376
+ numMipLevels,
377
+ ok,
378
+ readFileSync,
379
+ requireRenderer,
380
+ resolveAssetHandle,
381
+ rhi,
382
+ rhiOk,
383
+ sceneLoader,
384
+ skeletonLoader,
385
+ skinLoader,
386
+ spies,
387
+ textureLoader,
388
+ toShared,
389
+ toUnique,
390
+ unwrapHandle,
391
+ vi,
392
+ walkMaterialPassesOverSharedRefs,
393
+ wireDefaultLoaders,
394
+ ];
395
+ type __MergedKeep =
396
+ | EquirectAsset
397
+ | Handle
398
+ | ImportTransport
399
+ | MaterialAsset
400
+ | MaterialPass
401
+ | SamplerAsset;
402
+
403
+ {
404
+ // --- from resolve-scene-guids.test.ts ---
405
+ // A non-builtin user mesh GUID. Must not collide with the builtin mesh GUIDs
406
+ // pre-registered by the AssetRegistry constructor (HANDLE_CUBE is
407
+ // cbe42beb-..., etc., feat-20260603 Tier 0) — those now resolve, so reusing
408
+ // one here would make registerWithGuid throw a collision.
409
+ const MESH_GUID_STR = 'b1c2d3e4-f5a6-4b7c-8d9e-0a1b2c3d4e5f';
410
+ const MATERIAL_GUID_STR = 'f6af7007-158f-4d92-9e47-93bf2f213e1f';
411
+ const SKELETON_GUID_STR = 'a1b2c3d4-e5f6-7890-abcd-ef1234567890';
412
+ const UNREGISTERED_GUID_STR = 'deadbeef-dead-beef-dead-beefdeadbeef';
413
+
414
+ function parseGuid(s: string): AssetGuid {
415
+ const parsed = AssetGuid.parse(s);
416
+ if (!parsed.ok) throw new Error(`invalid test GUID: ${s}`);
417
+ return parsed.value;
418
+ }
419
+
420
+ function localId(n: number): LocalEntityId {
421
+ return n as LocalEntityId;
422
+ }
423
+
424
+ /** Build a minimal SceneAsset suitable for _resolveSceneGuids testing. */
425
+ function buildTestAsset(
426
+ nodes: Array<{ localId: number; components: Record<string, Record<string, unknown>> }>,
427
+ ): SceneAsset {
428
+ const sceneNodes: SceneEntity[] = nodes.map((n) => ({
429
+ localId: localId(n.localId),
430
+ components: n.components,
431
+ }));
432
+ return { kind: 'scene', entities: sceneNodes };
433
+ }
434
+
435
+ describe('w4 - _resolveSceneGuids success path', () => {
436
+ it('(a) resolves GUID string handle fields to Handle numbers via schema-driven handle<> detection', () => {
437
+ // Register components so World knows their schemas (plan-strategy D-4:
438
+ // _resolveSceneGuids uses world._getComponentByName to read fieldType).
439
+ defineComponent('Transform', { pos: 'array<f32, 3>' });
440
+ const reg = new AssetRegistry(makeMockShaderRegistry());
441
+
442
+ // Pre-register mesh asset so resolveGuid finds it.
443
+ const meshGuid = parseGuid(MESH_GUID_STR);
444
+ reg.catalog(meshGuid, {
445
+ kind: 'mesh',
446
+ vertices: new Float32Array(0),
447
+ indices: new Uint16Array(0),
448
+ attributes: { position: new Float32Array(0) },
449
+ materialSlots: [{ slotName: 'Default' }],
450
+ submeshes: [
451
+ {
452
+ indexOffset: 0,
453
+ indexCount: 0,
454
+ vertexCount: 0,
455
+ materialSlot: 0,
456
+ topology: 'triangle-list',
457
+ },
458
+ ],
459
+ });
460
+
461
+ // Also register material and MeshRenderer in world so we verify
462
+ // multi-handle-type detection (MeshFilter + MeshRenderer both have handle fields).
463
+ const materialGuid = parseGuid(MATERIAL_GUID_STR);
464
+ reg.catalog(materialGuid, {
465
+ kind: 'material',
466
+ passes: [{ name: 'forward', program: { module: 'test::dummy' } }],
467
+ values: {},
468
+ });
469
+
470
+ // Build a SceneAsset with GUID strings in both MeshFilter.assetHandle and
471
+ // MeshRenderer.material (simulating parseScenePayload refs replacement from M1).
472
+ const asset = buildTestAsset([
473
+ {
474
+ localId: 0,
475
+ components: {
476
+ Transform: { pos: [1, 0, 0] },
477
+ MeshFilter: { assetHandle: MESH_GUID_STR },
478
+ MeshRenderer: { materials: [MATERIAL_GUID_STR] },
479
+ },
480
+ },
481
+ ]);
482
+
483
+ const world = new World();
484
+ const MeshFilter = defineComponent('MeshFilter', { assetHandle: 'shared<MeshAsset>' });
485
+ const MeshRenderer = defineComponent('MeshRenderer', {
486
+ materials: 'array<shared<MaterialAsset>>',
487
+ });
488
+ world.components.register(MeshFilter);
489
+ world.components.register(MeshRenderer);
490
+
491
+ // Private function access for unit-test isolation (same pattern as parseAssetPayload in
492
+ // asset-registry-scene.test.ts).
493
+ // biome-ignore lint/suspicious/noExplicitAny: private helper access for round-trip test
494
+ const internal = reg as any as {
495
+ _resolveSceneGuids(
496
+ scene: SceneAsset,
497
+ world: World,
498
+ ): { ok: true; value: SceneAsset } | { ok: false; error: unknown };
499
+ };
500
+ const result = internal._resolveSceneGuids(asset, world);
501
+
502
+ expect(result.ok).toBe(true);
503
+ if (!result.ok) return;
504
+ const resolvedAsset = result.value;
505
+ expect(resolvedAsset.kind).toBe('scene');
506
+ expect(resolvedAsset.entities.length).toBe(1);
507
+
508
+ const resolvedComp = resolvedAsset.entities[0]?.components as Record<
509
+ string,
510
+ Record<string, unknown>
511
+ >;
512
+ // assetHandle should now be a Handle number (not a string)
513
+ expect(resolvedComp.MeshFilter?.assetHandle).toBeTypeOf('number');
514
+ // materials[0] should also be a Handle number (not a string)
515
+ const matsValue = resolvedComp.MeshRenderer?.materials as readonly unknown[] | undefined;
516
+ expect(Array.isArray(matsValue)).toBe(true);
517
+ expect(matsValue?.[0]).toBeTypeOf('number');
518
+ });
519
+
520
+ it('(b) Skin.skeleton field resolves correctly', () => {
521
+ const reg = new AssetRegistry(makeMockShaderRegistry());
522
+
523
+ const skelGuid = parseGuid(SKELETON_GUID_STR);
524
+ reg.catalog(skelGuid, {
525
+ kind: 'skeleton',
526
+ inverseBindMatrices: new Float32Array(3 * 16),
527
+ jointCount: 3,
528
+ });
529
+
530
+ const asset = buildTestAsset([
531
+ {
532
+ localId: 0,
533
+ components: {
534
+ Skin: { skeleton: SKELETON_GUID_STR, joints: [1, 2] },
535
+ },
536
+ },
537
+ ]);
538
+
539
+ const world = new World();
540
+ const Skin = defineComponent('Skin', {
541
+ skeleton: 'shared<SkeletonAsset>',
542
+ joints: 'array<entity>',
543
+ });
544
+ world.components.register(Skin);
545
+
546
+ // biome-ignore lint/suspicious/noExplicitAny: private helper access
547
+ const internal = reg as any as {
548
+ _resolveSceneGuids(
549
+ scene: SceneAsset,
550
+ world: World,
551
+ ): { ok: true; value: SceneAsset } | { ok: false; error: unknown };
552
+ };
553
+ const result = internal._resolveSceneGuids(asset, world);
554
+
555
+ expect(result.ok).toBe(true);
556
+ if (!result.ok) return;
557
+ const resolvedAsset = result.value;
558
+ const resolvedComp = resolvedAsset.entities[0]?.components as Record<
559
+ string,
560
+ Record<string, unknown>
561
+ >;
562
+ // skeleton should now be a Handle number
563
+ expect(resolvedComp.Skin?.skeleton).toBeTypeOf('number');
564
+ // joints (array<entity>) should NOT be touched — it's not a handle field
565
+ expect(resolvedComp.Skin?.joints).toEqual([1, 2]);
566
+ });
567
+
568
+ it('(c) same GUID referenced by multiple nodes resolves to the same Handle number', () => {
569
+ const reg = new AssetRegistry(makeMockShaderRegistry());
570
+
571
+ const meshGuid = parseGuid(MESH_GUID_STR);
572
+ reg.catalog(meshGuid, {
573
+ kind: 'mesh',
574
+ vertices: new Float32Array(0),
575
+ indices: new Uint16Array(0),
576
+ attributes: { position: new Float32Array(0) },
577
+ materialSlots: [{ slotName: 'Default' }],
578
+ submeshes: [
579
+ {
580
+ indexOffset: 0,
581
+ indexCount: 0,
582
+ vertexCount: 0,
583
+ materialSlot: 0,
584
+ topology: 'triangle-list',
585
+ },
586
+ ],
587
+ });
588
+
589
+ const asset = buildTestAsset([
590
+ {
591
+ localId: 0,
592
+ components: {
593
+ MeshFilter: { assetHandle: MESH_GUID_STR },
594
+ },
595
+ },
596
+ {
597
+ localId: 1,
598
+ components: {
599
+ MeshFilter: { assetHandle: MESH_GUID_STR },
600
+ },
601
+ },
602
+ ]);
603
+
604
+ const world = new World();
605
+ const MeshFilter = defineComponent('MeshFilter', { assetHandle: 'shared<MeshAsset>' });
606
+ world.components.register(MeshFilter);
607
+
608
+ // biome-ignore lint/suspicious/noExplicitAny: private helper access
609
+ const internal = reg as any as {
610
+ _resolveSceneGuids(
611
+ scene: SceneAsset,
612
+ world: World,
613
+ ): { ok: true; value: SceneAsset } | { ok: false; error: unknown };
614
+ };
615
+ const result = internal._resolveSceneGuids(asset, world);
616
+
617
+ expect(result.ok).toBe(true);
618
+ if (!result.ok) return;
619
+ const resolvedAsset = result.value;
620
+ const comp0 = (
621
+ resolvedAsset.entities[0]?.components as Record<string, Record<string, unknown>>
622
+ ).MeshFilter;
623
+ const comp1 = (
624
+ resolvedAsset.entities[1]?.components as Record<string, Record<string, unknown>>
625
+ ).MeshFilter;
626
+ expect(comp0?.assetHandle).toBe(comp1?.assetHandle);
627
+ expect(comp0?.assetHandle).toBeTypeOf('number');
628
+ });
629
+
630
+ it('(d) nodes without handle fields pass through unchanged', () => {
631
+ const reg = new AssetRegistry(makeMockShaderRegistry());
632
+
633
+ const asset = buildTestAsset([{ localId: 0, components: { Transform: { pos: [1, 2, 3] } } }]);
634
+
635
+ const world = new World();
636
+ defineComponent('Transform', { pos: 'array<f32, 3>' });
637
+
638
+ // biome-ignore lint/suspicious/noExplicitAny: private helper access
639
+ const internal = reg as any as {
640
+ _resolveSceneGuids(
641
+ scene: SceneAsset,
642
+ world: World,
643
+ ): { ok: true; value: SceneAsset } | { ok: false; error: unknown };
644
+ };
645
+ const result = internal._resolveSceneGuids(asset, world);
646
+
647
+ expect(result.ok).toBe(true);
648
+ if (!result.ok) return;
649
+ const resolvedAsset = result.value;
650
+ const comp = (
651
+ resolvedAsset.entities[0]?.components as Record<string, Record<string, unknown>>
652
+ ).Transform;
653
+ expect(comp).toEqual({ pos: [1, 2, 3] });
654
+ });
655
+ });
656
+
657
+ describe('w5 - _resolveSceneGuids error path', () => {
658
+ it('(e) unregistered GUID returns asset-not-found with hint containing GUID, localId, and field', () => {
659
+ const reg = new AssetRegistry(makeMockShaderRegistry());
660
+
661
+ const asset = buildTestAsset([
662
+ {
663
+ localId: 0,
664
+ components: {
665
+ MeshFilter: { assetHandle: UNREGISTERED_GUID_STR },
666
+ },
667
+ },
668
+ ]);
669
+
670
+ const world = new World();
671
+ const MeshFilter = defineComponent('MeshFilter', { assetHandle: 'shared<MeshAsset>' });
672
+ world.components.register(MeshFilter);
673
+
674
+ // biome-ignore lint/suspicious/noExplicitAny: private helper access
675
+ const internal = reg as any as {
676
+ _resolveSceneGuids(
677
+ scene: SceneAsset,
678
+ world: World,
679
+ ): { ok: true; value: SceneAsset } | { ok: false; error: unknown };
680
+ };
681
+ const result = internal._resolveSceneGuids(asset, world);
682
+
683
+ expect(result.ok).toBe(false);
684
+ if (result.ok) return;
685
+ const e = result.error as { code: string; hint: string };
686
+ expect(e.code).toBe('asset-not-found');
687
+ // Hint must contain the GUID, localId, and field for AI-user debuggability (AC-04)
688
+ expect(e.hint).toContain(UNREGISTERED_GUID_STR);
689
+ expect(e.hint).toContain('0'); // localId
690
+ expect(e.hint).toContain('assetHandle');
691
+ });
692
+
693
+ it('(f) stop-on-first-error: only the first unregistered GUID among multiple nodes is reported', () => {
694
+ const reg = new AssetRegistry(makeMockShaderRegistry());
695
+
696
+ // Register the first GUID but not the second — first node (localId=1) should
697
+ // fail before reaching localId=2
698
+ const meshGuid = parseGuid(MESH_GUID_STR);
699
+ reg.catalog(meshGuid, {
700
+ kind: 'mesh',
701
+ vertices: new Float32Array(0),
702
+ indices: new Uint16Array(0),
703
+ attributes: { position: new Float32Array(0) },
704
+ materialSlots: [{ slotName: 'Default' }],
705
+ submeshes: [
706
+ {
707
+ indexOffset: 0,
708
+ indexCount: 0,
709
+ vertexCount: 0,
710
+ materialSlot: 0,
711
+ topology: 'triangle-list',
712
+ },
713
+ ],
714
+ });
715
+
716
+ const SECOND_UNREGISTERED = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee';
717
+
718
+ const asset = buildTestAsset([
719
+ {
720
+ localId: 1,
721
+ components: {
722
+ MeshFilter: { assetHandle: UNREGISTERED_GUID_STR },
723
+ },
724
+ },
725
+ {
726
+ localId: 2,
727
+ components: {
728
+ MeshFilter: { assetHandle: SECOND_UNREGISTERED },
729
+ },
730
+ },
731
+ ]);
732
+
733
+ const world = new World();
734
+ const MeshFilter = defineComponent('MeshFilter', { assetHandle: 'shared<MeshAsset>' });
735
+ world.components.register(MeshFilter);
736
+
737
+ // biome-ignore lint/suspicious/noExplicitAny: private helper access
738
+ const internal = reg as any as {
739
+ _resolveSceneGuids(
740
+ scene: SceneAsset,
741
+ world: World,
742
+ ): { ok: true; value: SceneAsset } | { ok: false; error: unknown };
743
+ };
744
+ const result = internal._resolveSceneGuids(asset, world);
745
+
746
+ expect(result.ok).toBe(false);
747
+ if (result.ok) return;
748
+ const e = result.error as { code: string; hint: string };
749
+ expect(e.code).toBe('asset-not-found');
750
+ // Hint should reference the first failing node (localId=1), not the second
751
+ expect(e.hint).toContain(UNREGISTERED_GUID_STR);
752
+ expect(e.hint).toContain('1'); // localId of the first failing node
753
+ // Must NOT contain the second GUID (stopped before reaching it)
754
+ expect(e.hint).not.toContain(SECOND_UNREGISTERED.slice(0, 8)); // prefix match sufficient
755
+ });
756
+
757
+ describe('w10 - M3 reverse-decode from envelope.refs + buildSceneChildContext from edge lookup', () => {
758
+ const MATERIAL2_GUID_STR = 'e1e2e3e4-a5a6-4b7c-8d9e-0a1b2c3d4e5f';
759
+ const MATERIAL3_GUID_STR = 'd1d2d3d4-b5b6-4c7d-8e9f-0a1b2c3d4e5f';
760
+ const TEXTURE_GUID_STR = 'cccccccc-aaaa-bbbb-cccc-dddddddddddd';
761
+
762
+ function makeTestMesh(): MeshAsset {
763
+ return {
764
+ kind: 'mesh',
765
+ vertices: new Float32Array(0),
766
+ indices: new Uint16Array(0),
767
+ attributes: { position: new Float32Array(0) },
768
+ materialSlots: [{ slotName: 'Default' }],
769
+ submeshes: [
770
+ {
771
+ indexOffset: 0,
772
+ indexCount: 0,
773
+ vertexCount: 0,
774
+ materialSlot: 0,
775
+ topology: 'triangle-list' as const,
776
+ },
777
+ ],
778
+ };
779
+ }
780
+
781
+ function makeTestMaterial(): MaterialAsset {
782
+ return {
783
+ kind: 'material',
784
+ passes: [{ name: 'forward', program: { module: 'test::dummy' } }],
785
+ values: {},
786
+ };
787
+ }
788
+
789
+ function registerAsset<T extends Asset>(reg: AssetRegistry, guidStr: string, asset: T): void {
790
+ const guid = parseGuid(guidStr);
791
+ reg.catalog(guid, asset);
792
+ }
793
+
794
+ it('(a) per-field equivalence: scalar handle + array handle resolve correctly via envelope.refs', () => {
795
+ const reg = new AssetRegistry(makeMockShaderRegistry());
796
+
797
+ registerAsset(reg, MESH_GUID_STR, makeTestMesh());
798
+ registerAsset(reg, MATERIAL_GUID_STR, makeTestMaterial());
799
+ registerAsset(reg, MATERIAL2_GUID_STR, makeTestMaterial());
800
+ registerAsset(reg, MATERIAL3_GUID_STR, makeTestMaterial());
801
+
802
+ // Build a scene and catalogue it with refs that carry edge metadata
803
+ const sceneGuidStr = '00001111-2222-3333-4444-555566667777';
804
+ const sceneGuid = parseGuid(sceneGuidStr);
805
+ const sceneAsset = buildTestAsset([
806
+ {
807
+ localId: 0,
808
+ components: {
809
+ Transform: { pos: [1, 0, 0] },
810
+ MeshFilter: { assetHandle: MESH_GUID_STR },
811
+ MeshRenderer: {
812
+ materials: [MATERIAL_GUID_STR, MATERIAL2_GUID_STR, MATERIAL3_GUID_STR],
813
+ },
814
+ },
815
+ },
816
+ ]);
817
+
818
+ // Catalogue the scene WITH refs edge metadata (envelope.refs path)
819
+ const refs: import('@forgeax/engine-types').AssetRef[] = [
820
+ {
821
+ guid: MESH_GUID_STR,
822
+ sourceField: { componentName: 'MeshFilter', fieldName: 'assetHandle' },
823
+ sceneEntityId: 0,
824
+ },
825
+ {
826
+ guid: MATERIAL_GUID_STR,
827
+ sourceField: {
828
+ componentName: 'MeshRenderer',
829
+ fieldName: 'materials',
830
+ arrayIndex: 0,
831
+ },
832
+ sceneEntityId: 0,
833
+ },
834
+ {
835
+ guid: MATERIAL2_GUID_STR,
836
+ sourceField: {
837
+ componentName: 'MeshRenderer',
838
+ fieldName: 'materials',
839
+ arrayIndex: 1,
840
+ },
841
+ sceneEntityId: 0,
842
+ },
843
+ {
844
+ guid: MATERIAL3_GUID_STR,
845
+ sourceField: {
846
+ componentName: 'MeshRenderer',
847
+ fieldName: 'materials',
848
+ arrayIndex: 2,
849
+ },
850
+ sceneEntityId: 0,
851
+ },
852
+ ];
853
+ reg.catalog(sceneGuid, sceneAsset, refs);
854
+
855
+ const world = new World();
856
+ defineComponent('MeshFilter', { assetHandle: 'shared<MeshAsset>' });
857
+ defineComponent('MeshRenderer', { materials: 'array<shared<MaterialAsset>>' });
858
+
859
+ // biome-ignore lint/suspicious/noExplicitAny: private helper access
860
+ const internal = reg as any as {
861
+ _resolveSceneGuids(
862
+ scene: SceneAsset,
863
+ world: World,
864
+ sceneGuidKey?: string,
865
+ ): { ok: true; value: SceneAsset } | { ok: false; error: unknown };
866
+ };
867
+ const result = internal._resolveSceneGuids(sceneAsset, world, sceneGuidStr.toLowerCase());
868
+
869
+ expect(result.ok).toBe(true);
870
+ if (!result.ok) return;
871
+ const resolvedAsset = result.value;
872
+ expect(resolvedAsset.kind).toBe('scene');
873
+ expect(resolvedAsset.entities.length).toBe(1);
874
+
875
+ const resolvedComp = resolvedAsset.entities[0]?.components as Record<
876
+ string,
877
+ Record<string, unknown>
878
+ >;
879
+ // Scalar handle
880
+ expect(resolvedComp.MeshFilter?.assetHandle).toBeTypeOf('number');
881
+ // Array handles — all 3 slots
882
+ const mats = resolvedComp.MeshRenderer?.materials as readonly unknown[] | undefined;
883
+ expect(Array.isArray(mats)).toBe(true);
884
+ expect(mats?.length).toBe(3);
885
+ expect(mats?.[0]).toBeTypeOf('number');
886
+ expect(mats?.[1]).toBeTypeOf('number');
887
+ expect(mats?.[2]).toBeTypeOf('number');
888
+ // Each slot has a distinct material GUID → distinct handle
889
+ expect(mats?.[0]).not.toBe(mats?.[1]);
890
+ expect(mats?.[1]).not.toBe(mats?.[2]);
891
+ });
892
+
893
+ it('(b) arrayIndex lossless: array<handle<MaterialAsset>> of 3 → each slot gets correct GUID', () => {
894
+ const reg = new AssetRegistry(makeMockShaderRegistry());
895
+
896
+ registerAsset(reg, MATERIAL_GUID_STR, makeTestMaterial());
897
+ registerAsset(reg, MATERIAL2_GUID_STR, makeTestMaterial());
898
+ registerAsset(reg, MATERIAL3_GUID_STR, makeTestMaterial());
899
+
900
+ const sceneGuidStr = '00001111-2222-3333-4444-555566667777';
901
+ const sceneGuid = parseGuid(sceneGuidStr);
902
+ const sceneAsset = buildTestAsset([
903
+ {
904
+ localId: 0,
905
+ components: {
906
+ MeshRenderer: {
907
+ materials: [MATERIAL_GUID_STR, MATERIAL2_GUID_STR, MATERIAL3_GUID_STR],
908
+ },
909
+ },
910
+ },
911
+ ]);
912
+
913
+ const refs: import('@forgeax/engine-types').AssetRef[] = [
914
+ {
915
+ guid: MATERIAL_GUID_STR,
916
+ sourceField: {
917
+ componentName: 'MeshRenderer',
918
+ fieldName: 'materials',
919
+ arrayIndex: 0,
920
+ },
921
+ sceneEntityId: 0,
922
+ },
923
+ {
924
+ guid: MATERIAL2_GUID_STR,
925
+ sourceField: {
926
+ componentName: 'MeshRenderer',
927
+ fieldName: 'materials',
928
+ arrayIndex: 1,
929
+ },
930
+ sceneEntityId: 0,
931
+ },
932
+ {
933
+ guid: MATERIAL3_GUID_STR,
934
+ sourceField: {
935
+ componentName: 'MeshRenderer',
936
+ fieldName: 'materials',
937
+ arrayIndex: 2,
938
+ },
939
+ sceneEntityId: 0,
940
+ },
941
+ ];
942
+ reg.catalog(sceneGuid, sceneAsset, refs);
943
+
944
+ const world = new World();
945
+ defineComponent('MeshRenderer', { materials: 'array<shared<MaterialAsset>>' });
946
+
947
+ // biome-ignore lint/suspicious/noExplicitAny: private helper access
948
+ const internal = reg as any as {
949
+ _resolveSceneGuids(
950
+ scene: SceneAsset,
951
+ world: World,
952
+ sceneGuidKey?: string,
953
+ ): { ok: true; value: SceneAsset } | { ok: false; error: unknown };
954
+ };
955
+ const result = internal._resolveSceneGuids(sceneAsset, world, sceneGuidStr.toLowerCase());
956
+
957
+ expect(result.ok).toBe(true);
958
+ if (!result.ok) return;
959
+ const resolvedComp = result.value.entities[0]?.components as Record<
960
+ string,
961
+ Record<string, unknown>
962
+ >;
963
+ const mats = resolvedComp.MeshRenderer?.materials as readonly number[] | undefined;
964
+ expect(mats?.length).toBe(3);
965
+ // Verify each slot is a number (resolved handle) and distinct
966
+ expect(typeof mats?.[0]).toBe('number');
967
+ expect(typeof mats?.[1]).toBe('number');
968
+ expect(typeof mats?.[2]).toBe('number');
969
+ // Different GUIDs → different handles
970
+ expect(mats?.[0]).not.toBe(mats?.[1]);
971
+ expect(mats?.[1]).not.toBe(mats?.[2]);
972
+ expect(mats?.[0]).not.toBe(mats?.[2]);
973
+ });
974
+
975
+ it('(c) dedup contract: same GUID from multiple entities → same handle', () => {
976
+ const reg = new AssetRegistry(makeMockShaderRegistry());
977
+
978
+ registerAsset(reg, MESH_GUID_STR, makeTestMesh());
979
+
980
+ const sceneGuidStr = '00001111-2222-3333-4444-555566667777';
981
+ const sceneGuid = parseGuid(sceneGuidStr);
982
+ const sceneAsset = buildTestAsset([
983
+ {
984
+ localId: 0,
985
+ components: { MeshFilter: { assetHandle: MESH_GUID_STR } },
986
+ },
987
+ {
988
+ localId: 1,
989
+ components: { MeshFilter: { assetHandle: MESH_GUID_STR } },
990
+ },
991
+ ]);
992
+
993
+ const refs: import('@forgeax/engine-types').AssetRef[] = [
994
+ {
995
+ guid: MESH_GUID_STR,
996
+ sourceField: { componentName: 'MeshFilter', fieldName: 'assetHandle' },
997
+ sceneEntityId: 0,
998
+ },
999
+ {
1000
+ guid: MESH_GUID_STR,
1001
+ sourceField: { componentName: 'MeshFilter', fieldName: 'assetHandle' },
1002
+ sceneEntityId: 1,
1003
+ },
1004
+ ];
1005
+ reg.catalog(sceneGuid, sceneAsset, refs);
1006
+
1007
+ const world = new World();
1008
+ defineComponent('MeshFilter', { assetHandle: 'shared<MeshAsset>' });
1009
+
1010
+ // biome-ignore lint/suspicious/noExplicitAny: private helper access
1011
+ const internal = reg as any as {
1012
+ _resolveSceneGuids(
1013
+ scene: SceneAsset,
1014
+ world: World,
1015
+ sceneGuidKey?: string,
1016
+ ): { ok: true; value: SceneAsset } | { ok: false; error: unknown };
1017
+ };
1018
+ const result = internal._resolveSceneGuids(sceneAsset, world, sceneGuidStr.toLowerCase());
1019
+
1020
+ expect(result.ok).toBe(true);
1021
+ if (!result.ok) return;
1022
+ const comp0 = (
1023
+ result.value.entities[0]?.components as Record<string, Record<string, unknown>>
1024
+ ).MeshFilter;
1025
+ const comp1 = (
1026
+ result.value.entities[1]?.components as Record<string, Record<string, unknown>>
1027
+ ).MeshFilter;
1028
+ // Same GUID → same handle (one allocSharedRef per unique payload)
1029
+ expect(comp0?.assetHandle).toBe(comp1?.assetHandle);
1030
+ expect(comp0?.assetHandle).toBeTypeOf('number');
1031
+ });
1032
+
1033
+ it('(d) breadcrumb from envelope.refs: buildSceneChildContext returns correct sceneEntityId + componentField', () => {
1034
+ const reg = new AssetRegistry(makeMockShaderRegistry());
1035
+
1036
+ registerAsset(reg, MESH_GUID_STR, makeTestMesh());
1037
+
1038
+ const sceneGuidStr = '00001111-2222-3333-4444-555566667777';
1039
+ const sceneGuid = parseGuid(sceneGuidStr);
1040
+ const sceneAsset = buildTestAsset([
1041
+ {
1042
+ localId: 5,
1043
+ components: { MeshFilter: { assetHandle: MESH_GUID_STR } },
1044
+ },
1045
+ ]);
1046
+
1047
+ const refs: import('@forgeax/engine-types').AssetRef[] = [
1048
+ {
1049
+ guid: MESH_GUID_STR,
1050
+ sourceField: { componentName: 'MeshFilter', fieldName: 'assetHandle' },
1051
+ sceneEntityId: 5,
1052
+ },
1053
+ ];
1054
+ reg.catalog(sceneGuid, sceneAsset, refs);
1055
+
1056
+ const ctx = buildSceneChildContext(reg, sceneAsset, MESH_GUID_STR.toLowerCase());
1057
+
1058
+ expect(ctx).toBeDefined();
1059
+ expect(ctx?.sceneEntityId).toBe(5);
1060
+ expect(ctx?.componentField).toBe('MeshFilter.assetHandle');
1061
+ });
1062
+
1063
+ it('(e) texture edge: sourceField=undefined → buildSceneChildContext returns componentField undefined', () => {
1064
+ const reg = new AssetRegistry(makeMockShaderRegistry());
1065
+
1066
+ registerAsset(reg, MATERIAL_GUID_STR, makeTestMaterial());
1067
+
1068
+ const textureGuid = parseGuid(TEXTURE_GUID_STR);
1069
+ reg.catalog(textureGuid, {
1070
+ kind: 'texture',
1071
+ // biome-ignore lint/suspicious/noExplicitAny: test fixture uses minimal texture shape
1072
+ texture: {} as any,
1073
+ });
1074
+
1075
+ const sceneGuidStr = '00001111-2222-3333-4444-555566667777';
1076
+ const sceneGuid = parseGuid(sceneGuidStr);
1077
+ const sceneAsset = buildTestAsset([
1078
+ {
1079
+ localId: 0,
1080
+ components: {
1081
+ MeshRenderer: { materials: [MATERIAL_GUID_STR] },
1082
+ },
1083
+ },
1084
+ ]);
1085
+
1086
+ // Scene refs include a texture edge (flat superset per D-2) with
1087
+ // sourceField=undefined — texture has no per-entity origin.
1088
+ const refs: import('@forgeax/engine-types').AssetRef[] = [
1089
+ {
1090
+ guid: MATERIAL_GUID_STR,
1091
+ sourceField: {
1092
+ componentName: 'MeshRenderer',
1093
+ fieldName: 'materials',
1094
+ arrayIndex: 0,
1095
+ },
1096
+ sceneEntityId: 0,
1097
+ },
1098
+ {
1099
+ guid: TEXTURE_GUID_STR,
1100
+ // sourceField intentionally omitted — texture edge, D-2
1101
+ sceneEntityId: undefined,
1102
+ },
1103
+ ];
1104
+ reg.catalog(sceneGuid, sceneAsset, refs);
1105
+
1106
+ // Look up the material GUID — should find sceneEntityId + componentField
1107
+ const materialCtx = buildSceneChildContext(
1108
+ reg,
1109
+ sceneAsset,
1110
+ MATERIAL_GUID_STR.toLowerCase(),
1111
+ );
1112
+ expect(materialCtx).toBeDefined();
1113
+ expect(materialCtx?.componentField).toBe('MeshRenderer.materials[0]');
1114
+
1115
+ // Look up the texture GUID — sourceField=undefined → componentField undefined
1116
+ const textureCtx = buildSceneChildContext(reg, sceneAsset, TEXTURE_GUID_STR.toLowerCase());
1117
+ expect(textureCtx).toBeDefined();
1118
+ expect(textureCtx?.sceneEntityId).toBeUndefined();
1119
+ expect(textureCtx?.componentField).toBeUndefined();
1120
+ });
1121
+ });
1122
+ });
1123
+ }