@forgeax/engine-render 0.1.33 → 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 (518) hide show
  1. package/README.md +1010 -124
  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/dynamic-geometry-host.d.ts +4 -1
  11. package/dist/assembly/dynamic-geometry-host.d.ts.map +1 -1
  12. package/dist/assembly/dynamic-geometry-runtime.d.ts +1 -1
  13. package/dist/assembly/dynamic-geometry-runtime.d.ts.map +1 -1
  14. package/dist/assembly/factory.d.ts +1 -1
  15. package/dist/assembly/factory.d.ts.map +1 -1
  16. package/dist/assembly/host-contract.d.ts +3 -3
  17. package/dist/assembly/host-contract.d.ts.map +1 -1
  18. package/dist/assembly/material/assembly.d.ts.map +1 -1
  19. package/dist/assembly/material/pipeline-helpers.d.ts +5 -0
  20. package/dist/assembly/material/pipeline-helpers.d.ts.map +1 -1
  21. package/dist/assembly/material-shader-policy.d.ts +16 -2
  22. package/dist/assembly/material-shader-policy.d.ts.map +1 -1
  23. package/dist/assembly/material-shader-prewarm.d.ts.map +1 -1
  24. package/dist/assembly/recovery/device-loss-fanout.d.ts.map +1 -1
  25. package/dist/assembly/recovery/renderer-recover.d.ts +2 -0
  26. package/dist/assembly/recovery/renderer-recover.d.ts.map +1 -1
  27. package/dist/assembly/render-feature-post-processes.d.ts +6 -0
  28. package/dist/assembly/render-feature-post-processes.d.ts.map +1 -0
  29. package/dist/assembly/renderer-facade.d.ts.map +1 -1
  30. package/dist/assembly/renderer-feature-inspection.d.ts +1 -1
  31. package/dist/assembly/renderer-feature-inspection.d.ts.map +1 -1
  32. package/dist/assembly/renderer-frame-transaction.d.ts +12 -0
  33. package/dist/assembly/renderer-frame-transaction.d.ts.map +1 -1
  34. package/dist/assembly/renderer-inspection.d.ts +11 -0
  35. package/dist/assembly/renderer-inspection.d.ts.map +1 -1
  36. package/dist/assembly/webgpu-pbr-ready.d.ts +1 -0
  37. package/dist/assembly/webgpu-pbr-ready.d.ts.map +1 -1
  38. package/dist/assembly/webgpu-ready-per-pass.d.ts +39 -0
  39. package/dist/assembly/webgpu-ready-per-pass.d.ts.map +1 -0
  40. package/dist/assembly/webgpu-ready.d.ts +1 -1
  41. package/dist/assembly/webgpu-ready.d.ts.map +1 -1
  42. package/dist/assembly/webgpu-renderer-bootstrap.d.ts.map +1 -1
  43. package/dist/assembly/webgpu-renderer-observation-owner.d.ts +35 -0
  44. package/dist/assembly/webgpu-renderer-observation-owner.d.ts.map +1 -0
  45. package/dist/assembly/webgpu-renderer.d.ts +1 -1
  46. package/dist/assembly/webgpu-renderer.d.ts.map +1 -1
  47. package/dist/authoring.mjs +1 -1
  48. package/dist/barrel-distortion.d.ts +47 -0
  49. package/dist/barrel-distortion.d.ts.map +1 -0
  50. package/dist/{chunk-PA5DRJNI.mjs → chunk-ELJFJ2BU.mjs} +2 -2
  51. package/dist/chunk-ELJFJ2BU.mjs.map +1 -0
  52. package/dist/{chunk-ZDQWGQW7.mjs → chunk-HEB564WF.mjs} +4 -4
  53. package/dist/{chunk-ZDQWGQW7.mjs.map → chunk-HEB564WF.mjs.map} +1 -1
  54. package/dist/{chunk-VOZWDM3L.mjs → chunk-MKDIT2EM.mjs} +1307 -188
  55. package/dist/chunk-MKDIT2EM.mjs.map +1 -0
  56. package/dist/{chunk-LEAXOMYJ.mjs → chunk-OCDLEI6I.mjs} +3 -3
  57. package/dist/{chunk-LEAXOMYJ.mjs.map → chunk-OCDLEI6I.mjs.map} +1 -1
  58. package/dist/chunk-ODZWR6JQ.mjs +4927 -0
  59. package/dist/chunk-ODZWR6JQ.mjs.map +1 -0
  60. package/dist/{chunk-3RTCJTH4.mjs → chunk-SHFUFJ5N.mjs} +184 -13
  61. package/dist/chunk-SHFUFJ5N.mjs.map +1 -0
  62. package/dist/{chunk-OO3DRYK7.mjs → chunk-UI5SFODX.mjs} +176 -96
  63. package/dist/chunk-UI5SFODX.mjs.map +1 -0
  64. package/dist/{chunk-PLAXOOX5.mjs → chunk-UINHLCZA.mjs} +561 -79
  65. package/dist/chunk-UINHLCZA.mjs.map +1 -0
  66. package/dist/{chunk-NB2S7ZRX.mjs → chunk-WEX45HKH.mjs} +127 -386
  67. package/dist/chunk-WEX45HKH.mjs.map +1 -0
  68. package/dist/cloud/density.d.ts +55 -0
  69. package/dist/cloud/density.d.ts.map +1 -0
  70. package/dist/cloud/extract.d.ts +25 -0
  71. package/dist/cloud/extract.d.ts.map +1 -0
  72. package/dist/cloud/feature.d.ts +57 -0
  73. package/dist/cloud/feature.d.ts.map +1 -0
  74. package/dist/cloud/frame-context.d.ts +8 -0
  75. package/dist/cloud/frame-context.d.ts.map +1 -0
  76. package/dist/cloud/inspection.d.ts +59 -0
  77. package/dist/cloud/inspection.d.ts.map +1 -0
  78. package/dist/cloud/optics.d.ts +56 -0
  79. package/dist/cloud/optics.d.ts.map +1 -0
  80. package/dist/cloud/parameters.d.ts +73 -0
  81. package/dist/cloud/parameters.d.ts.map +1 -0
  82. package/dist/cloud/resources.d.ts +60 -0
  83. package/dist/cloud/resources.d.ts.map +1 -0
  84. package/dist/cloud/shadow.d.ts +42 -0
  85. package/dist/cloud/shadow.d.ts.map +1 -0
  86. package/dist/cloud/temporal.d.ts +74 -0
  87. package/dist/cloud/temporal.d.ts.map +1 -0
  88. package/dist/components/barrel-distortion.d.ts +13 -0
  89. package/dist/components/barrel-distortion.d.ts.map +1 -0
  90. package/dist/components/cloud-layer.d.ts +29 -0
  91. package/dist/components/cloud-layer.d.ts.map +1 -0
  92. package/dist/components/index.d.ts +2 -0
  93. package/dist/components/index.d.ts.map +1 -1
  94. package/dist/components/instances.d.ts +20 -6
  95. package/dist/components/instances.d.ts.map +1 -1
  96. package/dist/construct-renderer.d.ts +1 -1
  97. package/dist/construct-renderer.d.ts.map +1 -1
  98. package/dist/construct-renderer.mjs +6759 -2875
  99. package/dist/construct-renderer.mjs.map +1 -1
  100. package/dist/dynamic-geometry.d.ts +5 -1
  101. package/dist/dynamic-geometry.d.ts.map +1 -1
  102. package/dist/errors/cloud.d.ts +59 -0
  103. package/dist/errors/cloud.d.ts.map +1 -0
  104. package/dist/errors/render.d.ts +18 -4
  105. package/dist/errors/render.d.ts.map +1 -1
  106. package/dist/examples/single-layer-medium-surface.d.ts +47 -0
  107. package/dist/examples/single-layer-medium-surface.d.ts.map +1 -0
  108. package/dist/extract/camera.d.ts.map +1 -1
  109. package/dist/extract/contracts.d.ts +5 -1
  110. package/dist/extract/contracts.d.ts.map +1 -1
  111. package/dist/features/barrel-distortion.d.ts +29 -0
  112. package/dist/features/barrel-distortion.d.ts.map +1 -0
  113. package/dist/features/error-reporting.d.ts +6 -2
  114. package/dist/features/error-reporting.d.ts.map +1 -1
  115. package/dist/features/host.d.ts +10 -0
  116. package/dist/features/host.d.ts.map +1 -1
  117. package/dist/features/plan.d.ts +8 -0
  118. package/dist/features/plan.d.ts.map +1 -1
  119. package/dist/features/render-graph-contribution.d.ts +5 -0
  120. package/dist/features/render-graph-contribution.d.ts.map +1 -1
  121. package/dist/features/render-graph-raster.d.ts +1 -0
  122. package/dist/features/render-graph-raster.d.ts.map +1 -1
  123. package/dist/features/targets.d.ts +2 -0
  124. package/dist/features/targets.d.ts.map +1 -1
  125. package/dist/features/types.d.ts +44 -0
  126. package/dist/features/types.d.ts.map +1 -1
  127. package/dist/fullscreen-post-process-pass.d.ts +15 -7
  128. package/dist/fullscreen-post-process-pass.d.ts.map +1 -1
  129. package/dist/gpu-dirty-ranges.d.ts +11 -0
  130. package/dist/gpu-dirty-ranges.d.ts.map +1 -0
  131. package/dist/gpu-driven/batch-topology.d.ts +17 -0
  132. package/dist/gpu-driven/batch-topology.d.ts.map +1 -1
  133. package/dist/gpu-driven/material-artifacts.d.ts +6 -0
  134. package/dist/gpu-driven/material-artifacts.d.ts.map +1 -1
  135. package/dist/gpu-driven/prepared-draw.d.ts.map +1 -1
  136. package/dist/gpu-driven/production-raster-material.d.ts +178 -0
  137. package/dist/gpu-driven/production-raster-material.d.ts.map +1 -0
  138. package/dist/gpu-driven/production-raster.d.ts +47 -3
  139. package/dist/gpu-driven/production-raster.d.ts.map +1 -1
  140. package/dist/gpu-driven/resource-allocation.d.ts +31 -0
  141. package/dist/gpu-driven/resource-allocation.d.ts.map +1 -0
  142. package/dist/gpu-driven/shadow-ownership.d.ts +14 -0
  143. package/dist/gpu-driven/shadow-ownership.d.ts.map +1 -0
  144. package/dist/gpu-driven/view-gpu.d.ts +26 -2
  145. package/dist/gpu-driven/view-gpu.d.ts.map +1 -1
  146. package/dist/gpu-scene.d.ts +5 -0
  147. package/dist/gpu-scene.d.ts.map +1 -1
  148. package/dist/hdrp-buffers.d.ts +2 -2
  149. package/dist/hdrp-buffers.d.ts.map +1 -1
  150. package/dist/ibl/skylight-bind-group.d.ts +10 -1
  151. package/dist/ibl/skylight-bind-group.d.ts.map +1 -1
  152. package/dist/index.d.ts +27 -7
  153. package/dist/index.d.ts.map +1 -1
  154. package/dist/index.mjs +258 -8
  155. package/dist/index.mjs.map +1 -1
  156. package/dist/inspection-types.d.ts +135 -1
  157. package/dist/inspection-types.d.ts.map +1 -1
  158. package/dist/instance-buffer-cache.d.ts +4 -4
  159. package/dist/instance-buffer-cache.d.ts.map +1 -1
  160. package/dist/instances.d.ts +48 -100
  161. package/dist/instances.d.ts.map +1 -1
  162. package/dist/internal.mjs +7 -7
  163. package/dist/pbr-pipeline.d.ts +19 -1
  164. package/dist/pbr-pipeline.d.ts.map +1 -1
  165. package/dist/pipeline/single-layer-medium-passes.d.ts +73 -0
  166. package/dist/pipeline/single-layer-medium-passes.d.ts.map +1 -0
  167. package/dist/pipeline/standard-forward-lane.d.ts.map +1 -1
  168. package/dist/pipeline/standard-output/graph.d.ts.map +1 -1
  169. package/dist/pipeline/standard-output/types.d.ts +4 -2
  170. package/dist/pipeline/standard-output/types.d.ts.map +1 -1
  171. package/dist/pipeline/standard-pipeline.d.ts.map +1 -1
  172. package/dist/pipeline/standard-post.d.ts.map +1 -1
  173. package/dist/pipeline-builder.d.ts +1 -1
  174. package/dist/pipeline-builder.d.ts.map +1 -1
  175. package/dist/plugin.d.ts.map +1 -1
  176. package/dist/prepare/prepared-graphics-resolver.d.ts +2 -0
  177. package/dist/prepare/prepared-graphics-resolver.d.ts.map +1 -1
  178. package/dist/record/barrel-distortion-frame.d.ts +15 -0
  179. package/dist/record/barrel-distortion-frame.d.ts.map +1 -0
  180. package/dist/record/dynamic-geometry-consumption.d.ts +10 -2
  181. package/dist/record/dynamic-geometry-consumption.d.ts.map +1 -1
  182. package/dist/record/fold-buckets.d.ts +95 -0
  183. package/dist/record/fold-buckets.d.ts.map +1 -0
  184. package/dist/record/fold-instance-buffer.d.ts +6 -0
  185. package/dist/record/fold-instance-buffer.d.ts.map +1 -0
  186. package/dist/record/frame-lighting.d.ts +1 -0
  187. package/dist/record/frame-lighting.d.ts.map +1 -1
  188. package/dist/record/frame-snapshot.d.ts +37 -3
  189. package/dist/record/frame-snapshot.d.ts.map +1 -1
  190. package/dist/record/frame.d.ts +10 -61
  191. package/dist/record/frame.d.ts.map +1 -1
  192. package/dist/record/gpu-draw-receipts.d.ts +11 -0
  193. package/dist/record/gpu-draw-receipts.d.ts.map +1 -0
  194. package/dist/record/gpu-timing.d.ts +8 -0
  195. package/dist/record/gpu-timing.d.ts.map +1 -1
  196. package/dist/record/main-pass-geometry.d.ts +21 -5
  197. package/dist/record/main-pass-geometry.d.ts.map +1 -1
  198. package/dist/record/main-pass-material.d.ts +7 -1
  199. package/dist/record/main-pass-material.d.ts.map +1 -1
  200. package/dist/record/main-pass-sprite-draws.d.ts.map +1 -1
  201. package/dist/record/main-pass.d.ts +13 -2
  202. package/dist/record/main-pass.d.ts.map +1 -1
  203. package/dist/record/motion-blur-receipt.d.ts +11 -0
  204. package/dist/record/motion-blur-receipt.d.ts.map +1 -0
  205. package/dist/record/probe-blend-buffer.d.ts +16 -0
  206. package/dist/record/probe-blend-buffer.d.ts.map +1 -0
  207. package/dist/record/recovery-pipeline.d.ts.map +1 -1
  208. package/dist/record/render-context.d.ts +22 -3
  209. package/dist/record/render-context.d.ts.map +1 -1
  210. package/dist/record/shadow-pass.d.ts +2 -0
  211. package/dist/record/shadow-pass.d.ts.map +1 -1
  212. package/dist/record/skybox-post-pass.d.ts +1 -1
  213. package/dist/record/skybox-post-pass.d.ts.map +1 -1
  214. package/dist/record/typed-frame-graph.d.ts +22 -2
  215. package/dist/record/typed-frame-graph.d.ts.map +1 -1
  216. package/dist/record/view-ubo.d.ts +11 -3
  217. package/dist/record/view-ubo.d.ts.map +1 -1
  218. package/dist/record/volume-params.d.ts +9 -0
  219. package/dist/record/volume-params.d.ts.map +1 -0
  220. package/dist/recovery/render-system-candidate.d.ts +10 -0
  221. package/dist/recovery/render-system-candidate.d.ts.map +1 -1
  222. package/dist/reflection/record-owner.d.ts.map +1 -1
  223. package/dist/render-contract.d.ts +42 -4
  224. package/dist/render-contract.d.ts.map +1 -1
  225. package/dist/render-graph-primitives.d.ts.map +1 -1
  226. package/dist/render-pipeline.d.ts +103 -2
  227. package/dist/render-pipeline.d.ts.map +1 -1
  228. package/dist/render-system-extract-tail.d.ts +3 -2
  229. package/dist/render-system-extract-tail.d.ts.map +1 -1
  230. package/dist/render-system-extract.d.ts +16 -9
  231. package/dist/render-system-extract.d.ts.map +1 -1
  232. package/dist/render-system-presentation.d.ts +23 -0
  233. package/dist/render-system-presentation.d.ts.map +1 -0
  234. package/dist/render-system-producer-roots.d.ts +10 -0
  235. package/dist/render-system-producer-roots.d.ts.map +1 -0
  236. package/dist/render-system-residency.d.ts +12 -0
  237. package/dist/render-system-residency.d.ts.map +1 -0
  238. package/dist/render-system.d.ts +19 -14
  239. package/dist/render-system.d.ts.map +1 -1
  240. package/dist/scene/probe-blend.d.ts +27 -0
  241. package/dist/scene/probe-blend.d.ts.map +1 -1
  242. package/dist/scene/render-scene.d.ts +12 -5
  243. package/dist/scene/render-scene.d.ts.map +1 -1
  244. package/dist/ssao-buffers.d.ts +9 -0
  245. package/dist/ssao-buffers.d.ts.map +1 -1
  246. package/dist/surface/dynamic-input.d.ts +133 -0
  247. package/dist/surface/dynamic-input.d.ts.map +1 -0
  248. package/dist/surface/gpu-driven.d.ts +41 -0
  249. package/dist/surface/gpu-driven.d.ts.map +1 -0
  250. package/dist/surface/submission-observation.d.ts +136 -0
  251. package/dist/surface/submission-observation.d.ts.map +1 -0
  252. package/dist/systems/skin-palette-allocator.d.ts.map +1 -1
  253. package/dist/temporal/gpu.d.ts +13 -1
  254. package/dist/temporal/gpu.d.ts.map +1 -1
  255. package/dist/temporal/index.mjs +5 -5
  256. package/dist/temporal/inspection.d.ts.map +1 -1
  257. package/dist/temporal/standard-scene-data.d.ts.map +1 -1
  258. package/dist/temporal/target-coverage-attachment.d.ts.map +1 -1
  259. package/dist/transmission/single-layer-medium.d.ts +89 -0
  260. package/dist/transmission/single-layer-medium.d.ts.map +1 -0
  261. package/dist/typed-render-graph-primitives.d.ts +14 -2
  262. package/dist/typed-render-graph-primitives.d.ts.map +1 -1
  263. package/dist/volume/component.d.ts +9 -4
  264. package/dist/volume/component.d.ts.map +1 -1
  265. package/dist/volume/extract.d.ts +1 -1
  266. package/dist/volume/extract.d.ts.map +1 -1
  267. package/dist/volume/inspection.d.ts +2 -0
  268. package/dist/volume/inspection.d.ts.map +1 -1
  269. package/dist/volume/passes.d.ts +4 -1
  270. package/dist/volume/passes.d.ts.map +1 -1
  271. package/dist/volume/resources.d.ts.map +1 -1
  272. package/package.json +22 -21
  273. package/src/__tests__/atmosphere-daylight.dawn.test.ts +72 -0
  274. package/src/__tests__/barrel-distortion-lkg.integration.test.ts +353 -0
  275. package/src/__tests__/barrel-distortion.dawn.test.ts +41 -0
  276. package/src/__tests__/barrel-distortion.unit.test.ts +364 -0
  277. package/src/__tests__/construct-renderer-identity.unit.test.ts +49 -0
  278. package/src/__tests__/detached-recovery-graph.unit.test.ts +277 -0
  279. package/src/__tests__/device-loss-fanout.unit.test.ts +36 -1
  280. package/src/__tests__/docs-gate-green.unit.test.ts +112 -1
  281. package/src/__tests__/factory-contract.integration.test.ts +295 -45
  282. package/src/__tests__/feature-depth-input.dawn.test.ts +129 -2
  283. package/src/__tests__/fullscreen-feature-plan.unit.test.ts +14 -3
  284. package/src/__tests__/gpu-dirty-ranges.unit.test.ts +22 -0
  285. package/src/__tests__/gpu-driven-baseline.characterization.test.ts +16 -3
  286. package/src/__tests__/gpu-driven-batch-topology-pbr.unit.test.ts +12 -0
  287. package/src/__tests__/gpu-driven-material-artifacts.unit.test.ts +143 -0
  288. package/src/__tests__/gpu-driven-pbr.dawn.test.ts +2 -2
  289. package/src/__tests__/gpu-driven-production.integration.test.ts +482 -3
  290. package/src/__tests__/gpu-driven-shadow-views.dawn.test.ts +6 -5
  291. package/src/__tests__/gpu-driven-view-gpu-evidence.ts +94 -6
  292. package/src/__tests__/gpu-driven-view-graph.integration.test.ts +18 -2
  293. package/src/__tests__/gpu-driven-view.browser.test.ts +5 -8
  294. package/src/__tests__/gpu-driven-view.dawn.test.ts +6 -2
  295. package/src/__tests__/gpu-driven-view.unit.test.ts +1 -1
  296. package/src/__tests__/gpu-pass-timing-lifecycle.integration.test.ts +1 -0
  297. package/src/__tests__/gpu-pass-timing.browser.test.ts +6 -4
  298. package/src/__tests__/gpu-pass-timing.rhi-null.unit.test.ts +1 -0
  299. package/src/__tests__/gpu-scene.unit.test.ts +119 -9
  300. package/src/__tests__/instance-allocation-failure.unit.test.ts +57 -0
  301. package/src/__tests__/instances-culling.integration.test.ts +10 -36
  302. package/src/__tests__/instances-store.unit.test.ts +97 -305
  303. package/src/__tests__/instances-world-ownership.integration.test.ts +62 -0
  304. package/src/__tests__/main-pass-material-medium-selection.unit.test.ts +146 -0
  305. package/src/__tests__/material-shader-prewarm.unit.test.ts +59 -0
  306. package/src/__tests__/material-snapshot-mutation.integration.test.ts +39 -1
  307. package/src/__tests__/morph-culling-reentry.dawn.test.ts +5 -5
  308. package/src/__tests__/morph-visual-evidence.integration.test.ts +8 -7
  309. package/src/__tests__/motion-blur-compute.dawn.test.ts +13 -7
  310. package/src/__tests__/motion-blur.pixel.integration.test.ts +2 -2
  311. package/src/__tests__/nineslice-handle-surface.unit.test.ts +4 -4
  312. package/src/__tests__/pbr-view-layout.unit.test.ts +9 -0
  313. package/src/__tests__/physical-clearcoat.integration.test.ts +58 -0
  314. package/src/__tests__/prepared-graphics-capability.unit.test.ts +66 -2
  315. package/src/__tests__/probe-bindings.unit.test.ts +582 -1
  316. package/src/__tests__/probe-scene-dirty.integration.test.ts +28 -1
  317. package/src/__tests__/receipt-observation-retirement.unit.test.ts +65 -0
  318. package/src/__tests__/recovery-candidate-prepare.contract.test.ts +69 -0
  319. package/src/__tests__/render-error-code-owner.test-d.ts +7 -0
  320. package/src/__tests__/render-feature-diagnostics.unit.test.ts +15 -2
  321. package/src/__tests__/render-feature-error-reporting.unit.test.ts +21 -1
  322. package/src/__tests__/render-feature-prepared-diagnostics.unit.test.ts +10 -1
  323. package/src/__tests__/render-feature-retry.unit.test.ts +12 -1
  324. package/src/__tests__/render-scene-mixed-updates.integration.test.ts +145 -19
  325. package/src/__tests__/render-scene-projection.unit.test.ts +36 -11
  326. package/src/__tests__/render-scene-temporal-retry.unit.test.ts +20 -20
  327. package/src/__tests__/render-target-probe-soak.perf.test.ts +2 -2
  328. package/src/__tests__/render-temporal-environment-consumer.browser.test.ts +3 -3
  329. package/src/__tests__/render-temporal-environment-consumer.dawn.test.ts +5 -2
  330. package/src/__tests__/renderer-factory-material-contract.unit.test.ts +41 -7
  331. package/src/__tests__/renderer-frame-generation-fence.integration.test.ts +16 -0
  332. package/src/__tests__/renderer-frame-transaction.integration.test.ts +26 -10
  333. package/src/__tests__/resource-allocation.unit.test.ts +123 -0
  334. package/src/__tests__/single-layer-medium-passes.unit.test.ts +275 -0
  335. package/src/__tests__/skin-motion-regression.unit.test.ts +70 -1
  336. package/src/__tests__/skin-palette-recycling.dawn.test.ts +97 -0
  337. package/src/__tests__/skinned-shadow-caster.test.ts +15 -0
  338. package/src/__tests__/ssr-carrier-public-contract.unit.test.ts +21 -0
  339. package/src/__tests__/ssr-gpu-dispatch.browser.test.ts +4 -4
  340. package/src/__tests__/ssr-gpu-dispatch.dawn.test.ts +2 -2
  341. package/src/__tests__/ssr-gpu-dispatch.ts +1 -1
  342. package/src/__tests__/ssr-receiver-coverage.dawn.test.ts +6 -2
  343. package/src/__tests__/standard-output-chain-contract.unit.test.ts +28 -0
  344. package/src/__tests__/standard-pbr-artifact-assembly.unit.test.ts +46 -1
  345. package/src/__tests__/standard-pipeline.integration.test.ts +182 -1
  346. package/src/__tests__/surface-dynamic-buffer-lifecycle.unit.test.ts +506 -0
  347. package/src/__tests__/surface-submission-observation.unit.test.ts +812 -0
  348. package/src/__tests__/temporal-history-lifecycle.unit.test.ts +32 -0
  349. package/src/__tests__/temporal-performance.integration.test.ts +3 -3
  350. package/src/__tests__/visibility-instances.integration.test.ts +7 -19
  351. package/src/__tests__/visibility-producer-matrix.integration.test.ts +9 -20
  352. package/src/__tests__/volume-optics-oracle.unit.test.ts +4 -2
  353. package/src/__tests__/volumetric-fog-budget-recovery.integration.test.ts +30 -20
  354. package/src/__tests__/volumetric-fog-errors.unit.test.ts +4 -3
  355. package/src/__tests__/volumetric-fog-multiple.unit.test.ts +55 -0
  356. package/src/__tests__/volumetric-fog-spot-frame-optics.integration.test.ts +1 -1
  357. package/src/__tests__/volumetric-fog-stage-readback.dawn.test.ts +113 -9
  358. package/src/__tests__/volumetric-fog-topology.unit.test.ts +5 -3
  359. package/src/assembly/backend-contract.ts +30 -0
  360. package/src/assembly/barrel-distortion-inspection.ts +22 -0
  361. package/src/assembly/canvas-draw-guard.ts +11 -0
  362. package/src/assembly/cloud-shadow-entry.ts +22 -0
  363. package/src/assembly/dynamic-geometry-host.ts +65 -37
  364. package/src/assembly/dynamic-geometry-runtime.ts +7 -1
  365. package/src/assembly/factory.ts +2 -30
  366. package/src/assembly/host-contract.ts +3 -3
  367. package/src/assembly/material/assembly.ts +25 -13
  368. package/src/assembly/material/pipeline-helpers.ts +53 -12
  369. package/src/assembly/material-shader-policy.ts +55 -0
  370. package/src/assembly/material-shader-prewarm.ts +21 -10
  371. package/src/assembly/recovery/device-loss-fanout.ts +13 -0
  372. package/src/assembly/recovery/renderer-recover.ts +41 -18
  373. package/src/assembly/render-feature-post-processes.ts +54 -0
  374. package/src/assembly/renderer-facade.ts +4 -1
  375. package/src/assembly/renderer-feature-inspection.ts +8 -2
  376. package/src/assembly/renderer-frame-transaction.ts +23 -0
  377. package/src/assembly/renderer-inspection.ts +28 -0
  378. package/src/assembly/skin-palette-owner.ts +1 -1
  379. package/src/assembly/temporal-post-process-prewarm.ts +3 -3
  380. package/src/assembly/webgpu-pbr-ready.ts +9 -1
  381. package/src/assembly/webgpu-ready-per-pass.ts +104 -0
  382. package/src/assembly/webgpu-ready.ts +115 -61
  383. package/src/assembly/webgpu-renderer-bootstrap.ts +0 -12
  384. package/src/assembly/webgpu-renderer-observation-owner.ts +222 -0
  385. package/src/assembly/webgpu-renderer.ts +214 -252
  386. package/src/assembly/webgpu-vertex-layouts.ts +1 -1
  387. package/src/barrel-distortion.ts +247 -0
  388. package/src/cloud/__tests__/cloud-density-parity.dawn.test.ts +173 -0
  389. package/src/cloud/__tests__/cloud-layer-framebuffer-y.dawn.test.ts +265 -0
  390. package/src/cloud/__tests__/cloud-layer.browser.test.ts +432 -0
  391. package/src/cloud/__tests__/cloud-layer.unit.test.ts +634 -0
  392. package/src/cloud/density.ts +600 -0
  393. package/src/cloud/extract.ts +110 -0
  394. package/src/cloud/feature.ts +1810 -0
  395. package/src/cloud/frame-context.ts +42 -0
  396. package/src/cloud/inspection.ts +130 -0
  397. package/src/cloud/optics.ts +285 -0
  398. package/src/cloud/parameters.ts +255 -0
  399. package/src/cloud/resources.ts +122 -0
  400. package/src/cloud/shadow.ts +167 -0
  401. package/src/cloud/temporal.ts +226 -0
  402. package/src/components/barrel-distortion.ts +46 -0
  403. package/src/components/cloud-layer.ts +47 -0
  404. package/src/components/index.ts +2 -0
  405. package/src/components/instances.ts +18 -45
  406. package/src/components/sprite-instances.ts +1 -1
  407. package/src/construct-renderer.ts +1 -1
  408. package/src/dynamic-geometry.ts +12 -1
  409. package/src/errors/cloud.ts +103 -0
  410. package/src/errors/render.ts +44 -7
  411. package/src/examples/single-layer-medium-surface.ts +59 -0
  412. package/src/extract/camera.ts +15 -1
  413. package/src/extract/contracts.ts +5 -0
  414. package/src/features/barrel-distortion.ts +172 -0
  415. package/src/features/error-reporting.ts +26 -6
  416. package/src/features/host.ts +115 -14
  417. package/src/features/plan.ts +8 -0
  418. package/src/features/render-graph-contribution.ts +9 -0
  419. package/src/features/render-graph-raster.ts +45 -3
  420. package/src/features/targets.ts +66 -0
  421. package/src/features/types.ts +47 -0
  422. package/src/fullscreen-post-process-pass.ts +41 -9
  423. package/src/gpu-dirty-ranges.ts +48 -0
  424. package/src/gpu-driven/batch-topology.ts +53 -0
  425. package/src/gpu-driven/material-artifacts.ts +33 -25
  426. package/src/gpu-driven/prepared-draw.ts +12 -4
  427. package/src/gpu-driven/production-raster-material.ts +1894 -0
  428. package/src/gpu-driven/production-raster.ts +825 -784
  429. package/src/gpu-driven/resource-allocation.ts +142 -0
  430. package/src/gpu-driven/shadow-ownership.ts +71 -0
  431. package/src/gpu-driven/view-gpu.ts +164 -10
  432. package/src/gpu-scene.ts +75 -84
  433. package/src/hdrp-buffers.ts +4 -3
  434. package/src/ibl/skylight-bind-group.ts +49 -7
  435. package/src/index.ts +196 -9
  436. package/src/inspection-types.ts +162 -0
  437. package/src/instance-buffer-cache.ts +4 -4
  438. package/src/instances.ts +149 -569
  439. package/src/pbr-pipeline.ts +219 -13
  440. package/src/pipeline/single-layer-medium-passes.ts +267 -0
  441. package/src/pipeline/standard-forward-lane.ts +313 -57
  442. package/src/pipeline/standard-output/graph.ts +8 -2
  443. package/src/pipeline/standard-output/types.ts +12 -1
  444. package/src/pipeline/standard-pipeline.ts +291 -18
  445. package/src/pipeline/standard-post.ts +90 -24
  446. package/src/pipeline-builder.ts +1 -1
  447. package/src/plugin.ts +4 -0
  448. package/src/points-lines/__tests__/points-lines.bench.ts +17 -8
  449. package/src/prepare/prepared-graphics-resolver.ts +2 -0
  450. package/src/record/__tests__/main-pass-geometry.unit.test.ts +30 -0
  451. package/src/record/__tests__/sprite-material-abi.unit.test.ts +1 -1
  452. package/src/record/__tests__/temporal-payload.unit.test.ts +1 -1
  453. package/src/record/__tests__/transparent-group2-selection.unit.test.ts +11 -0
  454. package/src/record/__tests__/typed-frame-graph-plan.unit.test.ts +1 -0
  455. package/src/record/__tests__/view-ubo-layout.unit.test.ts +4 -3
  456. package/src/record/barrel-distortion-frame.ts +110 -0
  457. package/src/record/dynamic-geometry-consumption.ts +39 -9
  458. package/src/record/fold-buckets.ts +852 -0
  459. package/src/record/fold-instance-buffer.ts +51 -0
  460. package/src/record/frame-lighting.ts +36 -2
  461. package/src/record/frame-snapshot.ts +39 -3
  462. package/src/record/frame.ts +217 -1084
  463. package/src/record/gpu-draw-receipts.ts +38 -0
  464. package/src/record/gpu-timing.ts +48 -5
  465. package/src/record/main-pass-geometry.ts +251 -82
  466. package/src/record/main-pass-material.ts +66 -4
  467. package/src/record/main-pass-sprite-draws.ts +7 -65
  468. package/src/record/main-pass.ts +156 -20
  469. package/src/record/motion-blur-receipt.ts +91 -0
  470. package/src/record/probe-blend-buffer.ts +180 -0
  471. package/src/record/recovery-pipeline.ts +35 -3
  472. package/src/record/render-context.ts +39 -3
  473. package/src/record/shadow-pass.ts +30 -1
  474. package/src/record/skybox-post-pass.ts +28 -4
  475. package/src/record/typed-frame-graph.ts +770 -134
  476. package/src/record/view-ubo.ts +26 -3
  477. package/src/record/volume-params.ts +225 -0
  478. package/src/recovery/render-system-candidate.ts +235 -42
  479. package/src/reflection/record-owner.ts +7 -3
  480. package/src/render-contract.ts +48 -4
  481. package/src/render-graph-primitives.ts +7 -1
  482. package/src/render-pipeline.ts +130 -0
  483. package/src/render-system-extract-tail.ts +57 -61
  484. package/src/render-system-extract.ts +74 -76
  485. package/src/render-system-presentation.ts +56 -0
  486. package/src/render-system-producer-roots.ts +96 -0
  487. package/src/render-system-residency.ts +159 -0
  488. package/src/render-system.ts +347 -363
  489. package/src/scene/probe-blend.ts +96 -4
  490. package/src/scene/render-scene.ts +349 -280
  491. package/src/ssao-buffers.ts +95 -2
  492. package/src/surface/__tests__/dynamic-input.unit.test.ts +374 -0
  493. package/src/surface/__tests__/gpu-driven.unit.test.ts +87 -0
  494. package/src/surface/dynamic-input.ts +658 -0
  495. package/src/surface/gpu-driven.ts +147 -0
  496. package/src/surface/submission-observation.ts +517 -0
  497. package/src/systems/skin-palette-allocator.ts +4 -1
  498. package/src/temporal/gpu.ts +101 -17
  499. package/src/temporal/inspection.ts +7 -1
  500. package/src/temporal/standard-scene-data.ts +5 -0
  501. package/src/temporal/target-coverage-attachment.ts +4 -0
  502. package/src/transmission/__tests__/single-layer-medium.unit.test.ts +149 -0
  503. package/src/transmission/__tests__/standard-transmission.dawn.test.ts +12 -11
  504. package/src/transmission/single-layer-medium.ts +322 -0
  505. package/src/typed-render-graph-primitives.ts +70 -2
  506. package/src/volume/component.ts +10 -9
  507. package/src/volume/extract.ts +11 -5
  508. package/src/volume/inspection.ts +3 -0
  509. package/src/volume/passes.ts +87 -4
  510. package/src/volume/resources.ts +2 -1
  511. package/dist/chunk-3RTCJTH4.mjs.map +0 -1
  512. package/dist/chunk-NB2S7ZRX.mjs.map +0 -1
  513. package/dist/chunk-NNQL7CB3.mjs +0 -1713
  514. package/dist/chunk-NNQL7CB3.mjs.map +0 -1
  515. package/dist/chunk-OO3DRYK7.mjs.map +0 -1
  516. package/dist/chunk-PA5DRJNI.mjs.map +0 -1
  517. package/dist/chunk-PLAXOOX5.mjs.map +0 -1
  518. package/dist/chunk-VOZWDM3L.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 {
@@ -37,7 +37,7 @@ import type {
37
37
  GpuDrivenStructureMetrics,
38
38
  PersistentRenderSceneInspection,
39
39
  } from '../inspection-types';
40
- import type { InstanceCollectionStore } from '../instances';
40
+ import type { InstanceProjectionStore } from '../instances';
41
41
  import { fingerprintNumericArray, InstanceBoundsCache } from '../instances-derived-bounds';
42
42
  import type { PointsLinesInspection } from '../points-lines/inspection';
43
43
  import type { PointsLinesRetainedSnapshot } from '../points-lines/snapshot';
@@ -62,7 +62,11 @@ import {
62
62
  type TransmissionDemand,
63
63
  type TransmissionDemandOperation,
64
64
  } from '../transmission/projection';
65
- import { ProbeBlendSceneProjection, type ProbeSceneObjectInput } from './probe-blend';
65
+ import {
66
+ type ProbeBlendBufferProjection,
67
+ ProbeBlendSceneProjection,
68
+ type ProbeSceneObjectInput,
69
+ } from './probe-blend';
66
70
  import type {
67
71
  RenderSceneApplyResult,
68
72
  RenderSceneBounds,
@@ -402,16 +406,33 @@ export class RenderScene {
402
406
  return this.emptyResult();
403
407
  }
404
408
 
405
- 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;
406
413
  if (
407
- operation.kind === 'update' &&
408
414
  operation.snapshot !== undefined &&
409
415
  (operation.worldId !== operation.snapshot.worldId ||
410
416
  operation.entityKey !== operation.snapshot.entityKey)
411
417
  ) {
412
418
  throw new RangeError('RenderScene update identity must match its source snapshot');
413
419
  }
414
- }
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
+ });
415
436
  this.deltaFrames += 1;
