@forgeax/engine-render 0.1.29 → 0.1.31

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 (350) hide show
  1. package/README.md +248 -21
  2. package/dist/assembly/atmosphere-shader-prewarm.d.ts +1 -1
  3. package/dist/assembly/atmosphere-shader-prewarm.d.ts.map +1 -1
  4. package/dist/assembly/device-feature-admission.d.ts +4 -3
  5. package/dist/assembly/device-feature-admission.d.ts.map +1 -1
  6. package/dist/assembly/dynamic-geometry-host.d.ts +6 -0
  7. package/dist/assembly/dynamic-geometry-host.d.ts.map +1 -1
  8. package/dist/assembly/host-contract.d.ts +6 -0
  9. package/dist/assembly/host-contract.d.ts.map +1 -1
  10. package/dist/assembly/material/assembly.d.ts +26 -3
  11. package/dist/assembly/material/assembly.d.ts.map +1 -1
  12. package/dist/assembly/material/pipeline-helpers.d.ts +24 -0
  13. package/dist/assembly/material/pipeline-helpers.d.ts.map +1 -0
  14. package/dist/assembly/material-shader-policy.d.ts.map +1 -1
  15. package/dist/assembly/recovery/device-loss-fanout.d.ts.map +1 -1
  16. package/dist/assembly/recovery/recovery-attempt.d.ts +1 -6
  17. package/dist/assembly/recovery/recovery-attempt.d.ts.map +1 -1
  18. package/dist/assembly/recovery/renderer-recover.d.ts +1 -0
  19. package/dist/assembly/recovery/renderer-recover.d.ts.map +1 -1
  20. package/dist/assembly/renderer-facade.d.ts.map +1 -1
  21. package/dist/assembly/shader-prewarm-policy.d.ts +1 -1
  22. package/dist/assembly/shader-prewarm-policy.d.ts.map +1 -1
  23. package/dist/assembly/webgpu-ready.d.ts +1 -1
  24. package/dist/assembly/webgpu-ready.d.ts.map +1 -1
  25. package/dist/assembly/webgpu-renderer.d.ts.map +1 -1
  26. package/dist/capture/scene-projection.d.ts +41 -0
  27. package/dist/capture/scene-projection.d.ts.map +1 -0
  28. package/dist/{chunk-BQVQOQJH.mjs → chunk-7MQ3XBSP.mjs} +20 -14
  29. package/dist/chunk-7MQ3XBSP.mjs.map +1 -0
  30. package/dist/{chunk-S4DNKQYQ.mjs → chunk-C3DDSXYK.mjs} +86 -33
  31. package/dist/chunk-C3DDSXYK.mjs.map +1 -0
  32. package/dist/{chunk-BTWH7BP4.mjs → chunk-FUUO4UND.mjs} +73 -29
  33. package/dist/chunk-FUUO4UND.mjs.map +1 -0
  34. package/dist/{chunk-NRNXBLIJ.mjs → chunk-JRBDO2KB.mjs} +46 -6
  35. package/dist/chunk-JRBDO2KB.mjs.map +1 -0
  36. package/dist/{chunk-NICXSHAT.mjs → chunk-MDQJPY3T.mjs} +8 -6
  37. package/dist/chunk-MDQJPY3T.mjs.map +1 -0
  38. package/dist/{chunk-JQTSEI3Z.mjs → chunk-NO4WZCAH.mjs} +3 -3
  39. package/dist/chunk-NO4WZCAH.mjs.map +1 -0
  40. package/dist/{chunk-FUODTTIA.mjs → chunk-PSEQ6QPH.mjs} +621 -108
  41. package/dist/chunk-PSEQ6QPH.mjs.map +1 -0
  42. package/dist/{chunk-33XZFTQU.mjs → chunk-S5UE2T5P.mjs} +48 -6
  43. package/dist/chunk-S5UE2T5P.mjs.map +1 -0
  44. package/dist/components/directional-light.d.ts +2 -2
  45. package/dist/components/dynamic-resolution.d.ts +15 -0
  46. package/dist/components/dynamic-resolution.d.ts.map +1 -0
  47. package/dist/components/index.d.ts +1 -0
  48. package/dist/components/index.d.ts.map +1 -1
  49. package/dist/components/light-helpers.d.ts.map +1 -1
  50. package/dist/components/reflection-probe.d.ts +1 -0
  51. package/dist/components/reflection-probe.d.ts.map +1 -1
  52. package/dist/components/skylight.d.ts +4 -1
  53. package/dist/components/skylight.d.ts.map +1 -1
  54. package/dist/construct-renderer.mjs +2172 -796
  55. package/dist/construct-renderer.mjs.map +1 -1
  56. package/dist/device/device-scope.d.ts +2 -0
  57. package/dist/device/device-scope.d.ts.map +1 -1
  58. package/dist/device/gpu-residency.d.ts +2 -2
  59. package/dist/device/gpu-residency.d.ts.map +1 -1
  60. package/dist/dynamic-geometry.d.ts.map +1 -1
  61. package/dist/environment/background.d.ts +3 -2
  62. package/dist/environment/background.d.ts.map +1 -1
  63. package/dist/environment/ibl.d.ts +11 -0
  64. package/dist/environment/ibl.d.ts.map +1 -0
  65. package/dist/environment/storage.d.ts +14 -0
  66. package/dist/environment/storage.d.ts.map +1 -0
  67. package/dist/errors/gpu-driven.d.ts +2 -2
  68. package/dist/errors/gpu-driven.d.ts.map +1 -1
  69. package/dist/errors/render.d.ts +35 -1
  70. package/dist/errors/render.d.ts.map +1 -1
  71. package/dist/extract/camera.d.ts.map +1 -1
  72. package/dist/extract/contracts.d.ts +7 -0
  73. package/dist/extract/contracts.d.ts.map +1 -1
  74. package/dist/extract/gpu-driven.d.ts +18 -1
  75. package/dist/extract/gpu-driven.d.ts.map +1 -1
  76. package/dist/gpu-driven/batch-topology.d.ts.map +1 -1
  77. package/dist/gpu-driven/material-artifacts.d.ts +24 -0
  78. package/dist/gpu-driven/material-artifacts.d.ts.map +1 -0
  79. package/dist/gpu-driven/material-bindings.d.ts +7 -0
  80. package/dist/gpu-driven/material-bindings.d.ts.map +1 -0
  81. package/dist/gpu-driven/prepared-draw.d.ts +2 -0
  82. package/dist/gpu-driven/prepared-draw.d.ts.map +1 -1
  83. package/dist/gpu-driven/production-raster.d.ts +27 -6
  84. package/dist/gpu-driven/production-raster.d.ts.map +1 -1
  85. package/dist/gpu-resource.d.ts +5 -2
  86. package/dist/gpu-resource.d.ts.map +1 -1
  87. package/dist/gpu-scene-schema.d.ts.map +1 -1
  88. package/dist/gpu-scene.d.ts.map +1 -1
  89. package/dist/hdrp-buffers.d.ts +3 -0
  90. package/dist/hdrp-buffers.d.ts.map +1 -1
  91. package/dist/ibl/IblPipelineCache.d.ts +2 -0
  92. package/dist/ibl/IblPipelineCache.d.ts.map +1 -1
  93. package/dist/ibl/skylight-bind-group.d.ts +3 -1
  94. package/dist/ibl/skylight-bind-group.d.ts.map +1 -1
  95. package/dist/index.d.ts +83 -81
  96. package/dist/index.d.ts.map +1 -1
  97. package/dist/index.mjs +7 -5
  98. package/dist/index.mjs.map +1 -1
  99. package/dist/inspection-types.d.ts +16 -1
  100. package/dist/inspection-types.d.ts.map +1 -1
  101. package/dist/internal.mjs +6 -6
  102. package/dist/material-row.d.ts +10 -0
  103. package/dist/material-row.d.ts.map +1 -1
  104. package/dist/pbr-pipeline.d.ts +3 -1
  105. package/dist/pbr-pipeline.d.ts.map +1 -1
  106. package/dist/pipeline/render-extent.d.ts +51 -0
  107. package/dist/pipeline/render-extent.d.ts.map +1 -0
  108. package/dist/pipeline/standard-build-context.d.ts +7 -0
  109. package/dist/pipeline/standard-build-context.d.ts.map +1 -0
  110. package/dist/pipeline/standard-forward-lane.d.ts +3 -2
  111. package/dist/pipeline/standard-forward-lane.d.ts.map +1 -1
  112. package/dist/pipeline/standard-pipeline.d.ts.map +1 -1
  113. package/dist/pipeline/standard-post.d.ts +1 -1
  114. package/dist/pipeline/standard-post.d.ts.map +1 -1
  115. package/dist/pipeline/standard-profile.d.ts +2 -1
  116. package/dist/pipeline/standard-profile.d.ts.map +1 -1
  117. package/dist/pipeline-builder.d.ts +3 -1
  118. package/dist/pipeline-builder.d.ts.map +1 -1
  119. package/dist/plugin.d.ts.map +1 -1
  120. package/dist/post-process-errors.d.ts +9 -2
  121. package/dist/post-process-errors.d.ts.map +1 -1
  122. package/dist/prepare/extended-lighting/resources.d.ts +3 -3
  123. package/dist/record/frame-lighting.d.ts.map +1 -1
  124. package/dist/record/frame-snapshot.d.ts +3 -8
  125. package/dist/record/frame-snapshot.d.ts.map +1 -1
  126. package/dist/record/frame.d.ts +3 -2
  127. package/dist/record/frame.d.ts.map +1 -1
  128. package/dist/record/main-pass-geometry.d.ts +7 -2
  129. package/dist/record/main-pass-geometry.d.ts.map +1 -1
  130. package/dist/record/main-pass-material.d.ts +6 -3
  131. package/dist/record/main-pass-material.d.ts.map +1 -1
  132. package/dist/record/main-pass.d.ts +2 -0
  133. package/dist/record/main-pass.d.ts.map +1 -1
  134. package/dist/record/prepared-material-bindings.d.ts.map +1 -1
  135. package/dist/record/recovery-pipeline.d.ts +9 -1
  136. package/dist/record/recovery-pipeline.d.ts.map +1 -1
  137. package/dist/record/render-context.d.ts +34 -3
  138. package/dist/record/render-context.d.ts.map +1 -1
  139. package/dist/record/shadow-pass.d.ts +2 -2
  140. package/dist/record/shadow-pass.d.ts.map +1 -1
  141. package/dist/record/typed-frame-graph.d.ts +5 -2
  142. package/dist/record/typed-frame-graph.d.ts.map +1 -1
  143. package/dist/recovery/render-system-candidate.d.ts.map +1 -1
  144. package/dist/reflection/background.d.ts +14 -0
  145. package/dist/reflection/background.d.ts.map +1 -0
  146. package/dist/reflection/filter.d.ts.map +1 -1
  147. package/dist/reflection/projection.d.ts +1 -0
  148. package/dist/reflection/projection.d.ts.map +1 -1
  149. package/dist/reflection/record-owner.d.ts +4 -2
  150. package/dist/reflection/record-owner.d.ts.map +1 -1
  151. package/dist/render-contract.d.ts +47 -32
  152. package/dist/render-contract.d.ts.map +1 -1
  153. package/dist/render-graph-primitives.d.ts.map +1 -1
  154. package/dist/render-pipeline.d.ts +17 -1
  155. package/dist/render-pipeline.d.ts.map +1 -1
  156. package/dist/render-system-extract-tail.d.ts +9 -2
  157. package/dist/render-system-extract-tail.d.ts.map +1 -1
  158. package/dist/render-system-extract.d.ts +29 -2
  159. package/dist/render-system-extract.d.ts.map +1 -1
  160. package/dist/render-system.d.ts.map +1 -1
  161. package/dist/scene/render-scene.d.ts +2 -0
  162. package/dist/scene/render-scene.d.ts.map +1 -1
  163. package/dist/ssao-config.d.ts +8 -6
  164. package/dist/ssao-config.d.ts.map +1 -1
  165. package/dist/ssr/graph.d.ts.map +1 -1
  166. package/dist/temporal/gpu.d.ts.map +1 -1
  167. package/dist/temporal/index.mjs +5 -5
  168. package/dist/temporal/standard-scene-data.d.ts +2 -1
  169. package/dist/temporal/standard-scene-data.d.ts.map +1 -1
  170. package/dist/temporal/target-coverage-attachment.d.ts +15 -0
  171. package/dist/temporal/target-coverage-attachment.d.ts.map +1 -0
  172. package/dist/temporal/temporal-view.d.ts +6 -2
  173. package/dist/temporal/temporal-view.d.ts.map +1 -1
  174. package/dist/temporal/view.d.ts +6 -0
  175. package/dist/temporal/view.d.ts.map +1 -1
  176. package/dist/typed-render-graph-primitives.d.ts +6 -0
  177. package/dist/typed-render-graph-primitives.d.ts.map +1 -1
  178. package/dist/volume/component.d.ts +1 -0
  179. package/dist/volume/component.d.ts.map +1 -1
  180. package/package.json +21 -21
  181. package/src/__tests__/capture-scene-projection.unit.test.ts +24 -0
  182. package/src/__tests__/device-feature-admission.unit.test.ts +15 -24
  183. package/src/__tests__/device-loss-fanout.unit.test.ts +43 -0
  184. package/src/__tests__/dynamic-geometry-batch.unit.test.ts +157 -0
  185. package/src/__tests__/dynamic-geometry.unit.test.ts +36 -0
  186. package/src/__tests__/dynamic-resolution-plugin.integration.test.ts +81 -0
  187. package/src/__tests__/factory-contract.integration.test.ts +164 -1
  188. package/src/__tests__/frame-observation.contract.test.ts +21 -0
  189. package/src/__tests__/gpu-driven-baseline.characterization.test.ts +1 -1
  190. package/src/__tests__/gpu-driven-batch-topology-pbr.unit.test.ts +20 -1
  191. package/src/__tests__/gpu-driven-lod-cache.unit.test.ts +0 -3
  192. package/src/__tests__/gpu-driven-mesh-world-identity.unit.test.ts +0 -3
  193. package/src/__tests__/gpu-driven-pbr.dawn.test.ts +11 -6
  194. package/src/__tests__/gpu-driven-production.integration.test.ts +13 -25
  195. package/src/__tests__/gpu-driven-view.unit.test.ts +1 -0
  196. package/src/__tests__/gpu-pass-timing-browser-runner.ts +15 -0
  197. package/src/__tests__/gpu-pass-timing.browser.test.ts +14 -1
  198. package/src/__tests__/gpu-resource-scope.unit.test.ts +71 -0
  199. package/src/__tests__/gpu-scene.unit.test.ts +55 -1
  200. package/src/__tests__/materials-standard-contract.unit.test.ts +5 -1
  201. package/src/__tests__/mesh-submission-lifetime.unit.test.ts +8 -0
  202. package/src/__tests__/morph-neutral-admission.unit.test.ts +19 -0
  203. package/src/__tests__/physical-clearcoat.integration.test.ts +6 -1
  204. package/src/__tests__/post-process-error-policy-owner.unit.test.ts +6 -5
  205. package/src/__tests__/prepared-gpu-driven-pbr.integration.test.ts +216 -5
  206. package/src/__tests__/reflection-fallback-async-publication.unit.test.ts +68 -1
  207. package/src/__tests__/reflection-probe-authoring.unit.test.ts +12 -10
  208. package/src/__tests__/reflection-probe-box.dawn.test.ts +71 -0
  209. package/src/__tests__/reflection-probe-filter.unit.test.ts +9 -0
  210. package/src/__tests__/reflection-probe-orientation.dawn.test.ts +294 -0
  211. package/src/__tests__/reflection-probe-record-owner.characterization.unit.test.ts +5 -15
  212. package/src/__tests__/reflection-probe-updates.unit.test.ts +129 -0
  213. package/src/__tests__/render-error-code-owner.test-d.ts +3 -0
  214. package/src/__tests__/render-error-detail-ssot.test-d.ts +5 -1
  215. package/src/__tests__/render-extent.unit.test.ts +63 -0
  216. package/src/__tests__/render-scene-projection.unit.test.ts +3 -2
  217. package/src/__tests__/renderer-factory-material-contract.unit.test.ts +173 -9
  218. package/src/__tests__/shader-manifest-fixture.ts +6 -0
  219. package/src/__tests__/skinned-shadow-caster.test.ts +8 -1
  220. package/src/__tests__/specular-environment-scale.dawn.test.ts +4 -2
  221. package/src/__tests__/ssao-config.unit.test.ts +7 -0
  222. package/src/__tests__/ssao-surface.dawn.test.ts +252 -0
  223. package/src/__tests__/ssr-cube-silhouette.dawn.test.ts +270 -0
  224. package/src/__tests__/ssr-gpu-dispatch.browser.test.ts +51 -43
  225. package/src/__tests__/ssr-gpu-dispatch.dawn.test.ts +4 -0
  226. package/src/__tests__/ssr-gpu-dispatch.ts +10 -5
  227. package/src/__tests__/ssr-graph-topology.unit.test.ts +2 -2
  228. package/src/__tests__/ssr-hiz.browser.test.ts +1 -1
  229. package/src/__tests__/ssr-hiz.dawn.test.ts +1 -1
  230. package/src/__tests__/ssr-spatial.integration.test.ts +1 -1
  231. package/src/__tests__/standard-cluster-variant-resolution.unit.test.ts +40 -0
  232. package/src/__tests__/standard-pbr-artifact-assembly.unit.test.ts +238 -1
  233. package/src/__tests__/standard-profile-oracle.unit.test.ts +27 -0
  234. package/src/__tests__/taa-coverage-edge.dawn.test.ts +6 -0
  235. package/src/__tests__/taa-neighborhood-equivalence.dawn.test.ts +5 -1
  236. package/src/__tests__/taa-stability-feedback.ts +1 -0
  237. package/src/__tests__/temporal-reset-matrix.unit.test.ts +2 -2
  238. package/src/__tests__/temporal-view.unit.test.ts +17 -0
  239. package/src/__tests__/transmission-prewarm.unit.test.ts +57 -0
  240. package/src/__tests__/volumetric-fog-stage-readback.dawn.test.ts +62 -13
  241. package/src/__tests__/volumetric-fog-topology.unit.test.ts +7 -7
  242. package/src/assembly/atmosphere-shader-prewarm.ts +3 -2
  243. package/src/assembly/device-feature-admission.ts +9 -4
  244. package/src/assembly/dynamic-geometry-host.ts +236 -163
  245. package/src/assembly/host-contract.ts +9 -0
  246. package/src/assembly/material/assembly.ts +350 -6
  247. package/src/assembly/material/pipeline-helpers.ts +100 -0
  248. package/src/assembly/material-shader-policy.ts +39 -18
  249. package/src/assembly/recovery/device-loss-fanout.ts +5 -0
  250. package/src/assembly/recovery/recovery-attempt.ts +2 -29
  251. package/src/assembly/recovery/renderer-recover.ts +6 -9
  252. package/src/assembly/renderer-facade.ts +22 -1
  253. package/src/assembly/shader-prewarm-policy.ts +14 -1
  254. package/src/assembly/webgpu-ready.ts +28 -19
  255. package/src/assembly/webgpu-renderer.ts +95 -95
  256. package/src/capture/scene-projection.ts +37 -0
  257. package/src/components/__tests__/dynamic-resolution-schema.unit.test.ts +65 -0
  258. package/src/components/__tests__/light-radius.integration.test.ts +59 -0
  259. package/src/components/directional-light.ts +3 -3
  260. package/src/components/dynamic-resolution.ts +67 -0
  261. package/src/components/index.ts +1 -0
  262. package/src/components/light-helpers.ts +6 -1
  263. package/src/components/reflection-probe.ts +1 -0
  264. package/src/components/skylight.ts +4 -1
  265. package/src/device/device-scope.ts +13 -7
  266. package/src/device/gpu-residency.ts +11 -11
  267. package/src/dynamic-geometry.ts +13 -8
  268. package/src/environment/background.ts +30 -12
  269. package/src/environment/ibl.ts +211 -0
  270. package/src/environment/storage.ts +63 -0
  271. package/src/errors/gpu-driven.ts +10 -3
  272. package/src/errors/render.ts +64 -0
  273. package/src/extract/camera.ts +12 -1
  274. package/src/extract/contracts.ts +10 -0
  275. package/src/extract/gpu-driven.ts +72 -17
  276. package/src/gpu-driven/batch-topology.ts +33 -11
  277. package/src/gpu-driven/material-artifacts.ts +179 -0
  278. package/src/gpu-driven/material-bindings.ts +41 -0
  279. package/src/gpu-driven/prepared-draw.ts +13 -2
  280. package/src/gpu-driven/production-raster.ts +681 -164
  281. package/src/gpu-resource.ts +21 -2
  282. package/src/gpu-scene-schema.ts +11 -2
  283. package/src/gpu-scene.ts +14 -3
  284. package/src/hdrp-buffers.ts +69 -0
  285. package/src/ibl/IblPipelineCache.ts +5 -0
  286. package/src/ibl/skylight-bind-group.ts +27 -17
  287. package/src/index.ts +92 -81
  288. package/src/inspection-types.ts +16 -1
  289. package/src/material-row.ts +60 -11
  290. package/src/pbr-pipeline.ts +13 -2
  291. package/src/pipeline/render-extent.ts +111 -0
  292. package/src/pipeline/standard-build-context.ts +7 -0
  293. package/src/pipeline/standard-forward-lane.ts +41 -24
  294. package/src/pipeline/standard-pipeline.ts +47 -18
  295. package/src/pipeline/standard-post.ts +4 -0
  296. package/src/pipeline/standard-profile.ts +2 -1
  297. package/src/pipeline-builder.ts +12 -3
  298. package/src/plugin.ts +4 -0
  299. package/src/post-process-errors.ts +26 -18
  300. package/src/prepare/__tests__/extended-lighting-capability.unit.test.ts +2 -2
  301. package/src/prepare/extended-lighting/resources.ts +3 -3
  302. package/src/record/__tests__/main-pass-geometry.unit.test.ts +68 -0
  303. package/src/record/__tests__/material-bg-cache.unit.test.ts +45 -0
  304. package/src/record/__tests__/material-slot-plan.unit.test.ts +9 -0
  305. package/src/record/__tests__/recovery-pipeline.unit.test.ts +47 -1
  306. package/src/record/__tests__/sprite-material-abi.unit.test.ts +7 -3
  307. package/src/record/__tests__/temporal-payload.unit.test.ts +2 -0
  308. package/src/record/frame-lighting.ts +3 -1
  309. package/src/record/frame-snapshot.ts +13 -11
  310. package/src/record/frame.ts +150 -74
  311. package/src/record/main-pass-geometry.ts +60 -9
  312. package/src/record/main-pass-material.ts +47 -19
  313. package/src/record/main-pass.ts +63 -4
  314. package/src/record/prepared-material-bindings.ts +4 -0
  315. package/src/record/recovery-pipeline.ts +56 -0
  316. package/src/record/render-context.ts +34 -3
  317. package/src/record/shadow-pass.ts +187 -50
  318. package/src/record/typed-frame-graph.ts +60 -3
  319. package/src/record/view-ubo.ts +1 -1
  320. package/src/recovery/render-system-candidate.ts +54 -11
  321. package/src/reflection/background.ts +151 -0
  322. package/src/reflection/filter.ts +3 -0
  323. package/src/reflection/projection.ts +16 -6
  324. package/src/reflection/record-owner.ts +282 -86
  325. package/src/render-contract.ts +50 -32
  326. package/src/render-graph-primitives.ts +28 -3
  327. package/src/render-pipeline.ts +20 -1
  328. package/src/render-system-extract-tail.ts +23 -3
  329. package/src/render-system-extract.ts +127 -42
  330. package/src/render-system.ts +35 -7
  331. package/src/scene/render-scene.ts +8 -0
  332. package/src/ssao-config.ts +25 -8
  333. package/src/ssr/graph.ts +78 -58
  334. package/src/temporal/gpu.ts +5 -0
  335. package/src/temporal/standard-scene-data.ts +7 -1
  336. package/src/temporal/target-coverage-attachment.ts +72 -0
  337. package/src/temporal/temporal-view.ts +10 -4
  338. package/src/temporal/view.ts +12 -2
  339. package/src/transmission/__tests__/standard-transmission.dawn.test.ts +24 -9
  340. package/src/typed-render-graph-primitives.ts +38 -0
  341. package/src/volume/component.ts +1 -0
  342. package/src/volume/passes.ts +3 -3
  343. package/dist/chunk-33XZFTQU.mjs.map +0 -1
  344. package/dist/chunk-BQVQOQJH.mjs.map +0 -1
  345. package/dist/chunk-BTWH7BP4.mjs.map +0 -1
  346. package/dist/chunk-FUODTTIA.mjs.map +0 -1
  347. package/dist/chunk-JQTSEI3Z.mjs.map +0 -1
  348. package/dist/chunk-NICXSHAT.mjs.map +0 -1
  349. package/dist/chunk-NRNXBLIJ.mjs.map +0 -1
  350. package/dist/chunk-S4DNKQYQ.mjs.map +0 -1
