@forgeax/engine-runtime 0.1.4 → 0.1.7

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 (606) hide show
  1. package/README.md +65 -83
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/__tests__/animation-graph-collect-roundtrip.test.d.ts +2 -0
  4. package/dist/__tests__/animation-graph-collect-roundtrip.test.d.ts.map +1 -0
  5. package/dist/__tests__/animation-graph-serialize.unit.test.d.ts +2 -0
  6. package/dist/__tests__/animation-graph-serialize.unit.test.d.ts.map +1 -0
  7. package/dist/__tests__/asset-brand-and-name-retirement.test.d.ts +2 -0
  8. package/dist/__tests__/asset-brand-and-name-retirement.test.d.ts.map +1 -0
  9. package/dist/__tests__/asset-error-exhaustive.test-d.d.ts +11 -0
  10. package/dist/__tests__/asset-error-exhaustive.test-d.d.ts.map +1 -0
  11. package/dist/__tests__/asset-registry-guid-reverse.test.d.ts +2 -0
  12. package/dist/__tests__/asset-registry-guid-reverse.test.d.ts.map +1 -0
  13. package/dist/__tests__/asset-registry-hdr-equirect.test.d.ts +2 -0
  14. package/dist/__tests__/asset-registry-hdr-equirect.test.d.ts.map +1 -0
  15. package/dist/__tests__/asset-registry-mesh-pack-payload.dawn.test.d.ts +2 -0
  16. package/dist/__tests__/asset-registry-mesh-pack-payload.dawn.test.d.ts.map +1 -0
  17. package/dist/__tests__/asset-registry-mounts.test.d.ts +2 -0
  18. package/dist/__tests__/asset-registry-mounts.test.d.ts.map +1 -0
  19. package/dist/__tests__/asset-registry.browser.test.d.ts +2 -0
  20. package/dist/__tests__/asset-registry.browser.test.d.ts.map +1 -0
  21. package/dist/__tests__/asset-registry.dawn.test.d.ts +2 -0
  22. package/dist/__tests__/asset-registry.dawn.test.d.ts.map +1 -0
  23. package/dist/__tests__/asset-registry.listCatalog.test.d.ts +2 -0
  24. package/dist/__tests__/asset-registry.listCatalog.test.d.ts.map +1 -0
  25. package/dist/__tests__/asset-registry.mesh-bin.test.d.ts +2 -0
  26. package/dist/__tests__/asset-registry.mesh-bin.test.d.ts.map +1 -0
  27. package/dist/__tests__/asset-registry.pack-cache.test.d.ts +2 -0
  28. package/dist/__tests__/asset-registry.pack-cache.test.d.ts.map +1 -0
  29. package/dist/__tests__/asset-registry.raw-container-failfast.test.d.ts +2 -0
  30. package/dist/__tests__/asset-registry.raw-container-failfast.test.d.ts.map +1 -0
  31. package/dist/__tests__/asset-registry.recursive.spec.d.ts +2 -0
  32. package/dist/__tests__/asset-registry.recursive.spec.d.ts.map +1 -0
  33. package/dist/__tests__/asset-registry.unit.test.d.ts +2 -0
  34. package/dist/__tests__/asset-registry.unit.test.d.ts.map +1 -0
  35. package/dist/__tests__/asset.unit.test.d.ts +2 -0
  36. package/dist/__tests__/asset.unit.test.d.ts.map +1 -0
  37. package/dist/__tests__/audio-load-by-guid.unit.test.d.ts +2 -0
  38. package/dist/__tests__/audio-load-by-guid.unit.test.d.ts.map +1 -0
  39. package/dist/__tests__/backend-selection.integration.test.d.ts +2 -0
  40. package/dist/__tests__/backend-selection.integration.test.d.ts.map +1 -0
  41. package/dist/__tests__/basis-catalog-dispatch.integration.test.d.ts +2 -0
  42. package/dist/__tests__/basis-catalog-dispatch.integration.test.d.ts.map +1 -0
  43. package/dist/__tests__/builtin-asset-registry.unit.test.d.ts +2 -0
  44. package/dist/__tests__/builtin-asset-registry.unit.test.d.ts.map +1 -0
  45. package/dist/__tests__/collect-scene-asset.test.d.ts +1 -2
  46. package/dist/__tests__/collect-scene-asset.test.d.ts.map +1 -1
  47. package/dist/__tests__/collect-scene-mount-collapse.test.d.ts +2 -0
  48. package/dist/__tests__/collect-scene-mount-collapse.test.d.ts.map +1 -0
  49. package/dist/__tests__/collect-transient-roundtrip.test.d.ts +1 -1
  50. package/dist/__tests__/collect-transient-roundtrip.test.d.ts.map +1 -1
  51. package/dist/__tests__/collect-transient-skip.test.d.ts +1 -2
  52. package/dist/__tests__/collect-transient-skip.test.d.ts.map +1 -1
  53. package/dist/__tests__/compressed-texture-physical-extent.integration.test.d.ts +2 -0
  54. package/dist/__tests__/compressed-texture-physical-extent.integration.test.d.ts.map +1 -0
  55. package/dist/__tests__/dawn/mesh-update-dawn.test.d.ts +2 -0
  56. package/dist/__tests__/dawn/mesh-update-dawn.test.d.ts.map +1 -0
  57. package/dist/__tests__/dawn/upload-texture.dawn.test.d.ts +2 -0
  58. package/dist/__tests__/dawn/upload-texture.dawn.test.d.ts.map +1 -0
  59. package/dist/__tests__/draw-published.d.ts +2 -2
  60. package/dist/__tests__/draw-published.d.ts.map +1 -1
  61. package/dist/__tests__/dynamic-texture-store.unit.test.d.ts +2 -0
  62. package/dist/__tests__/dynamic-texture-store.unit.test.d.ts.map +1 -0
  63. package/dist/__tests__/errors.unit.test.d.ts +2 -0
  64. package/dist/__tests__/errors.unit.test.d.ts.map +1 -0
  65. package/dist/__tests__/extract-skin-mismatch.unit.test.d.ts +2 -0
  66. package/dist/__tests__/extract-skin-mismatch.unit.test.d.ts.map +1 -0
  67. package/dist/__tests__/fetch-binary-decompress.test.d.ts +2 -0
  68. package/dist/__tests__/fetch-binary-decompress.test.d.ts.map +1 -0
  69. package/dist/__tests__/four-path-compression.integration.test.d.ts +2 -0
  70. package/dist/__tests__/four-path-compression.integration.test.d.ts.map +1 -0
  71. package/dist/__tests__/fullscreen-feature-host.dawn.test.d.ts +2 -0
  72. package/dist/__tests__/fullscreen-feature-host.dawn.test.d.ts.map +1 -0
  73. package/dist/__tests__/guid-for-asset.unit.test.d.ts +2 -0
  74. package/dist/__tests__/guid-for-asset.unit.test.d.ts.map +1 -0
  75. package/dist/__tests__/handle-migration.test.d.ts +2 -0
  76. package/dist/__tests__/handle-migration.test.d.ts.map +1 -0
  77. package/dist/__tests__/helpers/register-runtime-components.d.ts.map +1 -1
  78. package/dist/__tests__/host-custom-kind-contract.test-d.d.ts +2 -0
  79. package/dist/__tests__/host-custom-kind-contract.test-d.d.ts.map +1 -0
  80. package/dist/__tests__/host-custom-kind-contract.test.d.ts +2 -0
  81. package/dist/__tests__/host-custom-kind-contract.test.d.ts.map +1 -0
  82. package/dist/__tests__/inspect-name.test.d.ts +2 -0
  83. package/dist/__tests__/inspect-name.test.d.ts.map +1 -0
  84. package/dist/__tests__/instantiate-mount-deferred-wiring.test.d.ts +2 -0
  85. package/dist/__tests__/instantiate-mount-deferred-wiring.test.d.ts.map +1 -0
  86. package/dist/__tests__/loadbyguid-payload.unit.test.d.ts +2 -0
  87. package/dist/__tests__/loadbyguid-payload.unit.test.d.ts.map +1 -0
  88. package/dist/__tests__/loadbyguid-texture-intern.unit.test.d.ts +2 -0
  89. package/dist/__tests__/loadbyguid-texture-intern.unit.test.d.ts.map +1 -0
  90. package/dist/__tests__/loader-extract-graceful.test.d.ts +2 -0
  91. package/dist/__tests__/loader-extract-graceful.test.d.ts.map +1 -0
  92. package/dist/__tests__/loader-name-survival.test.d.ts +2 -0
  93. package/dist/__tests__/loader-name-survival.test.d.ts.map +1 -0
  94. package/dist/__tests__/mesh-bin-contract.test.d.ts +2 -0
  95. package/dist/__tests__/mesh-bin-contract.test.d.ts.map +1 -0
  96. package/dist/__tests__/mesh-bin-skin-roundtrip.unit.test.d.ts +2 -0
  97. package/dist/__tests__/mesh-bin-skin-roundtrip.unit.test.d.ts.map +1 -0
  98. package/dist/__tests__/mesh-bin.test.d.ts +2 -0
  99. package/dist/__tests__/mesh-bin.test.d.ts.map +1 -0
  100. package/dist/__tests__/mesh-loader-skin-contract.unit.test.d.ts +2 -0
  101. package/dist/__tests__/mesh-loader-skin-contract.unit.test.d.ts.map +1 -0
  102. package/dist/__tests__/new-kind-refs-contract.test.d.ts +2 -0
  103. package/dist/__tests__/new-kind-refs-contract.test.d.ts.map +1 -0
  104. package/dist/__tests__/package-promotion.test.d.ts +2 -0
  105. package/dist/__tests__/package-promotion.test.d.ts.map +1 -0
  106. package/dist/__tests__/package-rename.test.d.ts +2 -0
  107. package/dist/__tests__/package-rename.test.d.ts.map +1 -0
  108. package/dist/__tests__/recover-error-exhaustive.test-d.d.ts +1 -1
  109. package/dist/__tests__/recover-error-exhaustive.test-d.d.ts.map +1 -1
  110. package/dist/__tests__/render-error-exhaustive.test-d.d.ts +1 -1
  111. package/dist/__tests__/render-error-exhaustive.test-d.d.ts.map +1 -1
  112. package/dist/__tests__/render-feature-prepared-graphics.fixture.d.ts +10 -0
  113. package/dist/__tests__/render-feature-prepared-graphics.fixture.d.ts.map +1 -0
  114. package/dist/__tests__/render-system-extract.unit.test.d.ts +2 -0
  115. package/dist/__tests__/render-system-extract.unit.test.d.ts.map +1 -0
  116. package/dist/__tests__/renderer-test-utils.d.ts +10 -0
  117. package/dist/__tests__/renderer-test-utils.d.ts.map +1 -0
  118. package/dist/__tests__/renderer.test-d.d.ts +2 -0
  119. package/dist/__tests__/renderer.test-d.d.ts.map +1 -0
  120. package/dist/__tests__/resolve-asset-handle-err.test.d.ts +2 -0
  121. package/dist/__tests__/resolve-asset-handle-err.test.d.ts.map +1 -0
  122. package/dist/__tests__/resolve-asset-handle.unit.test.d.ts +2 -0
  123. package/dist/__tests__/resolve-asset-handle.unit.test.d.ts.map +1 -0
  124. package/dist/__tests__/resolveName-fallback.test.d.ts +2 -0
  125. package/dist/__tests__/resolveName-fallback.test.d.ts.map +1 -0
  126. package/dist/__tests__/resolveName-null-package.test.d.ts +2 -0
  127. package/dist/__tests__/resolveName-null-package.test.d.ts.map +1 -0
  128. package/dist/__tests__/resolveName-unified-entry.test.d.ts +2 -0
  129. package/dist/__tests__/resolveName-unified-entry.test.d.ts.map +1 -0
  130. package/dist/__tests__/resolveName.test.d.ts +2 -0
  131. package/dist/__tests__/resolveName.test.d.ts.map +1 -0
  132. package/dist/__tests__/roots-to-scene-asset.test.d.ts +1 -1
  133. package/dist/__tests__/roots-to-scene-asset.test.d.ts.map +1 -1
  134. package/dist/__tests__/roundtrip-primitive.test.d.ts +2 -0
  135. package/dist/__tests__/roundtrip-primitive.test.d.ts.map +1 -0
  136. package/dist/__tests__/scene-bare-guid-refs-instantiate.test.d.ts +2 -0
  137. package/dist/__tests__/scene-bare-guid-refs-instantiate.test.d.ts.map +1 -0
  138. package/dist/__tests__/scene-instance-authoring.integration.test.d.ts +2 -0
  139. package/dist/__tests__/scene-instance-authoring.integration.test.d.ts.map +1 -0
  140. package/dist/__tests__/scene-mount-fixed-point.test.d.ts +2 -0
  141. package/dist/__tests__/scene-mount-fixed-point.test.d.ts.map +1 -0
  142. package/dist/__tests__/scene-mount-roundtrip.test.d.ts +2 -0
  143. package/dist/__tests__/scene-mount-roundtrip.test.d.ts.map +1 -0
  144. package/dist/__tests__/scene-mount-wrapper-roundtrip.test.d.ts +2 -0
  145. package/dist/__tests__/scene-mount-wrapper-roundtrip.test.d.ts.map +1 -0
  146. package/dist/__tests__/scene-sugar-instantiate.test.d.ts +2 -0
  147. package/dist/__tests__/scene-sugar-instantiate.test.d.ts.map +1 -0
  148. package/dist/__tests__/serialize-mounts-roundtrip.test.d.ts +2 -0
  149. package/dist/__tests__/serialize-mounts-roundtrip.test.d.ts.map +1 -0
  150. package/dist/__tests__/serialize-scene-asset.test.d.ts +1 -1
  151. package/dist/__tests__/serialize-scene-asset.test.d.ts.map +1 -1
  152. package/dist/__tests__/template-game-default-api-contract.test.d.ts +2 -0
  153. package/dist/__tests__/template-game-default-api-contract.test.d.ts.map +1 -0
  154. package/dist/__tests__/tileset-asset-validate.test.d.ts +2 -0
  155. package/dist/__tests__/tileset-asset-validate.test.d.ts.map +1 -0
  156. package/dist/__tests__/transcode-fallback.unit.test.d.ts +2 -0
  157. package/dist/__tests__/transcode-fallback.unit.test.d.ts.map +1 -0
  158. package/dist/__tests__/transient-runtime-behavior.test.d.ts +2 -0
  159. package/dist/__tests__/transient-runtime-behavior.test.d.ts.map +1 -0
  160. package/dist/__tests__/video-loader-consumption.test.d.ts +2 -0
  161. package/dist/__tests__/video-loader-consumption.test.d.ts.map +1 -0
  162. package/dist/__tests__/video-loader-smoke.dawn.test.d.ts +2 -0
  163. package/dist/__tests__/video-loader-smoke.dawn.test.d.ts.map +1 -0
  164. package/dist/backend-selection.d.ts +13 -0
  165. package/dist/backend-selection.d.ts.map +1 -0
  166. package/dist/collect-scene-asset.d.ts +7 -4
  167. package/dist/collect-scene-asset.d.ts.map +1 -1
  168. package/dist/createRenderer.d.ts +14 -2
  169. package/dist/createRenderer.d.ts.map +1 -1
  170. package/dist/errors/environment.d.ts +1 -1
  171. package/dist/errors/environment.d.ts.map +1 -1
  172. package/dist/index.d.ts +20 -82
  173. package/dist/index.d.ts.map +1 -1
  174. package/dist/index.mjs +107 -108
  175. package/dist/index.mjs.map +1 -1
  176. package/dist/renderer-host.d.ts +9 -0
  177. package/dist/renderer-host.d.ts.map +1 -0
  178. package/dist/renderer-host.mjs +62 -0
  179. package/dist/renderer-host.mjs.map +1 -0
  180. package/dist/scene-instances/index.d.ts +2 -0
  181. package/dist/scene-instances/index.d.ts.map +1 -0
  182. package/dist/scene-utils/mount-override-fold.d.ts +1 -2
  183. package/dist/scene-utils/mount-override-fold.d.ts.map +1 -1
  184. package/dist/systems/sprite-animation-tick.d.ts +1 -1
  185. package/dist/systems/sprite-animation-tick.d.ts.map +1 -1
  186. package/dist/systems/transparent-sort.d.ts +1 -2
  187. package/dist/systems/transparent-sort.d.ts.map +1 -1
  188. package/dist/tilemap-register-validate.d.ts.map +1 -1
  189. package/package.json +34 -33
  190. package/src/__tests__/active-camera.unit.test.ts +19 -9
  191. package/src/__tests__/animation-graph-collect-roundtrip.test.ts +213 -0
  192. package/src/__tests__/animation-graph-serialize.unit.test.ts +171 -0
  193. package/src/__tests__/append-injection.test.ts +1 -1
  194. package/src/__tests__/asset-brand-and-name-retirement.test.ts +102 -0
  195. package/src/__tests__/asset-error-exhaustive.test-d.ts +62 -0
  196. package/src/__tests__/asset-registry-guid-reverse.test.ts +608 -0
  197. package/src/__tests__/asset-registry-hdr-equirect.test.ts +78 -0
  198. package/src/__tests__/asset-registry-mesh-pack-payload.dawn.test.ts +232 -0
  199. package/src/__tests__/asset-registry-mounts.test.ts +806 -0
  200. package/src/__tests__/asset-registry.browser.test.ts +81 -0
  201. package/src/__tests__/asset-registry.dawn.test.ts +94 -0
  202. package/src/__tests__/asset-registry.listCatalog.test.ts +170 -0
  203. package/src/__tests__/asset-registry.mesh-bin.test.ts +316 -0
  204. package/src/__tests__/asset-registry.pack-cache.test.ts +969 -0
  205. package/src/__tests__/asset-registry.raw-container-failfast.test.ts +225 -0
  206. package/src/__tests__/asset-registry.recursive.spec.ts +1397 -0
  207. package/src/__tests__/asset-registry.unit.test.ts +1030 -0
  208. package/src/__tests__/asset.unit.test.ts +7107 -0
  209. package/src/__tests__/audio-load-by-guid.unit.test.ts +126 -0
  210. package/src/__tests__/backend-selection.integration.test.ts +11 -0
  211. package/src/__tests__/basis-catalog-dispatch.integration.test.ts +320 -0
  212. package/src/__tests__/bind-group-cache-keying.test.ts +4 -4
  213. package/src/__tests__/block-upload-math.unit.test.ts +2 -2
  214. package/src/__tests__/builtin-asset-registry.unit.test.ts +112 -0
  215. package/src/__tests__/builtin-shader-register-e2e.test.ts +1 -1
  216. package/src/__tests__/cache-false-eviction.test.ts +2 -1
  217. package/src/__tests__/cache-isolation-matrix.test.ts +2 -1
  218. package/src/__tests__/camera-clear-snapshot-equivalence.unit.test.ts +5 -6
  219. package/src/__tests__/clamp-to-last.e2e.browser.test.ts +29 -19
  220. package/src/__tests__/collect-scene-asset.test.ts +49 -52
  221. package/src/__tests__/collect-scene-mount-collapse.test.ts +391 -0
  222. package/src/__tests__/collect-subtree.unit.test.ts +1 -2
  223. package/src/__tests__/collect-transient-children-absent.test.ts +24 -10
  224. package/src/__tests__/collect-transient-roundtrip.test.ts +44 -20
  225. package/src/__tests__/collect-transient-skip.test.ts +24 -13
  226. package/src/__tests__/components.test-d.ts +1 -6
  227. package/src/__tests__/components.unit.test.ts +20 -19
  228. package/src/__tests__/composite-skybox-cross-world.browser.test.ts +59 -27
  229. package/src/__tests__/compressed-texture-physical-extent.integration.test.ts +139 -0
  230. package/src/__tests__/create-renderer-assembly.integration.test.ts +42 -29
  231. package/src/__tests__/createRenderer-dispose.test.ts +35 -402
  232. package/src/__tests__/createRenderer.browser-no-webgpu.test.ts +25 -30
  233. package/src/__tests__/createRenderer.unit.test.ts +1 -1
  234. package/src/__tests__/dawn/hello-sprite-nineslice-section.dawn.test.ts +27 -63
  235. package/src/__tests__/dawn/ibl-precompute-readback.dawn.test.ts +56 -47
  236. package/src/__tests__/dawn/instances-per-instance-pbr.dawn.test.ts +49 -29
  237. package/src/__tests__/dawn/instances-shadow.dawn.test.ts +26 -15
  238. package/src/__tests__/dawn/instances-uniform-fallback.dawn.test.ts +13 -14
  239. package/src/__tests__/dawn/material-alpha.dawn.test.ts +23 -31
  240. package/src/__tests__/dawn/material-cooked-fixture.dawn.test.ts +24 -9
  241. package/src/__tests__/dawn/{mesh-update.dawn.test.ts → mesh-update-dawn.test.ts} +42 -134
  242. package/src/__tests__/dawn/mipmap-srgb-linear.dawn.test.ts +14 -7
  243. package/src/__tests__/dawn/per-entity-material-texture-isolation.dawn.test.ts +16 -28
  244. package/src/__tests__/dawn/sprite-nineslice-mesh-bind.dawn.test.ts +23 -39
  245. package/src/__tests__/dawn/upload-texture.dawn.test.ts +198 -0
  246. package/src/__tests__/dawn/user-handle-mesh-render.dawn.test.ts +12 -14
  247. package/src/__tests__/dawn/user-mesh-upload.dawn.test.ts +27 -22
  248. package/src/__tests__/dawn/zero-manifest-mesh-fail.dawn.test.ts +25 -28
  249. package/src/__tests__/dawn/zero-manifest-ready.dawn.test.ts +10 -11
  250. package/src/__tests__/demo-sortscope-migration.unit.test.ts +5 -5
  251. package/src/__tests__/derive-bgl-integration.test.ts +34 -44
  252. package/src/__tests__/derive-render-data-skin.unit.test.ts +1 -1
  253. package/src/__tests__/draw-published.ts +11 -4
  254. package/src/__tests__/dynamic-texture-store.unit.test.ts +190 -0
  255. package/src/__tests__/equirect-bc6h.integration.test.ts +144 -21
  256. package/src/__tests__/errors.unit.test.ts +1227 -0
  257. package/src/__tests__/extract-frames-graceful-degradation.test.ts +3 -7
  258. package/src/__tests__/extract-frames-merge.test.ts +35 -22
  259. package/src/__tests__/extract-record-no-hardcoded-texture-fields.test.ts +5 -16
  260. package/src/__tests__/extract-skin-mismatch.unit.test.ts +322 -0
  261. package/src/__tests__/fetch-binary-decompress.test.ts +356 -0
  262. package/src/__tests__/four-path-compression.integration.test.ts +395 -0
  263. package/src/__tests__/frame-targets-shadow-regression.unit.test.ts +1 -1
  264. package/src/__tests__/fullscreen-feature-host.dawn.test.ts +80 -0
  265. package/src/__tests__/fullscreen-post-bgl-depth.test.ts +4 -2
  266. package/src/__tests__/fullscreen-post-depth-params-arity.test.ts +3 -3
  267. package/src/__tests__/fullscreen-post-process-pass.dawn.test.ts +39 -23
  268. package/src/__tests__/fullscreen-post-process-pass.unit.test.ts +4 -2
  269. package/src/__tests__/fxaa-pixel-diff.dawn.test.ts +28 -17
  270. package/src/__tests__/fxaa-validation-and-resize.dawn.test.ts +29 -18
  271. package/src/__tests__/fxaa-zero-overhead.dawn.test.ts +16 -12
  272. package/src/__tests__/geometry.unit.test.ts +525 -159
  273. package/src/__tests__/glyph-tilemap-worldid.test.ts +1 -1
  274. package/src/__tests__/gpu-resource-cow-survivor.test.ts +6 -4
  275. package/src/__tests__/guid-for-asset.unit.test.ts +102 -0
  276. package/src/__tests__/handle-migration.test.ts +136 -0
  277. package/src/__tests__/helpers/register-runtime-components.ts +4 -2
  278. package/src/__tests__/helpers/register-scene-components.ts +1 -1
  279. package/src/__tests__/host-custom-kind-contract.test-d.ts +47 -0
  280. package/src/__tests__/host-custom-kind-contract.test.ts +578 -0
  281. package/src/__tests__/inspect-name.test.ts +139 -0
  282. package/src/__tests__/instances.test-d.ts +1 -1
  283. package/src/__tests__/instantiate-mount-deferred-wiring.test.ts +112 -0
  284. package/src/__tests__/light-casters-9-light.browser.test.ts +73 -76
  285. package/src/__tests__/light-gpu-byte-neutral.unit.test.ts +7 -10
  286. package/src/__tests__/light-snapshot.test-d.ts +2 -2
  287. package/src/__tests__/lights-vec-negative.unit.test.ts +1 -1
  288. package/src/__tests__/lights.unit.test.ts +31 -511
  289. package/src/__tests__/loadbyguid-payload.unit.test.ts +117 -0
  290. package/src/__tests__/loadbyguid-texture-intern.unit.test.ts +213 -0
  291. package/src/__tests__/loader-extract-graceful.test.ts +146 -0
  292. package/src/__tests__/loader-name-survival.test.ts +213 -0
  293. package/src/__tests__/m3-vec-negative.unit.test.ts +3 -2
  294. package/src/__tests__/material-texture-uv-scale.unit.test.ts +101 -31
  295. package/src/__tests__/materials.unit.test.ts +10 -27
  296. package/src/__tests__/mesh-bin-contract.test.ts +28 -0
  297. package/src/__tests__/mesh-bin-skin-roundtrip.unit.test.ts +149 -0
  298. package/src/__tests__/mesh-bin.test.ts +38 -0
  299. package/src/__tests__/mesh-loader-skin-contract.unit.test.ts +120 -0
  300. package/src/__tests__/mesh-ssbo-grow.unit.test.ts +3 -3
  301. package/src/__tests__/mip-gate.unit.test.ts +1 -1
  302. package/src/__tests__/msaa-sprite-pixel-diff.dawn.test.ts +29 -68
  303. package/src/__tests__/new-kind-refs-contract.test.ts +132 -0
  304. package/src/__tests__/node-shims.d.ts +4 -6
  305. package/src/__tests__/package-promotion.test.ts +88 -0
  306. package/src/__tests__/package-rename.test.ts +103 -0
  307. package/src/__tests__/pass-selector.unit.test.ts +2 -2
  308. package/src/__tests__/pbr-pipeline.unit.test.ts +21 -25
  309. package/src/__tests__/pipeline-builder-msaa.unit.test.ts +6 -9
  310. package/src/__tests__/pipeline-cache-keying.unit.test.ts +6 -9
  311. package/src/__tests__/pipeline-spec.test.ts +16 -177
  312. package/src/__tests__/pipeline.unit.test.ts +154 -1693
  313. package/src/__tests__/point-light-shadow.dawn.test.ts +33 -42
  314. package/src/__tests__/point-light-shadow.unit.test.ts +19 -60
  315. package/src/__tests__/post-process-error-codes.unit.test.ts +4 -1
  316. package/src/__tests__/post-process-params-component.unit.test.ts +1 -1
  317. package/src/__tests__/post-process-params-extract.unit.test.ts +2 -5
  318. package/src/__tests__/postprocess-camera-consistency.test.ts +4 -7
  319. package/src/__tests__/profiler-assembly.integration.test.ts +1 -1
  320. package/src/__tests__/propagate-transforms.dawn.test.ts +12 -11
  321. package/src/__tests__/record-worldid-isolation.test.ts +7 -29
  322. package/src/__tests__/recover-error-exhaustive.test-d.ts +1 -1
  323. package/src/__tests__/render-error-exhaustive.test-d.ts +117 -71
  324. package/src/__tests__/render-feature-assembly.test-d.ts +20 -9
  325. package/src/__tests__/render-feature-prepared-graphics-docs.unit.test.ts +8 -6
  326. package/src/__tests__/render-feature-prepared-graphics.browser.test.ts +65 -559
  327. package/src/__tests__/render-feature-prepared-graphics.dawn.test.ts +57 -544
  328. package/src/__tests__/render-feature-prepared-graphics.fixture.ts +104 -0
  329. package/src/__tests__/render-feature-prepared-graphics.integration.test.ts +40 -248
  330. package/src/__tests__/render-feature-prepared-graphics.regression.integration.test.ts +83 -340
  331. package/src/__tests__/render-feature-recover.integration.test.ts +9 -49
  332. package/src/__tests__/render-feature-runtime-surface.test-d.ts +17 -20
  333. package/src/__tests__/render-schema-consumer.integration.test.ts +20 -13
  334. package/src/__tests__/render-schema-owner-boundary.unit.test.ts +2 -6
  335. package/src/__tests__/render-system-cross-world-overlay-queue.test.ts +5 -5
  336. package/src/__tests__/render-system-extract-dispatch-ordering.test.ts +16 -31
  337. package/src/__tests__/render-system-extract.test.ts +36 -51
  338. package/src/__tests__/render-system-extract.unit.test.ts +360 -0
  339. package/src/__tests__/render-system-fold-bucket.unit.test.ts +3 -5
  340. package/src/__tests__/render-system-fold-fallback.unit.test.ts +6 -3
  341. package/src/__tests__/render-system-fold-material.unit.test.ts +3 -5
  342. package/src/__tests__/render-system-fold-metrics.unit.test.ts +5 -5
  343. package/src/__tests__/render-system-fold-modegate.unit.test.ts +6 -6
  344. package/src/__tests__/render-system-fold.dawn.test.ts +7 -5
  345. package/src/__tests__/render-system-mega.test.ts +292 -190
  346. package/src/__tests__/render-system-owner-split-contract.test.ts +10 -32
  347. package/src/__tests__/render-system-record-multi-material-textureview.test.ts +59 -45
  348. package/src/__tests__/render-system-record-per-submesh-transparency.test.ts +23 -29
  349. package/src/__tests__/render-system-record.test.ts +398 -235
  350. package/src/__tests__/render-system-record.unit.test.ts +25 -26
  351. package/src/__tests__/render-system-skin-bg.unit.test.ts +8 -12
  352. package/src/__tests__/renderer-consumer.test-d.ts +16 -3
  353. package/src/__tests__/renderer-health.unit.test.ts +26 -916
  354. package/src/__tests__/renderer-lifecycle.integration.test.ts +19 -6
  355. package/src/__tests__/renderer-recover.unit.test.ts +49 -1069
  356. package/src/__tests__/renderer-result.test-d.ts +19 -9
  357. package/src/__tests__/renderer-surface.unit.test.ts +3 -5
  358. package/src/__tests__/renderer-test-utils.ts +56 -0
  359. package/src/__tests__/renderer.test-d.ts +61 -0
  360. package/src/__tests__/resolve-asset-handle-err.test.ts +111 -0
  361. package/src/__tests__/resolve-asset-handle.unit.test.ts +64 -0
  362. package/src/__tests__/resolve-depth-only-view.test.ts +1 -1
  363. package/src/__tests__/resolveName-fallback.test.ts +64 -0
  364. package/src/__tests__/resolveName-null-package.test.ts +38 -0
  365. package/src/__tests__/resolveName-unified-entry.test.ts +65 -0
  366. package/src/__tests__/resolveName.test.ts +64 -0
  367. package/src/__tests__/rhi-null-command-flow.unit.test.ts +56 -260
  368. package/src/__tests__/rhi-null-renderer-lifecycle.unit.test.ts +41 -169
  369. package/src/__tests__/roots-to-scene-asset.test.ts +199 -93
  370. package/src/__tests__/roundtrip-primitive.test.ts +351 -0
  371. package/src/__tests__/scene-bare-guid-refs-instantiate.test.ts +123 -0
  372. package/src/__tests__/scene-equirect-roundtrip.test.ts +103 -16
  373. package/src/__tests__/scene-instance-authoring.integration.test.ts +8 -0
  374. package/src/__tests__/scene-instance-bridge.integration.test.ts +1 -1
  375. package/src/__tests__/scene-instance-component.test.ts +1 -1
  376. package/src/__tests__/scene-mount-fixed-point.test.ts +257 -0
  377. package/src/__tests__/scene-mount-roundtrip.test.ts +987 -0
  378. package/src/__tests__/scene-mount-wrapper-roundtrip.test.ts +202 -0
  379. package/src/__tests__/scene-sugar-instantiate.test.ts +324 -0
  380. package/src/__tests__/serialize-mounts-roundtrip.test.ts +533 -0
  381. package/src/__tests__/serialize-scene-asset.test.ts +219 -22
  382. package/src/__tests__/shadow-caster-empty-schema.test.ts +3 -3
  383. package/src/__tests__/shadow-csm-cascade-loadop.test.ts +37 -17
  384. package/src/__tests__/shadow-csm-component.test.ts +3 -5
  385. package/src/__tests__/shadow-csm-extract.test.ts +2 -100
  386. package/src/__tests__/shadow-csm-orthoz.test.ts +2 -16
  387. package/src/__tests__/shadow-csm-runtime-vary.dawn.test.ts +19 -14
  388. package/src/__tests__/shadow-csm-tile-consistency.test.ts +32 -26
  389. package/src/__tests__/shadow-csm-ubo.dawn.test.ts +8 -49
  390. package/src/__tests__/shadow-csm-ubo.test.ts +3 -25
  391. package/src/__tests__/shadow-csm-viewport.browser.test.ts +1 -1
  392. package/src/__tests__/shadow-fields-observable.dawn.test.ts +67 -56
  393. package/src/__tests__/shadow-m1.dawn.test.ts +8 -9
  394. package/src/__tests__/shadow-mesh-ssbo-order.test.ts +11 -27
  395. package/src/__tests__/skin-errors.unit.test.ts +1 -1
  396. package/src/__tests__/skin-extract-getarrayview-count.unit.test.ts +7 -5
  397. package/src/__tests__/skylight-lazy-projection.test.ts +36 -15
  398. package/src/__tests__/sparse-tag-scene-roundtrip.test.ts +5 -5
  399. package/src/__tests__/sprite-cullmode-flip.dawn.test.ts +47 -64
  400. package/src/__tests__/sprite-lit-bgl-byte-identical.test.ts +15 -14
  401. package/src/__tests__/sprite-lit-extract-branch.test.ts +1 -1
  402. package/src/__tests__/sprite-lit-pipeline-cache-key.test.ts +1 -1
  403. package/src/__tests__/ssao-bgl.test.ts +16 -9
  404. package/src/__tests__/ssao-buffers.test.ts +2 -2
  405. package/src/__tests__/ssao-data.test.ts +2 -2
  406. package/src/__tests__/ssao-errors.test.ts +1 -1
  407. package/src/__tests__/ssao-passes.test.ts +68 -392
  408. package/src/__tests__/ssao-pipelines.test.ts +1 -1
  409. package/src/__tests__/ssao-uniform-layout.test.ts +8 -6
  410. package/src/__tests__/stencil-outline-pixel.dawn.test.ts +25 -17
  411. package/src/__tests__/swap-chain-format.unit.test.ts +5 -7
  412. package/src/__tests__/systems.unit.test.ts +536 -1326
  413. package/src/__tests__/template-game-default-api-contract.test.ts +362 -0
  414. package/src/__tests__/text.unit.test.ts +56 -33
  415. package/src/__tests__/tilemap-childed-despawn.test.ts +8 -14
  416. package/src/__tests__/tilemap-chunk-extract.overhang-bounds.test.ts +8 -12
  417. package/src/__tests__/tilemap-chunk-extract.propagate-order.test.ts +2 -1
  418. package/src/__tests__/tilemap-chunk-extract.test.ts +13 -17
  419. package/src/__tests__/tilemap-dirty-rebuild.test.ts +7 -11
  420. package/src/__tests__/tilemap-material-cache.test.ts +8 -13
  421. package/src/__tests__/tilemap-multi-atlas.test.ts +12 -32
  422. package/src/__tests__/tilemap-owner-boundary.unit.test.ts +5 -2
  423. package/src/__tests__/tilemap-region-pack.test.ts +8 -12
  424. package/src/__tests__/tilemap-spawn-flip-pivot.test.ts +8 -12
  425. package/src/__tests__/tilemap-uv-inset.test.ts +11 -14
  426. package/src/__tests__/tilemap-validate.test.ts +1 -1
  427. package/src/__tests__/tileset-asset-validate.test.ts +638 -0
  428. package/src/__tests__/transcode-fallback.unit.test.ts +207 -0
  429. package/src/__tests__/transform-hierarchy-pixel-diff.dawn.test.ts +36 -28
  430. package/src/__tests__/transient-fields-collect.test.ts +28 -21
  431. package/src/__tests__/transient-runtime-behavior.test.ts +227 -0
  432. package/src/__tests__/transparent-sort-tilemap-sprite-interleave.test.ts +4 -4
  433. package/src/__tests__/variant-selection.unit.test.ts +1 -1
  434. package/src/__tests__/video-extract-bindgroup.dawn.test.ts +89 -136
  435. package/src/__tests__/video-loader-consumption.test.ts +86 -0
  436. package/src/__tests__/video-loader-smoke.dawn.test.ts +60 -0
  437. package/src/__tests__/view-ubo-layout.browser.test.ts +0 -22
  438. package/src/__tests__/world-entity-key.test.ts +1 -1
  439. package/src/__tests__/world-scene-despawn.test.ts +1 -1
  440. package/src/__tests__/world-scene-instantiate.test.ts +2 -36
  441. package/src/__tests__/world-scene-nested.test.ts +1 -1
  442. package/src/__tests__/world-scene-override.test.ts +1 -1
  443. package/src/backend-selection.ts +69 -0
  444. package/src/collect-scene-asset.ts +35 -23
  445. package/src/components/__tests__/camera.test-d.ts +1 -1
  446. package/src/components/__tests__/camera.test.ts +1 -1
  447. package/src/components/__tests__/sprite-animation.test-d.ts +2 -2
  448. package/src/components/__tests__/tile-layer.test-d.ts +1 -1
  449. package/src/components/__tests__/tile-layer.unit.test.ts +2 -2
  450. package/src/createRenderer.ts +24 -36
  451. package/src/errors/environment.ts +1 -1
  452. package/src/index.ts +51 -113
  453. package/src/renderer-host.ts +33 -0
  454. package/src/scene-instances/__tests__/post-spawn-resolve-joints.unit.test.ts +1 -1
  455. package/src/scene-instances/index.ts +4 -0
  456. package/src/scene-utils/mount-override-fold.ts +3 -7
  457. package/src/systems/__tests__/transparent-sort.bench.ts +3 -3
  458. package/src/systems/sprite-animation-tick.ts +1 -1
  459. package/src/systems/transparent-sort.ts +38 -38
  460. package/src/tilemap-register-validate.ts +4 -4
  461. package/dist/__tests__/bind-group-cache-binding.test.d.ts +0 -2
  462. package/dist/__tests__/bind-group-cache-binding.test.d.ts.map +0 -1
  463. package/dist/__tests__/bind-group-cache-frame.test.d.ts +0 -2
  464. package/dist/__tests__/bind-group-cache-frame.test.d.ts.map +0 -1
  465. package/dist/__tests__/dawn/mesh-update.dawn.test.d.ts +0 -2
  466. package/dist/__tests__/dawn/mesh-update.dawn.test.d.ts.map +0 -1
  467. package/dist/__tests__/dispatch-fullscreen-pass-composite-swapchain.dawn.test.d.ts +0 -2
  468. package/dist/__tests__/dispatch-fullscreen-pass-composite-swapchain.dawn.test.d.ts.map +0 -1
  469. package/dist/__tests__/dispatch-fullscreen-pass-custom-reads.dawn.test.d.ts +0 -2
  470. package/dist/__tests__/dispatch-fullscreen-pass-custom-reads.dawn.test.d.ts.map +0 -1
  471. package/dist/__tests__/dispatch-fullscreen-pass-depth-composite.dawn.test.d.ts +0 -2
  472. package/dist/__tests__/dispatch-fullscreen-pass-depth-composite.dawn.test.d.ts.map +0 -1
  473. package/dist/__tests__/dispatch-fullscreen-pass-params.unit.test.d.ts +0 -2
  474. package/dist/__tests__/dispatch-fullscreen-pass-params.unit.test.d.ts.map +0 -1
  475. package/dist/__tests__/fog-producer-manifest.d.ts +0 -131
  476. package/dist/__tests__/fog-producer-manifest.d.ts.map +0 -1
  477. package/dist/__tests__/fog-producer-pixel-probe.d.ts +0 -42
  478. package/dist/__tests__/fog-producer-pixel-probe.d.ts.map +0 -1
  479. package/dist/__tests__/fog-producer-pixel.browser.test.d.ts +0 -2
  480. package/dist/__tests__/fog-producer-pixel.browser.test.d.ts.map +0 -1
  481. package/dist/__tests__/fog-producer-pixel.dawn.test.d.ts +0 -2
  482. package/dist/__tests__/fog-producer-pixel.dawn.test.d.ts.map +0 -1
  483. package/dist/__tests__/fog-producer-real-fixture.d.ts +0 -5
  484. package/dist/__tests__/fog-producer-real-fixture.d.ts.map +0 -1
  485. package/dist/__tests__/fog-producer-vfx-effect.d.ts +0 -3
  486. package/dist/__tests__/fog-producer-vfx-effect.d.ts.map +0 -1
  487. package/dist/__tests__/fog-scene-asset.integration.test.d.ts +0 -2
  488. package/dist/__tests__/fog-scene-asset.integration.test.d.ts.map +0 -1
  489. package/dist/__tests__/gpu-resource-store-cubemap-status.test.d.ts +0 -2
  490. package/dist/__tests__/gpu-resource-store-cubemap-status.test.d.ts.map +0 -1
  491. package/dist/__tests__/gpu-resource.test.d.ts +0 -2
  492. package/dist/__tests__/gpu-resource.test.d.ts.map +0 -1
  493. package/dist/__tests__/hdrp-bgl-slots.browser.test.d.ts +0 -2
  494. package/dist/__tests__/hdrp-bgl-slots.browser.test.d.ts.map +0 -1
  495. package/dist/__tests__/hdrp-buffers.unit.test.d.ts +0 -2
  496. package/dist/__tests__/hdrp-buffers.unit.test.d.ts.map +0 -1
  497. package/dist/__tests__/hdrp-execute-filter.unit.test.d.ts +0 -2
  498. package/dist/__tests__/hdrp-execute-filter.unit.test.d.ts.map +0 -1
  499. package/dist/__tests__/hdrp-gbuffer-attachments.browser.test.d.ts +0 -2
  500. package/dist/__tests__/hdrp-gbuffer-attachments.browser.test.d.ts.map +0 -1
  501. package/dist/__tests__/hdrp-pipeline-asset-type.test-d.d.ts +0 -2
  502. package/dist/__tests__/hdrp-pipeline-asset-type.test-d.d.ts.map +0 -1
  503. package/dist/__tests__/hdrp-ssao-boundary.test.d.ts +0 -2
  504. package/dist/__tests__/hdrp-ssao-boundary.test.d.ts.map +0 -1
  505. package/dist/__tests__/hdrp-ssao-falsification.test.d.ts +0 -2
  506. package/dist/__tests__/hdrp-ssao-falsification.test.d.ts.map +0 -1
  507. package/dist/__tests__/helpers/primitive-world.d.ts +0 -8
  508. package/dist/__tests__/helpers/primitive-world.d.ts.map +0 -1
  509. package/dist/__tests__/helpers/render-asset-port.d.ts +0 -15
  510. package/dist/__tests__/helpers/render-asset-port.d.ts.map +0 -1
  511. package/dist/__tests__/helpers/scene-asset-port.d.ts +0 -9
  512. package/dist/__tests__/helpers/scene-asset-port.d.ts.map +0 -1
  513. package/dist/__tests__/material-derived-coordinates.integration.test.d.ts +0 -2
  514. package/dist/__tests__/material-derived-coordinates.integration.test.d.ts.map +0 -1
  515. package/dist/__tests__/pipeline-layout-selection.test.d.ts +0 -2
  516. package/dist/__tests__/pipeline-layout-selection.test.d.ts.map +0 -1
  517. package/dist/__tests__/post-process-reads-narrowing.test-d.d.ts +0 -2
  518. package/dist/__tests__/post-process-reads-narrowing.test-d.d.ts.map +0 -1
  519. package/dist/__tests__/post-process-register-roundtrip.dawn.test.d.ts +0 -2
  520. package/dist/__tests__/post-process-register-roundtrip.dawn.test.d.ts.map +0 -1
  521. package/dist/__tests__/render-feature-zero-feature.browser.test.d.ts +0 -2
  522. package/dist/__tests__/render-feature-zero-feature.browser.test.d.ts.map +0 -1
  523. package/dist/__tests__/render-pipeline-trivial.dawn.test.d.ts +0 -2
  524. package/dist/__tests__/render-pipeline-trivial.dawn.test.d.ts.map +0 -1
  525. package/dist/__tests__/shadow-csm-caster-membership.test.d.ts +0 -2
  526. package/dist/__tests__/shadow-csm-caster-membership.test.d.ts.map +0 -1
  527. package/dist/__tests__/shadow-csm-pssm.test.d.ts +0 -2
  528. package/dist/__tests__/shadow-csm-pssm.test.d.ts.map +0 -1
  529. package/dist/__tests__/shadow-csm-temporal.browser.test.d.ts +0 -2
  530. package/dist/__tests__/shadow-csm-temporal.browser.test.d.ts.map +0 -1
  531. package/dist/__tests__/shadow-csm-viewport.test.d.ts +0 -2
  532. package/dist/__tests__/shadow-csm-viewport.test.d.ts.map +0 -1
  533. package/dist/__tests__/shadow-m2.dawn.test.d.ts +0 -2
  534. package/dist/__tests__/shadow-m2.dawn.test.d.ts.map +0 -1
  535. package/dist/__tests__/shadow-m3-calibrate-run.dawn.test.d.ts +0 -2
  536. package/dist/__tests__/shadow-m3-calibrate-run.dawn.test.d.ts.map +0 -1
  537. package/dist/__tests__/shadow-m3-calibrate.d.ts +0 -83
  538. package/dist/__tests__/shadow-m3-calibrate.d.ts.map +0 -1
  539. package/dist/__tests__/shadow-m3.dawn.test.d.ts +0 -2
  540. package/dist/__tests__/shadow-m3.dawn.test.d.ts.map +0 -1
  541. package/dist/__tests__/shadow-probe-control-flow.dawn.test.d.ts +0 -2
  542. package/dist/__tests__/shadow-probe-control-flow.dawn.test.d.ts.map +0 -1
  543. package/dist/__tests__/shadow-quality-metrics.dawn.test.d.ts +0 -2
  544. package/dist/__tests__/shadow-quality-metrics.dawn.test.d.ts.map +0 -1
  545. package/dist/__tests__/temporal-off-path.unit.test.d.ts +0 -2
  546. package/dist/__tests__/temporal-off-path.unit.test.d.ts.map +0 -1
  547. package/dist/__tests__/video-cache-regression.unit.test.d.ts +0 -2
  548. package/dist/__tests__/video-cache-regression.unit.test.d.ts.map +0 -1
  549. package/dist/asset-kind.d.ts +0 -4
  550. package/dist/asset-kind.d.ts.map +0 -1
  551. package/dist/errors/sprite-animation.d.ts +0 -3
  552. package/dist/errors/sprite-animation.d.ts.map +0 -1
  553. package/dist/scene-asset-port.d.ts +0 -17
  554. package/dist/scene-asset-port.d.ts.map +0 -1
  555. package/dist/scene-collect-errors.d.ts +0 -22
  556. package/dist/scene-collect-errors.d.ts.map +0 -1
  557. package/dist/shadow-m3-calibrate.d.ts +0 -40
  558. package/dist/shadow-m3-calibrate.d.ts.map +0 -1
  559. package/src/__tests__/bind-group-cache-binding.test.ts +0 -2513
  560. package/src/__tests__/bind-group-cache-frame.test.ts +0 -1511
  561. package/src/__tests__/dispatch-fullscreen-pass-composite-swapchain.dawn.test.ts +0 -204
  562. package/src/__tests__/dispatch-fullscreen-pass-custom-reads.dawn.test.ts +0 -297
  563. package/src/__tests__/dispatch-fullscreen-pass-depth-composite.dawn.test.ts +0 -271
  564. package/src/__tests__/dispatch-fullscreen-pass-params.unit.test.ts +0 -321
  565. package/src/__tests__/fog-producer-manifest.ts +0 -162
  566. package/src/__tests__/fog-producer-pixel-probe.ts +0 -263
  567. package/src/__tests__/fog-producer-pixel.browser.test.ts +0 -26
  568. package/src/__tests__/fog-producer-pixel.dawn.test.ts +0 -55
  569. package/src/__tests__/fog-producer-real-fixture.ts +0 -805
  570. package/src/__tests__/fog-producer-vfx-effect.ts +0 -272
  571. package/src/__tests__/fog-scene-asset.integration.test.ts +0 -123
  572. package/src/__tests__/gpu-resource-store-cubemap-status.test.ts +0 -207
  573. package/src/__tests__/gpu-resource.test.ts +0 -1206
  574. package/src/__tests__/hdrp-bgl-slots.browser.test.ts +0 -50
  575. package/src/__tests__/hdrp-buffers.unit.test.ts +0 -70
  576. package/src/__tests__/hdrp-execute-filter.unit.test.ts +0 -200
  577. package/src/__tests__/hdrp-gbuffer-attachments.browser.test.ts +0 -64
  578. package/src/__tests__/hdrp-pipeline-asset-type.test-d.ts +0 -139
  579. package/src/__tests__/hdrp-ssao-boundary.test.ts +0 -237
  580. package/src/__tests__/hdrp-ssao-falsification.test.ts +0 -289
  581. package/src/__tests__/helpers/primitive-world.ts +0 -91
  582. package/src/__tests__/helpers/render-asset-port.ts +0 -65
  583. package/src/__tests__/helpers/scene-asset-port.ts +0 -23
  584. package/src/__tests__/material-derived-coordinates.integration.test.ts +0 -100
  585. package/src/__tests__/pipeline-layout-selection.test.ts +0 -298
  586. package/src/__tests__/post-process-reads-narrowing.test-d.ts +0 -52
  587. package/src/__tests__/post-process-register-roundtrip.dawn.test.ts +0 -230
  588. package/src/__tests__/render-feature-zero-feature.browser.test.ts +0 -152
  589. package/src/__tests__/render-pipeline-trivial.dawn.test.ts +0 -525
  590. package/src/__tests__/shadow-csm-caster-membership.test.ts +0 -232
  591. package/src/__tests__/shadow-csm-pssm.test.ts +0 -142
  592. package/src/__tests__/shadow-csm-temporal.browser.test.ts +0 -145
  593. package/src/__tests__/shadow-csm-viewport.test.ts +0 -74
  594. package/src/__tests__/shadow-m2.dawn.test.ts +0 -394
  595. package/src/__tests__/shadow-m3-calibrate-run.dawn.test.ts +0 -248
  596. package/src/__tests__/shadow-m3-calibrate.ts +0 -203
  597. package/src/__tests__/shadow-m3.dawn.test.ts +0 -288
  598. package/src/__tests__/shadow-probe-control-flow.dawn.test.ts +0 -264
  599. package/src/__tests__/shadow-quality-metrics.dawn.test.ts +0 -324
  600. package/src/__tests__/temporal-off-path.unit.test.ts +0 -69
  601. package/src/__tests__/video-cache-regression.unit.test.ts +0 -186
  602. package/src/asset-kind.ts +0 -7
  603. package/src/errors/sprite-animation.ts +0 -5
  604. package/src/scene-asset-port.ts +0 -32
  605. package/src/scene-collect-errors.ts +0 -30
  606. package/src/shadow-m3-calibrate.ts +0 -171
