@forgeax/engine-render 0.1.28 → 0.1.30

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 (768) hide show
  1. package/README.md +856 -126
  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/device-feature-admission.d.ts +4 -3
  5. package/dist/assembly/device-feature-admission.d.ts.map +1 -1
  6. package/dist/assembly/dynamic-geometry-host.d.ts +27 -0
  7. package/dist/assembly/dynamic-geometry-host.d.ts.map +1 -1
  8. package/dist/assembly/dynamic-geometry-runtime.d.ts +20 -0
  9. package/dist/assembly/dynamic-geometry-runtime.d.ts.map +1 -0
  10. package/dist/assembly/factory.d.ts +3 -1
  11. package/dist/assembly/factory.d.ts.map +1 -1
  12. package/dist/assembly/gpu-driven-owner.d.ts +10 -0
  13. package/dist/assembly/gpu-driven-owner.d.ts.map +1 -0
  14. package/dist/assembly/host-contract.d.ts +14 -0
  15. package/dist/assembly/host-contract.d.ts.map +1 -1
  16. package/dist/assembly/material/assembly.d.ts +31 -1
  17. package/dist/assembly/material/assembly.d.ts.map +1 -1
  18. package/dist/assembly/material/pipeline-helpers.d.ts +24 -0
  19. package/dist/assembly/material/pipeline-helpers.d.ts.map +1 -0
  20. package/dist/assembly/material-shader-policy.d.ts +3 -1
  21. package/dist/assembly/material-shader-policy.d.ts.map +1 -1
  22. package/dist/assembly/recovery/device-loss-fanout.d.ts.map +1 -1
  23. package/dist/assembly/recovery/failure-location.d.ts +12 -0
  24. package/dist/assembly/recovery/failure-location.d.ts.map +1 -0
  25. package/dist/assembly/recovery/recovery-attempt.d.ts +1 -6
  26. package/dist/assembly/recovery/recovery-attempt.d.ts.map +1 -1
  27. package/dist/assembly/recovery/renderer-recover.d.ts +7 -4
  28. package/dist/assembly/recovery/renderer-recover.d.ts.map +1 -1
  29. package/dist/assembly/renderer-facade.d.ts.map +1 -1
  30. package/dist/assembly/renderer-feature-inspection.d.ts +5 -0
  31. package/dist/assembly/renderer-feature-inspection.d.ts.map +1 -0
  32. package/dist/assembly/renderer-frame-transaction.d.ts +19 -0
  33. package/dist/assembly/renderer-frame-transaction.d.ts.map +1 -1
  34. package/dist/assembly/renderer-helpers.d.ts +3 -0
  35. package/dist/assembly/renderer-helpers.d.ts.map +1 -1
  36. package/dist/assembly/renderer-inspection.d.ts +4 -0
  37. package/dist/assembly/renderer-inspection.d.ts.map +1 -1
  38. package/dist/assembly/shader-prewarm-policy.d.ts +7 -1
  39. package/dist/assembly/shader-prewarm-policy.d.ts.map +1 -1
  40. package/dist/assembly/skin-palette-owner.d.ts +8 -0
  41. package/dist/assembly/skin-palette-owner.d.ts.map +1 -0
  42. package/dist/assembly/ssr-shader-prewarm.d.ts +6 -0
  43. package/dist/assembly/ssr-shader-prewarm.d.ts.map +1 -0
  44. package/dist/assembly/webgpu-device-loss.d.ts +15 -0
  45. package/dist/assembly/webgpu-device-loss.d.ts.map +1 -0
  46. package/dist/assembly/webgpu-pbr-ready.d.ts +38 -0
  47. package/dist/assembly/webgpu-pbr-ready.d.ts.map +1 -0
  48. package/dist/assembly/webgpu-ready-mipmap.d.ts +11 -0
  49. package/dist/assembly/webgpu-ready-mipmap.d.ts.map +1 -0
  50. package/dist/assembly/webgpu-ready.d.ts +7 -3
  51. package/dist/assembly/webgpu-ready.d.ts.map +1 -1
  52. package/dist/assembly/webgpu-renderer-bootstrap.d.ts +10 -0
  53. package/dist/assembly/webgpu-renderer-bootstrap.d.ts.map +1 -0
  54. package/dist/assembly/webgpu-renderer-contract.d.ts +62 -0
  55. package/dist/assembly/webgpu-renderer-contract.d.ts.map +1 -0
  56. package/dist/assembly/webgpu-renderer-guards.d.ts +9 -0
  57. package/dist/assembly/webgpu-renderer-guards.d.ts.map +1 -0
  58. package/dist/assembly/webgpu-renderer-observation.d.ts +6 -0
  59. package/dist/assembly/webgpu-renderer-observation.d.ts.map +1 -0
  60. package/dist/assembly/webgpu-renderer-recovery-failure.d.ts +12 -0
  61. package/dist/assembly/webgpu-renderer-recovery-failure.d.ts.map +1 -0
  62. package/dist/assembly/webgpu-renderer.d.ts +7 -141
  63. package/dist/assembly/webgpu-renderer.d.ts.map +1 -1
  64. package/dist/assembly/webgpu-vertex-layouts.d.ts +28 -84
  65. package/dist/assembly/webgpu-vertex-layouts.d.ts.map +1 -1
  66. package/dist/authoring.mjs +2 -2
  67. package/dist/capture/scene-projection.d.ts +41 -0
  68. package/dist/capture/scene-projection.d.ts.map +1 -0
  69. package/dist/{chunk-J52NMQKR.mjs → chunk-7MQ3XBSP.mjs} +1098 -844
  70. package/dist/chunk-7MQ3XBSP.mjs.map +1 -0
  71. package/dist/{chunk-GF523LHF.mjs → chunk-BLXGONR2.mjs} +30 -12
  72. package/dist/chunk-BLXGONR2.mjs.map +1 -0
  73. package/dist/{chunk-VSL23LZE.mjs → chunk-C3DDSXYK.mjs} +1188 -463
  74. package/dist/chunk-C3DDSXYK.mjs.map +1 -0
  75. package/dist/{chunk-IQMKJLNU.mjs → chunk-FUUO4UND.mjs} +443 -30
  76. package/dist/chunk-FUUO4UND.mjs.map +1 -0
  77. package/dist/{chunk-J6KACNOH.mjs → chunk-JRBDO2KB.mjs} +617 -7
  78. package/dist/chunk-JRBDO2KB.mjs.map +1 -0
  79. package/dist/{chunk-GE3SDD72.mjs → chunk-MDQJPY3T.mjs} +20 -40
  80. package/dist/chunk-MDQJPY3T.mjs.map +1 -0
  81. package/dist/{chunk-MYJ5RPJO.mjs → chunk-NO4WZCAH.mjs} +3 -3
  82. package/dist/chunk-NO4WZCAH.mjs.map +1 -0
  83. package/dist/{chunk-RBDBZ5R7.mjs → chunk-PA5DRJNI.mjs} +2 -2
  84. package/dist/chunk-PA5DRJNI.mjs.map +1 -0
  85. package/dist/{chunk-MBU4ZVE7.mjs → chunk-PSEQ6QPH.mjs} +4655 -892
  86. package/dist/chunk-PSEQ6QPH.mjs.map +1 -0
  87. package/dist/{chunk-JAASCMXA.mjs → chunk-S5UE2T5P.mjs} +289 -41
  88. package/dist/chunk-S5UE2T5P.mjs.map +1 -0
  89. package/dist/components/camera.d.ts +62 -2
  90. package/dist/components/camera.d.ts.map +1 -1
  91. package/dist/components/directional-light.d.ts +2 -2
  92. package/dist/components/dynamic-resolution.d.ts +15 -0
  93. package/dist/components/dynamic-resolution.d.ts.map +1 -0
  94. package/dist/components/index.d.ts +2 -0
  95. package/dist/components/index.d.ts.map +1 -1
  96. package/dist/components/instances.d.ts +6 -20
  97. package/dist/components/instances.d.ts.map +1 -1
  98. package/dist/components/light-helpers.d.ts.map +1 -1
  99. package/dist/components/reflection-probe.d.ts +1 -0
  100. package/dist/components/reflection-probe.d.ts.map +1 -1
  101. package/dist/components/scene-instance.d.ts +2 -1
  102. package/dist/components/scene-instance.d.ts.map +1 -1
  103. package/dist/components/screen-space-reflection.d.ts +14 -0
  104. package/dist/components/screen-space-reflection.d.ts.map +1 -0
  105. package/dist/components/skylight.d.ts +4 -1
  106. package/dist/components/skylight.d.ts.map +1 -1
  107. package/dist/construct-renderer.mjs +14809 -7570
  108. package/dist/construct-renderer.mjs.map +1 -1
  109. package/dist/device/device-scope.d.ts +2 -0
  110. package/dist/device/device-scope.d.ts.map +1 -1
  111. package/dist/device/gpu-residency.d.ts +3 -14
  112. package/dist/device/gpu-residency.d.ts.map +1 -1
  113. package/dist/dynamic-geometry.d.ts.map +1 -1
  114. package/dist/environment/background.d.ts +7 -0
  115. package/dist/environment/background.d.ts.map +1 -0
  116. package/dist/environment/ibl.d.ts +11 -0
  117. package/dist/environment/ibl.d.ts.map +1 -0
  118. package/dist/environment/storage.d.ts +14 -0
  119. package/dist/environment/storage.d.ts.map +1 -0
  120. package/dist/errors/gpu-driven.d.ts +6 -3
  121. package/dist/errors/gpu-driven.d.ts.map +1 -1
  122. package/dist/errors/render.d.ts +41 -4
  123. package/dist/errors/render.d.ts.map +1 -1
  124. package/dist/extract/camera.d.ts.map +1 -1
  125. package/dist/extract/contracts.d.ts +7 -0
  126. package/dist/extract/contracts.d.ts.map +1 -1
  127. package/dist/extract/gpu-driven.d.ts +50 -3
  128. package/dist/extract/gpu-driven.d.ts.map +1 -1
  129. package/dist/features/host.d.ts +23 -1
  130. package/dist/features/host.d.ts.map +1 -1
  131. package/dist/features/noise-texture.d.ts +17 -0
  132. package/dist/features/noise-texture.d.ts.map +1 -0
  133. package/dist/features/plan.d.ts +83 -2
  134. package/dist/features/plan.d.ts.map +1 -1
  135. package/dist/features/prepared-gpu-work.d.ts +37 -4
  136. package/dist/features/prepared-gpu-work.d.ts.map +1 -1
  137. package/dist/features/prepared-graphics-store.d.ts.map +1 -1
  138. package/dist/features/prepared-graphics.d.ts +5 -0
  139. package/dist/features/prepared-graphics.d.ts.map +1 -1
  140. package/dist/features/render-graph-compute.d.ts +5 -3
  141. package/dist/features/render-graph-compute.d.ts.map +1 -1
  142. package/dist/features/render-graph-contribution.d.ts +11 -3
  143. package/dist/features/render-graph-contribution.d.ts.map +1 -1
  144. package/dist/features/render-graph-raster.d.ts +19 -3
  145. package/dist/features/render-graph-raster.d.ts.map +1 -1
  146. package/dist/features/types.d.ts +11 -0
  147. package/dist/features/types.d.ts.map +1 -1
  148. package/dist/fullscreen-post-process-pass.d.ts +2 -0
  149. package/dist/fullscreen-post-process-pass.d.ts.map +1 -1
  150. package/dist/gpu-driven/batch-topology.d.ts +34 -1
  151. package/dist/gpu-driven/batch-topology.d.ts.map +1 -1
  152. package/dist/gpu-driven/material-artifacts.d.ts +24 -0
  153. package/dist/gpu-driven/material-artifacts.d.ts.map +1 -0
  154. package/dist/gpu-driven/material-bindings.d.ts +7 -0
  155. package/dist/gpu-driven/material-bindings.d.ts.map +1 -0
  156. package/dist/gpu-driven/pbr-program.d.ts +10 -0
  157. package/dist/gpu-driven/pbr-program.d.ts.map +1 -0
  158. package/dist/gpu-driven/prepared-draw.d.ts +2 -0
  159. package/dist/gpu-driven/prepared-draw.d.ts.map +1 -1
  160. package/dist/gpu-driven/production-raster.d.ts +169 -27
  161. package/dist/gpu-driven/production-raster.d.ts.map +1 -1
  162. package/dist/gpu-driven/shadow-views.d.ts +126 -0
  163. package/dist/gpu-driven/shadow-views.d.ts.map +1 -0
  164. package/dist/gpu-driven/view-gpu.d.ts +18 -2
  165. package/dist/gpu-driven/view-gpu.d.ts.map +1 -1
  166. package/dist/gpu-resource.d.ts +5 -2
  167. package/dist/gpu-resource.d.ts.map +1 -1
  168. package/dist/gpu-scene-schema.d.ts +2 -8
  169. package/dist/gpu-scene-schema.d.ts.map +1 -1
  170. package/dist/gpu-scene.d.ts +14 -0
  171. package/dist/gpu-scene.d.ts.map +1 -1
  172. package/dist/gpu-texture-usage.d.ts +1 -0
  173. package/dist/gpu-texture-usage.d.ts.map +1 -1
  174. package/dist/hdrp-buffers.d.ts +9 -0
  175. package/dist/hdrp-buffers.d.ts.map +1 -1
  176. package/dist/ibl/IblPipelineCache.d.ts +2 -0
  177. package/dist/ibl/IblPipelineCache.d.ts.map +1 -1
  178. package/dist/ibl/frame-resources.d.ts +8 -0
  179. package/dist/ibl/frame-resources.d.ts.map +1 -0
  180. package/dist/ibl/skylight-bind-group.d.ts +3 -1
  181. package/dist/ibl/skylight-bind-group.d.ts.map +1 -1
  182. package/dist/index.d.ts +87 -75
  183. package/dist/index.d.ts.map +1 -1
  184. package/dist/index.mjs +10 -8
  185. package/dist/index.mjs.map +1 -1
  186. package/dist/inspection-types.d.ts +180 -1
  187. package/dist/inspection-types.d.ts.map +1 -1
  188. package/dist/instances-derived-bounds.d.ts +11 -0
  189. package/dist/instances-derived-bounds.d.ts.map +1 -1
  190. package/dist/instances.d.ts +89 -44
  191. package/dist/instances.d.ts.map +1 -1
  192. package/dist/internal.d.ts +3 -0
  193. package/dist/internal.d.ts.map +1 -1
  194. package/dist/internal.mjs +117 -9
  195. package/dist/internal.mjs.map +1 -1
  196. package/dist/lifecycle.d.ts +2 -1
  197. package/dist/lifecycle.d.ts.map +1 -1
  198. package/dist/material-row.d.ts +15 -0
  199. package/dist/material-row.d.ts.map +1 -0
  200. package/dist/materials.d.ts +15 -0
  201. package/dist/materials.d.ts.map +1 -1
  202. package/dist/pbr-pipeline.d.ts +23 -5
  203. package/dist/pbr-pipeline.d.ts.map +1 -1
  204. package/dist/pipeline/render-extent.d.ts +51 -0
  205. package/dist/pipeline/render-extent.d.ts.map +1 -0
  206. package/dist/pipeline/standard-build-context.d.ts +7 -0
  207. package/dist/pipeline/standard-build-context.d.ts.map +1 -0
  208. package/dist/pipeline/standard-forward-lane.d.ts +3 -2
  209. package/dist/pipeline/standard-forward-lane.d.ts.map +1 -1
  210. package/dist/pipeline/standard-output/auto-exposure/capability.d.ts +12 -0
  211. package/dist/pipeline/standard-output/auto-exposure/capability.d.ts.map +1 -0
  212. package/dist/pipeline/standard-output/auto-exposure/gpu.d.ts +70 -0
  213. package/dist/pipeline/standard-output/auto-exposure/gpu.d.ts.map +1 -0
  214. package/dist/pipeline/standard-output/auto-exposure/graph.d.ts +75 -0
  215. package/dist/pipeline/standard-output/auto-exposure/graph.d.ts.map +1 -0
  216. package/dist/pipeline/standard-output/auto-exposure/inspection.d.ts +111 -0
  217. package/dist/pipeline/standard-output/auto-exposure/inspection.d.ts.map +1 -0
  218. package/dist/pipeline/standard-output/auto-exposure/oracle.d.ts +16 -0
  219. package/dist/pipeline/standard-output/auto-exposure/oracle.d.ts.map +1 -0
  220. package/dist/pipeline/standard-output/auto-exposure/preset.d.ts +14 -0
  221. package/dist/pipeline/standard-output/auto-exposure/preset.d.ts.map +1 -0
  222. package/dist/pipeline/standard-output/auto-exposure/state.d.ts +46 -0
  223. package/dist/pipeline/standard-output/auto-exposure/state.d.ts.map +1 -0
  224. package/dist/pipeline/standard-output/color-transform.d.ts +27 -0
  225. package/dist/pipeline/standard-output/color-transform.d.ts.map +1 -0
  226. package/dist/pipeline/standard-output/graph.d.ts +15 -0
  227. package/dist/pipeline/standard-output/graph.d.ts.map +1 -0
  228. package/dist/pipeline/standard-output/lut-admission.d.ts +29 -0
  229. package/dist/pipeline/standard-output/lut-admission.d.ts.map +1 -0
  230. package/dist/pipeline/standard-output/lut-gpu.d.ts +48 -0
  231. package/dist/pipeline/standard-output/lut-gpu.d.ts.map +1 -0
  232. package/dist/pipeline/standard-output/lut-state.d.ts +68 -0
  233. package/dist/pipeline/standard-output/lut-state.d.ts.map +1 -0
  234. package/dist/pipeline/standard-output/resources.d.ts +10 -0
  235. package/dist/pipeline/standard-output/resources.d.ts.map +1 -0
  236. package/dist/pipeline/standard-output/types.d.ts +39 -0
  237. package/dist/pipeline/standard-output/types.d.ts.map +1 -0
  238. package/dist/pipeline/standard-pipeline.d.ts.map +1 -1
  239. package/dist/pipeline/standard-post.d.ts +2 -2
  240. package/dist/pipeline/standard-post.d.ts.map +1 -1
  241. package/dist/pipeline/standard-profile.d.ts +2 -1
  242. package/dist/pipeline/standard-profile.d.ts.map +1 -1
  243. package/dist/pipeline-builder.d.ts +5 -2
  244. package/dist/pipeline-builder.d.ts.map +1 -1
  245. package/dist/pipeline-spec-types.d.ts +4 -0
  246. package/dist/pipeline-spec-types.d.ts.map +1 -1
  247. package/dist/pipeline-spec.d.ts +1 -9
  248. package/dist/pipeline-spec.d.ts.map +1 -1
  249. package/dist/plugin.d.ts.map +1 -1
  250. package/dist/points-lines/standard-owner.d.ts +1 -1
  251. package/dist/points-lines/standard-owner.d.ts.map +1 -1
  252. package/dist/post-process-errors.d.ts +9 -2
  253. package/dist/post-process-errors.d.ts.map +1 -1
  254. package/dist/prepare/extended-lighting/resources.d.ts +3 -3
  255. package/dist/prepare/prepared-graphics-resolver.d.ts +6 -1
  256. package/dist/prepare/prepared-graphics-resolver.d.ts.map +1 -1
  257. package/dist/record/dynamic-geometry-consumption.d.ts +15 -0
  258. package/dist/record/dynamic-geometry-consumption.d.ts.map +1 -0
  259. package/dist/record/frame-lighting.d.ts +3 -3
  260. package/dist/record/frame-lighting.d.ts.map +1 -1
  261. package/dist/record/frame-snapshot.d.ts +109 -13
  262. package/dist/record/frame-snapshot.d.ts.map +1 -1
  263. package/dist/record/frame-targets.d.ts.map +1 -1
  264. package/dist/record/frame.d.ts +41 -5
  265. package/dist/record/frame.d.ts.map +1 -1
  266. package/dist/record/main-pass-geometry.d.ts +13 -9
  267. package/dist/record/main-pass-geometry.d.ts.map +1 -1
  268. package/dist/record/main-pass-material.d.ts +43 -3
  269. package/dist/record/main-pass-material.d.ts.map +1 -1
  270. package/dist/record/main-pass-sprite-draws.d.ts +2 -10
  271. package/dist/record/main-pass-sprite-draws.d.ts.map +1 -1
  272. package/dist/record/main-pass.d.ts +14 -2
  273. package/dist/record/main-pass.d.ts.map +1 -1
  274. package/dist/record/mesh-ssbo.d.ts +15 -6
  275. package/dist/record/mesh-ssbo.d.ts.map +1 -1
  276. package/dist/record/prepared-material-bindings.d.ts +19 -0
  277. package/dist/record/prepared-material-bindings.d.ts.map +1 -0
  278. package/dist/record/recovery-pipeline.d.ts +1 -0
  279. package/dist/record/recovery-pipeline.d.ts.map +1 -1
  280. package/dist/record/render-context.d.ts +101 -22
  281. package/dist/record/render-context.d.ts.map +1 -1
  282. package/dist/record/shadow-pass.d.ts +62 -7
  283. package/dist/record/shadow-pass.d.ts.map +1 -1
  284. package/dist/record/skybox-post-pass.d.ts +1 -1
  285. package/dist/record/skybox-post-pass.d.ts.map +1 -1
  286. package/dist/record/typed-frame-graph.d.ts +39 -8
  287. package/dist/record/typed-frame-graph.d.ts.map +1 -1
  288. package/dist/record/view-ubo.d.ts +1 -0
  289. package/dist/record/view-ubo.d.ts.map +1 -1
  290. package/dist/recovery/render-system-candidate.d.ts +10 -2
  291. package/dist/recovery/render-system-candidate.d.ts.map +1 -1
  292. package/dist/recovery/render-system-roots.d.ts +11 -0
  293. package/dist/recovery/render-system-roots.d.ts.map +1 -0
  294. package/dist/recovery/types.d.ts +76 -0
  295. package/dist/recovery/types.d.ts.map +1 -0
  296. package/dist/reflection/background.d.ts +14 -0
  297. package/dist/reflection/background.d.ts.map +1 -0
  298. package/dist/reflection/filter.d.ts.map +1 -1
  299. package/dist/reflection/projection.d.ts +1 -0
  300. package/dist/reflection/projection.d.ts.map +1 -1
  301. package/dist/reflection/record-owner.d.ts +4 -4
  302. package/dist/reflection/record-owner.d.ts.map +1 -1
  303. package/dist/render-contract.d.ts +211 -53
  304. package/dist/render-contract.d.ts.map +1 -1
  305. package/dist/render-graph-primitives.d.ts +1 -0
  306. package/dist/render-graph-primitives.d.ts.map +1 -1
  307. package/dist/render-pipeline.d.ts +81 -6
  308. package/dist/render-pipeline.d.ts.map +1 -1
  309. package/dist/render-system-extract-tail.d.ts +15 -6
  310. package/dist/render-system-extract-tail.d.ts.map +1 -1
  311. package/dist/render-system-extract.d.ts +128 -41
  312. package/dist/render-system-extract.d.ts.map +1 -1
  313. package/dist/render-system-projections.d.ts +22 -0
  314. package/dist/render-system-projections.d.ts.map +1 -0
  315. package/dist/render-system.d.ts +27 -77
  316. package/dist/render-system.d.ts.map +1 -1
  317. package/dist/scene/render-scene-types.d.ts +14 -7
  318. package/dist/scene/render-scene-types.d.ts.map +1 -1
  319. package/dist/scene/render-scene.d.ts +31 -11
  320. package/dist/scene/render-scene.d.ts.map +1 -1
  321. package/dist/scene/shadow-visibility.d.ts +5 -0
  322. package/dist/scene/shadow-visibility.d.ts.map +1 -0
  323. package/dist/ssao-config.d.ts +8 -6
  324. package/dist/ssao-config.d.ts.map +1 -1
  325. package/dist/ssr/admission.d.ts +70 -1
  326. package/dist/ssr/admission.d.ts.map +1 -1
  327. package/dist/ssr/compose.d.ts +13 -0
  328. package/dist/ssr/compose.d.ts.map +1 -0
  329. package/dist/ssr/errors.d.ts +35 -0
  330. package/dist/ssr/errors.d.ts.map +1 -1
  331. package/dist/ssr/graph.d.ts +60 -0
  332. package/dist/ssr/graph.d.ts.map +1 -0
  333. package/dist/ssr/history.d.ts +97 -0
  334. package/dist/ssr/history.d.ts.map +1 -0
  335. package/dist/ssr/hiz.d.ts +37 -0
  336. package/dist/ssr/hiz.d.ts.map +1 -0
  337. package/dist/ssr/inspection.d.ts +42 -0
  338. package/dist/ssr/inspection.d.ts.map +1 -0
  339. package/dist/ssr/resources.d.ts +58 -0
  340. package/dist/ssr/resources.d.ts.map +1 -0
  341. package/dist/ssr/temporal.d.ts +38 -0
  342. package/dist/ssr/temporal.d.ts.map +1 -0
  343. package/dist/systems/skin-palette-allocator.d.ts +32 -10
  344. package/dist/systems/skin-palette-allocator.d.ts.map +1 -1
  345. package/dist/systems/skin-palette-types.d.ts +15 -0
  346. package/dist/systems/skin-palette-types.d.ts.map +1 -1
  347. package/dist/systems/transparent-dispatch.d.ts +9 -0
  348. package/dist/systems/transparent-dispatch.d.ts.map +1 -0
  349. package/dist/targets/material-source.d.ts +7 -0
  350. package/dist/targets/material-source.d.ts.map +1 -1
  351. package/dist/temporal/frame-coordinator.d.ts +7 -0
  352. package/dist/temporal/frame-coordinator.d.ts.map +1 -1
  353. package/dist/temporal/gpu.d.ts +8 -0
  354. package/dist/temporal/gpu.d.ts.map +1 -1
  355. package/dist/temporal/history.d.ts.map +1 -1
  356. package/dist/temporal/index.mjs +6 -6
  357. package/dist/temporal/inspection.d.ts.map +1 -1
  358. package/dist/temporal/standard-scene-data.d.ts +4 -2
  359. package/dist/temporal/standard-scene-data.d.ts.map +1 -1
  360. package/dist/temporal/target-coverage-attachment.d.ts +15 -0
  361. package/dist/temporal/target-coverage-attachment.d.ts.map +1 -0
  362. package/dist/temporal/temporal-view.d.ts +6 -0
  363. package/dist/temporal/temporal-view.d.ts.map +1 -1
  364. package/dist/temporal/view.d.ts +6 -0
  365. package/dist/temporal/view.d.ts.map +1 -1
  366. package/dist/typed-render-graph-primitives.d.ts +41 -1
  367. package/dist/typed-render-graph-primitives.d.ts.map +1 -1
  368. package/dist/typed-shadow-passes.d.ts +3 -1
  369. package/dist/typed-shadow-passes.d.ts.map +1 -1
  370. package/dist/volume/component.d.ts +1 -0
  371. package/dist/volume/component.d.ts.map +1 -1
  372. package/dist/volume/temporal.d.ts +6 -0
  373. package/dist/volume/temporal.d.ts.map +1 -1
  374. package/package.json +21 -20
  375. package/src/__tests__/auto-exposure-gpu-evidence.ts +291 -0
  376. package/src/__tests__/auto-exposure-inspection.unit.test.ts +80 -0
  377. package/src/__tests__/auto-exposure-transaction.integration.test.ts +99 -0
  378. package/src/__tests__/auto-exposure.browser.test.ts +33 -0
  379. package/src/__tests__/auto-exposure.dawn.test.ts +33 -0
  380. package/src/__tests__/capture-scene-projection.unit.test.ts +24 -0
  381. package/src/__tests__/detached-inspection.integration.test.ts +5 -0
  382. package/src/__tests__/device-feature-admission.unit.test.ts +15 -24
  383. package/src/__tests__/device-loss-fanout.unit.test.ts +43 -0
  384. package/src/__tests__/device-scope-lifecycle.unit.test.ts +18 -0
  385. package/src/__tests__/device-scope-stale-matrix.unit.test.ts +18 -0
  386. package/src/__tests__/docs-gate-green.unit.test.ts +3 -3
  387. package/src/__tests__/dynamic-geometry-batch.unit.test.ts +157 -0
  388. package/src/__tests__/dynamic-geometry.unit.test.ts +36 -0
  389. package/src/__tests__/dynamic-resolution-plugin.integration.test.ts +81 -0
  390. package/src/__tests__/factory-contract.integration.test.ts +231 -9
  391. package/src/__tests__/fallback-row-stride-surface.unit.test.ts +19 -0
  392. package/src/__tests__/feature-depth-input.dawn.test.ts +121 -0
  393. package/src/__tests__/feature-noise.unit.test.ts +38 -0
  394. package/src/__tests__/fixtures/taa-resolve-pinned-baseline.wgsl +301 -0
  395. package/src/__tests__/frame-camera-selection.unit.test.ts +131 -0
  396. package/src/__tests__/frame-observation.contract.test.ts +21 -0
  397. package/src/__tests__/frame-plan-contract.unit.test.ts +24 -0
  398. package/src/__tests__/frame-targets.unit.test.ts +24 -0
  399. package/src/__tests__/fullscreen-feature-plan.unit.test.ts +3 -0
  400. package/src/__tests__/gpu-driven-baseline.characterization.test.ts +79 -0
  401. package/src/__tests__/gpu-driven-batch-topology-pbr.unit.test.ts +307 -7
  402. package/src/__tests__/gpu-driven-indirect-raster-evidence.ts +122 -142
  403. package/src/__tests__/gpu-driven-indirect-raster.browser.test.ts +10 -1
  404. package/src/__tests__/gpu-driven-lod-cache.unit.test.ts +261 -0
  405. package/src/__tests__/gpu-driven-mesh-world-identity.unit.test.ts +346 -0
  406. package/src/__tests__/gpu-driven-pbr.dawn.test.ts +1422 -0
  407. package/src/__tests__/gpu-driven-pbr.integration.test.ts +21 -0
  408. package/src/__tests__/gpu-driven-production-fixture.ts +96 -0
  409. package/src/__tests__/gpu-driven-production.integration.test.ts +1076 -106
  410. package/src/__tests__/gpu-driven-scaling.unit.test.ts +1 -3
  411. package/src/__tests__/gpu-driven-shadow-views.browser.test.ts +63 -0
  412. package/src/__tests__/gpu-driven-shadow-views.dawn.test.ts +294 -0
  413. package/src/__tests__/gpu-driven-shadow-views.integration.test.ts +97 -0
  414. package/src/__tests__/gpu-driven-shadow-views.unit.test.ts +168 -0
  415. package/src/__tests__/gpu-driven-skin-contract.test-d.ts +11 -0
  416. package/src/__tests__/gpu-driven-view-gpu-evidence.ts +72 -20
  417. package/src/__tests__/gpu-driven-view-graph.integration.test.ts +30 -9
  418. package/src/__tests__/gpu-driven-view.unit.test.ts +160 -52
  419. package/src/__tests__/gpu-pass-timing-browser-runner.ts +15 -0
  420. package/src/__tests__/gpu-pass-timing.browser.test.ts +20 -2
  421. package/src/__tests__/gpu-resource-scope.unit.test.ts +71 -0
  422. package/src/__tests__/gpu-resource-store-stride.unit.test.ts +42 -0
  423. package/src/__tests__/gpu-scene-render-graph-gpu.ts +19 -5
  424. package/src/__tests__/gpu-scene.dawn.test.ts +40 -13
  425. package/src/__tests__/gpu-scene.unit.test.ts +613 -33
  426. package/src/__tests__/instances-culling.integration.test.ts +36 -10
  427. package/src/__tests__/instances-derived-bounds.unit.test.ts +54 -0
  428. package/src/__tests__/instances-store.unit.test.ts +242 -39
  429. package/src/__tests__/ktx2-basis-gpu-consumer.dawn.test.ts +6 -0
  430. package/src/__tests__/lighting-scene-roundtrip.integration.test.ts +13 -16
  431. package/src/__tests__/lut-lkg-transaction.integration.test.ts +122 -0
  432. package/src/__tests__/material-contract-inventory.unit.test.ts +5 -6
  433. package/src/__tests__/material-dispatch-shader-id.unit.test.ts +51 -0
  434. package/src/__tests__/material-lane-selection.unit.test.ts +60 -0
  435. package/src/__tests__/material-numeric-upload.unit.test.ts +53 -0
  436. package/src/__tests__/material-texture-handles.unit.test.ts +18 -0
  437. package/src/__tests__/material-texture-source.unit.test.ts +87 -0
  438. package/src/__tests__/materials-authoring-validation.unit.test.ts +41 -0
  439. package/src/__tests__/materials-standard-contract.unit.test.ts +57 -3
  440. package/src/__tests__/mesh-buffer-usage-surface.unit.test.ts +4 -0
  441. package/src/__tests__/mesh-submission-lifetime.unit.test.ts +8 -0
  442. package/src/__tests__/morph-neutral-admission.unit.test.ts +19 -0
  443. package/src/__tests__/normal-fallback.dawn.test.ts +122 -0
  444. package/src/__tests__/particle-material-input-layout.unit.test.ts +104 -0
  445. package/src/__tests__/physical-clearcoat.integration.test.ts +42 -3
  446. package/src/__tests__/pipeline-texture-specialization.unit.test.ts +138 -0
  447. package/src/__tests__/point-shadow-inspection.unit.test.ts +2 -0
  448. package/src/__tests__/post-process-error-policy-owner.unit.test.ts +6 -5
  449. package/src/__tests__/prepared-gpu-driven-pbr-contract.test-d.ts +31 -0
  450. package/src/__tests__/prepared-gpu-driven-pbr-contract.unit.test.ts +39 -0
  451. package/src/__tests__/prepared-gpu-driven-pbr.integration.test.ts +251 -9
  452. package/src/__tests__/prepared-graphics-capability.unit.test.ts +21 -0
  453. package/src/__tests__/prepared-graphics-pipeline-warmup.unit.test.ts +6 -2
  454. package/src/__tests__/profiler-phase-catalog.test.ts +5 -0
  455. package/src/__tests__/public-surface.test-d.ts +3 -0
  456. package/src/__tests__/recovery-candidate-prepare.contract.test.ts +30 -12
  457. package/src/__tests__/reflection-fallback-async-publication.unit.test.ts +171 -0
  458. package/src/__tests__/reflection-probe-authoring.unit.test.ts +12 -10
  459. package/src/__tests__/reflection-probe-box.dawn.test.ts +71 -0
  460. package/src/__tests__/reflection-probe-filter.unit.test.ts +9 -0
  461. package/src/__tests__/reflection-probe-orientation.dawn.test.ts +294 -0
  462. package/src/__tests__/reflection-probe-record-owner.characterization.unit.test.ts +5 -14
  463. package/src/__tests__/reflection-probe-updates.unit.test.ts +129 -0
  464. package/src/__tests__/render-error-code-owner.test-d.ts +7 -0
  465. package/src/__tests__/render-error-detail-ssot.test-d.ts +27 -1
  466. package/src/__tests__/render-error-exhaustive.test-d.ts +15 -0
  467. package/src/__tests__/render-extent.unit.test.ts +63 -0
  468. package/src/__tests__/render-feature-gpu-work.integration.test.ts +285 -2
  469. package/src/__tests__/render-feature-plan.unit.test.ts +245 -0
  470. package/src/__tests__/render-feature-shadow-order.unit.test.ts +228 -0
  471. package/src/__tests__/render-feature-zero-work.unit.test.ts +35 -1
  472. package/src/__tests__/render-graph-fallback.integration.test.ts +2 -2
  473. package/src/__tests__/render-owner-cohesion.unit.test.ts +55 -0
  474. package/src/__tests__/render-scene-mixed-updates.integration.test.ts +426 -0
  475. package/src/__tests__/render-scene-projection.unit.test.ts +915 -23
  476. package/src/__tests__/render-scene-resource-updates.integration.test.ts +214 -0
  477. package/src/__tests__/render-scene-skin-lifecycle.integration.test.ts +173 -0
  478. package/src/__tests__/render-scene-temporal-retry.unit.test.ts +133 -0
  479. package/src/__tests__/render-target-material-source.test-d.ts +27 -3
  480. package/src/__tests__/renderer-factory-material-contract.unit.test.ts +233 -9
  481. package/src/__tests__/renderer-frame-transaction.integration.test.ts +923 -5
  482. package/src/__tests__/shader-manifest-fixture.ts +45 -0
  483. package/src/__tests__/skin-motion-regression.unit.test.ts +0 -33
  484. package/src/__tests__/skin-palette-persistent.integration.test.ts +28 -0
  485. package/src/__tests__/skin-palette-persistent.unit.test.ts +247 -0
  486. package/src/__tests__/skinned-shadow-caster.test.ts +125 -14
  487. package/src/__tests__/specular-environment-scale.dawn.test.ts +158 -0
  488. package/src/__tests__/ssao-config.unit.test.ts +7 -0
  489. package/src/__tests__/ssao-surface.dawn.test.ts +252 -0
  490. package/src/__tests__/ssr-admission-matrix.integration.test.ts +7 -1
  491. package/src/__tests__/ssr-confidence-compose.unit.test.ts +84 -0
  492. package/src/__tests__/ssr-coordinate-oracle.unit.test.ts +28 -0
  493. package/src/__tests__/ssr-cube-silhouette.dawn.test.ts +270 -0
  494. package/src/__tests__/ssr-format-probe-gating.integration.test.ts +82 -0
  495. package/src/__tests__/ssr-gpu-dispatch.browser.test.ts +99 -0
  496. package/src/__tests__/ssr-gpu-dispatch.dawn.test.ts +116 -0
  497. package/src/__tests__/ssr-gpu-dispatch.ts +951 -0
  498. package/src/__tests__/ssr-graph-topology.unit.test.ts +104 -0
  499. package/src/__tests__/ssr-history-filter.dawn.test.ts +198 -0
  500. package/src/__tests__/ssr-hit-color.dawn.test.ts +273 -0
  501. package/src/__tests__/ssr-hiz-odd-reduction.dawn.test.ts +569 -0
  502. package/src/__tests__/ssr-hiz-rescue.dawn.test.ts +279 -0
  503. package/src/__tests__/ssr-hiz.browser.test.ts +150 -0
  504. package/src/__tests__/ssr-hiz.dawn.test.ts +150 -0
  505. package/src/__tests__/ssr-hiz.unit.test.ts +56 -0
  506. package/src/__tests__/ssr-inspection.unit.test.ts +105 -0
  507. package/src/__tests__/ssr-memory-budget.unit.test.ts +45 -0
  508. package/src/__tests__/ssr-performance-sequence-aggregation.unit.test.ts +15 -0
  509. package/src/__tests__/ssr-public-surface.test-d.ts +37 -0
  510. package/src/__tests__/ssr-receiver-coverage.dawn.test.ts +387 -0
  511. package/src/__tests__/ssr-reflection-mip-reducer.dawn.test.ts +309 -0
  512. package/src/__tests__/ssr-reprojection-probe.ts +90 -0
  513. package/src/__tests__/ssr-roughness-mip.dawn.test.ts +163 -0
  514. package/src/__tests__/ssr-shader-prewarm.unit.test.ts +59 -0
  515. package/src/__tests__/ssr-spatial.integration.test.ts +141 -0
  516. package/src/__tests__/ssr-stable-lattice.dawn.test.ts +475 -0
  517. package/src/__tests__/ssr-temporal-history.unit.test.ts +202 -0
  518. package/src/__tests__/ssr-texel-crossing.dawn.test.ts +448 -0
  519. package/src/__tests__/ssr-zero-work.integration.test.ts +87 -0
  520. package/src/__tests__/standard-cluster-variant-resolution.unit.test.ts +84 -12
  521. package/src/__tests__/standard-clustered-pipeline.unit.test.ts +29 -0
  522. package/src/__tests__/standard-output-chain-contract.unit.test.ts +99 -0
  523. package/src/__tests__/standard-output-chain.integration.test.ts +73 -0
  524. package/src/__tests__/standard-output-gpu-evidence.ts +205 -0
  525. package/src/__tests__/standard-output.browser.test.ts +15 -0
  526. package/src/__tests__/standard-output.dawn.test.ts +15 -0
  527. package/src/__tests__/standard-pbr-artifact-assembly.unit.test.ts +275 -0
  528. package/src/__tests__/standard-pipeline.integration.test.ts +320 -3
  529. package/src/__tests__/standard-profile-oracle.unit.test.ts +49 -0
  530. package/src/__tests__/standard-surface-lighting.unit.test.ts +41 -0
  531. package/src/__tests__/standard-texture-specialization.dawn.test.ts +141 -0
  532. package/src/__tests__/surface-adjacent-regressions.integration.test.ts +6 -3
  533. package/src/__tests__/taa-coverage-edge.dawn.test.ts +199 -0
  534. package/src/__tests__/taa-history-clipping.dawn.test.ts +328 -0
  535. package/src/__tests__/taa-neighborhood-equivalence.dawn.test.ts +782 -0
  536. package/src/__tests__/taa-stability-feedback.browser.test.ts +23 -0
  537. package/src/__tests__/taa-stability-feedback.dawn.test.ts +68 -0
  538. package/src/__tests__/taa-stability-feedback.ts +342 -0
  539. package/src/__tests__/temporal-history-lifecycle.unit.test.ts +49 -7
  540. package/src/__tests__/temporal-reset-matrix.unit.test.ts +9 -0
  541. package/src/__tests__/temporal-transaction.integration.test.ts +28 -0
  542. package/src/__tests__/temporal-view.unit.test.ts +17 -0
  543. package/src/__tests__/texture-usage-surface.unit.test.ts +4 -2
  544. package/src/__tests__/transmission-prewarm.unit.test.ts +57 -0
  545. package/src/__tests__/typed-pipeline-topology.unit.test.ts +109 -4
  546. package/src/__tests__/typed-scene-pass-material-cache.integration.test.ts +126 -0
  547. package/src/__tests__/vertex-layouts-ssot.unit.test.ts +47 -0
  548. package/src/__tests__/visibility-extract.perf.test.ts +20 -8
  549. package/src/__tests__/visibility-instances.integration.test.ts +19 -7
  550. package/src/__tests__/visibility-producer-matrix.integration.test.ts +20 -9
  551. package/src/__tests__/volumetric-fog-stage-readback.dawn.test.ts +62 -13
  552. package/src/__tests__/volumetric-fog-topology.unit.test.ts +7 -7
  553. package/src/assembly/atmosphere-shader-prewarm.ts +39 -0
  554. package/src/assembly/device-feature-admission.ts +9 -4
  555. package/src/assembly/dynamic-geometry-host.ts +284 -163
  556. package/src/assembly/dynamic-geometry-runtime.ts +71 -0
  557. package/src/assembly/factory.ts +4 -0
  558. package/src/assembly/gpu-driven-owner.ts +29 -0
  559. package/src/assembly/host-contract.ts +19 -0
  560. package/src/assembly/material/assembly.ts +383 -1
  561. package/src/assembly/material/pipeline-helpers.ts +100 -0
  562. package/src/assembly/material-shader-policy.ts +110 -46
  563. package/src/assembly/recovery/device-loss-fanout.ts +5 -0
  564. package/src/assembly/recovery/failure-location.ts +62 -0
  565. package/src/assembly/recovery/recovery-attempt.ts +2 -29
  566. package/src/assembly/recovery/renderer-recover.ts +16 -17
  567. package/src/assembly/renderer-facade.ts +34 -1
  568. package/src/assembly/renderer-feature-inspection.ts +13 -0
  569. package/src/assembly/renderer-frame-transaction.ts +63 -0
  570. package/src/assembly/renderer-helpers.ts +26 -0
  571. package/src/assembly/renderer-inspection.ts +6 -0
  572. package/src/assembly/shader-prewarm-policy.ts +46 -1
  573. package/src/assembly/skin-palette-owner.ts +21 -0
  574. package/src/assembly/ssr-shader-prewarm.ts +41 -0
  575. package/src/assembly/webgpu-device-loss.ts +75 -0
  576. package/src/assembly/webgpu-pbr-ready.ts +232 -0
  577. package/src/assembly/webgpu-ready-mipmap.ts +26 -0
  578. package/src/assembly/webgpu-ready.ts +161 -131
  579. package/src/assembly/webgpu-renderer-bootstrap.ts +85 -0
  580. package/src/assembly/webgpu-renderer-contract.ts +69 -0
  581. package/src/assembly/webgpu-renderer-guards.ts +32 -0
  582. package/src/assembly/webgpu-renderer-observation.ts +43 -0
  583. package/src/assembly/webgpu-renderer-recovery-failure.ts +62 -0
  584. package/src/assembly/webgpu-renderer.ts +744 -730
  585. package/src/assembly/webgpu-vertex-layouts.ts +233 -87
  586. package/src/capture/scene-projection.ts +37 -0
  587. package/src/components/__tests__/camera-exposure.contract.unit.test.ts +153 -0
  588. package/src/components/__tests__/dynamic-resolution-schema.unit.test.ts +65 -0
  589. package/src/components/__tests__/light-radius.integration.test.ts +59 -0
  590. package/src/components/camera.ts +287 -7
  591. package/src/components/directional-light.ts +3 -3
  592. package/src/components/dynamic-resolution.ts +67 -0
  593. package/src/components/index.ts +2 -0
  594. package/src/components/instances.ts +45 -18
  595. package/src/components/light-helpers.ts +6 -1
  596. package/src/components/reflection-probe.ts +1 -0
  597. package/src/components/scene-instance.ts +2 -1
  598. package/src/components/screen-space-reflection.ts +21 -0
  599. package/src/components/skylight.ts +4 -1
  600. package/src/components/sprite-animation.ts +2 -2
  601. package/src/components/sprite-instances.ts +1 -1
  602. package/src/device/device-scope.ts +13 -7
  603. package/src/device/gpu-residency.ts +45 -42
  604. package/src/dynamic-geometry.ts +13 -8
  605. package/src/environment/background.ts +281 -0
  606. package/src/environment/ibl.ts +211 -0
  607. package/src/environment/storage.ts +63 -0
  608. package/src/errors/gpu-driven.ts +49 -8
  609. package/src/errors/render.ts +91 -3
  610. package/src/extract/camera.ts +37 -1
  611. package/src/extract/contracts.ts +10 -0
  612. package/src/extract/gpu-driven.ts +240 -10
  613. package/src/features/host.ts +237 -19
  614. package/src/features/noise-texture.ts +91 -0
  615. package/src/features/plan.ts +553 -24
  616. package/src/features/prepared-gpu-work.ts +631 -49
  617. package/src/features/prepared-graphics-store.ts +31 -0
  618. package/src/features/prepared-graphics.ts +5 -0
  619. package/src/features/render-graph-compute.ts +41 -8
  620. package/src/features/render-graph-contribution.ts +141 -3
  621. package/src/features/render-graph-raster.ts +143 -30
  622. package/src/features/types.ts +11 -0
  623. package/src/fullscreen-post-process-pass.ts +3 -0
  624. package/src/gpu-driven/batch-topology.ts +452 -108
  625. package/src/gpu-driven/material-artifacts.ts +179 -0
  626. package/src/gpu-driven/material-bindings.ts +41 -0
  627. package/src/gpu-driven/pbr-program.ts +31 -0
  628. package/src/gpu-driven/prepared-draw.ts +42 -6
  629. package/src/gpu-driven/production-raster.ts +2806 -716
  630. package/src/gpu-driven/shadow-views.ts +539 -0
  631. package/src/gpu-driven/view-gpu.ts +188 -41
  632. package/src/gpu-resource.ts +21 -2
  633. package/src/gpu-scene-schema.ts +61 -11
  634. package/src/gpu-scene.ts +197 -99
  635. package/src/gpu-texture-usage.ts +1 -0
  636. package/src/hdrp-buffers.ts +141 -36
  637. package/src/ibl/IblPipelineCache.ts +5 -0
  638. package/src/ibl/frame-resources.ts +38 -0
  639. package/src/ibl/skylight-bind-group.ts +32 -21
  640. package/src/index.ts +194 -75
  641. package/src/inspection-types.ts +207 -1
  642. package/src/instances-derived-bounds.ts +258 -34
  643. package/src/instances.ts +459 -150
  644. package/src/internal.ts +22 -0
  645. package/src/lifecycle.ts +2 -0
  646. package/src/material-row.ts +194 -0
  647. package/src/materials.ts +44 -8
  648. package/src/pbr-pipeline.ts +172 -19
  649. package/src/pipeline/render-extent.ts +111 -0
  650. package/src/pipeline/standard-build-context.ts +7 -0
  651. package/src/pipeline/standard-forward-lane.ts +74 -29
  652. package/src/pipeline/standard-output/__tests__/lut-admission.unit.test.ts +103 -0
  653. package/src/pipeline/standard-output/auto-exposure/__tests__/capability.unit.test.ts +40 -0
  654. package/src/pipeline/standard-output/auto-exposure/__tests__/gpu.contract.unit.test.ts +125 -0
  655. package/src/pipeline/standard-output/auto-exposure/__tests__/graph.contract.unit.test.ts +94 -0
  656. package/src/pipeline/standard-output/auto-exposure/__tests__/oracle.test.ts +108 -0
  657. package/src/pipeline/standard-output/auto-exposure/__tests__/preset.test.ts +26 -0
  658. package/src/pipeline/standard-output/auto-exposure/capability.ts +38 -0
  659. package/src/pipeline/standard-output/auto-exposure/gpu.ts +567 -0
  660. package/src/pipeline/standard-output/auto-exposure/graph.ts +221 -0
  661. package/src/pipeline/standard-output/auto-exposure/inspection.ts +231 -0
  662. package/src/pipeline/standard-output/auto-exposure/oracle.ts +291 -0
  663. package/src/pipeline/standard-output/auto-exposure/preset.ts +28 -0
  664. package/src/pipeline/standard-output/auto-exposure/state.ts +156 -0
  665. package/src/pipeline/standard-output/color-transform.ts +124 -0
  666. package/src/pipeline/standard-output/graph.ts +139 -0
  667. package/src/pipeline/standard-output/lut-admission.ts +121 -0
  668. package/src/pipeline/standard-output/lut-gpu.ts +399 -0
  669. package/src/pipeline/standard-output/lut-state.ts +176 -0
  670. package/src/pipeline/standard-output/resources.ts +121 -0
  671. package/src/pipeline/standard-output/types.ts +58 -0
  672. package/src/pipeline/standard-pipeline.ts +232 -70
  673. package/src/pipeline/standard-post.ts +190 -27
  674. package/src/pipeline/standard-profile.ts +2 -1
  675. package/src/pipeline-builder.ts +28 -9
  676. package/src/pipeline-spec-types.ts +4 -0
  677. package/src/pipeline-spec.ts +37 -29
  678. package/src/plugin.ts +4 -0
  679. package/src/points-lines/standard-owner.ts +6 -14
  680. package/src/post-process-errors.ts +26 -18
  681. package/src/prepare/__tests__/extended-lighting-capability.unit.test.ts +2 -2
  682. package/src/prepare/extended-lighting/resources.ts +3 -3
  683. package/src/prepare/prepared-graphics-resolver.ts +9 -2
  684. package/src/record/__tests__/main-pass-geometry.unit.test.ts +68 -0
  685. package/src/record/__tests__/material-bg-cache.unit.test.ts +45 -0
  686. package/src/record/__tests__/material-slot-plan.unit.test.ts +57 -0
  687. package/src/record/__tests__/mesh-ssbo.unit.test.ts +40 -1
  688. package/src/record/__tests__/recovery-pipeline.unit.test.ts +20 -1
  689. package/src/record/__tests__/sprite-material-abi.unit.test.ts +41 -6
  690. package/src/record/__tests__/temporal-payload.unit.test.ts +2 -0
  691. package/src/record/__tests__/typed-frame-graph-plan.unit.test.ts +45 -0
  692. package/src/record/__tests__/view-ubo-layout.unit.test.ts +5 -0
  693. package/src/record/dynamic-geometry-consumption.ts +59 -0
  694. package/src/record/frame-lighting.ts +15 -12
  695. package/src/record/frame-snapshot.ts +146 -34
  696. package/src/record/frame-targets.ts +9 -0
  697. package/src/record/frame.ts +838 -170
  698. package/src/record/main-pass-geometry.ts +209 -103
  699. package/src/record/main-pass-material.ts +157 -40
  700. package/src/record/main-pass-sprite-draws.ts +16 -107
  701. package/src/record/main-pass.ts +141 -12
  702. package/src/record/material-uniforms.ts +1 -1
  703. package/src/record/mesh-ssbo.ts +59 -15
  704. package/src/record/prepared-material-bindings.ts +238 -0
  705. package/src/record/recovery-pipeline.ts +69 -90
  706. package/src/record/render-context.ts +123 -24
  707. package/src/record/shadow-pass.ts +1389 -230
  708. package/src/record/skybox-post-pass.ts +14 -3
  709. package/src/record/typed-frame-graph.ts +1064 -47
  710. package/src/record/view-ubo.ts +12 -1
  711. package/src/recovery/render-system-candidate.ts +135 -68
  712. package/src/recovery/render-system-roots.ts +113 -0
  713. package/src/recovery/types.ts +77 -0
  714. package/src/reflection/background.ts +151 -0
  715. package/src/reflection/filter.ts +3 -0
  716. package/src/reflection/projection.ts +16 -6
  717. package/src/reflection/record-owner.ts +308 -140
  718. package/src/render-contract.ts +237 -49
  719. package/src/render-graph-primitives.ts +34 -5
  720. package/src/render-pipeline.ts +90 -2
  721. package/src/render-system-extract-tail.ts +431 -162
  722. package/src/render-system-extract.ts +478 -119
  723. package/src/render-system-projections.ts +241 -0
  724. package/src/render-system.ts +715 -828
  725. package/src/scene/render-scene-types.ts +13 -4
  726. package/src/scene/render-scene.ts +577 -355
  727. package/src/scene/shadow-visibility.ts +21 -0
  728. package/src/ssao-config.ts +25 -8
  729. package/src/ssr/admission.ts +380 -14
  730. package/src/ssr/compose.ts +144 -0
  731. package/src/ssr/errors.ts +48 -0
  732. package/src/ssr/graph.ts +797 -0
  733. package/src/ssr/history.ts +436 -0
  734. package/src/ssr/hiz.ts +124 -0
  735. package/src/ssr/inspection.ts +124 -0
  736. package/src/ssr/resources.ts +202 -0
  737. package/src/ssr/temporal.ts +164 -0
  738. package/src/systems/skin-palette-allocator.ts +495 -148
  739. package/src/systems/skin-palette-types.ts +17 -0
  740. package/src/systems/transparent-dispatch.ts +90 -0
  741. package/src/targets/material-source.ts +34 -0
  742. package/src/temporal/coverage.ts +1 -1
  743. package/src/temporal/frame-coordinator.ts +23 -2
  744. package/src/temporal/gpu.ts +79 -12
  745. package/src/temporal/history.ts +3 -1
  746. package/src/temporal/inspection.ts +2 -1
  747. package/src/temporal/standard-scene-data.ts +10 -2
  748. package/src/temporal/target-coverage-attachment.ts +72 -0
  749. package/src/temporal/temporal-view.ts +11 -2
  750. package/src/temporal/view.ts +12 -2
  751. package/src/transmission/__tests__/standard-transmission.dawn.test.ts +219 -139
  752. package/src/typed-render-graph-primitives.ts +304 -74
  753. package/src/typed-shadow-passes.ts +122 -12
  754. package/src/volume/component.ts +1 -0
  755. package/src/volume/passes.ts +3 -3
  756. package/src/volume/temporal.ts +10 -0
  757. package/dist/chunk-GE3SDD72.mjs.map +0 -1
  758. package/dist/chunk-GF523LHF.mjs.map +0 -1
  759. package/dist/chunk-IQMKJLNU.mjs.map +0 -1
  760. package/dist/chunk-J52NMQKR.mjs.map +0 -1
  761. package/dist/chunk-J6KACNOH.mjs.map +0 -1
  762. package/dist/chunk-JAASCMXA.mjs.map +0 -1
  763. package/dist/chunk-MBU4ZVE7.mjs.map +0 -1
  764. package/dist/chunk-MYJ5RPJO.mjs.map +0 -1
  765. package/dist/chunk-RBDBZ5R7.mjs.map +0 -1
  766. package/dist/chunk-VSL23LZE.mjs.map +0 -1
  767. package/src/__tests__/instances-world-ownership.integration.test.ts +0 -62
  768. package/src/scene/__tests__/changed-block-summary.candidate.test.ts +0 -36
