@forgeax/engine-render 0.1.28 → 0.1.29

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 (642) hide show
  1. package/README.md +627 -124
  2. package/dist/assembly/atmosphere-shader-prewarm.d.ts +6 -0
  3. package/dist/assembly/atmosphere-shader-prewarm.d.ts.map +1 -0
  4. package/dist/assembly/dynamic-geometry-host.d.ts +21 -0
  5. package/dist/assembly/dynamic-geometry-host.d.ts.map +1 -1
  6. package/dist/assembly/dynamic-geometry-runtime.d.ts +20 -0
  7. package/dist/assembly/dynamic-geometry-runtime.d.ts.map +1 -0
  8. package/dist/assembly/factory.d.ts +3 -1
  9. package/dist/assembly/factory.d.ts.map +1 -1
  10. package/dist/assembly/gpu-driven-owner.d.ts +10 -0
  11. package/dist/assembly/gpu-driven-owner.d.ts.map +1 -0
  12. package/dist/assembly/host-contract.d.ts +8 -0
  13. package/dist/assembly/host-contract.d.ts.map +1 -1
  14. package/dist/assembly/material/assembly.d.ts +7 -0
  15. package/dist/assembly/material/assembly.d.ts.map +1 -1
  16. package/dist/assembly/material-shader-policy.d.ts +3 -1
  17. package/dist/assembly/material-shader-policy.d.ts.map +1 -1
  18. package/dist/assembly/recovery/failure-location.d.ts +12 -0
  19. package/dist/assembly/recovery/failure-location.d.ts.map +1 -0
  20. package/dist/assembly/recovery/renderer-recover.d.ts +6 -4
  21. package/dist/assembly/recovery/renderer-recover.d.ts.map +1 -1
  22. package/dist/assembly/renderer-facade.d.ts.map +1 -1
  23. package/dist/assembly/renderer-feature-inspection.d.ts +5 -0
  24. package/dist/assembly/renderer-feature-inspection.d.ts.map +1 -0
  25. package/dist/assembly/renderer-frame-transaction.d.ts +19 -0
  26. package/dist/assembly/renderer-frame-transaction.d.ts.map +1 -1
  27. package/dist/assembly/renderer-helpers.d.ts +3 -0
  28. package/dist/assembly/renderer-helpers.d.ts.map +1 -1
  29. package/dist/assembly/renderer-inspection.d.ts +4 -0
  30. package/dist/assembly/renderer-inspection.d.ts.map +1 -1
  31. package/dist/assembly/shader-prewarm-policy.d.ts +6 -0
  32. package/dist/assembly/shader-prewarm-policy.d.ts.map +1 -1
  33. package/dist/assembly/skin-palette-owner.d.ts +8 -0
  34. package/dist/assembly/skin-palette-owner.d.ts.map +1 -0
  35. package/dist/assembly/ssr-shader-prewarm.d.ts +6 -0
  36. package/dist/assembly/ssr-shader-prewarm.d.ts.map +1 -0
  37. package/dist/assembly/webgpu-device-loss.d.ts +15 -0
  38. package/dist/assembly/webgpu-device-loss.d.ts.map +1 -0
  39. package/dist/assembly/webgpu-pbr-ready.d.ts +38 -0
  40. package/dist/assembly/webgpu-pbr-ready.d.ts.map +1 -0
  41. package/dist/assembly/webgpu-ready-mipmap.d.ts +11 -0
  42. package/dist/assembly/webgpu-ready-mipmap.d.ts.map +1 -0
  43. package/dist/assembly/webgpu-ready.d.ts +7 -3
  44. package/dist/assembly/webgpu-ready.d.ts.map +1 -1
  45. package/dist/assembly/webgpu-renderer-bootstrap.d.ts +10 -0
  46. package/dist/assembly/webgpu-renderer-bootstrap.d.ts.map +1 -0
  47. package/dist/assembly/webgpu-renderer-contract.d.ts +62 -0
  48. package/dist/assembly/webgpu-renderer-contract.d.ts.map +1 -0
  49. package/dist/assembly/webgpu-renderer-guards.d.ts +9 -0
  50. package/dist/assembly/webgpu-renderer-guards.d.ts.map +1 -0
  51. package/dist/assembly/webgpu-renderer-observation.d.ts +6 -0
  52. package/dist/assembly/webgpu-renderer-observation.d.ts.map +1 -0
  53. package/dist/assembly/webgpu-renderer-recovery-failure.d.ts +12 -0
  54. package/dist/assembly/webgpu-renderer-recovery-failure.d.ts.map +1 -0
  55. package/dist/assembly/webgpu-renderer.d.ts +7 -141
  56. package/dist/assembly/webgpu-renderer.d.ts.map +1 -1
  57. package/dist/assembly/webgpu-vertex-layouts.d.ts +28 -84
  58. package/dist/assembly/webgpu-vertex-layouts.d.ts.map +1 -1
  59. package/dist/authoring.mjs +2 -2
  60. package/dist/{chunk-JAASCMXA.mjs → chunk-33XZFTQU.mjs} +245 -39
  61. package/dist/chunk-33XZFTQU.mjs.map +1 -0
  62. package/dist/{chunk-GF523LHF.mjs → chunk-BLXGONR2.mjs} +30 -12
  63. package/dist/chunk-BLXGONR2.mjs.map +1 -0
  64. package/dist/{chunk-J52NMQKR.mjs → chunk-BQVQOQJH.mjs} +1086 -838
  65. package/dist/chunk-BQVQOQJH.mjs.map +1 -0
  66. package/dist/{chunk-IQMKJLNU.mjs → chunk-BTWH7BP4.mjs} +386 -17
  67. package/dist/chunk-BTWH7BP4.mjs.map +1 -0
  68. package/dist/{chunk-MBU4ZVE7.mjs → chunk-FUODTTIA.mjs} +3921 -671
  69. package/dist/chunk-FUODTTIA.mjs.map +1 -0
  70. package/dist/{chunk-MYJ5RPJO.mjs → chunk-JQTSEI3Z.mjs} +3 -3
  71. package/dist/{chunk-MYJ5RPJO.mjs.map → chunk-JQTSEI3Z.mjs.map} +1 -1
  72. package/dist/{chunk-GE3SDD72.mjs → chunk-NICXSHAT.mjs} +16 -38
  73. package/dist/chunk-NICXSHAT.mjs.map +1 -0
  74. package/dist/{chunk-J6KACNOH.mjs → chunk-NRNXBLIJ.mjs} +577 -7
  75. package/dist/chunk-NRNXBLIJ.mjs.map +1 -0
  76. package/dist/{chunk-RBDBZ5R7.mjs → chunk-PA5DRJNI.mjs} +2 -2
  77. package/dist/chunk-PA5DRJNI.mjs.map +1 -0
  78. package/dist/{chunk-VSL23LZE.mjs → chunk-S4DNKQYQ.mjs} +1117 -445
  79. package/dist/chunk-S4DNKQYQ.mjs.map +1 -0
  80. package/dist/components/camera.d.ts +62 -2
  81. package/dist/components/camera.d.ts.map +1 -1
  82. package/dist/components/index.d.ts +1 -0
  83. package/dist/components/index.d.ts.map +1 -1
  84. package/dist/components/instances.d.ts +6 -20
  85. package/dist/components/instances.d.ts.map +1 -1
  86. package/dist/components/scene-instance.d.ts +2 -1
  87. package/dist/components/scene-instance.d.ts.map +1 -1
  88. package/dist/components/screen-space-reflection.d.ts +14 -0
  89. package/dist/components/screen-space-reflection.d.ts.map +1 -0
  90. package/dist/construct-renderer.mjs +12888 -7025
  91. package/dist/construct-renderer.mjs.map +1 -1
  92. package/dist/device/gpu-residency.d.ts +1 -12
  93. package/dist/device/gpu-residency.d.ts.map +1 -1
  94. package/dist/environment/background.d.ts +6 -0
  95. package/dist/environment/background.d.ts.map +1 -0
  96. package/dist/errors/gpu-driven.d.ts +6 -3
  97. package/dist/errors/gpu-driven.d.ts.map +1 -1
  98. package/dist/errors/render.d.ts +7 -4
  99. package/dist/errors/render.d.ts.map +1 -1
  100. package/dist/extract/camera.d.ts.map +1 -1
  101. package/dist/extract/gpu-driven.d.ts +33 -3
  102. package/dist/extract/gpu-driven.d.ts.map +1 -1
  103. package/dist/features/host.d.ts +23 -1
  104. package/dist/features/host.d.ts.map +1 -1
  105. package/dist/features/noise-texture.d.ts +17 -0
  106. package/dist/features/noise-texture.d.ts.map +1 -0
  107. package/dist/features/plan.d.ts +83 -2
  108. package/dist/features/plan.d.ts.map +1 -1
  109. package/dist/features/prepared-gpu-work.d.ts +37 -4
  110. package/dist/features/prepared-gpu-work.d.ts.map +1 -1
  111. package/dist/features/prepared-graphics-store.d.ts.map +1 -1
  112. package/dist/features/prepared-graphics.d.ts +5 -0
  113. package/dist/features/prepared-graphics.d.ts.map +1 -1
  114. package/dist/features/render-graph-compute.d.ts +5 -3
  115. package/dist/features/render-graph-compute.d.ts.map +1 -1
  116. package/dist/features/render-graph-contribution.d.ts +11 -3
  117. package/dist/features/render-graph-contribution.d.ts.map +1 -1
  118. package/dist/features/render-graph-raster.d.ts +19 -3
  119. package/dist/features/render-graph-raster.d.ts.map +1 -1
  120. package/dist/features/types.d.ts +11 -0
  121. package/dist/features/types.d.ts.map +1 -1
  122. package/dist/fullscreen-post-process-pass.d.ts +2 -0
  123. package/dist/fullscreen-post-process-pass.d.ts.map +1 -1
  124. package/dist/gpu-driven/batch-topology.d.ts +34 -1
  125. package/dist/gpu-driven/batch-topology.d.ts.map +1 -1
  126. package/dist/gpu-driven/pbr-program.d.ts +10 -0
  127. package/dist/gpu-driven/pbr-program.d.ts.map +1 -0
  128. package/dist/gpu-driven/prepared-draw.d.ts.map +1 -1
  129. package/dist/gpu-driven/production-raster.d.ts +144 -23
  130. package/dist/gpu-driven/production-raster.d.ts.map +1 -1
  131. package/dist/gpu-driven/shadow-views.d.ts +126 -0
  132. package/dist/gpu-driven/shadow-views.d.ts.map +1 -0
  133. package/dist/gpu-driven/view-gpu.d.ts +18 -2
  134. package/dist/gpu-driven/view-gpu.d.ts.map +1 -1
  135. package/dist/gpu-scene-schema.d.ts +2 -8
  136. package/dist/gpu-scene-schema.d.ts.map +1 -1
  137. package/dist/gpu-scene.d.ts +14 -0
  138. package/dist/gpu-scene.d.ts.map +1 -1
  139. package/dist/gpu-texture-usage.d.ts +1 -0
  140. package/dist/gpu-texture-usage.d.ts.map +1 -1
  141. package/dist/hdrp-buffers.d.ts +6 -0
  142. package/dist/hdrp-buffers.d.ts.map +1 -1
  143. package/dist/ibl/frame-resources.d.ts +8 -0
  144. package/dist/ibl/frame-resources.d.ts.map +1 -0
  145. package/dist/ibl/skylight-bind-group.d.ts.map +1 -1
  146. package/dist/index.d.ts +18 -8
  147. package/dist/index.d.ts.map +1 -1
  148. package/dist/index.mjs +8 -8
  149. package/dist/inspection-types.d.ts +164 -0
  150. package/dist/inspection-types.d.ts.map +1 -1
  151. package/dist/instances-derived-bounds.d.ts +11 -0
  152. package/dist/instances-derived-bounds.d.ts.map +1 -1
  153. package/dist/instances.d.ts +89 -44
  154. package/dist/instances.d.ts.map +1 -1
  155. package/dist/internal.d.ts +3 -0
  156. package/dist/internal.d.ts.map +1 -1
  157. package/dist/internal.mjs +117 -9
  158. package/dist/internal.mjs.map +1 -1
  159. package/dist/lifecycle.d.ts +2 -1
  160. package/dist/lifecycle.d.ts.map +1 -1
  161. package/dist/material-row.d.ts +5 -0
  162. package/dist/material-row.d.ts.map +1 -0
  163. package/dist/materials.d.ts +15 -0
  164. package/dist/materials.d.ts.map +1 -1
  165. package/dist/pbr-pipeline.d.ts +21 -5
  166. package/dist/pbr-pipeline.d.ts.map +1 -1
  167. package/dist/pipeline/standard-forward-lane.d.ts.map +1 -1
  168. package/dist/pipeline/standard-output/auto-exposure/capability.d.ts +12 -0
  169. package/dist/pipeline/standard-output/auto-exposure/capability.d.ts.map +1 -0
  170. package/dist/pipeline/standard-output/auto-exposure/gpu.d.ts +70 -0
  171. package/dist/pipeline/standard-output/auto-exposure/gpu.d.ts.map +1 -0
  172. package/dist/pipeline/standard-output/auto-exposure/graph.d.ts +75 -0
  173. package/dist/pipeline/standard-output/auto-exposure/graph.d.ts.map +1 -0
  174. package/dist/pipeline/standard-output/auto-exposure/inspection.d.ts +111 -0
  175. package/dist/pipeline/standard-output/auto-exposure/inspection.d.ts.map +1 -0
  176. package/dist/pipeline/standard-output/auto-exposure/oracle.d.ts +16 -0
  177. package/dist/pipeline/standard-output/auto-exposure/oracle.d.ts.map +1 -0
  178. package/dist/pipeline/standard-output/auto-exposure/preset.d.ts +14 -0
  179. package/dist/pipeline/standard-output/auto-exposure/preset.d.ts.map +1 -0
  180. package/dist/pipeline/standard-output/auto-exposure/state.d.ts +46 -0
  181. package/dist/pipeline/standard-output/auto-exposure/state.d.ts.map +1 -0
  182. package/dist/pipeline/standard-output/color-transform.d.ts +27 -0
  183. package/dist/pipeline/standard-output/color-transform.d.ts.map +1 -0
  184. package/dist/pipeline/standard-output/graph.d.ts +15 -0
  185. package/dist/pipeline/standard-output/graph.d.ts.map +1 -0
  186. package/dist/pipeline/standard-output/lut-admission.d.ts +29 -0
  187. package/dist/pipeline/standard-output/lut-admission.d.ts.map +1 -0
  188. package/dist/pipeline/standard-output/lut-gpu.d.ts +48 -0
  189. package/dist/pipeline/standard-output/lut-gpu.d.ts.map +1 -0
  190. package/dist/pipeline/standard-output/lut-state.d.ts +68 -0
  191. package/dist/pipeline/standard-output/lut-state.d.ts.map +1 -0
  192. package/dist/pipeline/standard-output/resources.d.ts +10 -0
  193. package/dist/pipeline/standard-output/resources.d.ts.map +1 -0
  194. package/dist/pipeline/standard-output/types.d.ts +39 -0
  195. package/dist/pipeline/standard-output/types.d.ts.map +1 -0
  196. package/dist/pipeline/standard-pipeline.d.ts.map +1 -1
  197. package/dist/pipeline/standard-post.d.ts +2 -2
  198. package/dist/pipeline/standard-post.d.ts.map +1 -1
  199. package/dist/pipeline-builder.d.ts +2 -1
  200. package/dist/pipeline-builder.d.ts.map +1 -1
  201. package/dist/pipeline-spec-types.d.ts +4 -0
  202. package/dist/pipeline-spec-types.d.ts.map +1 -1
  203. package/dist/pipeline-spec.d.ts +1 -9
  204. package/dist/pipeline-spec.d.ts.map +1 -1
  205. package/dist/points-lines/standard-owner.d.ts +1 -1
  206. package/dist/points-lines/standard-owner.d.ts.map +1 -1
  207. package/dist/prepare/prepared-graphics-resolver.d.ts +6 -1
  208. package/dist/prepare/prepared-graphics-resolver.d.ts.map +1 -1
  209. package/dist/record/dynamic-geometry-consumption.d.ts +15 -0
  210. package/dist/record/dynamic-geometry-consumption.d.ts.map +1 -0
  211. package/dist/record/frame-lighting.d.ts +3 -3
  212. package/dist/record/frame-lighting.d.ts.map +1 -1
  213. package/dist/record/frame-snapshot.d.ts +106 -5
  214. package/dist/record/frame-snapshot.d.ts.map +1 -1
  215. package/dist/record/frame-targets.d.ts.map +1 -1
  216. package/dist/record/frame.d.ts +39 -4
  217. package/dist/record/frame.d.ts.map +1 -1
  218. package/dist/record/main-pass-geometry.d.ts +7 -8
  219. package/dist/record/main-pass-geometry.d.ts.map +1 -1
  220. package/dist/record/main-pass-material.d.ts +38 -1
  221. package/dist/record/main-pass-material.d.ts.map +1 -1
  222. package/dist/record/main-pass-sprite-draws.d.ts +2 -10
  223. package/dist/record/main-pass-sprite-draws.d.ts.map +1 -1
  224. package/dist/record/main-pass.d.ts +12 -2
  225. package/dist/record/main-pass.d.ts.map +1 -1
  226. package/dist/record/mesh-ssbo.d.ts +15 -6
  227. package/dist/record/mesh-ssbo.d.ts.map +1 -1
  228. package/dist/record/prepared-material-bindings.d.ts +19 -0
  229. package/dist/record/prepared-material-bindings.d.ts.map +1 -0
  230. package/dist/record/recovery-pipeline.d.ts +2 -9
  231. package/dist/record/recovery-pipeline.d.ts.map +1 -1
  232. package/dist/record/render-context.d.ts +74 -26
  233. package/dist/record/render-context.d.ts.map +1 -1
  234. package/dist/record/shadow-pass.d.ts +62 -7
  235. package/dist/record/shadow-pass.d.ts.map +1 -1
  236. package/dist/record/skybox-post-pass.d.ts +1 -1
  237. package/dist/record/skybox-post-pass.d.ts.map +1 -1
  238. package/dist/record/typed-frame-graph.d.ts +35 -7
  239. package/dist/record/typed-frame-graph.d.ts.map +1 -1
  240. package/dist/record/view-ubo.d.ts +1 -0
  241. package/dist/record/view-ubo.d.ts.map +1 -1
  242. package/dist/recovery/render-system-candidate.d.ts +10 -2
  243. package/dist/recovery/render-system-candidate.d.ts.map +1 -1
  244. package/dist/recovery/render-system-roots.d.ts +11 -0
  245. package/dist/recovery/render-system-roots.d.ts.map +1 -0
  246. package/dist/recovery/types.d.ts +76 -0
  247. package/dist/recovery/types.d.ts.map +1 -0
  248. package/dist/reflection/record-owner.d.ts +0 -2
  249. package/dist/reflection/record-owner.d.ts.map +1 -1
  250. package/dist/render-contract.d.ts +173 -30
  251. package/dist/render-contract.d.ts.map +1 -1
  252. package/dist/render-graph-primitives.d.ts +1 -0
  253. package/dist/render-graph-primitives.d.ts.map +1 -1
  254. package/dist/render-pipeline.d.ts +64 -5
  255. package/dist/render-pipeline.d.ts.map +1 -1
  256. package/dist/render-system-extract-tail.d.ts +7 -5
  257. package/dist/render-system-extract-tail.d.ts.map +1 -1
  258. package/dist/render-system-extract.d.ts +101 -41
  259. package/dist/render-system-extract.d.ts.map +1 -1
  260. package/dist/render-system-projections.d.ts +22 -0
  261. package/dist/render-system-projections.d.ts.map +1 -0
  262. package/dist/render-system.d.ts +27 -77
  263. package/dist/render-system.d.ts.map +1 -1
  264. package/dist/scene/render-scene-types.d.ts +14 -7
  265. package/dist/scene/render-scene-types.d.ts.map +1 -1
  266. package/dist/scene/render-scene.d.ts +29 -11
  267. package/dist/scene/render-scene.d.ts.map +1 -1
  268. package/dist/scene/shadow-visibility.d.ts +5 -0
  269. package/dist/scene/shadow-visibility.d.ts.map +1 -0
  270. package/dist/ssr/admission.d.ts +70 -1
  271. package/dist/ssr/admission.d.ts.map +1 -1
  272. package/dist/ssr/compose.d.ts +13 -0
  273. package/dist/ssr/compose.d.ts.map +1 -0
  274. package/dist/ssr/errors.d.ts +35 -0
  275. package/dist/ssr/errors.d.ts.map +1 -1
  276. package/dist/ssr/graph.d.ts +60 -0
  277. package/dist/ssr/graph.d.ts.map +1 -0
  278. package/dist/ssr/history.d.ts +97 -0
  279. package/dist/ssr/history.d.ts.map +1 -0
  280. package/dist/ssr/hiz.d.ts +37 -0
  281. package/dist/ssr/hiz.d.ts.map +1 -0
  282. package/dist/ssr/inspection.d.ts +42 -0
  283. package/dist/ssr/inspection.d.ts.map +1 -0
  284. package/dist/ssr/resources.d.ts +58 -0
  285. package/dist/ssr/resources.d.ts.map +1 -0
  286. package/dist/ssr/temporal.d.ts +38 -0
  287. package/dist/ssr/temporal.d.ts.map +1 -0
  288. package/dist/systems/skin-palette-allocator.d.ts +32 -10
  289. package/dist/systems/skin-palette-allocator.d.ts.map +1 -1
  290. package/dist/systems/skin-palette-types.d.ts +15 -0
  291. package/dist/systems/skin-palette-types.d.ts.map +1 -1
  292. package/dist/systems/transparent-dispatch.d.ts +9 -0
  293. package/dist/systems/transparent-dispatch.d.ts.map +1 -0
  294. package/dist/targets/material-source.d.ts +7 -0
  295. package/dist/targets/material-source.d.ts.map +1 -1
  296. package/dist/temporal/frame-coordinator.d.ts +7 -0
  297. package/dist/temporal/frame-coordinator.d.ts.map +1 -1
  298. package/dist/temporal/gpu.d.ts +8 -0
  299. package/dist/temporal/gpu.d.ts.map +1 -1
  300. package/dist/temporal/history.d.ts.map +1 -1
  301. package/dist/temporal/index.mjs +6 -6
  302. package/dist/temporal/inspection.d.ts.map +1 -1
  303. package/dist/temporal/standard-scene-data.d.ts +2 -1
  304. package/dist/temporal/standard-scene-data.d.ts.map +1 -1
  305. package/dist/temporal/temporal-view.d.ts +2 -0
  306. package/dist/temporal/temporal-view.d.ts.map +1 -1
  307. package/dist/typed-render-graph-primitives.d.ts +35 -1
  308. package/dist/typed-render-graph-primitives.d.ts.map +1 -1
  309. package/dist/typed-shadow-passes.d.ts +3 -1
  310. package/dist/typed-shadow-passes.d.ts.map +1 -1
  311. package/dist/volume/temporal.d.ts +6 -0
  312. package/dist/volume/temporal.d.ts.map +1 -1
  313. package/package.json +21 -20
  314. package/src/__tests__/auto-exposure-gpu-evidence.ts +291 -0
  315. package/src/__tests__/auto-exposure-inspection.unit.test.ts +80 -0
  316. package/src/__tests__/auto-exposure-transaction.integration.test.ts +99 -0
  317. package/src/__tests__/auto-exposure.browser.test.ts +33 -0
  318. package/src/__tests__/auto-exposure.dawn.test.ts +33 -0
  319. package/src/__tests__/detached-inspection.integration.test.ts +5 -0
  320. package/src/__tests__/device-scope-lifecycle.unit.test.ts +18 -0
  321. package/src/__tests__/device-scope-stale-matrix.unit.test.ts +18 -0
  322. package/src/__tests__/docs-gate-green.unit.test.ts +3 -3
  323. package/src/__tests__/factory-contract.integration.test.ts +67 -8
  324. package/src/__tests__/fallback-row-stride-surface.unit.test.ts +19 -0
  325. package/src/__tests__/feature-depth-input.dawn.test.ts +121 -0
  326. package/src/__tests__/feature-noise.unit.test.ts +38 -0
  327. package/src/__tests__/fixtures/taa-resolve-pinned-baseline.wgsl +301 -0
  328. package/src/__tests__/frame-camera-selection.unit.test.ts +131 -0
  329. package/src/__tests__/frame-plan-contract.unit.test.ts +24 -0
  330. package/src/__tests__/frame-targets.unit.test.ts +24 -0
  331. package/src/__tests__/fullscreen-feature-plan.unit.test.ts +3 -0
  332. package/src/__tests__/gpu-driven-baseline.characterization.test.ts +79 -0
  333. package/src/__tests__/gpu-driven-batch-topology-pbr.unit.test.ts +287 -6
  334. package/src/__tests__/gpu-driven-indirect-raster-evidence.ts +122 -142
  335. package/src/__tests__/gpu-driven-indirect-raster.browser.test.ts +10 -1
  336. package/src/__tests__/gpu-driven-lod-cache.unit.test.ts +264 -0
  337. package/src/__tests__/gpu-driven-mesh-world-identity.unit.test.ts +349 -0
  338. package/src/__tests__/gpu-driven-pbr.dawn.test.ts +1417 -0
  339. package/src/__tests__/gpu-driven-pbr.integration.test.ts +21 -0
  340. package/src/__tests__/gpu-driven-production-fixture.ts +96 -0
  341. package/src/__tests__/gpu-driven-production.integration.test.ts +1075 -93
  342. package/src/__tests__/gpu-driven-scaling.unit.test.ts +1 -3
  343. package/src/__tests__/gpu-driven-shadow-views.browser.test.ts +63 -0
  344. package/src/__tests__/gpu-driven-shadow-views.dawn.test.ts +294 -0
  345. package/src/__tests__/gpu-driven-shadow-views.integration.test.ts +97 -0
  346. package/src/__tests__/gpu-driven-shadow-views.unit.test.ts +168 -0
  347. package/src/__tests__/gpu-driven-skin-contract.test-d.ts +11 -0
  348. package/src/__tests__/gpu-driven-view-gpu-evidence.ts +72 -20
  349. package/src/__tests__/gpu-driven-view-graph.integration.test.ts +30 -9
  350. package/src/__tests__/gpu-driven-view.unit.test.ts +159 -52
  351. package/src/__tests__/gpu-pass-timing.browser.test.ts +6 -1
  352. package/src/__tests__/gpu-resource-store-stride.unit.test.ts +42 -0
  353. package/src/__tests__/gpu-scene-render-graph-gpu.ts +19 -5
  354. package/src/__tests__/gpu-scene.dawn.test.ts +40 -13
  355. package/src/__tests__/gpu-scene.unit.test.ts +559 -33
  356. package/src/__tests__/instances-culling.integration.test.ts +36 -10
  357. package/src/__tests__/instances-derived-bounds.unit.test.ts +54 -0
  358. package/src/__tests__/instances-store.unit.test.ts +242 -39
  359. package/src/__tests__/ktx2-basis-gpu-consumer.dawn.test.ts +6 -0
  360. package/src/__tests__/lighting-scene-roundtrip.integration.test.ts +13 -16
  361. package/src/__tests__/lut-lkg-transaction.integration.test.ts +122 -0
  362. package/src/__tests__/material-contract-inventory.unit.test.ts +5 -6
  363. package/src/__tests__/material-dispatch-shader-id.unit.test.ts +51 -0
  364. package/src/__tests__/material-lane-selection.unit.test.ts +60 -0
  365. package/src/__tests__/material-numeric-upload.unit.test.ts +53 -0
  366. package/src/__tests__/material-texture-handles.unit.test.ts +18 -0
  367. package/src/__tests__/material-texture-source.unit.test.ts +87 -0
  368. package/src/__tests__/materials-authoring-validation.unit.test.ts +41 -0
  369. package/src/__tests__/materials-standard-contract.unit.test.ts +52 -2
  370. package/src/__tests__/mesh-buffer-usage-surface.unit.test.ts +4 -0
  371. package/src/__tests__/normal-fallback.dawn.test.ts +122 -0
  372. package/src/__tests__/particle-material-input-layout.unit.test.ts +104 -0
  373. package/src/__tests__/physical-clearcoat.integration.test.ts +37 -3
  374. package/src/__tests__/pipeline-texture-specialization.unit.test.ts +138 -0
  375. package/src/__tests__/point-shadow-inspection.unit.test.ts +2 -0
  376. package/src/__tests__/prepared-gpu-driven-pbr-contract.test-d.ts +31 -0
  377. package/src/__tests__/prepared-gpu-driven-pbr-contract.unit.test.ts +39 -0
  378. package/src/__tests__/prepared-gpu-driven-pbr.integration.test.ts +35 -4
  379. package/src/__tests__/prepared-graphics-capability.unit.test.ts +21 -0
  380. package/src/__tests__/prepared-graphics-pipeline-warmup.unit.test.ts +6 -2
  381. package/src/__tests__/profiler-phase-catalog.test.ts +5 -0
  382. package/src/__tests__/public-surface.test-d.ts +3 -0
  383. package/src/__tests__/recovery-candidate-prepare.contract.test.ts +30 -12
  384. package/src/__tests__/reflection-fallback-async-publication.unit.test.ts +104 -0
  385. package/src/__tests__/reflection-probe-record-owner.characterization.unit.test.ts +2 -1
  386. package/src/__tests__/render-error-code-owner.test-d.ts +4 -0
  387. package/src/__tests__/render-error-detail-ssot.test-d.ts +22 -0
  388. package/src/__tests__/render-error-exhaustive.test-d.ts +15 -0
  389. package/src/__tests__/render-feature-gpu-work.integration.test.ts +285 -2
  390. package/src/__tests__/render-feature-plan.unit.test.ts +245 -0
  391. package/src/__tests__/render-feature-shadow-order.unit.test.ts +228 -0
  392. package/src/__tests__/render-feature-zero-work.unit.test.ts +35 -1
  393. package/src/__tests__/render-graph-fallback.integration.test.ts +2 -2
  394. package/src/__tests__/render-owner-cohesion.unit.test.ts +55 -0
  395. package/src/__tests__/render-scene-mixed-updates.integration.test.ts +426 -0
  396. package/src/__tests__/render-scene-projection.unit.test.ts +914 -23
  397. package/src/__tests__/render-scene-resource-updates.integration.test.ts +214 -0
  398. package/src/__tests__/render-scene-skin-lifecycle.integration.test.ts +173 -0
  399. package/src/__tests__/render-scene-temporal-retry.unit.test.ts +133 -0
  400. package/src/__tests__/render-target-material-source.test-d.ts +27 -3
  401. package/src/__tests__/renderer-factory-material-contract.unit.test.ts +60 -0
  402. package/src/__tests__/renderer-frame-transaction.integration.test.ts +923 -5
  403. package/src/__tests__/shader-manifest-fixture.ts +39 -0
  404. package/src/__tests__/skin-motion-regression.unit.test.ts +0 -33
  405. package/src/__tests__/skin-palette-persistent.integration.test.ts +28 -0
  406. package/src/__tests__/skin-palette-persistent.unit.test.ts +247 -0
  407. package/src/__tests__/skinned-shadow-caster.test.ts +118 -14
  408. package/src/__tests__/specular-environment-scale.dawn.test.ts +156 -0
  409. package/src/__tests__/ssr-admission-matrix.integration.test.ts +7 -1
  410. package/src/__tests__/ssr-confidence-compose.unit.test.ts +84 -0
  411. package/src/__tests__/ssr-coordinate-oracle.unit.test.ts +28 -0
  412. package/src/__tests__/ssr-format-probe-gating.integration.test.ts +82 -0
  413. package/src/__tests__/ssr-gpu-dispatch.browser.test.ts +91 -0
  414. package/src/__tests__/ssr-gpu-dispatch.dawn.test.ts +112 -0
  415. package/src/__tests__/ssr-gpu-dispatch.ts +946 -0
  416. package/src/__tests__/ssr-graph-topology.unit.test.ts +104 -0
  417. package/src/__tests__/ssr-history-filter.dawn.test.ts +198 -0
  418. package/src/__tests__/ssr-hit-color.dawn.test.ts +273 -0
  419. package/src/__tests__/ssr-hiz-odd-reduction.dawn.test.ts +569 -0
  420. package/src/__tests__/ssr-hiz-rescue.dawn.test.ts +279 -0
  421. package/src/__tests__/ssr-hiz.browser.test.ts +150 -0
  422. package/src/__tests__/ssr-hiz.dawn.test.ts +150 -0
  423. package/src/__tests__/ssr-hiz.unit.test.ts +56 -0
  424. package/src/__tests__/ssr-inspection.unit.test.ts +105 -0
  425. package/src/__tests__/ssr-memory-budget.unit.test.ts +45 -0
  426. package/src/__tests__/ssr-performance-sequence-aggregation.unit.test.ts +15 -0
  427. package/src/__tests__/ssr-public-surface.test-d.ts +37 -0
  428. package/src/__tests__/ssr-receiver-coverage.dawn.test.ts +387 -0
  429. package/src/__tests__/ssr-reflection-mip-reducer.dawn.test.ts +309 -0
  430. package/src/__tests__/ssr-reprojection-probe.ts +90 -0
  431. package/src/__tests__/ssr-roughness-mip.dawn.test.ts +163 -0
  432. package/src/__tests__/ssr-shader-prewarm.unit.test.ts +59 -0
  433. package/src/__tests__/ssr-spatial.integration.test.ts +141 -0
  434. package/src/__tests__/ssr-stable-lattice.dawn.test.ts +475 -0
  435. package/src/__tests__/ssr-temporal-history.unit.test.ts +202 -0
  436. package/src/__tests__/ssr-texel-crossing.dawn.test.ts +448 -0
  437. package/src/__tests__/ssr-zero-work.integration.test.ts +87 -0
  438. package/src/__tests__/standard-cluster-variant-resolution.unit.test.ts +44 -12
  439. package/src/__tests__/standard-clustered-pipeline.unit.test.ts +29 -0
  440. package/src/__tests__/standard-output-chain-contract.unit.test.ts +99 -0
  441. package/src/__tests__/standard-output-chain.integration.test.ts +73 -0
  442. package/src/__tests__/standard-output-gpu-evidence.ts +205 -0
  443. package/src/__tests__/standard-output.browser.test.ts +15 -0
  444. package/src/__tests__/standard-output.dawn.test.ts +15 -0
  445. package/src/__tests__/standard-pbr-artifact-assembly.unit.test.ts +38 -0
  446. package/src/__tests__/standard-pipeline.integration.test.ts +320 -3
  447. package/src/__tests__/standard-profile-oracle.unit.test.ts +22 -0
  448. package/src/__tests__/standard-surface-lighting.unit.test.ts +41 -0
  449. package/src/__tests__/standard-texture-specialization.dawn.test.ts +141 -0
  450. package/src/__tests__/surface-adjacent-regressions.integration.test.ts +6 -3
  451. package/src/__tests__/taa-coverage-edge.dawn.test.ts +193 -0
  452. package/src/__tests__/taa-history-clipping.dawn.test.ts +328 -0
  453. package/src/__tests__/taa-neighborhood-equivalence.dawn.test.ts +778 -0
  454. package/src/__tests__/taa-stability-feedback.browser.test.ts +23 -0
  455. package/src/__tests__/taa-stability-feedback.dawn.test.ts +68 -0
  456. package/src/__tests__/taa-stability-feedback.ts +341 -0
  457. package/src/__tests__/temporal-history-lifecycle.unit.test.ts +49 -7
  458. package/src/__tests__/temporal-reset-matrix.unit.test.ts +9 -0
  459. package/src/__tests__/temporal-transaction.integration.test.ts +28 -0
  460. package/src/__tests__/texture-usage-surface.unit.test.ts +4 -2
  461. package/src/__tests__/typed-pipeline-topology.unit.test.ts +109 -4
  462. package/src/__tests__/typed-scene-pass-material-cache.integration.test.ts +126 -0
  463. package/src/__tests__/vertex-layouts-ssot.unit.test.ts +47 -0
  464. package/src/__tests__/visibility-extract.perf.test.ts +20 -8
  465. package/src/__tests__/visibility-instances.integration.test.ts +19 -7
  466. package/src/__tests__/visibility-producer-matrix.integration.test.ts +20 -9
  467. package/src/assembly/atmosphere-shader-prewarm.ts +38 -0
  468. package/src/assembly/dynamic-geometry-host.ts +48 -0
  469. package/src/assembly/dynamic-geometry-runtime.ts +71 -0
  470. package/src/assembly/factory.ts +4 -0
  471. package/src/assembly/gpu-driven-owner.ts +29 -0
  472. package/src/assembly/host-contract.ts +10 -0
  473. package/src/assembly/material/assembly.ts +38 -0
  474. package/src/assembly/material-shader-policy.ts +71 -28
  475. package/src/assembly/recovery/failure-location.ts +62 -0
  476. package/src/assembly/recovery/renderer-recover.ts +19 -17
  477. package/src/assembly/renderer-facade.ts +12 -0
  478. package/src/assembly/renderer-feature-inspection.ts +13 -0
  479. package/src/assembly/renderer-frame-transaction.ts +63 -0
  480. package/src/assembly/renderer-helpers.ts +26 -0
  481. package/src/assembly/renderer-inspection.ts +6 -0
  482. package/src/assembly/shader-prewarm-policy.ts +32 -0
  483. package/src/assembly/skin-palette-owner.ts +21 -0
  484. package/src/assembly/ssr-shader-prewarm.ts +41 -0
  485. package/src/assembly/webgpu-device-loss.ts +75 -0
  486. package/src/assembly/webgpu-pbr-ready.ts +232 -0
  487. package/src/assembly/webgpu-ready-mipmap.ts +26 -0
  488. package/src/assembly/webgpu-ready.ts +142 -121
  489. package/src/assembly/webgpu-renderer-bootstrap.ts +85 -0
  490. package/src/assembly/webgpu-renderer-contract.ts +69 -0
  491. package/src/assembly/webgpu-renderer-guards.ts +32 -0
  492. package/src/assembly/webgpu-renderer-observation.ts +43 -0
  493. package/src/assembly/webgpu-renderer-recovery-failure.ts +62 -0
  494. package/src/assembly/webgpu-renderer.ts +666 -652
  495. package/src/assembly/webgpu-vertex-layouts.ts +233 -87
  496. package/src/components/__tests__/camera-exposure.contract.unit.test.ts +153 -0
  497. package/src/components/camera.ts +287 -7
  498. package/src/components/index.ts +1 -0
  499. package/src/components/instances.ts +45 -18
  500. package/src/components/scene-instance.ts +2 -1
  501. package/src/components/screen-space-reflection.ts +21 -0
  502. package/src/components/sprite-animation.ts +2 -2
  503. package/src/components/sprite-instances.ts +1 -1
  504. package/src/device/gpu-residency.ts +34 -31
  505. package/src/environment/background.ts +263 -0
  506. package/src/errors/gpu-driven.ts +39 -5
  507. package/src/errors/render.ts +27 -3
  508. package/src/extract/camera.ts +26 -1
  509. package/src/extract/gpu-driven.ts +185 -10
  510. package/src/features/host.ts +237 -19
  511. package/src/features/noise-texture.ts +91 -0
  512. package/src/features/plan.ts +553 -24
  513. package/src/features/prepared-gpu-work.ts +631 -49
  514. package/src/features/prepared-graphics-store.ts +31 -0
  515. package/src/features/prepared-graphics.ts +5 -0
  516. package/src/features/render-graph-compute.ts +41 -8
  517. package/src/features/render-graph-contribution.ts +141 -3
  518. package/src/features/render-graph-raster.ts +143 -30
  519. package/src/features/types.ts +11 -0
  520. package/src/fullscreen-post-process-pass.ts +3 -0
  521. package/src/gpu-driven/batch-topology.ts +424 -102
  522. package/src/gpu-driven/pbr-program.ts +31 -0
  523. package/src/gpu-driven/prepared-draw.ts +29 -4
  524. package/src/gpu-driven/production-raster.ts +2267 -694
  525. package/src/gpu-driven/shadow-views.ts +539 -0
  526. package/src/gpu-driven/view-gpu.ts +188 -41
  527. package/src/gpu-scene-schema.ts +52 -11
  528. package/src/gpu-scene.ts +186 -99
  529. package/src/gpu-texture-usage.ts +1 -0
  530. package/src/hdrp-buffers.ts +72 -36
  531. package/src/ibl/frame-resources.ts +38 -0
  532. package/src/ibl/skylight-bind-group.ts +5 -4
  533. package/src/index.ts +109 -1
  534. package/src/inspection-types.ts +191 -0
  535. package/src/instances-derived-bounds.ts +258 -34
  536. package/src/instances.ts +459 -150
  537. package/src/internal.ts +22 -0
  538. package/src/lifecycle.ts +2 -0
  539. package/src/material-row.ts +145 -0
  540. package/src/materials.ts +44 -8
  541. package/src/pbr-pipeline.ts +159 -17
  542. package/src/pipeline/standard-forward-lane.ts +52 -24
  543. package/src/pipeline/standard-output/__tests__/lut-admission.unit.test.ts +103 -0
  544. package/src/pipeline/standard-output/auto-exposure/__tests__/capability.unit.test.ts +40 -0
  545. package/src/pipeline/standard-output/auto-exposure/__tests__/gpu.contract.unit.test.ts +125 -0
  546. package/src/pipeline/standard-output/auto-exposure/__tests__/graph.contract.unit.test.ts +94 -0
  547. package/src/pipeline/standard-output/auto-exposure/__tests__/oracle.test.ts +108 -0
  548. package/src/pipeline/standard-output/auto-exposure/__tests__/preset.test.ts +26 -0
  549. package/src/pipeline/standard-output/auto-exposure/capability.ts +38 -0
  550. package/src/pipeline/standard-output/auto-exposure/gpu.ts +567 -0
  551. package/src/pipeline/standard-output/auto-exposure/graph.ts +221 -0
  552. package/src/pipeline/standard-output/auto-exposure/inspection.ts +231 -0
  553. package/src/pipeline/standard-output/auto-exposure/oracle.ts +291 -0
  554. package/src/pipeline/standard-output/auto-exposure/preset.ts +28 -0
  555. package/src/pipeline/standard-output/auto-exposure/state.ts +156 -0
  556. package/src/pipeline/standard-output/color-transform.ts +124 -0
  557. package/src/pipeline/standard-output/graph.ts +139 -0
  558. package/src/pipeline/standard-output/lut-admission.ts +121 -0
  559. package/src/pipeline/standard-output/lut-gpu.ts +399 -0
  560. package/src/pipeline/standard-output/lut-state.ts +176 -0
  561. package/src/pipeline/standard-output/resources.ts +121 -0
  562. package/src/pipeline/standard-output/types.ts +58 -0
  563. package/src/pipeline/standard-pipeline.ts +192 -59
  564. package/src/pipeline/standard-post.ts +186 -27
  565. package/src/pipeline-builder.ts +16 -6
  566. package/src/pipeline-spec-types.ts +4 -0
  567. package/src/pipeline-spec.ts +37 -29
  568. package/src/points-lines/standard-owner.ts +6 -14
  569. package/src/prepare/prepared-graphics-resolver.ts +9 -2
  570. package/src/record/__tests__/material-slot-plan.unit.test.ts +48 -0
  571. package/src/record/__tests__/mesh-ssbo.unit.test.ts +40 -1
  572. package/src/record/__tests__/recovery-pipeline.unit.test.ts +2 -29
  573. package/src/record/__tests__/sprite-material-abi.unit.test.ts +36 -5
  574. package/src/record/__tests__/typed-frame-graph-plan.unit.test.ts +45 -0
  575. package/src/record/__tests__/view-ubo-layout.unit.test.ts +5 -0
  576. package/src/record/dynamic-geometry-consumption.ts +59 -0
  577. package/src/record/frame-lighting.ts +12 -11
  578. package/src/record/frame-snapshot.ts +133 -23
  579. package/src/record/frame-targets.ts +9 -0
  580. package/src/record/frame.ts +725 -133
  581. package/src/record/main-pass-geometry.ts +149 -94
  582. package/src/record/main-pass-material.ts +115 -26
  583. package/src/record/main-pass-sprite-draws.ts +16 -107
  584. package/src/record/main-pass.ts +78 -8
  585. package/src/record/material-uniforms.ts +1 -1
  586. package/src/record/mesh-ssbo.ts +59 -15
  587. package/src/record/prepared-material-bindings.ts +234 -0
  588. package/src/record/recovery-pipeline.ts +59 -136
  589. package/src/record/render-context.ts +90 -22
  590. package/src/record/shadow-pass.ts +1231 -209
  591. package/src/record/skybox-post-pass.ts +14 -3
  592. package/src/record/typed-frame-graph.ts +1004 -44
  593. package/src/record/view-ubo.ts +11 -0
  594. package/src/recovery/render-system-candidate.ts +90 -66
  595. package/src/recovery/render-system-roots.ts +113 -0
  596. package/src/recovery/types.ts +77 -0
  597. package/src/reflection/record-owner.ts +26 -54
  598. package/src/render-contract.ts +195 -25
  599. package/src/render-graph-primitives.ts +6 -2
  600. package/src/render-pipeline.ts +70 -1
  601. package/src/render-system-extract-tail.ts +409 -160
  602. package/src/render-system-extract.ts +376 -102
  603. package/src/render-system-projections.ts +241 -0
  604. package/src/render-system.ts +681 -822
  605. package/src/scene/render-scene-types.ts +13 -4
  606. package/src/scene/render-scene.ts +569 -355
  607. package/src/scene/shadow-visibility.ts +21 -0
  608. package/src/ssr/admission.ts +380 -14
  609. package/src/ssr/compose.ts +144 -0
  610. package/src/ssr/errors.ts +48 -0
  611. package/src/ssr/graph.ts +777 -0
  612. package/src/ssr/history.ts +436 -0
  613. package/src/ssr/hiz.ts +124 -0
  614. package/src/ssr/inspection.ts +124 -0
  615. package/src/ssr/resources.ts +202 -0
  616. package/src/ssr/temporal.ts +164 -0
  617. package/src/systems/skin-palette-allocator.ts +495 -148
  618. package/src/systems/skin-palette-types.ts +17 -0
  619. package/src/systems/transparent-dispatch.ts +90 -0
  620. package/src/targets/material-source.ts +34 -0
  621. package/src/temporal/coverage.ts +1 -1
  622. package/src/temporal/frame-coordinator.ts +23 -2
  623. package/src/temporal/gpu.ts +74 -12
  624. package/src/temporal/history.ts +3 -1
  625. package/src/temporal/inspection.ts +2 -1
  626. package/src/temporal/standard-scene-data.ts +3 -1
  627. package/src/temporal/temporal-view.ts +3 -0
  628. package/src/transmission/__tests__/standard-transmission.dawn.test.ts +204 -139
  629. package/src/typed-render-graph-primitives.ts +266 -74
  630. package/src/typed-shadow-passes.ts +122 -12
  631. package/src/volume/temporal.ts +10 -0
  632. package/dist/chunk-GE3SDD72.mjs.map +0 -1
  633. package/dist/chunk-GF523LHF.mjs.map +0 -1
  634. package/dist/chunk-IQMKJLNU.mjs.map +0 -1
  635. package/dist/chunk-J52NMQKR.mjs.map +0 -1
  636. package/dist/chunk-J6KACNOH.mjs.map +0 -1
  637. package/dist/chunk-JAASCMXA.mjs.map +0 -1
  638. package/dist/chunk-MBU4ZVE7.mjs.map +0 -1
  639. package/dist/chunk-RBDBZ5R7.mjs.map +0 -1
  640. package/dist/chunk-VSL23LZE.mjs.map +0 -1
  641. package/src/__tests__/instances-world-ownership.integration.test.ts +0 -62
  642. package/src/scene/__tests__/changed-block-summary.candidate.test.ts +0 -36