416
437
  this.renderableScans = operations.length;
417
438
  const pendingByWorld = new Map<number, Map<number, PendingIdentity>>();
@@ -453,7 +474,7 @@ export class RenderScene {
453
474
  }
454
475
  }
455
476
  };
456
- for (const operation of operations) {
477
+ for (const operation of preparedOperations) {
457
478
  const pending = pendingFor(operation.worldId, operation.entityKey);
458
479
  if (operation.kind === 'remove') {
459
480
  pending.current = undefined;
@@ -462,7 +483,7 @@ export class RenderScene {
462
483
  }
463
484
  if (operation.snapshot !== undefined) {
464
485
  const previousMaterials = pending.current?.materials;
465
- const next = ownSnapshot(operation.snapshot);
486
+ const next = operation.snapshot;
466
487
  pending.current =
467
488
  previousMaterials !== undefined &&
468
489
  previousMaterials.length === next.materials.length &&
@@ -478,13 +499,7 @@ export class RenderScene {
478
499
  if (operation.instances !== undefined) {
479
500
  pending.current = {
480
501
  ...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
- },
502
+ instances: operation.instances,
488
503
  };
489
504
  pending.instanceChanged = true;
490
505
  }
@@ -753,6 +768,11 @@ export class RenderScene {
753
768
  return this.slotSnapshot;
754
769
  }
755
770
 
771
+ /** Cheap owner revision for retained derived projections. */
772
+ revisionValue(): number {
773
+ return this.revision;
774
+ }
775
+
756
776
  slot(worldId: number, entityKey: number): RenderSceneSlot | undefined {
757
777
  return this.lookup(worldId, entityKey);
758
778
  }
@@ -1047,10 +1067,10 @@ export class RenderScene {
1047
1067
  export interface PersistentRenderSceneOptions {
1048
1068
  readonly getDevice?: (() => RhiDevice) | undefined;
1049
1069
  readonly onGpuError?: ((error: RhiError) => void) | undefined;
1050
- readonly onSharedRefMutation?: ((worldId: number, handle: number) => void) | undefined;
1070
+ readonly onRuntimeAssetChange?: ((worldId: number, handle: number) => void) | undefined;
1051
1071
  readonly configuredQueryBudget?: number | undefined;
1052
- /** Producer-owned instance collections consumed by the same source update flow. */
1053
- readonly instanceCollections?: InstanceCollectionStore | undefined;
1072
+ /** Instance projection accepted by the same World source update flow. */
1073
+ readonly instanceCollections?: InstanceProjectionStore | undefined;
1054
1074
  }
1055
1075
 
1056
1076
  export interface PersistentGpuDrivenState {
@@ -1059,7 +1079,11 @@ export interface PersistentGpuDrivenState {
1059
1079
  readonly slots: readonly RenderSceneSlot[];
1060
1080
  /** Stable renderer-local World keys aligned with RenderSceneSlot.worldId. */
1061
1081
  readonly worldKeys?: readonly number[];
1082
+ /** Public World identities aligned with RenderSceneSlot.worldId. */
1083
+ readonly worldIdentities?: readonly string[];
1062
1084
  readonly structureMetrics?: GpuDrivenStructureMetrics;
1085
+ /** Stable retained ProbeBlend upload projection from the Scene owner. */
1086
+ readonly probeBlend?: ProbeBlendBufferProjection;
1063
1087
  }
1064
1088
 
1065
1089
  export interface CpuDirectViewInput {
@@ -1137,7 +1161,11 @@ interface PersistentCompositionEntry {
1137
1161
  readonly readVersions: RenderReadVersion[];
1138
1162
  readonly leaseIdentities: readonly string[];
1139
1163
  readonly transformQueries: readonly GlobalTransformChangeQuery[];
1140
- 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
+ }[];
1141
1169
  readonly dispatchBySlot: Map<number, readonly DispatchEntry[]>;
1142
1170
  dispatchRevision: number;
1143
1171
  dispatchCache: { readonly revision: number; readonly value: DispatchEntry[] } | undefined;
@@ -1147,8 +1175,14 @@ interface PersistentCompositionEntry {
1147
1175
  /** Slots retained for recovery but excluded from direct and indirect submission. */
1148
1176
  readonly unavailableSlots: Set<number>;
1149
1177
  catalogEpoch: number;
1150
- instanceEpoch: number;
1151
1178
  readonly probeProjection: ProbeBlendSceneProjection;
1179
+ probeObjects:
1180
+ | {
1181
+ readonly revision: number;
1182
+ readonly value: readonly ProbeSceneObjectInput[];
1183
+ readonly surfaceValue: readonly ProbeSceneObjectInput[];
1184
+ }
1185
+ | undefined;
1152
1186
  temporalDemanded: boolean;
1153
1187
  gpuDrivenSceneRequired: boolean;
1154
1188
  }
@@ -1157,7 +1191,6 @@ interface PersistentCompositionEntry {
1157
1191
  // environment facts are freshly extracted by deriveFramePlan on every draw;
1158
1192
  // their edits must not recreate unrelated geometry or its submission history.
1159
1193
  const RENDERABLE_SOURCE_COMPONENTS = [
1160
- GlobalTransform,
1161
1194
  ChildOf,
1162
1195
  MeshFilter,
1163
1196
  MeshRenderer,
@@ -1172,21 +1205,6 @@ const RENDERABLE_SOURCE_COMPONENTS = [
1172
1205
  Lines,
1173
1206
  SortKey,
1174
1207
  ] 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
1208
  function createGlobalTransformChangeQuery(world: World): GlobalTransformChangeQuery {
1191
1209
  const result = world.query({ read: [GlobalTransform], changed: [GlobalTransform] });
1192
1210
  if (!result.ok) throw result.error;
@@ -1197,29 +1215,43 @@ function createGlobalTransformChangeQuery(world: World): GlobalTransformChangeQu
1197
1215
  return query;
1198
1216
  }
1199
1217
 
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;
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 };
1216
1247
  }
1217
1248
 
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;
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
+ );
1223
1255
  }
1224
1256
 
1225
1257
  function sourceMembershipChanged(world: World, slot: RenderSceneSlot): boolean {
@@ -1899,7 +1931,6 @@ export class PersistentRenderScene {
1899
1931
  preserveVisibilityOnReorder,
1900
1932
  );
1901
1933
  }
1902
- let requiresSourceRefresh = false;
1903
1934
  const changedEntitiesByWorld = worlds.map(
1904
1935
  (_, worldId) => new Set(entry.pendingRenderableEntitiesByWorld[worldId] ?? []),
1905
1936
  );
@@ -1912,171 +1943,144 @@ export class PersistentRenderScene {
1912
1943
  contentChangedByWorld[slot.worldId]?.add(slot.entityKey);
1913
1944
  }
1914
1945
  }
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
1946
  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;
1947
+ const sourceBatches: StateProjectionBatch[] = [];
1930
1948
  const removedSlots: RenderSceneSlot[] = [];
1931
- for (let worldId = 0; worldId < worlds.length; worldId += 1) {
1949
+ const transformSpans: WorldTransformSpan[] = [];
1950
+ for (let worldId = 0; worldId < worlds.length; worldId++) {
1932
1951
  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
- }
1952
+ const source = entry.sourceStates[worldId];
1953
+ const lease = leases[worldId];
1939
1954
  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);
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();
1953
1970
  }
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);
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));
1964
2003
  }
