@forgeax/engine-render 0.1.32 → 0.1.33

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 (250) hide show
  1. package/README.md +379 -33
  2. package/dist/assembly/analytic-fog-registration.d.ts +3 -0
  3. package/dist/assembly/analytic-fog-registration.d.ts.map +1 -0
  4. package/dist/assembly/fxaa-registration.d.ts +4 -0
  5. package/dist/assembly/fxaa-registration.d.ts.map +1 -0
  6. package/dist/assembly/lod-observation.d.ts +4 -0
  7. package/dist/assembly/lod-observation.d.ts.map +1 -0
  8. package/dist/assembly/motion-blur-registration.d.ts +3 -0
  9. package/dist/assembly/motion-blur-registration.d.ts.map +1 -0
  10. package/dist/assembly/renderer-facade.d.ts.map +1 -1
  11. package/dist/assembly/temporal-post-process-prewarm.d.ts +13 -0
  12. package/dist/assembly/temporal-post-process-prewarm.d.ts.map +1 -0
  13. package/dist/assembly/webgpu-ready-contract.d.ts +4 -3
  14. package/dist/assembly/webgpu-ready-contract.d.ts.map +1 -1
  15. package/dist/assembly/webgpu-ready.d.ts +1 -1
  16. package/dist/assembly/webgpu-ready.d.ts.map +1 -1
  17. package/dist/assembly/webgpu-renderer.d.ts.map +1 -1
  18. package/dist/bloom-admission.d.ts +13 -0
  19. package/dist/bloom-admission.d.ts.map +1 -0
  20. package/dist/{chunk-DRR7D3KX.mjs → chunk-3RTCJTH4.mjs} +12 -9
  21. package/dist/chunk-3RTCJTH4.mjs.map +1 -0
  22. package/dist/{chunk-S665NJNF.mjs → chunk-LEAXOMYJ.mjs} +3 -3
  23. package/dist/{chunk-S665NJNF.mjs.map → chunk-LEAXOMYJ.mjs.map} +1 -1
  24. package/dist/{chunk-6TFBXLRJ.mjs → chunk-NB2S7ZRX.mjs} +155 -11
  25. package/dist/chunk-NB2S7ZRX.mjs.map +1 -0
  26. package/dist/{chunk-LULCA3OF.mjs → chunk-NNQL7CB3.mjs} +4 -4
  27. package/dist/{chunk-LULCA3OF.mjs.map → chunk-NNQL7CB3.mjs.map} +1 -1
  28. package/dist/{chunk-4NNZQCL3.mjs → chunk-OO3DRYK7.mjs} +66 -200
  29. package/dist/chunk-OO3DRYK7.mjs.map +1 -0
  30. package/dist/{chunk-GWCEMDVR.mjs → chunk-PLAXOOX5.mjs} +232 -7
  31. package/dist/chunk-PLAXOOX5.mjs.map +1 -0
  32. package/dist/{chunk-JRIPCMYC.mjs → chunk-VOZWDM3L.mjs} +283 -193
  33. package/dist/chunk-VOZWDM3L.mjs.map +1 -0
  34. package/dist/{chunk-JCIRYQ4V.mjs → chunk-ZDQWGQW7.mjs} +10 -4
  35. package/dist/chunk-ZDQWGQW7.mjs.map +1 -0
  36. package/dist/components/atmosphere.d.ts +2 -0
  37. package/dist/components/atmosphere.d.ts.map +1 -1
  38. package/dist/components/camera.d.ts +20 -4
  39. package/dist/components/camera.d.ts.map +1 -1
  40. package/dist/components/mesh-filter.d.ts +6 -5
  41. package/dist/components/mesh-filter.d.ts.map +1 -1
  42. package/dist/components/motion-blur.d.ts +1 -0
  43. package/dist/components/motion-blur.d.ts.map +1 -1
  44. package/dist/construct-renderer.mjs +2865 -526
  45. package/dist/construct-renderer.mjs.map +1 -1
  46. package/dist/device/gpu-residency.d.ts +6 -0
  47. package/dist/device/gpu-residency.d.ts.map +1 -1
  48. package/dist/environment/analytic-fog.d.ts +6 -0
  49. package/dist/environment/analytic-fog.d.ts.map +1 -0
  50. package/dist/errors/render.d.ts +6 -2
  51. package/dist/errors/render.d.ts.map +1 -1
  52. package/dist/extract/camera.d.ts.map +1 -1
  53. package/dist/extract/contracts.d.ts +7 -0
  54. package/dist/extract/contracts.d.ts.map +1 -1
  55. package/dist/extract/environment.d.ts +2 -0
  56. package/dist/extract/environment.d.ts.map +1 -1
  57. package/dist/features/error-reporting.d.ts +13 -0
  58. package/dist/features/error-reporting.d.ts.map +1 -0
  59. package/dist/features/host.d.ts +5 -0
  60. package/dist/features/host.d.ts.map +1 -1
  61. package/dist/features/motion-blur/motion-blur-feature.d.ts +20 -2
  62. package/dist/features/motion-blur/motion-blur-feature.d.ts.map +1 -1
  63. package/dist/features/motion-blur/motion-blur-params.d.ts +21 -0
  64. package/dist/features/motion-blur/motion-blur-params.d.ts.map +1 -1
  65. package/dist/features/motion-blur/motion-blur-runtime.d.ts +54 -0
  66. package/dist/features/motion-blur/motion-blur-runtime.d.ts.map +1 -0
  67. package/dist/features/plan.d.ts +7 -4
  68. package/dist/features/plan.d.ts.map +1 -1
  69. package/dist/features/prepared-gpu-work.d.ts +9 -5
  70. package/dist/features/prepared-gpu-work.d.ts.map +1 -1
  71. package/dist/features/render-graph-compute.d.ts.map +1 -1
  72. package/dist/features/render-graph-contribution.d.ts.map +1 -1
  73. package/dist/gpu-driven/material-artifacts.d.ts.map +1 -1
  74. package/dist/gpu-driven/prepared-draw.d.ts +7 -0
  75. package/dist/gpu-driven/prepared-draw.d.ts.map +1 -1
  76. package/dist/gpu-driven/production-raster.d.ts +3 -1
  77. package/dist/gpu-driven/production-raster.d.ts.map +1 -1
  78. package/dist/gpu-driven/view-gpu.d.ts +9 -0
  79. package/dist/gpu-driven/view-gpu.d.ts.map +1 -1
  80. package/dist/gpu-scene.d.ts +6 -1
  81. package/dist/gpu-scene.d.ts.map +1 -1
  82. package/dist/index.d.ts +5 -2
  83. package/dist/index.d.ts.map +1 -1
  84. package/dist/index.mjs +5 -5
  85. package/dist/inspection-types.d.ts +43 -2
  86. package/dist/inspection-types.d.ts.map +1 -1
  87. package/dist/instances.d.ts +8 -1
  88. package/dist/instances.d.ts.map +1 -1
  89. package/dist/internal.mjs +6 -6
  90. package/dist/pipeline/standard-post.d.ts +28 -19
  91. package/dist/pipeline/standard-post.d.ts.map +1 -1
  92. package/dist/pipeline-spec.d.ts +2 -2
  93. package/dist/record/frame-snapshot.d.ts +3 -0
  94. package/dist/record/frame-snapshot.d.ts.map +1 -1
  95. package/dist/record/frame-targets.d.ts.map +1 -1
  96. package/dist/record/frame.d.ts +5 -2
  97. package/dist/record/frame.d.ts.map +1 -1
  98. package/dist/record/main-pass-geometry.d.ts.map +1 -1
  99. package/dist/record/mesh-ssbo.d.ts +6 -5
  100. package/dist/record/mesh-ssbo.d.ts.map +1 -1
  101. package/dist/record/render-context.d.ts +12 -28
  102. package/dist/record/render-context.d.ts.map +1 -1
  103. package/dist/record/shadow-pass.d.ts.map +1 -1
  104. package/dist/record/skybox-post-pass.d.ts +5 -3
  105. package/dist/record/skybox-post-pass.d.ts.map +1 -1
  106. package/dist/record/typed-frame-graph.d.ts +1 -1
  107. package/dist/record/typed-frame-graph.d.ts.map +1 -1
  108. package/dist/recovery/render-system-candidate.d.ts.map +1 -1
  109. package/dist/render-contract.d.ts +24 -4
  110. package/dist/render-contract.d.ts.map +1 -1
  111. package/dist/render-pipeline.d.ts +3 -1
  112. package/dist/render-pipeline.d.ts.map +1 -1
  113. package/dist/render-system-extract-tail.d.ts.map +1 -1
  114. package/dist/render-system-extract.d.ts +8 -1
  115. package/dist/render-system-extract.d.ts.map +1 -1
  116. package/dist/render-system.d.ts +7 -1
  117. package/dist/render-system.d.ts.map +1 -1
  118. package/dist/scene/render-scene.d.ts +2 -0
  119. package/dist/scene/render-scene.d.ts.map +1 -1
  120. package/dist/temporal/frame-coordinator.d.ts.map +1 -1
  121. package/dist/temporal/frame.d.ts +7 -0
  122. package/dist/temporal/frame.d.ts.map +1 -1
  123. package/dist/temporal/index.mjs +5 -5
  124. package/dist/temporal/view.d.ts +6 -1
  125. package/dist/temporal/view.d.ts.map +1 -1
  126. package/dist/typed-render-graph-primitives.d.ts +10 -7
  127. package/dist/typed-render-graph-primitives.d.ts.map +1 -1
  128. package/dist/volume/component.d.ts +0 -1
  129. package/dist/volume/component.d.ts.map +1 -1
  130. package/dist/volume/passes.d.ts.map +1 -1
  131. package/dist/volume/temporal.d.ts +3 -1
  132. package/dist/volume/temporal.d.ts.map +1 -1
  133. package/package.json +21 -21
  134. package/src/__tests__/analytic-fog-graph.unit.test.ts +199 -0
  135. package/src/__tests__/analytic-fog-optics.unit.test.ts +38 -0
  136. package/src/__tests__/bloom-exact-zero.unit.test.ts +112 -18
  137. package/src/__tests__/bloom-lifecycle.integration.test.ts +17 -4
  138. package/src/__tests__/dynamic-resolution-plugin.integration.test.ts +20 -0
  139. package/src/__tests__/environment-fog-lanes.integration.test.ts +14 -0
  140. package/src/__tests__/environment-selection.unit.test.ts +12 -0
  141. package/src/__tests__/factory-contract.integration.test.ts +331 -68
  142. package/src/__tests__/frame-camera-selection.unit.test.ts +39 -1
  143. package/src/__tests__/frame-plan-contract.unit.test.ts +2 -0
  144. package/src/__tests__/gpu-driven-indirect-raster-evidence.ts +2 -1
  145. package/src/__tests__/gpu-driven-shadow-views.dawn.test.ts +1 -1
  146. package/src/__tests__/gpu-driven-shadow-views.integration.test.ts +1 -1
  147. package/src/__tests__/gpu-driven-view-gpu-evidence.ts +11 -0
  148. package/src/__tests__/gpu-resource-store-stride.unit.test.ts +12 -0
  149. package/src/__tests__/gpu-scene.unit.test.ts +91 -0
  150. package/src/__tests__/instances-store.unit.test.ts +75 -0
  151. package/src/__tests__/mesh-submission-lifetime.unit.test.ts +31 -0
  152. package/src/__tests__/motion-blur-compute.dawn.test.ts +583 -0
  153. package/src/__tests__/prepared-gpu-driven-pbr.integration.test.ts +21 -0
  154. package/src/__tests__/prepared-graphics-lifecycle.unit.test.ts +86 -0
  155. package/src/__tests__/public-surface.test-d.ts +8 -0
  156. package/src/__tests__/render-error-code-owner.test-d.ts +2 -0
  157. package/src/__tests__/render-feature-error-reporting.unit.test.ts +24 -0
  158. package/src/__tests__/render-feature-stage-fault-recovery.unit.test.ts +23 -0
  159. package/src/__tests__/render-scene-temporal-retry.unit.test.ts +31 -0
  160. package/src/__tests__/renderer-frame-transaction.integration.test.ts +7 -1
  161. package/src/__tests__/shader-manifest-fixture.ts +4 -4
  162. package/src/__tests__/skinned-shadow-caster.test.ts +2 -0
  163. package/src/__tests__/standard-lighting-write.unit.test.ts +2 -1
  164. package/src/__tests__/standard-pipeline.dawn.test.ts +1 -1
  165. package/src/__tests__/standard-pipeline.integration.test.ts +18 -8
  166. package/src/__tests__/temporal-reset-matrix.unit.test.ts +10 -0
  167. package/src/__tests__/temporal-view.unit.test.ts +2 -1
  168. package/src/__tests__/typed-pipeline-topology.unit.test.ts +20 -4
  169. package/src/__tests__/volume-optics-oracle.unit.test.ts +3 -1
  170. package/src/__tests__/volumetric-fog-budget-recovery.integration.test.ts +177 -13
  171. package/src/__tests__/volumetric-fog-stage-readback.dawn.test.ts +447 -61
  172. package/src/__tests__/volumetric-fog-temporal.unit.test.ts +27 -0
  173. package/src/__tests__/volumetric-fog-topology.unit.test.ts +19 -3
  174. package/src/assembly/analytic-fog-registration.ts +14 -0
  175. package/src/assembly/fxaa-registration.ts +17 -0
  176. package/src/assembly/lod-observation.ts +12 -0
  177. package/src/assembly/motion-blur-registration.ts +17 -0
  178. package/src/assembly/renderer-facade.ts +2 -0
  179. package/src/assembly/temporal-post-process-prewarm.ts +40 -0
  180. package/src/assembly/webgpu-ready-contract.ts +8 -3
  181. package/src/assembly/webgpu-ready.ts +181 -245
  182. package/src/assembly/webgpu-renderer.ts +16 -31
  183. package/src/bloom-admission.ts +21 -0
  184. package/src/components/__tests__/motion-blur.unit.test.ts +1 -0
  185. package/src/components/atmosphere.ts +4 -0
  186. package/src/components/camera.ts +66 -10
  187. package/src/components/mesh-filter.ts +11 -9
  188. package/src/components/motion-blur.ts +1 -0
  189. package/src/device/gpu-residency.ts +56 -12
  190. package/src/environment/analytic-fog.ts +44 -0
  191. package/src/environment/background.ts +2 -2
  192. package/src/errors/render.ts +14 -1
  193. package/src/extract/camera.ts +20 -7
  194. package/src/extract/contracts.ts +7 -0
  195. package/src/extract/environment.ts +4 -0
  196. package/src/features/error-reporting.ts +78 -0
  197. package/src/features/host.ts +25 -12
  198. package/src/features/motion-blur/__tests__/motion-blur-feature.unit.test.ts +111 -1
  199. package/src/features/motion-blur/__tests__/motion-blur-params.unit.test.ts +58 -3
  200. package/src/features/motion-blur/motion-blur-feature.ts +1519 -15
  201. package/src/features/motion-blur/motion-blur-params.ts +70 -2
  202. package/src/features/motion-blur/motion-blur-runtime.ts +176 -0
  203. package/src/features/plan.ts +25 -8
  204. package/src/features/prepared-gpu-work.ts +86 -35
  205. package/src/features/render-graph-compute.ts +13 -5
  206. package/src/features/render-graph-contribution.ts +2 -0
  207. package/src/gpu-driven/material-artifacts.ts +2 -7
  208. package/src/gpu-driven/prepared-draw.ts +15 -10
  209. package/src/gpu-driven/production-raster.ts +83 -7
  210. package/src/gpu-driven/view-gpu.ts +17 -0
  211. package/src/gpu-scene.ts +188 -21
  212. package/src/index.ts +32 -0
  213. package/src/inspection-types.ts +60 -2
  214. package/src/instances.ts +80 -1
  215. package/src/pipeline/standard-post.ts +307 -78
  216. package/src/pipeline-spec.ts +5 -5
  217. package/src/points-lines/__tests__/view-ubo.unit.test.ts +2 -1
  218. package/src/record/__tests__/temporal-payload.unit.test.ts +2 -1
  219. package/src/record/frame-snapshot.ts +5 -1
  220. package/src/record/frame-targets.ts +10 -6
  221. package/src/record/frame.ts +163 -22
  222. package/src/record/main-pass-geometry.ts +30 -1
  223. package/src/record/main-pass-sprite-draws.ts +6 -1
  224. package/src/record/mesh-ssbo.ts +43 -6
  225. package/src/record/render-context.ts +12 -28
  226. package/src/record/shadow-pass.ts +6 -1
  227. package/src/record/skybox-post-pass.ts +122 -196
  228. package/src/record/typed-frame-graph.ts +153 -9
  229. package/src/recovery/render-system-candidate.ts +22 -10
  230. package/src/render-contract.ts +23 -4
  231. package/src/render-pipeline.ts +6 -1
  232. package/src/render-system-extract-tail.ts +16 -0
  233. package/src/render-system-extract.ts +45 -13
  234. package/src/render-system.ts +255 -120
  235. package/src/scene/render-scene.ts +85 -4
  236. package/src/temporal/__tests__/temporal-frame-transaction.unit.test.ts +36 -0
  237. package/src/temporal/frame-coordinator.ts +8 -1
  238. package/src/temporal/frame.ts +20 -0
  239. package/src/temporal/view.ts +13 -2
  240. package/src/typed-render-graph-primitives.ts +121 -48
  241. package/src/volume/component.ts +0 -1
  242. package/src/volume/passes.ts +8 -4
  243. package/src/volume/resources.ts +1 -1
  244. package/src/volume/temporal.ts +11 -0
  245. package/dist/chunk-4NNZQCL3.mjs.map +0 -1
  246. package/dist/chunk-6TFBXLRJ.mjs.map +0 -1
  247. package/dist/chunk-DRR7D3KX.mjs.map +0 -1
  248. package/dist/chunk-GWCEMDVR.mjs.map +0 -1
  249. package/dist/chunk-JCIRYQ4V.mjs.map +0 -1
  250. package/dist/chunk-JRIPCMYC.mjs.map +0 -1
