@forgeax/engine-render 0.1.33 → 0.1.35

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 +1062 -127
  2. package/dist/assembly/backend-contract.d.ts +2 -0
  3. package/dist/assembly/backend-contract.d.ts.map +1 -1
  4. package/dist/assembly/barrel-distortion-inspection.d.ts +3 -0
  5. package/dist/assembly/barrel-distortion-inspection.d.ts.map +1 -0
  6. package/dist/assembly/canvas-draw-guard.d.ts +3 -0
  7. package/dist/assembly/canvas-draw-guard.d.ts.map +1 -0
  8. package/dist/assembly/cloud-shadow-entry.d.ts +4 -0
  9. package/dist/assembly/cloud-shadow-entry.d.ts.map +1 -0
  10. package/dist/assembly/device-feature-admission.d.ts +2 -2
  11. package/dist/assembly/dynamic-geometry-host.d.ts +4 -1
  12. package/dist/assembly/dynamic-geometry-host.d.ts.map +1 -1
  13. package/dist/assembly/dynamic-geometry-runtime.d.ts +1 -1
  14. package/dist/assembly/dynamic-geometry-runtime.d.ts.map +1 -1
  15. package/dist/assembly/factory.d.ts +1 -1
  16. package/dist/assembly/factory.d.ts.map +1 -1
  17. package/dist/assembly/host-contract.d.ts +6 -5
  18. package/dist/assembly/host-contract.d.ts.map +1 -1
  19. package/dist/assembly/material/assembly.d.ts.map +1 -1
  20. package/dist/assembly/material/pipeline-helpers.d.ts +5 -0
  21. package/dist/assembly/material/pipeline-helpers.d.ts.map +1 -1
  22. package/dist/assembly/material-shader-policy.d.ts +16 -2
  23. package/dist/assembly/material-shader-policy.d.ts.map +1 -1
  24. package/dist/assembly/material-shader-prewarm.d.ts.map +1 -1
  25. package/dist/assembly/recovery/device-loss-fanout.d.ts.map +1 -1
  26. package/dist/assembly/recovery/renderer-recover.d.ts +2 -0
  27. package/dist/assembly/recovery/renderer-recover.d.ts.map +1 -1
  28. package/dist/assembly/render-feature-post-processes.d.ts +6 -0
  29. package/dist/assembly/render-feature-post-processes.d.ts.map +1 -0
  30. package/dist/assembly/render-target-host.d.ts +5 -0
  31. package/dist/assembly/render-target-host.d.ts.map +1 -1
  32. package/dist/assembly/renderer-facade.d.ts.map +1 -1
  33. package/dist/assembly/renderer-feature-inspection.d.ts +1 -1
  34. package/dist/assembly/renderer-feature-inspection.d.ts.map +1 -1
  35. package/dist/assembly/renderer-frame-transaction.d.ts +12 -0
  36. package/dist/assembly/renderer-frame-transaction.d.ts.map +1 -1
  37. package/dist/assembly/renderer-inspection.d.ts +11 -0
  38. package/dist/assembly/renderer-inspection.d.ts.map +1 -1
  39. package/dist/assembly/webgpu-pbr-ready.d.ts +1 -0
  40. package/dist/assembly/webgpu-pbr-ready.d.ts.map +1 -1
  41. package/dist/assembly/webgpu-ready-per-pass.d.ts +39 -0
  42. package/dist/assembly/webgpu-ready-per-pass.d.ts.map +1 -0
  43. package/dist/assembly/webgpu-ready.d.ts +1 -1
  44. package/dist/assembly/webgpu-ready.d.ts.map +1 -1
  45. package/dist/assembly/webgpu-renderer-bootstrap.d.ts.map +1 -1
  46. package/dist/assembly/webgpu-renderer-observation-owner.d.ts +35 -0
  47. package/dist/assembly/webgpu-renderer-observation-owner.d.ts.map +1 -0
  48. package/dist/assembly/webgpu-renderer.d.ts +2 -1
  49. package/dist/assembly/webgpu-renderer.d.ts.map +1 -1
  50. package/dist/authoring.mjs +2 -4
  51. package/dist/authoring.mjs.map +1 -1
  52. package/dist/barrel-distortion.d.ts +47 -0
  53. package/dist/barrel-distortion.d.ts.map +1 -0
  54. package/dist/chunk-3XUBWRY2.mjs +1842 -0
  55. package/dist/chunk-3XUBWRY2.mjs.map +1 -0
  56. package/dist/{chunk-OO3DRYK7.mjs → chunk-G6HDNGY7.mjs} +2400 -1767
  57. package/dist/chunk-G6HDNGY7.mjs.map +1 -0
  58. package/dist/{chunk-ZDQWGQW7.mjs → chunk-LM5IE7GF.mjs} +4 -4
  59. package/dist/{chunk-ZDQWGQW7.mjs.map → chunk-LM5IE7GF.mjs.map} +1 -1
  60. package/dist/{chunk-VOZWDM3L.mjs → chunk-LNIUIKQ7.mjs} +1514 -498
  61. package/dist/chunk-LNIUIKQ7.mjs.map +1 -0
  62. package/dist/chunk-MXTWKZO4.mjs +3268 -0
  63. package/dist/chunk-MXTWKZO4.mjs.map +1 -0
  64. package/dist/chunk-MZ4C7EHF.mjs +6942 -0
  65. package/dist/chunk-MZ4C7EHF.mjs.map +1 -0
  66. package/dist/chunk-RIPAEGI6.mjs +48 -0
  67. package/dist/chunk-RIPAEGI6.mjs.map +1 -0
  68. package/dist/{chunk-LEAXOMYJ.mjs → chunk-SGPUJW3F.mjs} +117 -5
  69. package/dist/chunk-SGPUJW3F.mjs.map +1 -0
  70. package/dist/{chunk-PA5DRJNI.mjs → chunk-X2KA6WHM.mjs} +29 -4
  71. package/dist/chunk-X2KA6WHM.mjs.map +1 -0
  72. package/dist/{chunk-BLXGONR2.mjs → chunk-XSFB7XXQ.mjs} +51 -4
  73. package/dist/chunk-XSFB7XXQ.mjs.map +1 -0
  74. package/dist/cloud/density.d.ts +55 -0
  75. package/dist/cloud/density.d.ts.map +1 -0
  76. package/dist/cloud/extract.d.ts +25 -0
  77. package/dist/cloud/extract.d.ts.map +1 -0
  78. package/dist/cloud/feature.d.ts +57 -0
  79. package/dist/cloud/feature.d.ts.map +1 -0
  80. package/dist/cloud/frame-context.d.ts +8 -0
  81. package/dist/cloud/frame-context.d.ts.map +1 -0
  82. package/dist/cloud/inspection.d.ts +59 -0
  83. package/dist/cloud/inspection.d.ts.map +1 -0
  84. package/dist/cloud/optics.d.ts +56 -0
  85. package/dist/cloud/optics.d.ts.map +1 -0
  86. package/dist/cloud/parameters.d.ts +73 -0
  87. package/dist/cloud/parameters.d.ts.map +1 -0
  88. package/dist/cloud/resources.d.ts +60 -0
  89. package/dist/cloud/resources.d.ts.map +1 -0
  90. package/dist/cloud/shadow.d.ts +42 -0
  91. package/dist/cloud/shadow.d.ts.map +1 -0
  92. package/dist/cloud/temporal.d.ts +74 -0
  93. package/dist/cloud/temporal.d.ts.map +1 -0
  94. package/dist/components/barrel-distortion.d.ts +13 -0
  95. package/dist/components/barrel-distortion.d.ts.map +1 -0
  96. package/dist/components/cloud-layer.d.ts +29 -0
  97. package/dist/components/cloud-layer.d.ts.map +1 -0
  98. package/dist/components/index.d.ts +2 -0
  99. package/dist/components/index.d.ts.map +1 -1
  100. package/dist/components/instances.d.ts +20 -6
  101. package/dist/components/instances.d.ts.map +1 -1
  102. package/dist/construct-renderer.d.ts +1 -1
  103. package/dist/construct-renderer.d.ts.map +1 -1
  104. package/dist/construct-renderer.mjs +8406 -5785
  105. package/dist/construct-renderer.mjs.map +1 -1
  106. package/dist/device/gpu-residency.d.ts +19 -19
  107. package/dist/device/gpu-residency.d.ts.map +1 -1
  108. package/dist/dynamic-geometry.d.ts +5 -1
  109. package/dist/dynamic-geometry.d.ts.map +1 -1
  110. package/dist/errors/cloud.d.ts +59 -0
  111. package/dist/errors/cloud.d.ts.map +1 -0
  112. package/dist/errors/render.d.ts +20 -5
  113. package/dist/errors/render.d.ts.map +1 -1
  114. package/dist/examples/single-layer-medium-surface.d.ts +47 -0
  115. package/dist/examples/single-layer-medium-surface.d.ts.map +1 -0
  116. package/dist/extract/camera.d.ts.map +1 -1
  117. package/dist/extract/contracts.d.ts +5 -1
  118. package/dist/extract/contracts.d.ts.map +1 -1
  119. package/dist/extract/material-context.d.ts +7 -0
  120. package/dist/extract/material-context.d.ts.map +1 -0
  121. package/dist/features/barrel-distortion.d.ts +29 -0
  122. package/dist/features/barrel-distortion.d.ts.map +1 -0
  123. package/dist/features/error-reporting.d.ts +6 -2
  124. package/dist/features/error-reporting.d.ts.map +1 -1
  125. package/dist/features/host.d.ts +15 -0
  126. package/dist/features/host.d.ts.map +1 -1
  127. package/dist/features/plan.d.ts +10 -0
  128. package/dist/features/plan.d.ts.map +1 -1
  129. package/dist/features/render-graph-contribution.d.ts +5 -0
  130. package/dist/features/render-graph-contribution.d.ts.map +1 -1
  131. package/dist/features/render-graph-raster.d.ts +1 -0
  132. package/dist/features/render-graph-raster.d.ts.map +1 -1
  133. package/dist/features/targets.d.ts +2 -0
  134. package/dist/features/targets.d.ts.map +1 -1
  135. package/dist/features/types.d.ts +48 -0
  136. package/dist/features/types.d.ts.map +1 -1
  137. package/dist/fullscreen-post-process-pass.d.ts +15 -7
  138. package/dist/fullscreen-post-process-pass.d.ts.map +1 -1
  139. package/dist/glyph-text-layout-system.d.ts +1 -1
  140. package/dist/glyph-text-layout-system.d.ts.map +1 -1
  141. package/dist/gpu-dirty-ranges.d.ts +11 -0
  142. package/dist/gpu-dirty-ranges.d.ts.map +1 -0
  143. package/dist/gpu-driven/batch-topology.d.ts +17 -0
  144. package/dist/gpu-driven/batch-topology.d.ts.map +1 -1
  145. package/dist/gpu-driven/material-artifacts.d.ts +6 -0
  146. package/dist/gpu-driven/material-artifacts.d.ts.map +1 -1
  147. package/dist/gpu-driven/prepared-draw.d.ts.map +1 -1
  148. package/dist/gpu-driven/production-raster-material.d.ts +178 -0
  149. package/dist/gpu-driven/production-raster-material.d.ts.map +1 -0
  150. package/dist/gpu-driven/production-raster.d.ts +47 -3
  151. package/dist/gpu-driven/production-raster.d.ts.map +1 -1
  152. package/dist/gpu-driven/resource-allocation.d.ts +31 -0
  153. package/dist/gpu-driven/resource-allocation.d.ts.map +1 -0
  154. package/dist/gpu-driven/shadow-ownership.d.ts +14 -0
  155. package/dist/gpu-driven/shadow-ownership.d.ts.map +1 -0
  156. package/dist/gpu-driven/view-gpu.d.ts +26 -2
  157. package/dist/gpu-driven/view-gpu.d.ts.map +1 -1
  158. package/dist/gpu-scene.d.ts +5 -0
  159. package/dist/gpu-scene.d.ts.map +1 -1
  160. package/dist/hdrp-buffers.d.ts +2 -2
  161. package/dist/hdrp-buffers.d.ts.map +1 -1
  162. package/dist/ibl/frame-resources.d.ts.map +1 -1
  163. package/dist/ibl/skylight-bind-group.d.ts +14 -5
  164. package/dist/ibl/skylight-bind-group.d.ts.map +1 -1
  165. package/dist/index.d.ts +30 -7
  166. package/dist/index.d.ts.map +1 -1
  167. package/dist/index.mjs +780 -9
  168. package/dist/index.mjs.map +1 -1
  169. package/dist/inspection-types.d.ts +135 -1
  170. package/dist/inspection-types.d.ts.map +1 -1
  171. package/dist/instance-buffer-cache.d.ts +4 -4
  172. package/dist/instance-buffer-cache.d.ts.map +1 -1
  173. package/dist/instances.d.ts +47 -100
  174. package/dist/instances.d.ts.map +1 -1
  175. package/dist/internal.mjs +7 -8
  176. package/dist/internal.mjs.map +1 -1
  177. package/dist/pbr-pipeline.d.ts +22 -4
  178. package/dist/pbr-pipeline.d.ts.map +1 -1
  179. package/dist/pipeline/single-layer-medium-passes.d.ts +73 -0
  180. package/dist/pipeline/single-layer-medium-passes.d.ts.map +1 -0
  181. package/dist/pipeline/standard-forward-lane.d.ts.map +1 -1
  182. package/dist/pipeline/standard-output/graph.d.ts.map +1 -1
  183. package/dist/pipeline/standard-output/lut-gpu.d.ts +2 -2
  184. package/dist/pipeline/standard-output/lut-gpu.d.ts.map +1 -1
  185. package/dist/pipeline/standard-output/resources.d.ts +2 -2
  186. package/dist/pipeline/standard-output/resources.d.ts.map +1 -1
  187. package/dist/pipeline/standard-output/types.d.ts +4 -2
  188. package/dist/pipeline/standard-output/types.d.ts.map +1 -1
  189. package/dist/pipeline/standard-pipeline.d.ts.map +1 -1
  190. package/dist/pipeline/standard-post.d.ts.map +1 -1
  191. package/dist/pipeline-builder.d.ts +1 -1
  192. package/dist/pipeline-builder.d.ts.map +1 -1
  193. package/dist/plugin.d.ts.map +1 -1
  194. package/dist/prepare/prepared-graphics-resolver.d.ts +2 -0
  195. package/dist/prepare/prepared-graphics-resolver.d.ts.map +1 -1
  196. package/dist/publication/contract.d.ts +77 -0
  197. package/dist/publication/contract.d.ts.map +1 -0
  198. package/dist/publication/dependencies.d.ts +7 -0
  199. package/dist/publication/dependencies.d.ts.map +1 -0
  200. package/dist/publication/prepare-geometry.d.ts +9 -0
  201. package/dist/publication/prepare-geometry.d.ts.map +1 -0
  202. package/dist/publication/programs.d.ts +5 -0
  203. package/dist/publication/programs.d.ts.map +1 -0
  204. package/dist/publication/publisher.d.ts +33 -0
  205. package/dist/publication/publisher.d.ts.map +1 -0
  206. package/dist/publication/receiver.d.ts +29 -0
  207. package/dist/publication/receiver.d.ts.map +1 -0
  208. package/dist/publication/resource-scope.d.ts +18 -0
  209. package/dist/publication/resource-scope.d.ts.map +1 -0
  210. package/dist/publication/targets.d.ts +33 -0
  211. package/dist/publication/targets.d.ts.map +1 -0
  212. package/dist/publication/video.d.ts +5 -0
  213. package/dist/publication/video.d.ts.map +1 -0
  214. package/dist/record/barrel-distortion-frame.d.ts +15 -0
  215. package/dist/record/barrel-distortion-frame.d.ts.map +1 -0
  216. package/dist/record/dynamic-geometry-consumption.d.ts +10 -2
  217. package/dist/record/dynamic-geometry-consumption.d.ts.map +1 -1
  218. package/dist/record/fold-buckets.d.ts +95 -0
  219. package/dist/record/fold-buckets.d.ts.map +1 -0
  220. package/dist/record/fold-instance-buffer.d.ts +6 -0
  221. package/dist/record/fold-instance-buffer.d.ts.map +1 -0
  222. package/dist/record/frame-lighting.d.ts +1 -0
  223. package/dist/record/frame-lighting.d.ts.map +1 -1
  224. package/dist/record/frame-snapshot.d.ts +43 -9
  225. package/dist/record/frame-snapshot.d.ts.map +1 -1
  226. package/dist/record/frame.d.ts +15 -65
  227. package/dist/record/frame.d.ts.map +1 -1
  228. package/dist/record/gpu-draw-receipts.d.ts +11 -0
  229. package/dist/record/gpu-draw-receipts.d.ts.map +1 -0
  230. package/dist/record/gpu-timing.d.ts +8 -0
  231. package/dist/record/gpu-timing.d.ts.map +1 -1
  232. package/dist/record/helpers.d.ts +2 -2
  233. package/dist/record/helpers.d.ts.map +1 -1
  234. package/dist/record/main-pass-geometry.d.ts +22 -6
  235. package/dist/record/main-pass-geometry.d.ts.map +1 -1
  236. package/dist/record/main-pass-material.d.ts +14 -10
  237. package/dist/record/main-pass-material.d.ts.map +1 -1
  238. package/dist/record/main-pass-sprite-draws.d.ts +3 -16
  239. package/dist/record/main-pass-sprite-draws.d.ts.map +1 -1
  240. package/dist/record/main-pass.d.ts +13 -2
  241. package/dist/record/main-pass.d.ts.map +1 -1
  242. package/dist/record/motion-blur-receipt.d.ts +11 -0
  243. package/dist/record/motion-blur-receipt.d.ts.map +1 -0
  244. package/dist/record/prepared-material-bindings.d.ts +3 -3
  245. package/dist/record/prepared-material-bindings.d.ts.map +1 -1
  246. package/dist/record/probe-blend-buffer.d.ts +16 -0
  247. package/dist/record/probe-blend-buffer.d.ts.map +1 -0
  248. package/dist/record/recovery-pipeline.d.ts.map +1 -1
  249. package/dist/record/render-context.d.ts +24 -5
  250. package/dist/record/render-context.d.ts.map +1 -1
  251. package/dist/record/shadow-pass.d.ts +2 -0
  252. package/dist/record/shadow-pass.d.ts.map +1 -1
  253. package/dist/record/skybox-post-pass.d.ts +1 -1
  254. package/dist/record/skybox-post-pass.d.ts.map +1 -1
  255. package/dist/record/sprite-instance-buffer.d.ts +31 -0
  256. package/dist/record/sprite-instance-buffer.d.ts.map +1 -0
  257. package/dist/record/target-capture-graph.d.ts +19 -0
  258. package/dist/record/target-capture-graph.d.ts.map +1 -0
  259. package/dist/record/typed-frame-graph.d.ts +24 -15
  260. package/dist/record/typed-frame-graph.d.ts.map +1 -1
  261. package/dist/record/view-ubo.d.ts +11 -3
  262. package/dist/record/view-ubo.d.ts.map +1 -1
  263. package/dist/record/volume-params.d.ts +9 -0
  264. package/dist/record/volume-params.d.ts.map +1 -0
  265. package/dist/recovery/render-system-candidate.d.ts +14 -4
  266. package/dist/recovery/render-system-candidate.d.ts.map +1 -1
  267. package/dist/reflection/record-owner.d.ts.map +1 -1
  268. package/dist/render-contract.d.ts +46 -5
  269. package/dist/render-contract.d.ts.map +1 -1
  270. package/dist/render-graph-primitives.d.ts.map +1 -1
  271. package/dist/render-pipeline.d.ts +103 -2
  272. package/dist/render-pipeline.d.ts.map +1 -1
  273. package/dist/render-system-extract-tail.d.ts +3 -2
  274. package/dist/render-system-extract-tail.d.ts.map +1 -1
  275. package/dist/render-system-extract.d.ts +18 -9
  276. package/dist/render-system-extract.d.ts.map +1 -1
  277. package/dist/render-system-presentation.d.ts +23 -0
  278. package/dist/render-system-presentation.d.ts.map +1 -0
  279. package/dist/render-system-producer-roots.d.ts +10 -0
  280. package/dist/render-system-producer-roots.d.ts.map +1 -0
  281. package/dist/render-system-projections.d.ts +3 -2
  282. package/dist/render-system-projections.d.ts.map +1 -1
  283. package/dist/render-system-residency.d.ts +12 -0
  284. package/dist/render-system-residency.d.ts.map +1 -0
  285. package/dist/render-system.d.ts +22 -21
  286. package/dist/render-system.d.ts.map +1 -1
  287. package/dist/scene/probe-blend.d.ts +27 -0
  288. package/dist/scene/probe-blend.d.ts.map +1 -1
  289. package/dist/scene/render-scene.d.ts +18 -7
  290. package/dist/scene/render-scene.d.ts.map +1 -1
  291. package/dist/scene/render-source.d.ts +42 -0
  292. package/dist/scene/render-source.d.ts.map +1 -0
  293. package/dist/scene/source-systems.d.ts +6 -0
  294. package/dist/scene/source-systems.d.ts.map +1 -0
  295. package/dist/ssao-buffers.d.ts +9 -0
  296. package/dist/ssao-buffers.d.ts.map +1 -1
  297. package/dist/surface/dynamic-input.d.ts +133 -0
  298. package/dist/surface/dynamic-input.d.ts.map +1 -0
  299. package/dist/surface/gpu-driven.d.ts +41 -0
  300. package/dist/surface/gpu-driven.d.ts.map +1 -0
  301. package/dist/surface/submission-observation.d.ts +136 -0
  302. package/dist/surface/submission-observation.d.ts.map +1 -0
  303. package/dist/systems/skin-palette-allocator.d.ts.map +1 -1
  304. package/dist/systems/skin-palette-types.d.ts +10 -0
  305. package/dist/systems/skin-palette-types.d.ts.map +1 -1
  306. package/dist/systems/transparent-dispatch.d.ts +2 -2
  307. package/dist/systems/transparent-dispatch.d.ts.map +1 -1
  308. package/dist/systems/transparent-sort-config.d.ts +2 -1
  309. package/dist/systems/transparent-sort-config.d.ts.map +1 -1
  310. package/dist/temporal/gpu.d.ts +13 -1
  311. package/dist/temporal/gpu.d.ts.map +1 -1
  312. package/dist/temporal/index.mjs +6 -7
  313. package/dist/temporal/inspection.d.ts.map +1 -1
  314. package/dist/temporal/standard-scene-data.d.ts.map +1 -1
  315. package/dist/temporal/target-coverage-attachment.d.ts.map +1 -1
  316. package/dist/transmission/single-layer-medium.d.ts +89 -0
  317. package/dist/transmission/single-layer-medium.d.ts.map +1 -0
  318. package/dist/typed-render-graph-primitives.d.ts +14 -2
  319. package/dist/typed-render-graph-primitives.d.ts.map +1 -1
  320. package/dist/volume/component.d.ts +9 -4
  321. package/dist/volume/component.d.ts.map +1 -1
  322. package/dist/volume/extract.d.ts +1 -1
  323. package/dist/volume/extract.d.ts.map +1 -1
  324. package/dist/volume/inspection.d.ts +2 -0
  325. package/dist/volume/inspection.d.ts.map +1 -1
  326. package/dist/volume/passes.d.ts +4 -1
  327. package/dist/volume/passes.d.ts.map +1 -1
  328. package/dist/volume/resources.d.ts.map +1 -1
  329. package/package.json +22 -21
  330. package/src/__tests__/atmosphere-daylight.dawn.test.ts +72 -0
  331. package/src/__tests__/auto-exposure-transaction.integration.test.ts +4 -9
  332. package/src/__tests__/barrel-distortion-lkg.integration.test.ts +353 -0
  333. package/src/__tests__/barrel-distortion.dawn.test.ts +41 -0
  334. package/src/__tests__/barrel-distortion.unit.test.ts +364 -0
  335. package/src/__tests__/construct-renderer-identity.unit.test.ts +49 -0
  336. package/src/__tests__/detached-recovery-graph.unit.test.ts +277 -0
  337. package/src/__tests__/device-feature-admission.unit.test.ts +3 -2
  338. package/src/__tests__/device-loss-fanout.unit.test.ts +36 -1
  339. package/src/__tests__/docs-gate-green.unit.test.ts +112 -1
  340. package/src/__tests__/factory-contract.integration.test.ts +295 -45
  341. package/src/__tests__/feature-depth-input.dawn.test.ts +129 -2
  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-material-artifacts.unit.test.ts +143 -0
  347. package/src/__tests__/gpu-driven-pbr.dawn.test.ts +5 -6
  348. package/src/__tests__/gpu-driven-production.integration.test.ts +482 -3
  349. package/src/__tests__/gpu-driven-shadow-views.dawn.test.ts +6 -5
  350. package/src/__tests__/gpu-driven-view-gpu-evidence.ts +94 -6
  351. package/src/__tests__/gpu-driven-view-graph.integration.test.ts +18 -2
  352. package/src/__tests__/gpu-driven-view.browser.test.ts +5 -8
  353. package/src/__tests__/gpu-driven-view.dawn.test.ts +6 -2
  354. package/src/__tests__/gpu-driven-view.unit.test.ts +1 -1
  355. package/src/__tests__/gpu-pass-timing-lifecycle.integration.test.ts +1 -0
  356. package/src/__tests__/gpu-pass-timing.browser.test.ts +6 -4
  357. package/src/__tests__/gpu-pass-timing.rhi-null.unit.test.ts +1 -0
  358. package/src/__tests__/gpu-scene.unit.test.ts +119 -9
  359. package/src/__tests__/instance-allocation-failure.unit.test.ts +57 -0
  360. package/src/__tests__/instances-culling.integration.test.ts +10 -36
  361. package/src/__tests__/instances-store.unit.test.ts +97 -305
  362. package/src/__tests__/instances-world-ownership.integration.test.ts +62 -0
  363. package/src/__tests__/ktx2-basis-gpu-consumer.dawn.test.ts +4 -1
  364. package/src/__tests__/main-pass-material-medium-selection.unit.test.ts +145 -0
  365. package/src/__tests__/material-shader-prewarm.unit.test.ts +59 -0
  366. package/src/__tests__/material-snapshot-mutation.integration.test.ts +39 -1
  367. package/src/__tests__/materials-standard-contract.unit.test.ts +0 -1
  368. package/src/__tests__/morph-culling-reentry.dawn.test.ts +5 -5
  369. package/src/__tests__/morph-visual-evidence.integration.test.ts +8 -7
  370. package/src/__tests__/motion-blur-compute.dawn.test.ts +13 -7
  371. package/src/__tests__/motion-blur.pixel.integration.test.ts +2 -2
  372. package/src/__tests__/nineslice-handle-surface.unit.test.ts +4 -4
  373. package/src/__tests__/pbr-view-layout.unit.test.ts +9 -0
  374. package/src/__tests__/physical-clearcoat.integration.test.ts +66 -8
  375. package/src/__tests__/physical-material-budget.unit.test.ts +40 -0
  376. package/src/__tests__/prepared-graphics-capability.unit.test.ts +66 -2
  377. package/src/__tests__/probe-bindings.unit.test.ts +582 -1
  378. package/src/__tests__/probe-scene-dirty.integration.test.ts +28 -1
  379. package/src/__tests__/receipt-observation-retirement.unit.test.ts +65 -0
  380. package/src/__tests__/recovery-candidate-prepare.contract.test.ts +69 -0
  381. package/src/__tests__/render-error-code-owner.test-d.ts +8 -0
  382. package/src/__tests__/render-feature-diagnostics.unit.test.ts +15 -2
  383. package/src/__tests__/render-feature-error-reporting.unit.test.ts +21 -1
  384. package/src/__tests__/render-feature-prepared-diagnostics.unit.test.ts +10 -1
  385. package/src/__tests__/render-feature-retry.unit.test.ts +12 -1
  386. package/src/__tests__/render-scene-mixed-updates.integration.test.ts +145 -19
  387. package/src/__tests__/render-scene-projection.unit.test.ts +36 -11
  388. package/src/__tests__/render-scene-temporal-retry.unit.test.ts +20 -20
  389. package/src/__tests__/render-target-probe-soak.perf.test.ts +2 -2
  390. package/src/__tests__/render-temporal-environment-consumer.browser.test.ts +3 -3
  391. package/src/__tests__/render-temporal-environment-consumer.dawn.test.ts +5 -2
  392. package/src/__tests__/renderer-factory-material-contract.unit.test.ts +42 -8
  393. package/src/__tests__/renderer-frame-generation-fence.integration.test.ts +16 -0
  394. package/src/__tests__/renderer-frame-transaction.integration.test.ts +26 -10
  395. package/src/__tests__/resource-allocation.unit.test.ts +123 -0
  396. package/src/__tests__/single-layer-medium-passes.unit.test.ts +275 -0
  397. package/src/__tests__/skin-motion-regression.unit.test.ts +70 -1
  398. package/src/__tests__/skin-palette-recycling.dawn.test.ts +97 -0
  399. package/src/__tests__/skinned-shadow-caster.test.ts +15 -0
  400. package/src/__tests__/ssr-carrier-public-contract.unit.test.ts +21 -0
  401. package/src/__tests__/ssr-gpu-dispatch.browser.test.ts +4 -4
  402. package/src/__tests__/ssr-gpu-dispatch.dawn.test.ts +2 -2
  403. package/src/__tests__/ssr-gpu-dispatch.ts +1 -1
  404. package/src/__tests__/ssr-receiver-coverage.dawn.test.ts +6 -2
  405. package/src/__tests__/standard-output-chain-contract.unit.test.ts +28 -0
  406. package/src/__tests__/standard-pbr-artifact-assembly.unit.test.ts +46 -1
  407. package/src/__tests__/standard-pipeline.integration.test.ts +182 -1
  408. package/src/__tests__/surface-dynamic-buffer-lifecycle.unit.test.ts +506 -0
  409. package/src/__tests__/surface-submission-observation.unit.test.ts +812 -0
  410. package/src/__tests__/temporal-history-lifecycle.unit.test.ts +32 -0
  411. package/src/__tests__/temporal-performance.integration.test.ts +3 -3
  412. package/src/__tests__/visibility-instances.integration.test.ts +7 -19
  413. package/src/__tests__/visibility-producer-matrix.integration.test.ts +9 -20
  414. package/src/__tests__/volume-optics-oracle.unit.test.ts +4 -2
  415. package/src/__tests__/volumetric-fog-budget-recovery.integration.test.ts +30 -20
  416. package/src/__tests__/volumetric-fog-errors.unit.test.ts +4 -3
  417. package/src/__tests__/volumetric-fog-multiple.unit.test.ts +55 -0
  418. package/src/__tests__/volumetric-fog-spot-frame-optics.integration.test.ts +1 -1
  419. package/src/__tests__/volumetric-fog-stage-readback.dawn.test.ts +113 -9
  420. package/src/__tests__/volumetric-fog-topology.unit.test.ts +5 -3
  421. package/src/assembly/backend-contract.ts +30 -0
  422. package/src/assembly/barrel-distortion-inspection.ts +22 -0
  423. package/src/assembly/canvas-draw-guard.ts +11 -0
  424. package/src/assembly/cloud-shadow-entry.ts +22 -0
  425. package/src/assembly/device-feature-admission.ts +2 -2
  426. package/src/assembly/dynamic-geometry-host.ts +65 -37
  427. package/src/assembly/dynamic-geometry-runtime.ts +7 -1
  428. package/src/assembly/factory.ts +2 -30
  429. package/src/assembly/host-contract.ts +8 -5
  430. package/src/assembly/material/assembly.ts +25 -13
  431. package/src/assembly/material/pipeline-helpers.ts +53 -12
  432. package/src/assembly/material-shader-policy.ts +55 -0
  433. package/src/assembly/material-shader-prewarm.ts +21 -10
  434. package/src/assembly/recovery/device-loss-fanout.ts +13 -0
  435. package/src/assembly/recovery/renderer-recover.ts +50 -18
  436. package/src/assembly/render-feature-post-processes.ts +54 -0
  437. package/src/assembly/render-target-host.ts +57 -47
  438. package/src/assembly/renderer-facade.ts +7 -2
  439. package/src/assembly/renderer-feature-inspection.ts +8 -2
  440. package/src/assembly/renderer-frame-transaction.ts +23 -0
  441. package/src/assembly/renderer-inspection.ts +28 -0
  442. package/src/assembly/skin-palette-owner.ts +1 -1
  443. package/src/assembly/temporal-post-process-prewarm.ts +3 -3
  444. package/src/assembly/webgpu-pbr-ready.ts +9 -1
  445. package/src/assembly/webgpu-ready-per-pass.ts +104 -0
  446. package/src/assembly/webgpu-ready.ts +116 -62
  447. package/src/assembly/webgpu-renderer-bootstrap.ts +0 -12
  448. package/src/assembly/webgpu-renderer-observation-owner.ts +222 -0
  449. package/src/assembly/webgpu-renderer.ts +303 -313
  450. package/src/assembly/webgpu-vertex-layouts.ts +1 -1
  451. package/src/barrel-distortion.ts +247 -0
  452. package/src/cloud/__tests__/cloud-density-parity.dawn.test.ts +173 -0
  453. package/src/cloud/__tests__/cloud-layer-framebuffer-y.dawn.test.ts +265 -0
  454. package/src/cloud/__tests__/cloud-layer.browser.test.ts +432 -0
  455. package/src/cloud/__tests__/cloud-layer.unit.test.ts +634 -0
  456. package/src/cloud/density.ts +600 -0
  457. package/src/cloud/extract.ts +110 -0
  458. package/src/cloud/feature.ts +1810 -0
  459. package/src/cloud/frame-context.ts +42 -0
  460. package/src/cloud/inspection.ts +130 -0
  461. package/src/cloud/optics.ts +285 -0
  462. package/src/cloud/parameters.ts +255 -0
  463. package/src/cloud/resources.ts +122 -0
  464. package/src/cloud/shadow.ts +167 -0
  465. package/src/cloud/temporal.ts +226 -0
  466. package/src/components/barrel-distortion.ts +46 -0
  467. package/src/components/cloud-layer.ts +47 -0
  468. package/src/components/index.ts +2 -0
  469. package/src/components/instances.ts +18 -45
  470. package/src/components/sprite-instances.ts +1 -1
  471. package/src/construct-renderer.ts +1 -1
  472. package/src/device/gpu-residency.ts +26 -25
  473. package/src/dynamic-geometry.ts +12 -1
  474. package/src/errors/cloud.ts +103 -0
  475. package/src/errors/render.ts +47 -8
  476. package/src/examples/single-layer-medium-surface.ts +59 -0
  477. package/src/extract/camera.ts +15 -1
  478. package/src/extract/contracts.ts +5 -0
  479. package/src/extract/material-context.ts +21 -0
  480. package/src/features/barrel-distortion.ts +172 -0
  481. package/src/features/error-reporting.ts +26 -6
  482. package/src/features/host.ts +128 -19
  483. package/src/features/plan.ts +10 -0
  484. package/src/features/render-graph-contribution.ts +9 -0
  485. package/src/features/render-graph-raster.ts +45 -3
  486. package/src/features/targets.ts +66 -0
  487. package/src/features/types.ts +51 -0
  488. package/src/fullscreen-post-process-pass.ts +41 -9
  489. package/src/glyph-text-layout-system.ts +2 -2
  490. package/src/gpu-dirty-ranges.ts +48 -0
  491. package/src/gpu-driven/batch-topology.ts +53 -0
  492. package/src/gpu-driven/material-artifacts.ts +33 -25
  493. package/src/gpu-driven/prepared-draw.ts +12 -4
  494. package/src/gpu-driven/production-raster-material.ts +1894 -0
  495. package/src/gpu-driven/production-raster.ts +825 -784
  496. package/src/gpu-driven/resource-allocation.ts +142 -0
  497. package/src/gpu-driven/shadow-ownership.ts +71 -0
  498. package/src/gpu-driven/view-gpu.ts +164 -10
  499. package/src/gpu-scene.ts +75 -84
  500. package/src/hdrp-buffers.ts +4 -3
  501. package/src/ibl/frame-resources.ts +0 -1
  502. package/src/ibl/skylight-bind-group.ts +62 -32
  503. package/src/index.ts +205 -9
  504. package/src/inspection-types.ts +162 -0
  505. package/src/instance-buffer-cache.ts +4 -4
  506. package/src/instances.ts +152 -569
  507. package/src/pbr-pipeline.ts +230 -30
  508. package/src/pipeline/single-layer-medium-passes.ts +267 -0
  509. package/src/pipeline/standard-forward-lane.ts +313 -57
  510. package/src/pipeline/standard-output/graph.ts +8 -2
  511. package/src/pipeline/standard-output/lut-gpu.ts +3 -3
  512. package/src/pipeline/standard-output/resources.ts +2 -2
  513. package/src/pipeline/standard-output/types.ts +12 -1
  514. package/src/pipeline/standard-pipeline.ts +291 -18
  515. package/src/pipeline/standard-post.ts +90 -24
  516. package/src/pipeline-builder.ts +1 -1
  517. package/src/plugin.ts +4 -0
  518. package/src/points-lines/__tests__/points-lines.bench.ts +17 -8
  519. package/src/prepare/prepared-graphics-resolver.ts +2 -0
  520. package/src/publication/__tests__/derived-geometry.integration.test.ts +177 -0
  521. package/src/publication/__tests__/features.integration.test.ts +122 -0
  522. package/src/publication/__tests__/programs.unit.test.ts +25 -0
  523. package/src/publication/__tests__/publication.integration.test.ts +370 -0
  524. package/src/publication/__tests__/targets.integration.test.ts +120 -0
  525. package/src/publication/__tests__/video.browser.test.ts +50 -0
  526. package/src/publication/contract.ts +104 -0
  527. package/src/publication/dependencies.ts +54 -0
  528. package/src/publication/prepare-geometry.ts +94 -0
  529. package/src/publication/programs.ts +45 -0
  530. package/src/publication/publisher.ts +618 -0
  531. package/src/publication/receiver.ts +297 -0
  532. package/src/publication/resource-scope.ts +31 -0
  533. package/src/publication/targets.ts +182 -0
  534. package/src/publication/video.ts +33 -0
  535. package/src/record/__tests__/main-pass-geometry.unit.test.ts +30 -0
  536. package/src/record/__tests__/material-bg-cache.unit.test.ts +15 -1
  537. package/src/record/__tests__/sprite-material-abi.unit.test.ts +9 -11
  538. package/src/record/__tests__/temporal-payload.unit.test.ts +1 -1
  539. package/src/record/__tests__/transparent-group2-selection.unit.test.ts +11 -0
  540. package/src/record/__tests__/typed-frame-graph-plan.unit.test.ts +1 -0
  541. package/src/record/__tests__/video-texture-view.unit.test.ts +9 -9
  542. package/src/record/__tests__/view-ubo-layout.unit.test.ts +4 -3
  543. package/src/record/barrel-distortion-frame.ts +110 -0
  544. package/src/record/dynamic-geometry-consumption.ts +39 -9
  545. package/src/record/fold-buckets.ts +855 -0
  546. package/src/record/fold-instance-buffer.ts +51 -0
  547. package/src/record/frame-lighting.ts +36 -2
  548. package/src/record/frame-snapshot.ts +45 -9
  549. package/src/record/frame.ts +258 -1105
  550. package/src/record/gpu-draw-receipts.ts +38 -0
  551. package/src/record/gpu-timing.ts +48 -5
  552. package/src/record/helpers.ts +2 -2
  553. package/src/record/main-pass-geometry.ts +272 -84
  554. package/src/record/main-pass-material.ts +102 -36
  555. package/src/record/main-pass-sprite-draws.ts +13 -217
  556. package/src/record/main-pass.ts +164 -27
  557. package/src/record/motion-blur-receipt.ts +91 -0
  558. package/src/record/prepared-material-bindings.ts +11 -6
  559. package/src/record/probe-blend-buffer.ts +180 -0
  560. package/src/record/recovery-pipeline.ts +35 -3
  561. package/src/record/render-context.ts +41 -5
  562. package/src/record/shadow-pass.ts +30 -4
  563. package/src/record/skybox-post-pass.ts +28 -4
  564. package/src/record/sprite-instance-buffer.ts +155 -0
  565. package/src/record/target-capture-graph.ts +258 -0
  566. package/src/record/typed-frame-graph.ts +776 -340
  567. package/src/record/view-ubo.ts +27 -4
  568. package/src/record/volume-params.ts +225 -0
  569. package/src/recovery/render-system-candidate.ts +243 -50
  570. package/src/reflection/record-owner.ts +7 -3
  571. package/src/render-contract.ts +54 -5
  572. package/src/render-graph-primitives.ts +7 -1
  573. package/src/render-pipeline.ts +130 -0
  574. package/src/render-system-extract-tail.ts +90 -78
  575. package/src/render-system-extract.ts +76 -76
  576. package/src/render-system-presentation.ts +56 -0
  577. package/src/render-system-producer-roots.ts +96 -0
  578. package/src/render-system-projections.ts +3 -2
  579. package/src/render-system-residency.ts +159 -0
  580. package/src/render-system.ts +421 -408
  581. package/src/scene/probe-blend.ts +96 -4
  582. package/src/scene/render-scene.ts +416 -337
  583. package/src/scene/render-source.ts +90 -0
  584. package/src/scene/source-systems.ts +33 -0
  585. package/src/ssao-buffers.ts +95 -2
  586. package/src/surface/__tests__/dynamic-input.unit.test.ts +374 -0
  587. package/src/surface/__tests__/gpu-driven.unit.test.ts +87 -0
  588. package/src/surface/dynamic-input.ts +658 -0
  589. package/src/surface/gpu-driven.ts +147 -0
  590. package/src/surface/submission-observation.ts +517 -0
  591. package/src/systems/skin-palette-allocator.ts +5 -1
  592. package/src/systems/skin-palette-types.ts +11 -0
  593. package/src/systems/transparent-dispatch.ts +2 -2
  594. package/src/systems/transparent-sort-config.ts +3 -1
  595. package/src/temporal/gpu.ts +101 -17
  596. package/src/temporal/inspection.ts +7 -1
  597. package/src/temporal/standard-scene-data.ts +5 -0
  598. package/src/temporal/target-coverage-attachment.ts +4 -0
  599. package/src/transmission/__tests__/single-layer-medium.unit.test.ts +149 -0
  600. package/src/transmission/__tests__/standard-transmission.dawn.test.ts +12 -11
  601. package/src/transmission/single-layer-medium.ts +322 -0
  602. package/src/typed-render-graph-primitives.ts +70 -2
  603. package/src/volume/component.ts +10 -9
  604. package/src/volume/extract.ts +11 -5
  605. package/src/volume/inspection.ts +3 -0
  606. package/src/volume/passes.ts +87 -4
  607. package/src/volume/resources.ts +2 -1
  608. package/dist/chunk-3RTCJTH4.mjs +0 -1610
  609. package/dist/chunk-3RTCJTH4.mjs.map +0 -1
  610. package/dist/chunk-BLXGONR2.mjs.map +0 -1
  611. package/dist/chunk-ILQ5MEZF.mjs +0 -32
  612. package/dist/chunk-ILQ5MEZF.mjs.map +0 -1
  613. package/dist/chunk-LEAXOMYJ.mjs.map +0 -1
  614. package/dist/chunk-NB2S7ZRX.mjs +0 -1783
  615. package/dist/chunk-NB2S7ZRX.mjs.map +0 -1
  616. package/dist/chunk-NNQL7CB3.mjs +0 -1713
  617. package/dist/chunk-NNQL7CB3.mjs.map +0 -1
  618. package/dist/chunk-OO3DRYK7.mjs.map +0 -1
  619. package/dist/chunk-PA5DRJNI.mjs.map +0 -1
  620. package/dist/chunk-PLAXOOX5.mjs +0 -1907
  621. package/dist/chunk-PLAXOOX5.mjs.map +0 -1
  622. package/dist/chunk-RXZ3RGOU.mjs +0 -53
  623. package/dist/chunk-RXZ3RGOU.mjs.map +0 -1
  624. package/dist/chunk-VOZWDM3L.mjs.map +0 -1
