@forgeax/engine-render 0.1.32 → 0.1.34

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 (624) hide show
  1. package/README.md +1374 -142
  2. package/dist/assembly/analytic-fog-registration.d.ts +3 -0
  3. package/dist/assembly/analytic-fog-registration.d.ts.map +1 -0
  4. package/dist/assembly/backend-contract.d.ts +2 -0
  5. package/dist/assembly/backend-contract.d.ts.map +1 -1
  6. package/dist/assembly/barrel-distortion-inspection.d.ts +3 -0
  7. package/dist/assembly/barrel-distortion-inspection.d.ts.map +1 -0
  8. package/dist/assembly/canvas-draw-guard.d.ts +3 -0
  9. package/dist/assembly/canvas-draw-guard.d.ts.map +1 -0
  10. package/dist/assembly/cloud-shadow-entry.d.ts +4 -0
  11. package/dist/assembly/cloud-shadow-entry.d.ts.map +1 -0
  12. package/dist/assembly/dynamic-geometry-host.d.ts +4 -1
  13. package/dist/assembly/dynamic-geometry-host.d.ts.map +1 -1
  14. package/dist/assembly/dynamic-geometry-runtime.d.ts +1 -1
  15. package/dist/assembly/dynamic-geometry-runtime.d.ts.map +1 -1
  16. package/dist/assembly/factory.d.ts +1 -1
  17. package/dist/assembly/factory.d.ts.map +1 -1
  18. package/dist/assembly/fxaa-registration.d.ts +4 -0
  19. package/dist/assembly/fxaa-registration.d.ts.map +1 -0
  20. package/dist/assembly/host-contract.d.ts +3 -3
  21. package/dist/assembly/host-contract.d.ts.map +1 -1
  22. package/dist/assembly/lod-observation.d.ts +4 -0
  23. package/dist/assembly/lod-observation.d.ts.map +1 -0
  24. package/dist/assembly/material/assembly.d.ts.map +1 -1
  25. package/dist/assembly/material/pipeline-helpers.d.ts +5 -0
  26. package/dist/assembly/material/pipeline-helpers.d.ts.map +1 -1
  27. package/dist/assembly/material-shader-policy.d.ts +16 -2
  28. package/dist/assembly/material-shader-policy.d.ts.map +1 -1
  29. package/dist/assembly/material-shader-prewarm.d.ts.map +1 -1
  30. package/dist/assembly/motion-blur-registration.d.ts +3 -0
  31. package/dist/assembly/motion-blur-registration.d.ts.map +1 -0
  32. package/dist/assembly/recovery/device-loss-fanout.d.ts.map +1 -1
  33. package/dist/assembly/recovery/renderer-recover.d.ts +2 -0
  34. package/dist/assembly/recovery/renderer-recover.d.ts.map +1 -1
  35. package/dist/assembly/render-feature-post-processes.d.ts +6 -0
  36. package/dist/assembly/render-feature-post-processes.d.ts.map +1 -0
  37. package/dist/assembly/renderer-facade.d.ts.map +1 -1
  38. package/dist/assembly/renderer-feature-inspection.d.ts +1 -1
  39. package/dist/assembly/renderer-feature-inspection.d.ts.map +1 -1
  40. package/dist/assembly/renderer-frame-transaction.d.ts +12 -0
  41. package/dist/assembly/renderer-frame-transaction.d.ts.map +1 -1
  42. package/dist/assembly/renderer-inspection.d.ts +11 -0
  43. package/dist/assembly/renderer-inspection.d.ts.map +1 -1
  44. package/dist/assembly/temporal-post-process-prewarm.d.ts +13 -0
  45. package/dist/assembly/temporal-post-process-prewarm.d.ts.map +1 -0
  46. package/dist/assembly/webgpu-pbr-ready.d.ts +1 -0
  47. package/dist/assembly/webgpu-pbr-ready.d.ts.map +1 -1
  48. package/dist/assembly/webgpu-ready-contract.d.ts +4 -3
  49. package/dist/assembly/webgpu-ready-contract.d.ts.map +1 -1
  50. package/dist/assembly/webgpu-ready-per-pass.d.ts +39 -0
  51. package/dist/assembly/webgpu-ready-per-pass.d.ts.map +1 -0
  52. package/dist/assembly/webgpu-ready.d.ts +2 -2
  53. package/dist/assembly/webgpu-ready.d.ts.map +1 -1
  54. package/dist/assembly/webgpu-renderer-bootstrap.d.ts.map +1 -1
  55. package/dist/assembly/webgpu-renderer-observation-owner.d.ts +35 -0
  56. package/dist/assembly/webgpu-renderer-observation-owner.d.ts.map +1 -0
  57. package/dist/assembly/webgpu-renderer.d.ts +1 -1
  58. package/dist/assembly/webgpu-renderer.d.ts.map +1 -1
  59. package/dist/authoring.mjs +1 -1
  60. package/dist/barrel-distortion.d.ts +47 -0
  61. package/dist/barrel-distortion.d.ts.map +1 -0
  62. package/dist/bloom-admission.d.ts +13 -0
  63. package/dist/bloom-admission.d.ts.map +1 -0
  64. package/dist/{chunk-PA5DRJNI.mjs → chunk-ELJFJ2BU.mjs} +2 -2
  65. package/dist/chunk-ELJFJ2BU.mjs.map +1 -0
  66. package/dist/{chunk-JCIRYQ4V.mjs → chunk-HEB564WF.mjs} +10 -4
  67. package/dist/chunk-HEB564WF.mjs.map +1 -0
  68. package/dist/{chunk-JRIPCMYC.mjs → chunk-MKDIT2EM.mjs} +1584 -375
  69. package/dist/chunk-MKDIT2EM.mjs.map +1 -0
  70. package/dist/{chunk-S665NJNF.mjs → chunk-OCDLEI6I.mjs} +3 -3
  71. package/dist/{chunk-S665NJNF.mjs.map → chunk-OCDLEI6I.mjs.map} +1 -1
  72. package/dist/chunk-ODZWR6JQ.mjs +4927 -0
  73. package/dist/chunk-ODZWR6JQ.mjs.map +1 -0
  74. package/dist/{chunk-DRR7D3KX.mjs → chunk-SHFUFJ5N.mjs} +192 -18
  75. package/dist/chunk-SHFUFJ5N.mjs.map +1 -0
  76. package/dist/{chunk-4NNZQCL3.mjs → chunk-UI5SFODX.mjs} +223 -277
  77. package/dist/chunk-UI5SFODX.mjs.map +1 -0
  78. package/dist/{chunk-GWCEMDVR.mjs → chunk-UINHLCZA.mjs} +787 -80
  79. package/dist/chunk-UINHLCZA.mjs.map +1 -0
  80. package/dist/{chunk-6TFBXLRJ.mjs → chunk-WEX45HKH.mjs} +222 -337
  81. package/dist/chunk-WEX45HKH.mjs.map +1 -0
  82. package/dist/cloud/density.d.ts +55 -0
  83. package/dist/cloud/density.d.ts.map +1 -0
  84. package/dist/cloud/extract.d.ts +25 -0
  85. package/dist/cloud/extract.d.ts.map +1 -0
  86. package/dist/cloud/feature.d.ts +57 -0
  87. package/dist/cloud/feature.d.ts.map +1 -0
  88. package/dist/cloud/frame-context.d.ts +8 -0
  89. package/dist/cloud/frame-context.d.ts.map +1 -0
  90. package/dist/cloud/inspection.d.ts +59 -0
  91. package/dist/cloud/inspection.d.ts.map +1 -0
  92. package/dist/cloud/optics.d.ts +56 -0
  93. package/dist/cloud/optics.d.ts.map +1 -0
  94. package/dist/cloud/parameters.d.ts +73 -0
  95. package/dist/cloud/parameters.d.ts.map +1 -0
  96. package/dist/cloud/resources.d.ts +60 -0
  97. package/dist/cloud/resources.d.ts.map +1 -0
  98. package/dist/cloud/shadow.d.ts +42 -0
  99. package/dist/cloud/shadow.d.ts.map +1 -0
  100. package/dist/cloud/temporal.d.ts +74 -0
  101. package/dist/cloud/temporal.d.ts.map +1 -0
  102. package/dist/components/atmosphere.d.ts +2 -0
  103. package/dist/components/atmosphere.d.ts.map +1 -1
  104. package/dist/components/barrel-distortion.d.ts +13 -0
  105. package/dist/components/barrel-distortion.d.ts.map +1 -0
  106. package/dist/components/camera.d.ts +20 -4
  107. package/dist/components/camera.d.ts.map +1 -1
  108. package/dist/components/cloud-layer.d.ts +29 -0
  109. package/dist/components/cloud-layer.d.ts.map +1 -0
  110. package/dist/components/index.d.ts +2 -0
  111. package/dist/components/index.d.ts.map +1 -1
  112. package/dist/components/instances.d.ts +20 -6
  113. package/dist/components/instances.d.ts.map +1 -1
  114. package/dist/components/mesh-filter.d.ts +6 -5
  115. package/dist/components/mesh-filter.d.ts.map +1 -1
  116. package/dist/components/motion-blur.d.ts +1 -0
  117. package/dist/components/motion-blur.d.ts.map +1 -1
  118. package/dist/construct-renderer.d.ts +1 -1
  119. package/dist/construct-renderer.d.ts.map +1 -1
  120. package/dist/construct-renderer.mjs +9751 -3528
  121. package/dist/construct-renderer.mjs.map +1 -1
  122. package/dist/device/gpu-residency.d.ts +6 -0
  123. package/dist/device/gpu-residency.d.ts.map +1 -1
  124. package/dist/dynamic-geometry.d.ts +5 -1
  125. package/dist/dynamic-geometry.d.ts.map +1 -1
  126. package/dist/environment/analytic-fog.d.ts +6 -0
  127. package/dist/environment/analytic-fog.d.ts.map +1 -0
  128. package/dist/errors/cloud.d.ts +59 -0
  129. package/dist/errors/cloud.d.ts.map +1 -0
  130. package/dist/errors/render.d.ts +23 -5
  131. package/dist/errors/render.d.ts.map +1 -1
  132. package/dist/examples/single-layer-medium-surface.d.ts +47 -0
  133. package/dist/examples/single-layer-medium-surface.d.ts.map +1 -0
  134. package/dist/extract/camera.d.ts.map +1 -1
  135. package/dist/extract/contracts.d.ts +12 -1
  136. package/dist/extract/contracts.d.ts.map +1 -1
  137. package/dist/extract/environment.d.ts +2 -0
  138. package/dist/extract/environment.d.ts.map +1 -1
  139. package/dist/features/barrel-distortion.d.ts +29 -0
  140. package/dist/features/barrel-distortion.d.ts.map +1 -0
  141. package/dist/features/error-reporting.d.ts +17 -0
  142. package/dist/features/error-reporting.d.ts.map +1 -0
  143. package/dist/features/host.d.ts +15 -0
  144. package/dist/features/host.d.ts.map +1 -1
  145. package/dist/features/motion-blur/motion-blur-feature.d.ts +20 -2
  146. package/dist/features/motion-blur/motion-blur-feature.d.ts.map +1 -1
  147. package/dist/features/motion-blur/motion-blur-params.d.ts +21 -0
  148. package/dist/features/motion-blur/motion-blur-params.d.ts.map +1 -1
  149. package/dist/features/motion-blur/motion-blur-runtime.d.ts +54 -0
  150. package/dist/features/motion-blur/motion-blur-runtime.d.ts.map +1 -0
  151. package/dist/features/plan.d.ts +15 -4
  152. package/dist/features/plan.d.ts.map +1 -1
  153. package/dist/features/prepared-gpu-work.d.ts +9 -5
  154. package/dist/features/prepared-gpu-work.d.ts.map +1 -1
  155. package/dist/features/render-graph-compute.d.ts.map +1 -1
  156. package/dist/features/render-graph-contribution.d.ts +5 -0
  157. package/dist/features/render-graph-contribution.d.ts.map +1 -1
  158. package/dist/features/render-graph-raster.d.ts +1 -0
  159. package/dist/features/render-graph-raster.d.ts.map +1 -1
  160. package/dist/features/targets.d.ts +2 -0
  161. package/dist/features/targets.d.ts.map +1 -1
  162. package/dist/features/types.d.ts +44 -0
  163. package/dist/features/types.d.ts.map +1 -1
  164. package/dist/fullscreen-post-process-pass.d.ts +15 -7
  165. package/dist/fullscreen-post-process-pass.d.ts.map +1 -1
  166. package/dist/gpu-dirty-ranges.d.ts +11 -0
  167. package/dist/gpu-dirty-ranges.d.ts.map +1 -0
  168. package/dist/gpu-driven/batch-topology.d.ts +17 -0
  169. package/dist/gpu-driven/batch-topology.d.ts.map +1 -1
  170. package/dist/gpu-driven/material-artifacts.d.ts +6 -0
  171. package/dist/gpu-driven/material-artifacts.d.ts.map +1 -1
  172. package/dist/gpu-driven/prepared-draw.d.ts +7 -0
  173. package/dist/gpu-driven/prepared-draw.d.ts.map +1 -1
  174. package/dist/gpu-driven/production-raster-material.d.ts +178 -0
  175. package/dist/gpu-driven/production-raster-material.d.ts.map +1 -0
  176. package/dist/gpu-driven/production-raster.d.ts +50 -4
  177. package/dist/gpu-driven/production-raster.d.ts.map +1 -1
  178. package/dist/gpu-driven/resource-allocation.d.ts +31 -0
  179. package/dist/gpu-driven/resource-allocation.d.ts.map +1 -0
  180. package/dist/gpu-driven/shadow-ownership.d.ts +14 -0
  181. package/dist/gpu-driven/shadow-ownership.d.ts.map +1 -0
  182. package/dist/gpu-driven/view-gpu.d.ts +35 -2
  183. package/dist/gpu-driven/view-gpu.d.ts.map +1 -1
  184. package/dist/gpu-scene.d.ts +11 -1
  185. package/dist/gpu-scene.d.ts.map +1 -1
  186. package/dist/hdrp-buffers.d.ts +2 -2
  187. package/dist/hdrp-buffers.d.ts.map +1 -1
  188. package/dist/ibl/skylight-bind-group.d.ts +10 -1
  189. package/dist/ibl/skylight-bind-group.d.ts.map +1 -1
  190. package/dist/index.d.ts +31 -8
  191. package/dist/index.d.ts.map +1 -1
  192. package/dist/index.mjs +258 -8
  193. package/dist/index.mjs.map +1 -1
  194. package/dist/inspection-types.d.ts +178 -3
  195. package/dist/inspection-types.d.ts.map +1 -1
  196. package/dist/instance-buffer-cache.d.ts +4 -4
  197. package/dist/instance-buffer-cache.d.ts.map +1 -1
  198. package/dist/instances.d.ts +49 -94
  199. package/dist/instances.d.ts.map +1 -1
  200. package/dist/internal.mjs +7 -7
  201. package/dist/pbr-pipeline.d.ts +19 -1
  202. package/dist/pbr-pipeline.d.ts.map +1 -1
  203. package/dist/pipeline/single-layer-medium-passes.d.ts +73 -0
  204. package/dist/pipeline/single-layer-medium-passes.d.ts.map +1 -0
  205. package/dist/pipeline/standard-forward-lane.d.ts.map +1 -1
  206. package/dist/pipeline/standard-output/graph.d.ts.map +1 -1
  207. package/dist/pipeline/standard-output/types.d.ts +4 -2
  208. package/dist/pipeline/standard-output/types.d.ts.map +1 -1
  209. package/dist/pipeline/standard-pipeline.d.ts.map +1 -1
  210. package/dist/pipeline/standard-post.d.ts +28 -19
  211. package/dist/pipeline/standard-post.d.ts.map +1 -1
  212. package/dist/pipeline-builder.d.ts +1 -1
  213. package/dist/pipeline-builder.d.ts.map +1 -1
  214. package/dist/pipeline-spec.d.ts +2 -2
  215. package/dist/plugin.d.ts.map +1 -1
  216. package/dist/prepare/prepared-graphics-resolver.d.ts +2 -0
  217. package/dist/prepare/prepared-graphics-resolver.d.ts.map +1 -1
  218. package/dist/record/barrel-distortion-frame.d.ts +15 -0
  219. package/dist/record/barrel-distortion-frame.d.ts.map +1 -0
  220. package/dist/record/dynamic-geometry-consumption.d.ts +10 -2
  221. package/dist/record/dynamic-geometry-consumption.d.ts.map +1 -1
  222. package/dist/record/fold-buckets.d.ts +95 -0
  223. package/dist/record/fold-buckets.d.ts.map +1 -0
  224. package/dist/record/fold-instance-buffer.d.ts +6 -0
  225. package/dist/record/fold-instance-buffer.d.ts.map +1 -0
  226. package/dist/record/frame-lighting.d.ts +1 -0
  227. package/dist/record/frame-lighting.d.ts.map +1 -1
  228. package/dist/record/frame-snapshot.d.ts +40 -3
  229. package/dist/record/frame-snapshot.d.ts.map +1 -1
  230. package/dist/record/frame-targets.d.ts.map +1 -1
  231. package/dist/record/frame.d.ts +14 -62
  232. package/dist/record/frame.d.ts.map +1 -1
  233. package/dist/record/gpu-draw-receipts.d.ts +11 -0
  234. package/dist/record/gpu-draw-receipts.d.ts.map +1 -0
  235. package/dist/record/gpu-timing.d.ts +8 -0
  236. package/dist/record/gpu-timing.d.ts.map +1 -1
  237. package/dist/record/main-pass-geometry.d.ts +21 -5
  238. package/dist/record/main-pass-geometry.d.ts.map +1 -1
  239. package/dist/record/main-pass-material.d.ts +7 -1
  240. package/dist/record/main-pass-material.d.ts.map +1 -1
  241. package/dist/record/main-pass-sprite-draws.d.ts.map +1 -1
  242. package/dist/record/main-pass.d.ts +13 -2
  243. package/dist/record/main-pass.d.ts.map +1 -1
  244. package/dist/record/mesh-ssbo.d.ts +6 -5
  245. package/dist/record/mesh-ssbo.d.ts.map +1 -1
  246. package/dist/record/motion-blur-receipt.d.ts +11 -0
  247. package/dist/record/motion-blur-receipt.d.ts.map +1 -0
  248. package/dist/record/probe-blend-buffer.d.ts +16 -0
  249. package/dist/record/probe-blend-buffer.d.ts.map +1 -0
  250. package/dist/record/recovery-pipeline.d.ts.map +1 -1
  251. package/dist/record/render-context.d.ts +34 -31
  252. package/dist/record/render-context.d.ts.map +1 -1
  253. package/dist/record/shadow-pass.d.ts +2 -0
  254. package/dist/record/shadow-pass.d.ts.map +1 -1
  255. package/dist/record/skybox-post-pass.d.ts +6 -4
  256. package/dist/record/skybox-post-pass.d.ts.map +1 -1
  257. package/dist/record/typed-frame-graph.d.ts +22 -2
  258. package/dist/record/typed-frame-graph.d.ts.map +1 -1
  259. package/dist/record/view-ubo.d.ts +11 -3
  260. package/dist/record/view-ubo.d.ts.map +1 -1
  261. package/dist/record/volume-params.d.ts +9 -0
  262. package/dist/record/volume-params.d.ts.map +1 -0
  263. package/dist/recovery/render-system-candidate.d.ts +10 -0
  264. package/dist/recovery/render-system-candidate.d.ts.map +1 -1
  265. package/dist/reflection/record-owner.d.ts.map +1 -1
  266. package/dist/render-contract.d.ts +66 -8
  267. package/dist/render-contract.d.ts.map +1 -1
  268. package/dist/render-graph-primitives.d.ts.map +1 -1
  269. package/dist/render-pipeline.d.ts +106 -3
  270. package/dist/render-pipeline.d.ts.map +1 -1
  271. package/dist/render-system-extract-tail.d.ts +3 -2
  272. package/dist/render-system-extract-tail.d.ts.map +1 -1
  273. package/dist/render-system-extract.d.ts +24 -10
  274. package/dist/render-system-extract.d.ts.map +1 -1
  275. package/dist/render-system-presentation.d.ts +23 -0
  276. package/dist/render-system-presentation.d.ts.map +1 -0
  277. package/dist/render-system-producer-roots.d.ts +10 -0
  278. package/dist/render-system-producer-roots.d.ts.map +1 -0
  279. package/dist/render-system-residency.d.ts +12 -0
  280. package/dist/render-system-residency.d.ts.map +1 -0
  281. package/dist/render-system.d.ts +26 -15
  282. package/dist/render-system.d.ts.map +1 -1
  283. package/dist/scene/probe-blend.d.ts +27 -0
  284. package/dist/scene/probe-blend.d.ts.map +1 -1
  285. package/dist/scene/render-scene.d.ts +14 -5
  286. package/dist/scene/render-scene.d.ts.map +1 -1
  287. package/dist/ssao-buffers.d.ts +9 -0
  288. package/dist/ssao-buffers.d.ts.map +1 -1
  289. package/dist/surface/dynamic-input.d.ts +133 -0
  290. package/dist/surface/dynamic-input.d.ts.map +1 -0
  291. package/dist/surface/gpu-driven.d.ts +41 -0
  292. package/dist/surface/gpu-driven.d.ts.map +1 -0
  293. package/dist/surface/submission-observation.d.ts +136 -0
  294. package/dist/surface/submission-observation.d.ts.map +1 -0
  295. package/dist/systems/skin-palette-allocator.d.ts.map +1 -1
  296. package/dist/temporal/frame-coordinator.d.ts.map +1 -1
  297. package/dist/temporal/frame.d.ts +7 -0
  298. package/dist/temporal/frame.d.ts.map +1 -1
  299. package/dist/temporal/gpu.d.ts +13 -1
  300. package/dist/temporal/gpu.d.ts.map +1 -1
  301. package/dist/temporal/index.mjs +5 -5
  302. package/dist/temporal/inspection.d.ts.map +1 -1
  303. package/dist/temporal/standard-scene-data.d.ts.map +1 -1
  304. package/dist/temporal/target-coverage-attachment.d.ts.map +1 -1
  305. package/dist/temporal/view.d.ts +6 -1
  306. package/dist/temporal/view.d.ts.map +1 -1
  307. package/dist/transmission/single-layer-medium.d.ts +89 -0
  308. package/dist/transmission/single-layer-medium.d.ts.map +1 -0
  309. package/dist/typed-render-graph-primitives.d.ts +24 -9
  310. package/dist/typed-render-graph-primitives.d.ts.map +1 -1
  311. package/dist/volume/component.d.ts +9 -5
  312. package/dist/volume/component.d.ts.map +1 -1
  313. package/dist/volume/extract.d.ts +1 -1
  314. package/dist/volume/extract.d.ts.map +1 -1
  315. package/dist/volume/inspection.d.ts +2 -0
  316. package/dist/volume/inspection.d.ts.map +1 -1
  317. package/dist/volume/passes.d.ts +4 -1
  318. package/dist/volume/passes.d.ts.map +1 -1
  319. package/dist/volume/resources.d.ts.map +1 -1
  320. package/dist/volume/temporal.d.ts +3 -1
  321. package/dist/volume/temporal.d.ts.map +1 -1
  322. package/package.json +22 -21
  323. package/src/__tests__/analytic-fog-graph.unit.test.ts +199 -0
  324. package/src/__tests__/analytic-fog-optics.unit.test.ts +38 -0
  325. package/src/__tests__/atmosphere-daylight.dawn.test.ts +72 -0
  326. package/src/__tests__/barrel-distortion-lkg.integration.test.ts +353 -0
  327. package/src/__tests__/barrel-distortion.dawn.test.ts +41 -0
  328. package/src/__tests__/barrel-distortion.unit.test.ts +364 -0
  329. package/src/__tests__/bloom-exact-zero.unit.test.ts +112 -18
  330. package/src/__tests__/bloom-lifecycle.integration.test.ts +17 -4
  331. package/src/__tests__/construct-renderer-identity.unit.test.ts +49 -0
  332. package/src/__tests__/detached-recovery-graph.unit.test.ts +277 -0
  333. package/src/__tests__/device-loss-fanout.unit.test.ts +36 -1
  334. package/src/__tests__/docs-gate-green.unit.test.ts +112 -1
  335. package/src/__tests__/dynamic-resolution-plugin.integration.test.ts +20 -0
  336. package/src/__tests__/environment-fog-lanes.integration.test.ts +14 -0
  337. package/src/__tests__/environment-selection.unit.test.ts +12 -0
  338. package/src/__tests__/factory-contract.integration.test.ts +626 -113
  339. package/src/__tests__/feature-depth-input.dawn.test.ts +129 -2
  340. package/src/__tests__/frame-camera-selection.unit.test.ts +39 -1
  341. package/src/__tests__/frame-plan-contract.unit.test.ts +2 -0
  342. package/src/__tests__/fullscreen-feature-plan.unit.test.ts +14 -3
  343. package/src/__tests__/gpu-dirty-ranges.unit.test.ts +22 -0
  344. package/src/__tests__/gpu-driven-baseline.characterization.test.ts +16 -3
  345. package/src/__tests__/gpu-driven-batch-topology-pbr.unit.test.ts +12 -0
  346. package/src/__tests__/gpu-driven-indirect-raster-evidence.ts +2 -1
  347. package/src/__tests__/gpu-driven-material-artifacts.unit.test.ts +143 -0
  348. package/src/__tests__/gpu-driven-pbr.dawn.test.ts +2 -2
  349. package/src/__tests__/gpu-driven-production.integration.test.ts +482 -3
  350. package/src/__tests__/gpu-driven-shadow-views.dawn.test.ts +7 -6
  351. package/src/__tests__/gpu-driven-shadow-views.integration.test.ts +1 -1
  352. package/src/__tests__/gpu-driven-view-gpu-evidence.ts +103 -4
  353. package/src/__tests__/gpu-driven-view-graph.integration.test.ts +18 -2
  354. package/src/__tests__/gpu-driven-view.browser.test.ts +5 -8
  355. package/src/__tests__/gpu-driven-view.dawn.test.ts +6 -2
  356. package/src/__tests__/gpu-driven-view.unit.test.ts +1 -1
  357. package/src/__tests__/gpu-pass-timing-lifecycle.integration.test.ts +1 -0
  358. package/src/__tests__/gpu-pass-timing.browser.test.ts +6 -4
  359. package/src/__tests__/gpu-pass-timing.rhi-null.unit.test.ts +1 -0
  360. package/src/__tests__/gpu-resource-store-stride.unit.test.ts +12 -0
  361. package/src/__tests__/gpu-scene.unit.test.ts +210 -9
  362. package/src/__tests__/instance-allocation-failure.unit.test.ts +57 -0
  363. package/src/__tests__/instances-culling.integration.test.ts +10 -36
  364. package/src/__tests__/instances-store.unit.test.ts +105 -238
  365. package/src/__tests__/instances-world-ownership.integration.test.ts +62 -0
  366. package/src/__tests__/main-pass-material-medium-selection.unit.test.ts +146 -0
  367. package/src/__tests__/material-shader-prewarm.unit.test.ts +59 -0
  368. package/src/__tests__/material-snapshot-mutation.integration.test.ts +39 -1
  369. package/src/__tests__/mesh-submission-lifetime.unit.test.ts +31 -0
  370. package/src/__tests__/morph-culling-reentry.dawn.test.ts +5 -5
  371. package/src/__tests__/morph-visual-evidence.integration.test.ts +8 -7
  372. package/src/__tests__/motion-blur-compute.dawn.test.ts +589 -0
  373. package/src/__tests__/motion-blur.pixel.integration.test.ts +2 -2
  374. package/src/__tests__/nineslice-handle-surface.unit.test.ts +4 -4
  375. package/src/__tests__/pbr-view-layout.unit.test.ts +9 -0
  376. package/src/__tests__/physical-clearcoat.integration.test.ts +58 -0
  377. package/src/__tests__/prepared-gpu-driven-pbr.integration.test.ts +21 -0
  378. package/src/__tests__/prepared-graphics-capability.unit.test.ts +66 -2
  379. package/src/__tests__/prepared-graphics-lifecycle.unit.test.ts +86 -0
  380. package/src/__tests__/probe-bindings.unit.test.ts +582 -1
  381. package/src/__tests__/probe-scene-dirty.integration.test.ts +28 -1
  382. package/src/__tests__/public-surface.test-d.ts +8 -0
  383. package/src/__tests__/receipt-observation-retirement.unit.test.ts +65 -0
  384. package/src/__tests__/recovery-candidate-prepare.contract.test.ts +69 -0
  385. package/src/__tests__/render-error-code-owner.test-d.ts +9 -0
  386. package/src/__tests__/render-feature-diagnostics.unit.test.ts +15 -2
  387. package/src/__tests__/render-feature-error-reporting.unit.test.ts +44 -0
  388. package/src/__tests__/render-feature-prepared-diagnostics.unit.test.ts +10 -1
  389. package/src/__tests__/render-feature-retry.unit.test.ts +12 -1
  390. package/src/__tests__/render-feature-stage-fault-recovery.unit.test.ts +23 -0
  391. package/src/__tests__/render-scene-mixed-updates.integration.test.ts +145 -19
  392. package/src/__tests__/render-scene-projection.unit.test.ts +36 -11
  393. package/src/__tests__/render-scene-temporal-retry.unit.test.ts +37 -6
  394. package/src/__tests__/render-target-probe-soak.perf.test.ts +2 -2
  395. package/src/__tests__/render-temporal-environment-consumer.browser.test.ts +3 -3
  396. package/src/__tests__/render-temporal-environment-consumer.dawn.test.ts +5 -2
  397. package/src/__tests__/renderer-factory-material-contract.unit.test.ts +41 -7
  398. package/src/__tests__/renderer-frame-generation-fence.integration.test.ts +16 -0
  399. package/src/__tests__/renderer-frame-transaction.integration.test.ts +33 -11
  400. package/src/__tests__/resource-allocation.unit.test.ts +123 -0
  401. package/src/__tests__/shader-manifest-fixture.ts +4 -4
  402. package/src/__tests__/single-layer-medium-passes.unit.test.ts +275 -0
  403. package/src/__tests__/skin-motion-regression.unit.test.ts +70 -1
  404. package/src/__tests__/skin-palette-recycling.dawn.test.ts +97 -0
  405. package/src/__tests__/skinned-shadow-caster.test.ts +17 -0
  406. package/src/__tests__/ssr-carrier-public-contract.unit.test.ts +21 -0
  407. package/src/__tests__/ssr-gpu-dispatch.browser.test.ts +4 -4
  408. package/src/__tests__/ssr-gpu-dispatch.dawn.test.ts +2 -2
  409. package/src/__tests__/ssr-gpu-dispatch.ts +1 -1
  410. package/src/__tests__/ssr-receiver-coverage.dawn.test.ts +6 -2
  411. package/src/__tests__/standard-lighting-write.unit.test.ts +2 -1
  412. package/src/__tests__/standard-output-chain-contract.unit.test.ts +28 -0
  413. package/src/__tests__/standard-pbr-artifact-assembly.unit.test.ts +46 -1
  414. package/src/__tests__/standard-pipeline.dawn.test.ts +1 -1
  415. package/src/__tests__/standard-pipeline.integration.test.ts +200 -9
  416. package/src/__tests__/surface-dynamic-buffer-lifecycle.unit.test.ts +506 -0
  417. package/src/__tests__/surface-submission-observation.unit.test.ts +812 -0
  418. package/src/__tests__/temporal-history-lifecycle.unit.test.ts +32 -0
  419. package/src/__tests__/temporal-performance.integration.test.ts +3 -3
  420. package/src/__tests__/temporal-reset-matrix.unit.test.ts +10 -0
  421. package/src/__tests__/temporal-view.unit.test.ts +2 -1
  422. package/src/__tests__/typed-pipeline-topology.unit.test.ts +20 -4
  423. package/src/__tests__/visibility-instances.integration.test.ts +7 -19
  424. package/src/__tests__/visibility-producer-matrix.integration.test.ts +9 -20
  425. package/src/__tests__/volume-optics-oracle.unit.test.ts +7 -3
  426. package/src/__tests__/volumetric-fog-budget-recovery.integration.test.ts +189 -15
  427. package/src/__tests__/volumetric-fog-errors.unit.test.ts +4 -3
  428. package/src/__tests__/volumetric-fog-multiple.unit.test.ts +55 -0
  429. package/src/__tests__/volumetric-fog-spot-frame-optics.integration.test.ts +1 -1
  430. package/src/__tests__/volumetric-fog-stage-readback.dawn.test.ts +558 -68
  431. package/src/__tests__/volumetric-fog-temporal.unit.test.ts +27 -0
  432. package/src/__tests__/volumetric-fog-topology.unit.test.ts +23 -5
  433. package/src/assembly/analytic-fog-registration.ts +14 -0
  434. package/src/assembly/backend-contract.ts +30 -0
  435. package/src/assembly/barrel-distortion-inspection.ts +22 -0
  436. package/src/assembly/canvas-draw-guard.ts +11 -0
  437. package/src/assembly/cloud-shadow-entry.ts +22 -0
  438. package/src/assembly/dynamic-geometry-host.ts +65 -37
  439. package/src/assembly/dynamic-geometry-runtime.ts +7 -1
  440. package/src/assembly/factory.ts +2 -30
  441. package/src/assembly/fxaa-registration.ts +17 -0
  442. package/src/assembly/host-contract.ts +3 -3
  443. package/src/assembly/lod-observation.ts +12 -0
  444. package/src/assembly/material/assembly.ts +25 -13
  445. package/src/assembly/material/pipeline-helpers.ts +53 -12
  446. package/src/assembly/material-shader-policy.ts +55 -0
  447. package/src/assembly/material-shader-prewarm.ts +21 -10
  448. package/src/assembly/motion-blur-registration.ts +17 -0
  449. package/src/assembly/recovery/device-loss-fanout.ts +13 -0
  450. package/src/assembly/recovery/renderer-recover.ts +41 -18
  451. package/src/assembly/render-feature-post-processes.ts +54 -0
  452. package/src/assembly/renderer-facade.ts +6 -1
  453. package/src/assembly/renderer-feature-inspection.ts +8 -2
  454. package/src/assembly/renderer-frame-transaction.ts +23 -0
  455. package/src/assembly/renderer-inspection.ts +28 -0
  456. package/src/assembly/skin-palette-owner.ts +1 -1
  457. package/src/assembly/temporal-post-process-prewarm.ts +40 -0
  458. package/src/assembly/webgpu-pbr-ready.ts +9 -1
  459. package/src/assembly/webgpu-ready-contract.ts +8 -3
  460. package/src/assembly/webgpu-ready-per-pass.ts +104 -0
  461. package/src/assembly/webgpu-ready.ts +296 -306
  462. package/src/assembly/webgpu-renderer-bootstrap.ts +0 -12
  463. package/src/assembly/webgpu-renderer-observation-owner.ts +222 -0
  464. package/src/assembly/webgpu-renderer.ts +228 -281
  465. package/src/assembly/webgpu-vertex-layouts.ts +1 -1
  466. package/src/barrel-distortion.ts +247 -0
  467. package/src/bloom-admission.ts +21 -0
  468. package/src/cloud/__tests__/cloud-density-parity.dawn.test.ts +173 -0
  469. package/src/cloud/__tests__/cloud-layer-framebuffer-y.dawn.test.ts +265 -0
  470. package/src/cloud/__tests__/cloud-layer.browser.test.ts +432 -0
  471. package/src/cloud/__tests__/cloud-layer.unit.test.ts +634 -0
  472. package/src/cloud/density.ts +600 -0
  473. package/src/cloud/extract.ts +110 -0
  474. package/src/cloud/feature.ts +1810 -0
  475. package/src/cloud/frame-context.ts +42 -0
  476. package/src/cloud/inspection.ts +130 -0
  477. package/src/cloud/optics.ts +285 -0
  478. package/src/cloud/parameters.ts +255 -0
  479. package/src/cloud/resources.ts +122 -0
  480. package/src/cloud/shadow.ts +167 -0
  481. package/src/cloud/temporal.ts +226 -0
  482. package/src/components/__tests__/motion-blur.unit.test.ts +1 -0
  483. package/src/components/atmosphere.ts +4 -0
  484. package/src/components/barrel-distortion.ts +46 -0
  485. package/src/components/camera.ts +66 -10
  486. package/src/components/cloud-layer.ts +47 -0
  487. package/src/components/index.ts +2 -0
  488. package/src/components/instances.ts +18 -45
  489. package/src/components/mesh-filter.ts +11 -9
  490. package/src/components/motion-blur.ts +1 -0
  491. package/src/components/sprite-instances.ts +1 -1
  492. package/src/construct-renderer.ts +1 -1
  493. package/src/device/gpu-residency.ts +56 -12
  494. package/src/dynamic-geometry.ts +12 -1
  495. package/src/environment/analytic-fog.ts +44 -0
  496. package/src/environment/background.ts +2 -2
  497. package/src/errors/cloud.ts +103 -0
  498. package/src/errors/render.ts +58 -8
  499. package/src/examples/single-layer-medium-surface.ts +59 -0
  500. package/src/extract/camera.ts +35 -8
  501. package/src/extract/contracts.ts +12 -0
  502. package/src/extract/environment.ts +4 -0
  503. package/src/features/barrel-distortion.ts +172 -0
  504. package/src/features/error-reporting.ts +98 -0
  505. package/src/features/host.ts +138 -24
  506. package/src/features/motion-blur/__tests__/motion-blur-feature.unit.test.ts +111 -1
  507. package/src/features/motion-blur/__tests__/motion-blur-params.unit.test.ts +58 -3
  508. package/src/features/motion-blur/motion-blur-feature.ts +1519 -15
  509. package/src/features/motion-blur/motion-blur-params.ts +70 -2
  510. package/src/features/motion-blur/motion-blur-runtime.ts +176 -0
  511. package/src/features/plan.ts +33 -8
  512. package/src/features/prepared-gpu-work.ts +86 -35
  513. package/src/features/render-graph-compute.ts +13 -5
  514. package/src/features/render-graph-contribution.ts +11 -0
  515. package/src/features/render-graph-raster.ts +45 -3
  516. package/src/features/targets.ts +66 -0
  517. package/src/features/types.ts +47 -0
  518. package/src/fullscreen-post-process-pass.ts +41 -9
  519. package/src/gpu-dirty-ranges.ts +48 -0
  520. package/src/gpu-driven/batch-topology.ts +53 -0
  521. package/src/gpu-driven/material-artifacts.ts +35 -32
  522. package/src/gpu-driven/prepared-draw.ts +27 -14
  523. package/src/gpu-driven/production-raster-material.ts +1894 -0
  524. package/src/gpu-driven/production-raster.ts +904 -787
  525. package/src/gpu-driven/resource-allocation.ts +142 -0
  526. package/src/gpu-driven/shadow-ownership.ts +71 -0
  527. package/src/gpu-driven/view-gpu.ts +181 -10
  528. package/src/gpu-scene.ts +259 -101
  529. package/src/hdrp-buffers.ts +4 -3
  530. package/src/ibl/skylight-bind-group.ts +49 -7
  531. package/src/index.ts +228 -9
  532. package/src/inspection-types.ts +222 -2
  533. package/src/instance-buffer-cache.ts +4 -4
  534. package/src/instances.ts +151 -492
  535. package/src/pbr-pipeline.ts +219 -13
  536. package/src/pipeline/single-layer-medium-passes.ts +267 -0
  537. package/src/pipeline/standard-forward-lane.ts +313 -57
  538. package/src/pipeline/standard-output/graph.ts +8 -2
  539. package/src/pipeline/standard-output/types.ts +12 -1
  540. package/src/pipeline/standard-pipeline.ts +291 -18
  541. package/src/pipeline/standard-post.ts +397 -102
  542. package/src/pipeline-builder.ts +1 -1
  543. package/src/pipeline-spec.ts +5 -5
  544. package/src/plugin.ts +4 -0
  545. package/src/points-lines/__tests__/points-lines.bench.ts +17 -8
  546. package/src/points-lines/__tests__/view-ubo.unit.test.ts +2 -1
  547. package/src/prepare/prepared-graphics-resolver.ts +2 -0
  548. package/src/record/__tests__/main-pass-geometry.unit.test.ts +30 -0
  549. package/src/record/__tests__/sprite-material-abi.unit.test.ts +1 -1
  550. package/src/record/__tests__/temporal-payload.unit.test.ts +3 -2
  551. package/src/record/__tests__/transparent-group2-selection.unit.test.ts +11 -0
  552. package/src/record/__tests__/typed-frame-graph-plan.unit.test.ts +1 -0
  553. package/src/record/__tests__/view-ubo-layout.unit.test.ts +4 -3
  554. package/src/record/barrel-distortion-frame.ts +110 -0
  555. package/src/record/dynamic-geometry-consumption.ts +39 -9
  556. package/src/record/fold-buckets.ts +852 -0
  557. package/src/record/fold-instance-buffer.ts +51 -0
  558. package/src/record/frame-lighting.ts +36 -2
  559. package/src/record/frame-snapshot.ts +44 -4
  560. package/src/record/frame-targets.ts +10 -6
  561. package/src/record/frame.ts +373 -1099
  562. package/src/record/gpu-draw-receipts.ts +38 -0
  563. package/src/record/gpu-timing.ts +48 -5
  564. package/src/record/main-pass-geometry.ts +281 -83
  565. package/src/record/main-pass-material.ts +66 -4
  566. package/src/record/main-pass-sprite-draws.ts +13 -66
  567. package/src/record/main-pass.ts +156 -20
  568. package/src/record/mesh-ssbo.ts +43 -6
  569. package/src/record/motion-blur-receipt.ts +91 -0
  570. package/src/record/probe-blend-buffer.ts +180 -0
  571. package/src/record/recovery-pipeline.ts +35 -3
  572. package/src/record/render-context.ts +51 -31
  573. package/src/record/shadow-pass.ts +36 -2
  574. package/src/record/skybox-post-pass.ts +150 -200
  575. package/src/record/typed-frame-graph.ts +824 -44
  576. package/src/record/view-ubo.ts +26 -3
  577. package/src/record/volume-params.ts +225 -0
  578. package/src/recovery/render-system-candidate.ts +257 -52
  579. package/src/reflection/record-owner.ts +7 -3
  580. package/src/render-contract.ts +71 -8
  581. package/src/render-graph-primitives.ts +7 -1
  582. package/src/render-pipeline.ts +136 -1
  583. package/src/render-system-extract-tail.ts +72 -60
  584. package/src/render-system-extract.ts +117 -87
  585. package/src/render-system-presentation.ts +56 -0
  586. package/src/render-system-producer-roots.ts +96 -0
  587. package/src/render-system-residency.ts +159 -0
  588. package/src/render-system.ts +601 -482
  589. package/src/scene/probe-blend.ts +96 -4
  590. package/src/scene/render-scene.ts +431 -281
  591. package/src/ssao-buffers.ts +95 -2
  592. package/src/surface/__tests__/dynamic-input.unit.test.ts +374 -0
  593. package/src/surface/__tests__/gpu-driven.unit.test.ts +87 -0
  594. package/src/surface/dynamic-input.ts +658 -0
  595. package/src/surface/gpu-driven.ts +147 -0
  596. package/src/surface/submission-observation.ts +517 -0
  597. package/src/systems/skin-palette-allocator.ts +4 -1
  598. package/src/temporal/__tests__/temporal-frame-transaction.unit.test.ts +36 -0
  599. package/src/temporal/frame-coordinator.ts +8 -1
  600. package/src/temporal/frame.ts +20 -0
  601. package/src/temporal/gpu.ts +101 -17
  602. package/src/temporal/inspection.ts +7 -1
  603. package/src/temporal/standard-scene-data.ts +5 -0
  604. package/src/temporal/target-coverage-attachment.ts +4 -0
  605. package/src/temporal/view.ts +13 -2
  606. package/src/transmission/__tests__/single-layer-medium.unit.test.ts +149 -0
  607. package/src/transmission/__tests__/standard-transmission.dawn.test.ts +12 -11
  608. package/src/transmission/single-layer-medium.ts +322 -0
  609. package/src/typed-render-graph-primitives.ts +191 -50
  610. package/src/volume/component.ts +10 -10
  611. package/src/volume/extract.ts +11 -5
  612. package/src/volume/inspection.ts +3 -0
  613. package/src/volume/passes.ts +95 -8
  614. package/src/volume/resources.ts +2 -1
  615. package/src/volume/temporal.ts +11 -0
  616. package/dist/chunk-4NNZQCL3.mjs.map +0 -1
  617. package/dist/chunk-6TFBXLRJ.mjs.map +0 -1
  618. package/dist/chunk-DRR7D3KX.mjs.map +0 -1
  619. package/dist/chunk-GWCEMDVR.mjs.map +0 -1
  620. package/dist/chunk-JCIRYQ4V.mjs.map +0 -1
  621. package/dist/chunk-JRIPCMYC.mjs.map +0 -1
  622. package/dist/chunk-LULCA3OF.mjs +0 -1713
  623. package/dist/chunk-LULCA3OF.mjs.map +0 -1
  624. package/dist/chunk-PA5DRJNI.mjs.map +0 -1