@@ -1,6 +1,10 @@
1
1
  import { createStandardPbrArtifactReceipt } from '@forgeax/engine-shader';
2
2
  import { describe, expect, it } from 'vitest';
3
- import { assembleStandardPbrArtifact } from '../assembly/material/assembly';
3
+ import {
4
+ assembleStandardPbrArtifact,
5
+ resolveMaterialShaderArtifact,
6
+ resolveStandardPbrArtifact,
7
+ } from '../assembly/material/assembly';
4
8
 
5
9
  const source = '@vertex fn vs_main() -> @builtin(position) vec4<f32> { return vec4(0.0); }';
6
10
 
@@ -23,6 +27,24 @@ describe('Standard PBR artifact assembly', () => {
23
27
  );
24
28
  });
25
29
 
30
+ it('retains shader UV aliases beyond the minimum geometry receipt after recovery', () => {
31
+ const receipt = createStandardPbrArtifactReceipt();
32
+ const wgsl = `struct VsIn { @location(2) uv: vec2<f32>, @location(12) uv7: vec2<f32>, }
33
+ @vertex fn vs_scene_index(input: VsIn) -> @builtin(position) vec4<f32> { return vec4(input.uv, 0., 1.); }`;
34
+ const artifact = resolveMaterialShaderArtifact(
35
+ 'forgeax::default-standard-pbr',
36
+ {
37
+ findMaterialArtifact: () => ({
38
+ ok: true,
39
+ value: { source: wgsl, paramSchema: [], receipt },
40
+ }),
41
+ } as unknown as Parameters<typeof resolveMaterialShaderArtifact>[1],
42
+ { address: 'scene-index' },
43
+ );
44
+ expect(artifact?.uvSetCount).toBe(8);
45
+ expect(artifact?.receipt).toBe(receipt);
46
+ });
47
+
26
48
  it('adds the skin palette receipt facts for the skinned registry entry', () => {
27
49
  const artifact = assembleStandardPbrArtifact('forgeax::default-standard-pbr-skin', source);
28
50
  const receipt = createStandardPbrArtifactReceipt(true);
@@ -35,4 +57,219 @@ describe('Standard PBR artifact assembly', () => {
35
57
  it('rejects unsupported shader identifiers without manufacturing an artifact', () => {
36
58
  expect(assembleStandardPbrArtifact('forgeax::default-unlit', source)).toBeUndefined();
37
59
  });
60
+
61
+ it('fails closed when a geometry-specific color fact has no matching producer receipt', () => {
62
+ expect(
63
+ assembleStandardPbrArtifact(
64
+ 'forgeax::default-standard-pbr',
65
+ { source, paramSchema: [] },
66
+ { vertexColorAvailable: true },
67
+ ),
68
+ ).toBeUndefined();
69
+ expect(
70
+ assembleStandardPbrArtifact(
71
+ 'forgeax::default-standard-pbr',
72
+ { source, paramSchema: [], receipt: createStandardPbrArtifactReceipt() },
73
+ { vertexColorAvailable: true },
74
+ ),
75
+ ).toBeUndefined();
76
+ });
77
+
78
+ it('selects the published COLOR_0 ABI and keeps colored/plain identities apart', () => {
79
+ const plain = createStandardPbrArtifactReceipt();
80
+ const colored = createStandardPbrArtifactReceipt(false, true);
81
+ const shader = {
82
+ findMaterialArtifact: () => ({
83
+ ok: true as const,
84
+ value: { source: 'fallback', paramSchema: [], receipt: plain },
85
+ }),
86
+ materialShaderManifestEntries: () => [
87
+ {
88
+ identifier: 'forgeax::default-standard-pbr',
89
+ sourcePath: 'default-standard-pbr.wgsl',
90
+ composedWgsl: 'fallback',
91
+ paramSchema: '[]',
92
+ variants: [
93
+ {
94
+ definesKey: 'VERTEX_COLOR_AVAILABLE=false',
95
+ defines: { VERTEX_COLOR_AVAILABLE: false },
96
+ composedWgsl: 'plain-variant',
97
+ receipt: plain,
98
+ },
99
+ {
100
+ definesKey: 'VERTEX_COLOR_AVAILABLE=true',
101
+ defines: { VERTEX_COLOR_AVAILABLE: true },
102
+ composedWgsl: 'colored-variant',
103
+ receipt: colored,
104
+ },
105
+ ],
106
+ },
107
+ ],
108
+ } as unknown as Parameters<typeof resolveStandardPbrArtifact>[1];
109
+
110
+ const coloredArtifact = resolveStandardPbrArtifact('forgeax::default-standard-pbr', shader, {
111
+ vertexColorAvailable: true,
112
+ });
113
+ const plainArtifact = resolveStandardPbrArtifact('forgeax::default-standard-pbr', shader, {
114
+ vertexColorAvailable: false,
115
+ });
116
+
117
+ expect(coloredArtifact?.wgsl).toBe('colored-variant');
118
+ expect(coloredArtifact?.receipt).toEqual(colored);
119
+ expect(coloredArtifact?.receipt?.vertexInputs).toContainEqual({
120
+ semantic: 'color',
121
+ location: 13,
122
+ format: 'float32x4',
123
+ });
124
+ expect(plainArtifact?.wgsl).toBe('plain-variant');
125
+ expect(plainArtifact?.receipt).toEqual(plain);
126
+ expect(plainArtifact?.receipt?.vertexInputs).not.toContainEqual(
127
+ expect.objectContaining({ semantic: 'color' }),
128
+ );
129
+ expect(coloredArtifact?.receipt?.receiptIdentity).not.toBe(
130
+ plainArtifact?.receipt?.receiptIdentity,
131
+ );
132
+
133
+ const custom = resolveMaterialShaderArtifact(
134
+ 'game::custom-surface',
135
+ {
136
+ findMaterialArtifact: () => ({
137
+ ok: true as const,
138
+ value: { source: 'custom-fallback', paramSchema: [], receipt: plain },
139
+ }),
140
+ materialShaderManifestEntries: () => [
141
+ {
142
+ identifier: 'game::custom-surface',
143
+ sourcePath: 'custom.wgsl',
144
+ composedWgsl: 'custom-fallback',
145
+ paramSchema: '[]',
146
+ variants: [
147
+ {
148
+ definesKey: 'VERTEX_COLOR_AVAILABLE=true',
149
+ defines: { VERTEX_COLOR_AVAILABLE: true },
150
+ composedWgsl: 'custom-colored',
151
+ receipt: colored,
152
+ },
153
+ ],
154
+ },
155
+ ],
156
+ } as unknown as Parameters<typeof resolveMaterialShaderArtifact>[1],
157
+ { vertexColorAvailable: true },
158
+ );
159
+ expect(custom?.wgsl).toBe('custom-colored');
160
+ expect(custom?.receipt).toEqual(colored);
161
+ });
162
+
163
+ it('routes built-in Standard shadow requests through the shadow-caster artifact', () => {
164
+ const rigidPlain = createStandardPbrArtifactReceipt();
165
+ const rigidColor = createStandardPbrArtifactReceipt(false, true);
166
+ const skinPlain = createStandardPbrArtifactReceipt(true);
167
+ const shadowEntry = {
168
+ identifier: 'forgeax::default-shadow-caster',
169
+ sourcePath: 'default-shadow-caster.wgsl',
170
+ composedWgsl: 'shadow-primary',
171
+ paramSchema: '[]',
172
+ variants: [
173
+ {
174
+ definesKey: 'SKINNING_DISABLED=true+VERTEX_COLOR_AVAILABLE=false',
175
+ defines: { SKINNING_DISABLED: true, VERTEX_COLOR_AVAILABLE: false },
176
+ composedWgsl: 'shadow-rigid-plain-with-fs_shadow',
177
+ receipt: rigidPlain,
178
+ },
179
+ {
180
+ definesKey: 'SKINNING_DISABLED=true+VERTEX_COLOR_AVAILABLE=true',
181
+ defines: { SKINNING_DISABLED: true, VERTEX_COLOR_AVAILABLE: true },
182
+ composedWgsl: 'shadow-rigid-color-with-fs_shadow',
183
+ receipt: rigidColor,
184
+ },
185
+ {
186
+ definesKey: 'SKINNING_DISABLED=false+VERTEX_COLOR_AVAILABLE=false',
187
+ defines: { SKINNING_DISABLED: false, VERTEX_COLOR_AVAILABLE: false },
188
+ composedWgsl: 'shadow-skin-plain-with-fs_shadow',
189
+ receipt: skinPlain,
190
+ },
191
+ ],
192
+ };
193
+ const standardEntry = {
194
+ identifier: 'forgeax::default-standard-pbr',
195
+ sourcePath: 'default-standard-pbr.wgsl',
196
+ composedWgsl: 'standard-forward-without-fs_shadow',
197
+ paramSchema: '[]',
198
+ variants: [],
199
+ };
200
+
201
+ for (const entries of [
202
+ [standardEntry, shadowEntry],
203
+ [shadowEntry, standardEntry],
204
+ ]) {
205
+ const shader = {
206
+ findMaterialArtifact: (identifier: string) => ({
207
+ ok: true as const,
208
+ value: {
209
+ source: identifier === 'forgeax::default-shadow-caster' ? 'shadow-primary' : 'standard',
210
+ paramSchema: [],
211
+ receipt: rigidPlain,
212
+ },
213
+ }),
214
+ materialShaderManifestEntries: () => entries,
215
+ } as unknown as Parameters<typeof resolveMaterialShaderArtifact>[1];
216
+
217
+ const rigidColorArtifact = resolveMaterialShaderArtifact(
218
+ 'forgeax::default-standard-pbr',
219
+ shader,
220
+ {
221
+ address: 'scene-index',
222
+ deformation: 'rigid',
223
+ pass: 'shadow',
224
+ vertexColorAvailable: true,
225
+ },
226
+ );
227
+ const skinPlainArtifact = resolveMaterialShaderArtifact(
228
+ 'forgeax::default-standard-pbr-skin',
229
+ shader,
230
+ { address: 'scene-index', deformation: 'skin', pass: 'shadow' },
231
+ );
232
+
233
+ expect(rigidColorArtifact?.material).toBe('forgeax::default-shadow-caster');
234
+ expect(rigidColorArtifact?.fragmentEntry).toBe('fs_shadow');
235
+ expect(rigidColorArtifact?.wgsl).toBe('shadow-rigid-color-with-fs_shadow');
236
+ expect(rigidColorArtifact?.receipt).toEqual(rigidColor);
237
+ expect(skinPlainArtifact?.material).toBe('forgeax::default-shadow-caster');
238
+ expect(skinPlainArtifact?.wgsl).toBe('shadow-skin-plain-with-fs_shadow');
239
+ expect(skinPlainArtifact?.receipt).toEqual(skinPlain);
240
+ }
241
+ });
242
+ it('reuses immutable publications and invalidates changed registry or manifest identities', () => {
243
+ const receipt = createStandardPbrArtifactReceipt();
244
+ let entry = { source, paramSchema: [], receipt };
245
+ let manifest = {
246
+ identifier: 'game::surface',
247
+ sourcePath: 'surface.wgsl',
248
+ composedWgsl: source,
249
+ paramSchema: '[]',
250
+ variants: [{ definesKey: '', defines: {}, composedWgsl: 'first', receipt }],
251
+ };
252
+ const registry = {
253
+ findMaterialArtifact: () => ({ ok: true, value: entry }),
254
+ materialShaderManifestEntries: () => [manifest].values(),
255
+ } as unknown as Parameters<typeof resolveMaterialShaderArtifact>[1];
256
+ const resolve = () =>
257
+ resolveMaterialShaderArtifact('game::surface', registry, { address: 'scene-index' });
258
+ const first = resolve();
259
+ expect(first?.wgsl).toBe('first');
260
+ expect(resolve()).toBe(first);
261
+ expect(
262
+ resolveMaterialShaderArtifact('game::surface', registry, { address: 'direct' })?.vertexEntry,
263
+ ).toBe(receipt.directEntry);
264
+ manifest = {
265
+ ...manifest,
266
+ variants: [{ definesKey: '', defines: {}, receipt, composedWgsl: 'second' }],
267
+ };
268
+ const second = resolve();
269
+ expect(second).not.toBe(first);
270
+ expect(second?.wgsl).toBe('second');
271
+ expect(resolve()).toBe(second);
272
+ entry = { ...entry };
273
+ expect(resolve()).not.toBe(second);
274
+ });
38
275
  });
@@ -1,4 +1,5 @@
1
1
  import { describe, expect, it } from 'vitest';
2
+ import { freezeRenderProfile, validateRenderProfile } from '../assembly/renderer-facade';
2
3
  import { AUTO_EXPOSURE_PRESET_V1 } from '../pipeline/standard-output/auto-exposure/preset';
3
4
  import {
4
5
  DEFAULT_STANDARD_PROFILE,
@@ -77,3 +78,29 @@ describe('Standard profile oracle', () => {
77
78
  expect(AUTO_EXPOSURE_PRESET_V1.histogramBins).toBe(256);
78
79
  });
79
80
  });
81
+
82
+ describe('SSAO public profile', () => {
83
+ it('preserves immutable AO parameters through the public profile', () => {
84
+ const ssao = { radius: 0.8, bias: 0.01, intensity: 1.5, quality: 'low' as const };
85
+ const profile = { ...DEFAULT_STANDARD_PROFILE, renderPath: 'deferred' as const, ssao };
86
+ expect(validateRenderProfile(profile)).toBeUndefined();
87
+ const frozen = freezeRenderProfile(profile);
88
+ ssao.radius = 2;
89
+ expect(frozen.ssao).toEqual({ radius: 0.8, bias: 0.01, intensity: 1.5, quality: 'low' });
90
+ expect(Object.isFrozen(frozen.ssao)).toBe(true);
91
+ });
92
+ it('rejects unavailable normals and malformed parameter objects', () => {
93
+ expect(validateRenderProfile({ ...DEFAULT_STANDARD_PROFILE, ssao: true })).toContain(
94
+ 'deferred',
95
+ );
96
+ for (const ssao of [[], null, { quality: 'ultra' }, { radius: Number.NaN }, { other: 1 }]) {
97
+ expect(
98
+ validateRenderProfile({
99
+ ...DEFAULT_STANDARD_PROFILE,
100
+ renderPath: 'deferred',
101
+ ssao,
102
+ } as unknown as StandardProfile),
103
+ ).toBeTypeOf('string');
104
+ }
105
+ });
106
+ });
@@ -92,6 +92,11 @@ async function resolveCoverage(source: string, edge: boolean) {
92
92
  sampler: { type: 'filtering' as const },
93
93
  })),
