@forgeax/engine-render 0.1.27 → 0.1.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (655) hide show
  1. package/README.md +842 -124
  2. package/dist/assembly/atmosphere-shader-prewarm.d.ts +6 -0
  3. package/dist/assembly/atmosphere-shader-prewarm.d.ts.map +1 -0
  4. package/dist/assembly/dynamic-geometry-host.d.ts +52 -0
  5. package/dist/assembly/dynamic-geometry-host.d.ts.map +1 -0
  6. package/dist/assembly/dynamic-geometry-runtime.d.ts +20 -0
  7. package/dist/assembly/dynamic-geometry-runtime.d.ts.map +1 -0
  8. package/dist/assembly/factory.d.ts +3 -1
  9. package/dist/assembly/factory.d.ts.map +1 -1
  10. package/dist/assembly/gpu-driven-owner.d.ts +10 -0
  11. package/dist/assembly/gpu-driven-owner.d.ts.map +1 -0
  12. package/dist/assembly/host-contract.d.ts +19 -0
  13. package/dist/assembly/host-contract.d.ts.map +1 -1
  14. package/dist/assembly/material/assembly.d.ts +7 -0
  15. package/dist/assembly/material/assembly.d.ts.map +1 -1
  16. package/dist/assembly/material-shader-policy.d.ts +3 -1
  17. package/dist/assembly/material-shader-policy.d.ts.map +1 -1
  18. package/dist/assembly/recovery/failure-location.d.ts +12 -0
  19. package/dist/assembly/recovery/failure-location.d.ts.map +1 -0
  20. package/dist/assembly/recovery/renderer-recover.d.ts +6 -4
  21. package/dist/assembly/recovery/renderer-recover.d.ts.map +1 -1
  22. package/dist/assembly/renderer-facade.d.ts.map +1 -1
  23. package/dist/assembly/renderer-feature-inspection.d.ts +5 -0
  24. package/dist/assembly/renderer-feature-inspection.d.ts.map +1 -0
  25. package/dist/assembly/renderer-frame-transaction.d.ts +19 -0
  26. package/dist/assembly/renderer-frame-transaction.d.ts.map +1 -1
  27. package/dist/assembly/renderer-helpers.d.ts +3 -0
  28. package/dist/assembly/renderer-helpers.d.ts.map +1 -1
  29. package/dist/assembly/renderer-inspection.d.ts +4 -0
  30. package/dist/assembly/renderer-inspection.d.ts.map +1 -1
  31. package/dist/assembly/shader-prewarm-policy.d.ts +6 -0
  32. package/dist/assembly/shader-prewarm-policy.d.ts.map +1 -1
  33. package/dist/assembly/skin-palette-owner.d.ts +8 -0
  34. package/dist/assembly/skin-palette-owner.d.ts.map +1 -0
  35. package/dist/assembly/ssr-shader-prewarm.d.ts +6 -0
  36. package/dist/assembly/ssr-shader-prewarm.d.ts.map +1 -0
  37. package/dist/assembly/webgpu-device-loss.d.ts +15 -0
  38. package/dist/assembly/webgpu-device-loss.d.ts.map +1 -0
  39. package/dist/assembly/webgpu-pbr-ready.d.ts +38 -0
  40. package/dist/assembly/webgpu-pbr-ready.d.ts.map +1 -0
  41. package/dist/assembly/webgpu-ready-mipmap.d.ts +11 -0
  42. package/dist/assembly/webgpu-ready-mipmap.d.ts.map +1 -0
  43. package/dist/assembly/webgpu-ready.d.ts +7 -3
  44. package/dist/assembly/webgpu-ready.d.ts.map +1 -1
  45. package/dist/assembly/webgpu-renderer-bootstrap.d.ts +10 -0
  46. package/dist/assembly/webgpu-renderer-bootstrap.d.ts.map +1 -0
  47. package/dist/assembly/webgpu-renderer-contract.d.ts +62 -0
  48. package/dist/assembly/webgpu-renderer-contract.d.ts.map +1 -0
  49. package/dist/assembly/webgpu-renderer-guards.d.ts +9 -0
  50. package/dist/assembly/webgpu-renderer-guards.d.ts.map +1 -0
  51. package/dist/assembly/webgpu-renderer-observation.d.ts +6 -0
  52. package/dist/assembly/webgpu-renderer-observation.d.ts.map +1 -0
  53. package/dist/assembly/webgpu-renderer-recovery-failure.d.ts +12 -0
  54. package/dist/assembly/webgpu-renderer-recovery-failure.d.ts.map +1 -0
  55. package/dist/assembly/webgpu-renderer.d.ts +7 -141
  56. package/dist/assembly/webgpu-renderer.d.ts.map +1 -1
  57. package/dist/assembly/webgpu-vertex-layouts.d.ts +28 -84
  58. package/dist/assembly/webgpu-vertex-layouts.d.ts.map +1 -1
  59. package/dist/authoring.mjs +3 -3
  60. package/dist/{chunk-ADHHVYLW.mjs → chunk-33XZFTQU.mjs} +245 -39
  61. package/dist/chunk-33XZFTQU.mjs.map +1 -0
  62. package/dist/{chunk-GF523LHF.mjs → chunk-BLXGONR2.mjs} +30 -12
  63. package/dist/chunk-BLXGONR2.mjs.map +1 -0
  64. package/dist/{chunk-KM2NOX2I.mjs → chunk-BQVQOQJH.mjs} +958 -254
  65. package/dist/chunk-BQVQOQJH.mjs.map +1 -0
  66. package/dist/{chunk-HKXTW355.mjs → chunk-BTWH7BP4.mjs} +386 -17
  67. package/dist/chunk-BTWH7BP4.mjs.map +1 -0
  68. package/dist/{chunk-TMIASV2N.mjs → chunk-FUODTTIA.mjs} +3934 -676
  69. package/dist/chunk-FUODTTIA.mjs.map +1 -0
  70. package/dist/{chunk-FJ6P52EE.mjs → chunk-ILQ5MEZF.mjs} +3 -17
  71. package/dist/chunk-ILQ5MEZF.mjs.map +1 -0
  72. package/dist/{chunk-E34VL5VI.mjs → chunk-JQTSEI3Z.mjs} +3 -3
  73. package/dist/{chunk-E34VL5VI.mjs.map → chunk-JQTSEI3Z.mjs.map} +1 -1
  74. package/dist/{chunk-GE3SDD72.mjs → chunk-NICXSHAT.mjs} +16 -38
  75. package/dist/chunk-NICXSHAT.mjs.map +1 -0
  76. package/dist/{chunk-ZZ4YQ474.mjs → chunk-NRNXBLIJ.mjs} +577 -7
  77. package/dist/chunk-NRNXBLIJ.mjs.map +1 -0
  78. package/dist/{chunk-JDWARUOI.mjs → chunk-PA5DRJNI.mjs} +17 -3
  79. package/dist/chunk-PA5DRJNI.mjs.map +1 -0
  80. package/dist/{chunk-XDP6XE5V.mjs → chunk-S4DNKQYQ.mjs} +1118 -446
  81. package/dist/chunk-S4DNKQYQ.mjs.map +1 -0
  82. package/dist/components/camera.d.ts +62 -2
  83. package/dist/components/camera.d.ts.map +1 -1
  84. package/dist/components/index.d.ts +1 -0
  85. package/dist/components/index.d.ts.map +1 -1
  86. package/dist/components/instances.d.ts +6 -20
  87. package/dist/components/instances.d.ts.map +1 -1
  88. package/dist/components/scene-instance.d.ts +2 -1
  89. package/dist/components/scene-instance.d.ts.map +1 -1
  90. package/dist/components/screen-space-reflection.d.ts +14 -0
  91. package/dist/components/screen-space-reflection.d.ts.map +1 -0
  92. package/dist/construct-renderer.mjs +13805 -7186
  93. package/dist/construct-renderer.mjs.map +1 -1
  94. package/dist/device/gpu-residency.d.ts +11 -17
  95. package/dist/device/gpu-residency.d.ts.map +1 -1
  96. package/dist/device/mesh-residency-lifetime.d.ts +21 -0
  97. package/dist/device/mesh-residency-lifetime.d.ts.map +1 -0
  98. package/dist/dynamic-geometry.d.ts +125 -0
  99. package/dist/dynamic-geometry.d.ts.map +1 -0
  100. package/dist/environment/background.d.ts +6 -0
  101. package/dist/environment/background.d.ts.map +1 -0
  102. package/dist/errors/gpu-driven.d.ts +6 -3
  103. package/dist/errors/gpu-driven.d.ts.map +1 -1
  104. package/dist/errors/render.d.ts +7 -4
  105. package/dist/errors/render.d.ts.map +1 -1
  106. package/dist/extract/camera.d.ts.map +1 -1
  107. package/dist/extract/gpu-driven.d.ts +33 -3
  108. package/dist/extract/gpu-driven.d.ts.map +1 -1
  109. package/dist/features/host.d.ts +23 -1
  110. package/dist/features/host.d.ts.map +1 -1
  111. package/dist/features/noise-texture.d.ts +17 -0
  112. package/dist/features/noise-texture.d.ts.map +1 -0
  113. package/dist/features/plan.d.ts +83 -2
  114. package/dist/features/plan.d.ts.map +1 -1
  115. package/dist/features/prepared-gpu-work.d.ts +37 -4
  116. package/dist/features/prepared-gpu-work.d.ts.map +1 -1
  117. package/dist/features/prepared-graphics-store.d.ts.map +1 -1
  118. package/dist/features/prepared-graphics.d.ts +5 -0
  119. package/dist/features/prepared-graphics.d.ts.map +1 -1
  120. package/dist/features/render-graph-compute.d.ts +5 -3
  121. package/dist/features/render-graph-compute.d.ts.map +1 -1
  122. package/dist/features/render-graph-contribution.d.ts +11 -3
  123. package/dist/features/render-graph-contribution.d.ts.map +1 -1
  124. package/dist/features/render-graph-raster.d.ts +19 -3
  125. package/dist/features/render-graph-raster.d.ts.map +1 -1
  126. package/dist/features/types.d.ts +11 -0
  127. package/dist/features/types.d.ts.map +1 -1
  128. package/dist/fullscreen-post-process-pass.d.ts +2 -0
  129. package/dist/fullscreen-post-process-pass.d.ts.map +1 -1
  130. package/dist/gpu-driven/batch-topology.d.ts +34 -1
  131. package/dist/gpu-driven/batch-topology.d.ts.map +1 -1
  132. package/dist/gpu-driven/pbr-program.d.ts +10 -0
  133. package/dist/gpu-driven/pbr-program.d.ts.map +1 -0
  134. package/dist/gpu-driven/prepared-draw.d.ts.map +1 -1
  135. package/dist/gpu-driven/production-raster.d.ts +144 -23
  136. package/dist/gpu-driven/production-raster.d.ts.map +1 -1
  137. package/dist/gpu-driven/shadow-views.d.ts +126 -0
  138. package/dist/gpu-driven/shadow-views.d.ts.map +1 -0
  139. package/dist/gpu-driven/view-gpu.d.ts +18 -2
  140. package/dist/gpu-driven/view-gpu.d.ts.map +1 -1
  141. package/dist/gpu-scene-schema.d.ts +2 -8
  142. package/dist/gpu-scene-schema.d.ts.map +1 -1
  143. package/dist/gpu-scene.d.ts +14 -0
  144. package/dist/gpu-scene.d.ts.map +1 -1
  145. package/dist/gpu-texture-usage.d.ts +1 -0
  146. package/dist/gpu-texture-usage.d.ts.map +1 -1
  147. package/dist/hdrp-buffers.d.ts +6 -0
  148. package/dist/hdrp-buffers.d.ts.map +1 -1
  149. package/dist/ibl/frame-resources.d.ts +8 -0
  150. package/dist/ibl/frame-resources.d.ts.map +1 -0
  151. package/dist/ibl/skylight-bind-group.d.ts.map +1 -1
  152. package/dist/index.d.ts +21 -8
  153. package/dist/index.d.ts.map +1 -1
  154. package/dist/index.mjs +8 -7
  155. package/dist/index.mjs.map +1 -1
  156. package/dist/inspection-types.d.ts +164 -0
  157. package/dist/inspection-types.d.ts.map +1 -1
  158. package/dist/instances-derived-bounds.d.ts +11 -0
  159. package/dist/instances-derived-bounds.d.ts.map +1 -1
  160. package/dist/instances.d.ts +89 -44
  161. package/dist/instances.d.ts.map +1 -1
  162. package/dist/internal.d.ts +3 -0
  163. package/dist/internal.d.ts.map +1 -1
  164. package/dist/internal.mjs +118 -10
  165. package/dist/internal.mjs.map +1 -1
  166. package/dist/lifecycle.d.ts +2 -1
  167. package/dist/lifecycle.d.ts.map +1 -1
  168. package/dist/material-row.d.ts +5 -0
  169. package/dist/material-row.d.ts.map +1 -0
  170. package/dist/materials.d.ts +15 -0
  171. package/dist/materials.d.ts.map +1 -1
  172. package/dist/pbr-pipeline.d.ts +21 -5
  173. package/dist/pbr-pipeline.d.ts.map +1 -1
  174. package/dist/pipeline/standard-forward-lane.d.ts.map +1 -1
  175. package/dist/pipeline/standard-output/auto-exposure/capability.d.ts +12 -0
  176. package/dist/pipeline/standard-output/auto-exposure/capability.d.ts.map +1 -0
  177. package/dist/pipeline/standard-output/auto-exposure/gpu.d.ts +70 -0
  178. package/dist/pipeline/standard-output/auto-exposure/gpu.d.ts.map +1 -0
  179. package/dist/pipeline/standard-output/auto-exposure/graph.d.ts +75 -0
  180. package/dist/pipeline/standard-output/auto-exposure/graph.d.ts.map +1 -0
  181. package/dist/pipeline/standard-output/auto-exposure/inspection.d.ts +111 -0
  182. package/dist/pipeline/standard-output/auto-exposure/inspection.d.ts.map +1 -0
  183. package/dist/pipeline/standard-output/auto-exposure/oracle.d.ts +16 -0
  184. package/dist/pipeline/standard-output/auto-exposure/oracle.d.ts.map +1 -0
  185. package/dist/pipeline/standard-output/auto-exposure/preset.d.ts +14 -0
  186. package/dist/pipeline/standard-output/auto-exposure/preset.d.ts.map +1 -0
  187. package/dist/pipeline/standard-output/auto-exposure/state.d.ts +46 -0
  188. package/dist/pipeline/standard-output/auto-exposure/state.d.ts.map +1 -0
  189. package/dist/pipeline/standard-output/color-transform.d.ts +27 -0
  190. package/dist/pipeline/standard-output/color-transform.d.ts.map +1 -0
  191. package/dist/pipeline/standard-output/graph.d.ts +15 -0
  192. package/dist/pipeline/standard-output/graph.d.ts.map +1 -0
  193. package/dist/pipeline/standard-output/lut-admission.d.ts +29 -0
  194. package/dist/pipeline/standard-output/lut-admission.d.ts.map +1 -0
  195. package/dist/pipeline/standard-output/lut-gpu.d.ts +48 -0
  196. package/dist/pipeline/standard-output/lut-gpu.d.ts.map +1 -0
  197. package/dist/pipeline/standard-output/lut-state.d.ts +68 -0
  198. package/dist/pipeline/standard-output/lut-state.d.ts.map +1 -0
  199. package/dist/pipeline/standard-output/resources.d.ts +10 -0
  200. package/dist/pipeline/standard-output/resources.d.ts.map +1 -0
  201. package/dist/pipeline/standard-output/types.d.ts +39 -0
  202. package/dist/pipeline/standard-output/types.d.ts.map +1 -0
  203. package/dist/pipeline/standard-pipeline.d.ts.map +1 -1
  204. package/dist/pipeline/standard-post.d.ts +2 -2
  205. package/dist/pipeline/standard-post.d.ts.map +1 -1
  206. package/dist/pipeline-builder.d.ts +2 -1
  207. package/dist/pipeline-builder.d.ts.map +1 -1
  208. package/dist/pipeline-spec-types.d.ts +4 -0
  209. package/dist/pipeline-spec-types.d.ts.map +1 -1
  210. package/dist/pipeline-spec.d.ts +1 -9
  211. package/dist/pipeline-spec.d.ts.map +1 -1
  212. package/dist/points-lines/standard-owner.d.ts +1 -1
  213. package/dist/points-lines/standard-owner.d.ts.map +1 -1
  214. package/dist/prepare/prepared-graphics-resolver.d.ts +6 -1
  215. package/dist/prepare/prepared-graphics-resolver.d.ts.map +1 -1
  216. package/dist/record/dynamic-geometry-consumption.d.ts +15 -0
  217. package/dist/record/dynamic-geometry-consumption.d.ts.map +1 -0
  218. package/dist/record/frame-lighting.d.ts +3 -3
  219. package/dist/record/frame-lighting.d.ts.map +1 -1
  220. package/dist/record/frame-snapshot.d.ts +106 -5
  221. package/dist/record/frame-snapshot.d.ts.map +1 -1
  222. package/dist/record/frame-targets.d.ts.map +1 -1
  223. package/dist/record/frame.d.ts +39 -4
  224. package/dist/record/frame.d.ts.map +1 -1
  225. package/dist/record/main-pass-geometry.d.ts +7 -8
  226. package/dist/record/main-pass-geometry.d.ts.map +1 -1
  227. package/dist/record/main-pass-material.d.ts +38 -1
  228. package/dist/record/main-pass-material.d.ts.map +1 -1
  229. package/dist/record/main-pass-sprite-draws.d.ts +2 -10
  230. package/dist/record/main-pass-sprite-draws.d.ts.map +1 -1
  231. package/dist/record/main-pass.d.ts +12 -2
  232. package/dist/record/main-pass.d.ts.map +1 -1
  233. package/dist/record/mesh-ssbo.d.ts +15 -6
  234. package/dist/record/mesh-ssbo.d.ts.map +1 -1
  235. package/dist/record/prepared-material-bindings.d.ts +19 -0
  236. package/dist/record/prepared-material-bindings.d.ts.map +1 -0
  237. package/dist/record/recovery-pipeline.d.ts +2 -9
  238. package/dist/record/recovery-pipeline.d.ts.map +1 -1
  239. package/dist/record/render-context.d.ts +76 -26
  240. package/dist/record/render-context.d.ts.map +1 -1
  241. package/dist/record/shadow-pass.d.ts +62 -7
  242. package/dist/record/shadow-pass.d.ts.map +1 -1
  243. package/dist/record/skybox-post-pass.d.ts +1 -1
  244. package/dist/record/skybox-post-pass.d.ts.map +1 -1
  245. package/dist/record/typed-frame-graph.d.ts +35 -7
  246. package/dist/record/typed-frame-graph.d.ts.map +1 -1
  247. package/dist/record/view-ubo.d.ts +1 -0
  248. package/dist/record/view-ubo.d.ts.map +1 -1
  249. package/dist/recovery/render-system-candidate.d.ts +10 -2
  250. package/dist/recovery/render-system-candidate.d.ts.map +1 -1
  251. package/dist/recovery/render-system-roots.d.ts +11 -0
  252. package/dist/recovery/render-system-roots.d.ts.map +1 -0
  253. package/dist/recovery/types.d.ts +76 -0
  254. package/dist/recovery/types.d.ts.map +1 -0
  255. package/dist/reflection/record-owner.d.ts +0 -2
  256. package/dist/reflection/record-owner.d.ts.map +1 -1
  257. package/dist/render-contract.d.ts +185 -30
  258. package/dist/render-contract.d.ts.map +1 -1
  259. package/dist/render-graph-primitives.d.ts +1 -0
  260. package/dist/render-graph-primitives.d.ts.map +1 -1
  261. package/dist/render-pipeline.d.ts +64 -5
  262. package/dist/render-pipeline.d.ts.map +1 -1
  263. package/dist/render-system-extract-tail.d.ts +7 -5
  264. package/dist/render-system-extract-tail.d.ts.map +1 -1
  265. package/dist/render-system-extract.d.ts +101 -41
  266. package/dist/render-system-extract.d.ts.map +1 -1
  267. package/dist/render-system-projections.d.ts +22 -0
  268. package/dist/render-system-projections.d.ts.map +1 -0
  269. package/dist/render-system.d.ts +34 -77
  270. package/dist/render-system.d.ts.map +1 -1
  271. package/dist/scene/render-scene-types.d.ts +14 -7
  272. package/dist/scene/render-scene-types.d.ts.map +1 -1
  273. package/dist/scene/render-scene.d.ts +29 -11
  274. package/dist/scene/render-scene.d.ts.map +1 -1
  275. package/dist/scene/shadow-visibility.d.ts +5 -0
  276. package/dist/scene/shadow-visibility.d.ts.map +1 -0
  277. package/dist/ssr/admission.d.ts +70 -1
  278. package/dist/ssr/admission.d.ts.map +1 -1
  279. package/dist/ssr/compose.d.ts +13 -0
  280. package/dist/ssr/compose.d.ts.map +1 -0
  281. package/dist/ssr/errors.d.ts +35 -0
  282. package/dist/ssr/errors.d.ts.map +1 -1
  283. package/dist/ssr/graph.d.ts +60 -0
  284. package/dist/ssr/graph.d.ts.map +1 -0
  285. package/dist/ssr/history.d.ts +97 -0
  286. package/dist/ssr/history.d.ts.map +1 -0
  287. package/dist/ssr/hiz.d.ts +37 -0
  288. package/dist/ssr/hiz.d.ts.map +1 -0
  289. package/dist/ssr/inspection.d.ts +42 -0
  290. package/dist/ssr/inspection.d.ts.map +1 -0
  291. package/dist/ssr/resources.d.ts +58 -0
  292. package/dist/ssr/resources.d.ts.map +1 -0
  293. package/dist/ssr/temporal.d.ts +38 -0
  294. package/dist/ssr/temporal.d.ts.map +1 -0
  295. package/dist/systems/skin-palette-allocator.d.ts +32 -10
  296. package/dist/systems/skin-palette-allocator.d.ts.map +1 -1
  297. package/dist/systems/skin-palette-types.d.ts +15 -0
  298. package/dist/systems/skin-palette-types.d.ts.map +1 -1
  299. package/dist/systems/transparent-dispatch.d.ts +9 -0
  300. package/dist/systems/transparent-dispatch.d.ts.map +1 -0
  301. package/dist/targets/material-source.d.ts +7 -0
  302. package/dist/targets/material-source.d.ts.map +1 -1
  303. package/dist/temporal/frame-coordinator.d.ts +7 -0
  304. package/dist/temporal/frame-coordinator.d.ts.map +1 -1
  305. package/dist/temporal/gpu.d.ts +8 -0
  306. package/dist/temporal/gpu.d.ts.map +1 -1
  307. package/dist/temporal/history.d.ts.map +1 -1
  308. package/dist/temporal/index.mjs +6 -6
  309. package/dist/temporal/inspection.d.ts.map +1 -1
  310. package/dist/temporal/standard-scene-data.d.ts +2 -1
  311. package/dist/temporal/standard-scene-data.d.ts.map +1 -1
  312. package/dist/temporal/temporal-view.d.ts +2 -0
  313. package/dist/temporal/temporal-view.d.ts.map +1 -1
  314. package/dist/typed-render-graph-primitives.d.ts +35 -1
  315. package/dist/typed-render-graph-primitives.d.ts.map +1 -1
  316. package/dist/typed-shadow-passes.d.ts +3 -1
  317. package/dist/typed-shadow-passes.d.ts.map +1 -1
  318. package/dist/volume/temporal.d.ts +6 -0
  319. package/dist/volume/temporal.d.ts.map +1 -1
  320. package/package.json +21 -20
  321. package/src/__tests__/auto-exposure-gpu-evidence.ts +291 -0
  322. package/src/__tests__/auto-exposure-inspection.unit.test.ts +80 -0
  323. package/src/__tests__/auto-exposure-transaction.integration.test.ts +99 -0
  324. package/src/__tests__/auto-exposure.browser.test.ts +33 -0
  325. package/src/__tests__/auto-exposure.dawn.test.ts +33 -0
  326. package/src/__tests__/detached-inspection.integration.test.ts +5 -0
  327. package/src/__tests__/device-scope-lifecycle.unit.test.ts +18 -0
  328. package/src/__tests__/device-scope-stale-matrix.unit.test.ts +18 -0
  329. package/src/__tests__/docs-gate-green.unit.test.ts +3 -3
  330. package/src/__tests__/dynamic-geometry-host.unit.test.ts +1123 -0
  331. package/src/__tests__/dynamic-geometry.unit.test.ts +233 -0
  332. package/src/__tests__/factory-contract.integration.test.ts +73 -22
  333. package/src/__tests__/fallback-row-stride-surface.unit.test.ts +19 -0
  334. package/src/__tests__/feature-depth-input.dawn.test.ts +121 -0
  335. package/src/__tests__/feature-noise.unit.test.ts +38 -0
  336. package/src/__tests__/fixtures/taa-resolve-pinned-baseline.wgsl +301 -0
  337. package/src/__tests__/frame-camera-selection.unit.test.ts +131 -0
  338. package/src/__tests__/frame-plan-contract.unit.test.ts +24 -0
  339. package/src/__tests__/frame-targets.unit.test.ts +24 -0
  340. package/src/__tests__/fullscreen-feature-plan.unit.test.ts +3 -0
  341. package/src/__tests__/gpu-driven-baseline.characterization.test.ts +79 -0
  342. package/src/__tests__/gpu-driven-batch-topology-pbr.unit.test.ts +287 -6
  343. package/src/__tests__/gpu-driven-indirect-raster-evidence.ts +122 -142
  344. package/src/__tests__/gpu-driven-indirect-raster.browser.test.ts +10 -1
  345. package/src/__tests__/gpu-driven-lod-cache.unit.test.ts +264 -0
  346. package/src/__tests__/gpu-driven-mesh-world-identity.unit.test.ts +349 -0
  347. package/src/__tests__/gpu-driven-pbr.dawn.test.ts +1417 -0
  348. package/src/__tests__/gpu-driven-pbr.integration.test.ts +21 -0
  349. package/src/__tests__/gpu-driven-production-fixture.ts +96 -0
  350. package/src/__tests__/gpu-driven-production.integration.test.ts +1075 -93
  351. package/src/__tests__/gpu-driven-scaling.unit.test.ts +1 -3
  352. package/src/__tests__/gpu-driven-shadow-views.browser.test.ts +63 -0
  353. package/src/__tests__/gpu-driven-shadow-views.dawn.test.ts +294 -0
  354. package/src/__tests__/gpu-driven-shadow-views.integration.test.ts +97 -0
  355. package/src/__tests__/gpu-driven-shadow-views.unit.test.ts +168 -0
  356. package/src/__tests__/gpu-driven-skin-contract.test-d.ts +11 -0
  357. package/src/__tests__/gpu-driven-view-gpu-evidence.ts +72 -20
  358. package/src/__tests__/gpu-driven-view-graph.integration.test.ts +30 -9
  359. package/src/__tests__/gpu-driven-view.unit.test.ts +159 -52
  360. package/src/__tests__/gpu-pass-timing.browser.test.ts +6 -1
  361. package/src/__tests__/gpu-resource-store-stride.unit.test.ts +42 -0
  362. package/src/__tests__/gpu-scene-render-graph-gpu.ts +19 -5
  363. package/src/__tests__/gpu-scene.dawn.test.ts +40 -13
  364. package/src/__tests__/gpu-scene.unit.test.ts +559 -33
  365. package/src/__tests__/instances-culling.integration.test.ts +36 -10
  366. package/src/__tests__/instances-derived-bounds.unit.test.ts +54 -0
  367. package/src/__tests__/instances-store.unit.test.ts +242 -39
  368. package/src/__tests__/ktx2-basis-gpu-consumer.dawn.test.ts +6 -0
  369. package/src/__tests__/lighting-scene-roundtrip.integration.test.ts +13 -16
  370. package/src/__tests__/lut-lkg-transaction.integration.test.ts +122 -0
  371. package/src/__tests__/material-contract-inventory.unit.test.ts +5 -6
  372. package/src/__tests__/material-dispatch-shader-id.unit.test.ts +51 -0
  373. package/src/__tests__/material-lane-selection.unit.test.ts +60 -0
  374. package/src/__tests__/material-numeric-upload.unit.test.ts +53 -0
  375. package/src/__tests__/material-texture-handles.unit.test.ts +18 -0
  376. package/src/__tests__/material-texture-source.unit.test.ts +87 -0
  377. package/src/__tests__/materials-authoring-validation.unit.test.ts +41 -0
  378. package/src/__tests__/materials-standard-contract.unit.test.ts +52 -2
  379. package/src/__tests__/mesh-buffer-usage-surface.unit.test.ts +4 -0
  380. package/src/__tests__/mesh-submission-lifetime.unit.test.ts +53 -0
  381. package/src/__tests__/normal-fallback.dawn.test.ts +122 -0
  382. package/src/__tests__/particle-material-input-layout.unit.test.ts +104 -0
  383. package/src/__tests__/physical-clearcoat.integration.test.ts +37 -3
  384. package/src/__tests__/pipeline-texture-specialization.unit.test.ts +138 -0
  385. package/src/__tests__/point-shadow-inspection.unit.test.ts +2 -0
  386. package/src/__tests__/prepared-gpu-driven-pbr-contract.test-d.ts +31 -0
  387. package/src/__tests__/prepared-gpu-driven-pbr-contract.unit.test.ts +39 -0
  388. package/src/__tests__/prepared-gpu-driven-pbr.integration.test.ts +35 -4
  389. package/src/__tests__/prepared-graphics-capability.unit.test.ts +21 -0
  390. package/src/__tests__/prepared-graphics-pipeline-warmup.unit.test.ts +6 -2
  391. package/src/__tests__/profiler-phase-catalog.test.ts +5 -0
  392. package/src/__tests__/public-surface.test-d.ts +3 -0
  393. package/src/__tests__/recovery-candidate-prepare.contract.test.ts +30 -12
  394. package/src/__tests__/reflection-fallback-async-publication.unit.test.ts +104 -0
  395. package/src/__tests__/reflection-probe-record-owner.characterization.unit.test.ts +2 -1
  396. package/src/__tests__/render-error-code-owner.test-d.ts +4 -0
  397. package/src/__tests__/render-error-detail-ssot.test-d.ts +22 -0
  398. package/src/__tests__/render-error-exhaustive.test-d.ts +15 -0
  399. package/src/__tests__/render-feature-gpu-work.integration.test.ts +285 -2
  400. package/src/__tests__/render-feature-plan.unit.test.ts +245 -0
  401. package/src/__tests__/render-feature-shadow-order.unit.test.ts +228 -0
  402. package/src/__tests__/render-feature-zero-work.unit.test.ts +35 -1
  403. package/src/__tests__/render-graph-fallback.integration.test.ts +2 -2
  404. package/src/__tests__/render-owner-cohesion.unit.test.ts +55 -0
  405. package/src/__tests__/render-scene-mixed-updates.integration.test.ts +426 -0
  406. package/src/__tests__/render-scene-projection.unit.test.ts +914 -23
  407. package/src/__tests__/render-scene-resource-updates.integration.test.ts +214 -0
  408. package/src/__tests__/render-scene-skin-lifecycle.integration.test.ts +173 -0
  409. package/src/__tests__/render-scene-temporal-retry.unit.test.ts +133 -0
  410. package/src/__tests__/render-target-material-source.test-d.ts +27 -3
  411. package/src/__tests__/renderer-factory-material-contract.unit.test.ts +61 -1
  412. package/src/__tests__/renderer-frame-transaction.integration.test.ts +923 -5
  413. package/src/__tests__/shader-manifest-fixture.ts +39 -0
  414. package/src/__tests__/skin-motion-regression.unit.test.ts +0 -33
  415. package/src/__tests__/skin-palette-persistent.integration.test.ts +28 -0
  416. package/src/__tests__/skin-palette-persistent.unit.test.ts +247 -0
  417. package/src/__tests__/skinned-shadow-caster.test.ts +118 -14
  418. package/src/__tests__/specular-environment-scale.dawn.test.ts +156 -0
  419. package/src/__tests__/ssr-admission-matrix.integration.test.ts +7 -1
  420. package/src/__tests__/ssr-confidence-compose.unit.test.ts +84 -0
  421. package/src/__tests__/ssr-coordinate-oracle.unit.test.ts +28 -0
  422. package/src/__tests__/ssr-format-probe-gating.integration.test.ts +82 -0
  423. package/src/__tests__/ssr-gpu-dispatch.browser.test.ts +91 -0
  424. package/src/__tests__/ssr-gpu-dispatch.dawn.test.ts +112 -0
  425. package/src/__tests__/ssr-gpu-dispatch.ts +946 -0
  426. package/src/__tests__/ssr-graph-topology.unit.test.ts +104 -0
  427. package/src/__tests__/ssr-history-filter.dawn.test.ts +198 -0
  428. package/src/__tests__/ssr-hit-color.dawn.test.ts +273 -0
  429. package/src/__tests__/ssr-hiz-odd-reduction.dawn.test.ts +569 -0
  430. package/src/__tests__/ssr-hiz-rescue.dawn.test.ts +279 -0
  431. package/src/__tests__/ssr-hiz.browser.test.ts +150 -0
  432. package/src/__tests__/ssr-hiz.dawn.test.ts +150 -0
  433. package/src/__tests__/ssr-hiz.unit.test.ts +56 -0
  434. package/src/__tests__/ssr-inspection.unit.test.ts +105 -0
  435. package/src/__tests__/ssr-memory-budget.unit.test.ts +45 -0
  436. package/src/__tests__/ssr-performance-sequence-aggregation.unit.test.ts +15 -0
  437. package/src/__tests__/ssr-public-surface.test-d.ts +37 -0
  438. package/src/__tests__/ssr-receiver-coverage.dawn.test.ts +387 -0
  439. package/src/__tests__/ssr-reflection-mip-reducer.dawn.test.ts +309 -0
  440. package/src/__tests__/ssr-reprojection-probe.ts +90 -0
  441. package/src/__tests__/ssr-roughness-mip.dawn.test.ts +163 -0
  442. package/src/__tests__/ssr-shader-prewarm.unit.test.ts +59 -0
  443. package/src/__tests__/ssr-spatial.integration.test.ts +141 -0
  444. package/src/__tests__/ssr-stable-lattice.dawn.test.ts +475 -0
  445. package/src/__tests__/ssr-temporal-history.unit.test.ts +202 -0
  446. package/src/__tests__/ssr-texel-crossing.dawn.test.ts +448 -0
  447. package/src/__tests__/ssr-zero-work.integration.test.ts +87 -0
  448. package/src/__tests__/standard-cluster-variant-resolution.unit.test.ts +44 -12
  449. package/src/__tests__/standard-clustered-pipeline.unit.test.ts +29 -0
  450. package/src/__tests__/standard-output-chain-contract.unit.test.ts +99 -0
  451. package/src/__tests__/standard-output-chain.integration.test.ts +73 -0
  452. package/src/__tests__/standard-output-gpu-evidence.ts +205 -0
  453. package/src/__tests__/standard-output.browser.test.ts +15 -0
  454. package/src/__tests__/standard-output.dawn.test.ts +15 -0
  455. package/src/__tests__/standard-pbr-artifact-assembly.unit.test.ts +38 -0
  456. package/src/__tests__/standard-pipeline.integration.test.ts +320 -3
  457. package/src/__tests__/standard-profile-oracle.unit.test.ts +22 -0
  458. package/src/__tests__/standard-surface-lighting.unit.test.ts +41 -0
  459. package/src/__tests__/standard-texture-specialization.dawn.test.ts +141 -0
  460. package/src/__tests__/surface-adjacent-regressions.integration.test.ts +6 -3
  461. package/src/__tests__/taa-coverage-edge.dawn.test.ts +193 -0
  462. package/src/__tests__/taa-history-clipping.dawn.test.ts +328 -0
  463. package/src/__tests__/taa-neighborhood-equivalence.dawn.test.ts +778 -0
  464. package/src/__tests__/taa-stability-feedback.browser.test.ts +23 -0
  465. package/src/__tests__/taa-stability-feedback.dawn.test.ts +68 -0
  466. package/src/__tests__/taa-stability-feedback.ts +341 -0
  467. package/src/__tests__/temporal-history-lifecycle.unit.test.ts +49 -7
  468. package/src/__tests__/temporal-reset-matrix.unit.test.ts +9 -0
  469. package/src/__tests__/temporal-transaction.integration.test.ts +28 -0
  470. package/src/__tests__/texture-usage-surface.unit.test.ts +4 -2
  471. package/src/__tests__/typed-pipeline-topology.unit.test.ts +109 -4
  472. package/src/__tests__/typed-scene-pass-material-cache.integration.test.ts +126 -0
  473. package/src/__tests__/vertex-layouts-ssot.unit.test.ts +47 -0
  474. package/src/__tests__/visibility-extract.perf.test.ts +20 -8
  475. package/src/__tests__/visibility-instances.integration.test.ts +19 -7
  476. package/src/__tests__/visibility-producer-matrix.integration.test.ts +20 -9
  477. package/src/assembly/atmosphere-shader-prewarm.ts +38 -0
  478. package/src/assembly/dynamic-geometry-host.ts +887 -0
  479. package/src/assembly/dynamic-geometry-runtime.ts +71 -0
  480. package/src/assembly/factory.ts +4 -0
  481. package/src/assembly/gpu-driven-owner.ts +29 -0
  482. package/src/assembly/host-contract.ts +36 -0
  483. package/src/assembly/material/assembly.ts +38 -0
  484. package/src/assembly/material-shader-policy.ts +71 -28
  485. package/src/assembly/recovery/failure-location.ts +62 -0
  486. package/src/assembly/recovery/renderer-recover.ts +19 -17
  487. package/src/assembly/renderer-facade.ts +29 -0
  488. package/src/assembly/renderer-feature-inspection.ts +13 -0
  489. package/src/assembly/renderer-frame-transaction.ts +63 -0
  490. package/src/assembly/renderer-helpers.ts +26 -0
  491. package/src/assembly/renderer-inspection.ts +6 -0
  492. package/src/assembly/shader-prewarm-policy.ts +32 -0
  493. package/src/assembly/skin-palette-owner.ts +21 -0
  494. package/src/assembly/ssr-shader-prewarm.ts +41 -0
  495. package/src/assembly/webgpu-device-loss.ts +75 -0
  496. package/src/assembly/webgpu-pbr-ready.ts +232 -0
  497. package/src/assembly/webgpu-ready-mipmap.ts +26 -0
  498. package/src/assembly/webgpu-ready.ts +142 -121
  499. package/src/assembly/webgpu-renderer-bootstrap.ts +85 -0
  500. package/src/assembly/webgpu-renderer-contract.ts +69 -0
  501. package/src/assembly/webgpu-renderer-guards.ts +32 -0
  502. package/src/assembly/webgpu-renderer-observation.ts +43 -0
  503. package/src/assembly/webgpu-renderer-recovery-failure.ts +62 -0
  504. package/src/assembly/webgpu-renderer.ts +675 -614
  505. package/src/assembly/webgpu-vertex-layouts.ts +233 -87
  506. package/src/components/__tests__/camera-exposure.contract.unit.test.ts +153 -0
  507. package/src/components/camera.ts +287 -7
  508. package/src/components/index.ts +1 -0
  509. package/src/components/instances.ts +45 -18
  510. package/src/components/scene-instance.ts +2 -1
  511. package/src/components/screen-space-reflection.ts +21 -0
  512. package/src/components/sprite-animation.ts +3 -3
  513. package/src/components/sprite-instances.ts +1 -1
  514. package/src/device/gpu-residency.ts +96 -93
  515. package/src/device/mesh-residency-lifetime.ts +73 -0
  516. package/src/dynamic-geometry.ts +736 -0
  517. package/src/environment/background.ts +263 -0
  518. package/src/errors/gpu-driven.ts +39 -5
  519. package/src/errors/render.ts +27 -3
  520. package/src/extract/camera.ts +26 -1
  521. package/src/extract/gpu-driven.ts +185 -10
  522. package/src/features/host.ts +237 -19
  523. package/src/features/noise-texture.ts +91 -0
  524. package/src/features/plan.ts +553 -24
  525. package/src/features/prepared-gpu-work.ts +631 -49
  526. package/src/features/prepared-graphics-store.ts +31 -0
  527. package/src/features/prepared-graphics.ts +5 -0
  528. package/src/features/render-graph-compute.ts +41 -8
  529. package/src/features/render-graph-contribution.ts +141 -3
  530. package/src/features/render-graph-raster.ts +143 -30
  531. package/src/features/types.ts +11 -0
  532. package/src/fullscreen-post-process-pass.ts +3 -0
  533. package/src/gpu-driven/batch-topology.ts +424 -102
  534. package/src/gpu-driven/pbr-program.ts +31 -0
  535. package/src/gpu-driven/prepared-draw.ts +29 -4
  536. package/src/gpu-driven/production-raster.ts +2267 -694
  537. package/src/gpu-driven/shadow-views.ts +539 -0
  538. package/src/gpu-driven/view-gpu.ts +188 -41
  539. package/src/gpu-scene-schema.ts +52 -11
  540. package/src/gpu-scene.ts +186 -99
  541. package/src/gpu-texture-usage.ts +1 -0
  542. package/src/hdrp-buffers.ts +72 -36
  543. package/src/ibl/frame-resources.ts +38 -0
  544. package/src/ibl/skylight-bind-group.ts +5 -4
  545. package/src/index.ts +125 -1
  546. package/src/inspection-types.ts +191 -0
  547. package/src/instances-derived-bounds.ts +258 -34
  548. package/src/instances.ts +459 -150
  549. package/src/internal.ts +22 -0
  550. package/src/lifecycle.ts +2 -0
  551. package/src/material-row.ts +145 -0
  552. package/src/materials.ts +44 -8
  553. package/src/pbr-pipeline.ts +159 -17
  554. package/src/pipeline/standard-forward-lane.ts +52 -24
  555. package/src/pipeline/standard-output/__tests__/lut-admission.unit.test.ts +103 -0
  556. package/src/pipeline/standard-output/auto-exposure/__tests__/capability.unit.test.ts +40 -0
  557. package/src/pipeline/standard-output/auto-exposure/__tests__/gpu.contract.unit.test.ts +125 -0
  558. package/src/pipeline/standard-output/auto-exposure/__tests__/graph.contract.unit.test.ts +94 -0
  559. package/src/pipeline/standard-output/auto-exposure/__tests__/oracle.test.ts +108 -0
  560. package/src/pipeline/standard-output/auto-exposure/__tests__/preset.test.ts +26 -0
  561. package/src/pipeline/standard-output/auto-exposure/capability.ts +38 -0
  562. package/src/pipeline/standard-output/auto-exposure/gpu.ts +567 -0
  563. package/src/pipeline/standard-output/auto-exposure/graph.ts +221 -0
  564. package/src/pipeline/standard-output/auto-exposure/inspection.ts +231 -0
  565. package/src/pipeline/standard-output/auto-exposure/oracle.ts +291 -0
  566. package/src/pipeline/standard-output/auto-exposure/preset.ts +28 -0
  567. package/src/pipeline/standard-output/auto-exposure/state.ts +156 -0
  568. package/src/pipeline/standard-output/color-transform.ts +124 -0
  569. package/src/pipeline/standard-output/graph.ts +139 -0
  570. package/src/pipeline/standard-output/lut-admission.ts +121 -0
  571. package/src/pipeline/standard-output/lut-gpu.ts +399 -0
  572. package/src/pipeline/standard-output/lut-state.ts +176 -0
  573. package/src/pipeline/standard-output/resources.ts +121 -0
  574. package/src/pipeline/standard-output/types.ts +58 -0
  575. package/src/pipeline/standard-pipeline.ts +192 -59
  576. package/src/pipeline/standard-post.ts +186 -27
  577. package/src/pipeline-builder.ts +16 -6
  578. package/src/pipeline-spec-types.ts +4 -0
  579. package/src/pipeline-spec.ts +37 -29
  580. package/src/points-lines/standard-owner.ts +6 -14
  581. package/src/prepare/prepared-graphics-resolver.ts +9 -2
  582. package/src/record/__tests__/material-slot-plan.unit.test.ts +48 -0
  583. package/src/record/__tests__/mesh-ssbo.unit.test.ts +40 -1
  584. package/src/record/__tests__/recovery-pipeline.unit.test.ts +2 -29
  585. package/src/record/__tests__/sprite-material-abi.unit.test.ts +36 -5
  586. package/src/record/__tests__/typed-frame-graph-plan.unit.test.ts +45 -0
  587. package/src/record/__tests__/view-ubo-layout.unit.test.ts +5 -0
  588. package/src/record/dynamic-geometry-consumption.ts +59 -0
  589. package/src/record/frame-lighting.ts +12 -11
  590. package/src/record/frame-snapshot.ts +133 -23
  591. package/src/record/frame-targets.ts +9 -0
  592. package/src/record/frame.ts +727 -133
  593. package/src/record/main-pass-geometry.ts +162 -98
  594. package/src/record/main-pass-material.ts +115 -26
  595. package/src/record/main-pass-sprite-draws.ts +18 -107
  596. package/src/record/main-pass.ts +78 -8
  597. package/src/record/material-uniforms.ts +1 -1
  598. package/src/record/mesh-ssbo.ts +59 -15
  599. package/src/record/prepared-material-bindings.ts +234 -0
  600. package/src/record/recovery-pipeline.ts +59 -136
  601. package/src/record/render-context.ts +92 -22
  602. package/src/record/shadow-pass.ts +1231 -209
  603. package/src/record/skybox-post-pass.ts +14 -3
  604. package/src/record/typed-frame-graph.ts +1006 -53
  605. package/src/record/view-ubo.ts +11 -0
  606. package/src/recovery/render-system-candidate.ts +90 -66
  607. package/src/recovery/render-system-roots.ts +113 -0
  608. package/src/recovery/types.ts +77 -0
  609. package/src/reflection/record-owner.ts +26 -54
  610. package/src/render-contract.ts +219 -25
  611. package/src/render-graph-primitives.ts +6 -2
  612. package/src/render-pipeline.ts +70 -1
  613. package/src/render-system-extract-tail.ts +409 -160
  614. package/src/render-system-extract.ts +376 -102
  615. package/src/render-system-projections.ts +241 -0
  616. package/src/render-system.ts +692 -791
  617. package/src/scene/render-scene-types.ts +13 -4
  618. package/src/scene/render-scene.ts +569 -355
  619. package/src/scene/shadow-visibility.ts +21 -0
  620. package/src/ssr/admission.ts +380 -14
  621. package/src/ssr/compose.ts +144 -0
  622. package/src/ssr/errors.ts +48 -0
  623. package/src/ssr/graph.ts +777 -0
  624. package/src/ssr/history.ts +436 -0
  625. package/src/ssr/hiz.ts +124 -0
  626. package/src/ssr/inspection.ts +124 -0
  627. package/src/ssr/resources.ts +202 -0
  628. package/src/ssr/temporal.ts +164 -0
  629. package/src/systems/skin-palette-allocator.ts +495 -148
  630. package/src/systems/skin-palette-types.ts +17 -0
  631. package/src/systems/transparent-dispatch.ts +90 -0
  632. package/src/targets/material-source.ts +34 -0
  633. package/src/temporal/coverage.ts +1 -1
  634. package/src/temporal/frame-coordinator.ts +23 -2
  635. package/src/temporal/gpu.ts +74 -12
  636. package/src/temporal/history.ts +3 -1
  637. package/src/temporal/inspection.ts +2 -1
  638. package/src/temporal/standard-scene-data.ts +3 -1
  639. package/src/temporal/temporal-view.ts +3 -0
  640. package/src/transmission/__tests__/standard-transmission.dawn.test.ts +204 -139
  641. package/src/typed-render-graph-primitives.ts +266 -74
  642. package/src/typed-shadow-passes.ts +122 -12
  643. package/src/volume/temporal.ts +10 -0
  644. package/dist/chunk-ADHHVYLW.mjs.map +0 -1
  645. package/dist/chunk-FJ6P52EE.mjs.map +0 -1
  646. package/dist/chunk-GE3SDD72.mjs.map +0 -1
  647. package/dist/chunk-GF523LHF.mjs.map +0 -1
  648. package/dist/chunk-HKXTW355.mjs.map +0 -1
  649. package/dist/chunk-JDWARUOI.mjs.map +0 -1
  650. package/dist/chunk-KM2NOX2I.mjs.map +0 -1
  651. package/dist/chunk-TMIASV2N.mjs.map +0 -1
  652. package/dist/chunk-XDP6XE5V.mjs.map +0 -1
  653. package/dist/chunk-ZZ4YQ474.mjs.map +0 -1
  654. package/src/__tests__/instances-world-ownership.integration.test.ts +0 -62
  655. 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,10 @@ 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