@@ -1,15 +1,15 @@
1
+ import { RuntimeMaterialValue, RuntimeMeshVertices } from '@forgeax/engine-assets-runtime';
1
2
  import type { EntityHandle, Query, World } from '@forgeax/engine-ecs';
2
- import { componentId } from '@forgeax/engine-ecs/internal';
3
- import type { RenderReadLease, RenderReadVersion } from '@forgeax/engine-ecs/projection';
3
+ import {
4
+ createStateProjection,
5
+ type RenderReadLease,
6
+ type RenderReadVersion,
7
+ type StateProjection,
8
+ type StateProjectionBatch,
9
+ } from '@forgeax/engine-ecs/projection';
4
10
  import { box3, frustum, mat4 } from '@forgeax/engine-math';
5
11
  import { type RhiDevice, RhiError } from '@forgeax/engine-rhi';
6
- import {
7
- ChildOf,
8
- collectSubtree,
9
- GlobalTransform,
10
- MorphWeights,
11
- Transform,
12
- } from '@forgeax/engine-scene';
12
+ import { ChildOf, Children, GlobalTransform, MorphWeights, Transform } from '@forgeax/engine-scene';
13
13
  import { Skin } from '@forgeax/engine-skinning';
14
14
  import { err, ok, type Result } from '@forgeax/engine-types';
