@forgeax/engine-render 0.1.24 → 0.1.25

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 (135) hide show
  1. package/README.md +100 -1
  2. package/dist/assembly/factory.d.ts +1 -1
  3. package/dist/assembly/factory.d.ts.map +1 -1
  4. package/dist/assembly/material/surface-projection.d.ts +24 -0
  5. package/dist/assembly/material/surface-projection.d.ts.map +1 -0
  6. package/dist/assembly/material-shader-policy.d.ts +20 -0
  7. package/dist/assembly/material-shader-policy.d.ts.map +1 -1
  8. package/dist/assembly/webgpu-ready.d.ts.map +1 -1
  9. package/dist/assembly/webgpu-renderer.d.ts +1 -1
  10. package/dist/assembly/webgpu-renderer.d.ts.map +1 -1
  11. package/dist/assembly/webgpu-vertex-layouts.d.ts +89 -0
  12. package/dist/assembly/webgpu-vertex-layouts.d.ts.map +1 -0
  13. package/dist/assets/asset-decoders.d.ts.map +1 -1
  14. package/dist/authoring.mjs +1 -1
  15. package/dist/{chunk-Q752SHI5.mjs → chunk-ADHHVYLW.mjs} +3 -3
  16. package/dist/chunk-ADHHVYLW.mjs.map +1 -0
  17. package/dist/{chunk-TFH5I7JJ.mjs → chunk-E34VL5VI.mjs} +3 -3
  18. package/dist/{chunk-TFH5I7JJ.mjs.map → chunk-E34VL5VI.mjs.map} +1 -1
  19. package/dist/{chunk-KZFTK6DO.mjs → chunk-HKXTW355.mjs} +7 -4
  20. package/dist/chunk-HKXTW355.mjs.map +1 -0
  21. package/dist/{chunk-YKB5DQV7.mjs → chunk-JSEDTZYS.mjs} +87 -19
  22. package/dist/chunk-JSEDTZYS.mjs.map +1 -0
  23. package/dist/{chunk-UQYS7FRM.mjs → chunk-KM2NOX2I.mjs} +4 -4
  24. package/dist/{chunk-UQYS7FRM.mjs.map → chunk-KM2NOX2I.mjs.map} +1 -1
  25. package/dist/{chunk-Z3YYH2MB.mjs → chunk-N3RT2EUR.mjs} +94 -41
  26. package/dist/chunk-N3RT2EUR.mjs.map +1 -0
  27. package/dist/{chunk-YRUYGA4A.mjs → chunk-TCLDX7SZ.mjs} +559 -138
  28. package/dist/chunk-TCLDX7SZ.mjs.map +1 -0
  29. package/dist/{chunk-RR5L4SRS.mjs → chunk-ZZ4YQ474.mjs} +2 -2
  30. package/dist/{chunk-RR5L4SRS.mjs.map → chunk-ZZ4YQ474.mjs.map} +1 -1
  31. package/dist/components/instances.d.ts +13 -31
  32. package/dist/components/instances.d.ts.map +1 -1
  33. package/dist/construct-renderer.mjs +865 -142
  34. package/dist/construct-renderer.mjs.map +1 -1
  35. package/dist/gpu-driven/production-raster.d.ts +6 -1
  36. package/dist/gpu-driven/production-raster.d.ts.map +1 -1
  37. package/dist/gpu-scene.d.ts +4 -0
  38. package/dist/gpu-scene.d.ts.map +1 -1
  39. package/dist/index.d.ts +1 -0
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.mjs +27 -9
  42. package/dist/index.mjs.map +1 -1
  43. package/dist/instance-buffer-cache.d.ts +7 -1
  44. package/dist/instance-buffer-cache.d.ts.map +1 -1
  45. package/dist/instances.d.ts +101 -0
  46. package/dist/instances.d.ts.map +1 -0
  47. package/dist/internal.mjs +5 -5
  48. package/dist/materials.d.ts +63 -2
  49. package/dist/materials.d.ts.map +1 -1
  50. package/dist/pipeline-builder.d.ts.map +1 -1
  51. package/dist/pipeline-spec.d.ts.map +1 -1
  52. package/dist/record/frame-snapshot.d.ts +13 -2
  53. package/dist/record/frame-snapshot.d.ts.map +1 -1
  54. package/dist/record/frame.d.ts.map +1 -1
  55. package/dist/record/main-pass-geometry.d.ts +22 -3
  56. package/dist/record/main-pass-geometry.d.ts.map +1 -1
  57. package/dist/record/render-context.d.ts +2 -1
  58. package/dist/record/render-context.d.ts.map +1 -1
  59. package/dist/record/shadow-pass.d.ts +11 -1
  60. package/dist/record/shadow-pass.d.ts.map +1 -1
  61. package/dist/recovery/render-system-candidate.d.ts +1 -1
  62. package/dist/recovery/render-system-candidate.d.ts.map +1 -1
  63. package/dist/render-contract.d.ts +4 -0
  64. package/dist/render-contract.d.ts.map +1 -1
  65. package/dist/render-system-extract-tail.d.ts +4 -0
  66. package/dist/render-system-extract-tail.d.ts.map +1 -1
  67. package/dist/render-system-extract.d.ts +25 -1
  68. package/dist/render-system-extract.d.ts.map +1 -1
  69. package/dist/render-system.d.ts +4 -1
  70. package/dist/render-system.d.ts.map +1 -1
  71. package/dist/scene/render-scene-types.d.ts +2 -0
  72. package/dist/scene/render-scene-types.d.ts.map +1 -1
  73. package/dist/scene/render-scene.d.ts +12 -1
  74. package/dist/scene/render-scene.d.ts.map +1 -1
  75. package/dist/systems/skin-palette-allocator.d.ts +6 -0
  76. package/dist/systems/skin-palette-allocator.d.ts.map +1 -1
  77. package/dist/temporal/index.mjs +5 -5
  78. package/package.json +20 -20
  79. package/src/__tests__/gpu-scene.unit.test.ts +38 -2
  80. package/src/__tests__/instances-store.unit.test.ts +46 -0
  81. package/src/__tests__/instances-world-ownership.integration.test.ts +62 -0
  82. package/src/__tests__/material-contract-inventory.unit.test.ts +5 -13
  83. package/src/__tests__/material-pass-policy.unit.test.ts +69 -0
  84. package/src/__tests__/material-snapshot-mutation.integration.test.ts +69 -0
  85. package/src/__tests__/material-surface-authoring.test-d.ts +62 -0
  86. package/src/__tests__/materials-transmission-validation.unit.test.ts +24 -0
  87. package/src/__tests__/mesh-buffer-usage-surface.unit.test.ts +0 -4
  88. package/src/__tests__/renderer-factory-material-contract.unit.test.ts +42 -0
  89. package/src/__tests__/skin-motion-regression.unit.test.ts +33 -0
  90. package/src/__tests__/skinned-shadow-caster.test.ts +30 -2
  91. package/src/__tests__/surface-adjacent-regressions.integration.test.ts +60 -0
  92. package/src/__tests__/surface-standard-pipeline.browser.test.ts +20 -0
  93. package/src/__tests__/surface-standard-pipeline.dawn.test.ts +20 -0
  94. package/src/__tests__/surface-standard-pipeline.fixture.ts +132 -0
  95. package/src/__tests__/surface-variant-provenance.integration.test.ts +33 -0
  96. package/src/__tests__/volumetric-fog-budget-recovery.integration.test.ts +1 -1
  97. package/src/assembly/factory.ts +2 -0
  98. package/src/assembly/material/__tests__/standard-layer-plan-projection.integration.test.ts +18 -0
  99. package/src/assembly/material/__tests__/surface-pass-projection.integration.test.ts +62 -0
  100. package/src/assembly/material/surface-projection.ts +90 -0
  101. package/src/assembly/material-shader-policy.ts +88 -10
  102. package/src/assembly/recovery/renderer-recover.ts +1 -1
  103. package/src/assembly/webgpu-ready.ts +3 -16
  104. package/src/assembly/webgpu-renderer.ts +90 -158
  105. package/src/assembly/webgpu-vertex-layouts.ts +132 -0
  106. package/src/assets/asset-decoders.ts +27 -4
  107. package/src/components/instances.ts +14 -197
  108. package/src/gpu-driven/production-raster.ts +34 -1
  109. package/src/gpu-scene.ts +84 -29
  110. package/src/index.ts +1 -0
  111. package/src/instance-buffer-cache.ts +14 -1
  112. package/src/instances.ts +288 -0
  113. package/src/materials.ts +107 -12
  114. package/src/pipeline-builder.ts +14 -10
  115. package/src/pipeline-spec.ts +6 -14
  116. package/src/record/frame-snapshot.ts +19 -2
  117. package/src/record/frame.ts +31 -2
  118. package/src/record/main-pass-geometry.ts +498 -59
  119. package/src/record/main-pass-sprite-draws.ts +1 -1
  120. package/src/record/render-context.ts +4 -0
  121. package/src/record/shadow-pass.ts +46 -99
  122. package/src/recovery/render-system-candidate.ts +41 -4
  123. package/src/render-contract.ts +4 -0
  124. package/src/render-system-extract-tail.ts +107 -51
  125. package/src/render-system-extract.ts +53 -26
  126. package/src/render-system.ts +40 -5
  127. package/src/scene/render-scene-types.ts +2 -0
  128. package/src/scene/render-scene.ts +579 -36
  129. package/src/systems/skin-palette-allocator.ts +129 -7
  130. package/src/transmission/__tests__/surface-compatibility.integration.test.ts +32 -0
  131. package/dist/chunk-KZFTK6DO.mjs.map +0 -1
  132. package/dist/chunk-Q752SHI5.mjs.map +0 -1
  133. package/dist/chunk-YKB5DQV7.mjs.map +0 -1
  134. package/dist/chunk-YRUYGA4A.mjs.map +0 -1
  135. package/dist/chunk-Z3YYH2MB.mjs.map +0 -1
