@forgeax/engine-render 0.1.19 → 0.1.21

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 (452) hide show
  1. package/README.md +215 -0
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/assembly/device-feature-admission.d.ts +18 -0
  4. package/dist/assembly/device-feature-admission.d.ts.map +1 -0
  5. package/dist/assembly/directional-shadow-inspection.d.ts +38 -0
  6. package/dist/assembly/directional-shadow-inspection.d.ts.map +1 -0
  7. package/dist/assembly/factory.d.ts +34 -20
  8. package/dist/assembly/factory.d.ts.map +1 -1
  9. package/dist/assembly/host-contract.d.ts +9 -0
  10. package/dist/assembly/host-contract.d.ts.map +1 -1
  11. package/dist/assembly/render-target-host.d.ts +46 -0
  12. package/dist/assembly/render-target-host.d.ts.map +1 -0
  13. package/dist/assets/render-asset-port.d.ts.map +1 -1
  14. package/dist/authoring.mjs +3 -0
  15. package/dist/authoring.mjs.map +1 -1
  16. package/dist/capture/cube-views.d.ts +21 -0
  17. package/dist/capture/cube-views.d.ts.map +1 -0
  18. package/dist/capture/scheduler.d.ts +54 -0
  19. package/dist/capture/scheduler.d.ts.map +1 -0
  20. package/dist/{chunk-RJRUS55L.mjs → chunk-5YADUCA7.mjs} +1896 -1624
  21. package/dist/chunk-5YADUCA7.mjs.map +1 -0
  22. package/dist/{chunk-LOSO4CTV.mjs → chunk-6OPXDQ2E.mjs} +423 -46
  23. package/dist/chunk-6OPXDQ2E.mjs.map +1 -0
  24. package/dist/{chunk-IPXCABEP.mjs → chunk-BXBQQKFQ.mjs} +3 -9
  25. package/dist/chunk-BXBQQKFQ.mjs.map +1 -0
  26. package/dist/chunk-FS2KNUQH.mjs +1294 -0
  27. package/dist/chunk-FS2KNUQH.mjs.map +1 -0
  28. package/dist/components/camera.d.ts +1 -0
  29. package/dist/components/camera.d.ts.map +1 -1
  30. package/dist/components/cube-camera.d.ts +20 -0
  31. package/dist/components/cube-camera.d.ts.map +1 -0
  32. package/dist/components/directional-light.d.ts +11 -2
  33. package/dist/components/directional-light.d.ts.map +1 -1
  34. package/dist/components/directional-shadow-filter.d.ts +25 -0
  35. package/dist/components/directional-shadow-filter.d.ts.map +1 -0
  36. package/dist/components/index.d.ts +2 -0
  37. package/dist/components/index.d.ts.map +1 -1
  38. package/dist/components/light-helpers.d.ts.map +1 -1
  39. package/dist/components/reflection-probe.d.ts +17 -0
  40. package/dist/components/reflection-probe.d.ts.map +1 -0
  41. package/dist/components/spot-light.d.ts +12 -0
  42. package/dist/components/spot-light.d.ts.map +1 -1
  43. package/dist/construct-renderer.mjs +8552 -1612
  44. package/dist/construct-renderer.mjs.map +1 -1
  45. package/dist/device/gpu-residency.d.ts +19 -2
  46. package/dist/device/gpu-residency.d.ts.map +1 -1
  47. package/dist/environment/frame.d.ts.map +1 -1
  48. package/dist/environment/lifecycle.d.ts +5 -0
  49. package/dist/environment/lifecycle.d.ts.map +1 -1
  50. package/dist/errors/render.d.ts +209 -93
  51. package/dist/errors/render.d.ts.map +1 -1
  52. package/dist/extract/directional-shadow-projection.d.ts +23 -0
  53. package/dist/extract/directional-shadow-projection.d.ts.map +1 -0
  54. package/dist/extract/environment.d.ts +14 -0
  55. package/dist/extract/environment.d.ts.map +1 -1
  56. package/dist/extract/visibility.d.ts.map +1 -1
  57. package/dist/features/fullscreen.d.ts.map +1 -1
  58. package/dist/features/targets.d.ts.map +1 -1
  59. package/dist/features/types.d.ts +10 -0
  60. package/dist/features/types.d.ts.map +1 -1
  61. package/dist/fullscreen-post-process-pass.d.ts +13 -2
  62. package/dist/fullscreen-post-process-pass.d.ts.map +1 -1
  63. package/dist/gpu-driven/batch-topology.d.ts.map +1 -1
  64. package/dist/gpu-driven/production-raster.d.ts +0 -1
  65. package/dist/gpu-driven/production-raster.d.ts.map +1 -1
  66. package/dist/gpu-scene.d.ts.map +1 -1
  67. package/dist/gpu-usage.d.ts +2 -0
  68. package/dist/gpu-usage.d.ts.map +1 -1
  69. package/dist/hdrp-buffers.d.ts +19 -17
  70. package/dist/hdrp-buffers.d.ts.map +1 -1
  71. package/dist/ibl/IblPipelineCache.d.ts +4 -0
  72. package/dist/ibl/IblPipelineCache.d.ts.map +1 -1
  73. package/dist/ibl/kernel-cache.d.ts +36 -0
  74. package/dist/ibl/kernel-cache.d.ts.map +1 -0
  75. package/dist/index.d.ts +35 -4
  76. package/dist/index.d.ts.map +1 -1
  77. package/dist/index.mjs +4 -4
  78. package/dist/index.mjs.map +1 -1
  79. package/dist/inspection-types.d.ts +51 -0
  80. package/dist/inspection-types.d.ts.map +1 -1
  81. package/dist/instances-derived-bounds.d.ts +50 -0
  82. package/dist/instances-derived-bounds.d.ts.map +1 -0
  83. package/dist/internal.mjs +1 -1
  84. package/dist/light-buffer-layout.d.ts +30 -13
  85. package/dist/light-buffer-layout.d.ts.map +1 -1
  86. package/dist/mesh-material-bindings.d.ts +54 -0
  87. package/dist/mesh-material-bindings.d.ts.map +1 -1
  88. package/dist/pbr-pipeline.d.ts +20 -7
  89. package/dist/pbr-pipeline.d.ts.map +1 -1
  90. package/dist/pipeline/standard-forward-lane.d.ts +2 -1
  91. package/dist/pipeline/standard-forward-lane.d.ts.map +1 -1
  92. package/dist/pipeline/standard-lighting/errors.d.ts +4 -0
  93. package/dist/pipeline/standard-lighting/errors.d.ts.map +1 -0
  94. package/dist/pipeline/standard-lighting/graph.d.ts +24 -0
  95. package/dist/pipeline/standard-lighting/graph.d.ts.map +1 -0
  96. package/dist/pipeline/standard-lighting/inspection.d.ts +38 -0
  97. package/dist/pipeline/standard-lighting/inspection.d.ts.map +1 -0
  98. package/dist/pipeline/standard-lighting/layout.d.ts +28 -0
  99. package/dist/pipeline/standard-lighting/layout.d.ts.map +1 -0
  100. package/dist/pipeline/standard-lighting/light-frame.d.ts +33 -0
  101. package/dist/pipeline/standard-lighting/light-frame.d.ts.map +1 -0
  102. package/dist/pipeline/standard-lighting/membership.d.ts +14 -0
  103. package/dist/pipeline/standard-lighting/membership.d.ts.map +1 -0
  104. package/dist/pipeline/standard-lighting/prepare.d.ts +16 -0
  105. package/dist/pipeline/standard-lighting/prepare.d.ts.map +1 -0
  106. package/dist/pipeline/standard-lighting/topology.d.ts +45 -0
  107. package/dist/pipeline/standard-lighting/topology.d.ts.map +1 -0
  108. package/dist/pipeline/standard-lighting/transport.d.ts +36 -0
  109. package/dist/pipeline/standard-lighting/transport.d.ts.map +1 -0
  110. package/dist/pipeline/standard-pipeline.d.ts +4 -13
  111. package/dist/pipeline/standard-pipeline.d.ts.map +1 -1
  112. package/dist/pipeline/standard-post.d.ts.map +1 -1
  113. package/dist/pipeline/standard-profile.d.ts +12 -20
  114. package/dist/pipeline/standard-profile.d.ts.map +1 -1
  115. package/dist/pipeline-spec.d.ts +40 -7
  116. package/dist/pipeline-spec.d.ts.map +1 -1
  117. package/dist/point-shadow-inspection.d.ts +21 -0
  118. package/dist/point-shadow-inspection.d.ts.map +1 -0
  119. package/dist/record/frame-lighting.d.ts +29 -32
  120. package/dist/record/frame-lighting.d.ts.map +1 -1
  121. package/dist/record/frame-snapshot.d.ts +65 -15
  122. package/dist/record/frame-snapshot.d.ts.map +1 -1
  123. package/dist/record/frame-targets.d.ts +5 -0
  124. package/dist/record/frame-targets.d.ts.map +1 -1
  125. package/dist/record/frame.d.ts +12 -5
  126. package/dist/record/frame.d.ts.map +1 -1
  127. package/dist/record/gpu-pass-timing/contract.d.ts +96 -0
  128. package/dist/record/gpu-pass-timing/contract.d.ts.map +1 -0
  129. package/dist/record/gpu-pass-timing/errors.d.ts +18 -0
  130. package/dist/record/gpu-pass-timing/errors.d.ts.map +1 -0
  131. package/dist/record/gpu-pass-timing/index.d.ts +8 -0
  132. package/dist/record/gpu-pass-timing/index.d.ts.map +1 -0
  133. package/dist/record/gpu-pass-timing/parser.d.ts +11 -0
  134. package/dist/record/gpu-pass-timing/parser.d.ts.map +1 -0
  135. package/dist/record/gpu-pass-timing/session.d.ts +43 -0
  136. package/dist/record/gpu-pass-timing/session.d.ts.map +1 -0
  137. package/dist/record/gpu-timing.d.ts +37 -0
  138. package/dist/record/gpu-timing.d.ts.map +1 -0
  139. package/dist/record/main-pass-geometry.d.ts.map +1 -1
  140. package/dist/record/main-pass-material.d.ts +23 -2
  141. package/dist/record/main-pass-material.d.ts.map +1 -1
  142. package/dist/record/main-pass-sprite-draws.d.ts.map +1 -1
  143. package/dist/record/main-pass.d.ts +2 -0
  144. package/dist/record/main-pass.d.ts.map +1 -1
  145. package/dist/record/render-context.d.ts +48 -3
  146. package/dist/record/render-context.d.ts.map +1 -1
  147. package/dist/record/shadow-pass.d.ts.map +1 -1
  148. package/dist/record/skybox-post-pass.d.ts.map +1 -1
  149. package/dist/record/typed-frame-graph.d.ts +81 -5
  150. package/dist/record/typed-frame-graph.d.ts.map +1 -1
  151. package/dist/record/view-ubo.d.ts +7 -4
  152. package/dist/record/view-ubo.d.ts.map +1 -1
  153. package/dist/reflection/filter.d.ts +22 -0
  154. package/dist/reflection/filter.d.ts.map +1 -0
  155. package/dist/reflection/gpu-table.d.ts +25 -0
  156. package/dist/reflection/gpu-table.d.ts.map +1 -0
  157. package/dist/reflection/projection.d.ts +91 -0
  158. package/dist/reflection/projection.d.ts.map +1 -0
  159. package/dist/render-contract.d.ts +114 -7
  160. package/dist/render-contract.d.ts.map +1 -1
  161. package/dist/render-data.d.ts +10 -6
  162. package/dist/render-data.d.ts.map +1 -1
  163. package/dist/render-graph-primitives.d.ts +1 -0
  164. package/dist/render-graph-primitives.d.ts.map +1 -1
  165. package/dist/render-pipeline.d.ts +78 -2
  166. package/dist/render-pipeline.d.ts.map +1 -1
  167. package/dist/render-system-extract.d.ts +113 -20
  168. package/dist/render-system-extract.d.ts.map +1 -1
  169. package/dist/render-system.d.ts +24 -3
  170. package/dist/render-system.d.ts.map +1 -1
  171. package/dist/renderer-inspect.d.ts +29 -0
  172. package/dist/renderer-inspect.d.ts.map +1 -0
  173. package/dist/renderer.d.ts +43 -0
  174. package/dist/renderer.d.ts.map +1 -0
  175. package/dist/scene/render-scene.d.ts +14 -0
  176. package/dist/scene/render-scene.d.ts.map +1 -1
  177. package/dist/shadow-atlas.d.ts +3 -3
  178. package/dist/shadow-atlas.d.ts.map +1 -1
  179. package/dist/ssao-buffers.d.ts +3 -0
  180. package/dist/ssao-buffers.d.ts.map +1 -1
  181. package/dist/targets/contracts.d.ts +66 -0
  182. package/dist/targets/contracts.d.ts.map +1 -0
  183. package/dist/targets/graph-projection.d.ts +29 -0
  184. package/dist/targets/graph-projection.d.ts.map +1 -0
  185. package/dist/targets/material-source.d.ts +34 -0
  186. package/dist/targets/material-source.d.ts.map +1 -0
  187. package/dist/targets/owner.d.ts +32 -0
  188. package/dist/targets/owner.d.ts.map +1 -0
  189. package/dist/targets/physical.d.ts +18 -0
  190. package/dist/targets/physical.d.ts.map +1 -0
  191. package/dist/targets/readback.d.ts +39 -0
  192. package/dist/targets/readback.d.ts.map +1 -0
  193. package/dist/temporal/frame.d.ts +44 -0
  194. package/dist/temporal/frame.d.ts.map +1 -0
  195. package/dist/temporal/index.mjs +3 -3
  196. package/dist/typed-render-graph-primitives.d.ts +6 -1
  197. package/dist/typed-render-graph-primitives.d.ts.map +1 -1
  198. package/dist/typed-shadow-atlas.d.ts +10 -0
  199. package/dist/typed-shadow-atlas.d.ts.map +1 -0
  200. package/dist/typed-shadow-passes.d.ts +1 -1
  201. package/dist/typed-shadow-passes.d.ts.map +1 -1
  202. package/dist/volume/capability.d.ts +57 -0
  203. package/dist/volume/capability.d.ts.map +1 -0
  204. package/dist/volume/component.d.ts +61 -0
  205. package/dist/volume/component.d.ts.map +1 -0
  206. package/dist/volume/extract.d.ts +11 -0
  207. package/dist/volume/extract.d.ts.map +1 -0
  208. package/dist/volume/inspection.d.ts +38 -0
  209. package/dist/volume/inspection.d.ts.map +1 -0
  210. package/dist/volume/passes.d.ts +38 -0
  211. package/dist/volume/passes.d.ts.map +1 -0
  212. package/dist/volume/recovery.d.ts +38 -0
  213. package/dist/volume/recovery.d.ts.map +1 -0
  214. package/dist/volume/resources.d.ts +57 -0
  215. package/dist/volume/resources.d.ts.map +1 -0
  216. package/dist/volume/temporal.d.ts +35 -0
  217. package/dist/volume/temporal.d.ts.map +1 -0
  218. package/package.json +20 -20
  219. package/src/__tests__/bloom-exact-zero.unit.test.ts +48 -19
  220. package/src/__tests__/bloom-lifecycle.integration.test.ts +41 -13
  221. package/src/__tests__/camera-target-consumption.unit.test.ts +53 -0
  222. package/src/__tests__/cluster-binner.unit.test.ts +35 -0
  223. package/src/__tests__/cluster-grid-default-surface.unit.test.ts +7 -2
  224. package/src/__tests__/cube-camera-scheduler.unit.test.ts +70 -0
  225. package/src/__tests__/cube-camera-single-submit.perf.test.ts +75 -0
  226. package/src/__tests__/cube-camera-view-matrices.unit.test.ts +41 -0
  227. package/src/__tests__/cube-camera-vocabulary.unit.test.ts +47 -0
  228. package/src/__tests__/cube-camera.integration.test.ts +42 -0
  229. package/src/__tests__/device-feature-admission.unit.test.ts +73 -0
  230. package/src/__tests__/direct-light-snapshot.unit.test.ts +3 -3
  231. package/src/__tests__/docs-gate-green.unit.test.ts +38 -0
  232. package/src/__tests__/factory-contract.integration.test.ts +255 -29
  233. package/src/__tests__/fog-component.unit.test.ts +67 -0
  234. package/src/__tests__/fog.integration.test.ts +86 -0
  235. package/src/__tests__/fullscreen-feature-plan.unit.test.ts +37 -0
  236. package/src/__tests__/gpu-driven-indirect-raster-evidence.ts +10 -9
  237. package/src/__tests__/gpu-driven-indirect-raster.dawn.test.ts +17 -1
  238. package/src/__tests__/gpu-driven-production.integration.test.ts +16 -17
  239. package/src/__tests__/gpu-driven-view-graph.integration.test.ts +12 -4
  240. package/src/__tests__/gpu-pass-timing-browser-runner.ts +281 -0
  241. package/src/__tests__/gpu-pass-timing-coexistence.integration.test.ts +80 -0
  242. package/src/__tests__/gpu-pass-timing-contract.unit.test.ts +243 -0
  243. package/src/__tests__/gpu-pass-timing-exact-zero.unit.test.ts +131 -0
  244. package/src/__tests__/gpu-pass-timing-lifecycle.integration.test.ts +230 -0
  245. package/src/__tests__/gpu-pass-timing-observation.unit.test.ts +233 -0
  246. package/src/__tests__/gpu-pass-timing-session.unit.test.ts +438 -0
  247. package/src/__tests__/gpu-pass-timing.browser.test.ts +26 -0
  248. package/src/__tests__/gpu-pass-timing.rhi-null.unit.test.ts +125 -0
  249. package/src/__tests__/hdrp-group2-visibility.unit.test.ts +24 -0
  250. package/src/__tests__/instances-culling.integration.test.ts +94 -0
  251. package/src/__tests__/instances-derived-bounds.unit.test.ts +118 -0
  252. package/src/__tests__/ktx2-basis-gpu-consumer.dawn.test.ts +14 -14
  253. package/src/__tests__/material-observation.integration.test.ts +101 -0
  254. package/src/__tests__/mesh-buffer-usage-surface.unit.test.ts +1 -1
  255. package/src/__tests__/pbr-view-layout.unit.test.ts +16 -0
  256. package/src/__tests__/point-shadow-inspection.unit.test.ts +60 -0
  257. package/src/__tests__/public-surface.test-d.ts +10 -0
  258. package/src/__tests__/reflection-probe-authoring.unit.test.ts +71 -0
  259. package/src/__tests__/reflection-probe-filter.unit.test.ts +54 -0
  260. package/src/__tests__/reflection-probe-ibl-topology.unit.test.ts +45 -0
  261. package/src/__tests__/reflection-probe-pass-encode.integration.test.ts +101 -0
  262. package/src/__tests__/reflection-probe-performance.perf.test.ts +60 -0
  263. package/src/__tests__/reflection-probe-recovery.integration.test.ts +55 -0
  264. package/src/__tests__/reflection-probe-selection.unit.test.ts +46 -0
  265. package/src/__tests__/render-error-code-owner.test-d.ts +23 -3
  266. package/src/__tests__/render-feature-docs.unit.test.ts +1 -1
  267. package/src/__tests__/render-target-admission.unit.test.ts +80 -0
  268. package/src/__tests__/render-target-graph-projection.unit.test.ts +102 -0
  269. package/src/__tests__/render-target-inspection.unit.test.ts +52 -0
  270. package/src/__tests__/render-target-material-source.test-d.ts +30 -0
  271. package/src/__tests__/render-target-material-source.unit.test.ts +97 -0
  272. package/src/__tests__/render-target-owner-placement.unit.test.ts +203 -0
  273. package/src/__tests__/render-target-owner.unit.test.ts +122 -0
  274. package/src/__tests__/render-target-probe-soak.perf.test.ts +94 -0
  275. package/src/__tests__/render-target-public-schema.json +107 -0
  276. package/src/__tests__/render-target-public-schema.unit.test.ts +91 -0
  277. package/src/__tests__/render-target-public-surface.test-d.ts +83 -0
  278. package/src/__tests__/render-target-readback.integration.test.ts +90 -0
  279. package/src/__tests__/render-target-recovery.integration.test.ts +85 -0
  280. package/src/__tests__/renderer-factory-material-contract.unit.test.ts +76 -0
  281. package/src/__tests__/renderer-frame-transaction.integration.test.ts +1 -1
  282. package/src/__tests__/renderer-lifecycle.integration.test.ts +14 -0
  283. package/src/__tests__/shader-stage-surface.unit.test.ts +19 -4
  284. package/src/__tests__/skin-motion-regression.unit.test.ts +132 -0
  285. package/src/__tests__/skinned-shadow-caster.test.ts +32 -2
  286. package/src/__tests__/standard-cluster-capability.integration.test.ts +63 -0
  287. package/src/__tests__/standard-cluster-transport.unit.test.ts +121 -0
  288. package/src/__tests__/standard-cluster-variant-resolution.unit.test.ts +232 -0
  289. package/src/__tests__/standard-clustered-pipeline.unit.test.ts +8 -5
  290. package/src/__tests__/standard-lighting-inspection.integration.test.ts +138 -0
  291. package/src/__tests__/standard-lighting-ssot.unit.test.ts +18 -0
  292. package/src/__tests__/standard-lighting-write.unit.test.ts +377 -0
  293. package/src/__tests__/standard-lighting.integration.test.ts +67 -0
  294. package/src/__tests__/standard-pipeline.dawn.test.ts +206 -5
  295. package/src/__tests__/standard-pipeline.integration.test.ts +181 -150
  296. package/src/__tests__/standard-probe-binding.unit.test.ts +25 -0
  297. package/src/__tests__/standard-profile-oracle.unit.test.ts +23 -36
  298. package/src/__tests__/typed-pipeline-topology.unit.test.ts +395 -14
  299. package/src/__tests__/urp-pipeline-targets.unit.test.ts +2 -2
  300. package/src/__tests__/visibility-resolver.unit.test.ts +15 -0
  301. package/src/__tests__/volume-optics-oracle.unit.test.ts +85 -0
  302. package/src/__tests__/volumetric-fog-budget-recovery.integration.test.ts +655 -0
  303. package/src/__tests__/volumetric-fog-component.unit.test.ts +71 -0
  304. package/src/__tests__/volumetric-fog-composite-stage-readback.dawn.test.ts +376 -0
  305. package/src/__tests__/volumetric-fog-errors.unit.test.ts +51 -0
  306. package/src/__tests__/volumetric-fog-inspection.unit.test.ts +77 -0
  307. package/src/__tests__/volumetric-fog-light-capability.unit.test.ts +19 -0
  308. package/src/__tests__/volumetric-fog-light-consumers.integration.test.ts +18 -0
  309. package/src/__tests__/volumetric-fog-light-errors.unit.test.ts +17 -0
  310. package/src/__tests__/volumetric-fog-light-topology.unit.test.ts +21 -0
  311. package/src/__tests__/volumetric-fog-light-transaction.integration.test.ts +53 -0
  312. package/src/__tests__/volumetric-fog-m9-watcher.integration.test.ts +27 -0
  313. package/src/__tests__/volumetric-fog-point-light-owner.integration.test.ts +44 -0
  314. package/src/__tests__/volumetric-fog-point-spot-accumulation.integration.test.ts +49 -0
  315. package/src/__tests__/volumetric-fog-point-spot-contract.unit.test.ts +28 -0
  316. package/src/__tests__/volumetric-fog-point-spot-lkg.integration.test.ts +65 -0
  317. package/src/__tests__/volumetric-fog-point-spot-selection.integration.test.ts +49 -0
  318. package/src/__tests__/volumetric-fog-point-spot-temporal.unit.test.ts +55 -0
  319. package/src/__tests__/volumetric-fog-projector-contract.unit.test.ts +22 -0
  320. package/src/__tests__/volumetric-fog-projector-inspection.unit.test.ts +12 -0
  321. package/src/__tests__/volumetric-fog-projector-surface-volume.integration.test.ts +11 -0
  322. package/src/__tests__/volumetric-fog-projector-transaction.integration.test.ts +26 -0
  323. package/src/__tests__/volumetric-fog-recovery.integration.test.ts +47 -0
  324. package/src/__tests__/volumetric-fog-selected-light-resolution.integration.test.ts +63 -0
  325. package/src/__tests__/volumetric-fog-spot-closure.integration.test.ts +26 -0
  326. package/src/__tests__/volumetric-fog-spot-contract.test-d.ts +21 -0
  327. package/src/__tests__/volumetric-fog-spot-contract.unit.test.ts +27 -0
  328. package/src/__tests__/volumetric-fog-spot-frame-optics.integration.test.ts +14 -0
  329. package/src/__tests__/volumetric-fog-spot-optics.integration.test.ts +29 -0
  330. package/src/__tests__/volumetric-fog-stage-readback.dawn.test.ts +550 -0
  331. package/src/__tests__/volumetric-fog-temporal-selected-light.unit.test.ts +33 -0
  332. package/src/__tests__/volumetric-fog-temporal.unit.test.ts +45 -0
  333. package/src/__tests__/volumetric-fog-topology.unit.test.ts +237 -0
  334. package/src/__tests__/volumetric-fog-transparent.integration.test.ts +27 -0
  335. package/src/assembly/device-feature-admission.ts +73 -0
  336. package/src/assembly/directional-shadow-inspection.ts +122 -0
  337. package/src/assembly/factory.ts +1093 -246
  338. package/src/assembly/host-contract.ts +25 -0
  339. package/src/assembly/render-target-host.ts +847 -0
  340. package/src/assets/asset-decoders.ts +1 -1
  341. package/src/assets/render-asset-port.ts +7 -1
  342. package/src/capture/cube-views.ts +69 -0
  343. package/src/capture/scheduler.ts +265 -0
  344. package/src/components/__tests__/schema-contract.unit.test.ts +12 -0
  345. package/src/components/__tests__/schema-parity.unit.test.ts +9 -0
  346. package/src/components/camera.ts +4 -0
  347. package/src/components/cube-camera.ts +52 -0
  348. package/src/components/directional-light.ts +22 -2
  349. package/src/components/directional-shadow-filter.ts +57 -0
  350. package/src/components/index.ts +2 -0
  351. package/src/components/light-helpers.ts +85 -7
  352. package/src/components/reflection-probe.ts +49 -0
  353. package/src/components/spot-light.ts +22 -3
  354. package/src/device/gpu-residency.ts +168 -58
  355. package/src/environment/frame.ts +6 -24
  356. package/src/environment/lifecycle.ts +12 -0
  357. package/src/errors/render.ts +405 -143
  358. package/src/extract/directional-shadow-projection.ts +61 -0
  359. package/src/extract/environment.ts +57 -0
  360. package/src/extract/visibility.ts +25 -1
  361. package/src/features/fullscreen.ts +3 -0
  362. package/src/features/targets.ts +4 -1
  363. package/src/features/types.ts +10 -0
  364. package/src/fullscreen-post-process-pass.ts +43 -2
  365. package/src/gpu-driven/batch-topology.ts +1 -0
  366. package/src/gpu-driven/production-raster.ts +1 -3
  367. package/src/gpu-scene.ts +8 -1
  368. package/src/gpu-usage.ts +2 -0
  369. package/src/hdrp-buffers.ts +165 -59
  370. package/src/ibl/IblPipelineCache.ts +11 -0
  371. package/src/ibl/kernel-cache.ts +88 -0
  372. package/src/index.ts +186 -1
  373. package/src/inspection-types.ts +56 -0
  374. package/src/instances-derived-bounds.ts +171 -0
  375. package/src/light-buffer-layout.ts +74 -26
  376. package/src/mesh-material-bindings.ts +132 -0
  377. package/src/pbr-pipeline.ts +139 -27
  378. package/src/pipeline/standard-forward-lane.ts +65 -15
  379. package/src/pipeline/standard-lighting/errors.ts +6 -0
  380. package/src/pipeline/standard-lighting/graph.ts +157 -0
  381. package/src/pipeline/standard-lighting/inspection.ts +88 -0
  382. package/src/pipeline/standard-lighting/layout.ts +31 -0
  383. package/src/pipeline/standard-lighting/light-frame.ts +34 -0
  384. package/src/pipeline/standard-lighting/membership.ts +43 -0
  385. package/src/pipeline/standard-lighting/prepare.ts +37 -0
  386. package/src/pipeline/standard-lighting/topology.ts +120 -0
  387. package/src/pipeline/standard-lighting/transport.ts +77 -0
  388. package/src/pipeline/standard-pipeline.ts +141 -192
  389. package/src/pipeline/standard-post.ts +26 -7
  390. package/src/pipeline/standard-profile.ts +23 -30
  391. package/src/pipeline-spec.ts +84 -21
  392. package/src/point-shadow-inspection.ts +55 -0
  393. package/src/record/__tests__/temporal-payload.unit.test.ts +42 -1
  394. package/src/record/__tests__/transparent-group2-selection.unit.test.ts +73 -0
  395. package/src/record/__tests__/view-ubo-layout.unit.test.ts +40 -0
  396. package/src/record/frame-lighting.ts +470 -516
  397. package/src/record/frame-snapshot.ts +76 -28
  398. package/src/record/frame-targets.ts +23 -2
  399. package/src/record/frame.ts +1169 -79
  400. package/src/record/gpu-pass-timing/contract.ts +180 -0
  401. package/src/record/gpu-pass-timing/errors.ts +34 -0
  402. package/src/record/gpu-pass-timing/index.ts +37 -0
  403. package/src/record/gpu-pass-timing/parser.ts +97 -0
  404. package/src/record/gpu-pass-timing/session.ts +938 -0
  405. package/src/record/gpu-timing.ts +233 -0
  406. package/src/record/main-pass-geometry.ts +120 -80
  407. package/src/record/main-pass-material.ts +101 -11
  408. package/src/record/main-pass-sprite-draws.ts +54 -25
  409. package/src/record/main-pass.ts +71 -15
  410. package/src/record/render-context.ts +77 -0
  411. package/src/record/shadow-pass.ts +104 -66
  412. package/src/record/skybox-post-pass.ts +20 -4
  413. package/src/record/typed-frame-graph.ts +1016 -32
  414. package/src/record/view-ubo.ts +63 -28
  415. package/src/reflection/filter.ts +81 -0
  416. package/src/reflection/gpu-table.ts +44 -0
  417. package/src/reflection/projection.ts +281 -0
  418. package/src/render-contract.ts +147 -4
  419. package/src/render-data.ts +50 -22
  420. package/src/render-graph-primitives.ts +9 -4
  421. package/src/render-pipeline.ts +161 -2
  422. package/src/render-system-extract.ts +886 -103
  423. package/src/render-system.ts +1174 -66
  424. package/src/renderer-inspect.ts +66 -0
  425. package/src/renderer.ts +222 -0
  426. package/src/scene/render-scene.ts +117 -9
  427. package/src/shadow-atlas.ts +5 -5
  428. package/src/ssao-buffers.ts +9 -2
  429. package/src/targets/contracts.ts +182 -0
  430. package/src/targets/graph-projection.ts +169 -0
  431. package/src/targets/material-source.ts +143 -0
  432. package/src/targets/owner.ts +214 -0
  433. package/src/targets/physical.ts +18 -0
  434. package/src/targets/readback.ts +153 -0
  435. package/src/temporal/frame.ts +128 -0
  436. package/src/transmission/__tests__/standard-transmission.dawn.test.ts +61 -23
  437. package/src/typed-render-graph-primitives.ts +34 -2
  438. package/src/typed-shadow-atlas.ts +17 -0
  439. package/src/typed-shadow-passes.ts +47 -40
  440. package/src/volume/capability.ts +173 -0
  441. package/src/volume/component.ts +110 -0
  442. package/src/volume/extract.ts +21 -0
  443. package/src/volume/inspection.ts +78 -0
  444. package/src/volume/passes.ts +686 -0
  445. package/src/volume/recovery.ts +86 -0
  446. package/src/volume/resources.ts +236 -0
  447. package/src/volume/temporal.ts +89 -0
  448. package/dist/chunk-IPXCABEP.mjs.map +0 -1
  449. package/dist/chunk-LOSO4CTV.mjs.map +0 -1
  450. package/dist/chunk-RJRUS55L.mjs.map +0 -1
  451. package/dist/chunk-YTQ5C7YQ.mjs +0 -584
  452. package/dist/chunk-YTQ5C7YQ.mjs.map +0 -1