@@ -1,29 +1,17 @@
1
- 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';
1
+ import { RuntimeMaterialValue, RuntimeMeshVertices } from '@forgeax/engine-assets-runtime';
2
+ import type { EntityHandle, World } from '@forgeax/engine-ecs';
3
+ import type {
4
+ RenderReadLease,
5
+ RenderReadVersion,
6
+ StateProjection,
7
+ StateProjectionBatch,
8
+ } from '@forgeax/engine-ecs/projection';
4
9
  import { box3, frustum, mat4 } from '@forgeax/engine-math';
5
10
  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';
11
+ import { ChildOf, Children, GlobalTransform, MorphWeights } from '@forgeax/engine-scene';
13
12
  import { Skin } from '@forgeax/engine-skinning';
14
13
  import { err, ok, type Result } from '@forgeax/engine-types';
15
- import {
16
- Instances,
17
- Layer,
18
- Lines,
19
- MeshFilter,
20
- MeshRenderer,
21
- Points,
22
- SortKey,
23
- SpriteInstances,
24
- SpriteRegionOverride,
25
- Visibility,
26
- } from '../components';
14
+ import { Instances, Lines, Points, SpriteInstances, Visibility } from '../components';
27
15
  import type { DeviceScope, LifecycleResourceSpec } from '../device/device-scope';
