@forgeax/engine-render 0.1.28 → 0.1.30

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 (768) hide show
  1. package/README.md +856 -126
  2. package/dist/assembly/atmosphere-shader-prewarm.d.ts +6 -0
  3. package/dist/assembly/atmosphere-shader-prewarm.d.ts.map +1 -0
  4. package/dist/assembly/device-feature-admission.d.ts +4 -3
  5. package/dist/assembly/device-feature-admission.d.ts.map +1 -1
  6. package/dist/assembly/dynamic-geometry-host.d.ts +27 -0
  7. package/dist/assembly/dynamic-geometry-host.d.ts.map +1 -1
  8. package/dist/assembly/dynamic-geometry-runtime.d.ts +20 -0
  9. package/dist/assembly/dynamic-geometry-runtime.d.ts.map +1 -0
  10. package/dist/assembly/factory.d.ts +3 -1
  11. package/dist/assembly/factory.d.ts.map +1 -1
  12. package/dist/assembly/gpu-driven-owner.d.ts +10 -0
  13. package/dist/assembly/gpu-driven-owner.d.ts.map +1 -0
  14. package/dist/assembly/host-contract.d.ts +14 -0
  15. package/dist/assembly/host-contract.d.ts.map +1 -1
  16. package/dist/assembly/material/assembly.d.ts +31 -1
  17. package/dist/assembly/material/assembly.d.ts.map +1 -1
  18. package/dist/assembly/material/pipeline-helpers.d.ts +24 -0
  19. package/dist/assembly/material/pipeline-helpers.d.ts.map +1 -0
  20. package/dist/assembly/material-shader-policy.d.ts +3 -1
  21. package/dist/assembly/material-shader-policy.d.ts.map +1 -1
  22. package/dist/assembly/recovery/device-loss-fanout.d.ts.map +1 -1
  23. package/dist/assembly/recovery/failure-location.d.ts +12 -0
  24. package/dist/assembly/recovery/failure-location.d.ts.map +1 -0
  25. package/dist/assembly/recovery/recovery-attempt.d.ts +1 -6
  26. package/dist/assembly/recovery/recovery-attempt.d.ts.map +1 -1
  27. package/dist/assembly/recovery/renderer-recover.d.ts +7 -4
  28. package/dist/assembly/recovery/renderer-recover.d.ts.map +1 -1
  29. package/dist/assembly/renderer-facade.d.ts.map +1 -1
  30. package/dist/assembly/renderer-feature-inspection.d.ts +5 -0
  31. package/dist/assembly/renderer-feature-inspection.d.ts.map +1 -0
  32. package/dist/assembly/renderer-frame-transaction.d.ts +19 -0
  33. package/dist/assembly/renderer-frame-transaction.d.ts.map +1 -1
  34. package/dist/assembly/renderer-helpers.d.ts +3 -0
  35. package/dist/assembly/renderer-helpers.d.ts.map +1 -1
  36. package/dist/assembly/renderer-inspection.d.ts +4 -0
  37. package/dist/assembly/renderer-inspection.d.ts.map +1 -1
  38. package/dist/assembly/shader-prewarm-policy.d.ts +7 -1
  39. package/dist/assembly/shader-prewarm-policy.d.ts.map +1 -1
  40. package/dist/assembly/skin-palette-owner.d.ts +8 -0
  41. package/dist/assembly/skin-palette-owner.d.ts.map +1 -0
  42. package/dist/assembly/ssr-shader-prewarm.d.ts +6 -0
  43. package/dist/assembly/ssr-shader-prewarm.d.ts.map +1 -0
  44. package/dist/assembly/webgpu-device-loss.d.ts +15 -0
  45. package/dist/assembly/webgpu-device-loss.d.ts.map +1 -0
  46. package/dist/assembly/webgpu-pbr-ready.d.ts +38 -0
  47. package/dist/assembly/webgpu-pbr-ready.d.ts.map +1 -0
  48. package/dist/assembly/webgpu-ready-mipmap.d.ts +11 -0
  49. package/dist/assembly/webgpu-ready-mipmap.d.ts.map +1 -0
  50. package/dist/assembly/webgpu-ready.d.ts +7 -3
  51. package/dist/assembly/webgpu-ready.d.ts.map +1 -1
  52. package/dist/assembly/webgpu-renderer-bootstrap.d.ts +10 -0
  53. package/dist/assembly/webgpu-renderer-bootstrap.d.ts.map +1 -0
  54. package/dist/assembly/webgpu-renderer-contract.d.ts +62 -0
  55. package/dist/assembly/webgpu-renderer-contract.d.ts.map +1 -0
  56. package/dist/assembly/webgpu-renderer-guards.d.ts +9 -0
  57. package/dist/assembly/webgpu-renderer-guards.d.ts.map +1 -0
  58. package/dist/assembly/webgpu-renderer-observation.d.ts +6 -0
  59. package/dist/assembly/webgpu-renderer-observation.d.ts.map +1 -0
  60. package/dist/assembly/webgpu-renderer-recovery-failure.d.ts +12 -0
  61. package/dist/assembly/webgpu-renderer-recovery-failure.d.ts.map +1 -0
  62. package/dist/assembly/webgpu-renderer.d.ts +7 -141
  63. package/dist/assembly/webgpu-renderer.d.ts.map +1 -1
  64. package/dist/assembly/webgpu-vertex-layouts.d.ts +28 -84
  65. package/dist/assembly/webgpu-vertex-layouts.d.ts.map +1 -1
  66. package/dist/authoring.mjs +2 -2
  67. package/dist/capture/scene-projection.d.ts +41 -0
  68. package/dist/capture/scene-projection.d.ts.map +1 -0
  69. package/dist/{chunk-J52NMQKR.mjs → chunk-7MQ3XBSP.mjs} +1098 -844
  70. package/dist/chunk-7MQ3XBSP.mjs.map +1 -0
  71. package/dist/{chunk-GF523LHF.mjs → chunk-BLXGONR2.mjs} +30 -12
  72. package/dist/chunk-BLXGONR2.mjs.map +1 -0
  73. package/dist/{chunk-VSL23LZE.mjs → chunk-C3DDSXYK.mjs} +1188 -463
  74. package/dist/chunk-C3DDSXYK.mjs.map +1 -0
  75. package/dist/{chunk-IQMKJLNU.mjs → chunk-FUUO4UND.mjs} +443 -30
  76. package/dist/chunk-FUUO4UND.mjs.map +1 -0
  77. package/dist/{chunk-J6KACNOH.mjs → chunk-JRBDO2KB.mjs} +617 -7
  78. package/dist/chunk-JRBDO2KB.mjs.map +1 -0
  79. package/dist/{chunk-GE3SDD72.mjs → chunk-MDQJPY3T.mjs} +20 -40
  80. package/dist/chunk-MDQJPY3T.mjs.map +1 -0
  81. package/dist/{chunk-MYJ5RPJO.mjs → chunk-NO4WZCAH.mjs} +3 -3
  82. package/dist/chunk-NO4WZCAH.mjs.map +1 -0
  83. package/dist/{chunk-RBDBZ5R7.mjs → chunk-PA5DRJNI.mjs} +2 -2
  84. package/dist/chunk-PA5DRJNI.mjs.map +1 -0
  85. package/dist/{chunk-MBU4ZVE7.mjs → chunk-PSEQ6QPH.mjs} +4655 -892
  86. package/dist/chunk-PSEQ6QPH.mjs.map +1 -0
  87. package/dist/{chunk-JAASCMXA.mjs → chunk-S5UE2T5P.mjs} +289 -41
  88. package/dist/chunk-S5UE2T5P.mjs.map +1 -0
  89. package/dist/components/camera.d.ts +62 -2
  90. package/dist/components/camera.d.ts.map +1 -1
  91. package/dist/components/directional-light.d.ts +2 -2
  92. package/dist/components/dynamic-resolution.d.ts +15 -0
  93. package/dist/components/dynamic-resolution.d.ts.map +1 -0
  94. package/dist/components/index.d.ts +2 -0
  95. package/dist/components/index.d.ts.map +1 -1
  96. package/dist/components/instances.d.ts +6 -20
  97. package/dist/components/instances.d.ts.map +1 -1
  98. package/dist/components/light-helpers.d.ts.map +1 -1
  99. package/dist/components/reflection-probe.d.ts +1 -0
  100. package/dist/components/reflection-probe.d.ts.map +1 -1
  101. package/dist/components/scene-instance.d.ts +2 -1
  102. package/dist/components/scene-instance.d.ts.map +1 -1
  103. package/dist/components/screen-space-reflection.d.ts +14 -0
  104. package/dist/components/screen-space-reflection.d.ts.map +1 -0
  105. package/dist/components/skylight.d.ts +4 -1
  106. package/dist/components/skylight.d.ts.map +1 -1
  107. package/dist/construct-renderer.mjs +14809 -7570
  108. package/dist/construct-renderer.mjs.map +1 -1
  109. package/dist/device/device-scope.d.ts +2 -0
  110. package/dist/device/device-scope.d.ts.map +1 -1
  111. package/dist/device/gpu-residency.d.ts +3 -14
  112. package/dist/device/gpu-residency.d.ts.map +1 -1
  113. package/dist/dynamic-geometry.d.ts.map +1 -1
  114. package/dist/environment/background.d.ts +7 -0
  115. package/dist/environment/background.d.ts.map +1 -0
  116. package/dist/environment/ibl.d.ts +11 -0
  117. package/dist/environment/ibl.d.ts.map +1 -0
  118. package/dist/environment/storage.d.ts +14 -0
  119. package/dist/environment/storage.d.ts.map +1 -0
  120. package/dist/errors/gpu-driven.d.ts +6 -3
  121. package/dist/errors/gpu-driven.d.ts.map +1 -1
  122. package/dist/errors/render.d.ts +41 -4
  123. package/dist/errors/render.d.ts.map +1 -1
  124. package/dist/extract/camera.d.ts.map +1 -1
  125. package/dist/extract/contracts.d.ts +7 -0
  126. package/dist/extract/contracts.d.ts.map +1 -1
  127. package/dist/extract/gpu-driven.d.ts +50 -3
  128. package/dist/extract/gpu-driven.d.ts.map +1 -1
  129. package/dist/features/host.d.ts +23 -1
  130. package/dist/features/host.d.ts.map +1 -1
  131. package/dist/features/noise-texture.d.ts +17 -0
  132. package/dist/features/noise-texture.d.ts.map +1 -0
  133. package/dist/features/plan.d.ts +83 -2
  134. package/dist/features/plan.d.ts.map +1 -1
  135. package/dist/features/prepared-gpu-work.d.ts +37 -4
  136. package/dist/features/prepared-gpu-work.d.ts.map +1 -1
  137. package/dist/features/prepared-graphics-store.d.ts.map +1 -1
  138. package/dist/features/prepared-graphics.d.ts +5 -0
  139. package/dist/features/prepared-graphics.d.ts.map +1 -1
  140. package/dist/features/render-graph-compute.d.ts +5 -3
  141. package/dist/features/render-graph-compute.d.ts.map +1 -1
  142. package/dist/features/render-graph-contribution.d.ts +11 -3
  143. package/dist/features/render-graph-contribution.d.ts.map +1 -1
  144. package/dist/features/render-graph-raster.d.ts +19 -3
  145. package/dist/features/render-graph-raster.d.ts.map +1 -1
  146. package/dist/features/types.d.ts +11 -0
  147. package/dist/features/types.d.ts.map +1 -1
  148. package/dist/fullscreen-post-process-pass.d.ts +2 -0
  149. package/dist/fullscreen-post-process-pass.d.ts.map +1 -1
  150. package/dist/gpu-driven/batch-topology.d.ts +34 -1
  151. package/dist/gpu-driven/batch-topology.d.ts.map +1 -1
  152. package/dist/gpu-driven/material-artifacts.d.ts +24 -0
  153. package/dist/gpu-driven/material-artifacts.d.ts.map +1 -0
  154. package/dist/gpu-driven/material-bindings.d.ts +7 -0
  155. package/dist/gpu-driven/material-bindings.d.ts.map +1 -0
  156. package/dist/gpu-driven/pbr-program.d.ts +10 -0
  157. package/dist/gpu-driven/pbr-program.d.ts.map +1 -0
  158. package/dist/gpu-driven/prepared-draw.d.ts +2 -0
  159. package/dist/gpu-driven/prepared-draw.d.ts.map +1 -1
  160. package/dist/gpu-driven/production-raster.d.ts +169 -27
  161. package/dist/gpu-driven/production-raster.d.ts.map +1 -1
  162. package/dist/gpu-driven/shadow-views.d.ts +126 -0
  163. package/dist/gpu-driven/shadow-views.d.ts.map +1 -0
  164. package/dist/gpu-driven/view-gpu.d.ts +18 -2
  165. package/dist/gpu-driven/view-gpu.d.ts.map +1 -1
  166. package/dist/gpu-resource.d.ts +5 -2
  167. package/dist/gpu-resource.d.ts.map +1 -1
  168. package/dist/gpu-scene-schema.d.ts +2 -8
  169. package/dist/gpu-scene-schema.d.ts.map +1 -1
  170. package/dist/gpu-scene.d.ts +14 -0
  171. package/dist/gpu-scene.d.ts.map +1 -1
  172. package/dist/gpu-texture-usage.d.ts +1 -0
  173. package/dist/gpu-texture-usage.d.ts.map +1 -1
  174. package/dist/hdrp-buffers.d.ts +9 -0
  175. package/dist/hdrp-buffers.d.ts.map +1 -1
  176. package/dist/ibl/IblPipelineCache.d.ts +2 -0
  177. package/dist/ibl/IblPipelineCache.d.ts.map +1 -1
  178. package/dist/ibl/frame-resources.d.ts +8 -0
  179. package/dist/ibl/frame-resources.d.ts.map +1 -0
  180. package/dist/ibl/skylight-bind-group.d.ts +3 -1
  181. package/dist/ibl/skylight-bind-group.d.ts.map +1 -1
  182. package/dist/index.d.ts +87 -75
  183. package/dist/index.d.ts.map +1 -1
  184. package/dist/index.mjs +10 -8
  185. package/dist/index.mjs.map +1 -1
  186. package/dist/inspection-types.d.ts +180 -1
  187. package/dist/inspection-types.d.ts.map +1 -1
  188. package/dist/instances-derived-bounds.d.ts +11 -0
  189. package/dist/instances-derived-bounds.d.ts.map +1 -1
  190. package/dist/instances.d.ts +89 -44
  191. package/dist/instances.d.ts.map +1 -1
  192. package/dist/internal.d.ts +3 -0
  193. package/dist/internal.d.ts.map +1 -1
  194. package/dist/internal.mjs +117 -9
  195. package/dist/internal.mjs.map +1 -1
  196. package/dist/lifecycle.d.ts +2 -1
  197. package/dist/lifecycle.d.ts.map +1 -1
  198. package/dist/material-row.d.ts +15 -0
  199. package/dist/material-row.d.ts.map +1 -0
  200. package/dist/materials.d.ts +15 -0
  201. package/dist/materials.d.ts.map +1 -1
  202. package/dist/pbr-pipeline.d.ts +23 -5
  203. package/dist/pbr-pipeline.d.ts.map +1 -1
  204. package/dist/pipeline/render-extent.d.ts +51 -0
  205. package/dist/pipeline/render-extent.d.ts.map +1 -0
  206. package/dist/pipeline/standard-build-context.d.ts +7 -0
  207. package/dist/pipeline/standard-build-context.d.ts.map +1 -0
  208. package/dist/pipeline/standard-forward-lane.d.ts +3 -2
  209. package/dist/pipeline/standard-forward-lane.d.ts.map +1 -1
  210. package/dist/pipeline/standard-output/auto-exposure/capability.d.ts +12 -0
  211. package/dist/pipeline/standard-output/auto-exposure/capability.d.ts.map +1 -0
  212. package/dist/pipeline/standard-output/auto-exposure/gpu.d.ts +70 -0
  213. package/dist/pipeline/standard-output/auto-exposure/gpu.d.ts.map +1 -0
  214. package/dist/pipeline/standard-output/auto-exposure/graph.d.ts +75 -0
  215. package/dist/pipeline/standard-output/auto-exposure/graph.d.ts.map +1 -0
  216. package/dist/pipeline/standard-output/auto-exposure/inspection.d.ts +111 -0
  217. package/dist/pipeline/standard-output/auto-exposure/inspection.d.ts.map +1 -0
  218. package/dist/pipeline/standard-output/auto-exposure/oracle.d.ts +16 -0
  219. package/dist/pipeline/standard-output/auto-exposure/oracle.d.ts.map +1 -0
  220. package/dist/pipeline/standard-output/auto-exposure/preset.d.ts +14 -0
  221. package/dist/pipeline/standard-output/auto-exposure/preset.d.ts.map +1 -0
  222. package/dist/pipeline/standard-output/auto-exposure/state.d.ts +46 -0
  223. package/dist/pipeline/standard-output/auto-exposure/state.d.ts.map +1 -0
  224. package/dist/pipeline/standard-output/color-transform.d.ts +27 -0
  225. package/dist/pipeline/standard-output/color-transform.d.ts.map +1 -0
  226. package/dist/pipeline/standard-output/graph.d.ts +15 -0
  227. package/dist/pipeline/standard-output/graph.d.ts.map +1 -0
  228. package/dist/pipeline/standard-output/lut-admission.d.ts +29 -0
  229. package/dist/pipeline/standard-output/lut-admission.d.ts.map +1 -0
  230. package/dist/pipeline/standard-output/lut-gpu.d.ts +48 -0
  231. package/dist/pipeline/standard-output/lut-gpu.d.ts.map +1 -0
  232. package/dist/pipeline/standard-output/lut-state.d.ts +68 -0
  233. package/dist/pipeline/standard-output/lut-state.d.ts.map +1 -0
  234. package/dist/pipeline/standard-output/resources.d.ts +10 -0
  235. package/dist/pipeline/standard-output/resources.d.ts.map +1 -0
  236. package/dist/pipeline/standard-output/types.d.ts +39 -0
  237. package/dist/pipeline/standard-output/types.d.ts.map +1 -0
  238. package/dist/pipeline/standard-pipeline.d.ts.map +1 -1
  239. package/dist/pipeline/standard-post.d.ts +2 -2
  240. package/dist/pipeline/standard-post.d.ts.map +1 -1
  241. package/dist/pipeline/standard-profile.d.ts +2 -1
  242. package/dist/pipeline/standard-profile.d.ts.map +1 -1
  243. package/dist/pipeline-builder.d.ts +5 -2
  244. package/dist/pipeline-builder.d.ts.map +1 -1
  245. package/dist/pipeline-spec-types.d.ts +4 -0
  246. package/dist/pipeline-spec-types.d.ts.map +1 -1
  247. package/dist/pipeline-spec.d.ts +1 -9
  248. package/dist/pipeline-spec.d.ts.map +1 -1
  249. package/dist/plugin.d.ts.map +1 -1
  250. package/dist/points-lines/standard-owner.d.ts +1 -1
  251. package/dist/points-lines/standard-owner.d.ts.map +1 -1
  252. package/dist/post-process-errors.d.ts +9 -2
  253. package/dist/post-process-errors.d.ts.map +1 -1
  254. package/dist/prepare/extended-lighting/resources.d.ts +3 -3
  255. package/dist/prepare/prepared-graphics-resolver.d.ts +6 -1
  256. package/dist/prepare/prepared-graphics-resolver.d.ts.map +1 -1
  257. package/dist/record/dynamic-geometry-consumption.d.ts +15 -0
  258. package/dist/record/dynamic-geometry-consumption.d.ts.map +1 -0
  259. package/dist/record/frame-lighting.d.ts +3 -3
  260. package/dist/record/frame-lighting.d.ts.map +1 -1
  261. package/dist/record/frame-snapshot.d.ts +109 -13
  262. package/dist/record/frame-snapshot.d.ts.map +1 -1
  263. package/dist/record/frame-targets.d.ts.map +1 -1
  264. package/dist/record/frame.d.ts +41 -5
  265. package/dist/record/frame.d.ts.map +1 -1
  266. package/dist/record/main-pass-geometry.d.ts +13 -9
  267. package/dist/record/main-pass-geometry.d.ts.map +1 -1
  268. package/dist/record/main-pass-material.d.ts +43 -3
  269. package/dist/record/main-pass-material.d.ts.map +1 -1
  270. package/dist/record/main-pass-sprite-draws.d.ts +2 -10
  271. package/dist/record/main-pass-sprite-draws.d.ts.map +1 -1
  272. package/dist/record/main-pass.d.ts +14 -2
  273. package/dist/record/main-pass.d.ts.map +1 -1
  274. package/dist/record/mesh-ssbo.d.ts +15 -6
  275. package/dist/record/mesh-ssbo.d.ts.map +1 -1
  276. package/dist/record/prepared-material-bindings.d.ts +19 -0
  277. package/dist/record/prepared-material-bindings.d.ts.map +1 -0
  278. package/dist/record/recovery-pipeline.d.ts +1 -0
  279. package/dist/record/recovery-pipeline.d.ts.map +1 -1
  280. package/dist/record/render-context.d.ts +101 -22
  281. package/dist/record/render-context.d.ts.map +1 -1
  282. package/dist/record/shadow-pass.d.ts +62 -7
  283. package/dist/record/shadow-pass.d.ts.map +1 -1
  284. package/dist/record/skybox-post-pass.d.ts +1 -1
  285. package/dist/record/skybox-post-pass.d.ts.map +1 -1
  286. package/dist/record/typed-frame-graph.d.ts +39 -8
  287. package/dist/record/typed-frame-graph.d.ts.map +1 -1
  288. package/dist/record/view-ubo.d.ts +1 -0
  289. package/dist/record/view-ubo.d.ts.map +1 -1
  290. package/dist/recovery/render-system-candidate.d.ts +10 -2
  291. package/dist/recovery/render-system-candidate.d.ts.map +1 -1
  292. package/dist/recovery/render-system-roots.d.ts +11 -0
  293. package/dist/recovery/render-system-roots.d.ts.map +1 -0
  294. package/dist/recovery/types.d.ts +76 -0
  295. package/dist/recovery/types.d.ts.map +1 -0
  296. package/dist/reflection/background.d.ts +14 -0
  297. package/dist/reflection/background.d.ts.map +1 -0
  298. package/dist/reflection/filter.d.ts.map +1 -1
  299. package/dist/reflection/projection.d.ts +1 -0
  300. package/dist/reflection/projection.d.ts.map +1 -1
  301. package/dist/reflection/record-owner.d.ts +4 -4
  302. package/dist/reflection/record-owner.d.ts.map +1 -1
  303. package/dist/render-contract.d.ts +211 -53
  304. package/dist/render-contract.d.ts.map +1 -1
  305. package/dist/render-graph-primitives.d.ts +1 -0
  306. package/dist/render-graph-primitives.d.ts.map +1 -1
  307. package/dist/render-pipeline.d.ts +81 -6
  308. package/dist/render-pipeline.d.ts.map +1 -1
  309. package/dist/render-system-extract-tail.d.ts +15 -6
  310. package/dist/render-system-extract-tail.d.ts.map +1 -1
  311. package/dist/render-system-extract.d.ts +128 -41
  312. package/dist/render-system-extract.d.ts.map +1 -1
  313. package/dist/render-system-projections.d.ts +22 -0
  314. package/dist/render-system-projections.d.ts.map +1 -0
  315. package/dist/render-system.d.ts +27 -77
  316. package/dist/render-system.d.ts.map +1 -1
  317. package/dist/scene/render-scene-types.d.ts +14 -7
  318. package/dist/scene/render-scene-types.d.ts.map +1 -1
  319. package/dist/scene/render-scene.d.ts +31 -11
  320. package/dist/scene/render-scene.d.ts.map +1 -1
  321. package/dist/scene/shadow-visibility.d.ts +5 -0
  322. package/dist/scene/shadow-visibility.d.ts.map +1 -0
  323. package/dist/ssao-config.d.ts +8 -6
  324. package/dist/ssao-config.d.ts.map +1 -1
  325. package/dist/ssr/admission.d.ts +70 -1
  326. package/dist/ssr/admission.d.ts.map +1 -1
  327. package/dist/ssr/compose.d.ts +13 -0
  328. package/dist/ssr/compose.d.ts.map +1 -0
  329. package/dist/ssr/errors.d.ts +35 -0
  330. package/dist/ssr/errors.d.ts.map +1 -1
  331. package/dist/ssr/graph.d.ts +60 -0
  332. package/dist/ssr/graph.d.ts.map +1 -0
  333. package/dist/ssr/history.d.ts +97 -0
  334. package/dist/ssr/history.d.ts.map +1 -0
  335. package/dist/ssr/hiz.d.ts +37 -0
  336. package/dist/ssr/hiz.d.ts.map +1 -0
  337. package/dist/ssr/inspection.d.ts +42 -0
  338. package/dist/ssr/inspection.d.ts.map +1 -0
  339. package/dist/ssr/resources.d.ts +58 -0
  340. package/dist/ssr/resources.d.ts.map +1 -0
  341. package/dist/ssr/temporal.d.ts +38 -0
  342. package/dist/ssr/temporal.d.ts.map +1 -0
  343. package/dist/systems/skin-palette-allocator.d.ts +32 -10
  344. package/dist/systems/skin-palette-allocator.d.ts.map +1 -1
  345. package/dist/systems/skin-palette-types.d.ts +15 -0
  346. package/dist/systems/skin-palette-types.d.ts.map +1 -1
  347. package/dist/systems/transparent-dispatch.d.ts +9 -0
  348. package/dist/systems/transparent-dispatch.d.ts.map +1 -0
  349. package/dist/targets/material-source.d.ts +7 -0
  350. package/dist/targets/material-source.d.ts.map +1 -1
  351. package/dist/temporal/frame-coordinator.d.ts +7 -0
  352. package/dist/temporal/frame-coordinator.d.ts.map +1 -1
  353. package/dist/temporal/gpu.d.ts +8 -0
  354. package/dist/temporal/gpu.d.ts.map +1 -1
  355. package/dist/temporal/history.d.ts.map +1 -1
  356. package/dist/temporal/index.mjs +6 -6
  357. package/dist/temporal/inspection.d.ts.map +1 -1
  358. package/dist/temporal/standard-scene-data.d.ts +4 -2
  359. package/dist/temporal/standard-scene-data.d.ts.map +1 -1
  360. package/dist/temporal/target-coverage-attachment.d.ts +15 -0
  361. package/dist/temporal/target-coverage-attachment.d.ts.map +1 -0
  362. package/dist/temporal/temporal-view.d.ts +6 -0
  363. package/dist/temporal/temporal-view.d.ts.map +1 -1
  364. package/dist/temporal/view.d.ts +6 -0
  365. package/dist/temporal/view.d.ts.map +1 -1
  366. package/dist/typed-render-graph-primitives.d.ts +41 -1
  367. package/dist/typed-render-graph-primitives.d.ts.map +1 -1
  368. package/dist/typed-shadow-passes.d.ts +3 -1
  369. package/dist/typed-shadow-passes.d.ts.map +1 -1
  370. package/dist/volume/component.d.ts +1 -0
  371. package/dist/volume/component.d.ts.map +1 -1
  372. package/dist/volume/temporal.d.ts +6 -0
  373. package/dist/volume/temporal.d.ts.map +1 -1
  374. package/package.json +21 -20
  375. package/src/__tests__/auto-exposure-gpu-evidence.ts +291 -0
  376. package/src/__tests__/auto-exposure-inspection.unit.test.ts +80 -0
  377. package/src/__tests__/auto-exposure-transaction.integration.test.ts +99 -0
  378. package/src/__tests__/auto-exposure.browser.test.ts +33 -0
  379. package/src/__tests__/auto-exposure.dawn.test.ts +33 -0
  380. package/src/__tests__/capture-scene-projection.unit.test.ts +24 -0
  381. package/src/__tests__/detached-inspection.integration.test.ts +5 -0
  382. package/src/__tests__/device-feature-admission.unit.test.ts +15 -24
  383. package/src/__tests__/device-loss-fanout.unit.test.ts +43 -0
  384. package/src/__tests__/device-scope-lifecycle.unit.test.ts +18 -0
  385. package/src/__tests__/device-scope-stale-matrix.unit.test.ts +18 -0
  386. package/src/__tests__/docs-gate-green.unit.test.ts +3 -3
  387. package/src/__tests__/dynamic-geometry-batch.unit.test.ts +157 -0
  388. package/src/__tests__/dynamic-geometry.unit.test.ts +36 -0
  389. package/src/__tests__/dynamic-resolution-plugin.integration.test.ts +81 -0
  390. package/src/__tests__/factory-contract.integration.test.ts +231 -9
  391. package/src/__tests__/fallback-row-stride-surface.unit.test.ts +19 -0
  392. package/src/__tests__/feature-depth-input.dawn.test.ts +121 -0
  393. package/src/__tests__/feature-noise.unit.test.ts +38 -0
  394. package/src/__tests__/fixtures/taa-resolve-pinned-baseline.wgsl +301 -0
  395. package/src/__tests__/frame-camera-selection.unit.test.ts +131 -0
  396. package/src/__tests__/frame-observation.contract.test.ts +21 -0
  397. package/src/__tests__/frame-plan-contract.unit.test.ts +24 -0
  398. package/src/__tests__/frame-targets.unit.test.ts +24 -0
  399. package/src/__tests__/fullscreen-feature-plan.unit.test.ts +3 -0
  400. package/src/__tests__/gpu-driven-baseline.characterization.test.ts +79 -0
  401. package/src/__tests__/gpu-driven-batch-topology-pbr.unit.test.ts +307 -7
  402. package/src/__tests__/gpu-driven-indirect-raster-evidence.ts +122 -142
  403. package/src/__tests__/gpu-driven-indirect-raster.browser.test.ts +10 -1
  404. package/src/__tests__/gpu-driven-lod-cache.unit.test.ts +261 -0
  405. package/src/__tests__/gpu-driven-mesh-world-identity.unit.test.ts +346 -0
  406. package/src/__tests__/gpu-driven-pbr.dawn.test.ts +1422 -0
  407. package/src/__tests__/gpu-driven-pbr.integration.test.ts +21 -0
  408. package/src/__tests__/gpu-driven-production-fixture.ts +96 -0
  409. package/src/__tests__/gpu-driven-production.integration.test.ts +1076 -106
  410. package/src/__tests__/gpu-driven-scaling.unit.test.ts +1 -3
  411. package/src/__tests__/gpu-driven-shadow-views.browser.test.ts +63 -0
  412. package/src/__tests__/gpu-driven-shadow-views.dawn.test.ts +294 -0
  413. package/src/__tests__/gpu-driven-shadow-views.integration.test.ts +97 -0
  414. package/src/__tests__/gpu-driven-shadow-views.unit.test.ts +168 -0
  415. package/src/__tests__/gpu-driven-skin-contract.test-d.ts +11 -0
  416. package/src/__tests__/gpu-driven-view-gpu-evidence.ts +72 -20
  417. package/src/__tests__/gpu-driven-view-graph.integration.test.ts +30 -9
  418. package/src/__tests__/gpu-driven-view.unit.test.ts +160 -52
  419. package/src/__tests__/gpu-pass-timing-browser-runner.ts +15 -0
  420. package/src/__tests__/gpu-pass-timing.browser.test.ts +20 -2
  421. package/src/__tests__/gpu-resource-scope.unit.test.ts +71 -0
  422. package/src/__tests__/gpu-resource-store-stride.unit.test.ts +42 -0
  423. package/src/__tests__/gpu-scene-render-graph-gpu.ts +19 -5
  424. package/src/__tests__/gpu-scene.dawn.test.ts +40 -13
  425. package/src/__tests__/gpu-scene.unit.test.ts +613 -33
  426. package/src/__tests__/instances-culling.integration.test.ts +36 -10
  427. package/src/__tests__/instances-derived-bounds.unit.test.ts +54 -0
  428. package/src/__tests__/instances-store.unit.test.ts +242 -39
  429. package/src/__tests__/ktx2-basis-gpu-consumer.dawn.test.ts +6 -0
  430. package/src/__tests__/lighting-scene-roundtrip.integration.test.ts +13 -16
  431. package/src/__tests__/lut-lkg-transaction.integration.test.ts +122 -0
  432. package/src/__tests__/material-contract-inventory.unit.test.ts +5 -6
  433. package/src/__tests__/material-dispatch-shader-id.unit.test.ts +51 -0
  434. package/src/__tests__/material-lane-selection.unit.test.ts +60 -0
  435. package/src/__tests__/material-numeric-upload.unit.test.ts +53 -0
  436. package/src/__tests__/material-texture-handles.unit.test.ts +18 -0
  437. package/src/__tests__/material-texture-source.unit.test.ts +87 -0
  438. package/src/__tests__/materials-authoring-validation.unit.test.ts +41 -0
  439. package/src/__tests__/materials-standard-contract.unit.test.ts +57 -3
  440. package/src/__tests__/mesh-buffer-usage-surface.unit.test.ts +4 -0
  441. package/src/__tests__/mesh-submission-lifetime.unit.test.ts +8 -0
  442. package/src/__tests__/morph-neutral-admission.unit.test.ts +19 -0
  443. package/src/__tests__/normal-fallback.dawn.test.ts +122 -0
  444. package/src/__tests__/particle-material-input-layout.unit.test.ts +104 -0
  445. package/src/__tests__/physical-clearcoat.integration.test.ts +42 -3
  446. package/src/__tests__/pipeline-texture-specialization.unit.test.ts +138 -0
  447. package/src/__tests__/point-shadow-inspection.unit.test.ts +2 -0
  448. package/src/__tests__/post-process-error-policy-owner.unit.test.ts +6 -5
  449. package/src/__tests__/prepared-gpu-driven-pbr-contract.test-d.ts +31 -0
  450. package/src/__tests__/prepared-gpu-driven-pbr-contract.unit.test.ts +39 -0
  451. package/src/__tests__/prepared-gpu-driven-pbr.integration.test.ts +251 -9
  452. package/src/__tests__/prepared-graphics-capability.unit.test.ts +21 -0
  453. package/src/__tests__/prepared-graphics-pipeline-warmup.unit.test.ts +6 -2
  454. package/src/__tests__/profiler-phase-catalog.test.ts +5 -0
  455. package/src/__tests__/public-surface.test-d.ts +3 -0
  456. package/src/__tests__/recovery-candidate-prepare.contract.test.ts +30 -12
  457. package/src/__tests__/reflection-fallback-async-publication.unit.test.ts +171 -0
  458. package/src/__tests__/reflection-probe-authoring.unit.test.ts +12 -10
  459. package/src/__tests__/reflection-probe-box.dawn.test.ts +71 -0
  460. package/src/__tests__/reflection-probe-filter.unit.test.ts +9 -0
  461. package/src/__tests__/reflection-probe-orientation.dawn.test.ts +294 -0
  462. package/src/__tests__/reflection-probe-record-owner.characterization.unit.test.ts +5 -14
  463. package/src/__tests__/reflection-probe-updates.unit.test.ts +129 -0
  464. package/src/__tests__/render-error-code-owner.test-d.ts +7 -0
  465. package/src/__tests__/render-error-detail-ssot.test-d.ts +27 -1
  466. package/src/__tests__/render-error-exhaustive.test-d.ts +15 -0
  467. package/src/__tests__/render-extent.unit.test.ts +63 -0
  468. package/src/__tests__/render-feature-gpu-work.integration.test.ts +285 -2
  469. package/src/__tests__/render-feature-plan.unit.test.ts +245 -0
  470. package/src/__tests__/render-feature-shadow-order.unit.test.ts +228 -0
  471. package/src/__tests__/render-feature-zero-work.unit.test.ts +35 -1
  472. package/src/__tests__/render-graph-fallback.integration.test.ts +2 -2
  473. package/src/__tests__/render-owner-cohesion.unit.test.ts +55 -0
  474. package/src/__tests__/render-scene-mixed-updates.integration.test.ts +426 -0
  475. package/src/__tests__/render-scene-projection.unit.test.ts +915 -23
  476. package/src/__tests__/render-scene-resource-updates.integration.test.ts +214 -0
  477. package/src/__tests__/render-scene-skin-lifecycle.integration.test.ts +173 -0
  478. package/src/__tests__/render-scene-temporal-retry.unit.test.ts +133 -0
  479. package/src/__tests__/render-target-material-source.test-d.ts +27 -3
  480. package/src/__tests__/renderer-factory-material-contract.unit.test.ts +233 -9
  481. package/src/__tests__/renderer-frame-transaction.integration.test.ts +923 -5
  482. package/src/__tests__/shader-manifest-fixture.ts +45 -0
  483. package/src/__tests__/skin-motion-regression.unit.test.ts +0 -33
  484. package/src/__tests__/skin-palette-persistent.integration.test.ts +28 -0
  485. package/src/__tests__/skin-palette-persistent.unit.test.ts +247 -0
  486. package/src/__tests__/skinned-shadow-caster.test.ts +125 -14
  487. package/src/__tests__/specular-environment-scale.dawn.test.ts +158 -0
  488. package/src/__tests__/ssao-config.unit.test.ts +7 -0
  489. package/src/__tests__/ssao-surface.dawn.test.ts +252 -0
  490. package/src/__tests__/ssr-admission-matrix.integration.test.ts +7 -1
  491. package/src/__tests__/ssr-confidence-compose.unit.test.ts +84 -0
  492. package/src/__tests__/ssr-coordinate-oracle.unit.test.ts +28 -0
  493. package/src/__tests__/ssr-cube-silhouette.dawn.test.ts +270 -0
  494. package/src/__tests__/ssr-format-probe-gating.integration.test.ts +82 -0
  495. package/src/__tests__/ssr-gpu-dispatch.browser.test.ts +99 -0
  496. package/src/__tests__/ssr-gpu-dispatch.dawn.test.ts +116 -0
  497. package/src/__tests__/ssr-gpu-dispatch.ts +951 -0
  498. package/src/__tests__/ssr-graph-topology.unit.test.ts +104 -0
  499. package/src/__tests__/ssr-history-filter.dawn.test.ts +198 -0
  500. package/src/__tests__/ssr-hit-color.dawn.test.ts +273 -0
  501. package/src/__tests__/ssr-hiz-odd-reduction.dawn.test.ts +569 -0
  502. package/src/__tests__/ssr-hiz-rescue.dawn.test.ts +279 -0
  503. package/src/__tests__/ssr-hiz.browser.test.ts +150 -0
  504. package/src/__tests__/ssr-hiz.dawn.test.ts +150 -0
  505. package/src/__tests__/ssr-hiz.unit.test.ts +56 -0
  506. package/src/__tests__/ssr-inspection.unit.test.ts +105 -0
  507. package/src/__tests__/ssr-memory-budget.unit.test.ts +45 -0
  508. package/src/__tests__/ssr-performance-sequence-aggregation.unit.test.ts +15 -0
  509. package/src/__tests__/ssr-public-surface.test-d.ts +37 -0
  510. package/src/__tests__/ssr-receiver-coverage.dawn.test.ts +387 -0
  511. package/src/__tests__/ssr-reflection-mip-reducer.dawn.test.ts +309 -0
  512. package/src/__tests__/ssr-reprojection-probe.ts +90 -0
  513. package/src/__tests__/ssr-roughness-mip.dawn.test.ts +163 -0
  514. package/src/__tests__/ssr-shader-prewarm.unit.test.ts +59 -0
  515. package/src/__tests__/ssr-spatial.integration.test.ts +141 -0
  516. package/src/__tests__/ssr-stable-lattice.dawn.test.ts +475 -0
  517. package/src/__tests__/ssr-temporal-history.unit.test.ts +202 -0
  518. package/src/__tests__/ssr-texel-crossing.dawn.test.ts +448 -0
  519. package/src/__tests__/ssr-zero-work.integration.test.ts +87 -0
  520. package/src/__tests__/standard-cluster-variant-resolution.unit.test.ts +84 -12
  521. package/src/__tests__/standard-clustered-pipeline.unit.test.ts +29 -0
  522. package/src/__tests__/standard-output-chain-contract.unit.test.ts +99 -0
  523. package/src/__tests__/standard-output-chain.integration.test.ts +73 -0
  524. package/src/__tests__/standard-output-gpu-evidence.ts +205 -0
  525. package/src/__tests__/standard-output.browser.test.ts +15 -0
  526. package/src/__tests__/standard-output.dawn.test.ts +15 -0
  527. package/src/__tests__/standard-pbr-artifact-assembly.unit.test.ts +275 -0
  528. package/src/__tests__/standard-pipeline.integration.test.ts +320 -3
  529. package/src/__tests__/standard-profile-oracle.unit.test.ts +49 -0
  530. package/src/__tests__/standard-surface-lighting.unit.test.ts +41 -0
  531. package/src/__tests__/standard-texture-specialization.dawn.test.ts +141 -0
  532. package/src/__tests__/surface-adjacent-regressions.integration.test.ts +6 -3
  533. package/src/__tests__/taa-coverage-edge.dawn.test.ts +199 -0
  534. package/src/__tests__/taa-history-clipping.dawn.test.ts +328 -0
  535. package/src/__tests__/taa-neighborhood-equivalence.dawn.test.ts +782 -0
  536. package/src/__tests__/taa-stability-feedback.browser.test.ts +23 -0
  537. package/src/__tests__/taa-stability-feedback.dawn.test.ts +68 -0
  538. package/src/__tests__/taa-stability-feedback.ts +342 -0
  539. package/src/__tests__/temporal-history-lifecycle.unit.test.ts +49 -7
  540. package/src/__tests__/temporal-reset-matrix.unit.test.ts +9 -0
  541. package/src/__tests__/temporal-transaction.integration.test.ts +28 -0
  542. package/src/__tests__/temporal-view.unit.test.ts +17 -0
  543. package/src/__tests__/texture-usage-surface.unit.test.ts +4 -2
  544. package/src/__tests__/transmission-prewarm.unit.test.ts +57 -0
  545. package/src/__tests__/typed-pipeline-topology.unit.test.ts +109 -4
  546. package/src/__tests__/typed-scene-pass-material-cache.integration.test.ts +126 -0
  547. package/src/__tests__/vertex-layouts-ssot.unit.test.ts +47 -0
  548. package/src/__tests__/visibility-extract.perf.test.ts +20 -8
  549. package/src/__tests__/visibility-instances.integration.test.ts +19 -7
  550. package/src/__tests__/visibility-producer-matrix.integration.test.ts +20 -9
  551. package/src/__tests__/volumetric-fog-stage-readback.dawn.test.ts +62 -13
  552. package/src/__tests__/volumetric-fog-topology.unit.test.ts +7 -7
  553. package/src/assembly/atmosphere-shader-prewarm.ts +39 -0
  554. package/src/assembly/device-feature-admission.ts +9 -4
  555. package/src/assembly/dynamic-geometry-host.ts +284 -163
  556. package/src/assembly/dynamic-geometry-runtime.ts +71 -0
  557. package/src/assembly/factory.ts +4 -0
  558. package/src/assembly/gpu-driven-owner.ts +29 -0
  559. package/src/assembly/host-contract.ts +19 -0
  560. package/src/assembly/material/assembly.ts +383 -1
  561. package/src/assembly/material/pipeline-helpers.ts +100 -0
  562. package/src/assembly/material-shader-policy.ts +110 -46
  563. package/src/assembly/recovery/device-loss-fanout.ts +5 -0
  564. package/src/assembly/recovery/failure-location.ts +62 -0
  565. package/src/assembly/recovery/recovery-attempt.ts +2 -29
  566. package/src/assembly/recovery/renderer-recover.ts +16 -17
  567. package/src/assembly/renderer-facade.ts +34 -1
  568. package/src/assembly/renderer-feature-inspection.ts +13 -0
  569. package/src/assembly/renderer-frame-transaction.ts +63 -0
  570. package/src/assembly/renderer-helpers.ts +26 -0
  571. package/src/assembly/renderer-inspection.ts +6 -0
  572. package/src/assembly/shader-prewarm-policy.ts +46 -1
  573. package/src/assembly/skin-palette-owner.ts +21 -0
  574. package/src/assembly/ssr-shader-prewarm.ts +41 -0
  575. package/src/assembly/webgpu-device-loss.ts +75 -0
  576. package/src/assembly/webgpu-pbr-ready.ts +232 -0
  577. package/src/assembly/webgpu-ready-mipmap.ts +26 -0
  578. package/src/assembly/webgpu-ready.ts +161 -131
  579. package/src/assembly/webgpu-renderer-bootstrap.ts +85 -0
  580. package/src/assembly/webgpu-renderer-contract.ts +69 -0
  581. package/src/assembly/webgpu-renderer-guards.ts +32 -0
  582. package/src/assembly/webgpu-renderer-observation.ts +43 -0
  583. package/src/assembly/webgpu-renderer-recovery-failure.ts +62 -0
  584. package/src/assembly/webgpu-renderer.ts +744 -730
  585. package/src/assembly/webgpu-vertex-layouts.ts +233 -87
  586. package/src/capture/scene-projection.ts +37 -0
  587. package/src/components/__tests__/camera-exposure.contract.unit.test.ts +153 -0
  588. package/src/components/__tests__/dynamic-resolution-schema.unit.test.ts +65 -0
  589. package/src/components/__tests__/light-radius.integration.test.ts +59 -0
  590. package/src/components/camera.ts +287 -7
  591. package/src/components/directional-light.ts +3 -3
  592. package/src/components/dynamic-resolution.ts +67 -0
  593. package/src/components/index.ts +2 -0
  594. package/src/components/instances.ts +45 -18
  595. package/src/components/light-helpers.ts +6 -1
  596. package/src/components/reflection-probe.ts +1 -0
  597. package/src/components/scene-instance.ts +2 -1
  598. package/src/components/screen-space-reflection.ts +21 -0
  599. package/src/components/skylight.ts +4 -1
  600. package/src/components/sprite-animation.ts +2 -2
  601. package/src/components/sprite-instances.ts +1 -1
  602. package/src/device/device-scope.ts +13 -7
  603. package/src/device/gpu-residency.ts +45 -42
  604. package/src/dynamic-geometry.ts +13 -8
  605. package/src/environment/background.ts +281 -0
  606. package/src/environment/ibl.ts +211 -0
  607. package/src/environment/storage.ts +63 -0
  608. package/src/errors/gpu-driven.ts +49 -8
  609. package/src/errors/render.ts +91 -3
  610. package/src/extract/camera.ts +37 -1
  611. package/src/extract/contracts.ts +10 -0
  612. package/src/extract/gpu-driven.ts +240 -10
  613. package/src/features/host.ts +237 -19
  614. package/src/features/noise-texture.ts +91 -0
  615. package/src/features/plan.ts +553 -24
  616. package/src/features/prepared-gpu-work.ts +631 -49
  617. package/src/features/prepared-graphics-store.ts +31 -0
  618. package/src/features/prepared-graphics.ts +5 -0
  619. package/src/features/render-graph-compute.ts +41 -8
  620. package/src/features/render-graph-contribution.ts +141 -3
  621. package/src/features/render-graph-raster.ts +143 -30
  622. package/src/features/types.ts +11 -0
  623. package/src/fullscreen-post-process-pass.ts +3 -0
  624. package/src/gpu-driven/batch-topology.ts +452 -108
  625. package/src/gpu-driven/material-artifacts.ts +179 -0
  626. package/src/gpu-driven/material-bindings.ts +41 -0
  627. package/src/gpu-driven/pbr-program.ts +31 -0
  628. package/src/gpu-driven/prepared-draw.ts +42 -6
  629. package/src/gpu-driven/production-raster.ts +2806 -716
  630. package/src/gpu-driven/shadow-views.ts +539 -0
  631. package/src/gpu-driven/view-gpu.ts +188 -41
  632. package/src/gpu-resource.ts +21 -2
  633. package/src/gpu-scene-schema.ts +61 -11
  634. package/src/gpu-scene.ts +197 -99
  635. package/src/gpu-texture-usage.ts +1 -0
  636. package/src/hdrp-buffers.ts +141 -36
  637. package/src/ibl/IblPipelineCache.ts +5 -0
  638. package/src/ibl/frame-resources.ts +38 -0
  639. package/src/ibl/skylight-bind-group.ts +32 -21
  640. package/src/index.ts +194 -75
  641. package/src/inspection-types.ts +207 -1
  642. package/src/instances-derived-bounds.ts +258 -34
  643. package/src/instances.ts +459 -150
  644. package/src/internal.ts +22 -0
  645. package/src/lifecycle.ts +2 -0
  646. package/src/material-row.ts +194 -0
  647. package/src/materials.ts +44 -8
  648. package/src/pbr-pipeline.ts +172 -19
  649. package/src/pipeline/render-extent.ts +111 -0
  650. package/src/pipeline/standard-build-context.ts +7 -0
  651. package/src/pipeline/standard-forward-lane.ts +74 -29
  652. package/src/pipeline/standard-output/__tests__/lut-admission.unit.test.ts +103 -0
  653. package/src/pipeline/standard-output/auto-exposure/__tests__/capability.unit.test.ts +40 -0
  654. package/src/pipeline/standard-output/auto-exposure/__tests__/gpu.contract.unit.test.ts +125 -0
  655. package/src/pipeline/standard-output/auto-exposure/__tests__/graph.contract.unit.test.ts +94 -0
  656. package/src/pipeline/standard-output/auto-exposure/__tests__/oracle.test.ts +108 -0
  657. package/src/pipeline/standard-output/auto-exposure/__tests__/preset.test.ts +26 -0
  658. package/src/pipeline/standard-output/auto-exposure/capability.ts +38 -0
  659. package/src/pipeline/standard-output/auto-exposure/gpu.ts +567 -0
  660. package/src/pipeline/standard-output/auto-exposure/graph.ts +221 -0
  661. package/src/pipeline/standard-output/auto-exposure/inspection.ts +231 -0
  662. package/src/pipeline/standard-output/auto-exposure/oracle.ts +291 -0
  663. package/src/pipeline/standard-output/auto-exposure/preset.ts +28 -0
  664. package/src/pipeline/standard-output/auto-exposure/state.ts +156 -0
  665. package/src/pipeline/standard-output/color-transform.ts +124 -0
  666. package/src/pipeline/standard-output/graph.ts +139 -0
  667. package/src/pipeline/standard-output/lut-admission.ts +121 -0
  668. package/src/pipeline/standard-output/lut-gpu.ts +399 -0
  669. package/src/pipeline/standard-output/lut-state.ts +176 -0
  670. package/src/pipeline/standard-output/resources.ts +121 -0
  671. package/src/pipeline/standard-output/types.ts +58 -0
  672. package/src/pipeline/standard-pipeline.ts +232 -70
  673. package/src/pipeline/standard-post.ts +190 -27
  674. package/src/pipeline/standard-profile.ts +2 -1
  675. package/src/pipeline-builder.ts +28 -9
  676. package/src/pipeline-spec-types.ts +4 -0
  677. package/src/pipeline-spec.ts +37 -29
  678. package/src/plugin.ts +4 -0
  679. package/src/points-lines/standard-owner.ts +6 -14
  680. package/src/post-process-errors.ts +26 -18
  681. package/src/prepare/__tests__/extended-lighting-capability.unit.test.ts +2 -2
  682. package/src/prepare/extended-lighting/resources.ts +3 -3
  683. package/src/prepare/prepared-graphics-resolver.ts +9 -2
  684. package/src/record/__tests__/main-pass-geometry.unit.test.ts +68 -0
  685. package/src/record/__tests__/material-bg-cache.unit.test.ts +45 -0
  686. package/src/record/__tests__/material-slot-plan.unit.test.ts +57 -0
  687. package/src/record/__tests__/mesh-ssbo.unit.test.ts +40 -1
  688. package/src/record/__tests__/recovery-pipeline.unit.test.ts +20 -1
  689. package/src/record/__tests__/sprite-material-abi.unit.test.ts +41 -6
  690. package/src/record/__tests__/temporal-payload.unit.test.ts +2 -0
  691. package/src/record/__tests__/typed-frame-graph-plan.unit.test.ts +45 -0
  692. package/src/record/__tests__/view-ubo-layout.unit.test.ts +5 -0
  693. package/src/record/dynamic-geometry-consumption.ts +59 -0
  694. package/src/record/frame-lighting.ts +15 -12
  695. package/src/record/frame-snapshot.ts +146 -34
  696. package/src/record/frame-targets.ts +9 -0
  697. package/src/record/frame.ts +838 -170
  698. package/src/record/main-pass-geometry.ts +209 -103
  699. package/src/record/main-pass-material.ts +157 -40
  700. package/src/record/main-pass-sprite-draws.ts +16 -107
  701. package/src/record/main-pass.ts +141 -12
  702. package/src/record/material-uniforms.ts +1 -1
  703. package/src/record/mesh-ssbo.ts +59 -15
  704. package/src/record/prepared-material-bindings.ts +238 -0
  705. package/src/record/recovery-pipeline.ts +69 -90
  706. package/src/record/render-context.ts +123 -24
  707. package/src/record/shadow-pass.ts +1389 -230
  708. package/src/record/skybox-post-pass.ts +14 -3
  709. package/src/record/typed-frame-graph.ts +1064 -47
  710. package/src/record/view-ubo.ts +12 -1
  711. package/src/recovery/render-system-candidate.ts +135 -68
  712. package/src/recovery/render-system-roots.ts +113 -0
  713. package/src/recovery/types.ts +77 -0
  714. package/src/reflection/background.ts +151 -0
  715. package/src/reflection/filter.ts +3 -0
  716. package/src/reflection/projection.ts +16 -6
  717. package/src/reflection/record-owner.ts +308 -140
  718. package/src/render-contract.ts +237 -49
  719. package/src/render-graph-primitives.ts +34 -5
  720. package/src/render-pipeline.ts +90 -2
  721. package/src/render-system-extract-tail.ts +431 -162
  722. package/src/render-system-extract.ts +478 -119
  723. package/src/render-system-projections.ts +241 -0
  724. package/src/render-system.ts +715 -828
  725. package/src/scene/render-scene-types.ts +13 -4
  726. package/src/scene/render-scene.ts +577 -355
  727. package/src/scene/shadow-visibility.ts +21 -0
  728. package/src/ssao-config.ts +25 -8
  729. package/src/ssr/admission.ts +380 -14
  730. package/src/ssr/compose.ts +144 -0
  731. package/src/ssr/errors.ts +48 -0
  732. package/src/ssr/graph.ts +797 -0
  733. package/src/ssr/history.ts +436 -0
  734. package/src/ssr/hiz.ts +124 -0
  735. package/src/ssr/inspection.ts +124 -0
  736. package/src/ssr/resources.ts +202 -0
  737. package/src/ssr/temporal.ts +164 -0
  738. package/src/systems/skin-palette-allocator.ts +495 -148
  739. package/src/systems/skin-palette-types.ts +17 -0
  740. package/src/systems/transparent-dispatch.ts +90 -0
  741. package/src/targets/material-source.ts +34 -0
  742. package/src/temporal/coverage.ts +1 -1
  743. package/src/temporal/frame-coordinator.ts +23 -2
  744. package/src/temporal/gpu.ts +79 -12
  745. package/src/temporal/history.ts +3 -1
  746. package/src/temporal/inspection.ts +2 -1
  747. package/src/temporal/standard-scene-data.ts +10 -2
  748. package/src/temporal/target-coverage-attachment.ts +72 -0
  749. package/src/temporal/temporal-view.ts +11 -2
  750. package/src/temporal/view.ts +12 -2
  751. package/src/transmission/__tests__/standard-transmission.dawn.test.ts +219 -139
  752. package/src/typed-render-graph-primitives.ts +304 -74
  753. package/src/typed-shadow-passes.ts +122 -12
  754. package/src/volume/component.ts +1 -0
  755. package/src/volume/passes.ts +3 -3
  756. package/src/volume/temporal.ts +10 -0
  757. package/dist/chunk-GE3SDD72.mjs.map +0 -1
  758. package/dist/chunk-GF523LHF.mjs.map +0 -1
  759. package/dist/chunk-IQMKJLNU.mjs.map +0 -1
  760. package/dist/chunk-J52NMQKR.mjs.map +0 -1
  761. package/dist/chunk-J6KACNOH.mjs.map +0 -1
  762. package/dist/chunk-JAASCMXA.mjs.map +0 -1
  763. package/dist/chunk-MBU4ZVE7.mjs.map +0 -1
  764. package/dist/chunk-MYJ5RPJO.mjs.map +0 -1
  765. package/dist/chunk-RBDBZ5R7.mjs.map +0 -1
  766. package/dist/chunk-VSL23LZE.mjs.map +0 -1
  767. package/src/__tests__/instances-world-ownership.integration.test.ts +0 -62
  768. package/src/scene/__tests__/changed-block-summary.candidate.test.ts +0 -36