2004
+ for (const handle of runtimeHandles) runtimeAssets.add(handle);
1965
2005
  }
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
- }
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);
1994
2015
  }
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)) ??
2016
+ for (const consumer of entry.skinConsumersByJoint.get(skinJointKey(worldId, previous)) ??
2017
2017
  []) {
2018
- changedEntitiesByWorld[worldId]?.add(consumer);
2019
- contentChangedByWorld[worldId]?.add(consumer);
2018
+ changed.add(consumer);
2019
+ content.add(consumer);
2020
2020
  }
2021
2021
  }
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);
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);
2054
2057
  }
2055
2058
  }
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);
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
+ }
2071
2081
  }
2072
2082
  }
2073
2083
  }
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
2084
  }
2081
2085
  }
2082
2086
 
@@ -2089,6 +2093,8 @@ export class PersistentRenderScene {
2089
2093
  for (const entityKey of changedEntitiesByWorld[worldId] ?? []) pending.add(entityKey);
2090
2094
  }
2091
2095
 
2096
+ const resourceFrame = buildCandidateFrame('none');
2097
+ for (const batch of sourceBatches) batch.validate();
2092
2098
  let changedDelta: RenderSceneApplyResult | undefined;
2093
2099
  const hasChangedEntities = changedEntitiesByWorld.some((entities) => entities.size > 0);
