@forgeax/engine-render 0.1.6 → 0.1.19

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 (713) hide show
  1. package/README.md +279 -2
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/assembly/backend-contract.d.ts +12 -0
  4. package/dist/assembly/backend-contract.d.ts.map +1 -1
  5. package/dist/assembly/factory.d.ts +19 -2
  6. package/dist/assembly/factory.d.ts.map +1 -1
  7. package/dist/assembly/host-contract.d.ts +4 -0
  8. package/dist/assembly/host-contract.d.ts.map +1 -1
  9. package/dist/assembly/material/assembly.d.ts.map +1 -1
  10. package/dist/assembly/renderer-frame-transaction.d.ts +39 -0
  11. package/dist/assembly/renderer-frame-transaction.d.ts.map +1 -0
  12. package/dist/assembly/renderer-inspection.d.ts +22 -0
  13. package/dist/assembly/renderer-inspection.d.ts.map +1 -0
  14. package/dist/assembly/renderer-lifecycle.d.ts +4 -0
  15. package/dist/assembly/renderer-lifecycle.d.ts.map +1 -0
  16. package/dist/authoring.d.ts +1 -0
  17. package/dist/authoring.d.ts.map +1 -1
  18. package/dist/authoring.mjs +4 -2
  19. package/dist/authoring.mjs.map +1 -1
  20. package/dist/chunk-FJ6P52EE.mjs +46 -0
  21. package/dist/chunk-FJ6P52EE.mjs.map +1 -0
  22. package/dist/chunk-IPXCABEP.mjs +73 -0
  23. package/dist/chunk-IPXCABEP.mjs.map +1 -0
  24. package/dist/chunk-JDWARUOI.mjs +44 -0
  25. package/dist/chunk-JDWARUOI.mjs.map +1 -0
  26. package/dist/chunk-LOSO4CTV.mjs +901 -0
  27. package/dist/chunk-LOSO4CTV.mjs.map +1 -0
  28. package/dist/chunk-PCHBPYML.mjs +385 -0
  29. package/dist/chunk-PCHBPYML.mjs.map +1 -0
  30. package/dist/chunk-RJRUS55L.mjs +9323 -0
  31. package/dist/chunk-RJRUS55L.mjs.map +1 -0
  32. package/dist/{chunk-QC4VW3XJ.mjs → chunk-RXZ3RGOU.mjs} +4 -30
  33. package/dist/chunk-RXZ3RGOU.mjs.map +1 -0
  34. package/dist/{chunk-CQXZC5XI.mjs → chunk-VFPLTKCU.mjs} +79 -69
  35. package/dist/chunk-VFPLTKCU.mjs.map +1 -0
  36. package/dist/{chunk-SCRSAK5B.mjs → chunk-YTQ5C7YQ.mjs} +231 -127
  37. package/dist/chunk-YTQ5C7YQ.mjs.map +1 -0
  38. package/dist/components/atmosphere.d.ts +9 -0
  39. package/dist/components/atmosphere.d.ts.map +1 -0
  40. package/dist/components/camera.d.ts +2 -1
  41. package/dist/components/camera.d.ts.map +1 -1
  42. package/dist/components/fog.d.ts +8 -0
  43. package/dist/components/fog.d.ts.map +1 -0
  44. package/dist/components/index.d.ts +5 -0
  45. package/dist/components/index.d.ts.map +1 -1
  46. package/dist/components/lines.d.ts +5 -0
  47. package/dist/components/lines.d.ts.map +1 -0
  48. package/dist/components/motion-blur.d.ts +7 -0
  49. package/dist/components/motion-blur.d.ts.map +1 -0
  50. package/dist/components/points.d.ts +15 -0
  51. package/dist/components/points.d.ts.map +1 -0
  52. package/dist/construct-renderer.d.ts +2 -2
  53. package/dist/construct-renderer.d.ts.map +1 -1
  54. package/dist/construct-renderer.mjs +11419 -16442
  55. package/dist/construct-renderer.mjs.map +1 -1
  56. package/dist/device/device-scope.d.ts +4 -0
  57. package/dist/device/device-scope.d.ts.map +1 -1
  58. package/dist/environment/frame.d.ts +60 -0
  59. package/dist/environment/frame.d.ts.map +1 -0
  60. package/dist/environment/generation.d.ts +21 -0
  61. package/dist/environment/generation.d.ts.map +1 -0
  62. package/dist/environment/graph.d.ts +4 -0
  63. package/dist/environment/graph.d.ts.map +1 -0
  64. package/dist/environment/inspection.d.ts +48 -0
  65. package/dist/environment/inspection.d.ts.map +1 -0
  66. package/dist/environment/lifecycle.d.ts +39 -0
  67. package/dist/environment/lifecycle.d.ts.map +1 -0
  68. package/dist/environment/signature.d.ts +25 -0
  69. package/dist/environment/signature.d.ts.map +1 -0
  70. package/dist/errors/render.d.ts +217 -1
  71. package/dist/errors/render.d.ts.map +1 -1
  72. package/dist/extract/camera.d.ts +5 -0
  73. package/dist/extract/camera.d.ts.map +1 -0
  74. package/dist/extract/environment.d.ts +74 -0
  75. package/dist/extract/environment.d.ts.map +1 -0
  76. package/dist/extract/visibility.d.ts +1 -1
  77. package/dist/extract/visibility.d.ts.map +1 -1
  78. package/dist/features/host.d.ts +9 -2
  79. package/dist/features/host.d.ts.map +1 -1
  80. package/dist/features/motion-blur/motion-blur-feature.d.ts +16 -0
  81. package/dist/features/motion-blur/motion-blur-feature.d.ts.map +1 -0
  82. package/dist/features/motion-blur/motion-blur-params.d.ts +25 -0
  83. package/dist/features/motion-blur/motion-blur-params.d.ts.map +1 -0
  84. package/dist/features/plan.d.ts +17 -2
  85. package/dist/features/plan.d.ts.map +1 -1
  86. package/dist/features/prepared-gpu-work.d.ts +3 -1
  87. package/dist/features/prepared-gpu-work.d.ts.map +1 -1
  88. package/dist/features/prepared-graphics.d.ts +2 -1
  89. package/dist/features/prepared-graphics.d.ts.map +1 -1
  90. package/dist/features/render-graph-raster.d.ts +7 -2
  91. package/dist/features/render-graph-raster.d.ts.map +1 -1
  92. package/dist/features/targets.d.ts +3 -0
  93. package/dist/features/targets.d.ts.map +1 -1
  94. package/dist/features/types.d.ts +12 -0
  95. package/dist/features/types.d.ts.map +1 -1
  96. package/dist/fullscreen-post-process-pass.d.ts +7 -1
  97. package/dist/fullscreen-post-process-pass.d.ts.map +1 -1
  98. package/dist/gpu-driven/production-raster.d.ts.map +1 -1
  99. package/dist/gpu-scene.d.ts +2 -0
  100. package/dist/gpu-scene.d.ts.map +1 -1
  101. package/dist/ibl/skylight-bind-group.d.ts +5 -6
  102. package/dist/ibl/skylight-bind-group.d.ts.map +1 -1
  103. package/dist/index.d.ts +21 -6
  104. package/dist/index.d.ts.map +1 -1
  105. package/dist/index.mjs +8 -4
  106. package/dist/index.mjs.map +1 -1
  107. package/dist/inspection-types.d.ts +46 -1
  108. package/dist/inspection-types.d.ts.map +1 -1
  109. package/dist/internal.d.ts +2 -0
  110. package/dist/internal.d.ts.map +1 -1
  111. package/dist/internal.mjs +2 -0
  112. package/dist/materials.d.ts +22 -0
  113. package/dist/materials.d.ts.map +1 -1
  114. package/dist/pbr-pipeline.d.ts +12 -6
  115. package/dist/pbr-pipeline.d.ts.map +1 -1
  116. package/dist/pipeline/standard-forward-lane.d.ts.map +1 -1
  117. package/dist/pipeline/standard-pipeline.d.ts +3 -0
  118. package/dist/pipeline/standard-pipeline.d.ts.map +1 -1
  119. package/dist/pipeline/standard-post.d.ts +48 -0
  120. package/dist/pipeline/standard-post.d.ts.map +1 -0
  121. package/dist/pipeline/standard-profile.d.ts +3 -6
  122. package/dist/pipeline/standard-profile.d.ts.map +1 -1
  123. package/dist/pipeline-builder.d.ts +18 -0
  124. package/dist/pipeline-builder.d.ts.map +1 -1
  125. package/dist/pipeline-spec.d.ts.map +1 -1
  126. package/dist/plugin.d.ts.map +1 -1
  127. package/dist/points-lines/admission.d.ts +36 -0
  128. package/dist/points-lines/admission.d.ts.map +1 -0
  129. package/dist/points-lines/bounds.d.ts +8 -0
  130. package/dist/points-lines/bounds.d.ts.map +1 -0
  131. package/dist/points-lines/expansion-cache.d.ts +28 -0
  132. package/dist/points-lines/expansion-cache.d.ts.map +1 -0
  133. package/dist/points-lines/inspection.d.ts +53 -0
  134. package/dist/points-lines/inspection.d.ts.map +1 -0
  135. package/dist/points-lines/prepare.d.ts +67 -0
  136. package/dist/points-lines/prepare.d.ts.map +1 -0
  137. package/dist/points-lines/record.d.ts +60 -0
  138. package/dist/points-lines/record.d.ts.map +1 -0
  139. package/dist/points-lines/snapshot.d.ts +49 -0
  140. package/dist/points-lines/snapshot.d.ts.map +1 -0
  141. package/dist/record/frame-lighting.d.ts.map +1 -1
  142. package/dist/record/frame-snapshot.d.ts +100 -5
  143. package/dist/record/frame-snapshot.d.ts.map +1 -1
  144. package/dist/record/frame-targets.d.ts.map +1 -1
  145. package/dist/record/frame.d.ts +4 -2
  146. package/dist/record/frame.d.ts.map +1 -1
  147. package/dist/record/main-pass-geometry.d.ts +18 -0
  148. package/dist/record/main-pass-geometry.d.ts.map +1 -1
  149. package/dist/record/main-pass-material.d.ts +8 -5
  150. package/dist/record/main-pass-material.d.ts.map +1 -1
  151. package/dist/record/main-pass-sprite-draws.d.ts +8 -1
  152. package/dist/record/main-pass-sprite-draws.d.ts.map +1 -1
  153. package/dist/record/main-pass.d.ts +2 -0
  154. package/dist/record/main-pass.d.ts.map +1 -1
  155. package/dist/record/mesh-ssbo.d.ts.map +1 -1
  156. package/dist/record/render-context.d.ts +93 -18
  157. package/dist/record/render-context.d.ts.map +1 -1
  158. package/dist/record/shadow-pass.d.ts.map +1 -1
  159. package/dist/record/skybox-post-pass.d.ts +1 -11
  160. package/dist/record/skybox-post-pass.d.ts.map +1 -1
  161. package/dist/record/typed-frame-graph.d.ts +26 -2
  162. package/dist/record/typed-frame-graph.d.ts.map +1 -1
  163. package/dist/record/view-ubo.d.ts +10 -1
  164. package/dist/record/view-ubo.d.ts.map +1 -1
  165. package/dist/render-contract.d.ts +102 -10
  166. package/dist/render-contract.d.ts.map +1 -1
  167. package/dist/render-graph-primitives.d.ts.map +1 -1
  168. package/dist/render-pipeline.d.ts +50 -6
  169. package/dist/render-pipeline.d.ts.map +1 -1
  170. package/dist/render-system-extract.d.ts +32 -5
  171. package/dist/render-system-extract.d.ts.map +1 -1
  172. package/dist/render-system.d.ts +27 -5
  173. package/dist/render-system.d.ts.map +1 -1
  174. package/dist/scene/render-scene-types.d.ts +3 -0
  175. package/dist/scene/render-scene-types.d.ts.map +1 -1
  176. package/dist/scene/render-scene.d.ts +43 -2
  177. package/dist/scene/render-scene.d.ts.map +1 -1
  178. package/dist/temporal/coverage.d.ts +42 -0
  179. package/dist/temporal/coverage.d.ts.map +1 -0
  180. package/dist/temporal/frame-coordinator.d.ts +99 -0
  181. package/dist/temporal/frame-coordinator.d.ts.map +1 -0
  182. package/dist/temporal/gpu.d.ts +39 -0
  183. package/dist/temporal/gpu.d.ts.map +1 -0
  184. package/dist/temporal/history.d.ts +37 -0
  185. package/dist/temporal/history.d.ts.map +1 -0
  186. package/dist/temporal/index.d.ts +7 -0
  187. package/dist/temporal/index.d.ts.map +1 -0
  188. package/dist/temporal/index.mjs +8 -0
  189. package/dist/temporal/index.mjs.map +1 -0
  190. package/dist/temporal/inspection.d.ts +36 -0
  191. package/dist/temporal/inspection.d.ts.map +1 -0
  192. package/dist/temporal/scene-data-catalog.d.ts +48 -0
  193. package/dist/temporal/scene-data-catalog.d.ts.map +1 -0
  194. package/dist/temporal/scene-data.d.ts +43 -0
  195. package/dist/temporal/scene-data.d.ts.map +1 -0
  196. package/dist/temporal/standard-scene-data.d.ts +61 -0
  197. package/dist/temporal/standard-scene-data.d.ts.map +1 -0
  198. package/dist/temporal/taa-history-resources.d.ts +22 -0
  199. package/dist/temporal/taa-history-resources.d.ts.map +1 -0
  200. package/dist/temporal/taa-history-store.d.ts +27 -0
  201. package/dist/temporal/taa-history-store.d.ts.map +1 -0
  202. package/dist/temporal/taa-resolve.d.ts +15 -0
  203. package/dist/temporal/taa-resolve.d.ts.map +1 -0
  204. package/dist/temporal/temporal-view.d.ts +40 -0
  205. package/dist/temporal/temporal-view.d.ts.map +1 -0
  206. package/dist/temporal/transaction.d.ts +9 -0
  207. package/dist/temporal/transaction.d.ts.map +1 -0
  208. package/dist/temporal/view.d.ts +56 -0
  209. package/dist/temporal/view.d.ts.map +1 -0
  210. package/dist/transmission/backdrop.d.ts +147 -0
  211. package/dist/transmission/backdrop.d.ts.map +1 -0
  212. package/dist/transmission/inspection.d.ts +57 -0
  213. package/dist/transmission/inspection.d.ts.map +1 -0
  214. package/dist/transmission/oracle.d.ts +42 -0
  215. package/dist/transmission/oracle.d.ts.map +1 -0
  216. package/dist/transmission/projection.d.ts +37 -0
  217. package/dist/transmission/projection.d.ts.map +1 -0
  218. package/dist/typed-render-graph-primitives.d.ts +25 -2
  219. package/dist/typed-render-graph-primitives.d.ts.map +1 -1
  220. package/package.json +24 -19
  221. package/src/__tests__/ai-user-cold-start.integration.test.ts +59 -0
  222. package/src/__tests__/ai-user-public-surface.test-d.ts +45 -0
  223. package/src/__tests__/authoring-path.unit.test.ts +9 -0
  224. package/src/__tests__/bloom-exact-zero.unit.test.ts +202 -0
  225. package/src/__tests__/bloom-lifecycle.integration.test.ts +125 -0
  226. package/src/__tests__/construct-renderer-identity.unit.test.ts +4 -3
  227. package/src/__tests__/detached-inspection.integration.test.ts +177 -0
  228. package/src/__tests__/environment-fog-lanes.integration.test.ts +219 -0
  229. package/src/__tests__/environment-generation.integration.test.ts +141 -0
  230. package/src/__tests__/environment-selection.unit.test.ts +245 -0
  231. package/src/__tests__/factory-contract.integration.test.ts +1380 -9
  232. package/src/__tests__/frame-plan-contract.unit.test.ts +90 -0
  233. package/src/__tests__/fullscreen-feature-plan.unit.test.ts +7 -0
  234. package/src/__tests__/gpu-driven-indirect-raster-evidence.ts +12 -2
  235. package/src/__tests__/gpu-driven-production.integration.test.ts +74 -22
  236. package/src/__tests__/gpu-driven-view-graph.integration.test.ts +37 -0
  237. package/src/__tests__/gpu-scene.unit.test.ts +10 -0
  238. package/src/__tests__/injection-kind-owner.test-d.ts +2 -1
  239. package/src/__tests__/material-contract-inventory.unit.test.ts +62 -0
  240. package/src/__tests__/material-cooked-projection.unit.test.ts +18 -124
  241. package/src/__tests__/material-static-dynamic-route.unit.test.ts +28 -28
  242. package/src/__tests__/materials-transmission-validation.unit.test.ts +36 -0
  243. package/src/__tests__/morph-feature-plan.unit.test.ts +7 -0
  244. package/src/__tests__/motion-blur.pixel.integration.test.ts +121 -0
  245. package/src/__tests__/public-surface.test-d.ts +15 -1
  246. package/src/__tests__/render-error-code-owner.test-d.ts +15 -0
  247. package/src/__tests__/render-error-detail-ssot.test-d.ts +11 -2
  248. package/src/__tests__/render-error-exhaustive.test-d.ts +22 -0
  249. package/src/__tests__/render-feature-errors.unit.test.ts +33 -3
  250. package/src/__tests__/render-feature-gpu-work.integration.test.ts +44 -0
  251. package/src/__tests__/render-feature-plan.test-d.ts +22 -89
  252. package/src/__tests__/render-scene-projection.unit.test.ts +28 -0
  253. package/src/__tests__/render-temporal-environment-consumer.browser.test.ts +26 -0
  254. package/src/__tests__/render-temporal-environment-consumer.dawn.test.ts +21 -0
  255. package/src/__tests__/renderer-factory-material-contract.unit.test.ts +433 -1
  256. package/src/__tests__/renderer-frame-transaction.integration.test.ts +405 -0
  257. package/src/__tests__/renderer-lifecycle.integration.test.ts +22 -0
  258. package/src/__tests__/scene-data-catalog.unit.test.ts +59 -0
  259. package/src/__tests__/scene-data-consumer.integration.test.ts +67 -0
  260. package/src/__tests__/scene-data-contract.test-d.ts +35 -0
  261. package/src/__tests__/scene-data-inspection.unit.test.ts +41 -0
  262. package/src/__tests__/scene-data-public-surface.test-d.ts +34 -0
  263. package/src/__tests__/shader-manifest-fixture.ts +111 -0
  264. package/src/__tests__/skinned-shadow-caster.test.ts +102 -0
  265. package/src/__tests__/standard-clustered-pipeline.unit.test.ts +24 -3
  266. package/src/__tests__/standard-pipeline.dawn.test.ts +15 -0
  267. package/src/__tests__/standard-pipeline.integration.test.ts +543 -1
  268. package/src/__tests__/standard-profile-oracle.unit.test.ts +10 -2
  269. package/src/__tests__/standard-temporal-producer.integration.test.ts +35 -0
  270. package/src/__tests__/surface-configuration.unit.test.ts +211 -12
  271. package/src/__tests__/taa-transaction.integration.test.ts +30 -0
  272. package/src/__tests__/temporal-admission.integration.test.ts +40 -0
  273. package/src/__tests__/temporal-coverage.unit.test.ts +35 -0
  274. package/src/__tests__/temporal-demand.unit.test.ts +44 -0
  275. package/src/__tests__/temporal-frame-coordinator.unit.test.ts +64 -0
  276. package/src/__tests__/temporal-graph-topology.unit.test.ts +51 -0
  277. package/src/__tests__/temporal-history-lifecycle.unit.test.ts +170 -0
  278. package/src/__tests__/temporal-performance.integration.test.ts +615 -0
  279. package/src/__tests__/temporal-reset-matrix.unit.test.ts +97 -0
  280. package/src/__tests__/temporal-transaction.integration.test.ts +60 -0
  281. package/src/__tests__/temporal-view.unit.test.ts +152 -0
  282. package/src/__tests__/transmission-prewarm.unit.test.ts +93 -0
  283. package/src/__tests__/typed-pipeline-topology.unit.test.ts +230 -1
  284. package/src/__tests__/urp-pipeline-targets.unit.test.ts +25 -1
  285. package/src/__tests__/urp-pipeline.unit.test.ts +4 -3
  286. package/src/__tests__/visibility-extract.integration.test.ts +27 -1
  287. package/src/__tests__/visibility-producer-matrix.integration.test.ts +10 -0
  288. package/src/assembly/backend-contract.ts +47 -1
  289. package/src/assembly/factory.ts +1295 -507
  290. package/src/assembly/host-contract.ts +4 -0
  291. package/src/assembly/material/assembly.ts +2 -5
  292. package/src/assembly/renderer-frame-transaction.ts +75 -0
  293. package/src/assembly/renderer-inspection.ts +112 -0
  294. package/src/assembly/renderer-lifecycle.ts +6 -0
  295. package/src/authoring.ts +1 -0
  296. package/src/components/__tests__/motion-blur.unit.test.ts +14 -0
  297. package/src/components/__tests__/points-lines-admission.unit.test.ts +219 -0
  298. package/src/components/__tests__/points-lines-schema.unit.test.ts +56 -0
  299. package/src/components/__tests__/schema-contract.unit.test.ts +6 -1
  300. package/src/components/atmosphere.ts +10 -0
  301. package/src/components/camera.ts +2 -1
  302. package/src/components/fog.ts +9 -0
  303. package/src/components/index.ts +10 -0
  304. package/src/components/lines.ts +18 -0
  305. package/src/components/motion-blur.ts +8 -0
  306. package/src/components/points.ts +40 -0
  307. package/src/construct-renderer.ts +40 -3
  308. package/src/device/device-scope.ts +15 -1
  309. package/src/environment/frame.ts +185 -0
  310. package/src/environment/generation.ts +23 -0
  311. package/src/environment/graph.ts +9 -0
  312. package/src/environment/inspection.ts +87 -0
  313. package/src/environment/lifecycle.ts +250 -0
  314. package/src/environment/signature.ts +55 -0
  315. package/src/errors/render.ts +429 -1
  316. package/src/extract/camera.ts +78 -0
  317. package/src/extract/environment.ts +171 -0
  318. package/src/extract/visibility.ts +38 -19
  319. package/src/features/host.ts +36 -5
  320. package/src/features/motion-blur/__tests__/motion-blur-feature.unit.test.ts +67 -0
  321. package/src/features/motion-blur/__tests__/motion-blur-params.unit.test.ts +53 -0
  322. package/src/features/motion-blur/motion-blur-feature.ts +119 -0
  323. package/src/features/motion-blur/motion-blur-params.ts +89 -0
  324. package/src/features/plan.ts +32 -3
  325. package/src/features/prepared-gpu-work.ts +21 -6
  326. package/src/features/prepared-graphics.ts +2 -1
  327. package/src/features/render-graph-raster.ts +24 -4
  328. package/src/features/targets.ts +24 -2
  329. package/src/features/types.ts +13 -0
  330. package/src/fullscreen-post-process-pass.ts +34 -4
  331. package/src/gpu-driven/production-raster.ts +6 -1
  332. package/src/gpu-scene.ts +24 -0
  333. package/src/ibl/skylight-bind-group.ts +30 -38
  334. package/src/index.ts +83 -6
  335. package/src/inspection-types.ts +52 -0
  336. package/src/internal.ts +5 -0
  337. package/src/materials.ts +119 -24
  338. package/src/pbr-pipeline.ts +66 -20
  339. package/src/pipeline/standard-forward-lane.ts +158 -101
  340. package/src/pipeline/standard-pipeline.ts +156 -105
  341. package/src/pipeline/standard-post.ts +373 -0
  342. package/src/pipeline/standard-profile.ts +11 -11
  343. package/src/pipeline-builder.ts +102 -12
  344. package/src/pipeline-spec.ts +9 -0
  345. package/src/plugin.ts +6 -0
  346. package/src/points-lines/__tests__/bounds.unit.test.ts +21 -0
  347. package/src/points-lines/__tests__/cache-recovery.unit.test.ts +157 -0
  348. package/src/points-lines/__tests__/inspection-lanes.integration.test.ts +146 -0
  349. package/src/points-lines/__tests__/inspection.unit.test.ts +173 -0
  350. package/src/points-lines/__tests__/lane-structural.integration.test.ts +122 -0
  351. package/src/points-lines/__tests__/points-lines.bench.ts +400 -0
  352. package/src/points-lines/__tests__/raster-lane.integration.test.ts +44 -0
  353. package/src/points-lines/__tests__/snapshot.unit.test.ts +101 -0
  354. package/src/points-lines/__tests__/view-ubo.unit.test.ts +73 -0
  355. package/src/points-lines/admission.ts +238 -0
  356. package/src/points-lines/bounds.ts +23 -0
  357. package/src/points-lines/expansion-cache.ts +215 -0
  358. package/src/points-lines/inspection.ts +97 -0
  359. package/src/points-lines/prepare.ts +177 -0
  360. package/src/points-lines/record.ts +125 -0
  361. package/src/points-lines/snapshot.ts +130 -0
  362. package/src/record/__tests__/fullscreen-temporal-routing.unit.test.ts +25 -0
  363. package/src/record/__tests__/main-pass-geometry.unit.test.ts +47 -0
  364. package/src/record/__tests__/material-bg-cache.unit.test.ts +12 -1
  365. package/src/record/__tests__/material-pipeline-pending-cache.unit.test.ts +22 -0
  366. package/src/record/__tests__/sprite-material-abi.unit.test.ts +75 -0
  367. package/src/record/__tests__/standard-pbr-ubo-layout.unit.test.ts +67 -0
  368. package/src/record/__tests__/temporal-payload.unit.test.ts +164 -0
  369. package/src/record/__tests__/typed-frame-graph-plan.unit.test.ts +127 -0
  370. package/src/record/frame-lighting.ts +14 -2
  371. package/src/record/frame-snapshot.ts +129 -5
  372. package/src/record/frame-targets.ts +15 -6
  373. package/src/record/frame.ts +119 -4
  374. package/src/record/main-pass-geometry.ts +178 -40
  375. package/src/record/main-pass-material.ts +114 -66
  376. package/src/record/main-pass-sprite-draws.ts +79 -102
  377. package/src/record/main-pass.ts +83 -7
  378. package/src/record/mesh-ssbo.ts +22 -3
  379. package/src/record/render-context.ts +209 -23
  380. package/src/record/shadow-pass.ts +129 -28
  381. package/src/record/skybox-post-pass.ts +99 -42
  382. package/src/record/typed-frame-graph.ts +582 -42
  383. package/src/record/view-ubo.ts +99 -4
  384. package/src/render-contract.ts +121 -7
  385. package/src/render-graph-primitives.ts +18 -1
  386. package/src/render-pipeline.ts +100 -27
  387. package/src/render-system-extract.ts +264 -77
  388. package/src/render-system.ts +786 -43
  389. package/src/scene/render-scene-types.ts +3 -0
  390. package/src/scene/render-scene.ts +307 -9
  391. package/src/temporal/__tests__/taa-history-resources.unit.test.ts +45 -0
  392. package/src/temporal/coverage.ts +76 -0
  393. package/src/temporal/frame-coordinator.ts +311 -0
  394. package/src/temporal/gpu.ts +287 -0
  395. package/src/temporal/history.ts +92 -0
  396. package/src/temporal/index.ts +6 -0
  397. package/src/temporal/inspection.ts +97 -0
  398. package/src/temporal/scene-data-catalog.ts +200 -0
  399. package/src/temporal/scene-data.ts +149 -0
  400. package/src/temporal/standard-scene-data.ts +182 -0
  401. package/src/temporal/taa-history-resources.ts +128 -0
  402. package/src/temporal/taa-history-store.ts +79 -0
  403. package/src/temporal/taa-resolve.ts +34 -0
  404. package/src/temporal/temporal-view.ts +169 -0
  405. package/src/temporal/transaction.ts +17 -0
  406. package/src/temporal/view.ts +161 -0
  407. package/src/transmission/__tests__/capability.unit.test.ts +83 -0
  408. package/src/transmission/__tests__/composition.integration.test.ts +161 -0
  409. package/src/transmission/__tests__/inspection.unit.test.ts +241 -0
  410. package/src/transmission/__tests__/lifecycle.integration.test.ts +96 -0
  411. package/src/transmission/__tests__/oracle.unit.test.ts +131 -0
  412. package/src/transmission/__tests__/projection.unit.test.ts +71 -0
  413. package/src/transmission/__tests__/standard-transmission.dawn.test.ts +438 -0
  414. package/src/transmission/__tests__/transmission-backdrop-topology.unit.test.ts +109 -0
  415. package/src/transmission/__tests__/transmission-backdrop.dawn.test.ts +105 -0
  416. package/src/transmission/backdrop.ts +572 -0
  417. package/src/transmission/inspection.ts +190 -0
  418. package/src/transmission/oracle.ts +141 -0
  419. package/src/transmission/projection.ts +83 -0
  420. package/src/typed-render-graph-primitives.ts +162 -9
  421. package/dist/__tests__/authoring-path.unit.test.d.ts +0 -2
  422. package/dist/__tests__/authoring-path.unit.test.d.ts.map +0 -1
  423. package/dist/__tests__/backend-selection-rhi-capture.test-d.d.ts +0 -2
  424. package/dist/__tests__/backend-selection-rhi-capture.test-d.d.ts.map +0 -1
  425. package/dist/__tests__/callback-inference.test-d.d.ts +0 -2
  426. package/dist/__tests__/callback-inference.test-d.d.ts.map +0 -1
  427. package/dist/__tests__/cluster-binner.test.d.ts +0 -2
  428. package/dist/__tests__/cluster-binner.test.d.ts.map +0 -1
  429. package/dist/__tests__/cluster-grid-default-surface.unit.test.d.ts +0 -2
  430. package/dist/__tests__/cluster-grid-default-surface.unit.test.d.ts.map +0 -1
  431. package/dist/__tests__/construct-renderer-identity.unit.test.d.ts +0 -2
  432. package/dist/__tests__/construct-renderer-identity.unit.test.d.ts.map +0 -1
  433. package/dist/__tests__/device-scope-lifecycle.unit.test.d.ts +0 -2
  434. package/dist/__tests__/device-scope-lifecycle.unit.test.d.ts.map +0 -1
  435. package/dist/__tests__/device-scope-stale-matrix.unit.test.d.ts +0 -2
  436. package/dist/__tests__/device-scope-stale-matrix.unit.test.d.ts.map +0 -1
  437. package/dist/__tests__/direct-extract-consumer-contract.test-d.d.ts +0 -2
  438. package/dist/__tests__/direct-extract-consumer-contract.test-d.d.ts.map +0 -1
  439. package/dist/__tests__/direct-light-snapshot.unit.test.d.ts +0 -2
  440. package/dist/__tests__/direct-light-snapshot.unit.test.d.ts.map +0 -1
  441. package/dist/__tests__/factory-contract.integration.test.d.ts +0 -2
  442. package/dist/__tests__/factory-contract.integration.test.d.ts.map +0 -1
  443. package/dist/__tests__/fallback-row-stride-surface.unit.test.d.ts +0 -2
  444. package/dist/__tests__/fallback-row-stride-surface.unit.test.d.ts.map +0 -1
  445. package/dist/__tests__/frame-observation.contract.test.d.ts +0 -2
  446. package/dist/__tests__/frame-observation.contract.test.d.ts.map +0 -1
  447. package/dist/__tests__/fullscreen-feature-plan.unit.test.d.ts +0 -2
  448. package/dist/__tests__/fullscreen-feature-plan.unit.test.d.ts.map +0 -1
  449. package/dist/__tests__/glyph-text-layout-recovery.unit.test.d.ts +0 -2
  450. package/dist/__tests__/glyph-text-layout-recovery.unit.test.d.ts.map +0 -1
  451. package/dist/__tests__/gpu-driven-indirect-raster-evidence.d.ts +0 -6
  452. package/dist/__tests__/gpu-driven-indirect-raster-evidence.d.ts.map +0 -1
  453. package/dist/__tests__/gpu-driven-indirect-raster.browser.test.d.ts +0 -2
  454. package/dist/__tests__/gpu-driven-indirect-raster.browser.test.d.ts.map +0 -1
  455. package/dist/__tests__/gpu-driven-indirect-raster.dawn.test.d.ts +0 -2
  456. package/dist/__tests__/gpu-driven-indirect-raster.dawn.test.d.ts.map +0 -1
  457. package/dist/__tests__/gpu-driven-production.integration.test.d.ts +0 -2
  458. package/dist/__tests__/gpu-driven-production.integration.test.d.ts.map +0 -1
  459. package/dist/__tests__/gpu-driven-scaling.unit.test.d.ts +0 -2
  460. package/dist/__tests__/gpu-driven-scaling.unit.test.d.ts.map +0 -1
  461. package/dist/__tests__/gpu-driven-view-gpu-evidence.d.ts +0 -18
  462. package/dist/__tests__/gpu-driven-view-gpu-evidence.d.ts.map +0 -1
  463. package/dist/__tests__/gpu-driven-view-graph.integration.test.d.ts +0 -2
  464. package/dist/__tests__/gpu-driven-view-graph.integration.test.d.ts.map +0 -1
  465. package/dist/__tests__/gpu-driven-view-reference.d.ts +0 -15
  466. package/dist/__tests__/gpu-driven-view-reference.d.ts.map +0 -1
  467. package/dist/__tests__/gpu-driven-view.browser.test.d.ts +0 -2
  468. package/dist/__tests__/gpu-driven-view.browser.test.d.ts.map +0 -1
  469. package/dist/__tests__/gpu-driven-view.dawn.test.d.ts +0 -2
  470. package/dist/__tests__/gpu-driven-view.dawn.test.d.ts.map +0 -1
  471. package/dist/__tests__/gpu-driven-view.unit.test.d.ts +0 -2
  472. package/dist/__tests__/gpu-driven-view.unit.test.d.ts.map +0 -1
  473. package/dist/__tests__/gpu-resource-store-stride.unit.test.d.ts +0 -2
  474. package/dist/__tests__/gpu-resource-store-stride.unit.test.d.ts.map +0 -1
  475. package/dist/__tests__/gpu-scene-render-graph-gpu.d.ts +0 -7
  476. package/dist/__tests__/gpu-scene-render-graph-gpu.d.ts.map +0 -1
  477. package/dist/__tests__/gpu-scene-render-graph.browser.test.d.ts +0 -2
  478. package/dist/__tests__/gpu-scene-render-graph.browser.test.d.ts.map +0 -1
  479. package/dist/__tests__/gpu-scene-render-graph.dawn.test.d.ts +0 -2
  480. package/dist/__tests__/gpu-scene-render-graph.dawn.test.d.ts.map +0 -1
  481. package/dist/__tests__/gpu-scene-schema.unit.test.d.ts +0 -2
  482. package/dist/__tests__/gpu-scene-schema.unit.test.d.ts.map +0 -1
  483. package/dist/__tests__/gpu-scene.dawn.test.d.ts +0 -2
  484. package/dist/__tests__/gpu-scene.dawn.test.d.ts.map +0 -1
  485. package/dist/__tests__/gpu-scene.unit.test.d.ts +0 -2
  486. package/dist/__tests__/gpu-scene.unit.test.d.ts.map +0 -1
  487. package/dist/__tests__/ibl/face-uniforms.unit.test.d.ts +0 -2
  488. package/dist/__tests__/ibl/face-uniforms.unit.test.d.ts.map +0 -1
  489. package/dist/__tests__/infinite-grid-no-vertex-probe.unit.test.d.ts +0 -2
  490. package/dist/__tests__/infinite-grid-no-vertex-probe.unit.test.d.ts.map +0 -1
  491. package/dist/__tests__/injection-kind-owner.test-d.d.ts +0 -2
  492. package/dist/__tests__/injection-kind-owner.test-d.d.ts.map +0 -1
  493. package/dist/__tests__/ktx2-basis-gpu-consumer.dawn.test.d.ts +0 -2
  494. package/dist/__tests__/ktx2-basis-gpu-consumer.dawn.test.d.ts.map +0 -1
  495. package/dist/__tests__/light-buffer-layout.unit.test.d.ts +0 -2
  496. package/dist/__tests__/light-buffer-layout.unit.test.d.ts.map +0 -1
  497. package/dist/__tests__/material-contract-inventory.unit.test.d.ts +0 -2
  498. package/dist/__tests__/material-contract-inventory.unit.test.d.ts.map +0 -1
  499. package/dist/__tests__/material-cooked-projection.unit.test.d.ts +0 -2
  500. package/dist/__tests__/material-cooked-projection.unit.test.d.ts.map +0 -1
  501. package/dist/__tests__/material-snapshot-mutation.integration.test.d.ts +0 -2
  502. package/dist/__tests__/material-snapshot-mutation.integration.test.d.ts.map +0 -1
  503. package/dist/__tests__/material-static-dynamic-route.unit.test.d.ts +0 -2
  504. package/dist/__tests__/material-static-dynamic-route.unit.test.d.ts.map +0 -1
  505. package/dist/__tests__/material-stride-surface.unit.test.d.ts +0 -2
  506. package/dist/__tests__/material-stride-surface.unit.test.d.ts.map +0 -1
  507. package/dist/__tests__/mesh-buffer-usage-surface.unit.test.d.ts +0 -2
  508. package/dist/__tests__/mesh-buffer-usage-surface.unit.test.d.ts.map +0 -1
  509. package/dist/__tests__/mesh-material-bindings.unit.test.d.ts +0 -2
  510. package/dist/__tests__/mesh-material-bindings.unit.test.d.ts.map +0 -1
  511. package/dist/__tests__/mesh-stride-surface.unit.test.d.ts +0 -2
  512. package/dist/__tests__/mesh-stride-surface.unit.test.d.ts.map +0 -1
  513. package/dist/__tests__/morph-culling-reentry.dawn.test.d.ts +0 -2
  514. package/dist/__tests__/morph-culling-reentry.dawn.test.d.ts.map +0 -1
  515. package/dist/__tests__/morph-feature-gpu-work.integration.test.d.ts +0 -2
  516. package/dist/__tests__/morph-feature-gpu-work.integration.test.d.ts.map +0 -1
  517. package/dist/__tests__/morph-feature-plan.unit.test.d.ts +0 -2
  518. package/dist/__tests__/morph-feature-plan.unit.test.d.ts.map +0 -1
  519. package/dist/__tests__/morph-visual-evidence.integration.test.d.ts +0 -2
  520. package/dist/__tests__/morph-visual-evidence.integration.test.d.ts.map +0 -1
  521. package/dist/__tests__/nineslice-handle-surface.unit.test.d.ts +0 -2
  522. package/dist/__tests__/nineslice-handle-surface.unit.test.d.ts.map +0 -1
  523. package/dist/__tests__/post-process-error-policy-owner.unit.test.d.ts +0 -2
  524. package/dist/__tests__/post-process-error-policy-owner.unit.test.d.ts.map +0 -1
  525. package/dist/__tests__/prepared-buffer-kind-owner.test-d.d.ts +0 -2
  526. package/dist/__tests__/prepared-buffer-kind-owner.test-d.d.ts.map +0 -1
  527. package/dist/__tests__/prepared-graphics-capability.unit.test.d.ts +0 -2
  528. package/dist/__tests__/prepared-graphics-capability.unit.test.d.ts.map +0 -1
  529. package/dist/__tests__/prepared-graphics-generation.unit.test.d.ts +0 -2
  530. package/dist/__tests__/prepared-graphics-generation.unit.test.d.ts.map +0 -1
  531. package/dist/__tests__/prepared-graphics-lifecycle.unit.test.d.ts +0 -2
  532. package/dist/__tests__/prepared-graphics-lifecycle.unit.test.d.ts.map +0 -1
  533. package/dist/__tests__/prepared-graphics-pipeline-warmup.unit.test.d.ts +0 -2
  534. package/dist/__tests__/prepared-graphics-pipeline-warmup.unit.test.d.ts.map +0 -1
  535. package/dist/__tests__/prepared-graphics-resolver.unit.test.d.ts +0 -2
  536. package/dist/__tests__/prepared-graphics-resolver.unit.test.d.ts.map +0 -1
  537. package/dist/__tests__/prepared-graphics-snapshot.unit.test.d.ts +0 -2
  538. package/dist/__tests__/prepared-graphics-snapshot.unit.test.d.ts.map +0 -1
  539. package/dist/__tests__/prepared-graphics-store.unit.test.d.ts +0 -2
  540. package/dist/__tests__/prepared-graphics-store.unit.test.d.ts.map +0 -1
  541. package/dist/__tests__/prepared-graphics-validation.unit.test.d.ts +0 -2
  542. package/dist/__tests__/prepared-graphics-validation.unit.test.d.ts.map +0 -1
  543. package/dist/__tests__/profiler-default-off.test.d.ts +0 -2
  544. package/dist/__tests__/profiler-default-off.test.d.ts.map +0 -1
  545. package/dist/__tests__/profiler-phase-catalog.test.d.ts +0 -2
  546. package/dist/__tests__/profiler-phase-catalog.test.d.ts.map +0 -1
  547. package/dist/__tests__/profiler-phase-integration.test.d.ts +0 -2
  548. package/dist/__tests__/profiler-phase-integration.test.d.ts.map +0 -1
  549. package/dist/__tests__/profiler-phase-token.test.d.ts +0 -2
  550. package/dist/__tests__/profiler-phase-token.test.d.ts.map +0 -1
  551. package/dist/__tests__/public-surface.test-d.d.ts +0 -2
  552. package/dist/__tests__/public-surface.test-d.d.ts.map +0 -1
  553. package/dist/__tests__/recover-error-policy-owner.unit.test.d.ts +0 -2
  554. package/dist/__tests__/recover-error-policy-owner.unit.test.d.ts.map +0 -1
  555. package/dist/__tests__/render-error-code-owner.test-d.d.ts +0 -2
  556. package/dist/__tests__/render-error-code-owner.test-d.d.ts.map +0 -1
  557. package/dist/__tests__/render-error-detail-ssot.test-d.d.ts +0 -2
  558. package/dist/__tests__/render-error-detail-ssot.test-d.d.ts.map +0 -1
  559. package/dist/__tests__/render-error-exhaustive.test-d.d.ts +0 -2
  560. package/dist/__tests__/render-error-exhaustive.test-d.d.ts.map +0 -1
  561. package/dist/__tests__/render-feature-assembly.test-d.d.ts +0 -2
  562. package/dist/__tests__/render-feature-assembly.test-d.d.ts.map +0 -1
  563. package/dist/__tests__/render-feature-context.test-d.d.ts +0 -2
  564. package/dist/__tests__/render-feature-context.test-d.d.ts.map +0 -1
  565. package/dist/__tests__/render-feature-contract.test-d.d.ts +0 -2
  566. package/dist/__tests__/render-feature-contract.test-d.d.ts.map +0 -1
  567. package/dist/__tests__/render-feature-diagnostics.unit.test.d.ts +0 -2
  568. package/dist/__tests__/render-feature-diagnostics.unit.test.d.ts.map +0 -1
  569. package/dist/__tests__/render-feature-docs.unit.test.d.ts +0 -2
  570. package/dist/__tests__/render-feature-docs.unit.test.d.ts.map +0 -1
  571. package/dist/__tests__/render-feature-errors.unit.test.d.ts +0 -2
  572. package/dist/__tests__/render-feature-errors.unit.test.d.ts.map +0 -1
  573. package/dist/__tests__/render-feature-gpu-work.integration.test.d.ts +0 -2
  574. package/dist/__tests__/render-feature-gpu-work.integration.test.d.ts.map +0 -1
  575. package/dist/__tests__/render-feature-plan.test-d.d.ts +0 -2
  576. package/dist/__tests__/render-feature-plan.test-d.d.ts.map +0 -1
  577. package/dist/__tests__/render-feature-plan.unit.test.d.ts +0 -2
  578. package/dist/__tests__/render-feature-plan.unit.test.d.ts.map +0 -1
  579. package/dist/__tests__/render-feature-prepared-diagnostics.unit.test.d.ts +0 -2
  580. package/dist/__tests__/render-feature-prepared-diagnostics.unit.test.d.ts.map +0 -1
  581. package/dist/__tests__/render-feature-prepared-errors.test-d.d.ts +0 -2
  582. package/dist/__tests__/render-feature-prepared-errors.test-d.d.ts.map +0 -1
  583. package/dist/__tests__/render-feature-prepared-graphics-docs.unit.test.d.ts +0 -2
  584. package/dist/__tests__/render-feature-prepared-graphics-docs.unit.test.d.ts.map +0 -1
  585. package/dist/__tests__/render-feature-prepared-graphics-isolation.unit.test.d.ts +0 -2
  586. package/dist/__tests__/render-feature-prepared-graphics-isolation.unit.test.d.ts.map +0 -1
  587. package/dist/__tests__/render-feature-prepared-graphics-negative.test-d.d.ts +0 -2
  588. package/dist/__tests__/render-feature-prepared-graphics-negative.test-d.d.ts.map +0 -1
  589. package/dist/__tests__/render-feature-prepared-graphics.test-d.d.ts +0 -2
  590. package/dist/__tests__/render-feature-prepared-graphics.test-d.d.ts.map +0 -1
  591. package/dist/__tests__/render-feature-prepared-graphics.unit.test.d.ts +0 -2
  592. package/dist/__tests__/render-feature-prepared-graphics.unit.test.d.ts.map +0 -1
  593. package/dist/__tests__/render-feature-public-surface.test-d.d.ts +0 -2
  594. package/dist/__tests__/render-feature-public-surface.test-d.d.ts.map +0 -1
  595. package/dist/__tests__/render-feature-retry.unit.test.d.ts +0 -2
  596. package/dist/__tests__/render-feature-retry.unit.test.d.ts.map +0 -1
  597. package/dist/__tests__/render-feature-scheduling.unit.test.d.ts +0 -2
  598. package/dist/__tests__/render-feature-scheduling.unit.test.d.ts.map +0 -1
  599. package/dist/__tests__/render-feature-stage-failed-policy-owner.unit.test.d.ts +0 -2
  600. package/dist/__tests__/render-feature-stage-failed-policy-owner.unit.test.d.ts.map +0 -1
  601. package/dist/__tests__/render-feature-stage-fault-recovery.unit.test.d.ts +0 -2
  602. package/dist/__tests__/render-feature-stage-fault-recovery.unit.test.d.ts.map +0 -1
  603. package/dist/__tests__/render-feature-visibility-context.test-d.d.ts +0 -2
  604. package/dist/__tests__/render-feature-visibility-context.test-d.d.ts.map +0 -1
  605. package/dist/__tests__/render-feature-visibility-context.unit.test.d.ts +0 -2
  606. package/dist/__tests__/render-feature-visibility-context.unit.test.d.ts.map +0 -1
  607. package/dist/__tests__/render-feature-zero-work.unit.test.d.ts +0 -2
  608. package/dist/__tests__/render-feature-zero-work.unit.test.d.ts.map +0 -1
  609. package/dist/__tests__/render-graph-compute-raster-gpu.d.ts +0 -7
  610. package/dist/__tests__/render-graph-compute-raster-gpu.d.ts.map +0 -1
  611. package/dist/__tests__/render-graph-compute-raster.browser.test.d.ts +0 -2
  612. package/dist/__tests__/render-graph-compute-raster.browser.test.d.ts.map +0 -1
  613. package/dist/__tests__/render-graph-compute-raster.dawn.test.d.ts +0 -2
  614. package/dist/__tests__/render-graph-compute-raster.dawn.test.d.ts.map +0 -1
  615. package/dist/__tests__/render-graph-hzb-gpu.d.ts +0 -7
  616. package/dist/__tests__/render-graph-hzb-gpu.d.ts.map +0 -1
  617. package/dist/__tests__/render-graph-hzb.browser.test.d.ts +0 -2
  618. package/dist/__tests__/render-graph-hzb.browser.test.d.ts.map +0 -1
  619. package/dist/__tests__/render-graph-hzb.dawn.test.d.ts +0 -2
  620. package/dist/__tests__/render-graph-hzb.dawn.test.d.ts.map +0 -1
  621. package/dist/__tests__/render-graph-primitives.unit.test.d.ts +0 -2
  622. package/dist/__tests__/render-graph-primitives.unit.test.d.ts.map +0 -1
  623. package/dist/__tests__/render-graph-storage-indirect-gpu.d.ts +0 -12
  624. package/dist/__tests__/render-graph-storage-indirect-gpu.d.ts.map +0 -1
  625. package/dist/__tests__/render-graph-storage-indirect.browser.test.d.ts +0 -2
  626. package/dist/__tests__/render-graph-storage-indirect.browser.test.d.ts.map +0 -1
  627. package/dist/__tests__/render-graph-storage-indirect.dawn.test.d.ts +0 -2
  628. package/dist/__tests__/render-graph-storage-indirect.dawn.test.d.ts.map +0 -1
  629. package/dist/__tests__/render-scene-projection.unit.test.d.ts +0 -2
  630. package/dist/__tests__/render-scene-projection.unit.test.d.ts.map +0 -1
  631. package/dist/__tests__/renderer-factory-material-contract.unit.test.d.ts +0 -2
  632. package/dist/__tests__/renderer-factory-material-contract.unit.test.d.ts.map +0 -1
  633. package/dist/__tests__/renderer-lifecycle.integration.test.d.ts +0 -2
  634. package/dist/__tests__/renderer-lifecycle.integration.test.d.ts.map +0 -1
  635. package/dist/__tests__/rhi-backend-package-boundary.unit.test.d.ts +0 -2
  636. package/dist/__tests__/rhi-backend-package-boundary.unit.test.d.ts.map +0 -1
  637. package/dist/__tests__/scriptable-pack-consumer.test-d.d.ts +0 -2
  638. package/dist/__tests__/scriptable-pack-consumer.test-d.d.ts.map +0 -1
  639. package/dist/__tests__/shader-stage-surface.unit.test.d.ts +0 -2
  640. package/dist/__tests__/shader-stage-surface.unit.test.d.ts.map +0 -1
  641. package/dist/__tests__/ssao-config.unit.test.d.ts +0 -2
  642. package/dist/__tests__/ssao-config.unit.test.d.ts.map +0 -1
  643. package/dist/__tests__/ssao-kernel-surface.unit.test.d.ts +0 -2
  644. package/dist/__tests__/ssao-kernel-surface.unit.test.d.ts.map +0 -1
  645. package/dist/__tests__/standard-clustered-pipeline.unit.test.d.ts +0 -2
  646. package/dist/__tests__/standard-clustered-pipeline.unit.test.d.ts.map +0 -1
  647. package/dist/__tests__/standard-descriptors.test-d.d.ts +0 -2
  648. package/dist/__tests__/standard-descriptors.test-d.d.ts.map +0 -1
  649. package/dist/__tests__/standard-pipeline.dawn.test.d.ts +0 -2
  650. package/dist/__tests__/standard-pipeline.dawn.test.d.ts.map +0 -1
  651. package/dist/__tests__/standard-pipeline.integration.test.d.ts +0 -2
  652. package/dist/__tests__/standard-pipeline.integration.test.d.ts.map +0 -1
  653. package/dist/__tests__/standard-profile-oracle.unit.test.d.ts +0 -2
  654. package/dist/__tests__/standard-profile-oracle.unit.test.d.ts.map +0 -1
  655. package/dist/__tests__/surface-configuration.unit.test.d.ts +0 -2
  656. package/dist/__tests__/surface-configuration.unit.test.d.ts.map +0 -1
  657. package/dist/__tests__/texture-usage-surface.unit.test.d.ts +0 -2
  658. package/dist/__tests__/texture-usage-surface.unit.test.d.ts.map +0 -1
  659. package/dist/__tests__/typed-pipeline-topology.unit.test.d.ts +0 -2
  660. package/dist/__tests__/typed-pipeline-topology.unit.test.d.ts.map +0 -1
  661. package/dist/__tests__/urp-pipeline-targets.unit.test.d.ts +0 -2
  662. package/dist/__tests__/urp-pipeline-targets.unit.test.d.ts.map +0 -1
  663. package/dist/__tests__/urp-pipeline.unit.test.d.ts +0 -2
  664. package/dist/__tests__/urp-pipeline.unit.test.d.ts.map +0 -1
  665. package/dist/__tests__/vertex-color-pipeline.unit.test.d.ts +0 -2
  666. package/dist/__tests__/vertex-color-pipeline.unit.test.d.ts.map +0 -1
  667. package/dist/__tests__/vertex-layout-consumer.unit.test.d.ts +0 -2
  668. package/dist/__tests__/vertex-layout-consumer.unit.test.d.ts.map +0 -1
  669. package/dist/__tests__/visibility-diagnostics.integration.test.d.ts +0 -2
  670. package/dist/__tests__/visibility-diagnostics.integration.test.d.ts.map +0 -1
  671. package/dist/__tests__/visibility-extract.integration.test.d.ts +0 -2
  672. package/dist/__tests__/visibility-extract.integration.test.d.ts.map +0 -1
  673. package/dist/__tests__/visibility-extract.perf.test.d.ts +0 -2
  674. package/dist/__tests__/visibility-extract.perf.test.d.ts.map +0 -1
  675. package/dist/__tests__/visibility-feature-dedup.integration.test.d.ts +0 -2
  676. package/dist/__tests__/visibility-feature-dedup.integration.test.d.ts.map +0 -1
  677. package/dist/__tests__/visibility-hidden-report.unit.test.d.ts +0 -2
  678. package/dist/__tests__/visibility-hidden-report.unit.test.d.ts.map +0 -1
  679. package/dist/__tests__/visibility-instances.integration.test.d.ts +0 -2
  680. package/dist/__tests__/visibility-instances.integration.test.d.ts.map +0 -1
  681. package/dist/__tests__/visibility-multi-world.integration.test.d.ts +0 -2
  682. package/dist/__tests__/visibility-multi-world.integration.test.d.ts.map +0 -1
  683. package/dist/__tests__/visibility-producer-matrix.integration.test.d.ts +0 -2
  684. package/dist/__tests__/visibility-producer-matrix.integration.test.d.ts.map +0 -1
  685. package/dist/__tests__/visibility-producer-matrix.unit.test.d.ts +0 -2
  686. package/dist/__tests__/visibility-producer-matrix.unit.test.d.ts.map +0 -1
  687. package/dist/__tests__/visibility-public-surface.test-d.d.ts +0 -2
  688. package/dist/__tests__/visibility-public-surface.test-d.d.ts.map +0 -1
  689. package/dist/__tests__/visibility-resolver.unit.test.d.ts +0 -2
  690. package/dist/__tests__/visibility-resolver.unit.test.d.ts.map +0 -1
  691. package/dist/__tests__/visibility-resource-short-circuit.unit.test.d.ts +0 -2
  692. package/dist/__tests__/visibility-resource-short-circuit.unit.test.d.ts.map +0 -1
  693. package/dist/__tests__/visibility-snapshot-contract.unit.test.d.ts +0 -2
  694. package/dist/__tests__/visibility-snapshot-contract.unit.test.d.ts.map +0 -1
  695. package/dist/__tests__/vocabulary.unit.test.d.ts +0 -2
  696. package/dist/__tests__/vocabulary.unit.test.d.ts.map +0 -1
  697. package/dist/chunk-CQXZC5XI.mjs.map +0 -1
  698. package/dist/chunk-QC4VW3XJ.mjs.map +0 -1
  699. package/dist/chunk-SCRSAK5B.mjs.map +0 -1
  700. package/dist/components/__tests__/schema-contract.unit.test.d.ts +0 -2
  701. package/dist/components/__tests__/schema-contract.unit.test.d.ts.map +0 -1
  702. package/dist/components/__tests__/schema-parity.unit.test.d.ts +0 -2
  703. package/dist/components/__tests__/schema-parity.unit.test.d.ts.map +0 -1
  704. package/dist/components/__tests__/visibility-schema.unit.test.d.ts +0 -2
  705. package/dist/components/__tests__/visibility-schema.unit.test.d.ts.map +0 -1
  706. package/dist/record/__tests__/material-bg-cache.unit.test.d.ts +0 -2
  707. package/dist/record/__tests__/material-bg-cache.unit.test.d.ts.map +0 -1
  708. package/dist/record/__tests__/material-slot-plan.unit.test.d.ts +0 -2
  709. package/dist/record/__tests__/material-slot-plan.unit.test.d.ts.map +0 -1
  710. package/dist/record/__tests__/typed-frame-graph-plan.unit.test.d.ts +0 -2
  711. package/dist/record/__tests__/typed-frame-graph-plan.unit.test.d.ts.map +0 -1
  712. package/dist/record/__tests__/video-texture-view.unit.test.d.ts +0 -2
  713. package/dist/record/__tests__/video-texture-view.unit.test.d.ts.map +0 -1