@@ -75,27 +75,9 @@
75
75
  // `MeshFilter.assetHandle` dangling path
76
76
  // (charter proposition 5; AC-11).
77
77
  //
78
- // feat-20260514-ecs-children-instances-managed-buffer-array M3 / w15:
79
- // The legacy `Instances { buffer, count }` form (cross-coupled with the
80
- // deleted `AssetRegistry.createInstancedBuffer` triplet + the retired
81
- // `InstancedBufferAsset` POD) is gone. Per-entity instance transforms now
82
- // live inside the ECS `Instances { transforms: 'array<f32>' }` column;
83
- // extract materialises a `Float32Array` snapshot per Instances-bearing
84
- // entity (via `world.get(e, Instances).transforms`) so the record stage
85
- // can upload to a per-entity GPU storage buffer cached by entity packed
86
- // u32.
87
- //
88
- // feat-20260515-buffer-array-vocab-collapse M3 / w15: the component-level
89
- // `arrayStride` hook on `Instances.transforms` was retired (decision section
90
- // 2.3 stride responsibility migration -- AI users gate at the set / push
91
- // call sites + RenderSystem extract entry holds a defensive fail-fast).
92
- // The extract entry checks `transforms.length % 16 === 0` directly after
93
- // the `world.get(entity, Instances)` snapshot is taken; violations route
94
- // `InstanceTransformsStrideMismatchError`
95
- // (`code: 'instance-transforms-stride-mismatch'`,
96
- // `detail: { actualLength, expectedStride: 16 }`) through the World
97
- // Layer-3 ErrorHandler and the renderable is skipped. The record stage
98
- // trusts the invariant and does not re-check.
78
+ // World owns explicit Instances transforms. Extraction makes a detached
79
+ // per-renderer projection; GPU residents acknowledge complete snapshots only
80
+ // after successful uploads. Reading a snapshot never changes author state.
99
81
 
