@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,66 @@
1
+ import type { TextureFormat } from '@forgeax/engine-rhi';
2
+ import type { TemporalFrame, TemporalResetReason } from './temporal/frame';
3
+
4
+ export type TemporalInspectionStatus =
5
+ | 'authored-off'
6
+ | 'available'
7
+ | 'capability-unavailable'
8
+ | 'temporal-degraded';
9
+
10
+ export interface TemporalInspection {
11
+ readonly status: TemporalInspectionStatus;
12
+ readonly resetReason: TemporalResetReason;
13
+ readonly historyEpoch: number;
14
+ readonly deviceEpoch: number;
15
+ readonly backend: string | undefined;
16
+ readonly format: TextureFormat | undefined;
17
+ readonly limits: Readonly<{
18
+ readonly maxTextureDimension2D?: number;
19
+ readonly maxTextureDimension3D?: number;
20
+ readonly maxTextureArrayLayers?: number;
21
+ }>;
22
+ readonly compute: 'available' | 'unavailable';
23
+ readonly storage: 'available' | 'unavailable';
24
+ }
25
+
26
+ export interface TemporalInspectionInput {
27
+ readonly authored: boolean;
28
+ readonly capability: 'available' | 'unavailable';
29
+ readonly degraded?: boolean;
30
+ readonly frame?: TemporalFrame;
31
+ readonly backend?: string;
32
+ readonly format?: TextureFormat;
33
+ readonly limits?: Readonly<Record<string, number>>;
34
+ }
35
+
36
+ export function inspectTemporalFrame(input: TemporalInspectionInput): TemporalInspection {
37
+ const frame = input.frame;
38
+ const status: TemporalInspectionStatus = !input.authored
39
+ ? 'authored-off'
40
+ : input.capability === 'unavailable'
41
+ ? 'capability-unavailable'
42
+ : input.degraded === true
43
+ ? 'temporal-degraded'
44
+ : 'available';
45
+ return Object.freeze({
46
+ status,
47
+ resetReason: frame?.resetReason ?? 'none',
48
+ historyEpoch: frame?.historyEpoch ?? 0,
49
+ deviceEpoch: frame?.deviceEpoch ?? 0,
50
+ backend: input.backend,
51
+ format: input.format,
52
+ limits: Object.freeze({
53
+ ...(typeof input.limits?.maxTextureDimension2D === 'number'
54
+ ? { maxTextureDimension2D: input.limits.maxTextureDimension2D }
55
+ : {}),
56
+ ...(typeof input.limits?.maxTextureDimension3D === 'number'
57
+ ? { maxTextureDimension3D: input.limits.maxTextureDimension3D }
58
+ : {}),
59
+ ...(typeof input.limits?.maxTextureArrayLayers === 'number'
60
+ ? { maxTextureArrayLayers: input.limits.maxTextureArrayLayers }
61
+ : {}),
62
+ }),
63
+ compute: input.capability === 'available' ? 'available' : 'unavailable',
64
+ storage: input.capability === 'available' ? 'available' : 'unavailable',
65
+ });
66
+ }
@@ -0,0 +1,222 @@
1
+ import { err, ok } from '@forgeax/engine-types';
2
+ import type { RenderError } from './errors/render.js';
3
+ import { FrameReceiptStaleError } from './errors/render.js';
4
+ import type {
5
+ GpuPassTimingObservation,
6
+ GpuPassTimingReason,
7
+ GpuPassTimingRef,
8
+ } from './record/gpu-pass-timing/index.js';
9
+ import type {
10
+ FrameObservationRequest,
11
+ FrameReceipt,
12
+ FrameReceiptObservation,
13
+ RenderResult,
14
+ } from './render-contract.js';
15
+
16
+ /**
17
+ * Internal Render producer callback; public callers receive only frozen facts.
18
+ * The callback is receipt-bound and never exposes RHI handles or query state.
19
+ */
20
+ export type GpuPassTimingObservationSource = () => Promise<GpuPassTimingObservation>;
21
+
22
+ /**
23
+ * Receipt observation seam for the public `draw()` then `observe()` route.
24
+ * `status`, `completeness`, and `latestKnownGood` are separate projections;
25
+ * pass duration is not frame latency. Timing errors retain the closed code,
26
+ * expected, hint, and detail fields so a caller can follow producer recovery.
27
+ * See the bounded contract and fail-closed validator in the GPU pass timing
28
+ * record and benchmark modules.
29
+ */
30
+ export interface GpuPassTimingObservationStore {
31
+ register(receipt: FrameReceipt, source?: GpuPassTimingObservationSource): void;
32
+ observe(
33
+ receipt: FrameReceipt,
34
+ request: FrameObservationRequest,
35
+ ): Promise<RenderResult<FrameReceiptObservation, RenderError>>;
36
+ inspect(): {
37
+ readonly status: GpuPassTimingObservation['status'];
38
+ readonly completeness: 'complete' | 'partial' | 'unavailable' | 'failed';
39
+ readonly latestKnownGood?: GpuPassTimingRef | undefined;
40
+ };
41
+ }
42
+
43
+ /**
44
+ * Preserve the receipt observation contract without constructing the timing
45
+ * retention store when GPU timing was not opted in.
46
+ */
47
+ export async function observeGpuPassTimingDisabled(
48
+ receipt: FrameReceipt,
49
+ request: FrameObservationRequest,
50
+ currentDeviceGeneration: () => number,
51
+ ): Promise<RenderResult<FrameReceiptObservation, RenderError>> {
52
+ const completed = await receipt.completed;
53
+ if (!completed.ok) return completed;
54
+ if (receipt.deviceGeneration !== currentDeviceGeneration()) {
55
+ return err(
56
+ new FrameReceiptStaleError({
57
+ frameId: receipt.frameId,
58
+ receiptGeneration: receipt.deviceGeneration,
59
+ currentGeneration: currentDeviceGeneration(),
60
+ }),
61
+ );
62
+ }
63
+ const base = {
64
+ frameId: receipt.frameId,
65
+ deviceGeneration: receipt.deviceGeneration,
66
+ include: freeze([...request.include]),
67
+ } as const;
68
+ if (!request.include.includes('timings')) return ok(freeze(base));
69
+ return ok(freeze({ ...base, timings: notEnabled }));
70
+ }
71
+
72
+ interface ReceiptRecord {
73
+ readonly source: GpuPassTimingObservationSource;
74
+ }
75
+
76
+ interface ObservationStoreOptions {
77
+ readonly retentionFrames: number;
78
+ readonly currentDeviceGeneration: () => number;
79
+ }
80
+
81
+ const notEnabled: GpuPassTimingObservation = {
82
+ status: 'unavailable',
83
+ reason: {
84
+ code: 'gpu-timing-not-enabled',
85
+ expected: 'gpuPassTiming is enabled in RendererOptions',
86
+ hint: 'set RendererOptions.gpuPassTiming to opt into bounded timing facts',
87
+ detail: {},
88
+ },
89
+ capability: { timestampQuery: false, timestampPeriodNanoseconds: null },
90
+ };
91
+
92
+ function failedObservation(
93
+ error: GpuPassTimingReason,
94
+ latestKnownGood?: GpuPassTimingRef,
95
+ ): GpuPassTimingObservation {
96
+ return {
97
+ status: 'failed',
98
+ error,
99
+ ...(latestKnownGood === undefined ? {} : { latestKnownGood }),
100
+ };
101
+ }
102
+
103
+ function retentionExpired(): GpuPassTimingObservation {
104
+ return failedObservation({
105
+ code: 'timing-retention-expired',
106
+ expected: 'the requested receipt remains inside the bounded timing retention window',
107
+ hint: 'observe a newer receipt before the retention window expires',
108
+ detail: {},
109
+ });
110
+ }
111
+
112
+ function freeze<T>(value: T): T {
113
+ if (value === null || typeof value !== 'object' || Object.isFrozen(value)) return value;
114
+ Object.freeze(value);
115
+ for (const child of Object.values(value as Record<string, unknown>)) freeze(child);
116
+ return value;
117
+ }
118
+
119
+ /**
120
+ * Keep receipt-bound observations in a bounded Render-owned retention ring.
121
+ * This helper does not expose RHI handles, query objects, or a timing
122
+ * controller; the public lifecycle remains `draw` then `observe`.
123
+ * Omitting `timings` from the observation request does not materialize facts.
124
+ */
125
+ export function createGpuPassTimingObservationStore(
126
+ options: ObservationStoreOptions,
127
+ ): GpuPassTimingObservationStore {
128
+ const records = new Map<FrameReceipt, ReceiptRecord>();
129
+ const order: FrameReceipt[] = [];
130
+ const expired = new WeakSet<FrameReceipt>();
131
+ let latestKnownGood: GpuPassTimingRef | undefined;
132
+ let currentStatus: GpuPassTimingObservation['status'] = 'unavailable';
133
+ let currentCompleteness: 'complete' | 'partial' | 'unavailable' | 'failed' = 'unavailable';
134
+
135
+ const evict = (): void => {
136
+ while (order.length > options.retentionFrames) {
137
+ const old = order.shift();
138
+ if (old === undefined) break;
139
+ records.delete(old);
140
+ expired.add(old);
141
+ }
142
+ };
143
+
144
+ return {
145
+ register(receipt, source): void {
146
+ records.set(receipt, { source: source ?? (async () => notEnabled) });
147
+ order.push(receipt);
148
+ evict();
149
+ },
150
+ async observe(receipt, request): Promise<RenderResult<FrameReceiptObservation, RenderError>> {
151
+ const record = records.get(receipt);
152
+ if (record === undefined) {
153
+ if (expired.has(receipt) && request.include.includes('timings')) {
154
+ return ok(
155
+ freeze({
156
+ frameId: receipt.frameId,
157
+ deviceGeneration: receipt.deviceGeneration,
158
+ include: freeze([...request.include]),
159
+ timings: retentionExpired(),
160
+ }),
161
+ );
162
+ }
163
+ return err(
164
+ new FrameReceiptStaleError({
165
+ frameId: receipt.frameId,
166
+ receiptGeneration: receipt.deviceGeneration,
167
+ currentGeneration: options.currentDeviceGeneration(),
168
+ }),
169
+ );
170
+ }
171
+ const completed = await receipt.completed;
172
+ if (!completed.ok) return completed;
173
+ if (receipt.deviceGeneration !== options.currentDeviceGeneration()) {
174
+ return err(
175
+ new FrameReceiptStaleError({
176
+ frameId: receipt.frameId,
177
+ receiptGeneration: receipt.deviceGeneration,
178
+ currentGeneration: options.currentDeviceGeneration(),
179
+ }),
180
+ );
181
+ }
182
+ const base = {
183
+ frameId: receipt.frameId,
184
+ deviceGeneration: receipt.deviceGeneration,
185
+ include: freeze([...request.include]),
186
+ } as const;
187
+ if (!request.include.includes('timings')) return ok(freeze(base));
188
+ let timings: GpuPassTimingObservation;
189
+ try {
190
+ timings = await record.source();
191
+ } catch (cause) {
192
+ timings = failedObservation(
193
+ {
194
+ code: 'timestamp-readback-failed',
195
+ expected: 'the Render-owned timing observation completes without throwing',
196
+ hint: 'observe a later receipt and inspect latestKnownGood',
197
+ detail: { frameId: receipt.frameId },
198
+ cause: { message: cause instanceof Error ? cause.message : String(cause) },
199
+ },
200
+ latestKnownGood,
201
+ );
202
+ }
203
+ currentStatus = timings.status;
204
+ currentCompleteness = timings.status;
205
+ if (timings.status === 'complete') {
206
+ latestKnownGood = {
207
+ frameId: timings.frame.frameId,
208
+ deviceGeneration: timings.frame.deviceGeneration,
209
+ graphGeneration: timings.frame.graphGeneration,
210
+ };
211
+ }
212
+ return ok(freeze({ ...base, timings }));
213
+ },
214
+ inspect() {
215
+ return freeze({
216
+ status: currentStatus,
217
+ completeness: currentCompleteness,
218
+ latestKnownGood,
219
+ });
220
+ },
221
+ };
222
+ }
@@ -16,6 +16,7 @@ import {
16
16
  PointLight,
17
17
  PointLightShadow,
18
18
  PostProcessParams,
19
+ ReflectionProbe,
19
20
  SkyboxBackground,
20
21
  Skylight,
21
22
  SpotLight,
@@ -30,8 +31,14 @@ import {
30
31
  } from '../gpu-driven/batch-topology';
31
32
  import { GpuScene } from '../gpu-scene';
32
33
  import type { PersistentRenderSceneInspection } from '../inspection-types';
34
+ import { fingerprintNumericArray, InstanceBoundsCache } from '../instances-derived-bounds';
33
35
  import type { PointsLinesInspection } from '../points-lines/inspection';
34
36
  import type { PointsLinesRetainedSnapshot } from '../points-lines/snapshot';
37
+ import {
38
+ type ReflectionProbeFact,
39
+ ReflectionProbeProjection,
40
+ type ReflectionProbeSelectionResult,
41
+ } from '../reflection/projection';
35
42
  import type { CameraSnapshot } from '../render-contract';
36
43
  import type {
37
44
  DispatchEntry,
@@ -47,6 +54,7 @@ import {
47
54
  type TransmissionDemand,
48
55
  type TransmissionDemandOperation,
49
56
  } from '../transmission/projection';
57
+ import { VolumetricFog } from '../volume/component';
50
58
  import type {
51
59
  RenderSceneApplyResult,
52
60
  RenderSceneBounds,
@@ -219,6 +227,22 @@ function worldBounds(snapshot: RenderableSnapshot): RenderSceneBounds | undefine
219
227
  return { min, max };
220
228
  }
221
229
 
230
+ function boundsFromArray(bounds: ArrayLike<number>): RenderSceneBounds | undefined {
231
+ if (bounds.length < 6) return undefined;
232
+ const min: [number, number, number] = [Number(bounds[0]), Number(bounds[1]), Number(bounds[2])];
233
+ const max: [number, number, number] = [Number(bounds[3]), Number(bounds[4]), Number(bounds[5])];
234
+ if (
235
+ !min.every(Number.isFinite) ||
236
+ !max.every(Number.isFinite) ||
237
+ min[0] > max[0] ||
238
+ min[1] > max[1] ||
239
+ min[2] > max[2]
240
+ ) {
241
+ return undefined;
242
+ }
243
+ return { min, max };
244
+ }
245
+
222
246
  /**
223
247
  * The renderer's single rebuildable CPU scene authority.
224
248
  *
@@ -240,6 +264,7 @@ export class RenderScene {
240
264
  // derived value by snapshot identity so repeated view queries do not redo
241
265
  // the eight-corner transform for every view in the same frame.
242
266
  private readonly worldBoundsCache = new WeakMap<RenderableSnapshot, RenderSceneBounds | null>();
267
+ private readonly instanceBoundsCache = new InstanceBoundsCache();
243
268
  private orderedSlots: number[] = [];
244
269
  private materialized: readonly RenderableSnapshot[] | undefined;
245
270
  private slotSnapshot: readonly RenderSceneSlot[] | undefined;
@@ -551,11 +576,7 @@ export class RenderScene {
551
576
  const record = this.slots[slot];
552
577
  if (record === undefined) continue;
553
578
  const snapshot = record.snapshot;
554
- let candidate = this.worldBoundsCache.get(snapshot);
555
- if (candidate === undefined && !this.worldBoundsCache.has(snapshot)) {
556
- candidate = worldBounds(snapshot) ?? null;
557
- this.worldBoundsCache.set(snapshot, candidate);
558
- }
579
+ const candidate = this.cullingWorldBounds(snapshot);
559
580
  if (candidate === null || candidate === undefined) continue;
560
581
  if (intersects(candidate, bounds)) records.push(record);
561
582
  }
@@ -628,6 +649,7 @@ export class RenderScene {
628
649
 
629
650
  private release(record: RenderSceneSlot): void {
630
651
  this.slots[record.slot] = undefined;
652
+ this.instanceBoundsCache.invalidate(record.entityKey, record.worldId);
631
653
  this.unindexMaterials(record);
632
654
  const entities = this.slotsByWorld.get(record.worldId);
633
655
  entities?.delete(record.entityKey);
@@ -705,6 +727,41 @@ export class RenderScene {
705
727
  this.materialized = undefined;
706
728
  this.slotSnapshot = undefined;
707
729
  }
730
+
731
+ /**
732
+ * Return the CPU culling projection for one retained snapshot. Instances
733
+ * use a renderer-derived union, while the GPU scene keeps the mesh-local
734
+ * bounds for its independent per-instance visibility pass. Unknown or
735
+ * empty instance facts deliberately return undefined (conservative no-cull).
736
+ */
737
+ cullingWorldBounds(snapshot: RenderableSnapshot): RenderSceneBounds | undefined {
738
+ let candidate = this.worldBoundsCache.get(snapshot);
739
+ if (candidate !== undefined || this.worldBoundsCache.has(snapshot)) {
740
+ return candidate ?? undefined;
741
+ }
742
+ if (snapshot.instances !== undefined) {
743
+ const instanceCount = snapshot.instances.instanceCount;
744
+ if (instanceCount === 0 || snapshot.localAabb === undefined) {
745
+ this.worldBoundsCache.set(snapshot, null);
746
+ return undefined;
747
+ }
748
+ const derived = this.instanceBoundsCache.get({
749
+ worldId: snapshot.worldId,
750
+ entityKey: snapshot.entityKey,
751
+ meshGeneration: fingerprintNumericArray(snapshot.localAabb) ^ (snapshot.assetHandle >>> 0),
752
+ transformGeneration: fingerprintNumericArray(snapshot.transform.world),
753
+ matrixGeneration: snapshot.instances.archVersion,
754
+ meshAabb: snapshot.localAabb,
755
+ entityWorld: snapshot.transform.world,
756
+ transforms: snapshot.instances.transforms,
757
+ });
758
+ candidate = boundsFromArray(derived ?? []);
759
+ } else {
760
+ candidate = worldBounds(snapshot);
761
+ }
762
+ this.worldBoundsCache.set(snapshot, candidate ?? null);
763
+ return candidate;
764
+ }
708
765
  }
709
766
 
710
767
  function readTransformWorld(world: World, entity: EntityHandle): Float32Array | undefined {
@@ -761,6 +818,8 @@ const RENDER_RELEVANT_COMPONENT_IDS = new Set([
761
818
  componentId(Skylight),
762
819
  componentId(SkyboxBackground),
763
820
  componentId(PostProcessParams),
821
+ componentId(ReflectionProbe),
822
+ componentId(VolumetricFog),
764
823
  ]);
765
824
 
766
825
  const NON_RENDERABLE_TRANSFORM_CONSUMERS = [
@@ -808,6 +867,7 @@ function cameraFrusta(cameras: readonly CameraSnapshot[]): readonly Float32Array
808
867
  function cullPersistentFrame(
809
868
  frame: ExtractedFrame,
810
869
  candidates: readonly RenderableSnapshot[],
870
+ boundsOf: (snapshot: RenderableSnapshot) => RenderSceneBounds | undefined = worldBounds,
811
871
  ): ExtractedFrame {
812
872
  const planes = cameraFrusta(frame.cameras);
813
873
  const visible: RenderableSnapshot[] = [];
@@ -820,10 +880,16 @@ function cullPersistentFrame(
820
880
  const candidate = candidates[index];
821
881
  if (candidate === undefined) continue;
822
882
  let isVisible = true;
823
- if (candidate.localAabb !== undefined) {
883
+ const candidateBounds = boundsOf(candidate);
884
+ if (candidateBounds !== undefined) {
824
885
  total += 1;
825
886
  const worldAabb = box3.create();
826
- box3.transformBox3(worldAabb, candidate.localAabb, candidate.transform.world);
887
+ worldAabb[0] = candidateBounds.min[0] ?? 0;
888
+ worldAabb[1] = candidateBounds.min[1] ?? 0;
889
+ worldAabb[2] = candidateBounds.min[2] ?? 0;
890
+ worldAabb[3] = candidateBounds.max[0] ?? 0;
891
+ worldAabb[4] = candidateBounds.max[1] ?? 0;
892
+ worldAabb[5] = candidateBounds.max[2] ?? 0;
827
893
  isVisible = planes.length === 0;
828
894
  for (const cameraPlanes of planes) {
829
895
  if (
@@ -877,6 +943,7 @@ export class PersistentRenderScene {
877
943
  | { readonly projection: RenderScene; readonly capture: RenderSceneSubmissionCapture }
878
944
  | undefined;
879
945
  private pointsLinesInspections: readonly PointsLinesInspection[] = [];
946
+ private readonly reflectionProbes = new ReflectionProbeProjection();
880
947
 
881
948
  constructor(private readonly options: PersistentRenderSceneOptions = {}) {}
882
949
 
@@ -885,6 +952,37 @@ export class PersistentRenderScene {
885
952
  return this.materialSnapshotCaches;
886
953
  }
887
954
 
955
+ /** Project probe facts and per-primitive selection into the persistent scene owner. */
956
+ projectReflectionProbes(probes: readonly ReflectionProbeFact[]): void {
957
+ const projection = this.composition?.projection;
958
+ if (projection === undefined) return;
959
+ const primitives = projection
960
+ .slotsSnapshot()
961
+ .map((slot) => {
962
+ const resolved = worldBounds(slot.snapshot);
963
+ if (resolved === undefined || resolved === null) return undefined;
964
+ return {
965
+ worldId: slot.worldId,
966
+ entityKey: slot.entityKey,
967
+ center: [
968
+ (resolved.min[0] + resolved.max[0]) / 2,
969
+ (resolved.min[1] + resolved.max[1]) / 2,
970
+ (resolved.min[2] + resolved.max[2]) / 2,
971
+ ] as [number, number, number],
972
+ };
973
+ })
974
+ .filter((primitive): primitive is NonNullable<typeof primitive> => primitive !== undefined);
975
+ this.reflectionProbes.update(probes, primitives);
976
+ }
977
+
978
+ reflectionProbeSelection(worldId: number, entityKey: number): ReflectionProbeSelectionResult {
979
+ return this.reflectionProbes.selection(worldId, entityKey);
980
+ }
981
+
982
+ reflectionProbeProjection(): ReturnType<ReflectionProbeProjection['snapshot']> {
983
+ return this.reflectionProbes.snapshot();
984
+ }
985
+
888
986
  extractComposition(
889
987
  worlds: readonly World[],
890
988
  owner: { readonly cameraOwner: number; readonly resourceOwner: number },
@@ -1193,6 +1291,7 @@ export class PersistentRenderScene {
1193
1291
  transmissionDemand.apply(transmissionDemandOperations(candidateFrame.renderables));
1194
1292
  const topology = new BatchTopology();
1195
1293
  topology.rebuild(projection.slotsSnapshot());
1294
+ this.projectReflectionProbes(candidateFrame.reflectionProbes ?? []);
1196
1295
  const token = {};
1197
1296
  const entry: PersistentCompositionEntry = {
1198
1297
  token,
@@ -1222,7 +1321,11 @@ export class PersistentRenderScene {
1222
1321
  const candidates = projection
1223
1322
  .materialize()
1224
1323
  .map((snapshot) => projection.withTemporal(snapshot));
1225
- return cullPersistentFrame({ ...candidateFrame, renderables: [...candidates] }, candidates);
1324
+ return cullPersistentFrame(
1325
+ { ...candidateFrame, renderables: [...candidates] },
1326
+ candidates,
1327
+ (snapshot) => projection.cullingWorldBounds(snapshot),
1328
+ );
1226
1329
  }
1227
1330
 
1228
1331
  /** Internal topology fact; the renderer does not expose demand as public API. */
@@ -1244,10 +1347,15 @@ export class PersistentRenderScene {
1244
1347
  buildCandidateFrame: () => ExtractedFrame,
1245
1348
  ): ExtractedFrame {
1246
1349
  const candidateFrame = buildCandidateFrame();
1350
+ this.projectReflectionProbes(candidateFrame.reflectionProbes ?? []);
1247
1351
  const candidates = entry.projection
1248
1352
  .materialize()
1249
1353
  .map((snapshot) => entry.projection.withTemporal(snapshot));
1250
- return cullPersistentFrame({ ...candidateFrame, renderables: [...candidates] }, candidates);
1354
+ return cullPersistentFrame(
1355
+ { ...candidateFrame, renderables: [...candidates] },
1356
+ candidates,
1357
+ (snapshot) => entry.projection.cullingWorldBounds(snapshot),
1358
+ );
1251
1359
  }
1252
1360
 
1253
1361
  private syncGpuOwner(
@@ -3,8 +3,8 @@
3
3
  // feat-20260612-point-light-shadows-urp-hdrp M3 / T-M3-1 (plan-strategy §D-1).
4
4
  //
5
5
  // Lazy-allocated `texture_depth_cube_array` (depth32float) + per-face 2D
6
- // TextureView cache. One layer per shadow-casting PointLight (cap = 4 enforced
7
- // by the PointLightShadow ECS component cardinality). Total texel footprint at
6
+ // TextureView cache. One layer per admitted shadow-casting PointLight; the
7
+ // renderer owns the bounded atlas admission policy. Total texel footprint at
8
8
  // the 512x512 default = 4 layers x 6 faces x 512 x 512 x 4 B = 24 MiB.
9
9
  //
10
10
  // Why a separate module (vs inlining into createRenderer / pipelineState):
@@ -53,19 +53,19 @@ import { GPU_TEXTURE_USAGE_RENDER_ATTACHMENT_AND_TEXTURE_BINDING } from './gpu-t
53
53
  export interface ShadowAtlasOptions {
54
54
  /** Per-face square size in pixels. Default 512 (PointLightShadow.mapSize default). */
55
55
  readonly faceSize?: number;
56
- /** Number of cube layers. Default 4 (= PointLightShadow cardinality cap). */
56
+ /** Number of cube layers. Default is the renderer atlas admission capacity. */
57
57
  readonly layers?: number;
58
58
  }
59
59
 
60
60
  /** Default per-face square size in pixels. Mirrors PointLightShadow.mapSize default. */
61
61
  export const SHADOW_ATLAS_DEFAULT_FACE_SIZE = 512;
62
- /** Default cube layer count. Mirrors PointLightShadow cardinality cap. */
62
+ /** Default cube layer count. Renderer-owned point-shadow admission capacity. */
63
63
  export const SHADOW_ATLAS_DEFAULT_LAYERS = 4;
64
64
 
65
65
  /**
66
66
  * Cube-array depth atlas for point-light shadows.
67
67
  *
68
- * Shape: `texture_depth_cube_array` (depth32float) with `layers = 4` and per-
68
+ * Shape: `texture_depth_cube_array` (depth32float) with `layers` and per-
69
69
  * face size `faceSize x faceSize`. The cube_array sampling view feeds
70
70
  * `@group(0) @binding(5)` in URP shaders; the per-face 2D view feeds the
71
71
  * shadow-caster render pass attachment for one (layer, face) at a time
@@ -106,6 +106,7 @@ export const SSAO_UNIFORM_BYTES = UNIFORM_BYTES;
106
106
  // recompile across enable/disable.
107
107
 
108
108
  interface SsaoFallbackResources {
109
+ readonly device: RenderSystemRuntime['device'];
109
110
  readonly texture: Texture;
110
111
  readonly view: TextureView;
111
112
  readonly sampler: Sampler;
@@ -126,7 +127,8 @@ export function getOrCreateSsaoFallbackTexture(
126
127
  runtime: RenderSystemRuntime,
127
128
  ): SsaoFallbackResources | null {
128
129
  const cached = fallbackCache.get(runtime);
129
- if (cached !== undefined) return cached;
130
+ if (cached !== undefined && cached.device === runtime.device) return cached;
131
+ if (cached !== undefined) fallbackCache.delete(runtime);
130
132
 
131
133
  const device = runtime.device;
132
134
  const texRes = device.createTexture({
@@ -191,6 +193,7 @@ export function getOrCreateSsaoFallbackTexture(
191
193
  }
192
194
 
193
195
  const resources: SsaoFallbackResources = {
196
+ device,
194
197
  texture: texRes.value,
195
198
  view: viewRes.value,
196
199
  sampler: samplerRes.value,
@@ -205,6 +208,8 @@ export function getOrCreateSsaoFallbackTexture(
205
208
  * Fields are readonly per charter P5: one resource, one owner.
206
209
  */
207
210
  export interface SsaoBuffers {
211
+ /** Device identity that owns every handle in this bundle. */
212
+ readonly device: RenderSystemRuntime['device'];
208
213
  /** 64 padded vec4 uniform buffer for hemisphere samples. */
209
214
  readonly kernelBuffer: Buffer;
210
215
  readonly kernelBytes: number;
@@ -229,7 +234,8 @@ const cache = new WeakMap<RenderSystemRuntime, SsaoBuffers>();
229
234
  */
230
235
  export function getOrCreateSsaoBuffers(runtime: RenderSystemRuntime): SsaoBuffers | null {
231
236
  const cached = cache.get(runtime);
232
- if (cached !== undefined) return cached;
237
+ if (cached !== undefined && cached.device === runtime.device) return cached;
238
+ if (cached !== undefined) cache.delete(runtime);
233
239
 
234
240
  const device = runtime.device;
235
241
 
@@ -322,6 +328,7 @@ export function getOrCreateSsaoBuffers(runtime: RenderSystemRuntime): SsaoBuffer
322
328
  }
323
329
 
324
330
  const buffers: SsaoBuffers = {
331
+ device,
325
332
  kernelBuffer: kernelBufferRes.value,
326
333
  kernelBytes: SSAO_KERNEL_SAMPLE_COUNT * BYTES_PER_KERNEL_ELEMENT,
327
334
  noiseTexture: noiseTexRes.value,