@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
@@ -17,7 +17,8 @@ import type {
17
17
  PreparedGraphicsResolver,
18
18
  PreparedGraphicsResourceLease,
19
19
  } from '../prepare/prepared-graphics-resolver';
20
- import type { SceneDataTarget } from '../temporal/scene-data';
20
+ import type { CameraSnapshot } from '../render-contract';
21
+ import { isSceneDataTarget, type SceneDataTarget } from '../temporal/scene-data';
21
22
  import { createSceneDataCatalog, type SceneDataCatalog } from '../temporal/scene-data-catalog';
22
23
  import {
23
24
  cloneRenderFeaturePlanSignatureSnapshot,
@@ -114,6 +115,7 @@ export interface RenderFeaturePlanExecutionPass {
114
115
  export interface RenderFeaturePlanExecution {
115
116
  readonly featureIdentity: string;
116
117
  readonly order: number;
118
+ readonly placement?: import('./types').RenderFeaturePlacement;
117
119
  readonly passes: readonly RenderFeaturePlanExecutionPass[];
118
120
  }
119
121
 
@@ -134,6 +136,10 @@ export interface RenderFeatureFrameInput {
134
136
  readonly worlds: readonly import('@forgeax/engine-ecs').World[];
135
137
  readonly owner: number;
136
138
  readonly frameNumber: number;
139
+ /** Renderer-selected display camera forwarded to feature extraction. */
140
+ readonly selectedCamera?: CameraSnapshot;
141
+ /** Physical render extent for bounded feature dispatch sizing. */
142
+ readonly frameSize?: { readonly width: number; readonly height: number };
137
143
  readonly visibilitySnapshots?: readonly RenderFeatureWorldVisibilitySnapshot[];
138
144
  readonly hiddenEntityReports?: readonly RenderFeatureHiddenEntityReport[];
139
145
  /** Active-pipeline logical targets available to producer-owned features. */
@@ -142,6 +148,8 @@ export interface RenderFeatureFrameInput {
142
148
  readonly sceneData?: SceneDataCatalog;
143
149
  readonly generation?: number;
144
150
  readonly caps: Readonly<RhiCaps>;
151
+ /** Normal frame-owner facts shared with producer extraction. */
152
+ readonly frame?: import('./types').RenderFeatureFrameContext;
145
153
  /** Renderer-owned material binding contract projection for producer plans. */
146
154
  readonly materialShaderBindingContract?: (
147
155
  materialShaderId: string,
@@ -173,6 +181,8 @@ export interface RenderFeatureFrameResult {
173
181
  readonly errors: readonly RenderError[];
174
182
  readonly plans: readonly RenderFeaturePlannedFrame[];
175
183
  readonly fullscreenEffects: ReadonlyMap<string, PostProcessShaderEntry>;
184
+ /** Public Standard post-effect identities; graph-local fullscreen resources are not included. */
185
+ readonly postProcessIdentities: readonly string[];
176
186
  readonly preparedResourceBatches: readonly RenderFeaturePreparedResourceBatch[];
177
187
  /**
178
188
  * True only when the current graph resolves a transient graphics lease.
@@ -221,6 +231,8 @@ export interface RenderFeatureHost {
221
231
  recover(input: RenderFeatureRecoverInput): Result<void, RenderError>;
222
232
  createRecoveryRoot(scope: DeviceScope): LifecycleResourceSpec<unknown>;
223
233
  diagnostics(): readonly RenderFeatureDiagnostics[];
234
+ /** Retain an explicitly producer-published detached inspection value. */
235
+ setInspection(identity: string, inspection: unknown): void;
224
236
  /** Subscribe to lifecycle projection changes; unchanged active frames are silent. */
225
237
  subscribeDiagnostics(listener: () => void): () => void;
226
238
  /** Detached signature and prepared-resource accounting for diagnostics. */
@@ -267,6 +279,7 @@ interface FeatureSlot {
267
279
  readonly preparedStore: PreparedGraphicsStore;
268
280
  status: RenderFeatureStatus;
269
281
  latestError: RenderFeatureErrorDescriptor | undefined;
282
+ latestInspection?: unknown;
270
283
  }
271
284
 
272
285
  function freezeError(error: RenderFeatureErrorDescriptor): RenderFeatureErrorDescriptor {
@@ -289,6 +302,7 @@ function freezeDiagnostics(slot: FeatureSlot): RenderFeatureDiagnostics {
289
302
  order: slot.order,
290
303
  status: slot.status,
291
304
  latestError: slot.latestError === undefined ? undefined : freezeError(slot.latestError),
305
+ ...(slot.latestInspection === undefined ? {} : { inspection: slot.latestInspection }),
292
306
  });
293
307
  }
294
308
 
@@ -318,6 +332,16 @@ function missingCapability(
318
332
  return feature.requiredCapabilities?.find((capability) => caps[capability] !== true);
319
333
  }
320
334
 
335
+ /**
336
+ * Capability declarations gate work that the current plan actually admits.
337
+ * A producer is still allowed to extract and return an empty plan when its
338
+ * authored feature is disabled; that path must remain allocation-free on a
339
+ * backend which cannot support the optional feature.
340
+ */
341
+ function hasActivePlan(plan: RenderFeaturePlan): boolean {
342
+ return plan.resources.length > 0 || plan.passes.length > 0;
343
+ }
344
+
321
345
  function createPreparedGraphicsPrepare(
322
346
  transaction: PreparedGraphicsTransaction,
323
347
  ): RenderFeatureGraphicsPrepare {
@@ -344,6 +368,9 @@ function resolveGraphicsSnapshot(
344
368
  resolver: PreparedGraphicsResolver,
345
369
  descriptor: import('./prepared-graphics').RenderFeatureGraphicsPassDescriptor,
346
370
  generation: number,
371
+ resolveGpuResource?: (
372
+ name: string,
373
+ ) => import('./prepared-gpu-work').RenderFeatureResolvedGpuBuffer | undefined,
347
374
  ): Result<PreparedGraphicsResolvedSnapshot, RenderError> {
348
375
  const resources = new Map<
349
376
  object,
@@ -362,6 +389,7 @@ function resolveGraphicsSnapshot(
362
389
  ...(resolver.resolveGpuBuffer === undefined
363
390
  ? {}
364
391
  : { resolveGpuBuffer: resolver.resolveGpuBuffer }),
392
+ ...(resolveGpuResource === undefined ? {} : { resolveGpuResource }),
365
393
  });
366
394
  }
367
395
 
@@ -412,7 +440,7 @@ function asFeatureError(
412
440
  if (error instanceof Error && typeof (error as Partial<RenderError>).code === 'string') {
413
441
  return error as RenderError;
414
442
  }
415
- return new RenderFeatureStageFailedError(identity, order, stage, 'next-frame');
443
+ return new RenderFeatureStageFailedError(identity, order, stage, 'next-frame', error);
416
444
  }
417
445
 
418
446
  function featureErrorForSlot(slot: FeatureSlot, error: RenderError): RenderError {
@@ -431,6 +459,7 @@ function featureErrorForSlot(slot: FeatureSlot, error: RenderError): RenderError
431
459
  slot.order,
432
460
  'plan',
433
461
  'next-frame',
462
+ error,
434
463
  );
435
464
  default:
436
465
  return new RenderFeatureStageFailedError(
@@ -438,6 +467,7 @@ function featureErrorForSlot(slot: FeatureSlot, error: RenderError): RenderError
438
467
  slot.order,
439
468
  'plan',
440
469
  'next-frame',
470
+ error,
441
471
  );
442
472
  }
443
473
  }
@@ -467,6 +497,7 @@ function errorDescriptor(error: RenderError): RenderFeatureErrorDescriptor {
467
497
  order: -1,
468
498
  stage: 'plan',
469
499
  recovery: 'next-frame',
500
+ cause: error,
470
501
  },
471
502
  };
472
503
  }
@@ -606,14 +637,17 @@ function preparePlanResources(
606
637
  : targetHandle(resource.logicalTarget, targets);
607
638
  if (
608
639
  resource.logicalTarget !== undefined &&
609
- (logical === undefined || !isRenderFeatureTargetHandle(logical))
640
+ (logical === undefined ||
641
+ (typeof logical !== 'string' &&
642
+ !isRenderFeatureTargetHandle(logical) &&
643
+ !isSceneDataTarget(logical)))
610
644
  ) {
611
645
  return err(planFailure(slot));
612
646
  }
613
647
  const prepared = gpu.prepareTextureView(
614
648
  resource.name,
615
- resource.resource.value as TextureView,
616
- logical === undefined || !isRenderFeatureTargetHandle(logical) ? undefined : logical,
649
+ resource.resource.value as TextureView | undefined,
650
+ logical === undefined ? undefined : logical,
617
651
  );
618
652
  if (!prepared.ok) return prepared;
619
653
  preparedGpuResources.set(resource.name, {
@@ -963,6 +997,7 @@ function projectPlanPasses(
963
997
  return ok({
964
998
  featureIdentity: slot.feature.identity,
965
999
  order: slot.order,
1000
+ ...(slot.feature.placement === undefined ? {} : { placement: slot.feature.placement }),
966
1001
  passes: Object.freeze(projected),
967
1002
  });
968
1003
  }
@@ -1322,6 +1357,13 @@ class FeatureHostImpl implements RenderFeatureHost {
1322
1357
  return Object.freeze(this.slots.map(freezeDiagnostics));
1323
1358
  }
1324
1359
 
1360
+ /** Retain only an explicitly producer-published detached inspection value. */
1361
+ setInspection(identity: string, inspection: unknown): void {
1362
+ const slot = findSlot(this.slots, identity);
1363
+ if (slot === undefined || slot.status === 'disposed') return;
1364
+ slot.latestInspection = inspection;
1365
+ }
1366
+
1325
1367
  subscribeDiagnostics(listener: () => void): () => void {
1326
1368
  this.diagnosticsListeners.add(listener);
1327
1369
  return () => this.diagnosticsListeners.delete(listener);
@@ -1352,6 +1394,7 @@ export function runRenderFeatureFrame(
1352
1394
  const errors: RenderError[] = [];
1353
1395
  const plans: RenderFeaturePlannedFrame[] = [];
1354
1396
  const fullscreenEffects = new Map<string, PostProcessShaderEntry>();
1397
+ const postProcessIdentities = new Set<string>();
1355
1398
  const preparedResourceBatches: RenderFeaturePreparedResourceBatch[] = [];
1356
1399
  let requiresPreparedResourceKey = false;
1357
1400
  const hiddenEntityReports: RenderFeatureHiddenEntityReport[] = [
@@ -1362,17 +1405,18 @@ export function runRenderFeatureFrame(
1362
1405
  const diagnostics = host.diagnostics();
1363
1406
  for (const [order, feature] of host.features.entries()) {
1364
1407
  const diagnostic = diagnostics[order];
1365
- if (diagnostic?.status === 'disabled' || diagnostic?.status === 'disposed') continue;
1366
- const missing = missingCapability(feature, input.caps);
1367
- if (missing !== undefined) {
1368
- const capabilityError = new RenderFeatureCapabilityMissingError(
1369
- feature.identity,
1370
- order,
1371
- missing,
1372
- );
1373
- errors.push(capabilityError);
1374
- host.setStatus(feature.identity, 'disabled', errorDescriptor(capabilityError));
1375
- continue;
1408
+ if (diagnostic?.status === 'disposed') continue;
1409
+ // Preserve the existing recovery contract for a capability-disabled
1410
+ // feature once the device becomes capable: an explicit host.recover()
1411
+ // still re-admits it. While the capability is absent, however, rerun the
1412
+ // producer so a camera/world can disable its authored plan and recover to
1413
+ // an empty, zero-resource frame without a second manager.
1414
+ if (diagnostic?.status === 'disabled') {
1415
+ const diagnosticError = diagnostic.latestError;
1416
+ const missing = missingCapability(feature, input.caps);
1417
+ if (diagnosticError?.code !== 'render-feature-capability-missing' || missing === undefined) {
1418
+ continue;
1419
+ }
1376
1420
  }
1377
1421
  const slot: FeatureSlot = {
1378
1422
  feature,
@@ -1383,6 +1427,9 @@ export function runRenderFeatureFrame(
1383
1427
  latestError: undefined,
1384
1428
  };
1385
1429
  const identity = slot.feature.identity;
1430
+ for (const postProcess of feature.requiredFullscreenPostProcesses ?? []) {
1431
+ postProcessIdentities.add(postProcess.identity);
1432
+ }
1386
1433
  const transaction = host.beginPreparedFrame(identity, input.generation ?? 0);
1387
1434
  if (transaction === undefined) continue;
1388
1435
  const graphics = createPreparedGraphicsPrepare(transaction);
@@ -1396,6 +1443,9 @@ export function runRenderFeatureFrame(
1396
1443
  worlds: input.worlds,
1397
1444
  owner: input.owner,
1398
1445
  frameNumber: input.frameNumber,
1446
+ ...(input.selectedCamera === undefined ? {} : { selectedCamera: input.selectedCamera }),
1447
+ caps: input.caps,
1448
+ ...(input.frame === undefined ? {} : { frame: input.frame }),
1399
1449
  reportHiddenEntity: (report) => featureHiddenEntityReports.push(report),
1400
1450
  ...(input.visibilitySnapshots === undefined
1401
1451
  ? {}
@@ -1413,6 +1463,16 @@ export function runRenderFeatureFrame(
1413
1463
  host.setStatus(identity, 'failed', slot.latestError);
1414
1464
  continue;
1415
1465
  }
1466
+ if (
1467
+ typeof extracted.value === 'object' &&
1468
+ extracted.value !== null &&
1469
+ 'inspection' in extracted.value
1470
+ ) {
1471
+ host.setInspection(
1472
+ identity,
1473
+ (extracted.value as { readonly inspection?: unknown }).inspection,
1474
+ );
1475
+ }
1416
1476
 
1417
1477
  const targets = logicalTargets(input.targets ?? []);
1418
1478
  const sceneData =
@@ -1429,7 +1489,10 @@ export function runRenderFeatureFrame(
1429
1489
  () =>
1430
1490
  feature.plan(extracted.value, {
1431
1491
  caps: input.caps,
1432
- frame: { frameNumber: input.frameNumber },
1492
+ frame: {
1493
+ frameNumber: input.frameNumber,
1494
+ ...(input.frameSize === undefined ? {} : input.frameSize),
1495
+ },
1433
1496
  generation: transaction.generation,
1434
1497
  targets,
1435
1498
  sceneData,
@@ -1455,21 +1518,43 @@ export function runRenderFeatureFrame(
1455
1518
  host.setStatus(identity, 'failed', slot.latestError);
1456
1519
  continue;
1457
1520
  }
1521
+ const missing = missingCapability(feature, input.caps);
1522
+ if (missing !== undefined && hasActivePlan(frozen.value)) {
1523
+ const capabilityError = new RenderFeatureCapabilityMissingError(identity, order, missing);
1524
+ errors.push(capabilityError);
1525
+ transaction.abort();
1526
+ host.setStatus(identity, 'disabled', errorDescriptor(capabilityError));
1527
+ continue;
1528
+ }
1458
1529
  const plannedFrame: RenderFeaturePlannedFrame = Object.freeze({
1459
1530
  featureIdentity: identity,
1460
1531
  generation: transaction.generation,
1461
1532
  signature:
1462
1533
  host.recordPlanSignature?.(identity, frozen.value) ??
1463
1534
  renderFeaturePlanSignature(frozen.value),
1535
+ ...(feature.placement === undefined ? {} : { placement: feature.placement }),
1464
1536
  plan: frozen.value,
1465
1537
  });
1466
1538
  for (const resource of frozen.value.resources) {
1467
1539
  if (resource.kind !== 'fullscreen-program') continue;
1468
- fullscreenEffects.set(identity, {
1540
+ const entry = {
1469
1541
  source: resource.source,
1542
+ ...(resource.fragmentEntryPoint === undefined
1543
+ ? {}
1544
+ : { fragmentEntryPoint: resource.fragmentEntryPoint }),
1470
1545
  ...(resource.reads === undefined ? {} : { reads: resource.reads }),
1546
+ ...(resource.usesView === undefined ? {} : { usesView: resource.usesView }),
1547
+ ...(resource.storageBindings === undefined
1548
+ ? {}
1549
+ : { storageBindings: resource.storageBindings }),
1471
1550
  ...(resource.params === undefined ? {} : { params: resource.params }),
1472
- });
1551
+ } satisfies PostProcessShaderEntry;
1552
+ // A feature may expose more than one graph-local fullscreen producer
1553
+ // (the cloud display composite and its shadow map). Keep each resource
1554
+ // addressable by its declared name and retain the feature identity alias
1555
+ // for the original public post-process route.
1556
+ fullscreenEffects.set(resource.name, entry);
1557
+ if (!fullscreenEffects.has(identity)) fullscreenEffects.set(identity, entry);
1473
1558
  }
1474
1559
  const prepared = preparePlanResources(slot, frozen.value, graphics, gpu, input.targets ?? []);
1475
1560
  if (!prepared.ok) {
@@ -1499,8 +1584,34 @@ export function runRenderFeatureFrame(
1499
1584
  resolver === undefined
1500
1585
  ? undefined
1501
1586
  : (descriptor: import('./prepared-graphics').RenderFeatureGraphicsPassDescriptor) => {
1502
- const resolved = resolveGraphicsSnapshot(resolver, descriptor, transaction.generation);
1503
- if (!resolved.ok) resolver.release();
1587
+ // A pending pass can be skipped while earlier passes remain usable.
1588
+ // Keep their shared resolver leases alive until submission retires them.
1589
+ const resolveGpuResource = (name: string) => {
1590
+ const owned = prepared.value.buffers.get(name);
1591
+ const borrowed = prepared.value.preparedGpuResources.get(name);
1592
+ const reference =
1593
+ owned ?? (borrowed?.kind === 'buffer' ? borrowed.reference : undefined);
1594
+ return reference === undefined ? undefined : gpu?.resolveBuffer(reference);
1595
+ };
1596
+ const resolved = resolveGraphicsSnapshot(
1597
+ resolver,
1598
+ descriptor,
1599
+ transaction.generation,
1600
+ resolveGpuResource,
1601
+ );
1602
+ // A pending pipeline is a retryable partial frame. The resolver
1603
+ // leases cover resources used by earlier ready passes and must
1604
+ // stay retained until the submitted batch retires; only terminal
1605
+ // resolution failures release them here.
1606
+ if (
1607
+ !resolved.ok &&
1608
+ !(
1609
+ resolved.error instanceof RenderFeaturePreparationFailedError &&
1610
+ resolved.error.detail.reason === 'pipeline-pending'
1611
+ )
1612
+ ) {
1613
+ resolver.release();
1614
+ }
1504
1615
  return resolved;
1505
1616
  };
1506
1617
  const releaseResolver = (): void => {
@@ -1529,9 +1640,11 @@ export function runRenderFeatureFrame(
1529
1640
  host.setStatus(identity, 'failed', slot.latestError);
1530
1641
  continue;
1531
1642
  }
1532
- const graphicsLeases = projected.value.passes.flatMap(
1533
- (pass) => pass.resolvedGraphics?.leases ?? [],
1534
- );
1643
+ // Include partial allocations from skipped pending passes, even when no pass
1644
+ // completed. Fatal projection failures release the resolver above instead.
1645
+ const graphicsLeases =
1646
+ resolver?.leases ??
1647
+ projected.value.passes.flatMap((pass) => pass.resolvedGraphics?.leases ?? []);
1535
1648
  if (graphicsLeases.length > 0) requiresPreparedResourceKey = true;
1536
1649
  const leases = [...new Set(graphicsLeases), ...(gpu?.retireUntouched() ?? [])];
1537
1650
  const retained = host.retainPreparedGraphics(identity, leases);
@@ -1564,6 +1677,7 @@ export function runRenderFeatureFrame(
1564
1677
  errors,
1565
1678
  plans,
1566
1679
  fullscreenEffects,
1680
+ postProcessIdentities: Object.freeze([...postProcessIdentities]),
1567
1681
  preparedResourceBatches,
1568
1682
  requiresPreparedResourceKey,
1569
1683
  hiddenEntityReports: mergeHiddenEntityReports(hiddenEntityReports),
@@ -2,7 +2,7 @@ import { describe, expect, it } from 'vitest';
2
2
  import { SCENE_DATA_TEMPORAL_V1_SCHEMA } from '../../../temporal/scene-data';
3
3
  import { createSceneDataCatalog } from '../../../temporal/scene-data-catalog';
4
4
  import { freezeRenderFeaturePlan } from '../../plan';
5
- import { planMotionBlur } from '../motion-blur-feature';
5
+ import { MOTION_BLUR_COMPUTE_WGSL, planMotionBlur } from '../motion-blur-feature';
6
6
  import { DEFAULT_MOTION_BLUR_PARAMS } from '../motion-blur-params';
7
7
 
8
8
  const caps = {
@@ -64,4 +64,114 @@ describe('Motion Blur feature plan', () => {
64
64
  expect(pass.sampledTargets).not.toContain('motion-depth');
65
65
  expect(catalog.schema).toBe(SCENE_DATA_TEMPORAL_V1_SCHEMA);
66
66
  });
67
+
68
+ it('selects one tile summary and one fused reconstruction on compute-capable targets', () => {
69
+ const computeCaps = {
70
+ rgba16floatRenderable: true,
71
+ compute: true,
72
+ storageBuffer: true,
73
+ storageTexture: true,
74
+ } as never;
75
+ const catalog = createSceneDataCatalog({
76
+ featureIdentity: 'forgeax.motion-blur',
77
+ generation: 4,
78
+ planIdentity: 'forgeax.motion-blur:4',
79
+ rgba16floatRenderable: true,
80
+ });
81
+ const targets = [
82
+ {
83
+ name: 'motion-input',
84
+ kind: 'color' as const,
85
+ format: 'rgba16float' as const,
86
+ sampleCount: 1 as const,
87
+ },
88
+ {
89
+ name: 'motion-output',
90
+ kind: 'color' as const,
91
+ format: 'rgba16float' as const,
92
+ sampleCount: 1 as const,
93
+ },
94
+ ];
95
+ const planned = planMotionBlur(
96
+ DEFAULT_MOTION_BLUR_PARAMS,
97
+ {
98
+ caps: computeCaps,
99
+ frame: { frameNumber: 7, width: 1920, height: 1080 },
100
+ generation: 4,
101
+ targets,
102
+ sceneData: catalog,
103
+ },
104
+ { frameDeltaSeconds: 1 / 60, reset: false },
105
+ );
106
+ expect(planned.ok).toBe(true);
107
+ if (!planned.ok) return;
108
+ const frozen = freezeRenderFeaturePlan('forgeax.motion-blur', planned.value, targets);
109
+ expect(frozen.ok).toBe(true);
110
+ if (!frozen.ok) return;
111
+
112
+ const computeProgram = frozen.value.resources.find(
113
+ (resource) => resource.kind === 'compute-program',
114
+ );
115
+ const tileSummary = frozen.value.resources.find(
116
+ (resource) => resource.kind === 'buffer' && resource.name === 'motion-blur-tile-summary',
117
+ );
118
+ expect(computeProgram?.kind).toBe('compute-program');
119
+ expect(tileSummary).toMatchObject({
120
+ kind: 'buffer',
121
+ size: Math.ceil(1920 / 16) * Math.ceil(1080 / 16) * 32,
122
+ usage: ['storage'],
123
+ });
124
+ expect(frozen.value.passes.map((pass) => pass.name)).toEqual([
125
+ 'motion-blur-tile-summary',
126
+ 'motion-blur',
127
+ ]);
128
+ expect(frozen.value.passes).toHaveLength(2);
129
+ expect(frozen.value.passes[0]).toMatchObject({
130
+ kind: 'compute',
131
+ dispatches: [{ entryPoint: 'tile_summary', workgroups: [120, 68, 1] }],
132
+ });
133
+ expect(frozen.value.passes[1]).toMatchObject({
134
+ kind: 'compute',
135
+ dispatches: [{ entryPoint: 'reconstruct', workgroups: [240, 135, 1] }],
136
+ });
137
+ expect(MOTION_BLUR_COMPUTE_WGSL).toContain('2.0 * (f32(index) + 0.5) / denominator - 1.0');
138
+ expect(MOTION_BLUR_COMPUTE_WGSL).toContain('let sourcePixel = workgroupBase');
139
+ expect(MOTION_BLUR_COMPUTE_WGSL).toContain('halfFilteredColor');
140
+ expect(MOTION_BLUR_COMPUTE_WGSL).toContain('workgroupBarrier()');
141
+ expect(MOTION_BLUR_COMPUTE_WGSL).toContain('if (!inBounds) {');
142
+ expect(MOTION_BLUR_COMPUTE_WGSL).toContain('depthReject');
143
+ expect(MOTION_BLUR_COMPUTE_WGSL).toContain('sourceVelocitySegmentCovers');
144
+ expect(MOTION_BLUR_COMPUTE_WGSL).toContain('TILE_NEIGHBORHOOD_COUNT');
145
+ expect(MOTION_BLUR_COMPUTE_WGSL).toContain('tileIntersectsWorkgroupSupport');
146
+ expect(MOTION_BLUR_COMPUTE_WGSL).toContain('tileNeighborhoodOffset');
147
+ expect(MOTION_BLUR_COMPUTE_WGSL).toContain('const EDGE_FILL_FACTOR : f32 = 0.99;');
148
+ expect(MOTION_BLUR_COMPUTE_WGSL).toContain('let acceptedColor');
149
+ expect(MOTION_BLUR_COMPUTE_WGSL).toContain('let acceptedAlpha');
150
+ expect(MOTION_BLUR_COMPUTE_WGSL).toContain('let emptyReceiver');
151
+ expect(MOTION_BLUR_COMPUTE_WGSL).toContain('let sourceScale');
152
+ expect(MOTION_BLUR_COMPUTE_WGSL).toContain('let sourceContribution');
153
+ expect(MOTION_BLUR_COMPUTE_WGSL).toContain('let trailScale');
154
+ expect(MOTION_BLUR_COMPUTE_WGSL).toContain('let outputAlpha');
155
+ expect(MOTION_BLUR_COMPUTE_WGSL).not.toContain('TILE_CANDIDATE_OFFSETS');
156
+ expect(MOTION_BLUR_COMPUTE_WGSL).not.toContain('atomic');
157
+ expect(MOTION_BLUR_COMPUTE_WGSL).not.toContain('textureSampleLevel');
158
+ if (computeProgram?.kind === 'compute-program') {
159
+ const entries = computeProgram.program.bindings?.[0]?.entries ?? [];
160
+ expect(entries.find((entry) => entry.binding === 2)?.buffer?.type).toBe('storage');
161
+ expect(entries.find((entry) => entry.binding === 3)?.storageTexture?.access).toBe(
162
+ 'write-only',
163
+ );
164
+ }
165
+ });
166
+
167
+ it('compiles the compute lane WGSL before it reaches a backend', async () => {
168
+ const { compileShader } = await import(
169
+ /* @vite-ignore */
170
+ new URL('../../../../../shader-compiler/dist/index.mjs', import.meta.url).href
171
+ );
172
+ const compiled = await compileShader(MOTION_BLUR_COMPUTE_WGSL, {
173
+ id: 'forgeax::motion-blur-compute',
174
+ });
175
+ expect(compiled.ok, compiled.ok ? undefined : String(compiled.error)).toBe(true);
176
+ });
67
177
  });
@@ -1,6 +1,10 @@
1
1
  import { describe, expect, it } from 'vitest';
2
2
  import {
3
3
  DEFAULT_MOTION_BLUR_PARAMS,
4
+ effectiveMotionBlurSampleCount,
5
+ isMotionBlurIntervalValid,
6
+ motionBlurExposureScale,
7
+ motionBlurSampleDelta,
4
8
  motionBlurTemporalDemand,
5
9
  validateMotionBlurParams,
6
10
  } from '../motion-blur-params';
@@ -12,6 +16,7 @@ describe('Motion Blur parameter validation', () => {
12
16
  shutterAngle: 180,
13
17
  maxRadiusPixels: 32,
14
18
  sampleCount: 8,
19
+ targetFps: 60,
15
20
  });
16
21
  });
17
22
 
@@ -32,6 +37,9 @@ describe('Motion Blur parameter validation', () => {
32
37
  ['sampleCount', 3],
33
38
  ['sampleCount', 17],
34
39
  ['sampleCount', 8.5],
40
+ ['targetFps', -1],
41
+ ['targetFps', 240.5],
42
+ ['targetFps', 59.5],
35
43
  ] as const)('returns structured failure for %s=%s', (field, value) => {
36
44
  const result = validateMotionBlurParams({ [field]: value });
37
45
  expect(result.ok).toBe(false);
@@ -45,9 +53,56 @@ describe('Motion Blur parameter validation', () => {
45
53
 
46
54
  it('does no temporal work when omitted or shutter is zero', () => {
47
55
  expect(motionBlurTemporalDemand(undefined)).toBe(false);
48
- expect(motionBlurTemporalDemand({ shutterAngle: 0, maxRadiusPixels: 32, sampleCount: 8 })).toBe(
49
- false,
50
- );
56
+ expect(
57
+ motionBlurTemporalDemand({
58
+ shutterAngle: 0,
59
+ maxRadiusPixels: 32,
60
+ sampleCount: 8,
61
+ targetFps: 60,
62
+ }),
63
+ ).toBe(false);
64
+ expect(
65
+ motionBlurTemporalDemand({
66
+ shutterAngle: 180,
67
+ maxRadiusPixels: 0,
68
+ sampleCount: 8,
69
+ targetFps: 60,
70
+ }),
71
+ ).toBe(false);
51
72
  expect(motionBlurTemporalDemand(DEFAULT_MOTION_BLUR_PARAMS)).toBe(true);
52
73
  });
74
+
75
+ it('rounds authored sample budgets down to the fixed shader tiers', () => {
76
+ expect(effectiveMotionBlurSampleCount(3)).toBe(0);
77
+ expect(effectiveMotionBlurSampleCount(4)).toBe(4);
78
+ expect(effectiveMotionBlurSampleCount(7)).toBe(4);
79
+ expect(effectiveMotionBlurSampleCount(8)).toBe(8);
80
+ expect(effectiveMotionBlurSampleCount(15)).toBe(8);
81
+ expect(effectiveMotionBlurSampleCount(16)).toBe(16);
82
+ });
83
+
84
+ it('rejects unstable intervals and scales displacement to the target rate', () => {
85
+ expect(isMotionBlurIntervalValid(1 / 60)).toBe(true);
86
+ expect(isMotionBlurIntervalValid(0)).toBe(false);
87
+ expect(isMotionBlurIntervalValid(0.101)).toBe(false);
88
+ expect(motionBlurExposureScale(1 / 60, 60)).toBeCloseTo(1);
89
+ expect(motionBlurExposureScale(1 / 120, 60)).toBeCloseTo(2);
90
+ expect(motionBlurExposureScale(1 / 60, 0)).toBe(1);
91
+ expect(motionBlurExposureScale(0, 60)).toBe(0);
92
+ expect(motionBlurExposureScale(1 / 60, 60.5)).toBe(0);
93
+ expect(motionBlurExposureScale(1 / 60, 241)).toBe(0);
94
+ });
95
+
96
+ it('keeps raw render hitches visible before ECS clamp policy', () => {
97
+ expect(motionBlurSampleDelta(1.2, 1, 0.1)).toBeCloseTo(0.2);
98
+ expect(motionBlurSampleDelta(1 / 30, 0, 0.1)).toBeCloseTo(1 / 30);
99
+ expect(isMotionBlurIntervalValid(motionBlurSampleDelta(1.2, 1, 0.1))).toBe(false);
100
+ expect(isMotionBlurIntervalValid(motionBlurSampleDelta(1 / 30, 0, 0.1))).toBe(true);
101
+ expect(motionBlurSampleDelta(undefined, undefined, 0.1)).toBe(0.1);
102
+ });
103
+
104
+ it('fails closed for an explicitly invalid host/replay sample', () => {
105
+ expect(Number.isNaN(motionBlurSampleDelta(Number.NaN, 1, 1 / 60))).toBe(true);
106
+ expect(Number.isNaN(motionBlurSampleDelta(1, Number.POSITIVE_INFINITY, 1 / 60))).toBe(true);
107
+ });
53
108
  });