@@ -1,5 +1,10 @@
1
1
  import { describe, expectTypeOf, it } from 'vitest';
2
2
  import type { RenderError } from '../errors/render';
3
+ import type {
4
+ AutoExposureError,
5
+ AutoExposureErrorCode,
6
+ AutoExposureErrorDetail,
7
+ } from '../pipeline/standard-output/auto-exposure/inspection';
3
8
  import type {
4
9
  FullscreenInputNotFoundDetail,
5
10
  PostProcessErrorCode,
@@ -15,6 +20,16 @@ import type {
15
20
  } from '../post-process-errors';
16
21
  import { PostProcessError } from '../post-process-errors';
17
22
 
23
+ type ExpectedAutoExposureCodes =
24
+ | 'auto-exposure-invalid-parameter'
25
+ | 'auto-exposure-capability-unavailable'
26
+ | 'auto-exposure-stale-generation'
27
+ | 'auto-exposure-stage-failed';
28
+ type AutoExposureDetailFor<C extends AutoExposureErrorCode> = Extract<
29
+ AutoExposureError,
30
+ { readonly code: C }
31
+ >['detail'];
32
+
18
33
  type ExpectedPostProcessDetails = {
19
34
  readonly 'post-process-already-registered': PostProcessPreviouslyRegisteredDetail;
20
35
  readonly 'post-process-not-found': PostProcessNotFoundDetail;
@@ -32,6 +47,13 @@ type DetailFor<C extends PostProcessErrorCode> = Extract<
32
47
  >['detail'];
33
48
 
34
49
  describe('render error detail aliases derive from their code resolvers', () => {
50
+ it('keeps auto-exposure error codes and code/detail correlation closed', () => {
51
+ expectTypeOf<AutoExposureErrorCode>().toEqualTypeOf<ExpectedAutoExposureCodes>();
52
+ expectTypeOf<AutoExposureErrorCode>().toEqualTypeOf<AutoExposureError['code']>();
53
+ expectTypeOf<AutoExposureErrorDetail>().toEqualTypeOf<
54
+ AutoExposureDetailFor<AutoExposureErrorCode>
55
+ >();
56
+ });
35
57
  it('preserves the complete post-process detail union', () => {
36
58
  expectTypeOf<PostProcessErrorDetail>().toEqualTypeOf<
37
59
  PostProcessErrorDetailFor<PostProcessErrorCode>
@@ -11,6 +11,20 @@ import type {
11
11
  VertexColorVariantConflictError,
12
12
  } from '../errors/render';
13
13
  import type { RenderFeatureErrorCode } from '../features/types';
14
+ import type { AutoExposureErrorCode } from '../pipeline/standard-output/auto-exposure/inspection';
15
+
16
+ function autoExposureCodeLabel(code: AutoExposureErrorCode): string {
17
+ switch (code) {
18
+ case 'auto-exposure-invalid-parameter':
19
+ return code;
20
+ case 'auto-exposure-capability-unavailable':
21
+ return code;
22
+ case 'auto-exposure-stale-generation':
23
+ return code;
24
+ case 'auto-exposure-stage-failed':
25
+ return code;
26
+ }
27
+ }
14
28
 
15
29
  function renderFeatureCodeLabel(code: RenderFeatureErrorCode): string {
16
30
  switch (code) {
@@ -111,3 +125,4 @@ const taaErrorCode: RenderErrorCode = 'taa-unavailable';
111
125
  void taaErrorCode;
112
126
  void renderCodeLabel;
113
127
  void renderFeatureErrorLabel;
128
+ void autoExposureCodeLabel;
@@ -10,14 +10,109 @@ import {
10
10
  import { rhi } from '@forgeax/engine-rhi-null';
11
11
  import { err, ok } from '@forgeax/engine-types';
12
12
  import { describe, expect, it } from 'vitest';
13
+ import type { RenderFeatureGpuProgramDescriptor } from '../features/prepared-gpu-work';
13
14
  import { createRenderFeatureGpuWorkOwner } from '../features/prepared-gpu-work';
14
15
  import { RenderFeatureComputeGraphProjection } from '../features/render-graph-compute';
15
16
  import { RenderFeatureRasterGraphProjection } from '../features/render-graph-raster';
16
17
  import { createRenderFeatureGraphBufferState } from '../features/render-graph-resources';
18
+ import { createRenderFeatureTarget } from '../features/targets';
17
19
  import type { PreparedGraphicsResolvedResource } from '../prepare/prepared-graphics-resolver';
18
20
 
19
21
  describe('RenderFeature persistent GPU work', () => {
20
- it('projects compute output and indirect raster consumption into one typed graph', async () => {
22
+ it('binds graph-owned sampled textures at execution instead of the preparation fallback', async () => {
23
+ const adapter = (await rhi.requestAdapter()).unwrap();
24
+ const device = (await adapter.requestDevice()).unwrap();
25
+ const shader = (await rhi.createShaderModule(device, { code: 'synthetic' })).unwrap();
26
+ const owner = createRenderFeatureGpuWorkOwner({
27
+ getDevice: () => device,
28
+ getShaderModuleFactory: () => ({ createShaderModule: () => ok(shader) }),
29
+ });
30
+ const session = owner.beginFeature('sampled-compute', 0);
31
+ const program = session
32
+ .prepareProgram('sample', {
33
+ wgsl: 'synthetic',
34
+ entryPoints: ['sample'],
35
+ bindings: [{ entries: [{ binding: 0, visibility: 4, texture: { sampleType: 'depth' } }] }],
36
+ })
37
+ .unwrap();
38
+ const texture = device
39
+ .createTexture({
40
+ size: { width: 1, height: 1 },
41
+ format: 'depth32float',
42
+ usage: 0x04 | 0x10,
43
+ textureBindingViewDimension: undefined,
44
+ })
45
+ .unwrap();
46
+ const fallback = device.createTextureView(texture, {}).unwrap();
47
+ const reference = session
48
+ .prepareTextureView(
49
+ 'depth',
50
+ fallback,
51
+ createRenderFeatureTarget({ kind: 'scene-depth', format: 'depth32float', sampleCount: 1 }),
52
+ )
53
+ .unwrap();
54
+ const bindings = session
55
+ .prepareBindings('bindings', {
56
+ program,
57
+ entries: [{ binding: 0, resource: { kind: 'texture-view', reference } }],
58
+ })
59
+ .unwrap();
60
+ const work = session
61
+ .resolveComputePass('sampled-compute', {
62
+ program,
63
+ bindings,
64
+ dispatches: [{ entryPoint: 'sample', workgroups: [1] }],
65
+ })
66
+ .unwrap();
67
+ expect(work.sampledTargets).toMatchObject([
68
+ { kind: 'scene-depth', format: 'depth32float', sampleCount: 1 },
69
+ ]);
70
+ const graph = new RenderGraphBuilder<{ readonly encoder: RhiCommandEncoder }>();
71
+ const depth = graph
72
+ .createTexture('real-depth', {
73
+ format: 'depth32float',
74
+ size: { width: 1, height: 1 },
75
+ })
76
+ .unwrap();
77
+ const view = graph.view(depth).unwrap();
78
+ graph
79
+ .addRasterPass('depth-producer', {
80
+ colorAttachments: [],
81
+ depthStencilAttachment: {
82
+ view,
83
+ depthLoadOp: 'clear',
84
+ depthStoreOp: 'store',
85
+ depthClearValue: 0.25,
86
+ },
87
+ accesses: [{ resource: view, usage: 'depth-stencil-write' }],
88
+ encode: () => {},
89
+ })
90
+ .unwrap();
91
+ const seen: unknown[] = [];
92
+ const createBindGroup = device.createBindGroup.bind(device);
93
+ device.createBindGroup = (descriptor) => {
94
+ seen.push([...descriptor.entries][0]?.resource);
95
+ return createBindGroup(descriptor);
96
+ };
97
+ const projection = new RenderFeatureComputeGraphProjection(graph, undefined, undefined, () => ({
98
+ texture: depth,
99
+ view,
100
+ }));
101
+ projection.addPass('sample', 'sampled-compute', 0, work).unwrap();
102
+ const compiled = graph.compile({ device, surfaceSize: { width: 1, height: 1 } }).unwrap();
103
+ expect(compiled.inspect().passes[1]?.dependencies).toEqual(['depth-producer']);
104
+ compiled.execute({ encoder: device.createCommandEncoder().unwrap() }).unwrap();
105
+ expect(seen).toHaveLength(1);
106
+ expect(seen[0]).not.toEqual({ kind: 'textureView', value: fallback });
107
+ await compiled.retire();
108
+ owner.dispose().unwrap();
109
+ });
110
+ it.each([
111
+ [1, false],
112
+ [4, false],
113
+ [1, true],
114
+ [4, true],
115
+ ] as const)('projects compute and indirect raster consumption with %i samples, Standard lighting %s', async (sampleCount, standardLighting) => {
21
116
  const adapter = (await rhi.requestAdapter()).unwrap();
22
117
  const device = (await adapter.requestDevice()).unwrap();
23
118
  const indirectBuffer = device
@@ -28,6 +123,18 @@ describe('RenderFeature persistent GPU work', () => {
28
123
  .createTexture('output', { format: 'rgba8unorm', size: { width: 1, height: 1 } })
29
124
  .unwrap();
30
125
  const outputView = graph.view(output, { label: 'output.view' }).unwrap();
126
+ const multisample =
127
+ sampleCount === 1
128
+ ? undefined
129
+ : graph
130
+ .createTexture('multisample', {
131
+ format: 'rgba8unorm',
132
+ size: { width: 1, height: 1 },
133
+ sampleCount,
134
+ })
135
+ .unwrap();
136
+ const multisampleView =
137
+ multisample === undefined ? undefined : graph.view(multisample).unwrap();
31
138
  const buffers = createRenderFeatureGraphBufferState();
32
139
  const compute = new RenderFeatureComputeGraphProjection(graph, buffers);
33
140
  expect(
@@ -62,6 +169,7 @@ describe('RenderFeature persistent GPU work', () => {
62
169
  kind: 'pipeline',
63
170
  reference: pipeline as never,
64
171
  handle: {} as RenderPipeline,
172
+ standardLighting,
65
173
  },
66
174
  ],
67
175
  [
@@ -83,11 +191,18 @@ describe('RenderFeature persistent GPU work', () => {
83
191
  },
84
192
  ],
85
193
  ]);
194
+ const lightingGroup = {} as BindGroup;
86
195
  const raster = new RenderFeatureRasterGraphProjection(
87
196
  graph,
88
- () => ({ texture: output, view: outputView }),
197
+ () => ({
198
+ texture: multisample ?? output,
199
+ view: multisampleView ?? outputView,
200
+ ...(multisampleView === undefined ? {} : { resolveTarget: outputView }),
201
+ }),
89
202
  undefined,
90
203
  buffers,
204
+ undefined,
205
+ () => lightingGroup,
91
206
  );
92
207
  expect(
93
208
  raster.addPass(
@@ -143,7 +258,21 @@ describe('RenderFeature persistent GPU work', () => {
143
258
  { name: 'vfx.draw', kind: 'raster', dependencies: ['vfx.simulate-and-project'] },
144
259
  ]);
145
260
  const encoder = device.createCommandEncoder({ label: 'vfx-mixed-frame' }).unwrap();
261
+ const bound: Array<{ index: number; group: BindGroup }> = [];
262
+ const begin = encoder.beginRenderPass.bind(encoder);
263
+ encoder.beginRenderPass = (descriptor) => {
264
+ const pass = begin(descriptor);
265
+ const set = pass.setBindGroup.bind(pass);
266
+ pass.setBindGroup = (index, group, offsets) => {
267
+ bound.push({ index, group });
268
+ return set(index, group, offsets === undefined ? undefined : Array.from(offsets));
269
+ };
270
+ return pass;
271
+ };
146
272
  expect(compiled.execute({ encoder }).ok).toBe(true);
273
+ expect(bound.filter((entry) => entry.index === 2)).toEqual(
274
+ standardLighting ? [{ index: 2, group: lightingGroup }] : [],
275
+ );
147
276
  expect(encoder.finish().ok).toBe(true);
148
277
  });
149
278
 
@@ -227,6 +356,65 @@ describe('RenderFeature persistent GPU work', () => {
227
356
  expect(owner.dispose().ok).toBe(true);
228
357
  });
229
358
 
359
+ it('reuses equivalent prepared programs but rejects nested descriptor mutation', async () => {
360
+ const adapter = (await rhi.requestAdapter()).unwrap();
361
+ const device = (await adapter.requestDevice()).unwrap();
362
+ const shader = (await rhi.createShaderModule(device, { code: 'synthetic' })).unwrap();
363
+ const owner = createRenderFeatureGpuWorkOwner({
364
+ getDevice: () => device,
365
+ getShaderModuleFactory: () => ({ createShaderModule: () => ok(shader) }),
366
+ });
367
+ const resolver = owner.beginFeature('synthetic.program', 0);
368
+ const descriptor = {
369
+ wgsl: 'synthetic',
370
+ entryPoints: ['simulate'],
371
+ bindings: [
372
+ {
373
+ entries: [
374
+ {
375
+ binding: 0,
376
+ visibility: 0x4,
377
+ buffer: { type: 'storage', hasDynamicOffset: false, minBindingSize: 0 },
378
+ },
379
+ ],
380
+ },
381
+ ],
382
+ } satisfies RenderFeatureGpuProgramDescriptor;
383
+ const first = resolver.prepareProgram('program', descriptor).unwrap();
384
+ const equivalent = resolver.prepareProgram('program', {
385
+ wgsl: 'synthetic',
386
+ entryPoints: ['simulate'],
387
+ bindings: [
388
+ {
389
+ entries: [
390
+ {
391
+ binding: 0,
392
+ visibility: 0x4,
393
+ buffer: { type: 'storage', hasDynamicOffset: false, minBindingSize: 0 },
394
+ },
395
+ ],
396
+ },
397
+ ],
398
+ });
399
+ expect(equivalent).toMatchObject({ ok: true, value: first });
400
+
401
+ const firstBinding = descriptor.bindings[0];
402
+ if (firstBinding === undefined) throw new Error('fixture missing binding');
403
+ const firstEntry = firstBinding.entries[0];
404
+ if (firstEntry === undefined) throw new Error('fixture missing binding entry');
405
+ const mutableEntry = firstEntry as { visibility: number };
406
+ mutableEntry.visibility = 0x1;
407
+ const changed = resolver.prepareProgram('program', descriptor);
408
+ expect(changed).toMatchObject({
409
+ ok: false,
410
+ error: {
411
+ code: 'render-feature-preparation-failed',
412
+ detail: { reason: 'signature-mismatch' },
413
+ },
414
+ });
415
+ expect(owner.dispose().ok).toBe(true);
416
+ });
417
+
230
418
  it('records compute against persistent storage and releases it once', async () => {
231
419
  const adapter = (await rhi.requestAdapter()).unwrap();
232
420
  const device = (await adapter.requestDevice()).unwrap();
@@ -352,9 +540,104 @@ describe('RenderFeature persistent GPU work', () => {
352
540
  const retired = resolver.retireUntouched();
353
541
  expect(retired).toHaveLength(1);
354
542
  expect(resolver.resolveBuffer(storage)).toBeUndefined();
543
+ // A non-VFX feature retires all three resource kinds together. The
544
+ // binding/program indexes must be invalidated at the same boundary as the
545
+ // buffer; otherwise a stale compute descriptor can revive work that now
546
+ // references a destroyed buffer.
547
+ expect(resolver.retainBindings([bindings]).ok).toBe(false);
548
+ expect(resolver.resolveComputePass('synthetic.gpu', computeDescriptor).ok).toBe(false);
355
549
  expect(retired[0]?.release().ok).toBe(true);
356
550
  expect(retired[0]?.release().ok).toBe(true);
551
+ expect(resolver.retainBindings([bindings]).ok).toBe(false);
552
+ expect(resolver.resolveComputePass('synthetic.gpu', computeDescriptor).ok).toBe(false);
553
+
554
+ const replacementProgram = resolver
555
+ .prepareProgram('program', {
556
+ wgsl: 'synthetic',
557
+ entryPoints: ['simulate', 'compact'],
558
+ bindings: [
559
+ {
560
+ entries: [
561
+ {
562
+ binding: 0,
563
+ visibility: 0x4,
564
+ buffer: { type: 'storage', hasDynamicOffset: false, minBindingSize: 0 },
565
+ },
566
+ {
567
+ binding: 1,
568
+ visibility: 0x4,
569
+ buffer: { type: 'storage', hasDynamicOffset: false, minBindingSize: 0 },
570
+ },
571
+ ],
572
+ },
573
+ ],
574
+ })
575
+ .unwrap();
576
+ const replacementStorage = resolver
577
+ .prepareBuffer('particles', {
578
+ size: 4096,
579
+ usage: ['storage', 'vertex'],
580
+ data: new Uint32Array([5, 6, 7, 8]),
581
+ })
582
+ .unwrap();
583
+ const replacementIndirect = resolver
584
+ .prepareBuffer('indirect', {
585
+ size: 20,
586
+ usage: ['storage', 'indirect'],
587
+ data: new Uint32Array([6, 0, 0, 0, 0]),
588
+ })
589
+ .unwrap();
590
+ const replacementBindings = resolver
591
+ .prepareBindings('bindings', {
592
+ program: replacementProgram,
593
+ entries: [
594
+ { binding: 0, buffer: replacementStorage },
595
+ { binding: 1, buffer: replacementIndirect },
596
+ ],
597
+ })
598
+ .unwrap();
599
+ expect(replacementProgram).not.toBe(program);
600
+ expect(replacementBindings).not.toBe(bindings);
601
+ expect(resolver.retainBindings([bindings]).ok).toBe(false);
357
602
  expect(resolver.dispose().ok).toBe(true);
358
603
  expect(resolver.dispose().ok).toBe(true);
359
604
  });
605
+
606
+ it('parks short-lived VFX queue slots and revives the same allocation', async () => {
607
+ const adapter = (await rhi.requestAdapter()).unwrap();
608
+ const device = (await adapter.requestDevice()).unwrap();
609
+ const owner = createRenderFeatureGpuWorkOwner({
610
+ getDevice: () => device,
611
+ getShaderModuleFactory: () => ({ createShaderModule: () => ok({} as never) }),
612
+ });
613
+ const session = owner.beginFeature('forgeax.vfx-render.gpu-particles', 0);
614
+ const first = session
615
+ .prepareBuffer('queued-slot', { size: 16, usage: ['storage'], data: new Uint32Array([1]) })
616
+ .unwrap();
617
+ expect(session.retireUntouched()).toEqual([]);
618
+
619
+ // One missing fixed tick parks the slot instead of retiring its physical
620
+ // buffer. Re-publishing the same named slot must revive the exact ref.
621
+ session.beginFrame();
622
+ expect(session.retireUntouched()).toEqual([]);
623
+ session.beginFrame();
624
+ const revived = session
625
+ .prepareBuffer('queued-slot', { size: 16, usage: ['storage'], data: new Uint32Array([2]) })
626
+ .unwrap();
627
+ expect(revived).toBe(first);
628
+ expect(session.resolveBuffer(first)).toBeDefined();
629
+ expect(session.retireUntouched()).toEqual([]);
630
+
631
+ // Parking is bounded. After the window, the old allocation is retired via
632
+ // the normal lease path and cannot be resolved by a stale reference.
633
+ let retired = [] as ReturnType<typeof session.retireUntouched>;
634
+ for (let index = 0; index < 9; index += 1) {
635
+ session.beginFrame();
636
+ retired = session.retireUntouched();
637
+ }
638
+ expect(retired).toHaveLength(1);
639
+ expect(session.resolveBuffer(first)).toBeUndefined();
640
+ expect(retired[0]?.release().ok).toBe(true);
641
+ expect(owner.dispose().ok).toBe(true);
642
+ });
360
643
  });
@@ -2,10 +2,14 @@ import { ok } from '@forgeax/engine-types';
2
2
  import { describe, expect, it, vi } from 'vitest';
3
3
  import { createRenderFeatureHost, runRenderFeatureFrame } from '../features/host';
4
4
  import {
5
+ cloneRenderFeaturePlanSignatureSnapshot,
5
6
  deriveRenderFeaturePassAccess,
6
7
  freezeRenderFeaturePlan,
7
8
  type RenderFeaturePlan,
9
+ rememberRenderFeaturePlanSignature,
8
10
  renderFeaturePlanSignature,
11
+ renderFeaturePlanSignatureEvidenceMatches,
12
+ renderFeaturePlanSignatureSnapshotEquals,
9
13
  } from '../features/plan';
10
14
  import type { RenderFeature } from '../features/types';
11
15
 
@@ -103,6 +107,33 @@ const plan = (data = new Uint32Array([1, 2, 3, 4])) =>
103
107
  }) satisfies RenderFeaturePlan;
104
108
 
105
109
  describe('RenderFeature plan authority', () => {
110
+ it('validates shadow draws without author-owned light targets and derives their buffer reads', () => {
111
+ const source = plan();
112
+ const raster = source.passes[1];
113
+ if (raster?.kind !== 'raster') throw new Error('Missing fixture draw');
114
+ const shadow: RenderFeaturePlan = {
115
+ resources: source.resources.map((resource) =>
116
+ resource.kind === 'graphics-program'
117
+ ? {
118
+ ...resource,
119
+ program: { ...resource.program, colorFormats: [], depthFormat: 'depth32float' },
120
+ }
121
+ : resource,
122
+ ),
123
+ passes: [{ kind: 'shadow-caster', name: 'shadow', draws: raster.draws }],
124
+ };
125
+ expect(freezeRenderFeaturePlan('shadow', shadow).ok).toBe(true);
126
+ const shadowPass = shadow.passes[0];
127
+ if (shadowPass === undefined) throw new Error('Missing shadow fixture');
128
+ expect(deriveRenderFeaturePassAccess(shadow, shadowPass)).toEqual([
129
+ { resource: 'particles', usage: 'vertex-read' },
130
+ { resource: 'indirect', usage: 'indirect-read' },
131
+ ]);
132
+ expect(
133
+ freezeRenderFeaturePlan('invalid-shadow', { ...shadow, resources: source.resources }).ok,
134
+ ).toBe(false);
135
+ expect(renderFeaturePlanSignature(shadow)).not.toBe(renderFeaturePlanSignature(source));
136
+ });
106
137
  it('derives compute and raster access from descriptors', () => {
107
138
  const value = plan();
108
139
  expect(deriveRenderFeaturePassAccess(value, value.passes[0] as never)).toEqual([
@@ -122,6 +153,217 @@ describe('RenderFeature plan authority', () => {
122
153
  );
123
154
  });
124
155
 
156
+ it('accounts typed-array signature work per feature without changing identity', () => {
157
+ const metrics = { calls: 0, typedArrayBytes: 0, outputChars: 0 };
158
+ const first = renderFeaturePlanSignature(plan(new Uint32Array([1, 2])), metrics);
159
+ const second = renderFeaturePlanSignature(plan(new Uint32Array([3, 4])), metrics);
160
+ expect(first).toBe(second);
161
+ // Buffer payloads stay out of the topology identity, but the detached
162
+ // accounting still reports the bytes examined on both validation calls.
163
+ expect(metrics).toMatchObject({ calls: 2, typedArrayBytes: 16 });
164
+ expect(metrics.outputChars).toBeGreaterThan(0);
165
+ });
166
+
167
+ it('keeps vertex and index payloads in the canonical producer signature', () => {
168
+ const vertex = (value: number): RenderFeaturePlan => ({
169
+ resources: [
170
+ {
171
+ kind: 'vertex-data',
172
+ name: 'vertices',
173
+ layout: 'position',
174
+ data: new Float32Array([value]),
175
+ },
176
+ ],
177
+ passes: [],
178
+ });
179
+ const index = (value: number): RenderFeaturePlan => ({
180
+ resources: [
181
+ {
182
+ kind: 'index-data',
183
+ name: 'indices',
184
+ format: 'uint16',
185
+ data: new Uint16Array([value]),
186
+ },
187
+ ],
188
+ passes: [],
189
+ });
190
+ expect(renderFeaturePlanSignature(vertex(1))).not.toBe(renderFeaturePlanSignature(vertex(2)));
191
+ expect(renderFeaturePlanSignature(index(1))).not.toBe(renderFeaturePlanSignature(index(2)));
192
+ });
193
+
194
+ it('encodes every inline payload byte exactly across views and slices', () => {
195
+ const bytes = new Uint8Array([0, 1, 2, 15, 16, 127, 128, 254, 255]);
196
+ const sliced = bytes.subarray(2, 8);
197
+ const shared = new Uint16Array(sliced.buffer, sliced.byteOffset, 3);
198
+ const vertex = (data: ArrayBufferView): RenderFeaturePlan => ({
199
+ resources: [{ kind: 'vertex-data', name: 'vertices', layout: 'position', data }],
200
+ passes: [],
201
+ });
202
+ expect(renderFeaturePlanSignature(vertex(sliced))).toBe(
203
+ renderFeaturePlanSignature(vertex(shared)),
204
+ );
205
+ const mutated = new Uint8Array(sliced);
206
+ const original = mutated[3];
207
+ if (original === undefined) throw new Error('Missing mutation byte');
208
+ mutated[3] = original ^ 0x01;
209
+ expect(renderFeaturePlanSignature(vertex(mutated))).not.toBe(
210
+ renderFeaturePlanSignature(vertex(sliced)),
211
+ );
212
+ expect(renderFeaturePlanSignature(vertex(new Uint8Array()))).not.toBe(
213
+ renderFeaturePlanSignature(vertex(new Uint8Array([0]))),
214
+ );
215
+ });
216
+
217
+ it('matches detached snapshots without treating buffer uploads as topology changes', () => {
218
+ const first = plan(new Uint32Array([1, 2]));
219
+ const snapshot = cloneRenderFeaturePlanSignatureSnapshot(first);
220
+ expect(renderFeaturePlanSignatureSnapshotEquals(plan(new Uint32Array([9, 8])), snapshot)).toBe(
221
+ true,
222
+ );
223
+
224
+ const vertex = {
225
+ resources: [
226
+ {
227
+ kind: 'vertex-data',
228
+ name: 'vertices',
229
+ layout: 'position',
230
+ data: new Float32Array([1]),
231
+ },
232
+ ],
233
+ passes: [],
234
+ } satisfies RenderFeaturePlan;
235
+ const vertexSnapshot = cloneRenderFeaturePlanSignatureSnapshot(vertex);
236
+ (vertex.resources[0] as { data: Float32Array }).data[0] = 2;
237
+ expect(renderFeaturePlanSignatureSnapshotEquals(vertex, vertexSnapshot)).toBe(false);
238
+ });
239
+
240
+ it('shares unchanged detached subtrees across a changed plan revision', () => {
241
+ const first = plan();
242
+ const firstSnapshot = cloneRenderFeaturePlanSignatureSnapshot(first);
243
+ const changed: RenderFeaturePlan = {
244
+ resources: first.resources.map((resource, index) =>
245
+ index === 3 && resource.kind === 'buffer'
246
+ ? { ...resource, size: resource.size + 16 }
247
+ : resource,
248
+ ),
249
+ passes: first.passes.map((pass, index) =>
250
+ index === 0 && pass.kind === 'compute' ? { ...pass, name: 'simulate-next' } : pass,
251
+ ),
252
+ };
253
+
254
+ const nextSnapshot = cloneRenderFeaturePlanSignatureSnapshot(changed, firstSnapshot);
255
+ expect(nextSnapshot).not.toBe(firstSnapshot);
256
+ expect(nextSnapshot.resources[0]).toBe(firstSnapshot.resources[0]);
257
+ expect(nextSnapshot.resources[1]).toBe(firstSnapshot.resources[1]);
258
+ expect(nextSnapshot.resources[2]).toBe(firstSnapshot.resources[2]);
259
+ expect(nextSnapshot.resources[3]).not.toBe(firstSnapshot.resources[3]);
260
+ expect(nextSnapshot.resources[4]).toBe(firstSnapshot.resources[4]);
261
+ expect(nextSnapshot.passes).not.toBe(firstSnapshot.passes);
262
+ if (nextSnapshot.passes.kind !== 'array' || firstSnapshot.passes.kind !== 'array') return;
263
+ expect(nextSnapshot.passes.values[1]).toBe(firstSnapshot.passes.values[1]);
264
+ expect(renderFeaturePlanSignatureSnapshotEquals(first, firstSnapshot)).toBe(true);
265
+ expect(renderFeaturePlanSignatureSnapshotEquals(changed, nextSnapshot)).toBe(true);
266
+ });
267
+
268
+ it('reuses the prior snapshot for buffer-upload-only changes', () => {
269
+ const first = plan(new Uint32Array([1, 2]));
270
+ const firstSnapshot = cloneRenderFeaturePlanSignatureSnapshot(first);
271
+ const nextSnapshot = cloneRenderFeaturePlanSignatureSnapshot(
272
+ plan(new Uint32Array([9, 8])),
273
+ firstSnapshot,
274
+ );
275
+ expect(nextSnapshot).toBe(firstSnapshot);
276
+ });
277
+
278
+ it('does not share changed inline payload bytes', () => {
279
+ const vertex = (value: number): RenderFeaturePlan => ({
280
+ resources: [
281
+ {
282
+ kind: 'vertex-data',
283
+ name: 'vertices',
284
+ layout: 'position',
285
+ data: new Float32Array([value]),
286
+ },
287
+ ],
288
+ passes: [],
289
+ });
290
+ const first = vertex(1);
291
+ const firstSnapshot = cloneRenderFeaturePlanSignatureSnapshot(first);
292
+ const nextSnapshot = cloneRenderFeaturePlanSignatureSnapshot(vertex(2), firstSnapshot);
293
+ expect(nextSnapshot).not.toBe(firstSnapshot);
294
+ expect(nextSnapshot.resources[0]).not.toBe(firstSnapshot.resources[0]);
295
+ expect(renderFeaturePlanSignatureSnapshotEquals(vertex(2), nextSnapshot)).toBe(true);
296
+ expect(renderFeaturePlanSignatureSnapshotEquals(first, firstSnapshot)).toBe(true);
297
+ });
298
+
299
+ it('detects nested mutation after evidence is remembered', () => {
300
+ const value = {
301
+ resources: [
302
+ {
303
+ kind: 'vertex-data',
304
+ name: 'vertices',
305
+ layout: 'position',
306
+ data: new Float32Array([1]),
307
+ },
308
+ ],
309
+ passes: [],
310
+ } satisfies RenderFeaturePlan;
311
+ const signature = renderFeaturePlanSignature(value);
312
+ const snapshot = cloneRenderFeaturePlanSignatureSnapshot(value);
313
+ rememberRenderFeaturePlanSignature(value, signature, snapshot);
314
+ expect(renderFeaturePlanSignatureEvidenceMatches(value, signature)).toBe(true);
315
+ (value.resources[0] as { data: Float32Array }).data[0] = 2;
316
+ expect(renderFeaturePlanSignatureEvidenceMatches(value, signature)).toBe(false);
317
+ });
318
+
319
+ it('records host cache hits separately from canonical signature calls', () => {
320
+ const feature: RenderFeature<undefined> = {
321
+ identity: 'synthetic.signature-cache',
322
+ extract: () => ok(undefined),
323
+ plan: () => ok({ resources: [], passes: [] }),
324
+ };
325
+ const host = createRenderFeatureHost([feature]);
326
+ expect(host.ok).toBe(true);
327
+ if (!host.ok || host.value.recordPlanSignature === undefined) return;
328
+
329
+ const empty: RenderFeaturePlan = { resources: [], passes: [] };
330
+ host.value.recordPlanSignature(feature.identity, empty);
331
+ host.value.recordPlanSignature(feature.identity, { resources: [], passes: [] });
332
+ expect(host.value.inspection?.().signatures).toMatchObject([
333
+ { featureIdentity: feature.identity, calls: 1, cacheHits: 1, cacheMisses: 1 },
334
+ ]);
335
+ });
336
+
337
+ it('does not request a graph revision for retired GPU leases that the candidate does not use', () => {
338
+ const feature: RenderFeature<undefined> = {
339
+ identity: 'synthetic.retired-gpu-lease',
340
+ extract: () => ok(undefined),
341
+ plan: () => ok({ resources: [], passes: [] }),
342
+ };
343
+ const host = createRenderFeatureHost([feature]);
344
+ expect(host.ok).toBe(true);
345
+ if (!host.ok) return;
346
+ const release = vi.fn(() => ok(undefined));
347
+ const gpuWork = {
348
+ beginFeature: () => ({
349
+ retireUntouched: () => [{ release }],
350
+ }),
351
+ } as never;
352
+
353
+ const result = runRenderFeatureFrame(host.value, {
354
+ worlds: [],
355
+ owner: 0,
356
+ frameNumber: 1,
357
+ generation: 1,
358
+ caps: {} as never,
359
+ gpuWork,
360
+ });
361
+
362
+ expect(result.errors).toEqual([]);
363
+ expect(result.preparedResourceBatches).toHaveLength(1);
364
+ expect(result.requiresPreparedResourceKey).toBe(false);
365
+ });
366
+
125
367
  it('rejects descriptor references that do not exist', () => {
126
368
  const invalid: RenderFeaturePlan = {
127
369
  resources: [],
@@ -164,6 +406,9 @@ describe('RenderFeature plan authority', () => {
164
406
  expect(result.plans).toHaveLength(1);
165
407
  expect(result.plans[0]).not.toHaveProperty('execution');
166
408
  expect(result.stageEvents.map((event) => event.stage)).toEqual(['extract', 'plan']);
409
+ expect(host.value.inspection?.().signatures).toMatchObject([
410
+ { featureIdentity: 'synthetic.plan-only', calls: 1 },
411
+ ]);
167
412
  expect(prepare).not.toHaveBeenCalled();
168
413
  expect(contribute).not.toHaveBeenCalled();
169
414
  });