+ Recovery prepares the last successfully submitted workset; the next draw still
53
+ consumes current World edits and admits new resources through normal residency.
52
54
 
53
55
  `inspect().recovery` is always present. Its `phase` is `null` outside an active
54
56
  attempt and otherwise follows `quiesce`, `acquire-adapter`, `acquire-device`,
@@ -194,6 +196,28 @@ environment owners or multiple fog owners return structured errors with a
194
196
  code-specific `detail`; they do not create a second registry or silently pick
195
197
  the first entity. Frame facts contain IDs, revisions, and POD values only, not
196
198
  textures, buffers, devices, or other live GPU objects.
199
+
200
+ The Standard graph renders an Atmosphere source as a 128-by-128, six-face
201
+ `rgba16float` sky cube and a background pass before scene geometry. The selected
202
+ DirectionalLight supplies the sun direction, color, and illuminance; the sun
203
+ angle is in radians, and zero radius disables the visible disc. Geometry covers
204
+ the background through ordinary scene rendering, including transparent blending.
205
+ The cube excludes the disc. Weak ambient lighting remains the explicit `Skylight`
206
+ input; selecting an Atmosphere does not silently add a second ambient light.
207
+
208
+ The cube has 786,432 texture payload bytes plus 280 bytes of graph-owned
209
+ parameter and vertex payload, before backend allocation alignment. Its six faces update together when the selected environment
210
+ signature changes, and become current only after successful frame submission.
211
+ An unchanged source runs only the background pass. Resize, topology replacement,
212
+ and recovery use normal graph retirement and reconstruction. GPU pass observations
213
+ name `atmosphere-prepare`, `atmosphere-cube-0` through `atmosphere-cube-5`, and
214
+ `atmosphere-background`; unavailable timestamps remain explicitly unavailable.
215
+ The source lifecycle inspection and graph resource accounting describe different
216
+ owners: a source revision is not a claim that its GPU cube was submitted.
217
+
218
+ The reusable [Wave 1 recipe](../../scripts/dev-verify/wave1-rendering/README.md)
219
+ shows the public scene inputs and associated acceptance gates.
220
+
197
221
  ## GPU pass timing: opt in, draw, observe, branch on status