package/README.md CHANGED
@@ -33,7 +33,7 @@ GPU, Browser, and Dawn evidence that cannot execute is `not-run` or
33
33
  `unavailable`, never a verified structural substitute.
34
34
 
35
35
  The public frame vocabulary is intentionally small. `Camera` is the authoring
36
- owner for tone, antialiasing, bloom, and `historyVersion`; `Atmosphere` and
36
+ owner for tone, exposure, color grading, antialiasing, bloom, and `historyVersion`; `Atmosphere` and
37
37
  `Fog` are independent ECS components. The renderer extracts these facts into
38
38
  an immutable `FramePlan`, records one frame, and returns a `FrameReceipt`.
39
39
  `FrameReceipt` is the only successful synchronous proof that the host submit
@@ -47,8 +47,16 @@ An AI consumer can start with the public sequence `state -> inspect -> recover
47
47
  `alive` admits a frame; `device-lost` permits one explicit recovery flight;
48
48
  `recovering` shares that flight; `faulted` requires a new Renderer; and
49
49
  `disposed` is terminal. `inspect()` is detached POD evidence for the current
50
- state and recovery attempt. After `recover()` succeeds, submit the unchanged
50
+ state, recovery attempt, and named output contract. After `recover()` succeeds, submit the current
51
51
  draw input and use the new `FrameReceipt` as the proof for that generation.
52
+ An active device loss includes native `destroyed` notifications; only retired
53
+ or unpublished candidate generations are isolated from active health. Pending
54
+ dynamic geometry must be prepared again for the replacement generation before
55
+ admission. Explicit preparation between recovery and draw may upload its new
56
+ payload; the first-draw guard still rejects lazy cold uploads or pipeline builds
57
+ inside rendering. Preparation does not make an old-generation receipt current.
58
+ Recovery prepares the last successfully submitted workset; the next draw still
59
+ consumes current World edits and admits new resources through normal residency.
52
60
 
53
61
  `inspect().recovery` is always present. Its `phase` is `null` outside an active
54
62
  attempt and otherwise follows `quiesce`, `acquire-adapter`, `acquire-device`,
@@ -142,9 +150,20 @@ The remaining CSM fields keep these public units and defaults: `cascadeCount`
142
150
  is an integer in `[1, 4]` (default `4`), `splitLambda` is `[0, 1]` (default
143
151
  `0.75`), `cascadeBlend` is `[0, 0.5]` (default `0.2`), `mapSize` is a positive
144
152
  map resolution (default `2048`), `depthBias` is a depth value (default
145
- `0.005`), `normalBias` is a world-space offset (default `0.05`), and
153
+ `0.00001`, subtracted from normalized receiver depth), `normalBias` offsets the
154
+ surface receiver position along its world-space normal before projection (default `0.05`), and
146
155
  `shadowDistance` is a positive world-unit distance in meters (default `200`).
147
156
 
157
+ Normal offset and depth bias are independent: increasing the CSM depth span must
158
+ not magnify the world-space normal offset. Both PCF and PCSS project the offset
159
+ receiver; volumetric samples have no surface normal and use only depth bias.
160
+ Cascade projections include their incoming blend band, and PCSS depth spans
161
+ match the actual projection, including toward-light caster reach. The normal
162
+ position offset follows the approach in
163
+ [Three.js shadowmap_vertex](https://github.com/mrdoob/three.js/blob/e45cbf7ff66df079cfa39d9699ccb42729934be1/src/renderers/shaders/ShaderChunk/shadowmap_vertex.glsl.js).
164
+ The real-render regression is
165
+ [`shadow-contact.browser.test.ts`](../runtime/src/__tests__/shadow-contact.browser.test.ts).
166
+
148
167
  After `draw`, read the single `renderer.inspect().directionalShadow` projection.
149
168
  It is JSON-safe and bounded: `requested` is author intent, `effective` is the
150
169
  admitted profile, `status` is `accepted | fallback | rejected`, and
@@ -194,8 +213,42 @@ environment owners or multiple fog owners return structured errors with a
194
213
  code-specific `detail`; they do not create a second registry or silently pick
195
214
  the first entity. Frame facts contain IDs, revisions, and POD values only, not
196
215
  textures, buffers, devices, or other live GPU objects.
216
+
217
+ The Standard graph renders an Atmosphere source as a 128-by-128, six-face
218
+ `rgba16float` sky cube and a background pass before scene geometry. The selected
219
+ DirectionalLight supplies the sun direction, color, and illuminance; the sun
220
+ angle is in radians, and zero radius disables the visible disc. Geometry covers
221
+ the background through ordinary scene rendering, including transparent blending.
222
+ The cube excludes the disc. An explicit `Skylight` without an equirect asset
223
+ uses diffuse irradiance and roughness-prefiltered radiance from this same cube.
224
+ Its color and intensity remain the lighting controls; no Skylight means no
225
+ global ambient contribution. An explicit equirect keeps its image source.
226
+ Use a neutral Skylight tint when comparing local captures against the sky.
227
+
228
+ The cube has 786,432 texture payload bytes. Its 16-square irradiance cube and
229
+ 64-square, five-mip prefilter add 274,176 bytes; parameters and vertices total
230
+ 1,560 bytes, before backend allocation alignment. DeviceScope owns one cached
231
+ set, imported into each graph. Resize and probe topology changes reuse it;
232
+ disabling Atmosphere retains it until device-scope retirement. Device recovery
233
+ allocates a fresh set. Its six faces and lighting products update together only
234
+ when the selected environment signature changes, and become current only after
235
+ successful frame submission. An unchanged source runs only the background pass.
236
+ GPU pass observations
237
+ name `atmosphere-prepare`, `atmosphere-cube-0` through `atmosphere-cube-5`, and
238
+ `atmosphere-background`, `atmosphere-irradiance-*` and `atmosphere-prefilter-*`;
239
+ unavailable timestamps remain explicitly unavailable.
240
+ The source lifecycle inspection and graph resource accounting describe different
241
+ owners: a source revision is not a claim that its GPU cube was submitted.
242
+
243
+ The reusable [Wave 1 recipe](../../scripts/dev-verify/wave1-rendering/README.md)
244
+ shows the public scene inputs and associated acceptance gates.
245
+
197
246
  ## GPU pass timing: opt in, draw, observe, branch on status
198
247
 
248
+ When App owns the draw loop, `renderer.subscribe` delivers the exact `receipt`
249
+ in each `frame-submitted` event. Pass that receipt to `observe`; do not construct
250
+ one from the frame number. Start observation promptly because retention is bounded.
251
+
199
252
  GPU pass timing is disabled by default. Opt in once on `createRenderer`, keep the
200
253
  returned `FrameReceipt`, and request facts only for that receipt. The pass
201
254
  duration is a bounded GPU fact; it is not frame latency.
@@ -291,6 +344,18 @@ over-budget requests. A World with no `PointLightShadow` remains `inactive`;
291
344
  there is no pseudo-disabled shadow mode. Directional CSM remains a separate
292
345
  shadow owner; PointLightShadow is not a second directional-light path.
293
346
 
347
+ The cube atlas uses negated world axes: cube lookup is `lightPos - worldPos`,
348
+ and each face camera looks opposite its cube axis with the corresponding face-up
349
+ vector. This matches WebGPU framebuffer V without reflecting clip-space Y or
350
+ changing authored front-face/culling state. Raster matrices and sampling must
351
+ change together; a standalone Z flip is not a valid conversion.
352
+
353
+ Camera-culled scene meshes remain shadow candidates when their bounds intersect
354
+ an admitted light frustum. Their projected dispatch contains only `ShadowCaster`,
355
+ never Forward/Deferred work. Persistent composition uses the merged light frame,
356
+ including lights from another World. `frustumStats.culled` continues to count
357
+ camera rejection even when the retained object casts a shadow.
358
+
294
359
  ## Render happy path
295
360
 
296
361
  `RenderScene -> Standard Pipeline -> DeviceScope -> FrameReceipt` is the only frame
@@ -319,20 +384,50 @@ and keeps shadow, PBR, IBL, SSAO, bloom, tone, antialiasing, sky, material, VFX,
319
384
  ownership inside the same graph and submit boundary. CPU and WebGL2 remain capability lanes for the
320
385
  Cluster membership producer or for mesh/instance storage fallback; they do not reintroduce a
321
386
  PointLight/SpotLight buffer or a fixed four-light ABI.
322
- ## SSR M0 admission: inspect owners, then decide
387
+ ## SSR admission and bounded inspection
388
+
389
+ Coverage counters are `null` until GPU measurements are supplied; missing
390
+ measurements must not be interpreted as zero hits. The hello-ssr Browser smoke
391
+ captures a v7 RHI tape, reads trace/temporal/compose outputs at their producing
392
+ work indices, and requires positive confidence plus a measurable composition
393
+ delta. It also verifies that the composed resource reaches a later draw and
394
+ retains its pixels through that draw. Ordinary `hello-ssr` startup enables SSR.
395
+
396
+ The Standard G-buffer stores the visible material's response once:
397
+
398
+ | Target | RGB | Alpha |
399
+ |:--|:--|:--|
400
+ | Normal/roughness | Encoded world normal | Roughness |
401
+ | Albedo/metallic | Linear albedo | Metallic |
402
+ | Specular response/AO | Unit-radiance specular BRDF response, including occlusion | AO |
403
+
404
+ Forward lighting still owns direct light and emissive contributions. SSR uses
405
+ one fullscreen additive draw, not a geometry redraw: its RGB delta is
406
+ `confidence * (filteredRadiance * specularResponse - fallback)`. This prevents
407
+ equal-depth overlapping geometry from subtracting the fallback more than once.
408
+ Resolved radiance has a confidence-weighted mip chain; composition samples
409
+ `roughness² * (mipCount - 1)` with trilinear reconstruction. Receiver admission
410
+ uses the current lighting coverage, compatible half-resolution normals, and
411
+ the shared View's enable/roughness cutoff. It does not clip the rough lobe to
412
+ the central mirror ray's hit confidence. Material roughness changes the
413
+ reflection's filter footprint, not just its opacity; confidence remains bounded
414
+ by the current receiver's roughness fade. Coarse mips are spatial approximations,
415
+ not separate roughness-dependent ray traces.
323
416
 
324
417
  > [!IMPORTANT]
325
418
  > SSR M0 is a consumer-only admission boundary. It reads detached producer,
326
- > r32float format, and temporal receipts with one four-field integration
327
- > identity. Any missing, stale, structural-only, or mismatched receipt stays
328
- > `fallback-only` and emits zero SSR work; this section does not describe SSR
329
- > capture, ray tracing, Hi-Z, temporal resolve, composition, or history.
419
+ > r32float format, and optional prior temporal receipts with one four-field
420
+ > integration identity. Missing required, stale, structural-only, or mismatched receipts stay
421
+ > `fallback-only` and emits zero SSR work. An admitted Standard frame then
422
+ > projects the renderer-owned M1 spatial chain and M2 history through
423
+ > `renderer.inspect().ssr`; paired Browser/Dawn pixel evidence is still the
424
+ > gate for calling the result SSR v1.
330
425
 
331
426
  ```mermaid
