@forgeax/engine-render 0.1.33 → 0.1.35

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 +1062 -127
  2. package/dist/assembly/backend-contract.d.ts +2 -0
  3. package/dist/assembly/backend-contract.d.ts.map +1 -1
  4. package/dist/assembly/barrel-distortion-inspection.d.ts +3 -0
  5. package/dist/assembly/barrel-distortion-inspection.d.ts.map +1 -0
  6. package/dist/assembly/canvas-draw-guard.d.ts +3 -0
  7. package/dist/assembly/canvas-draw-guard.d.ts.map +1 -0
  8. package/dist/assembly/cloud-shadow-entry.d.ts +4 -0
  9. package/dist/assembly/cloud-shadow-entry.d.ts.map +1 -0
  10. package/dist/assembly/device-feature-admission.d.ts +2 -2
  11. package/dist/assembly/dynamic-geometry-host.d.ts +4 -1
  12. package/dist/assembly/dynamic-geometry-host.d.ts.map +1 -1
  13. package/dist/assembly/dynamic-geometry-runtime.d.ts +1 -1
  14. package/dist/assembly/dynamic-geometry-runtime.d.ts.map +1 -1
  15. package/dist/assembly/factory.d.ts +1 -1
  16. package/dist/assembly/factory.d.ts.map +1 -1
  17. package/dist/assembly/host-contract.d.ts +6 -5
  18. package/dist/assembly/host-contract.d.ts.map +1 -1
  19. package/dist/assembly/material/assembly.d.ts.map +1 -1
  20. package/dist/assembly/material/pipeline-helpers.d.ts +5 -0
  21. package/dist/assembly/material/pipeline-helpers.d.ts.map +1 -1
  22. package/dist/assembly/material-shader-policy.d.ts +16 -2
  23. package/dist/assembly/material-shader-policy.d.ts.map +1 -1
  24. package/dist/assembly/material-shader-prewarm.d.ts.map +1 -1
  25. package/dist/assembly/recovery/device-loss-fanout.d.ts.map +1 -1
  26. package/dist/assembly/recovery/renderer-recover.d.ts +2 -0
  27. package/dist/assembly/recovery/renderer-recover.d.ts.map +1 -1
  28. package/dist/assembly/render-feature-post-processes.d.ts +6 -0
  29. package/dist/assembly/render-feature-post-processes.d.ts.map +1 -0
  30. package/dist/assembly/render-target-host.d.ts +5 -0
  31. package/dist/assembly/render-target-host.d.ts.map +1 -1
  32. package/dist/assembly/renderer-facade.d.ts.map +1 -1
  33. package/dist/assembly/renderer-feature-inspection.d.ts +1 -1
  34. package/dist/assembly/renderer-feature-inspection.d.ts.map +1 -1
  35. package/dist/assembly/renderer-frame-transaction.d.ts +12 -0
  36. package/dist/assembly/renderer-frame-transaction.d.ts.map +1 -1
  37. package/dist/assembly/renderer-inspection.d.ts +11 -0
  38. package/dist/assembly/renderer-inspection.d.ts.map +1 -1
  39. package/dist/assembly/webgpu-pbr-ready.d.ts +1 -0
  40. package/dist/assembly/webgpu-pbr-ready.d.ts.map +1 -1
  41. package/dist/assembly/webgpu-ready-per-pass.d.ts +39 -0
  42. package/dist/assembly/webgpu-ready-per-pass.d.ts.map +1 -0
  43. package/dist/assembly/webgpu-ready.d.ts +1 -1
  44. package/dist/assembly/webgpu-ready.d.ts.map +1 -1
  45. package/dist/assembly/webgpu-renderer-bootstrap.d.ts.map +1 -1
  46. package/dist/assembly/webgpu-renderer-observation-owner.d.ts +35 -0
  47. package/dist/assembly/webgpu-renderer-observation-owner.d.ts.map +1 -0
  48. package/dist/assembly/webgpu-renderer.d.ts +2 -1
  49. package/dist/assembly/webgpu-renderer.d.ts.map +1 -1
  50. package/dist/authoring.mjs +2 -4
  51. package/dist/authoring.mjs.map +1 -1
  52. package/dist/barrel-distortion.d.ts +47 -0
  53. package/dist/barrel-distortion.d.ts.map +1 -0
  54. package/dist/chunk-3XUBWRY2.mjs +1842 -0
  55. package/dist/chunk-3XUBWRY2.mjs.map +1 -0
  56. package/dist/{chunk-OO3DRYK7.mjs → chunk-G6HDNGY7.mjs} +2400 -1767
  57. package/dist/chunk-G6HDNGY7.mjs.map +1 -0
  58. package/dist/{chunk-ZDQWGQW7.mjs → chunk-LM5IE7GF.mjs} +4 -4
  59. package/dist/{chunk-ZDQWGQW7.mjs.map → chunk-LM5IE7GF.mjs.map} +1 -1
  60. package/dist/{chunk-VOZWDM3L.mjs → chunk-LNIUIKQ7.mjs} +1514 -498
  61. package/dist/chunk-LNIUIKQ7.mjs.map +1 -0
  62. package/dist/chunk-MXTWKZO4.mjs +3268 -0
  63. package/dist/chunk-MXTWKZO4.mjs.map +1 -0
  64. package/dist/chunk-MZ4C7EHF.mjs +6942 -0
  65. package/dist/chunk-MZ4C7EHF.mjs.map +1 -0
  66. package/dist/chunk-RIPAEGI6.mjs +48 -0
  67. package/dist/chunk-RIPAEGI6.mjs.map +1 -0
  68. package/dist/{chunk-LEAXOMYJ.mjs → chunk-SGPUJW3F.mjs} +117 -5
  69. package/dist/chunk-SGPUJW3F.mjs.map +1 -0
  70. package/dist/{chunk-PA5DRJNI.mjs → chunk-X2KA6WHM.mjs} +29 -4
  71. package/dist/chunk-X2KA6WHM.mjs.map +1 -0
  72. package/dist/{chunk-BLXGONR2.mjs → chunk-XSFB7XXQ.mjs} +51 -4
  73. package/dist/chunk-XSFB7XXQ.mjs.map +1 -0
  74. package/dist/cloud/density.d.ts +55 -0
  75. package/dist/cloud/density.d.ts.map +1 -0
  76. package/dist/cloud/extract.d.ts +25 -0
  77. package/dist/cloud/extract.d.ts.map +1 -0
  78. package/dist/cloud/feature.d.ts +57 -0
  79. package/dist/cloud/feature.d.ts.map +1 -0
  80. package/dist/cloud/frame-context.d.ts +8 -0
  81. package/dist/cloud/frame-context.d.ts.map +1 -0
  82. package/dist/cloud/inspection.d.ts +59 -0
  83. package/dist/cloud/inspection.d.ts.map +1 -0
  84. package/dist/cloud/optics.d.ts +56 -0
  85. package/dist/cloud/optics.d.ts.map +1 -0
  86. package/dist/cloud/parameters.d.ts +73 -0
  87. package/dist/cloud/parameters.d.ts.map +1 -0
  88. package/dist/cloud/resources.d.ts +60 -0
  89. package/dist/cloud/resources.d.ts.map +1 -0
  90. package/dist/cloud/shadow.d.ts +42 -0
  91. package/dist/cloud/shadow.d.ts.map +1 -0
  92. package/dist/cloud/temporal.d.ts +74 -0
  93. package/dist/cloud/temporal.d.ts.map +1 -0
  94. package/dist/components/barrel-distortion.d.ts +13 -0
  95. package/dist/components/barrel-distortion.d.ts.map +1 -0
  96. package/dist/components/cloud-layer.d.ts +29 -0
  97. package/dist/components/cloud-layer.d.ts.map +1 -0
  98. package/dist/components/index.d.ts +2 -0
  99. package/dist/components/index.d.ts.map +1 -1
  100. package/dist/components/instances.d.ts +20 -6
  101. package/dist/components/instances.d.ts.map +1 -1
  102. package/dist/construct-renderer.d.ts +1 -1
  103. package/dist/construct-renderer.d.ts.map +1 -1
  104. package/dist/construct-renderer.mjs +8406 -5785
  105. package/dist/construct-renderer.mjs.map +1 -1
  106. package/dist/device/gpu-residency.d.ts +19 -19
  107. package/dist/device/gpu-residency.d.ts.map +1 -1
  108. package/dist/dynamic-geometry.d.ts +5 -1
  109. package/dist/dynamic-geometry.d.ts.map +1 -1
  110. package/dist/errors/cloud.d.ts +59 -0
  111. package/dist/errors/cloud.d.ts.map +1 -0
  112. package/dist/errors/render.d.ts +20 -5
  113. package/dist/errors/render.d.ts.map +1 -1
  114. package/dist/examples/single-layer-medium-surface.d.ts +47 -0
  115. package/dist/examples/single-layer-medium-surface.d.ts.map +1 -0
  116. package/dist/extract/camera.d.ts.map +1 -1
  117. package/dist/extract/contracts.d.ts +5 -1
  118. package/dist/extract/contracts.d.ts.map +1 -1
  119. package/dist/extract/material-context.d.ts +7 -0
  120. package/dist/extract/material-context.d.ts.map +1 -0
  121. package/dist/features/barrel-distortion.d.ts +29 -0
  122. package/dist/features/barrel-distortion.d.ts.map +1 -0
  123. package/dist/features/error-reporting.d.ts +6 -2
  124. package/dist/features/error-reporting.d.ts.map +1 -1
  125. package/dist/features/host.d.ts +15 -0
  126. package/dist/features/host.d.ts.map +1 -1
  127. package/dist/features/plan.d.ts +10 -0
  128. package/dist/features/plan.d.ts.map +1 -1
  129. package/dist/features/render-graph-contribution.d.ts +5 -0
  130. package/dist/features/render-graph-contribution.d.ts.map +1 -1
  131. package/dist/features/render-graph-raster.d.ts +1 -0
  132. package/dist/features/render-graph-raster.d.ts.map +1 -1
  133. package/dist/features/targets.d.ts +2 -0
  134. package/dist/features/targets.d.ts.map +1 -1
  135. package/dist/features/types.d.ts +48 -0
  136. package/dist/features/types.d.ts.map +1 -1
  137. package/dist/fullscreen-post-process-pass.d.ts +15 -7
  138. package/dist/fullscreen-post-process-pass.d.ts.map +1 -1
  139. package/dist/glyph-text-layout-system.d.ts +1 -1
  140. package/dist/glyph-text-layout-system.d.ts.map +1 -1
  141. package/dist/gpu-dirty-ranges.d.ts +11 -0
  142. package/dist/gpu-dirty-ranges.d.ts.map +1 -0
  143. package/dist/gpu-driven/batch-topology.d.ts +17 -0
  144. package/dist/gpu-driven/batch-topology.d.ts.map +1 -1
  145. package/dist/gpu-driven/material-artifacts.d.ts +6 -0
  146. package/dist/gpu-driven/material-artifacts.d.ts.map +1 -1
  147. package/dist/gpu-driven/prepared-draw.d.ts.map +1 -1
  148. package/dist/gpu-driven/production-raster-material.d.ts +178 -0
  149. package/dist/gpu-driven/production-raster-material.d.ts.map +1 -0
  150. package/dist/gpu-driven/production-raster.d.ts +47 -3
  151. package/dist/gpu-driven/production-raster.d.ts.map +1 -1
  152. package/dist/gpu-driven/resource-allocation.d.ts +31 -0
  153. package/dist/gpu-driven/resource-allocation.d.ts.map +1 -0
  154. package/dist/gpu-driven/shadow-ownership.d.ts +14 -0
  155. package/dist/gpu-driven/shadow-ownership.d.ts.map +1 -0
  156. package/dist/gpu-driven/view-gpu.d.ts +26 -2
  157. package/dist/gpu-driven/view-gpu.d.ts.map +1 -1
  158. package/dist/gpu-scene.d.ts +5 -0
  159. package/dist/gpu-scene.d.ts.map +1 -1
  160. package/dist/hdrp-buffers.d.ts +2 -2
  161. package/dist/hdrp-buffers.d.ts.map +1 -1
  162. package/dist/ibl/frame-resources.d.ts.map +1 -1
  163. package/dist/ibl/skylight-bind-group.d.ts +14 -5
  164. package/dist/ibl/skylight-bind-group.d.ts.map +1 -1
  165. package/dist/index.d.ts +30 -7
  166. package/dist/index.d.ts.map +1 -1
  167. package/dist/index.mjs +780 -9
  168. package/dist/index.mjs.map +1 -1
  169. package/dist/inspection-types.d.ts +135 -1
  170. package/dist/inspection-types.d.ts.map +1 -1
  171. package/dist/instance-buffer-cache.d.ts +4 -4
  172. package/dist/instance-buffer-cache.d.ts.map +1 -1
  173. package/dist/instances.d.ts +47 -100
  174. package/dist/instances.d.ts.map +1 -1
  175. package/dist/internal.mjs +7 -8
  176. package/dist/internal.mjs.map +1 -1
  177. package/dist/pbr-pipeline.d.ts +22 -4
  178. package/dist/pbr-pipeline.d.ts.map +1 -1
  179. package/dist/pipeline/single-layer-medium-passes.d.ts +73 -0
  180. package/dist/pipeline/single-layer-medium-passes.d.ts.map +1 -0
  181. package/dist/pipeline/standard-forward-lane.d.ts.map +1 -1
  182. package/dist/pipeline/standard-output/graph.d.ts.map +1 -1
  183. package/dist/pipeline/standard-output/lut-gpu.d.ts +2 -2
  184. package/dist/pipeline/standard-output/lut-gpu.d.ts.map +1 -1
  185. package/dist/pipeline/standard-output/resources.d.ts +2 -2
  186. package/dist/pipeline/standard-output/resources.d.ts.map +1 -1
  187. package/dist/pipeline/standard-output/types.d.ts +4 -2
  188. package/dist/pipeline/standard-output/types.d.ts.map +1 -1
  189. package/dist/pipeline/standard-pipeline.d.ts.map +1 -1
  190. package/dist/pipeline/standard-post.d.ts.map +1 -1
  191. package/dist/pipeline-builder.d.ts +1 -1
  192. package/dist/pipeline-builder.d.ts.map +1 -1
  193. package/dist/plugin.d.ts.map +1 -1
  194. package/dist/prepare/prepared-graphics-resolver.d.ts +2 -0
  195. package/dist/prepare/prepared-graphics-resolver.d.ts.map +1 -1
  196. package/dist/publication/contract.d.ts +77 -0
  197. package/dist/publication/contract.d.ts.map +1 -0
  198. package/dist/publication/dependencies.d.ts +7 -0
  199. package/dist/publication/dependencies.d.ts.map +1 -0
  200. package/dist/publication/prepare-geometry.d.ts +9 -0
  201. package/dist/publication/prepare-geometry.d.ts.map +1 -0
  202. package/dist/publication/programs.d.ts +5 -0
  203. package/dist/publication/programs.d.ts.map +1 -0
  204. package/dist/publication/publisher.d.ts +33 -0
  205. package/dist/publication/publisher.d.ts.map +1 -0
  206. package/dist/publication/receiver.d.ts +29 -0
  207. package/dist/publication/receiver.d.ts.map +1 -0
  208. package/dist/publication/resource-scope.d.ts +18 -0
  209. package/dist/publication/resource-scope.d.ts.map +1 -0
  210. package/dist/publication/targets.d.ts +33 -0
  211. package/dist/publication/targets.d.ts.map +1 -0
  212. package/dist/publication/video.d.ts +5 -0
  213. package/dist/publication/video.d.ts.map +1 -0
  214. package/dist/record/barrel-distortion-frame.d.ts +15 -0
  215. package/dist/record/barrel-distortion-frame.d.ts.map +1 -0
  216. package/dist/record/dynamic-geometry-consumption.d.ts +10 -2
  217. package/dist/record/dynamic-geometry-consumption.d.ts.map +1 -1
  218. package/dist/record/fold-buckets.d.ts +95 -0
  219. package/dist/record/fold-buckets.d.ts.map +1 -0
  220. package/dist/record/fold-instance-buffer.d.ts +6 -0
  221. package/dist/record/fold-instance-buffer.d.ts.map +1 -0
  222. package/dist/record/frame-lighting.d.ts +1 -0
  223. package/dist/record/frame-lighting.d.ts.map +1 -1
  224. package/dist/record/frame-snapshot.d.ts +43 -9
  225. package/dist/record/frame-snapshot.d.ts.map +1 -1
  226. package/dist/record/frame.d.ts +15 -65
  227. package/dist/record/frame.d.ts.map +1 -1
  228. package/dist/record/gpu-draw-receipts.d.ts +11 -0
  229. package/dist/record/gpu-draw-receipts.d.ts.map +1 -0
  230. package/dist/record/gpu-timing.d.ts +8 -0
  231. package/dist/record/gpu-timing.d.ts.map +1 -1
  232. package/dist/record/helpers.d.ts +2 -2
  233. package/dist/record/helpers.d.ts.map +1 -1
  234. package/dist/record/main-pass-geometry.d.ts +22 -6
  235. package/dist/record/main-pass-geometry.d.ts.map +1 -1
  236. package/dist/record/main-pass-material.d.ts +14 -10
  237. package/dist/record/main-pass-material.d.ts.map +1 -1
  238. package/dist/record/main-pass-sprite-draws.d.ts +3 -16
  239. package/dist/record/main-pass-sprite-draws.d.ts.map +1 -1
  240. package/dist/record/main-pass.d.ts +13 -2
  241. package/dist/record/main-pass.d.ts.map +1 -1
  242. package/dist/record/motion-blur-receipt.d.ts +11 -0
  243. package/dist/record/motion-blur-receipt.d.ts.map +1 -0
  244. package/dist/record/prepared-material-bindings.d.ts +3 -3
  245. package/dist/record/prepared-material-bindings.d.ts.map +1 -1
  246. package/dist/record/probe-blend-buffer.d.ts +16 -0
  247. package/dist/record/probe-blend-buffer.d.ts.map +1 -0
  248. package/dist/record/recovery-pipeline.d.ts.map +1 -1
  249. package/dist/record/render-context.d.ts +24 -5
  250. package/dist/record/render-context.d.ts.map +1 -1
  251. package/dist/record/shadow-pass.d.ts +2 -0
  252. package/dist/record/shadow-pass.d.ts.map +1 -1
  253. package/dist/record/skybox-post-pass.d.ts +1 -1
  254. package/dist/record/skybox-post-pass.d.ts.map +1 -1
  255. package/dist/record/sprite-instance-buffer.d.ts +31 -0
  256. package/dist/record/sprite-instance-buffer.d.ts.map +1 -0
  257. package/dist/record/target-capture-graph.d.ts +19 -0
  258. package/dist/record/target-capture-graph.d.ts.map +1 -0
  259. package/dist/record/typed-frame-graph.d.ts +24 -15
  260. package/dist/record/typed-frame-graph.d.ts.map +1 -1
  261. package/dist/record/view-ubo.d.ts +11 -3
  262. package/dist/record/view-ubo.d.ts.map +1 -1
  263. package/dist/record/volume-params.d.ts +9 -0
  264. package/dist/record/volume-params.d.ts.map +1 -0
  265. package/dist/recovery/render-system-candidate.d.ts +14 -4
  266. package/dist/recovery/render-system-candidate.d.ts.map +1 -1
  267. package/dist/reflection/record-owner.d.ts.map +1 -1
  268. package/dist/render-contract.d.ts +46 -5
  269. package/dist/render-contract.d.ts.map +1 -1
  270. package/dist/render-graph-primitives.d.ts.map +1 -1
  271. package/dist/render-pipeline.d.ts +103 -2
  272. package/dist/render-pipeline.d.ts.map +1 -1
  273. package/dist/render-system-extract-tail.d.ts +3 -2
  274. package/dist/render-system-extract-tail.d.ts.map +1 -1
  275. package/dist/render-system-extract.d.ts +18 -9
  276. package/dist/render-system-extract.d.ts.map +1 -1
  277. package/dist/render-system-presentation.d.ts +23 -0
  278. package/dist/render-system-presentation.d.ts.map +1 -0
  279. package/dist/render-system-producer-roots.d.ts +10 -0
  280. package/dist/render-system-producer-roots.d.ts.map +1 -0
  281. package/dist/render-system-projections.d.ts +3 -2
  282. package/dist/render-system-projections.d.ts.map +1 -1
  283. package/dist/render-system-residency.d.ts +12 -0
  284. package/dist/render-system-residency.d.ts.map +1 -0
  285. package/dist/render-system.d.ts +22 -21
  286. package/dist/render-system.d.ts.map +1 -1
  287. package/dist/scene/probe-blend.d.ts +27 -0
  288. package/dist/scene/probe-blend.d.ts.map +1 -1
  289. package/dist/scene/render-scene.d.ts +18 -7
  290. package/dist/scene/render-scene.d.ts.map +1 -1
  291. package/dist/scene/render-source.d.ts +42 -0
  292. package/dist/scene/render-source.d.ts.map +1 -0
  293. package/dist/scene/source-systems.d.ts +6 -0
  294. package/dist/scene/source-systems.d.ts.map +1 -0
  295. package/dist/ssao-buffers.d.ts +9 -0
  296. package/dist/ssao-buffers.d.ts.map +1 -1
  297. package/dist/surface/dynamic-input.d.ts +133 -0
  298. package/dist/surface/dynamic-input.d.ts.map +1 -0
  299. package/dist/surface/gpu-driven.d.ts +41 -0
  300. package/dist/surface/gpu-driven.d.ts.map +1 -0
  301. package/dist/surface/submission-observation.d.ts +136 -0
  302. package/dist/surface/submission-observation.d.ts.map +1 -0
  303. package/dist/systems/skin-palette-allocator.d.ts.map +1 -1
  304. package/dist/systems/skin-palette-types.d.ts +10 -0
  305. package/dist/systems/skin-palette-types.d.ts.map +1 -1
  306. package/dist/systems/transparent-dispatch.d.ts +2 -2
  307. package/dist/systems/transparent-dispatch.d.ts.map +1 -1
  308. package/dist/systems/transparent-sort-config.d.ts +2 -1
  309. package/dist/systems/transparent-sort-config.d.ts.map +1 -1
  310. package/dist/temporal/gpu.d.ts +13 -1
  311. package/dist/temporal/gpu.d.ts.map +1 -1
  312. package/dist/temporal/index.mjs +6 -7
  313. package/dist/temporal/inspection.d.ts.map +1 -1
  314. package/dist/temporal/standard-scene-data.d.ts.map +1 -1
  315. package/dist/temporal/target-coverage-attachment.d.ts.map +1 -1
  316. package/dist/transmission/single-layer-medium.d.ts +89 -0
  317. package/dist/transmission/single-layer-medium.d.ts.map +1 -0
  318. package/dist/typed-render-graph-primitives.d.ts +14 -2
  319. package/dist/typed-render-graph-primitives.d.ts.map +1 -1
  320. package/dist/volume/component.d.ts +9 -4
  321. package/dist/volume/component.d.ts.map +1 -1
  322. package/dist/volume/extract.d.ts +1 -1
  323. package/dist/volume/extract.d.ts.map +1 -1
  324. package/dist/volume/inspection.d.ts +2 -0
  325. package/dist/volume/inspection.d.ts.map +1 -1
  326. package/dist/volume/passes.d.ts +4 -1
  327. package/dist/volume/passes.d.ts.map +1 -1
  328. package/dist/volume/resources.d.ts.map +1 -1
  329. package/package.json +22 -21
  330. package/src/__tests__/atmosphere-daylight.dawn.test.ts +72 -0
  331. package/src/__tests__/auto-exposure-transaction.integration.test.ts +4 -9
  332. package/src/__tests__/barrel-distortion-lkg.integration.test.ts +353 -0
  333. package/src/__tests__/barrel-distortion.dawn.test.ts +41 -0
  334. package/src/__tests__/barrel-distortion.unit.test.ts +364 -0
  335. package/src/__tests__/construct-renderer-identity.unit.test.ts +49 -0
  336. package/src/__tests__/detached-recovery-graph.unit.test.ts +277 -0
  337. package/src/__tests__/device-feature-admission.unit.test.ts +3 -2
  338. package/src/__tests__/device-loss-fanout.unit.test.ts +36 -1
  339. package/src/__tests__/docs-gate-green.unit.test.ts +112 -1
  340. package/src/__tests__/factory-contract.integration.test.ts +295 -45
  341. package/src/__tests__/feature-depth-input.dawn.test.ts +129 -2
  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-material-artifacts.unit.test.ts +143 -0
  347. package/src/__tests__/gpu-driven-pbr.dawn.test.ts +5 -6
  348. package/src/__tests__/gpu-driven-production.integration.test.ts +482 -3
  349. package/src/__tests__/gpu-driven-shadow-views.dawn.test.ts +6 -5
  350. package/src/__tests__/gpu-driven-view-gpu-evidence.ts +94 -6
  351. package/src/__tests__/gpu-driven-view-graph.integration.test.ts +18 -2
  352. package/src/__tests__/gpu-driven-view.browser.test.ts +5 -8
  353. package/src/__tests__/gpu-driven-view.dawn.test.ts +6 -2
  354. package/src/__tests__/gpu-driven-view.unit.test.ts +1 -1
  355. package/src/__tests__/gpu-pass-timing-lifecycle.integration.test.ts +1 -0
  356. package/src/__tests__/gpu-pass-timing.browser.test.ts +6 -4
  357. package/src/__tests__/gpu-pass-timing.rhi-null.unit.test.ts +1 -0
  358. package/src/__tests__/gpu-scene.unit.test.ts +119 -9
  359. package/src/__tests__/instance-allocation-failure.unit.test.ts +57 -0
  360. package/src/__tests__/instances-culling.integration.test.ts +10 -36
  361. package/src/__tests__/instances-store.unit.test.ts +97 -305
  362. package/src/__tests__/instances-world-ownership.integration.test.ts +62 -0
  363. package/src/__tests__/ktx2-basis-gpu-consumer.dawn.test.ts +4 -1
  364. package/src/__tests__/main-pass-material-medium-selection.unit.test.ts +145 -0
  365. package/src/__tests__/material-shader-prewarm.unit.test.ts +59 -0
  366. package/src/__tests__/material-snapshot-mutation.integration.test.ts +39 -1
  367. package/src/__tests__/materials-standard-contract.unit.test.ts +0 -1
  368. package/src/__tests__/morph-culling-reentry.dawn.test.ts +5 -5
  369. package/src/__tests__/morph-visual-evidence.integration.test.ts +8 -7
  370. package/src/__tests__/motion-blur-compute.dawn.test.ts +13 -7
  371. package/src/__tests__/motion-blur.pixel.integration.test.ts +2 -2
  372. package/src/__tests__/nineslice-handle-surface.unit.test.ts +4 -4
  373. package/src/__tests__/pbr-view-layout.unit.test.ts +9 -0
  374. package/src/__tests__/physical-clearcoat.integration.test.ts +66 -8
  375. package/src/__tests__/physical-material-budget.unit.test.ts +40 -0
  376. package/src/__tests__/prepared-graphics-capability.unit.test.ts +66 -2
  377. package/src/__tests__/probe-bindings.unit.test.ts +582 -1
  378. package/src/__tests__/probe-scene-dirty.integration.test.ts +28 -1
  379. package/src/__tests__/receipt-observation-retirement.unit.test.ts +65 -0
  380. package/src/__tests__/recovery-candidate-prepare.contract.test.ts +69 -0
  381. package/src/__tests__/render-error-code-owner.test-d.ts +8 -0
  382. package/src/__tests__/render-feature-diagnostics.unit.test.ts +15 -2
  383. package/src/__tests__/render-feature-error-reporting.unit.test.ts +21 -1
  384. package/src/__tests__/render-feature-prepared-diagnostics.unit.test.ts +10 -1
  385. package/src/__tests__/render-feature-retry.unit.test.ts +12 -1
  386. package/src/__tests__/render-scene-mixed-updates.integration.test.ts +145 -19
  387. package/src/__tests__/render-scene-projection.unit.test.ts +36 -11
  388. package/src/__tests__/render-scene-temporal-retry.unit.test.ts +20 -20
  389. package/src/__tests__/render-target-probe-soak.perf.test.ts +2 -2
  390. package/src/__tests__/render-temporal-environment-consumer.browser.test.ts +3 -3
  391. package/src/__tests__/render-temporal-environment-consumer.dawn.test.ts +5 -2
  392. package/src/__tests__/renderer-factory-material-contract.unit.test.ts +42 -8
  393. package/src/__tests__/renderer-frame-generation-fence.integration.test.ts +16 -0
  394. package/src/__tests__/renderer-frame-transaction.integration.test.ts +26 -10
  395. package/src/__tests__/resource-allocation.unit.test.ts +123 -0
  396. package/src/__tests__/single-layer-medium-passes.unit.test.ts +275 -0
  397. package/src/__tests__/skin-motion-regression.unit.test.ts +70 -1
  398. package/src/__tests__/skin-palette-recycling.dawn.test.ts +97 -0
  399. package/src/__tests__/skinned-shadow-caster.test.ts +15 -0
  400. package/src/__tests__/ssr-carrier-public-contract.unit.test.ts +21 -0
  401. package/src/__tests__/ssr-gpu-dispatch.browser.test.ts +4 -4
  402. package/src/__tests__/ssr-gpu-dispatch.dawn.test.ts +2 -2
  403. package/src/__tests__/ssr-gpu-dispatch.ts +1 -1
  404. package/src/__tests__/ssr-receiver-coverage.dawn.test.ts +6 -2
  405. package/src/__tests__/standard-output-chain-contract.unit.test.ts +28 -0
  406. package/src/__tests__/standard-pbr-artifact-assembly.unit.test.ts +46 -1
  407. package/src/__tests__/standard-pipeline.integration.test.ts +182 -1
  408. package/src/__tests__/surface-dynamic-buffer-lifecycle.unit.test.ts +506 -0
  409. package/src/__tests__/surface-submission-observation.unit.test.ts +812 -0
  410. package/src/__tests__/temporal-history-lifecycle.unit.test.ts +32 -0
  411. package/src/__tests__/temporal-performance.integration.test.ts +3 -3
  412. package/src/__tests__/visibility-instances.integration.test.ts +7 -19
  413. package/src/__tests__/visibility-producer-matrix.integration.test.ts +9 -20
  414. package/src/__tests__/volume-optics-oracle.unit.test.ts +4 -2
  415. package/src/__tests__/volumetric-fog-budget-recovery.integration.test.ts +30 -20
  416. package/src/__tests__/volumetric-fog-errors.unit.test.ts +4 -3
  417. package/src/__tests__/volumetric-fog-multiple.unit.test.ts +55 -0
  418. package/src/__tests__/volumetric-fog-spot-frame-optics.integration.test.ts +1 -1
  419. package/src/__tests__/volumetric-fog-stage-readback.dawn.test.ts +113 -9
  420. package/src/__tests__/volumetric-fog-topology.unit.test.ts +5 -3
  421. package/src/assembly/backend-contract.ts +30 -0
  422. package/src/assembly/barrel-distortion-inspection.ts +22 -0
  423. package/src/assembly/canvas-draw-guard.ts +11 -0
  424. package/src/assembly/cloud-shadow-entry.ts +22 -0
  425. package/src/assembly/device-feature-admission.ts +2 -2
  426. package/src/assembly/dynamic-geometry-host.ts +65 -37
  427. package/src/assembly/dynamic-geometry-runtime.ts +7 -1
  428. package/src/assembly/factory.ts +2 -30
  429. package/src/assembly/host-contract.ts +8 -5
  430. package/src/assembly/material/assembly.ts +25 -13
  431. package/src/assembly/material/pipeline-helpers.ts +53 -12
  432. package/src/assembly/material-shader-policy.ts +55 -0
  433. package/src/assembly/material-shader-prewarm.ts +21 -10
  434. package/src/assembly/recovery/device-loss-fanout.ts +13 -0
  435. package/src/assembly/recovery/renderer-recover.ts +50 -18
  436. package/src/assembly/render-feature-post-processes.ts +54 -0
  437. package/src/assembly/render-target-host.ts +57 -47
  438. package/src/assembly/renderer-facade.ts +7 -2
  439. package/src/assembly/renderer-feature-inspection.ts +8 -2
  440. package/src/assembly/renderer-frame-transaction.ts +23 -0
  441. package/src/assembly/renderer-inspection.ts +28 -0
  442. package/src/assembly/skin-palette-owner.ts +1 -1
  443. package/src/assembly/temporal-post-process-prewarm.ts +3 -3
  444. package/src/assembly/webgpu-pbr-ready.ts +9 -1
  445. package/src/assembly/webgpu-ready-per-pass.ts +104 -0
  446. package/src/assembly/webgpu-ready.ts +116 -62
  447. package/src/assembly/webgpu-renderer-bootstrap.ts +0 -12
  448. package/src/assembly/webgpu-renderer-observation-owner.ts +222 -0
  449. package/src/assembly/webgpu-renderer.ts +303 -313
  450. package/src/assembly/webgpu-vertex-layouts.ts +1 -1
  451. package/src/barrel-distortion.ts +247 -0
  452. package/src/cloud/__tests__/cloud-density-parity.dawn.test.ts +173 -0
  453. package/src/cloud/__tests__/cloud-layer-framebuffer-y.dawn.test.ts +265 -0
  454. package/src/cloud/__tests__/cloud-layer.browser.test.ts +432 -0
  455. package/src/cloud/__tests__/cloud-layer.unit.test.ts +634 -0
  456. package/src/cloud/density.ts +600 -0
  457. package/src/cloud/extract.ts +110 -0
  458. package/src/cloud/feature.ts +1810 -0
  459. package/src/cloud/frame-context.ts +42 -0
  460. package/src/cloud/inspection.ts +130 -0
  461. package/src/cloud/optics.ts +285 -0
  462. package/src/cloud/parameters.ts +255 -0
  463. package/src/cloud/resources.ts +122 -0
  464. package/src/cloud/shadow.ts +167 -0
  465. package/src/cloud/temporal.ts +226 -0
  466. package/src/components/barrel-distortion.ts +46 -0
  467. package/src/components/cloud-layer.ts +47 -0
  468. package/src/components/index.ts +2 -0
  469. package/src/components/instances.ts +18 -45
  470. package/src/components/sprite-instances.ts +1 -1
  471. package/src/construct-renderer.ts +1 -1
  472. package/src/device/gpu-residency.ts +26 -25
  473. package/src/dynamic-geometry.ts +12 -1
  474. package/src/errors/cloud.ts +103 -0
  475. package/src/errors/render.ts +47 -8
  476. package/src/examples/single-layer-medium-surface.ts +59 -0
  477. package/src/extract/camera.ts +15 -1
  478. package/src/extract/contracts.ts +5 -0
  479. package/src/extract/material-context.ts +21 -0
  480. package/src/features/barrel-distortion.ts +172 -0
  481. package/src/features/error-reporting.ts +26 -6
  482. package/src/features/host.ts +128 -19
  483. package/src/features/plan.ts +10 -0
  484. package/src/features/render-graph-contribution.ts +9 -0
  485. package/src/features/render-graph-raster.ts +45 -3
  486. package/src/features/targets.ts +66 -0
  487. package/src/features/types.ts +51 -0
  488. package/src/fullscreen-post-process-pass.ts +41 -9
  489. package/src/glyph-text-layout-system.ts +2 -2
  490. package/src/gpu-dirty-ranges.ts +48 -0
  491. package/src/gpu-driven/batch-topology.ts +53 -0
  492. package/src/gpu-driven/material-artifacts.ts +33 -25
  493. package/src/gpu-driven/prepared-draw.ts +12 -4
  494. package/src/gpu-driven/production-raster-material.ts +1894 -0
  495. package/src/gpu-driven/production-raster.ts +825 -784
  496. package/src/gpu-driven/resource-allocation.ts +142 -0
  497. package/src/gpu-driven/shadow-ownership.ts +71 -0
  498. package/src/gpu-driven/view-gpu.ts +164 -10
  499. package/src/gpu-scene.ts +75 -84
  500. package/src/hdrp-buffers.ts +4 -3
  501. package/src/ibl/frame-resources.ts +0 -1
  502. package/src/ibl/skylight-bind-group.ts +62 -32
  503. package/src/index.ts +205 -9
  504. package/src/inspection-types.ts +162 -0
  505. package/src/instance-buffer-cache.ts +4 -4
  506. package/src/instances.ts +152 -569
  507. package/src/pbr-pipeline.ts +230 -30
  508. package/src/pipeline/single-layer-medium-passes.ts +267 -0
  509. package/src/pipeline/standard-forward-lane.ts +313 -57
  510. package/src/pipeline/standard-output/graph.ts +8 -2
  511. package/src/pipeline/standard-output/lut-gpu.ts +3 -3
  512. package/src/pipeline/standard-output/resources.ts +2 -2
  513. package/src/pipeline/standard-output/types.ts +12 -1
  514. package/src/pipeline/standard-pipeline.ts +291 -18
  515. package/src/pipeline/standard-post.ts +90 -24
  516. package/src/pipeline-builder.ts +1 -1
  517. package/src/plugin.ts +4 -0
  518. package/src/points-lines/__tests__/points-lines.bench.ts +17 -8
  519. package/src/prepare/prepared-graphics-resolver.ts +2 -0
  520. package/src/publication/__tests__/derived-geometry.integration.test.ts +177 -0
  521. package/src/publication/__tests__/features.integration.test.ts +122 -0
  522. package/src/publication/__tests__/programs.unit.test.ts +25 -0
  523. package/src/publication/__tests__/publication.integration.test.ts +370 -0
  524. package/src/publication/__tests__/targets.integration.test.ts +120 -0
  525. package/src/publication/__tests__/video.browser.test.ts +50 -0
  526. package/src/publication/contract.ts +104 -0
  527. package/src/publication/dependencies.ts +54 -0
  528. package/src/publication/prepare-geometry.ts +94 -0
  529. package/src/publication/programs.ts +45 -0
  530. package/src/publication/publisher.ts +618 -0
  531. package/src/publication/receiver.ts +297 -0
  532. package/src/publication/resource-scope.ts +31 -0
  533. package/src/publication/targets.ts +182 -0
  534. package/src/publication/video.ts +33 -0
  535. package/src/record/__tests__/main-pass-geometry.unit.test.ts +30 -0
  536. package/src/record/__tests__/material-bg-cache.unit.test.ts +15 -1
  537. package/src/record/__tests__/sprite-material-abi.unit.test.ts +9 -11
  538. package/src/record/__tests__/temporal-payload.unit.test.ts +1 -1
  539. package/src/record/__tests__/transparent-group2-selection.unit.test.ts +11 -0
  540. package/src/record/__tests__/typed-frame-graph-plan.unit.test.ts +1 -0
  541. package/src/record/__tests__/video-texture-view.unit.test.ts +9 -9
  542. package/src/record/__tests__/view-ubo-layout.unit.test.ts +4 -3
  543. package/src/record/barrel-distortion-frame.ts +110 -0
  544. package/src/record/dynamic-geometry-consumption.ts +39 -9
  545. package/src/record/fold-buckets.ts +855 -0
  546. package/src/record/fold-instance-buffer.ts +51 -0
  547. package/src/record/frame-lighting.ts +36 -2
  548. package/src/record/frame-snapshot.ts +45 -9
  549. package/src/record/frame.ts +258 -1105
  550. package/src/record/gpu-draw-receipts.ts +38 -0
  551. package/src/record/gpu-timing.ts +48 -5
  552. package/src/record/helpers.ts +2 -2
  553. package/src/record/main-pass-geometry.ts +272 -84
  554. package/src/record/main-pass-material.ts +102 -36
  555. package/src/record/main-pass-sprite-draws.ts +13 -217
  556. package/src/record/main-pass.ts +164 -27
  557. package/src/record/motion-blur-receipt.ts +91 -0
  558. package/src/record/prepared-material-bindings.ts +11 -6
  559. package/src/record/probe-blend-buffer.ts +180 -0
  560. package/src/record/recovery-pipeline.ts +35 -3
  561. package/src/record/render-context.ts +41 -5
  562. package/src/record/shadow-pass.ts +30 -4
  563. package/src/record/skybox-post-pass.ts +28 -4
  564. package/src/record/sprite-instance-buffer.ts +155 -0
  565. package/src/record/target-capture-graph.ts +258 -0
  566. package/src/record/typed-frame-graph.ts +776 -340
  567. package/src/record/view-ubo.ts +27 -4
  568. package/src/record/volume-params.ts +225 -0
  569. package/src/recovery/render-system-candidate.ts +243 -50
  570. package/src/reflection/record-owner.ts +7 -3
  571. package/src/render-contract.ts +54 -5
  572. package/src/render-graph-primitives.ts +7 -1
  573. package/src/render-pipeline.ts +130 -0
  574. package/src/render-system-extract-tail.ts +90 -78
  575. package/src/render-system-extract.ts +76 -76
  576. package/src/render-system-presentation.ts +56 -0
  577. package/src/render-system-producer-roots.ts +96 -0
  578. package/src/render-system-projections.ts +3 -2
  579. package/src/render-system-residency.ts +159 -0
  580. package/src/render-system.ts +421 -408
  581. package/src/scene/probe-blend.ts +96 -4
  582. package/src/scene/render-scene.ts +416 -337
  583. package/src/scene/render-source.ts +90 -0
  584. package/src/scene/source-systems.ts +33 -0
  585. package/src/ssao-buffers.ts +95 -2
  586. package/src/surface/__tests__/dynamic-input.unit.test.ts +374 -0
  587. package/src/surface/__tests__/gpu-driven.unit.test.ts +87 -0
  588. package/src/surface/dynamic-input.ts +658 -0
  589. package/src/surface/gpu-driven.ts +147 -0
  590. package/src/surface/submission-observation.ts +517 -0
  591. package/src/systems/skin-palette-allocator.ts +5 -1
  592. package/src/systems/skin-palette-types.ts +11 -0
  593. package/src/systems/transparent-dispatch.ts +2 -2
  594. package/src/systems/transparent-sort-config.ts +3 -1
  595. package/src/temporal/gpu.ts +101 -17
  596. package/src/temporal/inspection.ts +7 -1
  597. package/src/temporal/standard-scene-data.ts +5 -0
  598. package/src/temporal/target-coverage-attachment.ts +4 -0
  599. package/src/transmission/__tests__/single-layer-medium.unit.test.ts +149 -0
  600. package/src/transmission/__tests__/standard-transmission.dawn.test.ts +12 -11
  601. package/src/transmission/single-layer-medium.ts +322 -0
  602. package/src/typed-render-graph-primitives.ts +70 -2
  603. package/src/volume/component.ts +10 -9
  604. package/src/volume/extract.ts +11 -5
  605. package/src/volume/inspection.ts +3 -0
  606. package/src/volume/passes.ts +87 -4
  607. package/src/volume/resources.ts +2 -1
  608. package/dist/chunk-3RTCJTH4.mjs +0 -1610
  609. package/dist/chunk-3RTCJTH4.mjs.map +0 -1
  610. package/dist/chunk-BLXGONR2.mjs.map +0 -1
  611. package/dist/chunk-ILQ5MEZF.mjs +0 -32
  612. package/dist/chunk-ILQ5MEZF.mjs.map +0 -1
  613. package/dist/chunk-LEAXOMYJ.mjs.map +0 -1
  614. package/dist/chunk-NB2S7ZRX.mjs +0 -1783
  615. package/dist/chunk-NB2S7ZRX.mjs.map +0 -1
  616. package/dist/chunk-NNQL7CB3.mjs +0 -1713
  617. package/dist/chunk-NNQL7CB3.mjs.map +0 -1
  618. package/dist/chunk-OO3DRYK7.mjs.map +0 -1
  619. package/dist/chunk-PA5DRJNI.mjs.map +0 -1
  620. package/dist/chunk-PLAXOOX5.mjs +0 -1907
  621. package/dist/chunk-PLAXOOX5.mjs.map +0 -1
  622. package/dist/chunk-RXZ3RGOU.mjs +0 -53
  623. package/dist/chunk-RXZ3RGOU.mjs.map +0 -1
  624. package/dist/chunk-VOZWDM3L.mjs.map +0 -1