198
222
 
199
223
  GPU pass timing is disabled by default. Opt in once on `createRenderer`, keep the
@@ -291,6 +315,18 @@ over-budget requests. A World with no `PointLightShadow` remains `inactive`;
291
315
  there is no pseudo-disabled shadow mode. Directional CSM remains a separate
292
316
  shadow owner; PointLightShadow is not a second directional-light path.
293
317
 
318
+ The cube atlas uses negated world axes: cube lookup is `lightPos - worldPos`,
319
+ and each face camera looks opposite its cube axis with the corresponding face-up
320
+ vector. This matches WebGPU framebuffer V without reflecting clip-space Y or
321
+ changing authored front-face/culling state. Raster matrices and sampling must
322
+ change together; a standalone Z flip is not a valid conversion.
323
+
324
+ Camera-culled scene meshes remain shadow candidates when their bounds intersect
325
+ an admitted light frustum. Their projected dispatch contains only `ShadowCaster`,
326
+ never Forward/Deferred work. Persistent composition uses the merged light frame,
327
+ including lights from another World. `frustumStats.culled` continues to count
328
+ camera rejection even when the retained object casts a shadow.
329
+
294
330
  ## Render happy path
295
331
 
296
332
  `RenderScene -> Standard Pipeline -> DeviceScope -> FrameReceipt` is the only frame
