@forgeax/engine-render 0.1.7 → 0.1.20

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 (777) hide show
  1. package/README.md +140 -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 +26 -5
  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/renderer-frame-transaction.d.ts +39 -0
  10. package/dist/assembly/renderer-frame-transaction.d.ts.map +1 -0
  11. package/dist/assembly/renderer-inspection.d.ts +22 -0
  12. package/dist/assembly/renderer-inspection.d.ts.map +1 -0
  13. package/dist/assembly/renderer-lifecycle.d.ts +4 -0
  14. package/dist/assembly/renderer-lifecycle.d.ts.map +1 -0
  15. package/dist/authoring.mjs +4 -2
  16. package/dist/authoring.mjs.map +1 -1
  17. package/dist/chunk-4XINDLAY.mjs +9404 -0
  18. package/dist/chunk-4XINDLAY.mjs.map +1 -0
  19. package/dist/chunk-CDTWTHZR.mjs +752 -0
  20. package/dist/chunk-CDTWTHZR.mjs.map +1 -0
  21. package/dist/chunk-FJ6P52EE.mjs +46 -0
  22. package/dist/chunk-FJ6P52EE.mjs.map +1 -0
  23. package/dist/chunk-JDWARUOI.mjs +44 -0
  24. package/dist/chunk-JDWARUOI.mjs.map +1 -0
  25. package/dist/chunk-M2MP4ZOU.mjs +73 -0
  26. package/dist/chunk-M2MP4ZOU.mjs.map +1 -0
  27. package/dist/chunk-PCHBPYML.mjs +385 -0
  28. package/dist/chunk-PCHBPYML.mjs.map +1 -0
  29. package/dist/chunk-RXZ3RGOU.mjs +53 -0
  30. package/dist/chunk-RXZ3RGOU.mjs.map +1 -0
  31. package/dist/{chunk-YEJIUW7D.mjs → chunk-TBQJOK2L.mjs} +356 -543
  32. package/dist/chunk-TBQJOK2L.mjs.map +1 -0
  33. package/dist/{chunk-CQXZC5XI.mjs → chunk-VFPLTKCU.mjs} +79 -69
  34. package/dist/chunk-VFPLTKCU.mjs.map +1 -0
  35. package/dist/components/atmosphere.d.ts +9 -0
  36. package/dist/components/atmosphere.d.ts.map +1 -0
  37. package/dist/components/camera.d.ts +2 -1
  38. package/dist/components/camera.d.ts.map +1 -1
  39. package/dist/components/fog.d.ts +8 -0
  40. package/dist/components/fog.d.ts.map +1 -0
  41. package/dist/components/index.d.ts +3 -0
  42. package/dist/components/index.d.ts.map +1 -1
  43. package/dist/components/light-helpers.d.ts.map +1 -1
  44. package/dist/components/motion-blur.d.ts +7 -0
  45. package/dist/components/motion-blur.d.ts.map +1 -0
  46. package/dist/components/spot-light.d.ts +12 -0
  47. package/dist/components/spot-light.d.ts.map +1 -1
  48. package/dist/construct-renderer.d.ts +2 -2
  49. package/dist/construct-renderer.d.ts.map +1 -1
  50. package/dist/construct-renderer.mjs +11146 -14507
  51. package/dist/construct-renderer.mjs.map +1 -1
  52. package/dist/device/device-scope.d.ts +4 -0
  53. package/dist/device/device-scope.d.ts.map +1 -1
  54. package/dist/device/gpu-residency.d.ts +19 -2
  55. package/dist/device/gpu-residency.d.ts.map +1 -1
  56. package/dist/environment/frame.d.ts +60 -0
  57. package/dist/environment/frame.d.ts.map +1 -0
  58. package/dist/environment/generation.d.ts +21 -0
  59. package/dist/environment/generation.d.ts.map +1 -0
  60. package/dist/environment/graph.d.ts +4 -0
  61. package/dist/environment/graph.d.ts.map +1 -0
  62. package/dist/environment/inspection.d.ts +48 -0
  63. package/dist/environment/inspection.d.ts.map +1 -0
  64. package/dist/environment/lifecycle.d.ts +39 -0
  65. package/dist/environment/lifecycle.d.ts.map +1 -0
  66. package/dist/environment/signature.d.ts +25 -0
  67. package/dist/environment/signature.d.ts.map +1 -0
  68. package/dist/errors/render.d.ts +204 -1
  69. package/dist/errors/render.d.ts.map +1 -1
  70. package/dist/extract/camera.d.ts +5 -0
  71. package/dist/extract/camera.d.ts.map +1 -0
  72. package/dist/extract/environment.d.ts +74 -0
  73. package/dist/extract/environment.d.ts.map +1 -0
  74. package/dist/extract/visibility.d.ts +1 -1
  75. package/dist/extract/visibility.d.ts.map +1 -1
  76. package/dist/features/host.d.ts +9 -2
  77. package/dist/features/host.d.ts.map +1 -1
  78. package/dist/features/motion-blur/motion-blur-feature.d.ts +16 -0
  79. package/dist/features/motion-blur/motion-blur-feature.d.ts.map +1 -0
  80. package/dist/features/motion-blur/motion-blur-params.d.ts +25 -0
  81. package/dist/features/motion-blur/motion-blur-params.d.ts.map +1 -0
  82. package/dist/features/plan.d.ts +17 -2
  83. package/dist/features/plan.d.ts.map +1 -1
  84. package/dist/features/prepared-gpu-work.d.ts +3 -1
  85. package/dist/features/prepared-gpu-work.d.ts.map +1 -1
  86. package/dist/features/prepared-graphics.d.ts +2 -1
  87. package/dist/features/prepared-graphics.d.ts.map +1 -1
  88. package/dist/features/render-graph-raster.d.ts +2 -1
  89. package/dist/features/render-graph-raster.d.ts.map +1 -1
  90. package/dist/features/targets.d.ts +3 -0
  91. package/dist/features/targets.d.ts.map +1 -1
  92. package/dist/features/types.d.ts +12 -0
  93. package/dist/features/types.d.ts.map +1 -1
  94. package/dist/fullscreen-post-process-pass.d.ts +7 -1
  95. package/dist/fullscreen-post-process-pass.d.ts.map +1 -1
  96. package/dist/gpu-scene.d.ts +2 -0
  97. package/dist/gpu-scene.d.ts.map +1 -1
  98. package/dist/gpu-usage.d.ts +2 -0
  99. package/dist/gpu-usage.d.ts.map +1 -1
  100. package/dist/ibl/skylight-bind-group.d.ts +5 -6
  101. package/dist/ibl/skylight-bind-group.d.ts.map +1 -1
  102. package/dist/index.d.ts +24 -7
  103. package/dist/index.d.ts.map +1 -1
  104. package/dist/index.mjs +6 -4
  105. package/dist/index.mjs.map +1 -1
  106. package/dist/inspection-types.d.ts +43 -1
  107. package/dist/inspection-types.d.ts.map +1 -1
  108. package/dist/internal.d.ts +2 -0
  109. package/dist/internal.d.ts.map +1 -1
  110. package/dist/internal.mjs +2 -0
  111. package/dist/light-buffer-layout.d.ts +20 -12
  112. package/dist/light-buffer-layout.d.ts.map +1 -1
  113. package/dist/materials.d.ts +22 -0
  114. package/dist/materials.d.ts.map +1 -1
  115. package/dist/pbr-pipeline.d.ts +16 -9
  116. package/dist/pbr-pipeline.d.ts.map +1 -1
  117. package/dist/pipeline/standard-forward-lane.d.ts.map +1 -1
  118. package/dist/pipeline/standard-pipeline.d.ts +3 -0
  119. package/dist/pipeline/standard-pipeline.d.ts.map +1 -1
  120. package/dist/pipeline/standard-post.d.ts +48 -0
  121. package/dist/pipeline/standard-post.d.ts.map +1 -0
  122. package/dist/pipeline/standard-profile.d.ts +12 -2
  123. package/dist/pipeline/standard-profile.d.ts.map +1 -1
  124. package/dist/pipeline-builder.d.ts +18 -0
  125. package/dist/pipeline-builder.d.ts.map +1 -1
  126. package/dist/pipeline-spec.d.ts.map +1 -1
  127. package/dist/plugin.d.ts.map +1 -1
  128. package/dist/record/frame-lighting.d.ts +2 -0
  129. package/dist/record/frame-lighting.d.ts.map +1 -1
  130. package/dist/record/frame-snapshot.d.ts +137 -6
  131. package/dist/record/frame-snapshot.d.ts.map +1 -1
  132. package/dist/record/frame-targets.d.ts.map +1 -1
  133. package/dist/record/frame.d.ts +6 -3
  134. package/dist/record/frame.d.ts.map +1 -1
  135. package/dist/record/gpu-timing.d.ts +32 -0
  136. package/dist/record/gpu-timing.d.ts.map +1 -0
  137. package/dist/record/main-pass-geometry.d.ts +11 -0
  138. package/dist/record/main-pass-geometry.d.ts.map +1 -1
  139. package/dist/record/main-pass-material.d.ts +9 -6
  140. package/dist/record/main-pass-material.d.ts.map +1 -1
  141. package/dist/record/main-pass-sprite-draws.d.ts +8 -1
  142. package/dist/record/main-pass-sprite-draws.d.ts.map +1 -1
  143. package/dist/record/main-pass.d.ts +2 -0
  144. package/dist/record/main-pass.d.ts.map +1 -1
  145. package/dist/record/mesh-ssbo.d.ts.map +1 -1
  146. package/dist/record/render-context.d.ts +81 -19
  147. package/dist/record/render-context.d.ts.map +1 -1
  148. package/dist/record/shadow-pass.d.ts.map +1 -1
  149. package/dist/record/skybox-post-pass.d.ts +1 -11
  150. package/dist/record/skybox-post-pass.d.ts.map +1 -1
  151. package/dist/record/typed-frame-graph.d.ts +36 -4
  152. package/dist/record/typed-frame-graph.d.ts.map +1 -1
  153. package/dist/record/view-ubo.d.ts +4 -1
  154. package/dist/record/view-ubo.d.ts.map +1 -1
  155. package/dist/render-contract.d.ts +157 -8
  156. package/dist/render-contract.d.ts.map +1 -1
  157. package/dist/render-data.d.ts +10 -6
  158. package/dist/render-data.d.ts.map +1 -1
  159. package/dist/render-graph-primitives.d.ts.map +1 -1
  160. package/dist/render-pipeline.d.ts +86 -6
  161. package/dist/render-pipeline.d.ts.map +1 -1
  162. package/dist/render-system-extract.d.ts +84 -8
  163. package/dist/render-system-extract.d.ts.map +1 -1
  164. package/dist/render-system.d.ts +30 -5
  165. package/dist/render-system.d.ts.map +1 -1
  166. package/dist/renderer-inspect.d.ts +29 -0
  167. package/dist/renderer-inspect.d.ts.map +1 -0
  168. package/dist/scene/render-scene.d.ts +36 -2
  169. package/dist/scene/render-scene.d.ts.map +1 -1
  170. package/dist/temporal/coverage.d.ts +42 -0
  171. package/dist/temporal/coverage.d.ts.map +1 -0
  172. package/dist/temporal/frame-coordinator.d.ts +99 -0
  173. package/dist/temporal/frame-coordinator.d.ts.map +1 -0
  174. package/dist/temporal/frame.d.ts +44 -0
  175. package/dist/temporal/frame.d.ts.map +1 -0
  176. package/dist/temporal/gpu.d.ts +39 -0
  177. package/dist/temporal/gpu.d.ts.map +1 -0
  178. package/dist/temporal/history.d.ts +37 -0
  179. package/dist/temporal/history.d.ts.map +1 -0
  180. package/dist/temporal/index.d.ts +7 -0
  181. package/dist/temporal/index.d.ts.map +1 -0
  182. package/dist/temporal/index.mjs +8 -0
  183. package/dist/temporal/index.mjs.map +1 -0
  184. package/dist/temporal/inspection.d.ts +36 -0
  185. package/dist/temporal/inspection.d.ts.map +1 -0
  186. package/dist/temporal/scene-data-catalog.d.ts +48 -0
  187. package/dist/temporal/scene-data-catalog.d.ts.map +1 -0
  188. package/dist/temporal/scene-data.d.ts +43 -0
  189. package/dist/temporal/scene-data.d.ts.map +1 -0
  190. package/dist/temporal/standard-scene-data.d.ts +61 -0
  191. package/dist/temporal/standard-scene-data.d.ts.map +1 -0
  192. package/dist/temporal/taa-history-resources.d.ts +22 -0
  193. package/dist/temporal/taa-history-resources.d.ts.map +1 -0
  194. package/dist/temporal/taa-history-store.d.ts +27 -0
  195. package/dist/temporal/taa-history-store.d.ts.map +1 -0
  196. package/dist/temporal/taa-resolve.d.ts +15 -0
  197. package/dist/temporal/taa-resolve.d.ts.map +1 -0
  198. package/dist/temporal/temporal-view.d.ts +40 -0
  199. package/dist/temporal/temporal-view.d.ts.map +1 -0
  200. package/dist/temporal/transaction.d.ts +9 -0
  201. package/dist/temporal/transaction.d.ts.map +1 -0
  202. package/dist/temporal/view.d.ts +56 -0
  203. package/dist/temporal/view.d.ts.map +1 -0
  204. package/dist/transmission/backdrop.d.ts +147 -0
  205. package/dist/transmission/backdrop.d.ts.map +1 -0
  206. package/dist/transmission/inspection.d.ts +57 -0
  207. package/dist/transmission/inspection.d.ts.map +1 -0
  208. package/dist/transmission/oracle.d.ts +42 -0
  209. package/dist/transmission/oracle.d.ts.map +1 -0
  210. package/dist/transmission/projection.d.ts +37 -0
  211. package/dist/transmission/projection.d.ts.map +1 -0
  212. package/dist/typed-render-graph-primitives.d.ts +30 -2
  213. package/dist/typed-render-graph-primitives.d.ts.map +1 -1
  214. package/dist/typed-shadow-atlas.d.ts +10 -0
  215. package/dist/typed-shadow-atlas.d.ts.map +1 -0
  216. package/dist/typed-shadow-passes.d.ts.map +1 -1
  217. package/dist/volume/capability.d.ts +57 -0
  218. package/dist/volume/capability.d.ts.map +1 -0
  219. package/dist/volume/component.d.ts +61 -0
  220. package/dist/volume/component.d.ts.map +1 -0
  221. package/dist/volume/extract.d.ts +11 -0
  222. package/dist/volume/extract.d.ts.map +1 -0
  223. package/dist/volume/inspection.d.ts +38 -0
  224. package/dist/volume/inspection.d.ts.map +1 -0
  225. package/dist/volume/passes.d.ts +38 -0
  226. package/dist/volume/passes.d.ts.map +1 -0
  227. package/dist/volume/recovery.d.ts +38 -0
  228. package/dist/volume/recovery.d.ts.map +1 -0
  229. package/dist/volume/resources.d.ts +57 -0
  230. package/dist/volume/resources.d.ts.map +1 -0
  231. package/dist/volume/temporal.d.ts +35 -0
  232. package/dist/volume/temporal.d.ts.map +1 -0
  233. package/package.json +24 -19
  234. package/src/__tests__/ai-user-cold-start.integration.test.ts +59 -0
  235. package/src/__tests__/ai-user-public-surface.test-d.ts +45 -0
  236. package/src/__tests__/bloom-exact-zero.unit.test.ts +202 -0
  237. package/src/__tests__/bloom-lifecycle.integration.test.ts +125 -0
  238. package/src/__tests__/construct-renderer-identity.unit.test.ts +4 -3
  239. package/src/__tests__/detached-inspection.integration.test.ts +177 -0
  240. package/src/__tests__/environment-fog-lanes.integration.test.ts +219 -0
  241. package/src/__tests__/environment-generation.integration.test.ts +141 -0
  242. package/src/__tests__/environment-selection.unit.test.ts +245 -0
  243. package/src/__tests__/factory-contract.integration.test.ts +1380 -9
  244. package/src/__tests__/frame-plan-contract.unit.test.ts +90 -0
  245. package/src/__tests__/fullscreen-feature-plan.unit.test.ts +7 -0
  246. package/src/__tests__/gpu-driven-production.integration.test.ts +74 -22
  247. package/src/__tests__/gpu-driven-view-graph.integration.test.ts +37 -0
  248. package/src/__tests__/gpu-scene.unit.test.ts +10 -0
  249. package/src/__tests__/injection-kind-owner.test-d.ts +2 -1
  250. package/src/__tests__/ktx2-basis-gpu-consumer.dawn.test.ts +14 -14
  251. package/src/__tests__/material-contract-inventory.unit.test.ts +62 -0
  252. package/src/__tests__/material-cooked-projection.unit.test.ts +18 -146
  253. package/src/__tests__/materials-transmission-validation.unit.test.ts +36 -0
  254. package/src/__tests__/mesh-buffer-usage-surface.unit.test.ts +1 -1
  255. package/src/__tests__/morph-feature-plan.unit.test.ts +7 -0
  256. package/src/__tests__/motion-blur.pixel.integration.test.ts +121 -0
  257. package/src/__tests__/public-surface.test-d.ts +15 -1
  258. package/src/__tests__/render-error-code-owner.test-d.ts +14 -0
  259. package/src/__tests__/render-error-detail-ssot.test-d.ts +11 -2
  260. package/src/__tests__/render-error-exhaustive.test-d.ts +3 -0
  261. package/src/__tests__/render-feature-errors.unit.test.ts +33 -3
  262. package/src/__tests__/render-feature-gpu-work.integration.test.ts +44 -0
  263. package/src/__tests__/render-feature-plan.test-d.ts +22 -89
  264. package/src/__tests__/render-scene-projection.unit.test.ts +28 -0
  265. package/src/__tests__/render-temporal-environment-consumer.browser.test.ts +26 -0
  266. package/src/__tests__/render-temporal-environment-consumer.dawn.test.ts +21 -0
  267. package/src/__tests__/renderer-factory-material-contract.unit.test.ts +423 -1
  268. package/src/__tests__/renderer-frame-transaction.integration.test.ts +405 -0
  269. package/src/__tests__/renderer-lifecycle.integration.test.ts +22 -0
  270. package/src/__tests__/scene-data-catalog.unit.test.ts +59 -0
  271. package/src/__tests__/scene-data-consumer.integration.test.ts +67 -0
  272. package/src/__tests__/scene-data-contract.test-d.ts +35 -0
  273. package/src/__tests__/scene-data-inspection.unit.test.ts +41 -0
  274. package/src/__tests__/scene-data-public-surface.test-d.ts +34 -0
  275. package/src/__tests__/shader-manifest-fixture.ts +111 -0
  276. package/src/__tests__/skinned-shadow-caster.test.ts +123 -0
  277. package/src/__tests__/standard-clustered-pipeline.unit.test.ts +24 -3
  278. package/src/__tests__/standard-pipeline.dawn.test.ts +15 -0
  279. package/src/__tests__/standard-pipeline.integration.test.ts +543 -1
  280. package/src/__tests__/standard-profile-oracle.unit.test.ts +12 -2
  281. package/src/__tests__/standard-temporal-producer.integration.test.ts +35 -0
  282. package/src/__tests__/surface-configuration.unit.test.ts +211 -12
  283. package/src/__tests__/taa-transaction.integration.test.ts +30 -0
  284. package/src/__tests__/temporal-admission.integration.test.ts +40 -0
  285. package/src/__tests__/temporal-coverage.unit.test.ts +35 -0
  286. package/src/__tests__/temporal-demand.unit.test.ts +44 -0
  287. package/src/__tests__/temporal-frame-coordinator.unit.test.ts +64 -0
  288. package/src/__tests__/temporal-graph-topology.unit.test.ts +51 -0
  289. package/src/__tests__/temporal-history-lifecycle.unit.test.ts +170 -0
  290. package/src/__tests__/temporal-performance.integration.test.ts +615 -0
  291. package/src/__tests__/temporal-reset-matrix.unit.test.ts +97 -0
  292. package/src/__tests__/temporal-transaction.integration.test.ts +60 -0
  293. package/src/__tests__/temporal-view.unit.test.ts +152 -0
  294. package/src/__tests__/transmission-prewarm.unit.test.ts +93 -0
  295. package/src/__tests__/typed-pipeline-topology.unit.test.ts +230 -1
  296. package/src/__tests__/urp-pipeline-targets.unit.test.ts +25 -1
  297. package/src/__tests__/urp-pipeline.unit.test.ts +4 -3
  298. package/src/__tests__/visibility-extract.integration.test.ts +27 -1
  299. package/src/__tests__/visibility-producer-matrix.integration.test.ts +10 -0
  300. package/src/__tests__/volume-optics-oracle.unit.test.ts +85 -0
  301. package/src/__tests__/volumetric-fog-budget-recovery.integration.test.ts +655 -0
  302. package/src/__tests__/volumetric-fog-component.unit.test.ts +71 -0
  303. package/src/__tests__/volumetric-fog-composite-stage-readback.dawn.test.ts +376 -0
  304. package/src/__tests__/volumetric-fog-errors.unit.test.ts +51 -0
  305. package/src/__tests__/volumetric-fog-inspection.unit.test.ts +77 -0
  306. package/src/__tests__/volumetric-fog-light-capability.unit.test.ts +19 -0
  307. package/src/__tests__/volumetric-fog-light-consumers.integration.test.ts +18 -0
  308. package/src/__tests__/volumetric-fog-light-errors.unit.test.ts +17 -0
  309. package/src/__tests__/volumetric-fog-light-topology.unit.test.ts +21 -0
  310. package/src/__tests__/volumetric-fog-light-transaction.integration.test.ts +53 -0
  311. package/src/__tests__/volumetric-fog-m9-watcher.integration.test.ts +27 -0
  312. package/src/__tests__/volumetric-fog-point-light-owner.integration.test.ts +44 -0
  313. package/src/__tests__/volumetric-fog-point-spot-accumulation.integration.test.ts +49 -0
  314. package/src/__tests__/volumetric-fog-point-spot-contract.unit.test.ts +28 -0
  315. package/src/__tests__/volumetric-fog-point-spot-lkg.integration.test.ts +65 -0
  316. package/src/__tests__/volumetric-fog-point-spot-selection.integration.test.ts +49 -0
  317. package/src/__tests__/volumetric-fog-point-spot-temporal.unit.test.ts +55 -0
  318. package/src/__tests__/volumetric-fog-projector-contract.unit.test.ts +22 -0
  319. package/src/__tests__/volumetric-fog-projector-inspection.unit.test.ts +12 -0
  320. package/src/__tests__/volumetric-fog-projector-surface-volume.integration.test.ts +11 -0
  321. package/src/__tests__/volumetric-fog-projector-transaction.integration.test.ts +26 -0
  322. package/src/__tests__/volumetric-fog-recovery.integration.test.ts +47 -0
  323. package/src/__tests__/volumetric-fog-selected-light-resolution.integration.test.ts +63 -0
  324. package/src/__tests__/volumetric-fog-spot-closure.integration.test.ts +26 -0
  325. package/src/__tests__/volumetric-fog-spot-contract.test-d.ts +21 -0
  326. package/src/__tests__/volumetric-fog-spot-contract.unit.test.ts +27 -0
  327. package/src/__tests__/volumetric-fog-spot-frame-optics.integration.test.ts +14 -0
  328. package/src/__tests__/volumetric-fog-spot-optics.integration.test.ts +29 -0
  329. package/src/__tests__/volumetric-fog-stage-readback.dawn.test.ts +550 -0
  330. package/src/__tests__/volumetric-fog-temporal-selected-light.unit.test.ts +33 -0
  331. package/src/__tests__/volumetric-fog-temporal.unit.test.ts +45 -0
  332. package/src/__tests__/volumetric-fog-topology.unit.test.ts +237 -0
  333. package/src/__tests__/volumetric-fog-transparent.integration.test.ts +27 -0
  334. package/src/assembly/backend-contract.ts +47 -1
  335. package/src/assembly/factory.ts +1480 -505
  336. package/src/assembly/host-contract.ts +4 -0
  337. package/src/assembly/renderer-frame-transaction.ts +75 -0
  338. package/src/assembly/renderer-inspection.ts +112 -0
  339. package/src/assembly/renderer-lifecycle.ts +6 -0
  340. package/src/components/__tests__/motion-blur.unit.test.ts +14 -0
  341. package/src/components/__tests__/schema-contract.unit.test.ts +6 -1
  342. package/src/components/atmosphere.ts +10 -0
  343. package/src/components/camera.ts +2 -1
  344. package/src/components/fog.ts +9 -0
  345. package/src/components/index.ts +3 -0
  346. package/src/components/light-helpers.ts +4 -0
  347. package/src/components/motion-blur.ts +8 -0
  348. package/src/components/spot-light.ts +22 -3
  349. package/src/construct-renderer.ts +40 -3
  350. package/src/device/device-scope.ts +15 -1
  351. package/src/device/gpu-residency.ts +168 -58
  352. package/src/environment/frame.ts +185 -0
  353. package/src/environment/generation.ts +23 -0
  354. package/src/environment/graph.ts +9 -0
  355. package/src/environment/inspection.ts +87 -0
  356. package/src/environment/lifecycle.ts +250 -0
  357. package/src/environment/signature.ts +55 -0
  358. package/src/errors/render.ts +404 -1
  359. package/src/extract/camera.ts +78 -0
  360. package/src/extract/environment.ts +171 -0
  361. package/src/extract/visibility.ts +38 -19
  362. package/src/features/host.ts +36 -5
  363. package/src/features/motion-blur/__tests__/motion-blur-feature.unit.test.ts +67 -0
  364. package/src/features/motion-blur/__tests__/motion-blur-params.unit.test.ts +53 -0
  365. package/src/features/motion-blur/motion-blur-feature.ts +119 -0
  366. package/src/features/motion-blur/motion-blur-params.ts +89 -0
  367. package/src/features/plan.ts +32 -3
  368. package/src/features/prepared-gpu-work.ts +21 -6
  369. package/src/features/prepared-graphics.ts +2 -1
  370. package/src/features/render-graph-raster.ts +2 -1
  371. package/src/features/targets.ts +24 -2
  372. package/src/features/types.ts +13 -0
  373. package/src/fullscreen-post-process-pass.ts +34 -4
  374. package/src/gpu-scene.ts +24 -0
  375. package/src/gpu-usage.ts +2 -0
  376. package/src/ibl/skylight-bind-group.ts +30 -38
  377. package/src/index.ts +104 -7
  378. package/src/inspection-types.ts +48 -0
  379. package/src/internal.ts +5 -0
  380. package/src/light-buffer-layout.ts +59 -26
  381. package/src/materials.ts +119 -24
  382. package/src/pbr-pipeline.ts +91 -29
  383. package/src/pipeline/standard-forward-lane.ts +178 -101
  384. package/src/pipeline/standard-pipeline.ts +176 -105
  385. package/src/pipeline/standard-post.ts +373 -0
  386. package/src/pipeline/standard-profile.ts +26 -3
  387. package/src/pipeline-builder.ts +102 -12
  388. package/src/pipeline-spec.ts +9 -0
  389. package/src/plugin.ts +2 -0
  390. package/src/record/__tests__/fullscreen-temporal-routing.unit.test.ts +25 -0
  391. package/src/record/__tests__/main-pass-geometry.unit.test.ts +47 -0
  392. package/src/record/__tests__/material-bg-cache.unit.test.ts +12 -1
  393. package/src/record/__tests__/material-pipeline-pending-cache.unit.test.ts +22 -0
  394. package/src/record/__tests__/sprite-material-abi.unit.test.ts +75 -0
  395. package/src/record/__tests__/standard-pbr-ubo-layout.unit.test.ts +67 -0
  396. package/src/record/__tests__/temporal-payload.unit.test.ts +164 -0
  397. package/src/record/__tests__/typed-frame-graph-plan.unit.test.ts +127 -0
  398. package/src/record/frame-lighting.ts +103 -89
  399. package/src/record/frame-snapshot.ts +170 -6
  400. package/src/record/frame-targets.ts +21 -8
  401. package/src/record/frame.ts +1014 -7
  402. package/src/record/gpu-timing.ts +224 -0
  403. package/src/record/main-pass-geometry.ts +86 -40
  404. package/src/record/main-pass-material.ts +120 -68
  405. package/src/record/main-pass-sprite-draws.ts +78 -101
  406. package/src/record/main-pass.ts +77 -4
  407. package/src/record/mesh-ssbo.ts +22 -3
  408. package/src/record/render-context.ts +194 -23
  409. package/src/record/shadow-pass.ts +197 -58
  410. package/src/record/skybox-post-pass.ts +97 -41
  411. package/src/record/typed-frame-graph.ts +704 -30
  412. package/src/record/view-ubo.ts +52 -4
  413. package/src/render-contract.ts +181 -6
  414. package/src/render-data.ts +50 -22
  415. package/src/render-graph-primitives.ts +18 -1
  416. package/src/render-pipeline.ts +144 -27
  417. package/src/render-system-extract.ts +522 -72
  418. package/src/render-system.ts +561 -41
  419. package/src/renderer-inspect.ts +66 -0
  420. package/src/scene/render-scene.ts +252 -7
  421. package/src/temporal/__tests__/taa-history-resources.unit.test.ts +45 -0
  422. package/src/temporal/coverage.ts +76 -0
  423. package/src/temporal/frame-coordinator.ts +311 -0
  424. package/src/temporal/frame.ts +128 -0
  425. package/src/temporal/gpu.ts +287 -0
  426. package/src/temporal/history.ts +92 -0
  427. package/src/temporal/index.ts +6 -0
  428. package/src/temporal/inspection.ts +97 -0
  429. package/src/temporal/scene-data-catalog.ts +200 -0
  430. package/src/temporal/scene-data.ts +149 -0
  431. package/src/temporal/standard-scene-data.ts +182 -0
  432. package/src/temporal/taa-history-resources.ts +128 -0
  433. package/src/temporal/taa-history-store.ts +79 -0
  434. package/src/temporal/taa-resolve.ts +34 -0
  435. package/src/temporal/temporal-view.ts +169 -0
  436. package/src/temporal/transaction.ts +17 -0
  437. package/src/temporal/view.ts +161 -0
  438. package/src/transmission/__tests__/capability.unit.test.ts +83 -0
  439. package/src/transmission/__tests__/composition.integration.test.ts +161 -0
  440. package/src/transmission/__tests__/inspection.unit.test.ts +241 -0
  441. package/src/transmission/__tests__/lifecycle.integration.test.ts +96 -0
  442. package/src/transmission/__tests__/oracle.unit.test.ts +131 -0
  443. package/src/transmission/__tests__/projection.unit.test.ts +71 -0
  444. package/src/transmission/__tests__/standard-transmission.dawn.test.ts +437 -0
  445. package/src/transmission/__tests__/transmission-backdrop-topology.unit.test.ts +109 -0
  446. package/src/transmission/__tests__/transmission-backdrop.dawn.test.ts +105 -0
  447. package/src/transmission/backdrop.ts +572 -0
  448. package/src/transmission/inspection.ts +190 -0
  449. package/src/transmission/oracle.ts +141 -0
  450. package/src/transmission/projection.ts +83 -0
  451. package/src/typed-render-graph-primitives.ts +194 -11
  452. package/src/typed-shadow-atlas.ts +17 -0
  453. package/src/typed-shadow-passes.ts +6 -5
  454. package/src/volume/capability.ts +173 -0
  455. package/src/volume/component.ts +110 -0
  456. package/src/volume/extract.ts +21 -0
  457. package/src/volume/inspection.ts +78 -0
  458. package/src/volume/passes.ts +680 -0
  459. package/src/volume/recovery.ts +86 -0
  460. package/src/volume/resources.ts +236 -0
  461. package/src/volume/temporal.ts +89 -0
  462. package/dist/__tests__/authoring-path.unit.test.d.ts +0 -2
  463. package/dist/__tests__/authoring-path.unit.test.d.ts.map +0 -1
  464. package/dist/__tests__/backend-selection-rhi-capture.test-d.d.ts +0 -2
  465. package/dist/__tests__/backend-selection-rhi-capture.test-d.d.ts.map +0 -1
  466. package/dist/__tests__/callback-inference.test-d.d.ts +0 -2
  467. package/dist/__tests__/callback-inference.test-d.d.ts.map +0 -1
  468. package/dist/__tests__/cluster-binner.test.d.ts +0 -2
  469. package/dist/__tests__/cluster-binner.test.d.ts.map +0 -1
  470. package/dist/__tests__/cluster-grid-default-surface.unit.test.d.ts +0 -2
  471. package/dist/__tests__/cluster-grid-default-surface.unit.test.d.ts.map +0 -1
  472. package/dist/__tests__/construct-renderer-identity.unit.test.d.ts +0 -2
  473. package/dist/__tests__/construct-renderer-identity.unit.test.d.ts.map +0 -1
  474. package/dist/__tests__/device-scope-lifecycle.unit.test.d.ts +0 -2
  475. package/dist/__tests__/device-scope-lifecycle.unit.test.d.ts.map +0 -1
  476. package/dist/__tests__/device-scope-stale-matrix.unit.test.d.ts +0 -2
  477. package/dist/__tests__/device-scope-stale-matrix.unit.test.d.ts.map +0 -1
  478. package/dist/__tests__/direct-extract-consumer-contract.test-d.d.ts +0 -2
  479. package/dist/__tests__/direct-extract-consumer-contract.test-d.d.ts.map +0 -1
  480. package/dist/__tests__/direct-light-snapshot.unit.test.d.ts +0 -2
  481. package/dist/__tests__/direct-light-snapshot.unit.test.d.ts.map +0 -1
  482. package/dist/__tests__/factory-contract.integration.test.d.ts +0 -2
  483. package/dist/__tests__/factory-contract.integration.test.d.ts.map +0 -1
  484. package/dist/__tests__/fallback-row-stride-surface.unit.test.d.ts +0 -2
  485. package/dist/__tests__/fallback-row-stride-surface.unit.test.d.ts.map +0 -1
  486. package/dist/__tests__/frame-observation.contract.test.d.ts +0 -2
  487. package/dist/__tests__/frame-observation.contract.test.d.ts.map +0 -1
  488. package/dist/__tests__/fullscreen-feature-plan.unit.test.d.ts +0 -2
  489. package/dist/__tests__/fullscreen-feature-plan.unit.test.d.ts.map +0 -1
  490. package/dist/__tests__/glyph-text-layout-recovery.unit.test.d.ts +0 -2
  491. package/dist/__tests__/glyph-text-layout-recovery.unit.test.d.ts.map +0 -1
  492. package/dist/__tests__/gpu-driven-indirect-raster-evidence.d.ts +0 -6
  493. package/dist/__tests__/gpu-driven-indirect-raster-evidence.d.ts.map +0 -1
  494. package/dist/__tests__/gpu-driven-indirect-raster.browser.test.d.ts +0 -2
  495. package/dist/__tests__/gpu-driven-indirect-raster.browser.test.d.ts.map +0 -1
  496. package/dist/__tests__/gpu-driven-indirect-raster.dawn.test.d.ts +0 -2
  497. package/dist/__tests__/gpu-driven-indirect-raster.dawn.test.d.ts.map +0 -1
  498. package/dist/__tests__/gpu-driven-production.integration.test.d.ts +0 -2
  499. package/dist/__tests__/gpu-driven-production.integration.test.d.ts.map +0 -1
  500. package/dist/__tests__/gpu-driven-scaling.unit.test.d.ts +0 -2
  501. package/dist/__tests__/gpu-driven-scaling.unit.test.d.ts.map +0 -1
  502. package/dist/__tests__/gpu-driven-view-gpu-evidence.d.ts +0 -18
  503. package/dist/__tests__/gpu-driven-view-gpu-evidence.d.ts.map +0 -1
  504. package/dist/__tests__/gpu-driven-view-graph.integration.test.d.ts +0 -2
  505. package/dist/__tests__/gpu-driven-view-graph.integration.test.d.ts.map +0 -1
  506. package/dist/__tests__/gpu-driven-view-reference.d.ts +0 -15
  507. package/dist/__tests__/gpu-driven-view-reference.d.ts.map +0 -1
  508. package/dist/__tests__/gpu-driven-view.browser.test.d.ts +0 -2
  509. package/dist/__tests__/gpu-driven-view.browser.test.d.ts.map +0 -1
  510. package/dist/__tests__/gpu-driven-view.dawn.test.d.ts +0 -2
  511. package/dist/__tests__/gpu-driven-view.dawn.test.d.ts.map +0 -1
  512. package/dist/__tests__/gpu-driven-view.unit.test.d.ts +0 -2
  513. package/dist/__tests__/gpu-driven-view.unit.test.d.ts.map +0 -1
  514. package/dist/__tests__/gpu-resource-store-stride.unit.test.d.ts +0 -2
  515. package/dist/__tests__/gpu-resource-store-stride.unit.test.d.ts.map +0 -1
  516. package/dist/__tests__/gpu-scene-render-graph-gpu.d.ts +0 -7
  517. package/dist/__tests__/gpu-scene-render-graph-gpu.d.ts.map +0 -1
  518. package/dist/__tests__/gpu-scene-render-graph.browser.test.d.ts +0 -2
  519. package/dist/__tests__/gpu-scene-render-graph.browser.test.d.ts.map +0 -1
  520. package/dist/__tests__/gpu-scene-render-graph.dawn.test.d.ts +0 -2
  521. package/dist/__tests__/gpu-scene-render-graph.dawn.test.d.ts.map +0 -1
  522. package/dist/__tests__/gpu-scene-schema.unit.test.d.ts +0 -2
  523. package/dist/__tests__/gpu-scene-schema.unit.test.d.ts.map +0 -1
  524. package/dist/__tests__/gpu-scene.dawn.test.d.ts +0 -2
  525. package/dist/__tests__/gpu-scene.dawn.test.d.ts.map +0 -1
  526. package/dist/__tests__/gpu-scene.unit.test.d.ts +0 -2
  527. package/dist/__tests__/gpu-scene.unit.test.d.ts.map +0 -1
  528. package/dist/__tests__/ibl/face-uniforms.unit.test.d.ts +0 -2
  529. package/dist/__tests__/ibl/face-uniforms.unit.test.d.ts.map +0 -1
  530. package/dist/__tests__/infinite-grid-no-vertex-probe.unit.test.d.ts +0 -2
  531. package/dist/__tests__/infinite-grid-no-vertex-probe.unit.test.d.ts.map +0 -1
  532. package/dist/__tests__/injection-kind-owner.test-d.d.ts +0 -2
  533. package/dist/__tests__/injection-kind-owner.test-d.d.ts.map +0 -1
  534. package/dist/__tests__/ktx2-basis-gpu-consumer.dawn.test.d.ts +0 -2
  535. package/dist/__tests__/ktx2-basis-gpu-consumer.dawn.test.d.ts.map +0 -1
  536. package/dist/__tests__/light-buffer-layout.unit.test.d.ts +0 -2
  537. package/dist/__tests__/light-buffer-layout.unit.test.d.ts.map +0 -1
  538. package/dist/__tests__/material-contract-inventory.unit.test.d.ts +0 -2
  539. package/dist/__tests__/material-contract-inventory.unit.test.d.ts.map +0 -1
  540. package/dist/__tests__/material-cooked-projection.unit.test.d.ts +0 -2
  541. package/dist/__tests__/material-cooked-projection.unit.test.d.ts.map +0 -1
  542. package/dist/__tests__/material-snapshot-mutation.integration.test.d.ts +0 -2
  543. package/dist/__tests__/material-snapshot-mutation.integration.test.d.ts.map +0 -1
  544. package/dist/__tests__/material-static-dynamic-route.unit.test.d.ts +0 -2
  545. package/dist/__tests__/material-static-dynamic-route.unit.test.d.ts.map +0 -1
  546. package/dist/__tests__/material-stride-surface.unit.test.d.ts +0 -2
  547. package/dist/__tests__/material-stride-surface.unit.test.d.ts.map +0 -1
  548. package/dist/__tests__/mesh-buffer-usage-surface.unit.test.d.ts +0 -2
  549. package/dist/__tests__/mesh-buffer-usage-surface.unit.test.d.ts.map +0 -1
  550. package/dist/__tests__/mesh-material-bindings.unit.test.d.ts +0 -2
  551. package/dist/__tests__/mesh-material-bindings.unit.test.d.ts.map +0 -1
  552. package/dist/__tests__/mesh-stride-surface.unit.test.d.ts +0 -2
  553. package/dist/__tests__/mesh-stride-surface.unit.test.d.ts.map +0 -1
  554. package/dist/__tests__/morph-culling-reentry.dawn.test.d.ts +0 -2
  555. package/dist/__tests__/morph-culling-reentry.dawn.test.d.ts.map +0 -1
  556. package/dist/__tests__/morph-feature-gpu-work.integration.test.d.ts +0 -2
  557. package/dist/__tests__/morph-feature-gpu-work.integration.test.d.ts.map +0 -1
  558. package/dist/__tests__/morph-feature-plan.unit.test.d.ts +0 -2
  559. package/dist/__tests__/morph-feature-plan.unit.test.d.ts.map +0 -1
  560. package/dist/__tests__/morph-visual-evidence.integration.test.d.ts +0 -2
  561. package/dist/__tests__/morph-visual-evidence.integration.test.d.ts.map +0 -1
  562. package/dist/__tests__/nineslice-handle-surface.unit.test.d.ts +0 -2
  563. package/dist/__tests__/nineslice-handle-surface.unit.test.d.ts.map +0 -1
  564. package/dist/__tests__/post-process-error-policy-owner.unit.test.d.ts +0 -2
  565. package/dist/__tests__/post-process-error-policy-owner.unit.test.d.ts.map +0 -1
  566. package/dist/__tests__/prepared-buffer-kind-owner.test-d.d.ts +0 -2
  567. package/dist/__tests__/prepared-buffer-kind-owner.test-d.d.ts.map +0 -1
  568. package/dist/__tests__/prepared-graphics-capability.unit.test.d.ts +0 -2
  569. package/dist/__tests__/prepared-graphics-capability.unit.test.d.ts.map +0 -1
  570. package/dist/__tests__/prepared-graphics-generation.unit.test.d.ts +0 -2
  571. package/dist/__tests__/prepared-graphics-generation.unit.test.d.ts.map +0 -1
  572. package/dist/__tests__/prepared-graphics-lifecycle.unit.test.d.ts +0 -2
  573. package/dist/__tests__/prepared-graphics-lifecycle.unit.test.d.ts.map +0 -1
  574. package/dist/__tests__/prepared-graphics-pipeline-warmup.unit.test.d.ts +0 -2
  575. package/dist/__tests__/prepared-graphics-pipeline-warmup.unit.test.d.ts.map +0 -1
  576. package/dist/__tests__/prepared-graphics-resolver.unit.test.d.ts +0 -2
  577. package/dist/__tests__/prepared-graphics-resolver.unit.test.d.ts.map +0 -1
  578. package/dist/__tests__/prepared-graphics-snapshot.unit.test.d.ts +0 -2
  579. package/dist/__tests__/prepared-graphics-snapshot.unit.test.d.ts.map +0 -1
  580. package/dist/__tests__/prepared-graphics-store.unit.test.d.ts +0 -2
  581. package/dist/__tests__/prepared-graphics-store.unit.test.d.ts.map +0 -1
  582. package/dist/__tests__/prepared-graphics-validation.unit.test.d.ts +0 -2
  583. package/dist/__tests__/prepared-graphics-validation.unit.test.d.ts.map +0 -1
  584. package/dist/__tests__/profiler-default-off.test.d.ts +0 -2
  585. package/dist/__tests__/profiler-default-off.test.d.ts.map +0 -1
  586. package/dist/__tests__/profiler-phase-catalog.test.d.ts +0 -2
  587. package/dist/__tests__/profiler-phase-catalog.test.d.ts.map +0 -1
  588. package/dist/__tests__/profiler-phase-integration.test.d.ts +0 -2
  589. package/dist/__tests__/profiler-phase-integration.test.d.ts.map +0 -1
  590. package/dist/__tests__/profiler-phase-token.test.d.ts +0 -2
  591. package/dist/__tests__/profiler-phase-token.test.d.ts.map +0 -1
  592. package/dist/__tests__/public-surface.test-d.d.ts +0 -2
  593. package/dist/__tests__/public-surface.test-d.d.ts.map +0 -1
  594. package/dist/__tests__/recover-error-policy-owner.unit.test.d.ts +0 -2
  595. package/dist/__tests__/recover-error-policy-owner.unit.test.d.ts.map +0 -1
  596. package/dist/__tests__/render-error-code-owner.test-d.d.ts +0 -2
  597. package/dist/__tests__/render-error-code-owner.test-d.d.ts.map +0 -1
  598. package/dist/__tests__/render-error-detail-ssot.test-d.d.ts +0 -2
  599. package/dist/__tests__/render-error-detail-ssot.test-d.d.ts.map +0 -1
  600. package/dist/__tests__/render-error-exhaustive.test-d.d.ts +0 -2
  601. package/dist/__tests__/render-error-exhaustive.test-d.d.ts.map +0 -1
  602. package/dist/__tests__/render-feature-assembly.test-d.d.ts +0 -2
  603. package/dist/__tests__/render-feature-assembly.test-d.d.ts.map +0 -1
  604. package/dist/__tests__/render-feature-context.test-d.d.ts +0 -2
  605. package/dist/__tests__/render-feature-context.test-d.d.ts.map +0 -1
  606. package/dist/__tests__/render-feature-contract.test-d.d.ts +0 -2
  607. package/dist/__tests__/render-feature-contract.test-d.d.ts.map +0 -1
  608. package/dist/__tests__/render-feature-diagnostics.unit.test.d.ts +0 -2
  609. package/dist/__tests__/render-feature-diagnostics.unit.test.d.ts.map +0 -1
  610. package/dist/__tests__/render-feature-docs.unit.test.d.ts +0 -2
  611. package/dist/__tests__/render-feature-docs.unit.test.d.ts.map +0 -1
  612. package/dist/__tests__/render-feature-errors.unit.test.d.ts +0 -2
  613. package/dist/__tests__/render-feature-errors.unit.test.d.ts.map +0 -1
  614. package/dist/__tests__/render-feature-gpu-work.integration.test.d.ts +0 -2
  615. package/dist/__tests__/render-feature-gpu-work.integration.test.d.ts.map +0 -1
  616. package/dist/__tests__/render-feature-plan.test-d.d.ts +0 -2
  617. package/dist/__tests__/render-feature-plan.test-d.d.ts.map +0 -1
  618. package/dist/__tests__/render-feature-plan.unit.test.d.ts +0 -2
  619. package/dist/__tests__/render-feature-plan.unit.test.d.ts.map +0 -1
  620. package/dist/__tests__/render-feature-prepared-diagnostics.unit.test.d.ts +0 -2
  621. package/dist/__tests__/render-feature-prepared-diagnostics.unit.test.d.ts.map +0 -1
  622. package/dist/__tests__/render-feature-prepared-errors.test-d.d.ts +0 -2
  623. package/dist/__tests__/render-feature-prepared-errors.test-d.d.ts.map +0 -1
  624. package/dist/__tests__/render-feature-prepared-graphics-docs.unit.test.d.ts +0 -2
  625. package/dist/__tests__/render-feature-prepared-graphics-docs.unit.test.d.ts.map +0 -1
  626. package/dist/__tests__/render-feature-prepared-graphics-isolation.unit.test.d.ts +0 -2
  627. package/dist/__tests__/render-feature-prepared-graphics-isolation.unit.test.d.ts.map +0 -1
  628. package/dist/__tests__/render-feature-prepared-graphics-negative.test-d.d.ts +0 -2
  629. package/dist/__tests__/render-feature-prepared-graphics-negative.test-d.d.ts.map +0 -1
  630. package/dist/__tests__/render-feature-prepared-graphics.test-d.d.ts +0 -2
  631. package/dist/__tests__/render-feature-prepared-graphics.test-d.d.ts.map +0 -1
  632. package/dist/__tests__/render-feature-prepared-graphics.unit.test.d.ts +0 -2
  633. package/dist/__tests__/render-feature-prepared-graphics.unit.test.d.ts.map +0 -1
  634. package/dist/__tests__/render-feature-public-surface.test-d.d.ts +0 -2
  635. package/dist/__tests__/render-feature-public-surface.test-d.d.ts.map +0 -1
  636. package/dist/__tests__/render-feature-retry.unit.test.d.ts +0 -2
  637. package/dist/__tests__/render-feature-retry.unit.test.d.ts.map +0 -1
  638. package/dist/__tests__/render-feature-scheduling.unit.test.d.ts +0 -2
  639. package/dist/__tests__/render-feature-scheduling.unit.test.d.ts.map +0 -1
  640. package/dist/__tests__/render-feature-stage-failed-policy-owner.unit.test.d.ts +0 -2
  641. package/dist/__tests__/render-feature-stage-failed-policy-owner.unit.test.d.ts.map +0 -1
  642. package/dist/__tests__/render-feature-stage-fault-recovery.unit.test.d.ts +0 -2
  643. package/dist/__tests__/render-feature-stage-fault-recovery.unit.test.d.ts.map +0 -1
  644. package/dist/__tests__/render-feature-visibility-context.test-d.d.ts +0 -2
  645. package/dist/__tests__/render-feature-visibility-context.test-d.d.ts.map +0 -1
  646. package/dist/__tests__/render-feature-visibility-context.unit.test.d.ts +0 -2
  647. package/dist/__tests__/render-feature-visibility-context.unit.test.d.ts.map +0 -1
  648. package/dist/__tests__/render-feature-zero-work.unit.test.d.ts +0 -2
  649. package/dist/__tests__/render-feature-zero-work.unit.test.d.ts.map +0 -1
  650. package/dist/__tests__/render-graph-compute-raster-gpu.d.ts +0 -7
  651. package/dist/__tests__/render-graph-compute-raster-gpu.d.ts.map +0 -1
  652. package/dist/__tests__/render-graph-compute-raster.browser.test.d.ts +0 -2
  653. package/dist/__tests__/render-graph-compute-raster.browser.test.d.ts.map +0 -1
  654. package/dist/__tests__/render-graph-compute-raster.dawn.test.d.ts +0 -2
  655. package/dist/__tests__/render-graph-compute-raster.dawn.test.d.ts.map +0 -1
  656. package/dist/__tests__/render-graph-hzb-gpu.d.ts +0 -7
  657. package/dist/__tests__/render-graph-hzb-gpu.d.ts.map +0 -1
  658. package/dist/__tests__/render-graph-hzb.browser.test.d.ts +0 -2
  659. package/dist/__tests__/render-graph-hzb.browser.test.d.ts.map +0 -1
  660. package/dist/__tests__/render-graph-hzb.dawn.test.d.ts +0 -2
  661. package/dist/__tests__/render-graph-hzb.dawn.test.d.ts.map +0 -1
  662. package/dist/__tests__/render-graph-primitives.unit.test.d.ts +0 -2
  663. package/dist/__tests__/render-graph-primitives.unit.test.d.ts.map +0 -1
  664. package/dist/__tests__/render-graph-storage-indirect-gpu.d.ts +0 -12
  665. package/dist/__tests__/render-graph-storage-indirect-gpu.d.ts.map +0 -1
  666. package/dist/__tests__/render-graph-storage-indirect.browser.test.d.ts +0 -2
  667. package/dist/__tests__/render-graph-storage-indirect.browser.test.d.ts.map +0 -1
  668. package/dist/__tests__/render-graph-storage-indirect.dawn.test.d.ts +0 -2
  669. package/dist/__tests__/render-graph-storage-indirect.dawn.test.d.ts.map +0 -1
  670. package/dist/__tests__/render-scene-projection.unit.test.d.ts +0 -2
  671. package/dist/__tests__/render-scene-projection.unit.test.d.ts.map +0 -1
  672. package/dist/__tests__/renderer-factory-material-contract.unit.test.d.ts +0 -2
  673. package/dist/__tests__/renderer-factory-material-contract.unit.test.d.ts.map +0 -1
  674. package/dist/__tests__/renderer-lifecycle.integration.test.d.ts +0 -2
  675. package/dist/__tests__/renderer-lifecycle.integration.test.d.ts.map +0 -1
  676. package/dist/__tests__/rhi-backend-package-boundary.unit.test.d.ts +0 -2
  677. package/dist/__tests__/rhi-backend-package-boundary.unit.test.d.ts.map +0 -1
  678. package/dist/__tests__/scriptable-pack-consumer.test-d.d.ts +0 -2
  679. package/dist/__tests__/scriptable-pack-consumer.test-d.d.ts.map +0 -1
  680. package/dist/__tests__/shader-stage-surface.unit.test.d.ts +0 -2
  681. package/dist/__tests__/shader-stage-surface.unit.test.d.ts.map +0 -1
  682. package/dist/__tests__/ssao-config.unit.test.d.ts +0 -2
  683. package/dist/__tests__/ssao-config.unit.test.d.ts.map +0 -1
  684. package/dist/__tests__/ssao-kernel-surface.unit.test.d.ts +0 -2
  685. package/dist/__tests__/ssao-kernel-surface.unit.test.d.ts.map +0 -1
  686. package/dist/__tests__/standard-clustered-pipeline.unit.test.d.ts +0 -2
  687. package/dist/__tests__/standard-clustered-pipeline.unit.test.d.ts.map +0 -1
  688. package/dist/__tests__/standard-descriptors.test-d.d.ts +0 -2
  689. package/dist/__tests__/standard-descriptors.test-d.d.ts.map +0 -1
  690. package/dist/__tests__/standard-pipeline.dawn.test.d.ts +0 -2
  691. package/dist/__tests__/standard-pipeline.dawn.test.d.ts.map +0 -1
  692. package/dist/__tests__/standard-pipeline.integration.test.d.ts +0 -2
  693. package/dist/__tests__/standard-pipeline.integration.test.d.ts.map +0 -1
  694. package/dist/__tests__/standard-profile-oracle.unit.test.d.ts +0 -2
  695. package/dist/__tests__/standard-profile-oracle.unit.test.d.ts.map +0 -1
  696. package/dist/__tests__/surface-configuration.unit.test.d.ts +0 -2
  697. package/dist/__tests__/surface-configuration.unit.test.d.ts.map +0 -1
  698. package/dist/__tests__/texture-usage-surface.unit.test.d.ts +0 -2
  699. package/dist/__tests__/texture-usage-surface.unit.test.d.ts.map +0 -1
  700. package/dist/__tests__/typed-pipeline-topology.unit.test.d.ts +0 -2
  701. package/dist/__tests__/typed-pipeline-topology.unit.test.d.ts.map +0 -1
  702. package/dist/__tests__/urp-pipeline-targets.unit.test.d.ts +0 -2
  703. package/dist/__tests__/urp-pipeline-targets.unit.test.d.ts.map +0 -1
  704. package/dist/__tests__/urp-pipeline.unit.test.d.ts +0 -2
  705. package/dist/__tests__/urp-pipeline.unit.test.d.ts.map +0 -1
  706. package/dist/__tests__/vertex-color-pipeline.unit.test.d.ts +0 -2
  707. package/dist/__tests__/vertex-color-pipeline.unit.test.d.ts.map +0 -1
  708. package/dist/__tests__/vertex-layout-consumer.unit.test.d.ts +0 -2
  709. package/dist/__tests__/vertex-layout-consumer.unit.test.d.ts.map +0 -1
  710. package/dist/__tests__/visibility-diagnostics.integration.test.d.ts +0 -2
  711. package/dist/__tests__/visibility-diagnostics.integration.test.d.ts.map +0 -1
  712. package/dist/__tests__/visibility-extract.integration.test.d.ts +0 -2
  713. package/dist/__tests__/visibility-extract.integration.test.d.ts.map +0 -1
  714. package/dist/__tests__/visibility-extract.perf.test.d.ts +0 -2
  715. package/dist/__tests__/visibility-extract.perf.test.d.ts.map +0 -1
  716. package/dist/__tests__/visibility-feature-dedup.integration.test.d.ts +0 -2
  717. package/dist/__tests__/visibility-feature-dedup.integration.test.d.ts.map +0 -1
  718. package/dist/__tests__/visibility-hidden-report.unit.test.d.ts +0 -2
  719. package/dist/__tests__/visibility-hidden-report.unit.test.d.ts.map +0 -1
  720. package/dist/__tests__/visibility-instances.integration.test.d.ts +0 -2
  721. package/dist/__tests__/visibility-instances.integration.test.d.ts.map +0 -1
  722. package/dist/__tests__/visibility-multi-world.integration.test.d.ts +0 -2
  723. package/dist/__tests__/visibility-multi-world.integration.test.d.ts.map +0 -1
  724. package/dist/__tests__/visibility-producer-matrix.integration.test.d.ts +0 -2
  725. package/dist/__tests__/visibility-producer-matrix.integration.test.d.ts.map +0 -1
  726. package/dist/__tests__/visibility-producer-matrix.unit.test.d.ts +0 -2
  727. package/dist/__tests__/visibility-producer-matrix.unit.test.d.ts.map +0 -1
  728. package/dist/__tests__/visibility-public-surface.test-d.d.ts +0 -2
  729. package/dist/__tests__/visibility-public-surface.test-d.d.ts.map +0 -1
  730. package/dist/__tests__/visibility-resolver.unit.test.d.ts +0 -2
  731. package/dist/__tests__/visibility-resolver.unit.test.d.ts.map +0 -1
  732. package/dist/__tests__/visibility-resource-short-circuit.unit.test.d.ts +0 -2
  733. package/dist/__tests__/visibility-resource-short-circuit.unit.test.d.ts.map +0 -1
  734. package/dist/__tests__/visibility-snapshot-contract.unit.test.d.ts +0 -2
  735. package/dist/__tests__/visibility-snapshot-contract.unit.test.d.ts.map +0 -1
  736. package/dist/__tests__/vocabulary.unit.test.d.ts +0 -2
  737. package/dist/__tests__/vocabulary.unit.test.d.ts.map +0 -1
  738. package/dist/chunk-CQXZC5XI.mjs.map +0 -1
  739. package/dist/chunk-HCOGPHKS.mjs +0 -93
  740. package/dist/chunk-HCOGPHKS.mjs.map +0 -1
  741. package/dist/chunk-YEJIUW7D.mjs.map +0 -1
  742. package/dist/components/__tests__/points-lines-admission.unit.test.d.ts +0 -2
  743. package/dist/components/__tests__/points-lines-admission.unit.test.d.ts.map +0 -1
  744. package/dist/components/__tests__/points-lines-schema.unit.test.d.ts +0 -2
  745. package/dist/components/__tests__/points-lines-schema.unit.test.d.ts.map +0 -1
  746. package/dist/components/__tests__/schema-contract.unit.test.d.ts +0 -2
  747. package/dist/components/__tests__/schema-contract.unit.test.d.ts.map +0 -1
  748. package/dist/components/__tests__/schema-parity.unit.test.d.ts +0 -2
  749. package/dist/components/__tests__/schema-parity.unit.test.d.ts.map +0 -1
  750. package/dist/components/__tests__/visibility-schema.unit.test.d.ts +0 -2
  751. package/dist/components/__tests__/visibility-schema.unit.test.d.ts.map +0 -1
  752. package/dist/points-lines/__tests__/bounds.unit.test.d.ts +0 -2
  753. package/dist/points-lines/__tests__/bounds.unit.test.d.ts.map +0 -1
  754. package/dist/points-lines/__tests__/cache-recovery.unit.test.d.ts +0 -2
  755. package/dist/points-lines/__tests__/cache-recovery.unit.test.d.ts.map +0 -1
  756. package/dist/points-lines/__tests__/inspection-lanes.integration.test.d.ts +0 -2
  757. package/dist/points-lines/__tests__/inspection-lanes.integration.test.d.ts.map +0 -1
  758. package/dist/points-lines/__tests__/inspection.unit.test.d.ts +0 -2
  759. package/dist/points-lines/__tests__/inspection.unit.test.d.ts.map +0 -1
  760. package/dist/points-lines/__tests__/lane-structural.integration.test.d.ts +0 -2
  761. package/dist/points-lines/__tests__/lane-structural.integration.test.d.ts.map +0 -1
  762. package/dist/points-lines/__tests__/points-lines.bench.d.ts +0 -2
  763. package/dist/points-lines/__tests__/points-lines.bench.d.ts.map +0 -1
  764. package/dist/points-lines/__tests__/raster-lane.integration.test.d.ts +0 -2
  765. package/dist/points-lines/__tests__/raster-lane.integration.test.d.ts.map +0 -1
  766. package/dist/points-lines/__tests__/snapshot.unit.test.d.ts +0 -2
  767. package/dist/points-lines/__tests__/snapshot.unit.test.d.ts.map +0 -1
  768. package/dist/points-lines/__tests__/view-ubo.unit.test.d.ts +0 -2
  769. package/dist/points-lines/__tests__/view-ubo.unit.test.d.ts.map +0 -1
  770. package/dist/record/__tests__/material-bg-cache.unit.test.d.ts +0 -2
  771. package/dist/record/__tests__/material-bg-cache.unit.test.d.ts.map +0 -1
  772. package/dist/record/__tests__/material-slot-plan.unit.test.d.ts +0 -2
  773. package/dist/record/__tests__/material-slot-plan.unit.test.d.ts.map +0 -1
  774. package/dist/record/__tests__/typed-frame-graph-plan.unit.test.d.ts +0 -2
  775. package/dist/record/__tests__/typed-frame-graph-plan.unit.test.d.ts.map +0 -1
  776. package/dist/record/__tests__/video-texture-view.unit.test.d.ts +0 -2
  777. 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
  });