@@ -0,0 +1,364 @@
1
+ import { World } from '@forgeax/engine-ecs';
2
+ import { Transform } from '@forgeax/engine-scene';
3
+ import { describe, expect, it } from 'vitest';
4
+ import { BarrelDistortion, Camera } from '../components';
5
+ import {
6
+ BARREL_DISTORTION_POST_PROCESS_ID,
7
+ BARREL_DISTORTION_WGSL,
8
+ createBarrelDistortionRenderFeature,
9
+ } from '../features/barrel-distortion';
10
+ import { createRenderFeatureHost, runRenderFeatureFrame } from '../features/host';
11
+ import { resolveStandardRenderFeatureTargets } from '../features/targets';
12
+ import {
13
+ attachBarrelDistortionCameraFrame,
14
+ createBarrelDistortionMapping,
15
+ mapDisplayToScene,
16
+ mapDisplayUvToSceneUv,
17
+ mapSceneToDisplay,
18
+ mapSceneUvToDisplayUv,
19
+ validateBarrelDistortionParameters,
20
+ } from '../index';
21
+ import { projectBarrelDistortionInspection } from '../inspection-types';
22
+ import { extractCameraSnapshots } from '../render-system-extract';
23
+ import { setActiveCamera } from '../systems/active-camera';
24
+
25
+ function spawnBarrelTestCamera(world: World, strength?: number): number {
26
+ const transform = {
27
+ component: Transform,
28
+ data: { pos: [0, 0, 0], quat: [0, 0, 0, 1], scale: [1, 1, 1] },
29
+ } as const;
30
+ const camera = {
31
+ component: Camera,
32
+ data: {
33
+ fov: Math.PI / 3,
34
+ aspect: 16 / 9,
35
+ near: 0.1,
36
+ far: 100,
37
+ projection: 0,
38
+ left: -1,
39
+ right: 1,
40
+ bottom: -1,
41
+ top: 1,
42
+ },
43
+ } as const;
44
+ if (strength === undefined) {
45
+ return world.spawn(transform, camera).unwrap() as unknown as number;
46
+ }
47
+ return world
48
+ .spawn(transform, camera, {
49
+ component: BarrelDistortion,
50
+ data: { strength },
51
+ })
52
+ .unwrap() as unknown as number;
53
+ }
54
+
55
+ describe('bounded barrel distortion mapping', () => {
56
+ it('extracts work from the selected camera only', () => {
57
+ const world = new World();
58
+ const disabled = spawnBarrelTestCamera(world);
59
+ const enabled = spawnBarrelTestCamera(world, 0.2);
60
+ const zero = spawnBarrelTestCamera(world, 0);
61
+ const host = createRenderFeatureHost([createBarrelDistortionRenderFeature()]).unwrap();
62
+ const targets = resolveStandardRenderFeatureTargets({
63
+ tonemap: 'none',
64
+ antialias: 'none',
65
+ storageBuffer: false,
66
+ multisample: false,
67
+ colorAttachmentFormat: 'rgba8unorm',
68
+ });
69
+
70
+ const run = (frameNumber: number, entity: number) => {
71
+ setActiveCamera(world, entity);
72
+ const selected = extractCameraSnapshots(world)[0];
73
+ expect(selected?.entityKey).toBe(entity);
74
+ return runRenderFeatureFrame(host, {
75
+ worlds: [world],
76
+ owner: 0,
77
+ frameNumber,
78
+ generation: frameNumber,
79
+ ...(selected === undefined ? {} : { selectedCamera: selected }),
80
+ targets,
81
+ caps: { rgba16floatRenderable: true } as never,
82
+ });
83
+ };
84
+
85
+ try {
86
+ // A non-active enabled camera must not activate the display feature.
87
+ expect(run(1, disabled).plans[0]?.plan).toEqual({ resources: [], passes: [] });
88
+ // An explicitly selected zero-strength companion is also zero work.
89
+ expect(run(2, zero).plans[0]?.plan).toEqual({ resources: [], passes: [] });
90
+
91
+ const active = run(3, enabled);
92
+ expect(active.errors).toEqual([]);
93
+ expect(active.plans[0]?.plan.resources).toHaveLength(3);
94
+ expect(active.plans[0]?.plan.passes).toHaveLength(1);
95
+ expect(active.fullscreenEffects.has(BARREL_DISTORTION_POST_PROCESS_ID)).toBe(true);
96
+
97
+ const backToDisabled = run(4, disabled);
98
+ expect(backToDisabled.plans[0]?.plan).toEqual({ resources: [], passes: [] });
99
+ expect(backToDisabled.fullscreenEffects.size).toBe(0);
100
+ } finally {
101
+ host.dispose();
102
+ }
103
+ });
104
+
105
+ it('keeps an unconfigured camera at feature zero work', () => {
106
+ const host = createRenderFeatureHost([createBarrelDistortionRenderFeature()]).unwrap();
107
+ const frame = runRenderFeatureFrame(host, {
108
+ worlds: [new World()],
109
+ owner: 0,
110
+ frameNumber: 1,
111
+ caps: { rgba16floatRenderable: true } as never,
112
+ });
113
+
114
+ expect(frame.errors).toEqual([]);
115
+ expect(frame.fullscreenEffects.size).toBe(0);
116
+ expect(frame.preparedResourceBatches).toHaveLength(0);
117
+ expect(frame.requiresPreparedResourceKey).toBe(false);
118
+ expect(frame.plans[0]?.plan).toEqual({ resources: [], passes: [] });
119
+ host.dispose();
120
+ });
121
+
122
+ it('validates the production fullscreen entry point and keeps disabled plans exact-zero', () => {
123
+ expect(BARREL_DISTORTION_WGSL).toContain('@builtin(vertex_index) vertex_index : u32');
124
+ const feature = createBarrelDistortionRenderFeature();
125
+ expect(feature.identity).toBe(BARREL_DISTORTION_POST_PROCESS_ID);
126
+ expect(feature.requiredFullscreenPostProcesses).toEqual([
127
+ { identity: BARREL_DISTORTION_POST_PROCESS_ID, source: BARREL_DISTORTION_WGSL },
128
+ ]);
129
+ const disabled = feature.plan({ active: false }, {} as never);
130
+ expect(disabled.ok).toBe(true);
131
+ if (!disabled.ok) return;
132
+ expect(disabled.value.resources).toEqual([]);
133
+ expect(disabled.value.passes).toEqual([]);
134
+
135
+ const enabled = feature.plan({ active: true }, {} as never);
136
+ expect(enabled.ok).toBe(true);
137
+ if (!enabled.ok) return;
138
+ expect(enabled.value.resources).toHaveLength(3);
139
+ expect(enabled.value.resources[0]).toMatchObject({
140
+ kind: 'fullscreen-program',
141
+ name: BARREL_DISTORTION_POST_PROCESS_ID,
142
+ params: { byteSize: 16 },
143
+ });
144
+ expect(enabled.value.resources).toEqual(
145
+ expect.arrayContaining([
146
+ expect.objectContaining({
147
+ kind: 'graphics-program',
148
+ name: 'barrel-distortion-pipeline',
149
+ }),
150
+ expect.objectContaining({
151
+ kind: 'graphics-bindings',
152
+ name: 'barrel-distortion-bindings',
153
+ logicalTargets: { input: 'barrel-input' },
154
+ }),
155
+ ]),
156
+ );
157
+ expect(enabled.value.passes).toMatchObject([
158
+ {
159
+ kind: 'raster',
160
+ name: 'barrel-distortion',
161
+ colorAttachments: [{ target: 'barrel-output' }],
162
+ sampledTargets: ['barrel-input'],
163
+ },
164
+ ]);
165
+ });
166
+
167
+ it('keeps the disabled path exact and validates the authoring domain', () => {
168
+ const identity = createBarrelDistortionMapping(1920, 1080, undefined);
169
+ expect(identity.ok).toBe(true);
170
+ if (!identity.ok) return;
171
+ const mapped = { x: 0, y: 0 };
172
+ expect(mapDisplayToScene(mapped, identity.value, 321.5, 777.25)).toBe(true);
173
+ expect(mapped).toEqual({ x: 321.5, y: 777.25 });
174
+
175
+ expect(validateBarrelDistortionParameters({ strength: 0.351 }).ok).toBe(false);
176
+ expect(validateBarrelDistortionParameters({ centerX: Number.NaN }).ok).toBe(false);
177
+ expect(validateBarrelDistortionParameters({ centerY: 1.1 }).ok).toBe(false);
178
+ expect(createBarrelDistortionMapping(0, 1080, { strength: 0.2 }).ok).toBe(false);
179
+ });
180
+
181
+ it.each([
182
+ [1920, 1080, 0.5, 0.5, 0.2],
183
+ [1080, 1920, 0.2, 0.75, 0.35],
184
+ [2560, 1080, 0.8, 0.3, 0.1],
185
+ ])('round trips UVs for %s x %s with center %s,%s', (width, height, centerX, centerY, strength) => {
186
+ const mapping = createBarrelDistortionMapping(width, height, {
187
+ centerX,
188
+ centerY,
189
+ strength,
190
+ });
191
+ expect(mapping.ok).toBe(true);
192
+ if (!mapping.ok) return;
193
+ for (const uv of [
194
+ [0.01, 0.01],
195
+ [0.2, 0.7],
196
+ [0.5, 0.5],
197
+ [0.93, 0.12],
198
+ [0.99, 0.99],
199
+ ] as const) {
200
+ const display = { x: 0, y: 0 };
201
+ const scene = { x: 0, y: 0 };
202
+ expect(mapDisplayUvToSceneUv(scene, mapping.value, uv[0], uv[1])).toBe(true);
203
+ expect(mapSceneUvToDisplayUv(display, mapping.value, scene.x, scene.y)).toBe(true);
204
+ expect(display.x).toBeCloseTo(uv[0], 6);
205
+ expect(display.y).toBeCloseTo(uv[1], 6);
206
+ expect(scene.x).toBeGreaterThanOrEqual(0);
207
+ expect(scene.x).toBeLessThanOrEqual(1);
208
+ expect(scene.y).toBeGreaterThanOrEqual(0);
209
+ expect(scene.y).toBeLessThanOrEqual(1);
210
+ }
211
+ });
212
+
213
+ it('normalizes machine-precision boundaries without accepting real crop misses', () => {
214
+ const boundary = createBarrelDistortionMapping(1920, 1080, {
215
+ strength: 0.35,
216
+ centerX: 0,
217
+ centerY: 1,
218
+ }).unwrap();
219
+ const scene = { x: 0, y: 0 };
220
+ const display = { x: 0, y: 0 };
221
+ expect(mapDisplayUvToSceneUv(scene, boundary, 0, 1)).toBe(true);
222
+ expect(mapSceneUvToDisplayUv(display, boundary, scene.x, scene.y)).toBe(true);
223
+ expect(display).toEqual({ x: 0, y: 1 });
224
+
225
+ const centered = createBarrelDistortionMapping(1920, 1080, { strength: 0.35 }).unwrap();
226
+ // This source corner is genuinely outside the auto-cropped display; it
227
+ // must remain a miss instead of being clamped by the numerical repair.
228
+ expect(mapSceneUvToDisplayUv(display, centered, 0, 0.01)).toBe(false);
229
+ });
230
+
231
+ it('keeps the 1920x1080 offset-corner counterexample inside the inverse domain', () => {
232
+ const mapping = createBarrelDistortionMapping(1920, 1080, {
233
+ strength: 0.35,
234
+ centerX: 0,
235
+ centerY: 0,
236
+ }).unwrap();
237
+ const scene = { x: 0, y: 0 };
238
+ const display = { x: 0, y: 0 };
239
+ // The forward equation produces exactly 1.0000000000000002 for the
240
+ // inverse y at this valid displayed corner in binary64. It is a numerical
241
+ // boundary roundoff, not a crop miss, and must normalize to the edge.
242
+ expect(mapDisplayUvToSceneUv(scene, mapping, 0, 1)).toBe(true);
243
+ expect(mapSceneUvToDisplayUv(display, mapping, scene.x, scene.y)).toBe(true);
244
+ expect(display).toEqual({ x: 0, y: 1 });
245
+ });
246
+
247
+ it('keeps a seeded boundary sweep below the pixel error budget', () => {
248
+ const extents = [
249
+ [640, 360],
250
+ [1280, 720],
251
+ [1920, 1080],
252
+ ] as const;
253
+ const centers = Array.from({ length: 25 }, (_, index) => index / 24);
254
+ const strengths = [0.05, 0.2, 0.35];
255
+ let seed = 0x9e3779b9;
256
+ const next = () => {
257
+ seed = (Math.imul(seed, 1664525) + 1013904223) >>> 0;
258
+ return seed / 0x1_0000_0000;
259
+ };
260
+ const positions: [number, number][] = [
261
+ [0, 0],
262
+ [0, 1],
263
+ [1, 0],
264
+ [1, 1],
265
+ ...Array.from({ length: 21 }, () => [next(), next()] as [number, number]),
266
+ ];
267
+ let rejected = 0;
268
+ let maxPixelError = 0;
269
+ for (const [width, height] of extents) {
270
+ for (const centerX of centers) {
271
+ for (const centerY of centers) {
272
+ for (const strength of strengths) {
273
+ const mapping = createBarrelDistortionMapping(width, height, {
274
+ strength,
275
+ centerX,
276
+ centerY,
277
+ }).unwrap();
278
+ for (const [ux, uy] of positions) {
279
+ const scene = { x: 0, y: 0 };
280
+ const display = { x: 0, y: 0 };
281
+ if (!mapDisplayUvToSceneUv(scene, mapping, ux, uy)) {
282
+ rejected += 1;
283
+ continue;
284
+ }
285
+ if (!mapSceneUvToDisplayUv(display, mapping, scene.x, scene.y)) {
286
+ rejected += 1;
287
+ continue;
288
+ }
289
+ maxPixelError = Math.max(
290
+ maxPixelError,
291
+ Math.abs(display.x - ux) * width,
292
+ Math.abs(display.y - uy) * height,
293
+ );
294
+ }
295
+ }
296
+ }
297
+ }
298
+ }
299
+ expect(rejected).toBe(0);
300
+ expect(maxPixelError).toBeLessThanOrEqual(0.00001);
301
+ });
302
+
303
+ it('samples inward from the displayed edge under the documented direction', () => {
304
+ const mapping = createBarrelDistortionMapping(1920, 1080, { strength: 0.2 }).unwrap();
305
+ const scene = { x: 0, y: 0 };
306
+ expect(mapDisplayUvToSceneUv(scene, mapping, 0.75, 0.5)).toBe(true);
307
+ expect(scene.x).toBeCloseTo(0.707896, 5);
308
+ expect(scene.y).toBeCloseTo(0.5, 8);
309
+ });
310
+
311
+ it('rejects display and inverse points outside the physical output viewport', () => {
312
+ const mapping = createBarrelDistortionMapping(800, 600, { strength: 0.2 }).unwrap();
313
+ const out = { x: 0, y: 0 };
314
+ expect(mapDisplayToScene(out, mapping, -0.01, 10)).toBe(false);
315
+ expect(mapDisplayToScene(out, mapping, 801, 10)).toBe(false);
316
+ expect(mapSceneToDisplay(out, mapping, 10, 601)).toBe(false);
317
+ });
318
+
319
+ it('pairs the submitted mapping with immutable unjittered camera facts', () => {
320
+ const mapping = createBarrelDistortionMapping(640, 360, { strength: 0.2 }).unwrap();
321
+ const frame = attachBarrelDistortionCameraFrame(mapping, {
322
+ projection: 'perspective',
323
+ far: 100,
324
+ viewMatrix: new Float32Array(16).fill(1),
325
+ projectionMatrix: new Float32Array(16).fill(2),
326
+ });
327
+ expect(frame.camera).toMatchObject({ projection: 'perspective' });
328
+ expect(frame.camera?.viewMatrix).toEqual(Array(16).fill(1));
329
+ expect(frame.camera?.projectionMatrix).toEqual(Array(16).fill(2));
330
+ expect(Object.isFrozen(frame)).toBe(true);
331
+ expect(Object.isFrozen(frame.camera)).toBe(true);
332
+ });
333
+
334
+ it('projects the accepted mapping and LKG identity as one detached inspection', () => {
335
+ const mapping = attachBarrelDistortionCameraFrame(
336
+ createBarrelDistortionMapping(640, 360, { strength: 0.2 }).unwrap(),
337
+ {
338
+ projection: 'perspective',
339
+ far: 100,
340
+ viewMatrix: new Float32Array(16),
341
+ projectionMatrix: new Float32Array(16),
342
+ },
343
+ );
344
+ const inspection = projectBarrelDistortionInspection({
345
+ effectiveMapping: mapping,
346
+ frameId: 12,
347
+ deviceGeneration: 3,
348
+ graphGeneration: 7,
349
+ lastKnownGood: true,
350
+ });
351
+ expect(inspection).toMatchObject({
352
+ frameId: 12,
353
+ deviceGeneration: 3,
354
+ graphGeneration: 7,
355
+ lastKnownGood: true,
356
+ extent: { width: 640, height: 360 },
357
+ });
358
+ expect(inspection.effectiveMapping?.strength).toBe(0.2);
359
+ expect(Object.isFrozen(inspection)).toBe(true);
360
+ expect(Object.isFrozen(inspection.extent)).toBe(true);
361
+ expect(Object.isFrozen(inspection.effectiveMapping)).toBe(true);
362
+ expect(Object.isFrozen(inspection.effectiveMapping?.camera)).toBe(true);
363
+ });
364
+ });
@@ -2,8 +2,11 @@ import * as render from '@forgeax/engine-render';
2
2
  import {
3
3
  type EngineEnvironmentError,
4
4
  constructRendererHost as internalConstructRendererHost,
5
+ loadRhiPack as loadRenderRhiPack,
5
6
  type RendererHostAssembly,
7
+ type RhiBackendPack,
6
8
  } from '@forgeax/engine-render/internal/construct-renderer';