@@ -319,20 +355,50 @@ and keeps shadow, PBR, IBL, SSAO, bloom, tone, antialiasing, sky, material, VFX,
319
355
  ownership inside the same graph and submit boundary. CPU and WebGL2 remain capability lanes for the
320
356
  Cluster membership producer or for mesh/instance storage fallback; they do not reintroduce a
321
357
  PointLight/SpotLight buffer or a fixed four-light ABI.
322
- ## SSR M0 admission: inspect owners, then decide
358
+ ## SSR admission and bounded inspection
359
+
360
+ Coverage counters are `null` until GPU measurements are supplied; missing
361
+ measurements must not be interpreted as zero hits. The hello-ssr Browser smoke
362
+ captures a v7 RHI tape, reads trace/temporal/compose outputs at their producing
363
+ work indices, and requires positive confidence plus a measurable composition
364
+ delta. It also verifies that the composed resource reaches a later draw and
365
+ retains its pixels through that draw. Ordinary `hello-ssr` startup enables SSR.
366
+
367
+ The Standard G-buffer stores the visible material's response once:
368
+
369
+ | Target | RGB | Alpha |
370
+ |:--|:--|:--|
371
+ | Normal/roughness | Encoded world normal | Roughness |
372
+ | Albedo/metallic | Linear albedo | Metallic |
373
+ | Specular response/AO | Unit-radiance specular BRDF response, including occlusion | AO |
374
+
375
+ Forward lighting still owns direct light and emissive contributions. SSR uses
376
+ one fullscreen additive draw, not a geometry redraw: its RGB delta is
377
+ `confidence * (filteredRadiance * specularResponse - fallback)`. This prevents
378
+ equal-depth overlapping geometry from subtracting the fallback more than once.
379
+ Resolved radiance has a confidence-weighted mip chain; composition samples
380
+ `roughness² * (mipCount - 1)` with trilinear reconstruction. Receiver admission
381
+ uses the current lighting coverage, compatible half-resolution normals, and
382
+ the shared View's enable/roughness cutoff. It does not clip the rough lobe to
383
+ the central mirror ray's hit confidence. Material roughness changes the
384
+ reflection's filter footprint, not just its opacity; confidence remains bounded
385
+ by the current receiver's roughness fade. Coarse mips are spatial approximations,
386
+ not separate roughness-dependent ray traces.
323
387
 
