@forgeax/engine-render 0.1.20 → 0.1.23

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 (645) hide show
  1. package/README.md +365 -29
  2. package/dist/assembly/backend-contract.d.ts +13 -0
  3. package/dist/assembly/backend-contract.d.ts.map +1 -1
  4. package/dist/assembly/device-feature-admission.d.ts +18 -0
  5. package/dist/assembly/device-feature-admission.d.ts.map +1 -0
  6. package/dist/assembly/directional-shadow-inspection.d.ts +38 -0
  7. package/dist/assembly/directional-shadow-inspection.d.ts.map +1 -0
  8. package/dist/assembly/factory.d.ts +30 -19
  9. package/dist/assembly/factory.d.ts.map +1 -1
  10. package/dist/assembly/host-contract.d.ts +19 -0
  11. package/dist/assembly/host-contract.d.ts.map +1 -1
  12. package/dist/assembly/render-target-host.d.ts +46 -0
  13. package/dist/assembly/render-target-host.d.ts.map +1 -0
  14. package/dist/assets/render-asset-port.d.ts.map +1 -1
  15. package/dist/authoring.mjs +4 -1
  16. package/dist/authoring.mjs.map +1 -1
  17. package/dist/capture/cube-views.d.ts +21 -0
  18. package/dist/capture/cube-views.d.ts.map +1 -0
  19. package/dist/capture/scheduler.d.ts +54 -0
  20. package/dist/capture/scheduler.d.ts.map +1 -0
  21. package/dist/{chunk-PCHBPYML.mjs → chunk-4L3P6DT5.mjs} +2 -2
  22. package/dist/chunk-4L3P6DT5.mjs.map +1 -0
  23. package/dist/{chunk-VFPLTKCU.mjs → chunk-4NPUASZR.mjs} +15 -4
  24. package/dist/chunk-4NPUASZR.mjs.map +1 -0
  25. package/dist/chunk-DIYW3M5D.mjs +5085 -0
  26. package/dist/chunk-DIYW3M5D.mjs.map +1 -0
  27. package/dist/chunk-FBC4R44J.mjs +1003 -0
  28. package/dist/chunk-FBC4R44J.mjs.map +1 -0
  29. package/dist/{chunk-CDTWTHZR.mjs → chunk-FF7K3FSO.mjs} +474 -168
  30. package/dist/chunk-FF7K3FSO.mjs.map +1 -0
  31. package/dist/chunk-HZUPSYW7.mjs +1104 -0
  32. package/dist/chunk-HZUPSYW7.mjs.map +1 -0
  33. package/dist/{chunk-TBQJOK2L.mjs → chunk-QODA5UKU.mjs} +321 -269
  34. package/dist/chunk-QODA5UKU.mjs.map +1 -0
  35. package/dist/chunk-YALPS44A.mjs +296 -0
  36. package/dist/chunk-YALPS44A.mjs.map +1 -0
  37. package/dist/{chunk-4XINDLAY.mjs → chunk-ZUWKAZ2I.mjs} +1963 -2178
  38. package/dist/chunk-ZUWKAZ2I.mjs.map +1 -0
  39. package/dist/cluster-binner.d.ts +5 -0
  40. package/dist/cluster-binner.d.ts.map +1 -1
  41. package/dist/components/camera.d.ts +1 -0
  42. package/dist/components/camera.d.ts.map +1 -1
  43. package/dist/components/cube-camera.d.ts +20 -0
  44. package/dist/components/cube-camera.d.ts.map +1 -0
  45. package/dist/components/directional-light.d.ts +11 -2
  46. package/dist/components/directional-light.d.ts.map +1 -1
  47. package/dist/components/directional-shadow-filter.d.ts +25 -0
  48. package/dist/components/directional-shadow-filter.d.ts.map +1 -0
  49. package/dist/components/index.d.ts +5 -0
  50. package/dist/components/index.d.ts.map +1 -1
  51. package/dist/components/light-helpers.d.ts +4 -0
  52. package/dist/components/light-helpers.d.ts.map +1 -1
  53. package/dist/components/light-probe.d.ts +9 -0
  54. package/dist/components/light-probe.d.ts.map +1 -0
  55. package/dist/components/rect-area-light.d.ts +12 -0
  56. package/dist/components/rect-area-light.d.ts.map +1 -0
  57. package/dist/components/reflection-probe.d.ts +17 -0
  58. package/dist/components/reflection-probe.d.ts.map +1 -0
  59. package/dist/components/scene-instance.d.ts +4 -0
  60. package/dist/components/scene-instance.d.ts.map +1 -1
  61. package/dist/components/spot-light.d.ts +3 -0
  62. package/dist/components/spot-light.d.ts.map +1 -1
  63. package/dist/construct-renderer.mjs +14836 -9620
  64. package/dist/construct-renderer.mjs.map +1 -1
  65. package/dist/device/gpu-residency.d.ts +8 -1
  66. package/dist/device/gpu-residency.d.ts.map +1 -1
  67. package/dist/device/lifecycle-transaction.d.ts +2 -0
  68. package/dist/device/lifecycle-transaction.d.ts.map +1 -0
  69. package/dist/environment/frame.d.ts.map +1 -1
  70. package/dist/environment/lifecycle.d.ts +5 -0
  71. package/dist/environment/lifecycle.d.ts.map +1 -1
  72. package/dist/errors/gpu-driven.d.ts +26 -0
  73. package/dist/errors/gpu-driven.d.ts.map +1 -0
  74. package/dist/errors/index.d.ts +1 -0
  75. package/dist/errors/index.d.ts.map +1 -1
  76. package/dist/errors/recover.d.ts +9 -1
  77. package/dist/errors/recover.d.ts.map +1 -1
  78. package/dist/errors/render.d.ts +166 -93
  79. package/dist/errors/render.d.ts.map +1 -1
  80. package/dist/extract/directional-shadow-projection.d.ts +23 -0
  81. package/dist/extract/directional-shadow-projection.d.ts.map +1 -0
  82. package/dist/extract/environment.d.ts +14 -0
  83. package/dist/extract/environment.d.ts.map +1 -1
  84. package/dist/extract/gpu-driven.d.ts +50 -0
  85. package/dist/extract/gpu-driven.d.ts.map +1 -0
  86. package/dist/extract/visibility.d.ts.map +1 -1
  87. package/dist/features/fullscreen.d.ts.map +1 -1
  88. package/dist/features/targets.d.ts.map +1 -1
  89. package/dist/features/types.d.ts +10 -0
  90. package/dist/features/types.d.ts.map +1 -1
  91. package/dist/fullscreen-post-process-pass.d.ts +13 -2
  92. package/dist/fullscreen-post-process-pass.d.ts.map +1 -1
  93. package/dist/gpu-driven/batch-topology.d.ts +34 -0
  94. package/dist/gpu-driven/batch-topology.d.ts.map +1 -1
  95. package/dist/gpu-driven/prepared-draw.d.ts +63 -0
  96. package/dist/gpu-driven/prepared-draw.d.ts.map +1 -0
  97. package/dist/gpu-driven/production-raster.d.ts +97 -2
  98. package/dist/gpu-driven/production-raster.d.ts.map +1 -1
  99. package/dist/gpu-driven/view-gpu.d.ts +64 -1
  100. package/dist/gpu-driven/view-gpu.d.ts.map +1 -1
  101. package/dist/gpu-scene-schema.d.ts +14 -0
  102. package/dist/gpu-scene-schema.d.ts.map +1 -1
  103. package/dist/gpu-scene.d.ts +8 -1
  104. package/dist/gpu-scene.d.ts.map +1 -1
  105. package/dist/gpu-usage.d.ts +0 -1
  106. package/dist/gpu-usage.d.ts.map +1 -1
  107. package/dist/hdrp-buffers.d.ts +21 -19
  108. package/dist/hdrp-buffers.d.ts.map +1 -1
  109. package/dist/ibl/IblPipelineCache.d.ts +4 -0
  110. package/dist/ibl/IblPipelineCache.d.ts.map +1 -1
  111. package/dist/ibl/kernel-cache.d.ts +36 -0
  112. package/dist/ibl/kernel-cache.d.ts.map +1 -0
  113. package/dist/index.d.ts +39 -3
  114. package/dist/index.d.ts.map +1 -1
  115. package/dist/index.mjs +91 -6
  116. package/dist/index.mjs.map +1 -1
  117. package/dist/inspection-types.d.ts +214 -1
  118. package/dist/inspection-types.d.ts.map +1 -1
  119. package/dist/instances-derived-bounds.d.ts +50 -0
  120. package/dist/instances-derived-bounds.d.ts.map +1 -0
  121. package/dist/internal.d.ts +9 -0
  122. package/dist/internal.d.ts.map +1 -1
  123. package/dist/internal.mjs +23 -2
  124. package/dist/internal.mjs.map +1 -1
  125. package/dist/light-buffer-layout.d.ts +58 -140
  126. package/dist/light-buffer-layout.d.ts.map +1 -1
  127. package/dist/materials.d.ts.map +1 -1
  128. package/dist/mesh-material-bindings.d.ts +54 -0
  129. package/dist/mesh-material-bindings.d.ts.map +1 -1
  130. package/dist/pbr-pipeline.d.ts +38 -14
  131. package/dist/pbr-pipeline.d.ts.map +1 -1
  132. package/dist/pipeline/standard-forward-lane.d.ts +2 -1
  133. package/dist/pipeline/standard-forward-lane.d.ts.map +1 -1
  134. package/dist/pipeline/standard-lighting/errors.d.ts +4 -0
  135. package/dist/pipeline/standard-lighting/errors.d.ts.map +1 -0
  136. package/dist/pipeline/standard-lighting/graph.d.ts +24 -0
  137. package/dist/pipeline/standard-lighting/graph.d.ts.map +1 -0
  138. package/dist/pipeline/standard-lighting/inspection.d.ts +38 -0
  139. package/dist/pipeline/standard-lighting/inspection.d.ts.map +1 -0
  140. package/dist/pipeline/standard-lighting/layout.d.ts +28 -0
  141. package/dist/pipeline/standard-lighting/layout.d.ts.map +1 -0
  142. package/dist/pipeline/standard-lighting/light-frame.d.ts +33 -0
  143. package/dist/pipeline/standard-lighting/light-frame.d.ts.map +1 -0
  144. package/dist/pipeline/standard-lighting/membership.d.ts +14 -0
  145. package/dist/pipeline/standard-lighting/membership.d.ts.map +1 -0
  146. package/dist/pipeline/standard-lighting/prepare.d.ts +16 -0
  147. package/dist/pipeline/standard-lighting/prepare.d.ts.map +1 -0
  148. package/dist/pipeline/standard-lighting/topology.d.ts +45 -0
  149. package/dist/pipeline/standard-lighting/topology.d.ts.map +1 -0
  150. package/dist/pipeline/standard-lighting/transport.d.ts +36 -0
  151. package/dist/pipeline/standard-lighting/transport.d.ts.map +1 -0
  152. package/dist/pipeline/standard-pipeline.d.ts +4 -13
  153. package/dist/pipeline/standard-pipeline.d.ts.map +1 -1
  154. package/dist/pipeline/standard-post.d.ts.map +1 -1
  155. package/dist/pipeline/standard-profile.d.ts +5 -26
  156. package/dist/pipeline/standard-profile.d.ts.map +1 -1
  157. package/dist/pipeline-builder.d.ts +2 -0
  158. package/dist/pipeline-builder.d.ts.map +1 -1
  159. package/dist/pipeline-spec.d.ts +40 -7
  160. package/dist/pipeline-spec.d.ts.map +1 -1
  161. package/dist/plugin.d.ts.map +1 -1
  162. package/dist/point-shadow-inspection.d.ts +21 -0
  163. package/dist/point-shadow-inspection.d.ts.map +1 -0
  164. package/dist/prepare/extended-lighting/ltc-resources.d.ts +31 -0
  165. package/dist/prepare/extended-lighting/ltc-resources.d.ts.map +1 -0
  166. package/dist/prepare/extended-lighting/resources.d.ts +72 -0
  167. package/dist/prepare/extended-lighting/resources.d.ts.map +1 -0
  168. package/dist/prepare/extended-lighting/spot-modifiers.d.ts +42 -0
  169. package/dist/prepare/extended-lighting/spot-modifiers.d.ts.map +1 -0
  170. package/dist/prepare/extended-lighting/state.d.ts +24 -0
  171. package/dist/prepare/extended-lighting/state.d.ts.map +1 -0
  172. package/dist/record/frame-lighting.d.ts +33 -36
  173. package/dist/record/frame-lighting.d.ts.map +1 -1
  174. package/dist/record/frame-snapshot.d.ts +78 -24
  175. package/dist/record/frame-snapshot.d.ts.map +1 -1
  176. package/dist/record/frame-targets.d.ts +5 -0
  177. package/dist/record/frame-targets.d.ts.map +1 -1
  178. package/dist/record/frame.d.ts +16 -6
  179. package/dist/record/frame.d.ts.map +1 -1
  180. package/dist/record/gpu-pass-timing/contract.d.ts +96 -0
  181. package/dist/record/gpu-pass-timing/contract.d.ts.map +1 -0
  182. package/dist/record/gpu-pass-timing/errors.d.ts +18 -0
  183. package/dist/record/gpu-pass-timing/errors.d.ts.map +1 -0
  184. package/dist/record/gpu-pass-timing/index.d.ts +8 -0
  185. package/dist/record/gpu-pass-timing/index.d.ts.map +1 -0
  186. package/dist/record/gpu-pass-timing/parser.d.ts +11 -0
  187. package/dist/record/gpu-pass-timing/parser.d.ts.map +1 -0
  188. package/dist/record/gpu-pass-timing/session.d.ts +43 -0
  189. package/dist/record/gpu-pass-timing/session.d.ts.map +1 -0
  190. package/dist/record/gpu-timing.d.ts +20 -3
  191. package/dist/record/gpu-timing.d.ts.map +1 -1
  192. package/dist/record/helpers.d.ts +0 -20
  193. package/dist/record/helpers.d.ts.map +1 -1
  194. package/dist/record/main-pass-geometry.d.ts +6 -1
  195. package/dist/record/main-pass-geometry.d.ts.map +1 -1
  196. package/dist/record/main-pass-material.d.ts +25 -2
  197. package/dist/record/main-pass-material.d.ts.map +1 -1
  198. package/dist/record/main-pass-sprite-draws.d.ts.map +1 -1
  199. package/dist/record/main-pass.d.ts +2 -0
  200. package/dist/record/main-pass.d.ts.map +1 -1
  201. package/dist/record/mesh-ssbo.d.ts.map +1 -1
  202. package/dist/record/render-context.d.ts +98 -6
  203. package/dist/record/render-context.d.ts.map +1 -1
  204. package/dist/record/shadow-pass.d.ts.map +1 -1
  205. package/dist/record/skybox-post-pass.d.ts.map +1 -1
  206. package/dist/record/typed-frame-graph.d.ts +119 -4
  207. package/dist/record/typed-frame-graph.d.ts.map +1 -1
  208. package/dist/record/view-ubo.d.ts +7 -5
  209. package/dist/record/view-ubo.d.ts.map +1 -1
  210. package/dist/reflection/filter.d.ts +22 -0
  211. package/dist/reflection/filter.d.ts.map +1 -0
  212. package/dist/reflection/gpu-table.d.ts +25 -0
  213. package/dist/reflection/gpu-table.d.ts.map +1 -0
  214. package/dist/reflection/inspection.d.ts +10 -0
  215. package/dist/reflection/inspection.d.ts.map +1 -0
  216. package/dist/reflection/projection.d.ts +140 -0
  217. package/dist/reflection/projection.d.ts.map +1 -0
  218. package/dist/reflection/record-owner.d.ts +60 -0
  219. package/dist/reflection/record-owner.d.ts.map +1 -0
  220. package/dist/render-contract.d.ts +62 -14
  221. package/dist/render-contract.d.ts.map +1 -1
  222. package/dist/render-graph-primitives.d.ts +1 -0
  223. package/dist/render-graph-primitives.d.ts.map +1 -1
  224. package/dist/render-pipeline.d.ts +49 -2
  225. package/dist/render-pipeline.d.ts.map +1 -1
  226. package/dist/render-system-extract.d.ts +160 -31
  227. package/dist/render-system-extract.d.ts.map +1 -1
  228. package/dist/render-system.d.ts +28 -2
  229. package/dist/render-system.d.ts.map +1 -1
  230. package/dist/renderer.d.ts +43 -0
  231. package/dist/renderer.d.ts.map +1 -0
  232. package/dist/scene/probe-blend-record.d.ts +22 -0
  233. package/dist/scene/probe-blend-record.d.ts.map +1 -0
  234. package/dist/scene/probe-blend.d.ts +142 -0
  235. package/dist/scene/probe-blend.d.ts.map +1 -0
  236. package/dist/scene/render-scene-types.d.ts +0 -6
  237. package/dist/scene/render-scene-types.d.ts.map +1 -1
  238. package/dist/scene/render-scene.d.ts +117 -9
  239. package/dist/scene/render-scene.d.ts.map +1 -1
  240. package/dist/scene/visibility/bounds.d.ts +20 -0
  241. package/dist/scene/visibility/bounds.d.ts.map +1 -0
  242. package/dist/scene/visibility/budget.d.ts +16 -0
  243. package/dist/scene/visibility/budget.d.ts.map +1 -0
  244. package/dist/scene/visibility/facet.d.ts +73 -0
  245. package/dist/scene/visibility/facet.d.ts.map +1 -0
  246. package/dist/scene/visibility/gpu-frame-timing.d.ts +41 -0
  247. package/dist/scene/visibility/gpu-frame-timing.d.ts.map +1 -0
  248. package/dist/scene/visibility/gpu-lod.d.ts +46 -0
  249. package/dist/scene/visibility/gpu-lod.d.ts.map +1 -0
  250. package/dist/scene/visibility/inspection.d.ts +132 -0
  251. package/dist/scene/visibility/inspection.d.ts.map +1 -0
  252. package/dist/scene/visibility/lod-selector.d.ts +25 -0
  253. package/dist/scene/visibility/lod-selector.d.ts.map +1 -0
  254. package/dist/scene/visibility/occlusion-confidence.d.ts +81 -0
  255. package/dist/scene/visibility/occlusion-confidence.d.ts.map +1 -0
  256. package/dist/scene/visibility/occlusion-pass.d.ts +57 -0
  257. package/dist/scene/visibility/occlusion-pass.d.ts.map +1 -0
  258. package/dist/scene/visibility/occlusion-query-pool.d.ts +57 -0
  259. package/dist/scene/visibility/occlusion-query-pool.d.ts.map +1 -0
  260. package/dist/scene/visibility/occlusion-runtime.d.ts +96 -0
  261. package/dist/scene/visibility/occlusion-runtime.d.ts.map +1 -0
  262. package/dist/scene/visibility/types.d.ts +34 -0
  263. package/dist/scene/visibility/types.d.ts.map +1 -0
  264. package/dist/shadow-atlas.d.ts +3 -3
  265. package/dist/shadow-atlas.d.ts.map +1 -1
  266. package/dist/ssao-buffers.d.ts +3 -0
  267. package/dist/ssao-buffers.d.ts.map +1 -1
  268. package/dist/ssr/admission.d.ts +122 -0
  269. package/dist/ssr/admission.d.ts.map +1 -0
  270. package/dist/ssr/composition.d.ts +32 -0
  271. package/dist/ssr/composition.d.ts.map +1 -0
  272. package/dist/ssr/errors.d.ts +39 -0
  273. package/dist/ssr/errors.d.ts.map +1 -0
  274. package/dist/ssr/identity.d.ts +13 -0
  275. package/dist/ssr/identity.d.ts.map +1 -0
  276. package/dist/systems/skin-palette-allocator.d.ts +1 -1
  277. package/dist/targets/contracts.d.ts +66 -0
  278. package/dist/targets/contracts.d.ts.map +1 -0
  279. package/dist/targets/graph-projection.d.ts +29 -0
  280. package/dist/targets/graph-projection.d.ts.map +1 -0
  281. package/dist/targets/material-source.d.ts +34 -0
  282. package/dist/targets/material-source.d.ts.map +1 -0
  283. package/dist/targets/owner.d.ts +32 -0
  284. package/dist/targets/owner.d.ts.map +1 -0
  285. package/dist/targets/physical.d.ts +18 -0
  286. package/dist/targets/physical.d.ts.map +1 -0
  287. package/dist/targets/readback.d.ts +39 -0
  288. package/dist/targets/readback.d.ts.map +1 -0
  289. package/dist/temporal/frame-coordinator.d.ts +13 -0
  290. package/dist/temporal/frame-coordinator.d.ts.map +1 -1
  291. package/dist/temporal/frame.d.ts +2 -0
  292. package/dist/temporal/frame.d.ts.map +1 -1
  293. package/dist/temporal/index.mjs +6 -5
  294. package/dist/temporal/view.d.ts +7 -0
  295. package/dist/temporal/view.d.ts.map +1 -1
  296. package/dist/tilemap-chunk-extract-system.d.ts +1 -1
  297. package/dist/typed-render-graph-primitives.d.ts +5 -1
  298. package/dist/typed-render-graph-primitives.d.ts.map +1 -1
  299. package/dist/typed-shadow-passes.d.ts +1 -1
  300. package/dist/typed-shadow-passes.d.ts.map +1 -1
  301. package/dist/volume/passes.d.ts +9 -5
  302. package/dist/volume/passes.d.ts.map +1 -1
  303. package/package.json +20 -20
  304. package/src/__tests__/bloom-exact-zero.unit.test.ts +48 -19
  305. package/src/__tests__/bloom-lifecycle.integration.test.ts +41 -13
  306. package/src/__tests__/camera-target-consumption.unit.test.ts +53 -0
  307. package/src/__tests__/cluster-binner.unit.test.ts +35 -0
  308. package/src/__tests__/cluster-grid-default-surface.unit.test.ts +12 -2
  309. package/src/__tests__/cube-camera-scheduler.unit.test.ts +70 -0
  310. package/src/__tests__/cube-camera-single-submit.perf.test.ts +75 -0
  311. package/src/__tests__/cube-camera-view-matrices.unit.test.ts +41 -0
  312. package/src/__tests__/cube-camera-vocabulary.unit.test.ts +47 -0
  313. package/src/__tests__/cube-camera.integration.test.ts +42 -0
  314. package/src/__tests__/detached-inspection.integration.test.ts +6 -0
  315. package/src/__tests__/device-feature-admission.unit.test.ts +85 -0
  316. package/src/__tests__/direct-light-snapshot.unit.test.ts +62 -6
  317. package/src/__tests__/docs-gate-green.unit.test.ts +38 -0
  318. package/src/__tests__/environment-generation.integration.test.ts +16 -0
  319. package/src/__tests__/evidence-identity.ts +13 -0
  320. package/src/__tests__/evidence-identity.unit.test.ts +24 -0
  321. package/src/__tests__/extended-lighting-recovery.browser.test.ts +14 -0
  322. package/src/__tests__/extended-lighting-recovery.dawn.test.ts +14 -0
  323. package/src/__tests__/extended-lighting-recovery.integration.test.ts +81 -0
  324. package/src/__tests__/extended-lighting-resources.unit.test.ts +51 -0
  325. package/src/__tests__/extended-lighting-zero-feature.integration.test.ts +52 -0
  326. package/src/__tests__/factory-contract.integration.test.ts +288 -29
  327. package/src/__tests__/fog-component.unit.test.ts +67 -0
  328. package/src/__tests__/fog.integration.test.ts +86 -0
  329. package/src/__tests__/frame-observation.contract.test.ts +15 -0
  330. package/src/__tests__/fullscreen-feature-plan.unit.test.ts +37 -0
  331. package/src/__tests__/gpu-driven-batch-topology-pbr.unit.test.ts +146 -0
  332. package/src/__tests__/gpu-driven-indirect-raster-evidence.ts +10 -8
  333. package/src/__tests__/gpu-driven-indirect-raster.dawn.test.ts +18 -1
  334. package/src/__tests__/gpu-driven-production.integration.test.ts +579 -17
  335. package/src/__tests__/gpu-driven-scaling.unit.test.ts +66 -2
  336. package/src/__tests__/gpu-driven-view-gpu-evidence.ts +26 -1
  337. package/src/__tests__/gpu-driven-view-graph.integration.test.ts +101 -6
  338. package/src/__tests__/gpu-driven-view.browser.test.ts +3 -3
  339. package/src/__tests__/gpu-driven-view.dawn.test.ts +3 -3
  340. package/src/__tests__/gpu-driven-view.unit.test.ts +50 -0
  341. package/src/__tests__/gpu-lod-evidence.ts +188 -0
  342. package/src/__tests__/gpu-lod-fallback.integration.test.ts +10 -0
  343. package/src/__tests__/gpu-lod-indirect.integration.test.ts +40 -0
  344. package/src/__tests__/gpu-lod-parity.dawn.test.ts +29 -0
  345. package/src/__tests__/gpu-lod-readiness.integration.test.ts +52 -0
  346. package/src/__tests__/gpu-lod-schema.unit.test.ts +66 -0
  347. package/src/__tests__/gpu-lod-selector.unit.test.ts +28 -0
  348. package/src/__tests__/gpu-lod-silhouette.browser.test.ts +13 -0
  349. package/src/__tests__/gpu-pass-timing-browser-runner.ts +281 -0
  350. package/src/__tests__/gpu-pass-timing-coexistence.integration.test.ts +80 -0
  351. package/src/__tests__/gpu-pass-timing-contract.unit.test.ts +243 -0
  352. package/src/__tests__/gpu-pass-timing-exact-zero.unit.test.ts +131 -0
  353. package/src/__tests__/gpu-pass-timing-lifecycle.integration.test.ts +230 -0
  354. package/src/__tests__/gpu-pass-timing-observation.unit.test.ts +233 -0
  355. package/src/__tests__/gpu-pass-timing-session.unit.test.ts +438 -0
  356. package/src/__tests__/gpu-pass-timing.browser.test.ts +26 -0
  357. package/src/__tests__/gpu-pass-timing.rhi-null.unit.test.ts +125 -0
  358. package/src/__tests__/gpu-resource-store-stride.unit.test.ts +66 -1
  359. package/src/__tests__/gpu-scene.dawn.test.ts +8 -1
  360. package/src/__tests__/gpu-scene.unit.test.ts +49 -3
  361. package/src/__tests__/hdrp-group2-visibility.unit.test.ts +24 -0
  362. package/src/__tests__/instances-culling.integration.test.ts +94 -0
  363. package/src/__tests__/instances-derived-bounds.unit.test.ts +118 -0
  364. package/src/__tests__/light-buffer-layout.unit.test.ts +113 -3
  365. package/src/__tests__/light-inspection.unit.test.ts +55 -0
  366. package/src/__tests__/light-resource-error.unit.test.ts +43 -0
  367. package/src/__tests__/lighting-scene-roundtrip.integration.test.ts +109 -0
  368. package/src/__tests__/lod-bounds.unit.test.ts +42 -0
  369. package/src/__tests__/lod-cpu-lane.integration.test.ts +55 -0
  370. package/src/__tests__/lod-cpu-no-change.perf.test.ts +19 -0
  371. package/src/__tests__/lod-gpu-frame-timing.unit.test.ts +49 -0
  372. package/src/__tests__/lod-multi-view.integration.test.ts +63 -0
  373. package/src/__tests__/lod-occlusion-device-loss.integration.test.ts +27 -0
  374. package/src/__tests__/lod-occlusion-final-decision.integration.test.ts +47 -0
  375. package/src/__tests__/lod-occlusion-inspection.integration.test.ts +217 -0
  376. package/src/__tests__/lod-occlusion-inspection.schema.test-d.ts +14 -0
  377. package/src/__tests__/lod-occlusion-inspection.unit.test.ts +72 -0
  378. package/src/__tests__/lod-occlusion-visual-evidence.ts +91 -0
  379. package/src/__tests__/lod-occlusion.browser.test.ts +15 -0
  380. package/src/__tests__/lod-occlusion.dawn.test.ts +20 -0
  381. package/src/__tests__/lod-selector.profile.test.ts +35 -0
  382. package/src/__tests__/lod-selector.unit.test.ts +68 -0
  383. package/src/__tests__/lod-visibility-identity.unit.test.ts +198 -0
  384. package/src/__tests__/lod-visibility-lifecycle.integration.test.ts +132 -0
  385. package/src/__tests__/ltc-resources.unit.test.ts +32 -0
  386. package/src/__tests__/material-contract-inventory.unit.test.ts +17 -0
  387. package/src/__tests__/material-observation.integration.test.ts +101 -0
  388. package/src/__tests__/occlusion-confidence.unit.test.ts +168 -0
  389. package/src/__tests__/occlusion-invalidation.integration.test.ts +37 -0
  390. package/src/__tests__/occlusion-pass-routing.unit.test.ts +16 -0
  391. package/src/__tests__/occlusion-pass.dawn.test.ts +20 -0
  392. package/src/__tests__/occlusion-pass.integration.test.ts +43 -0
  393. package/src/__tests__/occlusion-pass.rhi-null.test.ts +22 -0
  394. package/src/__tests__/occlusion-query-pool.rhi-null.test.ts +21 -0
  395. package/src/__tests__/occlusion-query-pool.unit.test.ts +95 -0
  396. package/src/__tests__/occlusion-query-ticket.integration.test.ts +47 -0
  397. package/src/__tests__/occlusion-query.dawn.test.ts +16 -0
  398. package/src/__tests__/occlusion-query.evidence.ts +178 -0
  399. package/src/__tests__/occlusion-query.rhi-null.test.ts +31 -0
  400. package/src/__tests__/occlusion-runtime.unit.test.ts +120 -0
  401. package/src/__tests__/pbr-view-layout.unit.test.ts +16 -0
  402. package/src/__tests__/point-shadow-inspection.unit.test.ts +60 -0
  403. package/src/__tests__/prepared-gpu-driven-pbr.integration.test.ts +196 -0
  404. package/src/__tests__/probe-bindings.unit.test.ts +200 -0
  405. package/src/__tests__/probe-blend-numeric.unit.test.ts +67 -0
  406. package/src/__tests__/probe-blend-oracle.unit.test.ts +136 -0
  407. package/src/__tests__/probe-capacity.integration.test.ts +97 -0
  408. package/src/__tests__/probe-recovery.integration.test.ts +104 -0
  409. package/src/__tests__/probe-scene-dirty.integration.test.ts +211 -0
  410. package/src/__tests__/profiler-phase-catalog.test.ts +4 -1
  411. package/src/__tests__/profiler-phase-token.test.ts +1 -0
  412. package/src/__tests__/public-surface.test-d.ts +10 -0
  413. package/src/__tests__/rect-area-boundary.unit.test.ts +24 -0
  414. package/src/__tests__/reflection-fallback-graph.unit.test.ts +67 -0
  415. package/src/__tests__/reflection-fallback-inspection.unit.test.ts +77 -0
  416. package/src/__tests__/reflection-fallback-lifecycle.integration.test.ts +21 -0
  417. package/src/__tests__/reflection-fallback-projection.unit.test.ts +90 -0
  418. package/src/__tests__/reflection-fallback-source.unit.test.ts +32 -0
  419. package/src/__tests__/reflection-probe-authoring.unit.test.ts +71 -0
  420. package/src/__tests__/reflection-probe-filter.unit.test.ts +54 -0
  421. package/src/__tests__/reflection-probe-ibl-topology.unit.test.ts +45 -0
  422. package/src/__tests__/reflection-probe-owner-placement.unit.test.ts +39 -0
  423. package/src/__tests__/reflection-probe-pass-encode.integration.test.ts +101 -0
  424. package/src/__tests__/reflection-probe-performance.perf.test.ts +60 -0
  425. package/src/__tests__/reflection-probe-record-owner.characterization.unit.test.ts +120 -0
  426. package/src/__tests__/reflection-probe-recovery.integration.test.ts +55 -0
  427. package/src/__tests__/reflection-probe-selection.unit.test.ts +46 -0
  428. package/src/__tests__/render-error-code-owner.test-d.ts +19 -3
  429. package/src/__tests__/render-feature-docs.unit.test.ts +12 -7
  430. package/src/__tests__/render-graph-fallback.integration.test.ts +24 -0
  431. package/src/__tests__/render-perf/extended-lighting.bench.test.ts +66 -0
  432. package/src/__tests__/render-scene-projection.unit.test.ts +447 -18
  433. package/src/__tests__/render-target-admission.unit.test.ts +80 -0
  434. package/src/__tests__/render-target-graph-projection.unit.test.ts +102 -0
  435. package/src/__tests__/render-target-inspection.unit.test.ts +52 -0
  436. package/src/__tests__/render-target-material-source.test-d.ts +30 -0
  437. package/src/__tests__/render-target-material-source.unit.test.ts +97 -0
  438. package/src/__tests__/render-target-owner-placement.unit.test.ts +203 -0
  439. package/src/__tests__/render-target-owner.unit.test.ts +122 -0
  440. package/src/__tests__/render-target-probe-soak.perf.test.ts +94 -0
  441. package/src/__tests__/render-target-public-schema.json +107 -0
  442. package/src/__tests__/render-target-public-schema.unit.test.ts +91 -0
  443. package/src/__tests__/render-target-public-surface.test-d.ts +83 -0
  444. package/src/__tests__/render-target-readback.integration.test.ts +90 -0
  445. package/src/__tests__/render-target-recovery.integration.test.ts +85 -0
  446. package/src/__tests__/renderer-factory-material-contract.unit.test.ts +124 -0
  447. package/src/__tests__/renderer-lifecycle.integration.test.ts +14 -0
  448. package/src/__tests__/shader-stage-surface.unit.test.ts +19 -4
  449. package/src/__tests__/skin-motion-regression.unit.test.ts +154 -0
  450. package/src/__tests__/skinned-shadow-caster.test.ts +13 -4
  451. package/src/__tests__/spot-modifier-projection.unit.test.ts +70 -0
  452. package/src/__tests__/ssr-admission-matrix.integration.test.ts +206 -0
  453. package/src/__tests__/ssr-composition.unit.test.ts +46 -0
  454. package/src/__tests__/ssr-fallback-zero-work.bench.test.ts +94 -0
  455. package/src/__tests__/ssr-recovery-errors.unit.test.ts +58 -0
  456. package/src/__tests__/standard-cluster-capability.integration.test.ts +63 -0
  457. package/src/__tests__/standard-cluster-transport.unit.test.ts +121 -0
  458. package/src/__tests__/standard-cluster-variant-resolution.unit.test.ts +299 -0
  459. package/src/__tests__/standard-clustered-pipeline.unit.test.ts +15 -5
  460. package/src/__tests__/standard-lighting-inspection.integration.test.ts +138 -0
  461. package/src/__tests__/standard-lighting-ssot.unit.test.ts +18 -0
  462. package/src/__tests__/standard-lighting-write.unit.test.ts +441 -0
  463. package/src/__tests__/standard-lighting.integration.test.ts +67 -0
  464. package/src/__tests__/standard-pipeline.dawn.test.ts +206 -5
  465. package/src/__tests__/standard-pipeline.integration.test.ts +181 -150
  466. package/src/__tests__/standard-probe-binding.unit.test.ts +25 -0
  467. package/src/__tests__/standard-profile-oracle.unit.test.ts +23 -38
  468. package/src/__tests__/temporal-fallback-generation.integration.test.ts +16 -0
  469. package/src/__tests__/temporal-view.unit.test.ts +3 -3
  470. package/src/__tests__/transmission-prewarm.unit.test.ts +69 -0
  471. package/src/__tests__/typed-pipeline-topology.unit.test.ts +395 -14
  472. package/src/__tests__/urp-pipeline-targets.unit.test.ts +2 -2
  473. package/src/__tests__/visibility-budget.integration.test.ts +140 -0
  474. package/src/__tests__/visibility-budget.unit.test.ts +70 -0
  475. package/src/__tests__/visibility-extract.integration.test.ts +11 -6
  476. package/src/__tests__/visibility-resolver.unit.test.ts +15 -0
  477. package/src/__tests__/volumetric-fog-point-spot-accumulation.integration.test.ts +6 -3
  478. package/src/__tests__/volumetric-fog-stage-readback.dawn.test.ts +31 -28
  479. package/src/assembly/backend-contract.ts +13 -0
  480. package/src/assembly/device-feature-admission.ts +80 -0
  481. package/src/assembly/directional-shadow-inspection.ts +122 -0
  482. package/src/assembly/factory.ts +1411 -366
  483. package/src/assembly/host-contract.ts +35 -0
  484. package/src/assembly/render-target-host.ts +847 -0
  485. package/src/assets/asset-decoders.ts +1 -1
  486. package/src/assets/render-asset-port.ts +7 -1
  487. package/src/capture/cube-views.ts +69 -0
  488. package/src/capture/scheduler.ts +265 -0
  489. package/src/cluster-binner.ts +31 -0
  490. package/src/components/__tests__/lighting-authoring.unit.test.ts +39 -0
  491. package/src/components/__tests__/rect-area-light.unit.test.ts +33 -0
  492. package/src/components/__tests__/schema-contract.unit.test.ts +12 -0
  493. package/src/components/__tests__/schema-parity.unit.test.ts +9 -0
  494. package/src/components/camera.ts +4 -0
  495. package/src/components/cube-camera.ts +52 -0
  496. package/src/components/directional-light.ts +22 -2
  497. package/src/components/directional-shadow-filter.ts +57 -0
  498. package/src/components/index.ts +5 -0
  499. package/src/components/light-helpers.ts +133 -8
  500. package/src/components/light-probe.ts +13 -0
  501. package/src/components/rect-area-light.ts +15 -0
  502. package/src/components/reflection-probe.ts +49 -0
  503. package/src/components/scene-instance.ts +6 -0
  504. package/src/components/spot-light.ts +3 -0
  505. package/src/device/__tests__/extended-lighting-recovery.unit.test.ts +24 -0
  506. package/src/device/gpu-residency.ts +140 -9
  507. package/src/device/lifecycle-transaction.ts +12 -0
  508. package/src/environment/frame.ts +6 -24
  509. package/src/environment/lifecycle.ts +20 -1
  510. package/src/errors/gpu-driven.ts +92 -0
  511. package/src/errors/index.ts +1 -0
  512. package/src/errors/recover.ts +32 -0
  513. package/src/errors/render.ts +341 -143
  514. package/src/extract/camera.ts +3 -3
  515. package/src/extract/directional-shadow-projection.ts +61 -0
  516. package/src/extract/environment.ts +57 -0
  517. package/src/extract/gpu-driven.ts +108 -0
  518. package/src/extract/visibility.ts +2 -1
  519. package/src/features/fullscreen.ts +3 -0
  520. package/src/features/targets.ts +4 -1
  521. package/src/features/types.ts +10 -0
  522. package/src/fullscreen-post-process-pass.ts +43 -2
  523. package/src/gpu-driven/batch-topology.ts +207 -28
  524. package/src/gpu-driven/prepared-draw.ts +208 -0
  525. package/src/gpu-driven/production-raster.ts +953 -43
  526. package/src/gpu-driven/view-gpu.ts +411 -29
  527. package/src/gpu-scene-schema.ts +13 -0
  528. package/src/gpu-scene.ts +140 -64
  529. package/src/gpu-usage.ts +0 -1
  530. package/src/hdrp-buffers.ts +169 -63
  531. package/src/ibl/IblPipelineCache.ts +11 -0
  532. package/src/ibl/kernel-cache.ts +88 -0
  533. package/src/index.ts +222 -0
  534. package/src/inspection-types.ts +276 -1
  535. package/src/instances-derived-bounds.ts +171 -0
  536. package/src/internal.ts +38 -0
  537. package/src/light-buffer-layout.ts +155 -310
  538. package/src/materials.ts +16 -2
  539. package/src/mesh-material-bindings.ts +132 -0
  540. package/src/pbr-pipeline.ts +255 -60
  541. package/src/pipeline/standard-forward-lane.ts +93 -16
  542. package/src/pipeline/standard-lighting/errors.ts +6 -0
  543. package/src/pipeline/standard-lighting/graph.ts +158 -0
  544. package/src/pipeline/standard-lighting/inspection.ts +88 -0
  545. package/src/pipeline/standard-lighting/layout.ts +31 -0
  546. package/src/pipeline/standard-lighting/light-frame.ts +35 -0
  547. package/src/pipeline/standard-lighting/membership.ts +43 -0
  548. package/src/pipeline/standard-lighting/prepare.ts +37 -0
  549. package/src/pipeline/standard-lighting/topology.ts +120 -0
  550. package/src/pipeline/standard-lighting/transport.ts +78 -0
  551. package/src/pipeline/standard-pipeline.ts +124 -193
  552. package/src/pipeline/standard-post.ts +26 -7
  553. package/src/pipeline/standard-profile.ts +17 -47
  554. package/src/pipeline-builder.ts +5 -2
  555. package/src/pipeline-spec.ts +95 -21
  556. package/src/plugin.ts +4 -0
  557. package/src/point-shadow-inspection.ts +55 -0
  558. package/src/prepare/__tests__/extended-lighting-capability.unit.test.ts +56 -0
  559. package/src/prepare/extended-lighting/ltc-resources.ts +136 -0
  560. package/src/prepare/extended-lighting/resources.ts +389 -0
  561. package/src/prepare/extended-lighting/spot-modifiers.ts +194 -0
  562. package/src/prepare/extended-lighting/state.ts +92 -0
  563. package/src/record/__tests__/gpu-timing.unit.test.ts +177 -0
  564. package/src/record/__tests__/temporal-payload.unit.test.ts +64 -1
  565. package/src/record/__tests__/transparent-group2-selection.unit.test.ts +73 -0
  566. package/src/record/__tests__/typed-frame-graph-plan.unit.test.ts +20 -1
  567. package/src/record/__tests__/view-ubo-layout.unit.test.ts +40 -0
  568. package/src/record/frame-lighting.ts +591 -452
  569. package/src/record/frame-snapshot.ts +92 -39
  570. package/src/record/frame-targets.ts +17 -0
  571. package/src/record/frame.ts +662 -158
  572. package/src/record/gpu-pass-timing/contract.ts +180 -0
  573. package/src/record/gpu-pass-timing/errors.ts +34 -0
  574. package/src/record/gpu-pass-timing/index.ts +37 -0
  575. package/src/record/gpu-pass-timing/parser.ts +97 -0
  576. package/src/record/gpu-pass-timing/session.ts +938 -0
  577. package/src/record/gpu-timing.ts +112 -39
  578. package/src/record/helpers.ts +0 -58
  579. package/src/record/main-pass-geometry.ts +324 -119
  580. package/src/record/main-pass-material.ts +103 -10
  581. package/src/record/main-pass-sprite-draws.ts +99 -103
  582. package/src/record/main-pass.ts +71 -15
  583. package/src/record/mesh-ssbo.ts +47 -36
  584. package/src/record/render-context.ts +136 -3
  585. package/src/record/shadow-pass.ts +83 -68
  586. package/src/record/skybox-post-pass.ts +20 -4
  587. package/src/record/typed-frame-graph.ts +1034 -43
  588. package/src/record/view-ubo.ts +72 -47
  589. package/src/reflection/filter.ts +81 -0
  590. package/src/reflection/gpu-table.ts +44 -0
  591. package/src/reflection/inspection.ts +67 -0
  592. package/src/reflection/projection.ts +476 -0
  593. package/src/reflection/record-owner.ts +1283 -0
  594. package/src/render-contract.ts +93 -10
  595. package/src/render-graph-primitives.ts +9 -4
  596. package/src/render-pipeline.ts +122 -2
  597. package/src/render-system-extract.ts +1041 -225
  598. package/src/render-system.ts +1310 -85
  599. package/src/renderer.ts +222 -0
  600. package/src/scene/__tests__/changed-block-summary.candidate.test.ts +36 -0
  601. package/src/scene/probe-blend-record.ts +54 -0
  602. package/src/scene/probe-blend.ts +813 -0
  603. package/src/scene/render-scene-types.ts +0 -7
  604. package/src/scene/render-scene.ts +942 -171
  605. package/src/scene/visibility/bounds.ts +57 -0
  606. package/src/scene/visibility/budget.ts +40 -0
  607. package/src/scene/visibility/facet.ts +400 -0
  608. package/src/scene/visibility/gpu-frame-timing.ts +101 -0
  609. package/src/scene/visibility/gpu-lod.ts +119 -0
  610. package/src/scene/visibility/inspection.ts +313 -0
  611. package/src/scene/visibility/lod-selector.ts +84 -0
  612. package/src/scene/visibility/occlusion-confidence.ts +478 -0
  613. package/src/scene/visibility/occlusion-pass.ts +165 -0
  614. package/src/scene/visibility/occlusion-query-pool.ts +178 -0
  615. package/src/scene/visibility/occlusion-runtime.ts +672 -0
  616. package/src/scene/visibility/types.ts +50 -0
  617. package/src/shadow-atlas.ts +5 -5
  618. package/src/ssao-buffers.ts +9 -2
  619. package/src/ssr/admission.ts +451 -0
  620. package/src/ssr/composition.ts +73 -0
  621. package/src/ssr/errors.ts +76 -0
  622. package/src/ssr/identity.ts +12 -0
  623. package/src/systems/skin-palette-allocator.ts +1 -1
  624. package/src/targets/contracts.ts +182 -0
  625. package/src/targets/graph-projection.ts +169 -0
  626. package/src/targets/material-source.ts +143 -0
  627. package/src/targets/owner.ts +214 -0
  628. package/src/targets/physical.ts +18 -0
  629. package/src/targets/readback.ts +153 -0
  630. package/src/temporal/frame-coordinator.ts +26 -0
  631. package/src/temporal/frame.ts +14 -1
  632. package/src/temporal/view.ts +11 -0
  633. package/src/tilemap-chunk-extract-system.ts +4 -4
  634. package/src/transmission/__tests__/standard-transmission.dawn.test.ts +59 -20
  635. package/src/typed-render-graph-primitives.ts +53 -3
  636. package/src/typed-shadow-passes.ts +46 -40
  637. package/src/volume/passes.ts +61 -58
  638. package/dist/.tsbuildinfo +0 -1
  639. package/dist/chunk-4XINDLAY.mjs.map +0 -1
  640. package/dist/chunk-CDTWTHZR.mjs.map +0 -1
  641. package/dist/chunk-M2MP4ZOU.mjs +0 -73
  642. package/dist/chunk-M2MP4ZOU.mjs.map +0 -1
  643. package/dist/chunk-PCHBPYML.mjs.map +0 -1
  644. package/dist/chunk-TBQJOK2L.mjs.map +0 -1
  645. package/dist/chunk-VFPLTKCU.mjs.map +0 -1