@@ -3,41 +3,40 @@ import { componentId } from '@forgeax/engine-ecs/internal';
3
3
  import type { RenderReadLease, RenderReadVersion } from '@forgeax/engine-ecs/projection';
4
4
  import { box3, frustum, mat4 } from '@forgeax/engine-math';
5
5
  import { type RhiDevice, RhiError } from '@forgeax/engine-rhi';
6
- import { ChildOf, GlobalTransform, MorphWeights, Transform } from '@forgeax/engine-scene';
6
+ import {
7
+ ChildOf,
8
+ collectSubtree,
9
+ GlobalTransform,
10
+ MorphWeights,
11
+ Transform,
12
+ } from '@forgeax/engine-scene';
7
13
  import { Skin } from '@forgeax/engine-skinning';
8
14
  import { err, ok, type Result } from '@forgeax/engine-types';
9
15
  import {
10
- Camera,
11
- DirectionalLight,
12
16
  Instances,
13
17
  Layer,
14
- LightProbe,
15
18
  Lines,
16
19
  MeshFilter,
17
20
  MeshRenderer,
18
- MotionBlur,
19
- PointLight,
20
- PointLightShadow,
21
21
  Points,
22
- PostProcessParams,
23
- ReflectionProbe,
24
- SkyboxBackground,
25
- Skylight,
26
22
  SortKey,
27
- SpotLight,
28
23
  SpriteInstances,
29
24
  SpriteRegionOverride,
30
25
  Visibility,
31
26
  } from '../components';