324
388
  > [!IMPORTANT]
325
389
  > 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.
390
+ > r32float format, and optional prior temporal receipts with one four-field
391
+ > integration identity. Missing required, stale, structural-only, or mismatched receipts stay
392
+ > `fallback-only` and emits zero SSR work. An admitted Standard frame then
393
+ > projects the renderer-owned M1 spatial chain and M2 history through
394
+ > `renderer.inspect().ssr`; paired Browser/Dawn pixel evidence is still the
395
+ > gate for calling the result SSR v1.
330
396
 
331
397
  ```mermaid
332
398
  flowchart LR
333
399
  P["producer detached receipt"] --> A["admitSsrM0"]
334
400
  F["format stage receipt"] --> A
335
- T["temporal submit receipt"] --> A
401
+ T["optional prior temporal receipt"] -.-> A
336
402
  A -->|"all identity and verdict checks pass"| E["admitted: parent M1 may unlock"]
337
403
  A -->|"missing, stale, unavailable, or mismatched"| B["fallback-only: zero work"]
338
404
  ```
@@ -355,16 +421,64 @@ if (admission.status === 'fallback-only') {
355
421
  }
356
422
  ```
357
423
 
424
+ After the ordinary frame, read the detached spatial projection without opening
425
+ the graph or a GPU handle:
426
+
427
+ ```ts
428
+ const spatial = renderer.inspect().ssr;
429
+ switch (spatial.status) {
430
+ case 'not-requested':
431
+ case 'requested':
432
+ case 'fallback-only':
433
+ // Base probe / Skylight / neutral reflection remains visible.
434
+ break;
435
+ case 'structural-only':
436
+ // The graph is admitted, but this run has no executable shader carrier.
437
+ break;
438
+ case 'admitted':
439
+ // Inspect history, passRoster, fallbackSource, and coverage before evidence.
440
+ break;
441
+ }
442
+ ```
443
+
444
+ | `renderer.inspect().ssr.status` | Meaning | SSR work |
445
+ |:--|:--|:--|
446
+ | `not-requested` | Active camera has no `ScreenSpaceReflection` | Exact zero |
447
+ | `requested` | Authoring is present; M0/spatial facts are not yet complete | Exact zero |
448
+ | `fallback-only` | A closed config, lane, input, capability, or owner receipt failed | Exact zero |
449
+ | `structural-only` | Spatial admission and graph topology exist, but no executable shader source is bound | Topology only |
450
+ | `admitted` | Standard spatial path and shader manifest are bound | Inspect `history`, `passRoster`, `fallbackSource`, and coverage |
451
+
452
+ `history.state`, `history.bytes`, `history.resetCount`, `passRoster`, and
453
+ `fallbackSource` are bounded POD facts. They are evidence about the current
454
+ owner state, not an acceptance claim: M1/M2 remain an implementation checkpoint
455
+ until the paired Browser/Dawn 300-frame carrier, readback, falsifiers, and
456
+ performance gates are present.
457
+
458
+ SSR temporal feedback and presentation use distinct coordinates without another
459
+ allocation. The persistent color/depth and normal/confidence slots use fixed,
460
+ unjittered half-resolution centers (`2*p + 0.5` in full-resolution pixels).
461
+ Current trace samples are reconstructed onto that grid once; history reprojection
462
+ uses unjittered motion and validates depth/normal before interpolation. The
463
+ separate resolved output stays on the current jittered raster grid for composition
464
+ and reflection mip generation. Never feed that presentation reconstruction back
465
+ into history: repeated jitter filtering loses reflected texture contrast.
466
+
358
467
  Use `inspect -> owner recovery -> matching submit -> reinspect` as the complete
359
468
  AI route. No returned field is a device, graph node, texture, buffer, encoder,
360
469
  or other live handle. A stable generation keeps `resetCount`, `rebuildCount`,
361
470
  and additional format probe work at zero; a changed generation must be
362
471
  reinspected before admission can be considered again.
363
472
 
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.
473
+ `inspect()` is a synchronous snapshot. Explicit `RendererOptions.ssrIdentity`
474
+ opts into one real format probe during initialization of each device generation;
475
+ initialization and recovery await it before the first synchronous draw. A renderer
476
+ without that option does not probe. Camera demand still controls all per-frame
477
+ SSR attachments, passes, history and uploads. An unavailable format stays
478
+ fallback-only; an absent previous temporal receipt does not block fresh spatial
479
+ SSR. Current-frame motion/coverage comes from the Standard graph. History is
480
+ read only after its normal successful-submit validation, never fabricated to
481
+ admit the first frame.
368
482
 
369
483
  There are two action projections. `admission.failure.detail.action` is the coarse
370
484
  M0 gate instruction (`retry` for demand/readiness and `rebuild` for a missing or
@@ -377,8 +491,8 @@ API; use the existing owner operation shown here, then submit and inspect again:
377
491
  | `reflectionFallbackInspection.recoveryAction = use-LKG` | Keep the compatible probe row and draw the next frame | Read the committed row and matching temporal receipt |
378
492
  | `reflectionFallbackInspection.recoveryAction = use-Skylight` / `use-neutral` | Change the World-owned probe/`Skylight` source, then draw | Confirm selected source and committed generation |
379
493
  | `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 |
494
+ | `admission.failure.detail.action = retry` / `rebuild` | Retry the consumer frame, or rebuild the named owner receipt | Reinspect required receipts and any supplied temporal receipt |
495
+ | device-loss recovery | `await renderer.recover()` before the next draw | Confirm the replacement `deviceGeneration` and its completed format probe |
382
496
 
383
497
  SSR never invokes these operations or mutates producer/device state; this table
384
498
  maps the typed action to the existing public World and Renderer seams.
@@ -410,18 +524,56 @@ The exact public names and stable consumer IDs are machine-readable in
410
524
 
411
525
  Progressive disclosure is intentional: read Camera configuration first, then
412
526
  the graph facts, backend facts, and finally the stable observation identity.
413
- The JSON-safe `RenderInspection` projection names the output contract directly:
527
+ The JSON-safe `RenderInspection` projection keeps renderer lifecycle facts and
528
+ the output contract in separate named subtrees. Read `renderer.inspect().output`
529
+ for output facts; do not infer output state from renderer-level fields:
414
530
 
415
531
  | Field | Meaning | Owner |
416
532
  |:--|:--|:--|
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 |
533
+ | `output.outputTransform` | `forgeax::standard::output-transform` | Standard post chain |
534
+ | `output.displayEncoded` | final output is display encoded | Output Transform |
535
+ | `output.intermediateFormat` | linear intermediate format (`rgba16float`) | Standard target owner |
536
+ | `output.surfaceStorage` / `output.surfaceDisplay` | raw storage and display surface formats | surface adapter |
537
+ | `output.endpoint` | `surface.storage.raw` | backend adapter |
538
+ | `output.capability` / `output.error` | structured availability and recovery facts | RHI/render surface |
423
539
  | `observation.observationId` / `frameId` | stable correlation refs | renderer observation owner |
424
540
 
541
+ ### Auto exposure and 3D LUT authoring
542
+
543
+ Exposure is a Camera-owned closed union. Manual exposure is a positive
544
+ multiplier; auto exposure carries a fallback, EV compensation, bounded EV
545
+ range, and non-negative adaptation rates. The renderer owns the GPU receipt,
546
+ but the author owns the Camera input:
547
+
548
+ ```ts
549
+ world.set(camera, Camera, {
550
+ exposure: { kind: 'auto', fallback: 1, compensationEv: 0,
551
+ rangeEv: [-4, 4], rates: [2, 1] },
552
+ }).unwrap();
553
+
554
+ const output = renderer.inspect().output;
555
+ if (output.autoExposure?.receipt.committed !== true) {
556
+ throw new Error(output.error?.hint ?? 'auto exposure is not committed');
557
+ }
558
+ ```
559
+
560
+ Color grading uses the ordinary shared `TextureAsset` route. Import/cook the
561
+ 3D LUT through Pack/Catalog, load the shared handle by its GUID/source key, and
562
+ bind it to the Camera; there is no second LUT registry or URL-derived identity:
563
+
564
+ ```ts
565
+ world.set(camera, Camera, {
566
+ colorLut: world.allocSharedRef('TextureAsset', lutHandle),
567
+ colorLutStrength: 0.75,
568
+ }).unwrap();
569
+ const lut = renderer.inspect().output.standardLut;
570
+ // Match lut.sourceKey and lut.receipt.frameId to the submitted FrameReceipt.
571
+ ```
572
+
573
+ `output.autoExposure`, `output.standardLut`, and `output.error` are detached
574
+ facts. A fallback or last-known-good row is a recovery observation, not proof
575
+ that a physical Browser/Dawn workload or timing gate completed.
576
+
425
577
  `renderer.inspect()` and `renderer.observe()` never carry ROI pixels, scanlines,
426
578
  brightness or level metrics. The hello-fxaa `hello-fxaa/dark-gradient/v1`
427
579
  fixture report owns those fields and records the fixed camera, low-light scene,
@@ -508,6 +660,83 @@ gate exercises the browser delivery path. Runtime inspection uses the public
508
660
  belongs in `packages/render/bench` and its dev-verify producer. Render does
509
661
  not expose a timing controller or a second membership-specific API.
510
662
 
663
+ ## TAA accumulation and private stability
664
+
665
+ TAA reconstructs unjittered current color, validates reprojected depth, and
666
+ accumulates HDR using luminance-preserving compressed-domain weights. History
667
+ depth validation recognizes mixed current-depth coverage, following Three's
668
+ TRAA edge exception: background/geometry and depth-discontinuous 3x3 edges
669
+ may retain clipped history instead of alternating between accumulated and
670
+ raw current color. Uniform-depth disocclusion, absent current geometry,
671
+ out-of-bounds reprojection, invalid global history, motion and reactivity
672
+ retain their rejection or weighting behavior. The GPU coverage regression
673
+ executes the complete resolve, including the non-edge negative cases.
674
+ History clipping uses YCoCg bounds. After eight accepted stationary frames, its bounds
675
+ cover the texels supporting the reconstructed neighborhood; motion, rejection,
676
+ or reactivity resets that private age. The history still updates every frame:
677
+ this is not a frozen screenshot or an appearance acceptance claim.
678
+
679
+ The private age continues to 128 accepted stationary frames without another
680
+ surface. History weight stays at 0.95 through frame 64, then smoothly rises to
681
+ 0.99 by frame 128 to reduce residual phase response. The eight-frame clipping
682
+ footprint remains unchanged. Clipping relaxation runs earlier, from accepted
683
+ frame 8 through frame 64, while the 0.95 history weight still permits responsive
684
+ accumulation. Releasing it only after increasing history weight preserves a
685
+ biased clipped mean and causes prolonged static drift. Once settled, a single phase's unsupported
686
+ color does not erase accumulated subpixel coverage: clipping resumes only
687
+ after eight consecutive unsupported samples in the same signed dominant RGB
688
+ direction. Finding support or reversing direction breaks that streak.
689
+ Persistent unmarked color changes therefore restore color clipping within one
690
+ complete jitter cycle. This color correction preserves the accepted stationary
691
+ age and its reconstruction footprint; it must not collapse the next frame's
692
+ neighborhood from 5x5 to 3x3. Motion,
693
+ reactivity or rejection immediately reset
694
+ the age and restore fast recovery; this delay is necessary because zero
695
+ velocity alone does not establish settled radiance.
696
+
697
+ An optional secondary-source reactivity texture joins the scene reactive lane
698
+ before history weighting and stability counting. Standard forwards SSR's
699
+ existing hit-source mask through a typed sampled read; TAA takes a conservative
700
+ 7x7 maximum at that texture's resolution. This covers the measured vacated
701
+ reflection edge missed by the former radius-one footprint; it is a bounded
702
+ dilation, not an arbitrary-speed reflected-motion guarantee. Reflected source motion can therefore
703
+ reset a stationary receiver without changing the receiver's velocity or depth.
704
+ The disabled path binds existing temporal data but does not sample it as a mask;
705
+ no extra texture or pass is allocated. The 658-frame Browser and Dawn feedback
706
+ variant checks secondary-motion reset and recovery; Dawn additionally verifies
707
+ a failing control that ignores the mask. Each cyclic input step checks the
708
+ Karis recurrence from the actual stored history against its adjacent FP16
709
+ values; an independent infinite-precision tail detects accumulated bias.
710
+ The premature high-weight counterexample must also fail. This does not establish visual
711
+ convergence or ghost-free SSR.
712
+
713
+ Color writeback stochastically selects adjacent representable FP16 values before
714
+ attachment conversion. This prevents consistently downward conversion from
715
+ accumulating a dark history bias without widening the history textures. Its
716
+ deterministic pixel/frame hash shares one RGB threshold, leaves alpha and
717
+ temporal metadata unchanged, and does not freeze the jitter sequence. Browser
718
+ and Dawn run the same 528-frame raster-feedback regression against an analytic
719
+ compressed-HDR recurrence; this precision gate is not visual acceptance.
720
+
721
+ | Ping-pong history | Format | Consumer |
722
+ |:--|:--|:--|
723
+ | Color | `rgba16float` | TAA and downstream HDR processing |
724
+ | Motion, depth, reactivity | `rgba16float` | Unchanged `temporal-v1` metadata, including Motion Blur |
725
+ | Stationary age and settled clipping streak | `r8unorm` | TAA only; never stored in the reactive lane |
726
+
727
+ The private byte uses codes 0–128 for stationary age and 129–170 for one
728
+ through seven unsupported samples in six signed RGB directions. The eighth
729
+ matching sample restores clipping and resets age. This is one encoded state,
730
+ not an additional attachment or a change to downstream temporal metadata.
731
+ Browser and Dawn raster-feedback tests cover phase-local coverage, unmarked
732
+ color steps, and a falsifier that permanently disables clipping.
733
+
734
+ These six surfaces share the existing successful-submit, abort, replacement,
735
+ and retirement lifecycle. Texture accounting is 34 bytes per output pixel;
736
+ `inspect().temporal.resources` includes active, candidate, and retiring states.
737
+ Fullscreen pipeline targets come from the declared attachment format list,
738
+ not from inspecting shader source text for a particular output structure.
739
+
511
740
  ## Motion Blur temporal consumer