@@ -1,3 +1,5 @@
1
+ import { encodeAnalyticFogParams } from '../environment/analytic-fog';
2
+ import type { FogFrame } from '../extract/environment';
1
3
  import { collectGpuDrivenMaterialArtifacts } from '../gpu-driven/material-artifacts';
2
4
  import { materialBindingKey, projectMaterialBindingClasses } from '../gpu-driven/material-bindings';
3
5
  import { uploadMaterialUniforms } from './material-uniforms';
@@ -5,7 +7,7 @@ import { uploadMaterialUniforms } from './material-uniforms';
5
7
  // Extracted from render-system-record.ts (feat-20260704 M3/w17, pure move).
6
8
 
7
9
  import { HANDLE_NINESLICE_QUAD, resolveAssetHandle } from '@forgeax/engine-assets-runtime';
8
- import type { World } from '@forgeax/engine-ecs';
10
+ import { Time, type World } from '@forgeax/engine-ecs';
9
11
  import type { RenderReadLease } from '@forgeax/engine-ecs/projection';
10
12
  import { deriveVertexCount } from '@forgeax/engine-geometry';
11
13
  import { frustum, mat4, vec3 } from '@forgeax/engine-math';
@@ -44,6 +46,7 @@ import {
44
46
  } from '../features/depth-of-field/depth-of-field-params';
