@forgeax/engine-render 0.1.31 → 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 (293) hide show
  1. package/README.md +447 -34
  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 +3 -2
  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-FUUO4UND.mjs → chunk-3RTCJTH4.mjs} +12 -9
  21. package/dist/chunk-3RTCJTH4.mjs.map +1 -0
  22. package/dist/{chunk-NO4WZCAH.mjs → chunk-LEAXOMYJ.mjs} +3 -3
  23. package/dist/{chunk-NO4WZCAH.mjs.map → chunk-LEAXOMYJ.mjs.map} +1 -1
  24. package/dist/{chunk-JRBDO2KB.mjs → chunk-NB2S7ZRX.mjs} +166 -10
  25. package/dist/chunk-NB2S7ZRX.mjs.map +1 -0
  26. package/dist/{chunk-7MQ3XBSP.mjs → chunk-NNQL7CB3.mjs} +4 -4
  27. package/dist/{chunk-7MQ3XBSP.mjs.map → chunk-NNQL7CB3.mjs.map} +1 -1
  28. package/dist/{chunk-C3DDSXYK.mjs → chunk-OO3DRYK7.mjs} +171 -230
  29. package/dist/chunk-OO3DRYK7.mjs.map +1 -0
  30. package/dist/{chunk-S5UE2T5P.mjs → chunk-PLAXOOX5.mjs} +608 -7
  31. package/dist/chunk-PLAXOOX5.mjs.map +1 -0
  32. package/dist/{chunk-PSEQ6QPH.mjs → chunk-VOZWDM3L.mjs} +295 -198
  33. package/dist/chunk-VOZWDM3L.mjs.map +1 -0
  34. package/dist/{chunk-MDQJPY3T.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/depth-of-field.d.ts +28 -0
  41. package/dist/components/depth-of-field.d.ts.map +1 -0
  42. package/dist/components/directional-light.d.ts +2 -1
  43. package/dist/components/directional-light.d.ts.map +1 -1
  44. package/dist/components/index.d.ts +1 -0
  45. package/dist/components/index.d.ts.map +1 -1
  46. package/dist/components/mesh-filter.d.ts +6 -5
  47. package/dist/components/mesh-filter.d.ts.map +1 -1
  48. package/dist/components/motion-blur.d.ts +1 -0
  49. package/dist/components/motion-blur.d.ts.map +1 -1
  50. package/dist/construct-renderer.mjs +3746 -837
  51. package/dist/construct-renderer.mjs.map +1 -1
  52. package/dist/device/gpu-residency.d.ts +6 -0
  53. package/dist/device/gpu-residency.d.ts.map +1 -1
  54. package/dist/environment/analytic-fog.d.ts +6 -0
  55. package/dist/environment/analytic-fog.d.ts.map +1 -0
  56. package/dist/errors/render.d.ts +6 -2
  57. package/dist/errors/render.d.ts.map +1 -1
  58. package/dist/extract/camera.d.ts.map +1 -1
  59. package/dist/extract/contracts.d.ts +7 -0
  60. package/dist/extract/contracts.d.ts.map +1 -1
  61. package/dist/extract/environment.d.ts +2 -0
  62. package/dist/extract/environment.d.ts.map +1 -1
  63. package/dist/features/depth-of-field/depth-of-field-assembly.d.ts +19 -0
  64. package/dist/features/depth-of-field/depth-of-field-assembly.d.ts.map +1 -0
  65. package/dist/features/depth-of-field/depth-of-field-feature.d.ts +43 -0
  66. package/dist/features/depth-of-field/depth-of-field-feature.d.ts.map +1 -0
  67. package/dist/features/depth-of-field/depth-of-field-inspection.d.ts +34 -0
  68. package/dist/features/depth-of-field/depth-of-field-inspection.d.ts.map +1 -0
  69. package/dist/features/depth-of-field/depth-of-field-params.d.ts +83 -0
  70. package/dist/features/depth-of-field/depth-of-field-params.d.ts.map +1 -0
  71. package/dist/features/error-reporting.d.ts +13 -0
  72. package/dist/features/error-reporting.d.ts.map +1 -0
  73. package/dist/features/host.d.ts +5 -0
  74. package/dist/features/host.d.ts.map +1 -1
  75. package/dist/features/motion-blur/motion-blur-feature.d.ts +20 -2
  76. package/dist/features/motion-blur/motion-blur-feature.d.ts.map +1 -1
  77. package/dist/features/motion-blur/motion-blur-params.d.ts +21 -0
  78. package/dist/features/motion-blur/motion-blur-params.d.ts.map +1 -1
  79. package/dist/features/motion-blur/motion-blur-runtime.d.ts +54 -0
  80. package/dist/features/motion-blur/motion-blur-runtime.d.ts.map +1 -0
  81. package/dist/features/plan.d.ts +7 -4
  82. package/dist/features/plan.d.ts.map +1 -1
  83. package/dist/features/prepared-gpu-work.d.ts +9 -5
  84. package/dist/features/prepared-gpu-work.d.ts.map +1 -1
  85. package/dist/features/render-graph-compute.d.ts.map +1 -1
  86. package/dist/features/render-graph-contribution.d.ts.map +1 -1
  87. package/dist/features/types.d.ts +19 -1
  88. package/dist/features/types.d.ts.map +1 -1
  89. package/dist/gpu-driven/material-artifacts.d.ts.map +1 -1
  90. package/dist/gpu-driven/prepared-draw.d.ts +7 -0
  91. package/dist/gpu-driven/prepared-draw.d.ts.map +1 -1
  92. package/dist/gpu-driven/production-raster.d.ts +3 -1
  93. package/dist/gpu-driven/production-raster.d.ts.map +1 -1
  94. package/dist/gpu-driven/view-gpu.d.ts +9 -0
  95. package/dist/gpu-driven/view-gpu.d.ts.map +1 -1
  96. package/dist/gpu-scene.d.ts +6 -1
  97. package/dist/gpu-scene.d.ts.map +1 -1
  98. package/dist/index.d.ts +8 -3
  99. package/dist/index.d.ts.map +1 -1
  100. package/dist/index.mjs +6 -5
  101. package/dist/index.mjs.map +1 -1
  102. package/dist/inspection-types.d.ts +78 -2
  103. package/dist/inspection-types.d.ts.map +1 -1
  104. package/dist/instances.d.ts +15 -1
  105. package/dist/instances.d.ts.map +1 -1
  106. package/dist/internal.mjs +6 -6
  107. package/dist/mesh-material-bindings.d.ts +3 -0
  108. package/dist/mesh-material-bindings.d.ts.map +1 -1
  109. package/dist/pipeline/standard-output/graph.d.ts.map +1 -1
  110. package/dist/pipeline/standard-output/types.d.ts +4 -2
  111. package/dist/pipeline/standard-output/types.d.ts.map +1 -1
  112. package/dist/pipeline/standard-post.d.ts +28 -19
  113. package/dist/pipeline/standard-post.d.ts.map +1 -1
  114. package/dist/pipeline-spec.d.ts +2 -2
  115. package/dist/plugin.d.ts.map +1 -1
  116. package/dist/record/frame-snapshot.d.ts +24 -1
  117. package/dist/record/frame-snapshot.d.ts.map +1 -1
  118. package/dist/record/frame-targets.d.ts.map +1 -1
  119. package/dist/record/frame.d.ts +5 -2
  120. package/dist/record/frame.d.ts.map +1 -1
  121. package/dist/record/main-pass-geometry.d.ts.map +1 -1
  122. package/dist/record/main-pass.d.ts.map +1 -1
  123. package/dist/record/mesh-ssbo.d.ts +6 -5
  124. package/dist/record/mesh-ssbo.d.ts.map +1 -1
  125. package/dist/record/render-context.d.ts +12 -28
  126. package/dist/record/render-context.d.ts.map +1 -1
  127. package/dist/record/shadow-pass.d.ts.map +1 -1
  128. package/dist/record/skybox-post-pass.d.ts +5 -3
  129. package/dist/record/skybox-post-pass.d.ts.map +1 -1
  130. package/dist/record/typed-frame-graph.d.ts +8 -1
  131. package/dist/record/typed-frame-graph.d.ts.map +1 -1
  132. package/dist/recovery/render-system-candidate.d.ts.map +1 -1
  133. package/dist/render-contract.d.ts +32 -5
  134. package/dist/render-contract.d.ts.map +1 -1
  135. package/dist/render-graph-primitives.d.ts +2 -0
  136. package/dist/render-graph-primitives.d.ts.map +1 -1
  137. package/dist/render-pipeline.d.ts +15 -1
  138. package/dist/render-pipeline.d.ts.map +1 -1
  139. package/dist/render-system-extract-tail.d.ts.map +1 -1
  140. package/dist/render-system-extract.d.ts +26 -5
  141. package/dist/render-system-extract.d.ts.map +1 -1
  142. package/dist/render-system.d.ts +9 -2
  143. package/dist/render-system.d.ts.map +1 -1
  144. package/dist/scene/render-scene.d.ts +2 -0
  145. package/dist/scene/render-scene.d.ts.map +1 -1
  146. package/dist/temporal/frame-coordinator.d.ts.map +1 -1
  147. package/dist/temporal/frame.d.ts +7 -0
  148. package/dist/temporal/frame.d.ts.map +1 -1
  149. package/dist/temporal/index.mjs +5 -5
  150. package/dist/temporal/view.d.ts +6 -1
  151. package/dist/temporal/view.d.ts.map +1 -1
  152. package/dist/typed-render-graph-primitives.d.ts +10 -7
  153. package/dist/typed-render-graph-primitives.d.ts.map +1 -1
  154. package/dist/volume/component.d.ts +0 -1
  155. package/dist/volume/component.d.ts.map +1 -1
  156. package/dist/volume/passes.d.ts.map +1 -1
  157. package/dist/volume/temporal.d.ts +3 -1
  158. package/dist/volume/temporal.d.ts.map +1 -1
  159. package/package.json +21 -21
  160. package/src/__tests__/analytic-fog-graph.unit.test.ts +199 -0
  161. package/src/__tests__/analytic-fog-optics.unit.test.ts +38 -0
  162. package/src/__tests__/bloom-exact-zero.unit.test.ts +112 -18
  163. package/src/__tests__/bloom-lifecycle.integration.test.ts +17 -4
  164. package/src/__tests__/dynamic-resolution-plugin.integration.test.ts +20 -0
  165. package/src/__tests__/environment-fog-lanes.integration.test.ts +14 -0
  166. package/src/__tests__/environment-selection.unit.test.ts +12 -0
  167. package/src/__tests__/factory-contract.integration.test.ts +331 -68
  168. package/src/__tests__/frame-camera-selection.unit.test.ts +39 -1
  169. package/src/__tests__/frame-plan-contract.unit.test.ts +2 -0
  170. package/src/__tests__/gpu-driven-baseline.characterization.test.ts +12 -0
  171. package/src/__tests__/gpu-driven-indirect-raster-evidence.ts +2 -1
  172. package/src/__tests__/gpu-driven-production.integration.test.ts +27 -1
  173. package/src/__tests__/gpu-driven-shadow-views.dawn.test.ts +1 -1
  174. package/src/__tests__/gpu-driven-shadow-views.integration.test.ts +1 -1
  175. package/src/__tests__/gpu-driven-view-gpu-evidence.ts +11 -0
  176. package/src/__tests__/gpu-resource-store-stride.unit.test.ts +12 -0
  177. package/src/__tests__/gpu-scene.unit.test.ts +91 -0
  178. package/src/__tests__/instances-store.unit.test.ts +90 -0
  179. package/src/__tests__/mesh-submission-lifetime.unit.test.ts +31 -0
  180. package/src/__tests__/motion-blur-compute.dawn.test.ts +583 -0
  181. package/src/__tests__/prepared-gpu-driven-pbr.integration.test.ts +49 -0
  182. package/src/__tests__/prepared-graphics-lifecycle.unit.test.ts +86 -0
  183. package/src/__tests__/public-surface.test-d.ts +8 -0
  184. package/src/__tests__/render-error-code-owner.test-d.ts +2 -0
  185. package/src/__tests__/render-feature-error-reporting.unit.test.ts +24 -0
  186. package/src/__tests__/render-feature-stage-fault-recovery.unit.test.ts +23 -0
  187. package/src/__tests__/render-scene-temporal-retry.unit.test.ts +31 -0
  188. package/src/__tests__/renderer-frame-transaction.integration.test.ts +7 -1
  189. package/src/__tests__/shader-manifest-fixture.ts +4 -4
  190. package/src/__tests__/skinned-shadow-caster.test.ts +2 -0
  191. package/src/__tests__/standard-lighting-write.unit.test.ts +2 -1
  192. package/src/__tests__/standard-pipeline.dawn.test.ts +1 -1
  193. package/src/__tests__/standard-pipeline.integration.test.ts +18 -8
  194. package/src/__tests__/temporal-reset-matrix.unit.test.ts +10 -0
  195. package/src/__tests__/temporal-view.unit.test.ts +2 -1
  196. package/src/__tests__/typed-pipeline-topology.unit.test.ts +20 -4
  197. package/src/__tests__/volume-optics-oracle.unit.test.ts +3 -1
  198. package/src/__tests__/volumetric-fog-budget-recovery.integration.test.ts +177 -13
  199. package/src/__tests__/volumetric-fog-stage-readback.dawn.test.ts +447 -61
  200. package/src/__tests__/volumetric-fog-temporal.unit.test.ts +27 -0
  201. package/src/__tests__/volumetric-fog-topology.unit.test.ts +19 -3
  202. package/src/assembly/analytic-fog-registration.ts +14 -0
  203. package/src/assembly/fxaa-registration.ts +17 -0
  204. package/src/assembly/lod-observation.ts +12 -0
  205. package/src/assembly/motion-blur-registration.ts +17 -0
  206. package/src/assembly/renderer-facade.ts +2 -0
  207. package/src/assembly/temporal-post-process-prewarm.ts +40 -0
  208. package/src/assembly/webgpu-ready-contract.ts +8 -3
  209. package/src/assembly/webgpu-ready.ts +186 -250
  210. package/src/assembly/webgpu-renderer.ts +30 -45
  211. package/src/bloom-admission.ts +21 -0
  212. package/src/components/__tests__/motion-blur.unit.test.ts +1 -0
  213. package/src/components/atmosphere.ts +4 -0
  214. package/src/components/camera.ts +66 -10
  215. package/src/components/depth-of-field.ts +45 -0
  216. package/src/components/directional-light.ts +2 -1
  217. package/src/components/index.ts +1 -0
  218. package/src/components/mesh-filter.ts +11 -9
  219. package/src/components/motion-blur.ts +1 -0
  220. package/src/device/gpu-residency.ts +56 -12
  221. package/src/environment/analytic-fog.ts +44 -0
  222. package/src/environment/background.ts +2 -2
  223. package/src/errors/render.ts +14 -1
  224. package/src/extract/camera.ts +42 -9
  225. package/src/extract/contracts.ts +7 -0
  226. package/src/extract/environment.ts +4 -0
  227. package/src/features/depth-of-field/__tests__/depth-of-field-params.unit.test.ts +943 -0
  228. package/src/features/depth-of-field/depth-of-field-assembly.ts +100 -0
  229. package/src/features/depth-of-field/depth-of-field-feature.ts +295 -0
  230. package/src/features/depth-of-field/depth-of-field-inspection.ts +237 -0
  231. package/src/features/depth-of-field/depth-of-field-params.ts +556 -0
  232. package/src/features/error-reporting.ts +78 -0
  233. package/src/features/host.ts +26 -13
  234. package/src/features/motion-blur/__tests__/motion-blur-feature.unit.test.ts +111 -1
  235. package/src/features/motion-blur/__tests__/motion-blur-params.unit.test.ts +58 -3
  236. package/src/features/motion-blur/motion-blur-feature.ts +1519 -15
  237. package/src/features/motion-blur/motion-blur-params.ts +70 -2
  238. package/src/features/motion-blur/motion-blur-runtime.ts +176 -0
  239. package/src/features/plan.ts +25 -8
  240. package/src/features/prepared-gpu-work.ts +86 -35
  241. package/src/features/render-graph-compute.ts +13 -5
  242. package/src/features/render-graph-contribution.ts +2 -0
  243. package/src/features/types.ts +18 -1
  244. package/src/gpu-driven/material-artifacts.ts +2 -7
  245. package/src/gpu-driven/prepared-draw.ts +15 -10
  246. package/src/gpu-driven/production-raster.ts +83 -7
  247. package/src/gpu-driven/view-gpu.ts +17 -0
  248. package/src/gpu-scene.ts +188 -21
  249. package/src/index.ts +64 -0
  250. package/src/inspection-types.ts +98 -2
  251. package/src/instances.ts +93 -0
  252. package/src/mesh-material-bindings.ts +4 -0
  253. package/src/pipeline/standard-output/graph.ts +3 -0
  254. package/src/pipeline/standard-output/types.ts +4 -1
  255. package/src/pipeline/standard-post.ts +324 -76
  256. package/src/pipeline-spec.ts +5 -5
  257. package/src/plugin.ts +2 -0
  258. package/src/points-lines/__tests__/view-ubo.unit.test.ts +2 -1
  259. package/src/record/__tests__/temporal-payload.unit.test.ts +2 -1
  260. package/src/record/frame-snapshot.ts +31 -2
  261. package/src/record/frame-targets.ts +10 -6
  262. package/src/record/frame.ts +202 -28
  263. package/src/record/main-pass-geometry.ts +30 -1
  264. package/src/record/main-pass-sprite-draws.ts +6 -1
  265. package/src/record/main-pass.ts +1 -5
  266. package/src/record/mesh-ssbo.ts +43 -6
  267. package/src/record/render-context.ts +12 -28
  268. package/src/record/shadow-pass.ts +6 -1
  269. package/src/record/skybox-post-pass.ts +122 -196
  270. package/src/record/typed-frame-graph.ts +259 -15
  271. package/src/recovery/render-system-candidate.ts +28 -10
  272. package/src/render-contract.ts +34 -4
  273. package/src/render-graph-primitives.ts +8 -1
  274. package/src/render-pipeline.ts +22 -1
  275. package/src/render-system-extract-tail.ts +103 -13
  276. package/src/render-system-extract.ts +148 -38
  277. package/src/render-system.ts +332 -119
  278. package/src/scene/render-scene.ts +85 -4
  279. package/src/temporal/__tests__/temporal-frame-transaction.unit.test.ts +36 -0
  280. package/src/temporal/frame-coordinator.ts +8 -1
  281. package/src/temporal/frame.ts +20 -0
  282. package/src/temporal/view.ts +13 -2
  283. package/src/typed-render-graph-primitives.ts +124 -48
  284. package/src/volume/component.ts +0 -1
  285. package/src/volume/passes.ts +8 -4
  286. package/src/volume/resources.ts +1 -1
  287. package/src/volume/temporal.ts +11 -0
  288. package/dist/chunk-C3DDSXYK.mjs.map +0 -1
  289. package/dist/chunk-FUUO4UND.mjs.map +0 -1
  290. package/dist/chunk-JRBDO2KB.mjs.map +0 -1
  291. package/dist/chunk-MDQJPY3T.mjs.map +0 -1
  292. package/dist/chunk-PSEQ6QPH.mjs.map +0 -1
  293. package/dist/chunk-S5UE2T5P.mjs.map +0 -1
@@ -1,10 +1,11 @@
1
1
  import { execFileSync } from 'node:child_process';
2
2
  import { mkdir, writeFile } from 'node:fs/promises';
3
3
  import { dirname } from 'node:path';
4
- import { World } from '@forgeax/engine-ecs';
4
+ import { Time, World } from '@forgeax/engine-ecs';
5
5
  import { RenderGraphBuilder } from '@forgeax/engine-render-graph';
6
6
  import type {
7
7
  BindGroupLayoutDescriptor,
8
+ Buffer,
8
9
  RenderPipelineDescriptor,
9
10
  RhiAdapter,
10
11
  RhiDevice,
@@ -33,6 +34,7 @@ import {
33
34
  deriveVolumetricFogExtent,
34
35
  deriveVolumetricFogResolvedExtent,
35
36
  inspectVolumetricFogResources,
37
+ VOLUMETRIC_FOG_PARAMS_BYTES,
36
38
  } from '../volume/resources';
37
39
  import { resolveVolumeTemporalReset } from '../volume/temporal';
38
40
 
@@ -62,15 +64,12 @@ class RecoverableNullDevice extends RhiNullDevice {
62
64
  private readonly resolveLost: (info: LostInfo) => void;
63
65
  private readonly lostPromise: Promise<LostInfo>;
64
66
 
65
- constructor() {
67
+ constructor(queue: RhiNullQueue = new RhiNullQueue()) {
66
68
  let resolveLost!: (info: LostInfo) => void;
67
69
  const lostPromise = new Promise<LostInfo>((resolve) => {
68
70
  resolveLost = resolve;
69
71
  });
70
- super(
71
- new RhiNullQueue(),
72
- (bookkeeper, device) => new RhiNullCommandEncoder(bookkeeper, device),
73
- );
72
+ super(queue, (bookkeeper, device) => new RhiNullCommandEncoder(bookkeeper, device));
74
73
  this.resolveLost = resolveLost;
75
74
  this.lostPromise = lostPromise;
76
75
  }
@@ -104,13 +103,44 @@ class RecoverableNullAdapter implements RhiAdapter {
104
103
  readonly limits: Readonly<Record<string, number>> = {};
105
104
  readonly devices: RecoverableNullDevice[] = [];
106
105
 
106
+ constructor(private readonly queueFactory: () => RhiNullQueue = () => new RhiNullQueue()) {}
107
+
107
108
  requestDevice(): Promise<Result<RhiDevice, RhiError>> {
108
- const device = new RecoverableNullDevice();
109
+ const device = new RecoverableNullDevice(this.queueFactory());
109
110
  this.devices.push(device);
110
111
  return Promise.resolve(ok(device));
111
112
  }
112
113
  }
113
114
 
115
+ interface CapturedQueueWrite {
116
+ readonly buffer: Buffer;
117
+ readonly offset: number;
118
+ readonly bytes: Uint8Array;
119
+ }
120
+
121
+ /** Captures the real record-stage volume parameter writes without adding a
122
+ * second render implementation. */
123
+ class RecordingNullQueue extends RhiNullQueue {
124
+ readonly writes: CapturedQueueWrite[] = [];
125
+
126
+ override writeBuffer(
127
+ buffer: Buffer,
128
+ bufferOffset: number,
129
+ data: ArrayBufferView | ArrayBuffer,
130
+ dataOffset?: number,
131
+ size?: number,
132
+ ) {
133
+ const source =
134
+ data instanceof ArrayBuffer
135
+ ? new Uint8Array(data)
136
+ : new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
137
+ const start = dataOffset ?? 0;
138
+ const end = size === undefined ? source.byteLength : start + size;
139
+ this.writes.push({ buffer, offset: bufferOffset, bytes: source.slice(start, end) });
140
+ return super.writeBuffer(buffer, bufferOffset, data, dataOffset, size);
141
+ }
142
+ }
143
+
114
144
  function surfaceCanvas(width = 1920, height = 1080): HTMLCanvasElement {
115
145
  return { width, height, getContext: () => null } as unknown as HTMLCanvasElement;
116
146
  }
@@ -336,11 +366,11 @@ describe('volumetric fog budget and recovery contract', () => {
336
366
  label: 'volume-params',
337
367
  kind: 'buffer',
338
368
  origin: 'imported',
339
- descriptor: { kind: 'buffer', size: 128 },
369
+ descriptor: { kind: 'buffer', size: VOLUMETRIC_FOG_PARAMS_BYTES },
340
370
  firstUse: 0,
341
371
  lastUse: 3,
342
372
  derivedUsage: 64,
343
- byteSize: 128,
373
+ byteSize: VOLUMETRIC_FOG_PARAMS_BYTES,
344
374
  },
345
375
  ],
346
376
  } as const;
@@ -349,21 +379,25 @@ describe('volumetric fog budget and recovery contract', () => {
349
379
  expect(facts.sampleCount).toBe(960 * 540 * 64);
350
380
  expect(facts.resolvedPixelCount).toBe(960 * 540);
351
381
  expect(facts.currentBytes).toBe(960 * 540 * 8);
352
- expect(facts.bufferBytes).toBe(128);
382
+ expect(facts.bufferBytes).toBe(VOLUMETRIC_FOG_PARAMS_BYTES);
353
383
  expect(facts.liveResourceCount).toBe(5);
354
384
  expect(facts.historyBytes).toBe(960 * 540 * 8 * 2);
355
385
  expect(facts.physicalResourceCount).toBe(5);
356
- expect(facts.totalBytes).toBe(960 * 540 * 16 * 4 + 960 * 540 * 8 * 3 + 128);
386
+ expect(facts.totalBytes).toBe(
387
+ 960 * 540 * 16 * 4 + 960 * 540 * 8 * 3 + VOLUMETRIC_FOG_PARAMS_BYTES,
388
+ );
357
389
  expect(facts.totalBytes).toBe(
358
390
  facts.currentBytes + facts.historyBytes + facts.scratchBytes + facts.bufferBytes,
359
391
  );
360
392
  expect(facts.totalBytes).toBeLessThanOrEqual(MAX_MEMORY_BYTES);
361
393
 
362
394
  const doubleBuffered = inspectVolumetricFogResources(graph, { parameterBufferCount: 2 });
363
- expect(doubleBuffered.bufferBytes).toBe(256);
395
+ expect(doubleBuffered.bufferBytes).toBe(VOLUMETRIC_FOG_PARAMS_BYTES * 2);
364
396
  expect(doubleBuffered.liveResourceCount).toBe(6);
365
397
  expect(doubleBuffered.physicalResourceCount).toBe(6);
366
- expect(doubleBuffered.totalBytes).toBe(960 * 540 * 16 * 4 + 960 * 540 * 8 * 3 + 256);
398
+ expect(doubleBuffered.totalBytes).toBe(
399
+ 960 * 540 * 16 * 4 + 960 * 540 * 8 * 3 + VOLUMETRIC_FOG_PARAMS_BYTES * 2,
400
+ );
367
401
  expect(doubleBuffered.totalBytes).toBe(
368
402
  doubleBuffered.currentBytes +
369
403
  doubleBuffered.historyBytes +
@@ -372,6 +406,136 @@ describe('volumetric fog budget and recovery contract', () => {
372
406
  );
373
407
  });
374
408
 
409
+ it('uses the canonical World clock to bound real volume history reuse', async () => {
410
+ const queue = new RecordingNullQueue();
411
+ const adapter = new RecoverableNullAdapter(() => queue);
412
+ const backend = {
413
+ ...nullRhi,
414
+ requestAdapter: async () => ok(adapter),
415
+ };
416
+ const created = await constructRendererHost(
417
+ surfaceCanvas(640, 360),
418
+ { rhi: backend },
419
+ { shaderManifestUrl: VOLUME_MANIFEST },
420
+ );
421
+ expect(created.ok).toBe(true);
422
+ if (!created.ok) return;
423
+ const { renderer } = created.value;
424
+ const world = new World();
425
+ const density = world.allocSharedRef('TextureAsset', densityAsset());
426
+ world
427
+ .spawn(
428
+ { component: Transform, data: { pos: [0, 0, 3] } },
429
+ {
430
+ component: Camera,
431
+ data: {
432
+ ...perspective({ fov: Math.PI / 4, aspect: 16 / 9 }),
433
+ tonemap: TONEMAP_ACES_FILMIC,
434
+ },
435
+ },
436
+ )
437
+ .unwrap();
438
+ const sun = world
439
+ .spawn({
440
+ component: DirectionalLight,
441
+ data: { direction: [-0.4, -0.8, -0.3], castShadow: true },
442
+ })
443
+ .unwrap();
444
+ world
445
+ .spawn({
446
+ component: VolumetricFog,
447
+ data: {
448
+ light: sun,
449
+ density,
450
+ boundsMin: [-1, -1, -1],
451
+ boundsMax: [1, 1, 1],
452
+ extinction: [0.2, 0.2, 0.2],
453
+ albedo: [0.8, 0.8, 0.8],
454
+ emission: [0, 0, 0],
455
+ anisotropy: 0,
456
+ maxDistance: 50,
457
+ },
458
+ })
459
+ .unwrap();
460
+
461
+ const lease = renderer.attach(world);
462
+ expect(lease.ok).toBe(true);
463
+ if (!lease.ok) return;
464
+ const request = {
465
+ leases: [lease.value],
466
+ camera: { lease: lease.value },
467
+ environment: { lease: lease.value },
468
+ } as const;
469
+ const drawAndRead = async (): Promise<{
470
+ readonly history: number;
471
+ readonly worldTime: number;
472
+ readonly delta: number;
473
+ }> => {
474
+ queue.writes.length = 0;
475
+ const frame = renderer.draw(request);
476
+ expect(frame.ok).toBe(true);
477
+ if (!frame.ok) return { history: -1, worldTime: -1, delta: -1 };
478
+ expect((await frame.value.completed).ok).toBe(true);
479
+ const paramsWrite = queue.writes
480
+ .filter((write) => write.offset === 0 && write.bytes.byteLength === 144)
481
+ .at(-1);
482
+ expect(paramsWrite).toBeDefined();
483
+ if (paramsWrite === undefined) return { history: -1, worldTime: -1, delta: -1 };
484
+ const historyWrite = queue.writes
485
+ .filter(
486
+ (write) =>
487
+ write.buffer === paramsWrite.buffer &&
488
+ write.offset === 31 * Float32Array.BYTES_PER_ELEMENT &&
489
+ write.bytes.byteLength === 3 * Float32Array.BYTES_PER_ELEMENT,
490
+ )
491
+ .at(-1);
492
+ expect(historyWrite).toBeDefined();
493
+ if (historyWrite === undefined) return { history: -1, worldTime: -1, delta: -1 };
494
+ return {
495
+ history: new Float32Array(
496
+ historyWrite.bytes.buffer,
497
+ historyWrite.bytes.byteOffset,
498
+ 1,
499
+ )[0] as number,
500
+ worldTime: new Float32Array(
501
+ historyWrite.bytes.buffer,
502
+ historyWrite.bytes.byteOffset,
503
+ 3,
504
+ )[1] as number,
505
+ delta: new Float32Array(
506
+ historyWrite.bytes.buffer,
507
+ historyWrite.bytes.byteOffset,
508
+ 3,
509
+ )[2] as number,
510
+ };
511
+ };
512
+
513
+ world.update(1 / 60).unwrap();
514
+ const firstTime = world.getResource(Time).elapsed;
515
+ const first = await drawAndRead();
516
+ expect(first.worldTime).toBeCloseTo(firstTime, 5);
517
+ expect(first.history).toBe(0);
518
+
519
+ const stable = await drawAndRead();
520
+ expect(world.getResource(Time).elapsed).toBe(firstTime);
521
+ expect(stable.worldTime).toBeCloseTo(firstTime, 5);
522
+ expect(stable.history).toBe(1);
523
+
524
+ world.update(1 / 60).unwrap();
525
+ const movedTime = world.getResource(Time).elapsed;
526
+ const moved = await drawAndRead();
527
+ expect(moved.worldTime).toBeCloseTo(movedTime, 5);
528
+ expect(moved.worldTime).toBeGreaterThan(first.worldTime);
529
+ expect(moved.history).toBe(1);
530
+ expect(moved.delta).toBeCloseTo(1 / 60, 6);
531
+ expect(stable.delta).toBe(0);
532
+ for (let index = 0; index < 12; index += 1) world.update(1 / 60).unwrap();
533
+ const jumped = await drawAndRead();
534
+ expect(jumped.history).toBe(0);
535
+ expect(jumped.delta).toBeGreaterThan(0.1);
536
+ await renderer.dispose();
537
+ }, 30_000);
538
+
375
539
  it('derives a same-XY resolve descriptor when the packed raw extent is supplied alone', async () => {
376
540
  const adapter = await nullRhi.requestAdapter();
377
541
  expect(adapter.ok).toBe(true);