332
427
  flowchart LR
333
428
  P["producer detached receipt"] --> A["admitSsrM0"]
334
429
  F["format stage receipt"] --> A
335
- T["temporal submit receipt"] --> A
430
+ T["optional prior temporal receipt"] -.-> A
336
431
  A -->|"all identity and verdict checks pass"| E["admitted: parent M1 may unlock"]
337
432
  A -->|"missing, stale, unavailable, or mismatched"| B["fallback-only: zero work"]
338
433
  ```
@@ -355,16 +450,64 @@ if (admission.status === 'fallback-only') {
355
450
  }
356
451
  ```
357
452
 
453
+ After the ordinary frame, read the detached spatial projection without opening
454
+ the graph or a GPU handle:
455
+
456
+ ```ts
457
+ const spatial = renderer.inspect().ssr;
458
+ switch (spatial.status) {
459
+ case 'not-requested':
460
+ case 'requested':
461
+ case 'fallback-only':
462
+ // Base probe / Skylight / neutral reflection remains visible.
463
+ break;
464
+ case 'structural-only':
465
+ // The graph is admitted, but this run has no executable shader carrier.
466
+ break;
467
+ case 'admitted':
468
+ // Inspect history, passRoster, fallbackSource, and coverage before evidence.
469
+ break;
470
+ }
471
+ ```
472
+
473
+ | `renderer.inspect().ssr.status` | Meaning | SSR work |
474
+ |:--|:--|:--|
475
+ | `not-requested` | Active camera has no `ScreenSpaceReflection` | Exact zero |
476
+ | `requested` | Authoring is present; M0/spatial facts are not yet complete | Exact zero |
477
+ | `fallback-only` | A closed config, lane, input, capability, or owner receipt failed | Exact zero |
478
+ | `structural-only` | Spatial admission and graph topology exist, but no executable shader source is bound | Topology only |
479
+ | `admitted` | Standard spatial path and shader manifest are bound | Inspect `history`, `passRoster`, `fallbackSource`, and coverage |
480
+
481
+ `history.state`, `history.bytes`, `history.resetCount`, `passRoster`, and
482
+ `fallbackSource` are bounded POD facts. They are evidence about the current
483
+ owner state, not an acceptance claim: M1/M2 remain an implementation checkpoint
484
+ until the paired Browser/Dawn 300-frame carrier, readback, falsifiers, and
485
+ performance gates are present.
486
+
487
+ SSR temporal feedback and presentation use distinct coordinates without another
488
+ allocation. The persistent color/depth and normal/confidence slots use fixed,
489
+ unjittered half-resolution centers (`2*p + 0.5` in full-resolution pixels).
490
+ Current trace samples are reconstructed onto that grid once; history reprojection
491
+ uses unjittered motion and validates depth/normal before interpolation. The
492
+ separate resolved output stays on the current jittered raster grid for composition
493
+ and reflection mip generation. Never feed that presentation reconstruction back
494
+ into history: repeated jitter filtering loses reflected texture contrast.
495
+
358
496
  Use `inspect -> owner recovery -> matching submit -> reinspect` as the complete
