@forgeax/engine-render 0.1.19 → 0.1.21

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 (452) hide show
  1. package/README.md +215 -0
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/assembly/device-feature-admission.d.ts +18 -0
  4. package/dist/assembly/device-feature-admission.d.ts.map +1 -0
  5. package/dist/assembly/directional-shadow-inspection.d.ts +38 -0
  6. package/dist/assembly/directional-shadow-inspection.d.ts.map +1 -0
  7. package/dist/assembly/factory.d.ts +34 -20
  8. package/dist/assembly/factory.d.ts.map +1 -1
  9. package/dist/assembly/host-contract.d.ts +9 -0
  10. package/dist/assembly/host-contract.d.ts.map +1 -1
  11. package/dist/assembly/render-target-host.d.ts +46 -0
  12. package/dist/assembly/render-target-host.d.ts.map +1 -0
  13. package/dist/assets/render-asset-port.d.ts.map +1 -1
  14. package/dist/authoring.mjs +3 -0
  15. package/dist/authoring.mjs.map +1 -1
  16. package/dist/capture/cube-views.d.ts +21 -0
  17. package/dist/capture/cube-views.d.ts.map +1 -0
  18. package/dist/capture/scheduler.d.ts +54 -0
  19. package/dist/capture/scheduler.d.ts.map +1 -0
  20. package/dist/{chunk-RJRUS55L.mjs → chunk-5YADUCA7.mjs} +1896 -1624
  21. package/dist/chunk-5YADUCA7.mjs.map +1 -0
  22. package/dist/{chunk-LOSO4CTV.mjs → chunk-6OPXDQ2E.mjs} +423 -46
  23. package/dist/chunk-6OPXDQ2E.mjs.map +1 -0
  24. package/dist/{chunk-IPXCABEP.mjs → chunk-BXBQQKFQ.mjs} +3 -9
  25. package/dist/chunk-BXBQQKFQ.mjs.map +1 -0
  26. package/dist/chunk-FS2KNUQH.mjs +1294 -0
  27. package/dist/chunk-FS2KNUQH.mjs.map +1 -0
  28. package/dist/components/camera.d.ts +1 -0
  29. package/dist/components/camera.d.ts.map +1 -1
  30. package/dist/components/cube-camera.d.ts +20 -0
  31. package/dist/components/cube-camera.d.ts.map +1 -0
  32. package/dist/components/directional-light.d.ts +11 -2
  33. package/dist/components/directional-light.d.ts.map +1 -1
  34. package/dist/components/directional-shadow-filter.d.ts +25 -0
  35. package/dist/components/directional-shadow-filter.d.ts.map +1 -0
  36. package/dist/components/index.d.ts +2 -0
  37. package/dist/components/index.d.ts.map +1 -1
  38. package/dist/components/light-helpers.d.ts.map +1 -1
  39. package/dist/components/reflection-probe.d.ts +17 -0
  40. package/dist/components/reflection-probe.d.ts.map +1 -0
  41. package/dist/components/spot-light.d.ts +12 -0
  42. package/dist/components/spot-light.d.ts.map +1 -1
  43. package/dist/construct-renderer.mjs +8552 -1612
  44. package/dist/construct-renderer.mjs.map +1 -1
  45. package/dist/device/gpu-residency.d.ts +19 -2
  46. package/dist/device/gpu-residency.d.ts.map +1 -1
  47. package/dist/environment/frame.d.ts.map +1 -1
  48. package/dist/environment/lifecycle.d.ts +5 -0
  49. package/dist/environment/lifecycle.d.ts.map +1 -1
  50. package/dist/errors/render.d.ts +209 -93
  51. package/dist/errors/render.d.ts.map +1 -1
  52. package/dist/extract/directional-shadow-projection.d.ts +23 -0
  53. package/dist/extract/directional-shadow-projection.d.ts.map +1 -0
  54. package/dist/extract/environment.d.ts +14 -0
  55. package/dist/extract/environment.d.ts.map +1 -1
  56. package/dist/extract/visibility.d.ts.map +1 -1
  57. package/dist/features/fullscreen.d.ts.map +1 -1
  58. package/dist/features/targets.d.ts.map +1 -1
  59. package/dist/features/types.d.ts +10 -0
  60. package/dist/features/types.d.ts.map +1 -1
  61. package/dist/fullscreen-post-process-pass.d.ts +13 -2
  62. package/dist/fullscreen-post-process-pass.d.ts.map +1 -1
  63. package/dist/gpu-driven/batch-topology.d.ts.map +1 -1
  64. package/dist/gpu-driven/production-raster.d.ts +0 -1
  65. package/dist/gpu-driven/production-raster.d.ts.map +1 -1
  66. package/dist/gpu-scene.d.ts.map +1 -1
  67. package/dist/gpu-usage.d.ts +2 -0
  68. package/dist/gpu-usage.d.ts.map +1 -1
  69. package/dist/hdrp-buffers.d.ts +19 -17
  70. package/dist/hdrp-buffers.d.ts.map +1 -1
  71. package/dist/ibl/IblPipelineCache.d.ts +4 -0
  72. package/dist/ibl/IblPipelineCache.d.ts.map +1 -1
  73. package/dist/ibl/kernel-cache.d.ts +36 -0
  74. package/dist/ibl/kernel-cache.d.ts.map +1 -0
  75. package/dist/index.d.ts +35 -4
  76. package/dist/index.d.ts.map +1 -1
  77. package/dist/index.mjs +4 -4
  78. package/dist/index.mjs.map +1 -1
  79. package/dist/inspection-types.d.ts +51 -0
  80. package/dist/inspection-types.d.ts.map +1 -1
  81. package/dist/instances-derived-bounds.d.ts +50 -0
  82. package/dist/instances-derived-bounds.d.ts.map +1 -0
  83. package/dist/internal.mjs +1 -1
  84. package/dist/light-buffer-layout.d.ts +30 -13
  85. package/dist/light-buffer-layout.d.ts.map +1 -1
  86. package/dist/mesh-material-bindings.d.ts +54 -0
  87. package/dist/mesh-material-bindings.d.ts.map +1 -1
  88. package/dist/pbr-pipeline.d.ts +20 -7
  89. package/dist/pbr-pipeline.d.ts.map +1 -1
  90. package/dist/pipeline/standard-forward-lane.d.ts +2 -1
  91. package/dist/pipeline/standard-forward-lane.d.ts.map +1 -1
  92. package/dist/pipeline/standard-lighting/errors.d.ts +4 -0
  93. package/dist/pipeline/standard-lighting/errors.d.ts.map +1 -0
  94. package/dist/pipeline/standard-lighting/graph.d.ts +24 -0
  95. package/dist/pipeline/standard-lighting/graph.d.ts.map +1 -0
  96. package/dist/pipeline/standard-lighting/inspection.d.ts +38 -0
  97. package/dist/pipeline/standard-lighting/inspection.d.ts.map +1 -0
  98. package/dist/pipeline/standard-lighting/layout.d.ts +28 -0
  99. package/dist/pipeline/standard-lighting/layout.d.ts.map +1 -0
  100. package/dist/pipeline/standard-lighting/light-frame.d.ts +33 -0
  101. package/dist/pipeline/standard-lighting/light-frame.d.ts.map +1 -0
  102. package/dist/pipeline/standard-lighting/membership.d.ts +14 -0
  103. package/dist/pipeline/standard-lighting/membership.d.ts.map +1 -0
  104. package/dist/pipeline/standard-lighting/prepare.d.ts +16 -0
  105. package/dist/pipeline/standard-lighting/prepare.d.ts.map +1 -0
  106. package/dist/pipeline/standard-lighting/topology.d.ts +45 -0
  107. package/dist/pipeline/standard-lighting/topology.d.ts.map +1 -0
  108. package/dist/pipeline/standard-lighting/transport.d.ts +36 -0
  109. package/dist/pipeline/standard-lighting/transport.d.ts.map +1 -0
  110. package/dist/pipeline/standard-pipeline.d.ts +4 -13
  111. package/dist/pipeline/standard-pipeline.d.ts.map +1 -1
  112. package/dist/pipeline/standard-post.d.ts.map +1 -1
  113. package/dist/pipeline/standard-profile.d.ts +12 -20
  114. package/dist/pipeline/standard-profile.d.ts.map +1 -1
  115. package/dist/pipeline-spec.d.ts +40 -7
  116. package/dist/pipeline-spec.d.ts.map +1 -1
  117. package/dist/point-shadow-inspection.d.ts +21 -0
  118. package/dist/point-shadow-inspection.d.ts.map +1 -0
  119. package/dist/record/frame-lighting.d.ts +29 -32
  120. package/dist/record/frame-lighting.d.ts.map +1 -1
  121. package/dist/record/frame-snapshot.d.ts +65 -15
  122. package/dist/record/frame-snapshot.d.ts.map +1 -1
  123. package/dist/record/frame-targets.d.ts +5 -0
  124. package/dist/record/frame-targets.d.ts.map +1 -1
  125. package/dist/record/frame.d.ts +12 -5
  126. package/dist/record/frame.d.ts.map +1 -1
  127. package/dist/record/gpu-pass-timing/contract.d.ts +96 -0
  128. package/dist/record/gpu-pass-timing/contract.d.ts.map +1 -0
  129. package/dist/record/gpu-pass-timing/errors.d.ts +18 -0
  130. package/dist/record/gpu-pass-timing/errors.d.ts.map +1 -0
  131. package/dist/record/gpu-pass-timing/index.d.ts +8 -0
  132. package/dist/record/gpu-pass-timing/index.d.ts.map +1 -0
  133. package/dist/record/gpu-pass-timing/parser.d.ts +11 -0
  134. package/dist/record/gpu-pass-timing/parser.d.ts.map +1 -0
  135. package/dist/record/gpu-pass-timing/session.d.ts +43 -0
  136. package/dist/record/gpu-pass-timing/session.d.ts.map +1 -0
  137. package/dist/record/gpu-timing.d.ts +37 -0
  138. package/dist/record/gpu-timing.d.ts.map +1 -0
  139. package/dist/record/main-pass-geometry.d.ts.map +1 -1
  140. package/dist/record/main-pass-material.d.ts +23 -2
  141. package/dist/record/main-pass-material.d.ts.map +1 -1
  142. package/dist/record/main-pass-sprite-draws.d.ts.map +1 -1
  143. package/dist/record/main-pass.d.ts +2 -0
  144. package/dist/record/main-pass.d.ts.map +1 -1
  145. package/dist/record/render-context.d.ts +48 -3
  146. package/dist/record/render-context.d.ts.map +1 -1
  147. package/dist/record/shadow-pass.d.ts.map +1 -1
  148. package/dist/record/skybox-post-pass.d.ts.map +1 -1
  149. package/dist/record/typed-frame-graph.d.ts +81 -5
  150. package/dist/record/typed-frame-graph.d.ts.map +1 -1
  151. package/dist/record/view-ubo.d.ts +7 -4
  152. package/dist/record/view-ubo.d.ts.map +1 -1
  153. package/dist/reflection/filter.d.ts +22 -0
  154. package/dist/reflection/filter.d.ts.map +1 -0
  155. package/dist/reflection/gpu-table.d.ts +25 -0
  156. package/dist/reflection/gpu-table.d.ts.map +1 -0
  157. package/dist/reflection/projection.d.ts +91 -0
  158. package/dist/reflection/projection.d.ts.map +1 -0
  159. package/dist/render-contract.d.ts +114 -7
  160. package/dist/render-contract.d.ts.map +1 -1
  161. package/dist/render-data.d.ts +10 -6
  162. package/dist/render-data.d.ts.map +1 -1
  163. package/dist/render-graph-primitives.d.ts +1 -0
  164. package/dist/render-graph-primitives.d.ts.map +1 -1
  165. package/dist/render-pipeline.d.ts +78 -2
  166. package/dist/render-pipeline.d.ts.map +1 -1
  167. package/dist/render-system-extract.d.ts +113 -20
  168. package/dist/render-system-extract.d.ts.map +1 -1
  169. package/dist/render-system.d.ts +24 -3
  170. package/dist/render-system.d.ts.map +1 -1
  171. package/dist/renderer-inspect.d.ts +29 -0
  172. package/dist/renderer-inspect.d.ts.map +1 -0
  173. package/dist/renderer.d.ts +43 -0
  174. package/dist/renderer.d.ts.map +1 -0
  175. package/dist/scene/render-scene.d.ts +14 -0
  176. package/dist/scene/render-scene.d.ts.map +1 -1
  177. package/dist/shadow-atlas.d.ts +3 -3
  178. package/dist/shadow-atlas.d.ts.map +1 -1
  179. package/dist/ssao-buffers.d.ts +3 -0
  180. package/dist/ssao-buffers.d.ts.map +1 -1
  181. package/dist/targets/contracts.d.ts +66 -0
  182. package/dist/targets/contracts.d.ts.map +1 -0
  183. package/dist/targets/graph-projection.d.ts +29 -0
  184. package/dist/targets/graph-projection.d.ts.map +1 -0
  185. package/dist/targets/material-source.d.ts +34 -0
  186. package/dist/targets/material-source.d.ts.map +1 -0
  187. package/dist/targets/owner.d.ts +32 -0
  188. package/dist/targets/owner.d.ts.map +1 -0
  189. package/dist/targets/physical.d.ts +18 -0
  190. package/dist/targets/physical.d.ts.map +1 -0
  191. package/dist/targets/readback.d.ts +39 -0
  192. package/dist/targets/readback.d.ts.map +1 -0
  193. package/dist/temporal/frame.d.ts +44 -0
  194. package/dist/temporal/frame.d.ts.map +1 -0
  195. package/dist/temporal/index.mjs +3 -3
  196. package/dist/typed-render-graph-primitives.d.ts +6 -1
  197. package/dist/typed-render-graph-primitives.d.ts.map +1 -1
  198. package/dist/typed-shadow-atlas.d.ts +10 -0
  199. package/dist/typed-shadow-atlas.d.ts.map +1 -0
  200. package/dist/typed-shadow-passes.d.ts +1 -1
  201. package/dist/typed-shadow-passes.d.ts.map +1 -1
  202. package/dist/volume/capability.d.ts +57 -0
  203. package/dist/volume/capability.d.ts.map +1 -0
  204. package/dist/volume/component.d.ts +61 -0
  205. package/dist/volume/component.d.ts.map +1 -0
  206. package/dist/volume/extract.d.ts +11 -0
  207. package/dist/volume/extract.d.ts.map +1 -0
  208. package/dist/volume/inspection.d.ts +38 -0
  209. package/dist/volume/inspection.d.ts.map +1 -0
  210. package/dist/volume/passes.d.ts +38 -0
  211. package/dist/volume/passes.d.ts.map +1 -0
  212. package/dist/volume/recovery.d.ts +38 -0
  213. package/dist/volume/recovery.d.ts.map +1 -0
  214. package/dist/volume/resources.d.ts +57 -0
  215. package/dist/volume/resources.d.ts.map +1 -0
  216. package/dist/volume/temporal.d.ts +35 -0
  217. package/dist/volume/temporal.d.ts.map +1 -0
  218. package/package.json +20 -20
  219. package/src/__tests__/bloom-exact-zero.unit.test.ts +48 -19
  220. package/src/__tests__/bloom-lifecycle.integration.test.ts +41 -13
  221. package/src/__tests__/camera-target-consumption.unit.test.ts +53 -0
  222. package/src/__tests__/cluster-binner.unit.test.ts +35 -0
  223. package/src/__tests__/cluster-grid-default-surface.unit.test.ts +7 -2
  224. package/src/__tests__/cube-camera-scheduler.unit.test.ts +70 -0
  225. package/src/__tests__/cube-camera-single-submit.perf.test.ts +75 -0
  226. package/src/__tests__/cube-camera-view-matrices.unit.test.ts +41 -0
  227. package/src/__tests__/cube-camera-vocabulary.unit.test.ts +47 -0
  228. package/src/__tests__/cube-camera.integration.test.ts +42 -0
  229. package/src/__tests__/device-feature-admission.unit.test.ts +73 -0
  230. package/src/__tests__/direct-light-snapshot.unit.test.ts +3 -3
  231. package/src/__tests__/docs-gate-green.unit.test.ts +38 -0
  232. package/src/__tests__/factory-contract.integration.test.ts +255 -29
  233. package/src/__tests__/fog-component.unit.test.ts +67 -0
  234. package/src/__tests__/fog.integration.test.ts +86 -0
  235. package/src/__tests__/fullscreen-feature-plan.unit.test.ts +37 -0
  236. package/src/__tests__/gpu-driven-indirect-raster-evidence.ts +10 -9
  237. package/src/__tests__/gpu-driven-indirect-raster.dawn.test.ts +17 -1
  238. package/src/__tests__/gpu-driven-production.integration.test.ts +16 -17
  239. package/src/__tests__/gpu-driven-view-graph.integration.test.ts +12 -4
  240. package/src/__tests__/gpu-pass-timing-browser-runner.ts +281 -0
  241. package/src/__tests__/gpu-pass-timing-coexistence.integration.test.ts +80 -0
  242. package/src/__tests__/gpu-pass-timing-contract.unit.test.ts +243 -0
  243. package/src/__tests__/gpu-pass-timing-exact-zero.unit.test.ts +131 -0
  244. package/src/__tests__/gpu-pass-timing-lifecycle.integration.test.ts +230 -0
  245. package/src/__tests__/gpu-pass-timing-observation.unit.test.ts +233 -0
  246. package/src/__tests__/gpu-pass-timing-session.unit.test.ts +438 -0
  247. package/src/__tests__/gpu-pass-timing.browser.test.ts +26 -0
  248. package/src/__tests__/gpu-pass-timing.rhi-null.unit.test.ts +125 -0
  249. package/src/__tests__/hdrp-group2-visibility.unit.test.ts +24 -0
  250. package/src/__tests__/instances-culling.integration.test.ts +94 -0
  251. package/src/__tests__/instances-derived-bounds.unit.test.ts +118 -0
  252. package/src/__tests__/ktx2-basis-gpu-consumer.dawn.test.ts +14 -14
  253. package/src/__tests__/material-observation.integration.test.ts +101 -0
  254. package/src/__tests__/mesh-buffer-usage-surface.unit.test.ts +1 -1
  255. package/src/__tests__/pbr-view-layout.unit.test.ts +16 -0
  256. package/src/__tests__/point-shadow-inspection.unit.test.ts +60 -0
  257. package/src/__tests__/public-surface.test-d.ts +10 -0
  258. package/src/__tests__/reflection-probe-authoring.unit.test.ts +71 -0
  259. package/src/__tests__/reflection-probe-filter.unit.test.ts +54 -0
  260. package/src/__tests__/reflection-probe-ibl-topology.unit.test.ts +45 -0
  261. package/src/__tests__/reflection-probe-pass-encode.integration.test.ts +101 -0
  262. package/src/__tests__/reflection-probe-performance.perf.test.ts +60 -0
  263. package/src/__tests__/reflection-probe-recovery.integration.test.ts +55 -0
  264. package/src/__tests__/reflection-probe-selection.unit.test.ts +46 -0
  265. package/src/__tests__/render-error-code-owner.test-d.ts +23 -3
  266. package/src/__tests__/render-feature-docs.unit.test.ts +1 -1
  267. package/src/__tests__/render-target-admission.unit.test.ts +80 -0
  268. package/src/__tests__/render-target-graph-projection.unit.test.ts +102 -0
  269. package/src/__tests__/render-target-inspection.unit.test.ts +52 -0
  270. package/src/__tests__/render-target-material-source.test-d.ts +30 -0
  271. package/src/__tests__/render-target-material-source.unit.test.ts +97 -0
  272. package/src/__tests__/render-target-owner-placement.unit.test.ts +203 -0
  273. package/src/__tests__/render-target-owner.unit.test.ts +122 -0
  274. package/src/__tests__/render-target-probe-soak.perf.test.ts +94 -0
  275. package/src/__tests__/render-target-public-schema.json +107 -0
  276. package/src/__tests__/render-target-public-schema.unit.test.ts +91 -0
  277. package/src/__tests__/render-target-public-surface.test-d.ts +83 -0
  278. package/src/__tests__/render-target-readback.integration.test.ts +90 -0
  279. package/src/__tests__/render-target-recovery.integration.test.ts +85 -0
  280. package/src/__tests__/renderer-factory-material-contract.unit.test.ts +76 -0
  281. package/src/__tests__/renderer-frame-transaction.integration.test.ts +1 -1
  282. package/src/__tests__/renderer-lifecycle.integration.test.ts +14 -0
  283. package/src/__tests__/shader-stage-surface.unit.test.ts +19 -4
  284. package/src/__tests__/skin-motion-regression.unit.test.ts +132 -0
  285. package/src/__tests__/skinned-shadow-caster.test.ts +32 -2
  286. package/src/__tests__/standard-cluster-capability.integration.test.ts +63 -0
  287. package/src/__tests__/standard-cluster-transport.unit.test.ts +121 -0
  288. package/src/__tests__/standard-cluster-variant-resolution.unit.test.ts +232 -0
  289. package/src/__tests__/standard-clustered-pipeline.unit.test.ts +8 -5
  290. package/src/__tests__/standard-lighting-inspection.integration.test.ts +138 -0
  291. package/src/__tests__/standard-lighting-ssot.unit.test.ts +18 -0
  292. package/src/__tests__/standard-lighting-write.unit.test.ts +377 -0
  293. package/src/__tests__/standard-lighting.integration.test.ts +67 -0
  294. package/src/__tests__/standard-pipeline.dawn.test.ts +206 -5
  295. package/src/__tests__/standard-pipeline.integration.test.ts +181 -150
  296. package/src/__tests__/standard-probe-binding.unit.test.ts +25 -0
  297. package/src/__tests__/standard-profile-oracle.unit.test.ts +23 -36
  298. package/src/__tests__/typed-pipeline-topology.unit.test.ts +395 -14
  299. package/src/__tests__/urp-pipeline-targets.unit.test.ts +2 -2
  300. package/src/__tests__/visibility-resolver.unit.test.ts +15 -0
  301. package/src/__tests__/volume-optics-oracle.unit.test.ts +85 -0
  302. package/src/__tests__/volumetric-fog-budget-recovery.integration.test.ts +655 -0
  303. package/src/__tests__/volumetric-fog-component.unit.test.ts +71 -0
  304. package/src/__tests__/volumetric-fog-composite-stage-readback.dawn.test.ts +376 -0
  305. package/src/__tests__/volumetric-fog-errors.unit.test.ts +51 -0
  306. package/src/__tests__/volumetric-fog-inspection.unit.test.ts +77 -0
  307. package/src/__tests__/volumetric-fog-light-capability.unit.test.ts +19 -0
  308. package/src/__tests__/volumetric-fog-light-consumers.integration.test.ts +18 -0
  309. package/src/__tests__/volumetric-fog-light-errors.unit.test.ts +17 -0
  310. package/src/__tests__/volumetric-fog-light-topology.unit.test.ts +21 -0
  311. package/src/__tests__/volumetric-fog-light-transaction.integration.test.ts +53 -0
  312. package/src/__tests__/volumetric-fog-m9-watcher.integration.test.ts +27 -0
  313. package/src/__tests__/volumetric-fog-point-light-owner.integration.test.ts +44 -0
  314. package/src/__tests__/volumetric-fog-point-spot-accumulation.integration.test.ts +49 -0
  315. package/src/__tests__/volumetric-fog-point-spot-contract.unit.test.ts +28 -0
  316. package/src/__tests__/volumetric-fog-point-spot-lkg.integration.test.ts +65 -0
  317. package/src/__tests__/volumetric-fog-point-spot-selection.integration.test.ts +49 -0
  318. package/src/__tests__/volumetric-fog-point-spot-temporal.unit.test.ts +55 -0
  319. package/src/__tests__/volumetric-fog-projector-contract.unit.test.ts +22 -0
  320. package/src/__tests__/volumetric-fog-projector-inspection.unit.test.ts +12 -0
  321. package/src/__tests__/volumetric-fog-projector-surface-volume.integration.test.ts +11 -0
  322. package/src/__tests__/volumetric-fog-projector-transaction.integration.test.ts +26 -0
  323. package/src/__tests__/volumetric-fog-recovery.integration.test.ts +47 -0
  324. package/src/__tests__/volumetric-fog-selected-light-resolution.integration.test.ts +63 -0
  325. package/src/__tests__/volumetric-fog-spot-closure.integration.test.ts +26 -0
  326. package/src/__tests__/volumetric-fog-spot-contract.test-d.ts +21 -0
  327. package/src/__tests__/volumetric-fog-spot-contract.unit.test.ts +27 -0
  328. package/src/__tests__/volumetric-fog-spot-frame-optics.integration.test.ts +14 -0
  329. package/src/__tests__/volumetric-fog-spot-optics.integration.test.ts +29 -0
  330. package/src/__tests__/volumetric-fog-stage-readback.dawn.test.ts +550 -0
  331. package/src/__tests__/volumetric-fog-temporal-selected-light.unit.test.ts +33 -0
  332. package/src/__tests__/volumetric-fog-temporal.unit.test.ts +45 -0
  333. package/src/__tests__/volumetric-fog-topology.unit.test.ts +237 -0
  334. package/src/__tests__/volumetric-fog-transparent.integration.test.ts +27 -0
  335. package/src/assembly/device-feature-admission.ts +73 -0
  336. package/src/assembly/directional-shadow-inspection.ts +122 -0
  337. package/src/assembly/factory.ts +1093 -246
  338. package/src/assembly/host-contract.ts +25 -0
  339. package/src/assembly/render-target-host.ts +847 -0
  340. package/src/assets/asset-decoders.ts +1 -1
  341. package/src/assets/render-asset-port.ts +7 -1
  342. package/src/capture/cube-views.ts +69 -0
  343. package/src/capture/scheduler.ts +265 -0
  344. package/src/components/__tests__/schema-contract.unit.test.ts +12 -0
  345. package/src/components/__tests__/schema-parity.unit.test.ts +9 -0
  346. package/src/components/camera.ts +4 -0
  347. package/src/components/cube-camera.ts +52 -0
  348. package/src/components/directional-light.ts +22 -2
  349. package/src/components/directional-shadow-filter.ts +57 -0
  350. package/src/components/index.ts +2 -0
  351. package/src/components/light-helpers.ts +85 -7
  352. package/src/components/reflection-probe.ts +49 -0
  353. package/src/components/spot-light.ts +22 -3
  354. package/src/device/gpu-residency.ts +168 -58
  355. package/src/environment/frame.ts +6 -24
  356. package/src/environment/lifecycle.ts +12 -0
  357. package/src/errors/render.ts +405 -143
  358. package/src/extract/directional-shadow-projection.ts +61 -0
  359. package/src/extract/environment.ts +57 -0
  360. package/src/extract/visibility.ts +25 -1
  361. package/src/features/fullscreen.ts +3 -0
  362. package/src/features/targets.ts +4 -1
  363. package/src/features/types.ts +10 -0
  364. package/src/fullscreen-post-process-pass.ts +43 -2
  365. package/src/gpu-driven/batch-topology.ts +1 -0
  366. package/src/gpu-driven/production-raster.ts +1 -3
  367. package/src/gpu-scene.ts +8 -1
  368. package/src/gpu-usage.ts +2 -0
  369. package/src/hdrp-buffers.ts +165 -59
  370. package/src/ibl/IblPipelineCache.ts +11 -0
  371. package/src/ibl/kernel-cache.ts +88 -0
  372. package/src/index.ts +186 -1
  373. package/src/inspection-types.ts +56 -0
  374. package/src/instances-derived-bounds.ts +171 -0
  375. package/src/light-buffer-layout.ts +74 -26
  376. package/src/mesh-material-bindings.ts +132 -0
  377. package/src/pbr-pipeline.ts +139 -27
  378. package/src/pipeline/standard-forward-lane.ts +65 -15
  379. package/src/pipeline/standard-lighting/errors.ts +6 -0
  380. package/src/pipeline/standard-lighting/graph.ts +157 -0
  381. package/src/pipeline/standard-lighting/inspection.ts +88 -0
  382. package/src/pipeline/standard-lighting/layout.ts +31 -0
  383. package/src/pipeline/standard-lighting/light-frame.ts +34 -0
  384. package/src/pipeline/standard-lighting/membership.ts +43 -0
  385. package/src/pipeline/standard-lighting/prepare.ts +37 -0
  386. package/src/pipeline/standard-lighting/topology.ts +120 -0
  387. package/src/pipeline/standard-lighting/transport.ts +77 -0
  388. package/src/pipeline/standard-pipeline.ts +141 -192
  389. package/src/pipeline/standard-post.ts +26 -7
  390. package/src/pipeline/standard-profile.ts +23 -30
  391. package/src/pipeline-spec.ts +84 -21
  392. package/src/point-shadow-inspection.ts +55 -0
  393. package/src/record/__tests__/temporal-payload.unit.test.ts +42 -1
  394. package/src/record/__tests__/transparent-group2-selection.unit.test.ts +73 -0
  395. package/src/record/__tests__/view-ubo-layout.unit.test.ts +40 -0
  396. package/src/record/frame-lighting.ts +470 -516
  397. package/src/record/frame-snapshot.ts +76 -28
  398. package/src/record/frame-targets.ts +23 -2
  399. package/src/record/frame.ts +1169 -79
  400. package/src/record/gpu-pass-timing/contract.ts +180 -0
  401. package/src/record/gpu-pass-timing/errors.ts +34 -0
  402. package/src/record/gpu-pass-timing/index.ts +37 -0
  403. package/src/record/gpu-pass-timing/parser.ts +97 -0
  404. package/src/record/gpu-pass-timing/session.ts +938 -0
  405. package/src/record/gpu-timing.ts +233 -0
  406. package/src/record/main-pass-geometry.ts +120 -80
  407. package/src/record/main-pass-material.ts +101 -11
  408. package/src/record/main-pass-sprite-draws.ts +54 -25
  409. package/src/record/main-pass.ts +71 -15
  410. package/src/record/render-context.ts +77 -0
  411. package/src/record/shadow-pass.ts +104 -66
  412. package/src/record/skybox-post-pass.ts +20 -4
  413. package/src/record/typed-frame-graph.ts +1016 -32
  414. package/src/record/view-ubo.ts +63 -28
  415. package/src/reflection/filter.ts +81 -0
  416. package/src/reflection/gpu-table.ts +44 -0
  417. package/src/reflection/projection.ts +281 -0
  418. package/src/render-contract.ts +147 -4
  419. package/src/render-data.ts +50 -22
  420. package/src/render-graph-primitives.ts +9 -4
  421. package/src/render-pipeline.ts +161 -2
  422. package/src/render-system-extract.ts +886 -103
  423. package/src/render-system.ts +1174 -66
  424. package/src/renderer-inspect.ts +66 -0
  425. package/src/renderer.ts +222 -0
  426. package/src/scene/render-scene.ts +117 -9
  427. package/src/shadow-atlas.ts +5 -5
  428. package/src/ssao-buffers.ts +9 -2
  429. package/src/targets/contracts.ts +182 -0
  430. package/src/targets/graph-projection.ts +169 -0
  431. package/src/targets/material-source.ts +143 -0
  432. package/src/targets/owner.ts +214 -0
  433. package/src/targets/physical.ts +18 -0
  434. package/src/targets/readback.ts +153 -0
  435. package/src/temporal/frame.ts +128 -0
  436. package/src/transmission/__tests__/standard-transmission.dawn.test.ts +61 -23
  437. package/src/typed-render-graph-primitives.ts +34 -2
  438. package/src/typed-shadow-atlas.ts +17 -0
  439. package/src/typed-shadow-passes.ts +47 -40
  440. package/src/volume/capability.ts +173 -0
  441. package/src/volume/component.ts +110 -0
  442. package/src/volume/extract.ts +21 -0
  443. package/src/volume/inspection.ts +78 -0
  444. package/src/volume/passes.ts +686 -0
  445. package/src/volume/recovery.ts +86 -0
  446. package/src/volume/resources.ts +236 -0
  447. package/src/volume/temporal.ts +89 -0
  448. package/dist/chunk-IPXCABEP.mjs.map +0 -1
  449. package/dist/chunk-LOSO4CTV.mjs.map +0 -1
  450. package/dist/chunk-RJRUS55L.mjs.map +0 -1
  451. package/dist/chunk-YTQ5C7YQ.mjs +0 -584
  452. package/dist/chunk-YTQ5C7YQ.mjs.map +0 -1