32
27
  import type { DeviceScope, LifecycleResourceSpec } from '../device/device-scope';
28
+ import { gpuDrivenShadowDrawKey } from '../extract/gpu-driven';
33
29
  import {
34
30
  BatchTopology,
35
31
  type BatchTopologyInspection,
36
32
  type SubmissionPlan,
37
33
  } from '../gpu-driven/batch-topology';
38
34
  import { GpuScene } from '../gpu-scene';
39
- import type { PersistentRenderSceneInspection } from '../inspection-types';
40
- import type { InstanceProjectionStore } from '../instances';
35
+ import type {
36
+ GpuDrivenStructureMetrics,
37
+ PersistentRenderSceneInspection,
38
+ } from '../inspection-types';
39
+ import type { InstanceCollectionStore } from '../instances';
41
40
  import { fingerprintNumericArray, InstanceBoundsCache } from '../instances-derived-bounds';
42
41
  import type { PointsLinesInspection } from '../points-lines/inspection';
43
42
  import type { PointsLinesRetainedSnapshot } from '../points-lines/snapshot';
@@ -57,13 +56,11 @@ import type {
57
56
  RenderableSnapshot,
58
57
  RenderableTemporalSnapshot,
59
58
  } from '../render-system-extract';
60
- import type { SkinPaletteAllocator } from '../systems/skin-palette-allocator';
61
59
  import {
62
60
  PersistentTransmissionDemandProjection,
63
61
  type TransmissionDemand,
64
62
  type TransmissionDemandOperation,
65
63
  } from '../transmission/projection';