359
497
  AI route. No returned field is a device, graph node, texture, buffer, encoder,
360
498
  or other live handle. A stable generation keeps `resetCount`, `rebuildCount`,
361
499
  and additional format probe work at zero; a changed generation must be
362
500
  reinspected before admission can be considered again.
363
501
 
364
- `inspect()` is a synchronous snapshot. The first snapshot after binding may show
365
- an absent format or temporal receipt while the owner probe and first submit are
366
- still in flight; it is not a `pending` verdict. Run the ordinary owner frame,
367
- await `FrameReceipt.completed`, then inspect again before routing recovery.
502
+ `inspect()` is a synchronous snapshot. Explicit `RendererOptions.ssrIdentity`
503
+ opts into one real format probe during initialization of each device generation;
504
+ initialization and recovery await it before the first synchronous draw. A renderer
505
+ without that option does not probe. Camera demand still controls all per-frame
506
+ SSR attachments, passes, history and uploads. An unavailable format stays
507
+ fallback-only; an absent previous temporal receipt does not block fresh spatial
508
+ SSR. Current-frame motion/coverage comes from the Standard graph. History is
509
+ read only after its normal successful-submit validation, never fabricated to
510
+ admit the first frame.
368
511
 
369
512
  There are two action projections. `admission.failure.detail.action` is the coarse