@@ -0,0 +1,86 @@
1
+ import type { VolumeProjectorTuple } from './temporal';
2
+
3
+ export interface VolumeSelectedLightFacts {
4
+ readonly entity: number;
5
+ readonly kind: 'directional' | 'point' | 'spot';
6
+ readonly revision: number;
7
+ readonly shadowTile: number;
8
+ readonly pointLightEntity?: number;
9
+ readonly spotLightEntity?: number;
10
+ readonly projector?: VolumeProjectorTuple;
11
+ }
12
+
13
+ export interface VolumeRecoveryState {
14
+ readonly guid: string;
15
+ readonly generation: number;
16
+ readonly deviceEpoch: number;
17
+ readonly status: 'accepted' | 'candidate' | 'degraded' | 'recovering';
18
+ readonly candidateGeneration?: number;
19
+ readonly candidateDigest?: string;
20
+ readonly lkgGeneration?: number;
21
+ readonly candidateFailure?:
22
+ | 'submit-failed'
23
+ | 'stale-generation'
24
+ | 'missing-selected-light'
25
+ | 'missing-shadow';
26
+ readonly selectedLight?: VolumeSelectedLightFacts;
27
+ readonly candidateSelectedLight?: VolumeSelectedLightFacts;
28
+ }
29
+
30
+ export type VolumeRecoveryEvent =
31
+ | { readonly kind: 'submit-failed' }
32
+ | { readonly kind: 'device-lost'; readonly deviceEpoch: number }
33
+ | { readonly kind: 'accepted'; readonly generation: number };
34
+
35
+ export function stageVolumetricFog(
36
+ state: VolumeRecoveryState,
37
+ candidate: {
38
+ readonly generation: number;
39
+ readonly digest: string;
40
+ readonly selectedLight?: VolumeSelectedLightFacts;
41
+ },
42
+ ): VolumeRecoveryState {
43
+ return {
44
+ ...state,
45
+ status: 'candidate',
46
+ candidateGeneration: candidate.generation,
47
+ candidateDigest: candidate.digest,
48
+ ...(candidate.selectedLight === undefined
49
+ ? {}
50
+ : { candidateSelectedLight: candidate.selectedLight }),
51
+ };
52
+ }
53
+
54
+ export function recoverVolumetricFog(
55
+ state: VolumeRecoveryState,
56
+ event: VolumeRecoveryEvent,
57
+ ): VolumeRecoveryState {
58
+ if (event.kind === 'device-lost') {
59
+ return { ...state, status: 'recovering', deviceEpoch: event.deviceEpoch };
60
+ }
61
+ if (event.kind === 'submit-failed') {
62
+ return {
63
+ ...state,
64
+ status: 'degraded',
65
+ lkgGeneration: state.generation,
66
+ candidateFailure: 'submit-failed',
67
+ };
68
+ }
69
+ const expectedGeneration = state.candidateGeneration ?? state.generation;
70
+ if (state.status === 'degraded' || event.generation !== expectedGeneration) {
71
+ return {
72
+ ...state,
73
+ status: 'degraded',
74
+ lkgGeneration: state.generation,
75
+ candidateFailure: 'stale-generation',
76
+ };
77
+ }
78
+ return {
79
+ ...state,
80
+ status: 'accepted',
81
+ lkgGeneration: state.generation,
82
+ ...(state.candidateSelectedLight === undefined
83
+ ? {}
84
+ : { selectedLight: state.candidateSelectedLight }),
85
+ };
86
+ }
@@ -0,0 +1,236 @@
1
+ import type {
2
+ CompiledRenderGraphInfo,
3
+ GraphTexture,
4
+ GraphTextureDescriptor,
5
+ GraphTextureView,
6
+ RenderGraphBuilder,
7
+ RenderGraphError,
8
+ } from '@forgeax/engine-render-graph';
9
+ import { ok, type Result } from '@forgeax/engine-types';
10
+ import type { RenderPipelineFrame } from '../render-pipeline';
11
+
12
+ export const VOLUME_FROXEL_FORMAT = 'rgba8unorm' as const;
13
+ export const VOLUME_HISTORY_FORMAT = 'rgba16float' as const;
14
+ export const VOLUME_RESOLVED_FORMAT = 'rgba16float' as const;
15
+ export const VOLUME_DEPTH_FORMAT = 'r32float' as const;
16
+ export const VOLUME_PACKING_Z = 4;
17
+ /** Renderer-owned parameter allocation shared by staging and inspection. */
18
+ export const VOLUMETRIC_FOG_PARAMS_BYTES = 8 * 16;
19
+
20
+ export interface VolumetricFogExtent {
21
+ readonly width: number;
22
+ readonly height: number;
23
+ readonly depth: number;
24
+ }
25
+
26
+ /** Derive the renderer's high profile grid from the actual surface descriptor. */
27
+ export function deriveVolumetricFogExtent(
28
+ surface: { readonly width: number; readonly height: number },
29
+ depth = 64,
30
+ tileSize = 4,
31
+ ): VolumetricFogExtent {
32
+ // The profile tile describes the source-resolution quality lane. Packed
33
+ // visibility stores two source pixels per physical tile in each dimension.
34
+ const physicalTileSize = Math.max(2, Math.floor(tileSize / 2));
35
+ return {
36
+ width: Math.max(1, Math.ceil(surface.width / physicalTileSize)),
37
+ height: Math.max(1, Math.ceil(surface.height / physicalTileSize)),
38
+ depth: Math.max(1, depth),
39
+ };
40
+ }
41
+
42
+ /** Derive the 2D resolve extent from the same profile tile without a second profile. */
43
+ export function deriveVolumetricFogResolvedExtent(
44
+ surface: { readonly width: number; readonly height: number },
45
+ froxelTileSize = 4,
46
+ ): VolumetricFogExtent {
47
+ // Three's high-quality volume lane resolves at resolution=.25. The packed
48
+ // high profile uses a physical froxel tile of two source pixels, so its
49
+ // resolve needs the next coarser four-pixel lane. Keep the low profile's
50
+ // existing eight-pixel resolve so its quality/budget contract is stable.
51
+ const resolveTileSize = froxelTileSize === 4 ? 8 : Math.max(1, froxelTileSize);
52
+ return deriveVolumetricFogExtent(surface, 1, resolveTileSize);
53
+ }
54
+
55
+ export interface VolumetricFogResources {
56
+ readonly extent: VolumetricFogExtent;
57
+ readonly resolvedExtent: VolumetricFogExtent;
58
+ readonly froxel: GraphTexture;
59
+ readonly froxelView: GraphTextureView;
60
+ readonly resolved: GraphTexture;
61
+ readonly resolvedView: GraphTextureView;
62
+ readonly history: GraphTexture;
63
+ readonly historyView: GraphTextureView;
64
+ readonly temporal: GraphTexture;
65
+ readonly temporalView: GraphTextureView;
66
+ }
67
+
68
+ export interface VolumetricFogResourceFacts {
69
+ readonly generation: number;
70
+ readonly liveResourceCount: number;
71
+ readonly currentBytes: number;
72
+ readonly historyBytes: number;
73
+ readonly scratchBytes: number;
74
+ readonly bufferBytes: number;
75
+ readonly totalBytes: number;
76
+ readonly physicalResourceCount: number;
77
+ readonly sampleCount: number;
78
+ readonly resolvedPixelCount: number;
79
+ }
80
+
81
+ /** Project graph descriptors into the volume inspection without a second ledger. */
82
+ export interface VolumetricFogResourceInspectionOptions {
83
+ /** Number of non-null parameter buffers retained by the frame owner. */
84
+ readonly parameterBufferCount?: number;
85
+ }
86
+
87
+ export function inspectVolumetricFogResources(
88
+ graph: CompiledRenderGraphInfo,
89
+ options: VolumetricFogResourceInspectionOptions = {},
90
+ ): VolumetricFogResourceFacts {
91
+ const volumeResources = graph.resources.filter(
92
+ (resource) =>
93
+ resource.label === 'volume-froxel' ||
94
+ resource.label === 'volume-resolved-current' ||
95
+ resource.label === 'volume-history' ||
96
+ resource.label === 'volume-temporal' ||
97
+ resource.label === 'volume-params',
98
+ );
99
+ const bytes = (label: string): number =>
100
+ volumeResources
101
+ .filter((resource) => resource.label === label)
102
+ .reduce((total, resource) => total + (resource.byteSize ?? 0), 0);
103
+ const graphParameterResources = volumeResources.filter(
104
+ (resource) => resource.label === 'volume-params',
105
+ );
106
+ const graphParameterBufferCount = graphParameterResources.length;
107
+ const parameterBufferCount = Math.max(
108
+ 0,
109
+ Math.floor(options.parameterBufferCount ?? graphParameterBufferCount),
110
+ );
111
+ const froxel = volumeResources.find((resource) => resource.label === 'volume-froxel');
112
+ const froxelExtent = froxel?.extent;
113
+ const resolvedExtent = volumeResources.find(
114
+ (resource) => resource.label === 'volume-resolved-current',
115
+ )?.extent;
116
+ const currentBytes = bytes('volume-resolved-current');
117
+ // The two graph labels are ping-pong slots for one logical history volume.
118
+ // Report one slot and physical total separately so the budget remains honest.
119
+ const historyBytes = bytes('volume-history') + bytes('volume-temporal');
120
+ const scratchBytes = bytes('volume-froxel');
121
+ const bufferBytes = parameterBufferCount * VOLUMETRIC_FOG_PARAMS_BYTES;
122
+ const physicalAllocations = new Set<string>();
123
+ for (const resource of volumeResources) {
124
+ if (resource.label === 'volume-params') continue;
125
+ physicalAllocations.add(resource.physicalAllocationKey ?? resource.label);
126
+ }
127
+ // `physicalAllocationKey` remains the authoritative lifecycle identity for
128
+ // counting live allocations, while category bytes report each declared
129
+ // history slot so the public sum stays inspectable and exact.
130
+ const totalBytes = currentBytes + historyBytes + scratchBytes + bufferBytes;
131
+ return Object.freeze({
132
+ generation: graph.generation,
133
+ liveResourceCount: volumeResources.length - graphParameterBufferCount + parameterBufferCount,
134
+ currentBytes,
135
+ historyBytes,
136
+ scratchBytes,
137
+ bufferBytes,
138
+ totalBytes,
139
+ physicalResourceCount: physicalAllocations.size + parameterBufferCount,
140
+ sampleCount:
141
+ froxelExtent === undefined
142
+ ? 0
143
+ : froxelExtent.width *
144
+ froxelExtent.height *
145
+ froxelExtent.depthOrArrayLayers *
146
+ VOLUME_PACKING_Z,
147
+ resolvedPixelCount:
148
+ resolvedExtent === undefined ? 0 : resolvedExtent.width * resolvedExtent.height,
149
+ });
150
+ }
151
+
152
+ function textureDescriptor(
153
+ format: GPUTextureFormat,
154
+ extent: VolumetricFogExtent,
155
+ ): GraphTextureDescriptor {
156
+ return {
157
+ format,
158
+ size: {
159
+ width: extent.width,
160
+ height: extent.height,
161
+ depthOrArrayLayers: Math.ceil(extent.depth / VOLUME_PACKING_Z),
162
+ },
163
+ dimension: '2d',
164
+ };
165
+ }
166
+
167
+ function resolvedDescriptor(extent: VolumetricFogExtent): GraphTextureDescriptor {
168
+ return {
169
+ format: VOLUME_RESOLVED_FORMAT,
170
+ size: { width: extent.width, height: extent.height, depthOrArrayLayers: 1 },
171
+ dimension: '2d',
172
+ };
173
+ }
174
+
175
+ export function createVolumetricFogResources(
176
+ graph: RenderGraphBuilder<RenderPipelineFrame>,
177
+ extent: VolumetricFogExtent = { width: 64, height: 64, depth: 64 },
178
+ resolvedExtent?: VolumetricFogExtent,
179
+ ): Result<VolumetricFogResources, RenderGraphError> {
180
+ const effectiveResolvedExtent = resolvedExtent ?? {
181
+ width: extent.width,
182
+ height: extent.height,
183
+ depth: 1,
184
+ };
185
+ const froxel = graph.createTexture(
186
+ 'volume-froxel',
187
+ textureDescriptor(VOLUME_FROXEL_FORMAT, extent),
188
+ );
189
+ if (!froxel.ok) return froxel;
190
+ const froxelView = graph.view(froxel.value, {
191
+ label: 'volume-froxel.view',
192
+ dimension: '2d-array',
193
+ baseArrayLayer: 0,
194
+ arrayLayerCount: Math.ceil(extent.depth / VOLUME_PACKING_Z),
195
+ });
196
+ if (!froxelView.ok) return froxelView;
197
+ const resolved = graph.createTexture(
198
+ 'volume-resolved-current',
199
+ resolvedDescriptor(effectiveResolvedExtent),
200
+ );
201
+ if (!resolved.ok) return resolved;
202
+ const resolvedView = graph.view(resolved.value, {
203
+ label: 'volume-resolved-current.view',
204
+ dimension: '2d',
205
+ });
206
+ if (!resolvedView.ok) return resolvedView;
207
+ const history = graph.createTexture(
208
+ 'volume-history',
209
+ resolvedDescriptor(effectiveResolvedExtent),
210
+ );
211
+ if (!history.ok) return history;
212
+ const historyView = graph.view(history.value, { label: 'volume-history.view', dimension: '2d' });
213
+ if (!historyView.ok) return historyView;
214
+ const temporal = graph.createTexture(
215
+ 'volume-temporal',
216
+ resolvedDescriptor(effectiveResolvedExtent),
217
+ );
218
+ if (!temporal.ok) return temporal;
219
+ const temporalView = graph.view(temporal.value, {
220
+ label: 'volume-temporal.view',
221
+ dimension: '2d',
222
+ });
223
+ if (!temporalView.ok) return temporalView;
224
+ return ok({
225
+ extent,
226
+ resolvedExtent: effectiveResolvedExtent,
227
+ froxel: froxel.value,
228
+ froxelView: froxelView.value,
229
+ resolved: resolved.value,
230
+ resolvedView: resolvedView.value,
231
+ history: history.value,
232
+ historyView: historyView.value,
233
+ temporal: temporal.value,
234
+ temporalView: temporalView.value,
235
+ });
236
+ }
@@ -0,0 +1,89 @@
1
+ export interface VolumeTemporalSignature {
2
+ readonly cameraRevision: number;
3
+ readonly fogRevision: number;
4
+ readonly lightRevision: number;
5
+ readonly densityGeneration: number;
6
+ readonly width: number;
7
+ readonly height: number;
8
+ readonly lightKind?: 'directional' | 'point' | 'spot';
9
+ readonly lightEntity?: number;
10
+ readonly pointLightEntity?: number;
11
+ readonly spotLightEntity?: number;
12
+ readonly projector?: VolumeProjectorTuple;
13
+ readonly lightShadowRevision?: number;
14
+ }
15
+
16
+ /** Accepted projector facts shared by surface and volume consumers. */
17
+ export interface VolumeProjectorTuple {
18
+ readonly guid: string;
19
+ readonly generation: number;
20
+ readonly view: string;
21
+ readonly sampler: string;
22
+ readonly projection: string;
23
+ readonly revision: number;
24
+ }
25
+
26
+ export type VolumeTemporalResetReason =
27
+ | 'camera-cut'
28
+ | 'fog-revision'
29
+ | 'light-revision'
30
+ | 'density-generation'
31
+ | 'resize'
32
+ | 'reprojection-out-of-screen'
33
+ | 'reprojection-depth-discontinuity';
34
+
35
+ export type VolumeTemporalReset =
36
+ | { readonly reset: false }
37
+ | { readonly reset: true; readonly reason: VolumeTemporalResetReason };
38
+
39
+ export function resolveVolumeTemporalReset(
40
+ previous: VolumeTemporalSignature,
41
+ next: VolumeTemporalSignature,
42
+ options: {
43
+ readonly taaEnabled?: boolean;
44
+ readonly reprojection?: 'out-of-screen' | 'depth-discontinuity';
45
+ } = {},
46
+ ): VolumeTemporalReset {
47
+ if (options.reprojection === 'out-of-screen') {
48
+ return { reset: true, reason: 'reprojection-out-of-screen' };
49
+ }
50
+ if (options.reprojection === 'depth-discontinuity') {
51
+ return { reset: true, reason: 'reprojection-depth-discontinuity' };
52
+ }
53
+ if (previous.width !== next.width || previous.height !== next.height) {
54
+ return { reset: true, reason: 'resize' };
55
+ }
56
+ if (previous.cameraRevision !== next.cameraRevision) return { reset: true, reason: 'camera-cut' };
57
+ if (previous.fogRevision !== next.fogRevision) return { reset: true, reason: 'fog-revision' };
58
+ if (previous.lightRevision !== next.lightRevision) {
59
+ return { reset: true, reason: 'light-revision' };
60
+ }
61
+ if (previous.lightKind !== next.lightKind || previous.lightEntity !== next.lightEntity) {
62
+ return { reset: true, reason: 'light-revision' };
63
+ }
64
+ if (
65
+ previous.pointLightEntity !== next.pointLightEntity ||
66
+ previous.spotLightEntity !== next.spotLightEntity
67
+ ) {
68
+ return { reset: true, reason: 'light-revision' };
69
+ }
70
+ const previousProjector = previous.projector;
71
+ const nextProjector = next.projector;
72
+ if (
73
+ previousProjector?.guid !== nextProjector?.guid ||
74
+ previousProjector?.generation !== nextProjector?.generation ||
75
+ previousProjector?.view !== nextProjector?.view ||
76
+ previousProjector?.sampler !== nextProjector?.sampler ||
77
+ previousProjector?.projection !== nextProjector?.projection ||
78
+ previousProjector?.revision !== nextProjector?.revision
79
+ ) {
80
+ return { reset: true, reason: 'light-revision' };
81
+ }
82
+ if (previous.lightShadowRevision !== next.lightShadowRevision) {
83
+ return { reset: true, reason: 'light-revision' };
84
+ }
85
+ if (previous.densityGeneration !== next.densityGeneration) {
86
+ return { reset: true, reason: 'density-generation' };
87
+ }
88
+ return { reset: false };
89
+ }
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/record/frame-snapshot.ts"],"names":[],"mappings":";;;AAsCO,SAAS,cAAA,CAAe,SAAiB,SAAA,EAA2B;AACzE,EAAA,OAAO,UAAU,UAAA,GAAa,SAAA;AAChC;AA6CO,IAAM,0BAAA,GAAwE,CAAC,CAAA,EAAG,CAAA,EAAG,GAAG,CAAC,CAAA;AAsBhG,IAAM,iBAAA,uBAAwB,OAAA,EAAwB;AACtD,IAAI,mBAAA,GAAsB,CAAA;AAOnB,SAAS,mBAAmB,OAAA,EAA0B;AAC3D,EAAA,MAAM,MAAA,GAAS,OAAA;AACf,EAAA,MAAM,QAAA,GAAW,iBAAA,CAAkB,GAAA,CAAI,MAAM,CAAA;AAC7C,EAAA,IAAI,QAAA,KAAa,QAAW,OAAO,QAAA;AACnC,EAAA,MAAM,QAAA,GAAW,mBAAA,EAAA;AACjB,EAAA,iBAAA,CAAkB,GAAA,CAAI,QAAQ,QAAQ,CAAA;AACtC,EAAA,OAAO,QAAA;AACT;AAcO,SAAS,mCAAmC,KAAA,EAIV;AACvC,EAAA,MAAM,EAAE,KAAA,EAAO,kBAAA,EAAoB,QAAA,EAAS,GAAI,KAAA;AAChD,EAAA,IAAI,MAAM,UAAA,KAAe,kBAAA;AACvB,IAAA,OAAO,EAAE,EAAA,EAAI,KAAA,EAAO,IAAA,EAAM,yBAAA,EAA0B;AACtD,EAAA,IAAI,QAAA,EAAU,MAAM,CAAC,KAAA,EAAO,UAAU,KAAA,CAAM,KAAK,CAAA,KAAM,KAAK,CAAA,EAAG;AAC7D,IAAA,OAAO,EAAE,EAAA,EAAI,KAAA,EAAO,IAAA,EAAM,qCAAA,EAAsC;AAAA,EAClE;AACA,EAAA,IAAI,KAAA,CAAM,KAAA,CAAM,CAAC,KAAA,KAAU,KAAA,KAAU,CAAC,CAAA,EAAG,OAAO,EAAE,EAAA,EAAI,KAAA,EAAO,IAAA,EAAM,wBAAA,EAAyB;AAC5F,EAAA,KAAA,IAAS,QAAQ,CAAA,EAAG,KAAA,GAAQ,IAAI,KAAA,CAAM,MAAA,EAAQ,SAAS,CAAA,EAAG;AACxD,IAAA,MAAM,YAAa,KAAA,CAAM,KAAA,GAAQ,CAAC,CAAA,IAAK,OAAO,CAAA,GAAK,EAAA;AACnD,IAAA,IAAI,aAAa,EAAA,EAAM,OAAO,EAAE,EAAA,EAAI,KAAA,EAAO,MAAM,6BAAA,EAA8B;AAAA,EACjF;AACA,EAAA,OAAO,EAAE,IAAI,IAAA,EAAK;AACpB;AAiCO,SAAS,8BAAA,GAAiD;AAE/D,EAAA,MAAM,aAAA,GAAgB,IAAI,YAAA,CAAa,EAAE,CAAA;AACzC,EAAA,aAAA,CAAc,CAAC,CAAA,GAAI,CAAA;AACnB,EAAA,aAAA,CAAc,CAAC,CAAA,GAAI,CAAA;AACnB,EAAA,aAAA,CAAc,EAAE,CAAA,GAAI,CAAA;AACpB,EAAA,aAAA,CAAc,EAAE,CAAA,GAAI,CAAA;AACpB,EAAA,OAAO;AAAA,IACL,QAAA,EAAU,IAAA,CAAK,MAAA,CAAO,CAAA,EAAG,GAAG,CAAC,CAAA;AAAA,IAC7B,KAAA,EAAO,aAAA;AAAA,IACP,GAAA,EAAK,KAAK,EAAA,GAAK,CAAA;AAAA,IACf,MAAA,EAAQ,CAAA;AAAA,IACR,IAAA,EAAM,GAAA;AAAA,IACN,GAAA,EAAK,GAAA;AAAA;AAAA;AAAA;AAAA,IAIL,UAAA,EAAY,aAAA;AAAA,IACZ,SAAA,EAAW,EAAA;AAAA,IACX,UAAA,EAAY,CAAA;AAAA,IACZ,WAAA,EAAa,EAAA;AAAA,IACb,QAAA,EAAU,CAAA;AAAA,IACV,OAAA,EAAS,MAAA;AAAA,IACT,QAAA,EAAU,CAAA;AAAA,IACV,UAAA,EAAY,CAAA;AAAA,IACZ,SAAA,EAAW,MAAA;AAAA,IACX,KAAA,EAAO,KAAA;AAAA,IACP,cAAA,EAAgB,CAAA;AAAA,IAChB,cAAA,EAAgB,CAAA;AAAA,IAChB,eAAA,EAAiB,CAAA;AAAA,IACjB,UAAA,EAAY;AAAA,GACd;AACF;AASO,SAAS,mBAAmB,KAAA,EAAmC;AACpE,EAAA,IAAI,KAAA,KAAU,QAAW,OAAO,CAAA;AAChC,EAAA,IAAI,KAAA,IAAS,GAAG,OAAO,CAAA;AACvB,EAAA,IAAI,KAAA,IAAS,GAAG,OAAO,CAAA;AACvB,EAAA,OAAO,CAAA;AACT","file":"chunk-IPXCABEP.mjs","sourcesContent":["// @forgeax/engine-runtime - RenderSystem record stage: frame-snapshot.\n// Extracted from render-system-record.ts (feat-20260704 M3/w17, pure move).\n\nimport type { World } from '@forgeax/engine-ecs';\nimport { vec3 } from '@forgeax/engine-math';\nimport type { ResolvedColorTargetDescriptor } from '@forgeax/engine-render-graph';\nimport type { Texture } from '@forgeax/engine-rhi';\nimport type { ClusterBinScratch } from '../cluster-binner';\nimport type { EnvironmentGeneration } from '../environment/generation';\nimport type { EnvironmentLifecycle } from '../environment/lifecycle';\nimport type { GpuBuffer } from '../gpu-resource';\nimport type { TemporalGpuState } from '../temporal/gpu';\nimport type { TemporalView } from '../temporal/view';\n\nexport type TemporalCommitOutcome =\n | { readonly kind: 'none' }\n | { readonly kind: 'off'; readonly view: TemporalView }\n | { readonly kind: 'taa'; readonly view: TemporalView };\n\n/**\n * feat-20260708-composited-multi-world-rendering M1 / D-1 / D-9:\n * worldId-entityKey composite key helper. Formula: `worldId * 2^32 + entityKey`.\n *\n * worldId is the index into `worlds[]` (the `draw` call argument). entityKey\n * is the packed Entity u32 (encodeEntity(indexSlot, generation)) surfaced\n * through `RenderableSnapshot.entityKey`.\n *\n * Key properties:\n * - worldId=0 identity: `worldEntityKey(0, k) === k` — single-world path\n * cache behavior is bit-for-bit unchanged (AC-03 regression guarantee).\n * - JS safe integer: worldId < 2^21 keeps the composite key below\n * Number.MAX_SAFE_INTEGER (2^53).\n * - SSOT: all cache-key compositing consumers import this single helper;\n * no inline `worldId * 2^32 + k` duplication (D-9).\n *\n * @internal — exported for unit test + glyph/tilemap/record consumers;\n * not part of the public @forgeax/engine-runtime API surface.\n */\nexport function worldEntityKey(worldId: number, entityKey: number): number {\n return worldId * 4294967296 + entityKey; // 2^32 = 4294967296\n}\n\n/**\n * Cross-frame cache entry for a material bind group whose source material and\n * all resolved GPU resources are stable. The material snapshot identity is\n * the extract-layer invalidation token; the builder only stores entries after\n * all explicit resource handles resolved to resident GPU objects.\n */\nexport interface MaterialBgAssemblyCacheEntry {\n readonly material: MaterialSnapshot;\n /** Invalidates when a texture/sampler residency identity changes. */\n readonly materialResourceEpoch: number;\n readonly materialBgl: BindGroupLayout;\n /** Buffer identity changes when the shared material capacity grows. */\n readonly materialBuffer: Buffer;\n readonly skylightResources: SkylightBindGroupResources;\n readonly bindGroup: BindGroup;\n}\n\nimport type { CompiledRenderGraph } from '@forgeax/engine-render-graph';\nimport type { BindGroup, BindGroupLayout, Buffer, TextureView } from '@forgeax/engine-rhi';\nimport type { MaterialRenderState, RenderPipelineAsset } from '@forgeax/engine-types';\nimport type { MeshGpuHandles } from '../device/gpu-residency';\nimport type { SkylightBindGroupResources } from '../ibl/skylight-bind-group';\nimport type { InstanceBufferCacheEntry } from '../instance-buffer-cache';\nimport type { CameraSnapshot } from '../render-contract';\nimport type { RenderPipeline as RenderPipelineDef, RenderPipelineFrame } from '../render-pipeline';\nimport type {\n DispatchEntry,\n MaterialSnapshot,\n PointShadowSnapshot,\n RenderableSnapshot,\n SpotLightSnapshot,\n} from '../render-system-extract';\nimport type { ShadowAtlas } from '../shadow-atlas';\n\n/**\n * feat-20260608-create-app-param-surface-trim / M1 / D-8 (q8 user lock):\n * fallback clear color when the world carries no Camera entity. Opaque\n * black `[0, 0, 0, 1]`, replacing the historical\n * `[0.06, 0.06, 0.08, 1.0]` dark-slate sentinel. Single SSOT consumed by\n * the synthetic CameraSnapshot built when `cameras.length === 0`\n * (Case B in `recordFrame`) and read directly by tests\n * (`zero-camera-clear-fallback.test.ts`). AC-05.\n */\nexport const ZERO_CAMERA_CLEAR_FALLBACK: readonly [number, number, number, number] = [0, 0, 0, 1];\n\nexport interface FrameObservationSource {\n readonly texture: Texture;\n readonly descriptor: ResolvedColorTargetDescriptor;\n readonly frameId: number;\n readonly pipelineId: 'forgeax::standard';\n readonly backendId: string;\n}\n\n/** Internal test/diagnostic view of one compiled graph color target. */\nexport interface CurrentGraphTarget {\n readonly name: string;\n readonly texture: Texture;\n /** Stable opaque identity for this RHI handle during the renderer lifetime. */\n readonly textureIdentity: number;\n readonly descriptor: ResolvedColorTargetDescriptor;\n readonly frameId: number;\n /** Monotonic identity of the compiled graph that owns this texture. */\n readonly graphGeneration: number;\n}\n\nconst textureIdentities = new WeakMap<object, number>();\nlet nextTextureIdentity = 1;\n\n/**\n * Project an opaque RHI texture handle to a stable diagnostic identity.\n * RHI handles intentionally expose no serializable fields; object identity is\n * the only valid comparison boundary for a copy-time capture.\n */\nexport function getTextureIdentity(texture: Texture): number {\n const object = texture as object;\n const existing = textureIdentities.get(object);\n if (existing !== undefined) return existing;\n const identity = nextTextureIdentity++;\n textureIdentities.set(object, identity);\n return identity;\n}\n\nexport type GraphTargetCaptureReadbackValidation =\n | { readonly ok: true }\n | {\n readonly ok: false;\n readonly code:\n | 'capture-readback-length'\n | 'capture-readback-untouched-sentinel'\n | 'capture-readback-empty'\n | 'capture-readback-non-finite';\n };\n\n/** Validate a mapped rgba16float capture before a consumer decodes its values. */\nexport function validateGraphTargetCaptureReadback(input: {\n readonly bytes: Uint8Array;\n readonly expectedByteLength: number;\n readonly sentinel?: Uint8Array;\n}): GraphTargetCaptureReadbackValidation {\n const { bytes, expectedByteLength, sentinel } = input;\n if (bytes.byteLength !== expectedByteLength)\n return { ok: false, code: 'capture-readback-length' };\n if (sentinel?.every((value, index) => bytes[index] === value)) {\n return { ok: false, code: 'capture-readback-untouched-sentinel' };\n }\n if (bytes.every((value) => value === 0)) return { ok: false, code: 'capture-readback-empty' };\n for (let index = 0; index + 1 < bytes.length; index += 2) {\n const exponent = ((bytes[index + 1] ?? 0) >>> 2) & 0x1f;\n if (exponent === 0x1f) return { ok: false, code: 'capture-readback-non-finite' };\n }\n return { ok: true };\n}\n\n/** Internal readback request consumed by the record encoder in the same submit. */\nexport interface GraphTargetCaptureRequest {\n readonly name: string;\n readonly buffer: Buffer;\n readonly bytesPerRow: number;\n readonly width: number;\n readonly height: number;\n /** Descriptor contract checked against the resolved target at copy time. */\n readonly expected: {\n readonly format: ResolvedColorTargetDescriptor['format'];\n readonly width: number;\n readonly height: number;\n readonly usage: number;\n /** Optional caller binding; when present it is checked at copy time. */\n readonly identity?: {\n readonly graphGeneration: number;\n readonly frameId: number;\n readonly textureIdentity: number;\n };\n };\n}\n\n/**\n * Build a synthetic CameraSnapshot the record stage uses when the world\n * carries no Camera entity. Identity-shaped projection / view inputs\n * keep the existing matrix math numerically stable; clear-color quartet\n * sourced from `ZERO_CAMERA_CLEAR_FALLBACK` so the swap-chain still\n * paints `[0, 0, 0, 1]` (AI-user friendly observable signal that the\n * `'render-system-no-camera'` diagnostic fired without leaving stale\n * pixels on screen).\n */\nexport function makeZeroCameraFallbackSnapshot(): CameraSnapshot {\n // Identity world mat4 so `mat4.invert(world)` yields identity view.\n const identityWorld = new Float32Array(16);\n identityWorld[0] = 1;\n identityWorld[5] = 1;\n identityWorld[10] = 1;\n identityWorld[15] = 1;\n return {\n position: vec3.create(0, 0, 0),\n world: identityWorld,\n fov: Math.PI / 4,\n aspect: 1,\n near: 0.1,\n far: 100,\n // feat-20260613 M6 / w20: synthetic camera defaults to perspective so\n // the CSM extract path stays consistent with the previously-implicit\n // perspective shape (unchanged behavior; explicit field).\n projection: 'perspective',\n orthoLeft: -1,\n orthoRight: 1,\n orthoBottom: -1,\n orthoTop: 1,\n tonemap: 'none',\n exposure: 1.0,\n whitePoint: 4.0,\n antialias: 'none',\n bloom: 'off',\n bloomThreshold: 1.0,\n bloomIntensity: 1.0,\n bloomBlurRadius: 4.0,\n clearColor: ZERO_CAMERA_CLEAR_FALLBACK,\n };\n}\n\n// feat-20260621-merge-directionallightshadow-into-directionallight M3 / m3-t2\n// (D-7): host-clamp the merged DirectionalLight's pcfKernelSize to the nearest\n// valid odd kernel in {1,3,5} before it reaches the View UBO float [128].\n// lighting-directional.wgsl runs a constant-trip-count loop to MAX_PCF_HALF=2\n// (merged 5.3-production-shadow-demos AC-14 variant-free pattern) and clips each\n// iteration to half = (pcfKernelSize-1)/2, so the supported kernel set is\n// {1,3,5} (cap 5). undefined (no shadow fields) defaults to 3.\nexport function clampPcfKernelSize(value: number | undefined): number {\n if (value === undefined) return 3;\n if (value <= 1) return 1;\n if (value <= 3) return 3;\n return 5;\n}\n\n/**\n * Per-RenderSystem mutable frame state.\n *\n * Owned by the `createRenderSystem` closure; advanced once per\n * `recordFrame` invocation. The `instanceBuffers` map holds the per-entity\n * GPU storage buffers for Instances-bearing entities (cache key = the\n * packed Entity u32 surfaced via `InstancesSnapshot.cacheKey`); entries\n * are recreated when the `archVersion` bumps or `byteLength` changes.\n *\n * feat-20260518-pbr-direct-lighting-mvp M3 / w14 (AC-17 a): the\n * `warnedZeroLightStandard` flag latches the first-frame warning that\n * fires when the world contains a StandardMaterial entity but zero\n * DirectionalLight entries — once latched the next 100 frames suppress\n * the warning so the AI user is not flooded with repeated noise (charter\n * P3 silent failure -> explicit warning at most once per RenderSystem\n * lifetime).\n */\nexport interface RenderFrameState {\n frameNumber: number;\n /** Monotonic compiled-graph identity used by diagnostic readbacks. */\n graphGeneration: number;\n /** Number of successfully submitted TAA frames; failed attempts do not advance it. */\n successfulTemporalFrameIndex?: number;\n /** Optional graph resource lookup used by low-level record helpers and tests. */\n perFrameGraph?: {\n readonly getColorTargetDescriptor: (key: string) => ResolvedColorTargetDescriptor | undefined;\n readonly getColorTargetView: (key: string) => TextureView | undefined;\n readonly getColorTargetTexture: (key: string) => Texture | undefined;\n readonly graphGeneration: number;\n } | null;\n graphTargetCapture?: GraphTargetCaptureRequest | undefined;\n /** Last successfully rendered static directional shadow atlas token. */\n directionalShadowCache: DirectionalShadowCache | null;\n /** Last successfully submitted camera post-process mode. */\n lastSuccessfulBloom: 'off' | 'on';\n /** Bloom record receipts staged by this frame; cleared on abort. */\n bloomFrameReceipts?: BloomFrameReceipts | undefined;\n /** Set by a directional shadow pass when this frame refreshed the atlas. */\n directionalShadowCacheRecorded: boolean;\n /** The sole compiled owner for compute, copy, feature, and raster work. */\n compiledFrameGraph: CompiledRenderGraph<RenderPipelineFrame> | null;\n compiledFrameGraphTopologyKey: string | null;\n readonly retiredCompiledFrameGraphs: Set<CompiledRenderGraph<RenderPipelineFrame>>;\n /** Physical outputs projected by typed graph passes for downstream observers. */\n currentFrameObservationSource: FrameObservationSource | undefined;\n /** Camera antialias mode for the last successfully submitted graph. */\n lastSuccessfulCameraAntialias: CameraSnapshot['antialias'] | undefined;\n /** Staged TAA GPU state is promoted only after queue.submit succeeds. */\n temporalGpuState: TemporalGpuState | undefined;\n /** Last successful TAA GPU state; candidate replacement stays staged until submit. */\n activeTemporalGpuState: TemporalGpuState | undefined;\n /** Submitted temporal generations retained until their queue fence resolves. */\n readonly retiringTemporalGpuStates: Set<TemporalGpuState>;\n /** Last successful projection; candidate frames never overwrite it. */\n lastSuccessfulTemporalView: TemporalView | undefined;\n pendingTemporalCommit: TemporalCommitOutcome;\n /** Staged environment generation is promoted only after queue.submit succeeds. */\n environmentGeneration: EnvironmentGeneration | undefined;\n readonly environmentLifecycle: EnvironmentLifecycle | undefined;\n currentDirectionalShadowView: TextureView | null;\n currentSpotShadowView: TextureView | null;\n readonly instanceBuffers: Map<number, InstanceBufferCacheEntry>;\n /** Per-entity vertex buffers for the Standard CPU morph lane. */\n readonly morphBuffers?: Map<number, MorphBufferCacheEntry>;\n readonly hdrpClusterBinScratch: ClusterBinScratch;\n /** Reusable HDRP cluster output buffers; sized once and grown only if the grid/cap changes. */\n hdrpClusterGridScratch: Uint32Array | null;\n hdrpLightIndexListScratch: Uint32Array | null;\n /** Compute bind group for the optional WebGPU HDRP membership producer. */\n hdrpClusterMembershipBindGroup: BindGroup | null;\n transientInstanceBuffers: InstanceBufferCacheEntry[];\n warnedZeroLightStandard: boolean;\n /**\n * feax-20260608-multi-light-warn-once M3: once-warn latch for multi-light\n * overrun per bucket (directional N>1 / point N>4 / spot N>4). Fires at\n * most once per RenderSystem lifetime per bucket so AI users see a single\n * actionable console.warn without per-frame flooding (charter P3 explicit\n * failure: warn-once preserves signal/noise floor).\n */\n warnedMultiLightDirectional: boolean;\n warnedMultiLightPoint: boolean;\n warnedMultiLightSpot: boolean;\n /**\n * feat-20260630-equirect-kind-internalized-ibl-declarative-skyligh M3 / w19:\n * once-warn latches for >1 Skylight / >1 SkyboxBackground entity. Fire at most\n * once per RenderSystem lifetime so the multi-entity warn (which names the\n * winning entity handle, F-8) does not flood the console every frame.\n */\n warnedMultiSkylight: boolean;\n warnedMultiSkybox: boolean;\n /** feat-20260531-skybox-env-background M3 / w20: once-warn when camera\n * tonemap is 'none' but a SkyboxBackground entity exists. Skybox needs\n * the HDR render target allocated by the tonemap path; without it the\n * skybox pass is skipped (plan-strategy D-2 NOTE). */\n warnedSkyboxTonemapNone: boolean;\n /**\n * Per-handle warn-once anchor for the missing baseColor-texture fallback,\n * shared by every textured material path (sprite / sprite-lit / standard-pbr\n * / pbr-skin / unlit — feat-20260520-2d-sprite-layer-mvp M-3 / w25 seeded it\n * for sprites; feat-future-pbr-missing-texture-fallback-explicit generalised\n * it to PBR/skin so GLB textures that fail to reach the GPU no longer render\n * silently flat). A `Set<number>` keyed by the raw Handle<TextureAsset> id -\n * the writer fires `console.warn` exactly once per missing texture handle per\n * RenderSystem lifetime so AI users see a single actionable message without\n * per-frame log flooding. The set lives across frames so the second frame on\n * the same missing handle stays silent (charter P3 explicit failure:\n * warn-once preserves signal / noise floor while structured RhiError below\n * stays per-frame fire).\n *\n * Note on the WeakSet/Set choice: plan-strategy mentions WeakSet but\n * Handle values are numeric brand u32s (not object references), so\n * WeakSet<object> is not applicable; Set<number> gives identical\n * warn-once-per-handle semantics at zero additional cost (charter F1\n * minimal surface; the per-RenderSystem lifetime cap is the upper\n * bound).\n */\n readonly warnedMissingBaseColorTextureHandles: Set<number>;\n /**\n * feat-20260527-sprite-nineslice M2 / w11 + M4 / w16 (AC-16): once-per-\n * renderable guard for the `nineslice.scale-too-small` metric increment.\n * Keyed by `renderableIndex` so the second frame on the same mis-scaled\n * entity does NOT keep bumping the counter — AI users see one counter\n * tick per offending entity per RenderSystem lifetime, matching the\n * single-event semantics of the structured fail-fast (charter P3:\n * machine-readable signal, not a per-frame inflation). M4 retired the\n * placeholder console.warn; the increment lands on the per-Renderer\n * EngineMetrics through `runtime.metrics.increment(...)`.\n */\n readonly warnedNineSliceScaleEntities: Set<number>;\n /**\n * feat-20260630-equirect-kind-internalized-ibl-declarative-skyligh M3 / w18:\n * per-handle fire-once anchor for the lazy equirect-to-cubemap projection\n * failure. A `Set<number>` keyed by the raw Handle<EquirectAsset> id - the\n * record arm fires the structured `EquirectProjectionFailedError` exactly\n * once per failed source per RenderSystem lifetime (the store records\n * `status:'failed'` permanently and never retries, R-2 / AC-09; without this\n * latch the record arm would re-fire the error every frame). The set lives\n * across frames so the second frame on the same failed handle stays silent\n * (charter P3: warn-once preserves signal / noise floor).\n */\n readonly firedEquirectProjectionFailedHandles: Set<number>;\n /**\n * feat-20260622-handle-to-id-allocator-elimination M1 / w3: per-frame\n * bind group caches converted to nested WeakMap chain roots (D-3).\n * Each root is a WeakMap<object, WeakMap<...>> walked by\n * getOrCreateFromChain; the chain depth varies by variant\n * (view-main = 7, mesh = 1, hdrp-unified = 5, skin = 2).\n * Roots are stable between device recoveries and replaced when the renderer\n * swaps to a fresh device. Chain keys are always inner buffer handles, never\n * wrappers (D-3).\n */\n viewBindGroupCache: WeakMap<object, unknown>;\n meshBindGroupCache: WeakMap<object, unknown>;\n /**\n * feat-20260622-handle-to-id-allocator-elimination M1 / w2: per-entity\n * bind group caches scoped by entityKey (packed u32 as number).\n * Outer Map<number, WeakMap<handle, ...>> lets GC collect entries for\n * destroyed entities whose handles are unreachable; per-entity material\n * and instances share the same two-level shape (D-1). The inner WeakMap\n * value is opaque (`unknown`) because `getOrCreateFromChain` walks a\n * variable-depth chain — intermediate handles map to nested WeakMaps and\n * only the final handle maps to the variant->BindGroup leaf Map. Direct\n * readers (HDRP shadow-instances read end) assert the leaf shape locally.\n */\n /**\n * feat-20260708-composited-multi-world-rendering M1 / D-1a #2: outer Map\n * keyed by `worldEntityKey(worldId, entityKey)`. Inner WeakMap unchanged.\n */\n readonly materialBgPerEntity: Map<number, WeakMap<object, unknown>>;\n /**\n * feat-20260708-composited-multi-world-rendering M1 / D-1a #3: outer Map\n * keyed by `worldEntityKey(worldId, entityKey)`. Inner WeakMap unchanged.\n */\n readonly instancesBgPerEntity: Map<number, WeakMap<object, unknown>>;\n /** Identity-keyed instances BGs shared across entities using the same buffer. */\n readonly instancesBgShared: WeakMap<object, unknown>;\n /**\n * feat-20260622-handle-to-id-allocator-elimination M1 / w2: cross-entity\n * shared material bind group cache (OQ-1 option A). Outer key is the\n * material shaderId string; inner chain is a WeakMap keyed by the same\n * handle objects used in per-entity chains. Reuses the generic\n * `getOrCreatePerEntity` helper with outerKey string|number (D-1).\n */\n readonly materialBgShared: Map<string, WeakMap<object, unknown>>;\n /** Cross-frame fast path for fully-resident, immutable material snapshots. */\n readonly materialBgAssemblyCache: Map<string, MaterialBgAssemblyCacheEntry>;\n /**\n * feat-20260622-handle-to-id-allocator-elimination M1 / w2: singleton\n * material bind group cache (D-6). Single flat Map<variant, BindGroup>\n * for the one true singleton in production: shadow-material-singleton.\n * shadow-instances-singleton is a test fixture fiction (research F3);\n * no second singleton field is needed.\n */\n readonly singletonMaterialCache: Map<string, BindGroup>;\n /**\n * Post-process bind group cache: nested WeakMap chain root shared by the\n * built-in bloom (bright / blur-H / blur-V / composite), FXAA, and HDRP SSAO\n * (calc / blur) fullscreen passes. Each of these passes samples graph-owned\n * transient render targets (hdrColor / ldrColor / bloom* / ssaoRaw /\n * gbuf0 / hdrDepth); on canvas resize the graph retires the old physical\n * textures and allocates new ones, so the bind group must be rebuilt to\n * reference the new TextureView identity. Keying the cache on the physical\n * TextureView (or `::tex` texture handle for passes that recreate their bound\n * view every frame) makes invalidation automatic: a resized target yields a\n * new key object -> WeakMap miss -> factory rebuild, and dead views are GC'd\n * (same identity-invalidation mechanism as view/mesh caches, D-3). Replaces\n * the pre-fix `pp.*BindGroup === null` single-slot caches that never\n * invalidated on resize and submitted destroyed textures.\n */\n postProcessBgCache: WeakMap<object, unknown>;\n /**\n * Raw handle of the currently installed RenderPipelineAsset (0 = builtin).\n * Installation brands the next candidate topology; the prior compiled graph\n * remains last-known-good until the candidate compiles successfully.\n */\n installedPipelineHandle: number;\n /**\n * Active topology declaration resolved from the pipeline registry.\n */\n activePipeline: RenderPipelineDef;\n /**\n * Install-time config projected into `RenderPipelineTopology.config`.\n */\n installedPipelineConfig: RenderPipelineAsset['config'];\n /**\n * Whether the configured standard graph resolved to the clustered lane.\n * Capability gating keeps URP forward bindings on WebGL2 fallback even\n * when a clustered profile was requested.\n */\n isHdrpActive: boolean;\n /**\n * feat-20260608-cluster-lighting M5 / w20 + M6 / w23 + M5 / w22:\n * once-per-frame fire dedup set for HDRP per-frame fail-soft errors\n * (`hdrp-light-budget-exceeded`, `hdrp-index-list-overflow`). Cleared\n * at the end of each frame so the next frame re-fires if the condition\n * persists. Set<string> keyed by RuntimeErrorCode literal.\n */\n hdrpOncePerFrameFired: Set<string>;\n /**\n * feat-20260612-point-light-shadows-urp-hdrp M3 / T-M3-2 (plan-strategy §D-1).\n * Lazily-allocated cube_array shadow atlas owned by the RenderSystem\n * lifetime. `null` until the first frame whose `lights.pointShadow` is\n * non-empty (zero-shadow scenes never allocate; AC-09). The atlas is\n * disposed by the renderer dispose path; subsequent frames with a non-empty\n * snapshot list re-allocate.\n */\n pointShadowAtlas: ShadowAtlas | null;\n /**\n * feat-20260612-point-light-shadows-urp-hdrp M3 / T-M3-2 (plan-strategy §D-3):\n * per-frame projection of `lights.pointShadow` from the extract stage. The\n * typed point-shadow pass reads this list to drive the 6 x N face iteration.\n * Empty array means the topology declares no point-shadow passes.\n */\n pointShadowSnapshots: readonly PointShadowSnapshot[];\n /**\n * feat-20260622-chunk-gpu-instancing-sprite-tilemap M1 / w4 (D-1):\n * last fold-pass output, computed at `recordFrame` entry from the\n * transparent-sort-ordered dispatch + mode-gate (D-5: only mode 0\n * folds; other modes yield singleton buckets). Currently an\n * observational compute — the drawIndexed swap (1 instanced draw per\n * fold bucket) is the follow-on integration that wires each bucket\n * into the sprite-pass / geometry-pass dispatch loops. The bucket\n * count is the source of truth for `render.instancing.foldedDraws`\n * metric (M3 / w13) — every fold-eligible bucket (bucketSize >= 1\n * under mode 0 AND not bypassed) contributes one increment.\n */\n lastFoldBucketCount: number;\n /**\n * feat-20260625-spot-light-shadow-mapping M2 / w9 (D-2): per-frame projection\n * of `lights.spot` from the extract stage. The typed spot-shadow graph\n * pass reads this list to render each\n * castShadow spot's perspective depth into its `spotShadowDepth` atlas tile\n * (viewport keyed on `shadowAtlasTile`). `recordFrame` writes it before\n * graph execution. Spots with `shadowAtlasTile < 0` (castShadow:false,\n * degenerate direction, or clipped beyond cap 4) are skipped — zero\n * shadow-casting spots means zero spot shadow passes (AC-03).\n */\n spotShadowSnapshots: readonly SpotLightSnapshot[];\n}\n\nexport interface MorphBufferCacheEntry {\n readonly buffer: GpuBuffer;\n readonly byteLength: number;\n}\n\n/**\n * feat-20260518-pbr-direct-lighting-mvp M5 / w22.11 (D-2 + D-10 + AC-06):\n * mutable per-frame dispatch counter object owned by `createRenderSystem`\n * and bumped here at the actual `pass.setPipeline(...)` call site (the\n * only point with both `mat.materialShaderId` and `mesh.layoutProjection` in scope).\n * Unlit / custom shader pipeline dispatch tracked here.\n */\nexport interface DispatchCounts {\n unlit: number;\n /** feat-20260523 M4-T07: 'standard' counter removed; schema-driven\n * materials are tracked via materialShaderId in the pipeline cache. */\n}\n\n/**\n * feat-20260531-per-frame-bind-group-cache M1 / w4: per-frame\n * createBindGroup counter. Closure-mutable object aligned with\n * DispatchCounts precedent. Reset in draw(world) entry, bumped on\n * cache-miss createBindGroup calls (M2-M4).\n */\nexport interface BindGroupCounts {\n createBindGroup: number;\n /**\n * Debug-only: records the variant of every cache-miss BindGroup created via\n * `getOrCreateFromChain` for unit-test observability. Production paths do\n * not read this array.\n */\n keys: string[];\n}\n\n/**\n * Successful Bloom record operations staged for the current frame. These\n * counters are promoted only after the shared renderer queue submission.\n */\nexport interface BloomFrameReceipts {\n readonly uploadCount: number;\n readonly bindGroupCount: number;\n readonly encodeCount: number;\n}\n\n/**\n * A renderable resolved against the AssetRegistry for the current frame:\n * its source snapshot, the GPU mesh handles, the row index used to correlate\n * transparent-sort output back to this entry, and an optional per-material\n * renderState override (bug-20260527-renderstate-pipeline-dispatch-gap D-4).\n * Module-scoped so RenderPipelineContext can type `validated` / `validatedOrdered`\n * without a dynamic type escape (was a recordFrame-local interface before F-4).\n */\nexport interface ValidatedRenderable {\n readonly source: RenderableSnapshot;\n /** World that owns the renderable's asset handles and material snapshots. */\n readonly world?: World;\n readonly mesh: MeshGpuHandles;\n readonly renderableIndex: number;\n readonly renderState: MaterialRenderState | undefined;\n /**\n * Canonical authored material-variant request folded from the selected\n * pass's `defines` map. Capability axes are merged at the renderer seam;\n * this field carries only the pass-owned selection into record.\n */\n readonly variantSet?: string | undefined;\n /**\n * w10: per-pass stencil reference value folded from\n * {@link DispatchEntry.stencilReference} (draw-call dynamic state). The draw\n * loop calls `pass.setStencilReference(stencilReference ?? 0)` before each\n * draw; `undefined` falls back to the WebGPU default 0 (semantic no-op).\n */\n readonly stencilReference: number | undefined;\n}\n\n/**\n * Cross-frame validity token for the directional shadow atlas.\n *\n * The atlas is a render target, not authored state. Keeping its validity\n * token beside the other RenderFrameState caches lets recordFrame skip a\n * static caster submission without teaching the public render-pipeline\n * contract about caching. Every source that can change caster depth is part\n * of the token: world mutation, asset catalogue, graph/target identity, CSM\n * matrices, and the GPU mesh-residency epoch.\n *\n * @internal\n */\nexport interface DirectionalShadowCache {\n readonly worlds: readonly World[];\n readonly worldStateTokens: readonly DirectionalShadowWorldState[];\n readonly assetCatalogEpoch: number;\n readonly pipelineHandle: number;\n readonly graphTopologyKey: string | null;\n readonly shadowMapSize: number;\n readonly cascadeCount: number;\n readonly lightViewProj: readonly Float32Array[];\n readonly meshResidencyEpoch: number;\n}\n\n/**\n * ECS state that can change the contents of a directional shadow map.\n *\n * The ECS-owned RenderReadLease is the only render-facing mutation boundary.\n * Its cursor is intentionally conservative: any published World change\n * invalidates the cached atlas, while render never reaches through World for\n * component-clock storage.\n */\nexport interface DirectionalShadowWorldState {\n readonly worldIdentity: string;\n readonly changeCursor: number;\n}\n"]}