2094
2100
  if (hasChangedEntities || removedSlots.length > 0 || transformSpans.length > 0) {
@@ -2103,9 +2109,6 @@ export class PersistentRenderScene {
2103
2109
  }
2104
2110
  for (const slot of removedSlots) {
2105
2111
  operations.push({ kind: 'remove', worldId: slot.worldId, entityKey: slot.entityKey });
2106
- if (slot.snapshot.skinJointEntities !== undefined) {
2107
- removeSkinConsumer(entry.skinConsumersByJoint, slot.snapshot);
2108
- }
2109
2112
  for (
2110
2113
  let materialIndex = 0;
2111
2114
  materialIndex < slot.snapshot.materials.length;
@@ -2162,7 +2165,23 @@ export class PersistentRenderScene {
2162
2165
  });
2163
2166
  publishedEntitiesByWorld[snapshot.worldId]?.add(snapshot.entityKey);
2164
2167
  }
2168
+ for (const batch of sourceBatches) batch.validate();
2165
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);
2166
2185
  const publishedSkinKeys = new Set<string>();
2167
2186
  for (const snapshot of partial?.renderables ?? []) {
2168
2187
  publishedSkinKeys.add(`${snapshot.worldId}:${snapshot.entityKey}`);
@@ -2198,7 +2217,7 @@ export class PersistentRenderScene {
2198
2217
  return (
2199
2218
  worldId !== undefined &&
2200
2219
  !changedEntitiesByWorld[worldId]?.has(report.entity) &&
2201
- currentRenderableEntitiesByWorld?.[worldId]?.has(report.entity) !== false
2220
+ isRenderableMember(report.world, report.entity)
2202
2221
  );
2203
2222
  }),
