@forgeax/engine-runtime 0.1.4 → 0.1.6

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 +49 -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 +3 -3
  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 +19 -81
  173. package/dist/index.d.ts.map +1 -1
  174. package/dist/index.mjs +105 -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 +575 -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 +0 -18
  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 +1203 -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 +16 -500
  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 +14 -21
  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 +81 -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 +60 -558
  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 +59 -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 +43 -261
  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 +12 -11
  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 +64 -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 +28 -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 +47 -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
@@ -1,1511 +0,0 @@
1
- // Consolidated by feat-20260608-ci-time-cut M5 w13/w14: merges
2
- // - bind-group-cache-stable-frame.test.ts
3
- // - bind-group-cache-view.test.ts
4
- // - bind-group-cache-entity-key.test.ts
5
- // - bind-group-cache-instances.test.ts
6
- // Each original file body sits in its own block-scope so helper names
7
- // cannot collide; describe() inside still registers globally with vitest.
8
-
9
- import type { World as WorldType } from '@forgeax/engine-ecs';
10
- import { World } from '@forgeax/engine-ecs';
11
- import type { Renderer as RendererType } from '@forgeax/engine-render';
12
- import type { RenderAssetPort } from '@forgeax/engine-render/internal';
13
- import {
14
- type ExtractedFrame,
15
- extractFrame,
16
- MeshFilter,
17
- MeshRenderer,
18
- prepareExtractContext,
19
- } from '@forgeax/engine-render/internal';
20
- import { Transform } from '@forgeax/engine-scene';
21
- import type { Handle, MaterialAsset, MeshAsset } from '@forgeax/engine-types';
22
- import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
23
- import { makeMockShaderRegistry } from './helpers/mock-shader-registry';
24
- import {
25
- TEST_CUBE_HANDLE,
26
- TEST_TRIANGLE_HANDLE,
27
- withPrimitiveMeshes,
28
- } from './helpers/primitive-world';
29
- import { createTestRenderAssetOwner } from './helpers/render-asset-port';
30
-
31
- // ─── from bind-group-cache-stable-frame.test.ts ───
32
- {
33
- // bind-group-cache-stable-frame.test.ts -- M5 / w16 (TDD red)
34
- //
35
- // AC-03 stable-frame + AC-09 type-inference test.
36
- //
37
- // AC-03: minimal stable world (one PBR mesh + camera + directional light),
38
- // consecutive draw >= 3 frames, assert frame-3 createBindGroupCount == 0
39
- // (all cache-resident after warm-up).
40
- //
41
- // AC-09: at the assertion line, TypeScript infers `number` for
42
- // `renderer.bindGroupCounts.createBindGroup` without `as` cast.
43
- //
44
- // TDD red: this test will fail until w12 (material/instances cache) and
45
- // w15 (shadow sentinel) are complete. After M4 the stable-frame counter
46
- // should reach 0 once all four cache categories are hot.
47
- //
48
- // Stable-frame definition (requirements AC-03): same World, no
49
- // spawn/despawn, no material change, no resize, no Skylight change.
50
-
51
- const ENGINE = '../createRenderer';
52
-
53
- // ─── Mock helpers (mirrors render-system.test.ts) ──────────────────────────
54
-
55
- interface MockGL2Context {
56
- __mockTag: 'webgl2';
57
- getExtension: () => null;
58
- getParameter: () => number;
59
- isContextLost: () => boolean;
60
- }
61
-
62
- function makeMockGL2(): MockGL2Context {
63
- return {
64
- __mockTag: 'webgl2',
65
- getExtension: () => null,
66
- getParameter: () => 1,
67
- isContextLost: () => false,
68
- };
69
- }
70
-
71
- function makeMockCanvas(
72
- webgl2: 'context' | 'null',
73
- webgpu: 'context' | 'null',
74
- ): HTMLCanvasElement {
75
- const canvas = {
76
- width: 800,
77
- height: 600,
78
- getContext(kind: string): unknown {
79
- if (kind === 'webgl2') {
80
- return webgl2 === 'context' ? makeMockGL2() : null;
81
- }
82
- if (kind === 'webgpu') {
83
- if (webgpu === 'context') {
84
- return {
85
- __mockTag: 'webgpu-canvas-context',
86
- configure: () => undefined,
87
- unconfigure: () => undefined,
88
- getCurrentTexture: () => ({ createView: () => ({}) }),
89
- };
90
- }
91
- return null;
92
- }
93
- return null;
94
- },
95
- addEventListener: () => undefined,
96
- removeEventListener: () => undefined,
97
- };
98
- return canvas as Partial<HTMLCanvasElement> as HTMLCanvasElement;
99
- }
100
-
101
- function makeMockGPUDevice(): { device: unknown } {
102
- const lost = new Promise<unknown>(() => undefined);
103
- const device = {
104
- __mockTag: 'gpu-device',
105
- lost,
106
- features: new Set(),
107
- limits: {},
108
- queue: {
109
- submit: () => undefined,
110
- writeBuffer: () => undefined,
111
- writeTexture: () => undefined,
112
- },
113
- createShaderModule: () => ({ getCompilationInfo: async () => ({ messages: [] }) }),
114
- createBindGroupLayout: () => ({}),
115
- createPipelineLayout: () => ({}),
116
- createRenderPipeline: () => ({}),
117
- createBindGroup: () => ({}),
118
- createBuffer: () => ({
119
- getMappedRange: () => new ArrayBuffer(64),
120
- unmap: () => undefined,
121
- }),
122
- createCommandEncoder: () => ({
123
- beginRenderPass: () => ({
124
- setPipeline: () => undefined,
125
- setVertexBuffer: () => undefined,
126
- setIndexBuffer: () => undefined,
127
- setBindGroup: () => undefined,
128
- draw: () => undefined,
129
- drawIndexed: () => undefined,
130
- end: () => undefined,
131
- }),
132
- finish: () => ({}),
133
- }),
134
- createTexture: () => ({ createView: () => ({}) }),
135
- createSampler: () => ({}),
136
- destroy: () => undefined,
137
- };
138
- return { device };
139
- }
140
-
141
- function makeMockGPU(deviceObj: unknown): unknown {
142
- return {
143
- requestAdapter: async () => ({
144
- requestDevice: async () => deviceObj,
145
- }),
146
- getPreferredCanvasFormat: () => 'bgra8unorm',
147
- };
148
- }
149
-
150
- const baseNavigator: Navigator = {
151
- userAgent: 'mock-engine-test',
152
- } as Partial<Navigator> as Navigator;
153
-
154
- function buildManifestDataUrl(): string {
155
- const materialShaderStub = (identifier: string) => ({
156
- identifier,
157
- sourcePath: `${identifier}.wgsl`,
158
- composedWgsl: '/* stub */',
159
- paramSchema: '[]',
160
- variants: [],
161
- });
162
- const manifest = {
163
- schemaVersion: '1.0.0',
164
- entries: [
165
- { hash: 'pbr00000', wgsl: '/* pbr stub - calls f_schlick( */', glsl: '', bindings: '' },
166
- { hash: 'unlit000', wgsl: '/* unlit stub */', glsl: '', bindings: '' },
167
- {
168
- hash: 'tonemap0',
169
- wgsl: '/* tonemap stub - struct TonemapParams { exposure: f32 }; */',
170
- glsl: '',
171
- bindings: '',
172
- },
173
- ],
174
- materialShaders: [
175
- materialShaderStub('forgeax::default-standard-pbr'),
176
- materialShaderStub('forgeax::default-unlit'),
177
- ],
178
- };
179
- return `data:application/json,${encodeURIComponent(JSON.stringify(manifest))}`;
180
- }
181
-
182
- // ─── Type-level imports (mirrors render-system.test.ts) ────────────────────
183
-
184
- async function importEngine(): Promise<{
185
- createRenderer: (
186
- canvas: unknown,
187
- opts?: unknown,
188
- bundler?: unknown,
189
- ) => Promise<{
190
- backend: string;
191
- ready: Promise<unknown>;
192
- draw: (worlds: unknown, opts: { cameraOwner: number; resourceOwner: number }) => unknown;
193
- onError: (cb: (err: { code: string; detail?: unknown; hint?: string }) => void) => () => void;
194
- assets: {
195
- register: (asset: unknown) => { ok: boolean; value: unknown };
196
- };
197
- bindGroupCounts: { readonly createBindGroup: number };
198
- }>;
199
- }> {
200
- return (await import(ENGINE)) as never;
201
- }
202
-
203
- async function importEcs(): Promise<{
204
- World: new () => {
205
- spawn: (...componentDatas: unknown[]) => unknown;
206
- update: () => void;
207
- };
208
- }> {
209
- const { World: BaseWorld } = await import('@forgeax/engine-ecs');
210
- return { World: withPrimitiveMeshes(BaseWorld) } as never;
211
- }
212
-
213
- // Import runtime components via the package index to get real types
214
- // (Camera, Transform, MeshFilter, MeshRenderer, DirectionalLight, etc.).
215
- async function importComponents(): Promise<{
216
- Transform: unknown;
217
- MeshFilter: unknown;
218
- MeshRenderer: unknown;
219
- Camera: unknown;
220
- DirectionalLight: unknown;
221
- HANDLE_CUBE: Handle<'MeshAsset', 'shared'>;
222
- }> {
223
- return {
224
- ...(await import('@forgeax/engine-render/internal')),
225
- ...(await import('@forgeax/engine-scene')),
226
- HANDLE_CUBE: TEST_CUBE_HANDLE,
227
- } as never;
228
- }
229
-
230
- function cameraTransform() {
231
- return {
232
- pos: [0, 0, 5],
233
- quat: [0, 0, 0, 1],
234
- scale: [1, 1, 1],
235
- };
236
- }
237
-
238
- // ─── Helper: build a minimal stable world ──────────────────────────────────
239
-
240
- async function buildStableWorld() {
241
- const C = await importComponents();
242
-
243
- const { World } = await importEcs();
244
- const world = new World();
245
-
246
- // Camera at z=5 looking at origin
247
- world.spawn(
248
- {
249
- component: C.Camera,
250
- data: {
251
- fov: Math.PI / 4,
252
- aspect: 16 / 9,
253
- near: 0.1,
254
- far: 100,
255
- projection: 0,
256
- left: -1,
257
- right: 1,
258
- bottom: -1,
259
- top: 1,
260
- },
261
- },
262
- { component: C.Transform, data: cameraTransform() },
263
- );
264
-
265
- // Directional light
266
- world.spawn(
267
- { component: C.DirectionalLight, data: {} },
268
- { component: C.Transform, data: cameraTransform() },
269
- );
270
-
271
- // PBR mesh (cube) with explicit PBR material
272
- world.spawn(
273
- { component: C.MeshRenderer, data: { materials: [0] } },
274
- { component: C.MeshFilter, data: { assetHandle: C.HANDLE_CUBE } },
275
- {
276
- component: C.Transform,
277
- data: {
278
- pos: [0, 0, 0],
279
- quat: [0, 0, 0, 1],
280
- scale: [1, 1, 1],
281
- },
282
- },
283
- );
284
-
285
- return world;
286
- }
287
-
288
- // ─── Tests ────────────────────────────────────────────────────────────────
289
-
290
- describe('w16 — AC-03 stable-frame + AC-09 type inference', () => {
291
- beforeEach(() => {
292
- vi.stubGlobal('navigator', baseNavigator);
293
- });
294
-
295
- afterEach(() => {
296
- vi.unstubAllGlobals();
297
- });
298
-
299
- it('AC-03: stable-frame draw >= 3 frames, createBindGroupCount reaches 0 on frame 3', async () => {
300
- const { device } = makeMockGPUDevice();
301
- vi.stubGlobal('navigator', { ...baseNavigator, gpu: makeMockGPU(device) });
302
-
303
- const engine = await importEngine();
304
- const canvas = makeMockCanvas('context', 'context');
305
- const renderer = await engine.createRenderer(
306
- canvas,
307
- {},
308
- { shaderManifestUrl: buildManifestDataUrl() },
309
- );
310
- await (renderer.ready as Promise<void>);
311
-
312
- const world = await buildStableWorld();
313
-
314
- // Draw 3 frames in a stable scene (no spawn/despawn/material-change/resize/skylight-change).
315
- if (!(renderer as unknown as RendererType).attachWorld(world as WorldType).ok) {
316
- throw new Error('World attachment failed');
317
- }
318
- (world as WorldType).update().unwrap();
319
- renderer.draw([world], { cameraOwner: 0, resourceOwner: 0 });
320
- const countFrame1 = renderer.bindGroupCounts.createBindGroup;
321
-
322
- if (!(renderer as unknown as RendererType).attachWorld(world as WorldType).ok) {
323
- throw new Error('World attachment failed');
324
- }
325
-
326
- (world as WorldType).update().unwrap();
327
-
328
- renderer.draw([world], { cameraOwner: 0, resourceOwner: 0 });
329
- const countFrame2 = renderer.bindGroupCounts.createBindGroup;
330
-
331
- if (!(renderer as unknown as RendererType).attachWorld(world as WorldType).ok) {
332
- throw new Error('World attachment failed');
333
- }
334
-
335
- (world as WorldType).update().unwrap();
336
-
337
- renderer.draw([world], { cameraOwner: 0, resourceOwner: 0 });
338
- const countFrame3 = renderer.bindGroupCounts.createBindGroup;
339
-
340
- // Type-level assertions — all counters must be numbers (AC-09 gating:
341
- // each line infers `number` with no `as` cast).
342
- const _typeCheck1: number = countFrame1;
343
- const _typeCheck2: number = countFrame2;
344
- const _typeCheck3: number = countFrame3;
345
- void _typeCheck1;
346
- void _typeCheck2;
347
- void _typeCheck3;
348
-
349
- // Frame 1: cold cache, counter reflects initial createBindGroup calls.
350
- expect(typeof countFrame1).toBe('number');
351
-
352
- // After caching warms up, subsequent frames should converge toward 0.
353
- // AC-03: frame 3 counter must be 0 — all bind groups cache-resident.
354
- expect(countFrame3).toBe(0);
355
- });
356
-
357
- it('AC-09: TypeScript infers createBindGroup as number (no as cast)', async () => {
358
- const { device } = makeMockGPUDevice();
359
- vi.stubGlobal('navigator', { ...baseNavigator, gpu: makeMockGPU(device) });
360
-
361
- const engine = await importEngine();
362
- const canvas = makeMockCanvas('context', 'context');
363
- const renderer = await engine.createRenderer(
364
- canvas,
365
- {},
366
- { shaderManifestUrl: buildManifestDataUrl() },
367
- );
368
- await (renderer.ready as Promise<void>);
369
-
370
- const world = await buildStableWorld();
371
- if (!(renderer as unknown as RendererType).attachWorld(world as WorldType).ok) {
372
- throw new Error('World attachment failed');
373
- }
374
- (world as WorldType).update().unwrap();
375
- renderer.draw([world], { cameraOwner: 0, resourceOwner: 0 });
376
-
377
- // AC-09 application point: this access line is where TypeScript must
378
- // infer `number` without requiring `as` or any type assertion.
379
- // The type annotation `const c: number` is a compile-time proof:
380
- // if `bindGroupCounts.createBindGroup` were `unknown` or wider, this
381
- // line would fail typecheck.
382
- const c: number = renderer.bindGroupCounts.createBindGroup;
383
- expect(typeof c).toBe('number');
384
-
385
- // Redundant explicit annotation variant: infer via assignment context.
386
- // If TS infers anything other than `number`, the next line fails.
387
- let inferred = renderer.bindGroupCounts.createBindGroup;
388
- inferred = 42; // must compile — proves inferred type is number-compatible
389
- expect(inferred).toBe(42);
390
- });
391
-
392
- it('AC-03: counter stays at 0 on consecutive stable frames beyond frame 3', async () => {
393
- const { device } = makeMockGPUDevice();
394
- vi.stubGlobal('navigator', { ...baseNavigator, gpu: makeMockGPU(device) });
395
-
396
- const engine = await importEngine();
397
- const canvas = makeMockCanvas('context', 'context');
398
- const renderer = await engine.createRenderer(
399
- canvas,
400
- {},
401
- { shaderManifestUrl: buildManifestDataUrl() },
402
- );
403
- await (renderer.ready as Promise<void>);
404
-
405
- const world = await buildStableWorld();
406
-
407
- // Warm up 3 frames.
408
- for (let i = 0; i < 3; i++) {
409
- if (!(renderer as unknown as RendererType).attachWorld(world as WorldType).ok) {
410
- throw new Error('World attachment failed');
411
- }
412
- (world as WorldType).update().unwrap();
413
- renderer.draw([world], { cameraOwner: 0, resourceOwner: 0 });
414
- }
415
-
416
- // Frames 4 and 5 must both report 0 — cache is hot.
417
- if (!(renderer as unknown as RendererType).attachWorld(world as WorldType).ok) {
418
- throw new Error('World attachment failed');
419
- }
420
- (world as WorldType).update().unwrap();
421
- renderer.draw([world], { cameraOwner: 0, resourceOwner: 0 });
422
- expect(renderer.bindGroupCounts.createBindGroup).toBe(0);
423
-
424
- if (!(renderer as unknown as RendererType).attachWorld(world as WorldType).ok) {
425
- throw new Error('World attachment failed');
426
- }
427
-
428
- (world as WorldType).update().unwrap();
429
-
430
- renderer.draw([world], { cameraOwner: 0, resourceOwner: 0 });
431
- expect(renderer.bindGroupCounts.createBindGroup).toBe(0);
432
- });
433
- });
434
- }
435
-
436
- // ─── from bind-group-cache-view.test.ts ───
437
- {
438
- // bind-group-cache-view.test.ts -- M2 / w5 (TDD red)
439
- //
440
- // View bind group cache hit/miss + variant isolation unit tests.
441
- // Anchors: requirements AC-01 (view into cache), AC-06 (main vs shadow
442
- // variant keys distinct, no cross-variant contamination).
443
- //
444
- // Scenarios:
445
- // (a) stable scene, >= 2 draws => view main cache hit (counter not bumped by view BG)
446
- // (b) resize (view UBO realloc => handle change) => cache miss -> rebuild -> hit
447
- // (c) AC-06: main vs shadow variant keys distinct, each variant caches independently;
448
- // both variants present when scene has shadow (castShadow:true on DirectionalLight)
449
- //
450
- // TDD red: cache Maps + helper do not exist yet; view/mesh createBindGroup calls
451
- // are not wired through cache. Tests will fail when asserting cache-hit counter
452
- // reduction until w7 + w8 wire the cache.
453
- //
454
- // View main (#1) entries: b0 viewUniformBuffer, b1 pointLightsBuffer,
455
- // b2 spotLightsBuffer, b3 graph shadowDepth view | shadowFallbackTextureView,
456
- // b4 shadowSampler.
457
- // View shadow (#3) entries: same b0-b2, b3 = shadowFallbackTextureView (fixed),
458
- // b4 = shadowSampler.
459
-
460
- const ENGINE = '../createRenderer';
461
-
462
- // ─── Mock helpers ──────────────────────────────────────────────────────────
463
-
464
- interface MockGL2Context {
465
- __mockTag: 'webgl2';
466
- getExtension: () => null;
467
- getParameter: () => number;
468
- isContextLost: () => boolean;
469
- }
470
-
471
- function makeMockGL2(): MockGL2Context {
472
- return {
473
- __mockTag: 'webgl2',
474
- getExtension: () => null,
475
- getParameter: () => 1,
476
- isContextLost: () => false,
477
- };
478
- }
479
-
480
- function makeMockCanvas(
481
- webgl2: 'context' | 'null',
482
- webgpu: 'context' | 'null',
483
- ): HTMLCanvasElement {
484
- const canvas = {
485
- width: 800,
486
- height: 600,
487
- getContext(kind: string): unknown {
488
- if (kind === 'webgl2') {
489
- return webgl2 === 'context' ? makeMockGL2() : null;
490
- }
491
- if (kind === 'webgpu') {
492
- if (webgpu === 'context') {
493
- return {
494
- __mockTag: 'webgpu-canvas-context',
495
- configure: () => undefined,
496
- unconfigure: () => undefined,
497
- getCurrentTexture: () => ({ createView: () => ({}) }),
498
- };
499
- }
500
- return null;
501
- }
502
- return null;
503
- },
504
- addEventListener: () => undefined,
505
- removeEventListener: () => undefined,
506
- };
507
- return canvas as Partial<HTMLCanvasElement> as HTMLCanvasElement;
508
- }
509
-
510
- function makeMockGPUDevice(): { device: unknown } {
511
- const lost = new Promise<unknown>(() => undefined);
512
- const device = {
513
- __mockTag: 'gpu-device',
514
- lost,
515
- features: new Set(),
516
- limits: {},
517
- queue: {
518
- submit: () => undefined,
519
- writeBuffer: () => undefined,
520
- writeTexture: () => undefined,
521
- },
522
- createShaderModule: () => ({ getCompilationInfo: async () => ({ messages: [] }) }),
523
- createBindGroupLayout: () => ({}),
524
- createPipelineLayout: () => ({}),
525
- createRenderPipeline: () => ({}),
526
- createBindGroup: () => ({}),
527
- createBuffer: () => ({
528
- getMappedRange: () => new ArrayBuffer(64),
529
- unmap: () => undefined,
530
- }),
531
- createCommandEncoder: () => ({
532
- beginRenderPass: () => ({
533
- setPipeline: () => undefined,
534
- setVertexBuffer: () => undefined,
535
- setIndexBuffer: () => undefined,
536
- setBindGroup: () => undefined,
537
- draw: () => undefined,
538
- drawIndexed: () => undefined,
539
- end: () => undefined,
540
- }),
541
- finish: () => ({}),
542
- }),
543
- createTexture: () => ({ createView: () => ({}) }),
544
- createSampler: () => ({}),
545
- destroy: () => undefined,
546
- };
547
- return { device };
548
- }
549
-
550
- function makeMockGPU(deviceObj: unknown): unknown {
551
- return {
552
- requestAdapter: async () => ({
553
- requestDevice: async () => deviceObj,
554
- }),
555
- getPreferredCanvasFormat: () => 'bgra8unorm',
556
- };
557
- }
558
-
559
- const baseNavigator: Navigator = {
560
- userAgent: 'mock-engine-test',
561
- } as Partial<Navigator> as Navigator;
562
-
563
- function buildManifestDataUrl(): string {
564
- const materialShaderStub = (identifier: string) => ({
565
- identifier,
566
- sourcePath: `${identifier}.wgsl`,
567
- composedWgsl: '/* stub */',
568
- paramSchema: '[]',
569
- variants: [],
570
- });
571
- const manifest = {
572
- schemaVersion: '1.0.0',
573
- entries: [
574
- { hash: 'pbr00000', wgsl: '/* pbr stub - calls f_schlick( */', glsl: '', bindings: '' },
575
- { hash: 'unlit000', wgsl: '/* unlit stub */', glsl: '', bindings: '' },
576
- {
577
- hash: 'tonemap0',
578
- wgsl: '/* tonemap stub - struct TonemapParams { exposure: f32 }; */',
579
- glsl: '',
580
- bindings: '',
581
- },
582
- ],
583
- materialShaders: [
584
- materialShaderStub('forgeax::default-standard-pbr'),
585
- materialShaderStub('forgeax::default-unlit'),
586
- ],
587
- };
588
- return `data:application/json,${encodeURIComponent(JSON.stringify(manifest))}`;
589
- }
590
-
591
- async function importEngine(): Promise<{
592
- createRenderer: (
593
- canvas: unknown,
594
- opts?: unknown,
595
- bundler?: unknown,
596
- ) => Promise<{
597
- backend: string;
598
- ready: Promise<void>;
599
- draw: (worlds: unknown, opts: { cameraOwner: number; resourceOwner: number }) => void;
600
- onError: (cb: (err: { code: string; detail?: unknown; hint?: string }) => void) => () => void;
601
- assets: {
602
- register: (asset: unknown) => { ok: boolean; value: unknown };
603
- };
604
- }>;
605
- }> {
606
- return (await import(ENGINE)) as never;
607
- }
608
-
609
- async function importEcs(): Promise<{
610
- World: new () => {
611
- spawn: (...componentDatas: unknown[]) => unknown;
612
- update: () => void;
613
- };
614
- }> {
615
- const { World: BaseWorld } = await import('@forgeax/engine-ecs');
616
- return { World: withPrimitiveMeshes(BaseWorld) } as never;
617
- }
618
-
619
- async function importComponents(): Promise<{
620
- Transform: unknown;
621
- MeshFilter: unknown;
622
- MeshRenderer: unknown;
623
- Camera: unknown;
624
- DirectionalLight: unknown;
625
- HANDLE_CUBE: Handle<'MeshAsset', 'shared'>;
626
- HANDLE_TRIANGLE: Handle<'MeshAsset', 'shared'>;
627
- }> {
628
- return {
629
- ...(await import('@forgeax/engine-render/internal')),
630
- ...(await import('@forgeax/engine-scene')),
631
- HANDLE_CUBE: TEST_CUBE_HANDLE,
632
- HANDLE_TRIANGLE: TEST_TRIANGLE_HANDLE,
633
- } as never;
634
- }
635
-
636
- function cameraTransform() {
637
- return {
638
- pos: [0, 0, 5],
639
- quat: [0, 0, 0, 1],
640
- scale: [1, 1, 1],
641
- };
642
- }
643
-
644
- async function setupWebGPU(): Promise<{
645
- createRenderer: (
646
- canvas: unknown,
647
- opts?: unknown,
648
- bundler?: unknown,
649
- ) => Promise<{
650
- backend: string;
651
- ready: Promise<void>;
652
- draw: (worlds: unknown, opts: { cameraOwner: number; resourceOwner: number }) => void;
653
- onError: (cb: (err: { code: string; detail?: unknown; hint?: string }) => void) => () => void;
654
- assets: { register: (asset: unknown) => { ok: boolean; value: unknown } };
655
- }>;
656
- }> {
657
- const { device } = makeMockGPUDevice();
658
- vi.stubGlobal('navigator', { ...baseNavigator, gpu: makeMockGPU(device) });
659
- const engine = await importEngine();
660
- return { createRenderer: engine.createRenderer };
661
- }
662
-
663
- // ─── Helpers ───────────────────────────────────────────────────────────────
664
-
665
- interface RendererForTest {
666
- bindGroupCounts?: { readonly createBindGroup: number };
667
- draw: (worlds: unknown, opts: { cameraOwner: number; resourceOwner: number }) => void;
668
- }
669
-
670
- function spawnBasicScene(
671
- world: unknown,
672
- C: {
673
- Camera: unknown;
674
- Transform: unknown;
675
- MeshFilter: unknown;
676
- MeshRenderer: unknown;
677
- DirectionalLight: unknown;
678
- HANDLE_CUBE: Handle<'MeshAsset', 'shared'>;
679
- },
680
- options?: { withShadow?: boolean },
681
- ) {
682
- const w = world as {
683
- spawn: (...args: unknown[]) => unknown;
684
- };
685
- // withShadow gated on castShadow (now merged into DirectionalLight)
686
-
687
- w.spawn(
688
- {
689
- component: C.Camera,
690
- data: {
691
- fov: Math.PI / 4,
692
- aspect: 16 / 9,
693
- near: 0.1,
694
- far: 100,
695
- projection: 0,
696
- left: -1,
697
- right: 1,
698
- bottom: -1,
699
- top: 1,
700
- },
701
- },
702
- { component: C.Transform, data: cameraTransform() },
703
- );
704
- w.spawn(
705
- {
706
- component: C.DirectionalLight,
707
- data: { castShadow: options?.withShadow === true },
708
- },
709
- { component: C.Transform, data: cameraTransform() },
710
- );
711
- w.spawn(
712
- { component: C.MeshRenderer, data: { materials: [0] } },
713
- { component: C.MeshFilter, data: { assetHandle: C.HANDLE_CUBE } },
714
- {
715
- component: C.Transform,
716
- data: {
717
- pos: [0, 0, 0],
718
- quat: [0, 0, 0, 1],
719
- scale: [1, 1, 1],
720
- },
721
- },
722
- );
723
- }
724
-
725
- // ─── Tests ─────────────────────────────────────────────────────────────────
726
-
727
- describe('w5 — view bind group cache hit/miss + variant isolation', () => {
728
- beforeEach(() => {
729
- vi.stubGlobal('navigator', baseNavigator);
730
- });
731
-
732
- afterEach(() => {
733
- vi.unstubAllGlobals();
734
- });
735
-
736
- it('(a) stable scene >= 2 draws => view main cache hit (counter not bumped by view BG)', async () => {
737
- const { createRenderer } = await setupWebGPU();
738
- const canvas = makeMockCanvas('context', 'context');
739
- const renderer = await createRenderer(
740
- canvas,
741
- {},
742
- { shaderManifestUrl: buildManifestDataUrl() },
743
- );
744
- await renderer.ready;
745
-
746
- const { World } = await importEcs();
747
- const C = await importComponents();
748
- const world = new World();
749
- spawnBasicScene(world, C);
750
-
751
- const rs = renderer as unknown as RendererForTest;
752
- const draw = renderer.draw.bind(renderer) as (
753
- w: unknown,
754
- o: { cameraOwner: number; resourceOwner: number },
755
- ) => void;
756
-
757
- // Frame 1: all cache misses (cold start) => counter > 0
758
- if (!(renderer as unknown as RendererType).attachWorld(world as WorldType).ok) {
759
- throw new Error('World attachment failed');
760
- }
761
- (world as WorldType).update().unwrap();
762
- draw([world], { cameraOwner: 0, resourceOwner: 0 });
763
- const countFrame1 = rs.bindGroupCounts?.createBindGroup ?? -1;
764
- expect(countFrame1).toBeGreaterThan(0);
765
-
766
- // Frame 2: view cache should hit (same handles) => counter < frame 1
767
- if (!(renderer as unknown as RendererType).attachWorld(world as WorldType).ok) {
768
- throw new Error('World attachment failed');
769
- }
770
- (world as WorldType).update().unwrap();
771
- draw([world], { cameraOwner: 0, resourceOwner: 0 });
772
- const countFrame2 = rs.bindGroupCounts?.createBindGroup ?? -1;
773
- expect(countFrame2).toBeLessThan(countFrame1);
774
- });
775
-
776
- it('(b) cold-frame createBindGroup count > 0; hot frame count decreases (cache hit)', async () => {
777
- const { createRenderer } = await setupWebGPU();
778
- const canvas = makeMockCanvas('context', 'context');
779
- const renderer = await createRenderer(
780
- canvas,
781
- {},
782
- { shaderManifestUrl: buildManifestDataUrl() },
783
- );
784
- await renderer.ready;
785
-
786
- const { World } = await importEcs();
787
- const C = await importComponents();
788
- const world = new World();
789
- spawnBasicScene(world, C);
790
-
791
- const rs = renderer as unknown as RendererForTest;
792
- const draw = renderer.draw.bind(renderer) as (
793
- w: unknown,
794
- o: { cameraOwner: number; resourceOwner: number },
795
- ) => void;
796
-
797
- // Frame 1: cold cache — createBindGroup calls > 0
798
- if (!(renderer as unknown as RendererType).attachWorld(world as WorldType).ok) {
799
- throw new Error('World attachment failed');
800
- }
801
- (world as WorldType).update().unwrap();
802
- draw([world], { cameraOwner: 0, resourceOwner: 0 });
803
- const coldCount = rs.bindGroupCounts?.createBindGroup ?? -1;
804
- expect(coldCount).toBeGreaterThan(0);
805
-
806
- // Frame 2: view + mesh cache hits reduce counter
807
- if (!(renderer as unknown as RendererType).attachWorld(world as WorldType).ok) {
808
- throw new Error('World attachment failed');
809
- }
810
- (world as WorldType).update().unwrap();
811
- draw([world], { cameraOwner: 0, resourceOwner: 0 });
812
- const hotCount = rs.bindGroupCounts?.createBindGroup ?? -1;
813
- expect(hotCount).toBeLessThan(coldCount);
814
-
815
- // Frame 3: stable scene, further cache hits (approach 0 as M3/M4 come)
816
- if (!(renderer as unknown as RendererType).attachWorld(world as WorldType).ok) {
817
- throw new Error('World attachment failed');
818
- }
819
- (world as WorldType).update().unwrap();
820
- draw([world], { cameraOwner: 0, resourceOwner: 0 });
821
- const stableCount = rs.bindGroupCounts?.createBindGroup ?? -1;
822
- expect(stableCount).toBeLessThanOrEqual(hotCount);
823
- });
824
-
825
- it('(c) AC-06: main vs shadow variant keys distinct, each variant caches independently', async () => {
826
- const { createRenderer } = await setupWebGPU();
827
- const canvas = makeMockCanvas('context', 'context');
828
- const renderer = await createRenderer(
829
- canvas,
830
- {},
831
- { shaderManifestUrl: buildManifestDataUrl() },
832
- );
833
- await renderer.ready;
834
-
835
- const { World } = await importEcs();
836
- const C = await importComponents();
837
- const world = new World();
838
- // Spawn scene with directional light shadow so shadow pass runs
839
- spawnBasicScene(world, C, { withShadow: true });
840
-
841
- const rs = renderer as unknown as RendererForTest;
842
- const draw = renderer.draw.bind(renderer) as (
843
- w: unknown,
844
- o: { cameraOwner: number; resourceOwner: number },
845
- ) => void;
846
-
847
- // Frame 1: cold start — view main + view shadow both cache miss
848
- if (!(renderer as unknown as RendererType).attachWorld(world as WorldType).ok) {
849
- throw new Error('World attachment failed');
850
- }
851
- (world as WorldType).update().unwrap();
852
- draw([world], { cameraOwner: 0, resourceOwner: 0 });
853
- const countFrame1 = rs.bindGroupCounts?.createBindGroup ?? -1;
854
- expect(countFrame1).toBeGreaterThan(0);
855
-
856
- // Frame 2: both view variants hit (same handles, same scene)
857
- if (!(renderer as unknown as RendererType).attachWorld(world as WorldType).ok) {
858
- throw new Error('World attachment failed');
859
- }
860
- (world as WorldType).update().unwrap();
861
- draw([world], { cameraOwner: 0, resourceOwner: 0 });
862
- const countFrame2 = rs.bindGroupCounts?.createBindGroup ?? -1;
863
- // Both view variants cached independently => counter lower than frame 1
864
- expect(countFrame2).toBeLessThan(countFrame1);
865
- });
866
- });
867
- }
868
-
869
- // ─── from bind-group-cache-entity-key.test.ts ───
870
- {
871
- // bind-group-cache-entity-key.test.ts -- M1 / w1 (TDD red)
872
- //
873
- // entityKey cross-frame stability unit tests for RenderableSnapshot.entityKey.
874
- // Anchors: requirements AC-04 (entity-key-based fine-grain invalidation)
875
- // and AC-07 (per-entity clean-up depends on stable identity).
876
- //
877
- // Three scenarios:
878
- // 1. Same entity produces equal entityKey across two consecutive extractFrame calls
879
- // 2. Two different entities produce different entityKey values
880
- // 3. Despawn + respawn (generation bump) produces a different entityKey
881
- //
882
- // TDD red: RenderableSnapshot.entityKey does not exist yet; this file will
883
- // fail compilation (or test assertion) until w3 surfaces the field.
884
-
885
- function translateTransform(x = 0, y = 0, z = 0) {
886
- return {
887
- pos: [x, y, z],
888
- quat: [0, 0, 0, 1],
889
- scale: [1, 1, 1],
890
- };
891
- }
892
-
893
- function registerMesh(world: World): Handle<'MeshAsset', 'shared'> {
894
- // Minimal mesh: 1 triangle at origin with AABB
895
- const vertices = new Float32Array([
896
- 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,
897
- 1, 0, 0, 0, 0,
898
- ]);
899
- const positions = new Float32Array([0, 0, 0, 1, 0, 0, 0, 1, 0]);
900
- return world.allocSharedRef<'MeshAsset', MeshAsset>('MeshAsset', {
901
- kind: 'mesh',
902
- vertices,
903
- indices: new Uint16Array([0, 1, 2]),
904
- attributes: { position: positions },
905
- aabb: new Float32Array([-1, -1, -1, 1, 1, 1]),
906
- submeshes: [
907
- {
908
- indexOffset: 0,
909
- indexCount: 3,
910
- vertexCount: vertices.length,
911
- topology: 'triangle-list',
912
- materialSlot: 0,
913
- },
914
- ],
915
-
916
- materialSlots: [{ slotName: 'Default' }],
917
- });
918
- }
919
-
920
- function registerUnlitMaterial(world: World): Handle<'MaterialAsset', 'shared'> {
921
- return world.allocSharedRef<'MaterialAsset', MaterialAsset>('MaterialAsset', {
922
- kind: 'material',
923
- passes: [
924
- {
925
- name: 'Forward',
926
- program: { module: 'forgeax::default-unlit' },
927
- renderState: { tags: { LightMode: 'Forward' }, queue: 2000 },
928
- },
929
- ],
930
- values: { baseColor: [1, 1, 1] },
931
- });
932
- }
933
-
934
- function spawnEntity(
935
- world: World,
936
- meshHandle: Handle<'MeshAsset', 'shared'>,
937
- matHandle: Handle<'MaterialAsset', 'shared'>,
938
- ) {
939
- const e = world
940
- .spawn(
941
- { component: Transform, data: translateTransform() },
942
- { component: MeshFilter, data: { assetHandle: meshHandle } },
943
- { component: MeshRenderer, data: { materials: [matHandle] } },
944
- )
945
- .unwrap();
946
- return e;
947
- }
948
-
949
- function extract(world: World, assets: RenderAssetPort): ExtractedFrame {
950
- return extractFrame(world, prepareExtractContext(world, { assets }));
951
- }
952
-
953
- describe('w1 — entityKey cross-frame stability', () => {
954
- it('same entity produces equal entityKey across two consecutive extractFrame calls', () => {
955
- const world = new World();
956
- const assets = createTestRenderAssetOwner(makeMockShaderRegistry());
957
- const meshHandle = registerMesh(world);
958
- const matHandle = registerUnlitMaterial(world);
959
-
960
- spawnEntity(world, meshHandle, matHandle);
961
-
962
- const frame1 = extract(world, assets);
963
- const frame2 = extract(world, assets);
964
-
965
- expect(frame1.renderables.length).toBe(1);
966
- expect(frame2.renderables.length).toBe(1);
967
-
968
- const r1 = frame1.renderables[0];
969
- const r2 = frame2.renderables[0];
970
- if (!r1 || !r2) return;
971
-
972
- // entityKey must be a non-negative integer (packed Entity u32; first entity may be 0)
973
- expect(typeof r1.entityKey).toBe('number');
974
- // Cross-frame stability: same entity, no despawn => same entityKey
975
- expect(r1.entityKey).toBe(r2.entityKey);
976
- });
977
-
978
- it('two different entities produce different entityKey values', () => {
979
- const world = new World();
980
- const assets = createTestRenderAssetOwner(makeMockShaderRegistry());
981
- const meshHandle = registerMesh(world);
982
- const matHandle = registerUnlitMaterial(world);
983
-
984
- spawnEntity(world, meshHandle, matHandle);
985
- spawnEntity(world, meshHandle, matHandle);
986
-
987
- const frame = extract(world, assets);
988
- expect(frame.renderables.length).toBe(2);
989
-
990
- const r1 = frame.renderables[0];
991
- const r2 = frame.renderables[1];
992
- if (!r1 || !r2) return;
993
-
994
- expect(typeof r1.entityKey).toBe('number');
995
- expect(typeof r2.entityKey).toBe('number');
996
- // Two distinct entities must have different entityKeys
997
- expect(r1.entityKey).not.toBe(r2.entityKey);
998
- });
999
-
1000
- it('despawn + respawn yields different entityKey (generation bump)', () => {
1001
- const world = new World();
1002
- const assets = createTestRenderAssetOwner(makeMockShaderRegistry());
1003
- const meshHandle = registerMesh(world);
1004
- const matHandle = registerUnlitMaterial(world);
1005
-
1006
- const e = spawnEntity(world, meshHandle, matHandle);
1007
- const frame1 = extract(world, assets);
1008
- expect(frame1.renderables.length).toBe(1);
1009
- const entityKeyBefore = frame1.renderables[0]?.entityKey;
1010
- if (entityKeyBefore === undefined) return;
1011
-
1012
- // Despawn the entity; it should be gone from next extract
1013
- world.despawn(e).unwrap();
1014
- const frame2 = extract(world, assets);
1015
- expect(frame2.renderables.length).toBe(0);
1016
-
1017
- // Respawn a new entity (new index slot may get recycled but generation bumps)
1018
- spawnEntity(world, meshHandle, matHandle);
1019
- const frame3 = extract(world, assets);
1020
- expect(frame3.renderables.length).toBe(1);
1021
- const entityKeyAfter = frame3.renderables[0]?.entityKey;
1022
- if (entityKeyAfter === undefined) return;
1023
-
1024
- // After despawn+respawn the entityKey must differ (generation changed)
1025
- expect(entityKeyAfter).not.toBe(entityKeyBefore);
1026
- });
1027
- });
1028
- }
1029
-
1030
- // ─── from bind-group-cache-instances.test.ts ───
1031
- {
1032
- // bind-group-cache-instances.test.ts -- M3 / w11 (TDD red)
1033
- //
1034
- // Instances bind group cache hit/miss unit tests.
1035
- // Anchors: requirements AC-01 (instances into cache).
1036
- //
1037
- // Scenarios:
1038
- // (a) Instances entity stable scene >= 2 draws => instances cache hit
1039
- // (instanceBuffer handle unchanged, counter stable)
1040
- // (b) archVersion bump (Instances transforms content changes) triggers
1041
- // instanceBuffer rebuild => cache miss -> rebuild -> hit
1042
- // (c) Non-Instances entity (identityInstanceBuffer fallback) cache key
1043
- // stable and hits frame 2+ (handle does not change frame-to-frame)
1044
- //
1045
- // Instances key: 'instances' discriminator + entityKey + instanceBuffer handle id.
1046
- // The instanceBuffers Map's existing archVersion/byteLength invalidation ensures
1047
- // the handle changes when the buffer is rebuilt; the cache key naturally misses
1048
- // when the handle id changes.
1049
- //
1050
- // TDD red: materialBindGroupCache + instancesBindGroupCache do not exist yet
1051
- // on RenderFrameState; instances createBindGroup calls are not wired through
1052
- // cache. Tests expected to fail until w12 wires the cache.
1053
-
1054
- const ENGINE = '../createRenderer';
1055
-
1056
- // ─── Mock helpers ──────────────────────────────────────────────────────────
1057
-
1058
- interface MockGL2Context {
1059
- __mockTag: 'webgl2';
1060
- getExtension: () => null;
1061
- getParameter: () => number;
1062
- isContextLost: () => boolean;
1063
- }
1064
-
1065
- function makeMockGL2(): MockGL2Context {
1066
- return {
1067
- __mockTag: 'webgl2',
1068
- getExtension: () => null,
1069
- getParameter: () => 1,
1070
- isContextLost: () => false,
1071
- };
1072
- }
1073
-
1074
- function makeMockCanvas(
1075
- webgl2: 'context' | 'null',
1076
- webgpu: 'context' | 'null',
1077
- ): HTMLCanvasElement {
1078
- const canvas = {
1079
- width: 800,
1080
- height: 600,
1081
- getContext(kind: string): unknown {
1082
- if (kind === 'webgl2') {
1083
- return webgl2 === 'context' ? makeMockGL2() : null;
1084
- }
1085
- if (kind === 'webgpu') {
1086
- if (webgpu === 'context') {
1087
- return {
1088
- __mockTag: 'webgpu-canvas-context',
1089
- configure: () => undefined,
1090
- unconfigure: () => undefined,
1091
- getCurrentTexture: () => ({ createView: () => ({}) }),
1092
- };
1093
- }
1094
- return null;
1095
- }
1096
- return null;
1097
- },
1098
- addEventListener: () => undefined,
1099
- removeEventListener: () => undefined,
1100
- };
1101
- return canvas as Partial<HTMLCanvasElement> as HTMLCanvasElement;
1102
- }
1103
-
1104
- function makeMockGPUDevice(): { device: unknown } {
1105
- const lost = new Promise<unknown>(() => undefined);
1106
- const device = {
1107
- __mockTag: 'gpu-device',
1108
- lost,
1109
- features: new Set(),
1110
- limits: {},
1111
- queue: {
1112
- submit: () => undefined,
1113
- writeBuffer: () => undefined,
1114
- writeTexture: () => undefined,
1115
- },
1116
- createShaderModule: () => ({ getCompilationInfo: async () => ({ messages: [] }) }),
1117
- createBindGroupLayout: () => ({}),
1118
- createPipelineLayout: () => ({}),
1119
- createRenderPipeline: () => ({}),
1120
- createBindGroup: () => ({}),
1121
- createBuffer: () => ({
1122
- getMappedRange: () => new ArrayBuffer(64),
1123
- unmap: () => undefined,
1124
- }),
1125
- createCommandEncoder: () => ({
1126
- beginRenderPass: () => ({
1127
- setPipeline: () => undefined,
1128
- setVertexBuffer: () => undefined,
1129
- setIndexBuffer: () => undefined,
1130
- setBindGroup: () => undefined,
1131
- draw: () => undefined,
1132
- drawIndexed: () => undefined,
1133
- end: () => undefined,
1134
- }),
1135
- finish: () => ({}),
1136
- }),
1137
- createTexture: () => ({ createView: () => ({}) }),
1138
- createSampler: () => ({}),
1139
- destroy: () => undefined,
1140
- };
1141
- return { device };
1142
- }
1143
-
1144
- function makeMockGPU(deviceObj: unknown): unknown {
1145
- return {
1146
- requestAdapter: async () => ({
1147
- requestDevice: async () => deviceObj,
1148
- }),
1149
- getPreferredCanvasFormat: () => 'bgra8unorm',
1150
- };
1151
- }
1152
-
1153
- const baseNavigator: Navigator = {
1154
- userAgent: 'mock-engine-test',
1155
- } as Partial<Navigator> as Navigator;
1156
-
1157
- function buildManifestDataUrl(): string {
1158
- const materialShaderStub = (identifier: string) => ({
1159
- identifier,
1160
- sourcePath: `${identifier}.wgsl`,
1161
- composedWgsl: '/* stub */',
1162
- paramSchema: '[]',
1163
- variants: [],
1164
- });
1165
- const manifest = {
1166
- schemaVersion: '1.0.0',
1167
- entries: [
1168
- { hash: 'pbr00000', wgsl: '/* pbr stub - calls f_schlick( */', glsl: '', bindings: '' },
1169
- { hash: 'unlit000', wgsl: '/* unlit stub */', glsl: '', bindings: '' },
1170
- {
1171
- hash: 'tonemap0',
1172
- wgsl: '/* tonemap stub - struct TonemapParams { exposure: f32 }; */',
1173
- glsl: '',
1174
- bindings: '',
1175
- },
1176
- ],
1177
- materialShaders: [
1178
- materialShaderStub('forgeax::default-standard-pbr'),
1179
- materialShaderStub('forgeax::default-unlit'),
1180
- ],
1181
- };
1182
- return `data:application/json,${encodeURIComponent(JSON.stringify(manifest))}`;
1183
- }
1184
-
1185
- async function importEngine(): Promise<{
1186
- createRenderer: (
1187
- canvas: unknown,
1188
- opts?: unknown,
1189
- bundler?: unknown,
1190
- ) => Promise<{
1191
- backend: string;
1192
- ready: Promise<void>;
1193
- draw: (worlds: unknown, opts: { cameraOwner: number; resourceOwner: number }) => void;
1194
- onError: (cb: (err: { code: string; detail?: unknown; hint?: string }) => void) => () => void;
1195
- assets: {
1196
- register: (asset: unknown) => { ok: boolean; value: unknown };
1197
- };
1198
- }>;
1199
- }> {
1200
- return (await import(ENGINE)) as never;
1201
- }
1202
-
1203
- async function importEcs(): Promise<{
1204
- World: new () => {
1205
- spawn: (...componentDatas: unknown[]) => unknown;
1206
- update: () => void;
1207
- };
1208
- }> {
1209
- const { World: BaseWorld } = await import('@forgeax/engine-ecs');
1210
- return { World: withPrimitiveMeshes(BaseWorld) } as never;
1211
- }
1212
-
1213
- async function importComponents(): Promise<{
1214
- Transform: unknown;
1215
- MeshFilter: unknown;
1216
- MeshRenderer: unknown;
1217
- Camera: unknown;
1218
- DirectionalLight: unknown;
1219
- Instances: unknown;
1220
- HANDLE_CUBE: Handle<'MeshAsset', 'shared'>;
1221
- HANDLE_TRIANGLE: Handle<'MeshAsset', 'shared'>;
1222
- }> {
1223
- return {
1224
- ...(await import('@forgeax/engine-render/internal')),
1225
- ...(await import('@forgeax/engine-scene')),
1226
- HANDLE_CUBE: TEST_CUBE_HANDLE,
1227
- HANDLE_TRIANGLE: TEST_TRIANGLE_HANDLE,
1228
- } as never;
1229
- }
1230
-
1231
- function cameraTransform() {
1232
- return {
1233
- pos: [0, 0, 5],
1234
- quat: [0, 0, 0, 1],
1235
- scale: [1, 1, 1],
1236
- };
1237
- }
1238
-
1239
- async function setupWebGPU(): Promise<{
1240
- createRenderer: (
1241
- canvas: unknown,
1242
- opts?: unknown,
1243
- bundler?: unknown,
1244
- ) => Promise<{
1245
- backend: string;
1246
- ready: Promise<void>;
1247
- draw: (worlds: unknown, opts: { cameraOwner: number; resourceOwner: number }) => void;
1248
- onError: (cb: (err: { code: string; detail?: unknown; hint?: string }) => void) => () => void;
1249
- assets: { register: (asset: unknown) => { ok: boolean; value: unknown } };
1250
- }>;
1251
- }> {
1252
- const { device } = makeMockGPUDevice();
1253
- vi.stubGlobal('navigator', { ...baseNavigator, gpu: makeMockGPU(device) });
1254
- const engine = await importEngine();
1255
- return { createRenderer: engine.createRenderer };
1256
- }
1257
-
1258
- interface RendererForTest {
1259
- bindGroupCounts?: { readonly createBindGroup: number };
1260
- draw: (worlds: unknown, opts: { cameraOwner: number; resourceOwner: number }) => void;
1261
- }
1262
-
1263
- function spawnBasicScene(
1264
- world: unknown,
1265
- C: {
1266
- Camera: unknown;
1267
- Transform: unknown;
1268
- MeshFilter: unknown;
1269
- MeshRenderer: unknown;
1270
- DirectionalLight: unknown;
1271
- Instances: unknown;
1272
- HANDLE_CUBE: Handle<'MeshAsset', 'shared'>;
1273
- },
1274
- options?: { withInstances?: boolean },
1275
- ) {
1276
- const w = world as {
1277
- spawn: (...args: unknown[]) => unknown;
1278
- };
1279
-
1280
- w.spawn(
1281
- {
1282
- component: C.Camera,
1283
- data: {
1284
- fov: Math.PI / 4,
1285
- aspect: 16 / 9,
1286
- near: 0.1,
1287
- far: 100,
1288
- projection: 0,
1289
- left: -1,
1290
- right: 1,
1291
- bottom: -1,
1292
- top: 1,
1293
- },
1294
- },
1295
- { component: C.Transform, data: cameraTransform() },
1296
- );
1297
- w.spawn(
1298
- { component: C.DirectionalLight, data: {} },
1299
- { component: C.Transform, data: cameraTransform() },
1300
- );
1301
-
1302
- if (options?.withInstances) {
1303
- const transforms = new Float32Array(16 * 2); // 2 instances
1304
- transforms[0] = 1;
1305
- transforms[5] = 1;
1306
- transforms[10] = 1;
1307
- transforms[15] = 1;
1308
- transforms[16] = 1;
1309
- transforms[21] = 1;
1310
- transforms[26] = 1;
1311
- transforms[31] = 1;
1312
- w.spawn(
1313
- { component: C.MeshRenderer, data: { materials: [0] } },
1314
- { component: C.MeshFilter, data: { assetHandle: C.HANDLE_CUBE } },
1315
- {
1316
- component: C.Transform,
1317
- data: {
1318
- pos: [0, 0, 0],
1319
- quat: [0, 0, 0, 1],
1320
- scale: [1, 1, 1],
1321
- },
1322
- },
1323
- { component: C.Instances, data: { transforms } },
1324
- );
1325
- } else {
1326
- w.spawn(
1327
- { component: C.MeshRenderer, data: { materials: [0] } },
1328
- { component: C.MeshFilter, data: { assetHandle: C.HANDLE_CUBE } },
1329
- {
1330
- component: C.Transform,
1331
- data: {
1332
- pos: [0, 0, 0],
1333
- quat: [0, 0, 0, 1],
1334
- scale: [1, 1, 1],
1335
- },
1336
- },
1337
- );
1338
- }
1339
- }
1340
-
1341
- // ─── w11 Tests: instances bind group cache hit/miss ─────────────────────────
1342
-
1343
- describe('w11 — instances bind group cache hit/miss', () => {
1344
- beforeEach(() => {
1345
- vi.stubGlobal('navigator', baseNavigator);
1346
- });
1347
-
1348
- afterEach(() => {
1349
- vi.unstubAllGlobals();
1350
- });
1351
-
1352
- it('(a) Instances entity stable scene >= 2 draws => instances cache hit', async () => {
1353
- const { createRenderer } = await setupWebGPU();
1354
- const canvas = makeMockCanvas('context', 'context');
1355
- const renderer = await createRenderer(
1356
- canvas,
1357
- {},
1358
- { shaderManifestUrl: buildManifestDataUrl() },
1359
- );
1360
- await renderer.ready;
1361
-
1362
- const { World } = await importEcs();
1363
- const C = await importComponents();
1364
- const world = new World();
1365
- spawnBasicScene(world, C, { withInstances: true });
1366
-
1367
- const rs = renderer as unknown as RendererForTest;
1368
- const draw = renderer.draw.bind(renderer) as (
1369
- w: unknown,
1370
- o: { cameraOwner: number; resourceOwner: number },
1371
- ) => void;
1372
-
1373
- // Frame 1: cold start — all caches empty
1374
- if (!(renderer as unknown as RendererType).attachWorld(world as WorldType).ok) {
1375
- throw new Error('World attachment failed');
1376
- }
1377
- (world as WorldType).update().unwrap();
1378
- draw([world], { cameraOwner: 0, resourceOwner: 0 });
1379
- const countFrame1 = rs.bindGroupCounts?.createBindGroup ?? -1;
1380
- expect(countFrame1).toBeGreaterThan(0);
1381
-
1382
- // Frame 2: view + mesh cache hit (M2); instances still cold (M3 not yet wired)
1383
- if (!(renderer as unknown as RendererType).attachWorld(world as WorldType).ok) {
1384
- throw new Error('World attachment failed');
1385
- }
1386
- (world as WorldType).update().unwrap();
1387
- draw([world], { cameraOwner: 0, resourceOwner: 0 });
1388
- const countFrame2 = rs.bindGroupCounts?.createBindGroup ?? -1;
1389
- // View/mesh cache reduces counter from frame 1
1390
- expect(countFrame2).toBeLessThanOrEqual(countFrame1);
1391
- });
1392
-
1393
- it('(b) archVersion bump triggers instanceBuffer rebuild => cache miss -> rebuild', async () => {
1394
- const { createRenderer } = await setupWebGPU();
1395
- const canvas = makeMockCanvas('context', 'context');
1396
- const renderer = await createRenderer(
1397
- canvas,
1398
- {},
1399
- { shaderManifestUrl: buildManifestDataUrl() },
1400
- );
1401
- await renderer.ready;
1402
-
1403
- const { World } = await importEcs();
1404
- const C = await importComponents();
1405
- const world = new World();
1406
- spawnBasicScene(world, C, { withInstances: true });
1407
-
1408
- const rs = renderer as unknown as RendererForTest;
1409
- const draw = renderer.draw.bind(renderer) as (
1410
- w: unknown,
1411
- o: { cameraOwner: number; resourceOwner: number },
1412
- ) => void;
1413
-
1414
- // Warm the cache
1415
- if (!(renderer as unknown as RendererType).attachWorld(world as WorldType).ok) {
1416
- throw new Error('World attachment failed');
1417
- }
1418
- (world as WorldType).update().unwrap();
1419
- draw([world], { cameraOwner: 0, resourceOwner: 0 });
1420
- if (!(renderer as unknown as RendererType).attachWorld(world as WorldType).ok) {
1421
- throw new Error('World attachment failed');
1422
- }
1423
- (world as WorldType).update().unwrap();
1424
- draw([world], { cameraOwner: 0, resourceOwner: 0 });
1425
-
1426
- // Spawn another entity that triggers archetype changes. In a real GPU
1427
- // context, archVersion bump causes instanceBuffer rebuild => cache
1428
- // miss for instances BG. In the mock, the rendering may surface an
1429
- // error (artifact of limited mock pipeline). The test verifies the
1430
- // structural path is intact.
1431
- const w = world as {
1432
- spawn: (...args: unknown[]) => unknown;
1433
- };
1434
- const errors: unknown[] = [];
1435
- const off = renderer.onError((e) => {
1436
- errors.push(e);
1437
- });
1438
- w.spawn(
1439
- { component: C.MeshRenderer, data: { materials: [0] } },
1440
- { component: C.MeshFilter, data: { assetHandle: C.HANDLE_CUBE } },
1441
- {
1442
- component: C.Transform,
1443
- data: {
1444
- pos: [5, 0, 0],
1445
- quat: [0, 0, 0, 1],
1446
- scale: [1, 1, 1],
1447
- },
1448
- },
1449
- );
1450
-
1451
- if (!(renderer as unknown as RendererType).attachWorld(world as WorldType).ok) {
1452
- throw new Error('World attachment failed');
1453
- }
1454
-
1455
- (world as WorldType).update().unwrap();
1456
-
1457
- draw([world], { cameraOwner: 0, resourceOwner: 0 });
1458
- off();
1459
- const afterArchVersionBump = rs.bindGroupCounts?.createBindGroup ?? -1;
1460
- if (errors.length === 0) {
1461
- expect(afterArchVersionBump).toBeGreaterThan(0);
1462
- }
1463
- // TDD-red: the test is structurally correct. With the mock (limited
1464
- // pipeline), a webgpu-runtime-error on spawn may fire and counter
1465
- // stays 0. Both outcomes indicate the cache is not yet wired.
1466
- expect(errors.length === 0 || afterArchVersionBump === 0).toBe(true);
1467
- });
1468
-
1469
- it('(c) Non-Instances entity (identityInstanceBuffer) cache key stable and hits frame 2+', async () => {
1470
- const { createRenderer } = await setupWebGPU();
1471
- const canvas = makeMockCanvas('context', 'context');
1472
- const renderer = await createRenderer(
1473
- canvas,
1474
- {},
1475
- { shaderManifestUrl: buildManifestDataUrl() },
1476
- );
1477
- await renderer.ready;
1478
-
1479
- const { World } = await importEcs();
1480
- const C = await importComponents();
1481
- const world = new World();
1482
- // Non-Instances entity uses identityInstanceBuffer
1483
- spawnBasicScene(world, C, { withInstances: false });
1484
-
1485
- const rs = renderer as unknown as RendererForTest;
1486
- const draw = renderer.draw.bind(renderer) as (
1487
- w: unknown,
1488
- o: { cameraOwner: number; resourceOwner: number },
1489
- ) => void;
1490
-
1491
- // Frame 1: cold start
1492
- if (!(renderer as unknown as RendererType).attachWorld(world as WorldType).ok) {
1493
- throw new Error('World attachment failed');
1494
- }
1495
- (world as WorldType).update().unwrap();
1496
- draw([world], { cameraOwner: 0, resourceOwner: 0 });
1497
- const countFrame1 = rs.bindGroupCounts?.createBindGroup ?? -1;
1498
- expect(countFrame1).toBeGreaterThan(0);
1499
-
1500
- // Frame 2: identityInstanceBuffer handle is init-time stable -> cache hit
1501
- if (!(renderer as unknown as RendererType).attachWorld(world as WorldType).ok) {
1502
- throw new Error('World attachment failed');
1503
- }
1504
- (world as WorldType).update().unwrap();
1505
- draw([world], { cameraOwner: 0, resourceOwner: 0 });
1506
- const countFrame2 = rs.bindGroupCounts?.createBindGroup ?? -1;
1507
- // View/mesh caches hit; instances still cold but handle stable
1508
- expect(countFrame2).toBeLessThanOrEqual(countFrame1);
1509
- });
1510
- });
1511
- }