@@ -1,17 +1,112 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { resolve } from 'node:path';
1
3
  import { World } from '@forgeax/engine-ecs';
2
4
  import { createProfiler } from '@forgeax/engine-profiler';
3
- import { Camera, type RenderFeature } from '@forgeax/engine-render';
4
- import { rhi } from '@forgeax/engine-rhi-null';
5
- import { Transform } from '@forgeax/engine-scene';
6
- import { ok } from '@forgeax/engine-types';
7
- import { describe, expect, it } from 'vitest';
5
+ import { RhiError, type ShaderModule } from '@forgeax/engine-rhi';
6
+ import {
7
+ RhiNullAdapter,
8
+ RhiNullCommandEncoder,
9
+ RhiNullDevice,
10
+ RhiNullQueue,
11
+ rhi,
12
+ } from '@forgeax/engine-rhi-null';
13
+ import { registerPropagateTransforms, Transform } from '@forgeax/engine-scene';
14
+ import { err, ok } from '@forgeax/engine-types';
15
+ import { describe, expect, it, vi } from 'vitest';
16
+ import type { RhiBackendPack } from '../assembly/backend-contract';
8
17
  import { createRenderer as constructRenderer } from '../assembly/factory';
18
+ import {
19
+ ANTIALIAS_NONE,
20
+ ANTIALIAS_TAA,
21
+ BLOOM_DISABLED,
22
+ BLOOM_ENABLED,
23
+ Camera as LocalCamera,
24
+ TONEMAP_ACES_FILMIC,
25
+ } from '../components/camera';
26
+ import type { RenderFeature } from '../features/types';
27
+ import { renderLifecycleManifestUrl } from './shader-manifest-fixture';
9
28
 
