@forgeax/engine-render 0.1.34 → 0.1.36

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 (273) hide show
  1. package/README.md +69 -3
  2. package/dist/assembly/device-feature-admission.d.ts +2 -2
  3. package/dist/assembly/host-contract.d.ts +3 -2
  4. package/dist/assembly/host-contract.d.ts.map +1 -1
  5. package/dist/assembly/recovery/renderer-recover.d.ts.map +1 -1
  6. package/dist/assembly/render-target-host.d.ts +5 -0
  7. package/dist/assembly/render-target-host.d.ts.map +1 -1
  8. package/dist/assembly/renderer-facade.d.ts.map +1 -1
  9. package/dist/assembly/webgpu-renderer.d.ts +1 -0
  10. package/dist/assembly/webgpu-renderer.d.ts.map +1 -1
  11. package/dist/assembly/webgpu-vertex-layouts.d.ts.map +1 -1
  12. package/dist/authoring.mjs +2 -4
  13. package/dist/authoring.mjs.map +1 -1
  14. package/dist/chunk-2OZAS3WU.mjs +3489 -0
  15. package/dist/chunk-2OZAS3WU.mjs.map +1 -0
  16. package/dist/{chunk-UI5SFODX.mjs → chunk-BZQ4CDRF.mjs} +2338 -1991
  17. package/dist/chunk-BZQ4CDRF.mjs.map +1 -0
  18. package/dist/{chunk-MKDIT2EM.mjs → chunk-CJOV2XLO.mjs} +216 -261
  19. package/dist/chunk-CJOV2XLO.mjs.map +1 -0
  20. package/dist/chunk-D6WSBXJ2.mjs +1842 -0
  21. package/dist/chunk-D6WSBXJ2.mjs.map +1 -0
  22. package/dist/{chunk-HEB564WF.mjs → chunk-DRF6OULX.mjs} +4 -4
  23. package/dist/{chunk-HEB564WF.mjs.map → chunk-DRF6OULX.mjs.map} +1 -1
  24. package/dist/{chunk-OCDLEI6I.mjs → chunk-OB5WAFYH.mjs} +117 -5
  25. package/dist/chunk-OB5WAFYH.mjs.map +1 -0
  26. package/dist/{chunk-ODZWR6JQ.mjs → chunk-OEYKKMXG.mjs} +2042 -27
  27. package/dist/chunk-OEYKKMXG.mjs.map +1 -0
  28. package/dist/chunk-RIPAEGI6.mjs +48 -0
  29. package/dist/chunk-RIPAEGI6.mjs.map +1 -0
  30. package/dist/{chunk-ELJFJ2BU.mjs → chunk-X2KA6WHM.mjs} +29 -4
  31. package/dist/chunk-X2KA6WHM.mjs.map +1 -0
  32. package/dist/{chunk-BLXGONR2.mjs → chunk-XSFB7XXQ.mjs} +51 -4
  33. package/dist/chunk-XSFB7XXQ.mjs.map +1 -0
  34. package/dist/cloud/frame-context.d.ts +2 -2
  35. package/dist/cloud/frame-context.d.ts.map +1 -1
  36. package/dist/construct-renderer.mjs +936 -2140
  37. package/dist/construct-renderer.mjs.map +1 -1
  38. package/dist/device/device-scope.d.ts.map +1 -1
  39. package/dist/device/gpu-residency.d.ts +19 -19
  40. package/dist/device/gpu-residency.d.ts.map +1 -1
  41. package/dist/errors/render.d.ts +3 -2
  42. package/dist/errors/render.d.ts.map +1 -1
  43. package/dist/extract/material-context.d.ts +7 -0
  44. package/dist/extract/material-context.d.ts.map +1 -0
  45. package/dist/features/host.d.ts +5 -0
  46. package/dist/features/host.d.ts.map +1 -1
  47. package/dist/features/particle-mesh-layout.d.ts +19 -0
  48. package/dist/features/particle-mesh-layout.d.ts.map +1 -0
  49. package/dist/features/plan.d.ts +2 -0
  50. package/dist/features/plan.d.ts.map +1 -1
  51. package/dist/features/types.d.ts +8 -0
  52. package/dist/features/types.d.ts.map +1 -1
  53. package/dist/glyph-text-layout-system.d.ts +1 -1
  54. package/dist/glyph-text-layout-system.d.ts.map +1 -1
  55. package/dist/gpu-driven/production-raster.d.ts +1 -1
  56. package/dist/gpu-driven/production-raster.d.ts.map +1 -1
  57. package/dist/gpu-driven/shadow-ownership.d.ts +3 -3
  58. package/dist/gpu-driven/shadow-ownership.d.ts.map +1 -1
  59. package/dist/gpu-driven/shadow-views.d.ts +1 -1
  60. package/dist/gpu-driven/shadow-views.d.ts.map +1 -1
  61. package/dist/gpu-driven/view-gpu.d.ts +1 -1
  62. package/dist/gpu-driven/view-gpu.d.ts.map +1 -1
  63. package/dist/ibl/frame-resources.d.ts.map +1 -1
  64. package/dist/ibl/skylight-bind-group.d.ts +4 -4
  65. package/dist/ibl/skylight-bind-group.d.ts.map +1 -1
  66. package/dist/index.d.ts +3 -0
  67. package/dist/index.d.ts.map +1 -1
  68. package/dist/index.mjs +542 -9
  69. package/dist/index.mjs.map +1 -1
  70. package/dist/instances.d.ts +3 -4
  71. package/dist/instances.d.ts.map +1 -1
  72. package/dist/internal.mjs +7 -8
  73. package/dist/internal.mjs.map +1 -1
  74. package/dist/pbr-pipeline.d.ts +3 -3
  75. package/dist/pbr-pipeline.d.ts.map +1 -1
  76. package/dist/pipeline/standard-output/lut-gpu.d.ts +2 -2
  77. package/dist/pipeline/standard-output/lut-gpu.d.ts.map +1 -1
  78. package/dist/pipeline/standard-output/resources.d.ts +2 -2
  79. package/dist/pipeline/standard-output/resources.d.ts.map +1 -1
  80. package/dist/publication/contract.d.ts +77 -0
  81. package/dist/publication/contract.d.ts.map +1 -0
  82. package/dist/publication/dependencies.d.ts +7 -0
  83. package/dist/publication/dependencies.d.ts.map +1 -0
  84. package/dist/publication/prepare-geometry.d.ts +9 -0
  85. package/dist/publication/prepare-geometry.d.ts.map +1 -0
  86. package/dist/publication/programs.d.ts +5 -0
  87. package/dist/publication/programs.d.ts.map +1 -0
  88. package/dist/publication/publisher.d.ts +34 -0
  89. package/dist/publication/publisher.d.ts.map +1 -0
  90. package/dist/publication/receiver.d.ts +29 -0
  91. package/dist/publication/receiver.d.ts.map +1 -0
  92. package/dist/publication/resource-scope.d.ts +18 -0
  93. package/dist/publication/resource-scope.d.ts.map +1 -0
  94. package/dist/publication/targets.d.ts +33 -0
  95. package/dist/publication/targets.d.ts.map +1 -0
  96. package/dist/publication/video.d.ts +5 -0
  97. package/dist/publication/video.d.ts.map +1 -0
  98. package/dist/record/fold-buckets.d.ts +3 -3
  99. package/dist/record/fold-buckets.d.ts.map +1 -1
  100. package/dist/record/frame-snapshot.d.ts +6 -6
  101. package/dist/record/frame-snapshot.d.ts.map +1 -1
  102. package/dist/record/frame.d.ts +5 -4
  103. package/dist/record/frame.d.ts.map +1 -1
  104. package/dist/record/helpers.d.ts +2 -2
  105. package/dist/record/helpers.d.ts.map +1 -1
  106. package/dist/record/main-pass-geometry.d.ts +2 -2
  107. package/dist/record/main-pass-geometry.d.ts.map +1 -1
  108. package/dist/record/main-pass-material.d.ts +7 -9
  109. package/dist/record/main-pass-material.d.ts.map +1 -1
  110. package/dist/record/main-pass-sprite-draws.d.ts +3 -16
  111. package/dist/record/main-pass-sprite-draws.d.ts.map +1 -1
  112. package/dist/record/main-pass.d.ts.map +1 -1
  113. package/dist/record/prepared-material-bindings.d.ts +2 -3
  114. package/dist/record/prepared-material-bindings.d.ts.map +1 -1
  115. package/dist/record/render-context.d.ts +4 -2
  116. package/dist/record/render-context.d.ts.map +1 -1
  117. package/dist/record/shadow-pass.d.ts +2 -2
  118. package/dist/record/shadow-pass.d.ts.map +1 -1
  119. package/dist/record/sprite-instance-buffer.d.ts +31 -0
  120. package/dist/record/sprite-instance-buffer.d.ts.map +1 -0
  121. package/dist/record/target-capture-graph.d.ts +19 -0
  122. package/dist/record/target-capture-graph.d.ts.map +1 -0
  123. package/dist/record/typed-frame-graph.d.ts +2 -13
  124. package/dist/record/typed-frame-graph.d.ts.map +1 -1
  125. package/dist/record/view-ubo.d.ts.map +1 -1
  126. package/dist/record/volume-params.d.ts +2 -2
  127. package/dist/record/volume-params.d.ts.map +1 -1
  128. package/dist/recovery/render-system-candidate.d.ts +5 -12
  129. package/dist/recovery/render-system-candidate.d.ts.map +1 -1
  130. package/dist/render-contract.d.ts +4 -1
  131. package/dist/render-contract.d.ts.map +1 -1
  132. package/dist/render-system-extract-tail.d.ts.map +1 -1
  133. package/dist/render-system-extract.d.ts +2 -0
  134. package/dist/render-system-extract.d.ts.map +1 -1
  135. package/dist/render-system-presentation.d.ts +2 -2
  136. package/dist/render-system-presentation.d.ts.map +1 -1
  137. package/dist/render-system-projections.d.ts +3 -2
  138. package/dist/render-system-projections.d.ts.map +1 -1
  139. package/dist/render-system-residency.d.ts +2 -2
  140. package/dist/render-system-residency.d.ts.map +1 -1
  141. package/dist/render-system.d.ts +3 -7
  142. package/dist/render-system.d.ts.map +1 -1
  143. package/dist/scene/render-scene.d.ts +8 -4
  144. package/dist/scene/render-scene.d.ts.map +1 -1
  145. package/dist/scene/render-source.d.ts +42 -0
  146. package/dist/scene/render-source.d.ts.map +1 -0
  147. package/dist/scene/source-systems.d.ts +6 -0
  148. package/dist/scene/source-systems.d.ts.map +1 -0
  149. package/dist/systems/skin-palette-allocator.d.ts.map +1 -1
  150. package/dist/systems/skin-palette-types.d.ts +10 -0
  151. package/dist/systems/skin-palette-types.d.ts.map +1 -1
  152. package/dist/systems/transparent-dispatch.d.ts +2 -2
  153. package/dist/systems/transparent-dispatch.d.ts.map +1 -1
  154. package/dist/systems/transparent-sort-config.d.ts +2 -1
  155. package/dist/systems/transparent-sort-config.d.ts.map +1 -1
  156. package/dist/temporal/index.mjs +6 -7
  157. package/dist/typed-shadow-passes.d.ts.map +1 -1
  158. package/package.json +21 -21
  159. package/src/__tests__/auto-exposure-transaction.integration.test.ts +4 -9
  160. package/src/__tests__/device-feature-admission.unit.test.ts +3 -2
  161. package/src/__tests__/device-scope-lifecycle.unit.test.ts +26 -0
  162. package/src/__tests__/gpu-driven-pbr.dawn.test.ts +3 -4
  163. package/src/__tests__/gpu-driven-production.integration.test.ts +18 -1
  164. package/src/__tests__/gpu-driven-shadow-views.unit.test.ts +23 -0
  165. package/src/__tests__/ktx2-basis-gpu-consumer.dawn.test.ts +4 -1
  166. package/src/__tests__/main-pass-material-medium-selection.unit.test.ts +0 -1
  167. package/src/__tests__/material-vertex-texture.browser.test.ts +7 -0
  168. package/src/__tests__/material-vertex-texture.dawn.test.ts +7 -0
  169. package/src/__tests__/material-vertex-texture.fixture.ts +31 -0
  170. package/src/__tests__/materials-standard-contract.unit.test.ts +0 -1
  171. package/src/__tests__/physical-clearcoat.integration.test.ts +23 -23
  172. package/src/__tests__/physical-material-budget.unit.test.ts +40 -0
  173. package/src/__tests__/recovery-candidate-prepare.contract.test.ts +5 -0
  174. package/src/__tests__/render-error-code-owner.test-d.ts +1 -0
  175. package/src/__tests__/renderer-factory-material-contract.unit.test.ts +107 -5
  176. package/src/__tests__/runtime-material-program.integration.test.ts +79 -0
  177. package/src/__tests__/vertex-layouts-ssot.unit.test.ts +23 -3
  178. package/src/assembly/device-feature-admission.ts +2 -2
  179. package/src/assembly/host-contract.ts +5 -2
  180. package/src/assembly/recovery/renderer-recover.ts +9 -0
  181. package/src/assembly/render-target-host.ts +57 -47
  182. package/src/assembly/renderer-facade.ts +3 -1
  183. package/src/assembly/webgpu-ready.ts +1 -1
  184. package/src/assembly/webgpu-renderer.ts +101 -70
  185. package/src/assembly/webgpu-vertex-layouts.ts +2 -9
  186. package/src/cloud/frame-context.ts +2 -2
  187. package/src/device/device-scope.ts +3 -0
  188. package/src/device/gpu-residency.ts +26 -25
  189. package/src/errors/render.ts +3 -1
  190. package/src/extract/material-context.ts +21 -0
  191. package/src/features/host.ts +13 -5
  192. package/src/features/particle-mesh-layout.ts +25 -0
  193. package/src/features/plan.ts +2 -0
  194. package/src/features/types.ts +8 -0
  195. package/src/glyph-text-layout-system.ts +2 -2
  196. package/src/gpu-driven/production-raster.ts +9 -3
  197. package/src/gpu-driven/shadow-ownership.ts +3 -3
  198. package/src/gpu-driven/shadow-views.ts +27 -2
  199. package/src/gpu-driven/view-gpu.ts +5 -0
  200. package/src/ibl/frame-resources.ts +0 -1
  201. package/src/ibl/skylight-bind-group.ts +13 -25
  202. package/src/index.ts +9 -0
  203. package/src/instances.ts +9 -6
  204. package/src/pbr-pipeline.ts +15 -18
  205. package/src/pipeline/standard-output/lut-gpu.ts +3 -3
  206. package/src/pipeline/standard-output/resources.ts +2 -2
  207. package/src/publication/__tests__/derived-geometry.integration.test.ts +177 -0
  208. package/src/publication/__tests__/features.integration.test.ts +173 -0
  209. package/src/publication/__tests__/programs.unit.test.ts +25 -0
  210. package/src/publication/__tests__/publication.integration.test.ts +415 -0
  211. package/src/publication/__tests__/targets.integration.test.ts +120 -0
  212. package/src/publication/__tests__/video.browser.test.ts +50 -0
  213. package/src/publication/contract.ts +104 -0
  214. package/src/publication/dependencies.ts +54 -0
  215. package/src/publication/prepare-geometry.ts +94 -0
  216. package/src/publication/programs.ts +45 -0
  217. package/src/publication/publisher.ts +645 -0
  218. package/src/publication/receiver.ts +297 -0
  219. package/src/publication/resource-scope.ts +31 -0
  220. package/src/publication/targets.ts +182 -0
  221. package/src/publication/video.ts +33 -0
  222. package/src/record/__tests__/material-bg-cache.unit.test.ts +15 -1
  223. package/src/record/__tests__/prepared-runtime-material.integration.test.ts +134 -0
  224. package/src/record/__tests__/sprite-material-abi.unit.test.ts +8 -10
  225. package/src/record/__tests__/video-texture-view.unit.test.ts +9 -9
  226. package/src/record/fold-buckets.ts +8 -5
  227. package/src/record/frame-snapshot.ts +6 -6
  228. package/src/record/frame.ts +87 -28
  229. package/src/record/helpers.ts +2 -2
  230. package/src/record/main-pass-geometry.ts +21 -2
  231. package/src/record/main-pass-material.ts +37 -33
  232. package/src/record/main-pass-sprite-draws.ts +6 -152
  233. package/src/record/main-pass.ts +8 -7
  234. package/src/record/prepared-material-bindings.ts +16 -37
  235. package/src/record/render-context.ts +4 -2
  236. package/src/record/shadow-pass.ts +65 -7
  237. package/src/record/sprite-instance-buffer.ts +155 -0
  238. package/src/record/target-capture-graph.ts +258 -0
  239. package/src/record/typed-frame-graph.ts +11 -207
  240. package/src/record/view-ubo.ts +1 -1
  241. package/src/record/volume-params.ts +6 -6
  242. package/src/recovery/render-system-candidate.ts +10 -18
  243. package/src/render-contract.ts +6 -1
  244. package/src/render-system-extract-tail.ts +48 -23
  245. package/src/render-system-extract.ts +11 -2
  246. package/src/render-system-presentation.ts +2 -2
  247. package/src/render-system-projections.ts +3 -2
  248. package/src/render-system-residency.ts +2 -2
  249. package/src/render-system.ts +82 -54
  250. package/src/scene/render-scene.ts +110 -100
  251. package/src/scene/render-source.ts +90 -0
  252. package/src/scene/source-systems.ts +33 -0
  253. package/src/systems/skin-palette-allocator.ts +1 -0
  254. package/src/systems/skin-palette-types.ts +11 -0
  255. package/src/systems/transparent-dispatch.ts +2 -2
  256. package/src/systems/transparent-sort-config.ts +3 -1
  257. package/src/typed-shadow-passes.ts +36 -5
  258. package/dist/chunk-BLXGONR2.mjs.map +0 -1
  259. package/dist/chunk-ELJFJ2BU.mjs.map +0 -1
  260. package/dist/chunk-ILQ5MEZF.mjs +0 -32
  261. package/dist/chunk-ILQ5MEZF.mjs.map +0 -1
  262. package/dist/chunk-MKDIT2EM.mjs.map +0 -1
  263. package/dist/chunk-OCDLEI6I.mjs.map +0 -1
  264. package/dist/chunk-ODZWR6JQ.mjs.map +0 -1
  265. package/dist/chunk-RXZ3RGOU.mjs +0 -53
  266. package/dist/chunk-RXZ3RGOU.mjs.map +0 -1
  267. package/dist/chunk-SHFUFJ5N.mjs +0 -1781
  268. package/dist/chunk-SHFUFJ5N.mjs.map +0 -1
  269. package/dist/chunk-UI5SFODX.mjs.map +0 -1
  270. package/dist/chunk-UINHLCZA.mjs +0 -2389
  271. package/dist/chunk-UINHLCZA.mjs.map +0 -1
  272. package/dist/chunk-WEX45HKH.mjs +0 -1524
  273. package/dist/chunk-WEX45HKH.mjs.map +0 -1