@@ -2,14 +2,16 @@
2
2
  // Consolidated by feat-20260609-test-pool-startup-reduction-merge-tiny-test-files
3
3
  // biome-ignore-all lint/complexity/noUselessLoneBlockStatements: block-scope isolation between merged source files (consolidation paradigm)
4
4
  //
5
- // Source files (N=7):
5
+ // Source files (N=9):
6
6
  // - packages/runtime/src/__tests__/builder-topology-stripIndexFormat.test.ts
7
7
  // - packages/runtime/src/__tests__/frustum-culling.test.ts
8
8
  // - packages/runtime/src/__tests__/instances.test.ts
9
9
  // - packages/runtime/src/__tests__/mesh-gpu-handles-vertex-only.test.ts
10
10
  // - packages/runtime/src/__tests__/mesh-ssbo-grow.test.ts
11
11
  // - packages/runtime/src/__tests__/mesh-update-no-leak.test.ts
12
+ // - packages/runtime/src/__tests__/validate-mesh-topology.test.ts
12
13
  // - packages/runtime/src/__tests__/instances-with-submeshes.test.ts
14
+ // - packages/runtime/src/__tests__/mesh-asset-submeshes-validation.test.ts
13
15
  //
14
16
  // feat-20260704-runtime-tier1-decomposition (w6): the geometry-pure blocks