2204
2223
  ...(partial?.hiddenEntityReports ?? []),
@@ -2227,8 +2246,9 @@ export class PersistentRenderScene {
2227
2246
  continue;
2228
2247
  }
2229
2248
  if (slot === undefined) {
2230
- const current = currentRenderableEntitiesByWorld?.[worldId];
2231
- 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);
2232
2252
  continue;
2233
2253
  }
2234
2254
  // The producer did not publish this requested identity. Keep the
@@ -2279,9 +2299,43 @@ export class PersistentRenderScene {
2279
2299
  this.noChangeFrames += 1;
2280
2300
  this.syncGpuOwner(entry.token, entry.projection);
2281
2301
  }
2282
- 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
+ }
2283
2338
  entry.catalogEpoch = catalogEpoch;
2284
- entry.instanceEpoch = instanceEpoch;
2285
2339
  for (let worldId = 0; worldId < nextReadVersions.length; worldId += 1) {
2286
2340
  const version = nextReadVersions[worldId];
2287
2341
  if (version !== undefined) entry.readVersions[worldId] = version;
@@ -2328,11 +2382,13 @@ export class PersistentRenderScene {
2328
2382
  this.stableSlotByEntity.clear();
2329
2383
  }
2330
2384
  if (this.gpuOwner !== world && this.gpuOwner !== detachedComposition?.token) return;
2331
- this.retireGpuScene(this.gpuScene);
2332
- 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.
2333
2390
  this.gpuOwner = undefined;
2334
- this.gpuDevice = undefined;
2335
- this.gpuStatus = 'inactive';
2391
+ this.gpuStatus = this.gpuScene === undefined ? 'inactive' : 'resident';
2336
2392
  }