66
- import { VolumetricFog } from '../volume/component';
67
64
  import { ProbeBlendSceneProjection, type ProbeSceneObjectInput } from './probe-blend';
68
65
  import type {
69
66
  RenderSceneApplyResult,
@@ -74,6 +71,7 @@ import type {
74
71
  RenderSceneResyncReason,
75
72
  RenderSceneSlot,
76
73
  } from './render-scene-types';
74
+ import { buildShadowFrusta } from './shadow-visibility';
77
75
  import { createVisibilityBudget, type VisibilityBudget } from './visibility/budget';
78
76
  import {
79
77
  primitiveKey,
@@ -103,6 +101,10 @@ interface PendingIdentity {
103
101
  current: RenderableSnapshot | undefined;
104
102
  removed: boolean;
105
103
  contentChanged: boolean;
104
+ /** An Instances row changed during this apply batch. */
105
+ instanceChanged: boolean;
106
+ /** A retained world transform changed during this apply batch. */
107
+ transformChanged: boolean;
106
108
  }
107
109
 
108
110
  interface WorldTransformSpan {
@@ -137,6 +139,16 @@ export interface RenderSceneSubmissionDelta {
137
139
  readonly slots: readonly number[];
138
140
  }
139
141
 
142
+ /**
143
+ * Complete ShadowCaster producer projection retained beside the camera-cull
144
+ * submission frame. The persistent scene owns this sequence; record consumes
145
+ * it for the CPU residual lane without rebuilding a second scene projection.
146
+ */
147
+ export interface PersistentShadowCasterProjection {
148
+ readonly renderables: readonly RenderableSnapshot[];
149
+ readonly dispatch: readonly DispatchEntry[];
150
+ }
151
+
140
152
  function identityKey(worldId: number, entityKey: number): string {
141
153
  return `${worldId}:${entityKey}`;
142
154
  }
@@ -222,13 +234,6 @@ function ownPointsLinesSnapshot(
222
234
  };
223
235
  }
224
236
 
225
- function withWorld(snapshot: RenderableSnapshot, world: Float32Array): RenderableSnapshot {
226
- return {
227
- ...snapshot,
228
- transform: { ...snapshot.transform, world: new Float32Array(world) },
229
- };
230
- }
231
-
232
237
  function intersects(left: RenderSceneBounds, right: RenderSceneBounds): boolean {
233
238
  return (
234
239
  left.min[0] <= right.max[0] &&
@@ -322,8 +327,8 @@ export class RenderScene {
322
327
  private readonly freeSlots: number[] = [];
323
328
  private readonly slotsByWorld = new Map<number, Map<number, number>>();
324
329
  private readonly slotsByMaterial = new Map<number, Set<number>>();
330
+ private readonly slotsByAsset = new Map<number, Set<number>>();
325
331
  private readonly dirtySinceSubmitted = new Set<number>();
326
- private readonly transformUpdatedSlots: RenderSceneSlot[] = [];
327
332
  private temporalTracking = false;
328
333
  private lastSubmittedVisible = new Set<string>();
329
334
  private submittedEpoch = 0;
@@ -348,13 +353,26 @@ export class RenderScene {
348
353
  return this.orderedSlots.length;
349
354
  }
350
355
 
351
- apply(operations: readonly RenderSceneOperation[]): RenderSceneApplyResult {
352
- if (operations.length === 0) {
356
+ apply(
357
+ operations: readonly RenderSceneOperation[],
358
+ spans: readonly WorldTransformSpan[] = [],
359
+ ): RenderSceneApplyResult {
360
+ if (operations.length === 0 && spans.length === 0) {
353
361
  this.noChangeFrames += 1;
354
362
  this.renderableScans = 0;
355
363
  return this.emptyResult();
356
364
  }
357
365
 
366
+ for (const operation of operations) {
367
+ if (
368
+ operation.kind === 'update' &&
369
+ operation.snapshot !== undefined &&
370
+ (operation.worldId !== operation.snapshot.worldId ||
371
+ operation.entityKey !== operation.snapshot.entityKey)
372
+ ) {
373
+ throw new RangeError('RenderScene update identity must match its source snapshot');
374
+ }
375
+ }
358
376
  this.deltaFrames += 1;
359
377
  this.renderableScans = operations.length;
360
378
  const pendingByWorld = new Map<number, Map<number, PendingIdentity>>();
@@ -377,31 +395,75 @@ export class RenderScene {
377
395
  current: initial?.snapshot,
378
396
  removed: false,
379
397
  contentChanged: false,
398
+ instanceChanged: false,
399
+ transformChanged: false,
380
400
  };
381
401
  entities.set(entityKey, pending);
382
402
  pendingOrder.push(pending);
383
403
  return pending;
384
404
  };
385
405
 
386
- for (const operation of operations) {
387
- if (operation.kind === 'create') {
388
- const { worldId, entityKey } = operation.snapshot;
389
- const pending = pendingFor(worldId, entityKey);
390
- pending.current = ownSnapshot(operation.snapshot);
391
- pending.contentChanged = true;
392
- continue;
406
+ const updateWorld = (pending: PendingIdentity, source: Float32Array, offset: number): void => {
407
+ const target = pending.current?.transform.world;
408
+ if (target === undefined) return;
409
+ for (let lane = 0; lane < 16; lane += 1) {
410
+ const value = source[offset + lane] ?? 0;
411
+ if (target[lane] !== value) {
412
+ target[lane] = value;
413
+ pending.transformChanged = true;
414
+ }
393
415
  }
416
+ };
417
+ for (const operation of operations) {
394
418
  const pending = pendingFor(operation.worldId, operation.entityKey);
395
419
  if (operation.kind === 'remove') {
396
420
  pending.current = undefined;
397
421
  pending.removed = true;
398
422
  continue;
399
423
  }
424
+ if (operation.snapshot !== undefined) {
425
+ const previousMaterials = pending.current?.materials;
426
+ const next = ownSnapshot(operation.snapshot);
427
+ pending.current =
428
+ previousMaterials !== undefined &&
429
+ previousMaterials.length === next.materials.length &&
430
+ previousMaterials.every((material, index) => material === next.materials[index])
431
+ ? { ...next, materials: previousMaterials }
432
+ : next;
433
+ pending.contentChanged = true;
434
+ }
400
435
  if (pending.current === undefined) {
401
436
  ignoredLateUpdates += 1;
402
437
  continue;
403
438
  }
404
- pending.current = withWorld(pending.current, operation.world);
439
+ if (operation.instances !== undefined) {
440
+ pending.current = {
441
+ ...pending.current,
442
+ instances: {
443
+ ...operation.instances,
444
+ transforms: new Float32Array(operation.instances.transforms),
445
+ },
446
+ };
447
+ pending.instanceChanged = true;
448
+ }
449
+ if (operation.world !== undefined) {
450
+ updateWorld(pending, operation.world, 0);
451
+ }
452
+ }
453
+ // The same pending identity merges column updates with all other source
454
+ // edits. There is one publication and one consumer delta per entity.
455
+ for (const span of spans) {
456
+ this.renderableScans += span.entities.length;
457
+ for (let row = 0; row < span.entities.length; row += 1) {
458
+ const entity = span.entities[row];
459
+ if (entity === undefined) continue;
460
+ const pending = pendingFor(span.worldId, entity);
461
+ if (pending.current === undefined) {
462
+ ignoredLateUpdates += 1;
463
+ continue;
464
+ }
465
+ updateWorld(pending, span.worlds, row * 16);
466
+ }
405
467
  }
406
468
 
407
469
  let created = 0;
@@ -411,6 +473,7 @@ export class RenderScene {
411
473
  const createdSlots: RenderSceneSlot[] = [];
412
474
  const updatedSlots: RenderSceneSlot[] = [];
413
475
  const contentUpdatedSlots: RenderSceneSlot[] = [];
476
+ const instanceUpdatedSlots: RenderSceneSlot[] = [];
414
477
  const removedSlots: RenderSceneRecord[] = [];
415
478
  const recreatedSlots: RenderSceneSlot[] = [];
416
479
  for (const pending of pendingOrder) {
@@ -438,24 +501,38 @@ export class RenderScene {
438
501
  recreated += 1;
439
502
  continue;
440
503
  }
441
- const updatedSlot: RenderSceneSlot = {
442
- ...pending.initial,
443
- snapshot: pending.current,
444
- };
445
- this.unindexMaterials(pending.initial);
504
+ if (!pending.contentChanged && !pending.instanceChanged && !pending.transformChanged)
505
+ continue;
506
+ const updatedSlot: RenderSceneSlot =
507
+ pending.current === pending.initial.snapshot
508
+ ? pending.initial
509
+ : { ...pending.initial, snapshot: pending.current };
510
+ if (pending.contentChanged) this.unindexMaterials(pending.initial);
446
511
  this.slots[pending.initial.slot] = updatedSlot;
447
- this.worldBoundsBySlot[updatedSlot.slot] = undefined;
448
- this.indexMaterials(updatedSlot);
449
- this.dirtySinceSubmitted.add(updatedSlot.slot);
512
+ if (pending.current.instances === undefined && !pending.contentChanged) {
513
+ const cached = this.worldBoundsBySlot[updatedSlot.slot];
514
+ this.worldBoundsBySlot[updatedSlot.slot] =
515
+ worldBounds(pending.current, cached ?? undefined) ?? null;
516
+ } else {
517
+ this.worldBoundsBySlot[updatedSlot.slot] = undefined;
518
+ }
519
+ if (pending.contentChanged) this.indexMaterials(updatedSlot);
520
+ if (this.temporalTracking) this.dirtySinceSubmitted.add(updatedSlot.slot);
450
521
  updatedSlots.push(updatedSlot);
451
522
  if (pending.contentChanged) contentUpdatedSlots.push(updatedSlot);
523
+ else if (pending.instanceChanged) instanceUpdatedSlots.push(updatedSlot);
452
524
  updated += 1;
453
525
  }
454
526
 
455
527
  const changed = created + updated + removed + recreated > 0;
456
528
  if (changed) {
457
529
  this.revision += 1;
458
- this.invalidateSnapshots();
530
+ if (
531
+ created + removed + recreated > 0 ||
532
+ pendingOrder.some((pending) => pending.current !== pending.initial?.snapshot)
533
+ ) {
534
+ this.invalidateSnapshots();
535
+ }
459
536
  }
460
537
  return {
461
538
  created,
@@ -466,61 +543,31 @@ export class RenderScene {
466
543
  createdSlots,
467
544
  updatedSlots,
468
545
  contentUpdatedSlots,
546
+ instanceUpdatedSlots,
469
547
  removedSlots,
470
548
  recreatedSlots,
471
549
  resynced: 0,
472
550
  };
473
551
  }
474
552
 
475
- /** Apply version-selected GlobalTransform columns without allocating per-entity operations. */
476
- applyTransformSpans(spans: readonly WorldTransformSpan[]): RenderSceneApplyResult {
477
- this.deltaFrames += 1;
478
- let scanned = 0;
479
- let updated = 0;
480
- let ignoredLateUpdates = 0;
481
- const updatedSlots = this.transformUpdatedSlots;
482
- updatedSlots.length = 0;
483
- for (const span of spans) {
484
- scanned += span.entities.length;
485
- for (let row = 0; row < span.entities.length; row += 1) {
486
- const entity = span.entities[row];
487
- if (entity === undefined) continue;
488
- const slot = this.lookup(span.worldId, entity);
489
- if (slot === undefined) {
490
- ignoredLateUpdates += 1;
491
- continue;
492
- }
493
- const target = slot.snapshot.transform.world;
494
- const sourceStart = row * 16;
495
- for (let index = 0; index < 16; index += 1) {
496
- target[index] = span.worlds[sourceStart + index] ?? target[index] ?? 0;
497
- }
498
- if (slot.snapshot.instances === undefined) {
499
- const cached = this.worldBoundsBySlot[slot.slot];
500
- const bounds = worldBounds(slot.snapshot, cached ?? undefined);
501
- this.worldBoundsBySlot[slot.slot] = bounds ?? null;
502
- } else {
503
- this.worldBoundsBySlot[slot.slot] = undefined;
504
- }
505
- if (this.temporalTracking) this.dirtySinceSubmitted.add(slot.slot);
506
- updatedSlots.push(slot);
507
- updated += 1;
553
+ /** Rebind positional World indices while retaining stable slot and submission history. */
554
+ remapWorlds(indices: readonly number[]): void {
555
+ this.slotsByWorld.clear();
556
+ for (const slot of this.slots) {
557
+ if (slot === undefined) continue;
558
+ this.instanceBoundsCache.invalidate(slot.entityKey, slot.worldId);
559
+ const mapped = indices[slot.worldId];
560
+ const worldId = mapped === undefined || mapped < 0 ? -1 - slot.worldId : mapped;
561
+ const remapped = { ...slot, worldId, snapshot: { ...slot.snapshot, worldId } };
562
+ this.slots[slot.slot] = remapped;
563
+ let entities = this.slotsByWorld.get(worldId);
564
+ if (entities === undefined) {
565
+ entities = new Map<number, number>();
566
+ this.slotsByWorld.set(worldId, entities);
508
567
  }
568
+ entities.set(slot.entityKey, slot.slot);
509
569
  }
510
- this.renderableScans = scanned;
511
- if (updated > 0) this.revision += 1;
512
- return {
513
- created: 0,
514
- updated,
515
- removed: 0,
516
- recreated: 0,
517
- ignoredLateUpdates,
518
- createdSlots: [],
519
- updatedSlots,
520
- removedSlots: [],
521
- recreatedSlots: [],
522
- resynced: 0,
523
- };
570
+ this.invalidateSnapshots();
524
571
  }
525
572
 
526
573
  /** Reconcile the current identity set while retaining slots for survivors. */
@@ -545,7 +592,7 @@ export class RenderScene {
545
592
  for (const snapshot of desired.values()) {
546
593
  const existing = this.lookup(snapshot.worldId, snapshot.entityKey);
547
594
  if (existing === undefined) {
548
- const created = this.allocate(snapshot);
595
+ const created = this.allocate(ownSnapshot(snapshot));
549
596
  this.dirtySinceSubmitted.add(created.slot);
550
597
  continue;
551
598
  }
@@ -647,7 +694,6 @@ export class RenderScene {
647
694
  }
648
695
  this.lastSubmittedVisible = new Set(capture.visible);
649
696
  this.submittedEpoch += 1;
650
- this.invalidateSnapshots();
651
697
  return {
652
698
  epoch: this.submittedEpoch,
653
699
  slots: capture.slots.map((update) => update.slot),
@@ -712,6 +758,10 @@ export class RenderScene {
712
758
  return records;
713
759
  }
714
760
 
761
+ get contentRevision(): number {
762
+ return this.revision;
763
+ }
764
+
715
765
  inspect(): RenderSceneInspection {
716
766
  return {
717
767
  records: this.slotsSnapshot().map(({ slot, generation, worldId, entityKey }) => ({
@@ -729,6 +779,7 @@ export class RenderScene {
729
779
  fullRebuilds: this.fullRebuilds,
730
780
  resyncs: this.resyncs,
731
781
  lastResyncReason: this.lastResyncReason,
782
+ instanceBoundsCache: this.instanceBoundsCache.inspect(),
732
783
  };
733
784
  }
734
785
 
@@ -741,6 +792,8 @@ export class RenderScene {
741
792
  ignoredLateUpdates: 0,
742
793
  createdSlots: [],
743
794
  updatedSlots: [],
795
+ contentUpdatedSlots: [],
796
+ instanceUpdatedSlots: [],
744
797
  removedSlots: [],
745
798
  recreatedSlots: [],
746
799
  resynced: 0,
@@ -761,7 +814,7 @@ export class RenderScene {
761
814
  generation,
762
815
  worldId: snapshot.worldId,
763
816
  entityKey: snapshot.entityKey,
764
- snapshot: ownSnapshot(snapshot),
817
+ snapshot,
765
818
  };
766
819
  let previousWorld = this.previousWorldBySlot[slot];
767
820
  if (previousWorld === undefined) {
@@ -796,7 +849,23 @@ export class RenderScene {
796
849
  this.freeSlots.push(record.slot);
797
850
  }
798
851
 
852
+ addSharedRefConsumers(worldId: number, handle: number, target: Set<number>): void {
853
+ for (const index of [this.slotsByAsset, this.slotsByMaterial]) {
854
+ for (const slot of index.get(handle) ?? []) {
855
+ const record = this.slots[slot];
856
+ if (record?.worldId === worldId) target.add(record.entityKey);
857
+ }
858
+ }
859
+ }
860
+
799
861
  private indexMaterials(record: RenderSceneSlot): void {
862
+ const asset = record.snapshot.assetHandle;
863
+ let assetSlots = this.slotsByAsset.get(asset);
864
+ if (assetSlots === undefined) {
865
+ assetSlots = new Set<number>();
866
+ this.slotsByAsset.set(asset, assetSlots);
867
+ }
868
+ assetSlots.add(record.slot);
800
869
  for (const material of record.snapshot.materials) {
801
870
  const handle = material.materialHandle ?? 0;
802
871
  let slots = this.slotsByMaterial.get(handle);
@@ -809,6 +878,10 @@ export class RenderScene {
809
878
  }
810
879
 
811
880
  private unindexMaterials(record: RenderSceneSlot): void {
881
+ const asset = record.snapshot.assetHandle;
882
+ const assetSlots = this.slotsByAsset.get(asset);
883
+ assetSlots?.delete(record.slot);
884
+ if (assetSlots?.size === 0) this.slotsByAsset.delete(asset);
812
885
  for (const material of record.snapshot.materials) {
813
886
  const handle = material.materialHandle ?? 0;
814
887
  const slots = this.slotsByMaterial.get(handle);
@@ -914,9 +987,8 @@ export interface PersistentRenderSceneOptions {
914
987
  readonly onGpuError?: ((error: RhiError) => void) | undefined;
915
988
  readonly onSharedRefMutation?: ((worldId: number, handle: number) => void) | undefined;
916
989
  readonly configuredQueryBudget?: number | undefined;
917
- /** Rebuildable projection of World-owned instance matrices. */
918
- readonly instanceCollections?: InstanceProjectionStore | undefined;
919
- readonly getSkinPaletteAllocator?: (() => SkinPaletteAllocator | null) | undefined;
990
+ /** Producer-owned instance collections consumed by the same source update flow. */
991
+ readonly instanceCollections?: InstanceCollectionStore | undefined;
920
992
  }
921
993
 
922
994
  export interface PersistentGpuDrivenState {
@@ -925,6 +997,7 @@ export interface PersistentGpuDrivenState {
925
997
  readonly slots: readonly RenderSceneSlot[];
926
998
  /** Stable renderer-local World keys aligned with RenderSceneSlot.worldId. */
927
999
  readonly worldKeys?: readonly number[];
1000
+ readonly structureMetrics?: GpuDrivenStructureMetrics;
928
1001
  }
929
1002
 
930
1003
  export interface CpuDirectViewInput {
@@ -993,19 +1066,10 @@ type PersistentGpuSceneInspection = PersistentRenderSceneInspection['gpu'];
993
1066
  interface PersistentCompositionEntry {
994
1067
  readonly token: object;
995
1068
  readonly worlds: readonly World[];
996
- readonly cameraOwner: number;
997
- readonly resourceOwner: number;
998
1069
  readonly projection: RenderScene;
999
1070
  readonly topology: BatchTopology;
1000
1071
  readonly transmissionDemand: PersistentTransmissionDemandProjection;
1001
- structure: Pick<
1002
- ExtractedFrame,
1003
- | 'dispatch'
1004
- | 'visibilityStats'
1005
- | 'visibilitySnapshots'
1006
- | 'featureVisibilitySnapshots'
1007
- | 'hiddenEntityReports'
1008
- >;
1072
+ hiddenEntityReports: ExtractedFrame['hiddenEntityReports'];
1009
1073
  /** Stable renderer-local World keys; unlike RenderableSnapshot.worldId they survive reorder. */
1010
1074
  readonly worldKeys: readonly number[];
1011
1075
  readonly readVersions: RenderReadVersion[];
@@ -1014,53 +1078,22 @@ interface PersistentCompositionEntry {
1014
1078
  readonly renderChangeQueries: readonly ReadonlyMap<number, Query>[];
1015
1079
  readonly dispatchBySlot: Map<number, readonly DispatchEntry[]>;
1016
1080
  dispatchRevision: number;
1017
- dispatchCache:
1018
- | { readonly revision: number; readonly value: readonly DispatchEntry[] }
1019
- | undefined;
1081
+ dispatchCache: { readonly revision: number; readonly value: DispatchEntry[] } | undefined;
1020
1082
  readonly skinConsumersByJoint: Map<string, Set<number>>;
1021
1083
  /** Renderable identities whose partial producer still needs a successful publish. */
1022
1084
  readonly pendingRenderableEntitiesByWorld: Set<number>[];
1023
1085
  /** Slots retained for recovery but excluded from direct and indirect submission. */
1024
1086
  readonly unavailableSlots: Set<number>;
1025
1087
  catalogEpoch: number;
1088
+ instanceEpoch: number;
1026
1089
  readonly probeProjection: ProbeBlendSceneProjection;
1027
1090
  temporalDemanded: boolean;
1028
1091
  gpuDrivenSceneRequired: boolean;
1029
1092
  }
1030
1093
 
1031
- const RENDER_RELEVANT_COMPONENT_IDS = new Set([
1032
- componentId(GlobalTransform),
1033
- componentId(ChildOf),
1034
- componentId(MeshFilter),
1035
- componentId(MeshRenderer),
1036
- componentId(Instances),
1037
- componentId(SpriteInstances),
1038
- componentId(SpriteRegionOverride),
1039
- componentId(Skin),
1040
- componentId(Layer),
1041
- componentId(Visibility),
1042
- componentId(MorphWeights),
1043
- componentId(Points),
1044
- componentId(Lines),
1045
- componentId(SortKey),
1046
- componentId(Camera),
1047
- componentId(MotionBlur),
1048
- componentId(DirectionalLight),
1049
- componentId(LightProbe),
1050
- componentId(PointLight),
1051
- componentId(PointLightShadow),
1052
- componentId(SpotLight),
1053
- componentId(Skylight),
1054
- componentId(SkyboxBackground),
1055
- componentId(PostProcessParams),
1056
- componentId(ReflectionProbe),
1057
- componentId(VolumetricFog),
1058
- ]);
1059
-
1060
- // These components can change the retained renderable source. Other relevant
1061
- // components affect frame resources and still require a composition rebuild;
1062
- // they must not cause a renderable-only partial extraction to silently retain
1063
- // stale lights, cameras, or environment facts.
1094
+ // Retained geometry consumes these component changes. Camera, lighting and
1095
+ // environment facts are freshly extracted by deriveFramePlan on every draw;
1096
+ // their edits must not recreate unrelated geometry or its submission history.
1064
1097
  const RENDERABLE_SOURCE_COMPONENTS = [
1065
1098
  GlobalTransform,
1066
1099
  ChildOf,
@@ -1105,6 +1138,7 @@ function createGlobalTransformChangeQuery(world: World): GlobalTransformChangeQu
1105
1138
  function createRenderChangeQueries(world: World): ReadonlyMap<number, Query> {
1106
1139
  const queries = new Map<number, Query>();
1107
1140
  for (const component of RENDERABLE_SOURCE_COMPONENTS) {
1141
+ if (component === GlobalTransform) continue;
1108
1142
  const result = world.query({ read: [component], changed: [component] });
1109
1143
  if (!result.ok) throw result.error;
1110
1144
  const query = result.value as Query;
@@ -1190,7 +1224,7 @@ function dispatchForProjection(
1190
1224
  dispatchBySlot: ReadonlyMap<number, readonly DispatchEntry[]>,
1191
1225
  projection: RenderScene,
1192
1226
  unavailableSlots: ReadonlySet<number>,
1193
- ): readonly DispatchEntry[] {
1227
+ ): DispatchEntry[] {
1194
1228
  const dispatch: DispatchEntry[] = [];
1195
1229
  const renderables = projection.materialize();
1196
1230
  for (let renderableIndex = 0; renderableIndex < renderables.length; renderableIndex += 1) {
@@ -1200,7 +1234,9 @@ function dispatchForProjection(
1200
1234
  if (slot === undefined) continue;
1201
1235
  if (unavailableSlots.has(slot.slot)) continue;
1202
1236
  for (const entry of dispatchBySlot.get(slot.slot) ?? []) {
1203
- dispatch.push({ ...entry, renderableIndex });
1237
+ dispatch.push(
1238
+ renderableIndex === entry.renderableIndex ? entry : { ...entry, renderableIndex },
1239
+ );
1204
1240
  }
1205
1241
  }
1206
1242
  return dispatch;
@@ -1212,39 +1248,29 @@ function topologyDeltaForAvailable(
1212
1248
  newlyUnavailable: readonly RenderSceneSlot[],
1213
1249
  ): RenderSceneApplyResult {
1214
1250
  const updatedSlots = delta.updatedSlots.filter((slot) => !unavailableSlots.has(slot.slot));
1251
+ const unavailableUpdates = newlyUnavailable.map((slot) => ({
1252
+ ...slot,
1253
+ snapshot: { ...slot.snapshot, gpuDrivenDraws: [] },
1254
+ }));
1215
1255
  const contentUpdatedSlots = delta.contentUpdatedSlots?.filter(
1216
1256
  (slot) => !unavailableSlots.has(slot.slot),
1217
1257
  );
1258
+ const instanceUpdatedSlots = delta.instanceUpdatedSlots?.filter(
1259
+ (slot) => !unavailableSlots.has(slot.slot),
1260
+ );
1261
+ const contentBySlot =
1262
+ contentUpdatedSlots === undefined
1263
+ ? undefined
1264
+ : new Map(contentUpdatedSlots.map((slot) => [slot.slot, slot]));
1265
+ for (const slot of unavailableUpdates) contentBySlot?.set(slot.slot, slot);
1218
1266
  return {
1219
1267
  ...delta,
1220
- updatedSlots: [
1221
- ...updatedSlots,
1222
- ...newlyUnavailable.map((slot) => ({
1223
- ...slot,
1224
- snapshot: { ...slot.snapshot, gpuDrivenDraws: [] },
1225
- })),
1226
- ],
1227
- ...(contentUpdatedSlots === undefined ? {} : { contentUpdatedSlots }),
1268
+ updatedSlots: [...updatedSlots, ...unavailableUpdates],
1269
+ ...(contentBySlot === undefined ? {} : { contentUpdatedSlots: [...contentBySlot.values()] }),
1270
+ ...(instanceUpdatedSlots === undefined ? {} : { instanceUpdatedSlots }),
1228
1271
  };
1229
1272
  }
1230
1273
 
1231
- function addSharedRefConsumers(
1232
- projection: RenderScene,
1233
- worldId: number,
1234
- handle: number,
1235
- target: Set<number>,
1236
- ): void {
1237
- for (const slot of projection.slotsSnapshot()) {
1238
- if (
1239
- slot.worldId === worldId &&
1240
- (slot.snapshot.assetHandle === handle ||
1241
- slot.snapshot.materials.some((material) => material.materialHandle === handle))
1242
- ) {
1243
- target.add(slot.entityKey);
1244
- }
1245
- }
1246
- }
1247
-
1248
1274
  function frameDemandsTemporal(frame: ExtractedFrame): boolean {
1249
1275
  const camera = frame.cameras[0];
1250
1276
  return camera?.antialias === 'taa' || (camera?.motionBlur?.shutterAngle ?? 0) > 0;
@@ -1295,6 +1321,18 @@ function cullPersistentFrame(
1295
1321
  ) => worldBounds(snapshot),
1296
1322
  ): ExtractedFrame {
1297
1323
  const planes = cameraFrusta(frame.cameras);
1324
+ // Shadow casters are not constrained by the display camera. Keep the
1325
+ // primary camera cull intact, but admit a caster that is outside every
1326
+ // camera frustum when its bounds intersect one of the active light-space
1327
+ // frusta. This is especially important for point-light cube faces: the
1328
+ // caster can be behind the camera while still occluding the receiver.
1329
+ const casterIndices = new Set(
1330
+ frame.dispatch
1331
+ .filter((entry) => entry.tags.LightMode === 'ShadowCaster')
1332
+ .map((entry) => entry.renderableIndex),
1333
+ );
1334
+ const shadowFrusta = casterIndices.size === 0 ? [] : buildShadowFrusta(frame.lights);
1335
+ const shadowOnly = new Set<number>();
1298
1336
  const visible: RenderableSnapshot[] = [];
1299
1337
  if (cullProjectedIndexScratch.length < candidates.length) {
1300
1338
  cullProjectedIndexScratch = new Int32Array(candidates.length);
@@ -1313,6 +1351,7 @@ function cullPersistentFrame(
1313
1351
  continue;
1314
1352
  }
1315
1353
  let isVisible = true;
1354
+ let isShadowVisible = false;
1316
1355
  const candidateBounds = boundsOf(candidate, index);
1317
1356
  if (candidateBounds !== undefined) {
1318
1357
  total += 1;
@@ -1332,10 +1371,16 @@ function cullPersistentFrame(
1332
1371
  break;
1333
1372
  }
1334
1373
  }
1374
+ if (!isVisible && casterIndices.has(index)) {
1375
+ isShadowVisible = shadowFrusta.some((lightPlanes) =>
1376
+ frustum.intersectsBox(lightPlanes, worldAabb as box3.Box3Like),
1377
+ );
1378
+ }
1335
1379
  }
1336
1380
  if (!isVisible) {
1337
1381
  culled += 1;
1338
- continue;
1382
+ if (!isShadowVisible) continue;
1383
+ shadowOnly.add(index);
1339
1384
  }
1340
1385
  projectedIndex[index] = visible.length;
1341
1386
  // The culling candidate is the persistent projection snapshot used for
@@ -1349,9 +1394,15 @@ function cullPersistentFrame(
1349
1394
  const dispatch: DispatchEntry[] = [];
1350
1395
  for (const entry of frame.dispatch) {
1351
1396
  const candidateIndex = entry.renderableIndex;
1397
+ // Shadow-only candidates must never enter the main forward/transparent
1398
+ // dispatch. Their ShadowCaster entries remain available to the depth
1399
+ // passes, which consume the same validated ordered list.
1400
+ if (shadowOnly.has(candidateIndex) && entry.tags.LightMode !== 'ShadowCaster') continue;
1352
1401
  const renderableIndex = projectedIndex[candidateIndex];
1353
1402
  if (renderableIndex === undefined || renderableIndex < 0) continue;
1354
- dispatch.push({ ...entry, renderableIndex });
1403
+ dispatch.push(
1404
+ renderableIndex === entry.renderableIndex ? entry : { ...entry, renderableIndex },
1405
+ );
1355
1406
  }
1356
1407
  return {
1357
1408
  ...frame,
@@ -1391,12 +1442,15 @@ export class PersistentRenderScene {
1391
1442
  private readonly materialSnapshotCaches: MaterialSnapshotCachesByWorld = new WeakMap();
1392
1443
  private fullRebuilds = 0;
1393
1444
  private worldEntitiesScanned = 0;
1445
+ private sceneTableUploadBytes = 0;
1394
1446
  private noChangeFrames = 0;
1395
1447
  private deltaFrames = 0;
1396
1448
  private transformUpdates = 0;
1397
1449
  private lastResyncReason: RenderSceneResyncReason | undefined;
1398
1450
  private composition: PersistentCompositionEntry | undefined;
1399
1451
  private gpuScene: GpuScene | undefined;
1452
+ private readonly retiredGpuScenes = new Set<GpuScene>();
1453
+ private shadowProjection: PersistentShadowCasterProjection | undefined;
1400
1454
  private gpuOwner: object | undefined;
1401
1455
  private gpuDevice: RhiDevice | undefined;
1402
1456
  private gpuStatus: 'inactive' | 'unsupported' | 'resident' | 'rebuild-pending' | 'error' =
@@ -1424,6 +1478,7 @@ export class PersistentRenderScene {
1424
1478
  }
1425
1479
  | undefined;
1426
1480
  private visibilityProjectionRevision = 0;
1481
+ private projectedIndexScratch = new Int32Array(0);
1427
1482
  private lastActiveEntityKeys: ReadonlySet<number> | undefined;
1428
1483
  /** Stable renderer-world/entity lookup; rebuilt only with composition topology. */
1429
1484
  private readonly stableSlotByEntity = new Map<number, RenderSceneSlot>();
@@ -1473,6 +1528,10 @@ export class PersistentRenderScene {
1473
1528
  return this.composition?.projection.slotsSnapshot() ?? [];
1474
1529
  }
1475
1530
 
1531
+ shadowCasterProjection(): PersistentShadowCasterProjection | undefined {
1532
+ return this.shadowProjection;
1533
+ }
1534
+
1476
1535
  /** Stable renderer-world/entity lookup shared by query and final projection paths. */
1477
1536
  compositionSlotByStableEntity(): ReadonlyMap<number, RenderSceneSlot> {
1478
1537
  return this.stableSlotByEntity;
@@ -1517,7 +1576,7 @@ export class PersistentRenderScene {
1517
1576
  renderable.lods.length === 0 ||
1518
1577
  bounds === undefined ||
1519
1578
  bounds.length < 6 ||
1520
- [...bounds].some((value) => !Number.isFinite(value))
1579
+ !bounds.every(Number.isFinite)
1521
1580
  ) {
1522
1581
  continue;
1523
1582
  }
@@ -1594,15 +1653,19 @@ export class PersistentRenderScene {
1594
1653
  ) {
1595
1654
  return cached.projection;
1596
1655
  }
1597
- const projectedIndex = new Int32Array(renderables.length);
1598
- projectedIndex.fill(-1);
1656
+ if (this.projectedIndexScratch.length < renderables.length) {
1657
+ this.projectedIndexScratch = new Int32Array(renderables.length);
1658
+ }
1659
+ const projectedIndex = this.projectedIndexScratch;
1660
+ projectedIndex.fill(-1, 0, renderables.length);
1599
1661
  const projected: RenderableSnapshot[] = [];
1600
1662
  const activeEntityKeys = new Set<number>();
1601
1663
  let suppressed = 0;
1602
1664
  for (let index = 0; index < renderables.length; index += 1) {
1603
1665
  const renderable = renderables[index];
1604
1666
  if (renderable === undefined) continue;
1605
- const slot = this.stableSlotByEntity.get(this.stableEntityKey(worlds, renderable));
1667
+ const stableEntity = this.stableEntityKey(worlds, renderable);
1668
+ const slot = this.stableSlotByEntity.get(stableEntity);
1606
1669
  let draw = true;
1607
1670
  if (slot !== undefined && (renderable.lods?.length ?? 0) > 0) {
1608
1671
  const primitive = primitiveKey({
@@ -1613,9 +1676,7 @@ export class PersistentRenderScene {
1613
1676
  });
1614
1677
  const bounds = renderable.localAabb;
1615
1678
  const validBounds =
1616
- bounds !== undefined &&
1617
- bounds.length >= 6 &&
1618
- [...bounds].every((value) => Number.isFinite(value));
1679
+ bounds !== undefined && bounds.length >= 6 && bounds.every(Number.isFinite);
1619
1680
  draw = decideVisibility({
1620
1681
  authorVisible: renderable.authorVisible !== false,
1621
1682
  validBounds,
@@ -1631,13 +1692,15 @@ export class PersistentRenderScene {
1631
1692
  }
1632
1693
  projectedIndex[index] = projected.length;
1633
1694
  projected.push(renderable);
1634
- activeEntityKeys.add(this.stableEntityKey(worlds, renderable));
1695
+ activeEntityKeys.add(stableEntity);
1635
1696
  }
1636
1697
  const projectedDispatch: DispatchEntry[] = [];
1637
1698
  for (const entry of dispatch) {
1638
1699
  const renderableIndex = projectedIndex[entry.renderableIndex];
1639
1700
  if (renderableIndex === undefined || renderableIndex < 0) continue;
1640
- projectedDispatch.push({ ...entry, renderableIndex });
1701
+ projectedDispatch.push(
1702
+ renderableIndex === entry.renderableIndex ? entry : { ...entry, renderableIndex },
1703
+ );
1641
1704
  }
1642
1705
  const projection = {
1643
1706
  renderables: Object.freeze(projected),
@@ -1725,6 +1788,7 @@ export class PersistentRenderScene {
1725
1788
  leases?: readonly RenderReadLease[],
1726
1789
  ): ExtractedFrame {
1727
1790
  this.worldEntitiesScanned = 0;
1791
+ this.sceneTableUploadBytes = 0;
1728
1792
  const entry = this.composition;
1729
1793
  const sameWorldSet =
1730
1794
  entry !== undefined &&
@@ -1749,15 +1813,12 @@ export class PersistentRenderScene {
1749
1813
  leaseSetPreserved;
1750
1814
  if (
1751
1815
  entry === undefined ||
1752
- entry.catalogEpoch !== catalogEpoch ||
1753
- entry.cameraOwner !== owner.cameraOwner ||
1754
- entry.resourceOwner !== owner.resourceOwner ||
1755
1816
  entry.worlds.length !== worlds.length ||
1756
1817
  entry.worlds.some((world, index) => world !== worlds[index]) ||
1757
1818
  leases === undefined ||
1758
1819
  leases.length !== worlds.length
1759
1820
  ) {
1760
- return this.rebuildComposition(
1821
+ return this.reconcileComposition(
1761
1822
  worlds,
1762
1823
  owner,
1763
1824
  catalogEpoch,
@@ -1766,14 +1827,33 @@ export class PersistentRenderScene {
1766
1827
  preserveVisibilityOnReorder,
1767
1828
  );
1768
1829
  }
1769
- let requiresRebuild = false;
1830
+ let requiresSourceRefresh = false;
1770
1831
  const changedEntitiesByWorld = worlds.map(
1771
1832
  (_, worldId) => new Set(entry.pendingRenderableEntitiesByWorld[worldId] ?? []),
1772
1833
  );
1834
+ const contentChangedByWorld = worlds.map(
1835
+ (_, worldId) => new Set(entry.pendingRenderableEntitiesByWorld[worldId] ?? []),
1836
+ );
1837
+ if (entry.catalogEpoch !== catalogEpoch) {
1838
+ for (const slot of entry.projection.slotsSnapshot()) {
1839
+ changedEntitiesByWorld[slot.worldId]?.add(slot.entityKey);
1840
+ contentChangedByWorld[slot.worldId]?.add(slot.entityKey);
1841
+ }
1842
+ }
1843
+ const instanceEpoch = this.options.instanceCollections?.epoch ?? 0;
1844
+ if (entry.instanceEpoch !== instanceEpoch) {
1845
+ for (const slot of entry.projection.slotsSnapshot()) {
1846
+ const id = slot.snapshot.instances?.collectionId;
1847
+ if (id === undefined) continue;
1848
+ const current = this.options.instanceCollections?.inspect(id);
1849
+ if (!current?.ok || current.value.revision !== slot.snapshot.instances?.revision) {
1850
+ changedEntitiesByWorld[slot.worldId]?.add(slot.entityKey);
1851
+ }
1852
+ }
1853
+ }
1773
1854
  const nextReadVersions = entry.readVersions.slice();
1774
1855
  const structureChangedWorlds = new Set<number>();
1775
1856
  const structuralComponentIdsByWorld = worlds.map(() => new Set<number>());
1776
- let refreshStructureFacts = false;
1777
1857
  let currentRenderableEntitiesByWorld: readonly (ReadonlySet<number> | undefined)[] | undefined;
1778
1858
  const removedSlots: RenderSceneSlot[] = [];
1779
1859
  for (let worldId = 0; worldId < worlds.length; worldId += 1) {
@@ -1781,24 +1861,23 @@ export class PersistentRenderScene {
1781
1861
  if (world === undefined) continue;
1782
1862
  const lease = leases?.[worldId];
1783
1863
  if (lease === undefined || entry.leaseIdentities[worldId] !== lease.worldIdentity) {
1784
- requiresRebuild = true;
1864
+ requiresSourceRefresh = true;
1785
1865
  break;
1786
1866
  }
1787
1867
  const version = entry.readVersions[worldId];
1788
1868
  if (version === undefined) {
1789
- requiresRebuild = true;
1869
+ requiresSourceRefresh = true;
1790
1870
  break;
1791
1871
  }
1792
1872
  let read: ReturnType<RenderReadLease['readChanges']>;
1793
1873
  try {
1794
1874
  read = lease.readChanges(version);
1795
1875
  } catch {
1796
- requiresRebuild = true;
1876
+ requiresSourceRefresh = true;
1797
1877
  break;
1798
1878
  }
1799
1879
  if (read.status === 'rebuild') {
1800
1880
  structureChangedWorlds.add(worldId);
1801
- refreshStructureFacts = true;
1802
1881
  }
1803
1882
  const changes = read.world;
1804
1883
  const shared = read.sharedRefs;
@@ -1806,56 +1885,80 @@ export class PersistentRenderScene {
1806
1885
  for (const record of shared.records) {
1807
1886
  this.options.onSharedRefMutation?.(worldId, record.handle);
1808
1887
  const changed = changedEntitiesByWorld[worldId];
1809
- if (changed !== undefined)
1810
- addSharedRefConsumers(entry.projection, worldId, record.handle, changed);
1888
+ const content = contentChangedByWorld[worldId];
1889
+ if (changed !== undefined && content !== undefined) {
1890
+ entry.projection.addSharedRefConsumers(worldId, record.handle, content);
1891
+ for (const entity of content) changed.add(entity);
1892
+ }
1811
1893
  }
1812
1894
  }
1813
1895
  for (const changedComponentId of changes.changedComponentIds) {
1814
1896
  if (read.status === 'rebuild') {
1815
1897
  structuralComponentIdsByWorld[worldId]?.add(changedComponentId);
1816
1898
  }
1817
- if (!RENDER_RELEVANT_COMPONENT_IDS.has(changedComponentId)) continue;
1818
- if (!RENDERABLE_SOURCE_COMPONENT_IDS.has(changedComponentId)) {
1819
- requiresRebuild = true;
1820
- break;
1821
- }
1899
+ if (!RENDERABLE_SOURCE_COMPONENT_IDS.has(changedComponentId)) continue;
1822
1900
  const changed = changedEntitiesByWorld[worldId];
1823
1901
  if (changed === undefined) continue;
1824
- if (VISIBILITY_DEPENDENCY_COMPONENT_IDS.has(changedComponentId)) {
1825
- refreshStructureFacts = true;
1826
- // Visibility and parent changes affect the effective state of a
1827
- // subtree. The normal update path evaluates the current rows; the
1828
- // set is deliberately allowed to be broad, while empty work still
1829
- // exits naturally without a candidate loop.
1830
- for (const entity of collectRenderableEntities(world)) changed.add(entity);
1831
- continue;
1832
- }
1833
- if (changedComponentId === componentId(GlobalTransform)) {
1834
- const query = entry.renderChangeQueries[worldId]?.get(changedComponentId);
1835
- for (const row of query ?? []) {
1836
- const consumers = entry.skinConsumersByJoint.get(
1837
- skinJointKey(worldId, row.entity as number),
1838
- );
1839
- for (const consumer of consumers ?? []) changed.add(consumer);
1840
- }
1841
- continue;
1842
- }
1902
+ const otherChanged = contentChangedByWorld[worldId];
1843
1903
  const query = entry.renderChangeQueries[worldId]?.get(changedComponentId);
1844
1904
  if (query !== undefined) {
1845
- for (const row of query) changed.add(row.entity as number);
1905
+ for (const row of query) {
1906
+ const entity = row.entity as number;
1907
+ if (VISIBILITY_DEPENDENCY_COMPONENT_IDS.has(changedComponentId)) {
1908
+ const affected = collectSubtree(world, row.entity);
1909
+ for (const dependent of affected) {
1910
+ if (
1911
+ !entry.projection.has(worldId, dependent) &&
1912
+ !world.hasComponent(dependent as EntityHandle, MeshRenderer)
1913
+ )
1914
+ continue;
1915
+ changed.add(dependent);
1916
+ otherChanged?.add(dependent);
1917
+ }
1918
+ } else {
1919
+ changed.add(entity);
1920
+ if (changedComponentId !== componentId(Instances)) otherChanged?.add(entity);
1921
+ }
1922
+ }
1846
1923
  }
1847
1924
  }
1848
- if (requiresRebuild) break;
1925
+ if (requiresSourceRefresh) break;
1849
1926
  nextReadVersions[worldId] = read.version;
1850
1927
  }
1851
- if (requiresRebuild) {
1852
- return this.rebuildComposition(worlds, owner, catalogEpoch, buildCandidateFrame, leases);
1928
+ if (requiresSourceRefresh) {
1929
+ return this.reconcileComposition(worlds, owner, catalogEpoch, buildCandidateFrame, leases);
1853
1930
  }
1854
1931
 
1932
+ const transformSpans: WorldTransformSpan[] = [];
1933
+ for (let worldId = 0; worldId < entry.transformQueries.length; worldId += 1) {
1934
+ const query = entry.transformQueries[worldId];
1935
+ if (query === undefined) continue;
1936
+ for (const span of query.spans().unwrap()) {
1937
+ const changedWorlds = span.get(GlobalTransform).world;
1938
+ if (!(changedWorlds instanceof Float32Array)) {
1939
+ requiresSourceRefresh = true;
1940
+ break;
1941
+ }
1942
+ transformSpans.push({ worldId, entities: span.entities, worlds: changedWorlds });
1943
+ for (const entity of span.entities) {
1944
+ for (const consumer of entry.skinConsumersByJoint.get(skinJointKey(worldId, entity)) ??
1945
+ []) {
1946
+ changedEntitiesByWorld[worldId]?.add(consumer);
1947
+ contentChangedByWorld[worldId]?.add(consumer);
1948
+ }
1949
+ }
1950
+ }
1951
+ if (requiresSourceRefresh) break;
1952
+ }
1953
+ if (requiresSourceRefresh) {
1954
+ return this.reconcileComposition(worlds, owner, catalogEpoch, buildCandidateFrame, leases);
1955
+ }
1855
1956
  if (structureChangedWorlds.size > 0) {
1856
1957
  this.visibilityFacets.clear();
1857
1958
  this.invalidateActiveEntityRevision();
1858
- const currentByWorld = worlds.map((world) => collectRenderableEntities(world));
1959
+ const currentByWorld = worlds.map((world, worldId) =>
1960
+ structureChangedWorlds.has(worldId) ? collectRenderableEntities(world) : undefined,
1961
+ );
1859
1962
  currentRenderableEntitiesByWorld = currentByWorld;
1860
1963
  for (let worldId = 0; worldId < currentByWorld.length; worldId += 1) {
1861
1964
  const world = worlds[worldId];
@@ -1873,7 +1976,10 @@ export class PersistentRenderScene {
1873
1976
  if (indexedWorldId !== worldId) continue;
1874
1977
  const jointEntity = Number(jointKey.slice(separator + 1));
1875
1978
  if (world.hasComponent(jointEntity as EntityHandle, GlobalTransform)) continue;
1876
- for (const consumer of consumers) changed.add(consumer);
1979
+ for (const consumer of consumers) {
1980
+ changed.add(consumer);
1981
+ contentChangedByWorld[worldId]?.add(consumer);
1982
+ }
1877
1983
  }
1878
1984
  for (const entity of current) {
1879
1985
  if (entry.projection.slot(worldId, entity) === undefined) changed.add(entity);
@@ -1889,26 +1995,14 @@ export class PersistentRenderScene {
1889
1995
  const membershipChanged = sourceMembershipChanged(world, slot);
1890
1996
  if (scanAllMembership || membershipChanged) {
1891
1997
  changed.add(slot.entityKey);
1892
- if (
1893
- slot.snapshot.instances !== undefined &&
1894
- !world.hasComponent(slot.entityKey as EntityHandle, Instances)
1895
- ) {
1896
- this.options.instanceCollections?.release(world, slot.entityKey);
1897
- }
1898
- if (
1899
- slot.snapshot.skin !== undefined &&
1900
- !world.hasComponent(slot.entityKey as EntityHandle, Skin)
1901
- ) {
1902
- this.options
1903
- .getSkinPaletteAllocator?.()
1904
- ?.releaseSliceFor(`${world.identity}:${slot.entityKey}`);
1905
- }
1998
+ contentChangedByWorld[worldId]?.add(slot.entityKey);
1906
1999
  }
1907
2000
  }
1908
2001
  }
1909
2002
  for (const slot of entry.projection.slotsSnapshot()) {
1910
2003
  if (slot.worldId !== worldId || current.has(slot.entityKey)) continue;
1911
2004
  removedSlots.push(slot);
2005
+ changedEntitiesByWorld[worldId]?.add(slot.entityKey);
1912
2006
  }
1913
2007
  this.worldEntitiesScanned += current.size;
1914
2008
  }
@@ -1925,7 +2019,7 @@ export class PersistentRenderScene {
1925
2019
 
1926
2020
  let changedDelta: RenderSceneApplyResult | undefined;
1927
2021
  const hasChangedEntities = changedEntitiesByWorld.some((entities) => entities.size > 0);
1928
- if (hasChangedEntities || removedSlots.length > 0) {
2022
+ if (hasChangedEntities || removedSlots.length > 0 || transformSpans.length > 0) {
1929
2023
  const operations: RenderSceneOperation[] = [];
1930
2024
  const removedDemand: TransmissionDemandOperation[] = [];
1931
2025
  const replacedSkinSnapshots: RenderableSnapshot[] = [];
@@ -1940,11 +2034,6 @@ export class PersistentRenderScene {
1940
2034
  if (slot.snapshot.skinJointEntities !== undefined) {
1941
2035
  removeSkinConsumer(entry.skinConsumersByJoint, slot.snapshot);
1942
2036
  }
1943
- const world = worlds[slot.worldId];
1944
- if (world !== undefined) this.options.instanceCollections?.release(world, slot.entityKey);
1945
- this.options
1946
- .getSkinPaletteAllocator?.()
1947
- ?.releaseSliceFor(`${world?.identity ?? slot.worldId}:${slot.entityKey}`);
1948
2037
  for (
1949
2038
  let materialIndex = 0;
1950
2039
  materialIndex < slot.snapshot.materials.length;
@@ -1969,15 +2058,39 @@ export class PersistentRenderScene {
1969
2058
  }
1970
2059
  }
1971
2060
  }
1972
- const partial = hasChangedEntities
1973
- ? buildCandidateFrame({ kind: 'partial', entitiesByWorld: changedEntitiesByWorld })
1974
- : undefined;
2061
+ let partial: ExtractedFrame | undefined;
2062
+ try {
2063
+ partial = hasChangedEntities
2064
+ ? buildCandidateFrame({ kind: 'partial', entitiesByWorld: changedEntitiesByWorld })
2065
+ : undefined;
2066
+ } catch (cause) {
2067
+ // Change queries have been observed, but this publication never ran.
2068
+ // Retain every consumed transform identity for the producer retry.
2069
+ for (const span of transformSpans) {
2070
+ const pending = entry.pendingRenderableEntitiesByWorld[span.worldId];
2071
+ for (const entity of span.entities) {
2072
+ if (entry.projection.has(span.worldId, entity)) pending?.add(entity);
2073
+ }
2074
+ }
2075
+ throw cause;
2076
+ }
1975
2077
  const publishedEntitiesByWorld = worlds.map(() => new Set<number>());
1976
2078
  for (const snapshot of partial?.renderables ?? []) {
1977
- operations.push({ kind: 'create', snapshot });
2079
+ const existing = entry.projection.slot(snapshot.worldId, snapshot.entityKey);
2080
+ const contentChanged =
2081
+ existing === undefined ||
2082
+ contentChangedByWorld[snapshot.worldId]?.has(snapshot.entityKey);
2083
+ operations.push({
2084
+ kind: 'update',
2085
+ worldId: snapshot.worldId,
2086
+ entityKey: snapshot.entityKey,
2087
+ ...(contentChanged || snapshot.instances === undefined
2088
+ ? { snapshot }
2089
+ : { instances: snapshot.instances }),
2090
+ });
1978
2091
  publishedEntitiesByWorld[snapshot.worldId]?.add(snapshot.entityKey);
1979
2092
  }
1980
- changedDelta = entry.projection.apply(operations);
2093
+ changedDelta = entry.projection.apply(operations, transformSpans);
1981
2094
  const publishedSkinKeys = new Set<string>();
1982
2095
  for (const snapshot of partial?.renderables ?? []) {
1983
2096
  publishedSkinKeys.add(`${snapshot.worldId}:${snapshot.entityKey}`);
@@ -2005,17 +2118,21 @@ export class PersistentRenderScene {
2005
2118
  for (const removed of changedDelta.removedSlots) entry.dispatchBySlot.delete(removed.slot);
2006
2119
  for (const recreated of changedDelta.recreatedSlots)
2007
2120
  entry.dispatchBySlot.delete(recreated.slot);
2121
+ if (partial !== undefined || removedSlots.length > 0) {
2122
+ const worldIndices = new Map(worlds.map((world, index) => [world, index]));
2123
+ entry.hiddenEntityReports = [
2124
+ ...entry.hiddenEntityReports.filter((report) => {
2125
+ const worldId = worldIndices.get(report.world);
2126
+ return (
2127
+ worldId !== undefined &&
2128
+ !changedEntitiesByWorld[worldId]?.has(report.entity) &&
2129
+ currentRenderableEntitiesByWorld?.[worldId]?.has(report.entity) !== false
2130
+ );
2131
+ }),
2132
+ ...(partial?.hiddenEntityReports ?? []),
2133
+ ];
2134
+ }
2008
2135
  if (partial !== undefined) {
2009
- if (refreshStructureFacts) {
2010
- entry.structure = {
2011
- ...entry.structure,
2012
- dispatch: partial.dispatch,
2013
- visibilityStats: partial.visibilityStats,
2014
- visibilitySnapshots: partial.visibilitySnapshots,
2015
- featureVisibilitySnapshots: partial.featureVisibilitySnapshots,
2016
- hiddenEntityReports: partial.hiddenEntityReports,
2017
- };
2018
- }
2019
2136
  const partialDispatch = dispatchEntriesBySlot(partial, entry.projection);
2020
2137
  for (const [slot, dispatch] of partialDispatch) entry.dispatchBySlot.set(slot, dispatch);
2021
2138
  // A changed renderable which now fails producer validation still owns
@@ -2067,8 +2184,10 @@ export class PersistentRenderScene {
2067
2184
  for (const removed of changedDelta.removedSlots) entry.unavailableSlots.delete(removed.slot);
2068
2185
  for (const recreated of changedDelta.recreatedSlots)
2069
2186
  entry.unavailableSlots.delete(recreated.slot);
2070
- entry.dispatchRevision += 1;
2071
- entry.dispatchCache = undefined;
2187
+ if (partial !== undefined || removedSlots.length > 0) {
2188
+ entry.dispatchRevision += 1;
2189
+ entry.dispatchCache = undefined;
2190
+ }
2072
2191
  entry.topology.apply(
2073
2192
  topologyDeltaForAvailable(changedDelta, entry.unavailableSlots, newlyUnavailable),
2074
2193
  );
@@ -2084,41 +2203,13 @@ export class PersistentRenderScene {
2084
2203
  this.transformUpdates += changedDelta.updated;
2085
2204
  }
2086
2205
 
2087
- const transformSpans: WorldTransformSpan[] = [];
2088
- for (let worldId = 0; worldId < entry.transformQueries.length; worldId += 1) {
2089
- const query = entry.transformQueries[worldId];
2090
- if (query === undefined) continue;
2091
- for (const span of query.spans().unwrap()) {
2092
- const changedWorlds = span.get(GlobalTransform).world;
2093
- if (!(changedWorlds instanceof Float32Array)) {
2094
- requiresRebuild = true;
2095
- break;
2096
- }
2097
- transformSpans.push({ worldId, entities: span.entities, worlds: changedWorlds });
2098
- }
2099
- if (requiresRebuild) break;
2100
- }
2101
- if (requiresRebuild) {
2102
- return this.rebuildComposition(worlds, owner, catalogEpoch, buildCandidateFrame, leases);
2103
- }
2104
- if (transformSpans.length === 0 && changedDelta === undefined) {
2206
+ if (changedDelta === undefined) {
2105
2207
  this.noChangeFrames += 1;
2106
2208
  this.syncGpuOwner(entry.token, entry.projection);
2107
- const result = this.deriveFramePlan(entry, buildCandidateFrame);
2108
- for (let worldId = 0; worldId < nextReadVersions.length; worldId += 1) {
2109
- const version = nextReadVersions[worldId];
2110
- if (version !== undefined) entry.readVersions[worldId] = version;
2111
- }
2112
- return result;
2113
- }
2114
- if (transformSpans.length > 0) {
2115
- const delta = entry.projection.applyTransformSpans(transformSpans);
2116
- entry.topology.apply(topologyDeltaForAvailable(delta, entry.unavailableSlots, []));
2117
- this.syncGpuOwner(entry.token, entry.projection, delta);
2118
- this.deltaFrames += 1;
2119
- this.transformUpdates += delta.updated;
2120
2209
  }
2121
2210
  const result = this.deriveFramePlan(entry, buildCandidateFrame);
2211
+ entry.catalogEpoch = catalogEpoch;
2212
+ entry.instanceEpoch = instanceEpoch;
2122
2213
  for (let worldId = 0; worldId < nextReadVersions.length; worldId += 1) {
2123
2214
  const version = nextReadVersions[worldId];
2124
2215
  if (version !== undefined) entry.readVersions[worldId] = version;
@@ -2128,6 +2219,7 @@ export class PersistentRenderScene {
2128
2219
 
2129
2220
  invalidate(): void {
2130
2221
  this.composition = undefined;
2222
+ this.shadowProjection = undefined;
2131
2223
  this.temporalCapture = undefined;
2132
2224
  this.visibilityFacets.clear();
2133
2225
  this.invalidateActiveEntityRevision();
@@ -2150,16 +2242,9 @@ export class PersistentRenderScene {
2150
2242
  ? this.composition
2151
2243
  : undefined;
2152
2244
  if (detachedComposition !== undefined) {
2153
- for (const slot of detachedComposition.projection.slotsSnapshot()) {
2154
- if (detachedComposition.worlds[slot.worldId] !== world) continue;
2155
- this.options.instanceCollections?.release(world, slot.entityKey);
2156
- if (slot.snapshot.skin === undefined) continue;
2157
- this.options
2158
- .getSkinPaletteAllocator?.()
2159
- ?.releaseSliceFor(`${world.identity}:${slot.entityKey}`);
2160
- }
2245
+ this.composition = undefined;
2246
+ this.shadowProjection = undefined;
2161
2247
  }
2162
- if (detachedComposition !== undefined) this.composition = undefined;
2163
2248
  if (detachedComposition !== undefined) this.temporalCapture = undefined;
2164
2249
  if (detachedComposition !== undefined) {
2165
2250
  this.visibilityFacets.clear();
@@ -2171,7 +2256,7 @@ export class PersistentRenderScene {
2171
2256
  this.stableSlotByEntity.clear();
2172
2257
  }
2173
2258
  if (this.gpuOwner !== world && this.gpuOwner !== detachedComposition?.token) return;
2174
- this.gpuScene?.dispose();
2259
+ this.retireGpuScene(this.gpuScene);
2175
2260
  this.gpuScene = undefined;
2176
2261
  this.gpuOwner = undefined;
2177
2262
  this.gpuDevice = undefined;
@@ -2181,6 +2266,7 @@ export class PersistentRenderScene {
2181
2266
  /** Drop only device-owned tables; the CPU projection remains the recovery authority. */
2182
2267
  resetGpuForRecover(): void {
2183
2268
  if (this.composition !== undefined) this.composition.gpuDrivenSceneRequired = true;
2269
+ this.retireGpuScene(this.gpuScene);
2184
2270
  this.gpuScene = undefined;
2185
2271
  this.gpuOwner = undefined;
2186
2272
  this.gpuDevice = undefined;
@@ -2256,6 +2342,7 @@ export class PersistentRenderScene {
2256
2342
  publish: () => {
2257
2343
  if (published || released) return;
2258
2344
  published = true;
2345
+ this.retireGpuScene(this.gpuScene);
2259
2346
  this.gpuScene = scene;
2260
2347
  this.gpuOwner = composition.token;
2261
2348
  this.gpuDevice = device;
@@ -2301,12 +2388,13 @@ export class PersistentRenderScene {
2301
2388
  }
2302
2389
 
2303
2390
  dispose(): void {
2304
- this.gpuScene?.dispose();
2391
+ this.retireGpuScene(this.gpuScene);
2305
2392
  this.gpuScene = undefined;
2306
2393
  this.gpuOwner = undefined;
2307
2394
  this.gpuDevice = undefined;
2308
2395
  this.gpuStatus = 'inactive';
2309
2396
  this.composition = undefined;
2397
+ this.shadowProjection = undefined;
2310
2398
  this.temporalCapture = undefined;
2311
2399
  this.visibilityFacets.clear();
2312
2400
  this.invalidateActiveEntityRevision();
@@ -2316,6 +2404,59 @@ export class PersistentRenderScene {
2316
2404
  this.pointsLinesInspections = [];
2317
2405
  }
2318
2406
 
2407
+ private retireGpuScene(scene: GpuScene | undefined, device = this.gpuDevice): void {
2408
+ if (scene === undefined) return;
2409
+ if (device === undefined) {
2410
+ scene.dispose();
2411
+ return;
2412
+ }
2413
+ this.retiredGpuScenes.add(scene);
2414
+ const release = (): void => {
2415
+ if (!this.retiredGpuScenes.delete(scene)) return;
2416
+ scene.dispose();
2417
+ };
2418
+ // Retirement can happen while the current frame is still being encoded
2419
+ // (for example when a new composition token is published). Calling
2420
+ // onSubmittedWorkDone() synchronously at that point observes only work
2421
+ // already queued and may resolve before the frame that still references
2422
+ // this scene is submitted. Defer fence acquisition to the next
2423
+ // microtask, after the synchronous record/submit transaction has had a
2424
+ // chance to enqueue its command buffer.
2425
+ void Promise.resolve()
2426
+ .then(() => {
2427
+ try {
2428
+ return device.queue.onSubmittedWorkDone();
2429
+ } catch {
2430
+ return undefined;
2431
+ }
2432
+ })
2433
+ .then(release, release);
2434
+ }
2435
+
2436
+ /** Read existing culling bounds; never expose cached mutable arrays. */
2437
+ bounds(world: World, entity: number): RenderSceneBounds | undefined {
2438
+ const entry = this.composition;
2439
+ const worldId = entry?.worlds.indexOf(world) ?? -1;
2440
+ if (entry === undefined || worldId < 0) return undefined;
2441
+ const slot = entry.projection.slot(worldId, entity);
2442
+ if (slot === undefined) return undefined;
2443
+ const bounds = entry.projection.cullingWorldBoundsAt(slot);
2444
+ if (bounds === undefined) return undefined;
2445
+ for (const axis of [0, 1, 2] as const) {
2446
+ if (
2447
+ !Number.isFinite(bounds.min[axis]) ||
2448
+ !Number.isFinite(bounds.max[axis]) ||
2449
+ bounds.min[axis] > bounds.max[axis]
2450
+ )
2451
+ return undefined;
2452
+ }
2453
+ return { min: [...bounds.min], max: [...bounds.max] };
2454
+ }
2455
+
2456
+ reflectionCaptureRevision(): string {
2457
+ return `${this.fullRebuilds}:${this.composition?.projection.contentRevision ?? 0}`;
2458
+ }
2459
+
2319
2460
  inspect(): PersistentRenderSceneInspection {
2320
2461
  const entry = this.composition;
2321
2462
  const pointsLines = this.pointsLinesInspections;
@@ -2327,6 +2468,9 @@ export class PersistentRenderScene {
2327
2468
  transformUpdates: this.transformUpdates,
2328
2469
  lastResyncReason: this.lastResyncReason,
2329
2470
  projectionRecords: entry?.projection.inspect().records.length ?? 0,
2471
+ ...(entry === undefined
2472
+ ? {}
2473
+ : { instanceBoundsCache: entry.projection.inspect().instanceBoundsCache }),
2330
2474
  ...(entry === undefined ? {} : { probeBlend: entry.probeProjection.inspect() }),
2331
2475
  topology:
2332
2476
  entry?.topology.inspect() ??
@@ -2353,6 +2497,11 @@ export class PersistentRenderScene {
2353
2497
  plan: entry.topology.plan(),
2354
2498
  slots: entry.projection.slotsSnapshot(),
2355
2499
  worldKeys: entry.worldKeys,
2500
+ structureMetrics: {
2501
+ worldEntitiesScanned: this.worldEntitiesScanned,
2502
+ sceneTableUploadBytes: this.sceneTableUploadBytes,
2503
+ paletteUploadBytes: 0,
2504
+ },
2356
2505
  };
2357
2506
  }
2358
2507
 
@@ -2408,9 +2557,9 @@ export class PersistentRenderScene {
2408
2557
  return { status: 'inactive' };
2409
2558
  }
2410
2559
 
2411
- private rebuildComposition(
2560
+ private reconcileComposition(
2412
2561
  worlds: readonly World[],
2413
- owner: { readonly cameraOwner: number; readonly resourceOwner: number },
2562
+ _owner: { readonly cameraOwner: number; readonly resourceOwner: number },
2414
2563
  catalogEpoch: number,
2415
2564
  buildCandidateFrame: (request: PersistentRenderCandidateRequest) => ExtractedFrame,
2416
2565
  leases?: readonly RenderReadLease[],
@@ -2471,8 +2620,28 @@ export class PersistentRenderScene {
2471
2620
  renderables: sortedRenderables,
2472
2621
  dispatch: sortedDispatch,
2473
2622
  };
2474
- const projection = new RenderScene();
2475
- projection.reset(sortedRenderables);
2623
+ const projection = previousEntry?.projection ?? new RenderScene();
2624
+ if (previousEntry !== undefined) {
2625
+ projection.remapWorlds(previousEntry.worlds.map((world) => worlds.indexOf(world)));
2626
+ }
2627
+ const desired = new Set(
2628
+ sortedRenderables.map((snapshot) => identityKey(snapshot.worldId, snapshot.entityKey)),
2629
+ );
2630
+ const operations: RenderSceneOperation[] = [];
2631
+ for (const slot of projection.slotsSnapshot()) {
2632
+ if (!desired.has(identityKey(slot.worldId, slot.entityKey))) {
2633
+ operations.push({ kind: 'remove', worldId: slot.worldId, entityKey: slot.entityKey });
2634
+ }
2635
+ }
2636
+ for (const snapshot of sortedRenderables) {
2637
+ operations.push({
2638
+ kind: 'update',
2639
+ worldId: snapshot.worldId,
2640
+ entityKey: snapshot.entityKey,
2641
+ snapshot,
2642
+ });
2643
+ }
2644
+ const delta = projection.apply(operations);
2476
2645
  this.stableSlotByEntity.clear();
2477
2646
  for (const slot of projection.slotsSnapshot()) {
2478
2647
  const stableWorld = worldKeys[slot.worldId] ?? slot.worldId;
@@ -2480,28 +2649,20 @@ export class PersistentRenderScene {
2480
2649
  }
2481
2650
  const transmissionDemand = new PersistentTransmissionDemandProjection();
2482
2651
  transmissionDemand.apply(transmissionDemandOperations(candidateFrame.renderables));
2483
- const topology = new BatchTopology();
2484
- topology.rebuild(projection.slotsSnapshot());
2652
+ const topology = previousEntry?.topology ?? new BatchTopology();
2653
+ topology.apply(delta);
2485
2654
  const skinConsumersByJoint = new Map<string, Set<number>>();
2486
2655
  for (const snapshot of stableCandidateFrame.renderables) {
2487
2656
  if (snapshot.skinJointEntities !== undefined) addSkinConsumer(skinConsumersByJoint, snapshot);
2488
2657
  }
2489
- const token = {};
2658
+ const token = previousEntry?.token ?? {};
2490
2659
  const entry: PersistentCompositionEntry = {
2491
2660
  token,
2492
2661
  worlds: [...worlds],
2493
- cameraOwner: owner.cameraOwner,
2494
- resourceOwner: owner.resourceOwner,
2495
2662
  projection,
2496
2663
  topology,
2497
2664
  transmissionDemand,
2498
- structure: {
2499
- dispatch: stableCandidateFrame.dispatch,
2500
- visibilityStats: stableCandidateFrame.visibilityStats,
2501
- visibilitySnapshots: stableCandidateFrame.visibilitySnapshots,
2502
- featureVisibilitySnapshots: stableCandidateFrame.featureVisibilitySnapshots,
2503
- hiddenEntityReports: stableCandidateFrame.hiddenEntityReports,
2504
- },
2665
+ hiddenEntityReports: stableCandidateFrame.hiddenEntityReports,
2505
2666
  worldKeys,
2506
2667
  readVersions:
2507
2668
  leases === undefined || leases.length !== worlds.length
@@ -2526,6 +2687,7 @@ export class PersistentRenderScene {
2526
2687
  pendingRenderableEntitiesByWorld: worlds.map(() => new Set<number>()),
2527
2688
  unavailableSlots: new Set<number>(),
2528
2689
  catalogEpoch,
2690
+ instanceEpoch: this.options.instanceCollections?.epoch ?? 0,
2529
2691
  probeProjection: new ProbeBlendSceneProjection(),
2530
2692
  temporalDemanded: frameDemandsTemporal(candidateFrame),
2531
2693
  gpuDrivenSceneRequired: true,
@@ -2539,8 +2701,8 @@ export class PersistentRenderScene {
2539
2701
  // later rebuild.
2540
2702
  this.projectReflectionProbes(candidateFrame.reflectionProbes ?? []);
2541
2703
  this.projectProbes(entry, candidateFrame);
2542
- this.syncGpuOwner(token, projection, undefined, true);
2543
- this.fullRebuilds += 1;
2704
+ this.syncGpuOwner(token, projection, delta);
2705
+ if (previousEntry === undefined) this.fullRebuilds += 1;
2544
2706
  this.lastResyncReason = 'attach';
2545
2707
  const materialized = projection.materialize();
2546
2708
  const candidates = entry.temporalDemanded
@@ -2553,16 +2715,14 @@ export class PersistentRenderScene {
2553
2715
  entry.probeProjection,
2554
2716
  );
2555
2717
  const slots = projection.slotsSnapshot();
2556
- const result = cullPersistentFrame(
2557
- frameWithProbes,
2558
- frameWithProbes.renderables,
2559
- (snapshot, index) => {
2560
- const slot = slots[index];
2561
- return slot === undefined
2562
- ? projection.cullingWorldBounds(snapshot)
2563
- : projection.cullingWorldBoundsAt(slot);
2564
- },
2565
- );
2718
+ const ownedFrame = this.projectShadowOwnership(frameWithProbes);
2719
+ this.shadowProjection = { renderables: ownedFrame.renderables, dispatch: ownedFrame.dispatch };
2720
+ const result = cullPersistentFrame(ownedFrame, ownedFrame.renderables, (snapshot, index) => {
2721
+ const slot = slots[index];
2722
+ return slot === undefined
2723
+ ? projection.cullingWorldBounds(snapshot)
2724
+ : projection.cullingWorldBoundsAt(slot);
2725
+ });
2566
2726
  this.frameEpoch += 1;
2567
2727
  this.visibilityFacetFrame = undefined;
2568
2728
  this.visibilityProjectionCache = undefined;
@@ -2589,17 +2749,17 @@ export class PersistentRenderScene {
2589
2749
  ): ExtractedFrame {
2590
2750
  const resourceFrame = buildCandidateFrame('none');
2591
2751
  const cachedDispatch = entry.dispatchCache;
2592
- const dispatch: DispatchEntry[] = [
2593
- ...(cachedDispatch?.revision === entry.dispatchRevision
2752
+ const dispatch =
2753
+ cachedDispatch?.revision === entry.dispatchRevision
2594
2754
  ? cachedDispatch.value
2595
- : dispatchForProjection(entry.dispatchBySlot, entry.projection, entry.unavailableSlots)),
2596
- ];
2755
+ : dispatchForProjection(entry.dispatchBySlot, entry.projection, entry.unavailableSlots);
2597
2756
  if (cachedDispatch?.revision !== entry.dispatchRevision) {
2598
2757
  entry.dispatchCache = { revision: entry.dispatchRevision, value: dispatch };
2599
2758
  }
2600
2759
  const candidateFrame: ExtractedFrame = {
2601
2760
  ...resourceFrame,
2602
- ...entry.structure,
2761
+ hiddenEntityReports: entry.hiddenEntityReports,
2762
+ visibilityStats: { explicitlyHidden: entry.hiddenEntityReports.length },
2603
2763
  dispatch,
2604
2764
  };
2605
2765
  entry.temporalDemanded = frameDemandsTemporal(candidateFrame);
@@ -2626,22 +2786,55 @@ export class PersistentRenderScene {
2626
2786
  entry.probeProjection,
2627
2787
  );
2628
2788
  const slots = entry.projection.slotsSnapshot();
2629
- const result = cullPersistentFrame(
2630
- frameWithProbes,
2631
- frameWithProbes.renderables,
2632
- (snapshot, index) => {
2633
- const slot = slots[index];
2634
- return slot === undefined
2635
- ? entry.projection.cullingWorldBounds(snapshot)
2636
- : entry.projection.cullingWorldBoundsAt(slot);
2637
- },
2638
- );
2789
+ const ownedFrame = this.projectShadowOwnership(frameWithProbes);
2790
+ this.shadowProjection = { renderables: ownedFrame.renderables, dispatch: ownedFrame.dispatch };
2791
+ const result = cullPersistentFrame(ownedFrame, ownedFrame.renderables, (snapshot, index) => {
2792
+ const slot = slots[index];
2793
+ return slot === undefined
2794
+ ? entry.projection.cullingWorldBounds(snapshot)
2795
+ : entry.projection.cullingWorldBoundsAt(slot);
2796
+ });
2639
2797
  this.frameEpoch += 1;
2640
2798
  this.visibilityFacetFrame = undefined;
2641
2799
  this.visibilityProjectionCache = undefined;
2642
2800
  return result;
2643
2801
  }
2644
2802
 
2803
+ private projectShadowOwnership(frame: ExtractedFrame): ExtractedFrame {
2804
+ const shadowCasterEntityKeys = new Set<number>();
2805
+ const shadowCasterDrawKeys = new Set<string>();
2806
+ const shadowCasterMembership: NonNullable<ExtractedFrame['shadowCasterMembership']>[number][] =
2807
+ [];
2808
+ for (
2809
+ let renderableIndex = 0;
2810
+ renderableIndex < frame.renderables.length;
2811
+ renderableIndex += 1
2812
+ ) {
2813
+ const snapshot = frame.renderables[renderableIndex];
2814
+ if (snapshot === undefined || snapshot.authorVisible === false) continue;
2815
+ const worldEntity = worldEntityKey(snapshot.worldId, snapshot.entityKey);
2816
+ for (const pass of snapshot.shadowCasterPasses ?? []) {
2817
+ shadowCasterMembership.push({ ...pass, worldEntity, renderableIndex });
2818
+ shadowCasterEntityKeys.add(worldEntity);
2819
+ shadowCasterDrawKeys.add(
2820
+ gpuDrivenShadowDrawKey(
2821
+ worldEntity,
2822
+ pass.materialHandle,
2823
+ pass.drawItemIndex,
2824
+ pass.passIndex,
2825
+ ),
2826
+ );
2827
+ }
2828
+ }
2829
+ for (const draw of frame.dispatch) {
2830
+ if (draw.tags.LightMode !== 'ShadowCaster') continue;
2831
+ const snapshot = frame.renderables[draw.renderableIndex];
2832
+ if (snapshot !== undefined)
2833
+ shadowCasterEntityKeys.add(worldEntityKey(snapshot.worldId, snapshot.entityKey));
2834
+ }
2835
+ return { ...frame, shadowCasterEntityKeys, shadowCasterDrawKeys, shadowCasterMembership };
2836
+ }
2837
+
2645
2838
  private projectProbes(entry: PersistentCompositionEntry, frame: ExtractedFrame): void {
2646
2839
  const sky =
2647
2840
  frame.skylight === undefined
@@ -2692,14 +2885,19 @@ export class PersistentRenderScene {
2692
2885
  owner: object,
2693
2886
  projection: RenderScene,
2694
2887
  delta?: RenderSceneApplyResult,
2695
- full = false,
2696
2888
  ): void {
2697
2889
  if (this.composition?.token === owner && !this.composition.gpuDrivenSceneRequired) return;
2698
2890
  const acquired = this.acquireGpuScene(owner, projection);
2699
- if (acquired === undefined || acquired.rebuilt) return;
2700
- const result = full
2701
- ? acquired.scene.rebuild(projection.slotsSnapshot())
2702
- : delta === undefined
2891
+ if (acquired === undefined) {
2892
+ this.sceneTableUploadBytes = 0;
2893
+ return;
2894
+ }
2895
+ if (acquired.rebuilt) {
2896
+ this.sceneTableUploadBytes = acquired.uploadBytes;
2897
+ return;
2898
+ }
2899
+ const result =
2900
+ delta === undefined
2703
2901
  ? acquired.scene.sync({
2704
2902
  created: 0,
2705
2903
  updated: 0,
@@ -2708,22 +2906,32 @@ export class PersistentRenderScene {
2708
2906
  ignoredLateUpdates: 0,
2709
2907
  createdSlots: [],
2710
2908
  updatedSlots: [],
2909
+ contentUpdatedSlots: [],
2910
+ instanceUpdatedSlots: [],
2711
2911
  removedSlots: [],
2712
2912
  recreatedSlots: [],
2713
2913
  resynced: 0,
2714
2914
  })
2715
2915
  : acquired.scene.sync(delta);
2716
- if (!result.ok) this.failGpu(result.error);
2916
+ if (!result.ok) {
2917
+ this.sceneTableUploadBytes = 0;
2918
+ this.failGpu(result.error);
2919
+ return;
2920
+ }
2921
+ this.sceneTableUploadBytes = result.value.bytes;
2717
2922
  }
2718
2923
 
2719
2924
  private acquireGpuScene(
2720
2925
  owner: object,
2721
2926
  projection: RenderScene,
2722
- ): { readonly scene: GpuScene; readonly rebuilt: boolean } | undefined {
2927
+ ):
2928
+ | { readonly scene: GpuScene; readonly rebuilt: boolean; readonly uploadBytes: number }
2929
+ | undefined {
2723
2930
  const getDevice = this.options.getDevice;
2724
2931
  if (getDevice === undefined) return undefined;
2725
2932
  const device = getDevice();
2726
2933
  if (this.gpuDevice !== undefined && this.gpuDevice !== device) {
2934
+ this.retireGpuScene(this.gpuScene, this.gpuDevice);
2727
2935
  this.gpuScene = undefined;
2728
2936
  this.gpuOwner = undefined;
2729
2937
  this.gpuStatus = 'rebuild-pending';
@@ -2731,9 +2939,23 @@ export class PersistentRenderScene {
2731
2939
  if (this.gpuStatus === 'error' && this.gpuDevice === device) return undefined;
2732
2940
  if (this.gpuStatus === 'unsupported' && this.gpuDevice === device) return undefined;
2733
2941
  if (this.gpuScene !== undefined && this.gpuOwner === owner) {
2734
- return { scene: this.gpuScene, rebuilt: false };
2942
+ return { scene: this.gpuScene, rebuilt: false, uploadBytes: 0 };
2943
+ }
2944
+ if (this.gpuScene !== undefined && this.gpuDevice === device) {
2945
+ // Composition rebuilds replace the CPU projection token, but the GPU
2946
+ // table resource can be rebuilt in place. Keeping one scene identity
2947
+ // avoids destroying buffers that cached frame graphs still reference
2948
+ // and lets queue ordering preserve writes across the next submission.
2949
+ const rebuilt = this.gpuScene.rebuild(projection.slotsSnapshot());
2950
+ if (!rebuilt.ok) {
2951
+ this.failGpu(rebuilt.error);
2952
+ return undefined;
2953
+ }
2954
+ this.gpuOwner = owner;
2955
+ this.gpuStatus = 'resident';
2956
+ return { scene: this.gpuScene, rebuilt: true, uploadBytes: rebuilt.value.bytes };
2735
2957
  }
2736
- if (this.gpuScene !== undefined) this.gpuScene.dispose();
2958
+ this.retireGpuScene(this.gpuScene);
2737
2959
  this.gpuScene = undefined;
2738
2960
  this.gpuOwner = undefined;
2739
2961
  this.gpuDevice = device;
@@ -2756,11 +2978,11 @@ export class PersistentRenderScene {
2756
2978
  this.gpuScene = scene;
2757
2979
  this.gpuOwner = owner;
2758
2980
  this.gpuStatus = 'resident';
2759
- return { scene, rebuilt: true };
2981
+ return { scene, rebuilt: true, uploadBytes: rebuilt.value.bytes };
2760
2982
  }
2761
2983
 
2762
2984
  private failGpu(error: RhiError): void {
2763
- this.gpuScene?.dispose();
2985
+ this.retireGpuScene(this.gpuScene);
2764
2986
  this.gpuScene = undefined;
2765
2987
  this.gpuOwner = undefined;
2766
2988
  this.gpuStatus = 'error';