28
16
  import { gpuDrivenShadowDrawKey } from '../extract/gpu-driven';
29
17
  import { motionBlurTemporalDemand } from '../features/motion-blur/motion-blur-params';
@@ -37,10 +25,12 @@ import type {
37
25
  GpuDrivenStructureMetrics,
38
26
  PersistentRenderSceneInspection,
39
27
  } from '../inspection-types';
40
- import type { InstanceCollectionStore } from '../instances';
28
+ import type { InstanceProjectionStore } from '../instances';
41
29
  import { fingerprintNumericArray, InstanceBoundsCache } from '../instances-derived-bounds';
42
30
  import type { PointsLinesInspection } from '../points-lines/inspection';
43
31
  import type { PointsLinesRetainedSnapshot } from '../points-lines/snapshot';
32
+ import type { PreparedRenderPublication } from '../publication/receiver';
33
+ import type { RenderResourceScope } from '../publication/resource-scope';
44
34
  import { worldEntityKey } from '../record/frame-snapshot';
45
35
  import {
46
36
  type ReflectionProbeFact,
@@ -62,7 +52,11 @@ import {
62
52
  type TransmissionDemand,
63
53
  type TransmissionDemandOperation,
64
54
  } from '../transmission/projection';
65
- import { ProbeBlendSceneProjection, type ProbeSceneObjectInput } from './probe-blend';
55
+ import {
56
+ type ProbeBlendBufferProjection,
57
+ ProbeBlendSceneProjection,
58
+ type ProbeSceneObjectInput,
59
+ } from './probe-blend';
66
60
  import type {
67
61
  RenderSceneApplyResult,
68
62
  RenderSceneBounds,
@@ -72,6 +66,13 @@ import type {
72
66
  RenderSceneResyncReason,
73
67
  RenderSceneSlot,
74
68
  } from './render-scene-types';
69
+ import {
70
+ createGlobalTransformChangeQuery,
71
+ createRenderSourceState,
72
+ type GlobalTransformChangeQuery,
73
+ isRenderableMember,
74
+ RENDERABLE_SOURCE_COMPONENTS,
75
+ } from './render-source';
75
76
  import { buildShadowFrusta } from './shadow-visibility';
76
77
  import { createVisibilityBudget, type VisibilityBudget } from './visibility/budget';
77
78
  import {
@@ -114,8 +115,6 @@ interface WorldTransformSpan {
114
115
  readonly worlds: Readonly<Float32Array>;
115
116
  }
116
117
 
117
- type GlobalTransformChangeQuery = Query<readonly [typeof GlobalTransform]>;
118
-
119
118
  function sameInstanceIdentitySet(
120
119
  current: Uint32Array | undefined,
121
120
  previous: Uint32Array | undefined,
@@ -402,16 +401,33 @@ export class RenderScene {
402
401
  return this.emptyResult();
403
402
  }
404
403
 
405
- for (const operation of operations) {
404
+ // Prepare every potentially fallible payload copy before mutating retained
405
+ // columns. The commit loop below only uses owned POD and borrowed numeric spans.
406
+ const preparedOperations = operations.map((operation): RenderSceneOperation => {
407
+ if (operation.kind === 'remove') return operation;
406
408
  if (
407
- operation.kind === 'update' &&
408
409
  operation.snapshot !== undefined &&
409
410
  (operation.worldId !== operation.snapshot.worldId ||
410
411
  operation.entityKey !== operation.snapshot.entityKey)
411
412
  ) {
412
413
  throw new RangeError('RenderScene update identity must match its source snapshot');
413
414
  }
414
- }
415
+ return {
416
+ ...operation,
417
+ ...(operation.snapshot === undefined ? {} : { snapshot: ownSnapshot(operation.snapshot) }),
418
+ ...(operation.instances === undefined
419
+ ? {}
420
+ : {
421
+ instances: {
422
+ ...operation.instances,
423
+ transforms: new Float32Array(operation.instances.transforms),
424
+ ...(operation.instances.generations === undefined
425
+ ? {}
426
+ : { generations: new Uint32Array(operation.instances.generations) }),
427
+ },
428
+ }),
429
+ };
430
+ });
415
431
  this.deltaFrames += 1;
416
432
  this.renderableScans = operations.length;
417
433
  const pendingByWorld = new Map<number, Map<number, PendingIdentity>>();
@@ -453,7 +469,7 @@ export class RenderScene {
453
469
  }
454
470
  }
455
471
  };
456
- for (const operation of operations) {
472
+ for (const operation of preparedOperations) {
457
473
  const pending = pendingFor(operation.worldId, operation.entityKey);
458
474
  if (operation.kind === 'remove') {
459
475
  pending.current = undefined;
@@ -462,7 +478,7 @@ export class RenderScene {
462
478
  }
463
479
  if (operation.snapshot !== undefined) {
464
480
  const previousMaterials = pending.current?.materials;
465
- const next = ownSnapshot(operation.snapshot);
481
+ const next = operation.snapshot;
466
482
  pending.current =
467
483
  previousMaterials !== undefined &&
468
484
  previousMaterials.length === next.materials.length &&
@@ -478,13 +494,7 @@ export class RenderScene {
478
494
  if (operation.instances !== undefined) {
479
495
  pending.current = {
480
496
  ...pending.current,
481
- instances: {
482
- ...operation.instances,
483
- transforms: new Float32Array(operation.instances.transforms),
484
- ...(operation.instances.generations === undefined
485
- ? {}
486
- : { generations: new Uint32Array(operation.instances.generations) }),
487
- },
497
+ instances: operation.instances,
488
498
  };
489
499
  pending.instanceChanged = true;
490
500
  }
@@ -753,6 +763,11 @@ export class RenderScene {
753
763
  return this.slotSnapshot;
754
764
  }
755
765
 
766
+ /** Cheap owner revision for retained derived projections. */
767
+ revisionValue(): number {
768
+ return this.revision;
769
+ }
770
+
756
771
  slot(worldId: number, entityKey: number): RenderSceneSlot | undefined {
757
772
  return this.lookup(worldId, entityKey);
758
773
  }
@@ -1047,10 +1062,10 @@ export class RenderScene {
1047
1062
  export interface PersistentRenderSceneOptions {
1048
1063
  readonly getDevice?: (() => RhiDevice) | undefined;
1049
1064
  readonly onGpuError?: ((error: RhiError) => void) | undefined;
1050
- readonly onSharedRefMutation?: ((worldId: number, handle: number) => void) | undefined;
1065
+ readonly onRuntimeAssetChange?: ((worldId: number, handle: number) => void) | undefined;
1051
1066
  readonly configuredQueryBudget?: number | undefined;
1052
- /** Producer-owned instance collections consumed by the same source update flow. */
1053
- readonly instanceCollections?: InstanceCollectionStore | undefined;
1067
+ /** Instance projection accepted by the same World source update flow. */
1068
+ readonly instanceCollections?: InstanceProjectionStore | undefined;
1054
1069
  }
1055
1070
 
1056
1071
  export interface PersistentGpuDrivenState {
@@ -1059,7 +1074,11 @@ export interface PersistentGpuDrivenState {
1059
1074
  readonly slots: readonly RenderSceneSlot[];
1060
1075
  /** Stable renderer-local World keys aligned with RenderSceneSlot.worldId. */
1061
1076
  readonly worldKeys?: readonly number[];
1077
+ /** Public World identities aligned with RenderSceneSlot.worldId. */
1078
+ readonly worldIdentities?: readonly string[];
1062
1079
  readonly structureMetrics?: GpuDrivenStructureMetrics;
1080
+ /** Stable retained ProbeBlend upload projection from the Scene owner. */
1081
+ readonly probeBlend?: ProbeBlendBufferProjection;
1063
1082
  }
1064
1083
 
1065
1084
  export interface CpuDirectViewInput {
@@ -1127,7 +1146,7 @@ type PersistentGpuSceneInspection = PersistentRenderSceneInspection['gpu'];
1127
1146
 
1128
1147
  interface PersistentCompositionEntry {
1129
1148
  readonly token: object;
1130
- readonly worlds: readonly World[];
1149
+ readonly worlds: readonly RenderResourceScope[];
1131
1150
  readonly projection: RenderScene;
1132
1151
  readonly topology: BatchTopology;
1133
1152
  readonly transmissionDemand: PersistentTransmissionDemandProjection;
@@ -1137,7 +1156,11 @@ interface PersistentCompositionEntry {
1137
1156
  readonly readVersions: RenderReadVersion[];
1138
1157
  readonly leaseIdentities: readonly string[];
1139
1158
  readonly transformQueries: readonly GlobalTransformChangeQuery[];
1140
- readonly renderChangeQueries: readonly ReadonlyMap<number, Query>[];
1159
+ readonly sourceStates: readonly {
1160
+ projection: StateProjection;
1161
+ entities: Map<number, EntityHandle>;
1162
+ contentHandles: Map<number, readonly number[]>;
1163
+ }[];
1141
1164
  readonly dispatchBySlot: Map<number, readonly DispatchEntry[]>;
1142
1165
  dispatchRevision: number;
1143
1166
  dispatchCache: { readonly revision: number; readonly value: DispatchEntry[] } | undefined;
@@ -1147,81 +1170,18 @@ interface PersistentCompositionEntry {
1147
1170
  /** Slots retained for recovery but excluded from direct and indirect submission. */
1148
1171
  readonly unavailableSlots: Set<number>;
1149
1172
  catalogEpoch: number;
1150
- instanceEpoch: number;
1151
1173
  readonly probeProjection: ProbeBlendSceneProjection;
1174
+ probeObjects:
1175
+ | {
1176
+ readonly revision: number;
1177
+ readonly value: readonly ProbeSceneObjectInput[];
1178
+ readonly surfaceValue: readonly ProbeSceneObjectInput[];
1179
+ }
1180
+ | undefined;
1152
1181
  temporalDemanded: boolean;
1153
1182
  gpuDrivenSceneRequired: boolean;
1154
1183
  }
1155
1184
 
1156
- // Retained geometry consumes these component changes. Camera, lighting and
1157
- // environment facts are freshly extracted by deriveFramePlan on every draw;
1158
- // their edits must not recreate unrelated geometry or its submission history.
1159
- const RENDERABLE_SOURCE_COMPONENTS = [
1160
- GlobalTransform,
1161
- ChildOf,
1162
- MeshFilter,
1163
- MeshRenderer,
1164
- Instances,
1165
- SpriteInstances,
1166
- SpriteRegionOverride,
1167
- Skin,
1168
- Layer,
1169
- Visibility,
1170
- MorphWeights,
1171
- Points,
1172
- Lines,
1173
- SortKey,
1174
- ] as const;
1175
- const RENDERABLE_SOURCE_COMPONENT_IDS = new Set(
1176
- RENDERABLE_SOURCE_COMPONENTS.map((component) => componentId(component)),
1177
- );
1178
- const VISIBILITY_DEPENDENCY_COMPONENT_IDS = new Set([
1179
- componentId(ChildOf),
1180
- componentId(Visibility),
1181
- ]);
1182
- const STRUCTURAL_MEMBERSHIP_SCAN_COMPONENT_IDS = new Set([
1183
- componentId(ChildOf),
1184
- componentId(Layer),
1185
- componentId(Visibility),
1186
- componentId(SpriteRegionOverride),
1187
- componentId(SortKey),
1188
- ]);
1189
-
1190
- function createGlobalTransformChangeQuery(world: World): GlobalTransformChangeQuery {
1191
- const result = world.query({ read: [GlobalTransform], changed: [GlobalTransform] });
1192
- if (!result.ok) throw result.error;
1193
- const query = result.value as GlobalTransformChangeQuery;
1194
- for (const _span of query.spans().unwrap()) {
1195
- // The full rebuild consumed these values; establish the observation baseline.
1196
- }
1197
- return query;
1198
- }
1199
-
1200
- function createRenderChangeQueries(world: World): ReadonlyMap<number, Query> {
1201
- const queries = new Map<number, Query>();
1202
- for (const component of RENDERABLE_SOURCE_COMPONENTS) {
1203
- if (component === GlobalTransform) continue;
1204
- const result = world.query({ read: [component], changed: [component] });
1205
- if (!result.ok) throw result.error;
1206
- const query = result.value as Query;
1207
- // The initial full extraction has already consumed this change evidence.
1208
- // Keep the query compiled so subsequent frames iterate only rows whose
1209
- // component version changed.
1210
- for (const _span of query.spans().unwrap()) {
1211
- // Establish the observation baseline.
1212
- }
1213
- queries.set(componentId(component), query);
1214
- }
1215
- return queries;
1216
- }
1217
-
1218
- function collectRenderableEntities(world: World): ReadonlySet<number> {
1219
- const result = new Set<number>();
1220
- const query = world.query({ read: [MeshRenderer], with: [Transform, MeshFilter] }).unwrap();
1221
- for (const row of query) result.add(row.entity as number);
1222
- return result;
1223
- }
1224
-
1225
1185
  function sourceMembershipChanged(world: World, slot: RenderSceneSlot): boolean {
1226
1186
  const entity = slot.entityKey as EntityHandle;
1227
1187
  const snapshot = slot.snapshot;
@@ -1576,7 +1536,7 @@ export class PersistentRenderScene {
1576
1536
  return this.visibilityBudget;
1577
1537
  }
1578
1538
 
1579
- private visibilityWorldKey(world: World | undefined, fallback: number): number {
1539
+ private visibilityWorldKey(world: RenderResourceScope | undefined, fallback: number): number {
1580
1540
  if (world === undefined) return fallback;
1581
1541
  const existing = this.visibilityWorldKeys.get(world.identity);
1582
1542
  if (existing !== undefined) return existing;
@@ -1586,12 +1546,12 @@ export class PersistentRenderScene {
1586
1546
  return key;
1587
1547
  }
1588
1548
 
1589
- private worldKeyAt(worlds: readonly World[], worldId: number): number {
1549
+ private worldKeyAt(worlds: readonly RenderResourceScope[], worldId: number): number {
1590
1550
  return this.visibilityWorldKey(worlds[worldId], worldId);
1591
1551
  }
1592
1552
 
1593
1553
  /** Stable World routing keys for the current worlds[] frame projection. */
1594
- visibilityWorldKeysFor(worlds: readonly World[]): readonly number[] {
1554
+ visibilityWorldKeysFor(worlds: readonly RenderResourceScope[]): readonly number[] {
1595
1555
  return worlds.map((world, worldId) => this.visibilityWorldKey(world, worldId));
1596
1556
  }
1597
1557
 
@@ -1600,6 +1560,10 @@ export class PersistentRenderScene {
1600
1560
  return this.composition?.projection.slotsSnapshot() ?? [];
1601
1561
  }
1602
1562
 
1563
+ compositionSlot(worldId: number, entityKey: number): RenderSceneSlot | undefined {
1564
+ return this.composition?.projection.slot(worldId, entityKey);
1565
+ }
1566
+
1603
1567
  shadowCasterProjection(): PersistentShadowCasterProjection | undefined {
1604
1568
  return this.shadowProjection;
1605
1569
  }
@@ -1610,7 +1574,7 @@ export class PersistentRenderScene {
1610
1574
  }
1611
1575
 
1612
1576
  private stableEntityKey(
1613
- worlds: readonly World[],
1577
+ worlds: readonly RenderResourceScope[],
1614
1578
  renderable: Pick<RenderableSnapshot, 'worldId' | 'entityKey'>,
1615
1579
  ): number {
1616
1580
  return worldEntityKey(this.worldKeyAt(worlds, renderable.worldId), renderable.entityKey);
@@ -1618,7 +1582,7 @@ export class PersistentRenderScene {
1618
1582
 
1619
1583
  /** Feed the facet from extracted renderables using persistent slot identity. */
1620
1584
  updateVisibilityFacet(
1621
- worlds: readonly World[],
1585
+ worlds: readonly RenderResourceScope[],
1622
1586
  camera: CameraSnapshot | undefined,
1623
1587
  renderables: readonly RenderableSnapshot[],
1624
1588
  ): void {
@@ -1678,7 +1642,7 @@ export class PersistentRenderScene {
1678
1642
  * visible through `decideVisibility`.
1679
1643
  */
1680
1644
  projectVisibility(
1681
- worlds: readonly World[],
1645
+ worlds: readonly RenderResourceScope[],
1682
1646
  camera: CameraSnapshot | undefined,
1683
1647
  renderables: readonly RenderableSnapshot[],
1684
1648
  dispatch: readonly DispatchEntry[],
@@ -1852,6 +1816,77 @@ export class PersistentRenderScene {
1852
1816
  return this.reflectionProbes.snapshot();
1853
1817
  }
1854
1818
 
1819
+ consumePublication(input: PreparedRenderPublication): ExtractedFrame {
1820
+ const { resources, packet, frame } = input;
1821
+ let entry = this.composition;
1822
+ if (entry === undefined || packet.baseline) {
1823
+ this.invalidate();
1824
+ const projection = new RenderScene();
1825
+ entry = {
1826
+ token: {},
1827
+ worlds: [resources],
1828
+ projection,
1829
+ topology: new BatchTopology(),
1830
+ transmissionDemand: new PersistentTransmissionDemandProjection(),
1831
+ hiddenEntityReports: [],
1832
+ worldKeys: this.visibilityWorldKeysFor([resources]),
1833
+ readVersions: [],
1834
+ leaseIdentities: [],
1835
+ transformQueries: [],
1836
+ sourceStates: [],
1837
+ dispatchBySlot: new Map(),
1838
+ dispatchRevision: 0,
1839
+ dispatchCache: undefined,
1840
+ skinConsumersByJoint: new Map(),
1841
+ pendingRenderableEntitiesByWorld: [],
1842
+ unavailableSlots: new Set(),
1843
+ catalogEpoch: 0,
1844
+ probeProjection: new ProbeBlendSceneProjection(),
1845
+ probeObjects: undefined,
1846
+ temporalDemanded: false,
1847
+ gpuDrivenSceneRequired: true,
1848
+ };
1849
+ this.composition = entry;
1850
+ this.fullRebuilds++;
1851
+ this.lastResyncReason = 'attach';
1852
+ }
1853
+ this.worldEntitiesScanned = 0;
1854
+ this.sceneTableUploadBytes = 0;
1855
+ const removedDemand: TransmissionDemandOperation[] = [];
1856
+ for (const operation of input.operations) {
1857
+ if (operation.kind !== 'remove' && operation.snapshot === undefined) continue;
1858
+ const previous = entry.projection.slot(0, operation.entityKey);
1859
+ for (let index = 0; index < (previous?.snapshot.materials.length ?? 0); index++)
1860
+ removedDemand.push({ kind: 'remove', key: `0:${operation.entityKey}:${index}` });
1861
+ }
1862
+ const delta = entry.projection.apply(input.operations);
1863
+ for (const removed of delta.removedSlots) {
1864
+ entry.dispatchBySlot.delete(removed.slot);
1865
+ this.stableSlotByEntity.delete(worldEntityKey(entry.worldKeys[0] ?? 0, removed.entityKey));
1866
+ }
1867
+ for (const recreated of delta.recreatedSlots) entry.dispatchBySlot.delete(recreated.slot);
1868
+ for (const [slot, dispatch] of dispatchEntriesBySlot(frame, entry.projection))
1869
+ entry.dispatchBySlot.set(slot, dispatch);
1870
+ if (frame.renderables.length || packet.removed.length) {
1871
+ entry.dispatchRevision++;
1872
+ entry.dispatchCache = undefined;
1873
+ this.visibilityFacets.clear();
1874
+ this.invalidateActiveEntityRevision();
1875
+ }
1876
+ for (const slot of [...delta.createdSlots, ...delta.updatedSlots, ...delta.recreatedSlots])
1877
+ this.stableSlotByEntity.set(worldEntityKey(entry.worldKeys[0] ?? 0, slot.entityKey), slot);
1878
+ entry.transmissionDemand.apply([
1879
+ ...removedDemand,
1880
+ ...transmissionDemandOperations(frame.renderables),
1881
+ ]);
1882
+ entry.topology.apply(delta);
1883
+ this.syncGpuOwner(entry.token, entry.projection, delta);
1884
+ if (input.operations.length) this.deltaFrames++;
1885
+ else this.noChangeFrames++;
1886
+ this.transformUpdates += delta.updated;
1887
+ return this.deriveFramePlan(entry, frame);
1888
+ }
1889
+
1855
1890
  extractComposition(
1856
1891
  worlds: readonly World[],
1857
1892
  owner: { readonly cameraOwner: number; readonly resourceOwner: number },
@@ -1865,7 +1900,7 @@ export class PersistentRenderScene {
1865
1900
  const sameWorldSet =
1866
1901
  entry !== undefined &&
1867
1902
  entry.worlds.length === worlds.length &&
1868
- entry.worlds.every((world) => worlds.includes(world));
1903
+ entry.worlds.every((world) => worlds.some((local) => local === world));
1869
1904
  const worldOrderChanged =
1870
1905
  entry !== undefined &&
1871
1906
  sameWorldSet &&
@@ -1899,7 +1934,6 @@ export class PersistentRenderScene {
1899
1934
  preserveVisibilityOnReorder,
1900
1935
  );
1901
1936
  }
1902
- let requiresSourceRefresh = false;
1903
1937
  const changedEntitiesByWorld = worlds.map(
1904
1938
  (_, worldId) => new Set(entry.pendingRenderableEntitiesByWorld[worldId] ?? []),
1905
1939
  );
@@ -1912,171 +1946,144 @@ export class PersistentRenderScene {
1912
1946
  contentChangedByWorld[slot.worldId]?.add(slot.entityKey);
1913
1947
  }
1914
1948
  }
1915
- const instanceEpoch = this.options.instanceCollections?.epoch ?? 0;
1916
- if (entry.instanceEpoch !== instanceEpoch) {
1917
- for (const slot of entry.projection.slotsSnapshot()) {
1918
- const id = slot.snapshot.instances?.collectionId;
1919
- if (id === undefined) continue;
1920
- const current = this.options.instanceCollections?.inspect(id);
1921
- if (!current?.ok || current.value.revision !== slot.snapshot.instances?.revision) {
1922
- changedEntitiesByWorld[slot.worldId]?.add(slot.entityKey);
1923
- }
1924
- }
1925
- }
1926
1949
  const nextReadVersions = entry.readVersions.slice();
1927
- const structureChangedWorlds = new Set<number>();
1928
- const structuralComponentIdsByWorld = worlds.map(() => new Set<number>());
1929
- let currentRenderableEntitiesByWorld: readonly (ReadonlySet<number> | undefined)[] | undefined;
1950
+ const sourceBatches: StateProjectionBatch[] = [];
1930
1951
  const removedSlots: RenderSceneSlot[] = [];
1931
- for (let worldId = 0; worldId < worlds.length; worldId += 1) {
1952
+ const transformSpans: WorldTransformSpan[] = [];
1953
+ for (let worldId = 0; worldId < worlds.length; worldId++) {
1932
1954
  const world = worlds[worldId];
1933
- if (world === undefined) continue;
1934
- const lease = leases?.[worldId];
1935
- if (lease === undefined || entry.leaseIdentities[worldId] !== lease.worldIdentity) {
1936
- requiresSourceRefresh = true;
1937
- break;
1938
- }
1955
+ const source = entry.sourceStates[worldId];
1956
+ const lease = leases[worldId];
1939
1957
  const version = entry.readVersions[worldId];
1940
- if (version === undefined) {
1941
- requiresSourceRefresh = true;
1942
- break;
1943
- }
1944
- let read: ReturnType<RenderReadLease['readChanges']>;
1945
- try {
1946
- read = lease.readChanges(version);
1947
- } catch {
1948
- requiresSourceRefresh = true;
1949
- break;
1950
- }
1951
- if (read.status === 'rebuild') {
1952
- structureChangedWorlds.add(worldId);
1958
+ if (
1959
+ world === undefined ||
1960
+ source === undefined ||
1961
+ lease === undefined ||
1962
+ version === undefined
1963
+ )
1964
+ continue;
1965
+ const read = lease.readChanges(version);
1966
+ nextReadVersions[worldId] = read.version;
1967
+ const changed = changedEntitiesByWorld[worldId];
1968
+ const content = contentChangedByWorld[worldId];
1969
+ if (changed === undefined || content === undefined) continue;
1970
+ if (version.structureEpoch !== read.version.structureEpoch) {
1971
+ this.visibilityFacets.clear();
1972
+ this.invalidateActiveEntityRevision();
1953
1973
  }
1954
- const changes = read.world;
1955
- const shared = read.sharedRefs;
1956
- if (shared.records.length > 0) {
1957
- for (const record of shared.records) {
1958
- this.options.onSharedRefMutation?.(worldId, record.handle);
1959
- const changed = changedEntitiesByWorld[worldId];
1960
- const content = contentChangedByWorld[worldId];
1961
- if (changed !== undefined && content !== undefined) {
1962
- entry.projection.addSharedRefConsumers(worldId, record.handle, content);
1963
- for (const entity of content) changed.add(entity);
1974
+ const batch = source.projection.read();
1975
+ sourceBatches.push(batch);
1976
+ this.worldEntitiesScanned += batch.scannedRows;
1977
+ const dependencyVisited = new Set<number>();
1978
+ const runtimeAssets = new Set<number>();
1979
+ const runtimeContentChanged =
1980
+ batch.membershipChanged ||
1981
+ batch.changedComponents.includes(RuntimeMaterialValue) ||
1982
+ batch.changedComponents.includes(RuntimeMeshVertices);
1983
+ const contentComponents = RENDERABLE_SOURCE_COMPONENTS.filter(
1984
+ (component) => component !== Instances && batch.changedComponents.includes(component),
1985
+ );
1986
+ const instancesChanged = batch.changedComponents.includes(Instances);
1987
+ const reconcileIdentities =
1988
+ batch.membershipChanged ||
1989
+ runtimeContentChanged ||
1990
+ contentComponents.length > 0 ||
1991
+ instancesChanged;
1992
+ for (const index of reconcileIdentities ? batch.indices : []) {
1993
+ const previous = source.entities.get(index);
1994
+ const current = source.projection.entity(index);
1995
+ if (runtimeContentChanged) {
1996
+ const runtimeHandles = new Set(source.contentHandles.get(index));
1997
+ if (current !== undefined) {
1998
+ const material = world.hasComponent(current, RuntimeMaterialValue)
1999
+ ? world.get(current, RuntimeMaterialValue)
2000
+ : undefined;
2001
+ const mesh = world.hasComponent(current, RuntimeMeshVertices)
2002
+ ? world.get(current, RuntimeMeshVertices)
2003
+ : undefined;
2004
+ if (material?.ok) runtimeHandles.add(Number(material.value.asset));
2005
+ if (mesh?.ok) runtimeHandles.add(Number(mesh.value.asset));
1964
2006
  }
2007
+ for (const handle of runtimeHandles) runtimeAssets.add(handle);
1965
2008
  }
1966
- }
1967
- for (const changedComponentId of changes.changedComponentIds) {
1968
- if (read.status === 'rebuild') {
1969
- structuralComponentIdsByWorld[worldId]?.add(changedComponentId);
1970
- }
1971
- if (!RENDERABLE_SOURCE_COMPONENT_IDS.has(changedComponentId)) continue;
1972
- const changed = changedEntitiesByWorld[worldId];
1973
- if (changed === undefined) continue;
1974
- const otherChanged = contentChangedByWorld[worldId];
1975
- const query = entry.renderChangeQueries[worldId]?.get(changedComponentId);
1976
- if (query !== undefined) {
1977
- for (const row of query) {
1978
- const entity = row.entity as number;
1979
- if (VISIBILITY_DEPENDENCY_COMPONENT_IDS.has(changedComponentId)) {
1980
- const affected = collectSubtree(world, row.entity);
1981
- for (const dependent of affected) {
1982
- if (
1983
- !entry.projection.has(worldId, dependent) &&
1984
- !world.hasComponent(dependent as EntityHandle, MeshRenderer)
1985
- )
1986
- continue;
1987
- changed.add(dependent);
1988
- otherChanged?.add(dependent);
1989
- }
1990
- } else {
1991
- changed.add(entity);
1992
- if (changedComponentId !== componentId(Instances)) otherChanged?.add(entity);
1993
- }
2009
+ if (previous !== undefined) {
2010
+ const slot = entry.projection.slot(worldId, previous);
2011
+ if (
2012
+ batch.membershipChanged &&
2013
+ slot !== undefined &&
2014
+ (current !== previous || !isRenderableMember(world, previous))
2015
+ ) {
2016
+ removedSlots.push(slot);
2017
+ changed.add(previous);
1994
2018
  }
1995
- }
1996
- }
1997
- if (requiresSourceRefresh) break;
1998
- nextReadVersions[worldId] = read.version;
1999
- }
2000
- if (requiresSourceRefresh) {
2001
- return this.reconcileComposition(worlds, owner, catalogEpoch, buildCandidateFrame, leases);
2002
- }
2003
-
2004
- const transformSpans: WorldTransformSpan[] = [];
2005
- for (let worldId = 0; worldId < entry.transformQueries.length; worldId += 1) {
2006
- const query = entry.transformQueries[worldId];
2007
- if (query === undefined) continue;
2008
- for (const span of query.spans().unwrap()) {
2009
- const changedWorlds = span.get(GlobalTransform).world;
2010
- if (!(changedWorlds instanceof Float32Array)) {
2011
- requiresSourceRefresh = true;
2012
- break;
2013
- }
2014
- transformSpans.push({ worldId, entities: span.entities, worlds: changedWorlds });
2015
- for (const entity of span.entities) {
2016
- for (const consumer of entry.skinConsumersByJoint.get(skinJointKey(worldId, entity)) ??
2019
+ for (const consumer of entry.skinConsumersByJoint.get(skinJointKey(worldId, previous)) ??
2017
2020
  []) {
2018
- changedEntitiesByWorld[worldId]?.add(consumer);
2019
- contentChangedByWorld[worldId]?.add(consumer);
2021
+ changed.add(consumer);
2022
+ content.add(consumer);
2020
2023
  }
2021
2024
  }
2022
- }
2023
- if (requiresSourceRefresh) break;
2024
- }
2025
- if (requiresSourceRefresh) {
2026
- return this.reconcileComposition(worlds, owner, catalogEpoch, buildCandidateFrame, leases);
2027
- }
2028
- if (structureChangedWorlds.size > 0) {
2029
- this.visibilityFacets.clear();
2030
- this.invalidateActiveEntityRevision();
2031
- const currentByWorld = worlds.map((world, worldId) =>
2032
- structureChangedWorlds.has(worldId) ? collectRenderableEntities(world) : undefined,
2033
- );
2034
- currentRenderableEntitiesByWorld = currentByWorld;
2035
- for (let worldId = 0; worldId < currentByWorld.length; worldId += 1) {
2036
- const world = worlds[worldId];
2037
- const current = currentByWorld[worldId];
2038
- if (world === undefined || current === undefined) continue;
2039
- const changed = changedEntitiesByWorld[worldId];
2040
- if (changed !== undefined && structureChangedWorlds.has(worldId)) {
2041
- // A joint can disappear without leaving a row in the joint's
2042
- // GlobalTransform change query. Wake every dependent renderable so
2043
- // the next partial extraction validates the dangling reference and
2044
- // either republishes or retires its palette data.
2045
- for (const [jointKey, consumers] of entry.skinConsumersByJoint) {
2046
- const separator = jointKey.indexOf(':');
2047
- const indexedWorldId = Number(jointKey.slice(0, separator));
2048
- if (indexedWorldId !== worldId) continue;
2049
- const jointEntity = Number(jointKey.slice(separator + 1));
2050
- if (world.hasComponent(jointEntity as EntityHandle, GlobalTransform)) continue;
2051
- for (const consumer of consumers) {
2052
- changed.add(consumer);
2053
- contentChangedByWorld[worldId]?.add(consumer);
2025
+ if (current === undefined) continue;
2026
+ const existing = entry.projection.slot(worldId, current);
2027
+ const membershipChanged =
2028
+ batch.membershipChanged &&
2029
+ (existing === undefined || sourceMembershipChanged(world, existing));
2030
+ let contentChanged = membershipChanged;
2031
+ for (const component of contentComponents) {
2032
+ if (source.projection.changed(current, component)) contentChanged = true;
2033
+ }
2034
+ if (
2035
+ (contentChanged || (instancesChanged && source.projection.changed(current, Instances))) &&
2036
+ isRenderableMember(world, current)
2037
+ ) {
2038
+ changed.add(current);
2039
+ if (contentChanged) content.add(current);
2040
+ }
2041
+ if (
2042
+ version.structureEpoch !== read.version.structureEpoch ||
2043
+ source.projection.changed(current, ChildOf) ||
2044
+ source.projection.changed(current, Visibility)
2045
+ ) {
2046
+ if (!dependencyVisited.has(current)) {
2047
+ const pending = [current];
2048
+ while (pending.length > 0) {
2049
+ const dependent = pending.pop();
2050
+ if (dependent === undefined || dependencyVisited.has(dependent)) continue;
2051
+ dependencyVisited.add(dependent);
2052
+ const children = world.hasComponent(dependent, Children)
2053
+ ? world.get(dependent, Children)
2054
+ : undefined;
2055
+ if (children?.ok)
2056
+ for (const child of children.value.entities) pending.push(child as EntityHandle);
2057
+ if (!isRenderableMember(world, dependent)) continue;
2058
+ changed.add(dependent);
2059
+ content.add(dependent);
2054
2060
  }
2055
2061
  }
2056
- for (const entity of current) {
2057
- if (entry.projection.slot(worldId, entity) === undefined) changed.add(entity);
2058
- }
2059
- const structuralComponentIds = structuralComponentIdsByWorld[worldId];
2060
- const scanAllMembership =
2061
- structuralComponentIds !== undefined &&
2062
- [...structuralComponentIds].some((id) =>
2063
- STRUCTURAL_MEMBERSHIP_SCAN_COMPONENT_IDS.has(id),
2064
- );
2065
- for (const slot of entry.projection.slotsSnapshot()) {
2066
- if (slot.worldId !== worldId || !current.has(slot.entityKey)) continue;
2067
- const membershipChanged = sourceMembershipChanged(world, slot);
2068
- if (scanAllMembership || membershipChanged) {
2069
- changed.add(slot.entityKey);
2070
- contentChangedByWorld[worldId]?.add(slot.entityKey);
2062
+ }
2063
+ }
2064
+ for (const handle of runtimeAssets) {
2065
+ this.options.onRuntimeAssetChange?.(worldId, handle);
2066
+ entry.projection.addSharedRefConsumers(worldId, handle, content);
2067
+ }
2068
+ for (const entity of content) changed.add(entity);
2069
+ const query = entry.transformQueries[worldId];
2070
+ if (query !== undefined) {
2071
+ for (const span of query.spans().unwrap()) {
2072
+ const matrices = span.get(GlobalTransform).world;
2073
+ if (!(matrices instanceof Float32Array))
2074
+ throw new Error('GlobalTransform projection must expose a numeric column.');
2075
+ transformSpans.push({ worldId, entities: span.entities, worlds: matrices });
2076
+ if (entry.skinConsumersByJoint.size > 0) {
2077
+ for (const entity of span.entities) {
2078
+ for (const consumer of entry.skinConsumersByJoint.get(
2079
+ skinJointKey(worldId, entity),
2080
+ ) ?? []) {
2081
+ changed.add(consumer);
2082
+ content.add(consumer);
2083
+ }
2071
2084
  }
2072
2085
  }
2073
2086
  }
2074
- for (const slot of entry.projection.slotsSnapshot()) {
2075
- if (slot.worldId !== worldId || current.has(slot.entityKey)) continue;
2076
- removedSlots.push(slot);
2077
- changedEntitiesByWorld[worldId]?.add(slot.entityKey);
2078
- }
2079
- this.worldEntitiesScanned += current.size;
2080
2087
  }
2081
2088
  }
2082
2089
 
@@ -2089,6 +2096,8 @@ export class PersistentRenderScene {
2089
2096
  for (const entityKey of changedEntitiesByWorld[worldId] ?? []) pending.add(entityKey);
2090
2097
  }
2091
2098
 
2099
+ const resourceFrame = buildCandidateFrame('none');
2100
+ for (const batch of sourceBatches) batch.validate();
2092
2101
  let changedDelta: RenderSceneApplyResult | undefined;
2093
2102
  const hasChangedEntities = changedEntitiesByWorld.some((entities) => entities.size > 0);
2094
2103
  if (hasChangedEntities || removedSlots.length > 0 || transformSpans.length > 0) {
@@ -2103,9 +2112,6 @@ export class PersistentRenderScene {
2103
2112
  }
2104
2113
  for (const slot of removedSlots) {
2105
2114
  operations.push({ kind: 'remove', worldId: slot.worldId, entityKey: slot.entityKey });
2106
- if (slot.snapshot.skinJointEntities !== undefined) {
2107
- removeSkinConsumer(entry.skinConsumersByJoint, slot.snapshot);
2108
- }
2109
2115
  for (
2110
2116
  let materialIndex = 0;
2111
2117
  materialIndex < slot.snapshot.materials.length;
@@ -2162,7 +2168,23 @@ export class PersistentRenderScene {
2162
2168
  });
2163
2169
  publishedEntitiesByWorld[snapshot.worldId]?.add(snapshot.entityKey);
2164
2170
  }
2171
+ for (const batch of sourceBatches) batch.validate();
2165
2172
  changedDelta = entry.projection.apply(operations, transformSpans);
2173
+ for (const slot of changedDelta.removedSlots) {
2174
+ const world = worlds[slot.worldId];
2175
+ if (world !== undefined) this.options.instanceCollections?.release(world, slot.entityKey);
2176
+ }
2177
+ for (const snapshot of partial?.renderables ?? []) {
2178
+ const world = worlds[snapshot.worldId];
2179
+ if (world === undefined) continue;
2180
+ if (snapshot.instances === undefined)
2181
+ this.options.instanceCollections?.release(world, snapshot.entityKey);
2182
+ else
2183
+ this.options.instanceCollections?.accept(world, snapshot.entityKey, snapshot.instances);
2184
+ }
2185
+ for (const slot of removedSlots)
2186
+ if (slot.snapshot.skinJointEntities !== undefined)
2187
+ removeSkinConsumer(entry.skinConsumersByJoint, slot.snapshot);
2166
2188
  const publishedSkinKeys = new Set<string>();
2167
2189
  for (const snapshot of partial?.renderables ?? []) {
2168
2190
  publishedSkinKeys.add(`${snapshot.worldId}:${snapshot.entityKey}`);
@@ -2198,7 +2220,7 @@ export class PersistentRenderScene {
2198
2220
  return (
2199
2221
  worldId !== undefined &&
2200
2222
  !changedEntitiesByWorld[worldId]?.has(report.entity) &&
2201
- currentRenderableEntitiesByWorld?.[worldId]?.has(report.entity) !== false
2223
+ isRenderableMember(report.world, report.entity)
2202
2224
  );
2203
2225
  }),
2204
2226
  ...(partial?.hiddenEntityReports ?? []),
@@ -2227,8 +2249,9 @@ export class PersistentRenderScene {
2227
2249
  continue;
2228
2250
  }
2229
2251
  if (slot === undefined) {
2230
- const current = currentRenderableEntitiesByWorld?.[worldId];
2231
- if (current !== undefined && !current.has(entityKey)) pending?.delete(entityKey);
2252
+ const world = worlds[worldId];
2253
+ if (world !== undefined && !isRenderableMember(world, entityKey))
2254
+ pending?.delete(entityKey);
2232
2255
  continue;
2233
2256
  }
2234
2257
  // The producer did not publish this requested identity. Keep the
@@ -2279,9 +2302,43 @@ export class PersistentRenderScene {
2279
2302
  this.noChangeFrames += 1;
2280
2303
  this.syncGpuOwner(entry.token, entry.projection);
2281
2304
  }
2282
- const result = this.deriveFramePlan(entry, buildCandidateFrame);
2305
+ const result = this.deriveFramePlan(entry, resourceFrame);
2306
+ for (let worldId = 0; worldId < sourceBatches.length; worldId++) {
2307
+ const batch = sourceBatches[worldId];
2308
+ const source = entry.sourceStates[worldId];
2309
+ if (batch === undefined || source === undefined) continue;
2310
+ batch.accept();
2311
+ if (
2312
+ !batch.membershipChanged &&
2313
+ !batch.changedComponents.includes(RuntimeMaterialValue) &&
2314
+ !batch.changedComponents.includes(RuntimeMeshVertices)
2315
+ )
2316
+ continue;
2317
+ for (const index of batch.indices) {
2318
+ const entity = source.projection.entity(index);
2319
+ if (entity === undefined) {
2320
+ source.entities.delete(index);
2321
+ source.contentHandles.delete(index);
2322
+ } else {
2323
+ source.entities.set(index, entity);
2324
+ const handles: number[] = [];
2325
+ const world = worlds[worldId];
2326
+ if (world !== undefined) {
2327
+ const material = world.hasComponent(entity, RuntimeMaterialValue)
2328
+ ? world.get(entity, RuntimeMaterialValue)
2329
+ : undefined;
2330
+ const mesh = world.hasComponent(entity, RuntimeMeshVertices)
2331
+ ? world.get(entity, RuntimeMeshVertices)
2332
+ : undefined;
2333
+ if (material?.ok) handles.push(Number(material.value.asset));
2334
+ if (mesh?.ok) handles.push(Number(mesh.value.asset));
2335
+ }
2336
+ if (handles.length === 0) source.contentHandles.delete(index);
2337
+ else source.contentHandles.set(index, handles);
2338
+ }
2339
+ }
2340
+ }
2283
2341
  entry.catalogEpoch = catalogEpoch;
2284
- entry.instanceEpoch = instanceEpoch;
2285
2342
  for (let worldId = 0; worldId < nextReadVersions.length; worldId += 1) {
2286
2343
  const version = nextReadVersions[worldId];
2287
2344
  if (version !== undefined) entry.readVersions[worldId] = version;
@@ -2328,11 +2385,13 @@ export class PersistentRenderScene {
2328
2385
  this.stableSlotByEntity.clear();
2329
2386
  }
2330
2387
  if (this.gpuOwner !== world && this.gpuOwner !== detachedComposition?.token) return;
2331
- this.retireGpuScene(this.gpuScene);
2332
- this.gpuScene = undefined;
2388
+ // The GPU Scene is renderer-owned. A frame graph recorded for the current
2389
+ // device may retain its table bindings after a World detaches, while the
2390
+ // next attached composition can rebuild the same tables in place. Keep
2391
+ // that single scene resident until device replacement or renderer
2392
+ // disposal; retiring it here lets a reused graph submit destroyed buffers.
2333
2393
  this.gpuOwner = undefined;
2334
- this.gpuDevice = undefined;
2335
- this.gpuStatus = 'inactive';
2394
+ this.gpuStatus = this.gpuScene === undefined ? 'inactive' : 'resident';
2336
2395
  }
2337
2396
 
2338
2397
  /** Drop only device-owned tables; the CPU projection remains the recovery authority. */
@@ -2398,6 +2457,9 @@ export class PersistentRenderScene {
2398
2457
  scene,
2399
2458
  plan: composition.topology.plan(),
2400
2459
  slots,
2460
+ worldKeys: composition.worldKeys,
2461
+ worldIdentities: composition.worlds.map((world) => world.identity),
2462
+ probeBlend: composition.probeProjection.recordBufferProjection(),
2401
2463
  });
2402
2464
  let published = false;
2403
2465
  let released = false;
@@ -2569,11 +2631,13 @@ export class PersistentRenderScene {
2569
2631
  plan: entry.topology.plan(),
2570
2632
  slots: entry.projection.slotsSnapshot(),
2571
2633
  worldKeys: entry.worldKeys,
2634
+ worldIdentities: entry.worlds.map((world) => world.identity),
2572
2635
  structureMetrics: {
2573
2636
  worldEntitiesScanned: this.worldEntitiesScanned,
2574
2637
  sceneTableUploadBytes: this.sceneTableUploadBytes,
2575
2638
  paletteUploadBytes: 0,
2576
2639
  },
2640
+ probeBlend: entry.probeProjection.recordBufferProjection(),
2577
2641
  };
2578
2642
  }
2579
2643
 
@@ -2647,7 +2711,12 @@ export class PersistentRenderScene {
2647
2711
  preserveVisibilityOnReorder = false,
2648
2712
  ): ExtractedFrame {
2649
2713
  const previousEntry = this.composition;
2650
- const candidateFrame = buildCandidateFrame('full');
2714
+ const sourceStates = worlds.map((world) => createRenderSourceState(world));
2715
+ const candidateFrame = buildCandidateFrame({
2716
+ kind: 'partial',
2717
+ entitiesByWorld: sourceStates.map((source) => new Set(source.entities.values())),
2718
+ });
2719
+ for (const source of sourceStates) source.batch.validate();
2651
2720
  this.temporalCapture = undefined;
2652
2721
  this.worldEntitiesScanned = candidateFrame.renderables.length;
2653
2722
  const worldKeys = this.visibilityWorldKeysFor(worlds);
@@ -2675,38 +2744,18 @@ export class PersistentRenderScene {
2675
2744
  this.invalidateActiveEntityRevision();
2676
2745
  }
2677
2746
  }
2678
- // Slot allocation is renderer-owned identity. Sort by stable World identity
2679
- // before allocating so a worlds[] reorder does not silently change a
2680
- // primitive's slot/generation. Dispatch is remapped below to the sorted
2681
- // renderable projection and remains frame-order independent.
2682
- const sorted = candidateFrame.renderables
2683
- .map((snapshot, sourceIndex) => ({ snapshot, sourceIndex }))
2684
- .sort((left, right) => {
2685
- const leftWorld = worldKeys[left.snapshot.worldId] ?? left.snapshot.worldId;
2686
- const rightWorld = worldKeys[right.snapshot.worldId] ?? right.snapshot.worldId;
2687
- return leftWorld - rightWorld || left.snapshot.entityKey - right.snapshot.entityKey;
2688
- });
2689
- const sortedRenderables = sorted.map(({ snapshot }) => snapshot);
2690
- const renderableIndex = new Int32Array(candidateFrame.renderables.length);
2691
- for (let index = 0; index < sorted.length; index += 1) {
2692
- const sourceIndex = sorted[index]?.sourceIndex;
2693
- if (sourceIndex !== undefined) renderableIndex[sourceIndex] = index;
2694
- }
2695
- const sortedDispatch = candidateFrame.dispatch.flatMap((entry) => {
2696
- const index = renderableIndex[entry.renderableIndex];
2697
- return index === undefined ? [] : [{ ...entry, renderableIndex: index }];
2698
- });
2699
- const stableCandidateFrame = {
2700
- ...candidateFrame,
2701
- renderables: sortedRenderables,
2702
- dispatch: sortedDispatch,
2703
- };
2747
+ const stableCandidateFrame = candidateFrame;
2748
+ const renderables = candidateFrame.renderables;
2704
2749
  const projection = previousEntry?.projection ?? new RenderScene();
2705
2750
  if (previousEntry !== undefined) {
2706
- projection.remapWorlds(previousEntry.worlds.map((world) => worlds.indexOf(world)));
2751
+ projection.remapWorlds(
2752
+ previousEntry.worlds.map((world) =>
2753
+ (worlds as readonly RenderResourceScope[]).indexOf(world),
2754
+ ),
2755
+ );
2707
2756
  }
2708
2757
  const desired = new Set(
2709
- sortedRenderables.map((snapshot) => identityKey(snapshot.worldId, snapshot.entityKey)),
2758
+ renderables.map((snapshot) => identityKey(snapshot.worldId, snapshot.entityKey)),
2710
2759
  );
2711
2760
  const operations: RenderSceneOperation[] = [];
2712
2761
  for (const slot of projection.slotsSnapshot()) {
@@ -2714,7 +2763,7 @@ export class PersistentRenderScene {
2714
2763
  operations.push({ kind: 'remove', worldId: slot.worldId, entityKey: slot.entityKey });
2715
2764
  }
2716
2765
  }
2717
- for (const snapshot of sortedRenderables) {
2766
+ for (const snapshot of renderables) {
2718
2767
  operations.push({
2719
2768
  kind: 'update',
2720
2769
  worldId: snapshot.worldId,
@@ -2723,6 +2772,22 @@ export class PersistentRenderScene {
2723
2772
  });
2724
2773
  }
2725
2774
  const delta = projection.apply(operations);
2775
+ for (const snapshot of renderables) {
2776
+ const world = worlds[snapshot.worldId];
2777
+ if (world !== undefined && snapshot.instances !== undefined)
2778
+ this.options.instanceCollections?.accept(world, snapshot.entityKey, snapshot.instances);
2779
+ }
2780
+ this.options.instanceCollections?.retain(
2781
+ new Set(
2782
+ projection
2783
+ .slotsSnapshot()
2784
+ .flatMap((slot) =>
2785
+ slot.snapshot.instances?.collectionId === undefined
2786
+ ? []
2787
+ : [slot.snapshot.instances.collectionId],
2788
+ ),
2789
+ ),
2790
+ );
2726
2791
  this.stableSlotByEntity.clear();
2727
2792
  for (const slot of projection.slotsSnapshot()) {
2728
2793
  const stableWorld = worldKeys[slot.worldId] ?? slot.worldId;
@@ -2757,10 +2822,7 @@ export class PersistentRenderScene {
2757
2822
  leases === undefined || leases.length !== worlds.length
2758
2823
  ? []
2759
2824
  : worlds.map(createGlobalTransformChangeQuery),
2760
- renderChangeQueries:
2761
- leases === undefined || leases.length !== worlds.length
2762
- ? []
2763
- : worlds.map(createRenderChangeQueries),
2825
+ sourceStates,
2764
2826
  dispatchBySlot: dispatchEntriesBySlot(stableCandidateFrame, projection),
2765
2827
  dispatchRevision: 0,
2766
2828
  dispatchCache: undefined,
@@ -2768,8 +2830,8 @@ export class PersistentRenderScene {
2768
2830
  pendingRenderableEntitiesByWorld: worlds.map(() => new Set<number>()),
2769
2831
  unavailableSlots: new Set<number>(),
2770
2832
  catalogEpoch,
2771
- instanceEpoch: this.options.instanceCollections?.epoch ?? 0,
2772
2833
  probeProjection: new ProbeBlendSceneProjection(),
2834
+ probeObjects: undefined,
2773
2835
  temporalDemanded: frameDemandsTemporal(candidateFrame),
2774
2836
  gpuDrivenSceneRequired: true,
2775
2837
  };
@@ -2804,6 +2866,7 @@ export class PersistentRenderScene {
2804
2866
  ? projection.cullingWorldBounds(snapshot)
2805
2867
  : projection.cullingWorldBoundsAt(slot);
2806
2868
  });
2869
+ for (const source of sourceStates) source.batch.accept();
2807
2870
  this.frameEpoch += 1;
2808
2871
  this.visibilityFacetFrame = undefined;
2809
2872
  this.visibilityProjectionCache = undefined;
@@ -2826,9 +2889,8 @@ export class PersistentRenderScene {
2826
2889
  */
2827
2890
  private deriveFramePlan(
2828
2891
  entry: PersistentCompositionEntry,
2829
- buildCandidateFrame: (request: PersistentRenderCandidateRequest) => ExtractedFrame,
2892
+ resourceFrame: ExtractedFrame,
2830
2893
  ): ExtractedFrame {
2831
- const resourceFrame = buildCandidateFrame('none');
2832
2894
  const cachedDispatch = entry.dispatchCache;
2833
2895
  const dispatch =
2834
2896
  cachedDispatch?.revision === entry.dispatchRevision
@@ -2840,7 +2902,10 @@ export class PersistentRenderScene {
2840
2902
  const candidateFrame: ExtractedFrame = {
2841
2903
  ...resourceFrame,
2842
2904
  hiddenEntityReports: entry.hiddenEntityReports,
2843
- visibilityStats: { explicitlyHidden: entry.hiddenEntityReports.length },
2905
+ visibilityStats:
2906
+ entry.sourceStates.length === 0
2907
+ ? resourceFrame.visibilityStats
2908
+ : { explicitlyHidden: entry.hiddenEntityReports.length },
2844
2909
  dispatch,
2845
2910
  };
2846
2911
  entry.temporalDemanded = frameDemandsTemporal(candidateFrame);
@@ -2938,27 +3003,41 @@ export class PersistentRenderScene {
2938
3003
  ] as [number, number, number],
2939
3004
  };
2940
3005
  const probes = frame.lightProbes ?? [];
2941
- // Sky is evaluated by the global environment path. With no local light
2942
- // probes there can be no per-object ProbeBlendRecord, regardless of how
2943
- // many dynamic objects moved this frame. Preserve the projection call so
2944
- // a transition from probes to no probes still clears retained records.
2945
- if (probes.length === 0) {
2946
- entry.probeProjection.apply({ objects: [], probes, sky });
2947
- return;
3006
+ const revision = entry.projection.revisionValue();
3007
+ if (entry.probeObjects?.revision !== revision) {
3008
+ const slots = entry.projection.slotsSnapshot();
3009
+ const value = Object.freeze(
3010
+ slots.map((slot) =>
3011
+ Object.freeze({
3012
+ objectKey: slot.slot,
3013
+ generation: slot.generation,
3014
+ position: [
3015
+ slot.snapshot.transform.world[12] ?? 0,
3016
+ slot.snapshot.transform.world[13] ?? 0,
3017
+ slot.snapshot.transform.world[14] ?? 0,
3018
+ ] as const,
3019
+ }),
3020
+ ),
3021
+ );
3022
+ entry.probeObjects = {
3023
+ revision,
3024
+ value,
3025
+ surfaceValue: Object.freeze(
3026
+ value.filter((_object, index) =>
3027
+ slots[index]?.snapshot.materials.some(
3028
+ (material) => material.surfaceModel === 'single-layer-medium',
3029
+ ),
3030
+ ),
3031
+ ),
3032
+ };
2948
3033
  }
2949
- const objects: ProbeSceneObjectInput[] = entry.projection.slotsSnapshot().map((slot) => ({
2950
- objectKey: slot.slot,
2951
- generation: slot.generation,
2952
- position: [
2953
- slot.snapshot.transform.world[12] ?? 0,
2954
- slot.snapshot.transform.world[13] ?? 0,
2955
- slot.snapshot.transform.world[14] ?? 0,
2956
- ],
2957
- }));
3034
+ const surfaceOnly = probes.length === 0;
2958
3035
  entry.probeProjection.apply({
2959
- objects,
3036
+ objects: surfaceOnly ? entry.probeObjects.surfaceValue : entry.probeObjects.value,
2960
3037
  probes,
2961
3038
  sky,
3039
+ worldRevision: revision,
3040
+ ...(surfaceOnly ? { retainSkyResidualRecords: true } : {}),
2962
3041
  });
2963
3042
  }
2964
3043