2337
2393
 
2338
2394
  /** Drop only device-owned tables; the CPU projection remains the recovery authority. */
@@ -2398,6 +2454,9 @@ export class PersistentRenderScene {
2398
2454
  scene,
2399
2455
  plan: composition.topology.plan(),
2400
2456
  slots,
2457
+ worldKeys: composition.worldKeys,
2458
+ worldIdentities: composition.worlds.map((world) => world.identity),
2459
+ probeBlend: composition.probeProjection.recordBufferProjection(),
2401
2460
  });
2402
2461
  let published = false;
2403
2462
  let released = false;
@@ -2569,11 +2628,13 @@ export class PersistentRenderScene {
2569
2628
  plan: entry.topology.plan(),
2570
2629
  slots: entry.projection.slotsSnapshot(),
2571
2630
  worldKeys: entry.worldKeys,
2631
+ worldIdentities: entry.worlds.map((world) => world.identity),
2572
2632
  structureMetrics: {
2573
2633
  worldEntitiesScanned: this.worldEntitiesScanned,
2574
2634
  sceneTableUploadBytes: this.sceneTableUploadBytes,
2575
2635
  paletteUploadBytes: 0,
2576
2636
  },
2637
+ probeBlend: entry.probeProjection.recordBufferProjection(),
2577
2638
  };