15
17
  // (geometry-tangent / geometry-winding / geometry / vertex-attribute-layout)
@@ -20,66 +22,91 @@
20
22
  // Paradigm: each block-scoped describe('<source-filename>.test.ts', ...) preserves
21
23
  // source as ancestorTitles[0]. Top-level imports merged + deduped.
22
24
 
25
+ import type { AssetRuntimeError } from '@forgeax/engine-assets-runtime';
26
+ import { AssetRegistry } from '@forgeax/engine-assets-runtime';
23
27
  import type { World as WorldType } from '@forgeax/engine-ecs';
24
28
  import { World } from '@forgeax/engine-ecs';
25
- import { componentSchema } from '@forgeax/engine-ecs/internal';
26
- import { deriveVertexLayoutProjection } from '@forgeax/engine-geometry';
27
- import type { Renderer as RendererType } from '@forgeax/engine-render';
28
- import type { RenderAssetPort, RenderError, SkinError } from '@forgeax/engine-render/internal';
29
- import {
30
- Camera,
31
- createMeshSsboGrowController,
32
- Instances,
33
- MeshFilter,
34
- MeshRenderer,
35
- } from '@forgeax/engine-render/internal';
29
+ import { AssetGuid } from '@forgeax/engine-pack/guid';
30
+ import type { RenderError, Renderer as RendererType, SkinError } from '@forgeax/engine-render';
36
31
  import type { PipelineLayout, RenderPipeline, RhiDevice, ShaderModule } from '@forgeax/engine-rhi';
