@forgeax/engine-render 0.1.33 → 0.1.34

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 (518) hide show
  1. package/README.md +1010 -124
  2. package/dist/assembly/backend-contract.d.ts +2 -0
  3. package/dist/assembly/backend-contract.d.ts.map +1 -1
  4. package/dist/assembly/barrel-distortion-inspection.d.ts +3 -0
  5. package/dist/assembly/barrel-distortion-inspection.d.ts.map +1 -0
  6. package/dist/assembly/canvas-draw-guard.d.ts +3 -0
  7. package/dist/assembly/canvas-draw-guard.d.ts.map +1 -0
  8. package/dist/assembly/cloud-shadow-entry.d.ts +4 -0
  9. package/dist/assembly/cloud-shadow-entry.d.ts.map +1 -0
  10. package/dist/assembly/dynamic-geometry-host.d.ts +4 -1
  11. package/dist/assembly/dynamic-geometry-host.d.ts.map +1 -1
  12. package/dist/assembly/dynamic-geometry-runtime.d.ts +1 -1
  13. package/dist/assembly/dynamic-geometry-runtime.d.ts.map +1 -1
  14. package/dist/assembly/factory.d.ts +1 -1
  15. package/dist/assembly/factory.d.ts.map +1 -1
  16. package/dist/assembly/host-contract.d.ts +3 -3
  17. package/dist/assembly/host-contract.d.ts.map +1 -1
  18. package/dist/assembly/material/assembly.d.ts.map +1 -1
  19. package/dist/assembly/material/pipeline-helpers.d.ts +5 -0
  20. package/dist/assembly/material/pipeline-helpers.d.ts.map +1 -1
  21. package/dist/assembly/material-shader-policy.d.ts +16 -2
  22. package/dist/assembly/material-shader-policy.d.ts.map +1 -1
  23. package/dist/assembly/material-shader-prewarm.d.ts.map +1 -1
  24. package/dist/assembly/recovery/device-loss-fanout.d.ts.map +1 -1
  25. package/dist/assembly/recovery/renderer-recover.d.ts +2 -0
  26. package/dist/assembly/recovery/renderer-recover.d.ts.map +1 -1
  27. package/dist/assembly/render-feature-post-processes.d.ts +6 -0
  28. package/dist/assembly/render-feature-post-processes.d.ts.map +1 -0
  29. package/dist/assembly/renderer-facade.d.ts.map +1 -1
  30. package/dist/assembly/renderer-feature-inspection.d.ts +1 -1
  31. package/dist/assembly/renderer-feature-inspection.d.ts.map +1 -1
  32. package/dist/assembly/renderer-frame-transaction.d.ts +12 -0
  33. package/dist/assembly/renderer-frame-transaction.d.ts.map +1 -1
  34. package/dist/assembly/renderer-inspection.d.ts +11 -0
  35. package/dist/assembly/renderer-inspection.d.ts.map +1 -1
  36. package/dist/assembly/webgpu-pbr-ready.d.ts +1 -0
  37. package/dist/assembly/webgpu-pbr-ready.d.ts.map +1 -1
  38. package/dist/assembly/webgpu-ready-per-pass.d.ts +39 -0
  39. package/dist/assembly/webgpu-ready-per-pass.d.ts.map +1 -0
  40. package/dist/assembly/webgpu-ready.d.ts +1 -1
  41. package/dist/assembly/webgpu-ready.d.ts.map +1 -1
  42. package/dist/assembly/webgpu-renderer-bootstrap.d.ts.map +1 -1
  43. package/dist/assembly/webgpu-renderer-observation-owner.d.ts +35 -0
  44. package/dist/assembly/webgpu-renderer-observation-owner.d.ts.map +1 -0
  45. package/dist/assembly/webgpu-renderer.d.ts +1 -1
  46. package/dist/assembly/webgpu-renderer.d.ts.map +1 -1
  47. package/dist/authoring.mjs +1 -1
  48. package/dist/barrel-distortion.d.ts +47 -0
  49. package/dist/barrel-distortion.d.ts.map +1 -0
  50. package/dist/{chunk-PA5DRJNI.mjs → chunk-ELJFJ2BU.mjs} +2 -2
  51. package/dist/chunk-ELJFJ2BU.mjs.map +1 -0
  52. package/dist/{chunk-ZDQWGQW7.mjs → chunk-HEB564WF.mjs} +4 -4
  53. package/dist/{chunk-ZDQWGQW7.mjs.map → chunk-HEB564WF.mjs.map} +1 -1
  54. package/dist/{chunk-VOZWDM3L.mjs → chunk-MKDIT2EM.mjs} +1307 -188
  55. package/dist/chunk-MKDIT2EM.mjs.map +1 -0
  56. package/dist/{chunk-LEAXOMYJ.mjs → chunk-OCDLEI6I.mjs} +3 -3
  57. package/dist/{chunk-LEAXOMYJ.mjs.map → chunk-OCDLEI6I.mjs.map} +1 -1
  58. package/dist/chunk-ODZWR6JQ.mjs +4927 -0
  59. package/dist/chunk-ODZWR6JQ.mjs.map +1 -0
  60. package/dist/{chunk-3RTCJTH4.mjs → chunk-SHFUFJ5N.mjs} +184 -13
  61. package/dist/chunk-SHFUFJ5N.mjs.map +1 -0
  62. package/dist/{chunk-OO3DRYK7.mjs → chunk-UI5SFODX.mjs} +176 -96
  63. package/dist/chunk-UI5SFODX.mjs.map +1 -0
  64. package/dist/{chunk-PLAXOOX5.mjs → chunk-UINHLCZA.mjs} +561 -79
  65. package/dist/chunk-UINHLCZA.mjs.map +1 -0
  66. package/dist/{chunk-NB2S7ZRX.mjs → chunk-WEX45HKH.mjs} +127 -386
  67. package/dist/chunk-WEX45HKH.mjs.map +1 -0
  68. package/dist/cloud/density.d.ts +55 -0
  69. package/dist/cloud/density.d.ts.map +1 -0
  70. package/dist/cloud/extract.d.ts +25 -0
  71. package/dist/cloud/extract.d.ts.map +1 -0
  72. package/dist/cloud/feature.d.ts +57 -0
  73. package/dist/cloud/feature.d.ts.map +1 -0
  74. package/dist/cloud/frame-context.d.ts +8 -0
  75. package/dist/cloud/frame-context.d.ts.map +1 -0
  76. package/dist/cloud/inspection.d.ts +59 -0
  77. package/dist/cloud/inspection.d.ts.map +1 -0
  78. package/dist/cloud/optics.d.ts +56 -0
  79. package/dist/cloud/optics.d.ts.map +1 -0
  80. package/dist/cloud/parameters.d.ts +73 -0
  81. package/dist/cloud/parameters.d.ts.map +1 -0
  82. package/dist/cloud/resources.d.ts +60 -0
  83. package/dist/cloud/resources.d.ts.map +1 -0
  84. package/dist/cloud/shadow.d.ts +42 -0
  85. package/dist/cloud/shadow.d.ts.map +1 -0
  86. package/dist/cloud/temporal.d.ts +74 -0
  87. package/dist/cloud/temporal.d.ts.map +1 -0
  88. package/dist/components/barrel-distortion.d.ts +13 -0
  89. package/dist/components/barrel-distortion.d.ts.map +1 -0
  90. package/dist/components/cloud-layer.d.ts +29 -0
  91. package/dist/components/cloud-layer.d.ts.map +1 -0
  92. package/dist/components/index.d.ts +2 -0
  93. package/dist/components/index.d.ts.map +1 -1
  94. package/dist/components/instances.d.ts +20 -6
  95. package/dist/components/instances.d.ts.map +1 -1
  96. package/dist/construct-renderer.d.ts +1 -1
  97. package/dist/construct-renderer.d.ts.map +1 -1
  98. package/dist/construct-renderer.mjs +6759 -2875
  99. package/dist/construct-renderer.mjs.map +1 -1
  100. package/dist/dynamic-geometry.d.ts +5 -1
  101. package/dist/dynamic-geometry.d.ts.map +1 -1
  102. package/dist/errors/cloud.d.ts +59 -0
  103. package/dist/errors/cloud.d.ts.map +1 -0
  104. package/dist/errors/render.d.ts +18 -4
  105. package/dist/errors/render.d.ts.map +1 -1
  106. package/dist/examples/single-layer-medium-surface.d.ts +47 -0
  107. package/dist/examples/single-layer-medium-surface.d.ts.map +1 -0
  108. package/dist/extract/camera.d.ts.map +1 -1
  109. package/dist/extract/contracts.d.ts +5 -1
  110. package/dist/extract/contracts.d.ts.map +1 -1
  111. package/dist/features/barrel-distortion.d.ts +29 -0
  112. package/dist/features/barrel-distortion.d.ts.map +1 -0
  113. package/dist/features/error-reporting.d.ts +6 -2
  114. package/dist/features/error-reporting.d.ts.map +1 -1
  115. package/dist/features/host.d.ts +10 -0
  116. package/dist/features/host.d.ts.map +1 -1
  117. package/dist/features/plan.d.ts +8 -0
  118. package/dist/features/plan.d.ts.map +1 -1
  119. package/dist/features/render-graph-contribution.d.ts +5 -0
  120. package/dist/features/render-graph-contribution.d.ts.map +1 -1
  121. package/dist/features/render-graph-raster.d.ts +1 -0
  122. package/dist/features/render-graph-raster.d.ts.map +1 -1
  123. package/dist/features/targets.d.ts +2 -0
  124. package/dist/features/targets.d.ts.map +1 -1
  125. package/dist/features/types.d.ts +44 -0
  126. package/dist/features/types.d.ts.map +1 -1
  127. package/dist/fullscreen-post-process-pass.d.ts +15 -7
  128. package/dist/fullscreen-post-process-pass.d.ts.map +1 -1
  129. package/dist/gpu-dirty-ranges.d.ts +11 -0
  130. package/dist/gpu-dirty-ranges.d.ts.map +1 -0
  131. package/dist/gpu-driven/batch-topology.d.ts +17 -0
  132. package/dist/gpu-driven/batch-topology.d.ts.map +1 -1
  133. package/dist/gpu-driven/material-artifacts.d.ts +6 -0
  134. package/dist/gpu-driven/material-artifacts.d.ts.map +1 -1
  135. package/dist/gpu-driven/prepared-draw.d.ts.map +1 -1
  136. package/dist/gpu-driven/production-raster-material.d.ts +178 -0
  137. package/dist/gpu-driven/production-raster-material.d.ts.map +1 -0
  138. package/dist/gpu-driven/production-raster.d.ts +47 -3
  139. package/dist/gpu-driven/production-raster.d.ts.map +1 -1
  140. package/dist/gpu-driven/resource-allocation.d.ts +31 -0
  141. package/dist/gpu-driven/resource-allocation.d.ts.map +1 -0
  142. package/dist/gpu-driven/shadow-ownership.d.ts +14 -0
  143. package/dist/gpu-driven/shadow-ownership.d.ts.map +1 -0
  144. package/dist/gpu-driven/view-gpu.d.ts +26 -2
  145. package/dist/gpu-driven/view-gpu.d.ts.map +1 -1
  146. package/dist/gpu-scene.d.ts +5 -0
  147. package/dist/gpu-scene.d.ts.map +1 -1
  148. package/dist/hdrp-buffers.d.ts +2 -2
  149. package/dist/hdrp-buffers.d.ts.map +1 -1
  150. package/dist/ibl/skylight-bind-group.d.ts +10 -1
  151. package/dist/ibl/skylight-bind-group.d.ts.map +1 -1
  152. package/dist/index.d.ts +27 -7
  153. package/dist/index.d.ts.map +1 -1
  154. package/dist/index.mjs +258 -8
  155. package/dist/index.mjs.map +1 -1
  156. package/dist/inspection-types.d.ts +135 -1
  157. package/dist/inspection-types.d.ts.map +1 -1
  158. package/dist/instance-buffer-cache.d.ts +4 -4
  159. package/dist/instance-buffer-cache.d.ts.map +1 -1
  160. package/dist/instances.d.ts +48 -100
  161. package/dist/instances.d.ts.map +1 -1
  162. package/dist/internal.mjs +7 -7
  163. package/dist/pbr-pipeline.d.ts +19 -1
  164. package/dist/pbr-pipeline.d.ts.map +1 -1
  165. package/dist/pipeline/single-layer-medium-passes.d.ts +73 -0
  166. package/dist/pipeline/single-layer-medium-passes.d.ts.map +1 -0
  167. package/dist/pipeline/standard-forward-lane.d.ts.map +1 -1
  168. package/dist/pipeline/standard-output/graph.d.ts.map +1 -1
  169. package/dist/pipeline/standard-output/types.d.ts +4 -2
  170. package/dist/pipeline/standard-output/types.d.ts.map +1 -1
  171. package/dist/pipeline/standard-pipeline.d.ts.map +1 -1
  172. package/dist/pipeline/standard-post.d.ts.map +1 -1
  173. package/dist/pipeline-builder.d.ts +1 -1
  174. package/dist/pipeline-builder.d.ts.map +1 -1
  175. package/dist/plugin.d.ts.map +1 -1
  176. package/dist/prepare/prepared-graphics-resolver.d.ts +2 -0
  177. package/dist/prepare/prepared-graphics-resolver.d.ts.map +1 -1
  178. package/dist/record/barrel-distortion-frame.d.ts +15 -0
  179. package/dist/record/barrel-distortion-frame.d.ts.map +1 -0
  180. package/dist/record/dynamic-geometry-consumption.d.ts +10 -2
  181. package/dist/record/dynamic-geometry-consumption.d.ts.map +1 -1
  182. package/dist/record/fold-buckets.d.ts +95 -0
  183. package/dist/record/fold-buckets.d.ts.map +1 -0
  184. package/dist/record/fold-instance-buffer.d.ts +6 -0
  185. package/dist/record/fold-instance-buffer.d.ts.map +1 -0
  186. package/dist/record/frame-lighting.d.ts +1 -0
  187. package/dist/record/frame-lighting.d.ts.map +1 -1
  188. package/dist/record/frame-snapshot.d.ts +37 -3
  189. package/dist/record/frame-snapshot.d.ts.map +1 -1
  190. package/dist/record/frame.d.ts +10 -61
  191. package/dist/record/frame.d.ts.map +1 -1
  192. package/dist/record/gpu-draw-receipts.d.ts +11 -0
  193. package/dist/record/gpu-draw-receipts.d.ts.map +1 -0
  194. package/dist/record/gpu-timing.d.ts +8 -0
  195. package/dist/record/gpu-timing.d.ts.map +1 -1
  196. package/dist/record/main-pass-geometry.d.ts +21 -5
  197. package/dist/record/main-pass-geometry.d.ts.map +1 -1
  198. package/dist/record/main-pass-material.d.ts +7 -1
  199. package/dist/record/main-pass-material.d.ts.map +1 -1
  200. package/dist/record/main-pass-sprite-draws.d.ts.map +1 -1
  201. package/dist/record/main-pass.d.ts +13 -2
  202. package/dist/record/main-pass.d.ts.map +1 -1
  203. package/dist/record/motion-blur-receipt.d.ts +11 -0
  204. package/dist/record/motion-blur-receipt.d.ts.map +1 -0
  205. package/dist/record/probe-blend-buffer.d.ts +16 -0
  206. package/dist/record/probe-blend-buffer.d.ts.map +1 -0
  207. package/dist/record/recovery-pipeline.d.ts.map +1 -1
  208. package/dist/record/render-context.d.ts +22 -3
  209. package/dist/record/render-context.d.ts.map +1 -1
  210. package/dist/record/shadow-pass.d.ts +2 -0
  211. package/dist/record/shadow-pass.d.ts.map +1 -1
  212. package/dist/record/skybox-post-pass.d.ts +1 -1
  213. package/dist/record/skybox-post-pass.d.ts.map +1 -1
  214. package/dist/record/typed-frame-graph.d.ts +22 -2
  215. package/dist/record/typed-frame-graph.d.ts.map +1 -1
  216. package/dist/record/view-ubo.d.ts +11 -3
  217. package/dist/record/view-ubo.d.ts.map +1 -1
  218. package/dist/record/volume-params.d.ts +9 -0
  219. package/dist/record/volume-params.d.ts.map +1 -0
  220. package/dist/recovery/render-system-candidate.d.ts +10 -0
  221. package/dist/recovery/render-system-candidate.d.ts.map +1 -1
  222. package/dist/reflection/record-owner.d.ts.map +1 -1
  223. package/dist/render-contract.d.ts +42 -4
  224. package/dist/render-contract.d.ts.map +1 -1
  225. package/dist/render-graph-primitives.d.ts.map +1 -1
  226. package/dist/render-pipeline.d.ts +103 -2
  227. package/dist/render-pipeline.d.ts.map +1 -1
  228. package/dist/render-system-extract-tail.d.ts +3 -2
  229. package/dist/render-system-extract-tail.d.ts.map +1 -1
  230. package/dist/render-system-extract.d.ts +16 -9
  231. package/dist/render-system-extract.d.ts.map +1 -1
  232. package/dist/render-system-presentation.d.ts +23 -0
  233. package/dist/render-system-presentation.d.ts.map +1 -0
  234. package/dist/render-system-producer-roots.d.ts +10 -0
  235. package/dist/render-system-producer-roots.d.ts.map +1 -0
  236. package/dist/render-system-residency.d.ts +12 -0
  237. package/dist/render-system-residency.d.ts.map +1 -0
  238. package/dist/render-system.d.ts +19 -14
  239. package/dist/render-system.d.ts.map +1 -1
  240. package/dist/scene/probe-blend.d.ts +27 -0
  241. package/dist/scene/probe-blend.d.ts.map +1 -1
  242. package/dist/scene/render-scene.d.ts +12 -5
  243. package/dist/scene/render-scene.d.ts.map +1 -1
  244. package/dist/ssao-buffers.d.ts +9 -0
  245. package/dist/ssao-buffers.d.ts.map +1 -1
  246. package/dist/surface/dynamic-input.d.ts +133 -0
  247. package/dist/surface/dynamic-input.d.ts.map +1 -0
  248. package/dist/surface/gpu-driven.d.ts +41 -0
  249. package/dist/surface/gpu-driven.d.ts.map +1 -0
  250. package/dist/surface/submission-observation.d.ts +136 -0
  251. package/dist/surface/submission-observation.d.ts.map +1 -0
  252. package/dist/systems/skin-palette-allocator.d.ts.map +1 -1
  253. package/dist/temporal/gpu.d.ts +13 -1
  254. package/dist/temporal/gpu.d.ts.map +1 -1
  255. package/dist/temporal/index.mjs +5 -5
  256. package/dist/temporal/inspection.d.ts.map +1 -1
  257. package/dist/temporal/standard-scene-data.d.ts.map +1 -1
  258. package/dist/temporal/target-coverage-attachment.d.ts.map +1 -1
  259. package/dist/transmission/single-layer-medium.d.ts +89 -0
  260. package/dist/transmission/single-layer-medium.d.ts.map +1 -0
  261. package/dist/typed-render-graph-primitives.d.ts +14 -2
  262. package/dist/typed-render-graph-primitives.d.ts.map +1 -1
  263. package/dist/volume/component.d.ts +9 -4
  264. package/dist/volume/component.d.ts.map +1 -1
  265. package/dist/volume/extract.d.ts +1 -1
  266. package/dist/volume/extract.d.ts.map +1 -1
  267. package/dist/volume/inspection.d.ts +2 -0
  268. package/dist/volume/inspection.d.ts.map +1 -1
  269. package/dist/volume/passes.d.ts +4 -1
  270. package/dist/volume/passes.d.ts.map +1 -1
  271. package/dist/volume/resources.d.ts.map +1 -1
  272. package/package.json +22 -21
  273. package/src/__tests__/atmosphere-daylight.dawn.test.ts +72 -0
  274. package/src/__tests__/barrel-distortion-lkg.integration.test.ts +353 -0
  275. package/src/__tests__/barrel-distortion.dawn.test.ts +41 -0
  276. package/src/__tests__/barrel-distortion.unit.test.ts +364 -0
  277. package/src/__tests__/construct-renderer-identity.unit.test.ts +49 -0
  278. package/src/__tests__/detached-recovery-graph.unit.test.ts +277 -0
  279. package/src/__tests__/device-loss-fanout.unit.test.ts +36 -1
  280. package/src/__tests__/docs-gate-green.unit.test.ts +112 -1
  281. package/src/__tests__/factory-contract.integration.test.ts +295 -45
  282. package/src/__tests__/feature-depth-input.dawn.test.ts +129 -2
  283. package/src/__tests__/fullscreen-feature-plan.unit.test.ts +14 -3
  284. package/src/__tests__/gpu-dirty-ranges.unit.test.ts +22 -0
  285. package/src/__tests__/gpu-driven-baseline.characterization.test.ts +16 -3
  286. package/src/__tests__/gpu-driven-batch-topology-pbr.unit.test.ts +12 -0
  287. package/src/__tests__/gpu-driven-material-artifacts.unit.test.ts +143 -0
  288. package/src/__tests__/gpu-driven-pbr.dawn.test.ts +2 -2
  289. package/src/__tests__/gpu-driven-production.integration.test.ts +482 -3
  290. package/src/__tests__/gpu-driven-shadow-views.dawn.test.ts +6 -5
  291. package/src/__tests__/gpu-driven-view-gpu-evidence.ts +94 -6
  292. package/src/__tests__/gpu-driven-view-graph.integration.test.ts +18 -2
  293. package/src/__tests__/gpu-driven-view.browser.test.ts +5 -8
  294. package/src/__tests__/gpu-driven-view.dawn.test.ts +6 -2
  295. package/src/__tests__/gpu-driven-view.unit.test.ts +1 -1
  296. package/src/__tests__/gpu-pass-timing-lifecycle.integration.test.ts +1 -0
  297. package/src/__tests__/gpu-pass-timing.browser.test.ts +6 -4
  298. package/src/__tests__/gpu-pass-timing.rhi-null.unit.test.ts +1 -0
  299. package/src/__tests__/gpu-scene.unit.test.ts +119 -9
  300. package/src/__tests__/instance-allocation-failure.unit.test.ts +57 -0
  301. package/src/__tests__/instances-culling.integration.test.ts +10 -36
  302. package/src/__tests__/instances-store.unit.test.ts +97 -305
  303. package/src/__tests__/instances-world-ownership.integration.test.ts +62 -0
  304. package/src/__tests__/main-pass-material-medium-selection.unit.test.ts +146 -0
  305. package/src/__tests__/material-shader-prewarm.unit.test.ts +59 -0
  306. package/src/__tests__/material-snapshot-mutation.integration.test.ts +39 -1
  307. package/src/__tests__/morph-culling-reentry.dawn.test.ts +5 -5
  308. package/src/__tests__/morph-visual-evidence.integration.test.ts +8 -7
  309. package/src/__tests__/motion-blur-compute.dawn.test.ts +13 -7
  310. package/src/__tests__/motion-blur.pixel.integration.test.ts +2 -2
  311. package/src/__tests__/nineslice-handle-surface.unit.test.ts +4 -4
  312. package/src/__tests__/pbr-view-layout.unit.test.ts +9 -0
  313. package/src/__tests__/physical-clearcoat.integration.test.ts +58 -0
  314. package/src/__tests__/prepared-graphics-capability.unit.test.ts +66 -2
  315. package/src/__tests__/probe-bindings.unit.test.ts +582 -1
  316. package/src/__tests__/probe-scene-dirty.integration.test.ts +28 -1
  317. package/src/__tests__/receipt-observation-retirement.unit.test.ts +65 -0
  318. package/src/__tests__/recovery-candidate-prepare.contract.test.ts +69 -0
  319. package/src/__tests__/render-error-code-owner.test-d.ts +7 -0
  320. package/src/__tests__/render-feature-diagnostics.unit.test.ts +15 -2
  321. package/src/__tests__/render-feature-error-reporting.unit.test.ts +21 -1
  322. package/src/__tests__/render-feature-prepared-diagnostics.unit.test.ts +10 -1
  323. package/src/__tests__/render-feature-retry.unit.test.ts +12 -1
  324. package/src/__tests__/render-scene-mixed-updates.integration.test.ts +145 -19
  325. package/src/__tests__/render-scene-projection.unit.test.ts +36 -11
  326. package/src/__tests__/render-scene-temporal-retry.unit.test.ts +20 -20
  327. package/src/__tests__/render-target-probe-soak.perf.test.ts +2 -2
  328. package/src/__tests__/render-temporal-environment-consumer.browser.test.ts +3 -3
  329. package/src/__tests__/render-temporal-environment-consumer.dawn.test.ts +5 -2
  330. package/src/__tests__/renderer-factory-material-contract.unit.test.ts +41 -7
  331. package/src/__tests__/renderer-frame-generation-fence.integration.test.ts +16 -0
  332. package/src/__tests__/renderer-frame-transaction.integration.test.ts +26 -10
  333. package/src/__tests__/resource-allocation.unit.test.ts +123 -0
  334. package/src/__tests__/single-layer-medium-passes.unit.test.ts +275 -0
  335. package/src/__tests__/skin-motion-regression.unit.test.ts +70 -1
  336. package/src/__tests__/skin-palette-recycling.dawn.test.ts +97 -0
  337. package/src/__tests__/skinned-shadow-caster.test.ts +15 -0
  338. package/src/__tests__/ssr-carrier-public-contract.unit.test.ts +21 -0
  339. package/src/__tests__/ssr-gpu-dispatch.browser.test.ts +4 -4
  340. package/src/__tests__/ssr-gpu-dispatch.dawn.test.ts +2 -2
  341. package/src/__tests__/ssr-gpu-dispatch.ts +1 -1
  342. package/src/__tests__/ssr-receiver-coverage.dawn.test.ts +6 -2
  343. package/src/__tests__/standard-output-chain-contract.unit.test.ts +28 -0
  344. package/src/__tests__/standard-pbr-artifact-assembly.unit.test.ts +46 -1
  345. package/src/__tests__/standard-pipeline.integration.test.ts +182 -1
  346. package/src/__tests__/surface-dynamic-buffer-lifecycle.unit.test.ts +506 -0
  347. package/src/__tests__/surface-submission-observation.unit.test.ts +812 -0
  348. package/src/__tests__/temporal-history-lifecycle.unit.test.ts +32 -0
  349. package/src/__tests__/temporal-performance.integration.test.ts +3 -3
  350. package/src/__tests__/visibility-instances.integration.test.ts +7 -19
  351. package/src/__tests__/visibility-producer-matrix.integration.test.ts +9 -20
  352. package/src/__tests__/volume-optics-oracle.unit.test.ts +4 -2
  353. package/src/__tests__/volumetric-fog-budget-recovery.integration.test.ts +30 -20
  354. package/src/__tests__/volumetric-fog-errors.unit.test.ts +4 -3
  355. package/src/__tests__/volumetric-fog-multiple.unit.test.ts +55 -0
  356. package/src/__tests__/volumetric-fog-spot-frame-optics.integration.test.ts +1 -1
  357. package/src/__tests__/volumetric-fog-stage-readback.dawn.test.ts +113 -9
  358. package/src/__tests__/volumetric-fog-topology.unit.test.ts +5 -3
  359. package/src/assembly/backend-contract.ts +30 -0
  360. package/src/assembly/barrel-distortion-inspection.ts +22 -0
  361. package/src/assembly/canvas-draw-guard.ts +11 -0
  362. package/src/assembly/cloud-shadow-entry.ts +22 -0
  363. package/src/assembly/dynamic-geometry-host.ts +65 -37
  364. package/src/assembly/dynamic-geometry-runtime.ts +7 -1
  365. package/src/assembly/factory.ts +2 -30
  366. package/src/assembly/host-contract.ts +3 -3
  367. package/src/assembly/material/assembly.ts +25 -13
  368. package/src/assembly/material/pipeline-helpers.ts +53 -12
  369. package/src/assembly/material-shader-policy.ts +55 -0
  370. package/src/assembly/material-shader-prewarm.ts +21 -10
  371. package/src/assembly/recovery/device-loss-fanout.ts +13 -0
  372. package/src/assembly/recovery/renderer-recover.ts +41 -18
  373. package/src/assembly/render-feature-post-processes.ts +54 -0
  374. package/src/assembly/renderer-facade.ts +4 -1
  375. package/src/assembly/renderer-feature-inspection.ts +8 -2
  376. package/src/assembly/renderer-frame-transaction.ts +23 -0
  377. package/src/assembly/renderer-inspection.ts +28 -0
  378. package/src/assembly/skin-palette-owner.ts +1 -1
  379. package/src/assembly/temporal-post-process-prewarm.ts +3 -3
  380. package/src/assembly/webgpu-pbr-ready.ts +9 -1
  381. package/src/assembly/webgpu-ready-per-pass.ts +104 -0
  382. package/src/assembly/webgpu-ready.ts +115 -61
  383. package/src/assembly/webgpu-renderer-bootstrap.ts +0 -12
  384. package/src/assembly/webgpu-renderer-observation-owner.ts +222 -0
  385. package/src/assembly/webgpu-renderer.ts +214 -252
  386. package/src/assembly/webgpu-vertex-layouts.ts +1 -1
  387. package/src/barrel-distortion.ts +247 -0
  388. package/src/cloud/__tests__/cloud-density-parity.dawn.test.ts +173 -0
  389. package/src/cloud/__tests__/cloud-layer-framebuffer-y.dawn.test.ts +265 -0
  390. package/src/cloud/__tests__/cloud-layer.browser.test.ts +432 -0
  391. package/src/cloud/__tests__/cloud-layer.unit.test.ts +634 -0
  392. package/src/cloud/density.ts +600 -0
  393. package/src/cloud/extract.ts +110 -0
  394. package/src/cloud/feature.ts +1810 -0
  395. package/src/cloud/frame-context.ts +42 -0
  396. package/src/cloud/inspection.ts +130 -0
  397. package/src/cloud/optics.ts +285 -0
  398. package/src/cloud/parameters.ts +255 -0
  399. package/src/cloud/resources.ts +122 -0
  400. package/src/cloud/shadow.ts +167 -0
  401. package/src/cloud/temporal.ts +226 -0
  402. package/src/components/barrel-distortion.ts +46 -0
  403. package/src/components/cloud-layer.ts +47 -0
  404. package/src/components/index.ts +2 -0
  405. package/src/components/instances.ts +18 -45
  406. package/src/components/sprite-instances.ts +1 -1
  407. package/src/construct-renderer.ts +1 -1
  408. package/src/dynamic-geometry.ts +12 -1
  409. package/src/errors/cloud.ts +103 -0
  410. package/src/errors/render.ts +44 -7
  411. package/src/examples/single-layer-medium-surface.ts +59 -0
  412. package/src/extract/camera.ts +15 -1
  413. package/src/extract/contracts.ts +5 -0
  414. package/src/features/barrel-distortion.ts +172 -0
  415. package/src/features/error-reporting.ts +26 -6
  416. package/src/features/host.ts +115 -14
  417. package/src/features/plan.ts +8 -0
  418. package/src/features/render-graph-contribution.ts +9 -0
  419. package/src/features/render-graph-raster.ts +45 -3
  420. package/src/features/targets.ts +66 -0
  421. package/src/features/types.ts +47 -0
  422. package/src/fullscreen-post-process-pass.ts +41 -9
  423. package/src/gpu-dirty-ranges.ts +48 -0
  424. package/src/gpu-driven/batch-topology.ts +53 -0
  425. package/src/gpu-driven/material-artifacts.ts +33 -25
  426. package/src/gpu-driven/prepared-draw.ts +12 -4
  427. package/src/gpu-driven/production-raster-material.ts +1894 -0
  428. package/src/gpu-driven/production-raster.ts +825 -784
  429. package/src/gpu-driven/resource-allocation.ts +142 -0
  430. package/src/gpu-driven/shadow-ownership.ts +71 -0
  431. package/src/gpu-driven/view-gpu.ts +164 -10
  432. package/src/gpu-scene.ts +75 -84
  433. package/src/hdrp-buffers.ts +4 -3
  434. package/src/ibl/skylight-bind-group.ts +49 -7
  435. package/src/index.ts +196 -9
  436. package/src/inspection-types.ts +162 -0
  437. package/src/instance-buffer-cache.ts +4 -4
  438. package/src/instances.ts +149 -569
  439. package/src/pbr-pipeline.ts +219 -13
  440. package/src/pipeline/single-layer-medium-passes.ts +267 -0
  441. package/src/pipeline/standard-forward-lane.ts +313 -57
  442. package/src/pipeline/standard-output/graph.ts +8 -2
  443. package/src/pipeline/standard-output/types.ts +12 -1
  444. package/src/pipeline/standard-pipeline.ts +291 -18
  445. package/src/pipeline/standard-post.ts +90 -24
  446. package/src/pipeline-builder.ts +1 -1
  447. package/src/plugin.ts +4 -0
  448. package/src/points-lines/__tests__/points-lines.bench.ts +17 -8
  449. package/src/prepare/prepared-graphics-resolver.ts +2 -0
  450. package/src/record/__tests__/main-pass-geometry.unit.test.ts +30 -0
  451. package/src/record/__tests__/sprite-material-abi.unit.test.ts +1 -1
  452. package/src/record/__tests__/temporal-payload.unit.test.ts +1 -1
  453. package/src/record/__tests__/transparent-group2-selection.unit.test.ts +11 -0
  454. package/src/record/__tests__/typed-frame-graph-plan.unit.test.ts +1 -0
  455. package/src/record/__tests__/view-ubo-layout.unit.test.ts +4 -3
  456. package/src/record/barrel-distortion-frame.ts +110 -0
  457. package/src/record/dynamic-geometry-consumption.ts +39 -9
  458. package/src/record/fold-buckets.ts +852 -0
  459. package/src/record/fold-instance-buffer.ts +51 -0
  460. package/src/record/frame-lighting.ts +36 -2
  461. package/src/record/frame-snapshot.ts +39 -3
  462. package/src/record/frame.ts +217 -1084
  463. package/src/record/gpu-draw-receipts.ts +38 -0
  464. package/src/record/gpu-timing.ts +48 -5
  465. package/src/record/main-pass-geometry.ts +251 -82
  466. package/src/record/main-pass-material.ts +66 -4
  467. package/src/record/main-pass-sprite-draws.ts +7 -65
  468. package/src/record/main-pass.ts +156 -20
  469. package/src/record/motion-blur-receipt.ts +91 -0
  470. package/src/record/probe-blend-buffer.ts +180 -0
  471. package/src/record/recovery-pipeline.ts +35 -3
  472. package/src/record/render-context.ts +39 -3
  473. package/src/record/shadow-pass.ts +30 -1
  474. package/src/record/skybox-post-pass.ts +28 -4
  475. package/src/record/typed-frame-graph.ts +770 -134
  476. package/src/record/view-ubo.ts +26 -3
  477. package/src/record/volume-params.ts +225 -0
  478. package/src/recovery/render-system-candidate.ts +235 -42
  479. package/src/reflection/record-owner.ts +7 -3
  480. package/src/render-contract.ts +48 -4
  481. package/src/render-graph-primitives.ts +7 -1
  482. package/src/render-pipeline.ts +130 -0
  483. package/src/render-system-extract-tail.ts +57 -61
  484. package/src/render-system-extract.ts +74 -76
  485. package/src/render-system-presentation.ts +56 -0
  486. package/src/render-system-producer-roots.ts +96 -0
  487. package/src/render-system-residency.ts +159 -0
  488. package/src/render-system.ts +347 -363
  489. package/src/scene/probe-blend.ts +96 -4
  490. package/src/scene/render-scene.ts +349 -280
  491. package/src/ssao-buffers.ts +95 -2
  492. package/src/surface/__tests__/dynamic-input.unit.test.ts +374 -0
  493. package/src/surface/__tests__/gpu-driven.unit.test.ts +87 -0
  494. package/src/surface/dynamic-input.ts +658 -0
  495. package/src/surface/gpu-driven.ts +147 -0
  496. package/src/surface/submission-observation.ts +517 -0
  497. package/src/systems/skin-palette-allocator.ts +4 -1
  498. package/src/temporal/gpu.ts +101 -17
  499. package/src/temporal/inspection.ts +7 -1
  500. package/src/temporal/standard-scene-data.ts +5 -0
  501. package/src/temporal/target-coverage-attachment.ts +4 -0
  502. package/src/transmission/__tests__/single-layer-medium.unit.test.ts +149 -0
  503. package/src/transmission/__tests__/standard-transmission.dawn.test.ts +12 -11
  504. package/src/transmission/single-layer-medium.ts +322 -0
  505. package/src/typed-render-graph-primitives.ts +70 -2
  506. package/src/volume/component.ts +10 -9
  507. package/src/volume/extract.ts +11 -5
  508. package/src/volume/inspection.ts +3 -0
  509. package/src/volume/passes.ts +87 -4
  510. package/src/volume/resources.ts +2 -1
  511. package/dist/chunk-3RTCJTH4.mjs.map +0 -1
  512. package/dist/chunk-NB2S7ZRX.mjs.map +0 -1
  513. package/dist/chunk-NNQL7CB3.mjs +0 -1713
  514. package/dist/chunk-NNQL7CB3.mjs.map +0 -1
  515. package/dist/chunk-OO3DRYK7.mjs.map +0 -1
  516. package/dist/chunk-PA5DRJNI.mjs.map +0 -1
  517. package/dist/chunk-PLAXOOX5.mjs.map +0 -1
  518. package/dist/chunk-VOZWDM3L.mjs.map +0 -1