2578
2639
  }
2579
2640
 
@@ -2647,7 +2708,12 @@ export class PersistentRenderScene {
2647
2708
  preserveVisibilityOnReorder = false,
2648
2709
  ): ExtractedFrame {
2649
2710
  const previousEntry = this.composition;
2650
- 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();
2651
2717
  this.temporalCapture = undefined;
2652
2718
  this.worldEntitiesScanned = candidateFrame.renderables.length;
2653
2719
  const worldKeys = this.visibilityWorldKeysFor(worlds);
@@ -2675,38 +2741,14 @@ export class PersistentRenderScene {
2675
2741
  this.invalidateActiveEntityRevision();
2676
2742
  }
2677
2743
  }
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
- };
2744
+ const stableCandidateFrame = candidateFrame;
2745
+ const renderables = candidateFrame.renderables;
2704
2746
  const projection = previousEntry?.projection ?? new RenderScene();
2705
2747
  if (previousEntry !== undefined) {
2706
2748
  projection.remapWorlds(previousEntry.worlds.map((world) => worlds.indexOf(world)));
2707
2749
  }
2708
2750
  const desired = new Set(
2709
- sortedRenderables.map((snapshot) => identityKey(snapshot.worldId, snapshot.entityKey)),
2751
+ renderables.map((snapshot) => identityKey(snapshot.worldId, snapshot.entityKey)),
2710
2752
  );