370
513
  M0 gate instruction (`retry` for demand/readiness and `rebuild` for a missing or
@@ -377,12 +520,56 @@ API; use the existing owner operation shown here, then submit and inspect again:
377
520
  | `reflectionFallbackInspection.recoveryAction = use-LKG` | Keep the compatible probe row and draw the next frame | Read the committed row and matching temporal receipt |
378
521
  | `reflectionFallbackInspection.recoveryAction = use-Skylight` / `use-neutral` | Change the World-owned probe/`Skylight` source, then draw | Confirm selected source and committed generation |
379
522
  | `reflectionFallbackInspection.recoveryAction = recapture` / `rebuild` / `retry` | Draw again so the producer/RHI owner retries pending work | Await completion and inspect the owner receipt/failure |
380
- | `admission.failure.detail.action = retry` / `rebuild` | Retry the consumer frame, or rebuild the named owner receipt | Reinspect admission and all three receipt generations |
381
- | device-loss recovery | `await renderer.recover()` before the next draw | Confirm the replacement `deviceGeneration`, then re-probe |
523
+ | `admission.failure.detail.action = retry` / `rebuild` | Retry the consumer frame, or rebuild the named owner receipt | Reinspect required receipts and any supplied temporal receipt |
524
+ | device-loss recovery | `await renderer.recover()` before the next draw | Confirm the replacement `deviceGeneration` and its completed format probe |
382
525
 
383
526
  SSR never invokes these operations or mutates producer/device state; this table
384
527
  maps the typed action to the existing public World and Renderer seams.
385
528
 
529
+ ## Screen-space ambient occlusion
530
+
531
+ Enable AO through the same public Standard profile used at creation or by
532
+ `renderer.setProfile()`. It requires `renderPath: 'deferred'` for current-frame
533
+ depth and world normals; an incompatible profile returns a structured error.
534
+
535
+ The GLES fallback does not support the required raw-depth shader reads. It boots normally with AO disabled; requesting AO reports `feature-not-enabled` and requires WebGPU.
536
+
537
+ The default Vite shader producer includes SSAO, so `StandardProfile.ssao` needs no build-time opt-in. A deliberately stripped manifest reports `post-process-not-found` when AO is requested; it must not silently render white AO.
538
+
539
+ ```ts
540
+ const configured = renderer.setProfile({
541
+ ...renderer.inspect().profile,
542
+ renderPath: 'deferred',
543
+ ssao: { quality: 'medium', radius: 0.5, bias: 0.025, intensity: 1 },
544
+ });
545
+ if (!configured.ok) throw configured.error;
546
+ // Remove both AO passes and their per-frame upload work:
547
+ const disabled = renderer.setProfile({ ...renderer.inspect().profile, ssao: false });
548
+ if (!disabled.ok) throw disabled.error;
549
+ ```
550
+
551
+ | Parameter | Contract |
552
+ |:--|:--|
553
+ | `quality` | `low` / `medium` / `high`: 16 / 32 / 64 hemisphere samples; default `high` |
554
+ | `radius` | Positive finite view-space distance; default `0.5` |
555
+ | `bias` | Non-negative finite view-space depth bias; default `0.025` |
556
+ | `intensity` | Non-negative finite strength; default `1`; zero preserves the unoccluded image |
557
+ | `ssao: true` | Enables the same default parameter set |
558
+
559
+ AO runs at half resolution with a symmetric depth/normal-aware blur. Sampling
560
+ uses the raster projection, including camera jitter. Static, skinned, and
561
+ custom Standard surfaces multiply ambient illumination and the corresponding
562
+ SSR fallback by the same AO factor; direct lights remain unchanged. Material
563
+ occlusion is applied once. AO is reconstructed every frame and owns no temporal
564
+ history, so moved geometry cannot leave a retained AO image.
565
+
566
+ > [!NOTE]
567
+ > AO is a local screen-space approximation. Offscreen occluders are absent,
568
+ > and silhouettes can retain small half-resolution sampling artifacts. Use
569
+ > the plane-plus-cube example at `apps/learn-render/5.advanced-lighting/9.ssao`
570
+ > to compare contact, lift, quality, radius, and disabled behavior. GPU timings
571
+ > are opt-in via `gpuPassTiming` and receipt-bound `observe(..., { include: ['timings'] })`.
572
+
386
573
  ## Target and probe lifecycle index
387
574
 
388
575
  The public target route is one Renderer owner: create a typed `RenderTarget`, create a
@@ -406,22 +593,144 @@ must match the committed row's frame and device generation before it is used as
406
593
  The exact public names and stable consumer IDs are machine-readable in
407
594
  `src/__tests__/render-target-public-schema.json`.
408
595
 
596
+ ### Dynamic reflection probes
597
+
598
+ ```ts
599
+ const probe = world.spawn(
600
+ { component: Transform, data: { pos: [0, 1, 0] } },
601
+ { component: ReflectionProbe, data: {
602
+ halfExtents: [5, 3, 5], resolution: 128,
603
+ updateIntent: REFLECTION_PROBE_UPDATE_ON_CHANGE,
604
+ } },
605
+ ).unwrap();
606
+ // Explicit invalidation also works for a once probe.
607
+ const current = world.get(probe, ReflectionProbe).unwrap();
608
+ world.set(probe, ReflectionProbe, {
609
+ invalidationVersion: current.invalidationVersion + 1,
610
+ }).unwrap();
611
+ ```
612
+
613
+ | Update intent | Trigger |
614
+ |:--|:--|
615
+ | `REFLECTION_PROBE_UPDATE_ONCE` | Initial capture, explicit invalidation, or probe movement |
616
+ | `REFLECTION_PROBE_UPDATE_ON_CHANGE` | Above, plus retained scene/material/geometry or light/environment changes |
617
+ | `REFLECTION_PROBE_UPDATE_CONTINUOUS` | Start another capture after the previous candidate completes |
618
+
619
+ The renderer shares **one capture face or PMREM face/mip step per submitted
620
+ frame across all probes**, round-robin. One probe needs six raw faces plus 30
621
+ filter steps: 36 completed submissions after pipeline readiness. Multiple probes
622
+ share that budget. This is amortized realtime updating, with visible latency;
623
+ it does not provide a fresh six-face snapshot every display frame. Changes
624
+ arriving during a cycle coalesce into the next cycle rather than starving
625
+ publication. Video and time-driven shader changes require continuous mode or
626
+ explicit invalidation when their producer does not update scene change evidence.
627
+
628
+ A complete candidate swaps atomically after successful GPU completion. Failed
629
+ submissions retry the same step, and stale device-generation completions cannot
630
+ publish. A moving probe uses Skylight fallback until a capture at its new center
631
+ is ready. Each face sees the retained authored scene, including objects outside
632
+ the display camera; authored hidden objects stay excluded. Probe specular keeps
633
+ the global Skylight diffuse color, intensity, and rotation. Shared materials
634
+ still select probes independently for each object.
635
+
636
+ Raw faces sample the selected Atmosphere sky cube, otherwise the authored
637
+ `SkyboxBackground`, or the Skylight environment when no skybox is present,
638
+ in the same HDR geometry pass. Atmosphere capture declares a sampled-read of
639
+ the existing graph-owned cube; it does not evaluate another sky or allocate
640
+ another sky texture. Its sun disc remains in the display background only,
641
+ while directional lighting supplies the specular sun response. Changes to
642
+ Atmosphere parameters invalidate `on-change` probes through the existing
643
+ selected-environment signature. Camera-only changes do not invalidate them.
644
+ Environment asset readiness precedes capture. Camera-cube orientation is normalized during PMREM
645
+ production; runtime probe sampling does not apply the image-IBL Y conversion.
646
+ Place capture centers in empty space, away from the interior of opaque objects.
647
+
648
+ Fallback source receipts publish only after the actual graph output completes
649
+ on the GPU. Normal rendering performs no fallback pixel readback or hashing.
650
+ Set `captureReflectionFallbackReadback: true` on `createApp` / `createRenderer`
651
+ only for diagnostics that need `reflectionFallbackReadback`: this copies,
652
+ maps, validates and hashes the full HDR attachment each submitted frame.
653
+ Source receipts contain selection facts; the optional readback receipt contains
654
+ actual sampled pixel evidence. Submission failure preserves compatible LKG,
655
+ and stale device or source completions cannot publish either kind of receipt.
656
+
657
+ The selected probe is admitted per object, but its influence is evaluated per
658
+ shaded pixel. Pixels outside `halfExtents` use the global Skylight specular map;
659
+ the outer 10% of the box blends into that source. The global texture stays
660
+ resident alongside the local cube, including GPU-driven and custom Surface
661
+ materials. Changing either resource invalidates material binding reuse.
662
+
663
+ `boxProjection` defaults to `false`, as a general captured cubemap has no known
664
+ wall geometry. Set it to `true` only when `halfExtents` also describes a room's
665
+ reflection proxy. A small influence box around an outdoor object is not a room
666
+ proxy: forcing that correction can reflect the wrong surface even for an
667
+ otherwise valid ray. The SSR demo exposes independent SSR/probe controls and a
668
+ **Plane + cube** fixture; the Objects fixture keeps every object resting on its
669
+ floor and contains the probe receiver within the influence box.
670
+
671
+ Resolution is an actual power of two from 16 through 256. The 64 MiB admission
672
+ budget counts the raw cube, two filtered cubes, and depth attachments, with at
673
+ most 16 probes; at 256 pixels the byte budget admits five. Start at 64 or 128 for
674
+ frequent updates. `inspect().reflectionProbes.updates` reports intent, requested
675
+ and captured revision, active generation, pending work, and completion latency.
676
+ The SSR fallback source identity includes the active probe generation, so a
677
+ new environment invalidates stale SSR history. Probe capture, SSR ray tracing,
678
+ and SSR composition remain separate observable work.
679
+
409
680
  ## Stable inspection and dark-gradient evidence
410
681
 
411
682
  Progressive disclosure is intentional: read Camera configuration first, then
412
683
  the graph facts, backend facts, and finally the stable observation identity.
413
- The JSON-safe `RenderInspection` projection names the output contract directly:
684
+ The JSON-safe `RenderInspection` projection keeps renderer lifecycle facts and
685
+ the output contract in separate named subtrees. Read `renderer.inspect().output`
686
+ for output facts; do not infer output state from renderer-level fields:
414
687
 
415
688
  | Field | Meaning | Owner |
416
689
  |:--|:--|:--|
417
- | `outputTransform` | `forgeax::standard::output-transform` | Standard post chain |
418
- | `displayEncoded` | final output is display encoded | Output Transform |
419
- | `intermediateFormat` | linear intermediate format (`rgba16float`) | Standard target owner |
420
- | `surfaceStorage` / `surfaceDisplay` | raw storage and display surface formats | surface adapter |
421
- | `endpoint` | `surface.storage.raw` | backend adapter |
422
- | `capability` / `error` | structured availability and recovery facts | RHI/render surface |
690
+ | `output.outputTransform` | `forgeax::standard::output-transform` | Standard post chain |
691
+ | `output.displayEncoded` | final output is display encoded | Output Transform |
692
+ | `output.intermediateFormat` | linear intermediate format (`rgba16float`) | Standard target owner |
693
+ | `output.surfaceStorage` / `output.surfaceDisplay` | raw storage and display surface formats | surface adapter |
694
+ | `output.endpoint` | `surface.storage.raw` | backend adapter |
695
+ | `output.capability` / `output.error` | structured availability and recovery facts | RHI/render surface |
423
696
  | `observation.observationId` / `frameId` | stable correlation refs | renderer observation owner |
424
697
 
698
+ ### Auto exposure and 3D LUT authoring
699
+
700
+ Exposure is a Camera-owned closed union. Manual exposure is a positive
701
+ multiplier; auto exposure carries a fallback, EV compensation, bounded EV
702
+ range, and non-negative adaptation rates. The renderer owns the GPU receipt,
703
+ but the author owns the Camera input:
704
+
705
+ ```ts
706
+ world.set(camera, Camera, {
707
+ exposure: { kind: 'auto', fallback: 1, compensationEv: 0,
708
+ rangeEv: [-4, 4], rates: [2, 1] },
709
+ }).unwrap();
710
+
711
+ const output = renderer.inspect().output;
712
+ if (output.autoExposure?.receipt.committed !== true) {
713
+ throw new Error(output.error?.hint ?? 'auto exposure is not committed');
714
+ }
715
+ ```
716
+
717
+ Color grading uses the ordinary shared `TextureAsset` route. Import/cook the
718
+ 3D LUT through Pack/Catalog, load the shared handle by its GUID/source key, and
719
+ bind it to the Camera; there is no second LUT registry or URL-derived identity:
720
+
721
+ ```ts
722
+ world.set(camera, Camera, {
723
+ colorLut: world.allocSharedRef('TextureAsset', lutHandle),
724
+ colorLutStrength: 0.75,
725
+ }).unwrap();
726
+ const lut = renderer.inspect().output.standardLut;
727
+ // Match lut.sourceKey and lut.receipt.frameId to the submitted FrameReceipt.
728
+ ```
729
+
730
+ `output.autoExposure`, `output.standardLut`, and `output.error` are detached
731
+ facts. A fallback or last-known-good row is a recovery observation, not proof
732
+ that a physical Browser/Dawn workload or timing gate completed.
733
+
425
734
  `renderer.inspect()` and `renderer.observe()` never carry ROI pixels, scanlines,
426
735
  brightness or level metrics. The hello-fxaa `hello-fxaa/dark-gradient/v1`
427
736
  fixture report owns those fields and records the fixed camera, low-light scene,
@@ -508,6 +817,83 @@ gate exercises the browser delivery path. Runtime inspection uses the public
508
817
  belongs in `packages/render/bench` and its dev-verify producer. Render does
509
818
  not expose a timing controller or a second membership-specific API.
510
819
 
820
+ ## TAA accumulation and private stability
821
+
822
+ TAA reconstructs unjittered current color, validates reprojected depth, and
823
+ accumulates HDR using luminance-preserving compressed-domain weights. History
824
+ depth validation recognizes mixed current-depth coverage, following Three's
825
+ TRAA edge exception: background/geometry and depth-discontinuous 3x3 edges
826
+ may retain clipped history instead of alternating between accumulated and
827
+ raw current color. Uniform-depth disocclusion, absent current geometry,
828
+ out-of-bounds reprojection, invalid global history, motion and reactivity
829
+ retain their rejection or weighting behavior. The GPU coverage regression
830
+ executes the complete resolve, including the non-edge negative cases.
831
+ History clipping uses YCoCg bounds. After eight accepted stationary frames, its bounds
832
+ cover the texels supporting the reconstructed neighborhood; motion, rejection,
833
+ or reactivity resets that private age. The history still updates every frame:
834
+ this is not a frozen screenshot or an appearance acceptance claim.
835
+
836
+ The private age continues to 128 accepted stationary frames without another
837
+ surface. History weight stays at 0.95 through frame 64, then smoothly rises to
838
+ 0.99 by frame 128 to reduce residual phase response. The eight-frame clipping
839
+ footprint remains unchanged. Clipping relaxation runs earlier, from accepted
840
+ frame 8 through frame 64, while the 0.95 history weight still permits responsive
841
+ accumulation. Releasing it only after increasing history weight preserves a
842
+ biased clipped mean and causes prolonged static drift. Once settled, a single phase's unsupported
843
+ color does not erase accumulated subpixel coverage: clipping resumes only
844
+ after eight consecutive unsupported samples in the same signed dominant RGB
845
+ direction. Finding support or reversing direction breaks that streak.
846
+ Persistent unmarked color changes therefore restore color clipping within one
847
+ complete jitter cycle. This color correction preserves the accepted stationary
848
+ age and its reconstruction footprint; it must not collapse the next frame's
849
+ neighborhood from 5x5 to 3x3. Motion,
850
+ reactivity or rejection immediately reset
851
+ the age and restore fast recovery; this delay is necessary because zero
852
+ velocity alone does not establish settled radiance.
853
+
854
+ An optional secondary-source reactivity texture joins the scene reactive lane
855
+ before history weighting and stability counting. Standard forwards SSR's
856
+ existing hit-source mask through a typed sampled read; TAA takes a conservative
857
+ 7x7 maximum at that texture's resolution. This covers the measured vacated
858
+ reflection edge missed by the former radius-one footprint; it is a bounded
859
+ dilation, not an arbitrary-speed reflected-motion guarantee. Reflected source motion can therefore
860
+ reset a stationary receiver without changing the receiver's velocity or depth.
861
+ The disabled path binds existing temporal data but does not sample it as a mask;
862
+ no extra texture or pass is allocated. The 658-frame Browser and Dawn feedback
863
+ variant checks secondary-motion reset and recovery; Dawn additionally verifies
864
+ a failing control that ignores the mask. Each cyclic input step checks the
865
+ Karis recurrence from the actual stored history against its adjacent FP16
866
+ values; an independent infinite-precision tail detects accumulated bias.
867
+ The premature high-weight counterexample must also fail. This does not establish visual
868
+ convergence or ghost-free SSR.
869
+
870
+ Color writeback stochastically selects adjacent representable FP16 values before
871
+ attachment conversion. This prevents consistently downward conversion from
872
+ accumulating a dark history bias without widening the history textures. Its
873
+ deterministic pixel/frame hash shares one RGB threshold, leaves alpha and
874
+ temporal metadata unchanged, and does not freeze the jitter sequence. Browser
875
+ and Dawn run the same 528-frame raster-feedback regression against an analytic
876
+ compressed-HDR recurrence; this precision gate is not visual acceptance.
877
+
878
+ | Ping-pong history | Format | Consumer |
879
+ |:--|:--|:--|
880
+ | Color | `rgba16float` | TAA and downstream HDR processing |
881
+ | Motion, depth, reactivity | `rgba16float` | Unchanged `temporal-v1` metadata, including Motion Blur |
882
+ | Stationary age and settled clipping streak | `r8unorm` | TAA only; never stored in the reactive lane |
883
+
884
+ The private byte uses codes 0–128 for stationary age and 129–170 for one
885
+ through seven unsupported samples in six signed RGB directions. The eighth
886
+ matching sample restores clipping and resets age. This is one encoded state,
887
+ not an additional attachment or a change to downstream temporal metadata.
888
+ Browser and Dawn raster-feedback tests cover phase-local coverage, unmarked
889
+ color steps, and a falsifier that permanently disables clipping.
890
+
891
+ These six surfaces share the existing successful-submit, abort, replacement,
892
+ and retirement lifecycle. Texture accounting is 34 bytes per output pixel;
893
+ `inspect().temporal.resources` includes active, candidate, and retiring states.
894
+ Fullscreen pipeline targets come from the declared attachment format list,
895
+ not from inspecting shader source text for a particular output structure.
896
+
511
897
  ## Motion Blur temporal consumer
512
898
 
513
899
  The Standard camera may carry the presence-enabled `MotionBlur` component. Its
@@ -664,69 +1050,135 @@ infer state from URLs, or repair a producer failure. Aggregate counts are
664
1050
  derived from the observations, so an AI can inspect first, repair or recook the
665
1051
  producer, and retry without guessing at hidden renderer state.
666
1052
 
667
- ### Large Instances ownership
1053
+ ### Renderer-owned Instances and CPU bounds
668
1054
 
669
- > [!IMPORTANT]
670
- > World owns `Instances.transforms`. Create, replace and read instance matrices
671
- > through ECS without constructing a Renderer. A scene remains usable by another
672
- > Renderer after the first is disposed; glTF SceneAssets retain their matrices.
1055
+ `Instances` is a lightweight ECS association containing only a
1056
+ renderer-owned `collectionId`. Create and mutate the canonical packed matrices
1057
+ through the same `renderer.instances` owner; it validates mat4 stride, keeps
1058
+ revision/capacity/dirty ranges, and returns detached inspection snapshots.
1059
+
1060
+ ```ts
1061
+ function identityMatrices(count: number): Float32Array {
1062
+ const matrices = new Float32Array(count * 16);
1063
+ for (let index = 0; index < count; index += 1) {
1064
+ const offset = index * 16;
1065
+ // Column-major identity, with a deterministic layout so the instances are visible.
1066
+ matrices[offset] = 1;
1067
+ matrices[offset + 5] = 1;
1068
+ matrices[offset + 10] = 1;
1069
+ matrices[offset + 15] = 1;
1070
+ matrices[offset + 12] = (index % 100) * 2;
1071
+ matrices[offset + 14] = Math.floor(index / 100) * 2;
1072
+ }
1073
+ return matrices;
1074
+ }
1075
+
1076
+ const collection = renderer.instances.create({
1077
+ transforms: identityMatrices(instanceCount),
1078
+ }).unwrap();
1079
+ world.spawn(
1080
+ { component: MeshFilter, data: { assetHandle: cube } },
1081
+ { component: MeshRenderer, data: {} },
1082
+ { component: Instances, data: { collectionId: collection.collectionId } },
1083
+ );
1084
+
1085
+ const patch = identityMatrices(1);
1086
+ patch[12] = 4;
1087
+ renderer.instances.update(collection.collectionId, {
1088
+ start: 0,
1089
+ transforms: patch,
1090
+ });
1091
+
1092
+ // Replacement keeps the logical identity and publishes a new revision.
1093
+ renderer.instances.replace(collection.collectionId, identityMatrices(instanceCount + 1));
1094
+ const current = renderer.instances.inspect(collection.collectionId).unwrap();
1095
+ const detached = renderer.instances.snapshot(collection.collectionId).unwrap();
1096
+ renderer.instances.release(collection.collectionId);
1097
+ ```
673
1098
 
674
- | Data | Owner |
1099
+ The lifecycle is deliberately one owner: `create` allocates canonical CPU
1100
+ storage, `replace` publishes a complete new revision (and grows capacity when
1101
+ needed), `update` validates a bounded matrix interval and records its dirty
1102
+ range, `inspect` returns identity/count/capacity/revision, `snapshot` returns a
1103
+ detached matrix copy plus dirty ranges for extraction, and `release` makes the
1104
+ collection id terminal. A caller never provides a GPU buffer or maintains
1105
+ backend-specific chunks. `detached.transforms` is an observation, not mutable
1106
+ renderer state.
1107
+
1108
+ Render snapshots one collection per frame and derives CPU union bounds from the
1109
+ mesh AABB, entity world matrix, and detached matrices. Direct, GPU-driven, and
1110
+ backend fallback lanes consume that same projection; internal GPU chunks never
1111
+ become ECS or game-side state. Missing, malformed, or empty collections fail
1112
+ closed or remain a conservative no-cull result without manufacturing an
1113
+ identity instance. The initial payload must contain valid matrices: an
1114
+ all-zero mat4 has a zero homogeneous `w` and can produce a blank frame. Do not
1115
+ add matrix bytes, bounds, or chunk fields to the public `Instances` component or
1116
+ duplicate this renderer fact in Pack/asset state.
1117
+
1118
+ #### Instance inspection and recovery
1119
+
1120
+ `renderer.inspect().instanceCollections` is detached, bounded evidence for
1121
+ every live collection. Each row contains:
1122
+
1123
+ | Field | Meaning |
675
1124
  |:--|:--|
676
- | Instance matrices and entity lifecycle | World component storage |
677
- | Detached instance snapshot and content revision | Per-renderer projection |
678
- | GPU allocation, binding-sized chunks and uploaded revision | Existing renderer device generation |
679
-
680
- Small managed arrays keep the existing BufferPool size classes. Arrays larger
681
- than the largest pooled class use dedicated allocations and are released instead
682
- of retained in a large free bucket. The same authoring path supports 1,500,
683
- 10,000 and 20,000 instances without application chunking.
684
-
685
- Each renderer compares complete extracted matrices against its own prior
686
- projection. Identical input retains the projection revision; a changed input
687
- publishes a complete new snapshot. Upload completion belongs to each GPU
688
- resident, not to a shared authoring dirty-range or a snapshot read. Stable
689
- residents skip upload; changed or new residents upload the complete applicable
690
- binding chunk. A renderer that skips frames still receives every latest value.
691
-
692
- `renderer.inspect().instanceCollections` reports derived projection identity,
693
- count, revision and upload/residency facts. These IDs never appear in components
694
- or SceneAssets and cannot be used to author a scene. Residency here describes
695
- the direct/chunked instance buffers; GPU-driven primary rendering is reported
696
- by `inspect().renderScene` and does not retain those unused buffers.
697
-
698
- Device loss uses the existing `renderer.recover()` candidate-generation protocol.
699
- The World stays authoritative; candidate preparation initializes replacement
700
- instance buffers from the complete snapshot before publication. Main and shadow
701
- passes share these residents, so the first recovered frame performs no cold
702
- instance upload for the prepared visible workset. Instances
703
- introduce no additional recovery API or lifecycle.
704
-
705
- Shader preparation, layouts and uploads all consume `RhiDevice.caps.storageBuffer`.
706
- Material boot variants include only axes declared by the material manifest.
707
- The Dawn gate restricts the advertised capability on a real WebGPU device and
708
- verifies uniform bindings below and above 128 instances with material pixels,
709
- GPU validation and stable-upload receipts.
710
-
711
- Population checks use the same World component route:
1125
+ | `collectionId`, `count`, `capacity`, `revision` | Logical identity and canonical authoring revision. |
1126
+ | `residentGeneration`, `lane` | Device generation and the selected `direct-storage`, `chunked-storage`, `direct-uniform`, `chunked-uniform`, `unresident`, or `unavailable` lane. |
1127
+ | `dirtyRanges`, `uploadRanges` | Canonical pending edits and the ranges uploaded by the current resident. |
1128
+ | `uploadedBytes`, `requestedBytes`, `supportedBytes` | Measured upload and admission byte facts; unsupported limits remain explicit. |
1129
+ | `backend`, `owner` | Backend identity and the fixed owner `renderer.instances`. |
1130
+ | `error` | A structured record-stage failure with `code`, `expected`, `hint`, and typed `detail` facts. |
1131
+
1132
+ Authoring failures use the same closed `InstanceCollectionError` contract.
1133
+ For example, an invalid stride is rejected before publication and carries
1134
+ typed facts instead of requiring message parsing:
1135
+
1136
+ ```ts
1137
+ const result = renderer.instances.create({ transforms: new Float32Array(17) });
1138
+ if (!result.ok) {
1139
+ const { code, expected, hint, detail, facts } = result.error;
1140
+ // facts expose requestedBytes, supportedBytes, backend, owner, cause, and
1141
+ // recovery; detail retains operation metadata. Repair the payload and retry.
1142
+ void code;
1143
+ void expected;
1144
+ void hint;
1145
+ void detail;
1146
+ void facts;
1147
+ }
1148
+ ```
1149
+
1150
+ After a `device-lost` transition, call `await renderer.recover()` and retry the
1151
+ same draw request. The renderer drops generation-owned residents, keeps the
1152
+ canonical collection, and uploads every matrix into the new resident before
1153
+ recording; it does not reuse an old partial dirty range for uninitialized GPU
1154
+ memory. If inspection reports `unavailable`, follow its `error.detail.recovery`
1155
+ and repair the named capability/producer before retrying.
1156
+
1157
+ The large-instance smoke exercises the real Dawn path for all admitted sizes:
712
1158
 
713
1159
  ```bash
714
- pnpm exec vitest run --config vitest.browser.config.ts --project=browser apps/parity/instancing-static/src/__tests__/instances.browser.test.ts
715
- INSTANCE_COUNT=20000 SMOKE_MIN_FRAMES=600 SMOKE_DURATION_MS=0 pnpm --filter @forgeax/parity-instancing-static smoke
1160
+ for count in 1500 10000 20000; do
1161
+ INSTANCE_COUNT=$count SMOKE_MIN_FRAMES=600 SMOKE_DURATION_MS=0 \
1162
+ pnpm --filter @forgeax/parity-instancing-static smoke
1163
+ done
716
1164
  ```
717
1165
 
718
- ### Instances CPU bounds contract
1166
+ Each run must retain one logical collection, submit real work, upload on its
1167
+ first resident frame, and report zero transform-upload bytes on unchanged
1168
+ frames. Browser WebGPU acceptance additionally uses the browser/dev-server
1169
+ transport through the same production fixture and parameterizes the same three
1170
+ populations:
719
1171
 
720
- `Instances` remains author data containing only packed local transforms. The
721
- renderer derives a CPU union bound from each mesh AABB, entity world matrix,
722
- and every instance matrix. The union cache is scoped by World/entity and
723
- invalidated by mesh, entity-transform, instance-array, slot removal, and
724
- generation changes. Missing, malformed, or empty instance data is a
725
- conservative no-cull result; it never creates a synthetic identity instance.
726
- The GPU path keeps the mesh-local AABB and performs per-instance visibility,
727
- while the CPU path uses the derived union only to avoid dropping an entity
728
- whose origin is outside the view. Do not add `bounds` to the public
729
- `Instances` component or duplicate this renderer fact in Pack/asset state.
1172
+ ```bash
1173
+ pnpm exec vitest run --config vitest.browser.config.ts --project=browser \
1174
+ apps/parity/instancing-static/src/__tests__/instances.browser.test.ts
1175
+ ```
1176
+
1177
+ The Browser test submits 600 frames for each population, asserts a real
1178
+ `webgpu` backend, captures the first and stable `instanceCollections` upload
1179
+ witnesses, reads the presented canvas through the browser compositor, and
1180
+ requires visible spread at three or more grid samples with zero renderer/RHI
1181
+ error events. Dawn structural evidence alone is not a browser validation.
730
1182
 
731
1183
  ## Points and Lines authoring (M1)
732
1184
 
@@ -865,6 +1317,10 @@ the custom parameters on the returned root MaterialAsset. Compiler and loader
865
1317
  project that published contract without adding fields based on a Pass module.
866
1318
  Physical layer declarations remain sparse. A custom full-shader material does
867
1319
  not acquire Standard semantics by adding a shadow Pass or a `surface` slot.
1320
+ When a producer also publishes `vs_main` and `vs_scene_index` for a controlled
1321
+ full-custom program, the same prepared material can use the GPU-driven indirect
1322
+ lane; ordinary custom WGSL without that ABI remains on its direct/specialized
1323
+ lane.
868
1324
 
869
1325
  The custom WGSL implements only `evaluate_surface`; it does not declare
870
1326
  stages, bindings, lighting, or output code. The Engine composes the result
@@ -886,7 +1342,7 @@ consumer never re-infers physical layers from `SurfaceData`.
886
1342
  |:--|:--|:--|
887
1343
  | base-only Standard | no second physical layer | Deferred plus Forward and ShadowCaster |
888
1344
  | Standard with a physical layer | physical layer present | Forward plus ShadowCaster |
889
- | full-custom escape hatch | no Standard plan | authored custom pass contract |
1345
+ | full-custom escape hatch | no Standard plan | authored custom pass contract; GPU-driven only with a published direct/scene-index ABI |
890
1346
 
891
1347
  ## Points and Lines runtime contract
892
1348
 
@@ -959,18 +1415,32 @@ state published by `World.update()`.
959
1415
 
960
1416
  ## Persistent render scene
961
1417
 
962
- The ordinary single-World path bootstraps one renderer-owned CPU projection,
963
- then drains the World's bounded change journal. A no-change frame reuses the
964
- last visible snapshot without traversing renderable archetypes; transform
965
- changes update only their stable projection slots. Unrelated gameplay
966
- component writes do not invalidate render state.
1418
+ Every attached World composition bootstraps one renderer-owned CPU projection,
1419
+ then consumes each World's component and shared-reference change versions. One identity-based update
1420
+ publication merges content, root transforms, and instance changes, including
1421
+ when all occur in the same frame. Its GPU projection compares affected matrix
1422
+ and metadata rows before uploading them. An unchanged frame retains its existing
1423
+ snapshot; unrelated gameplay component writes do not invalidate render state.
1424
+ There is no exclusive transform/instance scene admission followed by a separate
1425
+ rebuild implementation. Missing producer evidence causes conservative source
1426
+ extraction into the same retained projection. World reordering and catalog
1427
+ reconciliation preserve surviving slots and their submitted temporal history.
967
1428
 
968
1429
  Mutable shared payloads use the same explicit-dirty rule: mutate the resolved
969
1430
  payload, then call `world.sharedRefs.markChanged(handle)`. The renderer compares
970
- one monotonic shared-ref epoch on the no-change path, drains the bounded exact
971
- mutation journal when it advances, and refreshes only projection slots indexed
972
- by the changed material handle. Journal overflow and topology-changing material
973
- edits remain explicit full-reconcile reasons rather than silent partial state.
1431
+ one monotonic shared-ref epoch on the no-change path, reads changed handles
1432
+ when it advances, and refreshes the projection slots indexed by changed
1433
+ material or mesh handles. Missing source evidence requests conservative
1434
+ source reconciliation. Camera, light, and environment facts refresh independently
1435
+ of geometry publication; their edits do not discard retained geometry.
1436
+
1437
+ Instance collection revisions refresh only their consumers. Visibility and
1438
+ parent changes refresh the affected subtree, and joint changes refresh the
1439
+ retained skin consumers. Shadow pass facts belong to each retained renderable;
1440
+ frame ownership is derived before camera culling so offscreen casters remain
1441
+ available. CPU visibility, occlusion, and LOD consume this same projection on
1442
+ every frame, including frames submitted through GPU-driven raster. No scene
1443
+ classification enables a second extraction or visibility implementation.
974
1444
 
975
1445
  When `RhiCaps.storageBuffer` is available, the same projection owns persistent
976
1446
  Primitive, Instance, Transform, DrawTemplate, and Material GPU tables. The
@@ -998,11 +1468,143 @@ reason. The nested `gpu` status is one of `inactive`, `unsupported`, `resident`,
998
1468
  `rebuild-pending`, or `error`; resident state additionally reports capacity,
999
1469
  upload ranges and bytes, grows, clears, rebuilds, and no-change frames. The
1000
1470
  `gpuDriven` inspection reports whether stable frames materialized or validated
1001
- GPU-owned rows and whether candidate or batch topology bytes were uploaded.
1471
+ GPU-owned rows and whether candidate or batch topology bytes were uploaded. Its
1472
+ lifetime `residencyValidationScans` and `residencyValidationCacheHits` counters
1473
+ make the CPU residency boundary auditable: an unchanged persistent frame should
1474
+ reuse the producer rows, while mesh, device, pipeline, or catalog generations
1475
+ force a new validation.
1002
1476
  GPU and renderer contract errors arrive through the single `renderer.subscribe`
1003
1477
  event stream (`event.kind === 'error'`); the renderer does not expose a second
1004
1478
  error listener registry.
1005
1479
 
1480
+ ## GPU-driven PBR / shadow / skin navigation
1481
+
1482
+ The shortest public declaration uses the same `Materials.standard` producer as
1483
+ the runtime and imported-skin carriers. `alphaCutoff` is the Alpha Mask
1484
+ contract; `castShadow` publishes the matching ShadowCaster pass.
1485
+
1486
+ ```ts
1487
+ import { Materials } from '@forgeax/engine/render';
1488
+
1489
+ const material = Materials.standard({
1490
+ baseColor: [1, 1, 1, 1],
1491
+ alphaCutoff: 0.5,
1492
+ castShadow: true,
1493
+ });
1494
+ const materialHandle = world.allocSharedRef('MaterialAsset', material);
1495
+ void materialHandle;
1496
+ ```
1497
+
1498
+ | Candidate | Main GPU lane | Shadow GPU lane | Closed result |
1499
+ |:--|:--:|:--:|:--|
1500
+ | Standard opaque or Alpha Mask, finite resources | supported | supported | `gpu` |
1501
+ | Standard skin with imported or explicitly authored finite animated bounds | supported | supported | `gpu` |
1502
+ | Skin bounds missing or non-finite | refused | refused | `cpu-deformation` |
1503
+ | Capable resource/pipeline not ready | refused | refused | `blocked` (`resource-not-ready`) |
1504
+ | Blend, transmission, morph, custom without a published ABI, or unsupported topology | refused | refused | `cpu-semantic` |
1505
+ | ABI-backed custom Surface/full-custom row within the producer page | supported | supported when a ShadowCaster ABI is published | `gpu` |
1506
+
1507
+ The custom row is deliberately about the published producer contract, not a
1508
+ shader-name allowlist. A custom program is eligible only when its cooked receipt
1509
+ contains matching direct/scene-index entries, reflected resources, vertex inputs,
1510
+ and a schema-derived row no larger than the 512-byte GPU Scene page. Custom
1511
+ storage-buffer resources, renderer-local/video sources, and pass attachments that
1512
+ have no corresponding prepared owner remain on their specialized lane until that
1513
+ owner publishes the missing binding and recovery contract. The legacy
1514
+ [`custom-shader` demo](../../apps/hello/custom-shader) is direct-only by design;
1515
+ it does not publish `vs_scene_index`, so it is not evidence for the indirect lane.
1516
+
1517
+ Clustered local lights and SSAO use the same group-2 lighting bindings in direct
1518
+ and scene-index programs, including custom Surface programs. These switches do
1519
+ not disable GPU-driven admission. SSAO requires `renderPath: 'deferred'` to
1520
+ produce depth and normals. SSR's fallback MRT selects the corresponding
1521
+ scene-index fragment variant; background probe capture preserves the display
1522
+ view's GPU submission. Each pass selects its own material slots and color
1523
+ attachments. Shared materials at different local probes retain separate binding
1524
+ classes so deduplication cannot substitute one object's reflection environment.
1525
+
1526
+
1527
+ ### Static TextureAsset admission
1528
+
1529
+ Static PBR and Alpha Mask resources are admitted only from registered shared
1530
+ `TextureAsset` and `SamplerAsset` payloads. The resource-class identity is the
1531
+ sorted handle-pair projection used by the prepared material; it is not an
1532
+ array-position guess. Dynamic `RenderTargetTextureSource` and video sources
1533
+ remain their own CPU semantic boundary and are never coerced into a static
1534
+ GPU texture candidate.
1535
+
1536
+ The focused topology fixture allocates and resolves real static shared refs for
1537
+ resource classes `1`, `16`, and `256`, then builds `100,000` candidates for each
1538
+ class. It is structural admission evidence, not a physical-GPU benchmark:
1539
+
1540
+ ```sh
1541
+ FORGEAX_SKIP_HARNESS_SYNC=1 pnpm exec vitest run --project=@forgeax/engine-render \
1542
+ packages/render/src/__tests__/gpu-driven-batch-topology-pbr.unit.test.ts \
1543
+ --no-file-parallelism --reporter=dot
1544
+ ```
1545
+
1546
+ ### Point and Spot shadow authoring → view inspection
1547
+
1548
+ `PointLight` and `SpotLight` shadow views are public light facts, not a hidden
1549
+ GPU slot API. A Spot author declares a companion `Transform`, outgoing
1550
+ `direction`, cone angles in degrees, and the embedded shadow policy:
1551
+
1552
+ ```ts
1553
+ import { SpotLight } from '@forgeax/engine-render';
1554
+ import { Transform } from '@forgeax/engine-scene';
1555
+
1556
+ world.spawn(
1557
+ { component: Transform, data: { pos: [0, 5, 0] } },
1558
+ {
1559
+ component: SpotLight,
1560
+ data: {
1561
+ direction: [0, -1, 0],
1562
+ range: 20,
1563
+ innerConeDeg: 15,
1564
+ outerConeDeg: 35,
1565
+ castShadow: true,
1566
+ mapSize: 1024,
1567
+ pcfKernelSize: 3,
1568
+ },
1569
+ },
1570
+ );
1571
+ ```
1572
+
1573
+ | Author fact | Contract |
1574
+ |:--|:--|
1575
+ | `direction` / `Transform` | Outgoing direction plus the light position; both are required for a useful view |
1576
+ | `range` | Meters; default `10` |
1577
+ | `innerConeDeg` / `outerConeDeg` | Degrees; `0 ≤ inner < outer ≤ 90`, defaults `0` / `45` |
1578
+ | `castShadow` | Defaults `true`; set `false` for the explicit no-shadow case |
1579
+ | `mapSize`, `pcfKernelSize` | Shadow map resolution and odd PCF width; defaults `2048` / `3` |
1580
+ | `cookie` / `projector` | Optional GUID-backed static texture facts; dynamic target/video sources stay outside GPU admission |
1581
+
1582
+ After a successful `draw`, inspect the same renderer projection:
1583
+
1584
+ ```ts
1585
+ const channels = renderer.inspect().renderScene.gpuDriven.channels;
1586
+ const spotViews = channels.filter((channel) => channel.viewPass === 'spot-shadow');
1587
+ const pointFaces = channels.filter((channel) => channel.viewPass === 'point-shadow');
1588
+ void spotViews; // lane, reason, drawCount, and viewIndex are bounded facts.
1589
+ void pointFaces; // point views additionally expose the cube `face`.
1590
+ ```
1591
+
1592
+ The per-view cache is keyed by light identity and view parameters. A cache hit
1593
+ does not dispatch or record a duplicate shadow view. The inspection labels and
1594
+ identity helpers are anchored in
1595
+ [`spot-light.ts`](src/components/spot-light.ts),
1596
+ [`shadow-views.ts`](src/gpu-driven/shadow-views.ts), and
1597
+ [`frame.ts`](src/record/frame.ts); repair the producer named by a structured
1598
+ `failure.detail` before retrying the unchanged frame.
1599
+
1600
+ For offline recovery, read `renderer.inspect().renderScene.gpuDriven.channels`.
1601
+ A blocked channel carries one bounded `failure` projection with the same
1602
+ closed `code`, `detail.owner`, `detail.reason`, and `detail.recovery` that the
1603
+ renderer error stream emits. Repair that owner, recook or republish its
1604
+ producer artifact, and retry the identical frame; `hint` is explanatory text,
1605
+ not a value to parse. A blocked capable candidate is not permission to issue a
1606
+ duplicate CPU draw.
1607
+
1006
1608
  ## GPU-driven view kernel
1007
1609
 
1008
1610
  `BatchTopology` groups eligible rigid draw items by immutable geometry,
@@ -1012,41 +1614,56 @@ non-indexed five-word indirect command. A per-view typed graph records
1012
1614
  `reset -> frustum/compact -> finalize` over persistent candidate and batch
1013
1615
  buffers. The compute path validates generation and active flags, composes the
1014
1616
  primitive world transform with each ordinary instance-local transform, writes a
1015
- compact `(instanceIndex, materialIndex)` stream, sets explicit overflow flags,
1016
- and emits indirect arguments with `firstInstance = 0`.
1017
-
1018
- The Standard frame activates the production raster lane when compute, storage
1019
- buffers, and indirect drawing are available. The current lane accepts opaque,
1020
- rigid default-unlit triangle-list passes with batch-stable render state,
1021
- ordinary instances, indexed or non-indexed geometry, multiple submeshes and
1022
- material slots, and the canonical `12F` vertex layout. It also accepts persistent rigid multi-World
1023
- composition. Candidates come from the complete persistent projection rather
1024
- than the CPU-visible snapshot. The same typed graph imports the scene and mesh
1025
- buffers, runs the three compute passes, and consumes generated arguments with
1026
- `drawIndexedIndirect` or `drawIndirect`. Entities accepted by that lane are not
1027
- also validated, uploaded, or recorded by the CPU forward loop on a stable
1028
- frame.
1029
-
1030
- clustered, PBR and custom shader variants, texture/sampler/video resource bindings,
1031
- transparent meshes, skinning, morphing, non-canonical layouts, and shadow views
1032
- keep explicit CPU or specialized lanes. WebGL2 selects capability fallback from
1033
- the same persistent projection; it does not emulate compute. Visibility and
1034
- hierarchy changes, non-rigid multi-World composition, and some prepared-resource
1035
- changes still require bounded reconcile work. The implemented lane proves a
1036
- zero-upload stable data path and broad rigid geometry coverage, but hardware
1037
- 100k timing, schema-derived material variants, stable world identities, and
1038
- GPU-driven shadows remain required before declaring P3 complete.
1039
-
1040
- The bounded inspection reports topology revision, candidates, batches, visible
1041
- capacity, buffer capacities, update count, upload bytes, rebuild count, and CPU
1042
- fallback rows. RhiNull verifies graph dependency order and stable-frame zero
1043
- work; Dawn and Chromium verification consume indexed, non-indexed,
1044
- multi-submesh, and instanced arguments, read persistent scene tables in the
1045
- vertex path, and compare the resulting pixel. The renderer-level RhiNull
1046
- integration also proves that the built-in Standard graph contains the compute chain
1047
- before `main`, including rigid multi-World composition, without GPU plus CPU
1048
- duplicate draws. The renderer retains one graph, one compile owner, one
1049
- encoder, and one submit route.
1617
+ compact `(meshProjectionRow, materialOrPaletteRow)` stream, sets explicit
1618
+ overflow flags, and emits indirect arguments with `firstInstance = 0`. The low
1619
+ bit of the candidate admission word controls submission; its upper bits carry
1620
+ the batch-local projected mesh row. Rigid rows address the generated material
1621
+ table, while skin rows address the persistent palette through
1622
+ `Instance.customDataStart`.
1623
+
1624
+ The Standard frame activates the production raster lane only when compute,
1625
+ storage buffers, indirect drawing, and producer-owned Standard PBR artifacts
1626
+ are all ready. It covers opaque and Alpha Mask rigid/skin candidates whose
1627
+ geometry, resource class, reflection, palette address, and (for skin) finite
1628
+ producer-authored bounds satisfy the prepared contract. Main and directional,
1629
+ spot, or point shadow views consume the same scene and topology, but keep
1630
+ view-local visibility and projection bindings. Ownership is per draw item and
1631
+ view pass, so one draw can be GPU-owned in the main/shadow channels while an
1632
+ unrelated semantic pass remains CPU-owned.
1633
+
1634
+ Transparent/blend, transmission, morph, unsupported prepared variants, and
1635
+ skin without a producer-authored conservative bound stay on an explicit CPU
1636
+ semantic or CPU deformation lane. A producer-published custom Surface or
1637
+ full-custom ABI follows the same GPU lane when its selected Pass passes the
1638
+ prepared contract; an ordinary custom WGSL artifact without that ABI remains
1639
+ CPU/specialized. WebGL2 selects capability
1640
+ fallback from the same persistent projection; it does not emulate compute.
1641
+ Missing capable-path artifacts and failed resource publication are structured
1642
+ errors that block graph promotion rather than silently drawing the same item a
1643
+ second time through CPU semantics.
1644
+
1645
+ > [!WARNING]
1646
+ > A GPU selector overflow is fail-closed: `finalize` emits zero indirect
1647
+ > instances for the affected batch and inspection reports `overflow=true`.
1648
+ > The last-known-good resource generation remains authoritative until the
1649
+ > producer can rebuild with sufficient capacity. Missing skin bounds are also
1650
+ > intentionally conservative: they remain CPU deformation work; bind-pose
1651
+ > bounds are never invented as animation bounds.
1652
+
1653
+ The bounded `gpuDriven.channels` inspection reports the selected lane and
1654
+ closed reason for each view pass. Blocked promotion additionally exposes the
1655
+ producer-owned `failure` (`code`, `expected`, `hint`, and typed `detail`) so an
1656
+ AI caller can branch on owner/recovery without decoding an error string. The
1657
+ same `GpuDrivenPreparationError` is delivered through the renderer error
1658
+ stream. The inspection also reports topology revision, candidates,
1659
+ batches, visible capacity, buffer capacities, update count, upload bytes,
1660
+ rebuild count, overflow, retry count, and last-known-good generation. RhiNull
1661
+ verifies graph dependency order and stable-frame zero work; local Dawn verifies
1662
+ the command, binding, palette, shadow-view, and readback contracts. The
1663
+ renderer-level integration also proves that the built-in Standard graph
1664
+ contains the compute chain before `main`, with no GPU plus CPU duplicate draws.
1665
+ The renderer retains one graph, one compile owner, one encoder, and one submit
1666
+ route.
1050
1667
 
1051
1668
  > [!NOTE]
1052
1669
  > Non-rigid multi-World composition, skinned lanes, visibility/hierarchy
@@ -1056,6 +1673,28 @@ encoder, and one submit route.
1056
1673
  > semantics; later coverage can make those changes entity-local without adding
1057
1674
  > another scene authority.
1058
1675
 
1676
+ ### Offline inspect → repair owner → retry
1677
+
1678
+ Use the detached channel to identify the next owner without opening a browser
1679
+ or reading live GPU objects:
1680
+
1681
+ ```mermaid
1682
+ flowchart LR
1683
+ A["inspect gpuDriven.channels"] --> B{"lane / reason"}
1684
+ B -->|"gpu / none"| C["record the same graph"]
1685
+ B -->|"blocked / resource-not-ready"| D["repair failure.detail.owner"]
1686
+ D --> E["recook or publish the receipt"]
1687
+ E --> F["retry the identical frame request"]
1688
+ ```
1689
+
1690
+ The three public navigation entries are [`shader`](../shader/README.md#gpu-driven-pbr--shadow--skin-navigation),
1691
+ [`runtime`](../runtime/README.md#gpu-driven-pbr--shadow--skin-navigation), and
1692
+ this render owner. The two executable carriers are
1693
+ [`hello-skin`](../../apps/hello/skin/README.md#gpu-driven-skin-carrier) and
1694
+ [`hello-fbx-skin`](../../apps/hello/fbx-skin/README.md#gpu-driven-fbx-carrier).
1695
+ Their Dawn smoke commands remain independent evidence; RhiNull proves graph
1696
+ ownership and deterministic lane facts only.
1697
+
1059
1698
  Features are supplied through the construction options (`features: [...]`) and
1060
1699
  enter the same host-owned extract → plan → graph projection path. The public
1061
1700
  `Renderer` intentionally has no install/uninstall methods: optional capability
@@ -1163,6 +1802,34 @@ targets, and draw/dispatch commands. Graph buffer and texture access is derived
1163
1802
  from those roles; producers never author a second `reads`/`writes` ledger and
1164
1803
  never receive an encoder or submit authority.
1165
1804
 
1805
+ Prepared material-input layouts accept `particleInputLanes` from 1 through 4
1806
+ (omission selects the canonical one-lane layout). The count is valid only for a
1807
+ material-input vertex layout. Render derives its instance stride and attributes
1808
+ from the base layout and uses the same complete multi-stream descriptor in the
1809
+ PipelineSpec cache identity. Invalid counts fail during resource preparation.
1810
+
1811
+ A prepared pipeline with empty `colorFormats` uses the depth-only route and
1812
+ preserves its declared `depthFormat`, depth state and complete vertex streams.
1813
+ A vertex-only shader needs no fragment entry. For a vertex-index-generated draw,
1814
+ declare `vertexLayout: 'none'` on the draw and supply the prepared empty group
1815
+ when the shader has no resources. This is depth-target rendering, not automatic
1816
+ registration as a Standard light-view shadow caster.
1817
+
1818
+ For light-view participation, declare a `shadow-caster` plan pass with the same
1819
+ draw vocabulary and a depth32float, zero-color graphics program. Standard owns
1820
+ the light-view binding, atlas, viewport and pass replication; the feature owns
1821
+ only geometry and instance inputs. It reuses the normal prepared draw encoder
1822
+ and the same graph buffer imports. Active GPU casters disable static directional
1823
+ atlas reuse because ECS mesh epochs do not describe GPU buffer contents.
1824
+
1825
+ The graph projects a feature's leading compute prefix before shadow views until
1826
+ the first scene-target dependency or ordinary raster. Remaining work stays at
1827
+ the normal scene contribution boundary. A caster whose projection is late reads
1828
+ the prior submitted instance data (initially empty); it must not be described as
1829
+ current-frame simulation. Owner-local pipeline implementations use
1830
+ `contributeShadowFeatures` with `addTypedShadowPasses`; graph construction and
1831
+ these helpers are not added to the public Render root.
1832
+
1166
1833
  ### Five render terms
1167
1834
 
1168
1835
  | Term | Meaning | Owner |
@@ -1355,9 +2022,20 @@ sibling-loop report or an authored manifest. Read identity, generation, evidence
1355
2022
  level, and closed failure fields before following the named owner recovery action.
1356
2023
  Submit the same consumer request and reinspect after recovery. The route returns
1357
2024
  detached facts only: no live GPU handle, second owner, or second ledger is exposed.
1358
- Missing or mismatched receipts remain
1359
- `fallback-only` with zero SSR work; SSR v1, Hi-Z, ray marching, temporal resolve,
1360
- compose, and history are out of scope.
2025
+ Missing or mismatched receipts remain `fallback-only` with zero SSR work. The
2026
+ current renderer may expose admitted structural Hi-Z, trace, temporal, compose,
2027
+ and history facts, but no single backend snapshot or non-black canvas is an SSR
2028
+ v1 acceptance result; use paired Browser/Dawn readback and the closed-loop gates
2029
+ before promoting that status.
2030
+
2031
+ The canonical paired carrier is `apps/hello/ssr`. It runs the same fixture at
2032
+ `http://127.0.0.1:4173/?forgeax-evidence=ssr` and
2033
+ `dawn://hello/ssr?forgeax-evidence=ssr`, records 300-frame identity-bound
2034
+ readbacks, and keeps visual rows in the form `observed` / `verdict` /
2035
+ `confidence`. Its performance lane derives the 1920x1080 descriptor and checks
2036
+ it against `estimateSsrSpatialMemory`; timestamp or paired-lane absence remains
2037
+ blocked.
2038
+
1361
2039
  ## Standard dynamic MeshAsset candidates
1362
2040
 
1363
2041
  The Renderer owns one bounded candidate lifecycle for geometry produced by an
@@ -1474,6 +2152,28 @@ physics.admitDerivedShapeCandidate(physicsCandidate, () => {
1474
2152
  // After World.update succeeds, inspect Physics publication/failure and draw.
1475
2153
  ```
1476
2154
 
2155
+ For a group of physical bodies, pair `physics.admitDerivedShapeCandidates`
2156
+ with `renderer.acceptDynamicGeometryCandidates`. The latter accepts one prepared
2157
+ candidate per distinct render entity, all in the same attached World and fixed
2158
+ step, using the existing candidate budget. It retains previous unpublished
2159
+ credentials and GPU leases until every MeshFilter swap succeeds. A later-member
2160
+ refusal restores earlier bindings; if restoration cannot be proven it throws so
2161
+ the paired physics owner requires reconstruction. Topology history is invalidated
2162
+ once after the whole successful group. Retain every returned accepted credential
2163
+ and inspect its normal FrameReceipt; callback success alone is not a draw receipt.
2164
+
2165
+ ```ts
2166
+ physics.admitDerivedShapeCandidates(physicsCandidates, () => {
2167
+ const admitted = renderer.acceptDynamicGeometryCandidates(geometryCandidates, {
2168
+ world,
2169
+ fixedStep: world.getResource(FixedTime).tick,
2170
+ });
2171
+ if (!admitted.ok) return err(admitted.error);
2172
+ acceptedGeometries = admitted.value;
2173
+ return ok(undefined);
2174
+ }).unwrap();
2175
+ ```
2176
+
1477
2177
  The commit must return immediately after its complete binding change, without
1478
2178
  additional fallible work. On physical preparation/native failure it is not called;
1479
2179
  cancel the still-prepared geometry using its normal owner. On a refused commit,
@@ -1573,3 +2273,33 @@ example; Renderer never registers or mints an asset. Provider/Catalog/GUID cold
1573
2273
  loading therefore continues through the existing route; a producer that needs
1574
2274
  a custom voxel artifact owns that schema and loader outside the Engine, while
1575
2275
  its projected standard mesh enters this candidate path.
2276
+
2277
+ ### Residency resource retirement
2278
+
2279
+ Scope-owned `GpuBuffer` and `GpuTexture` registrations end when their explicit
2280
+ `destroy()` succeeds. Failed destruction keeps scope ownership for cleanup;
2281
+ scope termination still cleans remaining resources in reverse adoption order.
2282
+ Mesh retirement waits for outstanding submission and candidate leases before
2283
+ releasing that registration, so repeated mesh replacement does not retain all
2284
+ destroyed handles in the renderer generation. Caller-held handles retain their
2285
+ existing observable state; resource disposal is distinct from JavaScript GC.
2286
+
2287
+ ### Targeted spatial inspection
2288
+
2289
+ `renderer.bounds(world, entity)` returns a detached world-space AABB from the last extracted composition, including the existing instance-bound projection. It returns `undefined` for an absent World/entity or unknown/invalid producer bounds. This is renderer geometry evidence, not collision geometry or a synchronous update of authored World changes. A subsequent draw refreshes the projection; callers must not substitute an origin for an unavailable mesh bound. The query reuses culling ownership and retains no additional bounds cache.
2290
+
2291
+ ### Volumetric density coordinates
2292
+
2293
+ `VolumetricFog.density` is a linear scalar field over `boundsMin` to `boundsMax`.
2294
+ World positions map to `(position - boundsMin) / (boundsMax - boundsMin)` and
2295
+ sample with clamp-to-edge addressing. The renderer clamps negative density at
2296
+ the optical-depth boundary, but adds no bias, octave synthesis, repetition or
2297
+ animation. White density means one; zero density contributes no extinction or
2298
+ scattering. Noise synthesis belongs to the asset producer. The existing volume
2299
+ passes integrate this field against scene depth before output tonemapping.
2300
+
2301
+ Density integration uses fixed segment midpoints and the profile's logical
2302
+ froxel depth, including the clipped final segment. It does not rotate density
2303
+ samples with frame number: static authored air must not create temporal noise.
2304
+ Shadow visibility uses matching froxel centers without per-frame XY jitter or
2305
+ stochastic byte dithering. Shared PCF shadow filtering remains in effect.