@forgeax/engine-render 0.1.32 → 0.1.34

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 (624) hide show
  1. package/README.md +1374 -142
  2. package/dist/assembly/analytic-fog-registration.d.ts +3 -0
  3. package/dist/assembly/analytic-fog-registration.d.ts.map +1 -0
  4. package/dist/assembly/backend-contract.d.ts +2 -0
  5. package/dist/assembly/backend-contract.d.ts.map +1 -1
  6. package/dist/assembly/barrel-distortion-inspection.d.ts +3 -0
  7. package/dist/assembly/barrel-distortion-inspection.d.ts.map +1 -0
  8. package/dist/assembly/canvas-draw-guard.d.ts +3 -0
  9. package/dist/assembly/canvas-draw-guard.d.ts.map +1 -0
  10. package/dist/assembly/cloud-shadow-entry.d.ts +4 -0
  11. package/dist/assembly/cloud-shadow-entry.d.ts.map +1 -0
  12. package/dist/assembly/dynamic-geometry-host.d.ts +4 -1
  13. package/dist/assembly/dynamic-geometry-host.d.ts.map +1 -1
  14. package/dist/assembly/dynamic-geometry-runtime.d.ts +1 -1
  15. package/dist/assembly/dynamic-geometry-runtime.d.ts.map +1 -1
  16. package/dist/assembly/factory.d.ts +1 -1
  17. package/dist/assembly/factory.d.ts.map +1 -1
  18. package/dist/assembly/fxaa-registration.d.ts +4 -0
  19. package/dist/assembly/fxaa-registration.d.ts.map +1 -0
  20. package/dist/assembly/host-contract.d.ts +3 -3
  21. package/dist/assembly/host-contract.d.ts.map +1 -1
  22. package/dist/assembly/lod-observation.d.ts +4 -0
  23. package/dist/assembly/lod-observation.d.ts.map +1 -0
  24. package/dist/assembly/material/assembly.d.ts.map +1 -1
  25. package/dist/assembly/material/pipeline-helpers.d.ts +5 -0
  26. package/dist/assembly/material/pipeline-helpers.d.ts.map +1 -1
  27. package/dist/assembly/material-shader-policy.d.ts +16 -2
  28. package/dist/assembly/material-shader-policy.d.ts.map +1 -1
  29. package/dist/assembly/material-shader-prewarm.d.ts.map +1 -1
  30. package/dist/assembly/motion-blur-registration.d.ts +3 -0
  31. package/dist/assembly/motion-blur-registration.d.ts.map +1 -0
  32. package/dist/assembly/recovery/device-loss-fanout.d.ts.map +1 -1
  33. package/dist/assembly/recovery/renderer-recover.d.ts +2 -0
  34. package/dist/assembly/recovery/renderer-recover.d.ts.map +1 -1
  35. package/dist/assembly/render-feature-post-processes.d.ts +6 -0
  36. package/dist/assembly/render-feature-post-processes.d.ts.map +1 -0
  37. package/dist/assembly/renderer-facade.d.ts.map +1 -1
  38. package/dist/assembly/renderer-feature-inspection.d.ts +1 -1
  39. package/dist/assembly/renderer-feature-inspection.d.ts.map +1 -1
  40. package/dist/assembly/renderer-frame-transaction.d.ts +12 -0
  41. package/dist/assembly/renderer-frame-transaction.d.ts.map +1 -1
  42. package/dist/assembly/renderer-inspection.d.ts +11 -0
  43. package/dist/assembly/renderer-inspection.d.ts.map +1 -1
  44. package/dist/assembly/temporal-post-process-prewarm.d.ts +13 -0
  45. package/dist/assembly/temporal-post-process-prewarm.d.ts.map +1 -0
  46. package/dist/assembly/webgpu-pbr-ready.d.ts +1 -0
  47. package/dist/assembly/webgpu-pbr-ready.d.ts.map +1 -1
  48. package/dist/assembly/webgpu-ready-contract.d.ts +4 -3
  49. package/dist/assembly/webgpu-ready-contract.d.ts.map +1 -1
  50. package/dist/assembly/webgpu-ready-per-pass.d.ts +39 -0
  51. package/dist/assembly/webgpu-ready-per-pass.d.ts.map +1 -0
  52. package/dist/assembly/webgpu-ready.d.ts +2 -2
  53. package/dist/assembly/webgpu-ready.d.ts.map +1 -1
  54. package/dist/assembly/webgpu-renderer-bootstrap.d.ts.map +1 -1
  55. package/dist/assembly/webgpu-renderer-observation-owner.d.ts +35 -0
  56. package/dist/assembly/webgpu-renderer-observation-owner.d.ts.map +1 -0
  57. package/dist/assembly/webgpu-renderer.d.ts +1 -1
  58. package/dist/assembly/webgpu-renderer.d.ts.map +1 -1
  59. package/dist/authoring.mjs +1 -1
  60. package/dist/barrel-distortion.d.ts +47 -0
  61. package/dist/barrel-distortion.d.ts.map +1 -0
  62. package/dist/bloom-admission.d.ts +13 -0
  63. package/dist/bloom-admission.d.ts.map +1 -0
  64. package/dist/{chunk-PA5DRJNI.mjs → chunk-ELJFJ2BU.mjs} +2 -2
  65. package/dist/chunk-ELJFJ2BU.mjs.map +1 -0
  66. package/dist/{chunk-JCIRYQ4V.mjs → chunk-HEB564WF.mjs} +10 -4
  67. package/dist/chunk-HEB564WF.mjs.map +1 -0
  68. package/dist/{chunk-JRIPCMYC.mjs → chunk-MKDIT2EM.mjs} +1584 -375
  69. package/dist/chunk-MKDIT2EM.mjs.map +1 -0
  70. package/dist/{chunk-S665NJNF.mjs → chunk-OCDLEI6I.mjs} +3 -3
  71. package/dist/{chunk-S665NJNF.mjs.map → chunk-OCDLEI6I.mjs.map} +1 -1
  72. package/dist/chunk-ODZWR6JQ.mjs +4927 -0
  73. package/dist/chunk-ODZWR6JQ.mjs.map +1 -0
  74. package/dist/{chunk-DRR7D3KX.mjs → chunk-SHFUFJ5N.mjs} +192 -18
  75. package/dist/chunk-SHFUFJ5N.mjs.map +1 -0
  76. package/dist/{chunk-4NNZQCL3.mjs → chunk-UI5SFODX.mjs} +223 -277
  77. package/dist/chunk-UI5SFODX.mjs.map +1 -0
  78. package/dist/{chunk-GWCEMDVR.mjs → chunk-UINHLCZA.mjs} +787 -80
  79. package/dist/chunk-UINHLCZA.mjs.map +1 -0
  80. package/dist/{chunk-6TFBXLRJ.mjs → chunk-WEX45HKH.mjs} +222 -337
  81. package/dist/chunk-WEX45HKH.mjs.map +1 -0
  82. package/dist/cloud/density.d.ts +55 -0
  83. package/dist/cloud/density.d.ts.map +1 -0
  84. package/dist/cloud/extract.d.ts +25 -0
  85. package/dist/cloud/extract.d.ts.map +1 -0
  86. package/dist/cloud/feature.d.ts +57 -0
  87. package/dist/cloud/feature.d.ts.map +1 -0
  88. package/dist/cloud/frame-context.d.ts +8 -0
  89. package/dist/cloud/frame-context.d.ts.map +1 -0
  90. package/dist/cloud/inspection.d.ts +59 -0
  91. package/dist/cloud/inspection.d.ts.map +1 -0
  92. package/dist/cloud/optics.d.ts +56 -0
  93. package/dist/cloud/optics.d.ts.map +1 -0
  94. package/dist/cloud/parameters.d.ts +73 -0
  95. package/dist/cloud/parameters.d.ts.map +1 -0
  96. package/dist/cloud/resources.d.ts +60 -0
  97. package/dist/cloud/resources.d.ts.map +1 -0
  98. package/dist/cloud/shadow.d.ts +42 -0
  99. package/dist/cloud/shadow.d.ts.map +1 -0
  100. package/dist/cloud/temporal.d.ts +74 -0
  101. package/dist/cloud/temporal.d.ts.map +1 -0
  102. package/dist/components/atmosphere.d.ts +2 -0
  103. package/dist/components/atmosphere.d.ts.map +1 -1
  104. package/dist/components/barrel-distortion.d.ts +13 -0
  105. package/dist/components/barrel-distortion.d.ts.map +1 -0
  106. package/dist/components/camera.d.ts +20 -4
  107. package/dist/components/camera.d.ts.map +1 -1
  108. package/dist/components/cloud-layer.d.ts +29 -0
  109. package/dist/components/cloud-layer.d.ts.map +1 -0
  110. package/dist/components/index.d.ts +2 -0
  111. package/dist/components/index.d.ts.map +1 -1
  112. package/dist/components/instances.d.ts +20 -6
  113. package/dist/components/instances.d.ts.map +1 -1
  114. package/dist/components/mesh-filter.d.ts +6 -5
  115. package/dist/components/mesh-filter.d.ts.map +1 -1
  116. package/dist/components/motion-blur.d.ts +1 -0
  117. package/dist/components/motion-blur.d.ts.map +1 -1
  118. package/dist/construct-renderer.d.ts +1 -1
  119. package/dist/construct-renderer.d.ts.map +1 -1
  120. package/dist/construct-renderer.mjs +9751 -3528
  121. package/dist/construct-renderer.mjs.map +1 -1
  122. package/dist/device/gpu-residency.d.ts +6 -0
  123. package/dist/device/gpu-residency.d.ts.map +1 -1
  124. package/dist/dynamic-geometry.d.ts +5 -1
  125. package/dist/dynamic-geometry.d.ts.map +1 -1
  126. package/dist/environment/analytic-fog.d.ts +6 -0
  127. package/dist/environment/analytic-fog.d.ts.map +1 -0
  128. package/dist/errors/cloud.d.ts +59 -0
  129. package/dist/errors/cloud.d.ts.map +1 -0
  130. package/dist/errors/render.d.ts +23 -5
  131. package/dist/errors/render.d.ts.map +1 -1
  132. package/dist/examples/single-layer-medium-surface.d.ts +47 -0
  133. package/dist/examples/single-layer-medium-surface.d.ts.map +1 -0
  134. package/dist/extract/camera.d.ts.map +1 -1
  135. package/dist/extract/contracts.d.ts +12 -1
  136. package/dist/extract/contracts.d.ts.map +1 -1
  137. package/dist/extract/environment.d.ts +2 -0
  138. package/dist/extract/environment.d.ts.map +1 -1
  139. package/dist/features/barrel-distortion.d.ts +29 -0
  140. package/dist/features/barrel-distortion.d.ts.map +1 -0
  141. package/dist/features/error-reporting.d.ts +17 -0
  142. package/dist/features/error-reporting.d.ts.map +1 -0
  143. package/dist/features/host.d.ts +15 -0
  144. package/dist/features/host.d.ts.map +1 -1
  145. package/dist/features/motion-blur/motion-blur-feature.d.ts +20 -2
  146. package/dist/features/motion-blur/motion-blur-feature.d.ts.map +1 -1
  147. package/dist/features/motion-blur/motion-blur-params.d.ts +21 -0
  148. package/dist/features/motion-blur/motion-blur-params.d.ts.map +1 -1
  149. package/dist/features/motion-blur/motion-blur-runtime.d.ts +54 -0
  150. package/dist/features/motion-blur/motion-blur-runtime.d.ts.map +1 -0
  151. package/dist/features/plan.d.ts +15 -4
  152. package/dist/features/plan.d.ts.map +1 -1
  153. package/dist/features/prepared-gpu-work.d.ts +9 -5
  154. package/dist/features/prepared-gpu-work.d.ts.map +1 -1
  155. package/dist/features/render-graph-compute.d.ts.map +1 -1
  156. package/dist/features/render-graph-contribution.d.ts +5 -0
  157. package/dist/features/render-graph-contribution.d.ts.map +1 -1
  158. package/dist/features/render-graph-raster.d.ts +1 -0
  159. package/dist/features/render-graph-raster.d.ts.map +1 -1
  160. package/dist/features/targets.d.ts +2 -0
  161. package/dist/features/targets.d.ts.map +1 -1
  162. package/dist/features/types.d.ts +44 -0
  163. package/dist/features/types.d.ts.map +1 -1
  164. package/dist/fullscreen-post-process-pass.d.ts +15 -7
  165. package/dist/fullscreen-post-process-pass.d.ts.map +1 -1
  166. package/dist/gpu-dirty-ranges.d.ts +11 -0
  167. package/dist/gpu-dirty-ranges.d.ts.map +1 -0
  168. package/dist/gpu-driven/batch-topology.d.ts +17 -0
  169. package/dist/gpu-driven/batch-topology.d.ts.map +1 -1
  170. package/dist/gpu-driven/material-artifacts.d.ts +6 -0
  171. package/dist/gpu-driven/material-artifacts.d.ts.map +1 -1
  172. package/dist/gpu-driven/prepared-draw.d.ts +7 -0
  173. package/dist/gpu-driven/prepared-draw.d.ts.map +1 -1
  174. package/dist/gpu-driven/production-raster-material.d.ts +178 -0
  175. package/dist/gpu-driven/production-raster-material.d.ts.map +1 -0
  176. package/dist/gpu-driven/production-raster.d.ts +50 -4
  177. package/dist/gpu-driven/production-raster.d.ts.map +1 -1
  178. package/dist/gpu-driven/resource-allocation.d.ts +31 -0
  179. package/dist/gpu-driven/resource-allocation.d.ts.map +1 -0
  180. package/dist/gpu-driven/shadow-ownership.d.ts +14 -0
  181. package/dist/gpu-driven/shadow-ownership.d.ts.map +1 -0
  182. package/dist/gpu-driven/view-gpu.d.ts +35 -2
  183. package/dist/gpu-driven/view-gpu.d.ts.map +1 -1
  184. package/dist/gpu-scene.d.ts +11 -1
  185. package/dist/gpu-scene.d.ts.map +1 -1
  186. package/dist/hdrp-buffers.d.ts +2 -2
  187. package/dist/hdrp-buffers.d.ts.map +1 -1
  188. package/dist/ibl/skylight-bind-group.d.ts +10 -1
  189. package/dist/ibl/skylight-bind-group.d.ts.map +1 -1
  190. package/dist/index.d.ts +31 -8
  191. package/dist/index.d.ts.map +1 -1
  192. package/dist/index.mjs +258 -8
  193. package/dist/index.mjs.map +1 -1
  194. package/dist/inspection-types.d.ts +178 -3
  195. package/dist/inspection-types.d.ts.map +1 -1
  196. package/dist/instance-buffer-cache.d.ts +4 -4
  197. package/dist/instance-buffer-cache.d.ts.map +1 -1
  198. package/dist/instances.d.ts +49 -94
  199. package/dist/instances.d.ts.map +1 -1
  200. package/dist/internal.mjs +7 -7
  201. package/dist/pbr-pipeline.d.ts +19 -1
  202. package/dist/pbr-pipeline.d.ts.map +1 -1
  203. package/dist/pipeline/single-layer-medium-passes.d.ts +73 -0
  204. package/dist/pipeline/single-layer-medium-passes.d.ts.map +1 -0
  205. package/dist/pipeline/standard-forward-lane.d.ts.map +1 -1
  206. package/dist/pipeline/standard-output/graph.d.ts.map +1 -1
  207. package/dist/pipeline/standard-output/types.d.ts +4 -2
  208. package/dist/pipeline/standard-output/types.d.ts.map +1 -1
  209. package/dist/pipeline/standard-pipeline.d.ts.map +1 -1
  210. package/dist/pipeline/standard-post.d.ts +28 -19
  211. package/dist/pipeline/standard-post.d.ts.map +1 -1
  212. package/dist/pipeline-builder.d.ts +1 -1
  213. package/dist/pipeline-builder.d.ts.map +1 -1
  214. package/dist/pipeline-spec.d.ts +2 -2
  215. package/dist/plugin.d.ts.map +1 -1
  216. package/dist/prepare/prepared-graphics-resolver.d.ts +2 -0
  217. package/dist/prepare/prepared-graphics-resolver.d.ts.map +1 -1
  218. package/dist/record/barrel-distortion-frame.d.ts +15 -0
  219. package/dist/record/barrel-distortion-frame.d.ts.map +1 -0
  220. package/dist/record/dynamic-geometry-consumption.d.ts +10 -2
  221. package/dist/record/dynamic-geometry-consumption.d.ts.map +1 -1
  222. package/dist/record/fold-buckets.d.ts +95 -0
  223. package/dist/record/fold-buckets.d.ts.map +1 -0
  224. package/dist/record/fold-instance-buffer.d.ts +6 -0
  225. package/dist/record/fold-instance-buffer.d.ts.map +1 -0
  226. package/dist/record/frame-lighting.d.ts +1 -0
  227. package/dist/record/frame-lighting.d.ts.map +1 -1
  228. package/dist/record/frame-snapshot.d.ts +40 -3
  229. package/dist/record/frame-snapshot.d.ts.map +1 -1
  230. package/dist/record/frame-targets.d.ts.map +1 -1
  231. package/dist/record/frame.d.ts +14 -62
  232. package/dist/record/frame.d.ts.map +1 -1
  233. package/dist/record/gpu-draw-receipts.d.ts +11 -0
  234. package/dist/record/gpu-draw-receipts.d.ts.map +1 -0
  235. package/dist/record/gpu-timing.d.ts +8 -0
  236. package/dist/record/gpu-timing.d.ts.map +1 -1
  237. package/dist/record/main-pass-geometry.d.ts +21 -5
  238. package/dist/record/main-pass-geometry.d.ts.map +1 -1
  239. package/dist/record/main-pass-material.d.ts +7 -1
  240. package/dist/record/main-pass-material.d.ts.map +1 -1
  241. package/dist/record/main-pass-sprite-draws.d.ts.map +1 -1
  242. package/dist/record/main-pass.d.ts +13 -2
  243. package/dist/record/main-pass.d.ts.map +1 -1
  244. package/dist/record/mesh-ssbo.d.ts +6 -5
  245. package/dist/record/mesh-ssbo.d.ts.map +1 -1
  246. package/dist/record/motion-blur-receipt.d.ts +11 -0
  247. package/dist/record/motion-blur-receipt.d.ts.map +1 -0
  248. package/dist/record/probe-blend-buffer.d.ts +16 -0
  249. package/dist/record/probe-blend-buffer.d.ts.map +1 -0
  250. package/dist/record/recovery-pipeline.d.ts.map +1 -1
  251. package/dist/record/render-context.d.ts +34 -31
  252. package/dist/record/render-context.d.ts.map +1 -1
  253. package/dist/record/shadow-pass.d.ts +2 -0
  254. package/dist/record/shadow-pass.d.ts.map +1 -1
  255. package/dist/record/skybox-post-pass.d.ts +6 -4
  256. package/dist/record/skybox-post-pass.d.ts.map +1 -1
  257. package/dist/record/typed-frame-graph.d.ts +22 -2
  258. package/dist/record/typed-frame-graph.d.ts.map +1 -1
  259. package/dist/record/view-ubo.d.ts +11 -3
  260. package/dist/record/view-ubo.d.ts.map +1 -1
  261. package/dist/record/volume-params.d.ts +9 -0
  262. package/dist/record/volume-params.d.ts.map +1 -0
  263. package/dist/recovery/render-system-candidate.d.ts +10 -0
  264. package/dist/recovery/render-system-candidate.d.ts.map +1 -1
  265. package/dist/reflection/record-owner.d.ts.map +1 -1
  266. package/dist/render-contract.d.ts +66 -8
  267. package/dist/render-contract.d.ts.map +1 -1
  268. package/dist/render-graph-primitives.d.ts.map +1 -1
  269. package/dist/render-pipeline.d.ts +106 -3
  270. package/dist/render-pipeline.d.ts.map +1 -1
  271. package/dist/render-system-extract-tail.d.ts +3 -2
  272. package/dist/render-system-extract-tail.d.ts.map +1 -1
  273. package/dist/render-system-extract.d.ts +24 -10
  274. package/dist/render-system-extract.d.ts.map +1 -1
  275. package/dist/render-system-presentation.d.ts +23 -0
  276. package/dist/render-system-presentation.d.ts.map +1 -0
  277. package/dist/render-system-producer-roots.d.ts +10 -0
  278. package/dist/render-system-producer-roots.d.ts.map +1 -0
  279. package/dist/render-system-residency.d.ts +12 -0
  280. package/dist/render-system-residency.d.ts.map +1 -0
  281. package/dist/render-system.d.ts +26 -15
  282. package/dist/render-system.d.ts.map +1 -1
  283. package/dist/scene/probe-blend.d.ts +27 -0
  284. package/dist/scene/probe-blend.d.ts.map +1 -1
  285. package/dist/scene/render-scene.d.ts +14 -5
  286. package/dist/scene/render-scene.d.ts.map +1 -1
  287. package/dist/ssao-buffers.d.ts +9 -0
  288. package/dist/ssao-buffers.d.ts.map +1 -1
  289. package/dist/surface/dynamic-input.d.ts +133 -0
  290. package/dist/surface/dynamic-input.d.ts.map +1 -0
  291. package/dist/surface/gpu-driven.d.ts +41 -0
  292. package/dist/surface/gpu-driven.d.ts.map +1 -0
  293. package/dist/surface/submission-observation.d.ts +136 -0
  294. package/dist/surface/submission-observation.d.ts.map +1 -0
  295. package/dist/systems/skin-palette-allocator.d.ts.map +1 -1
  296. package/dist/temporal/frame-coordinator.d.ts.map +1 -1
  297. package/dist/temporal/frame.d.ts +7 -0
  298. package/dist/temporal/frame.d.ts.map +1 -1
  299. package/dist/temporal/gpu.d.ts +13 -1
  300. package/dist/temporal/gpu.d.ts.map +1 -1
  301. package/dist/temporal/index.mjs +5 -5
  302. package/dist/temporal/inspection.d.ts.map +1 -1
  303. package/dist/temporal/standard-scene-data.d.ts.map +1 -1
  304. package/dist/temporal/target-coverage-attachment.d.ts.map +1 -1
  305. package/dist/temporal/view.d.ts +6 -1
  306. package/dist/temporal/view.d.ts.map +1 -1
  307. package/dist/transmission/single-layer-medium.d.ts +89 -0
  308. package/dist/transmission/single-layer-medium.d.ts.map +1 -0
  309. package/dist/typed-render-graph-primitives.d.ts +24 -9
  310. package/dist/typed-render-graph-primitives.d.ts.map +1 -1
  311. package/dist/volume/component.d.ts +9 -5
  312. package/dist/volume/component.d.ts.map +1 -1
  313. package/dist/volume/extract.d.ts +1 -1
  314. package/dist/volume/extract.d.ts.map +1 -1
  315. package/dist/volume/inspection.d.ts +2 -0
  316. package/dist/volume/inspection.d.ts.map +1 -1
  317. package/dist/volume/passes.d.ts +4 -1
  318. package/dist/volume/passes.d.ts.map +1 -1
  319. package/dist/volume/resources.d.ts.map +1 -1
  320. package/dist/volume/temporal.d.ts +3 -1
  321. package/dist/volume/temporal.d.ts.map +1 -1
  322. package/package.json +22 -21
  323. package/src/__tests__/analytic-fog-graph.unit.test.ts +199 -0
  324. package/src/__tests__/analytic-fog-optics.unit.test.ts +38 -0
  325. package/src/__tests__/atmosphere-daylight.dawn.test.ts +72 -0
  326. package/src/__tests__/barrel-distortion-lkg.integration.test.ts +353 -0
  327. package/src/__tests__/barrel-distortion.dawn.test.ts +41 -0
  328. package/src/__tests__/barrel-distortion.unit.test.ts +364 -0
  329. package/src/__tests__/bloom-exact-zero.unit.test.ts +112 -18
  330. package/src/__tests__/bloom-lifecycle.integration.test.ts +17 -4
  331. package/src/__tests__/construct-renderer-identity.unit.test.ts +49 -0
  332. package/src/__tests__/detached-recovery-graph.unit.test.ts +277 -0
  333. package/src/__tests__/device-loss-fanout.unit.test.ts +36 -1
  334. package/src/__tests__/docs-gate-green.unit.test.ts +112 -1
  335. package/src/__tests__/dynamic-resolution-plugin.integration.test.ts +20 -0
  336. package/src/__tests__/environment-fog-lanes.integration.test.ts +14 -0
  337. package/src/__tests__/environment-selection.unit.test.ts +12 -0
  338. package/src/__tests__/factory-contract.integration.test.ts +626 -113
  339. package/src/__tests__/feature-depth-input.dawn.test.ts +129 -2
  340. package/src/__tests__/frame-camera-selection.unit.test.ts +39 -1
  341. package/src/__tests__/frame-plan-contract.unit.test.ts +2 -0
  342. package/src/__tests__/fullscreen-feature-plan.unit.test.ts +14 -3
  343. package/src/__tests__/gpu-dirty-ranges.unit.test.ts +22 -0
  344. package/src/__tests__/gpu-driven-baseline.characterization.test.ts +16 -3
  345. package/src/__tests__/gpu-driven-batch-topology-pbr.unit.test.ts +12 -0
  346. package/src/__tests__/gpu-driven-indirect-raster-evidence.ts +2 -1
  347. package/src/__tests__/gpu-driven-material-artifacts.unit.test.ts +143 -0
  348. package/src/__tests__/gpu-driven-pbr.dawn.test.ts +2 -2
  349. package/src/__tests__/gpu-driven-production.integration.test.ts +482 -3
  350. package/src/__tests__/gpu-driven-shadow-views.dawn.test.ts +7 -6
  351. package/src/__tests__/gpu-driven-shadow-views.integration.test.ts +1 -1
  352. package/src/__tests__/gpu-driven-view-gpu-evidence.ts +103 -4
  353. package/src/__tests__/gpu-driven-view-graph.integration.test.ts +18 -2
  354. package/src/__tests__/gpu-driven-view.browser.test.ts +5 -8
  355. package/src/__tests__/gpu-driven-view.dawn.test.ts +6 -2
  356. package/src/__tests__/gpu-driven-view.unit.test.ts +1 -1
  357. package/src/__tests__/gpu-pass-timing-lifecycle.integration.test.ts +1 -0
  358. package/src/__tests__/gpu-pass-timing.browser.test.ts +6 -4
  359. package/src/__tests__/gpu-pass-timing.rhi-null.unit.test.ts +1 -0
  360. package/src/__tests__/gpu-resource-store-stride.unit.test.ts +12 -0
  361. package/src/__tests__/gpu-scene.unit.test.ts +210 -9
  362. package/src/__tests__/instance-allocation-failure.unit.test.ts +57 -0
  363. package/src/__tests__/instances-culling.integration.test.ts +10 -36
  364. package/src/__tests__/instances-store.unit.test.ts +105 -238
  365. package/src/__tests__/instances-world-ownership.integration.test.ts +62 -0
  366. package/src/__tests__/main-pass-material-medium-selection.unit.test.ts +146 -0
  367. package/src/__tests__/material-shader-prewarm.unit.test.ts +59 -0
  368. package/src/__tests__/material-snapshot-mutation.integration.test.ts +39 -1
  369. package/src/__tests__/mesh-submission-lifetime.unit.test.ts +31 -0
  370. package/src/__tests__/morph-culling-reentry.dawn.test.ts +5 -5
  371. package/src/__tests__/morph-visual-evidence.integration.test.ts +8 -7
  372. package/src/__tests__/motion-blur-compute.dawn.test.ts +589 -0
  373. package/src/__tests__/motion-blur.pixel.integration.test.ts +2 -2
  374. package/src/__tests__/nineslice-handle-surface.unit.test.ts +4 -4
  375. package/src/__tests__/pbr-view-layout.unit.test.ts +9 -0
  376. package/src/__tests__/physical-clearcoat.integration.test.ts +58 -0
  377. package/src/__tests__/prepared-gpu-driven-pbr.integration.test.ts +21 -0
  378. package/src/__tests__/prepared-graphics-capability.unit.test.ts +66 -2
  379. package/src/__tests__/prepared-graphics-lifecycle.unit.test.ts +86 -0
  380. package/src/__tests__/probe-bindings.unit.test.ts +582 -1
  381. package/src/__tests__/probe-scene-dirty.integration.test.ts +28 -1
  382. package/src/__tests__/public-surface.test-d.ts +8 -0
  383. package/src/__tests__/receipt-observation-retirement.unit.test.ts +65 -0
  384. package/src/__tests__/recovery-candidate-prepare.contract.test.ts +69 -0
  385. package/src/__tests__/render-error-code-owner.test-d.ts +9 -0
  386. package/src/__tests__/render-feature-diagnostics.unit.test.ts +15 -2
  387. package/src/__tests__/render-feature-error-reporting.unit.test.ts +44 -0
  388. package/src/__tests__/render-feature-prepared-diagnostics.unit.test.ts +10 -1
  389. package/src/__tests__/render-feature-retry.unit.test.ts +12 -1
  390. package/src/__tests__/render-feature-stage-fault-recovery.unit.test.ts +23 -0
  391. package/src/__tests__/render-scene-mixed-updates.integration.test.ts +145 -19
  392. package/src/__tests__/render-scene-projection.unit.test.ts +36 -11
  393. package/src/__tests__/render-scene-temporal-retry.unit.test.ts +37 -6
  394. package/src/__tests__/render-target-probe-soak.perf.test.ts +2 -2
  395. package/src/__tests__/render-temporal-environment-consumer.browser.test.ts +3 -3
  396. package/src/__tests__/render-temporal-environment-consumer.dawn.test.ts +5 -2
  397. package/src/__tests__/renderer-factory-material-contract.unit.test.ts +41 -7
  398. package/src/__tests__/renderer-frame-generation-fence.integration.test.ts +16 -0
  399. package/src/__tests__/renderer-frame-transaction.integration.test.ts +33 -11
  400. package/src/__tests__/resource-allocation.unit.test.ts +123 -0
  401. package/src/__tests__/shader-manifest-fixture.ts +4 -4
  402. package/src/__tests__/single-layer-medium-passes.unit.test.ts +275 -0
  403. package/src/__tests__/skin-motion-regression.unit.test.ts +70 -1
  404. package/src/__tests__/skin-palette-recycling.dawn.test.ts +97 -0
  405. package/src/__tests__/skinned-shadow-caster.test.ts +17 -0
  406. package/src/__tests__/ssr-carrier-public-contract.unit.test.ts +21 -0
  407. package/src/__tests__/ssr-gpu-dispatch.browser.test.ts +4 -4
  408. package/src/__tests__/ssr-gpu-dispatch.dawn.test.ts +2 -2
  409. package/src/__tests__/ssr-gpu-dispatch.ts +1 -1
  410. package/src/__tests__/ssr-receiver-coverage.dawn.test.ts +6 -2
  411. package/src/__tests__/standard-lighting-write.unit.test.ts +2 -1
  412. package/src/__tests__/standard-output-chain-contract.unit.test.ts +28 -0
  413. package/src/__tests__/standard-pbr-artifact-assembly.unit.test.ts +46 -1
  414. package/src/__tests__/standard-pipeline.dawn.test.ts +1 -1
  415. package/src/__tests__/standard-pipeline.integration.test.ts +200 -9
  416. package/src/__tests__/surface-dynamic-buffer-lifecycle.unit.test.ts +506 -0
  417. package/src/__tests__/surface-submission-observation.unit.test.ts +812 -0
  418. package/src/__tests__/temporal-history-lifecycle.unit.test.ts +32 -0
  419. package/src/__tests__/temporal-performance.integration.test.ts +3 -3
  420. package/src/__tests__/temporal-reset-matrix.unit.test.ts +10 -0
  421. package/src/__tests__/temporal-view.unit.test.ts +2 -1
  422. package/src/__tests__/typed-pipeline-topology.unit.test.ts +20 -4
  423. package/src/__tests__/visibility-instances.integration.test.ts +7 -19
  424. package/src/__tests__/visibility-producer-matrix.integration.test.ts +9 -20
  425. package/src/__tests__/volume-optics-oracle.unit.test.ts +7 -3
  426. package/src/__tests__/volumetric-fog-budget-recovery.integration.test.ts +189 -15
  427. package/src/__tests__/volumetric-fog-errors.unit.test.ts +4 -3
  428. package/src/__tests__/volumetric-fog-multiple.unit.test.ts +55 -0
  429. package/src/__tests__/volumetric-fog-spot-frame-optics.integration.test.ts +1 -1
  430. package/src/__tests__/volumetric-fog-stage-readback.dawn.test.ts +558 -68
  431. package/src/__tests__/volumetric-fog-temporal.unit.test.ts +27 -0
  432. package/src/__tests__/volumetric-fog-topology.unit.test.ts +23 -5
  433. package/src/assembly/analytic-fog-registration.ts +14 -0
  434. package/src/assembly/backend-contract.ts +30 -0
  435. package/src/assembly/barrel-distortion-inspection.ts +22 -0
  436. package/src/assembly/canvas-draw-guard.ts +11 -0
  437. package/src/assembly/cloud-shadow-entry.ts +22 -0
  438. package/src/assembly/dynamic-geometry-host.ts +65 -37
  439. package/src/assembly/dynamic-geometry-runtime.ts +7 -1
  440. package/src/assembly/factory.ts +2 -30
  441. package/src/assembly/fxaa-registration.ts +17 -0
  442. package/src/assembly/host-contract.ts +3 -3
  443. package/src/assembly/lod-observation.ts +12 -0
  444. package/src/assembly/material/assembly.ts +25 -13
  445. package/src/assembly/material/pipeline-helpers.ts +53 -12
  446. package/src/assembly/material-shader-policy.ts +55 -0
  447. package/src/assembly/material-shader-prewarm.ts +21 -10
  448. package/src/assembly/motion-blur-registration.ts +17 -0
  449. package/src/assembly/recovery/device-loss-fanout.ts +13 -0
  450. package/src/assembly/recovery/renderer-recover.ts +41 -18
  451. package/src/assembly/render-feature-post-processes.ts +54 -0
  452. package/src/assembly/renderer-facade.ts +6 -1
  453. package/src/assembly/renderer-feature-inspection.ts +8 -2
  454. package/src/assembly/renderer-frame-transaction.ts +23 -0
  455. package/src/assembly/renderer-inspection.ts +28 -0
  456. package/src/assembly/skin-palette-owner.ts +1 -1
  457. package/src/assembly/temporal-post-process-prewarm.ts +40 -0
  458. package/src/assembly/webgpu-pbr-ready.ts +9 -1
  459. package/src/assembly/webgpu-ready-contract.ts +8 -3
  460. package/src/assembly/webgpu-ready-per-pass.ts +104 -0
  461. package/src/assembly/webgpu-ready.ts +296 -306
  462. package/src/assembly/webgpu-renderer-bootstrap.ts +0 -12
  463. package/src/assembly/webgpu-renderer-observation-owner.ts +222 -0
  464. package/src/assembly/webgpu-renderer.ts +228 -281
  465. package/src/assembly/webgpu-vertex-layouts.ts +1 -1
  466. package/src/barrel-distortion.ts +247 -0
  467. package/src/bloom-admission.ts +21 -0
  468. package/src/cloud/__tests__/cloud-density-parity.dawn.test.ts +173 -0
  469. package/src/cloud/__tests__/cloud-layer-framebuffer-y.dawn.test.ts +265 -0
  470. package/src/cloud/__tests__/cloud-layer.browser.test.ts +432 -0
  471. package/src/cloud/__tests__/cloud-layer.unit.test.ts +634 -0
  472. package/src/cloud/density.ts +600 -0
  473. package/src/cloud/extract.ts +110 -0
  474. package/src/cloud/feature.ts +1810 -0
  475. package/src/cloud/frame-context.ts +42 -0
  476. package/src/cloud/inspection.ts +130 -0
  477. package/src/cloud/optics.ts +285 -0
  478. package/src/cloud/parameters.ts +255 -0
  479. package/src/cloud/resources.ts +122 -0
  480. package/src/cloud/shadow.ts +167 -0
  481. package/src/cloud/temporal.ts +226 -0
  482. package/src/components/__tests__/motion-blur.unit.test.ts +1 -0
  483. package/src/components/atmosphere.ts +4 -0
  484. package/src/components/barrel-distortion.ts +46 -0
  485. package/src/components/camera.ts +66 -10
  486. package/src/components/cloud-layer.ts +47 -0
  487. package/src/components/index.ts +2 -0
  488. package/src/components/instances.ts +18 -45
  489. package/src/components/mesh-filter.ts +11 -9
  490. package/src/components/motion-blur.ts +1 -0
  491. package/src/components/sprite-instances.ts +1 -1
  492. package/src/construct-renderer.ts +1 -1
  493. package/src/device/gpu-residency.ts +56 -12
  494. package/src/dynamic-geometry.ts +12 -1
  495. package/src/environment/analytic-fog.ts +44 -0
  496. package/src/environment/background.ts +2 -2
  497. package/src/errors/cloud.ts +103 -0
  498. package/src/errors/render.ts +58 -8
  499. package/src/examples/single-layer-medium-surface.ts +59 -0
  500. package/src/extract/camera.ts +35 -8
  501. package/src/extract/contracts.ts +12 -0
  502. package/src/extract/environment.ts +4 -0
  503. package/src/features/barrel-distortion.ts +172 -0
  504. package/src/features/error-reporting.ts +98 -0
  505. package/src/features/host.ts +138 -24
  506. package/src/features/motion-blur/__tests__/motion-blur-feature.unit.test.ts +111 -1
  507. package/src/features/motion-blur/__tests__/motion-blur-params.unit.test.ts +58 -3
  508. package/src/features/motion-blur/motion-blur-feature.ts +1519 -15
  509. package/src/features/motion-blur/motion-blur-params.ts +70 -2
  510. package/src/features/motion-blur/motion-blur-runtime.ts +176 -0
  511. package/src/features/plan.ts +33 -8
  512. package/src/features/prepared-gpu-work.ts +86 -35
  513. package/src/features/render-graph-compute.ts +13 -5
  514. package/src/features/render-graph-contribution.ts +11 -0
  515. package/src/features/render-graph-raster.ts +45 -3
  516. package/src/features/targets.ts +66 -0
  517. package/src/features/types.ts +47 -0
  518. package/src/fullscreen-post-process-pass.ts +41 -9
  519. package/src/gpu-dirty-ranges.ts +48 -0
  520. package/src/gpu-driven/batch-topology.ts +53 -0
  521. package/src/gpu-driven/material-artifacts.ts +35 -32
  522. package/src/gpu-driven/prepared-draw.ts +27 -14
  523. package/src/gpu-driven/production-raster-material.ts +1894 -0
  524. package/src/gpu-driven/production-raster.ts +904 -787
  525. package/src/gpu-driven/resource-allocation.ts +142 -0
  526. package/src/gpu-driven/shadow-ownership.ts +71 -0
  527. package/src/gpu-driven/view-gpu.ts +181 -10
  528. package/src/gpu-scene.ts +259 -101
  529. package/src/hdrp-buffers.ts +4 -3
  530. package/src/ibl/skylight-bind-group.ts +49 -7
  531. package/src/index.ts +228 -9
  532. package/src/inspection-types.ts +222 -2
  533. package/src/instance-buffer-cache.ts +4 -4
  534. package/src/instances.ts +151 -492
  535. package/src/pbr-pipeline.ts +219 -13
  536. package/src/pipeline/single-layer-medium-passes.ts +267 -0
  537. package/src/pipeline/standard-forward-lane.ts +313 -57
  538. package/src/pipeline/standard-output/graph.ts +8 -2
  539. package/src/pipeline/standard-output/types.ts +12 -1
  540. package/src/pipeline/standard-pipeline.ts +291 -18
  541. package/src/pipeline/standard-post.ts +397 -102
  542. package/src/pipeline-builder.ts +1 -1
  543. package/src/pipeline-spec.ts +5 -5
  544. package/src/plugin.ts +4 -0
  545. package/src/points-lines/__tests__/points-lines.bench.ts +17 -8
  546. package/src/points-lines/__tests__/view-ubo.unit.test.ts +2 -1
  547. package/src/prepare/prepared-graphics-resolver.ts +2 -0
  548. package/src/record/__tests__/main-pass-geometry.unit.test.ts +30 -0
  549. package/src/record/__tests__/sprite-material-abi.unit.test.ts +1 -1
  550. package/src/record/__tests__/temporal-payload.unit.test.ts +3 -2
  551. package/src/record/__tests__/transparent-group2-selection.unit.test.ts +11 -0
  552. package/src/record/__tests__/typed-frame-graph-plan.unit.test.ts +1 -0
  553. package/src/record/__tests__/view-ubo-layout.unit.test.ts +4 -3
  554. package/src/record/barrel-distortion-frame.ts +110 -0
  555. package/src/record/dynamic-geometry-consumption.ts +39 -9
  556. package/src/record/fold-buckets.ts +852 -0
  557. package/src/record/fold-instance-buffer.ts +51 -0
  558. package/src/record/frame-lighting.ts +36 -2
  559. package/src/record/frame-snapshot.ts +44 -4
  560. package/src/record/frame-targets.ts +10 -6
  561. package/src/record/frame.ts +373 -1099
  562. package/src/record/gpu-draw-receipts.ts +38 -0
  563. package/src/record/gpu-timing.ts +48 -5
  564. package/src/record/main-pass-geometry.ts +281 -83
  565. package/src/record/main-pass-material.ts +66 -4
  566. package/src/record/main-pass-sprite-draws.ts +13 -66
  567. package/src/record/main-pass.ts +156 -20
  568. package/src/record/mesh-ssbo.ts +43 -6
  569. package/src/record/motion-blur-receipt.ts +91 -0
  570. package/src/record/probe-blend-buffer.ts +180 -0
  571. package/src/record/recovery-pipeline.ts +35 -3
  572. package/src/record/render-context.ts +51 -31
  573. package/src/record/shadow-pass.ts +36 -2
  574. package/src/record/skybox-post-pass.ts +150 -200
  575. package/src/record/typed-frame-graph.ts +824 -44
  576. package/src/record/view-ubo.ts +26 -3
  577. package/src/record/volume-params.ts +225 -0
  578. package/src/recovery/render-system-candidate.ts +257 -52
  579. package/src/reflection/record-owner.ts +7 -3
  580. package/src/render-contract.ts +71 -8
  581. package/src/render-graph-primitives.ts +7 -1
  582. package/src/render-pipeline.ts +136 -1
  583. package/src/render-system-extract-tail.ts +72 -60
  584. package/src/render-system-extract.ts +117 -87
  585. package/src/render-system-presentation.ts +56 -0
  586. package/src/render-system-producer-roots.ts +96 -0
  587. package/src/render-system-residency.ts +159 -0
  588. package/src/render-system.ts +601 -482
  589. package/src/scene/probe-blend.ts +96 -4
  590. package/src/scene/render-scene.ts +431 -281
  591. package/src/ssao-buffers.ts +95 -2
  592. package/src/surface/__tests__/dynamic-input.unit.test.ts +374 -0
  593. package/src/surface/__tests__/gpu-driven.unit.test.ts +87 -0
  594. package/src/surface/dynamic-input.ts +658 -0
  595. package/src/surface/gpu-driven.ts +147 -0
  596. package/src/surface/submission-observation.ts +517 -0
  597. package/src/systems/skin-palette-allocator.ts +4 -1
  598. package/src/temporal/__tests__/temporal-frame-transaction.unit.test.ts +36 -0
  599. package/src/temporal/frame-coordinator.ts +8 -1
  600. package/src/temporal/frame.ts +20 -0
  601. package/src/temporal/gpu.ts +101 -17
  602. package/src/temporal/inspection.ts +7 -1
  603. package/src/temporal/standard-scene-data.ts +5 -0
  604. package/src/temporal/target-coverage-attachment.ts +4 -0
  605. package/src/temporal/view.ts +13 -2
  606. package/src/transmission/__tests__/single-layer-medium.unit.test.ts +149 -0
  607. package/src/transmission/__tests__/standard-transmission.dawn.test.ts +12 -11
  608. package/src/transmission/single-layer-medium.ts +322 -0
  609. package/src/typed-render-graph-primitives.ts +191 -50
  610. package/src/volume/component.ts +10 -10
  611. package/src/volume/extract.ts +11 -5
  612. package/src/volume/inspection.ts +3 -0
  613. package/src/volume/passes.ts +95 -8
  614. package/src/volume/resources.ts +2 -1
  615. package/src/volume/temporal.ts +11 -0
  616. package/dist/chunk-4NNZQCL3.mjs.map +0 -1
  617. package/dist/chunk-6TFBXLRJ.mjs.map +0 -1
  618. package/dist/chunk-DRR7D3KX.mjs.map +0 -1
  619. package/dist/chunk-GWCEMDVR.mjs.map +0 -1
  620. package/dist/chunk-JCIRYQ4V.mjs.map +0 -1
  621. package/dist/chunk-JRIPCMYC.mjs.map +0 -1
  622. package/dist/chunk-LULCA3OF.mjs +0 -1713
  623. package/dist/chunk-LULCA3OF.mjs.map +0 -1
  624. package/dist/chunk-PA5DRJNI.mjs.map +0 -1