512
741
 
513
742
  The Standard camera may carry the presence-enabled `MotionBlur` component. Its
@@ -664,69 +893,135 @@ infer state from URLs, or repair a producer failure. Aggregate counts are
664
893
  derived from the observations, so an AI can inspect first, repair or recook the
665
894
  producer, and retry without guessing at hidden renderer state.
666
895
 
667
- ### Large Instances ownership
896
+ ### Renderer-owned Instances and CPU bounds
668
897
 
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.
898
+ `Instances` is a lightweight ECS association containing only a
899
+ renderer-owned `collectionId`. Create and mutate the canonical packed matrices
900
+ through the same `renderer.instances` owner; it validates mat4 stride, keeps
901
+ revision/capacity/dirty ranges, and returns detached inspection snapshots.
902
+
903
+ ```ts
904
+ function identityMatrices(count: number): Float32Array {
905
+ const matrices = new Float32Array(count * 16);
906
+ for (let index = 0; index < count; index += 1) {
907
+ const offset = index * 16;
908
+ // Column-major identity, with a deterministic layout so the instances are visible.
909
+ matrices[offset] = 1;
910
+ matrices[offset + 5] = 1;
911
+ matrices[offset + 10] = 1;
912
+ matrices[offset + 15] = 1;
913
+ matrices[offset + 12] = (index % 100) * 2;
914
+ matrices[offset + 14] = Math.floor(index / 100) * 2;
915
+ }
916
+ return matrices;
917
+ }
918
+
919
+ const collection = renderer.instances.create({
920
+ transforms: identityMatrices(instanceCount),
921
+ }).unwrap();
922
+ world.spawn(
923
+ { component: MeshFilter, data: { assetHandle: cube } },
924
+ { component: MeshRenderer, data: {} },
925
+ { component: Instances, data: { collectionId: collection.collectionId } },
926
+ );
927
+
928
+ const patch = identityMatrices(1);
929
+ patch[12] = 4;
930
+ renderer.instances.update(collection.collectionId, {
931
+ start: 0,
932
+ transforms: patch,
933
+ });
934
+
935
+ // Replacement keeps the logical identity and publishes a new revision.
936
+ renderer.instances.replace(collection.collectionId, identityMatrices(instanceCount + 1));
937
+ const current = renderer.instances.inspect(collection.collectionId).unwrap();
938
+ const detached = renderer.instances.snapshot(collection.collectionId).unwrap();
939
+ renderer.instances.release(collection.collectionId);
940
+ ```
673
941
 
674
- | Data | Owner |
942
+ The lifecycle is deliberately one owner: `create` allocates canonical CPU
943
+ storage, `replace` publishes a complete new revision (and grows capacity when
944
+ needed), `update` validates a bounded matrix interval and records its dirty
945
+ range, `inspect` returns identity/count/capacity/revision, `snapshot` returns a
946
+ detached matrix copy plus dirty ranges for extraction, and `release` makes the
947
+ collection id terminal. A caller never provides a GPU buffer or maintains
948
+ backend-specific chunks. `detached.transforms` is an observation, not mutable
949
+ renderer state.
950
+
951
+ Render snapshots one collection per frame and derives CPU union bounds from the
952
+ mesh AABB, entity world matrix, and detached matrices. Direct, GPU-driven, and
953
+ backend fallback lanes consume that same projection; internal GPU chunks never
954
+ become ECS or game-side state. Missing, malformed, or empty collections fail
955
+ closed or remain a conservative no-cull result without manufacturing an
956
+ identity instance. The initial payload must contain valid matrices: an
957
+ all-zero mat4 has a zero homogeneous `w` and can produce a blank frame. Do not
958
+ add matrix bytes, bounds, or chunk fields to the public `Instances` component or
959
+ duplicate this renderer fact in Pack/asset state.
960
+
961
+ #### Instance inspection and recovery
962
+
963
+ `renderer.inspect().instanceCollections` is detached, bounded evidence for
964
+ every live collection. Each row contains:
965
+
966
+ | Field | Meaning |
675
967
  |:--|:--|
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:
968
+ | `collectionId`, `count`, `capacity`, `revision` | Logical identity and canonical authoring revision. |
969
+ | `residentGeneration`, `lane` | Device generation and the selected `direct-storage`, `chunked-storage`, `direct-uniform`, `chunked-uniform`, `unresident`, or `unavailable` lane. |
970
+ | `dirtyRanges`, `uploadRanges` | Canonical pending edits and the ranges uploaded by the current resident. |
971
+ | `uploadedBytes`, `requestedBytes`, `supportedBytes` | Measured upload and admission byte facts; unsupported limits remain explicit. |
972
+ | `backend`, `owner` | Backend identity and the fixed owner `renderer.instances`. |
973
+ | `error` | A structured record-stage failure with `code`, `expected`, `hint`, and typed `detail` facts. |
974
+
975
+ Authoring failures use the same closed `InstanceCollectionError` contract.
976
+ For example, an invalid stride is rejected before publication and carries
977
+ typed facts instead of requiring message parsing:
978
+
979
+ ```ts
980
+ const result = renderer.instances.create({ transforms: new Float32Array(17) });
981
+ if (!result.ok) {
982
+ const { code, expected, hint, detail, facts } = result.error;
983
+ // facts expose requestedBytes, supportedBytes, backend, owner, cause, and
984
+ // recovery; detail retains operation metadata. Repair the payload and retry.
985
+ void code;
986
+ void expected;
987
+ void hint;
988
+ void detail;
989
+ void facts;
990
+ }
991
+ ```
992
+
993
+ After a `device-lost` transition, call `await renderer.recover()` and retry the
994
+ same draw request. The renderer drops generation-owned residents, keeps the
995
+ canonical collection, and uploads every matrix into the new resident before
996
+ recording; it does not reuse an old partial dirty range for uninitialized GPU
997
+ memory. If inspection reports `unavailable`, follow its `error.detail.recovery`
998
+ and repair the named capability/producer before retrying.
999
+
1000
+ The large-instance smoke exercises the real Dawn path for all admitted sizes:
712
1001
 
713
1002
  ```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
1003
+ for count in 1500 10000 20000; do
1004
+ INSTANCE_COUNT=$count SMOKE_MIN_FRAMES=600 SMOKE_DURATION_MS=0 \
1005
+ pnpm --filter @forgeax/parity-instancing-static smoke
1006
+ done
716
1007
  ```
717
1008
 
718
- ### Instances CPU bounds contract
1009
+ Each run must retain one logical collection, submit real work, upload on its
1010
+ first resident frame, and report zero transform-upload bytes on unchanged
1011
+ frames. Browser WebGPU acceptance additionally uses the browser/dev-server
1012
+ transport through the same production fixture and parameterizes the same three
1013
+ populations:
1014
+
1015
+ ```bash
1016
+ pnpm exec vitest run --config vitest.browser.config.ts --project=browser \
1017
+ apps/parity/instancing-static/src/__tests__/instances.browser.test.ts
1018
+ ```
719
1019
 
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.
1020
+ The Browser test submits 600 frames for each population, asserts a real
1021
+ `webgpu` backend, captures the first and stable `instanceCollections` upload
1022
+ witnesses, reads the presented canvas through the browser compositor, and
1023
+ requires visible spread at three or more grid samples with zero renderer/RHI
1024
+ error events. Dawn structural evidence alone is not a browser validation.
730
1025
 
731
1026
  ## Points and Lines authoring (M1)
732
1027
 
@@ -959,18 +1254,32 @@ state published by `World.update()`.
959
1254
 
960
1255
  ## Persistent render scene
961
1256
 
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.
1257
+ Every attached World composition bootstraps one renderer-owned CPU projection,
1258
+ then consumes each World's component and shared-reference change versions. One identity-based update
1259
+ publication merges content, root transforms, and instance changes, including
1260
+ when all occur in the same frame. Its GPU projection compares affected matrix
1261
+ and metadata rows before uploading them. An unchanged frame retains its existing
1262
+ snapshot; unrelated gameplay component writes do not invalidate render state.
1263
+ There is no exclusive transform/instance scene admission followed by a separate
1264
+ rebuild implementation. Missing producer evidence causes conservative source
1265
+ extraction into the same retained projection. World reordering and catalog
1266
+ reconciliation preserve surviving slots and their submitted temporal history.
967
1267
 
968
1268
  Mutable shared payloads use the same explicit-dirty rule: mutate the resolved
969
1269
  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.
1270
+ one monotonic shared-ref epoch on the no-change path, reads changed handles
1271
+ when it advances, and refreshes the projection slots indexed by changed
1272
+ material or mesh handles. Missing source evidence requests conservative
1273
+ source reconciliation. Camera, light, and environment facts refresh independently
1274
+ of geometry publication; their edits do not discard retained geometry.
1275
+
1276
+ Instance collection revisions refresh only their consumers. Visibility and
1277
+ parent changes refresh the affected subtree, and joint changes refresh the
1278
+ retained skin consumers. Shadow pass facts belong to each retained renderable;
1279
+ frame ownership is derived before camera culling so offscreen casters remain
1280
+ available. CPU visibility, occlusion, and LOD consume this same projection on
1281
+ every frame, including frames submitted through GPU-driven raster. No scene
1282
+ classification enables a second extraction or visibility implementation.
974
1283
 
975
1284
  When `RhiCaps.storageBuffer` is available, the same projection owns persistent
976
1285
  Primitive, Instance, Transform, DrawTemplate, and Material GPU tables. The
@@ -998,11 +1307,128 @@ reason. The nested `gpu` status is one of `inactive`, `unsupported`, `resident`,
998
1307
  `rebuild-pending`, or `error`; resident state additionally reports capacity,
999
1308
  upload ranges and bytes, grows, clears, rebuilds, and no-change frames. The
1000
1309
  `gpuDriven` inspection reports whether stable frames materialized or validated
1001
- GPU-owned rows and whether candidate or batch topology bytes were uploaded.
1310
+ GPU-owned rows and whether candidate or batch topology bytes were uploaded. Its
1311
+ lifetime `residencyValidationScans` and `residencyValidationCacheHits` counters
1312
+ make the CPU residency boundary auditable: an unchanged persistent frame should
1313
+ reuse the producer rows, while mesh, device, pipeline, or catalog generations
1314
+ force a new validation.
1002
1315
  GPU and renderer contract errors arrive through the single `renderer.subscribe`
1003
1316
  event stream (`event.kind === 'error'`); the renderer does not expose a second
1004
1317
  error listener registry.
1005
1318
 