45
47
  import type { GpuDrivenProduction, PreparedGpuDrivenFrame } from '../gpu-driven/production-raster';
46
48
  import type { ShadowViewIdentity } from '../gpu-driven/shadow-views';
49
+ import type { GpuDrivenLodSubmitIdentity } from '../gpu-driven/view-gpu';
47
50
  import { GpuBuffer } from '../gpu-resource';
48
51
  import { GPU_TEXTURE_USAGE_COPY_SRC } from '../gpu-texture-usage';
49
52
  import {
@@ -304,6 +307,14 @@ export async function observeCurrentFrame(
304
307
  }
305
308
 
306
309
  import { acknowledgeRenderFeatureSubmission } from '../features/host';
310
+ import {
311
+ effectiveMotionBlurSampleCount,
312
+ isMotionBlurIntervalValid,
313
+ MOTION_BLUR_PARAMS_BYTE_SIZE,
314
+ motionBlurExposureScale,
315
+ motionBlurSampleDelta,
316
+ motionBlurTemporalDemand,
317
+ } from '../features/motion-blur/motion-blur-params';
307
318
  import { createTemporalView, resolveTemporalReset, type TemporalView } from '../temporal/view';
308
319
  import { inspectVolumetricFog } from '../volume/inspection';
309
320
  import {
@@ -904,6 +915,7 @@ function volumeTemporalSignature(
904
915
  densityGeneration: prepared.densityGeneration,
905
916
  width,
906
917
  height,
918
+ ...(fog.worldTimeSeconds === undefined ? {} : { worldTimeSeconds: fog.worldTimeSeconds }),
907
919
  ...(fog.lightKind === undefined ? {} : { lightKind: fog.lightKind }),
908
920
  ...(fog.lightEntity === undefined ? {} : { lightEntity: fog.lightEntity }),
909
921
  ...(fog.pointLightEntity === undefined ? {} : { pointLightEntity: fog.pointLightEntity }),
@@ -1008,7 +1020,7 @@ function prepareVolumetricFogFrame(
1008
1020
  }
1009
1021
  return undefined;
1010
1022
  }
1011
- const params = new Float32Array(32);
1023
+ const params = new Float32Array(36);
1012
1024
  params.set(fog.fog.bounds.min, 0);
1013
1025
  params.set(fog.fog.bounds.max, 4);
1014
1026
  params.set(fog.fog.extinction, 8);
@@ -1029,10 +1041,8 @@ function prepareVolumetricFogFrame(
1029
1041
  const lightColor = selectedSpot?.color ?? lights.directional?.color ?? vec3.create(0, 0, 0);
1030
1042
  if (lightDirection === undefined || lightColor === undefined) return undefined;
1031
1043
  params.set(lightDirection, 20);
1032
- // Reuse the vec4 padding lane as the renderer-owned frame phase for
1033
- // sub-froxel jitter. The temporal resolver consumes it without growing the
1034
- // public fog component or allocating another uniform buffer.
1035
- params[23] = frameState.frameNumber % 1024;
1044
+ // Reserved padding: render frame identity must not move volume samples.
1045
+ params[23] = 0;
1036
1046
  const selectedPointIndex =
1037
1047
  selectedPoint === undefined
1038
1048
  ? -1
@@ -1065,6 +1075,10 @@ function prepareVolumetricFogFrame(
1065
1075
  frameState.temporalFrame !== undefined && frameState.temporalFrame.resetReason === 'none'
1066
1076
  ? 1
1067
1077
  : 0;
1078
+ // World elapsed time is the sole authored-medium clock. The adjacent
1079
+ // frame phase above remains the deterministic sub-froxel jitter identity;
1080
+ // it never advances density motion.
1081
+ params[32] = fog.worldTimeSeconds ?? 0;
1068
1082
 
1069
1083
  const paramsBuffer = stageVolumetricFogParams(internals, frameState, params);
1070
1084
  if (paramsBuffer === undefined) return undefined;
@@ -1219,6 +1233,8 @@ function mapSsrHistoryResetReason(
1219
1233
  case 'environment-change':
1220
1234
  case 'fog-change':
1221
1235
  return 'reflection-generation';
1236
+ case 'time-discontinuity':
1237
+ return 'camera-cut';
1222
1238
  case 'device-recover':
1223
1239
  return 'device-recovery';
1224
1240
  case 'submit-failure':
@@ -1227,6 +1243,50 @@ function mapSsrHistoryResetReason(
1227
1243
  }
1228
1244
  }
1229
1245
 
1246
+ interface FrameClockSample {
1247
+ readonly deltaSeconds: number;
1248
+ readonly elapsedSeconds: number | undefined;
1249
+ /** Explicit host/replay clock was present but invalid. */
1250
+ readonly invalidSampleTime: boolean;
1251
+ }
1252
+
1253
+ /** Read the render sample clock from the camera's composition world once per frame. */
1254
+ function frameClockSample(
1255
+ world: World,
1256
+ worlds: readonly World[],
1257
+ camera: CameraSnapshot,
1258
+ sampleTimeSeconds?: number,
1259
+ ): FrameClockSample {
1260
+ const owner = worlds[camera.worldId ?? 0] ?? world;
1261
+ const invalidSampleTime = sampleTimeSeconds !== undefined && !Number.isFinite(sampleTimeSeconds);
1262
+ try {
1263
+ const clock = owner.getResource(Time);
1264
+ return {
1265
+ deltaSeconds: Number.isFinite(clock.delta) ? Math.max(0, clock.delta) : 0,
1266
+ // Keep the raw host sample even when ECS accepted only a clamped
1267
+ // simulation delta. Temporal admission compares this value with the
1268
+ // last successfully submitted sample and therefore sees real hitches.
1269
+ elapsedSeconds:
1270
+ sampleTimeSeconds === undefined
1271
+ ? Number.isFinite(clock.elapsed)
1272
+ ? clock.elapsed
1273
+ : undefined
1274
+ : Number.isFinite(sampleTimeSeconds)
1275
+ ? sampleTimeSeconds
1276
+ : undefined,
1277
+ invalidSampleTime,
1278
+ };
1279
+ } catch {
1280
+ // Test and recovery callers may provide a minimal World-shaped owner. An
1281
+ // absent clock is an invalid temporal pair and therefore safely resets.
1282
+ return {
1283
+ deltaSeconds: 0,
1284
+ elapsedSeconds: Number.isFinite(sampleTimeSeconds) ? sampleTimeSeconds : undefined,
1285
+ invalidSampleTime,
1286
+ };
1287
+ }
1288
+ }
1289
+
1230
1290
  function stageSsrHistoryCandidate(
1231
1291
  internals: RenderSystemInternals,
1232
1292
  frameState: RenderFrameState,
@@ -1611,6 +1671,7 @@ export function recordFrame(
1611
1671
  readonly activeEntityKeys?: ReadonlySet<number>;
1612
1672
  readonly activeEntityRevision?: number;
1613
1673
  readonly telemetryCandidateCount?: number;
1674
+ readonly telemetrySubmit?: GpuDrivenLodSubmitIdentity;
1614
1675
  readonly occlusion?: OcclusionFrameProjection;
1615
1676
  },
1616
1677
  renderReadLeases?: readonly RenderReadLease[],
@@ -1631,6 +1692,9 @@ export function recordFrame(
1631
1692
  preparedLighting?: ReturnType<typeof prepareFrameLighting>,
1632
1693
  ssrDependencies?: SsrDependenciesInspection,
1633
1694
  onRenderableDraw?: (entry: ValidatedRenderable) => void,
1695
+ analyticFog?: FogFrame,
1696
+ sampleTimeSeconds?: number,
1697
+ temporalReset = false,
1634
1698
  ): boolean {
1635
1699
  void shadowCasterEntityKeys;
1636
1700
  frameState.reflectionFallbackObservationSource = undefined;
@@ -2046,6 +2110,9 @@ export function recordFrame(
2046
2110
  ...(frameGpuDriven.telemetryCandidateCount === undefined
2047
2111
  ? {}
2048
2112
  : { telemetryCandidateCount: frameGpuDriven.telemetryCandidateCount }),
2113
+ ...(frameGpuDriven.telemetrySubmit === undefined
2114
+ ? {}
2115
+ : { telemetrySubmit: frameGpuDriven.telemetrySubmit }),
2049
2116
  ...(frameGpuDriven.occlusion === undefined ? {} : { occlusion: frameGpuDriven.occlusion }),
2050
2117
  ...(frameGpuDriven.scene?.structureMetrics === undefined
2051
2118
  ? {}
@@ -2190,6 +2257,7 @@ export function recordFrame(
2190
2257
  undefined,
2191
2258
  ssrAdmission,
2192
2259
  renderExtent,
2260
+ analyticFog !== undefined && analyticFog.density > 0 && analyticFog.maxOpacity > 0,
2193
2261
  ),
2194
2262
  );
2195
2263
  if (preflightGraph === null) {
@@ -2268,6 +2336,38 @@ export function recordFrame(
2268
2336
  const targetH = swapTarget.targetH;
2269
2337
 
2270
2338
  const previousTemporalView = frameState.lastSuccessfulTemporalView;
2339
+ const motionBlurDemand =
2340
+ camera.motionBlur === undefined
2341
+ ? false
2342
+ : motionBlurTemporalDemand({
2343
+ shutterAngle: camera.motionBlur.shutterAngle,
2344
+ maxRadiusPixels: camera.motionBlur.maxRadiusPixels,
2345
+ sampleCount: camera.motionBlur.sampleCount,
2346
+ targetFps: camera.motionBlur.targetFps ?? 60,
2347
+ });
2348
+ const temporalDemand = camera.antialias === 'taa' || motionBlurDemand;
2349
+ const clock = frameClockSample(world, worlds, camera, sampleTimeSeconds);
2350
+ const deltaSeconds =
2351
+ temporalReset || clock.invalidSampleTime
2352
+ ? Number.NaN
2353
+ : motionBlurSampleDelta(
2354
+ clock.elapsedSeconds,
2355
+ previousTemporalView?.sampleTimeSeconds,
2356
+ clock.deltaSeconds,
2357
+ );
2358
+ const intervalValid = isMotionBlurIntervalValid(deltaSeconds);
2359
+ // An off MB-only frame still carries an unjittered temporal view so the
2360
+ // accepted sample clock remains observable. Its history flag is false,
2361
+ // however, and must form a new baseline when the effect is enabled again.
2362
+ // A reset frame already has an explicit reason and is the valid baseline
2363
+ // for the following pair, so it is deliberately excluded here.
2364
+ const motionBlurFirstFrame =
2365
+ motionBlurDemand &&
2366
+ camera.antialias !== 'taa' &&
2367
+ (previousTemporalView === undefined ||
2368
+ (previousTemporalView.mode === 'off' &&
2369
+ !previousTemporalView.historyValid &&
2370
+ previousTemporalView.resetReason === undefined));
2271
2371
  const currentUnjitteredViewProjection = mat4.multiply(
2272
2372
  mat4.create(),
2273
2373
  computeProjectionMatrix(camera),
@@ -2294,19 +2394,32 @@ export function recordFrame(
2294
2394
  internalHeight: renderExtent.internalHeight,
2295
2395
  }),
2296
2396
  ...(camera.historyVersion === undefined ? {} : { historyVersion: camera.historyVersion }),
2397
+ ...(clock.elapsedSeconds === undefined ? {} : { sampleTimeSeconds: clock.elapsedSeconds }),
2297
2398
  historyValid: false,
2298
2399
  currentUnjitteredViewProjection,
2299
2400
  currentCameraPosition,
2300
2401
  });
2301
2402
  const resetReason =
2302
- camera.antialias === 'taa' && environmentReady
2303
- ? resolveTemporalReset(previousTemporalView, temporalCandidate)
2403
+ temporalDemand && environmentReady
2404
+ ? (resolveTemporalReset(previousTemporalView, temporalCandidate) ??
2405
+ (temporalReset || (motionBlurDemand && !intervalValid)
2406
+ ? 'time-discontinuity'
2407
+ : motionBlurFirstFrame
2408
+ ? 'first-frame'
2409
+ : undefined))
2304
2410
  : undefined;
2305
2411
  const canReusePrevious = previousTemporalView !== undefined && resetReason === undefined;
2306
2412
  const temporalView = createTemporalView({
2307
2413
  ...temporalCandidate.input,
2308
2414
  frameIndex: resetReason === undefined ? temporalCandidate.input.frameIndex : 0,
2309
- historyValid: camera.antialias === 'taa' && environmentReady && canReusePrevious,
2415
+ // Motion Blur needs a finite positive pair before it can read the
2416
+ // baseline. TAA-only frames retain their existing temporal contract and
2417
+ // do not depend on the simulation clock being present in a test host.
2418
+ historyValid:
2419
+ temporalDemand &&
2420
+ environmentReady &&
2421
+ canReusePrevious &&
2422
+ (!motionBlurDemand || intervalValid),
2310
2423
  resetReason,
2311
2424
  ...(canReusePrevious && previousTemporalView.currentUnjitteredViewProjection !== undefined
2312
2425
  ? {
@@ -2357,17 +2470,26 @@ export function recordFrame(
2357
2470
  );
2358
2471
  activeVolumetricFogContext = resolvedHistory.context;
2359
2472
  activeVolumetricFogSignature = resolvedHistory.signature;
2360
- const historyFlag = new Float32Array([activeVolumetricFogContext.historyValid ? 1 : 0]);
2473
+ const previousTime = frameState.volumetricFogHistorySignature?.worldTimeSeconds;
2474
+ const currentTime = resolvedHistory.signature.worldTimeSeconds;
2475
+ // Adjacent UBO lanes: validity, World time, elapsed time since accepted history.
2476
+ const historyParams = new Float32Array([
2477
+ activeVolumetricFogContext.historyValid ? 1 : 0,
2478
+ currentTime ?? 0,
2479
+ previousTime === undefined || currentTime === undefined
2480
+ ? 0
2481
+ : Math.max(0, currentTime - previousTime),
2482
+ ]);
2361
2483
  if (frameState.volumetricFogPendingParams !== undefined) {
2362
- frameState.volumetricFogPendingParams[31] = historyFlag[0] ?? 0;
2484
+ frameState.volumetricFogPendingParams.set(historyParams, 31);
2363
2485
  }
2364
- const historyFlagWritten = internals.device.queue.writeBuffer(
2486
+ const historyParamsWritten = internals.device.queue.writeBuffer(
2365
2487
  activeVolumetricFogContext.paramsBuffer,
2366
2488
  31 * Float32Array.BYTES_PER_ELEMENT,
2367
- historyFlag,
2489
+ historyParams,
2368
2490
  );
2369
- if (!historyFlagWritten.ok) {
2370
- internals.errorRegistry.fire(historyFlagWritten.error);
2491
+ if (!historyParamsWritten.ok) {
2492
+ internals.errorRegistry.fire(historyParamsWritten.error);
2371
2493
  abortPreparedGpuDriven();
2372
2494
  return false;
2373
2495
  }
@@ -2652,14 +2774,33 @@ export function recordFrame(
2652
2774
  // builtin motion-blur pass receives its all-zero registration default and
2653
2775
  // becomes a no-op even though the temporal scene target is populated.
2654
2776
  const framePostProcessParams = new Map(postProcessParams);
2777
+ if (analyticFog !== undefined)
2778
+ framePostProcessParams.set(
2779
+ 'forgeax.analytic-fog',
2780
+ encodeAnalyticFogParams(analyticFog, recordCamera),
2781
+ );
2655
2782
  if (camera.motionBlur !== undefined) {
2656
- const motionBlurParams = new Uint8Array(16);
2657
- const motionBlurView = new DataView(motionBlurParams.buffer);
2658
- motionBlurView.setFloat32(0, camera.motionBlur.shutterAngle, true);
2659
- motionBlurView.setFloat32(4, camera.motionBlur.maxRadiusPixels, true);
2660
- motionBlurView.setUint32(8, camera.motionBlur.sampleCount, true);
2661
- motionBlurView.setUint32(12, temporalView?.historyValid === true ? 0 : 1, true);
2662
- framePostProcessParams.set('forgeax.motion-blur', motionBlurParams);
2783
+ if (motionBlurDemand) {
2784
+ const motionBlurParams = new Uint8Array(MOTION_BLUR_PARAMS_BYTE_SIZE);
2785
+ const motionBlurView = new DataView(motionBlurParams.buffer);
2786
+ const uniformDeltaSeconds = Number.isFinite(deltaSeconds) ? deltaSeconds : 0;
2787
+ motionBlurView.setFloat32(0, camera.motionBlur.shutterAngle, true);
2788
+ motionBlurView.setFloat32(4, camera.motionBlur.maxRadiusPixels, true);
2789
+ motionBlurView.setUint32(
2790
+ 8,
2791
+ effectiveMotionBlurSampleCount(camera.motionBlur.sampleCount),
2792
+ true,
2793
+ );
2794
+ motionBlurView.setUint32(12, temporalView?.historyValid === true ? 0 : 1, true);
2795
+ motionBlurView.setFloat32(16, camera.motionBlur.targetFps ?? 60, true);
2796
+ motionBlurView.setFloat32(
2797
+ 20,
2798
+ motionBlurExposureScale(uniformDeltaSeconds, camera.motionBlur.targetFps ?? 60),
2799
+ true,
2800
+ );
2801
+ motionBlurView.setFloat32(24, uniformDeltaSeconds, true);
2802
+ framePostProcessParams.set('forgeax.motion-blur', motionBlurParams);
2803
+ }
2663
2804
  }
2664
2805
  if (camera.depthOfField !== undefined) {
2665
2806
  const dofParams = packDepthOfFieldParams(camera.depthOfField, {
@@ -1481,6 +1481,7 @@ function uploadInstanceRanges(
1481
1481
  ranges: readonly { readonly start: number; readonly end: number }[],
1482
1482
  storageBacked: boolean,
1483
1483
  sourceOffsetInstances = 0,
1484
+ previousInst: NonNullable<RenderableSnapshot['temporal']>['previousInstances'] = undefined,
1484
1485
  ): InstanceUploadResult {
1485
1486
  const strideBytes =
1486
1487
  (storageBacked ? INSTANCE_STORAGE_STRIDE_FLOATS : 16) * Float32Array.BYTES_PER_ELEMENT;
@@ -1494,7 +1495,31 @@ function uploadInstanceRanges(
1494
1495
  (sourceOffsetInstances + start) * 16,
1495
1496
  (sourceOffsetInstances + end) * 16,
1496
1497
  );
1497
- const payload = storageBacked ? packInstanceStorageBuffer(source) : source;
1498
+ // A generation-bearing previous collection is kept whole so a compacted
1499
+ // or reordered current range can resolve its real prior ordinal. Without
1500
+ // identity proof, keep the old range-local shape and let the packer seed
1501
+ // on count mismatch rather than pairing an unrelated slot.
1502
+ const hasIdentityProof =
1503
+ inst.generations !== undefined && previousInst?.generations !== undefined;
1504
+ const previousSource = hasIdentityProof
1505
+ ? previousInst?.transforms
1506
+ : previousInst?.transforms.subarray(
1507
+ (sourceOffsetInstances + start) * 16,
1508
+ (sourceOffsetInstances + end) * 16,
1509
+ );
1510
+ const currentGenerations = inst.generations?.subarray(
1511
+ sourceOffsetInstances + start,
1512
+ sourceOffsetInstances + end,
1513
+ );
1514
+ const previousGenerations = hasIdentityProof
1515
+ ? previousInst?.generations
1516
+ : previousInst?.generations?.subarray(
1517
+ sourceOffsetInstances + start,
1518
+ sourceOffsetInstances + end,
1519
+ );
1520
+ const payload = storageBacked
1521
+ ? packInstanceStorageBuffer(source, previousSource, currentGenerations, previousGenerations)
1522
+ : source;
1498
1523
  const written = c.runtime.device.queue.writeBuffer(buffer, start * strideBytes, payload);
1499
1524
  if (!written.ok) {
1500
1525
  c.runtime.errorRegistry.fire(written.error);
@@ -1673,6 +1698,7 @@ export function resolveStorageInstanceChunks(
1673
1698
  rangesToUpload,
1674
1699
  true,
1675
1700
  start,
1701
+ entry.source.temporal?.previousInstances,
1676
1702
  );
1677
1703
  for (const range of upload.ranges) {
1678
1704
  uploadedRanges.push({ start: range.start + start, end: range.end + start });
@@ -1858,6 +1884,7 @@ function resolveGeometryInstanceBuffer(
1858
1884
  rangesToUpload,
1859
1885
  false,
1860
1886
  start,
1887
+ entry.source.temporal?.previousInstances,
1861
1888
  );
1862
1889
  for (const range of upload.ranges) {
1863
1890
  uploadedRanges.push({ start: range.start + start, end: range.end + start });
@@ -1997,6 +2024,8 @@ function resolveGeometryInstanceBuffer(
1997
2024
  active.buffer.handle,
1998
2025
  ranges,
1999
2026
  !uniformFallback,
2027
+ 0,
2028
+ entry.source.temporal?.previousInstances,
2000
2029
  );
2001
2030
  uploadRanges = upload.ranges;
2002
2031
  uploadedBytes = upload.bytes;
@@ -690,7 +690,12 @@ function recordSpriteEntityDraws(
690
690
  {
691
691
  const instancePayload = uniformFallback
692
692
  ? spriteInst.transforms
693
- : packInstanceStorageBuffer(spriteInst.transforms);
693
+ : packInstanceStorageBuffer(
694
+ spriteInst.transforms,
695
+ spriteEntry.source.temporal?.previousInstances?.transforms,
696
+ spriteInst.generations,
697
+ spriteEntry.source.temporal?.previousInstances?.generations,
698
+ );
694
699
  const requestedBytes = instancePayload.byteLength;
695
700
  const cap = runtime.device.limits.maxStorageBufferBindingSize;
696
701
  if (typeof cap === 'number' && requestedBytes > cap) {
@@ -43,23 +43,58 @@ export const MAX_UNIFORM_INSTANCES = 128;
43
43
  /** The storage-backed InstanceData layout is current + previous mat4. */
44
44
  export const INSTANCE_STORAGE_STRIDE_FLOATS = 32;
45
45
 
46
+ function hasStableGenerationColumn(generations: Uint32Array | undefined, count: number): boolean {
47
+ if (generations === undefined || generations.length !== count) return false;
48
+ const seen = new Set<number>();
49
+ for (const generation of generations) {
50
+ if (generation === 0 || seen.has(generation)) return false;
51
+ seen.add(generation);
52
+ }
53
+ return true;
54
+ }
55
+
46
56
  /**
47
57
  * Project the ECS transform snapshot into the storage-backed InstanceData
48
- * layout. The extract contract carries one current mat4 per instance; until
49
- * the ECS snapshot owns a previous-instance column, current is the honest
50
- * previous value as well. Uniform-backed variants keep the compact 16-float
51
- * payload and do not use this projection.
58
+ * layout. Unique identity generations select the previous matrix only when
59
+ * the producer proves that the instance survived. Replaced slots seed
60
+ * previous=current; reordered slots map back to the real prior ordinal so a
61
+ * compaction cannot exchange velocities. Uniform-backed variants keep the
62
+ * compact 16-float payload and do not use this projection.
52
63
  */
53
- export function packInstanceStorageBuffer(transforms: Float32Array): Float32Array {
64
+ export function packInstanceStorageBuffer(
65
+ transforms: Float32Array,
66
+ previousTransforms: Float32Array = transforms,
67
+ generations?: Uint32Array,
68
+ previousGenerations?: Uint32Array,
69
+ ): Float32Array {
54
70
  const count = Math.floor(transforms.length / 16);
55
71
  const out = new Float32Array(count * INSTANCE_STORAGE_STRIDE_FLOATS);
72
+ const previousCount = Math.floor(previousTransforms.length / 16);
73
+ const generationInput = generations !== undefined || previousGenerations !== undefined;
74
+ const identityProof =
75
+ hasStableGenerationColumn(generations, count) &&
76
+ hasStableGenerationColumn(previousGenerations, previousCount);
77
+ const previousIndexByGeneration = identityProof
78
+ ? new Map(
79
+ Array.from(previousGenerations ?? [], (generation, index) => [generation, index] as const),
80
+ )
81
+ : undefined;
56
82
  for (let i = 0; i < count; i++) {
57
83
  const sourceBase = i * 16;
58
84
  const targetBase = i * INSTANCE_STORAGE_STRIDE_FLOATS;
85
+ const previousIndex = identityProof
86
+ ? previousIndexByGeneration?.get(generations?.[i] ?? 0)
87
+ : !generationInput && previousCount === count
88
+ ? i
89
+ : undefined;
90
+ const sameIdentity = previousIndex !== undefined && previousIndex < previousCount;
91
+ const previousBase = (previousIndex ?? i) * 16;
59
92
  for (let k = 0; k < 16; k++) {
60
93
  const value = transforms[sourceBase + k] ?? 0;
61
94
  out[targetBase + k] = value;
62
- out[targetBase + 16 + k] = value;
95
+ out[targetBase + 16 + k] = sameIdentity
96
+ ? (previousTransforms[previousBase + k] ?? value)
97
+ : value;
63
98
  }
64
99
  }
65
100
  return out;
@@ -451,6 +486,7 @@ export function uploadMeshSsboBatch(
451
486
  _meshSsboScratchFloats[slot + 38] = 1;
452
487
  _meshSsboScratchFloats[slot + 43] = 1;
453
488
  _meshSsboScratchFloats[slot + 44] = 1;
489
+ _meshSsboScratchFloats[slot + 45] = 1;
454
490
  } else {
455
491
  const worldFromLocal = entry.source.transform.world;
456
492
  for (let k = 0; k < 16; k++) _meshSsboScratchFloats[slot + k] = worldFromLocal[k] ?? 0;
@@ -483,6 +519,7 @@ export function uploadMeshSsboBatch(
483
519
  }
484
520
  _meshSsboScratchFloats[slot + 44] =
485
521
  entry.source.temporal?.reactive === true || reactiveMaterial ? 1 : 0;
522
+ _meshSsboScratchFloats[slot + 45] = entry.source.temporal?.motionValid === false ? 0 : 1;
486
523
  }
487
524
  }
488
525
  if (neededBytes > 0) {
@@ -337,17 +337,15 @@ export interface PerPassResources {
337
337
  readonly skyboxBindGroupLayout: BindGroupLayout | null;
338
338
  readonly skyboxSampler: Sampler | null;
339
339
  readonly skyboxRotationBuffer: Buffer | null;
340
- bloomBrightPipeline: RhiRenderPipeline | null;
341
- bloomBlurHPipeline: RhiRenderPipeline | null;
342
- bloomBlurVPipeline: RhiRenderPipeline | null;
340
+ bloomDownsamplePipeline: RhiRenderPipeline | null;
341
+ bloomUpsamplePipeline: RhiRenderPipeline | null;
343
342
  bloomCompositePipeline: RhiRenderPipeline | null;
344
- bloomBrightBindGroupLayout: BindGroupLayout | null;
345
- bloomBlurBindGroupLayout: BindGroupLayout | null;
343
+ bloomDownsampleBindGroupLayout: BindGroupLayout | null;
344
+ bloomUpsampleBindGroupLayout: BindGroupLayout | null;
346
345
  bloomCompositeBindGroupLayout: BindGroupLayout | null;
347
346
  bloomSampler: Sampler | null;
348
- bloomBrightParamsBuffer: Buffer | null;
349
- bloomBlurHParamsBuffer: Buffer | null;
350
- bloomBlurVParamsBuffer: Buffer | null;
347
+ bloomDownsampleParamsBuffer: Buffer | null;
348
+ bloomUpsampleParamsBuffer: Buffer | null;
351
349
  bloomCompositeParamsBuffer: Buffer | null;
352
350
  ensureBloomResources?: () => void;
353
351
  getBloomResources?: () => BloomPersistentBundle | null;
@@ -358,18 +356,6 @@ export interface PerPassResources {
358
356
  /** Drain candidate, active, and fence-retiring Bloom generations. */
359
357
  drainBloomResources?: () => void;
360
358
  inspectBloomResources?: (graph?: CompiledRenderGraphInfo) => BloomInspection;
361
- bloomBrightTexture: Texture | null;
362
- bloomBrightView: TextureView | null;
363
- bloomBrightWidth: number;
364
- bloomBrightHeight: number;
365
- bloomBlurHTexture: Texture | null;
366
- bloomBlurHView: TextureView | null;
367
- bloomBlurHWidth: number;
368
- bloomBlurHHeight: number;
369
- bloomBlurVTexture: Texture | null;
370
- bloomBlurVView: TextureView | null;
371
- bloomBlurVWidth: number;
372
- bloomBlurVHeight: number;
373
359
  ssaoCalcPipeline: RhiRenderPipeline | null;
374
360
  ssaoBlurPipeline: RhiRenderPipeline | null;
375
361
  ssaoBgl: BindGroupLayout | null;
@@ -392,17 +378,15 @@ export interface PerPassResources {
392
378
  export interface BloomPersistentBundle {
393
379
  readonly scope: DeviceScope;
394
380
  readonly generation: number;
395
- readonly bloomBrightPipeline: RhiRenderPipeline | null;
396
- readonly bloomBlurHPipeline: RhiRenderPipeline | null;
397
- readonly bloomBlurVPipeline: RhiRenderPipeline | null;
381
+ readonly bloomDownsamplePipeline: RhiRenderPipeline | null;
382
+ readonly bloomUpsamplePipeline: RhiRenderPipeline | null;
398
383
  readonly bloomCompositePipeline: RhiRenderPipeline | null;
399
- readonly bloomBrightBindGroupLayout: BindGroupLayout | null;
400
- readonly bloomBlurBindGroupLayout: BindGroupLayout | null;
384
+ readonly bloomDownsampleBindGroupLayout: BindGroupLayout | null;
385
+ readonly bloomUpsampleBindGroupLayout: BindGroupLayout | null;
401
386
  readonly bloomCompositeBindGroupLayout: BindGroupLayout | null;
402
387
  readonly bloomSampler: Sampler | null;
403
- readonly bloomBrightParamsBuffer: Buffer | null;
404
- readonly bloomBlurHParamsBuffer: Buffer | null;
405
- readonly bloomBlurVParamsBuffer: Buffer | null;
388
+ readonly bloomDownsampleParamsBuffer: Buffer | null;
389
+ readonly bloomUpsampleParamsBuffer: Buffer | null;
406
390
  readonly bloomCompositeParamsBuffer: Buffer | null;
407
391
  }
408
392
 
@@ -1503,7 +1503,12 @@ export function recordShadowCasterDraws(
1503
1503
  : GPU_BUFFER_USAGE_STORAGE | GPU_BUFFER_USAGE_COPY_DST;
1504
1504
  const instancePayload = uniformFallback
1505
1505
  ? shadowInst.transforms
1506
- : packInstanceStorageBuffer(shadowInst.transforms);
1506
+ : packInstanceStorageBuffer(
1507
+ shadowInst.transforms,
1508
+ entry.source.temporal?.previousInstances?.transforms,
1509
+ shadowInst.generations,
1510
+ entry.source.temporal?.previousInstances?.generations,
1511
+ );
1507
1512
  const requestedBytes = instancePayload.byteLength;
1508
1513
  const cap = runtime.device.limits.maxStorageBufferBindingSize;
1509
1514
  if (!uniformFallback && typeof cap === 'number' && cap > 0 && requestedBytes > cap) {