@@ -1,17 +1,32 @@
1
1
  import { readFileSync } from 'node:fs';
2
+ import { mat4, vec3 } from '@forgeax/engine-math';
2
3
  import { RenderGraphBuilder } from '@forgeax/engine-render-graph';
3
4
  import type { RhiDevice } from '@forgeax/engine-rhi';
4
5
  import { rhi } from '@forgeax/engine-rhi-null';
5
6
  import { ok } from '@forgeax/engine-types';
6
7
  import { beforeAll, describe, expect, it } from 'vitest';
8
+ import { shouldExecuteStandardClusterMembershipPass } from '../pipeline/standard-lighting/graph';
9
+ import {
10
+ prepareStandardLighting,
11
+ type StandardLightFrame,
12
+ } from '../pipeline/standard-lighting/prepare';
13
+ import {
14
+ deriveStandardTopologyInput,
15
+ standardLightingTopologySignature,
16
+ } from '../pipeline/standard-lighting/topology';
17
+ import { selectStandardClusterTransport } from '../pipeline/standard-lighting/transport';
7
18
  import { standardPipeline } from '../pipeline/standard-pipeline';
8
19
  import { DEFAULT_STANDARD_PROFILE } from '../pipeline/standard-profile';
9
- import { isMotionBlurTemporalDemand } from '../record/typed-frame-graph';
20
+ import {
21
+ isMotionBlurTemporalDemand,
22
+ resolvePostProcessChainAdmission,
23
+ } from '../record/typed-frame-graph';
10
24
  import type {
11
25
  RenderPipeline,
12
26
  RenderPipelineFrame,
13
27
  RenderPipelineTopology,
14
28
  } from '../render-pipeline';