@@ -0,0 +1,65 @@
1
+ import type { Buffer, MappedBuffer, RhiDevice, Texture } from '@forgeax/engine-rhi';
2
+ import { ok } from '@forgeax/engine-types';
3
+ import { expect, it, vi } from 'vitest';
4
+ import { createRendererObservationCaptureOwner } from '../assembly/webgpu-renderer-observation-owner';
5
+
6
+ function deferred<T>(): { readonly promise: Promise<T>; readonly resolve: (value: T) => void } {
7
+ let resolve!: (value: T) => void;
8
+ const promise = new Promise<T>((next) => {
9
+ resolve = next;
10
+ });
11
+ return { promise, resolve };
12
+ }
13
+
14
+ it('retires a receipt observation readback only after its pending map completes', async () => {
15
+ const mapStarted = deferred<void>();
16
+ const mapCompleted = deferred<ReturnType<typeof ok<MappedBuffer>>>();
17
+ const mappedBytes = new ArrayBuffer(256);
18
+ const unmap = vi.fn();
19
+ const buffer = {
20
+ mapAsync: async () => {
21
+ mapStarted.resolve();
22
+ return mapCompleted.promise;
23
+ },
24
+ } as unknown as Buffer;
25
+ const texture = {} as Texture;
26
+ const destroyBuffer = vi.fn(() => ok(undefined));
27
+ const device = { destroyBuffer } as unknown as RhiDevice;
28
+ const errors: unknown[] = [];
29
+ const owner = createRendererObservationCaptureOwner((error) => errors.push(error));
30
+ const capture = {
31
+ domain: 'linear-hdr' as const,
32
+ format: 'rgba8unorm' as const,
33
+ device,
34
+ texture,
35
+ buffer,
36
+ frameNumber: 17,
37
+ deviceGeneration: 3,
38
+ graphGeneration: 9,
39
+ backendId: 'null' as const,
40
+ width: 1,
41
+ height: 1,
42
+ bytesPerRow: 256,
43
+ };
44
+ owner.observationCaptureOwner.register(capture);
45
+ expect(owner.observationCaptureOwner.consume(17)).toEqual([capture]);
46
+
47
+ const pending = owner.readObservationCapture(capture);
48
+ await mapStarted.promise;
49
+ expect(destroyBuffer).not.toHaveBeenCalled();
50
+ expect(owner.stats).toMatchObject({ liveCount: 1, mapCount: 1, readbackCount: 0 });
51
+
52
+ mapCompleted.resolve(
53
+ ok({
54
+ getMappedRange: () => ok(mappedBytes),
55
+ unmap,
56
+ } as unknown as MappedBuffer),
57
+ );
58
+ await expect(pending).resolves.toMatchObject({ ok: true, value: new Uint8Array(256) });
59
+ expect(unmap).toHaveBeenCalledTimes(1);
60
+ expect(destroyBuffer).toHaveBeenCalledTimes(1);
61
+ expect(owner.stats).toMatchObject({ liveCount: 0, mapCount: 1, readbackCount: 1 });
62
+ expect(errors).toEqual([]);
63
+ expect(owner.disposeReceiptObservationCaptures()).toBeUndefined();
64
+ expect(destroyBuffer).toHaveBeenCalledTimes(1);
65
+ });
@@ -22,6 +22,10 @@ const rendererRecoverySource = readFileSync(
22
22
  resolve(import.meta.dirname, '../assembly/recovery/renderer-recover.ts'),
23
23
  'utf8',
24
24
  );