15
15
  import {
@@ -26,6 +26,7 @@ import {
26
26
  } from '../components';
27
27
  import type { DeviceScope, LifecycleResourceSpec } from '../device/device-scope';
28
28
  import { gpuDrivenShadowDrawKey } from '../extract/gpu-driven';
29
+ import { motionBlurTemporalDemand } from '../features/motion-blur/motion-blur-params';
29
30
  import {
30
31
  BatchTopology,
31
32
  type BatchTopologyInspection,
@@ -36,7 +37,7 @@ import type {
36
37
  GpuDrivenStructureMetrics,
37
38
  PersistentRenderSceneInspection,
38
39
  } from '../inspection-types';
39
- import type { InstanceCollectionStore } from '../instances';
40
+ import type { InstanceProjectionStore } from '../instances';
40
41
  import { fingerprintNumericArray, InstanceBoundsCache } from '../instances-derived-bounds';
41
42
  import type { PointsLinesInspection } from '../points-lines/inspection';
42
43
  import type { PointsLinesRetainedSnapshot } from '../points-lines/snapshot';
@@ -61,7 +62,11 @@ import {
61
62
  type TransmissionDemand,
62
63
  type TransmissionDemandOperation,
63
64
  } from '../transmission/projection';
64
- import { ProbeBlendSceneProjection, type ProbeSceneObjectInput } from './probe-blend';
65
+ import {
66
+ type ProbeBlendBufferProjection,
67
+ ProbeBlendSceneProjection,
68
+ type ProbeSceneObjectInput,
69
+ } from './probe-blend';
65
70
  import type {
66
71
  RenderSceneApplyResult,
67
72
  RenderSceneBounds,
@@ -115,6 +120,41 @@ interface WorldTransformSpan {
115
120
 
116
121
  type GlobalTransformChangeQuery = Query<readonly [typeof GlobalTransform]>;
117
122
 
123
+ function sameInstanceIdentitySet(
124
+ current: Uint32Array | undefined,
125
+ previous: Uint32Array | undefined,
126
+ ): boolean {
127
+ // An instanced producer without generation tokens cannot prove that a
128
+ // compacted/reordered row is the same logical object. Keep that legacy
129
+ // snapshot conservative: the record stage will seed its previous matrix
130
+ // instead of borrowing a velocity from an unknown ordinal.
131
+ if (current === undefined || previous === undefined) return false;
132
+ const currentSet = new Set(current);
133
+ const previousSet = new Set(previous);
134
+ if (
135
+ currentSet.size !== current.length ||
136
+ previousSet.size !== previous.length ||
137
+ current.some((identity) => identity === 0) ||
138
+ previous.some((identity) => identity === 0)
139
+ ) {
140
+ return false;
141
+ }
142
+ if (current.length === previous.length) {
143
+ let sameOrder = true;
144
+ for (let index = 0; index < current.length; index += 1) {
145
+ if (current[index] !== previous[index]) {
146
+ sameOrder = false;
147
+ break;
148
+ }
149
+ }
150
+ if (sameOrder) return true;
151
+ }
152
+ // Reorder and retirement are valid when every current token is unique and
153
+ // survives. The set is built only on the topology-change path; steady pose
154
+ // updates stay on the linear same-order check and do not allocate.
155
+ return current.every((identity) => previousSet.has(identity));
156
+ }
157
+
118
158
  /** The normal extract request used by the persistent scene owner. */
119
159
  export type PersistentRenderCandidateRequest =
120
160
  | 'full'
@@ -202,6 +242,9 @@ function ownSnapshot(snapshot: RenderableSnapshot): RenderableSnapshot {
202
242
  instances: {
203
243
  ...snapshot.instances,
204
244
  transforms: new Float32Array(snapshot.instances.transforms),
245
+ ...(snapshot.instances.generations === undefined
246
+ ? {}
247
+ : { generations: new Uint32Array(snapshot.instances.generations) }),
205
248
  },
206
249
  }),
207
250
  ...(snapshot.spriteInstances === undefined
@@ -363,16 +406,33 @@ export class RenderScene {
363
406
  return this.emptyResult();
364
407
  }
365
408
 
366
- for (const operation of operations) {
409
+ // Prepare every potentially fallible payload copy before mutating retained
410
+ // columns. The commit loop below only uses owned POD and borrowed numeric spans.
411
+ const preparedOperations = operations.map((operation): RenderSceneOperation => {
412
+ if (operation.kind === 'remove') return operation;
367
413
  if (
368
- operation.kind === 'update' &&
369
414
  operation.snapshot !== undefined &&
370
415
  (operation.worldId !== operation.snapshot.worldId ||
371
416
  operation.entityKey !== operation.snapshot.entityKey)
372
417
  ) {
373
418
  throw new RangeError('RenderScene update identity must match its source snapshot');
374
419
  }
375
- }
420
+ return {
421
+ ...operation,
422
+ ...(operation.snapshot === undefined ? {} : { snapshot: ownSnapshot(operation.snapshot) }),
423
+ ...(operation.instances === undefined
424
+ ? {}
425
+ : {
426
+ instances: {
427
+ ...operation.instances,
428
+ transforms: new Float32Array(operation.instances.transforms),
429
+ ...(operation.instances.generations === undefined
430
+ ? {}
431
+ : { generations: new Uint32Array(operation.instances.generations) }),
432
+ },
433
+ }),
434
+ };
435
+ });
376
436
  this.deltaFrames += 1;
377
437
  this.renderableScans = operations.length;
378
438
  const pendingByWorld = new Map<number, Map<number, PendingIdentity>>();
@@ -414,7 +474,7 @@ export class RenderScene {
414
474
  }
415
475
  }
416
476
  };
417
- for (const operation of operations) {
477
+ for (const operation of preparedOperations) {
418
478
  const pending = pendingFor(operation.worldId, operation.entityKey);
419
479
  if (operation.kind === 'remove') {
420
480
  pending.current = undefined;
@@ -423,7 +483,7 @@ export class RenderScene {
423
483
  }
424
484
  if (operation.snapshot !== undefined) {
425
485
  const previousMaterials = pending.current?.materials;
426
- const next = ownSnapshot(operation.snapshot);
486
+ const next = operation.snapshot;
427
487
  pending.current =
428
488
  previousMaterials !== undefined &&
429
489
  previousMaterials.length === next.materials.length &&
@@ -439,10 +499,7 @@ export class RenderScene {
439
499
  if (operation.instances !== undefined) {
440
500
  pending.current = {
441
501
  ...pending.current,
442
- instances: {
443
- ...operation.instances,
444
- transforms: new Float32Array(operation.instances.transforms),
445
- },
502
+ instances: operation.instances,
446
503
  };
447
504
  pending.instanceChanged = true;
448
505
  }
@@ -711,6 +768,11 @@ export class RenderScene {
711
768
  return this.slotSnapshot;
712
769
  }
713
770
 
771
+ /** Cheap owner revision for retained derived projections. */
772
+ revisionValue(): number {
773
+ return this.revision;
774
+ }
775
+
714
776
  slot(worldId: number, entityKey: number): RenderSceneSlot | undefined {
715
777
  return this.lookup(worldId, entityKey);
716
778
  }
@@ -922,16 +984,36 @@ export class RenderScene {
922
984
  reasons.includes('material-revision');
923
985
  const previous = seedCurrent || submitted === undefined ? slot.snapshot : submitted.snapshot;
924
986
  const previousWorld = this.previousWorldBySlot[slot.slot];
987
+ const currentInstances = slot.snapshot.instances;
988
+ const submittedInstances = submitted?.snapshot.instances;
989
+ const instancesCompatible =
990
+ currentInstances === undefined && submittedInstances === undefined
991
+ ? true
992
+ : currentInstances !== undefined &&
993
+ submittedInstances !== undefined &&
994
+ (currentInstances.collectionId === undefined ||
995
+ submittedInstances.collectionId === undefined ||
996
+ currentInstances.collectionId === submittedInstances.collectionId) &&
997
+ currentInstances.transforms.length >= currentInstances.instanceCount * 16 &&
998
+ submittedInstances.transforms.length >= submittedInstances.instanceCount * 16 &&
999
+ sameInstanceIdentitySet(currentInstances.generations, submittedInstances.generations);
1000
+ const motionValid = !seedCurrent && previousWorld !== undefined && instancesCompatible;
925
1001
  return {
926
1002
  previousEpoch: seedCurrent ? undefined : this.submittedEpoch,
927
1003
  previousSource: seedCurrent ? 'current-seed' : 'last-submitted',
1004
+ motionValid,
928
1005
  reactive: reasons.length > 0,
929
1006
  reactiveReasons: reasons,
930
1007
  previousTransform:
931
1008
  seedCurrent || previousWorld === undefined
932
1009
  ? previous.transform
933
1010
  : { ...previous.transform, world: previousWorld },
934
- previousInstances: previous.instances,
1011
+ // A collection identity/count break is a temporal cut for that
1012
+ // renderable. The record stage receives the current snapshot as its
1013
+ // baseline, while stable collections retain the submitted column and
1014
+ // use identity generations to map reordered entries and seed replaced
1015
+ // entries.
1016
+ previousInstances: seedCurrent ? slot.snapshot.instances : previous.instances,
935
1017
  previousSkin: previous.skin,
936
1018
  previousMorphWeights: previous.morph?.weights,
937
1019
  };
@@ -985,10 +1067,10 @@ export class RenderScene {
985
1067
  export interface PersistentRenderSceneOptions {
986
1068
  readonly getDevice?: (() => RhiDevice) | undefined;
987
1069
  readonly onGpuError?: ((error: RhiError) => void) | undefined;
988
- readonly onSharedRefMutation?: ((worldId: number, handle: number) => void) | undefined;
1070
+ readonly onRuntimeAssetChange?: ((worldId: number, handle: number) => void) | undefined;
989
1071
  readonly configuredQueryBudget?: number | undefined;
990
- /** Producer-owned instance collections consumed by the same source update flow. */
991
- readonly instanceCollections?: InstanceCollectionStore | undefined;
1072
+ /** Instance projection accepted by the same World source update flow. */
1073
+ readonly instanceCollections?: InstanceProjectionStore | undefined;
992
1074
  }
993
1075
 
994
1076
  export interface PersistentGpuDrivenState {
@@ -997,7 +1079,11 @@ export interface PersistentGpuDrivenState {
997
1079
  readonly slots: readonly RenderSceneSlot[];
998
1080
  /** Stable renderer-local World keys aligned with RenderSceneSlot.worldId. */
999
1081
  readonly worldKeys?: readonly number[];
1082
+ /** Public World identities aligned with RenderSceneSlot.worldId. */
1083
+ readonly worldIdentities?: readonly string[];
1000
1084
  readonly structureMetrics?: GpuDrivenStructureMetrics;
1085
+ /** Stable retained ProbeBlend upload projection from the Scene owner. */
1086
+ readonly probeBlend?: ProbeBlendBufferProjection;
1001
1087
  }
1002
1088
 
1003
1089
  export interface CpuDirectViewInput {
@@ -1075,7 +1161,11 @@ interface PersistentCompositionEntry {
1075
1161
  readonly readVersions: RenderReadVersion[];
1076
1162
  readonly leaseIdentities: readonly string[];
1077
1163
  readonly transformQueries: readonly GlobalTransformChangeQuery[];
1078
- readonly renderChangeQueries: readonly ReadonlyMap<number, Query>[];
1164
+ readonly sourceStates: readonly {
1165
+ projection: StateProjection;
1166
+ entities: Map<number, EntityHandle>;
1167
+ contentHandles: Map<number, readonly number[]>;
1168
+ }[];
1079
1169
  readonly dispatchBySlot: Map<number, readonly DispatchEntry[]>;
1080
1170
  dispatchRevision: number;
1081
1171
  dispatchCache: { readonly revision: number; readonly value: DispatchEntry[] } | undefined;
@@ -1085,8 +1175,14 @@ interface PersistentCompositionEntry {
1085
1175
  /** Slots retained for recovery but excluded from direct and indirect submission. */
1086
1176
  readonly unavailableSlots: Set<number>;
1087
1177
  catalogEpoch: number;
1088
- instanceEpoch: number;
1089
1178
  readonly probeProjection: ProbeBlendSceneProjection;
1179
+ probeObjects:
1180
+ | {
1181
+ readonly revision: number;
1182
+ readonly value: readonly ProbeSceneObjectInput[];
1183
+ readonly surfaceValue: readonly ProbeSceneObjectInput[];
1184
+ }
1185
+ | undefined;
1090
1186
  temporalDemanded: boolean;
1091
1187
  gpuDrivenSceneRequired: boolean;
1092
1188
  }
@@ -1095,7 +1191,6 @@ interface PersistentCompositionEntry {
1095
1191
  // environment facts are freshly extracted by deriveFramePlan on every draw;
1096
1192
  // their edits must not recreate unrelated geometry or its submission history.
1097
1193
  const RENDERABLE_SOURCE_COMPONENTS = [
1098
- GlobalTransform,
1099
1194
  ChildOf,
1100
1195
  MeshFilter,
1101
1196
  MeshRenderer,
@@ -1110,21 +1205,6 @@ const RENDERABLE_SOURCE_COMPONENTS = [
1110
1205
  Lines,
1111
1206
  SortKey,
1112
1207
  ] as const;
1113
- const RENDERABLE_SOURCE_COMPONENT_IDS = new Set(
1114
- RENDERABLE_SOURCE_COMPONENTS.map((component) => componentId(component)),
1115
- );
1116
- const VISIBILITY_DEPENDENCY_COMPONENT_IDS = new Set([
1117
- componentId(ChildOf),
1118
- componentId(Visibility),
1119
- ]);
1120
- const STRUCTURAL_MEMBERSHIP_SCAN_COMPONENT_IDS = new Set([
1121
- componentId(ChildOf),
1122
- componentId(Layer),
1123
- componentId(Visibility),
1124
- componentId(SpriteRegionOverride),
1125
- componentId(SortKey),
1126
- ]);
1127
-
1128
1208
  function createGlobalTransformChangeQuery(world: World): GlobalTransformChangeQuery {
1129
1209
  const result = world.query({ read: [GlobalTransform], changed: [GlobalTransform] });
1130
1210
  if (!result.ok) throw result.error;
@@ -1135,29 +1215,43 @@ function createGlobalTransformChangeQuery(world: World): GlobalTransformChangeQu
1135
1215
  return query;
1136
1216
  }
1137
1217
 
1138
- function createRenderChangeQueries(world: World): ReadonlyMap<number, Query> {
1139
- const queries = new Map<number, Query>();
1140
- for (const component of RENDERABLE_SOURCE_COMPONENTS) {
1141
- if (component === GlobalTransform) continue;
1142
- const result = world.query({ read: [component], changed: [component] });
1143
- if (!result.ok) throw result.error;
1144
- const query = result.value as Query;
1145
- // The initial full extraction has already consumed this change evidence.
1146
- // Keep the query compiled so subsequent frames iterate only rows whose
1147
- // component version changed.
1148
- for (const _span of query.spans().unwrap()) {
1149
- // Establish the observation baseline.
1150
- }
1151
- queries.set(componentId(component), query);
1152
- }
1153
- return queries;
1218
+ function createRenderSourceState(world: World) {
1219
+ // Numerical poses already have one span reader. Keep their table membership
1220
+ // in this projection, without scanning their changed rows a second time.
1221
+ const components = [...RENDERABLE_SOURCE_COMPONENTS, RuntimeMaterialValue, RuntimeMeshVertices];
1222
+ const projection = createStateProjection(world, components, [
1223
+ ...components,
1224
+ Transform,
1225
+ GlobalTransform,
1226
+ ]);
1227
+ const entities = new Map<number, EntityHandle>();
1228
+ const contentHandles = new Map<number, readonly number[]>();
1229
+ const batch = projection.read();
1230
+ for (const index of batch.indices) {
1231
+ const entity = projection.entity(index);
1232
+ if (entity !== undefined) {
1233
+ entities.set(index, entity);
1234
+ const handles: number[] = [];
1235
+ const material = world.hasComponent(entity, RuntimeMaterialValue)
1236
+ ? world.get(entity, RuntimeMaterialValue)
1237
+ : undefined;
1238
+ const mesh = world.hasComponent(entity, RuntimeMeshVertices)
1239
+ ? world.get(entity, RuntimeMeshVertices)
1240
+ : undefined;
1241
+ if (material?.ok) handles.push(Number(material.value.asset));
1242
+ if (mesh?.ok) handles.push(Number(mesh.value.asset));
1243
+ if (handles.length > 0) contentHandles.set(index, handles);
1244
+ }
1245
+ }
1246
+ return { projection, entities, contentHandles, batch };
1154
1247
  }
1155
1248
 
1156
- function collectRenderableEntities(world: World): ReadonlySet<number> {
1157
- const result = new Set<number>();
1158
- const query = world.query({ read: [MeshRenderer], with: [Transform, MeshFilter] }).unwrap();
1159
- for (const row of query) result.add(row.entity as number);
1160
- return result;
1249
+ function isRenderableMember(world: World, entity: number): boolean {
1250
+ return (
1251
+ world.hasComponent(entity as EntityHandle, MeshRenderer) &&
1252
+ world.hasComponent(entity as EntityHandle, Transform) &&
1253
+ world.hasComponent(entity as EntityHandle, MeshFilter)
1254
+ );
1161
1255
  }
1162
1256
 
1163
1257
  function sourceMembershipChanged(world: World, slot: RenderSceneSlot): boolean {
@@ -1273,7 +1367,17 @@ function topologyDeltaForAvailable(
1273
1367
 
1274
1368
  function frameDemandsTemporal(frame: ExtractedFrame): boolean {
1275
1369
  const camera = frame.cameras[0];
1276
- return camera?.antialias === 'taa' || (camera?.motionBlur?.shutterAngle ?? 0) > 0;
1370
+ const motionBlur = camera?.motionBlur;
1371
+ return (
1372
+ camera?.antialias === 'taa' ||
1373
+ (motionBlur !== undefined &&
1374
+ motionBlurTemporalDemand({
1375
+ shutterAngle: motionBlur.shutterAngle,
1376
+ maxRadiusPixels: motionBlur.maxRadiusPixels,
1377
+ sampleCount: motionBlur.sampleCount,
1378
+ targetFps: motionBlur.targetFps ?? 60,
1379
+ }))
1380
+ );
1277
1381
  }
1278
1382
 
1279
1383
  function cameraFrusta(cameras: readonly CameraSnapshot[]): readonly Float32Array[] {
@@ -1827,7 +1931,6 @@ export class PersistentRenderScene {
1827
1931
  preserveVisibilityOnReorder,
1828
1932
  );
1829
1933
  }
1830
- let requiresSourceRefresh = false;
1831
1934
  const changedEntitiesByWorld = worlds.map(
1832
1935
  (_, worldId) => new Set(entry.pendingRenderableEntitiesByWorld[worldId] ?? []),
1833
1936
  );
@@ -1840,171 +1943,144 @@ export class PersistentRenderScene {
1840
1943
  contentChangedByWorld[slot.worldId]?.add(slot.entityKey);
1841
1944
  }
1842
1945
  }
1843
- const instanceEpoch = this.options.instanceCollections?.epoch ?? 0;
1844
- if (entry.instanceEpoch !== instanceEpoch) {
1845
- for (const slot of entry.projection.slotsSnapshot()) {
1846
- const id = slot.snapshot.instances?.collectionId;
1847
- if (id === undefined) continue;
1848
- const current = this.options.instanceCollections?.inspect(id);
1849
- if (!current?.ok || current.value.revision !== slot.snapshot.instances?.revision) {
1850
- changedEntitiesByWorld[slot.worldId]?.add(slot.entityKey);
1851
- }
1852
- }
1853
- }
1854
1946
  const nextReadVersions = entry.readVersions.slice();
1855
- const structureChangedWorlds = new Set<number>();
1856
- const structuralComponentIdsByWorld = worlds.map(() => new Set<number>());
1857
- let currentRenderableEntitiesByWorld: readonly (ReadonlySet<number> | undefined)[] | undefined;
1947
+ const sourceBatches: StateProjectionBatch[] = [];
1858
1948
  const removedSlots: RenderSceneSlot[] = [];
1859
- for (let worldId = 0; worldId < worlds.length; worldId += 1) {
1949
+ const transformSpans: WorldTransformSpan[] = [];
1950
+ for (let worldId = 0; worldId < worlds.length; worldId++) {
1860
1951
  const world = worlds[worldId];
1861
- if (world === undefined) continue;
1862
- const lease = leases?.[worldId];
1863
- if (lease === undefined || entry.leaseIdentities[worldId] !== lease.worldIdentity) {
1864
- requiresSourceRefresh = true;
1865
- break;
1866
- }
1952
+ const source = entry.sourceStates[worldId];
1953
+ const lease = leases[worldId];
1867
1954
  const version = entry.readVersions[worldId];
1868
- if (version === undefined) {
1869
- requiresSourceRefresh = true;
1870
- break;
1871
- }
1872
- let read: ReturnType<RenderReadLease['readChanges']>;
1873
- try {
1874
- read = lease.readChanges(version);
1875
- } catch {
1876
- requiresSourceRefresh = true;
1877
- break;
1878
- }
1879
- if (read.status === 'rebuild') {
1880
- structureChangedWorlds.add(worldId);
1955
+ if (
1956
+ world === undefined ||
1957
+ source === undefined ||
1958
+ lease === undefined ||
1959
+ version === undefined
1960
+ )
1961
+ continue;
1962
+ const read = lease.readChanges(version);
1963
+ nextReadVersions[worldId] = read.version;
1964
+ const changed = changedEntitiesByWorld[worldId];
1965
+ const content = contentChangedByWorld[worldId];
1966
+ if (changed === undefined || content === undefined) continue;
1967
+ if (version.structureEpoch !== read.version.structureEpoch) {
1968
+ this.visibilityFacets.clear();
1969
+ this.invalidateActiveEntityRevision();
1881
1970
  }
1882
- const changes = read.world;
1883
- const shared = read.sharedRefs;
1884
- if (shared.records.length > 0) {
1885
- for (const record of shared.records) {
1886
- this.options.onSharedRefMutation?.(worldId, record.handle);
1887
- const changed = changedEntitiesByWorld[worldId];
1888
- const content = contentChangedByWorld[worldId];
1889
- if (changed !== undefined && content !== undefined) {
1890
- entry.projection.addSharedRefConsumers(worldId, record.handle, content);
1891
- for (const entity of content) changed.add(entity);
1971
+ const batch = source.projection.read();
1972
+ sourceBatches.push(batch);
1973
+ this.worldEntitiesScanned += batch.scannedRows;
1974
+ const dependencyVisited = new Set<number>();
1975
+ const runtimeAssets = new Set<number>();
1976
+ const runtimeContentChanged =
1977
+ batch.membershipChanged ||
1978
+ batch.changedComponents.includes(RuntimeMaterialValue) ||
1979
+ batch.changedComponents.includes(RuntimeMeshVertices);
1980
+ const contentComponents = RENDERABLE_SOURCE_COMPONENTS.filter(
1981
+ (component) => component !== Instances && batch.changedComponents.includes(component),
1982
+ );
1983
+ const instancesChanged = batch.changedComponents.includes(Instances);
1984
+ const reconcileIdentities =
1985
+ batch.membershipChanged ||
1986
+ runtimeContentChanged ||
1987
+ contentComponents.length > 0 ||
1988
+ instancesChanged;
1989
+ for (const index of reconcileIdentities ? batch.indices : []) {
1990
+ const previous = source.entities.get(index);
1991
+ const current = source.projection.entity(index);
1992
+ if (runtimeContentChanged) {
1993
+ const runtimeHandles = new Set(source.contentHandles.get(index));
1994
+ if (current !== undefined) {
1995
+ const material = world.hasComponent(current, RuntimeMaterialValue)
1996
+ ? world.get(current, RuntimeMaterialValue)
1997
+ : undefined;
1998
+ const mesh = world.hasComponent(current, RuntimeMeshVertices)
1999
+ ? world.get(current, RuntimeMeshVertices)
2000
+ : undefined;
2001
+ if (material?.ok) runtimeHandles.add(Number(material.value.asset));
2002
+ if (mesh?.ok) runtimeHandles.add(Number(mesh.value.asset));
1892
2003
  }
2004
+ for (const handle of runtimeHandles) runtimeAssets.add(handle);
1893
2005
  }
1894
- }
1895
- for (const changedComponentId of changes.changedComponentIds) {
1896
- if (read.status === 'rebuild') {
1897
- structuralComponentIdsByWorld[worldId]?.add(changedComponentId);
1898
- }
1899
- if (!RENDERABLE_SOURCE_COMPONENT_IDS.has(changedComponentId)) continue;
1900
- const changed = changedEntitiesByWorld[worldId];
1901
- if (changed === undefined) continue;
1902
- const otherChanged = contentChangedByWorld[worldId];
1903
- const query = entry.renderChangeQueries[worldId]?.get(changedComponentId);
1904
- if (query !== undefined) {
1905
- for (const row of query) {
1906
- const entity = row.entity as number;
1907
- if (VISIBILITY_DEPENDENCY_COMPONENT_IDS.has(changedComponentId)) {
1908
- const affected = collectSubtree(world, row.entity);
1909
- for (const dependent of affected) {
1910
- if (
1911
- !entry.projection.has(worldId, dependent) &&
1912
- !world.hasComponent(dependent as EntityHandle, MeshRenderer)
1913
- )
1914
- continue;
1915
- changed.add(dependent);
1916
- otherChanged?.add(dependent);
1917
- }
1918
- } else {
1919
- changed.add(entity);
1920
- if (changedComponentId !== componentId(Instances)) otherChanged?.add(entity);
1921
- }
2006
+ if (previous !== undefined) {
2007
+ const slot = entry.projection.slot(worldId, previous);
2008
+ if (
2009
+ batch.membershipChanged &&
2010
+ slot !== undefined &&
2011
+ (current !== previous || !isRenderableMember(world, previous))
2012
+ ) {
2013
+ removedSlots.push(slot);
2014
+ changed.add(previous);
1922
2015
  }
1923
- }
1924
- }
1925
- if (requiresSourceRefresh) break;
1926
- nextReadVersions[worldId] = read.version;
1927
- }
1928
- if (requiresSourceRefresh) {
1929
- return this.reconcileComposition(worlds, owner, catalogEpoch, buildCandidateFrame, leases);
1930
- }
1931
-
1932
- const transformSpans: WorldTransformSpan[] = [];
1933
- for (let worldId = 0; worldId < entry.transformQueries.length; worldId += 1) {
1934
- const query = entry.transformQueries[worldId];
1935
- if (query === undefined) continue;
1936
- for (const span of query.spans().unwrap()) {
1937
- const changedWorlds = span.get(GlobalTransform).world;
1938
- if (!(changedWorlds instanceof Float32Array)) {
1939
- requiresSourceRefresh = true;
1940
- break;
1941
- }
1942
- transformSpans.push({ worldId, entities: span.entities, worlds: changedWorlds });
1943
- for (const entity of span.entities) {
1944
- for (const consumer of entry.skinConsumersByJoint.get(skinJointKey(worldId, entity)) ??
2016
+ for (const consumer of entry.skinConsumersByJoint.get(skinJointKey(worldId, previous)) ??
1945
2017
  []) {
1946
- changedEntitiesByWorld[worldId]?.add(consumer);
1947
- contentChangedByWorld[worldId]?.add(consumer);
2018
+ changed.add(consumer);
2019
+ content.add(consumer);
1948
2020
  }
1949
2021
  }
1950
- }
1951
- if (requiresSourceRefresh) break;
1952
- }
1953
- if (requiresSourceRefresh) {
1954
- return this.reconcileComposition(worlds, owner, catalogEpoch, buildCandidateFrame, leases);
1955
- }
1956
- if (structureChangedWorlds.size > 0) {
1957
- this.visibilityFacets.clear();
1958
- this.invalidateActiveEntityRevision();
1959
- const currentByWorld = worlds.map((world, worldId) =>
1960
- structureChangedWorlds.has(worldId) ? collectRenderableEntities(world) : undefined,
1961
- );
1962
- currentRenderableEntitiesByWorld = currentByWorld;
1963
- for (let worldId = 0; worldId < currentByWorld.length; worldId += 1) {
1964
- const world = worlds[worldId];
1965
- const current = currentByWorld[worldId];
1966
- if (world === undefined || current === undefined) continue;
1967
- const changed = changedEntitiesByWorld[worldId];
1968
- if (changed !== undefined && structureChangedWorlds.has(worldId)) {
1969
- // A joint can disappear without leaving a row in the joint's
1970
- // GlobalTransform change query. Wake every dependent renderable so
1971
- // the next partial extraction validates the dangling reference and
1972
- // either republishes or retires its palette data.
1973
- for (const [jointKey, consumers] of entry.skinConsumersByJoint) {
1974
- const separator = jointKey.indexOf(':');
1975
- const indexedWorldId = Number(jointKey.slice(0, separator));
1976
- if (indexedWorldId !== worldId) continue;
1977
- const jointEntity = Number(jointKey.slice(separator + 1));
1978
- if (world.hasComponent(jointEntity as EntityHandle, GlobalTransform)) continue;
1979
- for (const consumer of consumers) {
1980
- changed.add(consumer);
1981
- contentChangedByWorld[worldId]?.add(consumer);
2022
+ if (current === undefined) continue;
2023
+ const existing = entry.projection.slot(worldId, current);
2024
+ const membershipChanged =
2025
+ batch.membershipChanged &&
2026
+ (existing === undefined || sourceMembershipChanged(world, existing));
2027
+ let contentChanged = membershipChanged;
2028
+ for (const component of contentComponents) {
2029
+ if (source.projection.changed(current, component)) contentChanged = true;
2030
+ }
2031
+ if (
2032
+ (contentChanged || (instancesChanged && source.projection.changed(current, Instances))) &&
2033
+ isRenderableMember(world, current)
2034
+ ) {
2035
+ changed.add(current);
2036
+ if (contentChanged) content.add(current);
2037
+ }
2038
+ if (
2039
+ version.structureEpoch !== read.version.structureEpoch ||
2040
+ source.projection.changed(current, ChildOf) ||
2041
+ source.projection.changed(current, Visibility)
2042
+ ) {
2043
+ if (!dependencyVisited.has(current)) {
2044
+ const pending = [current];
2045
+ while (pending.length > 0) {
2046
+ const dependent = pending.pop();
2047
+ if (dependent === undefined || dependencyVisited.has(dependent)) continue;
2048
+ dependencyVisited.add(dependent);
2049
+ const children = world.hasComponent(dependent, Children)
2050
+ ? world.get(dependent, Children)
2051
+ : undefined;
2052
+ if (children?.ok)
2053
+ for (const child of children.value.entities) pending.push(child as EntityHandle);
2054
+ if (!isRenderableMember(world, dependent)) continue;
2055
+ changed.add(dependent);
2056
+ content.add(dependent);
1982
2057
  }
1983
2058
  }
1984
- for (const entity of current) {
1985
- if (entry.projection.slot(worldId, entity) === undefined) changed.add(entity);
1986
- }
1987
- const structuralComponentIds = structuralComponentIdsByWorld[worldId];
1988
- const scanAllMembership =
1989
- structuralComponentIds !== undefined &&
1990
- [...structuralComponentIds].some((id) =>
1991
- STRUCTURAL_MEMBERSHIP_SCAN_COMPONENT_IDS.has(id),
1992
- );
1993
- for (const slot of entry.projection.slotsSnapshot()) {
1994
- if (slot.worldId !== worldId || !current.has(slot.entityKey)) continue;
1995
- const membershipChanged = sourceMembershipChanged(world, slot);
1996
- if (scanAllMembership || membershipChanged) {
1997
- changed.add(slot.entityKey);
1998
- contentChangedByWorld[worldId]?.add(slot.entityKey);
2059
+ }
2060
+ }
2061
+ for (const handle of runtimeAssets) {
2062
+ this.options.onRuntimeAssetChange?.(worldId, handle);
2063
+ entry.projection.addSharedRefConsumers(worldId, handle, content);
2064
+ }
2065
+ for (const entity of content) changed.add(entity);
2066
+ const query = entry.transformQueries[worldId];
2067
+ if (query !== undefined) {
2068
+ for (const span of query.spans().unwrap()) {
2069
+ const matrices = span.get(GlobalTransform).world;
2070
+ if (!(matrices instanceof Float32Array))
2071
+ throw new Error('GlobalTransform projection must expose a numeric column.');
2072
+ transformSpans.push({ worldId, entities: span.entities, worlds: matrices });
2073
+ if (entry.skinConsumersByJoint.size > 0) {
2074
+ for (const entity of span.entities) {
2075
+ for (const consumer of entry.skinConsumersByJoint.get(
2076
+ skinJointKey(worldId, entity),
2077
+ ) ?? []) {
2078
+ changed.add(consumer);
2079
+ content.add(consumer);
2080
+ }
1999
2081
  }
2000
2082
  }
2001
2083
  }
2002
- for (const slot of entry.projection.slotsSnapshot()) {
2003
- if (slot.worldId !== worldId || current.has(slot.entityKey)) continue;
2004
- removedSlots.push(slot);
2005
- changedEntitiesByWorld[worldId]?.add(slot.entityKey);
2006
- }
2007
- this.worldEntitiesScanned += current.size;
2008
2084
  }
2009
2085
  }
2010
2086
 
@@ -2017,6 +2093,8 @@ export class PersistentRenderScene {
2017
2093
  for (const entityKey of changedEntitiesByWorld[worldId] ?? []) pending.add(entityKey);
2018
2094
  }
2019
2095
 
2096
+ const resourceFrame = buildCandidateFrame('none');
2097
+ for (const batch of sourceBatches) batch.validate();
2020
2098
  let changedDelta: RenderSceneApplyResult | undefined;
2021
2099
  const hasChangedEntities = changedEntitiesByWorld.some((entities) => entities.size > 0);
2022
2100
  if (hasChangedEntities || removedSlots.length > 0 || transformSpans.length > 0) {
@@ -2031,9 +2109,6 @@ export class PersistentRenderScene {
2031
2109
  }
2032
2110
  for (const slot of removedSlots) {
2033
2111
  operations.push({ kind: 'remove', worldId: slot.worldId, entityKey: slot.entityKey });
2034
- if (slot.snapshot.skinJointEntities !== undefined) {
2035
- removeSkinConsumer(entry.skinConsumersByJoint, slot.snapshot);
2036
- }
2037
2112
  for (
2038
2113
  let materialIndex = 0;
2039
2114
  materialIndex < slot.snapshot.materials.length;
@@ -2090,7 +2165,23 @@ export class PersistentRenderScene {
2090
2165
  });
2091
2166
  publishedEntitiesByWorld[snapshot.worldId]?.add(snapshot.entityKey);
2092
2167
  }
2168
+ for (const batch of sourceBatches) batch.validate();
2093
2169
  changedDelta = entry.projection.apply(operations, transformSpans);
2170
+ for (const slot of changedDelta.removedSlots) {
2171
+ const world = worlds[slot.worldId];
2172
+ if (world !== undefined) this.options.instanceCollections?.release(world, slot.entityKey);
2173
+ }
2174
+ for (const snapshot of partial?.renderables ?? []) {
2175
+ const world = worlds[snapshot.worldId];
2176
+ if (world === undefined) continue;
2177
+ if (snapshot.instances === undefined)
2178
+ this.options.instanceCollections?.release(world, snapshot.entityKey);
2179
+ else
2180
+ this.options.instanceCollections?.accept(world, snapshot.entityKey, snapshot.instances);
2181
+ }
2182
+ for (const slot of removedSlots)
2183
+ if (slot.snapshot.skinJointEntities !== undefined)
2184
+ removeSkinConsumer(entry.skinConsumersByJoint, slot.snapshot);
2094
2185
  const publishedSkinKeys = new Set<string>();
2095
2186
  for (const snapshot of partial?.renderables ?? []) {
2096
2187
  publishedSkinKeys.add(`${snapshot.worldId}:${snapshot.entityKey}`);
@@ -2126,7 +2217,7 @@ export class PersistentRenderScene {
2126
2217
  return (
2127
2218
  worldId !== undefined &&
2128
2219
  !changedEntitiesByWorld[worldId]?.has(report.entity) &&
2129
- currentRenderableEntitiesByWorld?.[worldId]?.has(report.entity) !== false
2220
+ isRenderableMember(report.world, report.entity)
2130
2221
  );
2131
2222
  }),
2132
2223
  ...(partial?.hiddenEntityReports ?? []),
@@ -2155,8 +2246,9 @@ export class PersistentRenderScene {
2155
2246
  continue;
2156
2247
  }
2157
2248
  if (slot === undefined) {
2158
- const current = currentRenderableEntitiesByWorld?.[worldId];
2159
- if (current !== undefined && !current.has(entityKey)) pending?.delete(entityKey);
2249
+ const world = worlds[worldId];
2250
+ if (world !== undefined && !isRenderableMember(world, entityKey))
2251
+ pending?.delete(entityKey);
2160
2252
  continue;
2161
2253
  }
2162
2254
  // The producer did not publish this requested identity. Keep the
@@ -2207,9 +2299,43 @@ export class PersistentRenderScene {
2207
2299
  this.noChangeFrames += 1;
2208
2300
  this.syncGpuOwner(entry.token, entry.projection);
2209
2301
  }
2210
- const result = this.deriveFramePlan(entry, buildCandidateFrame);
2302
+ const result = this.deriveFramePlan(entry, resourceFrame);
2303
+ for (let worldId = 0; worldId < sourceBatches.length; worldId++) {
2304
+ const batch = sourceBatches[worldId];
2305
+ const source = entry.sourceStates[worldId];
2306
+ if (batch === undefined || source === undefined) continue;
2307
+ batch.accept();
2308
+ if (
2309
+ !batch.membershipChanged &&
2310
+ !batch.changedComponents.includes(RuntimeMaterialValue) &&
2311
+ !batch.changedComponents.includes(RuntimeMeshVertices)
2312
+ )
2313
+ continue;
2314
+ for (const index of batch.indices) {
2315
+ const entity = source.projection.entity(index);
2316
+ if (entity === undefined) {
2317
+ source.entities.delete(index);
2318
+ source.contentHandles.delete(index);
2319
+ } else {
2320
+ source.entities.set(index, entity);
2321
+ const handles: number[] = [];
2322
+ const world = worlds[worldId];
2323
+ if (world !== undefined) {
2324
+ const material = world.hasComponent(entity, RuntimeMaterialValue)
2325
+ ? world.get(entity, RuntimeMaterialValue)
2326
+ : undefined;
2327
+ const mesh = world.hasComponent(entity, RuntimeMeshVertices)
2328
+ ? world.get(entity, RuntimeMeshVertices)
2329
+ : undefined;
2330
+ if (material?.ok) handles.push(Number(material.value.asset));
2331
+ if (mesh?.ok) handles.push(Number(mesh.value.asset));
2332
+ }
2333
+ if (handles.length === 0) source.contentHandles.delete(index);
2334
+ else source.contentHandles.set(index, handles);
2335
+ }
2336
+ }
2337
+ }
2211
2338
  entry.catalogEpoch = catalogEpoch;
2212
- entry.instanceEpoch = instanceEpoch;
2213
2339
  for (let worldId = 0; worldId < nextReadVersions.length; worldId += 1) {
2214
2340
  const version = nextReadVersions[worldId];
2215
2341
  if (version !== undefined) entry.readVersions[worldId] = version;
@@ -2256,11 +2382,13 @@ export class PersistentRenderScene {
2256
2382
  this.stableSlotByEntity.clear();
2257
2383
  }
2258
2384
  if (this.gpuOwner !== world && this.gpuOwner !== detachedComposition?.token) return;
2259
- this.retireGpuScene(this.gpuScene);
2260
- this.gpuScene = undefined;
2385
+ // The GPU Scene is renderer-owned. A frame graph recorded for the current
2386
+ // device may retain its table bindings after a World detaches, while the
2387
+ // next attached composition can rebuild the same tables in place. Keep
2388
+ // that single scene resident until device replacement or renderer
2389
+ // disposal; retiring it here lets a reused graph submit destroyed buffers.
2261
2390
  this.gpuOwner = undefined;
2262
- this.gpuDevice = undefined;
2263
- this.gpuStatus = 'inactive';
2391
+ this.gpuStatus = this.gpuScene === undefined ? 'inactive' : 'resident';
2264
2392
  }
2265
2393
 
2266
2394
  /** Drop only device-owned tables; the CPU projection remains the recovery authority. */
@@ -2326,6 +2454,9 @@ export class PersistentRenderScene {
2326
2454
  scene,
2327
2455
  plan: composition.topology.plan(),
2328
2456
  slots,
2457
+ worldKeys: composition.worldKeys,
2458
+ worldIdentities: composition.worlds.map((world) => world.identity),
2459
+ probeBlend: composition.probeProjection.recordBufferProjection(),
2329
2460
  });
2330
2461
  let published = false;
2331
2462
  let released = false;
@@ -2497,11 +2628,13 @@ export class PersistentRenderScene {
2497
2628
  plan: entry.topology.plan(),
2498
2629
  slots: entry.projection.slotsSnapshot(),
2499
2630
  worldKeys: entry.worldKeys,
2631
+ worldIdentities: entry.worlds.map((world) => world.identity),
2500
2632
  structureMetrics: {
2501
2633
  worldEntitiesScanned: this.worldEntitiesScanned,
2502
2634
  sceneTableUploadBytes: this.sceneTableUploadBytes,
2503
2635
  paletteUploadBytes: 0,
2504
2636
  },
2637
+ probeBlend: entry.probeProjection.recordBufferProjection(),
2505
2638
  };
2506
2639
  }
2507
2640
 
@@ -2510,13 +2643,17 @@ export class PersistentRenderScene {
2510
2643
  // Previous transforms are consumed only by TAA and motion blur. Ordinary
2511
2644
  // frames must not copy and upload the complete GPU transform table.
2512
2645
  const gpuCommit = this.gpuScene?.commitTemporalFrame(this.temporalCapture !== undefined);
2513
- if (gpuCommit !== undefined && !gpuCommit.ok) return gpuCommit;
2646
+ if (gpuCommit !== undefined && !gpuCommit.ok) {
2647
+ this.temporalCapture = undefined;
2648
+ return gpuCommit;
2649
+ }
2514
2650
  const temporalCapture = this.temporalCapture;
2515
2651
  if (temporalCapture !== undefined) {
2516
2652
  try {
2517
2653
  temporalCapture.projection.commitSubmission(temporalCapture.capture);
2518
2654
  } catch (cause) {
2519
2655
  void cause;
2656
+ this.temporalCapture = undefined;
2520
2657
  return err(
2521
2658
  new RhiError({
2522
2659
  code: 'internal-error',
@@ -2530,6 +2667,11 @@ export class PersistentRenderScene {
2530
2667
  return ok(undefined);
2531
2668
  }
2532
2669
 
2670
+ /** Drop a staged temporal capture when the owning frame did not submit. */
2671
+ discardTemporalFrame(): void {
2672
+ this.temporalCapture = undefined;
2673
+ }
2674
+
2533
2675
  /** Capture current CPU scene facts; commitTemporalFrame publishes them only after submit. */
2534
2676
  prepareTemporalFrame(
2535
2677
  visible: readonly Pick<RenderableSnapshot, 'worldId' | 'entityKey'>[],
@@ -2566,7 +2708,12 @@ export class PersistentRenderScene {
2566
2708
  preserveVisibilityOnReorder = false,
2567
2709
  ): ExtractedFrame {
2568
2710
  const previousEntry = this.composition;
2569
- const candidateFrame = buildCandidateFrame('full');
2711
+ const sourceStates = worlds.map(createRenderSourceState);
2712
+ const candidateFrame = buildCandidateFrame({
2713
+ kind: 'partial',
2714
+ entitiesByWorld: sourceStates.map((source) => new Set(source.entities.values())),
2715
+ });
2716
+ for (const source of sourceStates) source.batch.validate();
2570
2717
  this.temporalCapture = undefined;
2571
2718
  this.worldEntitiesScanned = candidateFrame.renderables.length;
2572
2719
  const worldKeys = this.visibilityWorldKeysFor(worlds);
@@ -2594,38 +2741,14 @@ export class PersistentRenderScene {
2594
2741
  this.invalidateActiveEntityRevision();
2595
2742
  }
2596
2743
  }
2597
- // Slot allocation is renderer-owned identity. Sort by stable World identity
2598
- // before allocating so a worlds[] reorder does not silently change a
2599
- // primitive's slot/generation. Dispatch is remapped below to the sorted
2600
- // renderable projection and remains frame-order independent.
2601
- const sorted = candidateFrame.renderables
2602
- .map((snapshot, sourceIndex) => ({ snapshot, sourceIndex }))
2603
- .sort((left, right) => {
2604
- const leftWorld = worldKeys[left.snapshot.worldId] ?? left.snapshot.worldId;
2605
- const rightWorld = worldKeys[right.snapshot.worldId] ?? right.snapshot.worldId;
2606
- return leftWorld - rightWorld || left.snapshot.entityKey - right.snapshot.entityKey;
2607
- });
2608
- const sortedRenderables = sorted.map(({ snapshot }) => snapshot);
2609
- const renderableIndex = new Int32Array(candidateFrame.renderables.length);
2610
- for (let index = 0; index < sorted.length; index += 1) {
2611
- const sourceIndex = sorted[index]?.sourceIndex;
2612
- if (sourceIndex !== undefined) renderableIndex[sourceIndex] = index;
2613
- }
2614
- const sortedDispatch = candidateFrame.dispatch.flatMap((entry) => {
2615
- const index = renderableIndex[entry.renderableIndex];
2616
- return index === undefined ? [] : [{ ...entry, renderableIndex: index }];
2617
- });
2618
- const stableCandidateFrame = {
2619
- ...candidateFrame,
2620
- renderables: sortedRenderables,
2621
- dispatch: sortedDispatch,
2622
- };
2744
+ const stableCandidateFrame = candidateFrame;
2745
+ const renderables = candidateFrame.renderables;
2623
2746
  const projection = previousEntry?.projection ?? new RenderScene();
2624
2747
  if (previousEntry !== undefined) {
2625
2748
  projection.remapWorlds(previousEntry.worlds.map((world) => worlds.indexOf(world)));
2626
2749
  }
2627
2750
  const desired = new Set(
2628
- sortedRenderables.map((snapshot) => identityKey(snapshot.worldId, snapshot.entityKey)),
2751
+ renderables.map((snapshot) => identityKey(snapshot.worldId, snapshot.entityKey)),
2629
2752
  );
2630
2753
  const operations: RenderSceneOperation[] = [];
2631
2754
  for (const slot of projection.slotsSnapshot()) {
@@ -2633,7 +2756,7 @@ export class PersistentRenderScene {
2633
2756
  operations.push({ kind: 'remove', worldId: slot.worldId, entityKey: slot.entityKey });
2634
2757
  }
2635
2758
  }
2636
- for (const snapshot of sortedRenderables) {
2759
+ for (const snapshot of renderables) {
2637
2760
  operations.push({
2638
2761
  kind: 'update',
2639
2762
  worldId: snapshot.worldId,
@@ -2642,6 +2765,22 @@ export class PersistentRenderScene {
2642
2765
  });
2643
2766
  }
2644
2767
  const delta = projection.apply(operations);
2768
+ for (const snapshot of renderables) {
2769
+ const world = worlds[snapshot.worldId];
2770
+ if (world !== undefined && snapshot.instances !== undefined)
2771
+ this.options.instanceCollections?.accept(world, snapshot.entityKey, snapshot.instances);
2772
+ }
2773
+ this.options.instanceCollections?.retain(
2774
+ new Set(
2775
+ projection
2776
+ .slotsSnapshot()
2777
+ .flatMap((slot) =>
2778
+ slot.snapshot.instances?.collectionId === undefined
2779
+ ? []
2780
+ : [slot.snapshot.instances.collectionId],
2781
+ ),
2782
+ ),
2783
+ );
2645
2784
  this.stableSlotByEntity.clear();
2646
2785
  for (const slot of projection.slotsSnapshot()) {
2647
2786
  const stableWorld = worldKeys[slot.worldId] ?? slot.worldId;
@@ -2676,10 +2815,7 @@ export class PersistentRenderScene {
2676
2815
  leases === undefined || leases.length !== worlds.length
2677
2816
  ? []
2678
2817
  : worlds.map(createGlobalTransformChangeQuery),
2679
- renderChangeQueries:
2680
- leases === undefined || leases.length !== worlds.length
2681
- ? []
2682
- : worlds.map(createRenderChangeQueries),
2818
+ sourceStates,
2683
2819
  dispatchBySlot: dispatchEntriesBySlot(stableCandidateFrame, projection),
2684
2820
  dispatchRevision: 0,
2685
2821
  dispatchCache: undefined,
@@ -2687,8 +2823,8 @@ export class PersistentRenderScene {
2687
2823
  pendingRenderableEntitiesByWorld: worlds.map(() => new Set<number>()),
2688
2824
  unavailableSlots: new Set<number>(),
2689
2825
  catalogEpoch,
2690
- instanceEpoch: this.options.instanceCollections?.epoch ?? 0,
2691
2826
  probeProjection: new ProbeBlendSceneProjection(),
2827
+ probeObjects: undefined,
2692
2828
  temporalDemanded: frameDemandsTemporal(candidateFrame),
2693
2829
  gpuDrivenSceneRequired: true,
2694
2830
  };
@@ -2723,6 +2859,7 @@ export class PersistentRenderScene {
2723
2859
  ? projection.cullingWorldBounds(snapshot)
2724
2860
  : projection.cullingWorldBoundsAt(slot);
2725
2861
  });
2862
+ for (const source of sourceStates) source.batch.accept();
2726
2863
  this.frameEpoch += 1;
2727
2864
  this.visibilityFacetFrame = undefined;
2728
2865
  this.visibilityProjectionCache = undefined;
@@ -2745,9 +2882,8 @@ export class PersistentRenderScene {
2745
2882
  */
2746
2883
  private deriveFramePlan(
2747
2884
  entry: PersistentCompositionEntry,
2748
- buildCandidateFrame: (request: PersistentRenderCandidateRequest) => ExtractedFrame,
2885
+ resourceFrame: ExtractedFrame,
2749
2886
  ): ExtractedFrame {
2750
- const resourceFrame = buildCandidateFrame('none');
2751
2887
  const cachedDispatch = entry.dispatchCache;
2752
2888
  const dispatch =
2753
2889
  cachedDispatch?.revision === entry.dispatchRevision
@@ -2857,27 +2993,41 @@ export class PersistentRenderScene {
2857
2993
  ] as [number, number, number],
2858
2994
  };
2859
2995
  const probes = frame.lightProbes ?? [];
2860
- // Sky is evaluated by the global environment path. With no local light
2861
- // probes there can be no per-object ProbeBlendRecord, regardless of how
2862
- // many dynamic objects moved this frame. Preserve the projection call so
2863
- // a transition from probes to no probes still clears retained records.
2864
- if (probes.length === 0) {
2865
- entry.probeProjection.apply({ objects: [], probes, sky });
2866
- return;
2996
+ const revision = entry.projection.revisionValue();
2997
+ if (entry.probeObjects?.revision !== revision) {
2998
+ const slots = entry.projection.slotsSnapshot();
2999
+ const value = Object.freeze(
3000
+ slots.map((slot) =>
3001
+ Object.freeze({
3002
+ objectKey: slot.slot,
3003
+ generation: slot.generation,
3004
+ position: [
3005
+ slot.snapshot.transform.world[12] ?? 0,
3006
+ slot.snapshot.transform.world[13] ?? 0,
3007
+ slot.snapshot.transform.world[14] ?? 0,
3008
+ ] as const,
3009
+ }),
3010
+ ),
3011
+ );
3012
+ entry.probeObjects = {
3013
+ revision,
3014
+ value,
3015
+ surfaceValue: Object.freeze(
3016
+ value.filter((_object, index) =>
3017
+ slots[index]?.snapshot.materials.some(
3018
+ (material) => material.surfaceModel === 'single-layer-medium',
3019
+ ),
3020
+ ),
3021
+ ),
3022
+ };
2867
3023
  }
2868
- const objects: ProbeSceneObjectInput[] = entry.projection.slotsSnapshot().map((slot) => ({
2869
- objectKey: slot.slot,
2870
- generation: slot.generation,
2871
- position: [
2872
- slot.snapshot.transform.world[12] ?? 0,
2873
- slot.snapshot.transform.world[13] ?? 0,
2874
- slot.snapshot.transform.world[14] ?? 0,
2875
- ],
2876
- }));
3024
+ const surfaceOnly = probes.length === 0;
2877
3025
  entry.probeProjection.apply({
2878
- objects,
3026
+ objects: surfaceOnly ? entry.probeObjects.surfaceValue : entry.probeObjects.value,
2879
3027
  probes,
2880
3028
  sky,
3029
+ worldRevision: revision,
3030
+ ...(surfaceOnly ? { retainSkyResidualRecords: true } : {}),
2881
3031
  });
2882
3032
  }
2883
3033
 
@@ -2912,7 +3062,7 @@ export class PersistentRenderScene {
2912
3062
  recreatedSlots: [],
2913
3063
  resynced: 0,
2914
3064
  })
2915
- : acquired.scene.sync(delta);
3065
+ : acquired.scene.sync(delta, (slot) => projection.temporalSnapshotBySlot(slot.slot));
2916
3066
  if (!result.ok) {
2917
3067
  this.sceneTableUploadBytes = 0;
2918
3068
  this.failGpu(result.error);