1319
+ ## GPU-driven PBR / shadow / skin navigation
1320
+
1321
+ The shortest public declaration uses the same `Materials.standard` producer as
1322
+ the runtime and imported-skin carriers. `alphaCutoff` is the Alpha Mask
1323
+ contract; `castShadow` publishes the matching ShadowCaster pass.
1324
+
1325
+ ```ts
1326
+ import { Materials } from '@forgeax/engine/render';
1327
+
1328
+ const material = Materials.standard({
1329
+ baseColor: [1, 1, 1, 1],
1330
+ alphaCutoff: 0.5,
1331
+ castShadow: true,
1332
+ });
1333
+ const materialHandle = world.allocSharedRef('MaterialAsset', material);
1334
+ void materialHandle;
1335
+ ```
1336
+
1337
+ | Candidate | Main GPU lane | Shadow GPU lane | Closed result |
1338
+ |:--|:--:|:--:|:--|
1339
+ | Standard opaque or Alpha Mask, finite resources | supported | supported | `gpu` |
1340
+ | Standard skin with producer-authored finite animated bounds | supported | supported | `gpu` |
1341
+ | Skin bounds missing or non-finite | refused | refused | `cpu-deformation` |
1342
+ | Capable resource/pipeline not ready | refused | refused | `blocked` (`resource-not-ready`) |
1343
+ | Blend, transmission, morph, custom, or unsupported topology | refused | refused | `cpu-semantic` |
1344
+ | Clustered local lights or enabled SSAO, including zero local lights | refused | refused | `cpu-semantic` (`unsupported`) |
1345
+
1346
+ The current scene-index shader does not consume the unified Cluster surface
1347
+ bindings. Admission checks active surface requirements each frame; disabling
1348
+ SSAO restores GPU eligibility when no other unsupported input remains. The
1349
+ existing complete CPU submission path still renders on the selected GPU.
1350
+
1351
+ ### Static TextureAsset admission
1352
+
1353
+ Static PBR and Alpha Mask resources are admitted only from registered shared
1354
+ `TextureAsset` and `SamplerAsset` payloads. The resource-class identity is the
1355
+ sorted handle-pair projection used by the prepared material; it is not an
1356
+ array-position guess. Dynamic `RenderTargetTextureSource` and video sources
1357
+ remain their own CPU semantic boundary and are never coerced into a static
1358
+ GPU texture candidate.
1359
+
1360
+ The focused topology fixture allocates and resolves real static shared refs for
1361
+ resource classes `1`, `16`, and `256`, then builds `100,000` candidates for each
1362
+ class. It is structural admission evidence, not a physical-GPU benchmark:
1363
+
1364
+ ```sh
1365
+ FORGEAX_SKIP_HARNESS_SYNC=1 pnpm exec vitest run --project=@forgeax/engine-render \
1366
+ packages/render/src/__tests__/gpu-driven-batch-topology-pbr.unit.test.ts \
1367
+ --no-file-parallelism --reporter=dot
1368
+ ```
1369
+
1370
+ ### Point and Spot shadow authoring → view inspection
1371
+
1372
+ `PointLight` and `SpotLight` shadow views are public light facts, not a hidden
1373
+ GPU slot API. A Spot author declares a companion `Transform`, outgoing
1374
+ `direction`, cone angles in degrees, and the embedded shadow policy:
1375
+
1376
+ ```ts
1377
+ import { SpotLight } from '@forgeax/engine-render';
1378
+ import { Transform } from '@forgeax/engine-scene';
1379
+
1380
+ world.spawn(
1381
+ { component: Transform, data: { pos: [0, 5, 0] } },
1382
+ {
1383
+ component: SpotLight,
1384
+ data: {
1385
+ direction: [0, -1, 0],
1386
+ range: 20,
1387
+ innerConeDeg: 15,
1388
+ outerConeDeg: 35,
1389
+ castShadow: true,
1390
+ mapSize: 1024,
1391
+ pcfKernelSize: 3,
1392
+ },
1393
+ },
1394
+ );
1395
+ ```
1396
+
1397
+ | Author fact | Contract |
1398
+ |:--|:--|
1399
+ | `direction` / `Transform` | Outgoing direction plus the light position; both are required for a useful view |
1400
+ | `range` | Meters; default `10` |
1401
+ | `innerConeDeg` / `outerConeDeg` | Degrees; `0 ≤ inner < outer ≤ 90`, defaults `0` / `45` |
1402
+ | `castShadow` | Defaults `true`; set `false` for the explicit no-shadow case |
1403
+ | `mapSize`, `pcfKernelSize` | Shadow map resolution and odd PCF width; defaults `2048` / `3` |
1404
+ | `cookie` / `projector` | Optional GUID-backed static texture facts; dynamic target/video sources stay outside GPU admission |
1405
+
1406
+ After a successful `draw`, inspect the same renderer projection:
1407
+
1408
+ ```ts
1409
+ const channels = renderer.inspect().renderScene.gpuDriven.channels;
1410
+ const spotViews = channels.filter((channel) => channel.viewPass === 'spot-shadow');
1411
+ const pointFaces = channels.filter((channel) => channel.viewPass === 'point-shadow');
1412
+ void spotViews; // lane, reason, drawCount, and viewIndex are bounded facts.
1413
+ void pointFaces; // point views additionally expose the cube `face`.
1414
+ ```
1415
+
1416
+ The per-view cache is keyed by light identity and view parameters. A cache hit
1417
+ does not dispatch or record a duplicate shadow view. The inspection labels and
1418
+ identity helpers are anchored in
1419
+ [`spot-light.ts`](src/components/spot-light.ts),
1420
+ [`shadow-views.ts`](src/gpu-driven/shadow-views.ts), and
1421
+ [`frame.ts`](src/record/frame.ts); repair the producer named by a structured
1422
+ `failure.detail` before retrying the unchanged frame.
1423
+
1424
+ For offline recovery, read `renderer.inspect().renderScene.gpuDriven.channels`.
1425
+ A blocked channel carries one bounded `failure` projection with the same
1426
+ closed `code`, `detail.owner`, `detail.reason`, and `detail.recovery` that the
1427
+ renderer error stream emits. Repair that owner, recook or republish its
1428
+ producer artifact, and retry the identical frame; `hint` is explanatory text,
1429
+ not a value to parse. A blocked capable candidate is not permission to issue a
1430
+ duplicate CPU draw.
1431
+
1006
1432
  ## GPU-driven view kernel
1007
1433
 
1008
1434
  `BatchTopology` groups eligible rigid draw items by immutable geometry,
@@ -1012,41 +1438,53 @@ non-indexed five-word indirect command. A per-view typed graph records
1012
1438
  `reset -> frustum/compact -> finalize` over persistent candidate and batch
1013
1439
  buffers. The compute path validates generation and active flags, composes the
1014
1440
  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.
1441
+ compact `(meshProjectionRow, materialOrPaletteRow)` stream, sets explicit
1442
+ overflow flags, and emits indirect arguments with `firstInstance = 0`. The low
1443
+ bit of the candidate admission word controls submission; its upper bits carry
1444
+ the batch-local projected mesh row. Rigid rows address the generated material
1445
+ table, while skin rows address the persistent palette through
1446
+ `Instance.customDataStart`.
1447
+
1448
+ The Standard frame activates the production raster lane only when compute,
1449
+ storage buffers, indirect drawing, and producer-owned Standard PBR artifacts
1450
+ are all ready. It covers opaque and Alpha Mask rigid/skin candidates whose
1451
+ geometry, resource class, reflection, palette address, and (for skin) finite
1452
+ producer-authored bounds satisfy the prepared contract. Main and directional,
1453
+ spot, or point shadow views consume the same scene and topology, but keep
1454
+ view-local visibility and projection bindings. Ownership is per draw item and
1455
+ view pass, so one draw can be GPU-owned in the main/shadow channels while an
1456
+ unrelated semantic pass remains CPU-owned.
1457
+
1458
+ Transparent/blend, transmission, morph, custom or unsupported prepared
1459
+ variants, and skin without a producer-authored conservative bound stay on an
1460
+ explicit CPU semantic or CPU deformation lane. WebGL2 selects capability
1461
+ fallback from the same persistent projection; it does not emulate compute.
1462
+ Missing capable-path artifacts and failed resource publication are structured
1463
+ errors that block graph promotion rather than silently drawing the same item a
1464
+ second time through CPU semantics.
1465
+
1466
+ > [!WARNING]
1467
+ > A GPU selector overflow is fail-closed: `finalize` emits zero indirect
1468
+ > instances for the affected batch and inspection reports `overflow=true`.
1469
+ > The last-known-good resource generation remains authoritative until the
1470
+ > producer can rebuild with sufficient capacity. Missing skin bounds are also
1471
+ > intentionally conservative: they remain CPU deformation work; bind-pose
1472
+ > bounds are never invented as animation bounds.
1473
+
1474
+ The bounded `gpuDriven.channels` inspection reports the selected lane and
1475
+ closed reason for each view pass. Blocked promotion additionally exposes the
1476
+ producer-owned `failure` (`code`, `expected`, `hint`, and typed `detail`) so an
1477
+ AI caller can branch on owner/recovery without decoding an error string. The
1478
+ same `GpuDrivenPreparationError` is delivered through the renderer error
1479
+ stream. The inspection also reports topology revision, candidates,
1480
+ batches, visible capacity, buffer capacities, update count, upload bytes,
1481
+ rebuild count, overflow, retry count, and last-known-good generation. RhiNull
1482
+ verifies graph dependency order and stable-frame zero work; local Dawn verifies
1483
+ the command, binding, palette, shadow-view, and readback contracts. The
1484
+ renderer-level integration also proves that the built-in Standard graph
1485
+ contains the compute chain before `main`, with no GPU plus CPU duplicate draws.
1486
+ The renderer retains one graph, one compile owner, one encoder, and one submit
1487
+ route.
1050
1488
 
1051
1489
  > [!NOTE]
1052
1490
  > Non-rigid multi-World composition, skinned lanes, visibility/hierarchy
@@ -1056,6 +1494,28 @@ encoder, and one submit route.
1056
1494
  > semantics; later coverage can make those changes entity-local without adding
1057
1495
  > another scene authority.
1058
1496
 