@@ -1,5 +1,4 @@
1
1
  import { resolveAssetHandle } from '@forgeax/engine-assets-runtime';
2
- import type { World } from '@forgeax/engine-ecs';
3
2
  import {
4
3
  deriveTextureLayout,
5
4
  type MaterialAsset,
@@ -11,6 +10,7 @@ import type {
11
10
  MeshMaterialBindingPreparationFailure,
12
11
  MeshMaterialBindingResidency,
13
12
  } from './mesh-material-bindings';
13
+ import type { RenderResourceScope } from './publication/resource-scope';
14
14
  import type { RenderSystemInternals } from './record/render-context';
15
15
  import { materialSamplerHandles, materialTextureHandles } from './recovery/render-system-candidate';
16
16
  import type { MaterialSnapshot } from './render-system-extract';
@@ -49,7 +49,7 @@ function preparationFailureFrom(error: unknown): MeshMaterialBindingPreparationF
49
49
  * already resident resource remains last-known-good.
50
50
  */
51
51
  export function observeMaterialResidency(
52
- world: World | undefined,
52
+ world: RenderResourceScope | undefined,
53
53
  material: MaterialSnapshot,
54
54
  internals: RenderSystemInternals,
55
55
  ): MeshMaterialBindingResidency {
@@ -1,4 +1,9 @@
1
1
  import { projectCaptureScene } from './capture/scene-projection';
2
+ import { renderMaterialContext } from './extract/material-context';
3
+ import { RenderPublicationError } from './publication/contract';
4
+ import { preparePublicationGeometry } from './publication/prepare-geometry';
5
+ import type { PreparedRenderPublication } from './publication/receiver';
6
+ import { type RenderResourceScope, renderTime } from './publication/resource-scope';
2
7
  import type { RenderSceneBounds } from './scene/render-scene-types';
3
8
  // @forgeax/engine-math is referenced through render-system-record.ts which builds
4
9
  // view/proj matrices and worldFromLocal via `mat4.compose / .multiply /
@@ -6,13 +11,13 @@ import type { RenderSceneBounds } from './scene/render-scene-types';
6
11
  // asserts `/@forgeax\/engine-math/` shows up in render-system.ts source).
7
12
 
8
13
  import { numMipLevels } from '@forgeax/engine-assets-runtime';
9
- import { Time, type World } from '@forgeax/engine-ecs';
14
+ import type { World } from '@forgeax/engine-ecs';
10
15
  import type { RenderReadLease } from '@forgeax/engine-ecs/projection';
11
16
  import type { RecorderSession } from '@forgeax/engine-profiler';
12
17
  import type { CompiledRenderGraphInfo } from '@forgeax/engine-render-graph';
13
18
  import type { BindGroupLayout, Buffer, RenderPipeline, RhiDevice } from '@forgeax/engine-rhi';
14
19
  import { err, ok, type Result, RhiError } from '@forgeax/engine-rhi';
15
- import { type RenderPipelineAsset, toShared } from '@forgeax/engine-types';
20
+ import { type Handle, type RenderPipelineAsset, toShared } from '@forgeax/engine-types';
16
21
  import {
17
22
  type DirectionalShadowInspectionSource,
18
23
  directionalShadowProfileFromQuality,
@@ -363,6 +368,7 @@ export interface RenderSystem {
363
368
  opts: DrawOwnerOptions,
364
369
  renderReadLeases?: readonly RenderReadLease[],
365
370
  timingCapture?: GpuTimingCapture,
371
+ publication?: PreparedRenderPublication,
366
372
  ): boolean;
367
373
  /**
368
374
  * Return the asset binding consumed by the last successful record pass.
@@ -604,7 +610,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
604
610
  );
605
611
  let releaseProfilerCatalog =
606
612
  phaseCatalogRegistration?.ok === true ? phaseCatalogRegistration.value : undefined;
607
- let preparedWorlds: readonly World[] = [];
613
+ let preparedWorlds: readonly RenderResourceScope[] = [];
608
614
  const resolveFeatureNoise = createFeatureNoiseResolver({
609
615
  scope: internals.deviceScope,
610
616
  getDevice: () => internals.device,
@@ -1497,7 +1503,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
1497
1503
  let orderedDispatchCache:
1498
1504
  | {
1499
1505
  readonly source: readonly DispatchEntry[];
1500
- readonly world: World;
1506
+ readonly world: RenderResourceScope;
1501
1507
  readonly camera: readonly CameraSnapshot[];
1502
1508
  readonly renderables: readonly RenderableSnapshot[];
1503
1509
  readonly value: readonly DispatchEntry[];
@@ -1628,7 +1634,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
1628
1634
  (
1629
1635
  runtime: RenderSystemInternals,
1630
1636
  gpuWork: RenderFeatureGpuWorkOwner,
1631
- worlds: readonly World[],
1637
+ worlds: readonly RenderResourceScope[],
1632
1638
  caches: PreparedResolverCaches,
1633
1639
  ) =>
1634
1640
  (input: RenderFeaturePreparedGraphicsResolverInput): PreparedGraphicsResolver => {
@@ -1845,7 +1851,6 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
1845
1851
  ) {
1846
1852
  return preparedMaterialBindings(
1847
1853
  runtime,
1848
- caches,
1849
1854
  worlds,
1850
1855
  materialShaderId,
1851
1856
  material.world,
@@ -2198,9 +2203,12 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2198
2203
  opts: DrawOwnerOptions,
2199
2204
  renderReadLeases?: readonly RenderReadLease[],
2200
2205
  timingCapture?: GpuTimingCapture,
2206
+ publication?: PreparedRenderPublication,
2201
2207
  ): boolean {
2202
2208
  const composition = canonicalizeWorldComposition(worlds, opts, renderReadLeases);
2203
- const compositionWorlds = composition.worlds;
2209
+ const sourceWorlds = composition.worlds;
2210
+ const compositionWorlds: readonly RenderResourceScope[] =
2211
+ publication === undefined ? sourceWorlds : [publication.resources];
2204
2212
  const compositionLeases = composition.leases;
2205
2213
  preparedWorlds = compositionWorlds;
2206
2214
  const profileSession = internals.profiler?.activeSession();
@@ -2240,21 +2248,45 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2240
2248
  // so a hot-swap cannot reuse passes compiled for the retired pipeline.
2241
2249
  resetRenderFeatureGraphState(internals);
2242
2250
  lastBuiltPipelineHandle = frameState.installedPipelineHandle;
2243
- persistentRenderScene.invalidate();
2251
+ if (publication === undefined) persistentRenderScene.invalidate();
2244
2252
  }
2245
2253
  dispatchCounts.unlit = 0;
2246
2254
 
2247
- const resourceWorld = compositionWorlds[resourceOwner] as World;
2255
+ const resourceWorld = compositionWorlds[resourceOwner] as RenderResourceScope;
2248
2256
  let frame: ReturnType<PersistentRenderScene['extractComposition']>;
2249
2257
  try {
2250
2258
  frame = runProfiledRenderPhase(profileSession, 'extract', () => {
2259
+ if (publication !== undefined) {
2260
+ for (const handle of [
2261
+ ...publication.packet.invalidatedAssets,
2262
+ ...publication.packet.retiredAssets,
2263
+ ]) {
2264
+ internals.gpuStore.invalidateMesh(handle, publication.resources);
2265
+ internals.gpuStore.evictTexture(
2266
+ handle as Handle<'TextureAsset', 'shared'>,
2267
+ publication.resources,
2268
+ );
2269
+ }
2270
+ return persistentRenderScene.consumePublication(
2271
+ preparePublicationGeometry(
2272
+ publication,
2273
+ persistentRenderScene,
2274
+ internals.getPipelineState()?.skinPaletteAllocator,
2275
+ internals.assets,
2276
+ instanceCollections,
2277
+ internals.device.caps.storageBuffer
2278
+ ? internals.getMaterialShaderArtifact
2279
+ : undefined,
2280
+ ),
2281
+ );
2282
+ }
2251
2283
  return persistentRenderScene.extractComposition(
2252
- compositionWorlds,
2284
+ sourceWorlds,
2253
2285
  { cameraOwner, resourceOwner },
2254
2286
  internals.assets.catalogEpoch,
2255
2287
  (renderables) =>
2256
2288
  extractFrames(
2257
- compositionWorlds,
2289
+ sourceWorlds,
2258
2290
  {
2259
2291
  cameraOwner,
2260
2292
  resourceOwner,
@@ -2264,25 +2296,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2264
2296
  internals.getPipelineState(),
2265
2297
  persistentRenderScene.materialSnapshotCacheStore(),
2266
2298
  {
2267
- ...(internals.device.caps.backendKind === 'null'
2268
- ? {}
2269
- : {
2270
- materialContext: {
2271
- backend:
2272
- internals.device.caps.backendKind === 'wgpu-webgl2'
2273
- ? ('webgl2' as const)
2274
- : internals.device.caps.backendKind,
2275
- capability: internals.device.caps.storageBuffer
2276
- ? ('storage-buffer' as const)
2277
- : ('uniform-fallback' as const),
2278
- pipeline: 'forward' as const,
2279
- geometry: 'mesh' as const,
2280
- pass: 'forward' as const,
2281
- profile: 'forgeax-material-wgsl-v1' as const,
2282
- toolchain: 'naga-oil' as const,
2283
- instrumentation: 'none' as const,
2284
- },
2285
- }),
2299
+ ...renderMaterialContext(internals.device.caps),
2286
2300
  cull: 'none',
2287
2301
  retainHidden: true,
2288
2302
  renderables,
@@ -2308,6 +2322,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2308
2322
  lastMotionBlurInvalidParams = undefined;
2309
2323
  const {
2310
2324
  cameras: extractedCameras,
2325
+ auxiliaryCameras,
2311
2326
  cubeCameras,
2312
2327
  lights,
2313
2328
  environment,
@@ -2333,7 +2348,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2333
2348
  frameState,
2334
2349
  internals,
2335
2350
  cameras[0],
2336
- compositionWorlds[cameraOwner] as World,
2351
+ compositionWorlds[cameraOwner] as RenderResourceScope,
2337
2352
  );
2338
2353
  if (!preparedOutput.ok) {
2339
2354
  internals.errorRegistry.fire(preparedOutput.error);
@@ -2814,7 +2829,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2814
2829
  const motionBlurParams = cameras[0]?.motionBlur;
2815
2830
  let motionBlurDelta = 0;
2816
2831
  try {
2817
- const delta = (compositionWorlds[cameraOwner] as World).getResource(Time).delta;
2832
+ const delta = renderTime(compositionWorlds[cameraOwner] as RenderResourceScope).delta;
2818
2833
  motionBlurDelta = Number.isFinite(delta) ? Math.max(0, delta) : 0;
2819
2834
  } catch {
2820
2835
  motionBlurDelta = 0;
@@ -2856,9 +2871,29 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2856
2871
  .skylightResources;
2857
2872
  let featureGraphCandidate: RenderFeatureGraphCandidate | undefined;
2858
2873
  const preparedResourceBatches = runProfiledRenderPhase(profileSession, 'features', () => {
2874
+ if (
2875
+ publication?.packet.features.some(
2876
+ (row) =>
2877
+ !internals.featureHost?.features.some(
2878
+ (feature) => feature.identity === row.identity,
2879
+ ),
2880
+ )
2881
+ )
2882
+ throw new RenderPublicationError({
2883
+ reason: 'shape',
2884
+ subject: 'published feature has no receiver implementation',
2885
+ });
2859
2886
  if (internals.featureHost === undefined) return [];
2860
2887
  const featureFrame = runRenderFeatureFrame(internals.featureHost, {
2861
- worlds: compositionWorlds,
2888
+ worlds: sourceWorlds,
2889
+ ...(publication === undefined
2890
+ ? {}
2891
+ : {
2892
+ publishedFeatures: publication.packet.features,
2893
+ ...(publication.onFeatureSourceSubmitted === undefined
2894
+ ? {}
2895
+ : { onFeatureSourceSubmitted: publication.onFeatureSourceSubmitted }),
2896
+ }),
2862
2897
  owner: resourceOwner,
2863
2898
  frameNumber: frameState.frameNumber,
2864
2899
  ...(cameras[0] === undefined ? {} : { selectedCamera: cameras[0] }),
@@ -3053,8 +3088,8 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3053
3088
  frameState.standardLutState.resident !== null ||
3054
3089
  frameState.pendingStandardLutState !== undefined;
3055
3090
  if (hasLiveFeatureState) {
3056
- const cameraWorld = compositionWorlds[cameraOwner] as World;
3057
- const deltaTime = cameraWorld.getResource(Time).delta;
3091
+ const cameraWorld = compositionWorlds[cameraOwner] as RenderResourceScope;
3092
+ const deltaTime = renderTime(cameraWorld).delta;
3058
3093
  stageLiveFeatureState(
3059
3094
  cameras[0],
3060
3095
  Number.isFinite(deltaTime) ? Math.max(0, deltaTime) : 0,
@@ -3071,16 +3106,15 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3071
3106
  frameState.surfaceSubmissionObservation = surfaceSubmissionCandidate;
3072
3107
  const probeCaptureScene =
3073
3108
  shadowCasterProjection !== undefined &&
3074
- reflectionProbeRecord.graph.work.some((work) => work.rawCaptureFace !== undefined)
3109
+ (auxiliaryCameras.length > 0 ||
3110
+ cubeCameras.length > 0 ||
3111
+ reflectionProbeRecord.graph.work.some((work) => work.rawCaptureFace !== undefined))
3075
3112
  ? projectCaptureScene(shadowCasterProjection, submissionRenderables, orderedDispatch)
3076
3113
  : undefined;
3077
- // This is a transient projection of the record owner's actual visible
3078
- // draws. It lets the presentation fact distinguish a clear/submit
3079
- // from a frame where geometry or its pipeline was skipped, without
3080
- // retaining a second readiness registry or per-asset counter.
3114
+ // Track actual draws so a clear/submit cannot claim visible geometry was presented.
3081
3115
  const presentedRenderableKeys = new Set<number>();
3082
3116
  const presentedSubmeshKeys = new Set<string>();
3083
- const consumeDynamicGeometry = dynamicGeometryFrames.onRenderableDraw(compositionWorlds);
3117
+ const consumeDynamicGeometry = dynamicGeometryFrames.onRenderableDraw(sourceWorlds);
3084
3118
  const markPresentedRenderable = (
3085
3119
  entry: Parameters<typeof consumeDynamicGeometry>[0],
3086
3120
  submeshIndex?: number,
@@ -3131,7 +3165,9 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3131
3165
  },
3132
3166
  ...(occlusionProjection === undefined ? {} : { occlusion: occlusionProjection }),
3133
3167
  frameTime: (() => {
3134
- const elapsed = (compositionWorlds[cameraOwner] as World).getResource(Time).elapsed;
3168
+ const elapsed = renderTime(
3169
+ compositionWorlds[cameraOwner] as RenderResourceScope,
3170
+ ).elapsed;
3135
3171
  return Number.isFinite(elapsed) ? elapsed : 0;
3136
3172
  })(),
3137
3173
  deviceGeneration: internals.deviceScope.generation,
@@ -3145,6 +3181,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3145
3181
  pointsLinesOwner,
3146
3182
  {
3147
3183
  snapshots: cubeCameras,
3184
+ auxiliaryCameras,
3148
3185
  scheduler: cubeCaptureScheduler,
3149
3186
  state: cubeCaptureState,
3150
3187
  reflectionProbes: reflectionProbeRecord,
@@ -3329,7 +3366,9 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3329
3366
  width: Math.max(1, internals.canvas.width),
3330
3367
  height: Math.max(1, internals.canvas.height),
3331
3368
  frameTime: (() => {
3332
- const elapsed = (compositionWorlds[cameraOwner] as World).getResource(Time).elapsed;
3369
+ const elapsed = renderTime(
3370
+ compositionWorlds[cameraOwner] as RenderResourceScope,
3371
+ ).elapsed;
3333
3372
  return Number.isFinite(elapsed) ? elapsed : 0;
3334
3373
  })(),
3335
3374
  ...(surfaceDynamicInputFrame === undefined
@@ -4054,14 +4093,3 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
4054
4093
  export function resolveInspectionBuild(internals: Pick<RenderSystemInternals, 'build'>): string {
4055
4094
  return internals.build ?? 'render-system';
4056
4095
  }
4057
-
4058
- // @forgeax/engine-render - built-in record context extension.
4059
- //
4060
- // The public RenderPipelineContext is canonical in render-contract.ts. This
4061
- // owner adds only concrete assembly state for built-in record closures.
4062
-
4063
- /**
4064
- * Package-private extension consumed by the built-in record closures only.
4065
- * Custom pipeline authors see the leaf RenderPipelineContext contract and
4066
- * never this surface.
4067
- */
@@ -1,29 +1,17 @@
1
1
  import { RuntimeMaterialValue, RuntimeMeshVertices } from '@forgeax/engine-assets-runtime';
2
- import type { EntityHandle, Query, World } from '@forgeax/engine-ecs';
3
- import {
4
- createStateProjection,
5
- type RenderReadLease,
6
- type RenderReadVersion,
7
- type StateProjection,
8
- type StateProjectionBatch,
2
+ import type { EntityHandle, World } from '@forgeax/engine-ecs';
3
+ import type {
4
+ RenderReadLease,
5
+ RenderReadVersion,
6
+ StateProjection,
7
+ StateProjectionBatch,
9
8
  } from '@forgeax/engine-ecs/projection';
10
9
  import { box3, frustum, mat4 } from '@forgeax/engine-math';
11
10
  import { type RhiDevice, RhiError } from '@forgeax/engine-rhi';
12
- import { ChildOf, Children, GlobalTransform, MorphWeights, Transform } from '@forgeax/engine-scene';
11
+ import { ChildOf, Children, GlobalTransform, MorphWeights } from '@forgeax/engine-scene';
13
12
  import { Skin } from '@forgeax/engine-skinning';
14
13
  import { err, ok, type Result } from '@forgeax/engine-types';
15
- import {
16
- Instances,
17
- Layer,
18
- Lines,
19
- MeshFilter,
20
- MeshRenderer,
21
- Points,
22
- SortKey,
23
- SpriteInstances,
24
- SpriteRegionOverride,
25
- Visibility,
26
- } from '../components';
14
+ import { Instances, Lines, Points, SpriteInstances, Visibility } from '../components';
27
15
  import type { DeviceScope, LifecycleResourceSpec } from '../device/device-scope';
28
16
  import { gpuDrivenShadowDrawKey } from '../extract/gpu-driven';
29
17
  import { motionBlurTemporalDemand } from '../features/motion-blur/motion-blur-params';
@@ -41,6 +29,8 @@ import type { InstanceProjectionStore } from '../instances';
41
29
  import { fingerprintNumericArray, InstanceBoundsCache } from '../instances-derived-bounds';
42
30
  import type { PointsLinesInspection } from '../points-lines/inspection';
43
31
  import type { PointsLinesRetainedSnapshot } from '../points-lines/snapshot';
32
+ import type { PreparedRenderPublication } from '../publication/receiver';
33
+ import type { RenderResourceScope } from '../publication/resource-scope';
44
34
  import { worldEntityKey } from '../record/frame-snapshot';
45
35
  import {
46
36
  type ReflectionProbeFact,
@@ -76,6 +66,13 @@ import type {
76
66
  RenderSceneResyncReason,
77
67
  RenderSceneSlot,
78
68
  } from './render-scene-types';
69
+ import {
70
+ createGlobalTransformChangeQuery,
71
+ createRenderSourceState,
72
+ type GlobalTransformChangeQuery,
73
+ isRenderableMember,
74
+ RENDERABLE_SOURCE_COMPONENTS,
75
+ } from './render-source';
79
76
  import { buildShadowFrusta } from './shadow-visibility';
80
77
  import { createVisibilityBudget, type VisibilityBudget } from './visibility/budget';
81
78
  import {
@@ -118,8 +115,6 @@ interface WorldTransformSpan {
118
115
  readonly worlds: Readonly<Float32Array>;
119
116
  }
120
117
 
121
- type GlobalTransformChangeQuery = Query<readonly [typeof GlobalTransform]>;
122
-
123
118
  function sameInstanceIdentitySet(
124
119
  current: Uint32Array | undefined,
125
120
  previous: Uint32Array | undefined,
@@ -1151,7 +1146,7 @@ type PersistentGpuSceneInspection = PersistentRenderSceneInspection['gpu'];
1151
1146
 
1152
1147
  interface PersistentCompositionEntry {
1153
1148
  readonly token: object;
1154
- readonly worlds: readonly World[];
1149
+ readonly worlds: readonly RenderResourceScope[];
1155
1150
  readonly projection: RenderScene;
1156
1151
  readonly topology: BatchTopology;
1157
1152
  readonly transmissionDemand: PersistentTransmissionDemandProjection;
@@ -1187,73 +1182,6 @@ interface PersistentCompositionEntry {
1187
1182
  gpuDrivenSceneRequired: boolean;
1188
1183
  }
1189
1184
 
1190
- // Retained geometry consumes these component changes. Camera, lighting and
1191
- // environment facts are freshly extracted by deriveFramePlan on every draw;
1192
- // their edits must not recreate unrelated geometry or its submission history.
1193
- const RENDERABLE_SOURCE_COMPONENTS = [
1194
- ChildOf,
1195
- MeshFilter,
1196
- MeshRenderer,
1197
- Instances,
1198
- SpriteInstances,
1199
- SpriteRegionOverride,
1200
- Skin,
1201
- Layer,
1202
- Visibility,
1203
- MorphWeights,
1204
- Points,
1205
- Lines,
1206
- SortKey,
1207
- ] as const;
1208
- function createGlobalTransformChangeQuery(world: World): GlobalTransformChangeQuery {
1209
- const result = world.query({ read: [GlobalTransform], changed: [GlobalTransform] });
1210
- if (!result.ok) throw result.error;
1211
- const query = result.value as GlobalTransformChangeQuery;
1212
- for (const _span of query.spans().unwrap()) {
1213
- // The full rebuild consumed these values; establish the observation baseline.
1214
- }
1215
- return query;
1216
- }
1217
-
1218
- function createRenderSourceState(world: World) {
1219
- // Numerical poses already have one span reader. Keep their table membership
1220
- // in this projection, without scanning their changed rows a second time.
1221
- const components = [...RENDERABLE_SOURCE_COMPONENTS, RuntimeMaterialValue, RuntimeMeshVertices];
1222
- const projection = createStateProjection(world, components, [
1223
- ...components,
1224
- Transform,
1225
- GlobalTransform,
1226
- ]);
1227
- const entities = new Map<number, EntityHandle>();
1228
- const contentHandles = new Map<number, readonly number[]>();
1229
- const batch = projection.read();
1230
- for (const index of batch.indices) {
1231
- const entity = projection.entity(index);
1232
- if (entity !== undefined) {
1233
- entities.set(index, entity);
1234
- const handles: number[] = [];
1235
- const material = world.hasComponent(entity, RuntimeMaterialValue)
1236
- ? world.get(entity, RuntimeMaterialValue)
1237
- : undefined;
1238
- const mesh = world.hasComponent(entity, RuntimeMeshVertices)
1239
- ? world.get(entity, RuntimeMeshVertices)
1240
- : undefined;
1241
- if (material?.ok) handles.push(Number(material.value.asset));
1242
- if (mesh?.ok) handles.push(Number(mesh.value.asset));
1243
- if (handles.length > 0) contentHandles.set(index, handles);
1244
- }
1245
- }
1246
- return { projection, entities, contentHandles, batch };
1247
- }
1248
-
1249
- function isRenderableMember(world: World, entity: number): boolean {
1250
- return (
1251
- world.hasComponent(entity as EntityHandle, MeshRenderer) &&
1252
- world.hasComponent(entity as EntityHandle, Transform) &&
1253
- world.hasComponent(entity as EntityHandle, MeshFilter)
1254
- );
1255
- }
1256
-
1257
1185
  function sourceMembershipChanged(world: World, slot: RenderSceneSlot): boolean {
1258
1186
  const entity = slot.entityKey as EntityHandle;
1259
1187
  const snapshot = slot.snapshot;
@@ -1608,7 +1536,7 @@ export class PersistentRenderScene {
1608
1536
  return this.visibilityBudget;
1609
1537
  }
1610
1538
 
1611
- private visibilityWorldKey(world: World | undefined, fallback: number): number {
1539
+ private visibilityWorldKey(world: RenderResourceScope | undefined, fallback: number): number {
1612
1540
  if (world === undefined) return fallback;
1613
1541
  const existing = this.visibilityWorldKeys.get(world.identity);
1614
1542
  if (existing !== undefined) return existing;
@@ -1618,12 +1546,12 @@ export class PersistentRenderScene {
1618
1546
  return key;
1619
1547
  }
1620
1548
 
1621
- private worldKeyAt(worlds: readonly World[], worldId: number): number {
1549
+ private worldKeyAt(worlds: readonly RenderResourceScope[], worldId: number): number {
1622
1550
  return this.visibilityWorldKey(worlds[worldId], worldId);
1623
1551
  }
1624
1552
 
1625
1553
  /** Stable World routing keys for the current worlds[] frame projection. */
1626
- visibilityWorldKeysFor(worlds: readonly World[]): readonly number[] {
1554
+ visibilityWorldKeysFor(worlds: readonly RenderResourceScope[]): readonly number[] {
1627
1555
  return worlds.map((world, worldId) => this.visibilityWorldKey(world, worldId));
1628
1556
  }
1629
1557
 
@@ -1632,6 +1560,10 @@ export class PersistentRenderScene {
1632
1560
  return this.composition?.projection.slotsSnapshot() ?? [];
1633
1561
  }
1634
1562
 
1563
+ compositionSlot(worldId: number, entityKey: number): RenderSceneSlot | undefined {
1564
+ return this.composition?.projection.slot(worldId, entityKey);
1565
+ }
1566
+
1635
1567
  shadowCasterProjection(): PersistentShadowCasterProjection | undefined {
1636
1568
  return this.shadowProjection;
1637
1569
  }
@@ -1642,7 +1574,7 @@ export class PersistentRenderScene {
1642
1574
  }
1643
1575
 
1644
1576
  private stableEntityKey(
1645
- worlds: readonly World[],
1577
+ worlds: readonly RenderResourceScope[],
1646
1578
  renderable: Pick<RenderableSnapshot, 'worldId' | 'entityKey'>,
1647
1579
  ): number {
1648
1580
  return worldEntityKey(this.worldKeyAt(worlds, renderable.worldId), renderable.entityKey);
@@ -1650,7 +1582,7 @@ export class PersistentRenderScene {
1650
1582
 
1651
1583
  /** Feed the facet from extracted renderables using persistent slot identity. */
1652
1584
  updateVisibilityFacet(
1653
- worlds: readonly World[],
1585
+ worlds: readonly RenderResourceScope[],
1654
1586
  camera: CameraSnapshot | undefined,
1655
1587
  renderables: readonly RenderableSnapshot[],
1656
1588
  ): void {
@@ -1710,7 +1642,7 @@ export class PersistentRenderScene {
1710
1642
  * visible through `decideVisibility`.
1711
1643
  */
1712
1644
  projectVisibility(
1713
- worlds: readonly World[],
1645
+ worlds: readonly RenderResourceScope[],
1714
1646
  camera: CameraSnapshot | undefined,
1715
1647
  renderables: readonly RenderableSnapshot[],
1716
1648
  dispatch: readonly DispatchEntry[],
@@ -1884,6 +1816,77 @@ export class PersistentRenderScene {
1884
1816
  return this.reflectionProbes.snapshot();
1885
1817
  }
1886
1818
 
1819
+ consumePublication(input: PreparedRenderPublication): ExtractedFrame {
1820
+ const { resources, packet, frame } = input;
1821
+ let entry = this.composition;
1822
+ if (entry === undefined || packet.baseline) {
1823
+ this.invalidate();
1824
+ const projection = new RenderScene();
1825
+ entry = {
1826
+ token: {},
1827
+ worlds: [resources],
1828
+ projection,
1829
+ topology: new BatchTopology(),
1830
+ transmissionDemand: new PersistentTransmissionDemandProjection(),
1831
+ hiddenEntityReports: [],
1832
+ worldKeys: this.visibilityWorldKeysFor([resources]),
1833
+ readVersions: [],
1834
+ leaseIdentities: [],
1835
+ transformQueries: [],
1836
+ sourceStates: [],
1837
+ dispatchBySlot: new Map(),
1838
+ dispatchRevision: 0,
1839
+ dispatchCache: undefined,
1840
+ skinConsumersByJoint: new Map(),
1841
+ pendingRenderableEntitiesByWorld: [],
1842
+ unavailableSlots: new Set(),
1843
+ catalogEpoch: 0,
1844
+ probeProjection: new ProbeBlendSceneProjection(),
1845
+ probeObjects: undefined,
1846
+ temporalDemanded: false,
1847
+ gpuDrivenSceneRequired: true,
1848
+ };
1849
+ this.composition = entry;
1850
+ this.fullRebuilds++;
1851
+ this.lastResyncReason = 'attach';
1852
+ }
1853
+ this.worldEntitiesScanned = 0;
1854
+ this.sceneTableUploadBytes = 0;
1855
+ const removedDemand: TransmissionDemandOperation[] = [];
1856
+ for (const operation of input.operations) {
1857
+ if (operation.kind !== 'remove' && operation.snapshot === undefined) continue;
1858
+ const previous = entry.projection.slot(0, operation.entityKey);
1859
+ for (let index = 0; index < (previous?.snapshot.materials.length ?? 0); index++)
1860
+ removedDemand.push({ kind: 'remove', key: `0:${operation.entityKey}:${index}` });
1861
+ }
1862
+ const delta = entry.projection.apply(input.operations);
1863
+ for (const removed of delta.removedSlots) {
1864
+ entry.dispatchBySlot.delete(removed.slot);
1865
+ this.stableSlotByEntity.delete(worldEntityKey(entry.worldKeys[0] ?? 0, removed.entityKey));
1866
+ }
1867
+ for (const recreated of delta.recreatedSlots) entry.dispatchBySlot.delete(recreated.slot);
1868
+ for (const [slot, dispatch] of dispatchEntriesBySlot(frame, entry.projection))
1869
+ entry.dispatchBySlot.set(slot, dispatch);
1870
+ if (frame.renderables.length || packet.removed.length) {
1871
+ entry.dispatchRevision++;
1872
+ entry.dispatchCache = undefined;
1873
+ this.visibilityFacets.clear();
1874
+ this.invalidateActiveEntityRevision();
1875
+ }
1876
+ for (const slot of [...delta.createdSlots, ...delta.updatedSlots, ...delta.recreatedSlots])
1877
+ this.stableSlotByEntity.set(worldEntityKey(entry.worldKeys[0] ?? 0, slot.entityKey), slot);
1878
+ entry.transmissionDemand.apply([
1879
+ ...removedDemand,
1880
+ ...transmissionDemandOperations(frame.renderables),
1881
+ ]);
1882
+ entry.topology.apply(delta);
1883
+ this.syncGpuOwner(entry.token, entry.projection, delta);
1884
+ if (input.operations.length) this.deltaFrames++;
1885
+ else this.noChangeFrames++;
1886
+ this.transformUpdates += delta.updated;
1887
+ return this.deriveFramePlan(entry, frame);
1888
+ }
1889
+
1887
1890
  extractComposition(
1888
1891
  worlds: readonly World[],
1889
1892
  owner: { readonly cameraOwner: number; readonly resourceOwner: number },
@@ -1897,7 +1900,7 @@ export class PersistentRenderScene {
1897
1900
  const sameWorldSet =
1898
1901
  entry !== undefined &&
1899
1902
  entry.worlds.length === worlds.length &&
1900
- entry.worlds.every((world) => worlds.includes(world));
1903
+ entry.worlds.every((world) => worlds.some((local) => local === world));
1901
1904
  const worldOrderChanged =
1902
1905
  entry !== undefined &&
1903
1906
  sameWorldSet &&
@@ -2708,7 +2711,7 @@ export class PersistentRenderScene {
2708
2711
  preserveVisibilityOnReorder = false,
2709
2712
  ): ExtractedFrame {
2710
2713
  const previousEntry = this.composition;
2711
- const sourceStates = worlds.map(createRenderSourceState);
2714
+ const sourceStates = worlds.map((world) => createRenderSourceState(world));
2712
2715
  const candidateFrame = buildCandidateFrame({
2713
2716
  kind: 'partial',
2714
2717
  entitiesByWorld: sourceStates.map((source) => new Set(source.entities.values())),
@@ -2745,7 +2748,11 @@ export class PersistentRenderScene {
2745
2748
  const renderables = candidateFrame.renderables;
2746
2749
  const projection = previousEntry?.projection ?? new RenderScene();
2747
2750
  if (previousEntry !== undefined) {
2748
- projection.remapWorlds(previousEntry.worlds.map((world) => worlds.indexOf(world)));
2751
+ projection.remapWorlds(
2752
+ previousEntry.worlds.map((world) =>
2753
+ (worlds as readonly RenderResourceScope[]).indexOf(world),
2754
+ ),
2755
+ );
2749
2756
  }
2750
2757
  const desired = new Set(
2751
2758
  renderables.map((snapshot) => identityKey(snapshot.worldId, snapshot.entityKey)),
@@ -2895,7 +2902,10 @@ export class PersistentRenderScene {
2895
2902
  const candidateFrame: ExtractedFrame = {
2896
2903
  ...resourceFrame,
2897
2904
  hiddenEntityReports: entry.hiddenEntityReports,
2898
- visibilityStats: { explicitlyHidden: entry.hiddenEntityReports.length },
2905
+ visibilityStats:
2906
+ entry.sourceStates.length === 0
2907
+ ? resourceFrame.visibilityStats
2908
+ : { explicitlyHidden: entry.hiddenEntityReports.length },
2899
2909
  dispatch,
2900
2910
  };
2901
2911
  entry.temporalDemanded = frameDemandsTemporal(candidateFrame);