@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,1206 +0,0 @@
1
- // feat-20260612-rhi-destroy-renderer-dispose-gpu-lifecycle / M2 / w6 —
2
- // GpuBuffer / GpuTexture .destroy() unit tests.
3
- //
4
- // Coverage matrix (requirements AC-04 + plan-strategy §5.3):
5
- // 1. GpuBuffer.destroy: first call returns ok; isDestroyed flips to true.
6
- // 2. GpuBuffer.destroy: second call routes the underlying RhiDevice
7
- // 'destroy-after-destroy' err verbatim (D-7 SSOT — RHI shim is the
8
- // lifecycle SSOT; the runtime wrapper forwards instead of duplicating
9
- // the bookkeeping; charter §F1).
10
- // 3. GpuTexture.destroy: same first / ok + isDestroyed=true.
11
- // 4. GpuTexture.destroy: same second / 'destroy-after-destroy' forward.
12
- //
13
- // Mock shape: a minimal RhiDevice stub with destroyBuffer / destroyTexture
14
- // implementing per-handle `destroyed: Set` bookkeeping that mirrors the
15
- // real shim's contract (rhi-webgpu device.ts lines 1404–1473). Plain
16
- // branded handles are fabricated via `as unknown as Buffer / Texture` —
17
- // the runtime wrapper never reads the brand; only the device methods
18
- // touch the handle (charter §F4 explicit failure: any future shape
19
- // change to handle bookkeeping flips the second-destroy branch).
20
-
21
- import { World } from '@forgeax/engine-ecs';
22
- import { deriveVertexLayoutProjection } from '@forgeax/engine-geometry';
23
- import { GpuBuffer, GpuResourceStore, GpuTexture } from '@forgeax/engine-render/internal';
24
- import type { Buffer, Result, RhiCaps, RhiDevice, Texture } from '@forgeax/engine-rhi';
25
- import { err, ok, RhiError } from '@forgeax/engine-rhi';
26
- import {
27
- type EquirectAsset,
28
- type Handle,
29
- type MeshAsset,
30
- type TextureAsset,
31
- toShared,
32
- unwrapHandle,
33
- } from '@forgeax/engine-types';
34
- import { describe, expect, it } from 'vitest';
35
-
36
- const TEST_LAYOUT_PROJECTION = deriveVertexLayoutProjection({
37
- position: new Float32Array(0),
38
- normal: new Float32Array(0),
39
- uv: new Float32Array(0),
40
- tangent: new Float32Array(0),
41
- });
42
-
43
- // ── Minimal RhiDevice stub (only destroyBuffer / destroyTexture wired) ──
44
-
45
- type MinimalDevice = Pick<RhiDevice, 'destroyBuffer' | 'destroyTexture'>;
46
-
47
- function makeMockDevice(): MinimalDevice {
48
- const destroyedBufs = new WeakSet<Buffer>();
49
- const destroyedTexs = new WeakSet<Texture>();
50
- return {
51
- destroyBuffer(buf: Buffer): Result<void, RhiError> {
52
- if (destroyedBufs.has(buf)) {
53
- return err(
54
- new RhiError({
55
- code: 'destroy-after-destroy',
56
- expected: 'GPU buffer handle has not been destroyed yet',
57
- hint: 'object already destroyed; track lifecycle in caller or check isDestroyed before re-destroy',
58
- }),
59
- );
60
- }
61
- destroyedBufs.add(buf);
62
- return ok(undefined);
63
- },
64
- destroyTexture(tex: Texture): Result<void, RhiError> {
65
- if (destroyedTexs.has(tex)) {
66
- return err(
67
- new RhiError({
68
- code: 'destroy-after-destroy',
69
- expected: 'GPU texture handle has not been destroyed yet',
70
- hint: 'object already destroyed; track lifecycle in caller or check isDestroyed before re-destroy',
71
- }),
72
- );
73
- }
74
- destroyedTexs.add(tex);
75
- return ok(undefined);
76
- },
77
- };
78
- }
79
-
80
- // Branded opaque handles; the runtime wrapper never inspects the brand,
81
- // only the mock device methods key off identity.
82
- function makeBufHandle(): Buffer {
83
- return {} as unknown as Buffer;
84
- }
85
- function makeTexHandle(): Texture {
86
- return {} as unknown as Texture;
87
- }
88
-
89
- describe('GpuBuffer (feat-20260612 M2 / w6)', () => {
90
- it('destroy: first call returns ok; isDestroyed flips to true', () => {
91
- const device = makeMockDevice();
92
- const handle = makeBufHandle();
93
- const gpuBuf = new GpuBuffer(device as unknown as RhiDevice, handle);
94
-
95
- expect(gpuBuf.isDestroyed).toBe(false);
96
-
97
- const r = gpuBuf.destroy();
98
- expect(r.ok).toBe(true);
99
- expect(gpuBuf.isDestroyed).toBe(true);
100
- });
101
-
102
- it("destroy: second call returns err 'destroy-after-destroy' (forwarded from RHI shim)", () => {
103
- const device = makeMockDevice();
104
- const handle = makeBufHandle();
105
- const gpuBuf = new GpuBuffer(device as unknown as RhiDevice, handle);
106
-
107
- const first = gpuBuf.destroy();
108
- expect(first.ok).toBe(true);
109
-
110
- const second = gpuBuf.destroy();
111
- expect(second.ok).toBe(false);
112
- if (!second.ok) {
113
- expect(second.error.code).toBe('destroy-after-destroy');
114
- }
115
- // isDestroyed stays true — the second call did not un-destroy it.
116
- expect(gpuBuf.isDestroyed).toBe(true);
117
- });
118
- });
119
-
120
- describe('GpuTexture (feat-20260612 M2 / w6)', () => {
121
- it('destroy: first call returns ok; isDestroyed flips to true', () => {
122
- const device = makeMockDevice();
123
- const handle = makeTexHandle();
124
- const gpuTex = new GpuTexture(device as unknown as RhiDevice, handle);
125
-
126
- expect(gpuTex.isDestroyed).toBe(false);
127
-
128
- const r = gpuTex.destroy();
129
- expect(r.ok).toBe(true);
130
- expect(gpuTex.isDestroyed).toBe(true);
131
- });
132
-
133
- it("destroy: second call returns err 'destroy-after-destroy' (forwarded from RHI shim)", () => {
134
- const device = makeMockDevice();
135
- const handle = makeTexHandle();
136
- const gpuTex = new GpuTexture(device as unknown as RhiDevice, handle);
137
-
138
- const first = gpuTex.destroy();
139
- expect(first.ok).toBe(true);
140
-
141
- const second = gpuTex.destroy();
142
- expect(second.ok).toBe(false);
143
- if (!second.ok) {
144
- expect(second.error.code).toBe('destroy-after-destroy');
145
- }
146
- expect(gpuTex.isDestroyed).toBe(true);
147
- });
148
- });
149
-
150
- // ── M-3 / w9 + w10: gpuStore narrowing + destroyAll unit tests ──
151
- //
152
- // Coverage matrix (requirements AC-05 + AC-06 / plan-strategy §7 M-3 / D-2 / D-9):
153
- // w9: type-level narrowing — getMeshGpuHandles(...).vertexBuffer / indexBuffer
154
- // is GpuBuffer (not raw RHI Buffer); getCubemapGpuTexture(...) is GpuTexture
155
- // (not raw Texture). Validated by `const _: GpuBuffer = entry.vertexBuffer`
156
- // in the test body — `pnpm typecheck` is the gate.
157
- // w10: gpuStore.destroyAll() walks the three handle maps + every GpuResource
158
- // isDestroyed flips to true; second call is idempotent (no error).
159
- //
160
- // The mock device extends the pipeline.unit.test.ts shape with destroyBuffer /
161
- // destroyTexture (M-1) so the GpuBuffer / GpuTexture wrappers can forward.
162
-
163
- interface DeviceProbe {
164
- buffers: number;
165
- textures: number;
166
- views: number;
167
- destroyedBuffers: number;
168
- destroyedTextures: number;
169
- }
170
-
171
- function freshProbe(): DeviceProbe {
172
- return { buffers: 0, textures: 0, views: 0, destroyedBuffers: 0, destroyedTextures: 0 };
173
- }
174
-
175
- const okShim = <T>(v: T) => ({ ok: true as const, value: v });
176
-
177
- // biome-ignore lint/suspicious/noExplicitAny: opaque mock GPU device surface
178
- function makeStoreMockDevice(probe: DeviceProbe, maxTextureDimension2D = 16_384): any {
179
- const destroyedBufs = new WeakSet<object>();
180
- const destroyedTexs = new WeakSet<object>();
181
- return {
182
- createShaderModule: () => okShim({ __mock: 'shader' }),
183
- createSampler: () => okShim({ __mock: 'sampler' }),
184
- createBindGroupLayout: () => okShim({ __mock: 'bgl' }),
185
- createPipelineLayout: () => okShim({ __mock: 'layout' }),
186
- createRenderPipeline: () => okShim({ __mock: 'pipeline' }),
187
- createBindGroup: () => okShim({ __mock: 'bindGroup' }),
188
- createBuffer: (desc: { size?: number }) => {
189
- probe.buffers += 1;
190
- return okShim({ __mock: `buffer-${probe.buffers}`, size: desc.size ?? 0 });
191
- },
192
- createTexture: () => {
193
- probe.textures += 1;
194
- return okShim({ __mock: `texture-${probe.textures}` });
195
- },
196
- createTextureView: () => {
197
- probe.views += 1;
198
- return okShim({ __mock: `view-${probe.views}` });
199
- },
200
- limits: { maxTextureDimension2D },
201
- destroyBuffer(buf: object): Result<void, RhiError> {
202
- if (destroyedBufs.has(buf)) {
203
- return err(
204
- new RhiError({
205
- code: 'destroy-after-destroy',
206
- expected: 'GPU buffer handle has not been destroyed yet',
207
- hint: 'object already destroyed; track lifecycle in caller',
208
- }),
209
- );
210
- }
211
- destroyedBufs.add(buf);
212
- probe.destroyedBuffers += 1;
213
- return ok(undefined);
214
- },
215
- destroyTexture(tex: object): Result<void, RhiError> {
216
- if (destroyedTexs.has(tex)) {
217
- return err(
218
- new RhiError({
219
- code: 'destroy-after-destroy',
220
- expected: 'GPU texture handle has not been destroyed yet',
221
- hint: 'object already destroyed; track lifecycle in caller',
222
- }),
223
- );
224
- }
225
- destroyedTexs.add(tex);
226
- probe.destroyedTextures += 1;
227
- return ok(undefined);
228
- },
229
- queue: {
230
- writeBuffer: () => okShim(undefined),
231
- writeTexture: () => okShim(undefined),
232
- submit: () => okShim(undefined),
233
- },
234
- };
235
- }
236
-
237
- const mockCaps: RhiCaps = {
238
- backendKind: 'webgpu',
239
- compute: true,
240
- timestampQuery: false,
241
- timestampPeriodNanoseconds: null,
242
- indirectDrawing: false,
243
- textureCompressionBc: false,
244
- textureCompressionEtc2: false,
245
- textureCompressionAstc: false,
246
- multiDrawIndirect: false,
247
- pushConstants: false,
248
- textureBindingArray: false,
249
- samplerAliasing: false,
250
- firstInstanceIndirect: false,
251
- storageBuffer: true,
252
- storageTexture: false,
253
- rgba16floatRenderable: true,
254
- rg11b10ufloatRenderable: false,
255
- float32Filterable: false,
256
- maxColorAttachments: 8,
257
- };
258
-
259
- function makeRegisterCube(): (
260
- pod: EquirectAsset,
261
- ) => Result<Handle<'EquirectAsset', 'shared'>, never> {
262
- let next = 1000;
263
- return () => ok(toShared<'EquirectAsset'>(next++));
264
- }
265
-
266
- // biome-ignore lint/suspicious/noExplicitAny: shader-module factory shim
267
- const shaderFactory = async (_d: any, desc: { code: string; label?: string }) =>
268
- ok({ __mock: 'shader', label: desc.label ?? '' }) as never;
269
-
270
- function configuredStore(probe: DeviceProbe, maxTextureDimension2D = 16_384): GpuResourceStore {
271
- const store = new GpuResourceStore();
272
- store.configureGpuDevice(
273
- makeStoreMockDevice(probe, maxTextureDimension2D),
274
- shaderFactory,
275
- makeRegisterCube() as never,
276
- mockCaps,
277
- );
278
- return store;
279
- }
280
-
281
- function meshPodFixture(): MeshAsset {
282
- const attributes: MeshAsset['attributes'] = {
283
- position: new Float32Array(4 * 3),
284
- normal: new Float32Array(4 * 3),
285
- uv: new Float32Array(4 * 2),
286
- tangent: new Float32Array(4 * 4),
287
- };
288
- return {
289
- kind: 'mesh',
290
- vertices: new Float32Array(4 * 12),
291
- indices: new Uint16Array([0, 1, 2, 0, 2, 3]),
292
- attributes,
293
- aabb: new Float32Array(6),
294
- submeshes: [
295
- {
296
- indexOffset: 0,
297
- indexCount: 6,
298
- vertexCount: 0,
299
- topology: 'triangle-list',
300
- materialSlot: 0,
301
- },
302
- ],
303
-
304
- materialSlots: [{ slotName: 'Default' }],
305
- };
306
- }
307
-
308
- function texturePodFixture(): TextureAsset {
309
- return {
310
- kind: 'texture',
311
- width: 2,
312
- height: 2,
313
- format: 'rgba8unorm-srgb',
314
- data: new Uint8Array(2 * 2 * 4).fill(188),
315
- colorSpace: 'srgb',
316
- mipmap: false,
317
- };
318
- }
319
-
320
- describe('GpuResourceStore handle map narrowing (feat-20260612 M3 / w9, AC-05)', () => {
321
- it('getMeshGpuHandles returns entry whose vertexBuffer / indexBuffer are GpuBuffer (no `as` cast)', () => {
322
- const probe = freshProbe();
323
- const store = configuredStore(probe);
324
- const handle = toShared<'MeshAsset'>(1024);
325
-
326
- const res = store.ensureResident(handle, meshPodFixture());
327
- expect(res.ok).toBe(true);
328
-
329
- const entry = store.getMeshGpuHandles(handle);
330
- expect(entry).toBeDefined();
331
- if (entry === undefined) return;
332
-
333
- // Type-level narrowing assertion (AC-05): TS must accept the assignment
334
- // without an `as` cast. If the field type were still raw `any` this would
335
- // also compile (any flows everywhere) -- the structural guarantee is the
336
- // runtime instanceof below; together they catch the migration regression.
337
- const _vbo: GpuBuffer = entry.vertexBuffer;
338
- expect(_vbo).toBeInstanceOf(GpuBuffer);
339
-
340
- // Mesh has indices in the fixture, so indexBuffer is non-null GpuBuffer.
341
- expect(entry.indexBuffer).not.toBeNull();
342
- if (entry.indexBuffer !== null) {
343
- const _ibo: GpuBuffer = entry.indexBuffer;
344
- expect(_ibo).toBeInstanceOf(GpuBuffer);
345
- }
346
- });
347
-
348
- it('getTextureGpuView returns the underlying view; texture entry holds a GpuTexture (M-3 wrapper)', () => {
349
- const probe = freshProbe();
350
- const store = configuredStore(probe);
351
- const handle = toShared<'TextureAsset'>(2048);
352
-
353
- const res = store.ensureResident(handle, texturePodFixture());
354
- expect(res.ok).toBe(true);
355
-
356
- // The view accessor stays decoupled (TextureView != GpuResource); the
357
- // narrowing applies to the texture-side entry, exercised via the public
358
- // destroyAll path in w10 (the texture field is private to the entry).
359
- expect(store.getTextureGpuView(handle)).toBeDefined();
360
- });
361
-
362
- it('rejects a texture over maxTextureDimension2D before creating a GPU handle', () => {
363
- const probe = freshProbe();
364
- const store = configuredStore(probe, 2048);
365
- const handle = toShared<'TextureAsset'>(2049);
366
-
367
- const res = store.ensureResident(handle, {
368
- ...texturePodFixture(),
369
- width: 2049,
370
- height: 1,
371
- data: new Uint8Array(2049 * 4),
372
- });
373
-
374
- expect(res.ok).toBe(false);
375
- if (res.ok) return;
376
- expect(res.error.code).toBe('image-dimension-out-of-bounds');
377
- const detail = res.error.detail;
378
- if (detail === undefined || !('requested' in detail) || !('limit' in detail)) return;
379
- expect(detail.requested).toEqual({ width: 2049, height: 1 });
380
- expect(detail.limit).toBe(2048);
381
- expect(probe.textures).toBe(0);
382
- });
383
- });
384
-
385
- describe('GpuResourceStore.destroyAll (feat-20260612 M3 / w10, AC-06 prereq)', () => {
386
- it('walks all 3 handle maps + every entry is destroyed after destroyAll()', async () => {
387
- const probe = freshProbe();
388
- const store = configuredStore(probe);
389
- const meshHandle = toShared<'MeshAsset'>(1024);
390
- const texHandle = toShared<'TextureAsset'>(2048);
391
-
392
- // Populate mesh + texture maps via ensureResident.
393
- expect(store.ensureResident(meshHandle, meshPodFixture()).ok).toBe(true);
394
- expect(store.ensureResident(texHandle, texturePodFixture()).ok).toBe(true);
395
-
396
- const meshEntry = store.getMeshGpuHandles(meshHandle);
397
- expect(meshEntry).toBeDefined();
398
- if (meshEntry === undefined) return;
399
- expect(meshEntry.vertexBuffer.isDestroyed).toBe(false);
400
- if (meshEntry.indexBuffer !== null) {
401
- expect(meshEntry.indexBuffer.isDestroyed).toBe(false);
402
- }
403
-
404
- // destroyedBuffers / destroyedTextures probe baseline.
405
- const baselineDestroyedBufs = probe.destroyedBuffers;
406
- const baselineDestroyedTexs = probe.destroyedTextures;
407
-
408
- store.destroyAll();
409
-
410
- // Mesh entry's GpuBuffer wrappers flipped to destroyed.
411
- expect(meshEntry.vertexBuffer.isDestroyed).toBe(true);
412
- if (meshEntry.indexBuffer !== null) {
413
- expect(meshEntry.indexBuffer.isDestroyed).toBe(true);
414
- }
415
-
416
- // RHI device.destroyBuffer fired for vbo + ibo (2); destroyTexture for the
417
- // texture (1). The cubemap map is empty in this fixture (no equirect
418
- // upload); a non-empty cubemap path is exercised below.
419
- expect(probe.destroyedBuffers - baselineDestroyedBufs).toBe(2);
420
- expect(probe.destroyedTextures - baselineDestroyedTexs).toBe(1);
421
-
422
- // After destroyAll() the maps are cleared so subsequent accessors miss.
423
- expect(store.getMeshGpuHandles(meshHandle)).toBeUndefined();
424
- expect(store.getTextureGpuView(texHandle)).toBeUndefined();
425
- });
426
-
427
- it('idempotent: second destroyAll() does nothing and does not throw', () => {
428
- const probe = freshProbe();
429
- const store = configuredStore(probe);
430
- const meshHandle = toShared<'MeshAsset'>(1024);
431
-
432
- expect(store.ensureResident(meshHandle, meshPodFixture()).ok).toBe(true);
433
-
434
- store.destroyAll();
435
- const afterFirst = probe.destroyedBuffers;
436
-
437
- // Second call: maps are already empty; no further RHI destroy fires.
438
- expect(() => store.destroyAll()).not.toThrow();
439
- expect(probe.destroyedBuffers).toBe(afterFirst);
440
- });
441
-
442
- it('destroyAll on an empty store is a no-op', () => {
443
- const probe = freshProbe();
444
- const store = configuredStore(probe);
445
-
446
- expect(() => store.destroyAll()).not.toThrow();
447
- expect(probe.destroyedBuffers).toBe(0);
448
- expect(probe.destroyedTextures).toBe(0);
449
- });
450
- });
451
-
452
- // ── M-4 / w14: instanceBuffers dispose-path cleanup unit test ──
453
- //
454
- // Coverage matrix (requirements §scope item 7 + plan-strategy §7 M-4 + D-7):
455
- // 1. disposeInstanceBuffers walks the Map, calls .destroy() on every
456
- // entry.buffer (a GpuBuffer wrapper), and clears the Map.
457
- // 2. Every wrapped GpuBuffer flips isDestroyed=true after the helper runs.
458
- // 3. The helper is idempotent: a second call on the cleared Map is a no-op.
459
- // 4. The helper on an empty Map is a safe no-op.
460
- //
461
- // Dispose path is independent of the per-frame Map.delete cleanup — the
462
- // per-frame path keeps its existing 'just delete the key' semantics
463
- // (plan-strategy D-7 / OOS-11).
464
-
465
- import { disposeInstanceBuffers, GpuBuffer as GpuBufferCls } from '@forgeax/engine-render/internal';
466
-
467
- describe('disposeInstanceBuffers (feat-20260612 M4 / w14)', () => {
468
- it('walks the Map and destroys every entry; clears the Map', () => {
469
- const device = makeMockDevice();
470
- const map = new Map<
471
- number,
472
- { buffer: GpuBufferCls; uploadedByteLength: number; uploadedArchVersion: number }
473
- >();
474
- const buf1 = new GpuBufferCls(device as unknown as RhiDevice, makeBufHandle());
475
- const buf2 = new GpuBufferCls(device as unknown as RhiDevice, makeBufHandle());
476
- map.set(1, { buffer: buf1, uploadedByteLength: 256, uploadedArchVersion: 1 });
477
- map.set(2, { buffer: buf2, uploadedByteLength: 512, uploadedArchVersion: 1 });
478
-
479
- expect(buf1.isDestroyed).toBe(false);
480
- expect(buf2.isDestroyed).toBe(false);
481
- expect(map.size).toBe(2);
482
-
483
- disposeInstanceBuffers(map);
484
-
485
- expect(buf1.isDestroyed).toBe(true);
486
- expect(buf2.isDestroyed).toBe(true);
487
- expect(map.size).toBe(0);
488
- });
489
-
490
- it('idempotent: a second dispose on the cleared Map is a no-op', () => {
491
- const device = makeMockDevice();
492
- const map = new Map<
493
- number,
494
- { buffer: GpuBufferCls; uploadedByteLength: number; uploadedArchVersion: number }
495
- >();
496
- const buf = new GpuBufferCls(device as unknown as RhiDevice, makeBufHandle());
497
- map.set(1, { buffer: buf, uploadedByteLength: 256, uploadedArchVersion: 1 });
498
-
499
- disposeInstanceBuffers(map);
500
- expect(map.size).toBe(0);
501
-
502
- expect(() => disposeInstanceBuffers(map)).not.toThrow();
503
- expect(map.size).toBe(0);
504
- });
505
-
506
- it('on an empty Map is a safe no-op', () => {
507
- const map = new Map<
508
- number,
509
- { buffer: GpuBufferCls; uploadedByteLength: number; uploadedArchVersion: number }
510
- >();
511
- expect(() => disposeInstanceBuffers(map)).not.toThrow();
512
- expect(map.size).toBe(0);
513
- });
514
- });
515
-
516
- // ── M1 feat-20260619-gpu-resource-ownership-symmetric-release-primitive ──
517
- //
518
- // w1: AC-01 evict primitives basic unit tests (red phase)
519
- // w2: AC-01 cubemap wrapper shared-dedup unit tests (red phase)
520
- // w3: AC-03 releaseUnreferenced idempotent unit tests (red phase)
521
- // w4: AC-04 aggregate failure unit tests (red phase)
522
-
523
- // ── Helpers for A-family evict tests ──
524
-
525
- function evictableTextureStore(): {
526
- store: GpuResourceStore;
527
- probe: DeviceProbe;
528
- handle: ReturnType<typeof toShared<'TextureAsset'>>;
529
- tex: GpuTexture;
530
- } {
531
- const probe = freshProbe();
532
- const store = configuredStore(probe);
533
- const handle = toShared<'TextureAsset'>(3001);
534
-
535
- const res = store.ensureResident(handle, texturePodFixture());
536
- expect(res.ok).toBe(true);
537
-
538
- const tex = store._getTextureGpuTexture(handle);
539
- expect(tex).toBeDefined();
540
- if (tex === undefined) throw new Error('unreachable');
541
- expect(tex.isDestroyed).toBe(false);
542
-
543
- return { store, probe, handle, tex };
544
- }
545
-
546
- function evictableMeshStore(): {
547
- store: GpuResourceStore;
548
- probe: DeviceProbe;
549
- handle: ReturnType<typeof toShared<'MeshAsset'>>;
550
- vbo: GpuBuffer;
551
- ibo: GpuBuffer;
552
- } {
553
- const probe = freshProbe();
554
- const store = configuredStore(probe);
555
- const handle = toShared<'MeshAsset'>(3002);
556
-
557
- const res = store.ensureResident(handle, meshPodFixture());
558
- expect(res.ok).toBe(true);
559
-
560
- const entry = store.getMeshGpuHandles(handle);
561
- expect(entry).toBeDefined();
562
- if (entry === undefined) throw new Error('unreachable');
563
- expect(entry.vertexBuffer.isDestroyed).toBe(false);
564
- expect(entry.indexBuffer).not.toBeNull();
565
- if (entry.indexBuffer === null) throw new Error('unreachable');
566
-
567
- return {
568
- store,
569
- probe,
570
- handle,
571
- vbo: entry.vertexBuffer,
572
- ibo: entry.indexBuffer,
573
- };
574
- }
575
-
576
- describe('evictTexture / evictMesh / evictCubemap (feat-20260619 M1 / w1)', () => {
577
- it('evictTexture: removes entry from Map + isDestroyed === true', () => {
578
- const { store, probe, handle, tex } = evictableTextureStore();
579
- const baselineDestroyed = probe.destroyedTextures;
580
-
581
- const r = store.evictTexture(handle);
582
- expect(r.freed).toBe(1);
583
- expect(r.errors).toEqual([]);
584
-
585
- expect(tex.isDestroyed).toBe(true);
586
- expect(store._getTextureGpuTexture(handle)).toBeUndefined();
587
- expect(store.getTextureGpuView(handle)).toBeUndefined();
588
- expect(probe.destroyedTextures - baselineDestroyed).toBe(1);
589
- });
590
-
591
- it('evictTexture: double evict is no-op (freed=0, no error)', () => {
592
- const { store, handle } = evictableTextureStore();
593
-
594
- const r1 = store.evictTexture(handle);
595
- expect(r1.freed).toBe(1);
596
-
597
- const r2 = store.evictTexture(handle);
598
- expect(r2.freed).toBe(0);
599
- expect(r2.errors).toEqual([]);
600
- });
601
-
602
- it('evictTexture: non-existent key is no-op', () => {
603
- const probe = freshProbe();
604
- const store = configuredStore(probe);
605
- const handle = toShared<'TextureAsset'>(9999);
606
-
607
- const r = store.evictTexture(handle);
608
- expect(r.freed).toBe(0);
609
- expect(r.errors).toEqual([]);
610
- });
611
-
612
- it('evictMesh: removes entry + both vbo and ibo destroyed', () => {
613
- const { store, probe, handle, vbo, ibo } = evictableMeshStore();
614
- const baselineDestroyed = probe.destroyedBuffers;
615
-
616
- const r = store.evictMesh(handle);
617
- expect(r.freed).toBe(1);
618
- expect(r.errors).toEqual([]);
619
-
620
- expect(vbo.isDestroyed).toBe(true);
621
- expect(ibo.isDestroyed).toBe(true);
622
- expect(store.getMeshGpuHandles(handle)).toBeUndefined();
623
- // vbo + ibo = 2 buffer destroys
624
- expect(probe.destroyedBuffers - baselineDestroyed).toBe(2);
625
- });
626
-
627
- it('evictMesh: double evict is no-op', () => {
628
- const { store, handle } = evictableMeshStore();
629
-
630
- const r1 = store.evictMesh(handle);
631
- expect(r1.freed).toBe(1);
632
-
633
- const r2 = store.evictMesh(handle);
634
- expect(r2.freed).toBe(0);
635
- expect(r2.errors).toEqual([]);
636
- });
637
-
638
- it('evictMesh: non-existent key is no-op', () => {
639
- const probe = freshProbe();
640
- const store = configuredStore(probe);
641
- const handle = toShared<'MeshAsset'>(9999);
642
-
643
- const r = store.evictMesh(handle);
644
- expect(r.freed).toBe(0);
645
- expect(r.errors).toEqual([]);
646
- });
647
-
648
- it('evictCubemap: non-existent key is no-op', () => {
649
- const probe = freshProbe();
650
- const store = configuredStore(probe);
651
-
652
- const r = store.evictCubemap(9999);
653
- expect(r.freed).toBe(0);
654
- expect(r.errors).toEqual([]);
655
- });
656
- });
657
-
658
- describe('evictCubemap wrapper shared-dedup (feat-20260619 M1 / w2)', () => {
659
- it('evictCubemap on sourceId destroys wrapper; cubeId entry remains but is destroyed', () => {
660
- const probe = freshProbe();
661
- const store = configuredStore(probe);
662
-
663
- // Construct a cubemap with two entries sharing one GpuTexture wrapper
664
- // (mirrors uploadCubemapFromEquirect's dual set at sourceId + cubeId).
665
- const srcHandle = toShared<'EquirectAsset'>(4001);
666
- const cubeHandle = toShared<'EquirectAsset'>(4002);
667
- const sourceId = unwrapHandle(srcHandle);
668
- const cubeId = unwrapHandle(cubeHandle);
669
-
670
- // Create a GpuTexture wrapper directly
671
- const device = makeMockDevice() as unknown as RhiDevice;
672
- const gpuTex = new GpuTexture(device, {} as unknown as Texture);
673
-
674
- // biome-ignore lint/suspicious/noExplicitAny: access private store maps for test setup
675
- const storeAny = store as any;
676
- const sharedView = { __mock: 'cubemap-view' };
677
- const sharedFaceViews = [
678
- { __mock: 'face-0' },
679
- { __mock: 'face-1' },
680
- { __mock: 'face-2' },
681
- { __mock: 'face-3' },
682
- { __mock: 'face-4' },
683
- { __mock: 'face-5' },
684
- ];
685
- storeAny.cubemapGpuHandles.set(sourceId, {
686
- status: 'ready',
687
- texture: gpuTex,
688
- view: sharedView,
689
- faceViews: sharedFaceViews,
690
- });
691
- storeAny.cubemapGpuHandles.set(cubeId, {
692
- status: 'ready',
693
- texture: gpuTex,
694
- view: sharedView,
695
- faceViews: sharedFaceViews,
696
- });
697
-
698
- expect(gpuTex.isDestroyed).toBe(false);
699
- expect(storeAny.cubemapGpuHandles.has(sourceId)).toBe(true);
700
- expect(storeAny.cubemapGpuHandles.has(cubeId)).toBe(true);
701
-
702
- // Evict sourceId
703
- const r1 = store.evictCubemap(srcHandle);
704
- expect(r1.freed).toBe(1);
705
- expect(r1.errors).toEqual([]);
706
-
707
- expect(gpuTex.isDestroyed).toBe(true);
708
- expect(storeAny.cubemapGpuHandles.has(sourceId)).toBe(false);
709
- // cubeId entry still exists but wrapper is destroyed
710
- expect(storeAny.cubemapGpuHandles.has(cubeId)).toBe(true);
711
-
712
- // Evict cubeId: isDestroyed gate makes this no-op
713
- const r2 = store.evictCubemap(cubeHandle);
714
- expect(r2.freed).toBe(0);
715
- expect(r2.errors).toEqual([]);
716
- expect(storeAny.cubemapGpuHandles.has(cubeId)).toBe(false);
717
- });
718
- });
719
-
720
- describe('releaseUnreferenced (feat-20260619 M1 / w3)', () => {
721
- it('releases entries not in liveSet; keeps entries in liveSet', () => {
722
- const probe = freshProbe();
723
- const store = configuredStore(probe);
724
- const keepHandle = toShared<'TextureAsset'>(5001);
725
- const evictHandle = toShared<'TextureAsset'>(5002);
726
-
727
- store.ensureResident(keepHandle, texturePodFixture());
728
- store.ensureResident(evictHandle, texturePodFixture());
729
-
730
- const keepId = unwrapHandle(keepHandle);
731
- const liveSet = new Set<number>([keepId]);
732
-
733
- const r = store.releaseUnreferenced(liveSet);
734
- expect(r.freed).toBe(1);
735
- expect(r.errors).toEqual([]);
736
-
737
- // kept entry is intact
738
- expect(store._getTextureGpuTexture(keepHandle)).toBeDefined();
739
- expect(store._getTextureGpuTexture(keepHandle)?.isDestroyed).toBe(false);
740
-
741
- // evicted entry is gone
742
- expect(store._getTextureGpuTexture(evictHandle)).toBeUndefined();
743
- });
744
-
745
- it('ignores ids in liveSet that do not exist in store', () => {
746
- const probe = freshProbe();
747
- const store = configuredStore(probe);
748
- const handle = toShared<'TextureAsset'>(5003);
749
- store.ensureResident(handle, texturePodFixture());
750
-
751
- const liveSet = new Set<number>([unwrapHandle(handle), 99999]);
752
-
753
- const r = store.releaseUnreferenced(liveSet);
754
- // Only the resident texture should be kept; external id is ignored.
755
- expect(r.freed).toBe(0);
756
- expect(store._getTextureGpuTexture(handle)).toBeDefined();
757
- });
758
-
759
- it('empty liveSet releases everything; second call is no-op', () => {
760
- const probe = freshProbe();
761
- const store = configuredStore(probe);
762
- const h1 = toShared<'TextureAsset'>(5101);
763
- const h2 = toShared<'TextureAsset'>(5102);
764
- const h3 = toShared<'MeshAsset'>(5103);
765
-
766
- store.ensureResident(h1, texturePodFixture());
767
- store.ensureResident(h2, texturePodFixture());
768
- store.ensureResident(h3, meshPodFixture());
769
-
770
- const r1 = store.releaseUnreferenced(new Set());
771
- // 2 textures (1 each) + 1 mesh (vbo + ibo = 2 buffer frees) = 4
772
- expect(r1.freed).toBe(4);
773
-
774
- // Everything gone
775
- expect(store._getTextureGpuTexture(h1)).toBeUndefined();
776
- expect(store._getTextureGpuTexture(h2)).toBeUndefined();
777
- expect(store.getMeshGpuHandles(h3)).toBeUndefined();
778
-
779
- // Second call: no-op (Map already empty)
780
- const r2 = store.releaseUnreferenced(new Set());
781
- expect(r2.freed).toBe(0);
782
- expect(r2.errors).toEqual([]);
783
- });
784
-
785
- it('is idempotent: calling twice with same liveSet gives same result', () => {
786
- const probe = freshProbe();
787
- const store = configuredStore(probe);
788
- const keepHandle = toShared<'TextureAsset'>(5201);
789
- const evictHandle = toShared<'TextureAsset'>(5202);
790
-
791
- store.ensureResident(keepHandle, texturePodFixture());
792
- store.ensureResident(evictHandle, texturePodFixture());
793
-
794
- const liveSet = new Set<number>([unwrapHandle(keepHandle)]);
795
-
796
- const r1 = store.releaseUnreferenced(liveSet);
797
- expect(r1.freed).toBe(1);
798
-
799
- const r2 = store.releaseUnreferenced(liveSet);
800
- expect(r2.freed).toBe(0);
801
- expect(r2.errors).toEqual([]);
802
- });
803
- });
804
-
805
- describe('evict aggregate failure (feat-20260619 M1 / w4)', () => {
806
- function makeFailingMockDevice(): {
807
- device: MinimalDevice;
808
- failTex: Texture;
809
- failBuf: Buffer;
810
- } {
811
- const destroyedBufs = new WeakSet<Buffer>();
812
- const destroyedTexs = new WeakSet<Texture>();
813
- const failTex = {} as unknown as Texture;
814
- const failBuf = {} as unknown as Buffer;
815
-
816
- const device: MinimalDevice = {
817
- destroyBuffer(buf: Buffer): Result<void, RhiError> {
818
- if (buf === failBuf) {
819
- return err(
820
- new RhiError({
821
- code: 'destroy-after-destroy',
822
- expected: 'buffer not yet destroyed',
823
- hint: 'injected test failure',
824
- }),
825
- );
826
- }
827
- if (destroyedBufs.has(buf)) {
828
- return err(
829
- new RhiError({
830
- code: 'destroy-after-destroy',
831
- expected: 'GPU buffer handle has not been destroyed yet',
832
- hint: 'object already destroyed; track lifecycle in caller',
833
- }),
834
- );
835
- }
836
- destroyedBufs.add(buf);
837
- return ok(undefined);
838
- },
839
- destroyTexture(tex: Texture): Result<void, RhiError> {
840
- if (tex === failTex) {
841
- return err(
842
- new RhiError({
843
- code: 'destroy-after-destroy',
844
- expected: 'texture not yet destroyed',
845
- hint: 'injected test failure',
846
- }),
847
- );
848
- }
849
- if (destroyedTexs.has(tex)) {
850
- return err(
851
- new RhiError({
852
- code: 'destroy-after-destroy',
853
- expected: 'GPU texture handle has not been destroyed yet',
854
- hint: 'object already destroyed; track lifecycle in caller',
855
- }),
856
- );
857
- }
858
- destroyedTexs.add(tex);
859
- return ok(undefined);
860
- },
861
- };
862
-
863
- return { device, failTex, failBuf };
864
- }
865
-
866
- it('single evictTexture failure: errors collected, freed counts remaining', () => {
867
- const { device, failTex } = makeFailingMockDevice();
868
-
869
- // Build two GpuTexture wrappers: one that will fail, one normal.
870
- const goodTex = new GpuTexture(device as unknown as RhiDevice, {} as unknown as Texture);
871
- const badTex = new GpuTexture(device as unknown as RhiDevice, failTex);
872
-
873
- const probe = freshProbe();
874
- const store = configuredStore(probe);
875
-
876
- const goodHandle = toShared<'TextureAsset'>(6001);
877
- const badHandle = toShared<'TextureAsset'>(6002);
878
- const goodId = unwrapHandle(goodHandle);
879
- const badId = unwrapHandle(badHandle);
880
-
881
- // biome-ignore lint/suspicious/noExplicitAny: access private store maps for test setup
882
- const storeAny = store as any;
883
- storeAny.textureGpuHandles.set(goodId, {
884
- texture: goodTex,
885
- view: { __mock: 'view-good' },
886
- });
887
- storeAny.textureGpuHandles.set(badId, {
888
- texture: badTex,
889
- view: { __mock: 'view-bad' },
890
- });
891
-
892
- // Evict the good handle first
893
- const rGood = store.evictTexture(goodHandle);
894
- expect(rGood.freed).toBe(1);
895
- expect(rGood.errors).toEqual([]);
896
- expect(goodTex.isDestroyed).toBe(true);
897
-
898
- // Evict the bad handle
899
- const rBad = store.evictTexture(badHandle);
900
- expect(rBad.freed).toBe(0);
901
- expect(rBad.errors.length).toBe(1);
902
- expect(rBad.errors[0]?.code).toBe('destroy-after-destroy');
903
- // Sweep continued — bad handle entry removed
904
- expect(storeAny.textureGpuHandles.has(badId)).toBe(false);
905
- });
906
-
907
- it('single evictMesh failure: errors collected, freed counts remaining buffers', () => {
908
- const { device, failBuf } = makeFailingMockDevice();
909
- const probe = freshProbe();
910
- const store = configuredStore(probe);
911
-
912
- const handle = toShared<'MeshAsset'>(6003);
913
- const id = unwrapHandle(handle);
914
-
915
- const goodVbo = new GpuBuffer(device as unknown as RhiDevice, {} as unknown as Buffer);
916
- const badIbo = new GpuBuffer(device as unknown as RhiDevice, failBuf);
917
-
918
- // biome-ignore lint/suspicious/noExplicitAny: access private store maps for test setup
919
- const storeAny = store as any;
920
- storeAny.meshGpuHandles.set(id, {
921
- vertexBuffer: goodVbo,
922
- indexBuffer: badIbo,
923
- vboBytes: 256,
924
- iboBytes: 256,
925
- indexCount: 6,
926
- indexFormat: 'uint16',
927
- layoutProjection: TEST_LAYOUT_PROJECTION,
928
- vertexCount: 4,
929
- indexed: true,
930
- topology: 'triangle-list',
931
- materialSlot: 0,
932
- submeshes: [
933
- {
934
- indexOffset: 0,
935
- indexCount: 6,
936
- vertexCount: 0,
937
- topology: 'triangle-list',
938
- materialSlot: 0,
939
- },
940
- ],
941
- });
942
-
943
- const r = store.evictMesh(handle);
944
- // vbo destroyed ok but ibo failed — freed counts 0 (only vbo, but treat mesh as atomic evict)
945
- // The plan says evict counts freed per-resource, mesh has two buffers.
946
- // Per w5 design: evictMesh frees 1 (the mesh entry), errors collects both buffer failures.
947
- // Actually, let's check: D-1 says "read Result (ok→freed++; err→fire+errors.push)"
948
- // For mesh: two buffers → two destroy calls. freed counts successful destroys.
949
- expect(r.freed).toBe(1); // vbo ok
950
- expect(r.errors.length).toBe(1); // ibo failed
951
- expect(r.errors[0]?.code).toBe('destroy-after-destroy');
952
- expect(goodVbo.isDestroyed).toBe(true);
953
- expect(storeAny.meshGpuHandles.has(id)).toBe(false);
954
- });
955
-
956
- it('releaseUnreferenced with mixed failures: errors collected, sweep continues', () => {
957
- const { device, failTex } = makeFailingMockDevice();
958
- const probe = freshProbe();
959
- const store = configuredStore(probe);
960
-
961
- const badHandle = toShared<'TextureAsset'>(6010);
962
- const goodHandle = toShared<'MeshAsset'>(6011);
963
- const badId = unwrapHandle(badHandle);
964
- const goodId = unwrapHandle(goodHandle);
965
-
966
- const badTex = new GpuTexture(device as unknown as RhiDevice, failTex);
967
- const goodVbo = new GpuBuffer(device as unknown as RhiDevice, {} as unknown as Buffer);
968
- const goodIbo = new GpuBuffer(device as unknown as RhiDevice, {} as unknown as Buffer);
969
-
970
- // biome-ignore lint/suspicious/noExplicitAny: access private store maps for test setup
971
- const storeAny = store as any;
972
- storeAny.textureGpuHandles.set(badId, {
973
- texture: badTex,
974
- view: { __mock: 'view-bad' },
975
- });
976
- storeAny.meshGpuHandles.set(goodId, {
977
- vertexBuffer: goodVbo,
978
- indexBuffer: goodIbo,
979
- vboBytes: 256,
980
- iboBytes: 256,
981
- indexCount: 6,
982
- indexFormat: 'uint16',
983
- layoutProjection: TEST_LAYOUT_PROJECTION,
984
- vertexCount: 4,
985
- indexed: true,
986
- topology: 'triangle-list',
987
- materialSlot: 0,
988
- submeshes: [
989
- {
990
- indexOffset: 0,
991
- indexCount: 6,
992
- vertexCount: 0,
993
- topology: 'triangle-list',
994
- materialSlot: 0,
995
- },
996
- ],
997
- });
998
-
999
- const r = store.releaseUnreferenced(new Set());
1000
- // badTex fails (1 error), goodVbo+goodIbo both succeed — freed counts each
1001
- // per-resource destroy, so 2 buffer destroys = freed 2. bad tex = 0 frees, 1 error.
1002
- expect(r.freed).toBe(2); // 2 buffer frees (vbo + ibo), 0 texture frees
1003
- expect(r.errors.length).toBe(1);
1004
- expect(r.errors[0]?.code).toBe('destroy-after-destroy');
1005
- expect(goodVbo.isDestroyed).toBe(true);
1006
- expect(goodIbo.isDestroyed).toBe(true);
1007
- });
1008
- });
1009
-
1010
- // ── M2 w7: AC-02 event-driven recycle closed-loop integration test ──
1011
- //
1012
- // Validates the owner chain: SharedRefStore.release publishes evidence, then
1013
- // the GPU owner consumes that evidence and calls gpuStore.evictX.
1014
- //
1015
- // Scope per task description:
1016
- // (1) Three resource types (TextureAsset / MeshAsset / EquirectAsset cubemap)
1017
- // each construct one alloc -> retain -> release-to-rc=0 ->
1018
- // owner evidence triggers evict -> assert wrapper.isDestroyed===true.
1019
- // (2) Covers "no manual evict needed" — entity despawn path auto-recycles.
1020
- // (3) Negative assertion: ECS release alone does not evict the resource
1021
- // (stays resident in store).
1022
- //
1023
- describe('event-driven recycle closed-loop (feat-20260619 M2 / w7, AC-02)', () => {
1024
- // Helper: create a GpuResourceStore with configured device and probe,
1025
- // using the same mock infrastructure from earlier describe blocks.
1026
- function evictCapturingStore(): {
1027
- store: GpuResourceStore;
1028
- probe: DeviceProbe;
1029
- sharedRefs: World['sharedRefs'];
1030
- } {
1031
- const probe = freshProbe();
1032
- const store = new GpuResourceStore();
1033
- store.configureGpuDevice(
1034
- makeStoreMockDevice(probe),
1035
- shaderFactory,
1036
- makeRegisterCube() as never,
1037
- mockCaps,
1038
- );
1039
- return { store, probe, sharedRefs: new World().sharedRefs };
1040
- }
1041
-
1042
- it('TextureAsset: release evidence -> owner evicts texture', () => {
1043
- const { store, probe, sharedRefs } = evictCapturingStore();
1044
- const baselineDestroyed = probe.destroyedTextures;
1045
-
1046
- const payload: TextureAsset = texturePodFixture();
1047
- const handle = sharedRefs.alloc('TextureAsset', payload);
1048
-
1049
- // Step 2: ensureResident so GPU resources are materialised in the store
1050
- const residentRes = store.ensureResident(handle, payload);
1051
- expect(residentRes.ok).toBe(true);
1052
-
1053
- const tex = store._getTextureGpuTexture(handle);
1054
- expect(tex).toBeDefined();
1055
- if (tex === undefined) return;
1056
- expect(tex.isDestroyed).toBe(false);
1057
-
1058
- const releaseRes = sharedRefs.release(handle);
1059
- expect(releaseRes.ok).toBe(true);
1060
- expect(releaseRes.unwrap()).toMatchObject({ payload, refcount: 0, evidence: 'released' });
1061
- store.evictTexture(handle);
1062
-
1063
- expect(tex.isDestroyed).toBe(true);
1064
- expect(store._getTextureGpuTexture(handle)).toBeUndefined();
1065
- expect(store.getTextureGpuView(handle)).toBeUndefined();
1066
- expect(probe.destroyedTextures - baselineDestroyed).toBe(1);
1067
- });
1068
-
1069
- it('MeshAsset: release evidence -> owner evicts mesh', () => {
1070
- const { store, probe, sharedRefs } = evictCapturingStore();
1071
- const baselineDestroyed = probe.destroyedBuffers;
1072
-
1073
- const payload: MeshAsset = meshPodFixture();
1074
- const handle = sharedRefs.alloc('MeshAsset', payload);
1075
-
1076
- const residentRes = store.ensureResident(handle, payload);
1077
- expect(residentRes.ok).toBe(true);
1078
-
1079
- const entry = store.getMeshGpuHandles(handle);
1080
- expect(entry).toBeDefined();
1081
- if (entry === undefined) return;
1082
- expect(entry.vertexBuffer.isDestroyed).toBe(false);
1083
- expect(entry.indexBuffer).not.toBeNull();
1084
- if (entry.indexBuffer === null) return;
1085
-
1086
- const releaseRes = sharedRefs.release(handle);
1087
- expect(releaseRes.ok).toBe(true);
1088
- expect(releaseRes.unwrap()).toMatchObject({ payload, refcount: 0, evidence: 'released' });
1089
- store.evictMesh(handle);
1090
-
1091
- expect(entry.vertexBuffer.isDestroyed).toBe(true);
1092
- expect(entry.indexBuffer.isDestroyed).toBe(true);
1093
- expect(store.getMeshGpuHandles(handle)).toBeUndefined();
1094
- expect(probe.destroyedBuffers - baselineDestroyed).toBe(2);
1095
- });
1096
-
1097
- it('EquirectAsset cubemap: release evidence -> owner evicts cubemap', () => {
1098
- const { store, sharedRefs } = evictCapturingStore();
1099
-
1100
- let cubeIdNum = 0;
1101
- const cubePayload: EquirectAsset = {
1102
- kind: 'equirect',
1103
- width: 64,
1104
- height: 64,
1105
- format: 'rgba16float',
1106
- data: new Uint8Array(64 * 64 * 8),
1107
- colorSpace: 'linear',
1108
- };
1109
- const cubeHandle = sharedRefs.alloc('EquirectAsset', cubePayload);
1110
- cubeIdNum = unwrapHandle(cubeHandle);
1111
-
1112
- // Directly seed a cubemap entry into the store (simulating uploadCubemapFromEquirect
1113
- // with the matching sourceId). We don't test the full equirect upload path;
1114
- // we only verify the evict callback chain.
1115
- const device = makeMockDevice() as unknown as RhiDevice;
1116
- const gpuTex = new GpuTexture(device, {} as unknown as Texture);
1117
-
1118
- // biome-ignore lint/suspicious/noExplicitAny: access private store maps for test setup
1119
- const storeAny = store as any;
1120
- storeAny.cubemapGpuHandles.set(cubeIdNum, {
1121
- status: 'ready',
1122
- texture: gpuTex,
1123
- view: { __mock: 'cubemap-view' },
1124
- faceViews: [
1125
- { __mock: 'face-0' },
1126
- { __mock: 'face-1' },
1127
- { __mock: 'face-2' },
1128
- { __mock: 'face-3' },
1129
- { __mock: 'face-4' },
1130
- { __mock: 'face-5' },
1131
- ],
1132
- });
1133
-
1134
- expect(gpuTex.isDestroyed).toBe(false);
1135
- expect(storeAny.cubemapGpuHandles.has(cubeIdNum)).toBe(true);
1136
-
1137
- const releaseRes = sharedRefs.release(cubeHandle);
1138
- expect(releaseRes.ok).toBe(true);
1139
- expect(releaseRes.unwrap()).toMatchObject({
1140
- payload: cubePayload,
1141
- refcount: 0,
1142
- evidence: 'released',
1143
- });
1144
- store.evictCubemap(cubeIdNum);
1145
-
1146
- expect(gpuTex.isDestroyed).toBe(true);
1147
- expect(storeAny.cubemapGpuHandles.has(cubeIdNum)).toBe(false);
1148
- });
1149
-
1150
- it('negative: release alone does not evict a GPU resource', () => {
1151
- const { store, sharedRefs } = evictCapturingStore();
1152
-
1153
- const payload: TextureAsset = texturePodFixture();
1154
- const handle = sharedRefs.alloc('TextureAsset', payload);
1155
-
1156
- const residentRes = store.ensureResident(handle, payload);
1157
- expect(residentRes.ok).toBe(true);
1158
-
1159
- const tex = store._getTextureGpuTexture(handle);
1160
- expect(tex).toBeDefined();
1161
- if (tex === undefined) return;
1162
- expect(tex.isDestroyed).toBe(false);
1163
-
1164
- // Release to rc=0
1165
- const releaseRes = sharedRefs.release(handle);
1166
- expect(releaseRes.ok).toBe(true);
1167
-
1168
- // Resource still resident — the GPU owner has not consumed evidence.
1169
- expect(tex.isDestroyed).toBe(false);
1170
- expect(store._getTextureGpuTexture(handle)).toBeDefined();
1171
- });
1172
-
1173
- it('retain -> release retains rc>0: no premature evict at rc 2->1', () => {
1174
- const { store, sharedRefs } = evictCapturingStore();
1175
-
1176
- const payload: TextureAsset = texturePodFixture();
1177
- const handle = sharedRefs.alloc('TextureAsset', payload);
1178
-
1179
- store.ensureResident(handle, payload);
1180
- const tex = store._getTextureGpuTexture(handle);
1181
- expect(tex).toBeDefined();
1182
- if (tex === undefined) return;
1183
-
1184
- // retain (rc 1->2)
1185
- const retainRes = sharedRefs.retain(handle);
1186
- expect(retainRes.ok).toBe(true);
1187
- expect(sharedRefs.refcount(handle)).toBe(2);
1188
-
1189
- // release to rc=2->1 (no release evidence is published yet)
1190
- const releaseRes = sharedRefs.release(handle);
1191
- expect(releaseRes.ok).toBe(true);
1192
- expect(sharedRefs.refcount(handle)).toBe(1);
1193
-
1194
- // Resource still resident — no final-release evidence exists yet.
1195
- expect(tex.isDestroyed).toBe(false);
1196
- expect(store._getTextureGpuTexture(handle)).toBeDefined();
1197
-
1198
- // Final release to rc=0 publishes evidence; the owner performs eviction.
1199
- const finalRes = sharedRefs.release(handle);
1200
- expect(finalRes.ok).toBe(true);
1201
- expect(finalRes.unwrap()).toMatchObject({ payload, refcount: 0, evidence: 'released' });
1202
- store.evictTexture(handle);
1203
- expect(tex.isDestroyed).toBe(true);
1204
- expect(store._getTextureGpuTexture(handle)).toBeUndefined();
1205
- });
1206
- });