100
82
  import type { AssetRegistry } from '@forgeax/engine-assets-runtime';
101
83
  import {
@@ -166,6 +148,7 @@ import type {
166
148
  RenderFeatureHiddenEntityReport,
167
149
  RenderFeatureWorldVisibilitySnapshot,
168
150
  } from './features/types';
151
+ import type { InstanceCollectionId, InstanceProjectionStore } from './instances';
169
152
  import type {
170
153
  MeshMaterialBindingDiagnostic,
171
154
  MeshMaterialBindingSource,
@@ -696,6 +679,8 @@ export interface RenderableSnapshot {
696
679
  readonly materialBindingSources: readonly MeshMaterialBindingSource[];
697
680
  /** Active structured override diagnostics for this renderable. */
698
681
  readonly materialBindingDiagnostics?: readonly MeshMaterialBindingDiagnostic[];
682
+ /** Effective authored visibility retained by the persistent scene owner. */
683
+ readonly authorVisible?: boolean;
699
684
  /**
700
685
  * feat-20260708-composited-multi-world-rendering M1 / D-1: the worldId
701
686
  * of the world this renderable was extracted from. Defaults to 0 in
@@ -761,6 +746,8 @@ export interface RenderableSnapshot {
761
746
  * Absent (`undefined`) means the entity is not skinned.
762
747
  */
763
748
  readonly skin?: SkinPaletteSlice;
749
+ /** Joint entity identities used by the persistent skin dependency index. */
750
+ readonly skinJointEntities?: readonly number[];
764
751
  /**
765
752
  * ECS-owned morph weights. Presence selects the Standard Pipeline CPU
766
753
  * specialized deformation lane; the asset remains the source of target
@@ -822,10 +809,14 @@ export interface GpuDrivenDrawSnapshot {
822
809
  }
823
810
 
824
811
  export interface InstancesSnapshot {
812
+ /** Renderer-owned collection identity. */
813
+ readonly collectionId?: InstanceCollectionId;
825
814
  /** Packed column-major mat4 transforms (16 f32 per instance). */
826
815
  readonly transforms: Float32Array;
827
816
  /** Number of instances (transforms.length / 16). */
828
817
  readonly instanceCount: number;
818
+ /** Collection revision captured by this detached frame snapshot. */
819
+ readonly revision?: number;
829
820
  /** Stable per-entity GPU buffer cache key (the packed Entity u32). */
830
821
  readonly cacheKey: number;
831
822
  /**
@@ -1134,6 +1125,7 @@ export function appendMaterialDispatchEntries(
1134
1125
  layer: number,
1135
1126
  paramSnapshot: Readonly<Record<string, number | number[] | string>> | undefined,
1136
1127
  passIndexOffset = 0,
1128
+ materialShaderIdOverride?: string,
1137
1129
  ): void {
1138
1130
  const matchedPasses = selectPasses(passes, {});
1139
1131
  for (let pIdx = 0; pIdx < matchedPasses.length; pIdx++) {
@@ -1144,6 +1136,11 @@ export function appendMaterialDispatchEntries(
1144
1136
  readonly queue?: number;
1145
1137
  readonly stencilReference?: number;
1146
1138
  };
1139
+ const authoredShaderId = runtimeMaterialShaderId(pass.program.module, pass.name);
1140
+ const dispatchShaderId =
1141
+ authoredShaderId === 'forgeax::default-shadow-caster'
1142
+ ? authoredShaderId
1143
+ : (materialShaderIdOverride ?? authoredShaderId);
1147
1144
  pendingDispatch.push({
1148
1145
  entityIndex: entity,
1149
1146
  materialHandle,
@@ -1158,7 +1155,14 @@ export function appendMaterialDispatchEntries(
1158
1155
  defines: undefined,
1159
1156
  vertexEntry: pass.program.vertexEntry,
1160
1157
  fragmentEntry: pass.program.fragmentEntry,
1161
- materialShaderId: runtimeMaterialShaderId(pass.program.module, pass.name),
1158
+ // A cooked Standard publication supplies one specialization key for its
1159
+ // forward/deferred artifact, but its ShadowCaster pass still owns the
1160
+ // engine depth-only shader. Keep the pass-local canonical identity so a
1161
+ // specialized Standard source (which only declares fs_main/fs_gbuffer)
1162
+ // is never asked to build an fs_shadow pipeline. Authored non-Standard
1163
+ // ShadowCaster modules retain the publication override and continue to
1164
+ // use their producer-owned source.
1165
+ materialShaderId: dispatchShaderId,
1162
1166
  paramSnapshot,
1163
1167
  ...(passState.stencilReference !== undefined && {
1164
1168
  stencilReference: passState.stencilReference,
@@ -1410,6 +1414,20 @@ export function runtimeMaterialShaderId(
1410
1414
  }
1411
1415
  }
1412
1416
 
1417
+ /**
1418
+ * Select the runtime identity for one resolved material. A cooked publication
1419
+ * owns the composed material artifact identity; the authored pass module is
1420
+ * the built-in fallback used before that publication is ready.
1421
+ */
1422
+ export function runtimeMaterialShaderIdForMaterial(
1423
+ material: MaterialAsset,
1424
+ passes: readonly MaterialPass[],
1425
+ assets: AssetRegistry,
1426
+ ): string | undefined {
1427
+ const authored = runtimeMaterialShaderId(passes[0]?.program.module, passes[0]?.name);
1428
+ return assets.getMaterialProjectionForPayload(material)?.specializationKey ?? authored;
1429
+ }
1430
+
1413
1431
  export function pipelineRenderState(
1414
1432
  renderState:
1415
1433
  | (MaterialRenderState & {
@@ -1934,10 +1952,7 @@ export function resolveMaterialSnapshot(
1934
1952
  if (!resolvedResult.ok) return defaultMaterialSnapshot(handleRaw);
1935
1953
  const resolved = resolvedResult.value;
1936
1954
  const allPasses = resolved.passes;
1937
- const firstPassShader =
1938
- allPasses.length > 0
1939
- ? runtimeMaterialShaderId(allPasses[0]?.program.module, allPasses[0]?.name)
1940
- : undefined;
1955
+ const firstPassShader = runtimeMaterialShaderIdForMaterial(asset, allPasses, assetsRef);
1941
1956
  const pv = materialValuesToLinearRuntime(
1942
1957
  resolved.values,
1943
1958
  materialColorParameterSchema(resolved.parameters ?? [], firstPassShader, assetsRef),
@@ -2542,11 +2557,17 @@ export interface PreparedExtractContext {
2542
2557
  readonly cull: 'self' | 'none' | 'external';
2543
2558
  readonly cullCameras: readonly CameraSnapshot[] | undefined;
2544
2559
  readonly renderables: 'full' | 'none';
2560
+ /** Restrict renderable extraction to these entities while still reading frame resources. */
2561
+ readonly renderableEntities: ReadonlySet<number> | undefined;
2562
+ /** Keep authored-hidden renderables in the persistent source projection. */
2563
+ readonly retainHidden: boolean;
2545
2564
  readonly visibility: VisibilitySnapshot;
2546
2565
  /** Producer-owned artifact lookup; extraction only, never record-time asset reads. */
2547
2566
  readonly getMaterialShaderArtifact:
2548
2567
  | ((materialShaderId: string) => MaterialShaderArtifact | undefined)
2549
2568
  | undefined;
2569
+ /** Renderer-owned canonical source for explicit large instance collections. */
2570
+ readonly instanceCollections: InstanceProjectionStore | undefined;
2550
2571
  }
2551
2572
 
2552
2573
  const ALL_VISIBLE: VisibilitySnapshot = {
@@ -2734,9 +2755,12 @@ export function prepareExtractContext(
2734
2755
  readonly cull?: 'self' | 'none' | 'external';
2735
2756
  readonly cullCameras?: readonly CameraSnapshot[];
2736
2757
  readonly renderables?: 'full' | 'none';
2758
+ readonly renderableEntities?: ReadonlySet<number>;
2759
+ readonly retainHidden?: boolean;
2737
2760
  readonly getMaterialShaderArtifact?: (
2738
2761
  materialShaderId: string,
2739
2762
  ) => MaterialShaderArtifact | undefined;
2763
+ readonly instanceCollections?: InstanceProjectionStore;
2740
2764
  } = {},
2741
2765
  ): PreparedExtractContext {
2742
2766
  const renderables = options.renderables ?? 'full';
@@ -2748,8 +2772,11 @@ export function prepareExtractContext(
2748
2772
  cull: options.cull ?? 'self',
2749
2773
  cullCameras: options.cullCameras,
2750
2774
  renderables,
2775
+ renderableEntities: options.renderableEntities,
2776
+ retainHidden: options.retainHidden ?? false,
2751
2777
  visibility: renderables === 'none' ? ALL_VISIBLE : resolveVisibility(world),
2752
2778
  getMaterialShaderArtifact: options.getMaterialShaderArtifact,
2779
+ instanceCollections: options.instanceCollections,
2753
2780
  };
2754
2781
  }
2755
2782
 
@@ -118,7 +118,13 @@ import type { EnvironmentInspection } from './environment/inspection';
118
118
  import { GPU_BUFFER_USAGE_COPY_DST, GPU_BUFFER_USAGE_UNIFORM } from './gpu-usage';
119
119
  import { resetHdrpBuffers } from './hdrp-buffers';
120
120
  import { assembleMaterialWithSkylightEntries } from './ibl/skylight-bind-group';
121
- import { disposeInstanceBuffers, disposeTransientInstanceBuffers } from './instance-buffer-cache';
121
+ import {
122
+ disposeInstanceBufferChunks,
123
+ disposeInstanceBuffers,
124
+ disposeTransientInstanceBuffers,
125
+ } from './instance-buffer-cache';
126
+ import type { InstanceCollectionInspection } from './instances';
127
+ import { InstanceProjectionStore } from './instances';
122
128
  import {
123
129
  type MeshMaterialBindingObservation,
124
130
  type MeshMaterialBindingPreparationFailure,
@@ -182,7 +188,11 @@ import type {
182
188
  RenderableSnapshot,
183
189
  } from './render-system-extract';
184
190
  import { extractFrames } from './render-system-extract';
185
- import { type PersistentGpuDrivenCandidate, PersistentRenderScene } from './scene/render-scene';
191
+ import {
192
+ type PersistentGpuDrivenCandidate,
193
+ type PersistentRenderCandidateRequest,
194
+ PersistentRenderScene,
195
+ } from './scene/render-scene';
186
196
  import { inspectLodOcclusion } from './scene/visibility/inspection';
187
197
  import { resolveSsaoParameters } from './ssao-config';
188
198
  import { projectSsrDependencies, resolveSsrAdmissionGeneration } from './ssr/admission';
@@ -660,7 +670,7 @@ export interface RenderSystem {
660
670
  /** Prepare a detached graph candidate without entering the frame record path. */
661
671
  prepareRecoveryGraphCandidate(
662
672
  runtime: RecoveryGraphCandidateRuntime,
663
- ): RecoveryGraphCandidatePreparation;
673
+ ): Promise<RecoveryGraphCandidatePreparation>;
664
674
  /** Finish and submit detached candidate setup, never a frame receipt. */
665
675
  submitCandidateSetup(
666
676
  candidate: RecoveryGraphCandidate,
@@ -686,6 +696,8 @@ export interface RenderSystem {
686
696
  readonly pipelineDispatchCounts: {
687
697
  readonly unlit: number;
688
698
  };
699
+ /** Detached residency and upload facts for explicit instance collections. */
700
+ readonly instanceCollectionsInspection: readonly InstanceCollectionInspection[];
689
701
  /**
690
702
  * feat-20260528-frustum-culling M5 / w14: per-frame frustum-culling counters.
691
703
  * Updated by `draw([world], { cameraOwner: 0, resourceOwner: 0 })` on every call from the Extract stage.
@@ -1071,6 +1083,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
1071
1083
  let preparedWorlds: readonly World[] = [];
1072
1084
  let latestCamera: CameraSnapshot | undefined;
1073
1085
  const pointsLinesOwner = new StandardPointsLinesOwner(internals);
1086
+ const instanceCollections = new InstanceProjectionStore();
1074
1087
  const reflectionProbeOwner = new ReflectionProbeRecordOwner(internals);
1075
1088
  let ssrFormatReceipt: import('@forgeax/engine-rhi').RhiTextureFormatCapabilityReceipt | undefined;
1076
1089
  let ssrFormatProbeGeneration = -1;
@@ -1112,6 +1125,8 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
1112
1125
  onSharedRefMutation: (worldId, handle) => {
1113
1126
  internals.gpuStore.invalidateMesh(handle, preparedWorlds[worldId] ?? worldId);
1114
1127
  },
1128
+ instanceCollections,
1129
+ getSkinPaletteAllocator: () => internals.getPipelineState()?.skinPaletteAllocator ?? null,
1115
1130
  });
1116
1131
  let environmentLifecycle = new EnvironmentLifecycle(internals.deviceScope);
1117
1132
  const transmissionAdmission = new TransmissionCandidateAdmission();
@@ -1362,6 +1377,8 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
1362
1377
  currentDirectionalShadowView: null,
1363
1378
  currentSpotShadowView: null,
1364
1379
  instanceBuffers: new Map(),
1380
+ instanceResidency: new Map(),
1381
+ instanceBufferChunks: new Map(),
1365
1382
  probeBlendBuffers: new Map(),
1366
1383
  probeBlendRecordBufferCapacity: 0,
1367
1384
  morphBuffers: new Map(),
@@ -2294,6 +2311,12 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2294
2311
  createPreparedResolverCaches,
2295
2312
  });
2296
2313
  return {
2314
+ get instanceCollectionsInspection(): readonly InstanceCollectionInspection[] {
2315
+ return instanceCollections._inspections(
2316
+ frameState.instanceResidency,
2317
+ frameState.frameNumber - 1,
2318
+ );
2319
+ },
2297
2320
  releaseProfilerCatalog(): void {
2298
2321
  releaseProfilerCatalog?.();
2299
2322
  releaseProfilerCatalog = undefined;
@@ -2555,7 +2578,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2555
2578
  compositionWorlds,
2556
2579
  { cameraOwner, resourceOwner },
2557
2580
  internals.assets.catalogEpoch,
2558
- (renderables) =>
2581
+ (request: PersistentRenderCandidateRequest) =>
2559
2582
  extractFrames(
2560
2583
  compositionWorlds,
2561
2584
  { cameraOwner, resourceOwner },
@@ -2564,7 +2587,12 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2564
2587
  persistentRenderScene.materialSnapshotCacheStore(),
2565
2588
  {
2566
2589
  cull: 'none',
2567
- renderables,
2590
+ renderables: request === 'none' ? 'none' : 'full',
2591
+ ...(request === 'none' || request === 'full'
2592
+ ? {}
2593
+ : { renderableEntitiesByWorld: request.entitiesByWorld }),
2594
+ retainHidden: true,
2595
+ instanceCollections,
2568
2596
  ...(internals.getMaterialShaderArtifact === undefined
2569
2597
  ? {}
2570
2598
  : { getMaterialShaderArtifact: internals.getMaterialShaderArtifact }),
@@ -3496,6 +3524,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3496
3524
  (internals as TimingSessionHost).gpuPassTimingBeginReason = undefined;
3497
3525
  pointsLinesOwner.dispose();
3498
3526
  persistentRenderScene.dispose();
3527
+ instanceCollections.dispose();
3499
3528
  gpuDrivenProduction.dispose();
3500
3529
  occlusionRuntime?.dispose();
3501
3530
  disposeFeatureGpuWork();
@@ -3565,6 +3594,10 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3565
3594
  // so destroy failures fire structured errors (unified per-frame +
3566
3595
  // dispose error strategy).
3567
3596
  disposeInstanceBuffers(frameState.instanceBuffers, internals.errorRegistry);
3597
+ frameState.instanceResidency?.clear();
3598
+ if (frameState.instanceBufferChunks !== undefined) {
3599
+ disposeInstanceBufferChunks(frameState.instanceBufferChunks, internals.errorRegistry);
3600
+ }
3568
3601
  disposeTransientInstanceBuffers(frameState.transientInstanceBuffers, internals.errorRegistry);
3569
3602
  if (frameState.probeBlendRecordBuffer !== undefined) {
3570
3603
  if (!frameState.probeBlendRecordBuffer.isDestroyed) {
@@ -3706,6 +3739,8 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3706
3739
  }
3707
3740
  frameState.retiredCompiledFrameGraphs.clear();
3708
3741
  frameState.instanceBuffers.clear();
3742
+ frameState.instanceBufferChunks?.clear();
3743
+ frameState.instanceResidency?.clear();
3709
3744
  delete frameState.probeBlendRecordBuffer;
3710
3745
  frameState.probeBlendRecordBufferCapacity = 0;
3711
3746
  frameState.probeBlendBuffers.clear();
@@ -36,6 +36,8 @@ export interface RenderSceneApplyResult {
36
36
  readonly ignoredLateUpdates: number;
37
37
  readonly createdSlots: readonly RenderSceneSlot[];
38
38
  readonly updatedSlots: readonly RenderSceneSlot[];
39
+ /** Full retained-snapshot updates; transform spans stay in updatedSlots only. */
40
+ readonly contentUpdatedSlots?: readonly RenderSceneSlot[];
39
41
  readonly removedSlots: readonly RenderSceneRecord[];
40
42
  readonly recreatedSlots: readonly RenderSceneSlot[];
41
43
  readonly resynced: number;