37
32
  import { Transform } from '@forgeax/engine-scene';
38
- import type { AssetErrorDetail, Handle, MeshAsset } from '@forgeax/engine-types';
39
- import { ASSET_ERROR_HINTS, type AssetError, unwrapHandle } from '@forgeax/engine-types';
33
+ import type { AssetErrorDetail, Handle, MaterialAsset, MeshAsset } from '@forgeax/engine-types';
34
+ import { ASSET_ERROR_HINTS, AssetError, unwrapHandle } from '@forgeax/engine-types';
40
35
  import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
41
36
 
42
37
  // feat-20260704-runtime-tier1-decomposition M2 / w12: reconstitute the
43
38
  // eliminated top-level RuntimeError aggregate union (D-3) as a test-local alias
44
39
  // so the errorRegistry.fire mock callback signatures below stay byte-identical
45
- // (AC-09). Equal to RenderError | AssetError | SkinError.
46
- type RuntimeLayerError = RenderError | AssetError | SkinError;
40
+ // (AC-09). Equal to RenderError | AssetRuntimeError | SkinError.
41
+ type RuntimeLayerError = RenderError | AssetRuntimeError | SkinError;
47
42
 
43
+ import { resolveAssetHandle } from '@forgeax/engine-assets-runtime';
44
+ import { propagateTransforms } from '@forgeax/engine-scene';
45
+ import { buildMeshAttributeMapForUvSets } from '../../../geometry/src/vertex-attribute-layout';
46
+ import { createMeshSsboGrowController } from '../../../render/src/assembly/factory';
47
+ import { Camera } from '../../../render/src/components/camera';
48
+ import { Instances } from '../../../render/src/components/instances';
49
+ import { MeshFilter } from '../../../render/src/components/mesh-filter';
50
+ import { MeshRenderer } from '../../../render/src/components/mesh-renderer';
51
+ import type { MeshGpuHandles } from '../../../render/src/device/gpu-residency';
52
+ import { GpuResidencyCache } from '../../../render/src/device/gpu-residency';
53
+ import type { GpuBuffer } from '../../../render/src/gpu-resource';
48
54
  import type {
49
- ExtractedFrame,
50
- GpuBuffer,
51
- MeshGpuHandles,
52
55
  PipelineBuilderContext,
53
56
  PipelineBuilderShaderModuleFactory,
54
- } from '@forgeax/engine-render/internal';
57
+ } from '../../../render/src/pipeline-builder';
58
+ import { buildPipelineForMaterialShader } from '../../../render/src/pipeline-builder';
55
59
  import {
56
- buildPipelineForMaterialShader,
57
60
  ensureMeshSsboCapacity,
58
- extractFrame,
59
- GpuResourceStore,
60
61
  isMeshSsboDevMode,
61
- prepareExtractContext,
62
62
  setMeshSsboDevModeProbeForTests,
63
- } from '@forgeax/engine-render/internal';
64
- import { propagateTransforms } from '@forgeax/engine-scene';
65
- import {
66
- DEFAULT_STANDARD_PBR_PARAM_SCHEMA,
67
- DEFAULT_UNLIT_PARAM_SCHEMA,
68
- } from '@forgeax/engine-shader';
69
- import {
70
- canonicalDefaultMaterialVariants,
71
- makeMockShaderRegistry,
72
- } from './helpers/mock-shader-registry';
73
- import { registerRuntimeComponents } from './helpers/register-runtime-components';
74
-
75
- function canonicalMaterialParamSchema(identifier: string): string {
76
- if (identifier === 'forgeax::default-standard-pbr') {
77
- return JSON.stringify(DEFAULT_STANDARD_PBR_PARAM_SCHEMA);
78
- }
79
- if (identifier === 'forgeax::default-unlit') {
80
- return JSON.stringify(DEFAULT_UNLIT_PARAM_SCHEMA);
63
+ } from '../../../render/src/record/mesh-ssbo';
64
+ import type { ExtractedFrame } from '../../../render/src/render-system-extract';
65
+ import { extractFrame, prepareExtractContext } from '../../../render/src/render-system-extract';
66
+ import { makeMockShaderRegistry } from './helpers/mock-shader-registry';
67
+ import { drawWithOwners } from './renderer-test-utils';
68
+
69
+ type RendererErrorObservation = {
70
+ readonly code: string;
71
+ readonly detail?: unknown;
72
+ readonly hint?: string;
73
+ };
74
+
75
+ function unwrapRendererError(value: unknown): RendererErrorObservation {
76
+ let current = value as RendererErrorObservation;
77
+ while (
78
+ current.detail !== undefined &&
79
+ typeof current.detail === 'object' &&
80
+ current.detail !== null
81
+ ) {
82
+ const cause = (current.detail as { cause?: unknown }).cause;
83
+ if (
84
+ cause === undefined ||
85
+ typeof cause !== 'object' ||
86
+ cause === null ||
87
+ typeof (cause as { code?: unknown }).code !== 'string'
88
+ ) {
89
+ break;
90
+ }
91
+ current = cause as RendererErrorObservation;
81
92
  }
82
- return '[]';
93
+ return current;
94
+ }
95
+
96
+ function subscribeRendererErrors(
97
+ renderer: RendererType,
98
+ listener: (error: RendererErrorObservation) => void,
99
+ ): () => void {
100
+ return renderer.subscribe((event) => {
101
+ if (event.kind === 'error') listener(unwrapRendererError(event.error));
102
+ });
103
+ }
104
+
105
+ function drawPublished(renderer: RendererType, world: WorldType) {
106
+ const attached = renderer.attach(world);
107
+ if (!attached.ok) throw attached.error;
108
+ world.update().unwrap();
109
+ return drawWithOwners(renderer, world);
83
110
  }
84
111
 
85
112
  {
@@ -334,17 +361,25 @@ function canonicalMaterialParamSchema(identifier: string): string {
334
361
  return { fov, aspect, near, far, projection: 0, left: -1, right: 1, bottom: -1, top: 1 };
335
362
  }
336
363
 
337
- function registerMesh(world: World, aabb: Float32Array): Handle<'MeshAsset', 'shared'> {
364
+ function registerMesh(
365
+ world: World,
366
+ assets: AssetRegistry,
367
+ aabb: Float32Array,
368
+ ): Handle<'MeshAsset', 'shared'> {
338
369
  // Minimal mesh: 1 triangle at origin with AABB
339
370
  const vertices = new Float32Array([
340
371
  0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0,
341
372
  1, 0, 0, 0, 0,
342
373
  ]);
343
- return world.allocSharedRef('MeshAsset', {
374
+ const positions = new Float32Array([0, 0, 0, 1, 0, 0, 0, 1, 0]);
375
+ // catalog recomputes the AABB from positions (withMeshAabb), matching the
376
+ // prior register() behavior; mint the augmented payload on the world so
377
+ // the extract-stage frustum cull (via resolveAssetHandle) reads .aabb.
378
+ const result = assets.catalog<MeshAsset>(AssetGuid.format(AssetGuid.random()), {
344
379
  kind: 'mesh',
345
380
  vertices,
346
381
  indices: new Uint16Array([0, 1, 2]),
347
- attributes: { position: new Float32Array([0, 0, 0, 1, 0, 0, 0, 1, 0]) },
382
+ attributes: { position: positions },
348
383
  aabb,
349
384
  materialSlots: [{ slotName: 'Default' }],
350
385
  submeshes: [
@@ -357,10 +392,15 @@ function canonicalMaterialParamSchema(identifier: string): string {
357
392
  },
358
393
  ],
359
394
  });
395
+ if (!result.ok) throw new Error('catalog failed');
396
+ return world.allocSharedRef('MeshAsset', result.value);
360
397
  }
361
398
 
362
- function registerUnlitMaterial(world: World): Handle<'MaterialAsset', 'shared'> {
363
- return world.allocSharedRef('MaterialAsset', {
399
+ function registerUnlitMaterial(
400
+ world: World,
401
+ assets: AssetRegistry,
402
+ ): Handle<'MaterialAsset', 'shared'> {
403
+ const result = assets.catalog<MaterialAsset>(AssetGuid.format(AssetGuid.random()), {
364
404
  kind: 'material',
365
405
  passes: [
366
406
  {
@@ -371,32 +411,23 @@ function canonicalMaterialParamSchema(identifier: string): string {
371
411
  ],
372
412
  values: { baseColor: [1, 1, 1] },
373
413
  });
374
- }
375
-
376
- function makeRenderAssetPort(): RenderAssetPort {
377
- const shaderRegistry = makeMockShaderRegistry();
378
- return {
379
- epoch: 0,
380
- shaderRegistry,
381
- lookup: () => undefined,
382
- guidOf: () => undefined,
383
- materialShaderTextureFieldNames: () => [],
384
- };
414
+ if (!result.ok) throw new Error('catalog failed');
415
+ return world.allocSharedRef('MaterialAsset', result.value);
385
416
  }
386
417
 
387
418
  function makeWorldWithAssets(): {
388
419
  world: World;
389
- assets: RenderAssetPort;
420
+ assets: AssetRegistry;
390
421
  meshHandle: Handle<'MeshAsset', 'shared'>;
391
422
  matHandle: Handle<'MaterialAsset', 'shared'>;
392
423
  } {
393
424
  const world = new World();
394
- const assets = makeRenderAssetPort();
425
+ const assets = new AssetRegistry(makeMockShaderRegistry());
395
426
 
396
427
  // AABB: unit cube [-1,1] in each axis
397
428
  const aabb = new Float32Array([-1, -1, -1, 1, 1, 1]);
398
- const meshHandle = registerMesh(world, aabb);
399
- const matHandle = registerUnlitMaterial(world);
429
+ const meshHandle = registerMesh(world, assets, aabb);
430
+ const matHandle = registerUnlitMaterial(world, assets);
400
431
  return { world, assets, meshHandle, matHandle };
401
432
  }
402
433
 
@@ -485,14 +516,15 @@ function canonicalMaterialParamSchema(identifier: string): string {
485
516
 
486
517
  it('(4) entity with no position attribute (AABB is inverted-infinity) is always visible', () => {
487
518
  const world = new World();
488
- const assets = makeRenderAssetPort();
519
+ const assets = new AssetRegistry(makeMockShaderRegistry());
489
520
 
490
- // A mesh with no position attribute has no finite culling bounds and is always visible.
521
+ // Catalog a mesh with NO position attribute -> computeAABB returns empty box.
522
+ // The entity should be always-visible even far away from camera.
491
523
  const vertices = new Float32Array([
492
524
  0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0,
493
525
  1, 0, 0, 0, 0,
494
526
  ]);
495
- const meshHandle = world.allocSharedRef('MeshAsset', {
527
+ const meshResult = assets.catalog<MeshAsset>(AssetGuid.format(AssetGuid.random()), {
496
528
  kind: 'mesh',
497
529
  vertices,
498
530
  indices: new Uint16Array([0, 1, 2]),
@@ -508,7 +540,9 @@ function canonicalMaterialParamSchema(identifier: string): string {
508
540
  },
509
541
  ],
510
542
  });
511
- const matHandle = registerUnlitMaterial(world);
543
+ if (!meshResult.ok) throw new Error('catalog failed');
544
+ const meshHandle = world.allocSharedRef('MeshAsset', meshResult.value);
545
+ const matHandle = registerUnlitMaterial(world, assets);
512
546
 
513
547
  spawnCameraAt(world, 0, 0, 5);
514
548
  world
@@ -527,12 +561,13 @@ function canonicalMaterialParamSchema(identifier: string): string {
527
561
 
528
562
  it('(4b) entity with inverted-infinity AABB (no position attribute) is always visible', () => {
529
563
  const { world, assets, matHandle } = makeWorldWithAssets();
530
- // A mesh with no position attribute has no finite culling bounds.
564
+ // Catalog a mesh with NO position attribute -> computeAABB returns
565
+ // inverted-infinity empty box, which the culling path treats as always-visible.
531
566
  const vertices = new Float32Array([
532
567
  0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0,
533
568
  1, 0, 0, 0, 0,
534
569
  ]);
535
- const meshHandle = world.allocSharedRef('MeshAsset', {
570
+ const meshResult = assets.catalog<MeshAsset>(AssetGuid.format(AssetGuid.random()), {
536
571
  kind: 'mesh',
537
572
  vertices,
538
573
  indices: new Uint16Array([0, 1, 2]),
@@ -548,6 +583,8 @@ function canonicalMaterialParamSchema(identifier: string): string {
548
583
  },
549
584
  ],
550
585
  });
586
+ if (!meshResult.ok) throw new Error('catalog failed');
587
+ const meshHandle = world.allocSharedRef('MeshAsset', meshResult.value);
551
588
 
552
589
  spawnCameraAt(world, 0, 0, 5);
553
590
  spawnEntity(world, meshHandle, matHandle, 100, 100, 100);
@@ -666,18 +703,25 @@ function canonicalMaterialParamSchema(identifier: string): string {
666
703
  // Test path note: project convention places runtime tests under
667
704
  // `src/__tests__/` (see `instances.test-d.ts` header for the rationale).
668
705
 
669
- function makeHarness(): { world: World } {
670
- return { world: new World() };
706
+ interface CollectedError {
707
+ readonly code: string;
708
+ readonly detail: unknown;
709
+ }
710
+
711
+ function makeHarness(): { world: World; collected: CollectedError[] } {
712
+ const collected: CollectedError[] = [];
713
+ const world = new World();
714
+ return { world, collected };
671
715
  }
672
716
 
673
717
  describe('w14 - Instances { transforms: array<f32> } schema (AC-06)', () => {
674
- it('componentSchema(Instances).transforms is the array<f32> keyword (no legacy buffer/count fields)', () => {
718
+ it('Instances.schema.transforms is the array<f32> keyword (no legacy buffer/count fields)', () => {
675
719
  expect(Instances.name).toBe('Instances');
676
- expect(Object.keys(componentSchema(Instances)).length).toBe(1);
677
- expect((componentSchema(Instances) as Record<string, unknown>).transforms).toBe('array<f32>');
720
+ expect(Object.keys(Instances.fields).length).toBe(1);
721
+ expect(Instances.fields.transforms?.type).toBe('array<f32>');
678
722
  // Legacy fields retired in M3.
679
- expect((componentSchema(Instances) as Record<string, unknown>).buffer).toBeUndefined();
680
- expect((componentSchema(Instances) as Record<string, unknown>).count).toBeUndefined();
723
+ expect(Instances.fields.buffer).toBeUndefined();
724
+ expect(Instances.fields.count).toBeUndefined();
681
725
  });
682
726
 
683
727
  it('Instances component carries no per-component stride option (decision §2.3 migration)', () => {
@@ -688,7 +732,7 @@ function canonicalMaterialParamSchema(identifier: string): string {
688
732
  });
689
733
 
690
734
  it('(legal) spawn with 16 f32 multiples succeeds; snapshot length reflects element count', () => {
691
- const { world } = makeHarness();
735
+ const { world, collected } = makeHarness();
692
736
  const N = 4;
693
737
  const transforms = new Float32Array(N * 16);
694
738
  // Fill column 3 with distinguishable translations so the read path can
@@ -698,6 +742,10 @@ function canonicalMaterialParamSchema(identifier: string): string {
698
742
  transforms[i * 16 + 15] = 1;
699
743
  }
700
744
  const e = world.spawn({ component: Instances, data: { transforms } }).unwrap();
745
+ // No stride error fires on the ECS write path post-feat-20260515.
746
+ expect(collected.filter((c) => c.code === 'instance-transforms-stride-mismatch').length).toBe(
747
+ 0,
748
+ );
701
749
  const snap = world.get(e, Instances).unwrap().transforms;
702
750
  expect(snap.length).toBe(N * 16);
703
751
  // Sanity: column 3 translation x of instance 2 reads back as 3.
@@ -705,10 +753,15 @@ function canonicalMaterialParamSchema(identifier: string): string {
705
753
  });
706
754
 
707
755
  it('(ECS layer is stride-agnostic) spawn with 17 f32 does NOT route a stride error from ECS', () => {
708
- const { world } = makeHarness();
756
+ const { world, collected } = makeHarness();
709
757
  world.spawn({ component: Instances, data: { transforms: new Float32Array(17) } });
710
758
  // The runtime-side defensive lives in render-system-extract; the ECS
711
- // write path is deliberately stride-agnostic.
759
+ // write path itself MUST NOT route any stride error code.
760
+ expect(collected.filter((c) => c.code === 'instance-transforms-stride-mismatch').length).toBe(
761
+ 0,
762
+ );
763
+ // Negative anchor for the retired ECS-layer code.
764
+ expect(collected.filter((c) => c.code === 'managed-array-stride-mismatch').length).toBe(0);
712
765
  });
713
766
  });
714
767
  }
@@ -717,7 +770,7 @@ function canonicalMaterialParamSchema(identifier: string): string {
717
770
  // --- from mesh-gpu-handles-vertex-only.test.ts ---
718
771
  // mesh-gpu-handles-vertex-only.test - M2 / w3 (TDD).
719
772
  //
720
- // Coverage (vertex-only mesh upload chain through GpuResourceStore):
773
+ // Coverage (vertex-only mesh upload chain through GpuResidencyCache):
721
774
  // (a) MeshGpuHandles type carries `vertexCount: number` + `indexed: boolean`
722
775
  // and `indexBuffer: Buffer | null` (type-level via a const-assigned literal
723
776
  // that must compile).
@@ -762,15 +815,6 @@ function canonicalMaterialParamSchema(identifier: string): string {
762
815
  createdLabels: string[];
763
816
  }
764
817
 
765
- function canonicalAttributes(vertexCount: number): MeshAsset['attributes'] {
766
- return {
767
- position: new Float32Array(vertexCount * 3),
768
- normal: new Float32Array(vertexCount * 3),
769
- uv: new Float32Array(vertexCount * 2),
770
- tangent: new Float32Array(vertexCount * 4),
771
- };
772
- }
773
-
774
818
  // biome-ignore lint/suspicious/noExplicitAny: opaque mock device surface
775
819
  function makeMockDevice(probe: BufferProbe): any {
776
820
  const okShim = <T>(v: T) => ({ ok: true as const, value: v });
@@ -792,7 +836,7 @@ function canonicalMaterialParamSchema(identifier: string): string {
792
836
  kind: 'mesh',
793
837
  vertices,
794
838
  indices: new Uint16Array([0, 1, 2, 0, 2, 3]),
795
- attributes: canonicalAttributes(4),
839
+ attributes: buildMeshAttributeMapForUvSets(1),
796
840
  submeshes: [
797
841
  {
798
842
  indexOffset: 0,
@@ -809,7 +853,7 @@ function canonicalMaterialParamSchema(identifier: string): string {
809
853
  return {
810
854
  kind: 'mesh',
811
855
  vertices,
812
- attributes: canonicalAttributes(2),
856
+ attributes: buildMeshAttributeMapForUvSets(1),
813
857
  submeshes: [
814
858
  {
815
859
  indexOffset: 0,
@@ -834,12 +878,7 @@ function canonicalMaterialParamSchema(identifier: string): string {
834
878
  iboBytes: 0,
835
879
  indexCount: 0,
836
880
  indexFormat: 'uint16',
837
- layoutProjection: deriveVertexLayoutProjection({
838
- position: new Float32Array(6),
839
- normal: new Float32Array(6),
840
- uv: new Float32Array(4),
841
- tangent: new Float32Array(8),
842
- }),
881
+ layout: '12F',
843
882
  vertexCount: 2,
844
883
  indexed: false,
845
884
  topology: 'line-list',
@@ -854,7 +893,7 @@ function canonicalMaterialParamSchema(identifier: string): string {
854
893
  const probe: BufferProbe = { createdLabels: [] };
855
894
  const device = makeMockDevice(probe);
856
895
  const world = new World();
857
- const store = new GpuResourceStore();
896
+ const store = new GpuResidencyCache();
858
897
  store.configureGpuDevice(
859
898
  device,
860
899
  undefined,
@@ -885,7 +924,7 @@ function canonicalMaterialParamSchema(identifier: string): string {
885
924
  const probe: BufferProbe = { createdLabels: [] };
886
925
  const device = makeMockDevice(probe);
887
926
  const world = new World();
888
- const store = new GpuResourceStore();
927
+ const store = new GpuResidencyCache();
889
928
  store.configureGpuDevice(
890
929
  device,
891
930
  undefined,
@@ -1652,7 +1691,7 @@ function canonicalMaterialParamSchema(identifier: string): string {
1652
1691
  // (c) updateMesh on non-existent handle is a silent no-op (guards check
1653
1692
  // meshGpuHandles.has + device existence).
1654
1693
 
1655
- // feat-20260601-gpu-resource-store-extraction M1: updateMesh moved to the GPU
1694
+ // feat-20260601-device/gpu-residency-extraction M1: updateMesh moved to the GPU
1656
1695
  // store. The no-leak invariant is now structural-by-construction (the store
1657
1696
  // holds no registry reference, D-2), but the test still asserts that driving
1658
1697
  // updateMesh never grows the registry. Without a wired device the store
@@ -1666,7 +1705,7 @@ function canonicalMaterialParamSchema(identifier: string): string {
1666
1705
  kind: 'mesh',
1667
1706
  vertices,
1668
1707
  indices,
1669
- attributes: {},
1708
+ attributes: buildMeshAttributeMapForUvSets(1),
1670
1709
  aabb: new Float32Array(6),
1671
1710
  submeshes: [
1672
1711
  {
@@ -1705,7 +1744,7 @@ function canonicalMaterialParamSchema(identifier: string): string {
1705
1744
  const world = new World();
1706
1745
  const mesh = makeQuadMesh();
1707
1746
  const handle = world.allocSharedRef('MeshAsset', mesh);
1708
- const store = new GpuResourceStore();
1747
+ const store = new GpuResidencyCache();
1709
1748
  void unwrapHandle(handle);
1710
1749
 
1711
1750
  // Save the initial live shared-ref slot count.
@@ -1728,7 +1767,7 @@ function canonicalMaterialParamSchema(identifier: string): string {
1728
1767
  const world = new World();
1729
1768
  const mesh = makeQuadMesh();
1730
1769
  const handle = world.allocSharedRef('MeshAsset', mesh);
1731
- const store = new GpuResourceStore();
1770
+ const store = new GpuResidencyCache();
1732
1771
 
1733
1772
  const before = world.sharedRefs._liveCount();
1734
1773
  if (!(mesh.indices instanceof Uint16Array)) return;
@@ -1741,7 +1780,7 @@ function canonicalMaterialParamSchema(identifier: string): string {
1741
1780
  const world = new World();
1742
1781
  const mesh = makeQuadMesh();
1743
1782
  const handle = world.allocSharedRef('MeshAsset', mesh);
1744
- const store = new GpuResourceStore();
1783
+ const store = new GpuResidencyCache();
1745
1784
  const id = unwrapHandle(handle);
1746
1785
 
1747
1786
  // updateMesh with larger data (expansion path)
@@ -1749,7 +1788,7 @@ function canonicalMaterialParamSchema(identifier: string): string {
1749
1788
  if (!(bigger.indices instanceof Uint16Array)) return;
1750
1789
  store.updateMesh(handle, bigger.vertices, bigger.indices);
1751
1790
  // The id should still be the same — no new mint was performed.
1752
- const result = world.sharedRefs.resolve<string, MeshAsset>(handle);
1791
+ const result = resolveAssetHandle<MeshAsset>(world, handle);
1753
1792
  expect(result.ok).toBe(true);
1754
1793
  if (!result.ok) return;
1755
1794
  // The payload behind the handle is unchanged (updateMesh only touches the
@@ -1760,6 +1799,231 @@ function canonicalMaterialParamSchema(identifier: string): string {
1760
1799
  });
1761
1800
  }
1762
1801
 
1802
+ {
1803
+ // --- from validate-mesh-topology.test.ts ---
1804
+ // validate-mesh-topology.test - feat-20260604-mesh-topology-debug-draw M5 / w12 (TDD red).
1805
+ //
1806
+ // Coverage (AssetRegistry.catalog -> validateMeshPayload topology rules, AC-10):
1807
+ // (a) strip topology (line-strip / triangle-strip) with NO indices ->
1808
+ // Result.err code='asset-invalid-value', detail.field='topology' +
1809
+ // detail.value=<topology>, .hint non-empty (strip needs indices to
1810
+ // resolve stripIndexFormat).
1811
+ // (b) empty geometry (vertices.length === 0) with a non-default topology
1812
+ // (topology defined && !== 'triangle-list') -> same err shape.
1813
+ // (c) LEGAL combos pass (Result.ok): indexed point-list, indexed line-list,
1814
+ // vertex-only line-list, strip + indices.
1815
+ // (e) D-A4: a vertex-only mesh (indices omitted) does NOT trip the
1816
+ // :588 maxIndex+1===vertexCount invariant (it is index-only).
1817
+ //
1818
+ // Anchors: requirements AC-10 + constraint #8 (reuse asset-invalid-value, no new code);
1819
+ // plan-strategy D-A2 (illegal set = strip-no-indices + empty-non-default-topology);
1820
+ // plan-strategy D-A4 (maxIndex invariant gated on indices present);
1821
+ // research Finding 10 (validateMeshPayload :565-601) + Finding 5 (asset-invalid-value precedent).
1822
+
1823
+ function reg(): AssetRegistry {
1824
+ return new AssetRegistry(makeMockShaderRegistry());
1825
+ }
1826
+
1827
+ describe('validateMeshPayload topology rules (M5 w12 - AC-10)', () => {
1828
+ it('(a1) line-strip with no indices -> asset-invalid-value + detail.field=topology + non-empty hint', () => {
1829
+ const result = reg().catalog(AssetGuid.format(AssetGuid.random()), {
1830
+ kind: 'mesh',
1831
+ vertices: new Float32Array(2 * 12),
1832
+ attributes: {},
1833
+ materialSlots: [{ slotName: 'Default' }],
1834
+ submeshes: [
1835
+ {
1836
+ indexOffset: 0,
1837
+ indexCount: 0,
1838
+ vertexCount: 0,
1839
+ materialSlot: 0,
1840
+ topology: 'line-strip',
1841
+ },
1842
+ ],
1843
+ } as MeshAsset);
1844
+ expect(result.ok).toBe(false);
1845
+ if (!result.ok) {
1846
+ expect(result.error.code).toBe('asset-invalid-value');
1847
+ const d = result.error.detail as { field: string; value: string };
1848
+ expect(d.field).toBe('submeshes[0].topology');
1849
+ expect(d.value).toBe('line-strip');
1850
+ expect(result.error.hint.length).toBeGreaterThan(0);
1851
+ }
1852
+ });
1853
+
1854
+ it('(a2) triangle-strip with no indices -> asset-invalid-value + detail.value=triangle-strip', () => {
1855
+ const result = reg().catalog(AssetGuid.format(AssetGuid.random()), {
1856
+ kind: 'mesh',
1857
+ vertices: new Float32Array(3 * 12),
1858
+ attributes: {},
1859
+ materialSlots: [{ slotName: 'Default' }],
1860
+ submeshes: [
1861
+ {
1862
+ indexOffset: 0,
1863
+ indexCount: 0,
1864
+ vertexCount: 0,
1865
+ materialSlot: 0,
1866
+ topology: 'triangle-strip',
1867
+ },
1868
+ ],
1869
+ } as MeshAsset);
1870
+ expect(result.ok).toBe(false);
1871
+ if (!result.ok) {
1872
+ expect(result.error.code).toBe('asset-invalid-value');
1873
+ const d = result.error.detail as { field: string; value: string };
1874
+ expect(d.field).toBe('submeshes[0].topology');
1875
+ expect(d.value).toBe('triangle-strip');
1876
+ expect(result.error.hint.length).toBeGreaterThan(0);
1877
+ }
1878
+ });
1879
+
1880
+ it('(b) empty geometry with non-default topology -> asset-invalid-value', () => {
1881
+ const result = reg().catalog(AssetGuid.format(AssetGuid.random()), {
1882
+ kind: 'mesh',
1883
+ vertices: new Float32Array(0),
1884
+ attributes: {},
1885
+ materialSlots: [{ slotName: 'Default' }],
1886
+ submeshes: [
1887
+ {
1888
+ indexOffset: 0,
1889
+ indexCount: 0,
1890
+ vertexCount: 0,
1891
+ materialSlot: 0,
1892
+ topology: 'line-list',
1893
+ },
1894
+ ],
1895
+ } as MeshAsset);
1896
+ expect(result.ok).toBe(false);
1897
+ if (!result.ok) {
1898
+ expect(result.error.code).toBe('asset-invalid-value');
1899
+ const d = result.error.detail as { field: string; value: string };
1900
+ expect(d.field).toBe('submeshes[0].topology');
1901
+ expect(d.value).toBe('line-list');
1902
+ expect(result.error.hint.length).toBeGreaterThan(0);
1903
+ }
1904
+ });
1905
+
1906
+ it('(b-legal) empty geometry with default/omitted topology stays legal (Result.ok)', () => {
1907
+ const result = reg().catalog(AssetGuid.format(AssetGuid.random()), {
1908
+ kind: 'mesh',
1909
+ vertices: new Float32Array(0),
1910
+ attributes: {},
1911
+ materialSlots: [{ slotName: 'Default' }],
1912
+ submeshes: [
1913
+ {
1914
+ indexOffset: 0,
1915
+ indexCount: 0,
1916
+ vertexCount: 0,
1917
+ materialSlot: 0,
1918
+ topology: 'triangle-list',
1919
+ },
1920
+ ],
1921
+ } as MeshAsset);
1922
+ expect(result.ok).toBe(true);
1923
+ });
1924
+
1925
+ it('(c1) indexed point-list is legal (Result.ok)', () => {
1926
+ const result = reg().catalog(AssetGuid.format(AssetGuid.random()), {
1927
+ kind: 'mesh',
1928
+ vertices: new Float32Array(3 * 12),
1929
+ indices: new Uint16Array([0, 1, 2]),
1930
+ attributes: {},
1931
+ materialSlots: [{ slotName: 'Default' }],
1932
+ submeshes: [
1933
+ {
1934
+ indexOffset: 0,
1935
+ indexCount: 3,
1936
+ vertexCount: 0,
1937
+ materialSlot: 0,
1938
+ topology: 'point-list',
1939
+ },
1940
+ ],
1941
+ } as MeshAsset);
1942
+ expect(result.ok).toBe(true);
1943
+ });
1944
+
1945
+ it('(c2) indexed line-list is legal (Result.ok)', () => {
1946
+ const result = reg().catalog(AssetGuid.format(AssetGuid.random()), {
1947
+ kind: 'mesh',
1948
+ vertices: new Float32Array(2 * 12),
1949
+ indices: new Uint16Array([0, 1]),
1950
+ attributes: {},
1951
+ materialSlots: [{ slotName: 'Default' }],
1952
+ submeshes: [
1953
+ {
1954
+ indexOffset: 0,
1955
+ indexCount: 2,
1956
+ vertexCount: 0,
1957
+ materialSlot: 0,
1958
+ topology: 'line-list',
1959
+ },
1960
+ ],
1961
+ } as MeshAsset);
1962
+ expect(result.ok).toBe(true);
1963
+ });
1964
+
1965
+ it('(c3) vertex-only line-list (no indices) is legal (Result.ok)', () => {
1966
+ const result = reg().catalog(AssetGuid.format(AssetGuid.random()), {
1967
+ kind: 'mesh',
1968
+ vertices: new Float32Array(2 * 12),
1969
+ attributes: {},
1970
+ materialSlots: [{ slotName: 'Default' }],
1971
+ submeshes: [
1972
+ {
1973
+ indexOffset: 0,
1974
+ indexCount: 0,
1975
+ vertexCount: 0,
1976
+ materialSlot: 0,
1977
+ topology: 'line-list',
1978
+ },
1979
+ ],
1980
+ } as MeshAsset);
1981
+ expect(result.ok).toBe(true);
1982
+ });
1983
+
1984
+ it('(c4) triangle-strip WITH indices is legal (Result.ok)', () => {
1985
+ // 4 verts -> 2 triangles via strip; indices reference all 4 (maxIndex+1===vertexCount)
1986
+ const result = reg().catalog(AssetGuid.format(AssetGuid.random()), {
1987
+ kind: 'mesh',
1988
+ vertices: new Float32Array(4 * 12),
1989
+ indices: new Uint32Array([0, 1, 2, 3]),
1990
+ attributes: {},
1991
+ materialSlots: [{ slotName: 'Default' }],
1992
+ submeshes: [
1993
+ {
1994
+ indexOffset: 0,
1995
+ indexCount: 4,
1996
+ vertexCount: 0,
1997
+ materialSlot: 0,
1998
+ topology: 'triangle-strip',
1999
+ },
2000
+ ],
2001
+ } as MeshAsset);
2002
+ expect(result.ok).toBe(true);
2003
+ });
2004
+
2005
+ it('(e) D-A4: vertex-only triangle-list (no indices) does NOT trip maxIndex invariant', () => {
2006
+ // 3 verts, no indices: the :588 maxIndex+1===vertexCount check must be skipped.
2007
+ const result = reg().catalog(AssetGuid.format(AssetGuid.random()), {
2008
+ kind: 'mesh',
2009
+ vertices: new Float32Array(3 * 12),
2010
+ attributes: {},
2011
+ materialSlots: [{ slotName: 'Default' }],
2012
+ submeshes: [
2013
+ {
2014
+ indexOffset: 0,
2015
+ indexCount: 0,
2016
+ vertexCount: 0,
2017
+ materialSlot: 0,
2018
+ topology: 'triangle-list',
2019
+ },
2020
+ ],
2021
+ } as MeshAsset);
2022
+ expect(result.ok).toBe(true);
2023
+ });
2024
+ });
2025
+ }
2026
+
1763
2027
  {
1764
2028
  // --- from instances-with-submeshes.test.ts ---
1765
2029
  // instances-with-submeshes.test.ts -- unit tests for Instances x submesh
@@ -1769,15 +2033,6 @@ function canonicalMaterialParamSchema(identifier: string): string {
1769
2033
  // (shared instanceCount, not per-submesh); OOS-5 (per-submesh independent
1770
2034
  // instances explicitly excluded).
1771
2035
 
1772
- function canonicalAttributes(vertexCount: number): MeshAsset['attributes'] {
1773
- return {
1774
- position: new Float32Array(vertexCount * 3),
1775
- normal: new Float32Array(vertexCount * 3),
1776
- uv: new Float32Array(vertexCount * 2),
1777
- tangent: new Float32Array(vertexCount * 4),
1778
- };
1779
- }
1780
-
1781
2036
  const ENGINE = '../createRenderer';
1782
2037
 
1783
2038
  interface PassSpies {
@@ -1827,6 +2082,7 @@ function canonicalMaterialParamSchema(identifier: string): string {
1827
2082
  limits: {},
1828
2083
  queue: {
1829
2084
  submit: () => undefined,
2085
+ onSubmittedWorkDone: () => Promise.resolve(undefined),
1830
2086
  writeBuffer: () => undefined,
1831
2087
  writeTexture: () => undefined,
1832
2088
  },
@@ -1878,8 +2134,8 @@ function canonicalMaterialParamSchema(identifier: string): string {
1878
2134
  identifier,
1879
2135
  sourcePath: `${identifier}.wgsl`,
1880
2136
  composedWgsl: '/* stub */',
1881
- paramSchema: canonicalMaterialParamSchema(identifier),
1882
- variants: canonicalDefaultMaterialVariants(identifier),
2137
+ paramSchema: '[]',
2138
+ variants: [],
1883
2139
  });
1884
2140
  const manifest = {
1885
2141
  schemaVersion: '1.0.0',
@@ -1910,15 +2166,10 @@ function canonicalMaterialParamSchema(identifier: string): string {
1910
2166
  };
1911
2167
  }
1912
2168
 
1913
- interface RendererLike {
1914
- ready: Promise<void>;
1915
- draw: (worlds: unknown, opts: { cameraOwner: number; resourceOwner: number }) => void;
1916
- onError: (cb: (err: { code: string }) => void) => () => void;
1917
- assets: { register: (asset: unknown) => { ok: boolean; value: unknown } };
1918
- }
2169
+ type RendererLike = RendererType;
1919
2170
 
1920
2171
  async function importEngine(): Promise<{
1921
- createRenderer: (canvas: unknown, opts?: unknown) => Promise<RendererLike>;
2172
+ createRenderer: (...args: unknown[]) => Promise<{ unwrap(): RendererLike }>;
1922
2173
  }> {
1923
2174
  return (await import(ENGINE)) as never;
1924
2175
  }
@@ -1941,7 +2192,7 @@ function canonicalMaterialParamSchema(identifier: string): string {
1941
2192
  Instances: unknown;
1942
2193
  }> {
1943
2194
  return {
1944
- ...(await import('@forgeax/engine-render/internal')),
2195
+ ...(await import('@forgeax/engine-render')),
1945
2196
  ...(await import('@forgeax/engine-scene')),
1946
2197
  } as never;
1947
2198
  }
@@ -1977,12 +2228,11 @@ function canonicalMaterialParamSchema(identifier: string): string {
1977
2228
  }
1978
2229
 
1979
2230
  function twoSubmeshMesh(): MeshAsset {
1980
- const vertexCount = 6;
1981
2231
  return {
1982
2232
  kind: 'mesh',
1983
- vertices: new Float32Array(vertexCount * 12),
2233
+ vertices: new Float32Array(6 * 12),
1984
2234
  indices: new Uint16Array([0, 1, 2, 3, 4, 5]),
1985
- attributes: canonicalAttributes(vertexCount),
2235
+ attributes: buildMeshAttributeMapForUvSets(1),
1986
2236
  materialSlots: [{ slotName: 'First' }, { slotName: 'Second' }],
1987
2237
  submeshes: [
1988
2238
  {
@@ -2020,14 +2270,15 @@ function canonicalMaterialParamSchema(identifier: string): string {
2020
2270
  const { device } = makeMockGPUDevice(spies);
2021
2271
  vi.stubGlobal('navigator', { ...baseNavigator, gpu: makeMockGPU(device) });
2022
2272
  const { createRenderer } = await importEngine();
2023
- const renderer = await createRenderer(
2024
- makeMockCanvas(),
2025
- {},
2026
- {
2027
- shaderManifestUrl: buildManifestDataUrl(),
2028
- },
2029
- );
2030
- await renderer.ready;
2273
+ const renderer = (
2274
+ await createRenderer(
2275
+ makeMockCanvas(),
2276
+ {},
2277
+ {
2278
+ shaderManifestUrl: buildManifestDataUrl(),
2279
+ },
2280
+ )
2281
+ ).unwrap();
2031
2282
  return { renderer };
2032
2283
  }
2033
2284
 
@@ -2040,7 +2291,6 @@ function canonicalMaterialParamSchema(identifier: string): string {
2040
2291
  const { World } = await importEcs();
2041
2292
  const C = await importComponents();
2042
2293
  const world = new World();
2043
- registerRuntimeComponents(world);
2044
2294
  const meshHandle = world.allocSharedRef('MeshAsset', meshAsset) as Handle<
2045
2295
  'MeshAsset',
2046
2296
  'shared'
@@ -2082,7 +2332,7 @@ function canonicalMaterialParamSchema(identifier: string): string {
2082
2332
  { component: C.Transform, data: cameraTransform() },
2083
2333
  );
2084
2334
  world.spawn(
2085
- { component: C.DirectionalLight, data: { direction: [-0.5, -1, -0.3] } },
2335
+ { component: C.DirectionalLight, data: {} },
2086
2336
  { component: C.Transform, data: cameraTransform() },
2087
2337
  );
2088
2338
  world.spawn(
@@ -2107,14 +2357,14 @@ function canonicalMaterialParamSchema(identifier: string): string {
2107
2357
  const spies = makePassSpies();
2108
2358
  const { renderer } = await setupRenderer(spies);
2109
2359
  const errors: string[] = [];
2110
- renderer.onError((e) => errors.push(e.code));
2360
+ subscribeRendererErrors(renderer, (e) => errors.push(e.code));
2111
2361
 
2112
2362
  const world = await spawnInstancedScene(renderer, twoSubmeshMesh(), 2, 4);
2113
- if (!(renderer as unknown as RendererType).attachWorld(world as WorldType).ok) {
2114
- throw new Error('World attachment failed');
2115
- }
2116
- (world as WorldType).update().unwrap();
2117
- renderer.draw([world], { cameraOwner: 0, resourceOwner: 0 });
2363
+ drawPublished(renderer, world as WorldType);
2364
+ await new Promise((resolve) => setTimeout(resolve, 0));
2365
+ spies.draw.mockClear();
2366
+ spies.drawIndexed.mockClear();
2367
+ drawPublished(renderer, world as WorldType);
2118
2368
 
2119
2369
  // Each submesh draws M times (4 instances each, shared instanceCount).
2120
2370
  // N=2 submeshes * M=1 draw each = 2 drawIndexed calls.
@@ -2129,14 +2379,14 @@ function canonicalMaterialParamSchema(identifier: string): string {
2129
2379
  const spies = makePassSpies();
2130
2380
  const { renderer } = await setupRenderer(spies);
2131
2381
  const errors: string[] = [];
2132
- renderer.onError((e) => errors.push(e.code));
2382
+ subscribeRendererErrors(renderer, (e) => errors.push(e.code));
2133
2383
 
2134
2384
  const world = await spawnInstancedScene(renderer, twoSubmeshMesh(), 2, 4);
2135
- if (!(renderer as unknown as RendererType).attachWorld(world as WorldType).ok) {
2136
- throw new Error('World attachment failed');
2137
- }
2138
- (world as WorldType).update().unwrap();
2139
- renderer.draw([world], { cameraOwner: 0, resourceOwner: 0 });
2385
+ drawPublished(renderer, world as WorldType);
2386
+ await new Promise((resolve) => setTimeout(resolve, 0));
2387
+ spies.draw.mockClear();
2388
+ spies.drawIndexed.mockClear();
2389
+ drawPublished(renderer, world as WorldType);
2140
2390
 
2141
2391
  // All submesh draws share the same instanceCount (D-8).
2142
2392
  // Verify drawIndexed calls carry instanceCount parameter at index 1.
@@ -2159,13 +2409,12 @@ function canonicalMaterialParamSchema(identifier: string): string {
2159
2409
  const spies = makePassSpies();
2160
2410
  const { renderer } = await setupRenderer(spies);
2161
2411
  const errors: string[] = [];
2162
- renderer.onError((e) => errors.push(e.code));
2412
+ subscribeRendererErrors(renderer, (e) => errors.push(e.code));
2163
2413
 
2164
2414
  // Spawn without Instances component.
2165
2415
  const { World } = await importEcs();
2166
2416
  const C = await importComponents();
2167
2417
  const world = new World();
2168
- registerRuntimeComponents(world);
2169
2418
  const meshHandle = world.allocSharedRef('MeshAsset', twoSubmeshMesh()) as Handle<
2170
2419
  'MeshAsset',
2171
2420
  'shared'
@@ -2199,7 +2448,7 @@ function canonicalMaterialParamSchema(identifier: string): string {
2199
2448
  { component: C.Transform, data: cameraTransform() },
2200
2449
  );
2201
2450
  world.spawn(
2202
- { component: C.DirectionalLight, data: { direction: [-0.5, -1, -0.3] } },
2451
+ { component: C.DirectionalLight, data: {} },
2203
2452
  { component: C.Transform, data: cameraTransform() },
2204
2453
  );
2205
2454
  world.spawn(
@@ -2208,13 +2457,11 @@ function canonicalMaterialParamSchema(identifier: string): string {
2208
2457
  { component: C.Transform, data: originTransform() },
2209
2458
  );
2210
2459
 
2211
- if (!(renderer as unknown as RendererType).attachWorld(world as WorldType).ok) {
2212
- throw new Error('World attachment failed');
2213
- }
2214
-
2215
- (world as WorldType).update().unwrap();
2216
-
2217
- renderer.draw([world], { cameraOwner: 0, resourceOwner: 0 });
2460
+ drawPublished(renderer, world as WorldType);
2461
+ await new Promise((resolve) => setTimeout(resolve, 0));
2462
+ spies.draw.mockClear();
2463
+ spies.drawIndexed.mockClear();
2464
+ drawPublished(renderer, world as WorldType);
2218
2465
 
2219
2466
  // Without Instances, instanceCount defaults to 1.
2220
2467
  const indexedCalls = spies.drawIndexed.mock.calls as Array<
@@ -2240,6 +2487,125 @@ function canonicalMaterialParamSchema(identifier: string): string {
2240
2487
  // read-side interception); plan-strategy §5.3 key test points "AC-05 three
2241
2488
  // triggers".
2242
2489
 
2490
+ // ── helpers ──────────────────────────────────────────────────────────────────
2491
+
2492
+ function freshRegistry() {
2493
+ return new AssetRegistry(
2494
+ // biome-ignore lint/suspicious/noExplicitAny: mock ShaderRegistry
2495
+ { findMaterialArtifact: () => ({ ok: false }) } as any,
2496
+ );
2497
+ }
2498
+
2499
+ function makeMeshPayload(overrides: {
2500
+ submeshes?: {
2501
+ indexOffset: number;
2502
+ indexCount: number;
2503
+ vertexCount: number;
2504
+ topology: string;
2505
+ }[];
2506
+ indices?: Uint16Array;
2507
+ }) {
2508
+ const indices = overrides.indices ?? new Uint16Array([0, 1, 2, 0, 2, 3]);
2509
+ const submeshes = overrides.submeshes ?? [
2510
+ {
2511
+ indexOffset: 0,
2512
+ indexCount: 6,
2513
+ vertexCount: 4,
2514
+ topology: 'triangle-list' as const,
2515
+ },
2516
+ ];
2517
+ return {
2518
+ kind: 'mesh' as const,
2519
+ vertices: new Float32Array(4 * 12), // 4 verts x 12 floats
2520
+ indices,
2521
+ attributes: {
2522
+ position: new Float32Array(4 * 3),
2523
+ },
2524
+ materialSlots: [{ slotName: 'Default' }],
2525
+ submeshes: submeshes.map((submesh) => ({ ...submesh, materialSlot: 0 })),
2526
+ };
2527
+ }
2528
+
2529
+ // ── write-side: submeshes-empty ──────────────────────────────────────────────
2530
+
2531
+ describe('write-side: mesh-asset-submeshes-empty (AC-05 b)', () => {
2532
+ it('register with submeshes: [] yields err with code mesh-asset-submeshes-empty', () => {
2533
+ const registry = freshRegistry();
2534
+ const payload = makeMeshPayload({ submeshes: [] });
2535
+
2536
+ const result = registry.catalog(AssetGuid.format(AssetGuid.random()), payload as never);
2537
+ expect(result.ok).toBe(false);
2538
+ if (!result.ok) {
2539
+ expect(result.error).toBeInstanceOf(AssetError);
2540
+ expect(result.error.code).toBe('mesh-asset-submeshes-empty');
2541
+ expect(result.error.expected).toBeTruthy();
2542
+ expect(result.error.hint).toBeTruthy();
2543
+ expect(result.error.detail).toBeDefined();
2544
+ expect((result.error.detail as { meshAssetGuid?: string }).meshAssetGuid).toBeTruthy();
2545
+ }
2546
+ });
2547
+ });
2548
+
2549
+ // ── write-side: index-range-out-of-bounds ────────────────────────────────────
2550
+
2551
+ describe('write-side: mesh-submesh-index-range-out-of-bounds (AC-05 c)', () => {
2552
+ it('register with submesh index range exceeding index buffer yields err', () => {
2553
+ const registry = freshRegistry();
2554
+ const indices = new Uint16Array([0, 1, 2, 0, 2, 3]);
2555
+ const payload = makeMeshPayload({
2556
+ indices,
2557
+ submeshes: [
2558
+ {
2559
+ indexOffset: 4,
2560
+ indexCount: 4,
2561
+ vertexCount: 4,
2562
+ topology: 'triangle-list' as const,
2563
+ },
2564
+ ],
2565
+ });
2566
+
2567
+ const result = registry.catalog(AssetGuid.format(AssetGuid.random()), payload as never);
2568
+ expect(result.ok).toBe(false);
2569
+ if (!result.ok) {
2570
+ expect(result.error).toBeInstanceOf(AssetError);
2571
+ expect(result.error.code).toBe('mesh-submesh-index-range-out-of-bounds');
2572
+ expect(result.error.expected).toBeTruthy();
2573
+ expect(result.error.hint).toBeTruthy();
2574
+ const detail = result.error.detail as {
2575
+ submeshIndex?: number;
2576
+ indexOffset?: number;
2577
+ indexCount?: number;
2578
+ indexBufferLength?: number;
2579
+ meshAssetGuid?: string;
2580
+ };
2581
+ expect(detail.submeshIndex).toBe(0);
2582
+ expect(detail.indexOffset).toBe(4);
2583
+ expect(detail.indexCount).toBe(4);
2584
+ expect(detail.indexBufferLength).toBe(6);
2585
+ expect(detail.meshAssetGuid).toBeTruthy();
2586
+ }
2587
+ });
2588
+
2589
+ it('register with valid submesh index range passes', () => {
2590
+ const registry = freshRegistry();
2591
+ const indices = new Uint16Array([0, 1, 2, 0, 2, 3]);
2592
+ const payload = makeMeshPayload({
2593
+ indices,
2594
+ submeshes: [
2595
+ {
2596
+ indexOffset: 0,
2597
+ indexCount: 6,
2598
+ vertexCount: 4,
2599
+ topology: 'triangle-list' as const,
2600
+ },
2601
+ ],
2602
+ });
2603
+
2604
+ const result = registry.catalog(AssetGuid.format(AssetGuid.random()), payload as never);
2605
+ expect(result.ok).toBe(true);
2606
+ });
2607
+ });
2608
+
2243
2609
  // ── read-side: renderer override diagnostics ─────────────────────────────────
2244
2610
 
2245
2611
  describe('read-side: MeshRenderer override diagnostics', () => {