10
29
  function manifestUrl(): string {
11
30
  return `data:application/json,${encodeURIComponent(JSON.stringify({ schemaVersion: '1.0.0', entries: [] }))}`;
12
31
  }
13
32
 
33
+ function deferred<T>() {
34
+ let resolve!: (value: T | PromiseLike<T>) => void;
35
+ let reject!: (reason?: unknown) => void;
36
+ const promise = new Promise<T>((resolvePromise, rejectPromise) => {
37
+ resolve = resolvePromise;
38
+ reject = rejectPromise;
39
+ });
40
+ return { promise, resolve, reject };
41
+ }
42
+
43
+ async function createBloomScenario() {
44
+ const canvas = { width: 64, height: 64, getContext: () => null };
45
+ const renderer = await constructRenderer(
46
+ canvas,
47
+ { rhi },
48
+ { shaderManifestUrl: renderLifecycleManifestUrl() },
49
+ );
50
+ if (!(await renderer.initialization).ok) throw new Error('Bloom renderer initialization failed');
51
+ const world = new World();
52
+ const attached = renderer.attach(world);
53
+ if (!attached.ok) throw attached.error;
54
+ registerPropagateTransforms(world);
55
+ const camera = world
56
+ .spawn(
57
+ { component: Transform, data: { pos: [0, 0, 4] } },
58
+ {
59
+ component: LocalCamera,
60
+ data: {
61
+ fov: 1,
62
+ aspect: 1,
63
+ near: 0.1,
64
+ far: 100,
65
+ antialias: ANTIALIAS_NONE,
66
+ tonemap: TONEMAP_ACES_FILMIC,
67
+ bloom: BLOOM_ENABLED,
68
+ },
69
+ },
70
+ )
71
+ .unwrap();
72
+ const frameInput = () => ({
73
+ leases: [attached.value],
74
+ camera: { lease: attached.value },
75
+ environment: { lease: attached.value },
76
+ });
77
+ const draw = () => renderer.draw(frameInput());
78
+ const setBloom = (bloom: 'on' | 'off') => {
79
+ world
80
+ .set(camera, LocalCamera, {
81
+ bloom: bloom === 'on' ? BLOOM_ENABLED : BLOOM_DISABLED,
82
+ tonemap: TONEMAP_ACES_FILMIC,
83
+ })
84
+ .unwrap();
85
+ const updated = world.update();
86
+ if (!updated.ok) throw updated.error;
87
+ };
88
+ const dispose = async () => {
89
+ await renderer.dispose();
90
+ };
91
+ return { renderer, draw, setBloom, dispose, canvas };
92
+ }
93
+
14
94
  describe('factory contract', () => {
95
+ it('keeps the public output-transform identity and closes removed tone symbols', () => {
96
+ const source = [
97
+ 'render-contract.ts',
98
+ 'render-pipeline.ts',
99
+ 'typed-render-graph-primitives.ts',
100
+ 'assembly/factory.ts',
101
+ ]
102
+ .map((file) => readFileSync(resolve(import.meta.dirname, '..', file), 'utf8'))
103
+ .join('\n');
104
+ expect(source).toContain('STANDARD_OUTPUT_TRANSFORM_FEATURE_ID');
105
+ expect(source).not.toContain('STANDARD_TONEMAP_FEATURE_ID');
106
+ expect(source).not.toContain('addTypedTonemapPass');
107
+ expect(source).not.toContain('tone-output');
108
+ });
109
+
15
110
  it('releases its profiler catalog contribution on dispose', async () => {
16
111
  const profiler = createProfiler();
17
112
  const renderer = await constructRenderer(
@@ -19,7 +114,6 @@ describe('factory contract', () => {
19
114
  { profiler, rhi },
20
115
  { shaderManifestUrl: manifestUrl() },
21
116
  );
22
-
23
117
  expect(profiler.phaseCatalog.render.length).toBeGreaterThan(0);
24
118
  renderer.dispose();
25
119
  expect(profiler.phaseCatalog.render).toEqual([]);
@@ -54,6 +148,33 @@ describe('factory contract', () => {
54
148
  renderer.dispose();
55
149
  });
56
150
 
151
+ it('projects stable output and observation identity without pixel payloads', async () => {
152
+ const renderer = await constructRenderer(
153
+ { getContext: () => null },
154
+ { rhi },
155
+ { shaderManifestUrl: manifestUrl() },
156
+ );
157
+ const inspection = renderer.inspect() as unknown as Record<string, unknown>;
158
+ expect(inspection).toMatchObject({
159
+ // Before the first draw there is no committed graph from which to
160
+ // claim an output transform or an intermediate target. Inspection is
161
+ // intentionally conservative instead of projecting profile defaults.
162
+ displayEncoded: false,
163
+ surfaceStorage: expect.any(String),
164
+ surfaceDisplay: expect.any(String),
165
+ endpoint: 'surface.storage.raw',
166
+ capability: 'unavailable',
167
+ observation: {
168
+ observationId: expect.any(String),
169
+ frameId: expect.any(Number),
170
+ },
171
+ });
172
+ expect(inspection).not.toHaveProperty('outputTransform');
173
+ expect(inspection).not.toHaveProperty('intermediateFormat');
174
+ expect(inspection).not.toHaveProperty('pixelPayload');
175
+ renderer.dispose();
176
+ });
177
+
57
178
  it('does not promote a graph feature without a fullscreen effect into post-effects', async () => {
58
179
  let planCalls = 0;
59
180
  const feature = {
@@ -65,7 +186,7 @@ describe('factory contract', () => {
65
186
  },
66
187
  };
67
188
  const renderer = await constructRenderer(
68
- { getContext: () => null },
189
+ { width: 64, height: 64, getContext: () => null },
69
190
  { rhi, features: [feature] },
70
191
  { shaderManifestUrl: manifestUrl() },
71
192
  );
@@ -134,7 +255,7 @@ describe('factory contract', () => {
134
255
  },
135
256
  };
136
257
  const renderer = await constructRenderer(
137
- { getContext: () => null },
258
+ { width: 64, height: 64, getContext: () => null },
138
259
  { rhi, features: [feature] },
139
260
  { shaderManifestUrl: manifestUrl() },
140
261
  );
@@ -144,10 +265,11 @@ describe('factory contract', () => {
144
265
  const attached = renderer.attach(world);
145
266
  expect(attached.ok).toBe(true);
146
267
  if (!attached.ok) return;
268
+ registerPropagateTransforms(world);
147
269
  world
148
270
  .spawn(
149
271
  { component: Transform, data: { pos: [0, 0, 0], quat: [0, 0, 0, 1], scale: [1, 1, 1] } },
150
- { component: Camera, data: { fov: 1, aspect: 1, near: 0.1, far: 100 } },
272
+ { component: LocalCamera, data: { fov: 1, aspect: 1, near: 0.1, far: 100 } },
151
273
  )
152
274
  .unwrap();
153
275
  expect(world.update().ok).toBe(true);
@@ -277,4 +399,1253 @@ describe('factory contract', () => {
277
399
  renderer.dispose();
278
400
  expect(unconfigureCalls).toBe(1);
279
401
  });
402
+
403
+ it('invalidates deferred TAA shader epochs across off and dispose', async () => {
404
+ const pending: Array<{
405
+ readonly label: string;
406
+ readonly promise: Promise<ReturnType<typeof ok<ShaderModule>>>;
407
+ readonly resolve: (value: ReturnType<typeof ok<ShaderModule>>) => void;
408
+ }> = [];
409
+ const moduleCalls: string[] = [];
410
+ const backend: RhiBackendPack = {
411
+ rhi,
412
+ createShaderModule: (_device, desc) => {
413
+ const label = desc.label ?? '<unlabeled>';
414
+ moduleCalls.push(label);
415
+ if (label !== 'post-process-forgeax.taa-resolve-module') {
416
+ return Promise.resolve(ok({} as ShaderModule));
417
+ }
418
+ let resolve!: (value: ReturnType<typeof ok<ShaderModule>>) => void;
419
+ const promise = new Promise<ReturnType<typeof ok<ShaderModule>>>((complete) => {
420
+ resolve = complete;
421
+ });
422
+ pending.push({ label, promise, resolve });
423
+ return promise;
424
+ },
425
+ };
426
+ const renderer = await constructRenderer(
427
+ { width: 64, height: 64, getContext: () => null },
428
+ undefined,
429
+ { shaderManifestUrl: renderLifecycleManifestUrl() },
430
+ backend,
431
+ );
432
+ await vi.waitFor(() => expect(pending).toHaveLength(1));
433
+ const initialization = renderer.initialization;
434
+ const firstPromise = pending[0]?.promise;
435
+ pending[0]?.resolve(ok({} as ShaderModule));
436
+ expect((await initialization).ok).toBe(true);
437
+
438
+ const world = new World();
439
+ const attached = renderer.attach(world);
440
+ expect(attached.ok).toBe(true);
441
+ if (!attached.ok) return;
442
+ registerPropagateTransforms(world);
443
+ const camera = world
444
+ .spawn(
445
+ { component: Transform, data: { pos: [0, 0, 4] } },
446
+ {
447
+ component: LocalCamera,
448
+ data: {
449
+ fov: 1,
450
+ aspect: 1,
451
+ near: 0.1,
452
+ far: 100,
453
+ antialias: ANTIALIAS_TAA,
454
+ },
455
+ },
456
+ )
457
+ .unwrap();
458
+ expect(world.update().ok).toBe(true);
459
+ expect([...world.query({ read: [LocalCamera], with: [Transform] }).unwrap()]).toHaveLength(1);
460
+ const frameInput = () => ({
461
+ leases: [attached.value],
462
+ camera: { lease: attached.value },
463
+ environment: { lease: attached.value },
464
+ });
465
+
466
+ const first = renderer.draw(frameInput());
467
+ expect(first.ok).toBe(true);
468
+ expect(pending).toHaveLength(1);
469
+
470
+ world.set(camera, LocalCamera, { antialias: ANTIALIAS_NONE }).unwrap();
471
+ expect(world.update().ok).toBe(true);
472
+ const off = renderer.draw(frameInput());
473
+ expect(off.ok).toBe(true);
474
+
475
+ expect(
476
+ moduleCalls.filter((label) => label === 'post-process-forgeax.taa-resolve-module'),
477
+ ).toHaveLength(1);
478
+
479
+ world.set(camera, LocalCamera, { antialias: ANTIALIAS_TAA }).unwrap();
480
+ expect(world.update().ok).toBe(true);
481
+ const second = renderer.draw(frameInput());
482
+ expect(second.ok).toBe(false);
483
+ expect(pending).toHaveLength(2);
484
+ expect(pending[1]?.promise).not.toBe(firstPromise);
485
+
486
+ pending[1]?.resolve(ok({} as ShaderModule));
487
+ await pending[1]?.promise;
488
+ expect(renderer.draw(frameInput()).ok).toBe(true);
489
+ world.set(camera, LocalCamera, { antialias: ANTIALIAS_NONE }).unwrap();
490
+ expect(world.update().ok).toBe(true);
491
+ expect(renderer.draw(frameInput()).ok).toBe(true);
492
+ world.set(camera, LocalCamera, { antialias: ANTIALIAS_TAA }).unwrap();
493
+ expect(world.update().ok).toBe(true);
494
+ const replacement = renderer.draw(frameInput());
495
+ expect(replacement.ok).toBe(false);
496
+ expect(pending).toHaveLength(3);
497
+ expect(pending[2]?.promise).not.toBe(pending[1]?.promise);
498
+
499
+ expect((await renderer.dispose()).ok).toBe(true);
500
+ expect((await renderer.dispose()).ok).toBe(true);
501
+ pending[2]?.resolve(ok({} as ShaderModule));
502
+ await pending[2]?.promise;
503
+ expect(
504
+ moduleCalls.filter((label) => label === 'post-process-forgeax.taa-resolve-module'),
505
+ ).toHaveLength(3);
506
+ });
507
+
508
+ it('rebuilds a TAA graph for a same-mode target resize', async () => {
509
+ const moduleCalls: string[] = [];
510
+ const created = new Map<object, string>();
511
+ const destroyed = new Set<object>();
512
+ let wrappedDevice: object | undefined;
513
+ const trackDevice = (device: object): object =>
514
+ new Proxy(device, {
515
+ get(target, property, receiver) {
516
+ const value = Reflect.get(target, property, target);
517
+ if (typeof value !== 'function') return Reflect.get(target, property, receiver);
518
+ return (...args: unknown[]) => {
519
+ const label =
520
+ typeof args[0] === 'object' && args[0] !== null && 'label' in args[0]
521
+ ? String((args[0] as { label?: unknown }).label ?? '')
522
+ : '';
523
+ const result = value.apply(target, args);
524
+ if (property === 'createTexture' && result?.ok && typeof result.value === 'object') {
525
+ created.set(result.value, label);
526
+ }
527
+ if (property === 'destroyTexture') {
528
+ const handle = args[0];
529
+ if (typeof handle === 'object' && handle !== null) destroyed.add(handle);
530
+ }
531
+ return result;
532
+ };
533
+ },
534
+ });
535
+ const adapter = new RhiNullAdapter();
536
+ const instrumentedRhi = {
537
+ ...rhi,
538
+ requestAdapter: async () => {
539
+ const deviceResult = await adapter.requestDevice();
540
+ if (!deviceResult.ok) return deviceResult;
541
+ wrappedDevice = trackDevice(deviceResult.value);
542
+ return ok({
543
+ features: adapter.features,
544
+ limits: adapter.limits,
545
+ requestDevice: async () => ok(wrappedDevice as never),
546
+ });
547
+ },
548
+ } as never;
549
+ const backend: RhiBackendPack = {
550
+ rhi: instrumentedRhi,
551
+ createShaderModule: async (_device, desc) => {
552
+ moduleCalls.push(desc.label ?? '<unlabeled>');
553
+ return ok({} as ShaderModule);
554
+ },
555
+ };
556
+ const canvas = { width: 64, height: 64, getContext: () => null };
557
+ const renderer = await constructRenderer(
558
+ canvas,
559
+ undefined,
560
+ { shaderManifestUrl: renderLifecycleManifestUrl() },
561
+ backend,
562
+ );
563
+ expect((await renderer.initialization).ok).toBe(true);
564
+ expect(wrappedDevice).toBeDefined();
565
+
566
+ const world = new World();
567
+ const attached = renderer.attach(world);
568
+ expect(attached.ok).toBe(true);
569
+ if (!attached.ok) return;
570
+ registerPropagateTransforms(world);
571
+ const camera = world
572
+ .spawn(
573
+ { component: Transform, data: { pos: [0, 0, 4] } },
574
+ {
575
+ component: LocalCamera,
576
+ data: { fov: 1, aspect: 1, near: 0.1, far: 100, antialias: ANTIALIAS_TAA },
577
+ },
578
+ )
579
+ .unwrap();
580
+ expect(world.update().ok).toBe(true);
581
+ const frameInput = () => ({
582
+ leases: [attached.value],
583
+ camera: { lease: attached.value },
584
+ environment: { lease: attached.value },
585
+ });
586
+ let first = renderer.draw(frameInput());
587
+ if (!first.ok) {
588
+ await Promise.resolve();
589
+ first = renderer.draw(frameInput());
590
+ }
591
+ expect(first.ok).toBe(true);
592
+ const firstGeneration = [...created.entries()]
593
+ .filter(([_, label]) => label.startsWith('taa-history-'))
594
+ .slice(-4);
595
+ expect(firstGeneration).toHaveLength(4);
596
+ expect(
597
+ moduleCalls.filter((label) => label === 'post-process-forgeax.taa-resolve-module'),
598
+ ).toHaveLength(1);
599
+ const firstFrame = renderer.inspect().frame.frameId;
600
+
601
+ canvas.width = 96;
602
+ canvas.height = 80;
603
+ expect(world.update().ok).toBe(true);
604
+ const replacement = renderer.draw(frameInput());
605
+ expect(replacement.ok).toBe(true);
606
+ if (replacement.ok) expect(replacement.value).toMatchObject({ frameId: firstFrame + 1 });
607
+ const replacementGeneration = [...created.entries()]
608
+ .filter(([_, label]) => label.startsWith('taa-history-'))
609
+ .slice(-4);
610
+ expect(replacementGeneration).toHaveLength(4);
611
+ expect(replacementGeneration.map(([handle]) => handle)).not.toEqual(
612
+ firstGeneration.map(([handle]) => handle),
613
+ );
614
+ await Promise.resolve();
615
+ await Promise.resolve();
616
+ expect(firstGeneration.every(([handle]) => destroyed.has(handle))).toBe(true);
617
+ expect(replacementGeneration.every(([handle]) => !destroyed.has(handle))).toBe(true);
618
+ expect(renderer.inspect().frame.frameId).toBe(firstFrame + 1);
619
+ expect(renderer.inspect().state).toBe('alive');
620
+ expect(
621
+ moduleCalls.filter((label) => label === 'post-process-forgeax.taa-resolve-module'),
622
+ ).toHaveLength(1);
623
+ world.set(camera, LocalCamera, { antialias: ANTIALIAS_NONE }).unwrap();
624
+ expect(world.update().ok).toBe(true);
625
+ expect(renderer.draw(frameInput()).ok).toBe(true);
626
+ await Promise.resolve();
627
+ await Promise.resolve();
628
+ expect(replacementGeneration.every(([handle]) => destroyed.has(handle))).toBe(true);
629
+ expect((await renderer.dispose()).ok).toBe(true);
630
+ });
631
+
632
+ it('retires factory-owned TAA textures and params on an off submit', async () => {
633
+ const created = new Map<object, string>();
634
+ const destroyed = new Set<object>();
635
+ let wrappedDevice: object | undefined;
636
+ const trackDevice = (device: object): object =>
637
+ new Proxy(device, {
638
+ get(target, property, receiver) {
639
+ const value = Reflect.get(target, property, target);
640
+ if (typeof value !== 'function') return Reflect.get(target, property, receiver);
641
+ return (...args: unknown[]) => {
642
+ const label =
643
+ typeof args[0] === 'object' && args[0] !== null && 'label' in args[0]
644
+ ? String((args[0] as { label?: unknown }).label ?? '')
645
+ : '';
646
+ const result = value.apply(target, args);
647
+ if (property === 'createTexture' || property === 'createBuffer') {
648
+ if (result?.ok && typeof result.value === 'object' && result.value !== null) {
649
+ created.set(result.value, label);
650
+ }
651
+ }
652
+ if (property === 'destroyTexture' || property === 'destroyBuffer') {
653
+ const handle = args[0];
654
+ if (typeof handle === 'object' && handle !== null) destroyed.add(handle);
655
+ }
656
+ return result;
657
+ };
658
+ },
659
+ });
660
+ const adapter = new RhiNullAdapter();
661
+ const instrumentedRhi = {
662
+ ...rhi,
663
+ requestAdapter: async () => {
664
+ const deviceResult = await adapter.requestDevice();
665
+ if (!deviceResult.ok) return deviceResult;
666
+ wrappedDevice = trackDevice(deviceResult.value);
667
+ return ok({
668
+ features: adapter.features,
669
+ limits: adapter.limits,
670
+ requestDevice: async () => ok(wrappedDevice as never),
671
+ });
672
+ },
673
+ } as never;
674
+ const renderer = await constructRenderer(
675
+ { width: 64, height: 64, getContext: () => null },
676
+ { rhi: instrumentedRhi },
677
+ { shaderManifestUrl: renderLifecycleManifestUrl() },
678
+ );
679
+ expect((await renderer.initialization).ok).toBe(true);
680
+ expect(wrappedDevice).toBeDefined();
681
+ const world = new World();
682
+ const attached = renderer.attach(world);
683
+ expect(attached.ok).toBe(true);
684
+ if (!attached.ok) return;
685
+ registerPropagateTransforms(world);
686
+ const camera = world
687
+ .spawn(
688
+ { component: Transform, data: { pos: [0, 0, 4] } },
689
+ {
690
+ component: LocalCamera,
691
+ data: { fov: 1, aspect: 1, near: 0.1, far: 100, antialias: ANTIALIAS_TAA },
692
+ },
693
+ )
694
+ .unwrap();
695
+ const frameInput = () => ({
696
+ leases: [attached.value],
697
+ camera: { lease: attached.value },
698
+ environment: { lease: attached.value },
699
+ });
700
+ expect(world.update().ok).toBe(true);
701
+ let first = renderer.draw(frameInput());
702
+ if (!first.ok) {
703
+ await Promise.resolve();
704
+ first = renderer.draw(frameInput());
705
+ }
706
+ expect(first.ok).toBe(true);
707
+ const historyHandles = [...created.entries()].filter(([_, label]) =>
708
+ label.startsWith('taa-history-'),
709
+ );
710
+ expect(historyHandles.length).toBeGreaterThanOrEqual(4);
711
+ expect([...created.values()]).toContain('taa-resolve-params');
712
+
713
+ world.set(camera, LocalCamera, { antialias: ANTIALIAS_NONE }).unwrap();
714
+ expect(world.update().ok).toBe(true);
715
+ expect(renderer.draw(frameInput()).ok).toBe(true);
716
+ await Promise.resolve();
717
+ await Promise.resolve();
718
+ expect(renderer.inspect().perFramePassNames).not.toContain('taa-resolve');
719
+ expect(historyHandles.every(([handle]) => destroyed.has(handle))).toBe(true);
720
+ const params = [...created.entries()].filter(([, label]) => label === 'taa-resolve-params');
721
+ expect(params.length).toBeGreaterThan(0);
722
+ expect(params.every(([handle]) => destroyed.has(handle))).toBe(true);
723
+ expect((await renderer.dispose()).ok).toBe(true);
724
+ expect((await renderer.dispose()).ok).toBe(true);
725
+ expect(destroyed.size).toBeGreaterThanOrEqual(historyHandles.length + 1);
726
+ });
727
+
728
+ it('projects Bloom lifecycle through the factory-created per-pass owner', async () => {
729
+ const renderer = await constructRenderer(
730
+ { width: 64, height: 64, getContext: () => null },
731
+ { rhi },
732
+ { shaderManifestUrl: renderLifecycleManifestUrl() },
733
+ );
734
+ expect((await renderer.initialization).ok).toBe(true);
735
+ const world = new World();
736
+ const attached = renderer.attach(world);
737
+ expect(attached.ok).toBe(true);
738
+ if (!attached.ok) return;
739
+ registerPropagateTransforms(world);
740
+ const camera = world
741
+ .spawn(
742
+ { component: Transform, data: { pos: [0, 0, 4] } },
743
+ {
744
+ component: LocalCamera,
745
+ data: {
746
+ fov: 1,
747
+ aspect: 1,
748
+ near: 0.1,
749
+ far: 100,
750
+ antialias: ANTIALIAS_NONE,
751
+ tonemap: TONEMAP_ACES_FILMIC,
752
+ bloom: BLOOM_ENABLED,
753
+ },
754
+ },
755
+ )
756
+ .unwrap();
757
+ expect(world.update().ok).toBe(true);
758
+ expect(renderer.inspect().bloom.state).toBe('off');
759
+ const on = renderer.draw({
760
+ leases: [attached.value],
761
+ camera: { lease: attached.value },
762
+ environment: { lease: attached.value },
763
+ });
764
+ expect(on.ok).toBe(true);
765
+ const onInspection = renderer.inspect().bloom;
766
+ expect(onInspection.state).toBe('active');
767
+ expect(onInspection.graphStatus).toBe('valid');
768
+ expect(onInspection.targetCount).toBe(4);
769
+ expect(onInspection.targetBytes).toBe((64 * 64 + 3 * 32 * 32) * 8);
770
+ expect(onInspection.passCount).toBe(4);
771
+ expect(onInspection.uploadCount).toBe(4);
772
+ expect(onInspection.bindGroupCount).toBe(4);
773
+ expect(onInspection.encodeCount).toBe(4);
774
+ expect(JSON.parse(JSON.stringify(onInspection))).toEqual(onInspection);
775
+ const firstGeneration = onInspection.generation;
776
+ expect(firstGeneration).toBeGreaterThan(0);
777
+
778
+ world.set(camera, LocalCamera, { bloom: BLOOM_DISABLED }).unwrap();
779
+ expect(world.update().ok).toBe(true);
780
+ const off = renderer.draw({
781
+ leases: [attached.value],
782
+ camera: { lease: attached.value },
783
+ environment: { lease: attached.value },
784
+ });
785
+ expect(off.ok).toBe(true);
786
+ const offInspection = renderer.inspect().bloom;
787
+ expect(['retiring', 'off']).toContain(offInspection.state);
788
+ expect(offInspection.resourceCount).toBeGreaterThan(0);
789
+ expect(offInspection.passCount).toBe(0);
790
+ const retiringGeneration = offInspection.generation;
791
+ expect(retiringGeneration).toBe(firstGeneration);
792
+ world.set(camera, LocalCamera, { bloom: BLOOM_ENABLED }).unwrap();
793
+ expect(world.update().ok).toBe(true);
794
+ expect(
795
+ renderer.draw({
796
+ leases: [attached.value],
797
+ camera: { lease: attached.value },
798
+ environment: { lease: attached.value },
799
+ }).ok,
800
+ ).toBe(true);
801
+ const recoveredInspection = renderer.inspect().bloom;
802
+ expect(recoveredInspection.state).toBe('active');
803
+ expect(recoveredInspection.resourceCount).toBeGreaterThan(0);
804
+ expect(recoveredInspection.passCount).toBe(4);
805
+ expect(recoveredInspection.uploadCount).toBe(4);
806
+ expect(recoveredInspection.bindGroupCount).toBe(4);
807
+ expect(recoveredInspection.encodeCount).toBe(4);
808
+ expect(recoveredInspection.generation).toBeGreaterThan(firstGeneration);
809
+ await Promise.resolve();
810
+ await Promise.resolve();
811
+ expect(renderer.inspect().bloom.state).toBe('active');
812
+ expect((await renderer.dispose()).ok).toBe(true);
813
+ expect(renderer.inspect().bloom.state).toBe('off');
814
+ expect((await renderer.dispose()).ok).toBe(true);
815
+ });
816
+
817
+ it('publishes Bloom record receipts only after a successful submit', async () => {
818
+ const scenario = await createBloomScenario();
819
+ const submit = vi.spyOn(RhiNullQueue.prototype, 'submit');
820
+ const failure = new RhiError({
821
+ code: 'webgpu-runtime-error',
822
+ expected: 'Bloom queue submission succeeds',
823
+ hint: 'repair the queue and retry the frame',
824
+ });
825
+ try {
826
+ expect(scenario.draw().ok).toBe(true);
827
+ expect(scenario.renderer.inspect().bloom).toMatchObject({
828
+ uploadCount: 4,
829
+ bindGroupCount: 4,
830
+ encodeCount: 4,
831
+ });
832
+
833
+ submit.mockReturnValueOnce(err(failure));
834
+ expect(scenario.draw().ok).toBe(false);
835
+ expect(scenario.renderer.inspect().bloom).toMatchObject({
836
+ state: 'active',
837
+ uploadCount: 4,
838
+ bindGroupCount: 4,
839
+ encodeCount: 4,
840
+ });
841
+
842
+ expect(scenario.draw().ok).toBe(true);
843
+ expect(scenario.renderer.inspect().bloom).toMatchObject({
844
+ uploadCount: 4,
845
+ bindGroupCount: 4,
846
+ encodeCount: 4,
847
+ });
848
+ } finally {
849
+ submit.mockRestore();
850
+ await scenario.dispose();
851
+ }
852
+ });
853
+
854
+ it('aborts a Bloom pass write failure without publishing partial receipts', async () => {
855
+ const originalWriteBuffer = RhiNullQueue.prototype.writeBuffer;
856
+ const createBuffer = vi.spyOn(RhiNullDevice.prototype, 'createBuffer');
857
+ const writeBuffer = vi.spyOn(RhiNullQueue.prototype, 'writeBuffer');
858
+ const submit = vi.spyOn(RhiNullQueue.prototype, 'submit');
859
+ const scenario = await createBloomScenario();
860
+ const failure = new RhiError({
861
+ code: 'webgpu-runtime-error',
862
+ expected: 'Bloom blur-H params upload succeeds',
863
+ hint: 'repair the Bloom params buffer and retry the frame',
864
+ });
865
+ const errors: unknown[] = [];
866
+ const unsubscribe = scenario.renderer.onError((error) => errors.push(error));
867
+ try {
868
+ expect(scenario.draw().ok).toBe(true);
869
+ const blurHParams = createBuffer.mock.calls.reduce<object | undefined>(
870
+ (found, args, index) => {
871
+ if (found !== undefined || args[0].label !== 'bloom-blur-h-params-ubo') return found;
872
+ const result = createBuffer.mock.results[index];
873
+ if (result?.type !== 'return' || !result.value.ok) return undefined;
874
+ return result.value.value as object;
875
+ },
876
+ undefined,
877
+ );
878
+ expect(blurHParams).toBeDefined();
879
+ const lkg = scenario.renderer.inspect().bloom;
880
+ expect(lkg).toMatchObject({ uploadCount: 4, bindGroupCount: 4, encodeCount: 4 });
881
+ const submitCount = submit.mock.calls.length;
882
+
883
+ let injected = false;
884
+ writeBuffer.mockImplementation((buffer, ...args) => {
885
+ if (buffer === blurHParams && !injected) {
886
+ injected = true;
887
+ return err(failure);
888
+ }
889
+ return originalWriteBuffer.call(RhiNullQueue.prototype, buffer, ...args);
890
+ });
891
+ expect(scenario.draw().ok).toBe(false);
892
+ expect(injected).toBe(true);
893
+ expect(submit).toHaveBeenCalledTimes(submitCount);
894
+ expect(errors).toHaveLength(1);
895
+ expect(errors[0]).toMatchObject({
896
+ code: 'pass-encode-failed',
897
+ detail: { passName: 'bloom-blur-h', cause: failure },
898
+ });
899
+ expect(scenario.renderer.inspect().bloom).toMatchObject({
900
+ state: 'active',
901
+ generation: lkg.generation,
902
+ uploadCount: 4,
903
+ bindGroupCount: 4,
904
+ encodeCount: 4,
905
+ });
906
+
907
+ writeBuffer.mockRestore();
908
+ expect(scenario.draw().ok).toBe(true);
909
+ expect(submit).toHaveBeenCalledTimes(submitCount + 1);
910
+ expect(scenario.renderer.inspect().bloom).toMatchObject({
911
+ uploadCount: 4,
912
+ bindGroupCount: 4,
913
+ encodeCount: 4,
914
+ });
915
+ } finally {
916
+ writeBuffer.mockRestore();
917
+ submit.mockRestore();
918
+ createBuffer.mockRestore();
919
+ unsubscribe();
920
+ await scenario.dispose();
921
+ }
922
+ });
923
+
924
+ const bloomConstructionFailures = [
925
+ ['createSampler', 'bloom-sampler'],
926
+ ['createBindGroupLayout', 'bloom-bright-bgl'],
927
+ ['createBindGroupLayout', 'bloom-blur-bgl'],
928
+ ['createBindGroupLayout', 'bloom-composite-bgl'],
929
+ ['createPipelineLayout', 'bloom-bright-pl'],
930
+ ['createPipelineLayout', 'bloom-blur-pl'],
931
+ ['createPipelineLayout', 'bloom-composite-pl'],
932
+ ['createRenderPipeline', 'bloom-bright-pipeline'],
933
+ ['createRenderPipeline', 'bloom-blur-h-pipeline'],
934
+ ['createRenderPipeline', 'bloom-composite-pipeline'],
935
+ ['createBuffer', 'bloom-bright-params-ubo'],
936
+ ['createBuffer', 'bloom-blur-h-params-ubo'],
937
+ ['createBuffer', 'bloom-blur-v-params-ubo'],
938
+ ['createBuffer', 'bloom-composite-params-ubo'],
939
+ ] as const;
940
+
941
+ it.each(
942
+ bloomConstructionFailures,
943
+ )('abandons the Bloom candidate when %s(%s) fails and retries cleanly', async (method, label) => {
944
+ const scenario = await createBloomScenario();
945
+ const original = RhiNullDevice.prototype[method];
946
+ const failure = new RhiError({
947
+ code: 'webgpu-runtime-error',
948
+ expected: `${label} construction succeeds`,
949
+ hint: 'repair the Bloom construction operation and retry the frame',
950
+ });
951
+ let injected = false;
952
+ const operation = vi.spyOn(RhiNullDevice.prototype, method);
953
+ const submit = vi.spyOn(RhiNullQueue.prototype, 'submit');
954
+ const errors: unknown[] = [];
955
+ const unsubscribe = scenario.renderer.onError((error) => errors.push(error));
956
+ operation.mockImplementation(function (this: RhiNullDevice, ...args: never[]) {
957
+ const descriptor = args[0] as { readonly label?: unknown } | undefined;
958
+ if (!injected && descriptor?.label === label) {
959
+ injected = true;
960
+ return err(failure) as never;
961
+ }
962
+ return Reflect.apply(original as (...input: never[]) => unknown, this, args) as never;
963
+ } as never);
964
+ try {
965
+ const submitCount = submit.mock.calls.length;
966
+ expect(scenario.draw().ok).toBe(false);
967
+ expect(injected).toBe(true);
968
+ expect(submit).toHaveBeenCalledTimes(submitCount);
969
+ expect(errors).toHaveLength(1);
970
+ expect(errors[0]).toMatchObject({
971
+ code: 'webgpu-runtime-error',
972
+ detail: { error: expect.anything() },
973
+ });
974
+ const reported = errors[0] as { readonly detail?: { readonly error?: unknown } };
975
+ if (method === 'createRenderPipeline') {
976
+ expect(reported.detail?.error).toMatchObject({
977
+ code: 'pipeline-build-failed',
978
+ name: 'PipelineSpecError',
979
+ detail: { cause: failure },
980
+ });
981
+ } else {
982
+ expect(reported.detail?.error).toBe(failure);
983
+ }
984
+ expect(scenario.renderer.inspect().bloom).toMatchObject({
985
+ state: 'off',
986
+ graphStatus: 'empty',
987
+ targetCount: 0,
988
+ targetBytes: 0,
989
+ passCount: 0,
990
+ resourceCount: 0,
991
+ uploadCount: 0,
992
+ bindGroupCount: 0,
993
+ encodeCount: 0,
994
+ });
995
+
996
+ operation.mockRestore();
997
+ expect(scenario.draw().ok).toBe(true);
998
+ expect(submit).toHaveBeenCalledTimes(submitCount + 1);
999
+ expect(scenario.renderer.inspect().bloom).toMatchObject({
1000
+ state: 'active',
1001
+ graphStatus: 'valid',
1002
+ targetCount: 4,
1003
+ passCount: 4,
1004
+ uploadCount: 4,
1005
+ bindGroupCount: 4,
1006
+ encodeCount: 4,
1007
+ });
1008
+ } finally {
1009
+ operation.mockRestore();
1010
+ submit.mockRestore();
1011
+ unsubscribe();
1012
+ await scenario.dispose();
1013
+ }
1014
+ });
1015
+
1016
+ it('aborts a Bloom bind-group failure before submit and preserves committed receipts', async () => {
1017
+ const scenario = await createBloomScenario();
1018
+ const originalCreateBindGroup = RhiNullDevice.prototype.createBindGroup;
1019
+ const createBindGroup = vi.spyOn(RhiNullDevice.prototype, 'createBindGroup');
1020
+ const submit = vi.spyOn(RhiNullQueue.prototype, 'submit');
1021
+ const failure = new RhiError({
1022
+ code: 'webgpu-runtime-error',
1023
+ expected: 'Bloom blur-V bind-group creation succeeds',
1024
+ hint: 'repair the Bloom bind group and retry the frame',
1025
+ });
1026
+ let injected = false;
1027
+ try {
1028
+ expect(scenario.draw().ok).toBe(true);
1029
+ const lkg = scenario.renderer.inspect().bloom;
1030
+ const submitCount = submit.mock.calls.length;
1031
+ scenario.canvas.width = 96;
1032
+ scenario.canvas.height = 64;
1033
+ createBindGroup.mockImplementation(function (this: RhiNullDevice, descriptor) {
1034
+ if (!injected && descriptor.label === 'bloom-blur-v-bg') {
1035
+ injected = true;
1036
+ return err(failure);
1037
+ }
1038
+ return originalCreateBindGroup.call(this, descriptor);
1039
+ });
1040
+ expect(scenario.draw().ok).toBe(false);
1041
+ expect(injected).toBe(true);
1042
+ expect(submit).toHaveBeenCalledTimes(submitCount);
1043
+ expect(scenario.renderer.inspect().bloom).toMatchObject({
1044
+ state: 'active',
1045
+ generation: lkg.generation,
1046
+ uploadCount: 4,
1047
+ bindGroupCount: 4,
1048
+ encodeCount: 4,
1049
+ });
1050
+
1051
+ createBindGroup.mockRestore();
1052
+ expect(scenario.draw().ok).toBe(true);
1053
+ expect(submit).toHaveBeenCalledTimes(submitCount + 1);
1054
+ expect(scenario.renderer.inspect().bloom).toMatchObject({
1055
+ state: 'active',
1056
+ uploadCount: 4,
1057
+ bindGroupCount: 4,
1058
+ encodeCount: 4,
1059
+ });
1060
+ } finally {
1061
+ createBindGroup.mockRestore();
1062
+ submit.mockRestore();
1063
+ await scenario.dispose();
1064
+ }
1065
+ });
1066
+
1067
+ it('aggregates active and fence-retiring Bloom bundles through Renderer.inspect', async () => {
1068
+ const fence = deferred<undefined>();
1069
+ const onSubmittedWorkDone = vi
1070
+ .spyOn(RhiNullQueue.prototype, 'onSubmittedWorkDone')
1071
+ .mockReturnValue(fence.promise);
1072
+ const scenario = await createBloomScenario();
1073
+ try {
1074
+ expect(scenario.draw().ok).toBe(true);
1075
+ scenario.setBloom('off');
1076
+ expect(scenario.draw().ok).toBe(true);
1077
+ const retiring = scenario.renderer.inspect().bloom;
1078
+ expect(retiring.state).toBe('retiring');
1079
+ expect(retiring.graphStatus).toBe('empty');
1080
+ expect(retiring.enabled).toBe(false);
1081
+ expect(retiring.passCount).toBe(0);
1082
+ expect(retiring.targetCount).toBe(0);
1083
+ expect(retiring.targetBytes).toBe(0);
1084
+ expect(retiring.encodeCount).toBe(0);
1085
+ expect(retiring.bindGroupCount).toBe(0);
1086
+ expect(retiring.uploadCount).toBe(0);
1087
+ expect(retiring.resourceCount).toBeGreaterThan(0);
1088
+
1089
+ scenario.setBloom('on');
1090
+ expect(scenario.draw().ok).toBe(true);
1091
+ const overlap = scenario.renderer.inspect().bloom;
1092
+ expect(overlap.state).toBe('active');
1093
+ expect(overlap.graphStatus).toBe('valid');
1094
+ expect(overlap.enabled).toBe(true);
1095
+ expect(overlap.passCount).toBe(4);
1096
+ expect(overlap.uploadCount).toBe(4);
1097
+ expect(overlap.bindGroupCount).toBe(4);
1098
+ expect(overlap.encodeCount).toBe(4);
1099
+ expect(overlap.resourceCount).toBeGreaterThan(retiring.resourceCount);
1100
+
1101
+ fence.resolve(undefined);
1102
+ await Promise.resolve();
1103
+ await Promise.resolve();
1104
+ const settled = scenario.renderer.inspect().bloom;
1105
+ expect(settled.state).toBe('active');
1106
+ expect(settled.graphStatus).toBe('valid');
1107
+ expect(settled.passCount).toBe(4);
1108
+ expect(settled.targetCount).toBe(4);
1109
+ expect(settled.targetBytes).toBe((64 * 64 + 3 * 32 * 32) * 8);
1110
+ expect(settled.resourceCount).toBeLessThan(overlap.resourceCount);
1111
+ } finally {
1112
+ onSubmittedWorkDone.mockRestore();
1113
+ await scenario.dispose();
1114
+ }
1115
+ });
1116
+
1117
+ it('settles Bloom off to exact zero without re-enabling it', async () => {
1118
+ const fence = deferred<undefined>();
1119
+ const onSubmittedWorkDone = vi
1120
+ .spyOn(RhiNullQueue.prototype, 'onSubmittedWorkDone')
1121
+ .mockReturnValue(fence.promise);
1122
+ const scenario = await createBloomScenario();
1123
+ try {
1124
+ expect(scenario.draw().ok).toBe(true);
1125
+ scenario.setBloom('off');
1126
+ expect(scenario.draw().ok).toBe(true);
1127
+ const retiring = scenario.renderer.inspect().bloom;
1128
+ expect(retiring.state).toBe('retiring');
1129
+ expect(retiring.enabled).toBe(false);
1130
+ expect(retiring.targetCount).toBe(0);
1131
+ expect(retiring.targetBytes).toBe(0);
1132
+ expect(retiring.passCount).toBe(0);
1133
+ expect(retiring.resourceCount).toBeGreaterThan(0);
1134
+ expect(retiring.uploadCount).toBe(0);
1135
+
1136
+ fence.resolve(undefined);
1137
+ for (let attempt = 0; attempt < 4; attempt += 1) await Promise.resolve();
1138
+ expect(scenario.renderer.inspect().bloom).toMatchObject({
1139
+ state: 'off',
1140
+ graphStatus: 'empty',
1141
+ enabled: false,
1142
+ targetCount: 0,
1143
+ targetBytes: 0,
1144
+ passCount: 0,
1145
+ resourceCount: 0,
1146
+ uploadCount: 0,
1147
+ });
1148
+ } finally {
1149
+ onSubmittedWorkDone.mockRestore();
1150
+ await scenario.dispose();
1151
+ }
1152
+ });
1153
+
1154
+ it('reports one structured Bloom fence rejection and drains late-retiring resources', async () => {
1155
+ const fence = deferred<undefined>();
1156
+ const onSubmittedWorkDone = vi
1157
+ .spyOn(RhiNullQueue.prototype, 'onSubmittedWorkDone')
1158
+ .mockReturnValue(fence.promise);
1159
+ const scenario = await createBloomScenario();
1160
+ const errors: unknown[] = [];
1161
+ let errorSnapshot: ReturnType<typeof scenario.renderer.inspect> | undefined;
1162
+ const unsubscribe = scenario.renderer.onError((error) => {
1163
+ errors.push(error);
1164
+ errorSnapshot = scenario.renderer.inspect();
1165
+ });
1166
+ try {
1167
+ expect(scenario.draw().ok).toBe(true);
1168
+ scenario.setBloom('off');
1169
+ expect(scenario.draw().ok).toBe(true);
1170
+ expect(scenario.renderer.inspect().bloom.resourceCount).toBeGreaterThan(0);
1171
+ fence.reject(new Error('controlled Bloom fence failure'));
1172
+ await Promise.resolve();
1173
+ await Promise.resolve();
1174
+ expect(errors).toHaveLength(1);
1175
+ expect(errors[0]).toMatchObject({
1176
+ code: 'webgpu-runtime-error',
1177
+ detail: { error: { code: 'bloom-fence-rejected' } },
1178
+ });
1179
+ expect(errorSnapshot?.bloom).toMatchObject({
1180
+ state: 'retiring',
1181
+ graphStatus: 'empty',
1182
+ enabled: false,
1183
+ });
1184
+ expect(errorSnapshot?.bloom.resourceCount).toBeGreaterThan(0);
1185
+ expect(scenario.renderer.inspect().bloom).toMatchObject({
1186
+ state: 'off',
1187
+ enabled: false,
1188
+ resourceCount: 0,
1189
+ passCount: 0,
1190
+ encodeCount: 0,
1191
+ bindGroupCount: 0,
1192
+ uploadCount: 0,
1193
+ });
1194
+ expect((await scenario.renderer.dispose()).ok).toBe(true);
1195
+ expect((await scenario.renderer.dispose()).ok).toBe(true);
1196
+ expect(errors).toHaveLength(1);
1197
+ } finally {
1198
+ unsubscribe();
1199
+ onSubmittedWorkDone.mockRestore();
1200
+ await scenario.dispose();
1201
+ }
1202
+ });
1203
+
1204
+ it('keeps drained Bloom fence rejection callbacks inert', async () => {
1205
+ const callbacks: Array<{
1206
+ readonly resolve: (value: undefined) => unknown;
1207
+ readonly reject: ((reason: unknown) => unknown) | undefined;
1208
+ }> = [];
1209
+ const lateFence = {
1210
+ // biome-ignore lint/suspicious/noThenProperty: The test deliberately exposes a controllable fence thenable.
1211
+ then(onFulfilled: (value: undefined) => unknown, onRejected: (reason: unknown) => unknown) {
1212
+ callbacks.push({ resolve: onFulfilled, reject: onRejected });
1213
+ return Promise.resolve();
1214
+ },
1215
+ } as unknown as Promise<undefined>;
1216
+ const onSubmittedWorkDone = vi
1217
+ .spyOn(RhiNullQueue.prototype, 'onSubmittedWorkDone')
1218
+ .mockReturnValue(lateFence);
1219
+ const scenario = await createBloomScenario();
1220
+ const errors: unknown[] = [];
1221
+ const unsubscribe = scenario.renderer.onError((error) => errors.push(error));
1222
+ try {
1223
+ expect(scenario.draw().ok).toBe(true);
1224
+ scenario.setBloom('off');
1225
+ expect(scenario.draw().ok).toBe(true);
1226
+ expect(scenario.renderer.inspect().bloom.resourceCount).toBeGreaterThan(0);
1227
+ for (let attempt = 0; attempt < 8 && callbacks.length === 0; attempt += 1) {
1228
+ await Promise.resolve();
1229
+ }
1230
+ expect(callbacks.length).toBeGreaterThan(0);
1231
+ for (const callback of callbacks) callback.resolve(undefined);
1232
+ for (let attempt = 0; attempt < 4; attempt += 1) {
1233
+ await Promise.resolve();
1234
+ }
1235
+ expect(scenario.renderer.inspect().bloom.resourceCount).toBe(0);
1236
+ for (const callback of callbacks) {
1237
+ callback.reject?.(new Error('late Bloom fence rejection'));
1238
+ }
1239
+ await Promise.resolve();
1240
+ await Promise.resolve();
1241
+ expect(errors).toHaveLength(0);
1242
+ expect(scenario.renderer.inspect().bloom).toMatchObject({
1243
+ state: 'off',
1244
+ graphStatus: 'empty',
1245
+ resourceCount: 0,
1246
+ passCount: 0,
1247
+ encodeCount: 0,
1248
+ bindGroupCount: 0,
1249
+ uploadCount: 0,
1250
+ });
1251
+ } finally {
1252
+ unsubscribe();
1253
+ onSubmittedWorkDone.mockRestore();
1254
+ await scenario.dispose();
1255
+ }
1256
+ });
1257
+
1258
+ it.each([
1259
+ 'execute',
1260
+ 'finish',
1261
+ 'submit',
1262
+ 'throw',
1263
+ ] as const)('keeps the Bloom LKG invisible and retries after %s failure', async (failureAt) => {
1264
+ const scenario = await createBloomScenario();
1265
+ const errors: unknown[] = [];
1266
+ let failureSnapshot: ReturnType<typeof scenario.renderer.inspect> | undefined;
1267
+ const unsubscribe = scenario.renderer.onError((error) => {
1268
+ errors.push(error);
1269
+ failureSnapshot = scenario.renderer.inspect();
1270
+ });
1271
+ const failure = new RhiError({
1272
+ code: 'webgpu-runtime-error',
1273
+ expected: `Bloom ${failureAt} operation succeeds`,
1274
+ hint: 'repair the injected operation and retry the same frame',
1275
+ });
1276
+ const createEncoder = vi.spyOn(RhiNullDevice.prototype, 'createCommandEncoder');
1277
+ const beginRenderPass = vi.spyOn(RhiNullCommandEncoder.prototype, 'beginRenderPass');
1278
+ const finish = vi.spyOn(RhiNullCommandEncoder.prototype, 'finish');
1279
+ const submit = vi.spyOn(RhiNullQueue.prototype, 'submit');
1280
+ try {
1281
+ expect(scenario.draw().ok).toBe(true);
1282
+ scenario.setBloom('off');
1283
+ expect(scenario.draw().ok).toBe(true);
1284
+ const lkg = scenario.renderer.inspect().bloom;
1285
+ scenario.setBloom('on');
1286
+ if (failureAt === 'execute') {
1287
+ beginRenderPass.mockImplementation(() => {
1288
+ throw failure;
1289
+ });
1290
+ } else if (failureAt === 'finish') {
1291
+ finish.mockReturnValue(err(failure));
1292
+ } else if (failureAt === 'submit') {
1293
+ submit.mockReturnValue(err(failure));
1294
+ } else {
1295
+ createEncoder.mockImplementation(() => {
1296
+ throw failure;
1297
+ });
1298
+ }
1299
+ expect(scenario.draw().ok).toBe(false);
1300
+ expect(scenario.renderer.inspect().bloom.resourceCount).toBe(lkg.resourceCount);
1301
+ expect(scenario.renderer.inspect().bloom.passCount).toBe(0);
1302
+ expect(failureSnapshot?.bloom.generation).toBe(lkg.generation);
1303
+ expect(failureSnapshot?.bloom.resourceCount).toBe(lkg.resourceCount);
1304
+ expect(failureSnapshot?.bloom.passCount).toBe(0);
1305
+ expect(errors.length).toBeGreaterThan(0);
1306
+ const errorCount = errors.length;
1307
+ createEncoder.mockRestore();
1308
+ beginRenderPass.mockRestore();
1309
+ finish.mockRestore();
1310
+ submit.mockRestore();
1311
+ expect(scenario.draw().ok).toBe(true);
1312
+ expect(scenario.renderer.inspect().bloom.state).toBe('active');
1313
+ expect(scenario.renderer.inspect().bloom.generation).toBeGreaterThan(lkg.generation);
1314
+ expect(errors).toHaveLength(errorCount);
1315
+ } finally {
1316
+ createEncoder.mockRestore();
1317
+ beginRenderPass.mockRestore();
1318
+ finish.mockRestore();
1319
+ submit.mockRestore();
1320
+ unsubscribe();
1321
+ await scenario.dispose();
1322
+ }
1323
+ });
1324
+
1325
+ it('abandons later Bloom params construction and destroys adopted handles once', async () => {
1326
+ const scenario = await createBloomScenario();
1327
+ const originalCreateBuffer = RhiNullDevice.prototype.createBuffer;
1328
+ const originalDestroyBuffer = RhiNullDevice.prototype.destroyBuffer;
1329
+ const createBuffer = vi.spyOn(RhiNullDevice.prototype, 'createBuffer');
1330
+ const destroyBuffer = vi.spyOn(RhiNullDevice.prototype, 'destroyBuffer');
1331
+ const created = new Map<object, string>();
1332
+ const destroyCounts = new Map<object, number>();
1333
+ const failure = new RhiError({
1334
+ code: 'webgpu-runtime-error',
1335
+ expected: 'Bloom blur-v params buffer construction succeeds',
1336
+ hint: 'repair the injected construction failure and retry the same frame',
1337
+ });
1338
+ try {
1339
+ expect(scenario.draw().ok).toBe(true);
1340
+ scenario.setBloom('off');
1341
+ expect(scenario.draw().ok).toBe(true);
1342
+ const lkg = scenario.renderer.inspect().bloom;
1343
+ scenario.setBloom('on');
1344
+ createBuffer.mockImplementation(function (this: RhiNullDevice, descriptor) {
1345
+ if (descriptor.label === 'bloom-blur-v-params-ubo') {
1346
+ return err(failure);
1347
+ }
1348
+ const result = originalCreateBuffer.call(this, descriptor);
1349
+ if (
1350
+ result.ok &&
1351
+ typeof result.value === 'object' &&
1352
+ result.value !== null &&
1353
+ typeof descriptor.label === 'string' &&
1354
+ descriptor.label.startsWith('bloom-')
1355
+ ) {
1356
+ created.set(result.value, descriptor.label);
1357
+ }
1358
+ return result;
1359
+ });
1360
+ destroyBuffer.mockImplementation(function (this: RhiNullDevice, handle) {
1361
+ if (typeof handle === 'object' && handle !== null) {
1362
+ destroyCounts.set(handle, (destroyCounts.get(handle) ?? 0) + 1);
1363
+ }
1364
+ return originalDestroyBuffer.call(this, handle);
1365
+ });
1366
+ expect(scenario.draw().ok).toBe(false);
1367
+ expect(scenario.renderer.inspect().bloom.resourceCount).toBe(lkg.resourceCount);
1368
+ expect(scenario.renderer.inspect().bloom.passCount).toBe(0);
1369
+ const adoptedBeforeFailure = [...created.entries()].filter(
1370
+ ([, label]) => label === 'bloom-bright-params-ubo' || label === 'bloom-blur-h-params-ubo',
1371
+ );
1372
+ expect(adoptedBeforeFailure).toHaveLength(2);
1373
+ expect(adoptedBeforeFailure.every(([handle]) => destroyCounts.get(handle) === 1)).toBe(true);
1374
+ expect(
1375
+ [...created.values()].filter((label) => label === 'bloom-blur-v-params-ubo'),
1376
+ ).toHaveLength(0);
1377
+ createBuffer.mockRestore();
1378
+ destroyBuffer.mockRestore();
1379
+ expect(scenario.draw().ok).toBe(true);
1380
+ expect(scenario.renderer.inspect().bloom.generation).toBeGreaterThan(lkg.generation);
1381
+ } finally {
1382
+ createBuffer.mockRestore();
1383
+ destroyBuffer.mockRestore();
1384
+ await scenario.dispose();
1385
+ }
1386
+ });
1387
+
1388
+ it('keeps Bloom ownership scoped across failed and successful device recovery', async () => {
1389
+ const adapter = new RhiNullAdapter();
1390
+ const lost = deferred<{ readonly reason: 'unknown'; readonly message: string }>();
1391
+ const firstDevice = (await adapter.requestDevice()).unwrap();
1392
+ const replacementDevice = (await adapter.requestDevice()).unwrap();
1393
+ const lostProxy = new Proxy(firstDevice, {
1394
+ get(target, property) {
1395
+ if (property === 'lost') return lost.promise;
1396
+ const value = Reflect.get(target, property, target);
1397
+ return typeof value === 'function' ? value.bind(target) : value;
1398
+ },
1399
+ });
1400
+ let requestCount = 0;
1401
+ const recoveryFailure = new RhiError({
1402
+ code: 'webgpu-runtime-error',
1403
+ expected: 'replacement device acquisition succeeds',
1404
+ hint: 'retry recovery after the backend becomes available',
1405
+ });
1406
+ const recoverableRhi = {
1407
+ ...rhi,
1408
+ requestAdapter: async () =>
1409
+ ok({
1410
+ features: adapter.features,
1411
+ limits: adapter.limits,
1412
+ requestDevice: async () => {
1413
+ requestCount += 1;
1414
+ if (requestCount === 1) return ok(lostProxy);
1415
+ if (requestCount === 2) return { ok: false, error: recoveryFailure };
1416
+ return ok(replacementDevice);
1417
+ },
1418
+ } as never),
1419
+ } as never;
1420
+ const renderer = await constructRenderer(
1421
+ { width: 64, height: 64, getContext: () => null },
1422
+ { rhi: recoverableRhi },
1423
+ { shaderManifestUrl: renderLifecycleManifestUrl() },
1424
+ );
1425
+ const world = new World();
1426
+ const attached = renderer.attach(world);
1427
+ expect(attached.ok).toBe(true);
1428
+ if (!attached.ok) return;
1429
+ registerPropagateTransforms(world);
1430
+ world.spawn(
1431
+ { component: Transform, data: { pos: [0, 0, 4] } },
1432
+ {
1433
+ component: LocalCamera,
1434
+ data: {
1435
+ fov: 1,
1436
+ aspect: 1,
1437
+ near: 0.1,
1438
+ far: 100,
1439
+ antialias: ANTIALIAS_NONE,
1440
+ bloom: BLOOM_ENABLED,
1441
+ },
1442
+ },
1443
+ );
1444
+ const input = () => ({
1445
+ leases: [attached.value],
1446
+ camera: { lease: attached.value },
1447
+ environment: { lease: attached.value },
1448
+ });
1449
+ try {
1450
+ expect((await renderer.initialization).ok).toBe(true);
1451
+ expect(world.update().ok).toBe(true);
1452
+ expect(renderer.draw(input()).ok).toBe(true);
1453
+ const beforeLoss = renderer.inspect().bloom;
1454
+ const beforeDeviceGeneration = renderer.inspect().temporal.deviceGeneration;
1455
+ expect(beforeLoss.state).toBe('active');
1456
+ lost.reject({ reason: 'unknown', message: 'forced device loss' });
1457
+ await Promise.resolve();
1458
+ await Promise.resolve();
1459
+ expect(renderer.health().reason).toBe('device-lost');
1460
+ const failed = await renderer.recover();
1461
+ expect(failed.ok).toBe(false);
1462
+ expect(renderer.health().reason).toBe('device-lost');
1463
+ const recovered = await renderer.recover();
1464
+ expect(recovered.ok).toBe(true);
1465
+ expect(world.update().ok).toBe(true);
1466
+ expect(renderer.draw(input()).ok).toBe(true);
1467
+ expect(renderer.inspect().bloom.state).toBe('active');
1468
+ expect(renderer.inspect().bloom.generation).toBeGreaterThan(0);
1469
+ expect(renderer.inspect().temporal.deviceGeneration).toBeGreaterThan(beforeDeviceGeneration);
1470
+ expect((await renderer.dispose()).ok).toBe(true);
1471
+ expect((await renderer.dispose()).ok).toBe(true);
1472
+ expect(renderer.inspect().bloom.state).toBe('off');
1473
+ } finally {
1474
+ await renderer.dispose();
1475
+ }
1476
+ });
1477
+
1478
+ it('abandons a post-process recovery candidate before promotion and retries once', async () => {
1479
+ const adapter = new RhiNullAdapter();
1480
+ const lost = deferred<{ readonly reason: 'unknown'; readonly message: string }>();
1481
+ const firstDevice = (await adapter.requestDevice()).unwrap();
1482
+ const failingReplacement = (await adapter.requestDevice()).unwrap();
1483
+ const successfulReplacement = (await adapter.requestDevice()).unwrap();
1484
+ const lostProxy = new Proxy(firstDevice, {
1485
+ get(target, property) {
1486
+ if (property === 'lost') return lost.promise;
1487
+ const value = Reflect.get(target, property, target);
1488
+ return typeof value === 'function' ? value.bind(target) : value;
1489
+ },
1490
+ });
1491
+ const createdReplacementHandles = new Set<object>();
1492
+ const createdReplacementLabels = new Map<object, string>();
1493
+ const destroyedReplacementHandles = new Map<object, number>();
1494
+ let failPostProcessRestore = true;
1495
+ const recoveryFailure = new RhiError({
1496
+ code: 'webgpu-runtime-error',
1497
+ expected: 'post-process recovery params upload succeeds',
1498
+ hint: 'repair the replacement post-process resource and retry recovery',
1499
+ });
1500
+ const trackReplacement = new Proxy(failingReplacement, {
1501
+ get(target, property) {
1502
+ if (property === 'createBuffer') {
1503
+ return (descriptor: { readonly label?: string }) => {
1504
+ const result = failingReplacement.createBuffer(descriptor as never);
1505
+ if (
1506
+ result.ok &&
1507
+ typeof result.value === 'object' &&
1508
+ result.value !== null &&
1509
+ (descriptor.label?.startsWith('bloom-') ||
1510
+ descriptor.label?.startsWith('post-process-params-'))
1511
+ ) {
1512
+ createdReplacementHandles.add(result.value);
1513
+ if (descriptor.label !== undefined) {
1514
+ createdReplacementLabels.set(result.value, descriptor.label);
1515
+ }
1516
+ }
1517
+ return result;
1518
+ };
1519
+ }
1520
+ if (property === 'queue') {
1521
+ return new Proxy(failingReplacement.queue, {
1522
+ get(queue, queueProperty) {
1523
+ if (queueProperty === 'writeBuffer') {
1524
+ return (buffer: object, ...args: unknown[]) => {
1525
+ const label = createdReplacementLabels.get(buffer);
1526
+ if (failPostProcessRestore && label?.startsWith('post-process-params-')) {
1527
+ return err(recoveryFailure);
1528
+ }
1529
+ return failingReplacement.queue.writeBuffer(
1530
+ buffer as never,
1531
+ args[0] as number,
1532
+ args[1] as ArrayBufferView | ArrayBuffer,
1533
+ args[2] as number | undefined,
1534
+ args[3] as number | undefined,
1535
+ );
1536
+ };
1537
+ }
1538
+ const value = Reflect.get(queue, queueProperty, queue);
1539
+ return typeof value === 'function' ? value.bind(queue) : value;
1540
+ },
1541
+ });
1542
+ }
1543
+ if (property === 'destroyBuffer' || property === 'destroyTexture') {
1544
+ return (handle: object) => {
1545
+ destroyedReplacementHandles.set(
1546
+ handle,
1547
+ (destroyedReplacementHandles.get(handle) ?? 0) + 1,
1548
+ );
1549
+ const destroy = failingReplacement[property as 'destroyBuffer' | 'destroyTexture'];
1550
+ return destroy.call(failingReplacement, handle as never);
1551
+ };
1552
+ }
1553
+ const value = Reflect.get(target, property, target);
1554
+ return typeof value === 'function' ? value.bind(target) : value;
1555
+ },
1556
+ });
1557
+ let requestCount = 0;
1558
+ const recoveryFailureRhi = {
1559
+ ...rhi,
1560
+ requestAdapter: async () =>
1561
+ ok({
1562
+ features: adapter.features,
1563
+ limits: adapter.limits,
1564
+ requestDevice: async () => {
1565
+ requestCount += 1;
1566
+ if (requestCount === 1) return ok(lostProxy);
1567
+ if (requestCount === 2) return ok(trackReplacement as never);
1568
+ return ok(successfulReplacement);
1569
+ },
1570
+ } as never),
1571
+ } as never;
1572
+ const renderer = await constructRenderer(
1573
+ { width: 64, height: 64, getContext: () => null },
1574
+ { rhi: recoveryFailureRhi },
1575
+ { shaderManifestUrl: renderLifecycleManifestUrl() },
1576
+ );
1577
+ const world = new World();
1578
+ const attached = renderer.attach(world);
1579
+ expect(attached.ok).toBe(true);
1580
+ if (!attached.ok) return;
1581
+ registerPropagateTransforms(world);
1582
+ const camera = world
1583
+ .spawn(
1584
+ { component: Transform, data: { pos: [0, 0, 4] } },
1585
+ {
1586
+ component: LocalCamera,
1587
+ data: {
1588
+ fov: 1,
1589
+ aspect: 1,
1590
+ near: 0.1,
1591
+ far: 100,
1592
+ antialias: ANTIALIAS_NONE,
1593
+ bloom: BLOOM_DISABLED,
1594
+ },
1595
+ },
1596
+ )
1597
+ .unwrap();
1598
+ const input = () => ({
1599
+ leases: [attached.value],
1600
+ camera: { lease: attached.value },
1601
+ environment: { lease: attached.value },
1602
+ });
1603
+ try {
1604
+ expect((await renderer.initialization).ok).toBe(true);
1605
+ expect(world.update().ok).toBe(true);
1606
+ expect(renderer.draw(input()).ok).toBe(true);
1607
+ const beforeFailure = renderer.inspect();
1608
+ lost.reject({ reason: 'unknown', message: 'forced recovery candidate failure' });
1609
+ await Promise.resolve();
1610
+ await Promise.resolve();
1611
+ expect(renderer.health().reason).toBe('device-lost');
1612
+ expect((await renderer.recover()).ok).toBe(false);
1613
+ expect(renderer.health().reason).toBe('device-lost');
1614
+ expect(createdReplacementHandles.size).toBeGreaterThan(0);
1615
+ expect(
1616
+ [...createdReplacementHandles].every(
1617
+ (handle) => destroyedReplacementHandles.get(handle) === 1,
1618
+ ),
1619
+ ).toBe(true);
1620
+ const afterFailure = renderer.inspect();
1621
+ expect(afterFailure.frame.deviceGeneration).toBe(beforeFailure.frame.deviceGeneration);
1622
+ expect(afterFailure.temporal.deviceGeneration).toBe(beforeFailure.temporal.deviceGeneration);
1623
+ expect(afterFailure.environment).toEqual(beforeFailure.environment);
1624
+ expect(afterFailure.bloom.state).toBe('off');
1625
+
1626
+ failPostProcessRestore = false;
1627
+ const recovered = await renderer.recover();
1628
+ expect(recovered.ok).toBe(true);
1629
+ expect(renderer.inspect().frame.deviceGeneration).toBeGreaterThan(
1630
+ beforeFailure.frame.deviceGeneration,
1631
+ );
1632
+ world.set(camera, LocalCamera, { bloom: BLOOM_ENABLED }).unwrap();
1633
+ expect(world.update().ok).toBe(true);
1634
+ expect(renderer.draw(input()).ok).toBe(true);
1635
+ expect(renderer.inspect().bloom.state).toBe('active');
1636
+ expect((await renderer.dispose()).ok).toBe(true);
1637
+ expect((await renderer.dispose()).ok).toBe(true);
1638
+ expect(renderer.inspect().bloom).toMatchObject({
1639
+ state: 'off',
1640
+ graphStatus: 'empty',
1641
+ resourceCount: 0,
1642
+ passCount: 0,
1643
+ encodeCount: 0,
1644
+ bindGroupCount: 0,
1645
+ uploadCount: 0,
1646
+ });
1647
+ } finally {
1648
+ await renderer.dispose();
1649
+ }
1650
+ });
280
1651
  });