29
+ import { resolveOutputDither } from '../render-pipeline';
15
30
  import { standardTemporalPostOrder } from '../temporal/standard-scene-data';
16
31
 
17
32
  let device: RhiDevice;
@@ -39,8 +54,8 @@ function topology(
39
54
  },
40
55
  camera: { tonemap: 'aces-filmic', antialias: 'fxaa', bloom: 'on' },
41
56
  shadow: {
42
- mapSize: 1024,
43
- cascadeCount: 4,
57
+ directional: { mapSize: 1024, cascadeCount: 4 },
58
+ spotMapSize: 1024,
44
59
  pointCount: 1,
45
60
  pointFaceSize: 512,
46
61
  spotCount: 1,
@@ -57,13 +72,65 @@ function topology(
57
72
  };
58
73
  }
59
74
 
75
+ function zeroStandardLighting(caps: { compute: boolean; storageBuffer: boolean }, localCount = 1) {
76
+ const prepared = prepareStandardLighting({
77
+ directional: undefined,
78
+ local: Array.from({ length: localCount }, (_, index) => ({
79
+ kind: 'point' as const,
80
+ shadowed: false,
81
+ position: vec3.create(index, 0, -4),
82
+ range: 2,
83
+ })),
84
+ view: mat4.create(),
85
+ projection: mat4.create(),
86
+ near: 0.1,
87
+ far: 100,
88
+ grid: { x: 4, y: 3, z: 4 },
89
+ lightCount: DEFAULT_STANDARD_PROFILE.lightCount,
90
+ renderPath: 'forward',
91
+ });
92
+ if (!prepared.ok) throw prepared.error;
93
+ const transport = selectStandardClusterTransport(
94
+ { ...caps, membershipPipelineReady: caps.compute },
95
+ prepared.value,
96
+ );
97
+ if (!transport.ok) return transport;
98
+ return deriveStandardTopologyInput({
99
+ kind: 'clustered',
100
+ prepared: prepared.value,
101
+ transport: transport.value,
102
+ });
103
+ }
104
+
105
+ function noLocalStandardLighting() {
106
+ const prepared = prepareStandardLighting({
107
+ directional: undefined,
108
+ local: [],
109
+ view: mat4.create(),
110
+ projection: mat4.create(),
111
+ near: 0.1,
112
+ far: 100,
113
+ grid: { x: 4, y: 3, z: 4 },
114
+ lightCount: 1,
115
+ renderPath: 'forward',
116
+ });
117
+ if (!prepared.ok) throw prepared.error;
118
+ return deriveStandardTopologyInput({ kind: 'no-local-lights', prepared: prepared.value });
119
+ }
120
+
60
121
  function compile(pipeline: RenderPipeline, input: RenderPipelineTopology) {
61
122
  const graph = new RenderGraphBuilder<RenderPipelineFrame>();
123
+ const standardLighting = zeroStandardLighting({
124
+ compute: input.lane.compute,
125
+ storageBuffer: input.lane.storageBuffer,
126
+ });
127
+ if (!standardLighting.ok) return standardLighting;
62
128
  const importedHistory =
63
129
  input.camera.antialias === 'taa' ? createImportedHistory(graph) : undefined;
64
130
  const built = pipeline.build(
65
131
  {
66
132
  graph,
133
+ standardLighting: standardLighting.value,
67
134
  capabilities: { rgba16floatRenderable: true },
68
135
  ...(importedHistory === undefined ? {} : { taaHistory: importedHistory }),
69
136
  projectGpuDriven: () => ok(undefined),
@@ -78,6 +145,38 @@ function compile(pipeline: RenderPipeline, input: RenderPipelineTopology) {
78
145
  });
79
146
  }
80
147
 
148
+ function disabledDirectionalTopology(): RenderPipelineTopology {
149
+ return topology('forgeax::standard', {
150
+ shadow: {
151
+ directional: 'disabled',
152
+ pointCount: 0,
153
+ pointFaceSize: 512,
154
+ spotCount: 0,
155
+ } as unknown as RenderPipelineTopology['shadow'],
156
+ });
157
+ }
158
+
159
+ function disabledDirectionalVolumeTopology(lightKind: 'point' | 'spot'): RenderPipelineTopology {
160
+ return topology('forgeax::standard', {
161
+ shadow: {
162
+ directional: 'disabled',
163
+ spotMapSize: 1024,
164
+ pointCount: 0,
165
+ pointFaceSize: 512,
166
+ spotCount: lightKind === 'spot' ? 1 : 0,
167
+ },
168
+ volumetricFog: {
169
+ enabled: true,
170
+ lightKind,
171
+ lightEntity: 1,
172
+ format: 'rgba8unorm',
173
+ extent: { width: 8, height: 8, depth: 8 },
174
+ froxelExtent: { width: 16, height: 16, depth: 8 },
175
+ resolvedExtent: { width: 8, height: 8, depth: 1 },
176
+ },
177
+ });
178
+ }
179
+
81
180
  function createImportedHistory(graph: RenderGraphBuilder<RenderPipelineFrame>) {
82
181
  const target = (label: string) => {
83
182
  const texture = graph.importTexture(
@@ -104,6 +203,248 @@ function createImportedHistory(graph: RenderGraphBuilder<RenderPipelineFrame>) {
104
203
  }
105
204
 
106
205
  describe('typed built-in pipeline topology', () => {
206
+ it('defaults final output dither on and accepts an asset-level opt-out', () => {
207
+ expect(resolveOutputDither(undefined)).toBe(true);
208
+ expect(resolveOutputDither({ outputDither: true })).toBe(true);
209
+ expect(resolveOutputDither({ outputDither: false })).toBe(false);
210
+ });
211
+
212
+ it('omits every Directional shadow graph resource and pass when disabled', () => {
213
+ const compiled = compile(standardPipeline, disabledDirectionalTopology());
214
+ expect(compiled.ok).toBe(true);
215
+ if (!compiled.ok) return;
216
+ const info = compiled.value.inspect();
217
+ expect(info.resources.some((resource) => resource.label === 'directional-shadow-depth')).toBe(
218
+ false,
219
+ );
220
+ expect(
221
+ info.passes.some(
222
+ (pass) =>
223
+ pass.name.startsWith('shadowCascade') || pass.name === 'directional-shadow-observation',
224
+ ),
225
+ ).toBe(false);
226
+ const main = info.passes.find((pass) => pass.name === 'main');
227
+ expect(main?.accesses.some((access) => access.resource === 'directional-shadow-depth')).toBe(
228
+ false,
229
+ );
230
+ });
231
+
232
+ it('falsifies a test-only shadow-off topology that still builds a Directional pass', () => {
233
+ const compiled = compile(standardPipeline, disabledDirectionalTopology());
234
+ expect(compiled.ok).toBe(true);
235
+ if (!compiled.ok) return;
236
+ const count = compiled.value
237
+ .inspect()
238
+ .passes.filter(
239
+ (pass) =>
240
+ pass.name.startsWith('shadowCascade') || pass.name === 'directional-shadow-observation',
241
+ ).length;
242
+ const expected = process.env.FALSIFY === 'shadow-off-still-builds-pass' ? 1 : 0;
243
+ expect(count).toBe(expected);
244
+ });
245
+
246
+ it.each([
247
+ 'point',
248
+ 'spot',
249
+ ] as const)('keeps %s volumetric fog graphable when directional shadows are disabled', (lightKind) => {
250
+ const compiled = compile(standardPipeline, disabledDirectionalVolumeTopology(lightKind));
251
+ expect(compiled.ok).toBe(true);
252
+ if (!compiled.ok) return;
253
+ const info = compiled.value.inspect();
254
+ expect(info.resources.some((resource) => resource.label === 'directional-shadow-depth')).toBe(
255
+ false,
256
+ );
257
+ expect(info.passes.some((pass) => pass.name === 'volume-inject')).toBe(true);
258
+ expect(
259
+ info.passes.some(
260
+ (pass) =>
261
+ pass.name.startsWith('shadowCascade') || pass.name === 'directional-shadow-observation',
262
+ ),
263
+ ).toBe(false);
264
+ });
265
+
266
+ it('derives topology only from prepared transport facts', () => {
267
+ const frame: StandardLightFrame = {
268
+ directional: undefined,
269
+ local: [{ kind: 'point', shadowed: false, position: vec3.create(0, 0, -4), range: 2 }],
270
+ view: mat4.lookAt(mat4.create(), [0, 0, 0], [0, 0, -1], [0, 1, 0]),
271
+ projection: mat4.perspective(mat4.create(), Math.PI / 3, 1, 0.1, 100),
272
+ near: 0.1,
273
+ far: 100,
274
+ grid: { x: 4, y: 3, z: 4 },
275
+ lightCount: 32,
276
+ renderPath: 'forward',
277
+ };
278
+ const prepared = prepareStandardLighting(frame);
279
+ expect(prepared.ok).toBe(true);
280
+ if (!prepared.ok) return;
281
+ const transport = selectStandardClusterTransport(
282
+ { compute: true, storageBuffer: true, membershipPipelineReady: true },
283
+ prepared.value,
284
+ );
285
+ expect(transport.ok).toBe(true);
286
+ if (!transport.ok) return;
287
+ const derived = deriveStandardTopologyInput({
288
+ prepared: prepared.value,
289
+ kind: 'clustered',
290
+ transport: transport.value,
291
+ });
292
+ expect(derived.ok).toBe(true);
293
+ if (!derived.ok) return;
294
+ expect(derived.value.kind).toBe('clustered');
295
+ if (derived.value.kind !== 'clustered') return;
296
+ expect(derived.value.transport.kind).toBe('compute-storage');
297
+ expect(derived.value.prepared).toBe(prepared.value);
298
+ });
299
+
300
+ it('keeps graph identity stable for per-frame light movement and count changes', () => {
301
+ const make = (localCount: number) => {
302
+ const prepared = prepareStandardLighting({
303
+ directional: undefined,
304
+ local: Array.from({ length: localCount }, (_, index) => ({
305
+ kind: index % 2 === 0 ? ('point' as const) : ('spot' as const),
306
+ shadowed: false,
307
+ position: vec3.create(index, 0, -4),
308
+ range: 2,
309
+ })),
310
+ view: mat4.create(),
311
+ projection: mat4.create(),
312
+ near: 0.1,
313
+ far: 100,
314
+ grid: { x: 4, y: 3, z: 4 },
315
+ lightCount: 32,
316
+ renderPath: 'forward',
317
+ });
318
+ if (!prepared.ok) throw prepared.error;
319
+ const transport = selectStandardClusterTransport(
320
+ { compute: true, storageBuffer: true, membershipPipelineReady: true },
321
+ prepared.value,
322
+ );
323
+ if (!transport.ok) throw transport.error;
324
+ const input = deriveStandardTopologyInput({
325
+ kind: 'clustered',
326
+ prepared: prepared.value,
327
+ transport: transport.value,
328
+ });
329
+ if (!input.ok) throw input.error;
330
+ return input.value;
331
+ };
332
+ expect(standardLightingTopologySignature(make(1))).toBe(
333
+ standardLightingTopologySignature(make(2)),
334
+ );
335
+ const empty = noLocalStandardLighting();
336
+ expect(empty.ok).toBe(true);
337
+ if (!empty.ok) return;
338
+ expect(standardLightingTopologySignature(make(1))).not.toBe(
339
+ standardLightingTopologySignature(empty.value),
340
+ );
341
+ expect(standardLightingTopologySignature(undefined)).not.toBe(
342
+ standardLightingTopologySignature(empty.value),
343
+ );
344
+ });
345
+
346
+ it('keeps the clustered graph and ABI stable across local-light 1 to 0 to 1', () => {
347
+ const capabilities = { compute: true, storageBuffer: true };
348
+ const one = zeroStandardLighting(capabilities, 1);
349
+ const zero = zeroStandardLighting(capabilities, 0);
350
+ const oneAgain = zeroStandardLighting(capabilities, 1);
351
+ expect(one.ok && zero.ok && oneAgain.ok).toBe(true);
352
+ if (!one.ok || !zero.ok || !oneAgain.ok) return;
353
+ expect(one.value.kind).toBe('clustered');
354
+ expect(zero.value.kind).toBe('clustered');
355
+ expect(oneAgain.value.kind).toBe('clustered');
356
+ expect(standardLightingTopologySignature(zero.value)).toBe(
357
+ standardLightingTopologySignature(one.value),
358
+ );
359
+ expect(standardLightingTopologySignature(oneAgain.value)).toBe(
360
+ standardLightingTopologySignature(one.value),
361
+ );
362
+
363
+ const graph = new RenderGraphBuilder<RenderPipelineFrame>();
364
+ const built = standardPipeline.build(
365
+ {
366
+ graph,
367
+ standardLighting: zero.value,
368
+ capabilities: { rgba16floatRenderable: true },
369
+ projectGpuDriven: () => ok(undefined),
370
+ contributeFeatures: () => ok(undefined),
371
+ },
372
+ topology('forgeax::standard', {
373
+ shadow: {
374
+ directional: 'disabled',
375
+ pointCount: 0,
376
+ pointFaceSize: 512,
377
+ spotCount: 0,
378
+ } as unknown as RenderPipelineTopology['shadow'],
379
+ camera: { tonemap: 'none', antialias: 'none', bloom: 'off' },
380
+ }),
381
+ );
382
+ expect(built.ok).toBe(true);
383
+ if (!built.ok) return;
384
+ const compiled = graph.compile({ device, surfaceSize: { width: 800, height: 600 } });
385
+ expect(compiled.ok).toBe(true);
386
+ if (!compiled.ok) return;
387
+ const info = compiled.value.inspect();
388
+ expect(info.passes.map((pass) => pass.name)).toContain('cluster-membership-producer');
389
+ expect(info.resources.map((resource) => resource.label)).toContain('hdrp-light-data');
390
+ });
391
+
392
+ it('evaluates Cluster membership from the current frame, not graph-build light count', () => {
393
+ const one = zeroStandardLighting({ compute: true, storageBuffer: true }, 1);
394
+ const zero = zeroStandardLighting({ compute: true, storageBuffer: true }, 0);
395
+ expect(one.ok && zero.ok).toBe(true);
396
+ if (!one.ok || !zero.ok) return;
397
+ const asFrame = (standardLighting: typeof one.value) =>
398
+ ({ standardLighting }) as unknown as RenderPipelineFrame;
399
+ expect(shouldExecuteStandardClusterMembershipPass(asFrame(zero.value))).toBe(false);
400
+ expect(shouldExecuteStandardClusterMembershipPass(asFrame(one.value))).toBe(true);
401
+ expect(shouldExecuteStandardClusterMembershipPass(asFrame(zero.value))).toBe(false);
402
+ });
403
+
404
+ it('rejects raw capability and light data at the topology adapter boundary', () => {
405
+ const empty = zeroStandardLighting({ compute: true, storageBuffer: true });
406
+ expect(empty.ok).toBe(true);
407
+ if (!empty.ok) return;
408
+ expect(empty.value.kind).toBe('clustered');
409
+ if (empty.value.kind !== 'clustered') return;
410
+ const derived = deriveStandardTopologyInput({
411
+ kind: 'clustered',
412
+ prepared: empty.value.prepared,
413
+ transport: empty.value.transport,
414
+ lights: [],
415
+ } as unknown as Parameters<typeof deriveStandardTopologyInput>[0]);
416
+ expect(derived.ok).toBe(false);
417
+ if (derived.ok) return;
418
+ expect(derived.error.code).toBe('topology-input-invalid');
419
+ });
420
+
421
+ it('accepts an explicit no-local topology without allocating Cluster resources', () => {
422
+ const standardLighting = noLocalStandardLighting();
423
+ expect(standardLighting.ok).toBe(true);
424
+ if (!standardLighting.ok) return;
425
+ const graph = new RenderGraphBuilder<RenderPipelineFrame>();
426
+ const built = standardPipeline.build(
427
+ {
428
+ graph,
429
+ standardLighting: standardLighting.value,
430
+ capabilities: { rgba16floatRenderable: false },
431
+ projectGpuDriven: () => ok(undefined),
432
+ contributeFeatures: () => ok(undefined),
433
+ },
434
+ topology('forgeax::standard', {
435
+ lane: { compute: false, storageBuffer: false, multisample: false, maxColorAttachments: 4 },
436
+ camera: { tonemap: 'none', antialias: 'none', bloom: 'off' },
437
+ }),
438
+ );
439
+ expect(built.ok).toBe(true);
440
+ if (!built.ok) return;
441
+ const compiled = graph.compile({ device, surfaceSize: { width: 800, height: 600 } });
442
+ expect(compiled.ok).toBe(true);
443
+ if (!compiled.ok) return;
444
+ const info = compiled.value.inspect();
445
+ expect(info.passes.map((pass) => pass.name)).not.toContain('cluster-membership-producer');
446
+ expect(info.resources.map((resource) => resource.label)).not.toContain('hdrp-light-data');
447
+ });
107
448
  it('declares explicit output target domain and avoids alternate sRGB views', () => {
108
449
  const source = readFileSync(new URL('../render-pipeline.ts', import.meta.url), 'utf8');
109
450
  const postChainSource = readFileSync(
@@ -116,6 +457,25 @@ describe('typed built-in pipeline topology', () => {
116
457
  expect(source).not.toContain('viewFormats: [topology.surface.viewFormat]');
117
458
  });
118
459
 
460
+ it('forwards the output dither override into the fullscreen UBO write', () => {
461
+ const typedSource = readFileSync(
462
+ new URL('../typed-render-graph-primitives.ts', import.meta.url),
463
+ 'utf8',
464
+ );
465
+ const dispatcherSource = readFileSync(
466
+ new URL('../render-graph-primitives.ts', import.meta.url),
467
+ 'utf8',
468
+ );
469
+ expect(typedSource).toContain(
470
+ 'paramsOverride: options.paramsTransform(frame.postProcessParams.get(options.shader))',
471
+ );
472
+ expect(dispatcherSource).toContain('paramsOverride?: Uint8Array');
473
+ expect(dispatcherSource).toContain(
474
+ 'const data = paramsOverride ?? ctx.postProcessParams.get(shader);',
475
+ );
476
+ expect(dispatcherSource).toContain('input.paramsOverride,');
477
+ });
478
+
119
479
  it('declares stable JSON-safe output and observation inspection facts', () => {
120
480
  const source = readFileSync(new URL('../render-contract.ts', import.meta.url), 'utf8');
121
481
  for (const field of [
@@ -138,7 +498,7 @@ describe('typed built-in pipeline topology', () => {
138
498
  const compiled = compile(
139
499
  standardPipeline,
140
500
  topology('forgeax::standard', {
141
- standardProfile: { ...DEFAULT_STANDARD_PROFILE, lighting: 'clustered', ssao: true },
501
+ standardProfile: { ...DEFAULT_STANDARD_PROFILE, renderPath: 'deferred', ssao: true },
142
502
  }),
143
503
  );
144
504
  expect(compiled.ok).toBe(true);
@@ -147,6 +507,7 @@ describe('typed built-in pipeline topology', () => {
147
507
  const names = info.passes.map((pass) => pass.name);
148
508
  const ordered = [
149
509
  'cluster-membership-producer',
510
+ 'directional-shadow-observation',
150
511
  'g-buffer',
151
512
  'ssao-calc',
152
513
  'ssao-blur',
@@ -162,6 +523,8 @@ describe('typed built-in pipeline topology', () => {
162
523
  }
163
524
  expect(names.filter((name) => name.startsWith('point-shadow-'))).toHaveLength(6);
164
525
  expect(names.filter((name) => name.startsWith('shadowCascade'))).toHaveLength(4);
526
+ expect(names).toContain('directional-shadow-observation');
527
+ expect(names).toContain('linear-hdr-observation');
165
528
 
166
529
  const membership = info.passes.find((pass) => pass.name === 'cluster-membership-producer');
167
530
  expect(membership?.kind).toBe('compute');
@@ -185,11 +548,11 @@ describe('typed built-in pipeline topology', () => {
185
548
  const compiled = compile(
186
549
  standardPipeline,
187
550
  topology('forgeax::standard', {
188
- standardProfile: { ...DEFAULT_STANDARD_PROFILE, lighting: 'clustered' },
551
+ standardProfile: { ...DEFAULT_STANDARD_PROFILE, renderPath: 'deferred' },
189
552
  config: { ssao: { enabled: false } },
190
553
  lane: {
191
554
  compute: false,
192
- storageBuffer: false,
555
+ storageBuffer: true,
193
556
  multisample: false,
194
557
  maxColorAttachments: 8,
195
558
  },
@@ -245,7 +608,23 @@ describe('typed built-in pipeline topology', () => {
245
608
  expect(effect1?.dependencies).toContain('post-effect-copy-1');
246
609
  });
247
610
 
248
- it('removes unsupported MSAA and composite post work on the fallback lane', () => {
611
+ it('admits the fullscreen post-effect chain atomically', () => {
612
+ const visited: string[] = [];
613
+ const pending = resolvePostProcessChainAdmission(['example::a', 'example::b'], (identity) => {
614
+ visited.push(identity);
615
+ return identity === 'example::a';
616
+ });
617
+ expect(pending).toEqual({ admitted: [], pending: true });
618
+ expect(visited).toEqual(['example::a', 'example::b']);
619
+
620
+ const ready = resolvePostProcessChainAdmission(['example::a', 'example::b'], () => true);
621
+ expect(ready).toEqual({
622
+ admitted: ['example::a', 'example::b'],
623
+ pending: false,
624
+ });
625
+ });
626
+
627
+ it('removes unsupported MSAA while retaining the CPU storage lane', () => {
249
628
  const compiled = compile(
250
629
  standardPipeline,
251
630
  topology('forgeax::standard', {
@@ -253,7 +632,7 @@ describe('typed built-in pipeline topology', () => {
253
632
  config: { postEffects: ['example::a'] },
254
633
  lane: {
255
634
  compute: false,
256
- storageBuffer: false,
635
+ storageBuffer: true,
257
636
  multisample: false,
258
637
  maxColorAttachments: 4,
259
638
  },
@@ -263,7 +642,7 @@ describe('typed built-in pipeline topology', () => {
263
642
  if (!compiled.ok) return;
264
643
  const info = compiled.value.inspect();
265
644
  expect(info.resources.some((resource) => resource.label.includes('msaa'))).toBe(false);
266
- expect(info.passes.some((pass) => pass.name.startsWith('post-effect-'))).toBe(false);
645
+ expect(info.passes.some((pass) => pass.name.startsWith('post-effect-'))).toBe(true);
267
646
  });
268
647
 
269
648
  it('declares one TAA producer and resolve for TAA demand', () => {
@@ -299,9 +678,12 @@ describe('typed built-in pipeline topology', () => {
299
678
 
300
679
  it('fails closed when TAA history imports are missing', () => {
301
680
  const graph = new RenderGraphBuilder<RenderPipelineFrame>();
681
+ const standardLighting = zeroStandardLighting({ compute: true, storageBuffer: true });
682
+ if (!standardLighting.ok) throw standardLighting.error;
302
683
  const built = standardPipeline.build(
303
684
  {
304
685
  graph,
686
+ standardLighting: standardLighting.value,
305
687
  capabilities: { rgba16floatRenderable: true },
306
688
  projectGpuDriven: () => ok(undefined),
307
689
  contributeFeatures: () => ok(undefined),
@@ -326,7 +708,7 @@ describe('typed built-in pipeline topology', () => {
326
708
  temporal: { taa: true, motionBlur: true },
327
709
  lane: {
328
710
  compute: false,
329
- storageBuffer: false,
711
+ storageBuffer: true,
330
712
  multisample: false,
331
713
  maxColorAttachments: 4,
332
714
  },
@@ -412,11 +794,11 @@ describe('typed built-in pipeline topology', () => {
412
794
  ).toHaveLength(0);
413
795
  });
414
796
 
415
- it('returns a structured HDRP capability refusal before graph compilation', () => {
797
+ it('keeps deferred attachment admission independent from the Cluster transport', () => {
416
798
  const built = compile(
417
799
  standardPipeline,
418
800
  topology('forgeax::standard', {
419
- standardProfile: { ...DEFAULT_STANDARD_PROFILE, lighting: 'clustered' },
801
+ standardProfile: { ...DEFAULT_STANDARD_PROFILE, renderPath: 'deferred' },
420
802
  lane: {
421
803
  compute: true,
422
804
  storageBuffer: true,
@@ -425,8 +807,7 @@ describe('typed built-in pipeline topology', () => {
425
807
  },
426
808
  }),
427
809
  );
428
- expect(built.ok).toBe(false);
429
- if (!built.ok) expect(built.error.code).toBe('hdrp-deferred-caps-insufficient');
810
+ expect(built.ok).toBe(true);
430
811
  });
431
812
  });
432
813
 
@@ -40,10 +40,10 @@ describe('urp render feature targets', () => {
40
40
  });
41
41
  });
42
42
 
43
- it('keeps the surface target for non-storage-buffer LDR frames', () => {
43
+ it('publishes a float target for non-storage-buffer LDR frames', () => {
44
44
  expect(targets('none', 'none', false)?.[0]).toMatchObject({
45
45
  kind: 'scene-color',
46
- format: 'bgra8unorm-srgb',
46
+ format: 'rgba16float',
47
47
  sampleCount: 1,
48
48
  });
49
49
  });
@@ -82,4 +82,19 @@ describe('visibility resolver truth table', () => {
82
82
  expect(result.get(parent)?.intent).toBeUndefined();
83
83
  expect(result.get(child)?.effective).toBe('visible');
84
84
  });
85
+
86
+ it('invalidates the cached snapshot when visibility intent changes', () => {
87
+ const world = new World();
88
+ const parent = spawn(world, 'visible');
89
+ const child = spawn(world, 'inherited', parent);
90
+
91
+ const visible = resolveVisibility(world);
92
+ expect(visible.effective(child)).toBe('visible');
93
+
94
+ world.set(parent, Visibility, { state: VisibilityStateValue.hidden }).unwrap();
95
+
96
+ const hidden = resolveVisibility(world);
97
+ expect(hidden).not.toBe(visible);
98
+ expect(hidden.effective(child)).toBe('hidden');
99
+ });
85
100
  });
@@ -0,0 +1,85 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { fileURLToPath } from 'node:url';
3
+ import { describe, expect, it } from 'vitest';
4
+
5
+ const integrateShader = readFileSync(
6
+ fileURLToPath(new URL('../../../shader/src/volume/volume-integrate.wgsl', import.meta.url)),
7
+ 'utf8',
8
+ );
9
+
10
+ const EPSILON = 1e-6;
11
+
12
+ function clampExponent(value: number): number {
13
+ return Math.min(Math.max(value, -80), 80);
14
+ }
15
+
16
+ function beerLambert(extinction: number, distance: number, density: number): number {
17
+ const opticalDepth = Math.max(extinction * Math.max(distance, 0) * Math.max(density, 0), 0);
18
+ return Math.exp(-Math.min(opticalDepth, 80));
19
+ }
20
+
21
+ function henyeyGreenstein(cosTheta: number, anisotropy: number): number {
22
+ const g = Math.min(Math.max(anisotropy, -0.999), 0.999);
23
+ const denominator = Math.max(1 + g * g - 2 * g * cosTheta, EPSILON);
24
+ return (1 - g * g) / (4 * Math.PI * denominator ** 1.5);
25
+ }
26
+
27
+ describe('volumetric optics CPU oracle', () => {
28
+ it('requires the true volume integration shader owner', () => {
29
+ expect(integrateShader).toContain('fn hg');
30
+ expect(integrateShader).toContain('FOUR_PI');
31
+ expect(integrateShader).toContain('textureStore(resolved');
32
+ expect(integrateShader).not.toContain('apply_fog');
33
+ });
34
+
35
+ it('does not apply density twice to single-scatter radiance', () => {
36
+ expect(integrateShader).not.toContain('phase * sample.g * sample.r * (1.0 - transmittance)');
37
+ expect(integrateShader).toContain(
38
+ 'let local_scatter = radiance * volume_params.albedo.xyz * phase *',
39
+ );
40
+ expect(integrateShader).toContain('VOLUME_LIGHT_PHASE_SCALE * (1.0 - local_transmittance) +');
41
+ });
42
+
43
+ it('converts the shared integrated-light basis once before normalized HG', () => {
44
+ expect(integrateShader).toContain('const VOLUME_LIGHT_PHASE_SCALE : f32 = FOUR_PI;');
45
+ expect(integrateShader).toContain('phase *\n VOLUME_LIGHT_PHASE_SCALE *');
46
+ });
47
+
48
+ it('keeps empty density at identity transmittance', () => {
49
+ expect(beerLambert(2, 100, 0)).toBeCloseTo(1, 6);
50
+ expect(beerLambert(0, 100, 4)).toBeCloseTo(1, 6);
51
+ });
52
+
53
+ it('makes transmittance monotonic for increasing density', () => {
54
+ const samples = [0, 0.1, 0.5, 1, 4].map((density) => beerLambert(1.2, 3, density));
55
+ for (let index = 1; index < samples.length; index += 1) {
56
+ expect(samples[index]).toBeLessThanOrEqual((samples[index - 1] ?? 0) + EPSILON);
57
+ }
58
+ expect(samples.every(Number.isFinite)).toBe(true);
59
+ expect(samples.every((value) => value >= 0 && value <= 1)).toBe(true);
60
+ });
61
+
62
+ it('keeps phase finite and distinguishes forward and backward scattering', () => {
63
+ const isotropic = henyeyGreenstein(0, 0);
64
+ const forward = henyeyGreenstein(1, 0.7);
65
+ const backward = henyeyGreenstein(-1, 0.7);
66
+ expect(isotropic).toBeCloseTo(1 / (4 * Math.PI), 6);
67
+ expect(forward).toBeGreaterThan(backward);
68
+ expect([isotropic, forward, backward].every(Number.isFinite)).toBe(true);
69
+ });
70
+
71
+ it('clamps exponent input without producing NaN or Infinity', () => {
72
+ const values = [-Infinity, -120, 0, 120, Infinity].map((value) =>
73
+ Math.exp(clampExponent(value)),
74
+ );
75
+ expect(values.every(Number.isFinite)).toBe(true);
76
+ expect(values.every((value) => value > 0)).toBe(true);
77
+ });
78
+
79
+ it('cuts integration at the nearer depth or authored distance', () => {
80
+ const depthDistance = 4;
81
+ const maxDistance = 2;
82
+ expect(Math.min(depthDistance, maxDistance)).toBe(2);
83
+ expect(beerLambert(1, Math.min(depthDistance, maxDistance), 1)).toBeCloseTo(Math.exp(-2), 6);
84
+ });
85
+ });