9
+ import { rhi } from '@forgeax/engine-rhi-null';
7
10
  import { describe, expect, it } from 'vitest';
8
11
  import type { RenderError } from '../errors/render';
9
12
  import type { RenderResult } from '../render-contract';
@@ -21,4 +24,50 @@ describe('construct-renderer internal entry', () => {
21
24
  >;
22
25
  expect(null as unknown as Return satisfies Expected).toBeNull();
23
26
  });
27
+
28
+ it('projects backend fields with Render explicit-only instrumentation policy', () => {
29
+ const createShaderModule = async (..._args: unknown[]) => ({
30
+ ok: true as const,
31
+ value: {} as never,
32
+ });
33
+ const createShaderModuleImmediate = (..._args: unknown[]) => ({
34
+ ok: true as const,
35
+ value: {} as never,
36
+ });
37
+ const translateErrorEventToRhiError = (_event: unknown) => ({
38
+ ok: false as const,
39
+ error: {} as never,
40
+ });
41
+ const rawDeviceAccessor = (_device: unknown) => undefined;
42
+ const moduleInstrumentation: RhiBackendPack['instrumentation'] = {
43
+ onFrameBoundary: () => undefined,
44
+ };
45
+ const explicitInstrumentation: RhiBackendPack['instrumentation'] = {
46
+ onDeviceLost: () => undefined,
47
+ };
48
+ const module = {
49
+ rhi,
50
+ createShaderModule,
51
+ createShaderModuleImmediate,
52
+ translateErrorEventToRhiError,
53
+ _internal_getRawDevice: rawDeviceAccessor,
54
+ instrumentation: moduleInstrumentation,
55
+ };
56
+
57
+ const implicitPack = loadRenderRhiPack(module);
58
+ const explicitPack = loadRenderRhiPack(module, explicitInstrumentation);
59
+ expect(Object.hasOwn(implicitPack, 'instrumentation')).toBe(false);
60
+ expect(explicitPack.instrumentation).toBe(explicitInstrumentation);
61
+ expect(explicitPack.rhi).toBe(rhi);
62
+ expect(explicitPack.createShaderModule).toBe(createShaderModule);
63
+ expect(explicitPack.createShaderModuleImmediate).toBe(createShaderModuleImmediate);
64
+ expect(explicitPack.translateErrorEventToRhiError).toBe(translateErrorEventToRhiError);
65
+ expect(explicitPack._internal_getRawDevice).toBe(rawDeviceAccessor);
66
+
67
+ const absentPack = loadRenderRhiPack({ rhi });
68
+ expect(Object.hasOwn(absentPack, 'createShaderModule')).toBe(false);
69
+ expect(Object.hasOwn(absentPack, 'createShaderModuleImmediate')).toBe(false);
70
+ expect(Object.hasOwn(absentPack, 'translateErrorEventToRhiError')).toBe(false);
71
+ expect(Object.hasOwn(absentPack, '_internal_getRawDevice')).toBe(false);
72
+ });
24
73
  });