@@ -21,19 +21,30 @@ import {
21
21
  } from '@forgeax/engine-rhi';
22
22
  import type { MaterialRenderState, MeshAsset } from '@forgeax/engine-types';
23
23
  import { handleSlot, toShared } from '@forgeax/engine-types';
24
+ import { CUBE_CAMERA_FACE_ORDER } from '../components/cube-camera';
25
+ import type { DirectionalShadowQuality } from '../components/directional-shadow-filter';
24
26
  import type { MeshGpuHandles } from '../device/gpu-residency';
25
- import { ObservationUnavailableError, type ObservationUnavailableReason } from '../errors/render';
26
- import type { FogFrame } from '../extract/environment';
27
+ import {
28
+ ObservationUnavailableError,
29
+ type ObservationUnavailableReason,
30
+ RendererOperationError,
31
+ } from '../errors/render';
27
32
  import type { GpuDrivenProduction, PreparedGpuDrivenFrame } from '../gpu-driven/production-raster';
28
33
  import { GpuBuffer } from '../gpu-resource';
34
+ import { GPU_TEXTURE_USAGE_COPY_SRC } from '../gpu-texture-usage';
29
35
  import {
30
36
  GPU_BUFFER_USAGE_COPY_DST,
37
+ GPU_BUFFER_USAGE_MAP_READ,
31
38
  GPU_BUFFER_USAGE_UNIFORM,
32
39
  GPU_BUFFER_USAGE_VERTEX,
33
40
  } from '../gpu-usage';