@@ -0,0 +1,55 @@
1
+ import { type Result } from '@forgeax/engine-types';
2
+ import { type CloudLayerError } from '../errors/cloud';
3
+ import { type CloudQualityProfile, type ValidatedCloudLayer } from './parameters';
4
+ export interface CloudDensityCache {
5
+ readonly sourceKey: string;
6
+ readonly resolution: number;
7
+ /** R8 normalized density values in z-major, y, x order. */
8
+ readonly data: Uint8Array;
9
+ /**
10
+ * Weather, body and erosion bases in three contiguous R8 planes. `data` is
11
+ * retained as a diagnostic projection for callers that still need the
12
+ * composed density; runtime sampling uses these formation planes so coverage,
13
+ * density and wind remain evaluation controls.
14
+ */
15
+ readonly formationData: Uint8Array;
16
+ readonly formationByteLength: number;
17
+ /** Bytes uploaded for the reusable formation planes. */
18
+ readonly byteLength: number;
19
+ readonly digest: string;
20
+ }
21
+ export interface CloudDensityCacheSnapshot {
22
+ readonly sourceKey: string;
23
+ readonly resolution: number;
24
+ readonly data: Uint8Array;
25
+ readonly formationData?: Uint8Array;
26
+ readonly digest: string;
27
+ }
28
+ export interface CloudDensitySample {
29
+ readonly density: number;
30
+ readonly heightFraction: number;
31
+ readonly noise: number;
32
+ readonly weather?: number;
33
+ readonly body?: number;
34
+ readonly erosion?: number;
35
+ }
36
+ export interface CloudFormationSample {
37
+ readonly weather: number;
38
+ readonly body: number;
39
+ readonly erosion: number;
40
+ readonly noise: number;
41
+ }
42
+ export declare const CLOUD_VERTICAL_CELLS = 1;
43
+ /** Evaluate the reusable weather/body/erosion bases at one world position. */
44
+ export declare function evaluateCloudFormation(params: ValidatedCloudLayer, position: ArrayLike<number>, timeSeconds?: number): CloudFormationSample;
45
+ /** Analytic field used for cache generation, CPU diagnostics and recovery. */
46
+ export declare function evaluateCloudDensity(params: ValidatedCloudLayer, position: ArrayLike<number>, timeSeconds?: number): CloudDensitySample;
47
+ /** Build one canonical, periodic formation cache. */
48
+ export declare function buildCloudDensityCache(params: ValidatedCloudLayer, profile?: CloudQualityProfile): CloudDensityCache;
49
+ /** Export a detached snapshot suitable for persistence or a worker boundary. */
50
+ export declare function snapshotCloudDensityCache(cache: CloudDensityCache): CloudDensityCacheSnapshot;
51
+ /** Reconstruct a cache without running the procedural generator again. */
52
+ export declare function reconstructCloudDensityCache(params: ValidatedCloudLayer, snapshot: CloudDensityCacheSnapshot): Result<CloudDensityCache, CloudLayerError>;
53
+ /** Sample the cached field with periodic x/z addressing and wind advection. */
54
+ export declare function sampleCloudDensity(cache: CloudDensityCache, params: ValidatedCloudLayer, position: ArrayLike<number>, timeSeconds?: number): number;
55
+ //# sourceMappingURL=density.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"density.d.ts","sourceRoot":"","sources":["../../src/cloud/density.ts"],"names":[],"mappings":"AAAA,OAAO,EAAW,KAAK,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,EAA+B,KAAK,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACpF,OAAO,EAEL,KAAK,mBAAmB,EAExB,KAAK,mBAAmB,EACzB,MAAM,cAAc,CAAC;AAEtB,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,2DAA2D;IAC3D,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B;;;;;OAKG;IACH,QAAQ,CAAC,aAAa,EAAE,UAAU,CAAC;IACnC,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;IACrC,wDAAwD;IACxD,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,QAAQ,CAAC,aAAa,CAAC,EAAE,UAAU,CAAC;IACpC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAcD,eAAO,MAAM,oBAAoB,IAAI,CAAC;AAkQtC,8EAA8E;AAC9E,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,mBAAmB,EAC3B,QAAQ,EAAE,SAAS,CAAC,MAAM,CAAC,EAC3B,WAAW,SAAI,GACd,oBAAoB,CActB;AAED,8EAA8E;AAC9E,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,mBAAmB,EAC3B,QAAQ,EAAE,SAAS,CAAC,MAAM,CAAC,EAC3B,WAAW,SAAI,GACd,kBAAkB,CAwBpB;AAiBD,qDAAqD;AACrD,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,mBAAmB,EAC3B,OAAO,GAAE,mBAA4D,GACpE,iBAAiB,CA2CnB;AAED,gFAAgF;AAChF,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,iBAAiB,GAAG,yBAAyB,CAQ7F;AAED,0EAA0E;AAC1E,wBAAgB,4BAA4B,CAC1C,MAAM,EAAE,mBAAmB,EAC3B,QAAQ,EAAE,yBAAyB,GAClC,MAAM,CAAC,iBAAiB,EAAE,eAAe,CAAC,CAqC5C;AAOD,+EAA+E;AAC/E,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,iBAAiB,EACxB,MAAM,EAAE,mBAAmB,EAC3B,QAAQ,EAAE,SAAS,CAAC,MAAM,CAAC,EAC3B,WAAW,SAAI,GACd,MAAM,CAiGR"}
@@ -0,0 +1,25 @@
1
+ import type { EntityHandle, World } from '@forgeax/engine-ecs';
2
+ import { type Result } from '@forgeax/engine-types';
3
+ import { type CloudLayerData } from '../components/cloud-layer';
4
+ import { type CloudLayerError } from '../errors/cloud';
5
+ import { type ValidatedCloudLayer } from './parameters';
6
+ export interface CloudLayerCandidate {
7
+ readonly entityKey: EntityHandle;
8
+ readonly data: CloudLayerData;
9
+ }
10
+ export interface ExtractedCloudLayer {
11
+ readonly status: 'available';
12
+ readonly entityKey: EntityHandle;
13
+ readonly sourceKey: string;
14
+ readonly params: ValidatedCloudLayer;
15
+ readonly worldTimeSeconds: number;
16
+ /** First World-owned sun selected by the same extraction boundary. */
17
+ readonly sunDirection: readonly [number, number, number] | undefined;
18
+ readonly sunRadiance: readonly [number, number, number] | undefined;
19
+ readonly revision: number;
20
+ }
21
+ /** Select the one resource owner without creating a second World registry. */
22
+ export declare function selectCloudLayerFrame(candidates: readonly CloudLayerCandidate[], worldTimeSeconds?: number, sunDirection?: readonly [number, number, number] | undefined, sunRadiance?: readonly [number, number, number] | undefined): Result<ExtractedCloudLayer | undefined, CloudLayerError>;
23
+ /** Read the World-owned component and Time resource at the extraction boundary. */
24
+ export declare function extractCloudLayer(world: World): Result<ExtractedCloudLayer | undefined, CloudLayerError>;
25
+ //# sourceMappingURL=extract.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extract.d.ts","sourceRoot":"","sources":["../../src/cloud/extract.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAE/D,OAAO,EAAW,KAAK,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,EAAc,KAAK,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAE5E,OAAO,EAAE,KAAK,eAAe,EAAgC,MAAM,iBAAiB,CAAC;AACrF,OAAO,EAAuB,KAAK,mBAAmB,EAAsB,MAAM,cAAc,CAAC;AAEjG,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,SAAS,EAAE,YAAY,CAAC;IACjC,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC;CAC/B;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,QAAQ,CAAC,SAAS,EAAE,YAAY,CAAC;IACjC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,mBAAmB,CAAC;IACrC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,sEAAsE;IACtE,QAAQ,CAAC,YAAY,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAAC;IACrE,QAAQ,CAAC,WAAW,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAAC;IACpE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED,8EAA8E;AAC9E,wBAAgB,qBAAqB,CACnC,UAAU,EAAE,SAAS,mBAAmB,EAAE,EAC1C,gBAAgB,SAAI,EACpB,YAAY,GAAE,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,SAAqB,EACvE,WAAW,GAAE,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,SAAqB,GACrE,MAAM,CAAC,mBAAmB,GAAG,SAAS,EAAE,eAAe,CAAC,CAyB1D;AAED,mFAAmF;AACnF,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,KAAK,GACX,MAAM,CAAC,mBAAmB,GAAG,SAAS,EAAE,eAAe,CAAC,CAgD1D"}
@@ -0,0 +1,57 @@
1
+ import type { RenderFeature } from '../features/types';
2
+ import { type CloudDensityCache } from './density';
3
+ import { type CloudLayerInspection } from './inspection';
4
+ import { type ValidatedCloudLayer } from './parameters';
5
+ import { type CloudShadowProjection } from './shadow';
6
+ import { type CloudTemporalFrame } from './temporal';
7
+ export declare const CLOUD_LAYER_FEATURE_IDENTITY = "forgeax.cloud-layer";
8
+ export declare const CLOUD_LAYER_DENSITY_PROGRAM = "cloud-layer-density";
9
+ export declare const CLOUD_LAYER_DENSITY_BINDINGS = "cloud-layer-density-bindings";
10
+ export declare const CLOUD_LAYER_DENSITY_CACHE = "cloud-layer-density-cache";
11
+ export declare const CLOUD_LAYER_DENSITY_OUTPUT = "cloud-layer-density-output";
12
+ export declare const CLOUD_VIEW_PARAMS_BYTES = 176;
13
+ /** Minimal copy compute; one invocation owns one packed u32 cache element. */
14
+ export declare const CLOUD_DENSITY_COMPUTE_WGSL = "\nstruct CloudCacheParams { count: u32, }\n@group(0) @binding(0) var<storage, read> sourceDensity: array<u32>;\n@group(0) @binding(1) var<storage, read_write> cachedDensity: array<u32>;\n@group(0) @binding(2) var<uniform> params: CloudCacheParams;\n@compute @workgroup_size(64)\nfn cloud_density_cache(@builtin(global_invocation_id) id: vec3<u32>) {\n if (id.x >= params.count) { return; }\n cachedDensity[id.x] = sourceDensity[id.x];\n}\n";
15
+ /**
16
+ * Cloud view transport. The View group is the renderer-owned camera/light
17
+ * source of truth; group(1) carries frame-local cloud parameters, depth and
18
+ * the bounded packed 3D cache. Camera, solar-column and cloud-interior
19
+ * integration all call cloud_density(), so wind only changes the sample
20
+ * coordinate and never the authored field.
21
+ */
22
+ export declare const CLOUD_VIEW_FULLSCREEN_WGSL = "\nconst CLOUD_VERTICAL_CELLS: f32 = 1;\nconst CLOUD_VERTICAL_NOISE_CELLS: f32 = 2.0;\n\nstruct CloudCameraView {\n worldViewProj: mat4x4<f32>,\n lightDir: vec3<f32>,\n lightColor: vec3<f32>,\n cameraPos: vec3<f32>,\n lightViewProj_A: mat4x4<f32>,\n inverseViewProj: mat4x4<f32>,\n lightViewProj_B: mat4x4<f32>,\n lightViewProj_C: mat4x4<f32>,\n lightViewProj_D: mat4x4<f32>,\n splitPlanes: array<vec4<f32>, 4>,\n cascadeCount: f32,\n cascadeBlend: f32,\n depthBias: f32,\n normalBias: f32,\n directionalShadowFilter: vec4<f32>,\n spotLightViewProj: array<mat4x4<f32>, 4>,\n temporalCurrentViewProj: mat4x4<f32>,\n temporalPreviousViewProj: mat4x4<f32>,\n temporalProjection: vec4<f32>,\n temporalPreviousCameraPos: vec4<f32>,\n ssrParams: vec4<f32>,\n};\n\nstruct CloudViewParams {\n layer: vec4<f32>, // baseHeight, thickness, scale, density\n field: vec4<f32>, // coverage, timeSeconds, maxDistance, seed\n wind: vec4<f32>, // xyz m/s\n sunDirection: vec4<f32>,\n sunRadiance: vec4<f32>,\n integration: vec4<u32>, // cacheResolution, viewSteps, solarSteps, reserved\n shadowOrigin: vec4<f32>,\n shadowRight: vec4<f32>,\n shadowUp: vec4<f32>,\n shadowProjection: vec4<f32>, // range, valid, lowSun, texelSize\n temporal: vec4<f32>, // valid, reset, historyWeight, reserved\n};\n\n@group(0) @binding(0) var<uniform> view: CloudCameraView;\n@group(1) @binding(0) var sceneColor: texture_2d<f32>;\n@group(1) @binding(1) var sceneSampler: sampler;\n@group(1) @binding(2) var<uniform> cloud: CloudViewParams;\n@group(1) @binding(3) var sceneDepth: texture_depth_2d;\n@group(1) @binding(4) var depthSampler: sampler;\n@group(1) @binding(5) var previousRadiance: texture_2d<f32>;\n@group(1) @binding(6) var previousTransmittance: texture_2d<f32>;\n@group(1) @binding(7) var previousDepth: texture_2d<f32>;\n@group(1) @binding(8) var<storage, read> densityCache: array<u32>;\n\nstruct CloudViewOut {\n @builtin(position) position: vec4<f32>,\n @location(0) uv: vec2<f32>,\n};\n\nfn cloud_hash3(cell: vec3<i32>, seed: u32) -> f32 {\n var h = bitcast<u32>(cell.x) * 0x45d9f3bu;\n h = (h ^ (bitcast<u32>(cell.y) * 0x119de1f3u)) * 0x45d9f3bu;\n h = (h ^ (bitcast<u32>(cell.z) * 0x3449fu)) * 0x45d9f3bu;\n h = h ^ seed;\n h = (h ^ (h >> 16u)) * 0x45d9f3bu;\n h = (h ^ (h >> 13u)) * 0x27d4eb2du;\n return f32(h ^ (h >> 16u)) / 4294967296.0;\n}\n\nfn cloud_wrap_cell(value: i32, period: i32) -> i32 {\n let wrapped = value % period;\n return select(wrapped, wrapped + period, wrapped < 0);\n}\n\nfn cloud_hash3_periodic(\n cell: vec3<i32>,\n seed: u32,\n periodX: i32,\n periodY: i32,\n periodZ: i32,\n) -> f32 {\n return cloud_hash3(\n vec3<i32>(\n cloud_wrap_cell(cell.x, periodX),\n cloud_wrap_cell(cell.y, periodY),\n cloud_wrap_cell(cell.z, periodZ),\n ),\n seed,\n );\n}\n\nfn cloud_value_noise(\n position: vec3<f32>,\n seed: u32,\n periodX: i32,\n periodY: i32,\n periodZ: i32,\n) -> f32 {\n let cell = vec3<i32>(floor(position));\n let fraction = fract(position);\n let smoothFraction = fraction * fraction * (3.0 - 2.0 * fraction);\n let c000 = cloud_hash3_periodic(cell + vec3<i32>(0, 0, 0), seed, periodX, periodY, periodZ);\n let c100 = cloud_hash3_periodic(cell + vec3<i32>(1, 0, 0), seed, periodX, periodY, periodZ);\n let c010 = cloud_hash3_periodic(cell + vec3<i32>(0, 1, 0), seed, periodX, periodY, periodZ);\n let c110 = cloud_hash3_periodic(cell + vec3<i32>(1, 1, 0), seed, periodX, periodY, periodZ);\n let c001 = cloud_hash3_periodic(cell + vec3<i32>(0, 0, 1), seed, periodX, periodY, periodZ);\n let c101 = cloud_hash3_periodic(cell + vec3<i32>(1, 0, 1), seed, periodX, periodY, periodZ);\n let c011 = cloud_hash3_periodic(cell + vec3<i32>(0, 1, 1), seed, periodX, periodY, periodZ);\n let c111 = cloud_hash3_periodic(cell + vec3<i32>(1, 1, 1), seed, periodX, periodY, periodZ);\n let x00 = mix(c000, c100, smoothFraction.x);\n let x10 = mix(c010, c110, smoothFraction.x);\n let x01 = mix(c001, c101, smoothFraction.x);\n let x11 = mix(c011, c111, smoothFraction.x);\n return mix(mix(x00, x10, smoothFraction.y), mix(x01, x11, smoothFraction.y), smoothFraction.z);\n}\n\nfn cloud_cellular_noise(position: vec3<f32>, seed: u32, periodXZ: i32, periodY: i32) -> f32 {\n let cell = vec3<i32>(floor(position));\n let fraction = fract(position);\n var nearest = 1e9;\n var dz: i32 = -1;\n loop {\n var dy: i32 = -1;\n loop {\n var dx: i32 = -1;\n loop {\n let random = cloud_hash3_periodic(\n cell + vec3<i32>(dx, dy, dz),\n seed ^ 0x9e3779b9u,\n periodXZ,\n periodY,\n periodXZ,\n );\n let point = vec3<f32>(\n f32(dx) + fract(random * 17.0) - fraction.x,\n f32(dy) + fract(random * 31.0) - fraction.y,\n f32(dz) + fract(random * 47.0) - fraction.z,\n );\n nearest = min(nearest, dot(point, point));\n if (dx >= 1) { break; }\n dx = dx + 1;\n }\n if (dy >= 1) { break; }\n dy = dy + 1;\n }\n if (dz >= 1) { break; }\n dz = dz + 1;\n }\n return 1.0 - clamp(sqrt(nearest) * 1.25, 0.0, 1.0);\n}\n\nfn cloud_weather_field(position: vec3<f32>, seed: u32) -> f32 {\n return smoothstep(\n 0.34,\n 0.66,\n cloud_value_noise(\n vec3<f32>(position.x * 2.0, 0.37, position.z * 2.0),\n seed + 17041u,\n 2,\n 1,\n 2,\n ),\n );\n}\n\nfn cloud_formation_field(position: vec3<f32>, seed: u32) -> vec3<f32> {\n let macroWarpX = cloud_value_noise(\n vec3<f32>(position.x * 2.0, position.y * CLOUD_VERTICAL_NOISE_CELLS + 3.0, position.z * 2.0),\n seed + 41023u,\n 2,\n 2,\n 2,\n ) - 0.5;\n let macroWarpZ = cloud_value_noise(\n vec3<f32>(position.x * 2.0 + 5.0, position.y * CLOUD_VERTICAL_NOISE_CELLS - 7.0, position.z * 2.0),\n seed + 41023u,\n 2,\n 2,\n 2,\n ) - 0.5;\n let bodyX = position.x + macroWarpX * 0.3;\n let bodyZ = position.z + macroWarpZ * 0.3;\n var noise = 0.0;\n var weight = 0.0;\n var amplitude = 0.5;\n var frequency = 1.0;\n for (var octave = 0u; octave < 4u; octave = octave + 1u) {\n let tilePeriod = 4.0 * frequency;\n noise = noise + cloud_value_noise(\n vec3<f32>(bodyX * tilePeriod, position.y * frequency * CLOUD_VERTICAL_NOISE_CELLS, bodyZ * tilePeriod),\n seed + octave * 1013u,\n i32(tilePeriod),\n i32(frequency * CLOUD_VERTICAL_NOISE_CELLS),\n i32(tilePeriod),\n ) * amplitude;\n weight = weight + amplitude;\n amplitude = amplitude * 0.4;\n frequency = frequency * 2.0;\n }\n let broad = noise / max(0.0001, weight);\n let cells = cloud_cellular_noise(\n vec3<f32>(bodyX * 4.0, position.y * CLOUD_VERTICAL_NOISE_CELLS, bodyZ * 4.0),\n seed + 5011u,\n 4,\n i32(CLOUD_VERTICAL_NOISE_CELLS),\n );\n // Keep the broad field responsible for the cloud silhouette and use the\n // cellular term as bounded breakup. A broad-dominant mix reads as fewer,\n // wider formations instead of evenly spaced foam balls.\n let base = clamp(broad * 0.85 + cells * 0.15 + 0.15, 0.0, 1.0);\n let weather = cloud_weather_field(position, seed);\n let detailPosition = position * 3.0;\n let warpX = cloud_value_noise(\n vec3<f32>(detailPosition.x * 2.0, detailPosition.y * 2.0 + 11.0, detailPosition.z * 2.0),\n seed + 29011u,\n 2,\n 2,\n 2,\n ) - 0.5;\n let warpZ = cloud_value_noise(\n vec3<f32>(detailPosition.x * 2.0 + 7.0, detailPosition.y * 2.0 - 5.0, detailPosition.z * 2.0),\n seed + 29011u,\n 2,\n 2,\n 2,\n ) - 0.5;\n let erosion = cloud_cellular_noise(\n vec3<f32>(\n (detailPosition.x + warpX * 0.22) * 12.0,\n detailPosition.y * 3.0,\n (detailPosition.z + warpZ * 0.22) * 12.0,\n ),\n seed + 7919u,\n 12,\n 3,\n );\n return vec3<f32>(weather, base, erosion);\n}\n\nfn cloud_height_profile(height: f32, body: f32, weather: f32) -> f32 {\n // A body-dependent crown breaks the fixed horizontal slab silhouette while\n // keeping the profile smooth enough for the bounded ray step budget.\n let lowerEdge = 0.05 + (1.0 - body) * 0.1;\n let crownEdge = min(0.96, 0.6 + body * 0.3 + weather * 0.08);\n let lower = smoothstep(lowerEdge, min(1.0, lowerEdge + 0.12), height);\n let upper = 1.0 - smoothstep(crownEdge, min(1.0, crownEdge + 0.14), height);\n return lower * upper;\n}\n\nfn cloud_compose_density(height: f32, formation: vec3<f32>, coverage: f32) -> f32 {\n let threshold = 1.0 - coverage * (0.45 + formation.x * 0.55);\n let covered = smoothstep(0.0, 0.4, (formation.y - threshold) / max(0.001, 1.0 - threshold));\n let shaped = covered * cloud_height_profile(height, formation.y, formation.x);\n let erosion = (1.0 - formation.z) * 0.18;\n return clamp((shaped - erosion) / (1.0 - erosion), 0.0, 1.0);\n}\n\nfn cloud_analytic_density(position: vec3<f32>) -> f32 {\n let height = (position.y - cloud.layer.x) / max(0.000001, cloud.layer.y);\n if (height <= 0.0 || height >= 1.0 || cloud.layer.w <= 0.0 || cloud.field.x <= 0.0) { return 0.0; }\n let advected = position + cloud.wind.xyz * cloud.field.y;\n let advectedHeight = height +\n cloud.wind.y * cloud.field.y * cloud.layer.z / max(CLOUD_VERTICAL_CELLS, 0.000001);\n let p = vec3<f32>(\n advected.x * cloud.layer.z,\n fract(advectedHeight) * CLOUD_VERTICAL_CELLS,\n advected.z * cloud.layer.z,\n );\n let formation = cloud_formation_field(p, u32(round(cloud.field.w)));\n return cloud_compose_density(height, formation, cloud.field.x) * cloud.layer.w;\n}\n\nfn cloud_cache_byte(index: u32) -> f32 {\n let word = densityCache[index >> 2u];\n let shift = (index & 3u) * 8u;\n return f32((word >> shift) & 255u) / 255.0;\n}\n\nfn cloud_cache_component(plane: u32, index: u32, stride: u32) -> f32 {\n return cloud_cache_byte(plane * stride + index);\n}\n\nfn cloud_cache_trilinear(\n plane: u32,\n resolution: u32,\n x0: u32,\n x1: u32,\n y0: u32,\n y1: u32,\n z0: u32,\n z1: u32,\n fx: f32,\n fy: f32,\n fz: f32,\n) -> f32 {\n let stride = resolution * resolution * resolution;\n let layer = resolution * resolution;\n let c000 = cloud_cache_component(plane, z0 * layer + y0 * resolution + x0, stride);\n let c100 = cloud_cache_component(plane, z0 * layer + y0 * resolution + x1, stride);\n let c010 = cloud_cache_component(plane, z0 * layer + y1 * resolution + x0, stride);\n let c110 = cloud_cache_component(plane, z0 * layer + y1 * resolution + x1, stride);\n let c001 = cloud_cache_component(plane, z1 * layer + y0 * resolution + x0, stride);\n let c101 = cloud_cache_component(plane, z1 * layer + y0 * resolution + x1, stride);\n let c011 = cloud_cache_component(plane, z1 * layer + y1 * resolution + x0, stride);\n let c111 = cloud_cache_component(plane, z1 * layer + y1 * resolution + x1, stride);\n let x00 = mix(c000, c100, fx);\n let x10 = mix(c010, c110, fx);\n let x01 = mix(c001, c101, fx);\n let x11 = mix(c011, c111, fx);\n return mix(mix(x00, x10, fy), mix(x01, x11, fy), fz);\n}\n\nfn cloud_density(position: vec3<f32>) -> f32 {\n let height = (position.y - cloud.layer.x) / max(0.000001, cloud.layer.y);\n if (height <= 0.0 || height >= 1.0 || cloud.layer.w <= 0.0 || cloud.field.x <= 0.0) { return 0.0; }\n let resolution = cloud.integration.x;\n if (resolution < 4u) { return max(cloud_analytic_density(position), 0.0); }\n let periodCoordinate = fract((position.xz + cloud.wind.xz * cloud.field.y) * cloud.layer.z);\n let x = fract(periodCoordinate.x - 0.5 / f32(resolution)) * f32(resolution);\n let z = fract(periodCoordinate.y - 0.5 / f32(resolution)) * f32(resolution);\n let advectedHeight = height +\n cloud.wind.y * cloud.field.y * cloud.layer.z / max(CLOUD_VERTICAL_CELLS, 0.000001);\n let y = fract(advectedHeight - 0.5 / f32(resolution)) * f32(resolution);\n let x0 = u32(floor(x));\n let y0 = u32(floor(y));\n let z0 = u32(floor(z));\n let x1 = (x0 + 1u) % resolution;\n let y1 = (y0 + 1u) % resolution;\n let z1 = (z0 + 1u) % resolution;\n let fx = x - f32(x0);\n let fy = y - f32(y0);\n let fz = z - f32(z0);\n let weather = cloud_cache_trilinear(0u, resolution, x0, x1, y0, y1, z0, z1, fx, fy, fz);\n let body = cloud_cache_trilinear(1u, resolution, x0, x1, y0, y1, z0, z1, fx, fy, fz);\n // Repeat only the independent erosion plane: fine boundary structure with\n // the same eight packed reads and the same cache allocation.\n let detail = fract(vec3<f32>(periodCoordinate.x, advectedHeight, periodCoordinate.y) * 3.0 - vec3<f32>(0.5 / f32(resolution))) * f32(resolution);\n let d0 = vec3<u32>(floor(detail));\n let d1 = (d0 + vec3<u32>(1u)) % vec3<u32>(resolution);\n let df = fract(detail);\n let erosion = cloud_cache_trilinear(2u, resolution, d0.x, d1.x, d0.y, d1.y, d0.z, d1.z, df.x, df.y, df.z);\n return cloud_compose_density(height, vec3<f32>(weather, body, erosion), cloud.field.x) * cloud.layer.w;\n}\n\nfn cloud_layer_interval(origin: vec3<f32>, direction: vec3<f32>, maxDistance: f32) -> vec2<f32> {\n if (abs(direction.y) < 0.000001) {\n if (origin.y < cloud.layer.x || origin.y > cloud.layer.x + cloud.layer.y) {\n return vec2<f32>(1.0, 0.0);\n }\n return vec2<f32>(0.0, maxDistance);\n }\n let lower = (cloud.layer.x - origin.y) / direction.y;\n let upper = (cloud.layer.x + cloud.layer.y - origin.y) / direction.y;\n return vec2<f32>(\n max(0.0, min(lower, upper)),\n min(maxDistance, max(lower, upper)),\n );\n}\n\nfn cloud_solar_transmittance(position: vec3<f32>) -> f32 {\n let sunDirection = normalize(-view.lightDir);\n let solarDistance = select(cloud.field.z, cloud.shadowProjection.x, cloud.shadowProjection.x > 0.0);\n let interval = cloud_layer_interval(position, sunDirection, solarDistance);\n if (interval.y <= interval.x) { return 1.0; }\n let steps = min(cloud.integration.z, 64u);\n let stepLength = (interval.y - interval.x) / max(1.0, f32(steps));\n var opticalDepth = 0.0;\n let solarJitter = cloud_solar_jitter(position);\n for (var index = 0u; index < 64u; index = index + 1u) {\n if (index >= steps) { break; }\n let samplePosition = position + sunDirection * (interval.x + (f32(index) + solarJitter) * stepLength);\n opticalDepth = opticalDepth + cloud_density(samplePosition) * 0.03 * stepLength;\n }\n return exp(-max(opticalDepth, 0.0));\n}\n\nfn cloud_henyey_greenstein(cosTheta: f32, g: f32) -> f32 {\n let denominator = max(0.0001, 1.0 + g * g - 2.0 * g * cosTheta);\n return (1.0 - g * g) / (denominator * sqrt(denominator)) * 0.0795774715;\n}\n\nfn cloud_phase(cosTheta: f32) -> f32 {\n // A bounded dual lobe gives a readable forward highlight and a small\n // back-scatter rim without another solar march.\n return cloud_henyey_greenstein(cosTheta, 0.55) * 0.8 +\n cloud_henyey_greenstein(cosTheta, -0.2) * 0.2;\n}\n\n// Reuse the same remaining-path optical depth for two bounded scattering\n// orders. This adds arithmetic only; no extra density or light-ray samples.\nfn cloud_incident_light(solarT: f32, cosTheta: f32, height: f32) -> vec3<f32> {\n let direct = solarT * cloud_phase(cosTheta);\n let multiple = (0.5 * pow(solarT, 0.5) + 0.25 * pow(solarT, 0.25)) * 0.0795774715;\n let skyFill = mix(0.025, 0.075, smoothstep(0.1, 0.85, height));\n // Match volume-integrate: the shared DirectionalLight value uses the\n // unnormalized phase convention. Convert the normalized HG basis once.\n return cloud.sunRadiance.xyz *\n (vec3<f32>((direct + multiple) * 12.5663706144) + vec3<f32>(0.72, 0.84, 1.0) * skyFill);\n}\n\nfn cloud_ray_jitter(pixel: vec2<f32>) -> f32 {\n // A stable per-pixel offset removes view-step contour bands without adding\n // a frame-varying noise source that would fight temporal reprojection.\n return fract(sin(dot(pixel, vec2<f32>(12.9898, 78.233))) * 43758.5453);\n}\n\nfn cloud_solar_jitter(position: vec3<f32>) -> f32 {\n // Dither the light-column samples in world space so low shadow-step\n // profiles do not paint parallel contour bands across cloud bases.\n return fract(sin(dot(position.xz, vec2<f32>(19.193, 47.117))) * 15731.743);\n}\n\nfn reconstruct_world(uv: vec2<f32>, depth: f32) -> vec3<f32> {\n let ndc = vec4<f32>(uv.x * 2.0 - 1.0, 1.0 - uv.y * 2.0, depth, 1.0);\n let world = view.inverseViewProj * ndc;\n return world.xyz / max(abs(world.w), 0.00001);\n}\n\n@vertex\nfn vs_main(@builtin(vertex_index) index: u32) -> CloudViewOut {\n var positions = array<vec2<f32>, 3>(\n vec2<f32>(-1.0, -1.0),\n vec2<f32>(3.0, -1.0),\n vec2<f32>(-1.0, 3.0),\n );\n var output: CloudViewOut;\n output.position = vec4<f32>(positions[index], 0.0, 1.0);\n // WebGPU clip-space Y grows upward while sampled framebuffer rows and the\n // depth texture use a top-left origin. Keep this one top-left UV for color,\n // depth and inverse-projection reconstruction; flipping only the final\n // color would leave the camera ray and depth cut mirrored.\n output.uv = vec2<f32>(\n positions[index].x * 0.5 + 0.5,\n 1.0 - (positions[index].y * 0.5 + 0.5),\n );\n return output;\n}\n\n@fragment\nfn fs_main(input: CloudViewOut) -> @location(0) vec4<f32> {\n // The shadow producer uses this same production density/cache contract but\n // rasterizes a camera-independent, texel-snapped light-space map. It does\n // not consume scene colour; the generic fullscreen host still supplies the\n // input binding to keep the feature binding ABI single and validated.\n if (cloud.integration.w == 1u) {\n let projection = cloud.shadowProjection;\n if (projection.y < 0.5 || projection.z > 0.5 || projection.x <= 0.0) {\n return vec4<f32>(1.0);\n }\n let offset = (input.uv - vec2<f32>(0.5)) * projection.x;\n let receiver = cloud.shadowOrigin.xyz +\n cloud.shadowRight.xyz * offset.x + cloud.shadowUp.xyz * offset.y;\n let sunDirection = normalize(-view.lightDir);\n let interval = cloud_layer_interval(receiver, sunDirection, projection.x);\n if (interval.y <= interval.x) { return vec4<f32>(1.0); }\n let steps = min(cloud.integration.z, 64u);\n let stepLength = (interval.y - interval.x) / max(1.0, f32(steps));\n var opticalDepth = 0.0;\n var firstHeight = 1.0;\n var lastHeight = 0.0;\n var occupied = false;\n let solarJitter = cloud_solar_jitter(receiver);\n for (var index = 0u; index < 64u; index = index + 1u) {\n if (index >= steps) { break; }\n let samplePosition = receiver + sunDirection *\n (interval.x + (f32(index) + solarJitter) * stepLength);\n let localDensity = cloud_density(samplePosition);\n opticalDepth = opticalDepth + localDensity * 0.03 * stepLength;\n if (localDensity > 0.0001) {\n let sampleHeight = clamp(\n (samplePosition.y - cloud.layer.x) / max(0.0001, cloud.layer.y),\n 0.0,\n 1.0,\n );\n if (!occupied) { firstHeight = sampleHeight; }\n lastHeight = sampleHeight;\n occupied = true;\n }\n }\n let transmittance = exp(-max(opticalDepth, 0.0));\n // R stores full-column transmittance. G/B preserve the first/last\n // occupied normalized layer heights so interior samples can estimate the\n // remaining segment without pretending optical depth is uniform through\n // the whole layer. A stores the integrated optical depth and distinguishes\n // an empty projected column from a valid, nearly-clear one.\n return vec4<f32>(\n transmittance,\n select(1.0, firstHeight, occupied),\n select(0.0, lastHeight, occupied),\n select(0.0, opticalDepth, occupied),\n );\n }\n // The shadow producer branch above is per-fragment (its light-space\n // interval depends on the rasterized receiver), so derivative-based\n // sampling here would violate WGSL uniform-control-flow rules. The cloud\n // scene path already owns an explicit level-0 sampler contract.\n // Keep point semantics for the current full-resolution scene path. The\n // half-resolution transport contract is introduced with its own resolve\n // shader so this path retains the established depth convention.\n let depthPixel = vec2<i32>(input.position.xy);\n let depth = textureLoad(sceneDepth, depthPixel, 0);\n let farWorld = reconstruct_world(input.uv, 1.0);\n let sceneWorld = reconstruct_world(input.uv, clamp(depth, 0.0, 1.0));\n let ray = normalize(farWorld - view.cameraPos);\n let rayJitter = cloud_ray_jitter(input.position.xy);\n let sceneDistance = select(cloud.field.z, length(sceneWorld - view.cameraPos), depth < 0.999999);\n let interval = cloud_layer_interval(view.cameraPos, ray, sceneDistance);\n\n // The depth producer shares the same camera ray, interval and density\n // contract as the display path. It writes a cloud representative world\n // position and a validity bit so history remains comparable after camera\n // translation or rotation and never uses an opaque scene distance as a\n // proxy for a participating medium.\n if (cloud.integration.w == 2u) {\n if (interval.y <= interval.x) { return vec4<f32>(sceneWorld, 0.0); }\n let depthSteps = min(cloud.integration.y, 64u);\n let depthStepLength = (interval.y - interval.x) / max(1.0, f32(depthSteps));\n var depthTransmittance = 1.0;\n var weightedDistance = 0.0;\n var opticalWeight = 0.0;\n for (var depthIndex = 0u; depthIndex < 64u; depthIndex = depthIndex + 1u) {\n if (depthIndex >= depthSteps) { break; }\n let sampleDistance = interval.x + (f32(depthIndex) + rayJitter) * depthStepLength;\n let samplePosition = view.cameraPos + ray * sampleDistance;\n let localDensity = cloud_density(samplePosition);\n let localTransmittance = exp(-max(localDensity * 0.03 * depthStepLength, 0.0));\n let weight = depthTransmittance * (1.0 - localTransmittance);\n weightedDistance = weightedDistance + sampleDistance * weight;\n opticalWeight = opticalWeight + weight;\n depthTransmittance = depthTransmittance * localTransmittance;\n }\n let hasCloud = opticalWeight > 0.00001;\n let representativeDistance = select(sceneDistance, weightedDistance / opticalWeight, hasCloud);\n let representativeWorld = view.cameraPos + ray * representativeDistance;\n return vec4<f32>(representativeWorld, select(0.0, 1.0, hasCloud));\n }\n\n let scene = textureSampleLevel(sceneColor, sceneSampler, input.uv, 0.0);\n if (interval.y <= interval.x) { return scene; }\n let steps = min(cloud.integration.y, 64u);\n let stepLength = (interval.y - interval.x) / max(1.0, f32(steps));\n var transmittance = 1.0;\n var scattering = vec3<f32>(0.0);\n var weightedDistance = 0.0;\n var opticalWeight = 0.0;\n let sunDirection = normalize(-view.lightDir);\n for (var index = 0u; index < 64u; index = index + 1u) {\n if (index >= steps) { break; }\n let distance = interval.x + (f32(index) + rayJitter) * stepLength;\n let samplePosition = view.cameraPos + ray * distance;\n let localDensity = cloud_density(samplePosition);\n let extinction = localDensity * 0.03 * stepLength;\n let localTransmittance = exp(-max(extinction, 0.0));\n let depthWeight = transmittance * (1.0 - localTransmittance);\n weightedDistance = weightedDistance + distance * depthWeight;\n opticalWeight = opticalWeight + depthWeight;\n // Empty samples are common in the separated-cloud field. Avoid a nested\n // solar march for them and stop once the camera path is effectively opaque.\n if (localDensity > 0.0001 && transmittance > 0.01) {\n let solarTransmittance = cloud_solar_transmittance(samplePosition);\n let cloudHeight = clamp(\n (samplePosition.y - cloud.layer.x) / max(0.0001, cloud.layer.y), 0.0, 1.0,\n );\n scattering = scattering + cloud_incident_light(\n solarTransmittance, dot(ray, sunDirection), cloudHeight,\n ) * depthWeight;\n }\n transmittance = transmittance * localTransmittance;\n if (transmittance < 0.01) { break; }\n }\n let hasCloud = opticalWeight > 0.00001;\n let representativeDistance = select(sceneDistance, weightedDistance / opticalWeight, hasCloud);\n let representativeWorld = view.cameraPos + ray * representativeDistance;\n // Wind is an advective coordinate change. Reproject the cloud point into\n // the previous frame's world before applying the previous camera matrix;\n // authored field values remain immutable.\n let windDelta = cloud.wind.xyz * max(0.0, cloud.field.y - cloud.temporal.w);\n // cloud_density evaluates the authored field at position + wind*time.\n // Keeping the same field sample in the previous frame therefore advances\n // the previous world point by the elapsed wind delta as well.\n let previousCloudWorld = representativeWorld + windDelta;\n let previousCloudClip = view.temporalPreviousViewProj * vec4<f32>(previousCloudWorld, 1.0);\n let previousCloudNdc = previousCloudClip.xyz / max(abs(previousCloudClip.w), 0.00001);\n let previousCloudUv = vec2<f32>(\n previousCloudNdc.x * 0.5 + 0.5,\n 1.0 - (previousCloudNdc.y * 0.5 + 0.5),\n );\n let cloudInBounds = all(previousCloudUv >= vec2<f32>(0.0)) && all(previousCloudUv <= vec2<f32>(1.0));\n let cloudHistoryUv = clamp(previousCloudUv, vec2<f32>(0.0), vec2<f32>(1.0));\n let cloudHistoryDepthSample = textureSampleLevel(previousDepth, depthSampler, cloudHistoryUv, 0.0);\n let cloudHistoryWorld = cloudHistoryDepthSample.xyz;\n let previous = textureSampleLevel(previousRadiance, sceneSampler, cloudHistoryUv, 0.0);\n let previousTransport = textureSampleLevel(\n previousTransmittance,\n sceneSampler,\n cloudHistoryUv,\n 0.0,\n );\n let worldTolerance = max(0.05, length(previousCloudWorld - view.cameraPos) * 0.02);\n let depthAccepted = cloudHistoryDepthSample.a > 0.5 &&\n distance(cloudHistoryWorld, previousCloudWorld) <= worldTolerance;\n let accepted =\n hasCloud && cloud.temporal.x > 0.5 && cloud.temporal.y < 0.5 && cloudInBounds &&\n depthAccepted && previousTransport.a > 0.0;\n let historyWeight = select(0.0, clamp(cloud.temporal.z, 0.0, 0.95), accepted);\n let cloudRadiance = mix(scattering, previous.rgb, historyWeight);\n // Preserve scene-linear HDR output for the downstream material lane. The\n // history owner receives cloudRadiance separately through the history pass.\n return vec4<f32>(scene.rgb * transmittance + cloudRadiance, transmittance);\n}\n\n// The light-space shadow producer already paid for a bounded column integral.\n// Reuse that texel for camera samples instead of starting a full solar march\n// for every occupied camera step. Outside the occupied interval the cached\n// full-column transmittance is exact for the projected column. Inside the\n// interval, the cache supplies the stable projection/bounds and a short\n// residual march resolves the non-uniform density that a linear optical-depth\n// interpolation would lose. Invalid/out-of-range projections retain the\n// analytic path as an explicit fallback.\nfn cloud_solar_cached_transmittance(position: vec3<f32>) -> f32 {\n let projection = cloud.shadowProjection;\n if (projection.y < 0.5 || projection.x <= 0.0) {\n return cloud_solar_transmittance(position);\n }\n let relative = position - cloud.shadowOrigin.xyz;\n let uv = vec2<f32>(\n dot(relative, cloud.shadowRight.xyz) / projection.x + 0.5,\n dot(relative, cloud.shadowUp.xyz) / projection.x + 0.5,\n );\n if (any(uv < vec2<f32>(0.0)) || any(uv > vec2<f32>(1.0))) {\n return cloud_solar_transmittance(position);\n }\n let cachedShadow = textureSampleLevel(previousRadiance, sceneSampler, uv, 0.0);\n let cachedOpticalDepth = max(cachedShadow.a, 0.0);\n let layerHeight = clamp(\n (position.y - cloud.layer.x) / max(0.0001, cloud.layer.y),\n 0.0,\n 1.0,\n );\n if (cachedOpticalDepth <= 0.0001) { return 1.0; }\n let firstHeight = clamp(cachedShadow.g, 0.0, 1.0);\n let lastHeight = clamp(cachedShadow.b, 0.0, 1.0);\n let occupiedMin = min(firstHeight, lastHeight);\n let occupiedMax = max(firstHeight, lastHeight);\n let sunDirection = normalize(-view.lightDir);\n // G/B are ordered along the light ray, not necessarily bottom-to-top. Only\n // use the cached full/empty fast paths when the light has enough vertical\n // component to make that ordering meaningful; grazing light stays on the\n // residual path so a low-sun column cannot invert the cache bounds.\n if (abs(sunDirection.y) > 0.2) {\n if (sunDirection.y > 0.0) {\n if (layerHeight <= occupiedMin) { return exp(-cachedOpticalDepth); }\n if (layerHeight >= occupiedMax) { return 1.0; }\n } else {\n if (layerHeight >= occupiedMax) { return exp(-cachedOpticalDepth); }\n if (layerHeight <= occupiedMin) { return 1.0; }\n }\n }\n\n // A short residual march is only paid for samples inside the projected\n // cloud span. Vertical light uses twelve taps, oblique light sixteen, and\n // grazing light up to thirty-two because the same layer projects across a\n // much longer solar segment. This removes the large error caused by\n // assuming optical depth is uniform between the first and last occupied\n // heights while keeping the normal camera path far below a second full\n // solar integration.\n let solarDistance = select(cloud.field.z, projection.x, projection.x > 0.0);\n let interval = cloud_layer_interval(position, sunDirection, solarDistance);\n if (interval.y <= interval.x) { return 1.0; }\n let absLightY = abs(sunDirection.y);\n let directionBudget = select(\n 32u,\n select(16u, 12u, absLightY > 0.5),\n absLightY > 0.2,\n );\n let residualSteps = min(directionBudget, max(8u, cloud.integration.z * 3u));\n let stepLength = (interval.y - interval.x) / max(1.0, f32(residualSteps));\n var opticalDepth = 0.0;\n let solarJitter = cloud_solar_jitter(position);\n for (var index = 0u; index < 32u; index = index + 1u) {\n if (index >= residualSteps) { break; }\n let samplePosition = position + sunDirection *\n (interval.x + (f32(index) + solarJitter) * stepLength);\n opticalDepth = opticalDepth + cloud_density(samplePosition) *\n 0.03 * stepLength;\n }\n return exp(-max(opticalDepth, 0.0));\n}\n\nstruct CloudTransportOutput {\n @location(0) radiance: vec4<f32>,\n @location(1) transmittance: vec4<f32>,\n @location(2) depth: vec4<f32>,\n};\n\n// One half-resolution transport pass writes all three reusable cloud fields.\n// The full-resolution resolve below reconstructs colour and applies temporal\n// history; no second full raymarch exists solely to obtain cloud depth.\n@fragment\nfn fs_transport(input: CloudViewOut) -> CloudTransportOutput {\n let depth = textureSampleLevel(sceneDepth, depthSampler, input.uv, 0u);\n let farWorld = reconstruct_world(input.uv, 1.0);\n let sceneWorld = reconstruct_world(input.uv, clamp(depth, 0.0, 1.0));\n let ray = normalize(farWorld - view.cameraPos);\n let rayJitter = cloud_ray_jitter(input.position.xy);\n let sceneDistance = select(cloud.field.z, length(sceneWorld - view.cameraPos), depth < 0.999999);\n let interval = cloud_layer_interval(view.cameraPos, ray, sceneDistance);\n var output: CloudTransportOutput;\n output.radiance = vec4<f32>(0.0, 0.0, 0.0, 0.0);\n output.transmittance = vec4<f32>(1.0, 1.0, 1.0, 1.0);\n output.depth = vec4<f32>(sceneWorld, 0.0);\n if (interval.y <= interval.x) { return output; }\n\n let steps = min(cloud.integration.y, 64u);\n let stepLength = (interval.y - interval.x) / max(1.0, f32(steps));\n var transmittance = 1.0;\n var scattering = vec3<f32>(0.0);\n var weightedDistance = 0.0;\n var opticalWeight = 0.0;\n let sunDirection = normalize(-view.lightDir);\n for (var index = 0u; index < 64u; index = index + 1u) {\n if (index >= steps) { break; }\n let distance = interval.x + (f32(index) + rayJitter) * stepLength;\n let samplePosition = view.cameraPos + ray * distance;\n let localDensity = cloud_density(samplePosition);\n let extinction = localDensity * 0.03 * stepLength;\n let localTransmittance = exp(-max(extinction, 0.0));\n let depthWeight = transmittance * (1.0 - localTransmittance);\n weightedDistance = weightedDistance + distance * depthWeight;\n opticalWeight = opticalWeight + depthWeight;\n if (localDensity > 0.0001 && transmittance > 0.01) {\n let solarTransmittance = cloud_solar_cached_transmittance(samplePosition);\n let cloudHeight = clamp(\n (samplePosition.y - cloud.layer.x) / max(0.0001, cloud.layer.y), 0.0, 1.0,\n );\n scattering = scattering + cloud_incident_light(\n solarTransmittance, dot(ray, sunDirection), cloudHeight,\n ) * depthWeight;\n }\n transmittance = transmittance * localTransmittance;\n if (transmittance < 0.01) { break; }\n }\n let hasCloud = opticalWeight > 0.00001;\n let representativeDistance = select(sceneDistance, weightedDistance / max(opticalWeight, 0.00001), hasCloud);\n output.radiance = vec4<f32>(scattering, select(0.0, 1.0, hasCloud));\n output.transmittance = vec4<f32>(transmittance, transmittance, transmittance, 1.0);\n output.depth = vec4<f32>(view.cameraPos + ray * representativeDistance, select(0.0, 1.0, hasCloud));\n return output;\n}\n";
23
+ /** Shared cloud transport module; `fs_transport` is selected by the MRT PSO. */
24
+ export declare const CLOUD_TRANSPORT_FULLSCREEN_WGSL = "\nconst CLOUD_VERTICAL_CELLS: f32 = 1;\nconst CLOUD_VERTICAL_NOISE_CELLS: f32 = 2.0;\n\nstruct CloudCameraView {\n worldViewProj: mat4x4<f32>,\n lightDir: vec3<f32>,\n lightColor: vec3<f32>,\n cameraPos: vec3<f32>,\n lightViewProj_A: mat4x4<f32>,\n inverseViewProj: mat4x4<f32>,\n lightViewProj_B: mat4x4<f32>,\n lightViewProj_C: mat4x4<f32>,\n lightViewProj_D: mat4x4<f32>,\n splitPlanes: array<vec4<f32>, 4>,\n cascadeCount: f32,\n cascadeBlend: f32,\n depthBias: f32,\n normalBias: f32,\n directionalShadowFilter: vec4<f32>,\n spotLightViewProj: array<mat4x4<f32>, 4>,\n temporalCurrentViewProj: mat4x4<f32>,\n temporalPreviousViewProj: mat4x4<f32>,\n temporalProjection: vec4<f32>,\n temporalPreviousCameraPos: vec4<f32>,\n ssrParams: vec4<f32>,\n};\n\nstruct CloudViewParams {\n layer: vec4<f32>, // baseHeight, thickness, scale, density\n field: vec4<f32>, // coverage, timeSeconds, maxDistance, seed\n wind: vec4<f32>, // xyz m/s\n sunDirection: vec4<f32>,\n sunRadiance: vec4<f32>,\n integration: vec4<u32>, // cacheResolution, viewSteps, solarSteps, reserved\n shadowOrigin: vec4<f32>,\n shadowRight: vec4<f32>,\n shadowUp: vec4<f32>,\n shadowProjection: vec4<f32>, // range, valid, lowSun, texelSize\n temporal: vec4<f32>, // valid, reset, historyWeight, reserved\n};\n\n@group(0) @binding(0) var<uniform> view: CloudCameraView;\n@group(1) @binding(0) var sceneColor: texture_2d<f32>;\n@group(1) @binding(1) var sceneSampler: sampler;\n@group(1) @binding(2) var<uniform> cloud: CloudViewParams;\n@group(1) @binding(3) var sceneDepth: texture_depth_2d;\n@group(1) @binding(4) var depthSampler: sampler;\n@group(1) @binding(5) var previousRadiance: texture_2d<f32>;\n@group(1) @binding(6) var previousTransmittance: texture_2d<f32>;\n@group(1) @binding(7) var previousDepth: texture_2d<f32>;\n@group(1) @binding(8) var<storage, read> densityCache: array<u32>;\n\nstruct CloudViewOut {\n @builtin(position) position: vec4<f32>,\n @location(0) uv: vec2<f32>,\n};\n\nfn cloud_hash3(cell: vec3<i32>, seed: u32) -> f32 {\n var h = bitcast<u32>(cell.x) * 0x45d9f3bu;\n h = (h ^ (bitcast<u32>(cell.y) * 0x119de1f3u)) * 0x45d9f3bu;\n h = (h ^ (bitcast<u32>(cell.z) * 0x3449fu)) * 0x45d9f3bu;\n h = h ^ seed;\n h = (h ^ (h >> 16u)) * 0x45d9f3bu;\n h = (h ^ (h >> 13u)) * 0x27d4eb2du;\n return f32(h ^ (h >> 16u)) / 4294967296.0;\n}\n\nfn cloud_wrap_cell(value: i32, period: i32) -> i32 {\n let wrapped = value % period;\n return select(wrapped, wrapped + period, wrapped < 0);\n}\n\nfn cloud_hash3_periodic(\n cell: vec3<i32>,\n seed: u32,\n periodX: i32,\n periodY: i32,\n periodZ: i32,\n) -> f32 {\n return cloud_hash3(\n vec3<i32>(\n cloud_wrap_cell(cell.x, periodX),\n cloud_wrap_cell(cell.y, periodY),\n cloud_wrap_cell(cell.z, periodZ),\n ),\n seed,\n );\n}\n\nfn cloud_value_noise(\n position: vec3<f32>,\n seed: u32,\n periodX: i32,\n periodY: i32,\n periodZ: i32,\n) -> f32 {\n let cell = vec3<i32>(floor(position));\n let fraction = fract(position);\n let smoothFraction = fraction * fraction * (3.0 - 2.0 * fraction);\n let c000 = cloud_hash3_periodic(cell + vec3<i32>(0, 0, 0), seed, periodX, periodY, periodZ);\n let c100 = cloud_hash3_periodic(cell + vec3<i32>(1, 0, 0), seed, periodX, periodY, periodZ);\n let c010 = cloud_hash3_periodic(cell + vec3<i32>(0, 1, 0), seed, periodX, periodY, periodZ);\n let c110 = cloud_hash3_periodic(cell + vec3<i32>(1, 1, 0), seed, periodX, periodY, periodZ);\n let c001 = cloud_hash3_periodic(cell + vec3<i32>(0, 0, 1), seed, periodX, periodY, periodZ);\n let c101 = cloud_hash3_periodic(cell + vec3<i32>(1, 0, 1), seed, periodX, periodY, periodZ);\n let c011 = cloud_hash3_periodic(cell + vec3<i32>(0, 1, 1), seed, periodX, periodY, periodZ);\n let c111 = cloud_hash3_periodic(cell + vec3<i32>(1, 1, 1), seed, periodX, periodY, periodZ);\n let x00 = mix(c000, c100, smoothFraction.x);\n let x10 = mix(c010, c110, smoothFraction.x);\n let x01 = mix(c001, c101, smoothFraction.x);\n let x11 = mix(c011, c111, smoothFraction.x);\n return mix(mix(x00, x10, smoothFraction.y), mix(x01, x11, smoothFraction.y), smoothFraction.z);\n}\n\nfn cloud_cellular_noise(position: vec3<f32>, seed: u32, periodXZ: i32, periodY: i32) -> f32 {\n let cell = vec3<i32>(floor(position));\n let fraction = fract(position);\n var nearest = 1e9;\n var dz: i32 = -1;\n loop {\n var dy: i32 = -1;\n loop {\n var dx: i32 = -1;\n loop {\n let random = cloud_hash3_periodic(\n cell + vec3<i32>(dx, dy, dz),\n seed ^ 0x9e3779b9u,\n periodXZ,\n periodY,\n periodXZ,\n );\n let point = vec3<f32>(\n f32(dx) + fract(random * 17.0) - fraction.x,\n f32(dy) + fract(random * 31.0) - fraction.y,\n f32(dz) + fract(random * 47.0) - fraction.z,\n );\n nearest = min(nearest, dot(point, point));\n if (dx >= 1) { break; }\n dx = dx + 1;\n }\n if (dy >= 1) { break; }\n dy = dy + 1;\n }\n if (dz >= 1) { break; }\n dz = dz + 1;\n }\n return 1.0 - clamp(sqrt(nearest) * 1.25, 0.0, 1.0);\n}\n\nfn cloud_weather_field(position: vec3<f32>, seed: u32) -> f32 {\n return smoothstep(\n 0.34,\n 0.66,\n cloud_value_noise(\n vec3<f32>(position.x * 2.0, 0.37, position.z * 2.0),\n seed + 17041u,\n 2,\n 1,\n 2,\n ),\n );\n}\n\nfn cloud_formation_field(position: vec3<f32>, seed: u32) -> vec3<f32> {\n let macroWarpX = cloud_value_noise(\n vec3<f32>(position.x * 2.0, position.y * CLOUD_VERTICAL_NOISE_CELLS + 3.0, position.z * 2.0),\n seed + 41023u,\n 2,\n 2,\n 2,\n ) - 0.5;\n let macroWarpZ = cloud_value_noise(\n vec3<f32>(position.x * 2.0 + 5.0, position.y * CLOUD_VERTICAL_NOISE_CELLS - 7.0, position.z * 2.0),\n seed + 41023u,\n 2,\n 2,\n 2,\n ) - 0.5;\n let bodyX = position.x + macroWarpX * 0.3;\n let bodyZ = position.z + macroWarpZ * 0.3;\n var noise = 0.0;\n var weight = 0.0;\n var amplitude = 0.5;\n var frequency = 1.0;\n for (var octave = 0u; octave < 4u; octave = octave + 1u) {\n let tilePeriod = 4.0 * frequency;\n noise = noise + cloud_value_noise(\n vec3<f32>(bodyX * tilePeriod, position.y * frequency * CLOUD_VERTICAL_NOISE_CELLS, bodyZ * tilePeriod),\n seed + octave * 1013u,\n i32(tilePeriod),\n i32(frequency * CLOUD_VERTICAL_NOISE_CELLS),\n i32(tilePeriod),\n ) * amplitude;\n weight = weight + amplitude;\n amplitude = amplitude * 0.4;\n frequency = frequency * 2.0;\n }\n let broad = noise / max(0.0001, weight);\n let cells = cloud_cellular_noise(\n vec3<f32>(bodyX * 4.0, position.y * CLOUD_VERTICAL_NOISE_CELLS, bodyZ * 4.0),\n seed + 5011u,\n 4,\n i32(CLOUD_VERTICAL_NOISE_CELLS),\n );\n // Keep the broad field responsible for the cloud silhouette and use the\n // cellular term as bounded breakup. A broad-dominant mix reads as fewer,\n // wider formations instead of evenly spaced foam balls.\n let base = clamp(broad * 0.85 + cells * 0.15 + 0.15, 0.0, 1.0);\n let weather = cloud_weather_field(position, seed);\n let detailPosition = position * 3.0;\n let warpX = cloud_value_noise(\n vec3<f32>(detailPosition.x * 2.0, detailPosition.y * 2.0 + 11.0, detailPosition.z * 2.0),\n seed + 29011u,\n 2,\n 2,\n 2,\n ) - 0.5;\n let warpZ = cloud_value_noise(\n vec3<f32>(detailPosition.x * 2.0 + 7.0, detailPosition.y * 2.0 - 5.0, detailPosition.z * 2.0),\n seed + 29011u,\n 2,\n 2,\n 2,\n ) - 0.5;\n let erosion = cloud_cellular_noise(\n vec3<f32>(\n (detailPosition.x + warpX * 0.22) * 12.0,\n detailPosition.y * 3.0,\n (detailPosition.z + warpZ * 0.22) * 12.0,\n ),\n seed + 7919u,\n 12,\n 3,\n );\n return vec3<f32>(weather, base, erosion);\n}\n\nfn cloud_height_profile(height: f32, body: f32, weather: f32) -> f32 {\n // A body-dependent crown breaks the fixed horizontal slab silhouette while\n // keeping the profile smooth enough for the bounded ray step budget.\n let lowerEdge = 0.05 + (1.0 - body) * 0.1;\n let crownEdge = min(0.96, 0.6 + body * 0.3 + weather * 0.08);\n let lower = smoothstep(lowerEdge, min(1.0, lowerEdge + 0.12), height);\n let upper = 1.0 - smoothstep(crownEdge, min(1.0, crownEdge + 0.14), height);\n return lower * upper;\n}\n\nfn cloud_compose_density(height: f32, formation: vec3<f32>, coverage: f32) -> f32 {\n let threshold = 1.0 - coverage * (0.45 + formation.x * 0.55);\n let covered = smoothstep(0.0, 0.4, (formation.y - threshold) / max(0.001, 1.0 - threshold));\n let shaped = covered * cloud_height_profile(height, formation.y, formation.x);\n let erosion = (1.0 - formation.z) * 0.18;\n return clamp((shaped - erosion) / (1.0 - erosion), 0.0, 1.0);\n}\n\nfn cloud_analytic_density(position: vec3<f32>) -> f32 {\n let height = (position.y - cloud.layer.x) / max(0.000001, cloud.layer.y);\n if (height <= 0.0 || height >= 1.0 || cloud.layer.w <= 0.0 || cloud.field.x <= 0.0) { return 0.0; }\n let advected = position + cloud.wind.xyz * cloud.field.y;\n let advectedHeight = height +\n cloud.wind.y * cloud.field.y * cloud.layer.z / max(CLOUD_VERTICAL_CELLS, 0.000001);\n let p = vec3<f32>(\n advected.x * cloud.layer.z,\n fract(advectedHeight) * CLOUD_VERTICAL_CELLS,\n advected.z * cloud.layer.z,\n );\n let formation = cloud_formation_field(p, u32(round(cloud.field.w)));\n return cloud_compose_density(height, formation, cloud.field.x) * cloud.layer.w;\n}\n\nfn cloud_cache_byte(index: u32) -> f32 {\n let word = densityCache[index >> 2u];\n let shift = (index & 3u) * 8u;\n return f32((word >> shift) & 255u) / 255.0;\n}\n\nfn cloud_cache_component(plane: u32, index: u32, stride: u32) -> f32 {\n return cloud_cache_byte(plane * stride + index);\n}\n\nfn cloud_cache_trilinear(\n plane: u32,\n resolution: u32,\n x0: u32,\n x1: u32,\n y0: u32,\n y1: u32,\n z0: u32,\n z1: u32,\n fx: f32,\n fy: f32,\n fz: f32,\n) -> f32 {\n let stride = resolution * resolution * resolution;\n let layer = resolution * resolution;\n let c000 = cloud_cache_component(plane, z0 * layer + y0 * resolution + x0, stride);\n let c100 = cloud_cache_component(plane, z0 * layer + y0 * resolution + x1, stride);\n let c010 = cloud_cache_component(plane, z0 * layer + y1 * resolution + x0, stride);\n let c110 = cloud_cache_component(plane, z0 * layer + y1 * resolution + x1, stride);\n let c001 = cloud_cache_component(plane, z1 * layer + y0 * resolution + x0, stride);\n let c101 = cloud_cache_component(plane, z1 * layer + y0 * resolution + x1, stride);\n let c011 = cloud_cache_component(plane, z1 * layer + y1 * resolution + x0, stride);\n let c111 = cloud_cache_component(plane, z1 * layer + y1 * resolution + x1, stride);\n let x00 = mix(c000, c100, fx);\n let x10 = mix(c010, c110, fx);\n let x01 = mix(c001, c101, fx);\n let x11 = mix(c011, c111, fx);\n return mix(mix(x00, x10, fy), mix(x01, x11, fy), fz);\n}\n\nfn cloud_density(position: vec3<f32>) -> f32 {\n let height = (position.y - cloud.layer.x) / max(0.000001, cloud.layer.y);\n if (height <= 0.0 || height >= 1.0 || cloud.layer.w <= 0.0 || cloud.field.x <= 0.0) { return 0.0; }\n let resolution = cloud.integration.x;\n if (resolution < 4u) { return max(cloud_analytic_density(position), 0.0); }\n let periodCoordinate = fract((position.xz + cloud.wind.xz * cloud.field.y) * cloud.layer.z);\n let x = fract(periodCoordinate.x - 0.5 / f32(resolution)) * f32(resolution);\n let z = fract(periodCoordinate.y - 0.5 / f32(resolution)) * f32(resolution);\n let advectedHeight = height +\n cloud.wind.y * cloud.field.y * cloud.layer.z / max(CLOUD_VERTICAL_CELLS, 0.000001);\n let y = fract(advectedHeight - 0.5 / f32(resolution)) * f32(resolution);\n let x0 = u32(floor(x));\n let y0 = u32(floor(y));\n let z0 = u32(floor(z));\n let x1 = (x0 + 1u) % resolution;\n let y1 = (y0 + 1u) % resolution;\n let z1 = (z0 + 1u) % resolution;\n let fx = x - f32(x0);\n let fy = y - f32(y0);\n let fz = z - f32(z0);\n let weather = cloud_cache_trilinear(0u, resolution, x0, x1, y0, y1, z0, z1, fx, fy, fz);\n let body = cloud_cache_trilinear(1u, resolution, x0, x1, y0, y1, z0, z1, fx, fy, fz);\n // Repeat only the independent erosion plane: fine boundary structure with\n // the same eight packed reads and the same cache allocation.\n let detail = fract(vec3<f32>(periodCoordinate.x, advectedHeight, periodCoordinate.y) * 3.0 - vec3<f32>(0.5 / f32(resolution))) * f32(resolution);\n let d0 = vec3<u32>(floor(detail));\n let d1 = (d0 + vec3<u32>(1u)) % vec3<u32>(resolution);\n let df = fract(detail);\n let erosion = cloud_cache_trilinear(2u, resolution, d0.x, d1.x, d0.y, d1.y, d0.z, d1.z, df.x, df.y, df.z);\n return cloud_compose_density(height, vec3<f32>(weather, body, erosion), cloud.field.x) * cloud.layer.w;\n}\n\nfn cloud_layer_interval(origin: vec3<f32>, direction: vec3<f32>, maxDistance: f32) -> vec2<f32> {\n if (abs(direction.y) < 0.000001) {\n if (origin.y < cloud.layer.x || origin.y > cloud.layer.x + cloud.layer.y) {\n return vec2<f32>(1.0, 0.0);\n }\n return vec2<f32>(0.0, maxDistance);\n }\n let lower = (cloud.layer.x - origin.y) / direction.y;\n let upper = (cloud.layer.x + cloud.layer.y - origin.y) / direction.y;\n return vec2<f32>(\n max(0.0, min(lower, upper)),\n min(maxDistance, max(lower, upper)),\n );\n}\n\nfn cloud_solar_transmittance(position: vec3<f32>) -> f32 {\n let sunDirection = normalize(-view.lightDir);\n let solarDistance = select(cloud.field.z, cloud.shadowProjection.x, cloud.shadowProjection.x > 0.0);\n let interval = cloud_layer_interval(position, sunDirection, solarDistance);\n if (interval.y <= interval.x) { return 1.0; }\n let steps = min(cloud.integration.z, 64u);\n let stepLength = (interval.y - interval.x) / max(1.0, f32(steps));\n var opticalDepth = 0.0;\n let solarJitter = cloud_solar_jitter(position);\n for (var index = 0u; index < 64u; index = index + 1u) {\n if (index >= steps) { break; }\n let samplePosition = position + sunDirection * (interval.x + (f32(index) + solarJitter) * stepLength);\n opticalDepth = opticalDepth + cloud_density(samplePosition) * 0.03 * stepLength;\n }\n return exp(-max(opticalDepth, 0.0));\n}\n\nfn cloud_henyey_greenstein(cosTheta: f32, g: f32) -> f32 {\n let denominator = max(0.0001, 1.0 + g * g - 2.0 * g * cosTheta);\n return (1.0 - g * g) / (denominator * sqrt(denominator)) * 0.0795774715;\n}\n\nfn cloud_phase(cosTheta: f32) -> f32 {\n // A bounded dual lobe gives a readable forward highlight and a small\n // back-scatter rim without another solar march.\n return cloud_henyey_greenstein(cosTheta, 0.55) * 0.8 +\n cloud_henyey_greenstein(cosTheta, -0.2) * 0.2;\n}\n\n// Reuse the same remaining-path optical depth for two bounded scattering\n// orders. This adds arithmetic only; no extra density or light-ray samples.\nfn cloud_incident_light(solarT: f32, cosTheta: f32, height: f32) -> vec3<f32> {\n let direct = solarT * cloud_phase(cosTheta);\n let multiple = (0.5 * pow(solarT, 0.5) + 0.25 * pow(solarT, 0.25)) * 0.0795774715;\n let skyFill = mix(0.025, 0.075, smoothstep(0.1, 0.85, height));\n // Match volume-integrate: the shared DirectionalLight value uses the\n // unnormalized phase convention. Convert the normalized HG basis once.\n return cloud.sunRadiance.xyz *\n (vec3<f32>((direct + multiple) * 12.5663706144) + vec3<f32>(0.72, 0.84, 1.0) * skyFill);\n}\n\nfn cloud_ray_jitter(pixel: vec2<f32>) -> f32 {\n // A stable per-pixel offset removes view-step contour bands without adding\n // a frame-varying noise source that would fight temporal reprojection.\n return fract(sin(dot(pixel, vec2<f32>(12.9898, 78.233))) * 43758.5453);\n}\n\nfn cloud_solar_jitter(position: vec3<f32>) -> f32 {\n // Dither the light-column samples in world space so low shadow-step\n // profiles do not paint parallel contour bands across cloud bases.\n return fract(sin(dot(position.xz, vec2<f32>(19.193, 47.117))) * 15731.743);\n}\n\nfn reconstruct_world(uv: vec2<f32>, depth: f32) -> vec3<f32> {\n let ndc = vec4<f32>(uv.x * 2.0 - 1.0, 1.0 - uv.y * 2.0, depth, 1.0);\n let world = view.inverseViewProj * ndc;\n return world.xyz / max(abs(world.w), 0.00001);\n}\n\n@vertex\nfn vs_main(@builtin(vertex_index) index: u32) -> CloudViewOut {\n var positions = array<vec2<f32>, 3>(\n vec2<f32>(-1.0, -1.0),\n vec2<f32>(3.0, -1.0),\n vec2<f32>(-1.0, 3.0),\n );\n var output: CloudViewOut;\n output.position = vec4<f32>(positions[index], 0.0, 1.0);\n // WebGPU clip-space Y grows upward while sampled framebuffer rows and the\n // depth texture use a top-left origin. Keep this one top-left UV for color,\n // depth and inverse-projection reconstruction; flipping only the final\n // color would leave the camera ray and depth cut mirrored.\n output.uv = vec2<f32>(\n positions[index].x * 0.5 + 0.5,\n 1.0 - (positions[index].y * 0.5 + 0.5),\n );\n return output;\n}\n\n@fragment\nfn fs_main(input: CloudViewOut) -> @location(0) vec4<f32> {\n // The shadow producer uses this same production density/cache contract but\n // rasterizes a camera-independent, texel-snapped light-space map. It does\n // not consume scene colour; the generic fullscreen host still supplies the\n // input binding to keep the feature binding ABI single and validated.\n if (cloud.integration.w == 1u) {\n let projection = cloud.shadowProjection;\n if (projection.y < 0.5 || projection.z > 0.5 || projection.x <= 0.0) {\n return vec4<f32>(1.0);\n }\n let offset = (input.uv - vec2<f32>(0.5)) * projection.x;\n let receiver = cloud.shadowOrigin.xyz +\n cloud.shadowRight.xyz * offset.x + cloud.shadowUp.xyz * offset.y;\n let sunDirection = normalize(-view.lightDir);\n let interval = cloud_layer_interval(receiver, sunDirection, projection.x);\n if (interval.y <= interval.x) { return vec4<f32>(1.0); }\n let steps = min(cloud.integration.z, 64u);\n let stepLength = (interval.y - interval.x) / max(1.0, f32(steps));\n var opticalDepth = 0.0;\n var firstHeight = 1.0;\n var lastHeight = 0.0;\n var occupied = false;\n let solarJitter = cloud_solar_jitter(receiver);\n for (var index = 0u; index < 64u; index = index + 1u) {\n if (index >= steps) { break; }\n let samplePosition = receiver + sunDirection *\n (interval.x + (f32(index) + solarJitter) * stepLength);\n let localDensity = cloud_density(samplePosition);\n opticalDepth = opticalDepth + localDensity * 0.03 * stepLength;\n if (localDensity > 0.0001) {\n let sampleHeight = clamp(\n (samplePosition.y - cloud.layer.x) / max(0.0001, cloud.layer.y),\n 0.0,\n 1.0,\n );\n if (!occupied) { firstHeight = sampleHeight; }\n lastHeight = sampleHeight;\n occupied = true;\n }\n }\n let transmittance = exp(-max(opticalDepth, 0.0));\n // R stores full-column transmittance. G/B preserve the first/last\n // occupied normalized layer heights so interior samples can estimate the\n // remaining segment without pretending optical depth is uniform through\n // the whole layer. A stores the integrated optical depth and distinguishes\n // an empty projected column from a valid, nearly-clear one.\n return vec4<f32>(\n transmittance,\n select(1.0, firstHeight, occupied),\n select(0.0, lastHeight, occupied),\n select(0.0, opticalDepth, occupied),\n );\n }\n // The shadow producer branch above is per-fragment (its light-space\n // interval depends on the rasterized receiver), so derivative-based\n // sampling here would violate WGSL uniform-control-flow rules. The cloud\n // scene path already owns an explicit level-0 sampler contract.\n // Keep point semantics for the current full-resolution scene path. The\n // half-resolution transport contract is introduced with its own resolve\n // shader so this path retains the established depth convention.\n let depthPixel = vec2<i32>(input.position.xy);\n let depth = textureLoad(sceneDepth, depthPixel, 0);\n let farWorld = reconstruct_world(input.uv, 1.0);\n let sceneWorld = reconstruct_world(input.uv, clamp(depth, 0.0, 1.0));\n let ray = normalize(farWorld - view.cameraPos);\n let rayJitter = cloud_ray_jitter(input.position.xy);\n let sceneDistance = select(cloud.field.z, length(sceneWorld - view.cameraPos), depth < 0.999999);\n let interval = cloud_layer_interval(view.cameraPos, ray, sceneDistance);\n\n // The depth producer shares the same camera ray, interval and density\n // contract as the display path. It writes a cloud representative world\n // position and a validity bit so history remains comparable after camera\n // translation or rotation and never uses an opaque scene distance as a\n // proxy for a participating medium.\n if (cloud.integration.w == 2u) {\n if (interval.y <= interval.x) { return vec4<f32>(sceneWorld, 0.0); }\n let depthSteps = min(cloud.integration.y, 64u);\n let depthStepLength = (interval.y - interval.x) / max(1.0, f32(depthSteps));\n var depthTransmittance = 1.0;\n var weightedDistance = 0.0;\n var opticalWeight = 0.0;\n for (var depthIndex = 0u; depthIndex < 64u; depthIndex = depthIndex + 1u) {\n if (depthIndex >= depthSteps) { break; }\n let sampleDistance = interval.x + (f32(depthIndex) + rayJitter) * depthStepLength;\n let samplePosition = view.cameraPos + ray * sampleDistance;\n let localDensity = cloud_density(samplePosition);\n let localTransmittance = exp(-max(localDensity * 0.03 * depthStepLength, 0.0));\n let weight = depthTransmittance * (1.0 - localTransmittance);\n weightedDistance = weightedDistance + sampleDistance * weight;\n opticalWeight = opticalWeight + weight;\n depthTransmittance = depthTransmittance * localTransmittance;\n }\n let hasCloud = opticalWeight > 0.00001;\n let representativeDistance = select(sceneDistance, weightedDistance / opticalWeight, hasCloud);\n let representativeWorld = view.cameraPos + ray * representativeDistance;\n return vec4<f32>(representativeWorld, select(0.0, 1.0, hasCloud));\n }\n\n let scene = textureSampleLevel(sceneColor, sceneSampler, input.uv, 0.0);\n if (interval.y <= interval.x) { return scene; }\n let steps = min(cloud.integration.y, 64u);\n let stepLength = (interval.y - interval.x) / max(1.0, f32(steps));\n var transmittance = 1.0;\n var scattering = vec3<f32>(0.0);\n var weightedDistance = 0.0;\n var opticalWeight = 0.0;\n let sunDirection = normalize(-view.lightDir);\n for (var index = 0u; index < 64u; index = index + 1u) {\n if (index >= steps) { break; }\n let distance = interval.x + (f32(index) + rayJitter) * stepLength;\n let samplePosition = view.cameraPos + ray * distance;\n let localDensity = cloud_density(samplePosition);\n let extinction = localDensity * 0.03 * stepLength;\n let localTransmittance = exp(-max(extinction, 0.0));\n let depthWeight = transmittance * (1.0 - localTransmittance);\n weightedDistance = weightedDistance + distance * depthWeight;\n opticalWeight = opticalWeight + depthWeight;\n // Empty samples are common in the separated-cloud field. Avoid a nested\n // solar march for them and stop once the camera path is effectively opaque.\n if (localDensity > 0.0001 && transmittance > 0.01) {\n let solarTransmittance = cloud_solar_transmittance(samplePosition);\n let cloudHeight = clamp(\n (samplePosition.y - cloud.layer.x) / max(0.0001, cloud.layer.y), 0.0, 1.0,\n );\n scattering = scattering + cloud_incident_light(\n solarTransmittance, dot(ray, sunDirection), cloudHeight,\n ) * depthWeight;\n }\n transmittance = transmittance * localTransmittance;\n if (transmittance < 0.01) { break; }\n }\n let hasCloud = opticalWeight > 0.00001;\n let representativeDistance = select(sceneDistance, weightedDistance / opticalWeight, hasCloud);\n let representativeWorld = view.cameraPos + ray * representativeDistance;\n // Wind is an advective coordinate change. Reproject the cloud point into\n // the previous frame's world before applying the previous camera matrix;\n // authored field values remain immutable.\n let windDelta = cloud.wind.xyz * max(0.0, cloud.field.y - cloud.temporal.w);\n // cloud_density evaluates the authored field at position + wind*time.\n // Keeping the same field sample in the previous frame therefore advances\n // the previous world point by the elapsed wind delta as well.\n let previousCloudWorld = representativeWorld + windDelta;\n let previousCloudClip = view.temporalPreviousViewProj * vec4<f32>(previousCloudWorld, 1.0);\n let previousCloudNdc = previousCloudClip.xyz / max(abs(previousCloudClip.w), 0.00001);\n let previousCloudUv = vec2<f32>(\n previousCloudNdc.x * 0.5 + 0.5,\n 1.0 - (previousCloudNdc.y * 0.5 + 0.5),\n );\n let cloudInBounds = all(previousCloudUv >= vec2<f32>(0.0)) && all(previousCloudUv <= vec2<f32>(1.0));\n let cloudHistoryUv = clamp(previousCloudUv, vec2<f32>(0.0), vec2<f32>(1.0));\n let cloudHistoryDepthSample = textureSampleLevel(previousDepth, depthSampler, cloudHistoryUv, 0.0);\n let cloudHistoryWorld = cloudHistoryDepthSample.xyz;\n let previous = textureSampleLevel(previousRadiance, sceneSampler, cloudHistoryUv, 0.0);\n let previousTransport = textureSampleLevel(\n previousTransmittance,\n sceneSampler,\n cloudHistoryUv,\n 0.0,\n );\n let worldTolerance = max(0.05, length(previousCloudWorld - view.cameraPos) * 0.02);\n let depthAccepted = cloudHistoryDepthSample.a > 0.5 &&\n distance(cloudHistoryWorld, previousCloudWorld) <= worldTolerance;\n let accepted =\n hasCloud && cloud.temporal.x > 0.5 && cloud.temporal.y < 0.5 && cloudInBounds &&\n depthAccepted && previousTransport.a > 0.0;\n let historyWeight = select(0.0, clamp(cloud.temporal.z, 0.0, 0.95), accepted);\n let cloudRadiance = mix(scattering, previous.rgb, historyWeight);\n // Preserve scene-linear HDR output for the downstream material lane. The\n // history owner receives cloudRadiance separately through the history pass.\n return vec4<f32>(scene.rgb * transmittance + cloudRadiance, transmittance);\n}\n\n// The light-space shadow producer already paid for a bounded column integral.\n// Reuse that texel for camera samples instead of starting a full solar march\n// for every occupied camera step. Outside the occupied interval the cached\n// full-column transmittance is exact for the projected column. Inside the\n// interval, the cache supplies the stable projection/bounds and a short\n// residual march resolves the non-uniform density that a linear optical-depth\n// interpolation would lose. Invalid/out-of-range projections retain the\n// analytic path as an explicit fallback.\nfn cloud_solar_cached_transmittance(position: vec3<f32>) -> f32 {\n let projection = cloud.shadowProjection;\n if (projection.y < 0.5 || projection.x <= 0.0) {\n return cloud_solar_transmittance(position);\n }\n let relative = position - cloud.shadowOrigin.xyz;\n let uv = vec2<f32>(\n dot(relative, cloud.shadowRight.xyz) / projection.x + 0.5,\n dot(relative, cloud.shadowUp.xyz) / projection.x + 0.5,\n );\n if (any(uv < vec2<f32>(0.0)) || any(uv > vec2<f32>(1.0))) {\n return cloud_solar_transmittance(position);\n }\n let cachedShadow = textureSampleLevel(previousRadiance, sceneSampler, uv, 0.0);\n let cachedOpticalDepth = max(cachedShadow.a, 0.0);\n let layerHeight = clamp(\n (position.y - cloud.layer.x) / max(0.0001, cloud.layer.y),\n 0.0,\n 1.0,\n );\n if (cachedOpticalDepth <= 0.0001) { return 1.0; }\n let firstHeight = clamp(cachedShadow.g, 0.0, 1.0);\n let lastHeight = clamp(cachedShadow.b, 0.0, 1.0);\n let occupiedMin = min(firstHeight, lastHeight);\n let occupiedMax = max(firstHeight, lastHeight);\n let sunDirection = normalize(-view.lightDir);\n // G/B are ordered along the light ray, not necessarily bottom-to-top. Only\n // use the cached full/empty fast paths when the light has enough vertical\n // component to make that ordering meaningful; grazing light stays on the\n // residual path so a low-sun column cannot invert the cache bounds.\n if (abs(sunDirection.y) > 0.2) {\n if (sunDirection.y > 0.0) {\n if (layerHeight <= occupiedMin) { return exp(-cachedOpticalDepth); }\n if (layerHeight >= occupiedMax) { return 1.0; }\n } else {\n if (layerHeight >= occupiedMax) { return exp(-cachedOpticalDepth); }\n if (layerHeight <= occupiedMin) { return 1.0; }\n }\n }\n\n // A short residual march is only paid for samples inside the projected\n // cloud span. Vertical light uses twelve taps, oblique light sixteen, and\n // grazing light up to thirty-two because the same layer projects across a\n // much longer solar segment. This removes the large error caused by\n // assuming optical depth is uniform between the first and last occupied\n // heights while keeping the normal camera path far below a second full\n // solar integration.\n let solarDistance = select(cloud.field.z, projection.x, projection.x > 0.0);\n let interval = cloud_layer_interval(position, sunDirection, solarDistance);\n if (interval.y <= interval.x) { return 1.0; }\n let absLightY = abs(sunDirection.y);\n let directionBudget = select(\n 32u,\n select(16u, 12u, absLightY > 0.5),\n absLightY > 0.2,\n );\n let residualSteps = min(directionBudget, max(8u, cloud.integration.z * 3u));\n let stepLength = (interval.y - interval.x) / max(1.0, f32(residualSteps));\n var opticalDepth = 0.0;\n let solarJitter = cloud_solar_jitter(position);\n for (var index = 0u; index < 32u; index = index + 1u) {\n if (index >= residualSteps) { break; }\n let samplePosition = position + sunDirection *\n (interval.x + (f32(index) + solarJitter) * stepLength);\n opticalDepth = opticalDepth + cloud_density(samplePosition) *\n 0.03 * stepLength;\n }\n return exp(-max(opticalDepth, 0.0));\n}\n\nstruct CloudTransportOutput {\n @location(0) radiance: vec4<f32>,\n @location(1) transmittance: vec4<f32>,\n @location(2) depth: vec4<f32>,\n};\n\n// One half-resolution transport pass writes all three reusable cloud fields.\n// The full-resolution resolve below reconstructs colour and applies temporal\n// history; no second full raymarch exists solely to obtain cloud depth.\n@fragment\nfn fs_transport(input: CloudViewOut) -> CloudTransportOutput {\n let depth = textureSampleLevel(sceneDepth, depthSampler, input.uv, 0u);\n let farWorld = reconstruct_world(input.uv, 1.0);\n let sceneWorld = reconstruct_world(input.uv, clamp(depth, 0.0, 1.0));\n let ray = normalize(farWorld - view.cameraPos);\n let rayJitter = cloud_ray_jitter(input.position.xy);\n let sceneDistance = select(cloud.field.z, length(sceneWorld - view.cameraPos), depth < 0.999999);\n let interval = cloud_layer_interval(view.cameraPos, ray, sceneDistance);\n var output: CloudTransportOutput;\n output.radiance = vec4<f32>(0.0, 0.0, 0.0, 0.0);\n output.transmittance = vec4<f32>(1.0, 1.0, 1.0, 1.0);\n output.depth = vec4<f32>(sceneWorld, 0.0);\n if (interval.y <= interval.x) { return output; }\n\n let steps = min(cloud.integration.y, 64u);\n let stepLength = (interval.y - interval.x) / max(1.0, f32(steps));\n var transmittance = 1.0;\n var scattering = vec3<f32>(0.0);\n var weightedDistance = 0.0;\n var opticalWeight = 0.0;\n let sunDirection = normalize(-view.lightDir);\n for (var index = 0u; index < 64u; index = index + 1u) {\n if (index >= steps) { break; }\n let distance = interval.x + (f32(index) + rayJitter) * stepLength;\n let samplePosition = view.cameraPos + ray * distance;\n let localDensity = cloud_density(samplePosition);\n let extinction = localDensity * 0.03 * stepLength;\n let localTransmittance = exp(-max(extinction, 0.0));\n let depthWeight = transmittance * (1.0 - localTransmittance);\n weightedDistance = weightedDistance + distance * depthWeight;\n opticalWeight = opticalWeight + depthWeight;\n if (localDensity > 0.0001 && transmittance > 0.01) {\n let solarTransmittance = cloud_solar_cached_transmittance(samplePosition);\n let cloudHeight = clamp(\n (samplePosition.y - cloud.layer.x) / max(0.0001, cloud.layer.y), 0.0, 1.0,\n );\n scattering = scattering + cloud_incident_light(\n solarTransmittance, dot(ray, sunDirection), cloudHeight,\n ) * depthWeight;\n }\n transmittance = transmittance * localTransmittance;\n if (transmittance < 0.01) { break; }\n }\n let hasCloud = opticalWeight > 0.00001;\n let representativeDistance = select(sceneDistance, weightedDistance / max(opticalWeight, 0.00001), hasCloud);\n output.radiance = vec4<f32>(scattering, select(0.0, 1.0, hasCloud));\n output.transmittance = vec4<f32>(transmittance, transmittance, transmittance, 1.0);\n output.depth = vec4<f32>(view.cameraPos + ray * representativeDistance, select(0.0, 1.0, hasCloud));\n return output;\n}\n";
25
+ /** Analytic fallback for adapters without a projected cloud-shadow target. */
26
+ export declare const CLOUD_TRANSPORT_ANALYTIC_FULLSCREEN_WGSL: string;
27
+ /**
28
+ * History writer. It is a separate MRT pass so the cloud composite remains a
29
+ * normal scene-linear color producer while the renderer owns the ping-pong
30
+ * radiance/transmittance/depth attachments and advances them only on submit.
31
+ */
32
+ export declare const CLOUD_HISTORY_FULLSCREEN_WGSL = "\nstruct CloudCameraView {\n worldViewProj: mat4x4<f32>,\n lightDir: vec3<f32>,\n lightColor: vec3<f32>,\n cameraPos: vec3<f32>,\n lightViewProj_A: mat4x4<f32>,\n inverseViewProj: mat4x4<f32>,\n lightViewProj_B: mat4x4<f32>,\n lightViewProj_C: mat4x4<f32>,\n lightViewProj_D: mat4x4<f32>,\n splitPlanes: array<vec4<f32>, 4>,\n cascadeCount: f32,\n cascadeBlend: f32,\n depthBias: f32,\n normalBias: f32,\n directionalShadowFilter: vec4<f32>,\n spotLightViewProj: array<mat4x4<f32>, 4>,\n temporalCurrentViewProj: mat4x4<f32>,\n temporalPreviousViewProj: mat4x4<f32>,\n temporalProjection: vec4<f32>,\n temporalPreviousCameraPos: vec4<f32>,\n ssrParams: vec4<f32>,\n};\n\nstruct CloudHistoryParams {\n temporal: vec4<f32>, // valid, reset, historyWeight, reserved\n windTime: vec4<f32>, // wind.xyz, elapsed seconds since previous submit\n};\n\n@group(0) @binding(0) var<uniform> view: CloudCameraView;\n@group(1) @binding(0) var currentRadiance: texture_2d<f32>;\n@group(1) @binding(1) var sceneSampler: sampler;\n@group(1) @binding(2) var<uniform> params: CloudHistoryParams;\n@group(1) @binding(3) var sceneDepth: texture_depth_2d;\n@group(1) @binding(4) var depthSampler: sampler;\n@group(1) @binding(5) var currentCloudDepth: texture_2d<f32>;\n@group(1) @binding(6) var sceneBackground: texture_2d<f32>;\n\nstruct CloudHistoryInput {\n @builtin(position) position: vec4<f32>,\n @location(0) uv: vec2<f32>,\n};\n\nstruct CloudHistoryOutput {\n @location(0) radiance: vec4<f32>,\n @location(1) transmittance: vec4<f32>,\n};\n\nfn reconstruct_world(uv: vec2<f32>, depth: f32) -> vec3<f32> {\n let ndc = vec4<f32>(uv.x * 2.0 - 1.0, 1.0 - uv.y * 2.0, depth, 1.0);\n let world = view.inverseViewProj * ndc;\n return world.xyz / max(abs(world.w), 0.00001);\n}\n\n@vertex\nfn vs_main(@builtin(vertex_index) index: u32) -> CloudHistoryInput {\n var positions = array<vec2<f32>, 3>(\n vec2<f32>(-1.0, -1.0),\n vec2<f32>(3.0, -1.0),\n vec2<f32>(-1.0, 3.0),\n );\n var output: CloudHistoryInput;\n output.position = vec4<f32>(positions[index], 0.0, 1.0);\n output.uv = vec2<f32>(\n positions[index].x * 0.5 + 0.5,\n 1.0 - (positions[index].y * 0.5 + 0.5),\n );\n return output;\n}\n\n@fragment\nfn fs_main(input: CloudHistoryInput) -> CloudHistoryOutput {\n let current = textureSampleLevel(currentRadiance, sceneSampler, input.uv, 0.0);\n let background = textureSampleLevel(sceneBackground, sceneSampler, input.uv, 0.0);\n let cloudDepth = textureSampleLevel(currentCloudDepth, depthSampler, input.uv, 0.0);\n let currentValid = cloudDepth.a > 0.5;\n // currentRadiance is the scene-linear cloud composite. Remove the\n // unchanged scene background before writing history so the persistent owner\n // stores raw cloud radiance only. The view pass is the sole temporal blend\n // owner; blending here as well would accumulate history twice per frame.\n let currentCloudRadiance = max(current.rgb - background.rgb * current.a, vec3<f32>(0.0));\n let currentTransmittance = vec4<f32>(current.a, current.a, current.a, 1.0);\n var output: CloudHistoryOutput;\n output.radiance = vec4<f32>(currentCloudRadiance, select(0.0, 1.0, currentValid));\n output.transmittance = currentTransmittance;\n return output;\n}\n";
33
+ /** Full-resolution resolve for the half-resolution transport MRT. */
34
+ export declare const CLOUD_RESOLVE_FULLSCREEN_WGSL = "\nstruct CloudCameraView {\n worldViewProj: mat4x4<f32>,\n lightDir: vec3<f32>,\n lightColor: vec3<f32>,\n cameraPos: vec3<f32>,\n lightViewProj_A: mat4x4<f32>,\n inverseViewProj: mat4x4<f32>,\n lightViewProj_B: mat4x4<f32>,\n lightViewProj_C: mat4x4<f32>,\n lightViewProj_D: mat4x4<f32>,\n splitPlanes: array<vec4<f32>, 4>,\n cascadeCount: f32,\n cascadeBlend: f32,\n depthBias: f32,\n normalBias: f32,\n directionalShadowFilter: vec4<f32>,\n spotLightViewProj: array<mat4x4<f32>, 4>,\n temporalCurrentViewProj: mat4x4<f32>,\n temporalPreviousViewProj: mat4x4<f32>,\n temporalProjection: vec4<f32>,\n temporalPreviousCameraPos: vec4<f32>,\n ssrParams: vec4<f32>,\n};\n\nstruct CloudResolveParams {\n layer: vec4<f32>,\n field: vec4<f32>,\n wind: vec4<f32>,\n sunDirection: vec4<f32>,\n sunRadiance: vec4<f32>,\n integration: vec4<u32>,\n shadowOrigin: vec4<f32>,\n shadowRight: vec4<f32>,\n shadowUp: vec4<f32>,\n shadowProjection: vec4<f32>,\n temporal: vec4<f32>,\n};\n\n@group(0) @binding(0) var<uniform> view: CloudCameraView;\n@group(1) @binding(0) var sceneColor: texture_2d<f32>;\n@group(1) @binding(1) var sceneSampler: sampler;\n@group(1) @binding(2) var<uniform> cloud: CloudResolveParams;\n@group(1) @binding(3) var sceneDepth: texture_depth_2d;\n@group(1) @binding(4) var depthSampler: sampler;\n@group(1) @binding(5) var currentRadiance: texture_2d<f32>;\n@group(1) @binding(6) var currentTransmittance: texture_2d<f32>;\n@group(1) @binding(7) var currentDepth: texture_2d<f32>;\n@group(1) @binding(8) var previousRadiance: texture_2d<f32>;\n@group(1) @binding(9) var previousTransmittance: texture_2d<f32>;\n@group(1) @binding(10) var previousDepth: texture_2d<f32>;\n\nstruct CloudResolveInput {\n @builtin(position) position: vec4<f32>,\n @location(0) uv: vec2<f32>,\n};\n\nfn reconstruct_world(uv: vec2<f32>, depth: f32) -> vec3<f32> {\n let ndc = vec4<f32>(uv.x * 2.0 - 1.0, 1.0 - uv.y * 2.0, depth, 1.0);\n let world = view.inverseViewProj * ndc;\n return world.xyz / max(abs(world.w), 0.00001);\n}\n\n@vertex\nfn vs_main(@builtin(vertex_index) index: u32) -> CloudResolveInput {\n var positions = array<vec2<f32>, 3>(\n vec2<f32>(-1.0, -1.0),\n vec2<f32>(3.0, -1.0),\n vec2<f32>(-1.0, 3.0),\n );\n var output: CloudResolveInput;\n output.position = vec4<f32>(positions[index], 0.0, 1.0);\n output.uv = vec2<f32>(\n positions[index].x * 0.5 + 0.5,\n 1.0 - (positions[index].y * 0.5 + 0.5),\n );\n return output;\n}\n\n@fragment\nfn fs_main(input: CloudResolveInput) -> @location(0) vec4<f32> {\n let scene = textureSampleLevel(sceneColor, sceneSampler, input.uv, 0.0);\n // The resolve owns the full-resolution foreground test. Reject the current\n // low-resolution transport before any history or radiance is composited;\n // gating history alone still lets a newly sampled cloud bleed over geometry.\n let sceneDepthValue = textureLoad(sceneDepth, vec2<i32>(input.position.xy), 0);\n let sceneWorld = reconstruct_world(input.uv, clamp(sceneDepthValue, 0.0, 1.0));\n let sceneDistance = length(sceneWorld - view.cameraPos);\n let transport = textureSampleLevel(currentRadiance, sceneSampler, input.uv, 0.0);\n let transportT = textureSampleLevel(currentTransmittance, sceneSampler, input.uv, 0.0);\n // The transport depth is an rgba16float color attachment, not the scene's\n // depth texture. Sample it with the filtering color sampler; binding the\n // non-filtering scene-depth sampler here makes the shader contract depend on\n // an incompatible sampler/texture pair on strict WebGPU implementations.\n let cloudDepth = textureSampleLevel(currentDepth, sceneSampler, input.uv, 0.0);\n let currentValid = transport.a > 0.5 && cloudDepth.a > 0.5;\n let cloudDistance = length(cloudDepth.xyz - view.cameraPos);\n let foregroundAccepted = sceneDepthValue >= 0.999999 ||\n cloudDistance <= sceneDistance + max(0.05, sceneDistance * 0.01);\n if (!currentValid || !foregroundAccepted) { return scene; }\n\n let previousClip = view.temporalPreviousViewProj * vec4<f32>(\n cloudDepth.xyz + cloud.wind.xyz * max(0.0, cloud.field.y - cloud.temporal.w),\n 1.0,\n );\n let previousNdc = previousClip.xyz / max(abs(previousClip.w), 0.00001);\n let previousUv = vec2<f32>(\n previousNdc.x * 0.5 + 0.5,\n 1.0 - (previousNdc.y * 0.5 + 0.5),\n );\n let inBounds = all(previousUv >= vec2<f32>(0.0)) && all(previousUv <= vec2<f32>(1.0));\n let historyUv = clamp(previousUv, vec2<f32>(0.0), vec2<f32>(1.0));\n let historyDepth = textureSampleLevel(previousDepth, sceneSampler, historyUv, 0.0);\n let previous = textureSampleLevel(previousRadiance, sceneSampler, historyUv, 0.0);\n let previousTransport = textureSampleLevel(previousTransmittance, sceneSampler, historyUv, 0.0);\n let dims = vec2<f32>(textureDimensions(previousRadiance));\n let texel = 1.0 / max(dims, vec2<f32>(1.0));\n // Clamp reprojected history against the current low-resolution transport\n // neighborhood. Using only previous-frame neighbors (and including the\n // previous center in both extrema) is a no-op and lets stale bright cloud\n // pixels leak across a newly exposed edge.\n let c0 = transport.rgb;\n let c1 = textureSampleLevel(currentRadiance, sceneSampler, clamp(input.uv + vec2<f32>(-texel.x, 0.0), vec2<f32>(0.0), vec2<f32>(1.0)), 0.0).rgb;\n let c2 = textureSampleLevel(currentRadiance, sceneSampler, clamp(input.uv + vec2<f32>(texel.x, 0.0), vec2<f32>(0.0), vec2<f32>(1.0)), 0.0).rgb;\n let c3 = textureSampleLevel(currentRadiance, sceneSampler, clamp(input.uv + vec2<f32>(0.0, -texel.y), vec2<f32>(0.0), vec2<f32>(1.0)), 0.0).rgb;\n let c4 = textureSampleLevel(currentRadiance, sceneSampler, clamp(input.uv + vec2<f32>(0.0, texel.y), vec2<f32>(0.0), vec2<f32>(1.0)), 0.0).rgb;\n let minRadiance = min(c0, min(min(c1, c2), min(c3, c4)));\n let maxRadiance = max(c0, max(max(c1, c2), max(c3, c4)));\n let t1 = textureSampleLevel(currentTransmittance, sceneSampler, clamp(input.uv + vec2<f32>(-texel.x, 0.0), vec2<f32>(0.0), vec2<f32>(1.0)), 0.0).r;\n let t2 = textureSampleLevel(currentTransmittance, sceneSampler, clamp(input.uv + vec2<f32>(texel.x, 0.0), vec2<f32>(0.0), vec2<f32>(1.0)), 0.0).r;\n let t3 = textureSampleLevel(currentTransmittance, sceneSampler, clamp(input.uv + vec2<f32>(0.0, -texel.y), vec2<f32>(0.0), vec2<f32>(1.0)), 0.0).r;\n let t4 = textureSampleLevel(currentTransmittance, sceneSampler, clamp(input.uv + vec2<f32>(0.0, texel.y), vec2<f32>(0.0), vec2<f32>(1.0)), 0.0).r;\n let minTransport = min(transportT.r, min(min(t1, t2), min(t3, t4)));\n let maxTransport = max(transportT.r, max(max(t1, t2), max(t3, t4)));\n let worldTolerance = max(0.05, distance(cloudDepth.xyz, view.cameraPos) * 0.02);\n let depthAccepted = historyDepth.a > 0.5 && distance(historyDepth.xyz, cloudDepth.xyz) <= worldTolerance;\n let accepted = cloud.temporal.x > 0.5 && cloud.temporal.y < 0.5 && inBounds && depthAccepted;\n let historyWeight = select(0.0, clamp(cloud.temporal.z, 0.0, 0.92), accepted);\n let stableHistory = clamp(previous.rgb, minRadiance, maxRadiance);\n let stableTransport = clamp(previousTransport.r, minTransport, maxTransport);\n let radiance = mix(transport.rgb, stableHistory, historyWeight);\n let transmittance = mix(transportT.r, stableTransport, historyWeight);\n return vec4<f32>(scene.rgb * transmittance + radiance, transmittance);\n}\n";
35
+ export interface CloudLayerFeatureFrame {
36
+ readonly params: ValidatedCloudLayer | undefined;
37
+ readonly cache: CloudDensityCache | undefined;
38
+ readonly sourceKey: string | undefined;
39
+ readonly timeSeconds: number;
40
+ readonly sunDirection: readonly [number, number, number] | undefined;
41
+ readonly sunRadiance: readonly [number, number, number] | undefined;
42
+ readonly shadowProjection: CloudShadowProjection | undefined;
43
+ readonly temporal: CloudTemporalFrame | undefined;
44
+ readonly generation: number;
45
+ readonly inspection: CloudLayerInspection;
46
+ }
47
+ export interface CloudLayerFeatureOptions {
48
+ /** Disable the renderer-owned CloudLayer projection while retaining the component. */
49
+ readonly enabled?: boolean;
50
+ }
51
+ /**
52
+ * Renderer-installed cloud producer. Authoring is read once from World at
53
+ * extraction; resource preparation, graph ordering and retirement stay with
54
+ * RenderFeatureHost.
55
+ */
56
+ export declare function createCloudLayerFeature(options?: CloudLayerFeatureOptions): RenderFeature<CloudLayerFeatureFrame>;
57
+ //# sourceMappingURL=feature.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"feature.d.ts","sourceRoot":"","sources":["../../src/cloud/feature.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAA+B,MAAM,mBAAmB,CAAC;AACpF,OAAO,EAAgD,KAAK,iBAAiB,EAAE,MAAM,WAAW,CAAC;AACjG,OAAO,EACL,KAAK,oBAAoB,EAG1B,MAAM,cAAc,CAAC;AACtB,OAAO,EAML,KAAK,mBAAmB,EACzB,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,KAAK,qBAAqB,EAA+B,MAAM,UAAU,CAAC;AACnF,OAAO,EAEL,KAAK,kBAAkB,EAGxB,MAAM,YAAY,CAAC;AAEpB,eAAO,MAAM,4BAA4B,wBAAwB,CAAC;AAClE,eAAO,MAAM,2BAA2B,wBAAwB,CAAC;AACjE,eAAO,MAAM,4BAA4B,iCAAiC,CAAC;AAC3E,eAAO,MAAM,yBAAyB,8BAA8B,CAAC;AACrE,eAAO,MAAM,0BAA0B,+BAA+B,CAAC;AAWvE,eAAO,MAAM,uBAAuB,MAAM,CAAC;AAa3C,8EAA8E;AAC9E,eAAO,MAAM,0BAA0B,8bAUtC,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,0BAA0B,45gCA4uBtC,CAAC;AAEF,gFAAgF;AAChF,eAAO,MAAM,+BAA+B,45gCAA6B,CAAC;AAC1E,8EAA8E;AAC9E,eAAO,MAAM,wCAAwC,QAGpD,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,6BAA6B,ysGAwFzC,CAAC;AAEF,qEAAqE;AACrE,eAAO,MAAM,6BAA6B,+uOAgJzC,CAAC;AAEF,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,MAAM,EAAE,mBAAmB,GAAG,SAAS,CAAC;IACjD,QAAQ,CAAC,KAAK,EAAE,iBAAiB,GAAG,SAAS,CAAC;IAC9C,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IACvC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,YAAY,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAAC;IACrE,QAAQ,CAAC,WAAW,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAAC;IACpE,QAAQ,CAAC,gBAAgB,EAAE,qBAAqB,GAAG,SAAS,CAAC;IAC7D,QAAQ,CAAC,QAAQ,EAAE,kBAAkB,GAAG,SAAS,CAAC;IAClD,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,UAAU,EAAE,oBAAoB,CAAC;CAC3C;AAED,MAAM,WAAW,wBAAwB;IACvC,sFAAsF;IACtF,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;CAC5B;AAqgBD;;;;GAIG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,GAAE,wBAA6B,GACrC,aAAa,CAAC,sBAAsB,CAAC,CAkMvC"}
@@ -0,0 +1,8 @@
1
+ import type { World } from '@forgeax/engine-ecs';
2
+ import type { RenderFeatureFrameContext, RenderFeatureViewContext } from '../features/types';
3
+ import type { CameraSnapshot } from '../render-contract';
4
+ import type { ExtractedCloudLayer } from './extract';
5
+ /** Project the stable camera/world facts consumed by the cloud feature owner. */
6
+ export declare function createCloudFeatureView(camera: CameraSnapshot | undefined, worlds: readonly World[], cameraOwner: number, width: number, height: number, deviceGeneration: number, cameraCut: boolean, recovery: boolean): RenderFeatureViewContext | undefined;
7
+ export declare function createCloudFeatureFrameContext(cloudLayer: ExtractedCloudLayer | undefined, view: RenderFeatureViewContext | undefined): RenderFeatureFrameContext;
8
+ //# sourceMappingURL=frame-context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frame-context.d.ts","sourceRoot":"","sources":["../../src/cloud/frame-context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,KAAK,EAAE,yBAAyB,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAC;AAC7F,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAErD,iFAAiF;AACjF,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,cAAc,GAAG,SAAS,EAClC,MAAM,EAAE,SAAS,KAAK,EAAE,EACxB,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,gBAAgB,EAAE,MAAM,EACxB,SAAS,EAAE,OAAO,EAClB,QAAQ,EAAE,OAAO,GAChB,wBAAwB,GAAG,SAAS,CAgBtC;AAED,wBAAgB,8BAA8B,CAC5C,UAAU,EAAE,mBAAmB,GAAG,SAAS,EAC3C,IAAI,EAAE,wBAAwB,GAAG,SAAS,GACzC,yBAAyB,CAK3B"}
@@ -0,0 +1,59 @@
1
+ import type { RhiCaps } from '@forgeax/engine-rhi';
2
+ import type { CloudLayerResourceFacts } from './resources';
3
+ import type { CloudTemporalResetReason } from './temporal';
4
+ export type CloudLayerInspectionStatus = 'off' | 'available' | 'unavailable' | 'degraded';
5
+ export type CloudLayerResourceStage = 'none' | 'candidate' | 'accepted' | 'lkg' | 'recovering';
6
+ /** Physical-adapter evidence is explicit; RHI backend labels cannot prove it. */
7
+ export type CloudPhysicalGpuEvidence = 'unknown' | 'available' | 'unavailable';
8
+ export interface CloudLayerBudgetObservation {
9
+ readonly measured: boolean;
10
+ readonly physicalGpu: CloudPhysicalGpuEvidence;
11
+ readonly gpuTimestamp: boolean;
12
+ readonly quality: 'low' | 'medium' | 'high' | undefined;
13
+ readonly warmupFrames: number;
14
+ readonly sampleFrames: number;
15
+ readonly p50GpuMs: number | undefined;
16
+ readonly p95GpuMs: number | undefined;
17
+ readonly coldGenerationMs: number | undefined;
18
+ readonly frameBudgetMs: number | undefined;
19
+ readonly reason?: string;
20
+ }
21
+ export interface CloudLayerInspection {
22
+ readonly status: CloudLayerInspectionStatus;
23
+ readonly resourceStage: CloudLayerResourceStage;
24
+ readonly sourceKey: string | undefined;
25
+ readonly generation: number | undefined;
26
+ readonly candidateGeneration: number | undefined;
27
+ readonly lkgGeneration: number | undefined;
28
+ readonly shadowRevision: number | undefined;
29
+ readonly temporalResets: number;
30
+ readonly lastTemporalReset: CloudTemporalResetReason | undefined;
31
+ readonly resourceFacts: CloudLayerResourceFacts | undefined;
32
+ readonly capability: {
33
+ readonly compute: boolean;
34
+ readonly storageBuffer: boolean;
35
+ readonly rgba16floatRenderable: boolean;
36
+ readonly physicalGpu: CloudPhysicalGpuEvidence;
37
+ readonly timestampQuery: boolean;
38
+ };
39
+ readonly budget: CloudLayerBudgetObservation;
40
+ }
41
+ export interface CloudLayerInspectionInput {
42
+ readonly authored: boolean;
43
+ readonly sourceKey?: string;
44
+ readonly generation?: number;
45
+ readonly candidateGeneration?: number;
46
+ readonly lkgGeneration?: number;
47
+ readonly shadowRevision?: number;
48
+ readonly temporalResets?: number;
49
+ readonly lastTemporalReset?: CloudTemporalResetReason;
50
+ readonly resourceFacts?: CloudLayerResourceFacts;
51
+ readonly capability?: Partial<CloudLayerInspection['capability']>;
52
+ readonly degraded?: boolean;
53
+ readonly budget?: Partial<CloudLayerBudgetObservation>;
54
+ }
55
+ /** Capability and lifecycle facts are inspectable even when the lane is unavailable. */
56
+ export declare function inspectCloudLayer(input: CloudLayerInspectionInput): CloudLayerInspection;
57
+ /** Convert RHI facts without letting inspection invent physical measurements. */
58
+ export declare function cloudCapabilitiesFromRhi(caps: Readonly<RhiCaps>): CloudLayerInspection['capability'];
59
+ //# sourceMappingURL=inspection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inspection.d.ts","sourceRoot":"","sources":["../../src/cloud/inspection.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAC3D,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC;AAE3D,MAAM,MAAM,0BAA0B,GAAG,KAAK,GAAG,WAAW,GAAG,aAAa,GAAG,UAAU,CAAC;AAC1F,MAAM,MAAM,uBAAuB,GAAG,MAAM,GAAG,WAAW,GAAG,UAAU,GAAG,KAAK,GAAG,YAAY,CAAC;AAC/F,iFAAiF;AACjF,MAAM,MAAM,wBAAwB,GAAG,SAAS,GAAG,WAAW,GAAG,aAAa,CAAC;AAE/E,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,WAAW,EAAE,wBAAwB,CAAC;IAC/C,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;IAC/B,QAAQ,CAAC,OAAO,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,SAAS,CAAC;IACxD,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IACtC,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IACtC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9C,QAAQ,CAAC,aAAa,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3C,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,MAAM,EAAE,0BAA0B,CAAC;IAC5C,QAAQ,CAAC,aAAa,EAAE,uBAAuB,CAAC;IAChD,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IACvC,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;IACxC,QAAQ,CAAC,mBAAmB,EAAE,MAAM,GAAG,SAAS,CAAC;IACjD,QAAQ,CAAC,aAAa,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3C,QAAQ,CAAC,cAAc,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5C,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,iBAAiB,EAAE,wBAAwB,GAAG,SAAS,CAAC;IACjE,QAAQ,CAAC,aAAa,EAAE,uBAAuB,GAAG,SAAS,CAAC;IAC5D,QAAQ,CAAC,UAAU,EAAE;QACnB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;QAC1B,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;QAChC,QAAQ,CAAC,qBAAqB,EAAE,OAAO,CAAC;QACxC,QAAQ,CAAC,WAAW,EAAE,wBAAwB,CAAC;QAC/C,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;KAClC,CAAC;IACF,QAAQ,CAAC,MAAM,EAAE,2BAA2B,CAAC;CAC9C;AAED,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IACtC,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,iBAAiB,CAAC,EAAE,wBAAwB,CAAC;IACtD,QAAQ,CAAC,aAAa,CAAC,EAAE,uBAAuB,CAAC;IACjD,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC,oBAAoB,CAAC,YAAY,CAAC,CAAC,CAAC;IAClE,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,2BAA2B,CAAC,CAAC;CACxD;AASD,wFAAwF;AACxF,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,yBAAyB,GAAG,oBAAoB,CA+CxF;AAED,iFAAiF;AACjF,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,QAAQ,CAAC,OAAO,CAAC,GACtB,oBAAoB,CAAC,YAAY,CAAC,CAUpC"}
@@ -0,0 +1,56 @@
1
+ import type { CloudDensityCache } from './density';
2
+ import { type ValidatedCloudLayer } from './parameters';
3
+ export type CloudOpticalPath = 'camera' | 'solar-column' | 'cloud-interior';
4
+ export interface CloudRay {
5
+ readonly origin: readonly [number, number, number];
6
+ readonly direction: readonly [number, number, number];
7
+ readonly maxDistance: number;
8
+ }
9
+ export interface CloudOpticalResult {
10
+ readonly path: CloudOpticalPath;
11
+ readonly transmittance: number;
12
+ readonly scattering: readonly [number, number, number];
13
+ readonly opticalDepth: number;
14
+ readonly representativeDepth: number;
15
+ readonly steps: number;
16
+ readonly entered: boolean;
17
+ readonly exited: boolean;
18
+ }
19
+ export interface CloudInteriorLighting {
20
+ readonly sunRadiance: readonly [number, number, number];
21
+ readonly sunDirection: readonly [number, number, number];
22
+ readonly phaseG?: number;
23
+ }
24
+ export interface CloudOpticsInput {
25
+ readonly params: ValidatedCloudLayer;
26
+ readonly ray: CloudRay;
27
+ readonly path: CloudOpticalPath;
28
+ readonly timeSeconds?: number;
29
+ readonly cache?: CloudDensityCache;
30
+ readonly steps?: number;
31
+ readonly lighting?: CloudInteriorLighting;
32
+ }
33
+ export declare function intersectCloudLayer(ray: CloudRay, params: ValidatedCloudLayer): {
34
+ readonly start: number;
35
+ readonly end: number;
36
+ } | undefined;
37
+ /** Unit-sun incident light, using the same phase basis as production volumes. */
38
+ export declare function cloudIncidentLight(solarT: number, cosTheta: number, height: number, forwardG?: number): readonly [number, number, number];
39
+ /**
40
+ * Integrate one explicitly named optical path through the layer.
41
+ *
42
+ * Camera, solar-column and cloud-interior paths are intentionally separate
43
+ * call sites. A caller cannot accidentally use a camera interval as the
44
+ * light interval and apply the same cloud attenuation twice.
45
+ */
46
+ export declare function integrateCloudPath(input: CloudOpticsInput): CloudOpticalResult;
47
+ export declare function integrateCloudCameraPath(params: ValidatedCloudLayer, ray: CloudRay, options?: Omit<CloudOpticsInput, 'params' | 'ray' | 'path'>): CloudOpticalResult;
48
+ export declare function integrateCloudSolarColumn(params: ValidatedCloudLayer, origin: readonly [number, number, number], sunDirection: readonly [number, number, number], options?: Omit<CloudOpticsInput, 'params' | 'ray' | 'path'> & {
49
+ readonly maxDistance?: number;
50
+ }): CloudOpticalResult;
51
+ export declare function integrateCloudInterior(params: ValidatedCloudLayer, ray: CloudRay, lighting: CloudInteriorLighting, options?: Omit<CloudOpticsInput, 'params' | 'ray' | 'path' | 'lighting'>): CloudOpticalResult;
52
+ /** Standard's one selected sun term consumes this factor exactly once. */
53
+ export declare function applyCloudSolarTransmittance(directSunRadiance: readonly [number, number, number], cloudTransmittance: number, meshVisibility?: number): readonly [number, number, number];
54
+ /** Scene-linear HDR composition at the cloud interval/depth boundary. */
55
+ export declare function compositeCloudRadiance(sceneLinearHdr: readonly [number, number, number, number], cloud: CloudOpticalResult, sceneDepth: number, cloudDepth?: number): readonly [number, number, number, number];
56
+ //# sourceMappingURL=optics.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"optics.d.ts","sourceRoot":"","sources":["../../src/cloud/optics.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAEnD,OAAO,EAAgC,KAAK,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAEtF,MAAM,MAAM,gBAAgB,GAAG,QAAQ,GAAG,cAAc,GAAG,gBAAgB,CAAC;AAE5E,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IACnD,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IACtD,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAC;IAChC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,UAAU,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IACvD,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;IACrC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,WAAW,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IACxD,QAAQ,CAAC,YAAY,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IACzD,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,MAAM,EAAE,mBAAmB,CAAC;IACrC,QAAQ,CAAC,GAAG,EAAE,QAAQ,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAC;IAChC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,KAAK,CAAC,EAAE,iBAAiB,CAAC;IACnC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,CAAC,EAAE,qBAAqB,CAAC;CAC3C;AA0CD,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,QAAQ,EACb,MAAM,EAAE,mBAAmB,GAC1B;IAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAE9D;AAkBD,iFAAiF;AACjF,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,QAAQ,SAAO,GACd,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAMnC;AAQD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,gBAAgB,GAAG,kBAAkB,CAsF9E;AAED,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,mBAAmB,EAC3B,GAAG,EAAE,QAAQ,EACb,OAAO,GAAE,IAAI,CAAC,gBAAgB,EAAE,QAAQ,GAAG,KAAK,GAAG,MAAM,CAAM,GAC9D,kBAAkB,CAEpB;AAED,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,mBAAmB,EAC3B,MAAM,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EACzC,YAAY,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAC/C,OAAO,GAAE,IAAI,CAAC,gBAAgB,EAAE,QAAQ,GAAG,KAAK,GAAG,MAAM,CAAC,GAAG;IAC3D,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC1B,GACL,kBAAkB,CAWpB;AAED,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,mBAAmB,EAC3B,GAAG,EAAE,QAAQ,EACb,QAAQ,EAAE,qBAAqB,EAC/B,OAAO,GAAE,IAAI,CAAC,gBAAgB,EAAE,QAAQ,GAAG,KAAK,GAAG,MAAM,GAAG,UAAU,CAAM,GAC3E,kBAAkB,CAEpB;AAED,0EAA0E;AAC1E,wBAAgB,4BAA4B,CAC1C,iBAAiB,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EACpD,kBAAkB,EAAE,MAAM,EAC1B,cAAc,SAAI,GACjB,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAQnC;AAED,yEAAyE;AACzE,wBAAgB,sBAAsB,CACpC,cAAc,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EACzD,KAAK,EAAE,kBAAkB,EACzB,UAAU,EAAE,MAAM,EAClB,UAAU,SAA4B,GACrC,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAS3C"}
@@ -0,0 +1,73 @@
1
+ import { type Result } from '@forgeax/engine-types';
2
+ import { type CloudLayerData, type CloudQuality } from '../components/cloud-layer';
3
+ import { type CloudLayerError } from '../errors/cloud';
4
+ export interface CloudLayerAuthoring {
5
+ readonly seed?: number;
6
+ readonly baseHeight?: number;
7
+ readonly thickness?: number;
8
+ readonly scale?: number;
9
+ readonly coverage?: number;
10
+ readonly density?: number;
11
+ readonly wind?: ArrayLike<number>;
12
+ readonly quality?: CloudQuality | number;
13
+ readonly shadowRange?: number;
14
+ }
15
+ export interface ValidatedCloudLayer {
16
+ readonly seed: number;
17
+ readonly baseHeight: number;
18
+ readonly thickness: number;
19
+ readonly scale: number;
20
+ readonly coverage: number;
21
+ readonly density: number;
22
+ readonly wind: readonly [number, number, number];
23
+ readonly quality: CloudQuality;
24
+ readonly shadowRange: number;
25
+ }
26
+ export interface CloudQualityProfile {
27
+ readonly cacheResolution: number;
28
+ readonly viewSteps: number;
29
+ readonly shadowSteps: number;
30
+ readonly historyWeight: number;
31
+ /** Renderer-owned sky trace domain in world units; independent of Camera.far. */
32
+ readonly viewDistance: number;
33
+ }
34
+ /**
35
+ * Quality values are bounded in one place. The values are deliberately
36
+ * renderer policy, while the component stores only the closed quality code.
37
+ */
38
+ export declare const CLOUD_QUALITY_PROFILES: Readonly<Record<CloudQuality, CloudQualityProfile>>;
39
+ /**
40
+ * The spatial cloud-shadow map is intentionally derived from the same quality
41
+ * profile everywhere: graph allocation, light-space projection and inspection
42
+ * must agree on one texel grid. Keep the multiplier here rather than letting
43
+ * each owner invent a resolution.
44
+ */
45
+ export declare function cloudShadowResolutionForQuality(qualityValue: CloudQuality): number;
46
+ /**
47
+ * Cloud visibility is a sky-domain concern. It must not inherit the opaque
48
+ * geometry far plane or the finite receiver-shadow footprint: either value is
49
+ * commonly much shorter than a ground-view cloud trace and would turn the
50
+ * distant layer into a flat wall.
51
+ */
52
+ export declare function cloudViewDistanceForQuality(qualityValue: CloudQuality): number;
53
+ /** rgba16float is four 16-bit channels (8 bytes per texel). */
54
+ export declare const CLOUD_SHADOW_BYTES_PER_TEXEL = 8;
55
+ /**
56
+ * Authored density is a normalized formation value. Convert it to a bounded
57
+ * per-world-unit extinction coefficient only at integration time; otherwise a
58
+ * long horizon step would become opaque after its first sample and expose
59
+ * marching bands instead of a participating volume.
60
+ */
61
+ export declare const CLOUD_EXTINCTION_COEFFICIENT = 0.03;
62
+ export declare const DEFAULT_CLOUD_LAYER: ValidatedCloudLayer;
63
+ /** Validate one detached CloudLayer POD before deriving cache or graph state. */
64
+ export declare function validateCloudLayer(input: CloudLayerAuthoring | Partial<CloudLayerData> | undefined): Result<ValidatedCloudLayer, CloudLayerError>;
65
+ /** Stable source identity used by cache, history and recovery receipts. */
66
+ export declare function cloudLayerSourceKey(params: ValidatedCloudLayer): string;
67
+ /**
68
+ * Stable identity for the reusable formation bases. Coverage, density and
69
+ * wind are evaluation inputs and deliberately do not rebuild the weather,
70
+ * body or erosion cache when they change continuously.
71
+ */
72
+ export declare function cloudLayerFormationKey(params: ValidatedCloudLayer): string;
73
+ //# sourceMappingURL=parameters.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parameters.d.ts","sourceRoot":"","sources":["../../src/cloud/parameters.ts"],"names":[],"mappings":"AAAA,OAAO,EAAW,KAAK,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,YAAY,EAGlB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,KAAK,eAAe,EAAmC,MAAM,iBAAiB,CAAC;AAExF,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,CAAC,MAAM,CAAC,CAAC;IAClC,QAAQ,CAAC,OAAO,CAAC,EAAE,YAAY,GAAG,MAAM,CAAC;IACzC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IACjD,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAC;IAC/B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,iFAAiF;IACjF,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC/B;AAED;;;GAGG;AACH,eAAO,MAAM,sBAAsB,EAAE,QAAQ,CAAC,MAAM,CAAC,YAAY,EAAE,mBAAmB,CAAC,CAyBnF,CAAC;AAEL;;;;;GAKG;AACH,wBAAgB,+BAA+B,CAAC,YAAY,EAAE,YAAY,GAAG,MAAM,CAElF;AAED;;;;;GAKG;AACH,wBAAgB,2BAA2B,CAAC,YAAY,EAAE,YAAY,GAAG,MAAM,CAE9E;AAED,+DAA+D;AAC/D,eAAO,MAAM,4BAA4B,IAAI,CAAC;AAE9C;;;;;GAKG;AACH,eAAO,MAAM,4BAA4B,OAAO,CAAC;AAEjD,eAAO,MAAM,mBAAmB,EAAE,mBAUhC,CAAC;AAqDH,iFAAiF;AACjF,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAAC,cAAc,CAAC,GAAG,SAAS,GAC/D,MAAM,CAAC,mBAAmB,EAAE,eAAe,CAAC,CAkD9C;AAED,2EAA2E;AAC3E,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,mBAAmB,GAAG,MAAM,CAevE;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,mBAAmB,GAAG,MAAM,CAS1E"}