1497
+ ### Offline inspect → repair owner → retry
1498
+
1499
+ Use the detached channel to identify the next owner without opening a browser
1500
+ or reading live GPU objects:
1501
+
1502
+ ```mermaid
1503
+ flowchart LR
1504
+ A["inspect gpuDriven.channels"] --> B{"lane / reason"}
1505
+ B -->|"gpu / none"| C["record the same graph"]
1506
+ B -->|"blocked / resource-not-ready"| D["repair failure.detail.owner"]
1507
+ D --> E["recook or publish the receipt"]
1508
+ E --> F["retry the identical frame request"]
1509
+ ```
1510
+
1511
+ The three public navigation entries are [`shader`](../shader/README.md#gpu-driven-pbr--shadow--skin-navigation),
1512
+ [`runtime`](../runtime/README.md#gpu-driven-pbr--shadow--skin-navigation), and
1513
+ this render owner. The two executable carriers are
1514
+ [`hello-skin`](../../apps/hello/skin/README.md#gpu-driven-skin-carrier) and
1515
+ [`hello-fbx-skin`](../../apps/hello/fbx-skin/README.md#gpu-driven-fbx-carrier).
1516
+ Their Dawn smoke commands remain independent evidence; RhiNull proves graph
1517
+ ownership and deterministic lane facts only.
1518
+
1059
1519
  Features are supplied through the construction options (`features: [...]`) and
1060
1520
  enter the same host-owned extract → plan → graph projection path. The public
1061
1521
  `Renderer` intentionally has no install/uninstall methods: optional capability
@@ -1163,6 +1623,34 @@ targets, and draw/dispatch commands. Graph buffer and texture access is derived
1163
1623
  from those roles; producers never author a second `reads`/`writes` ledger and
1164
1624
  never receive an encoder or submit authority.
1165
1625
 
1626
+ Prepared material-input layouts accept `particleInputLanes` from 1 through 4
1627
+ (omission selects the canonical one-lane layout). The count is valid only for a
1628
+ material-input vertex layout. Render derives its instance stride and attributes
1629
+ from the base layout and uses the same complete multi-stream descriptor in the
1630
+ PipelineSpec cache identity. Invalid counts fail during resource preparation.
1631
+
1632
+ A prepared pipeline with empty `colorFormats` uses the depth-only route and
1633
+ preserves its declared `depthFormat`, depth state and complete vertex streams.
1634
+ A vertex-only shader needs no fragment entry. For a vertex-index-generated draw,
1635
+ declare `vertexLayout: 'none'` on the draw and supply the prepared empty group
1636
+ when the shader has no resources. This is depth-target rendering, not automatic
1637
+ registration as a Standard light-view shadow caster.
1638
+
1639
+ For light-view participation, declare a `shadow-caster` plan pass with the same
1640
+ draw vocabulary and a depth32float, zero-color graphics program. Standard owns
1641
+ the light-view binding, atlas, viewport and pass replication; the feature owns
1642
+ only geometry and instance inputs. It reuses the normal prepared draw encoder
1643
+ and the same graph buffer imports. Active GPU casters disable static directional
1644
+ atlas reuse because ECS mesh epochs do not describe GPU buffer contents.
1645
+
1646
+ The graph projects a feature's leading compute prefix before shadow views until
1647
+ the first scene-target dependency or ordinary raster. Remaining work stays at
1648
+ the normal scene contribution boundary. A caster whose projection is late reads
1649
+ the prior submitted instance data (initially empty); it must not be described as
1650
+ current-frame simulation. Owner-local pipeline implementations use
1651
+ `contributeShadowFeatures` with `addTypedShadowPasses`; graph construction and
1652
+ these helpers are not added to the public Render root.
1653
+
1166
1654
  ### Five render terms
1167
1655
 
1168
1656
  | Term | Meaning | Owner |
@@ -1355,6 +1843,236 @@ sibling-loop report or an authored manifest. Read identity, generation, evidence
1355
1843
  level, and closed failure fields before following the named owner recovery action.
1356
1844
  Submit the same consumer request and reinspect after recovery. The route returns
1357
1845
  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.
1846
+ Missing or mismatched receipts remain `fallback-only` with zero SSR work. The
1847
+ current renderer may expose admitted structural Hi-Z, trace, temporal, compose,
1848
+ and history facts, but no single backend snapshot or non-black canvas is an SSR
1849
+ v1 acceptance result; use paired Browser/Dawn readback and the closed-loop gates
1850
+ before promoting that status.
1851
+
1852
+ The canonical paired carrier is `apps/hello/ssr`. It runs the same fixture at
1853
+ `http://127.0.0.1:4173/?forgeax-evidence=ssr` and
1854
+ `dawn://hello/ssr?forgeax-evidence=ssr`, records 300-frame identity-bound
1855
+ readbacks, and keeps visual rows in the form `observed` / `verdict` /
1856
+ `confidence`. Its performance lane derives the 1920x1080 descriptor and checks
1857
+ it against `estimateSsrSpatialMemory`; timestamp or paired-lane absence remains
1858
+ blocked.
1859
+
1860
+ ## Standard dynamic MeshAsset candidates
1861
+
1862
+ The Renderer owns one bounded candidate lifecycle for geometry produced by an
1863
+ external consumer. The payload is the existing `MeshAsset` contract, not a
1864
+ voxel-specific asset kind. The consumer remains the World shared-reference
1865
+ owner and publishes `MeshFilter` / `MeshRenderer` through the ordinary ECS
1866
+ write barrier; the Renderer owns GPU residency, device generation, frame
1867
+ receipt, and delayed retirement.
1868
+
1869
+ ```ts
1870
+ import type { MeshAsset } from '@forgeax/engine-types';
1871
+ import { FixedTime, type World } from '@forgeax/engine-ecs';
1872
+ import { Transform } from '@forgeax/engine-scene';
1873
+ import { MeshFilter, MeshRenderer } from '@forgeax/engine-render';
1874
+ import type { FrameCamera, FrameEnvironment, Renderer } from '@forgeax/engine-render';
1875
+
1876
+ declare const renderer: Renderer;
1877
+ declare const world: World;
1878
+ declare const mesh: MeshAsset;
1879
+ declare const camera: FrameCamera;
1880
+ declare const environment: FrameEnvironment;
1881
+
1882
+ // Attach first: the Renderer owns the lease/generation boundary, while the
1883
+ // World remains the MeshAsset handle and ECS scene owner.
1884
+ const attached = renderer.attach(world);
1885
+ if (!attached.ok) throw attached.error;
1886
+ const meshHandle = world.allocSharedRef('MeshAsset', mesh);
1887
+ const entity = world
1888
+ .spawn(
1889
+ { component: Transform, data: {} },
1890
+ { component: MeshFilter, data: { assetHandle: meshHandle } },
1891
+ { component: MeshRenderer, data: { materials: [] } },
1892
+ )
1893
+ .unwrap();
1894
+ const fixedStep = world.getResource(FixedTime).tick;
1895
+ const prepared = renderer.prepareDynamicGeometry({
1896
+ world,
1897
+ entity,
1898
+ mesh,
1899
+ meshHandle,
1900
+ // Must match a live material handle/source slot; this mesh uses its default.
1901
+ materialIdentity: 'default',
1902
+ revision: 4,
1903
+ topologyRevision: 2,
1904
+ // For paired physics use the admission example below, not this draw-only path.
1905
+ });
1906
+ if (!prepared.ok) {
1907
+ // Branch on prepared.error.code; the previous visible mesh is retained.
1908
+ } else {
1909
+ // ECS FixedTime is the ordering proof for this render-only revision.
1910
+ const accepted = renderer.acceptDynamicGeometry(prepared.value, {
1911
+ world,
1912
+ fixedStep,
1913
+ });
1914
+ if (accepted.ok) {
1915
+ const frame = renderer.draw({
1916
+ leases: [attached.value],
1917
+ camera,
1918
+ environment,
1919
+ fixedStep,
1920
+ });
1921
+ if (frame.ok) {
1922
+ const geometryReceipt = renderer.dynamicGeometryReceipt(accepted.value);
1923
+ // geometryReceipt.frame.frameId === frame.value.frameId for this
1924
+ // generation; geometryReceipt.frame.completed fences GPU retirement.
1925
+ void geometryReceipt;
1926
+ }
1927
+ }
1928
+ }
1929
+ ```
1930
+
1931
+ The entity must already carry `Transform`, a live `MeshFilter`, and a
1932
+ `MeshRenderer` when the candidate is prepared and accepted; these are the same
1933
+ ECS prerequisites used by the render extraction query. Preparation records the
1934
+ old `MeshFilter.assetHandle` and stages the new standard handle without changing
1935
+ the visible ECS binding. Acceptance is the one ECS write-barrier that swaps in
1936
+ the candidate. If acceptance or GPU preparation fails, the old binding remains;
1937
+ cancelling an accepted candidate swaps that old handle back, and refuses to
1938
+ overwrite a newer external binding. The Renderer host reads
1939
+ `World.FixedTime.tick` itself; a caller-supplied `{ world, fixedStep }` object
1940
+ cannot bind an unrelated World or step. Preparation may precede acceptance by
1941
+ multiple fixed steps; acceptance records the actual current tick. If
1942
+ `physicsEntity` is supplied, acceptance requires that entity's active paired
1943
+ `PhysicsWorld` admission to match the revision and current step. An already
1944
+ published physics result is deliberately insufficient: it cannot roll back if
1945
+ geometry now fails. A
1946
+ candidate that is not present in the ECS render snapshot at the successful draw
1947
+ is left accepted without a receipt; the receipt is issued only after the
1948
+ submitted record stage consumed the live binding.
1949
+
1950
+ ### Paired physics admission
1951
+
1952
+ Prepare both domain candidates invisibly, with matching revision and the geometry
1953
+ candidate's `physicsEntity` naming the physical parent. Queue the physical candidate
1954
+ with one synchronous geometry commit. The existing Physics fixed-step owner runs it
1955
+ after native staging and before step/writeback/publication; no second clock or queue
1956
+ is created. A refused geometry commit restores the old native state. Physics queries,
1957
+ mutations and Renderer draw are refused during this borrowed admission interval.
1958
+
1959
+ ```ts
1960
+ import { err, ok } from '@forgeax/engine-types';
1961
+ import { FixedTime } from '@forgeax/engine-ecs';
1962
+
1963
+ physics.admitDerivedShapeCandidate(physicsCandidate, () => {
1964
+ const admitted = renderer.acceptDynamicGeometry(geometryCandidate, {
1965
+ world,
1966
+ fixedStep: world.getResource(FixedTime).tick,
1967
+ });
1968
+ if (!admitted.ok) return err(admitted.error);
1969
+ acceptedGeometry = admitted.value;
1970
+ return ok(undefined);
1971
+ }).unwrap();
1972
+ // Normal World FixedUpdate performs admission, physics step and writeback.
1973
+ // After World.update succeeds, inspect Physics publication/failure and draw.
1974
+ ```
1975
+
1976
+ The commit must return immediately after its complete binding change, without
1977
+ additional fallible work. On physical preparation/native failure it is not called;
1978
+ cancel the still-prepared geometry using its normal owner. On a refused commit,
1979
+ inspect `getDerivedFailure` and cancel that prepared geometry. Success publication
1980
+ remains after physics writeback and the real Renderer submission, not callback return.
1981
+
1982
+ `meshHandle` is mandatory on the public Renderer path. The pure
1983
+ `createDynamicGeometryLifecycle` helper may omit it for CPU-only validation, in
1984
+ which case `gpuReady` is false and no Renderer receipt can be produced. The
1985
+ attached Renderer uses the existing `GpuResidencyCache.ensureResident` path
1986
+ before acceptance; its failure is `dynamic-geometry-gpu-failed`, while a
1987
+ missing handle is the distinct `dynamic-geometry-gpu-not-ready` error. The
1988
+ handle is never minted by the Renderer and no RHI buffer or queue escapes the
1989
+ host. A supplied `materialIdentity` must match a live MeshRenderer slot or the
1990
+ MeshAsset material-slot source key; otherwise the host returns structured
1991
+ `dynamic-geometry-invalid`. The normal draw path consumes the accepted standard
1992
+ handle from `MeshFilter`, so main, depth, shadow, and motion/history share one
1993
+ committed geometry identity. The lifecycle also bounds aggregate typed-geometry
1994
+ bytes (`inspect().meshBytes` / `maxMeshBytes`) in addition to candidate count.
1995
+
1996
+ ```mermaid
1997
+ stateDiagram-v2
1998
+ [*] --> prepared: prepare
1999
+ prepared --> accepted: accept
2000
+ prepared --> cancelled: cancel / generation loss
2001
+ accepted --> published: successful draw FrameReceipt
2002
+ published --> retired: receipt-safe retirement
2003
+ accepted --> cancelled: detach / World teardown
2004
+ ```
2005
+
2006
+ `DynamicGeometryCandidate` is a short-lived credential bound to the attached
2007
+ World object, live ECS entity, lifecycle owner identity, candidate revision,
2008
+ copied MeshAsset, material identity, and active device generation. A Renderer
2009
+ acceptance records the attached World's `FixedTime` ordering and performs the
2010
+ binding swap; a candidate with `physicsEntity` also records that entity's
2011
+ PhysicsWorld admission step. A frame publishes only candidates for the
2012
+ drawn lease Worlds whose candidate step is no newer than the actual World
2013
+ `FixedTime.tick`, and whose PhysicsWorld publication has the exact candidate
2014
+ revision. The optional frame `fixedStep` is checked against that World tick; it
2015
+ is an assertion, not a second clock. Thus a multi-fixed-step host frame can
2016
+ issue one receipt for the latest submitted frame without dropping an accepted
2017
+ candidate, while a successful draw for another World cannot publish an
2018
+ arbitrary credential. The public attached-Renderer receipt carries
2019
+ `recordStageConsumed: true` and the embedded `frame.completed` fence, making it
2020
+ a real record/submit proof, not a non-empty handle or counter. The detached
2021
+ `createDynamicGeometryLifecycle` helper may intentionally publish a CPU-only
2022
+ receipt with `recordStageConsumed: false`; that helper is not a Renderer draw
2023
+ path and must not be used as GPU evidence.
2024
+
2025
+ `dynamicGeometryReceipt(candidate)` is present only after the accepted candidate
2026
+ is published by a successful `FrameReceipt`; it carries the same `frameId`,
2027
+ `deviceGeneration`, preparation fixed step, and actual publication fixed step.
2028
+ A topology revision invalidates lower-revision prepared/accepted candidates for the same entity and
2029
+ marks older published history invalid, but retains each published receipt's
2030
+ completion fence; an already-published credential remains until an explicit
2031
+ retire can release its GPU owner after that fence resolves. Each later
2032
+ record-stage consumption emits a refreshed receipt and adds its
2033
+ `FrameReceipt.frame.completed` fence, so retirement waits for every observed
2034
+ in-flight frame (resolved or rejected), not only the latest receipt. The
2035
+ attached host revalidates the live `(World, entity, MeshFilter)` binding for
2036
+ each candidate, so a topology change on entity B does not suppress a real
2037
+ record-stage use of still-live entity A; only the candidate/entity whose
2038
+ binding changed is refused.
2039
+ The shared `GpuResidencyCache` owns allocation leases and submission fences.
2040
+ A lease captures the concrete GPU allocation, not a reusable World handle slot;
2041
+ cancelling B cannot invalidate a resource still used by A. Every submitted
2042
+ frame conservatively fences all resident meshes, including ordinary, shadow-only
2043
+ and cached GPU-driven uses, independently of publication or history validity.
2044
+ In-place mesh invalidation immediately allows a new upload while old submitted
2045
+ buffers retire behind their own completion. An accepted topology change resets
2046
+ the existing temporal history owner before the next draw. The candidate's World
2047
+ reference is also retained through completion so its handle slot cannot be reused.
2048
+ The detached inspection reports both history invalidation and concrete
2049
+ candidate invalidation. Stale revisions, cross-World/lifecycle candidates, old
2050
+ generations, invalid GPU readiness, invalid ordering, duplicate acceptance,
2051
+ unknown retirement, and bounded in-flight count/byte overflow are structured
2052
+ `DynamicGeometryError` results. Cancelling or invalidating work leaves the last
2053
+ published candidate untouched, cleans candidate-only residency when no other
2054
+ ECS entity or candidate shares the handle, and never releases a caller-owned
2055
+ shared reference. Call `retireDynamicGeometry` only after the receipt-bound
2056
+ consumer no longer needs the old geometry, then release the World shared
2057
+ reference through its normal owner. Retirement is a logical state transition
2058
+ first: the attached host keeps the candidate count and typed mesh bytes in the
2059
+ bounded budget until all receipt fences resolve and the candidate's shared GPU
2060
+ lease is released. If the candidate is still the live `MeshFilter`, retirement
2061
+ returns `dynamic-geometry-invalid` without deleting its receipt or GPU owner;
2062
+ swap the binding through ECS first so the completion fence can safely retire it.
2063
+ The pure lifecycle exposes `finalizeRetirement` for its owner to release that
2064
+ deferred count/byte reservation after cleanup.
2065
+
2066
+ This seam intentionally does not add a second asset registry, Pack kind,
2067
+ RenderFeature, worker, or frame clock. Asset ownership is SSOT: the producer
2068
+ owns sourceKey/GUID and Cooked Pack payloads, Catalog is only the projection,
2069
+ the runtime `AssetRegistry` resolves GUIDs, and the World owns the shared
2070
+ `MeshAsset` handle. `world.allocSharedRef` is the only handle creation in this
2071
+ example; Renderer never registers or mints an asset. Provider/Catalog/GUID cold
2072
+ loading therefore continues through the existing route; a producer that needs
2073
+ a custom voxel artifact owns that schema and loader outside the Engine, while
2074
+ its projected standard mesh enters this candidate path.
2075
+
2076
+ ### Targeted spatial inspection
2077
+
2078
+ `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.