34
41
  import { disposeTransientInstanceBuffers } from '../instance-buffer-cache';
42
+ import { inspectStandardLighting } from '../pipeline/standard-lighting/inspection';
43
+ import { standardLightingTopologySignature } from '../pipeline/standard-lighting/topology';
44
+ import { inspectPointShadow } from '../point-shadow-inspection';
35
45
  import type {
36
46
  CameraSnapshot,
47
+ CubeCameraSnapshot,
37
48
  RenderRecordPhase,
38
49
  SpotLightProjectorFrameContext,
39
50
  VolumetricFogFrameContext,
@@ -48,6 +59,8 @@ import type {
48
59
  SkylightSnapshot,
49
60
  } from '../render-system-extract';
50
61
  import type { PersistentGpuDrivenState } from '../scene/render-scene';
62
+ import type { OcclusionFrameProjection } from '../scene/visibility/occlusion-runtime';
63
+ import { SHADOW_ATLAS_DEFAULT_LAYERS } from '../shadow-atlas';
51
64
  import {
52
65
  getTransparentSortConfig,
53
66
  TRANSPARENT_SORT_MODE_LAYER_Y,
@@ -63,8 +76,8 @@ import {
63
76
  resolveSkyboxActive,
64
77
  warnZeroLightStandard,
65
78
  writeHdrpClusterAndSsaoBuffers,
66
- writePointSpotLightBuffers,
67
79
  writeShadowParamsBuffer,
80
+ writeSpotModifierTextures,
68
81
  } from './frame-lighting';
69
82
  import {
70
83
  type BindGroupCounts,
@@ -72,9 +85,12 @@ import {
72
85
  type DirectionalShadowWorldState,
73
86
  type DispatchCounts,
74
87
  type FrameObservationSource,
88
+ getTextureIdentity,
75
89
  makeZeroCameraFallbackSnapshot,
90
+ type ReflectionFallbackReadbackRequest,
76
91
  type RenderFrameState,
77
92
  type ValidatedRenderable,
93
+ validateGraphTargetCaptureReadback,
78
94
  worldEntityKey,
79
95
  } from './frame-snapshot';
80
96
  // feat-20260622-chunk-gpu-instancing-sprite-tilemap M1 / w4 (D-1 record-stage
@@ -223,12 +239,87 @@ import { computeSplitLdrSprite } from './main-pass-sprite-draws';
223
239
  import { cleanPerEntityCache, ensureMeshSsboCapacity, uploadMeshSsboBatch } from './mesh-ssbo';
224
240
  import { writeShadowCasterUniforms } from './shadow-pass';
225
241
  import {
242
+ type CubeCaptureGraphState,
226
243
  ensureCompiledFrameGraph,
227
244
  executeCompiledFrameGraph,
228
245
  type RenderFeatureGraphCandidate,
229
246
  settleVolumetricFogGraphCandidate,
230
247
  } from './typed-frame-graph';
231
- import { writePointsLinesViewUbo, writeViewUbo } from './view-ubo';
248
+ import {
249
+ CUBE_CAPTURE_VIEW_SLOT_BASE,
250
+ VIEW_UNIFORM_SLOT_STRIDE,
251
+ writePointsLinesViewUbo,
252
+ writeViewUbo,
253
+ } from './view-ubo';
254
+
255
+ /**
256
+ * Resolve the GPU mesh view used by the optional production raster before its
257
+ * filtering pass runs. User meshes are pull-resident during record; relying
258
+ * only on the boot-time builtin map would make the first frame classify every
259
+ * imported mesh as a CPU fallback and would hide its LOD ranges from the GPU
260
+ * cull. Keep the map frame-local so the pipeline state remains the builtin
261
+ * compatibility owner while the residency cache remains the user-mesh SSOT.
262
+ */
263
+ function gpuDrivenMeshesForFrame(
264
+ internals: RenderSystemInternals,
265
+ pipelineState: PipelineState,
266
+ worlds: readonly World[],
267
+ scene: PersistentGpuDrivenState | undefined,
268
+ ): ReadonlyMap<number, MeshGpuHandles> {
269
+ const meshes = new Map(pipelineState.meshes);
270
+ if (scene === undefined) return meshes;
271
+ const cached = gpuDrivenMeshCache.get(scene.scene);
272
+ if (
273
+ cached !== undefined &&
274
+ cached.meshResidencyEpoch === internals.gpuStore.meshResidencyEpoch &&
275
+ cached.pipelineMeshes === pipelineState.meshes
276
+ ) {
277
+ return cached.value;
278
+ }
279
+ for (const slot of scene.slots) {
280
+ const world = worlds[slot.snapshot.worldId];
281
+ if (world === undefined) continue;
282
+ const assetHandle = slot.snapshot.assetHandle;
283
+ const sharedHandle = toShared<'MeshAsset'>(assetHandle);
284
+ let gpu = internals.gpuStore.getMeshGpuHandles(sharedHandle, world);
285
+ if (gpu === undefined && !meshes.has(assetHandle)) {
286
+ const asset = resolveAssetHandle<MeshAsset>(world, sharedHandle);
287
+ if (asset.ok) {
288
+ const lodMeshes = slot.snapshot.lods?.map((lod) =>
289
+ internals.assets.lookup<MeshAsset>(lod.mesh),
290
+ );
291
+ const resolvedLodMeshes =
292
+ lodMeshes?.every((lod): lod is MeshAsset => lod?.kind === 'mesh') === true
293
+ ? lodMeshes
294
+ : undefined;
295
+ const resident = internals.gpuStore.ensureResident(
296
+ sharedHandle,
297
+ asset.value,
298
+ world,
299
+ resolvedLodMeshes,
300
+ );
301
+ if (resident.ok) gpu = resident.value;
302
+ }
303
+ }
304
+ if (gpu !== undefined) meshes.set(assetHandle, gpu);
305
+ }
306
+ const value = meshes;
307
+ gpuDrivenMeshCache.set(scene.scene, {
308
+ meshResidencyEpoch: internals.gpuStore.meshResidencyEpoch,
309
+ pipelineMeshes: pipelineState.meshes,
310
+ value,
311
+ });
312
+ return value;
313
+ }
314
+
315
+ const gpuDrivenMeshCache = new WeakMap<
316
+ object,
317
+ {
318
+ readonly meshResidencyEpoch: number;
319
+ readonly pipelineMeshes: ReadonlyMap<number, MeshGpuHandles>;
320
+ readonly value: ReadonlyMap<number, MeshGpuHandles>;
321
+ }
322
+ >();
232
323
 
233
324
  /**
234
325
  * A frame-local material table. Repeated snapshot objects share one UBO slot;
@@ -301,6 +392,67 @@ export function materialSlotCountForPrefix(
301
392
 
302
393
  export type RecordProfileRunner = <T>(phase: RenderRecordPhase, action: () => T) => T;
303
394
 
395
+ export interface CubeCaptureFrameInput {
396
+ readonly snapshots: readonly CubeCameraSnapshot[];
397
+ readonly scheduler: import('../capture/scheduler').CubeCaptureScheduler;
398
+ readonly state: CubeCaptureGraphState;
399
+ readonly reflectionProbes?: import('./render-context').ReflectionProbeRecordState;
400
+ }
401
+
402
+ function prepareCubeCapture(
403
+ internals: RenderSystemInternals,
404
+ input: CubeCaptureFrameInput | undefined,
405
+ displayCamera: CameraSnapshot,
406
+ ): readonly import('./typed-frame-graph').CubeCaptureGraphWork[] {
407
+ if (input === undefined) return [];
408
+ if (input.reflectionProbes === undefined) delete input.state.reflectionProbes;
409
+ else input.state.reflectionProbes = input.reflectionProbes.graph;
410
+ input.scheduler.beginFrame();
411
+ for (const snapshot of input.snapshots) {
412
+ const inspection = input.scheduler.inspect(snapshot.target);
413
+ if (inspection.candidateGeneration !== undefined) continue;
414
+ const requested = input.scheduler.request({
415
+ target: snapshot.target,
416
+ position: snapshot.position,
417
+ near: snapshot.near,
418
+ far: snapshot.far,
419
+ updateIntent: snapshot.updateIntent,
420
+ requestVersion: snapshot.requestVersion,
421
+ faceBudget: snapshot.faceBudget,
422
+ });
423
+ if (!requested.ok) internals.errorRegistry.fire(requested.error);
424
+ }
425
+ const work = input.scheduler.nextWork();
426
+ const physicalWork: import('./typed-frame-graph').CubeCaptureGraphWork[] = [];
427
+ for (const item of work) {
428
+ const physical = internals.getRenderTargetPhysical?.(item.target);
429
+ if (physical === undefined) continue;
430
+ const faceIndex = CUBE_CAMERA_FACE_ORDER.indexOf(item.face);
431
+ if (faceIndex < 0) continue;
432
+ const faceWorld = mat4.create();
433
+ mat4.invert(faceWorld, item.view);
434
+ physicalWork.push({
435
+ target: item.target,
436
+ faceIndex,
437
+ physical,
438
+ faceCamera: {
439
+ ...displayCamera,
440
+ position: vec3.create(item.position[0] ?? 0, item.position[1] ?? 0, item.position[2] ?? 0),
441
+ world: faceWorld,
442
+ fov: Math.PI / 2,
443
+ aspect: 1,
444
+ near: item.near,
445
+ far: item.far,
446
+ projection: 'perspective',
447
+ },
448
+ viewBindGroupDynamicOffset:
449
+ (CUBE_CAPTURE_VIEW_SLOT_BASE + physicalWork.length) * VIEW_UNIFORM_SLOT_STRIDE,
450
+ });
451
+ }
452
+ input.state.work = physicalWork;
453
+ return physicalWork;
454
+ }
455
+
304
456
  const EMPTY_ENTITY_KEYS: ReadonlySet<number> = new Set<number>();
305
457
 
306
458
  function runRecordProfilePhase<T>(
@@ -311,6 +463,135 @@ function runRecordProfilePhase<T>(
311
463
  return runner === undefined ? action() : runner(phase, action);
312
464
  }
313
465
 
466
+ function halfFloatToNumber(bits: number): number {
467
+ const sign = (bits & 0x8000) === 0 ? 1 : -1;
468
+ const exponent = (bits >>> 10) & 0x1f;
469
+ const fraction = bits & 0x03ff;
470
+ if (exponent === 0) return sign * 2 ** -14 * (fraction / 1024);
471
+ if (exponent === 0x1f) return fraction === 0 ? sign * Infinity : Number.NaN;
472
+ return sign * 2 ** (exponent - 15) * (1 + fraction / 1024);
473
+ }
474
+
475
+ function createReflectionFallbackReadbackRequest(
476
+ internals: RenderSystemInternals,
477
+ frameState: RenderFrameState,
478
+ ): ReflectionFallbackReadbackRequest | undefined {
479
+ if (!frameState.reflectionFallbackDemand || frameState.reflectionFallbackReadback !== undefined) {
480
+ return undefined;
481
+ }
482
+ const graph = frameState.perFrameGraph;
483
+ if (graph === undefined || graph === null) return undefined;
484
+ const descriptor = graph.getColorTargetDescriptor('reflection-fallback-linear-hdr');
485
+ const texture = graph.getColorTargetTexture('reflection-fallback-linear-hdr');
486
+ if (
487
+ descriptor === undefined ||
488
+ texture === undefined ||
489
+ descriptor.format !== 'rgba16float' ||
490
+ descriptor.sample !== 1
491
+ ) {
492
+ return undefined;
493
+ }
494
+ const bytesPerRow = Math.ceil((descriptor.size.width * 8) / 256) * 256;
495
+ const created = internals.device.createBuffer({
496
+ label: 'reflection-fallback-linear-hdr.readback',
497
+ size: bytesPerRow * descriptor.size.height,
498
+ usage: GPU_BUFFER_USAGE_COPY_DST | GPU_BUFFER_USAGE_MAP_READ,
499
+ mappedAtCreation: false,
500
+ });
501
+ if (!created.ok) {
502
+ internals.errorRegistry.fire(created.error);
503
+ return undefined;
504
+ }
505
+ return {
506
+ name: 'reflection-fallback-linear-hdr',
507
+ buffer: created.value,
508
+ bytesPerRow,
509
+ width: descriptor.size.width,
510
+ height: descriptor.size.height,
511
+ expected: {
512
+ format: descriptor.format,
513
+ width: descriptor.size.width,
514
+ height: descriptor.size.height,
515
+ usage: GPU_TEXTURE_USAGE_COPY_SRC,
516
+ graphGeneration: graph.graphGeneration,
517
+ frameId: frameState.frameNumber,
518
+ textureIdentity: getTextureIdentity(texture),
519
+ },
520
+ encoded: false,
521
+ };
522
+ }
523
+
524
+ async function mapReflectionFallbackReadback(
525
+ internals: RenderSystemInternals,
526
+ request: ReflectionFallbackReadbackRequest,
527
+ allowZero: boolean,
528
+ ): Promise<{
529
+ readonly linearHdr: readonly [number, number, number, number];
530
+ readonly hash: string;
531
+ readonly graphGeneration: number;
532
+ readonly textureIdentity: number;
533
+ }> {
534
+ try {
535
+ if (!request.encoded) throw new Error('reflection fallback copy was not encoded');
536
+ const mapped = await request.buffer.mapAsync(GPU_BUFFER_USAGE_MAP_READ);
537
+ if (!mapped.ok) throw mapped.error;
538
+ const range = mapped.value.getMappedRange();
539
+ if (!range.ok) throw range.error;
540
+ const bytes = new Uint8Array(range.value.slice(0));
541
+ mapped.value.unmap();
542
+ const validation = validateGraphTargetCaptureReadback({
543
+ bytes,
544
+ expectedByteLength: request.bytesPerRow * request.height,
545
+ allowZero,
546
+ });
547
+ if (!validation.ok) throw new Error(validation.code);
548
+ let linearHdr: readonly [number, number, number, number] = [0, 0, 0, 0];
549
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
550
+ for (let y = 0; y < request.height; y += 1) {
551
+ for (let x = 0; x < request.width; x += 1) {
552
+ const offset = y * request.bytesPerRow + x * 8;
553
+ const pixel = [0, 1, 2, 3].map((channel) =>
554
+ halfFloatToNumber(view.getUint16(offset + channel * 2, true)),
555
+ ) as [number, number, number, number];
556
+ // The clear attachment carries alpha=1, so alpha alone is not
557
+ // evidence that the Standard BRDF wrote a fallback lobe. Decode the
558
+ // RGB channels from the mapped MRT and admit the first finite
559
+ // non-zero color sample only.
560
+ if (pixel.slice(0, 3).some((value) => Number.isFinite(value) && value !== 0)) {
561
+ linearHdr = pixel;
562
+ y = request.height;
563
+ break;
564
+ }
565
+ }
566
+ }
567
+ // A cleared attachment has a non-zero alpha lane, so byte-level
568
+ // validation alone can incorrectly admit an otherwise empty MRT. When
569
+ // the selected producer is non-neutral, require an actual finite RGB
570
+ // sample before publishing a complete receipt; neutral rows explicitly
571
+ // retain the zero quartet through allowZero=true.
572
+ if (
573
+ !allowZero &&
574
+ !linearHdr.slice(0, 3).some((value) => Number.isFinite(value) && value !== 0)
575
+ ) {
576
+ throw new Error('capture-readback-empty');
577
+ }
578
+ const subtle = globalThis.crypto?.subtle;
579
+ if (subtle === undefined) throw new Error('Web Crypto SHA-256 is unavailable');
580
+ const digest = await subtle.digest('SHA-256', bytes);
581
+ const hash = Array.from(new Uint8Array(digest), (value) =>
582
+ value.toString(16).padStart(2, '0'),
583
+ ).join('');
584
+ return {
585
+ linearHdr,
586
+ hash: `sha256:${hash}`,
587
+ graphGeneration: request.expected.graphGeneration,
588
+ textureIdentity: request.expected.textureIdentity,
589
+ };
590
+ } finally {
591
+ internals.device.destroyBuffer(request.buffer);
592
+ }
593
+ }
594
+
314
595
  const VOLUMETRIC_FOG_PASS_COUNT = 4;
315
596
 
316
597
  type VolumetricFogParamsSlot = 0 | 1;
@@ -619,16 +900,21 @@ function prepareVolumetricFogFrame(
619
900
  // sub-froxel jitter. The temporal resolver consumes it without growing the
620
901
  // public fog component or allocating another uniform buffer.
621
902
  params[23] = frameState.frameNumber % 1024;
903
+ const selectedPointIndex =
904
+ selectedPoint === undefined
905
+ ? -1
906
+ : lights.point.findIndex((light) => light.entity === selectedPoint.entity);
622
907
  const selectedSpotIndex =
623
908
  selectedSpot === undefined
624
909
  ? -1
625
910
  : lights.spot.findIndex((light) => light.entity === selectedSpot.entity);
911
+ // Cluster light_data is one point/spot/rect array. Point slots are emitted
912
+ // first, so volume stores the global slot rather than a per-kind array index.
913
+ const selectedSpotClusterIndex =
914
+ selectedSpotIndex < 0 ? -1 : lights.point.length + selectedSpotIndex;
626
915
  params.set(lightColor, 24);
627
- params[19] =
628
- selectedPoint === undefined
629
- ? -1
630
- : lights.point.findIndex((light) => light.entity === selectedPoint.entity);
631
- params[27] = selectedSpotIndex;
916
+ params[19] = selectedPointIndex;
917
+ params[27] = selectedSpotClusterIndex;
632
918
  params[28] = fog.fog.maxDistance;
633
919
  params[29] = fog.fog.anisotropy;
634
920
  params[30] =
@@ -739,6 +1025,8 @@ function prepareSpotLightProjectorFrame(
739
1025
  texture: resident.value.texture.handle,
740
1026
  view: resident.value.view,
741
1027
  sampler: sampler.value,
1028
+ spotIndex: lights.spot.indexOf(source),
1029
+ lightSlotIndex: lights.point.length + lights.spot.indexOf(source),
742
1030
  };
743
1031
  }
744
1032
 
@@ -1023,7 +1311,6 @@ export function recordFrame(
1023
1311
  skybox: SkyboxSnapshot | undefined,
1024
1312
  skyboxCount: number,
1025
1313
  postProcessParams: ReadonlyMap<string, Uint8Array>,
1026
- fog: FogFrame | undefined,
1027
1314
  // feat-20260709-editor-world-partition ENGINE-fix-round2 (defect 2): the
1028
1315
  // full worlds[] list passed to `renderer.draw`, indexed by
1029
1316
  // RenderableSnapshot.worldId so the record stage resolves each renderable's
@@ -1036,11 +1323,16 @@ export function recordFrame(
1036
1323
  gpuDriven?: {
1037
1324
  readonly owner: GpuDrivenProduction;
1038
1325
  readonly scene: PersistentGpuDrivenState | undefined;
1039
- readonly onCoverage: (ownsAll: boolean) => void;
1326
+ readonly onSceneDemand: (sceneRowsRequired: boolean) => void;
1327
+ readonly activeEntityKeys?: ReadonlySet<number>;
1328
+ readonly activeEntityRevision?: number;
1329
+ readonly telemetryCandidateCount?: number;
1330
+ readonly occlusion?: OcclusionFrameProjection;
1040
1331
  },
1041
1332
  renderReadLeases?: readonly RenderReadLease[],
1042
1333
  featureGraphCandidate?: RenderFeatureGraphCandidate,
1043
1334
  pointsLinesOwner?: import('./render-context').PointsLinesRecordOwner,
1335
+ cubeCapture?: CubeCaptureFrameInput,
1044
1336
  environmentSignature = '',
1045
1337
  fogSignature = '',
1046
1338
  environmentReady = true,
@@ -1048,6 +1340,8 @@ export function recordFrame(
1048
1340
  volumetricFog?: ExtractedVolumetricFog,
1049
1341
  timingCapture?: GpuTimingCapture,
1050
1342
  ): boolean {
1343
+ frameState.reflectionFallbackObservationSource = undefined;
1344
+ frameState.reflectionFallbackCompletion = undefined;
1051
1345
  // The `try / finally` wrapper advances `frameState.frameNumber` exactly
1052
1346
  // once per `recordFrame` invocation regardless of which early-return
1053
1347
  // branch is taken (camera missing, cap exceeded, pipeline pending,
@@ -1097,14 +1391,35 @@ export function recordFrame(
1097
1391
  if (!camera) return false;
1098
1392
  const pipelineState = internals.getPipelineState();
1099
1393
  if (pipelineState === null) return false;
1394
+ if (pipelineState.device !== internals.device) {
1395
+ internals.errorRegistry.fire(
1396
+ new RendererOperationError('device-operation-failed', {
1397
+ operation: 'draw',
1398
+ cause: {
1399
+ code: 'pipeline-device-mismatch',
1400
+ expected: 'pipelineState.device === runtime.device',
1401
+ hint: 'discard stale pipeline state before recording the next frame',
1402
+ detail: {
1403
+ expected: 'pipelineState.device === runtime.device',
1404
+ hint: 'discard stale pipeline state before recording the next frame',
1405
+ },
1406
+ },
1407
+ }),
1408
+ );
1409
+ return false;
1410
+ }
1100
1411
  frameState.currentFrameObservationSource = undefined;
1412
+ if (frameState.reflectionFallbackReadback !== undefined) {
1413
+ internals.device.destroyBuffer(frameState.reflectionFallbackReadback.buffer);
1414
+ frameState.reflectionFallbackReadback = undefined;
1415
+ }
1101
1416
  frameState.currentDirectionalShadowView = null;
1102
1417
  frameState.currentSpotShadowView = null;
1103
1418
 
1104
1419
  // Record-stage fold operator linear scan (groups transparent-sort entries
1105
1420
  // into fold buckets + records the fold-eligible count metric). Extracted to
1106
1421
  // computeFoldBuckets (M3/w18).
1107
- const { foldBuckets, light } = runRecordProfilePhase(profilePhase, 'record/scene-state', () => {
1422
+ const lightingPreparation = runRecordProfilePhase(profilePhase, 'record/scene-state', () => {
1108
1423
  const foldBuckets = runRecordProfilePhase(
1109
1424
  profilePhase,
1110
1425
  'record/scene-state/fold-buckets',
@@ -1113,13 +1428,13 @@ export function recordFrame(
1113
1428
 
1114
1429
  // Multi-light warnings + point/spot shadow-snapshot pin + point-shadow atlas
1115
1430
  // ensure + ExtractedLights three-arm destructure (directional fallback,
1116
- // point/spot arrays, totalLightCount). Extracted to prepareFrameLighting
1431
+ // point/spot/Rect arrays, totalLightCount). Extracted to prepareFrameLighting
1117
1432
  // (M3/w18) so recordFrame stays a skeleton.
1118
- const { light, pointLights, spotLights, totalLightCount } = runRecordProfilePhase(
1119
- profilePhase,
1120
- 'record/scene-state/lighting-prep',
1121
- () => prepareFrameLighting(internals, frameState, lights),
1433
+ const lighting = runRecordProfilePhase(profilePhase, 'record/scene-state/lighting-prep', () =>
1434
+ prepareFrameLighting(internals, frameState, lights, camera, pipelineState),
1122
1435
  );
1436
+ if (!lighting.ok) return lighting;
1437
+ const { light, totalLightCount, standard } = lighting.value;
1123
1438
 
1124
1439
  runRecordProfilePhase(profilePhase, 'record/scene-state/ambient-resolution', () => {
1125
1440
  // feat-20260520-skylight-ibl-cubemap M4 / t27 (AC-10 + F-4 nit):
@@ -1166,31 +1481,17 @@ export function recordFrame(
1166
1481
  }
1167
1482
  });
1168
1483
 
1169
- // feat-20260608-cluster-lighting M5 / w21 + M6 / w23: HDRP per-frame CPU
1170
- // cluster binning + light-data / cluster-grid / SSAO buffer uploads. Runs
1171
- // only when the HDRP pipeline is active and there is at least one punctual
1172
- // light. Extracted to a sub-function (M3/w18) so recordFrame stays a
1173
- // skeleton; fail-soft error semantics are preserved verbatim.
1174
- runRecordProfilePhase(profilePhase, 'record/scene-state/hdrp-cluster', () => {
1175
- writeHdrpClusterAndSsaoBuffers(
1176
- internals,
1177
- frameState,
1178
- camera,
1179
- pointLights,
1180
- spotLights,
1181
- profilePhase,
1182
- pipelineState.hdrpClusterMembershipPipeline !== null &&
1183
- internals.device.caps?.compute === true,
1184
- pipelineState.hdrpClusterMembershipBindGroupLayout,
1185
- );
1186
- });
1187
-
1188
1484
  // Zero-light standard-material once-warn (no Skylight + 0 direct light +
1189
1485
  // >=1 lit material -> black). Extracted to warnZeroLightStandard (M3/w18).
1190
1486
  warnZeroLightStandard(frameState, renderables, skylight, totalLightCount);
1191
1487
 
1192
- return { foldBuckets, light };
1488
+ return { ok: true as const, value: { foldBuckets, light, standard } };
1193
1489
  });
1490
+ if (!lightingPreparation.ok) {
1491
+ internals.errorRegistry.fire(lightingPreparation.error);
1492
+ return false;
1493
+ }
1494
+ const { foldBuckets, light, standard } = lightingPreparation.value;
1194
1495
 
1195
1496
  // Case E (this commit): 0 renderables = legitimate scene (LO §1.1
1196
1497
  // hello-window minimum semantic). Mirrors the Case C softening for
@@ -1276,13 +1577,32 @@ export function recordFrame(
1276
1577
 
1277
1578
  let preparedGpuDriven: PreparedGpuDrivenFrame | undefined;
1278
1579
  if (gpuDriven !== undefined) {
1580
+ const standardPbrArtifact = internals.getMaterialShaderArtifact?.(
1581
+ 'forgeax::default-standard-pbr',
1582
+ );
1279
1583
  const prepared = gpuDriven.owner.prepare({
1280
1584
  scene: gpuDriven.scene,
1281
1585
  camera,
1282
- meshes: pipelineState.meshes,
1586
+ meshes: gpuDrivenMeshesForFrame(internals, pipelineState, worlds, gpuDriven.scene),
1283
1587
  viewBindGroupLayout: pipelineState.viewBindGroupLayout,
1284
1588
  meshResidencyEpoch: internals.gpuStore.meshResidencyEpoch,
1285
- hdrp: frameState.isHdrpActive,
1589
+ // The built-in Standard graph (including its clustered transport) is
1590
+ // eligible for the GPU-driven LOD lane. The old HDRP boolean no
1591
+ // longer exists in the unified pipeline state, so it must not be
1592
+ // inferred from `standard.kind`.
1593
+ hdrp: false,
1594
+ ...(gpuDriven.activeEntityKeys === undefined
1595
+ ? {}
1596
+ : { activeEntityKeys: gpuDriven.activeEntityKeys }),
1597
+ ...(gpuDriven.activeEntityRevision === undefined
1598
+ ? {}
1599
+ : { activeEntityRevision: gpuDriven.activeEntityRevision }),
1600
+ ...(gpuDriven.telemetryCandidateCount === undefined
1601
+ ? {}
1602
+ : { telemetryCandidateCount: gpuDriven.telemetryCandidateCount }),
1603
+ ...(gpuDriven.occlusion === undefined ? {} : { occlusion: gpuDriven.occlusion }),
1604
+ ...(standardPbrArtifact === undefined ? {} : { standardPbrArtifact }),
1605
+ standardPbrPipelineState: pipelineState,
1286
1606
  });
1287
1607
  if (prepared.ok) preparedGpuDriven = prepared.value;
1288
1608
  else if (prepared.error.code !== 'rhi-not-available')
@@ -1296,7 +1616,48 @@ export function recordFrame(
1296
1616
  lights.pointShadow.length > 0 ||
1297
1617
  lights.spot.some((spot) => spot.castShadow);
1298
1618
  const gpuOwnsAllPrimaryRaster = preparedGpuDriven?.ownsAllRenderables === true;
1299
- gpuDriven?.onCoverage(gpuOwnsAllPrimaryRaster && !cpuShadowCastersActive);
1619
+ gpuDriven?.onSceneDemand(gpuDriven.owner.requiresSceneRows());
1620
+
1621
+ const cubeCaptureWork = prepareCubeCapture(internals, cubeCapture, camera);
1622
+ const reflectionFallbackCandidate = cubeCapture?.reflectionProbes?.fallbackDemand === true;
1623
+
1624
+ // Validate renderables before graph admission. Besides producing the draw
1625
+ // plan, this is the owner of per-frame cache eviction; running it before a
1626
+ // graph candidate can fail guarantees despawned instance buffers are
1627
+ // released even when the frame cannot be submitted.
1628
+ const gpuOwnedEntityKeys = preparedGpuDriven?.entityKeys ?? EMPTY_ENTITY_KEYS;
1629
+ const gpuDrivenWorldKeys = gpuDriven?.scene?.worldKeys;
1630
+ const validated = runRecordProfilePhase(profilePhase, 'record/validation', () =>
1631
+ gpuOwnsAllPrimaryRaster && !cpuShadowCastersActive && cubeCaptureWork.length === 0
1632
+ ? []
1633
+ : validateRenderables(
1634
+ internals,
1635
+ world,
1636
+ worlds,
1637
+ pipelineState,
1638
+ frameState,
1639
+ renderables,
1640
+ transparentDispatch,
1641
+ cpuShadowCastersActive || cubeCaptureWork.length > 0
1642
+ ? EMPTY_ENTITY_KEYS
1643
+ : gpuOwnedEntityKeys,
1644
+ gpuDrivenWorldKeys,
1645
+ ),
1646
+ );
1647
+ gpuDriven?.owner.recordCpuValidation(validated, gpuOwnedEntityKeys, gpuDrivenWorldKeys);
1648
+ cleanPerFrameCaches(internals, frameState, validated, gpuOwnedEntityKeys);
1649
+
1650
+ // S_fallback is the second output of the built-in Standard PBR BRDF. A
1651
+ // graph may still carry the detached owner tickets for inspection, but
1652
+ // the MRT is admitted only when every CPU-rendered primary row in this
1653
+ // frame uses that exact output contract. Unsupported compositions remain
1654
+ // fail-closed for SSR instead of submitting a device-invalid command.
1655
+ frameState.reflectionFallbackDemand =
1656
+ reflectionFallbackCandidate &&
1657
+ validated.length > 0 &&
1658
+ validated.every(
1659
+ (entry) => entry.source.material.materialShaderId === 'forgeax::default-standard-pbr',
1660
+ );
1300
1661
 
1301
1662
  const preflightGraph = runRecordProfilePhase(profilePhase, 'record/render-graph', () =>
1302
1663
  ensureCompiledFrameGraph(
@@ -1310,14 +1671,68 @@ export function recordFrame(
1310
1671
  graphShadowMapSize,
1311
1672
  preparedGpuDriven,
1312
1673
  featureGraphCandidate,
1674
+ cubeCapture?.state,
1313
1675
  cameras.length === 0,
1314
1676
  transmissionDemand,
1677
+ gpuDriven?.occlusion,
1315
1678
  graphVolumetricFog,
1316
1679
  volumeTopologyCandidate,
1680
+ standard,
1317
1681
  ),
1318
1682
  );
1319
1683
  if (preflightGraph === null) return false;
1320
1684
 
1685
+ // The compiled graph is the acceptance boundary for the prepared
1686
+ // Standard lighting declaration. Upload the same declaration only after
1687
+ // that graph is current; a failed candidate therefore cannot leave an old
1688
+ // graph consuming a new buffer generation.
1689
+ if (frameState.standardLightingGraphSignature !== standardLightingTopologySignature(standard)) {
1690
+ internals.errorRegistry.fire(
1691
+ new RendererOperationError('device-operation-failed', {
1692
+ operation: 'draw',
1693
+ cause: {
1694
+ code: 'standard-graph-generation-mismatch',
1695
+ expected: 'compiled graph and prepared Standard lighting share one topology signature',
1696
+ hint: 'retry the frame after the Standard graph candidate is accepted',
1697
+ detail: {
1698
+ expected: standardLightingTopologySignature(standard),
1699
+ actual: frameState.standardLightingGraphSignature,
1700
+ },
1701
+ },
1702
+ }),
1703
+ );
1704
+ return false;
1705
+ }
1706
+ if (standard.kind === 'clustered') {
1707
+ // The clustered HDRP payload owns the direct-light slot upload, so it
1708
+ // must also drive the shared Spot IES/Cookie resource upload from the
1709
+ // authoritative extracted-light corpus. The prepared local carrier can
1710
+ // omit the source payload on this path, leaving the shader bound to its
1711
+ // white Cookie fallback even though the authored handle is valid.
1712
+ writeSpotModifierTextures(internals, pipelineState, lights.spot);
1713
+ const clusterWrite = runRecordProfilePhase(
1714
+ profilePhase,
1715
+ 'record/scene-state/hdrp-cluster',
1716
+ () =>
1717
+ writeHdrpClusterAndSsaoBuffers(
1718
+ internals,
1719
+ frameState,
1720
+ camera,
1721
+ standard.prepared,
1722
+ standard.transport,
1723
+ profilePhase,
1724
+ pipelineState.hdrpClusterMembershipPipeline !== null,
1725
+ pipelineState.hdrpClusterMembershipBindGroupLayout,
1726
+ preparedSpotLightProjector?.lightSlotIndex,
1727
+ pipelineState,
1728
+ ),
1729
+ );
1730
+ if (!clusterWrite.ok) {
1731
+ internals.errorRegistry.fire(clusterWrite.error);
1732
+ return false;
1733
+ }
1734
+ }
1735
+
1321
1736
  // Acquire the swap-chain texture + colour view + target dimensions (with
1322
1737
  // one reconfigure-and-retry on surface-outdated). Extracted to
1323
1738
  // acquireSwapChainTarget (M3/w18); returns null on unrecoverable failure
@@ -1332,78 +1747,58 @@ export function recordFrame(
1332
1747
  const targetW = swapTarget.targetW;
1333
1748
  const targetH = swapTarget.targetH;
1334
1749
 
1335
- const temporalView =
1750
+ const previousTemporalView = frameState.lastSuccessfulTemporalView;
1751
+ const currentUnjitteredViewProjection = mat4.multiply(
1752
+ mat4.create(),
1753
+ computeProjectionMatrix(camera),
1754
+ computeViewMatrix(camera),
1755
+ );
1756
+ const currentCameraPosition = [
1757
+ camera.position[0] ?? 0,
1758
+ camera.position[1] ?? 0,
1759
+ camera.position[2] ?? 0,
1760
+ ] as const;
1761
+ const temporalCandidate = createTemporalView({
1762
+ antialias: camera.antialias,
1763
+ width: Math.max(1, targetW),
1764
+ height: Math.max(1, targetH),
1765
+ frameIndex: frameState.successfulTemporalFrameIndex ?? 0,
1766
+ viewIdentity: `camera:${camera.entityKey ?? 0}`,
1767
+ environmentSignature,
1768
+ fogSignature,
1769
+ deviceGeneration: internals.deviceScope?.generation ?? 0,
1770
+ ...(camera.historyVersion === undefined ? {} : { historyVersion: camera.historyVersion }),
1771
+ historyValid: false,
1772
+ currentUnjitteredViewProjection,
1773
+ currentCameraPosition,
1774
+ });
1775
+ const resetReason =
1336
1776
  camera.antialias === 'taa' && environmentReady
1337
- ? (() => {
1338
- const created = createTemporalView({
1339
- antialias: camera.antialias,
1340
- width: targetW,
1341
- height: targetH,
1342
- frameIndex: frameState.successfulTemporalFrameIndex ?? 0,
1343
- viewIdentity: `camera:${camera.entityKey ?? 0}`,
1344
- environmentSignature,
1345
- fogSignature,
1346
- deviceGeneration: internals.deviceScope.generation,
1347
- ...(camera.historyVersion === undefined
1348
- ? {}
1349
- : { historyVersion: camera.historyVersion }),
1350
- });
1351
- const previous = frameState.lastSuccessfulTemporalView;
1352
- const currentUnjitteredViewProjection = mat4.multiply(
1353
- mat4.create(),
1354
- computeProjectionMatrix(camera),
1355
- computeViewMatrix(camera),
1356
- );
1357
- const resetCandidate = createTemporalView({
1358
- antialias: created.input.antialias,
1359
- width: created.input.width,
1360
- height: created.input.height,
1361
- frameIndex: created.input.frameIndex,
1362
- historyVersion: created.input.historyVersion,
1363
- viewIdentity: created.input.viewIdentity,
1364
- environmentSignature,
1365
- fogSignature,
1366
- deviceGeneration: internals.deviceScope.generation,
1367
- cameraCut: created.input.cameraCut,
1368
- historyValid: false,
1369
- currentUnjitteredViewProjection,
1370
- });
1371
- const resetReason = resolveTemporalReset(previous, resetCandidate);
1372
- return createTemporalView({
1373
- ...resetCandidate.input,
1374
- frameIndex: resetReason === undefined ? resetCandidate.input.frameIndex : 0,
1375
- historyValid: previous !== undefined && resetReason === undefined,
1376
- currentUnjitteredViewProjection,
1377
- resetReason,
1378
- ...(resetReason !== undefined ||
1379
- previous?.currentUnjitteredViewProjection === undefined
1380
- ? {}
1381
- : {
1382
- previousUnjitteredViewProjection: previous.currentUnjitteredViewProjection,
1383
- }),
1384
- });
1385
- })()
1777
+ ? resolveTemporalReset(previousTemporalView, temporalCandidate)
1386
1778
  : undefined;
1779
+ const canReusePrevious = previousTemporalView !== undefined && resetReason === undefined;
1780
+ const temporalView = createTemporalView({
1781
+ ...temporalCandidate.input,
1782
+ frameIndex: resetReason === undefined ? temporalCandidate.input.frameIndex : 0,
1783
+ historyValid: camera.antialias === 'taa' && environmentReady && canReusePrevious,
1784
+ resetReason,
1785
+ ...(canReusePrevious && previousTemporalView.currentUnjitteredViewProjection !== undefined
1786
+ ? {
1787
+ previousUnjitteredViewProjection: previousTemporalView.currentUnjitteredViewProjection,
1788
+ }
1789
+ : {}),
1790
+ ...(canReusePrevious && previousTemporalView.currentCameraPosition !== undefined
1791
+ ? { previousCameraPosition: previousTemporalView.currentCameraPosition }
1792
+ : {}),
1793
+ });
1387
1794
  frameState.pendingTemporalCommit =
1388
1795
  cameras.length === 0 || !environmentReady || internals.deviceScope === undefined
1389
1796
  ? { kind: 'none' }
1390
- : temporalView === undefined
1391
- ? {
1392
- kind: 'off',
1393
- view: createTemporalView({
1394
- antialias: camera.antialias,
1395
- width: targetW,
1396
- height: targetH,
1397
- frameIndex: frameState.successfulTemporalFrameIndex ?? 0,
1398
- viewIdentity: `camera:${camera.entityKey ?? 0}`,
1399
- environmentSignature,
1400
- fogSignature,
1401
- deviceGeneration: internals.deviceScope.generation,
1402
- }),
1403
- }
1404
- : { kind: 'taa', view: temporalView };
1797
+ : temporalView.mode === 'taa'
1798
+ ? { kind: 'taa', view: temporalView }
1799
+ : { kind: 'off', view: temporalView };
1405
1800
  const recordCamera: CameraSnapshot =
1406
- temporalView === undefined ? camera : { ...camera, temporal: temporalView };
1801
+ temporalView.mode === 'taa' ? { ...camera, temporal: temporalView } : camera;
1407
1802
  let activeVolumetricFogContext: VolumetricFogFrameContext | undefined;
1408
1803
  let activeVolumetricFogSignature: VolumeTemporalSignature | undefined;
1409
1804
  if (graphVolumetricFog?.status === 'available' && baseVolumetricFogContext !== undefined) {
@@ -1501,41 +1896,6 @@ export function recordFrame(
1501
1896
  const geometryColorResolveView: TextureView | null = null;
1502
1897
  const ldrSpriteColorView: TextureView | null = null;
1503
1898
 
1504
- // Validate renderable handles + collect render plan. Empty `renderables`
1505
- // input or all-unregistered handles both produce `validatedOrdered.length === 0`,
1506
- // which is the Case E (clear-pass-only) path.
1507
- //
1508
- // feat-20260520-2d-sprite-layer-mvp M-3 / w25 (@new-surface): per-entry
1509
- // `renderableIndex` tracks the position into the original renderables[]
1510
- // array so the bucket-aware reordering below can correlate
1511
- // transparent-sort output (TransparentEntry.renderableIndex) back to
1512
- // the validated row.
1513
- // bug-20260527-renderstate-pipeline-dispatch-gap D-4:
1514
- // build a renderableIndex -> renderState map from dispatch entries
1515
- // so each ValidatedRenderable carries its per-material renderState
1516
- // override without an O(n^2) back-scan in the draw loop.
1517
- const gpuOwnedEntityKeys = preparedGpuDriven?.entityKeys ?? EMPTY_ENTITY_KEYS;
1518
- const validated = runRecordProfilePhase(profilePhase, 'record/validation', () =>
1519
- gpuOwnsAllPrimaryRaster && !cpuShadowCastersActive
1520
- ? []
1521
- : validateRenderables(
1522
- internals,
1523
- world,
1524
- worlds,
1525
- pipelineState,
1526
- frameState,
1527
- renderables,
1528
- transparentDispatch,
1529
- cpuShadowCastersActive ? EMPTY_ENTITY_KEYS : gpuOwnedEntityKeys,
1530
- ),
1531
- );
1532
- gpuDriven?.owner.recordCpuValidation(validated, gpuOwnedEntityKeys);
1533
-
1534
- // Per-frame cache clean-up: drop per-entity BG cache entries + instance
1535
- // buffers whose entityKey is absent from the current validated set (despawn
1536
- // eviction). Extracted to cleanPerFrameCaches (M3/w18).
1537
- cleanPerFrameCaches(internals, frameState, validated, gpuOwnedEntityKeys);
1538
-
1539
1899
  // Dispatch-ordered render plan: reorder validated renderables to dispatch
1540
1900
  // order, run the mesh-SSBO capacity gate (graceful truncation), and build
1541
1901
  // the fold dispatch plan. Extracted to buildDispatchPlan (M3/w18).
@@ -1605,8 +1965,8 @@ export function recordFrame(
1605
1965
  light,
1606
1966
  lights,
1607
1967
  frameState.spotShadowSnapshots,
1608
- fog,
1609
1968
  temporalView,
1969
+ preparedSpotLightProjector?.spotIndex,
1610
1970
  );
1611
1971
  if (pipelineState.pointsLinesViewBuffer !== undefined) {
1612
1972
  writePointsLinesViewUbo(
@@ -1623,11 +1983,6 @@ export function recordFrame(
1623
1983
  lights,
1624
1984
  );
1625
1985
 
1626
- // Per-frame full rewrite of the PointLight + SpotLight std430 storage
1627
- // buffers (header + first-slice cap N=4 slots). Extracted to
1628
- // writePointSpotLightBuffers (M3/w18).
1629
- writePointSpotLightBuffers(internals, pipelineState, lights);
1630
-
1631
1986
  // Per-renderable entity_world upload (batched): all N mat4+normalMatrix
1632
1987
  // slots assembled into a single contiguous scratch buffer, then flushed
1633
1988
  // as one writeBuffer call. Extracted to mesh-ssbo.ts (M3/w21) so its
@@ -1676,10 +2031,38 @@ export function recordFrame(
1676
2031
  validated.length > 0 || preparedGpuDriven !== undefined,
1677
2032
  bindGroupCounts,
1678
2033
  undefined,
1679
- false,
2034
+ true,
2035
+ standard,
1680
2036
  ),
1681
2037
  );
1682
2038
 
2039
+ for (const capture of cubeCaptureWork) {
2040
+ writeViewUbo(
2041
+ internals.device.queue,
2042
+ pipelineState.viewUniformBuffer,
2043
+ capture.faceCamera,
2044
+ light,
2045
+ lights,
2046
+ frameState.spotShadowSnapshots,
2047
+ capture.viewBindGroupDynamicOffset,
2048
+ preparedSpotLightProjector?.spotIndex,
2049
+ );
2050
+ }
2051
+ for (const probe of cubeCapture?.reflectionProbes?.graph.work ?? []) {
2052
+ if (probe.faceCamera === undefined || probe.viewBindGroupDynamicOffset === undefined)
2053
+ continue;
2054
+ writeViewUbo(
2055
+ internals.device.queue,
2056
+ pipelineState.viewUniformBuffer,
2057
+ probe.faceCamera,
2058
+ light,
2059
+ lights,
2060
+ frameState.spotShadowSnapshots,
2061
+ probe.viewBindGroupDynamicOffset,
2062
+ preparedSpotLightProjector?.spotIndex,
2063
+ );
2064
+ }
2065
+
1683
2066
  // ── feat-20260529-rendergraph-pass-abstraction M4 / w13b ───────────
1684
2067
  // The compiled typed graph is the sole per-frame pass owner.
1685
2068
  //
@@ -1711,6 +2094,7 @@ export function recordFrame(
1711
2094
  assets: internals.assets,
1712
2095
  world,
1713
2096
  gpuDrivenEntityKeys: gpuOwnedEntityKeys,
2097
+ ...(gpuDrivenWorldKeys === undefined ? {} : { gpuDrivenWorldKeys }),
1714
2098
  store: internals.gpuStore,
1715
2099
  pipelineState,
1716
2100
  bloomResources: pipelineState.perPassResources.getBloomResources?.() ?? null,
@@ -1755,16 +2139,34 @@ export function recordFrame(
1755
2139
  dispatch: transparentDispatch,
1756
2140
  hdrpClusterBindGroup,
1757
2141
  hdrpClusterMembershipBindGroup,
2142
+ ...(gpuDriven?.occlusion === undefined ? {} : { occlusion: gpuDriven.occlusion }),
2143
+ standardLighting: standard,
1758
2144
  foldDispatchPlan,
1759
2145
  materialSlotIndices,
1760
2146
  materialSlots,
1761
2147
  materialSlotOwners,
1762
2148
  materialSlotCount,
2149
+ ...(cubeCapture?.reflectionProbes === undefined
2150
+ ? {}
2151
+ : { reflectionProbes: cubeCapture.reflectionProbes }),
1763
2152
  pointsLines: pointsLinesOwner,
1764
2153
  materialBgAssemblyCache: frameState.materialBgAssemblyCache,
1765
2154
  directionalShadowCacheReuse: directionalShadowCache.reuse,
1766
2155
  ...(profilePhase !== undefined ? { profilePhase } : {}),
1767
2156
  };
2157
+ const fallbackReadbackRequest = createReflectionFallbackReadbackRequest(internals, frameState);
2158
+ if (fallbackReadbackRequest !== undefined) {
2159
+ frameState.reflectionFallbackReadback = fallbackReadbackRequest;
2160
+ }
2161
+ let completion: Promise<unknown> | undefined;
2162
+ let fallbackReadback:
2163
+ | Promise<{
2164
+ readonly linearHdr: readonly [number, number, number, number];
2165
+ readonly hash: string;
2166
+ readonly graphGeneration: number;
2167
+ readonly textureIdentity: number;
2168
+ }>
2169
+ | undefined;
1768
2170
  const stagedTemporalFrame = {
1769
2171
  frameId: frameState.frameNumber,
1770
2172
  currentViewProjection: new Float32Array(currentViewProjection),
@@ -1788,9 +2190,65 @@ export function recordFrame(
1788
2190
  profilePhase === undefined
1789
2191
  ? undefined
1790
2192
  : (pass, encode) => profilePhase(graphExecutionPhase(pass.name), encode),
2193
+ gpuDriven === undefined
2194
+ ? undefined
2195
+ : {
2196
+ afterGraphExecute: () => gpuDriven.occlusion?.recordResolve(encoder) ?? ok(undefined),
2197
+ onSubmitted: () => {
2198
+ gpuDriven.occlusion?.commit(true, frameState.frameNumber, profilePhase);
2199
+ },
2200
+ onAborted: () => {
2201
+ gpuDriven.occlusion?.commit(false, frameState.frameNumber);
2202
+ },
2203
+ },
2204
+ cubeCaptureWork.length === 0 ? undefined : cubeCaptureWork.map((work) => work.faceIndex),
2205
+ (done) => {
2206
+ completion = done;
2207
+ if (fallbackReadbackRequest !== undefined) {
2208
+ fallbackReadback = done.then(
2209
+ () =>
2210
+ mapReflectionFallbackReadback(
2211
+ internals,
2212
+ fallbackReadbackRequest,
2213
+ cubeCapture?.reflectionProbes?.fallbackHasNonNeutral !== true,
2214
+ ),
2215
+ (cause) => {
2216
+ internals.device.destroyBuffer(fallbackReadbackRequest.buffer);
2217
+ throw cause;
2218
+ },
2219
+ );
2220
+ }
2221
+ },
1791
2222
  timingCapture,
1792
2223
  ),
1793
2224
  );
2225
+ if (cubeCapture !== undefined && cubeCaptureWork.length > 0) {
2226
+ const completed = cubeCapture.scheduler.completeSubmission(submitted, completion);
2227
+ if (!completed.ok) internals.errorRegistry.fire(completed.error);
2228
+ }
2229
+ if (!submitted && fallbackReadbackRequest !== undefined) {
2230
+ internals.device.destroyBuffer(fallbackReadbackRequest.buffer);
2231
+ }
2232
+ const reflectionFallbackCompletion = cubeCapture?.reflectionProbes?.completeSubmission(
2233
+ submitted,
2234
+ completion,
2235
+ fallbackReadbackRequest === undefined || fallbackReadback === undefined
2236
+ ? undefined
2237
+ : {
2238
+ format: fallbackReadbackRequest.expected.format,
2239
+ size: {
2240
+ width: fallbackReadbackRequest.expected.width,
2241
+ height: fallbackReadbackRequest.expected.height,
2242
+ },
2243
+ frameId: fallbackReadbackRequest.expected.frameId,
2244
+ graphGeneration: fallbackReadbackRequest.expected.graphGeneration,
2245
+ textureIdentity: fallbackReadbackRequest.expected.textureIdentity,
2246
+ readback: fallbackReadback,
2247
+ },
2248
+ frameState.reflectionFallbackDemand,
2249
+ );
2250
+ frameState.reflectionFallbackCompletion = reflectionFallbackCompletion;
2251
+ frameState.reflectionFallbackObservationSource = undefined;
1794
2252
  if (submitted && !directionalShadowCache.reuse) {
1795
2253
  frameState.directionalShadowCache = frameState.directionalShadowCacheRecorded
1796
2254
  ? directionalShadowCache.next
@@ -1837,6 +2295,11 @@ export function recordFrame(
1837
2295
  frameState.volumetricFogAccepted = volumetricFog;
1838
2296
  frameState.volumetricFogAcceptedContext = preparedVolumetricFog;
1839
2297
  }
2298
+ frameState.standardLightingInspection = inspectStandardLighting(standard);
2299
+ frameState.pointShadowInspection = inspectPointShadow(
2300
+ lights.pointShadow,
2301
+ SHADOW_ATLAS_DEFAULT_LAYERS,
2302
+ );
1840
2303
  }
1841
2304
  return submitted;
1842
2305
  } finally {
@@ -1857,9 +2320,9 @@ export function recordFrame(
1857
2320
  );
1858
2321
  }
1859
2322
  frameState.frameNumber += 1;
1860
- // feat-20260608-cluster-lighting M5 / w22: clear HDRP once-per-frame fired
2323
+ // feat-20260608-cluster-lighting M5 / w22: clear Standard once-per-frame fired
1861
2324
  // set so the next frame re-fires if the condition persists.
1862
- frameState.hdrpOncePerFrameFired.clear();
2325
+ frameState.standardOncePerFrameFired.clear();
1863
2326
  }
1864
2327
  }
1865
2328
 
@@ -1871,7 +2334,7 @@ interface DirectionalShadowCacheDecision {
1871
2334
  function readWorldState(lease: RenderReadLease): DirectionalShadowWorldState {
1872
2335
  return {
1873
2336
  worldIdentity: lease.worldIdentity,
1874
- changeCursor: lease.inspectCursor(),
2337
+ version: lease.captureVersion(),
1875
2338
  };
1876
2339
  }
1877
2340
 
@@ -1893,16 +2356,14 @@ function sameWorldState(
1893
2356
  if (cachedState.worldIdentity !== lease.worldIdentity) return false;
1894
2357
  let changes: ReturnType<RenderReadLease['readChanges']>;
1895
2358
  try {
1896
- changes = lease.readChanges(cachedState.changeCursor);
2359
+ changes = lease.readChanges(cachedState.version);
1897
2360
  } catch {
1898
- // A lease may have evicted an old cursor after another owner consumed
1899
- // its bounded cursor window. Treat that as stale render evidence and
1900
- // rebuild the atlas from the current lease rather than reaching into
1901
- // World for a second mutation clock.
2361
+ // Treat malformed or foreign version evidence as stale and rebuild the
2362
+ // atlas rather than reaching through World for a second mutation clock.
1902
2363
  return false;
1903
2364
  }
1904
2365
  if (changes.status !== 'ok') return false;
1905
- if (changes.world.records.length !== 0 || changes.sharedRefs.records.length !== 0) {
2366
+ if (changes.world.changedComponentIds.length !== 0 || changes.sharedRefs.records.length !== 0) {
1906
2367
  return false;
1907
2368
  }
1908
2369
  }
@@ -1927,6 +2388,20 @@ function sameLightViewProj(
1927
2388
  return true;
1928
2389
  }
1929
2390
 
2391
+ function sameDirectionalShadowQuality(
2392
+ previous: DirectionalShadowQuality,
2393
+ next: DirectionalShadowQuality,
2394
+ ): boolean {
2395
+ if (previous.kind !== next.kind) return false;
2396
+ if (previous.kind === 'pcf' && next.kind === 'pcf') return previous.kernel === next.kernel;
2397
+ if (previous.kind !== 'pcss' || next.kind !== 'pcss') return false;
2398
+ return (
2399
+ previous.preset === next.preset &&
2400
+ previous.angularRadiusRadians === next.angularRadiusRadians &&
2401
+ previous.maxPenumbraTexels === next.maxPenumbraTexels
2402
+ );
2403
+ }
2404
+
1930
2405
  function prepareDirectionalShadowCache(
1931
2406
  internals: RenderSystemInternals,
1932
2407
  frameState: RenderFrameState,
@@ -1946,6 +2421,7 @@ function prepareDirectionalShadowCache(
1946
2421
  cascadeCount === undefined ||
1947
2422
  cascadeCount <= 0 ||
1948
2423
  lightViewProj === undefined ||
2424
+ lights.directionalShadowQuality === undefined ||
1949
2425
  validatedOrdered.length === 0
1950
2426
  ) {
1951
2427
  return { reuse: false, next: null };
@@ -1958,6 +2434,10 @@ function prepareDirectionalShadowCache(
1958
2434
  cached !== null &&
1959
2435
  cached.shadowMapSize === shadowMapSize &&
1960
2436
  cached.cascadeCount === cascadeCount &&
2437
+ sameDirectionalShadowQuality(
2438
+ cached.directionalShadowQuality,
2439
+ lights.directionalShadowQuality,
2440
+ ) &&
1961
2441
  cached.pipelineHandle === frameState.installedPipelineHandle &&
1962
2442
  cached.graphTopologyKey === frameState.compiledFrameGraphTopologyKey &&
1963
2443
  cached.assetCatalogEpoch === assetCatalogEpoch &&
@@ -1986,6 +2466,7 @@ function prepareDirectionalShadowCache(
1986
2466
  graphTopologyKey: frameState.compiledFrameGraphTopologyKey,
1987
2467
  shadowMapSize,
1988
2468
  cascadeCount,
2469
+ directionalShadowQuality: lights.directionalShadowQuality,
1989
2470
  lightViewProj: lightViewProj.map((matrix) => new Float32Array(matrix)),
1990
2471
  meshResidencyEpoch,
1991
2472
  },
@@ -2018,6 +2499,7 @@ function validateRenderables(
2018
2499
  renderables: readonly RenderableSnapshot[],
2019
2500
  transparentDispatch: readonly DispatchEntry[],
2020
2501
  excludedEntityKeys: ReadonlySet<number> = new Set<number>(),
2502
+ worldKeys?: readonly number[],
2021
2503
  ): ValidatedRenderable[] {
2022
2504
  // bug-20260527-renderstate-pipeline-dispatch-gap D-4:
2023
2505
  // build a renderableIndex -> renderState map from dispatch entries
@@ -2043,7 +2525,8 @@ function validateRenderables(
2043
2525
  for (let rIdx = 0; rIdx < renderables.length; rIdx++) {
2044
2526
  const r = renderables[rIdx];
2045
2527
  if (r === undefined) continue;
2046
- if (excludedEntityKeys.has(worldEntityKey(r.worldId, r.entityKey))) continue;
2528
+ if (excludedEntityKeys.has(worldEntityKey(worldKeys?.[r.worldId] ?? r.worldId, r.entityKey)))
2529
+ continue;
2047
2530
  // feat-20260709-editor-world-partition ENGINE-fix-round2 (defect 2): resolve
2048
2531
  // this renderable's mesh against the world it was EXTRACTED from
2049
2532
  // (worlds[r.worldId]) — NOT the single resource-owner `world`. Builtin mesh
@@ -2078,11 +2561,17 @@ function validateRenderables(
2078
2561
  // frames hit the O(1) cache.
2079
2562
  const meshAssetHandle = toShared<'MeshAsset'>(r.assetHandle);
2080
2563
  let meshHandles = internals.gpuStore.getMeshGpuHandles(meshAssetHandle, renderableWorld);
2564
+ const lodMeshes = r.lods?.map((lod) => internals.assets.lookup<MeshAsset>(lod.mesh));
2565
+ const resolvedLodMeshes =
2566
+ lodMeshes?.every((lod): lod is MeshAsset => lod?.kind === 'mesh') === true
2567
+ ? lodMeshes
2568
+ : undefined;
2081
2569
  if (meshHandles === undefined && r.assetHandle > handleSlot(HANDLE_NINESLICE_QUAD)) {
2082
2570
  const residentRes = internals.gpuStore.ensureResident(
2083
2571
  meshAssetHandle,
2084
2572
  assetRes.value,
2085
2573
  renderableWorld,
2574
+ resolvedLodMeshes,
2086
2575
  );
2087
2576
  if (residentRes.ok) {
2088
2577
  meshHandles = residentRes.value;
@@ -2264,6 +2753,21 @@ function buildDispatchPlan(
2264
2753
  materialSlotOwners: readonly number[];
2265
2754
  materialSlotCount: number;
2266
2755
  } {
2756
+ // A production GPU-owned frame has no CPU validated rows by design. The
2757
+ // dispatch list can still contain one entry per authored entity, but no
2758
+ // CPU plan, material table, or fold bucket is consumed in that lane. Return
2759
+ // the empty plan without walking that list so a steady 100k scene remains
2760
+ // bounded by the persistent GPU batches rather than per-entity record work.
2761
+ if (validated.length === 0) {
2762
+ return {
2763
+ validatedOrdered: [],
2764
+ foldDispatchPlan: null,
2765
+ materialSlotIndices: [],
2766
+ materialSlots: [],
2767
+ materialSlotOwners: [],
2768
+ materialSlotCount: 0,
2769
+ };
2770
+ }
2267
2771
  // M3 / w26: dispatch-ordered render. The dispatch list is pre-sorted
2268
2772
  // by queue (ascending, stable) by the extract stage per plan-strategy D-3.
2269
2773
  // Reorder validated renderables to follow the dispatch order, falling