94
94
  { binding: 8, visibility: GPU_SHADER_STAGE_COMPUTE, buffer: { type: 'uniform' } },
95
+ {
96
+ binding: 11,
97
+ visibility: GPU_SHADER_STAGE_COMPUTE,
98
+ texture: { sampleType: 'unfilterable-float' as const, viewDimension: '2d' as const },
99
+ },
95
100
  ],
96
101
  })
97
102
  .unwrap();
@@ -124,6 +129,7 @@ async function resolveCoverage(source: string, edge: boolean) {
124
129
  { binding: 8, resource: { kind: 'buffer', value: { buffer: params } } },
125
130
  { binding: 9, resource: { kind: 'textureView', value: age.view } },
126
131
  { binding: 10, resource: { kind: 'textureView', value: age.view } },
132
+ { binding: 11, resource: { kind: 'textureView', value: currentTemporal.view } },
127
133
  ],
128
134
  })
129
135
  .unwrap();
@@ -522,7 +522,7 @@ async function runPair(
522
522
  visibility: GPU_SHADER_STAGE_FRAGMENT,
523
523
  texture: { sampleType: 'float' as const, viewDimension: '2d' as const },
524
524
  })),
525
- ...[4, 6, 10].map((binding) => ({
525
+ ...[4, 6, 10, 11].map((binding) => ({
526
526
  binding,
527
527
  visibility: GPU_SHADER_STAGE_FRAGMENT,
528
528
  texture: { sampleType: 'unfilterable-float' as const, viewDimension: '2d' as const },
@@ -586,6 +586,10 @@ async function runPair(
586
586
  { binding: 8, resource: { kind: 'buffer', value: { buffer: params } } },
587
587
  { binding: 9, resource: { kind: 'textureView', value: historyStability.view } },
588
588
  { binding: 10, resource: { kind: 'textureView', value: secondaryReactivity.view } },
589
+ // Ordinary TAA does not allocate an output-domain coverage target.
590
+ // The production resolve still declares the stable binding and uses
591
+ // this current-temporal fallback while hasCurrentCoverage is zero.
592
+ { binding: 11, resource: { kind: 'textureView', value: currentTemporal.view } },
589
593
  ],
590
594
  })
591
595
  .unwrap();
@@ -162,6 +162,7 @@ export async function verifyTaaStabilityFeedback(
162
162
  { binding: 8, resource: { kind: 'buffer', value: { buffer: params } } },
163
163
  { binding: 9, resource: { kind: 'textureView', value: state.stability[index].view } },
164
164
  { binding: 10, resource: { kind: 'textureView', value: secondary.view } },
165
+ { binding: 11, resource: { kind: 'textureView', value: temporal.view } },
165
166
  ],
166
167
  })
167
168
  .unwrap();
@@ -97,8 +97,8 @@ describe('TemporalView reset matrix', () => {
97
97
  expect(next.jitter).toEqual(HALTON_23_8[1]);
98
98
  });
99
99
 
100
- it('records the unreachable TAAU sibling as dependency-deferred', () => {
101
- expect(TEMPORAL_TAAU_DEPENDENCY_STATUS).toBe('dependency-deferred');
100
+ it('records the renderer TAAU owner as available while volume stays deferred', () => {
101
+ expect(TEMPORAL_TAAU_DEPENDENCY_STATUS).toBe('available');
102
102
  expect(VOLUME_TAAU_DEPENDENCY.status).toBe('dependency-deferred');
103
103
  expect(VOLUME_TAAU_DEPENDENCY.siblingFeature).toBe('feat-20260831-taau-dynamic-resolution');
104
104
  expect(VOLUME_TAAU_DEPENDENCY.candidateShas).toHaveLength(2);
@@ -39,6 +39,23 @@ describe('TemporalView v1', () => {
39
39
  expect(view.jitterCancellationUv).toEqual([jitter[0] / 1280, jitter[1] / 720]);
40
40
  });
41
41
 
42
+ it('uses the fixed internal extent for TAAU jitter while preserving output dimensions', () => {
43
+ const view = createTemporalView({
44
+ antialias: 'taa',
45
+ width: 200,
46
+ height: 150,
47
+ internalWidth: 128,
48
+ internalHeight: 96,
49
+ frameIndex: 1,
50
+ });
51
+ const jitter = (HALTON_23_8[1] ?? [0, 0]) as readonly [number, number];
52
+ expect(view.input.width).toBe(200);
53
+ expect(view.input.height).toBe(150);
54
+ expect(view.input.internalWidth).toBe(128);
55
+ expect(view.input.internalHeight).toBe(96);
56
+ expect(view.jitterCancellationUv).toEqual([jitter[0] / 128, jitter[1] / 96]);
57
+ });
58
+
42
59
  it.each([
43
60
  ['first-frame', undefined, 'first-frame'],
44
61
  ['resize', { width: 640, height: 360 }, 'resize'],
@@ -1,6 +1,7 @@
1
1
  import { describe, expect, it } from 'vitest';
2
2
  import {
3
3
  type MaterialShaderManifestEntry,
4
+ selectGpuDrivenSceneIndexVariant,
4
5
  selectHdrpPbrPrewarmVariants,
5
6
  selectProbePrewarmVariants,
6
7
  selectStandardPbrTransmissionPrewarmVariants,
@@ -57,6 +58,16 @@ function transmissionVariant(definesKey: string, transmission: boolean) {
57
58
  };
58
59
  }
59
60
 
61
+ function coverageTransmissionVariant(definesKey: string, transmission: boolean) {
62
+ const base = transmissionVariant(definesKey, transmission);
63
+ return {
64
+ ...base,
65
+ definesKey: `COVERAGE_ONLY=true+${definesKey}`,
66
+ defines: { ...base.defines, COVERAGE_ONLY: true },
67
+ composedWgsl: `coverage-${transmission}`,
68
+ };
69
+ }
70
+
60
71
  function probeTransmissionVariant(definesKey: string, transmission: boolean) {
61
72
  const base = transmissionVariant(definesKey, transmission);
62
73
  return {
@@ -82,6 +93,18 @@ describe('Standard transmission exact-key prewarm selection', () => {
82
93
  'TRANSMISSION_AVAILABLE=true',
83
94
  );
84
95
  });
96
+
97
+ it('keeps coverage-only variants out of the ordinary prewarm set', () => {
98
+ const entry = standardEntry([
99
+ coverageTransmissionVariant(falseKey, false),
100
+ transmissionVariant(falseKey, false),
101
+ coverageTransmissionVariant(trueKey, true),
102
+ transmissionVariant(trueKey, true),
103
+ ]);
104
+ expect(
105
+ selectStandardPbrTransmissionPrewarmVariants(entry, true).map((item) => item.definesKey),
106
+ ).toEqual([falseKey, trueKey]);
107
+ });
85
108
  });
86
109
 
87
110
  describe('HDRP PBR capability prewarm selection', () => {
@@ -170,3 +193,37 @@ describe('probe material module prewarm capability selection', () => {
170
193
  expect(selected.map((candidate) => candidate.definesKey)).toEqual([goodKey]);
171
194
  });
172
195
  });
196
+
197
+ describe('GPU-driven scene-index prewarm capability selection', () => {
198
+ it('uses the non-reflection-fallback variant for the dedicated scene-index ABI', () => {
199
+ const makeVariant = (reflectionFallback: boolean) => {
200
+ const defines = {
201
+ CLUSTER_FORWARD_AVAILABLE: false,
202
+ DIRECTIONAL_PCSS_AVAILABLE: true,
203
+ EXTENDED_LIGHTING_AVAILABLE: true,
204
+ GPU_DRIVEN_SCENE_INDEX_AVAILABLE: true,
205
+ PROBE_BLEND_AVAILABLE: false,
206
+ PROJECTOR_AVAILABLE: true,
207
+ REFLECTION_FALLBACK_AVAILABLE: reflectionFallback,
208
+ STORAGE_BUFFER_AVAILABLE: true,
209
+ TRANSMISSION_AVAILABLE: false,
210
+ VERTEX_COLOR_AVAILABLE: false,
211
+ };
212
+ return {
213
+ defines,
214
+ definesKey: Object.entries(defines)
215
+ .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
216
+ .map(([name, value]) => `${name}=${value}`)
217
+ .join('+'),
218
+ composedWgsl: JSON.stringify(defines),
219
+ };
220
+ };
221
+ const selected = selectGpuDrivenSceneIndexVariant(
222
+ standardEntry([makeVariant(true), makeVariant(false)]),
223
+ true,
224
+ true,
225
+ true,
226
+ );
227
+ expect(selected?.defines.REFLECTION_FALLBACK_AVAILABLE).toBe(false);
228
+ });
229
+ });
@@ -73,7 +73,13 @@ async function requestDevice(): Promise<RhiDevice> {
73
73
  return device.value;
74
74
  }
75
75
 
76
- async function runInjectReadback(sceneDepthClear = 1): Promise<VolumeStageReadbacks> {
76
+ async function runInjectReadback(
77
+ sceneDepthClear = 1,
78
+ densitySource: number | 'height-gradient' | 'depth-gradient' = 255,
79
+ heightOffset = 0,
80
+ frameIndex = 0,
81
+ slopedShadow = false,
82
+ ): Promise<VolumeStageReadbacks> {
77
83
  const device = await requestDevice();
78
84
  const moduleResult = await createShaderModule(device, {
79
85
  code: manifestShader('fn volume_inject'),
@@ -227,22 +233,26 @@ async function runInjectReadback(sceneDepthClear = 1): Promise<VolumeStageReadba
227
233
  const viewData = new Float32Array(240);
228
234
  for (const offset of [0, 28, 44, 60, 76, 92, 196, 212]) identityMatrix(viewData, offset);
229
235
  viewData[24] = 0;
230
- viewData[25] = 0;
236
+ viewData[25] = heightOffset;
237
+ viewData[44 + 13] = heightOffset;
231
238
  viewData[26] = -1;
232
- viewData[124] = 0;
239
+ viewData[124] = slopedShadow ? 1 : 0;
240
+ viewData[108] = 10;
241
+ if (slopedShadow) viewData[28 + 2] = 0.5;
233
242
  viewData[228] = 0.1;
234
243
  viewData[229] = 10;
235
244
  viewData[230] = 0;
236
245
  viewData[235] = 1;
237
246
  const paramsData = new Float32Array(32);
238
- paramsData.set([-1, -1, 0.1, 0], 0);
239
- paramsData.set([1, 1, 0.9, 0], 4);
247
+ paramsData.set([-1, heightOffset - 1, 0.1, 0], 0);
248
+ paramsData.set([1, heightOffset + 1, 0.9, 0], 4);
240
249
  paramsData.set([1, 1, 1, 0], 8);
241
250
  paramsData.set([1, 1, 1, 0], 12);
242
251
  paramsData.set([0, 0, 0, 0], 16);
243
252
  paramsData.set([0, -1, 0, 0], 20);
244
253
  paramsData.set([1, 1, 1, 0], 24);
245
254
  paramsData.set([2, 0, 0, 0], 28);
255
+ paramsData[23] = frameIndex;
246
256
  const lightData = new Uint8Array(CLUSTER_LIGHT_DATA_BYTES);
247
257
  const clusterUniformData = new Float32Array(8);
248
258
  const clusterGrid = new Uint32Array(clusterUniformData.buffer);
@@ -261,7 +271,21 @@ async function runInjectReadback(sceneDepthClear = 1): Promise<VolumeStageReadba
261
271
  if (!viewWrite.ok || !paramsWrite.ok || !lightDataWrite.ok || !clusterUniformWrite.ok)
262
272
  throw new Error('Dawn volume stage readback unavailable: uniform upload failed');
263
273
  const densityData = new Uint8Array((BYTES_PER_PIXEL / 2) * SIZE * SIZE * SIZE);
264
- densityData.fill(255);
274
+ for (let z = 0; z < SIZE; z++)
275
+ for (let y = 0; y < SIZE; y++)
276
+ for (let x = 0; x < SIZE; x++) {
277
+ const value =
278
+ densitySource === 'height-gradient'
279
+ ? Math.round((y / (SIZE - 1)) * 255)
280
+ : densitySource === 'depth-gradient'
281
+ ? Math.round((z / (SIZE - 1)) * 255)
282
+ : densitySource;
283
+ densityData.fill(
284
+ value,
285
+ ((z * SIZE + y) * SIZE + x) * 4,
286
+ ((z * SIZE + y) * SIZE + x + 1) * 4,
287
+ );
288
+ }
265
289
  const densityWrite = device.queue.writeTexture(
266
290
  { texture: densityTexture.value },
267
291
  densityData,
@@ -438,7 +462,7 @@ async function runInjectReadback(sceneDepthClear = 1): Promise<VolumeStageReadba
438
462
  colorAttachments: [],
439
463
  depthStencilAttachment: {
440
464
  view,
441
- depthClearValue: index === 0 ? sceneDepthClear : 1,
465
+ depthClearValue: index === 0 ? sceneDepthClear : slopedShadow ? 0.5 : 1,
442
466
  depthLoadOp: 'clear',
443
467
  depthStoreOp: 'store',
444
468
  },
@@ -520,6 +544,22 @@ async function runInjectReadback(sceneDepthClear = 1): Promise<VolumeStageReadba
520
544
  }
521
545
 
522
546
  describe('volumetric fog production stage readback on Dawn', () => {
547
+ it('keeps static shadow visibility stable across renderer frame phases', async () => {
548
+ const first = await runInjectReadback(1, 255, 0, 0, true);
549
+ const next = await runInjectReadback(1, 255, 0, 1, true);
550
+ expect(first.inject.red.some((value) => value === 0)).toBe(true);
551
+ expect(first.inject.red.some((value) => value > 0.9)).toBe(true);
552
+ expect(next.inject).toEqual(first.inject);
553
+ });
554
+ it('keeps a static depth-density field stable across renderer frame phases', async () => {
555
+ const first = await runInjectReadback(1, 'depth-gradient', 0, 0);
556
+ for (const frameIndex of [1, 7]) {
557
+ const next = await runInjectReadback(1, 'depth-gradient', 0, frameIndex);
558
+ expect(next.integrate.alpha).toEqual(first.integrate.alpha);
559
+ expect(next.integrate.red).toEqual(first.integrate.red);
560
+ }
561
+ expect(first.integrate.alpha[0]).toBeCloseTo(Math.exp(-0.4), 2);
562
+ });
523
563
  it('injects raw density then resolves a 2d integrated volume', async () => {
524
564
  const values = await runInjectReadback();
525
565
  // biome-ignore lint/suspicious/noConsole: stage readback is machine evidence
@@ -542,12 +582,21 @@ describe('volumetric fog production stage readback on Dawn', () => {
542
582
  it('clips the final ray step to the scene-depth segment', async () => {
543
583
  const values = await runInjectReadback(0.57);
544
584
  const transmittance = values.integrate.alpha[0];
545
- // The fixture uploads an all-white source texture. The authored Three
546
- // smoke expression therefore evaluates to 2 * 1.5^3 - 1 = 5.75 before
547
- // Beer-Lambert consumes it; only the final 0.47-unit segment is allowed
548
- // to contribute after the scene-depth clip.
549
- const sourceDensity = 2 * 1.5 ** 3 - 1;
550
- expect(transmittance).toBeCloseTo(Math.exp(-0.47 * sourceDensity), 2);
585
+ // White encodes density one; integrate only the visible 0.47-unit segment.
586
+ expect(transmittance).toBeCloseTo(Math.exp(-0.47), 2);
551
587
  expect(values.integrate.red[0]).toBeGreaterThan(0);
552
588
  });
589
+ it.each([0, 64, 128])('preserves authored uniform density %s/255', async (densityByte) => {
590
+ const values = await runInjectReadback(0.57, densityByte);
591
+ expect(values.integrate.alpha[0]).toBeCloseTo(Math.exp((-0.47 * densityByte) / 255), 2);
592
+ if (densityByte === 0) expect(values.integrate.red.every((value) => value === 0)).toBe(true);
593
+ });
594
+
595
+ it.each([
596
+ 0, 20,
597
+ ])('maps height density through world bounds translated by %s', async (heightOffset) => {
598
+ const values = await runInjectReadback(0.57, 'height-gradient', heightOffset);
599
+ expect(values.integrate.alpha[0]).toBeCloseTo(Math.exp(-0.47), 2);
600
+ expect(values.integrate.alpha[(SIZE - 1) * SIZE]).toBeCloseTo(1, 3);
601
+ });
553
602
  });
@@ -34,9 +34,9 @@ describe('volumetric fog RenderGraph topology', () => {
34
34
  expect(composite).toContain('abs(neighbor.a - center.a) * 6.0');
35
35
  });
36
36
 
37
- it('uses the pinned twelve-step ray integration while retaining packed visibility depth', () => {
37
+ it('derives deterministic integration density from the authored froxel depth', () => {
38
38
  const integrate = shaderSource('volume-integrate.wgsl');
39
- expect(integrate).toContain('let ray_step_count = 12u;');
39
+ expect(integrate).toContain('let ray_step_count = visibility_depth;');
40
40
  expect(integrate).toContain('for (var step = 0u; step < ray_step_count; step = step + 1u)');
41
41
  expect(integrate).toContain('let visibility_depth = max(textureNumLayers(froxel) * 4u, 1u);');
42
42
  });
@@ -44,7 +44,7 @@ describe('volumetric fog RenderGraph topology', () => {
44
44
  it('keeps the Beer-Lambert extinction input physically non-negative', () => {
45
45
  const integrate = shaderSource('volume-integrate.wgsl');
46
46
  expect(integrate).toContain(
47
- 'let density_value = max(volume_scattering_density(world_position, frame_index), 0.0);',
47
+ 'let density_value = max(volume_scattering_density(world_position), 0.0);',
48
48
  );
49
49
  });
50
50
 
@@ -130,8 +130,8 @@ describe('volumetric fog RenderGraph topology', () => {
130
130
  expect(shaderSource('volume-inject.wgsl')).toContain('fn ray_box_interval');
131
131
  expect(shaderSource('volume-inject.wgsl')).toContain('view.inverseViewProj');
132
132
  expect(shaderSource('volume-inject.wgsl')).not.toContain('textureSampleLevel(density');
133
- expect(shaderSource('volume-inject.wgsl')).toContain('stratified32');
134
- expect(shaderSource('volume-inject.wgsl')).toContain('dither_unorm8');
133
+ expect(shaderSource('volume-inject.wgsl')).not.toContain('stratified32');
134
+ expect(shaderSource('volume-inject.wgsl')).not.toContain('dither_unorm8');
135
135
  expect(shaderSource('volume-integrate.wgsl')).toContain('exp(-sigma_scale');
136
136
  expect(shaderSource('volume-integrate.wgsl')).toContain('textureSampleLevel(froxel');
137
137
  expect(shaderSource('volume-integrate.wgsl')).toContain('textureStore(resolved');
@@ -159,8 +159,8 @@ describe('volumetric fog RenderGraph topology', () => {
159
159
  const integrate = shaderSource('volume-integrate.wgsl');
160
160
  expect(inject).toContain('(f32(logical_slice) + 0.5) / f32(logical_depth)');
161
161
  expect(inject).not.toContain('depth_phase');
162
- expect(integrate).toContain('fn stratified32');
163
- expect(integrate).toContain('segment_start + segment_length * depth_phase');
162
+ expect(integrate).not.toContain('fn stratified32');
163
+ expect(integrate).toContain('segment_start + segment_length * 0.5');
164
164
  });
165
165
 
166
166
  it('keeps temporal history graph-owned and slot-selectable', () => {
@@ -2,7 +2,7 @@ import { RhiError, type ShaderModule } from '@forgeax/engine-rhi';
2
2
  import type { ManifestEntry } from '@forgeax/engine-types';
3
3
  import type { AtmosphereShaderSources } from '../render-contract';
4
4
 
5
- /** Admit and compile the complete analytic-atmosphere shader pair atomically. */
5
+ /** Admit and compile the complete analytic-atmosphere shader programs atomically. */
6
6
  export async function prewarmAtmosphereShaders(
7
7
  entries: readonly ManifestEntry[],
8
8
  compile: (label: string, source: string) => Promise<ShaderModule>,
@@ -11,6 +11,7 @@ export async function prewarmAtmosphereShaders(
11
11
  const stages = [
12
12
  ['cube', 'atmosphere_cube', 'atmosphere_cubemap_vs'],
13
13
  ['background', 'atmosphere_background', 'atmosphere_background_vs'],
14
+ ['ibl', 'atmosphere_ibl', 'atmosphere_ibl_vs'],
14
15
  ] as const;
15
16
  const resolved = stages.map(([stage, label, entryPoint]) => ({
16
17
  stage,
@@ -22,7 +23,7 @@ export async function prewarmAtmosphereShaders(
22
23
  if (missing !== undefined) {
23
24
  throw new RhiError({
24
25
  code: 'shader-compile-failed',
25
- expected: 'shader manifest contains both analytic-atmosphere utility entries',
26
+ expected: 'shader manifest contains all analytic-atmosphere utility entries',
26
27
  hint: `add the package-owned WGSL entry for '${missing.label}' to the engine shader manifest`,
27
28
  });
28
29
  }