2711
2753
  const operations: RenderSceneOperation[] = [];
2712
2754
  for (const slot of projection.slotsSnapshot()) {
@@ -2714,7 +2756,7 @@ export class PersistentRenderScene {
2714
2756
  operations.push({ kind: 'remove', worldId: slot.worldId, entityKey: slot.entityKey });
2715
2757
  }
2716
2758
  }
2717
- for (const snapshot of sortedRenderables) {
2759
+ for (const snapshot of renderables) {
2718
2760
  operations.push({
2719
2761
  kind: 'update',
2720
2762
  worldId: snapshot.worldId,
@@ -2723,6 +2765,22 @@ export class PersistentRenderScene {
2723
2765
  });
2724
2766
  }
2725
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
+ );
2726
2784
  this.stableSlotByEntity.clear();
2727
2785
  for (const slot of projection.slotsSnapshot()) {
2728
2786
  const stableWorld = worldKeys[slot.worldId] ?? slot.worldId;
@@ -2757,10 +2815,7 @@ export class PersistentRenderScene {
2757
2815
  leases === undefined || leases.length !== worlds.length
2758
2816
  ? []
2759
2817
  : worlds.map(createGlobalTransformChangeQuery),
2760
- renderChangeQueries:
2761
- leases === undefined || leases.length !== worlds.length
2762
- ? []
2763
- : worlds.map(createRenderChangeQueries),
2818
+ sourceStates,
2764
2819
  dispatchBySlot: dispatchEntriesBySlot(stableCandidateFrame, projection),
2765
2820
  dispatchRevision: 0,
2766
2821
  dispatchCache: undefined,
@@ -2768,8 +2823,8 @@ export class PersistentRenderScene {
2768
2823
  pendingRenderableEntitiesByWorld: worlds.map(() => new Set<number>()),
2769
2824
  unavailableSlots: new Set<number>(),
2770
2825
  catalogEpoch,
2771
- instanceEpoch: this.options.instanceCollections?.epoch ?? 0,
2772
2826
  probeProjection: new ProbeBlendSceneProjection(),
2827
+ probeObjects: undefined,
2773
2828
  temporalDemanded: frameDemandsTemporal(candidateFrame),
2774
2829
  gpuDrivenSceneRequired: true,
2775
2830
  };
@@ -2804,6 +2859,7 @@ export class PersistentRenderScene {
2804
2859
  ? projection.cullingWorldBounds(snapshot)
2805
2860
  : projection.cullingWorldBoundsAt(slot);
2806
2861
  });
2862
+ for (const source of sourceStates) source.batch.accept();
2807
2863
  this.frameEpoch += 1;
2808
2864
  this.visibilityFacetFrame = undefined;
2809
2865
  this.visibilityProjectionCache = undefined;
@@ -2826,9 +2882,8 @@ export class PersistentRenderScene {
2826
2882
  */
2827
2883
  private deriveFramePlan(
2828
2884
  entry: PersistentCompositionEntry,
2829
- buildCandidateFrame: (request: PersistentRenderCandidateRequest) => ExtractedFrame,
2885
+ resourceFrame: ExtractedFrame,
2830
2886
  ): ExtractedFrame {
2831
- const resourceFrame = buildCandidateFrame('none');
2832
2887
  const cachedDispatch = entry.dispatchCache;
2833
2888
  const dispatch =
2834
2889
  cachedDispatch?.revision === entry.dispatchRevision
@@ -2938,27 +2993,41 @@ export class PersistentRenderScene {
2938
2993
  ] as [number, number, number],
2939
2994
  };
2940
2995
  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;
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
+ };
2948
3023
  }
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
- }));
3024
+ const surfaceOnly = probes.length === 0;
2958
3025
  entry.probeProjection.apply({
2959
- objects,
3026
+ objects: surfaceOnly ? entry.probeObjects.surfaceValue : entry.probeObjects.value,
2960
3027
  probes,
2961
3028
  sky,
3029
+ worldRevision: revision,
3030
+ ...(surfaceOnly ? { retainSkyResidualRecords: true } : {}),
2962
3031
  });
2963
3032
  }
2964
3033