@@ -0,0 +1,277 @@
1
+ import type { RhiCommandEncoder, RhiDevice } from '@forgeax/engine-rhi';
2
+ import { rhi } from '@forgeax/engine-rhi-null';
3
+ import { ok } from '@forgeax/engine-types';
4
+ import { describe, expect, it, vi } from 'vitest';
5
+ import { RhiErrorListenerRegistry } from '../lifecycle';
6
+ import type { RenderFrameState } from '../record/frame-snapshot';
7
+ import { makeZeroCameraFallbackSnapshot } from '../record/frame-snapshot';
8
+ import type { PipelineState, RenderSystemInternals } from '../record/render-context';
9
+ import {
10
+ ensureCompiledFrameGraph,
11
+ executeCompiledFrameGraph,
12
+ inspectRenderGraphGenerationAllocation,
13
+ retire as retireCompiledGraph,
14
+ settleBarrelDistortionGraphCandidate,
15
+ shareRenderGraphGenerationAllocationOwner,
16
+ } from '../record/typed-frame-graph';
17
+ import type { RenderPipeline } from '../render-pipeline';
18
+ import type { ExtractedLights } from '../render-system-extract';
19
+
20
+ function createRecoveryGraphHarness() {
21
+ let failBuild = false;
22
+ const activePipeline: RenderPipeline = {
23
+ build: ({ graph }) => {
24
+ if (failBuild) return { ok: false, error: { code: 'injected-build-failure' } } as never;
25
+ const buffer = graph.createBuffer('recovery-graph-bytes', { size: 16 });
26
+ if (!buffer.ok) return buffer;
27
+ return graph.addCopyPass('recovery-graph-seed', {
28
+ accesses: [{ resource: buffer.value, usage: 'copy-dst' }],
29
+ encode: () => undefined,
30
+ });
31
+ },
32
+ };
33
+ const frameState = {
34
+ compiledFrameGraph: null,
35
+ compiledFrameGraphTopologyKey: null,
36
+ retiredCompiledFrameGraphs: new Set(),
37
+ activePipeline,
38
+ installedPipelineConfig: undefined,
39
+ } as unknown as RenderFrameState;
40
+ const errorRegistry = new RhiErrorListenerRegistry();
41
+ let device: RhiDevice | undefined;
42
+ const internals = {
43
+ get device() {
44
+ return device as RhiDevice;
45
+ },
46
+ errorRegistry,
47
+ } as unknown as RenderSystemInternals;
48
+ const pipelineState = {
49
+ format: 'rgba8unorm',
50
+ colorAttachmentFormat: 'rgba8unorm',
51
+ } as unknown as PipelineState;
52
+ const lights = {
53
+ cascadeCount: undefined,
54
+ pointShadow: [],
55
+ spot: [],
56
+ } as unknown as ExtractedLights;
57
+ return {
58
+ activePipeline,
59
+ frameState,
60
+ internals,
61
+ pipelineState,
62
+ lights,
63
+ setDevice(nextDevice: RhiDevice) {
64
+ device = nextDevice;
65
+ },
66
+ compile() {
67
+ return ensureCompiledFrameGraph(
68
+ internals,
69
+ frameState,
70
+ pipelineState,
71
+ makeZeroCameraFallbackSnapshot(),
72
+ lights,
73
+ 1,
74
+ 1,
75
+ undefined,
76
+ );
77
+ },
78
+ accept() {
79
+ settleBarrelDistortionGraphCandidate(frameState, true);
80
+ },
81
+ setFail(value: boolean) {
82
+ failBuild = value;
83
+ },
84
+ };
85
+ }
86
+
87
+ describe('detached recovery graph execution boundary', () => {
88
+ it('keeps topology inspection while rejecting detached LKG and then retires it on replacement', async () => {
89
+ const device = (await (await rhi.requestAdapter()).unwrap().requestDevice()).unwrap();
90
+ const harness = createRecoveryGraphHarness();
91
+ harness.setDevice(device);
92
+
93
+ const candidate = harness.compile();
94
+ expect(candidate).not.toBeNull();
95
+ if (candidate === null) return;
96
+ harness.frameState.compiledFrameGraphTopologyKey = null;
97
+ harness.frameState.perFrameGraph = null;
98
+ harness.setFail(true);
99
+ expect(harness.compile()).toBeNull();
100
+ expect(harness.frameState.compiledFrameGraph).toBe(candidate);
101
+
102
+ harness.setFail(false);
103
+ const replacement = harness.compile();
104
+ expect(replacement).not.toBeNull();
105
+ expect(replacement).not.toBe(candidate);
106
+ expect(harness.frameState.compiledFrameGraph).toBe(replacement);
107
+ expect(harness.frameState.compiledFrameGraphTopologyKey).not.toBeNull();
108
+ expect(harness.frameState.retiredCompiledFrameGraphs.has(candidate)).toBe(false);
109
+ harness.accept();
110
+ expect(harness.frameState.retiredCompiledFrameGraphs.has(candidate)).toBe(true);
111
+
112
+ harness.setFail(true);
113
+ expect(harness.compile()).toBe(replacement);
114
+ });
115
+
116
+ it('does not execute or submit a detached graph', () => {
117
+ const execute = vi.fn(() => ok(undefined));
118
+ const submit = vi.fn(() => ok(undefined));
119
+ const frameState = {
120
+ compiledFrameGraph: { execute },
121
+ compiledFrameGraphTopologyKey: null,
122
+ retiredCompiledFrameGraphs: new Set(),
123
+ } as unknown as RenderFrameState;
124
+ const internals = {
125
+ errorRegistry: { fire: vi.fn() },
126
+ device: { queue: { submit } },
127
+ } as never;
128
+
129
+ const submitted = executeCompiledFrameGraph(
130
+ internals,
131
+ frameState,
132
+ {} as never,
133
+ {} as RhiCommandEncoder,
134
+ );
135
+
136
+ expect(submitted).toBe(false);
137
+ expect(execute).not.toHaveBeenCalled();
138
+ expect(submit).not.toHaveBeenCalled();
139
+ });
140
+
141
+ it('projects active candidate overlap and graph-generation peak bytes', async () => {
142
+ const device = (await (await rhi.requestAdapter()).unwrap().requestDevice()).unwrap();
143
+ const harness = createRecoveryGraphHarness();
144
+ harness.setDevice(device);
145
+
146
+ const first = harness.compile();
147
+ expect(first).not.toBeNull();
148
+ if (first === null) return;
149
+ harness.frameState.compiledFrameGraphTopologyKey = null;
150
+ const replacement = harness.compile();
151
+ expect(replacement).not.toBeNull();
152
+ if (replacement === null) return;
153
+ // The old graph remains live until the replacement submission is accepted.
154
+ expect(inspectRenderGraphGenerationAllocation(harness.frameState).liveBytes).toBe(32);
155
+ harness.accept();
156
+
157
+ const allocation = inspectRenderGraphGenerationAllocation(harness.frameState);
158
+ expect(allocation.availability).toBe('complete');
159
+ expect(allocation.entries).toHaveLength(2);
160
+ expect(
161
+ allocation.entries.find((entry) => entry.generation === replacement.inspect().generation)
162
+ ?.roles,
163
+ ).toEqual(expect.arrayContaining(['active']));
164
+ expect(
165
+ allocation.entries.find((entry) => entry.generation === first.inspect().generation)?.roles,
166
+ ).toEqual(expect.arrayContaining(['retiring']));
167
+ expect(allocation.liveBytes).toBe(16);
168
+ expect(allocation.pendingRetirementBytes).toBe(16);
169
+ expect(allocation.peakBytes).toBe(32);
170
+ });
171
+
172
+ it('records graph-generation overlap without an allocation inspection sample', async () => {
173
+ const device = (await (await rhi.requestAdapter()).unwrap().requestDevice()).unwrap();
174
+ const harness = createRecoveryGraphHarness();
175
+ harness.setDevice(device);
176
+
177
+ const first = harness.compile();
178
+ expect(first).not.toBeNull();
179
+ if (first === null) return;
180
+
181
+ // Force two ordinary graph replacements. Do not call the allocation
182
+ // inspection between them: the renderer owner must observe compile and
183
+ // retirement events itself rather than treating inspect() as a sampler.
184
+ harness.frameState.compiledFrameGraphTopologyKey = null;
185
+ const second = harness.compile();
186
+ expect(second).not.toBeNull();
187
+ if (second === null) return;
188
+ harness.accept();
189
+ await new Promise<void>((resolve) => setTimeout(resolve, 0));
190
+ harness.frameState.compiledFrameGraphTopologyKey = null;
191
+ const third = harness.compile();
192
+ expect(third).not.toBeNull();
193
+ if (third === null) return;
194
+ harness.accept();
195
+
196
+ // RHI-null retires the old generations on its queue fence. The first
197
+ // inspection therefore sees one live graph, while peakBytes retains the
198
+ // live + retiring overlap observed during replacement.
199
+ await new Promise<void>((resolve) => setTimeout(resolve, 0));
200
+ const allocation = inspectRenderGraphGenerationAllocation(harness.frameState);
201
+ expect(allocation.liveBytes).toBe(16);
202
+ expect(allocation.pendingRetirementBytes).toBe(0);
203
+ expect(allocation.peakBytes).toBe(32);
204
+ expect(allocation.entries).toHaveLength(1);
205
+ expect(allocation.entries[0]?.generation).toBe(third.inspect().generation);
206
+ });
207
+
208
+ it('keeps a failed graph retirement visible in the renderer generation ledger', async () => {
209
+ const device = (await (await rhi.requestAdapter()).unwrap().requestDevice()).unwrap();
210
+ const harness = createRecoveryGraphHarness();
211
+ harness.setDevice(device);
212
+
213
+ const first = harness.compile();
214
+ expect(first).not.toBeNull();
215
+ if (first === null) return;
216
+ (device as unknown as { destroyBuffer: RhiDevice['destroyBuffer'] }).destroyBuffer = () =>
217
+ ({ ok: false, error: { code: 'webgpu-runtime-error' } }) as never;
218
+ harness.frameState.compiledFrameGraphTopologyKey = null;
219
+ const replacement = harness.compile();
220
+ expect(replacement).not.toBeNull();
221
+ if (replacement === null) return;
222
+ harness.accept();
223
+ await new Promise<void>((resolve) => setTimeout(resolve, 0));
224
+
225
+ const allocation = inspectRenderGraphGenerationAllocation(harness.frameState);
226
+ const failed = allocation.entries.find(
227
+ (entry) => entry.generation === first.inspect().generation,
228
+ );
229
+ expect(failed?.retirement).toBe('failed');
230
+ expect(failed?.allocation.pendingRetirementBytes).toBe(16);
231
+ expect(allocation.failedRetirementCount).toBe(1);
232
+ expect(allocation.failedRetirementBytes).toBe(16);
233
+ });
234
+
235
+ it('keeps a detached recovery candidate failure in the shared owner', async () => {
236
+ const device = (await (await rhi.requestAdapter()).unwrap().requestDevice()).unwrap();
237
+ const harness = createRecoveryGraphHarness();
238
+ harness.setDevice(device);
239
+ const active = harness.compile();
240
+ expect(active).not.toBeNull();
241
+ if (active === null) return;
242
+
243
+ const candidateFrameState = {
244
+ ...harness.frameState,
245
+ compiledFrameGraph: null,
246
+ compiledFrameGraphTopologyKey: null,
247
+ retiredCompiledFrameGraphs: new Set(),
248
+ } as unknown as RenderFrameState;
249
+ shareRenderGraphGenerationAllocationOwner(harness.frameState, candidateFrameState);
250
+ const candidate = ensureCompiledFrameGraph(
251
+ harness.internals,
252
+ candidateFrameState,
253
+ harness.pipelineState,
254
+ makeZeroCameraFallbackSnapshot(),
255
+ harness.lights,
256
+ 1,
257
+ 1,
258
+ undefined,
259
+ );
260
+ expect(candidate).not.toBeNull();
261
+ if (candidate === null) return;
262
+
263
+ (device as unknown as { destroyBuffer: RhiDevice['destroyBuffer'] }).destroyBuffer = () =>
264
+ ({ ok: false, error: { code: 'webgpu-runtime-error' } }) as never;
265
+ retireCompiledGraph(candidateFrameState, candidate);
266
+ await new Promise<void>((resolve) => setTimeout(resolve, 0));
267
+
268
+ const allocation = inspectRenderGraphGenerationAllocation(harness.frameState);
269
+ const failed = allocation.entries.find(
270
+ (entry) => entry.generation === candidate.inspect().generation,
271
+ );
272
+ expect(failed?.retirement).toBe('failed');
273
+ expect(failed?.roles).toEqual(expect.arrayContaining(['retiring']));
274
+ expect(allocation.failedRetirementCount).toBe(1);
275
+ expect(allocation.failedRetirementBytes).toBe(16);
276
+ });
277
+ });