25
+ const recoveryPipelineSource = readFileSync(
26
+ resolve(import.meta.dirname, '../record/recovery-pipeline.ts'),
27
+ 'utf8',
28
+ );
25
29
 
26
30
  describe('recovery candidate preparation contract', () => {
27
31
  it('does not restore the lost device SSR history when publishing a detached candidate', () => {
@@ -145,6 +149,31 @@ describe('recovery candidate preparation contract', () => {
145
149
  expect(resolver).not.toContain('internals.device.limits');
146
150
  });
147
151
 
152
+ it('rehydrates capability variants before copying late-installed material declarations', () => {
153
+ const preparation = rendererRecoverySource.indexOf('await prepareMaterialShaders(');
154
+ const copy = rendererRecoverySource.indexOf(
155
+ 'const activeShaderCatalog = getActiveShaderState().shaderInstance',
156
+ );
157
+ expect(preparation).toBeGreaterThanOrEqual(0);
158
+ expect(copy).toBeGreaterThan(preparation);
159
+ const copyBlock = rendererRecoverySource.slice(copy);
160
+ expect(copyBlock).toContain('candidateShaderCatalog.findMaterialArtifact(identifier).ok');
161
+ expect(copyBlock).toContain('candidateShaderCatalog.installMaterialArtifact(identifier');
162
+ });
163
+
164
+ it('rehydrates Surface dynamic page bytes before candidate GPU preparation', () => {
165
+ const start = recoveryCandidateSource.indexOf('let recoverySurfaceDynamicInput =');
166
+ const end = recoveryCandidateSource.indexOf(
167
+ 'if (candidateGpuDrivenScene.state !== undefined)',
168
+ start,
169
+ );
170
+ const surface = recoveryCandidateSource.slice(start, end);
171
+ expect(surface).toContain('candidateSurfacePage.bytes.set(source.page.bytes)');
172
+ expect(surface.indexOf('candidateSurfacePage.bytes.set(source.page.bytes)')).toBeLessThan(
173
+ surface.indexOf('recoverySurfaceDynamicInput = {'),
174
+ );
175
+ });
176
+
148
177
  it('keeps render-system residency bindings live across publication', () => {
149
178
  const start = rendererSource.indexOf('const renderSystem: RenderSystem = createRenderSystem({');
150
179
  const end = rendererSource.indexOf('errorRegistry: internals.errorRegistry', start);
@@ -155,4 +184,44 @@ describe('recovery candidate preparation contract', () => {
155
184
  expect(assembly).not.toMatch(/\n\s*gpuStore,\n/);
156
185
  expect(assembly).not.toMatch(/\n\s*dynamicTextureStore,\n/);
157
186
  });
187
+
188
+ it('rebuilds candidate shaders from accepted receipts with candidate-bound adapters', () => {
189
+ expect(rendererRecoverySource).toContain('entry.value.receipt === undefined');
190
+ expect(rendererRecoverySource).toContain('{ receipt: entry.value.receipt }');
191
+ expect(rendererRecoverySource).toContain(
192
+ 'getShaderModuleAdapter().createShaderModule(descriptor)',
193
+ );
194
+ expect(rendererRecoverySource).toContain(
195
+ 'getImmediateShaderModuleAdapter().createShaderModule(descriptor)',
196
+ );
197
+ expect(renderSystemSource).toContain('materialArtifacts: frameState.recoveryMaterialArtifacts');
198
+ expect(recoveryCandidateSource).toContain('materialArtifacts.size === 0');
199
+ expect(recoveryCandidateSource).toContain('{ materialArtifacts }');
200
+ expect(recoveryCandidateSource).toContain('domain: range.domain');
201
+ expect(recoveryCandidateSource).not.toContain('recovery-water-domain');
202
+ });
203
+
204
+ it('prepares the submitted forward specialization with the direct Surface ABI', () => {
205
+ expect(recoveryPipelineSource).toContain('readonly dispatch: readonly DispatchEntry[]');
206
+ expect(recoveryPipelineSource).toContain(
207
+ "candidate.tags.LightMode !== 'Forward' || candidate.materialShaderId === undefined",
208
+ );
209
+ expect(recoveryPipelineSource).toContain('forwardProgram?.materialShaderId');
210
+ expect(recoveryPipelineSource).toContain("'surface-direct-cluster-pbr' as const");
211
+ expect(recoveryPipelineSource).toContain("'surface-direct-pbr' as const");
212
+ expect(recoveryPipelineSource).toContain("'immediate',");
213
+ });
214
+
215
+ it('retires the candidate display view before publishing its prepared owners', () => {
216
+ const start = recoveryCandidateSource.indexOf('const publishRecoveryGraphCandidate');
217
+ const publish = recoveryCandidateSource.slice(start);
218
+ const retire = publish.indexOf('candidateDisplayGraph?.retire()');
219
+ const publication = publish.indexOf('Object.assign(frameState, candidate.frameState)');
220
+
221
+ expect(retire).toBeGreaterThanOrEqual(0);
222
+ expect(publication).toBeGreaterThan(retire);
223
+ expect(publish).toContain('candidate.frameState.compiledFrameGraph = null');
224
+ expect(publish).toContain('candidate.frameState.compiledFrameGraphTopologyKey = null');
225
+ expect(publish).toContain('candidate.frameState.perFrameGraph = null');
226
+ });
158
227
  });
@@ -39,6 +39,9 @@ const expectedCodes = [
39
39
  'dynamic-resolution-invalid-parameter',
40
40
  'dynamic-resolution-requires-taa',
41
41
  'dynamic-resolution-timing-unavailable',
42
+ 'barrel-distortion-invalid-parameter',
43
+ 'motion-blur-invalid-params',
44
+
42
45
  'shadow-invalid-config',
43
46
  'equirect-projection-failed',
44
47
  'standard-light-budget-exceeded',
@@ -63,6 +66,7 @@ const expectedCodes = [
63
66
  'render-feature-draw-recording-failed',
64
67
  'environment-source-conflict',
65
68
  'fog-cardinality',
69
+ 'sun-cardinality',
66
70
  'taa-caps-insufficient',
67
71
  'environment-generation-failed',
68
72
  'atmosphere-invalid-parameter',
@@ -85,6 +89,11 @@ const expectedCodes = [
85
89
  'volume-invalid-bounds',
86
90
  'volume-invalid-parameters',
87
91
  'projector-binding-failed',
92
+ 'cloud-layer-invalid-parameter',
93
+ 'cloud-layer-owner-conflict',
94
+ 'cloud-layer-cache-invalid',
95
+ 'cloud-layer-capability-missing',
96
+ 'cloud-layer-resource-failed',
88
97
  ] as const satisfies readonly RenderErrorCode[];
89
98
  type ExpectedCodeUnion = (typeof expectedCodes)[number];
90
99
 
@@ -12,7 +12,17 @@ function gatedFeature(): RenderFeature<{ readonly frame: number }> {
12
12
  identity: 'synthetic.gated',
13
13
  requiredCapabilities: ['compute'],
14
14
  extract: ({ frameNumber }) => ok({ frame: frameNumber }),
15
- plan: () => ok({ resources: [], passes: [] }),
15
+ plan: () =>
16
+ ok({
17
+ resources: [
18
+ {
19
+ kind: 'fullscreen-program' as const,
20
+ name: 'synthetic.gated.program',
21
+ source: 'synthetic',
22
+ },
23
+ ],
24
+ passes: [],
25
+ }),
16
26
  };
17
27
  }
18
28
 
@@ -82,7 +92,10 @@ describe('render feature diagnostics and capability gate', () => {
82
92
  caps: caps(false),
83
93
  });
84
94
 
85
- expect(first.stageEvents).toEqual([]);
95
+ expect(first.stageEvents).toEqual([
96
+ { featureIdentity: 'synthetic.gated', order: 0, stage: 'extract' },
97
+ { featureIdentity: 'synthetic.gated', order: 0, stage: 'plan' },
98
+ ]);
86
99
  expect(first.errors).toHaveLength(1);
87
100
  expect(first.errors[0]?.code).toBe('render-feature-capability-missing');
88
101
  expect(host.diagnostics()[0]?.status).toBe('disabled');
@@ -0,0 +1,44 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { type RenderError, RenderFeatureStageFailedError } from '../errors/render.js';
3
+ import {
4
+ PENDING_RENDER_FEATURE_ERROR_REPORT_INTERVAL_FRAMES,
5
+ type PendingRenderFeatureErrorReport,
6
+ reportPendingRenderFeatureErrors,
7
+ shouldReportPendingRenderFeatureError,
8
+ } from '../features/error-reporting.js';
9
+
10
+ describe('pending render-feature error reporting', () => {
11
+ it('reports the first failure, suppresses the hot-loop, and reopens the cooldown', () => {
12
+ const lastReportedFrame = new Map<string, PendingRenderFeatureErrorReport>();
13
+ expect(shouldReportPendingRenderFeatureError(lastReportedFrame, 'feature\0plan', 10)).toBe(
14
+ true,
15
+ );
16
+ expect(shouldReportPendingRenderFeatureError(lastReportedFrame, 'feature\0plan', 11)).toBe(
17
+ false,
18
+ );
19
+ expect(
20
+ shouldReportPendingRenderFeatureError(
21
+ lastReportedFrame,
22
+ 'feature\0plan',
23
+ 10 + PENDING_RENDER_FEATURE_ERROR_REPORT_INTERVAL_FRAMES,
24
+ ),
25
+ ).toBe(true);
26
+ });
27
+ });
28
+
29
+ describe('changed render-feature failures', () => {
30
+ it('reports a new cause immediately without flooding repeats', () => {
31
+ const lastReportedFrame = new Map<string, PendingRenderFeatureErrorReport>();
32
+ const reported: RenderError[] = [];
33
+ const registry = { fire: (error: RenderError) => reported.push(error) };
34
+ const failure = (message: string) =>
35
+ new RenderFeatureStageFailedError('recovery', 0, 'plan', 'next-frame', new Error(message));
36
+ reportPendingRenderFeatureErrors([failure('invalid-format')], 1, lastReportedFrame, registry);
37
+ reportPendingRenderFeatureErrors([failure('invalid-format')], 2, lastReportedFrame, registry);
38
+ reportPendingRenderFeatureErrors([failure('cycle')], 3, lastReportedFrame, registry);
39
+ expect(reported).toHaveLength(2);
40
+ reportPendingRenderFeatureErrors([failure('cycle')], 4, lastReportedFrame, registry);
41
+ expect(reported).toHaveLength(2);
42
+ expect(lastReportedFrame.size).toBe(1);
43
+ });
44
+ });
@@ -23,7 +23,16 @@ function feature(mode: 'healthy' | 'failed'): RenderFeature<{ readonly ready: tr
23
23
  'next-frame',
24
24
  ),
25
25
  )
26
- : ok({ resources: [], passes: [] }),
26
+ : ok({
27
+ resources: [
28
+ {
29
+ kind: 'fullscreen-program' as const,
30
+ name: 'synthetic.diagnostics.program',
31
+ source: 'synthetic',
32
+ },
33
+ ],
34
+ passes: [],
35
+ }),
27
36
  };
28
37
  }
29
38
 
@@ -55,7 +55,17 @@ describe('render feature retry rules', () => {
55
55
  identity: 'synthetic.capability-retry',
56
56
  requiredCapabilities: ['compute'],
57
57
  extract: ({ frameNumber }) => ok({ frame: frameNumber }),
58
- plan: () => ok({ resources: [], passes: [] }),
58
+ plan: () =>
59
+ ok({
60
+ resources: [
61
+ {
62
+ kind: 'fullscreen-program' as const,
63
+ name: 'synthetic.capability-retry.program',
64
+ source: 'synthetic',
65
+ },
66
+ ],
67
+ passes: [],
68
+ }),
59
69
  };
60
70
  const host = createRenderFeatureHost([feature], caps(false)).unwrap();
61
71
 
@@ -65,6 +75,7 @@ describe('render feature retry rules', () => {
65
75
  frameNumber: 1,
66
76
  caps: caps(false),
67
77
  });
78
+ expect(host.diagnostics()[0]?.status).toBe('disabled');
68
79
  expect(
69
80
  runRenderFeatureFrame(host, {
70
81
  worlds: [],
@@ -89,6 +89,29 @@ function frame(host: RenderFeatureHost, frameNumber: number) {
89
89
  }
90
90
 
91
91
  describe('RenderFeature stage fault recovery lifecycle', () => {
92
+ it('retains the original exception when a feature stage throws', () => {
93
+ const cause = new TypeError('feature plan exploded');
94
+ const host = createRenderFeatureHost(
95
+ [
96
+ {
97
+ identity: 'synthetic.m10.throwing',
98
+ extract: () => ok({ ready: true }),
99
+ plan: () => {
100
+ throw cause;
101
+ },
102
+ },
103
+ ],
104
+ caps,
105
+ ).unwrap();
106
+
107
+ const result = frame(host, 1);
108
+ expect(result.errors[0]).toMatchObject({
109
+ code: 'render-feature-stage-failed',
110
+ detail: { featureIdentity: 'synthetic.m10.throwing', cause },
111
+ });
112
+ expect(host.diagnostics()[0]?.latestError).toMatchObject({ detail: { cause } });
113
+ });
114
+
92
115
  it.each<Fault>([
93
116
  'create',
94
117
  'prepared-resource',
@@ -1,4 +1,4 @@
1
- import { AssetRegistry, HANDLE_CUBE } from '@forgeax/engine-assets-runtime';
1
+ import { AssetRegistry, HANDLE_CUBE, RuntimeMaterialValue } from '@forgeax/engine-assets-runtime';
2
2
  import type { EntityHandle } from '@forgeax/engine-ecs';
3
3
  import { World } from '@forgeax/engine-ecs';
4
4
  import { createRenderReadLease } from '@forgeax/engine-ecs/projection';
@@ -15,7 +15,7 @@ import {
15
15
  Visibility,
16
16
  VisibilityStateValue,
17
17
  } from '../components';
18
- import { InstanceCollectionStore } from '../instances';
18
+ import { InstanceProjectionStore } from '../instances';
19
19
  import { type ExtractedFrame, extractFrames } from '../render-system-extract';
20
20
  import {
21
21
  type PersistentRenderCandidateRequest,
@@ -47,7 +47,7 @@ function material(values: Record<string, unknown>): MaterialAsset {
47
47
  };
48
48
  }
49
49
 
50
- function createWorld(instanceCollections: InstanceCollectionStore, withCamera: boolean) {
50
+ function createWorld(withCamera: boolean) {
51
51
  const world = new World();
52
52
  registerPropagateTransforms(world);
53
53
  const values: Record<string, unknown> = {
@@ -80,7 +80,6 @@ function createWorld(instanceCollections: InstanceCollectionStore, withCamera: b
80
80
  { component: Visibility, data: { state: VisibilityStateValue.visible } },
81
81
  )
82
82
  .unwrap();
83
- const mixedCollection = instanceCollections.create({ transforms: matrices(2, 0) }).unwrap();
84
83
  const mixed = world
85
84
  .spawn(
86
85
  { component: Transform, data: { pos: [0, 0, 0] } },
@@ -88,7 +87,7 @@ function createWorld(instanceCollections: InstanceCollectionStore, withCamera: b
88
87
  { component: Visibility, data: { state: VisibilityStateValue.inherited } },
89
88
  { component: MeshFilter, data: { assetHandle: HANDLE_CUBE } },
90
89
  { component: MeshRenderer, data: { materials: [materialHandle] } },
91
- { component: Instances, data: { collectionId: mixedCollection.collectionId } },
90
+ { component: Instances, data: { transforms: matrices(2, 0) } },
92
91
  )
93
92
  .unwrap();
94
93
  const sharedConsumer = world
@@ -119,7 +118,6 @@ function createWorld(instanceCollections: InstanceCollectionStore, withCamera: b
119
118
  world,
120
119
  values,
121
120
  materialHandle,
122
- mixedCollectionId: mixedCollection.collectionId,
123
121
  mixed,
124
122
  sharedConsumer,
125
123
  hiddenTarget,
@@ -167,10 +165,59 @@ function rowFor(frame: ExtractedFrame, worldId: number, entityKey: EntityHandle)
167
165
  }
168
166
 
169
167
  describe('PersistentRenderScene mixed World updates', () => {
168
+ it('publishes instance inspection only with the accepted Scene and retires removed components', () => {
169
+ const { world, mixed } = createWorld(true);
170
+ const instanceCollections = new InstanceProjectionStore();
171
+ const scene = new PersistentRenderScene({ instanceCollections });
172
+ const lease = createRenderReadLease(world);
173
+ const owner = { cameraOwner: 0, resourceOwner: 0 };
174
+ let rejectCandidate = false;
175
+ const build = (request: PersistentRenderCandidateRequest) => {
176
+ const frame = extractFrames([world], owner, undefined, undefined, undefined, {
177
+ cull: 'none',
178
+ retainHidden: true,
179
+ renderables: request,
180
+ instanceCollections,
181
+ });
182
+ if (rejectCandidate && request !== 'none') throw new Error('reject prepared candidate');
183
+ return frame;
184
+ };
185
+ const draw = () => scene.extractComposition([world], owner, 0, build, [lease]);
186
+ try {
187
+ draw();
188
+ const before = instanceCollections._inspections(0);
189
+ expect(before).toHaveLength(1);
190
+ world.set(mixed, Instances, { transforms: matrices(3, 9) }).unwrap();
191
+ rejectCandidate = true;
192
+ expect(draw).toThrow('reject prepared candidate');
193
+ expect(instanceCollections._inspections(0)).toEqual(before);
194
+ rejectCandidate = false;
195
+ expect(rowFor(draw(), 0, mixed)?.instances?.instanceCount).toBe(3);
196
+ expect(instanceCollections._inspections(0)[0]?.revision).toBe((before[0]?.revision ?? 0) + 1);
197
+ world.removeComponent(mixed, Instances).unwrap();
198
+ draw();
199
+ expect(instanceCollections._inspections(0)).toEqual([]);
200
+ world
201
+ .addComponent(mixed, { component: Instances, data: { transforms: matrices(1, 2) } })
202
+ .unwrap();
203
+ draw();
204
+ expect(instanceCollections._inspections(0)[0]?.collectionId).not.toBe(
205
+ before[0]?.collectionId,
206
+ );
207
+ world.despawn(mixed).unwrap();
208
+ draw();
209
+ expect(instanceCollections._inspections(0)).toEqual([]);
210
+ } finally {
211
+ lease.dispose();
212
+ scene.dispose();
213
+ instanceCollections.dispose();
214
+ }
215
+ });
216
+
170
217
  it('reconciles mixed fields against a fresh extract while retaining other slot history', () => {
171
- const instanceCollections = new InstanceCollectionStore();
172
- const first = createWorld(instanceCollections, true);
173
- const second = createWorld(instanceCollections, false);
218
+ const instanceCollections = new InstanceProjectionStore();
219
+ const first = createWorld(true);
220
+ const second = createWorld(false);
174
221
  const worlds = [first.world, second.world] as const;
175
222
  const leases = [
176
223
  createRenderReadLease(first.world),
@@ -226,15 +273,24 @@ describe('PersistentRenderScene mixed World updates', () => {
226
273
  expect(initialOtherWorld).toBeDefined();
227
274
 
228
275
  requests.length = 0;
229
- instanceCollections.replace(first.mixedCollectionId, matrices(3, 2)).unwrap();
276
+ first.world.set(first.mixed, Instances, { transforms: matrices(3, 2) }).unwrap();
230
277
  first.world.set(first.mixed, Transform, { pos: [0.25, 0, 0] }).unwrap();
231
278
  first.world.set(first.mixed, ChildOf, { parent: first.parentB }).unwrap();
232
279
  first.world.set(first.parentA, Visibility, { state: VisibilityStateValue.hidden }).unwrap();
233
280
  first.world
234
281
  .set(first.hiddenTarget, Visibility, { state: VisibilityStateValue.hidden })
235
282
  .unwrap();
236
- first.values.baseColor = [0, 1, 0, 1];
237
- first.world.sharedRefs.markChanged(first.materialHandle).unwrap();
283
+ first.world
284
+ .spawn({
285
+ component: RuntimeMaterialValue,
286
+ data: {
287
+ asset: first.materialHandle,
288
+ parameter: 'baseColor',
289
+ kind: 2,
290
+ value: [0, 1, 0, 1],
291
+ },
292
+ })
293
+ .unwrap();
238
294
  first.world.update(0).unwrap();
239
295
 
240
296
  const mixed = draw();
@@ -264,7 +320,7 @@ describe('PersistentRenderScene mixed World updates', () => {
264
320
  submit(mixed);
265
321
 
266
322
  requests.length = 0;
267
- instanceCollections.replace(first.mixedCollectionId, matrices(0, 0)).unwrap();
323
+ first.world.set(first.mixed, Instances, { transforms: matrices(0, 0) }).unwrap();
268
324
  first.world.update(0).unwrap();
269
325
  const zero = draw();
270
326
  expect(frameFacts(zero)).toEqual(frameFacts(drawableOracle()));
@@ -274,7 +330,7 @@ describe('PersistentRenderScene mixed World updates', () => {
274
330
  submit(zero);
275
331
 
276
332
  requests.length = 0;
277
- instanceCollections.replace(first.mixedCollectionId, matrices(1, -2)).unwrap();
333
+ first.world.set(first.mixed, Instances, { transforms: matrices(1, -2) }).unwrap();
278
334
  first.world.update(0).unwrap();
279
335
  const one = draw();
280
336
  expect(frameFacts(one)).toEqual(frameFacts(drawableOracle()));
@@ -290,9 +346,9 @@ describe('PersistentRenderScene mixed World updates', () => {
290
346
  });
291
347
 
292
348
  it('retains slot and temporal history across World reorder and catalog refresh', () => {
293
- const instanceCollections = new InstanceCollectionStore();
294
- const first = createWorld(instanceCollections, true);
295
- const second = createWorld(instanceCollections, false);
349
+ const instanceCollections = new InstanceProjectionStore();
350
+ const first = createWorld(true);
351
+ const second = createWorld(false);
296
352
  let worlds: readonly World[] = [first.world, second.world];
297
353
  let leases = [createRenderReadLease(first.world), createRenderReadLease(second.world)];
298
354
  let owner = { cameraOwner: 0, resourceOwner: 0 };
@@ -373,8 +429,8 @@ describe('PersistentRenderScene mixed World updates', () => {
373
429
  });
374
430
 
375
431
  it('scopes parent and leaf visibility changes while preserving unrelated snapshots', () => {
376
- const instanceCollections = new InstanceCollectionStore();
377
- const first = createWorld(instanceCollections, true);
432
+ const instanceCollections = new InstanceProjectionStore();
433
+ const first = createWorld(true);
378
434
  const lease = createRenderReadLease(first.world);
379
435
  const scene = new PersistentRenderScene({ instanceCollections });
380
436
  const assets = new AssetRegistry({
@@ -424,3 +480,73 @@ describe('PersistentRenderScene mixed World updates', () => {
424
480
  }
425
481
  });
426
482
  });
483
+
484
+ it('two Renderers independently observe shared content replacement, rebinding, deletion and retry', () => {
485
+ const fixture = createWorld(true);
486
+ const { world, materialHandle, sharedConsumer } = fixture;
487
+ const owner = { cameraOwner: 0, resourceOwner: 0 };
488
+ const assets = new AssetRegistry({
489
+ findMaterialArtifact: () => ({ ok: false, error: new Error('not registered') }),
490
+ } as unknown as ShaderRegistry);
491
+ const consumers = [0, 1].map(() => {
492
+ const instanceCollections = new InstanceProjectionStore();
493
+ const scene = new PersistentRenderScene({ instanceCollections });
494
+ const lease = createRenderReadLease(world);
495
+ let fail = false;
496
+ const build = (request: PersistentRenderCandidateRequest) => {
497
+ if (fail) {
498
+ fail = false;
499
+ throw new Error('injected extraction failure');
500
+ }
501
+ return extractFrames([world], owner, assets, undefined, scene.materialSnapshotCacheStore(), {
502
+ cull: 'none',
503
+ retainHidden: true,
504
+ renderables: request,
505
+ instanceCollections,
506
+ });
507
+ };
508
+ return {
509
+ scene,
510
+ lease,
511
+ draw: () => scene.extractComposition([world], owner, 0, build, [lease]),
512
+ fail: () => {
513
+ fail = true;
514
+ },
515
+ };
516
+ });
517
+ const color = (frame: ExtractedFrame) =>
518
+ Array.from(rowFor(frame, 0, sharedConsumer)?.material.baseColor ?? []);
519
+ try {
520
+ for (const consumer of consumers) expect(color(consumer.draw())).toEqual([1, 0, 0]);
521
+ const content = world
522
+ .spawn({
523
+ component: RuntimeMaterialValue,
524
+ data: {
525
+ asset: materialHandle,
526
+ parameter: 'baseColor',
527
+ kind: 2,
528
+ value: [0, 1, 0, 1],
529
+ },
530
+ })
531
+ .unwrap();
532
+ for (const consumer of consumers) {
533
+ consumer.fail();
534
+ expect(() => consumer.draw()).toThrow('injected extraction failure');
535
+ expect(color(consumer.draw())).toEqual([0, 1, 0]);
536
+ }
537
+ const other = world.allocSharedRef('MaterialAsset', material({ baseColor: [0, 0, 1, 1] }));
538
+ world.set(content, RuntimeMaterialValue, { asset: other }).unwrap();
539
+ for (const consumer of consumers) expect(color(consumer.draw())).toEqual([1, 0, 0]);
540
+ world
541
+ .set(content, RuntimeMaterialValue, { asset: materialHandle, value: [0, 0, 1, 1] })
542
+ .unwrap();
543
+ for (const consumer of consumers) expect(color(consumer.draw())).toEqual([0, 0, 1]);
544
+ world.despawn(content).unwrap();
545
+ for (const consumer of consumers) expect(color(consumer.draw())).toEqual([1, 0, 0]);
546
+ } finally {
547
+ for (const consumer of consumers) {
548
+ consumer.lease.dispose();
549
+ consumer.scene.dispose();
550
+ }
551
+ }
552
+ });