@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
@@ -11,7 +11,11 @@ import type {
11
11
  RenderPipelineFrame,
12
12
  RenderPipelineTopology,
13
13
  } from '../render-pipeline';
14
- import { createRenderPipelineTarget, type RenderPipelineTarget } from '../render-pipeline';
14
+ import {
15
+ createRenderPipelineTarget,
16
+ type RenderPipelineTarget,
17
+ resolveOutputDither,
18
+ } from '../render-pipeline';
15
19
  import {
16
20
  addTypedBloomPasses,
17
21
  addTypedCompositePostEffects,
@@ -134,7 +138,7 @@ function missingTemporalContributor(
134
138
  return new SceneDataUnavailableError({
135
139
  featureIdentity: 'forgeax::standard',
136
140
  schema: 'forgeax::scene-data::temporal-v1',
137
- lane: topology.lane.compute ? (topology.standardProfile?.lighting ?? 'direct') : 'cpu-webgl2',
141
+ lane: topology.lane.compute ? 'clustered' : 'cpu-webgl2',
138
142
  reason: 'producer-missing',
139
143
  missingContributorIds: [contributorId],
140
144
  omittedMissingContributorCount: 0,
@@ -165,9 +169,13 @@ export function addStandardPost(
165
169
  // left clear and the following Output Transform presents a black frame.
166
170
  const bloomActive = hdr && topology.camera.bloom === 'on' && topology.camera.tonemap !== 'none';
167
171
  const fxaa = !clearOnly && topology.camera.antialias === 'fxaa';
168
- const msaa = !clearOnly && topology.camera.antialias === 'msaa' && topology.lane.multisample;
169
- const linearLdr = !clearOnly && ((!hdr && topology.lane.storageBuffer) || fxaa || msaa);
172
+ // All non-clear Standard frames use the graph-owned float scene target and
173
+ // the shared Output Transform, even when AA is disabled or the backend has
174
+ // no storage buffers. This keeps the final dither at one owner-level
175
+ // surface boundary instead of making no-AA/WebGL2 a silent bypass.
176
+ const linearLdr = !clearOnly && !hdr;
170
177
  const rawOnly = !clearOnly && topology.surface.profile?.kind === 'raw-only';
178
+ const outputDither = resolveOutputDither(topology.config);
171
179
  const motionBlur = !clearOnly && topology.temporal?.motionBlur === true;
172
180
  const postEffects = clearOnly ? [] : (topology.config?.postEffects ?? []);
173
181
  const rawOnlyPostInput =
@@ -312,7 +320,11 @@ export function addStandardPost(
312
320
  domain: 'display-encoded',
313
321
  });
314
322
  if (!output.ok) return output;
315
- const outputTransform = addTypedOutputTransformPass(context.graph, postInput, output.value);
323
+ // FXAA samples this display-encoded intermediate; leave dither to the
324
+ // final FXAA writer so the same pixel is not dithered twice.
325
+ const outputTransform = addTypedOutputTransformPass(context.graph, postInput, output.value, {
326
+ dither: false,
327
+ });
316
328
  if (!outputTransform.ok) return outputTransform;
317
329
  const aa = addTypedFullscreenPass(context.graph, {
318
330
  name: 'fxaa',
@@ -326,13 +338,20 @@ export function addStandardPost(
326
338
  context.graph,
327
339
  postInput,
328
340
  rawOnlyPostInput.value,
341
+ {
342
+ dither: outputDither && postEffects.length === 0,
343
+ },
329
344
  );
330
345
  if (!transformed.ok) return transformed;
331
346
  } else if (rawOnly || linearLdr) {
332
- const transformed = addTypedOutputTransformPass(context.graph, postInput, finalPresent);
347
+ const transformed = addTypedOutputTransformPass(context.graph, postInput, finalPresent, {
348
+ dither: outputDither,
349
+ });
333
350
  if (!transformed.ok) return transformed;
334
351
  } else {
335
- const outputTransform = addTypedOutputTransformPass(context.graph, postInput, finalPresent);
352
+ const outputTransform = addTypedOutputTransformPass(context.graph, postInput, finalPresent, {
353
+ dither: outputDither,
354
+ });
336
355
  if (!outputTransform.ok) return outputTransform;
337
356
  }
338
357
  }
@@ -1,25 +1,22 @@
1
1
  export const STANDARD_PIPELINE_ID = 'forgeax::standard' as const;
2
2
  export const STANDARD_LIGHT_COUNTS = [1, 32, 256] as const;
3
- /** Shared clustered-lighting facts consumed by pipeline, record, and buffers. */
4
- export const DEFAULT_CLUSTER_GRID = { x: 16, y: 9, z: 24 } as const;
5
- export const CLUSTER_GRID_STRIDE_U32 = 2;
6
- export const LIGHT_INDEX_LIST_CAPACITY = 1048576;
7
- export const MAX_LIGHTS = 256;
3
+ export {
4
+ CLUSTER_GRID_STRIDE_U32,
5
+ DEFAULT_CLUSTER_GRID,
6
+ LIGHT_INDEX_LIST_CAPACITY,
7
+ MAX_LIGHTS,
8
+ } from './standard-lighting/layout';
8
9
 
9
10
  export type StandardLightCount = (typeof STANDARD_LIGHT_COUNTS)[number];
10
- export type StandardLightingLane = 'direct' | 'clustered';
11
- export type StandardFallbackLane = 'native' | 'cpu-webgl2';
12
11
  export type StandardShadowMode = 'off' | 'hard' | 'filtered';
13
- export type StandardTone =
14
- | 'none'
15
- | 'aces-filmic'
16
- | 'agx'
17
- | 'cineon'
18
- | 'linear'
19
- | 'neutral'
20
- | 'reinhard'
21
- | 'reinhard-extended';
22
- export type StandardAntialias = 'none' | 'fxaa' | 'msaa';
12
+ export type StandardVolumetricFogQuality = 'low' | 'high';
13
+
14
+ export interface StandardVolumetricFogProfile {
15
+ readonly quality: StandardVolumetricFogQuality;
16
+ readonly depth: 48 | 64;
17
+ readonly tileSize: 4 | 16;
18
+ }
19
+
23
20
  export const STANDARD_POST_STAGE_NAMES = [
24
21
  'transparent-blend',
25
22
  'bloom',
@@ -29,37 +26,33 @@ export const STANDARD_POST_STAGE_NAMES = [
29
26
  'present',
30
27
  ] as const;
31
28
  export type StandardPostStage = (typeof STANDARD_POST_STAGE_NAMES)[number];
32
-
33
29
  export interface StandardProfile {
34
30
  readonly pipelineId: typeof STANDARD_PIPELINE_ID;
35
31
  readonly lightCount: StandardLightCount;
36
- readonly lighting: StandardLightingLane;
32
+ /** Graph topology selector; all local lights use the shared Cluster path. */
33
+ readonly renderPath: 'forward' | 'deferred';
37
34
  readonly shadows: StandardShadowMode;
38
35
  readonly pbr: boolean;
39
36
  readonly ibl: boolean;
40
37
  readonly ssao: boolean;
41
- readonly fallback: StandardFallbackLane;
38
+ /** Renderer-owned 1080p volume profile; tile width/height remain surface-derived. */
39
+ readonly volumetricFog?: StandardVolumetricFogProfile | undefined;
42
40
  readonly postStages: typeof STANDARD_POST_STAGE_NAMES;
43
41
  }
44
42
 
45
43
  export const DEFAULT_STANDARD_PROFILE: StandardProfile = Object.freeze({
46
44
  pipelineId: STANDARD_PIPELINE_ID,
47
45
  lightCount: 32,
48
- lighting: 'direct',
46
+ renderPath: 'forward',
49
47
  shadows: 'filtered',
50
48
  pbr: true,
51
49
  ibl: true,
52
50
  ssao: false,
53
- fallback: 'native',
54
51
  postStages: STANDARD_POST_STAGE_NAMES,
55
52
  });
56
53
 
57
- export type StandardLane = StandardLightingLane | 'cpu-webgl2';
58
-
59
- export function resolveStandardLane(
60
- profile: Pick<StandardProfile, 'lighting'>,
61
- capabilities: { readonly compute: boolean; readonly storageBuffer: boolean },
62
- ): StandardLane {
63
- if (!capabilities.compute || !capabilities.storageBuffer) return 'cpu-webgl2';
64
- return profile.lighting;
54
+ export function resolveVolumetricFogProfile(
55
+ profile: Pick<StandardProfile, 'volumetricFog'>,
56
+ ): StandardVolumetricFogProfile {
57
+ return profile.volumetricFog ?? { quality: 'high', depth: 64, tileSize: 4 };
65
58
  }
@@ -287,6 +287,69 @@ export function variantSetFromVertexLayoutProjection(
287
287
  return ok(parts.join('+'));
288
288
  }
289
289
 
290
+ /**
291
+ * Derive the capability axes shared by every Standard PBR draw owner.
292
+ *
293
+ * The all-true key is the canonical empty key used by the shader manifest;
294
+ * every other capability pair stays explicit so a no-local-light frame on a
295
+ * device without storage buffers cannot accidentally select a storage-backed
296
+ * PSO. Vertex-color is supplied here because the empty-key rule is only valid
297
+ * when that geometry-owned axis is also true.
298
+ */
299
+ export function standardCapabilityVariantSet(
300
+ clustered: boolean,
301
+ storageBuffer: boolean,
302
+ vertexColorAvailable: boolean,
303
+ ): string {
304
+ if (clustered && storageBuffer && vertexColorAvailable) return '';
305
+ return `CLUSTER_FORWARD_AVAILABLE=${clustered}+STORAGE_BUFFER_AVAILABLE=${storageBuffer}`;
306
+ }
307
+
308
+ /**
309
+ * Project the frame-owned Standard topology onto its material variant axes.
310
+ *
311
+ * `StandardTopologyInputValue.kind` is the only runtime authority for the
312
+ * cluster axis: a frame with local lights must request the clustered artifact,
313
+ * while the no-local-light topology requests the direct artifact. Keeping
314
+ * this projection beside the canonical key builder prevents PBR, skin, and
315
+ * sprite-lit record owners from independently inferring the active lane from
316
+ * whichever bind group happened to be constructed first.
317
+ */
318
+ export function standardTopologyVariantSet(
319
+ topology: { readonly kind: 'no-local-lights' | 'clustered' } | undefined,
320
+ storageBuffer: boolean,
321
+ vertexColorAvailable: boolean,
322
+ ): string {
323
+ return standardCapabilityVariantSet(
324
+ topology?.kind === 'clustered',
325
+ storageBuffer,
326
+ vertexColorAvailable,
327
+ );
328
+ }
329
+
330
+ /**
331
+ * Keep a clustered frame fail-closed when its unified group(2) resource is
332
+ * unavailable. A missing group is not evidence that the frame is direct; it
333
+ * is an invalid prepared resource state.
334
+ */
335
+ export function standardTopologyBindGroupReady(
336
+ topology: { readonly kind: 'no-local-lights' | 'clustered' } | undefined,
337
+ clusterBindGroup: unknown,
338
+ ): boolean {
339
+ return (
340
+ topology?.kind !== 'clustered' || (clusterBindGroup !== null && clusterBindGroup !== undefined)
341
+ );
342
+ }
343
+
344
+ /** Derive the storage axis for Standard-compatible unlit material draws. */
345
+ export function standardStorageVariantSet(
346
+ storageBuffer: boolean,
347
+ vertexColorAvailable: boolean,
348
+ ): string {
349
+ if (storageBuffer && vertexColorAvailable) return '';
350
+ return `STORAGE_BUFFER_AVAILABLE=${storageBuffer}+VERTEX_COLOR_AVAILABLE=${vertexColorAvailable}`;
351
+ }
352
+
290
353
  /**
291
354
  * Build a `GPURenderPipelineDescriptor` from a PipelineSpec + shader modules.
292
355
  *
@@ -925,14 +988,14 @@ const TRI_GEOMETRY_PBR = {
925
988
  shaderUvSetCount: 8,
926
989
  };
927
990
 
928
- // M6 fix-up: URP-variant key string for the standard PBR shader. Mirrors the
929
- // boot-time variantSet computed at createRenderer.ts step 1b when
930
- // `isHdrpActive=false` and `storageBufferCapable=true` (the URP default
931
- // surface). The URP record path requests this exact string at every
991
+ // M6 fix-up: non-clustered boot-variant key string for the standard PBR shader.
992
+ // Mirrors the boot-time variantSet computed at createRenderer.ts step 1b when
993
+ // clustered lighting is not yet admitted and `storageBufferCapable=true` (the
994
+ // surface). The default record path requests this exact string at every
932
995
  // `getMaterialShaderPipeline` call site, so seeding the table with these
933
- // variants keeps the URP cache lookup hot from frame 1 instead of
996
+ // variants keeps the default cache lookup hot from frame 1 instead of
934
997
  // skip-drawing the first ~1 frames while the async compile resolves.
935
- const URP_PBR_VARIANT_SET = 'CLUSTER_FORWARD_AVAILABLE=false+STORAGE_BUFFER_AVAILABLE=true';
998
+ const STANDARD_BOOT_VARIANT_SET = standardCapabilityVariantSet(false, true, false);
936
999
 
937
1000
  /**
938
1001
  * Build the boot-time pre-warm table: 15 standard PipelineSpec variants.
@@ -944,22 +1007,22 @@ const URP_PBR_VARIANT_SET = 'CLUSTER_FORWARD_AVAILABLE=false+STORAGE_BUFFER_AVAI
944
1007
  *
945
1008
  * Matrix:
946
1009
  * - 8 base material variants — {unlit, standard-pbr} x {LDR, HDR} x {S1, S4}
947
- * - 4 URP-variant standard-pbr — standard-pbr (variantSet=URP_PBR_VARIANT_SET)
948
- * x {LDR, HDR} x {S1, S4}; URP record path requests these specs by
1010
+ * - 4 non-clustered standard-pbr — standard-pbr (variantSet=STANDARD_BOOT_VARIANT_SET)
1011
+ * x {LDR, HDR} x {S1, S4}; the default record path requests these specs by
949
1012
  * `getMaterialShaderPipeline` keying off `cacheKeyOf` so the boot-time
950
1013
  * prewarm seeds `materialShaderPipelineCache` and avoids a 1-frame
951
- * async-compile skip-draw (M6 fix-up; see render-system-record.ts §URP)
1014
+ * async-compile skip-draw (M6 fix-up; see render-system-record.ts)
952
1015
  * - 3 fullscreen-post — tonemap (LDR S1) + skybox (HDR S1, HDR S4)
953
1016
  *
954
- * Total: 15. The URP variant is PBR-only because:
955
- * - unlit URP record path passes `variantSet=undefined` (the no-variant
1017
+ * Total: 15. The non-clustered variant is PBR-only because:
1018
+ * - unlit record path passes `variantSet=undefined` (the no-variant
956
1019
  * boot-default entries already cover it; createRenderer.ts §unlitRsp)
957
1020
  * - sprite goes through `getMaterialShaderPipeline('forgeax::sprite', ...)`
958
1021
  * lazily at first transparent-LDR-split draw — no boot-time pre-warm.
959
- * - HDRP variant (`variantSet=''` or compound `=true` form) is registered
1022
+ * - clustered variant (`variantSet=''` or compound `=true` form) is registered
960
1023
  * lazily when the clustered Standard profile activates, not at boot — adding clustered
961
1024
  * prewarm here would build PSOs against the URP layout (the boot-time
962
- * `pbrModule` is the URP variant when `isHdrpActive=false`)
1025
+ * `pbrModule` is the non-clustered variant before a frame admits local lights)
963
1026
  *
964
1027
  * `ldrViewFormat` parameterises the LDR color attachment format. Callers
965
1028
  * pass the runtime-resolved swap-chain view format (Channel 2 typically
@@ -1074,12 +1137,12 @@ export function buildSpecConstTable(
1074
1137
  renderState: undefined,
1075
1138
  },
1076
1139
 
1077
- // ── M6 fix-up: URP-variant standard-pbr prewarm ─────────────────────────
1078
- // URP record path (`getMaterialShaderPipeline`) requests
1079
- // `variantSet=URP_PBR_VARIANT_SET` for standard-shading entities; pre-M6
1140
+ // ── M6 fix-up: non-clustered standard-pbr prewarm ─────────────────────────
1141
+ // The default record path (`getMaterialShaderPipeline`) requests
1142
+ // `variantSet=STANDARD_BOOT_VARIANT_SET` for standard-shading entities; pre-M6
1080
1143
  // the silent `selectStandardFallbackPipeline` shim served the no-variant
1081
1144
  // boot prewarm during the 1-frame async-compile warmup. With M6's
1082
- // explicit-failure surface, those URP requests must hit the boot prewarm
1145
+ // explicit-failure surface, those requests must hit the boot prewarm
1083
1146
  // by their own variantSet — these 4 entries close that gap.
1084
1147
 
1085
1148
  // standard PBR URP LDR S1
@@ -1087,7 +1150,7 @@ export function buildSpecConstTable(
1087
1150
  shader: {
1088
1151
  id: 'forgeax::default-standard-pbr',
1089
1152
  passKind: 'forward',
1090
- variantSet: URP_PBR_VARIANT_SET,
1153
+ variantSet: STANDARD_BOOT_VARIANT_SET,
1091
1154
  },
1092
1155
  attachments: TRI_ATTACHMENTS_LDR_S1,
1093
1156
  geometry: TRI_GEOMETRY_PBR,
@@ -1098,7 +1161,7 @@ export function buildSpecConstTable(
1098
1161
  shader: {
1099
1162
  id: 'forgeax::default-standard-pbr',
1100
1163
  passKind: 'forward',
1101
- variantSet: URP_PBR_VARIANT_SET,
1164
+ variantSet: STANDARD_BOOT_VARIANT_SET,
1102
1165
  },
1103
1166
  attachments: TRI_ATTACHMENTS_LDR_S4,
1104
1167
  geometry: TRI_GEOMETRY_PBR,
@@ -1109,7 +1172,7 @@ export function buildSpecConstTable(
1109
1172
  shader: {
1110
1173
  id: 'forgeax::default-standard-pbr',
1111
1174
  passKind: 'forward',
1112
- variantSet: URP_PBR_VARIANT_SET,
1175
+ variantSet: STANDARD_BOOT_VARIANT_SET,
1113
1176
  },
1114
1177
  attachments: TRI_ATTACHMENTS_HDR_S1,
1115
1178
  geometry: TRI_GEOMETRY_PBR,
@@ -1120,7 +1183,7 @@ export function buildSpecConstTable(
1120
1183
  shader: {
1121
1184
  id: 'forgeax::default-standard-pbr',
1122
1185
  passKind: 'forward',
1123
- variantSet: URP_PBR_VARIANT_SET,
1186
+ variantSet: STANDARD_BOOT_VARIANT_SET,
1124
1187
  },
1125
1188
  attachments: TRI_ATTACHMENTS_HDR_S4,
1126
1189
  geometry: TRI_GEOMETRY_PBR,
@@ -0,0 +1,55 @@
1
+ import type { PointShadowSnapshot } from './render-system-extract';
2
+
3
+ /**
4
+ * Detached point-shadow budget facts from one completed extract/record frame.
5
+ *
6
+ * `requested` is the number of PointLight + PointLightShadow pairs. The
7
+ * renderer owns the bounded cube-array atlas, so `admitted`, `shadowed`, and
8
+ * `shadowAtlasOccupancy` are all derived from the layer sentinel emitted by
9
+ * the same extract owner. No consumer needs to maintain a second budget
10
+ * ledger.
11
+ */
12
+ export interface PointShadowInspection {
13
+ readonly status: 'inactive' | 'ready' | 'over-budget';
14
+ readonly requested: number;
15
+ readonly admitted: number;
16
+ readonly shadowed: number;
17
+ readonly shadowAtlasOccupancy: number;
18
+ readonly shadowAtlasCapacity: number;
19
+ }
20
+
21
+ /** Project point-shadow snapshots to the public, JSON-safe inspection shape. */
22
+ export function inspectPointShadow(
23
+ snapshots: readonly PointShadowSnapshot[],
24
+ shadowAtlasCapacity: number,
25
+ ): PointShadowInspection {
26
+ const requested = snapshots.length;
27
+ const admitted = snapshots.reduce(
28
+ (count, snapshot) =>
29
+ snapshot.shadowAtlasLayer >= 0 && snapshot.shadowAtlasLayer < shadowAtlasCapacity
30
+ ? count + 1
31
+ : count,
32
+ 0,
33
+ );
34
+ // Never publish a misleading `ready` state when any requested shadow owns
35
+ // no atlas layer. The extract sentinel is the single admission projection;
36
+ // a partial or all-sentinel frame is therefore a failed/over-budget
37
+ // admission, not an active shadow path with incomplete output.
38
+ const status =
39
+ requested === 0
40
+ ? ('inactive' as const)
41
+ : requested > shadowAtlasCapacity || admitted !== requested
42
+ ? ('over-budget' as const)
43
+ : ('ready' as const);
44
+ return Object.freeze({
45
+ status,
46
+ requested,
47
+ admitted,
48
+ // A point shadow only samples an atlas after it owns an atlas layer. Keep
49
+ // these names explicit for AI diagnostics while deriving both from the
50
+ // one layer projection above.
51
+ shadowed: admitted,
52
+ shadowAtlasOccupancy: admitted,
53
+ shadowAtlasCapacity,
54
+ });
55
+ }
@@ -2,7 +2,7 @@ import { vec3 } from '@forgeax/engine-math';
2
2
  import type { Buffer, RhiQueue } from '@forgeax/engine-rhi';
3
3
  import { describe, expect, it } from 'vitest';
4
4
  import type { CameraSnapshot } from '../../render-contract';
5
- import type { RenderableSnapshot } from '../../render-system-extract';
5
+ import type { RenderableSnapshot, SpotLightSnapshot } from '../../render-system-extract';
6
6
  import type { TemporalView } from '../../temporal/temporal-view';
7
7
  import type { ValidatedRenderable } from '../frame-snapshot';
8
8
  import { uploadMeshSsboBatch } from '../mesh-ssbo';
@@ -98,6 +98,47 @@ function temporalView(): TemporalView {
98
98
  }
99
99
 
100
100
  describe('temporal GPU payloads', () => {
101
+ it('publishes a projector-only SpotLight matrix into the explicit lane-0 contract', () => {
102
+ const { queue, uploads } = recordingQueue();
103
+ const projectorMatrix = translatedMatrix(9);
104
+ const projector: SpotLightSnapshot = {
105
+ kind: 'spot',
106
+ entity: 4,
107
+ position: vec3.create(0, 2, 0),
108
+ direction: vec3.create(0, -1, 0),
109
+ color: vec3.create(1, 1, 1),
110
+ intensity: 1,
111
+ invRangeSquared: 0.04,
112
+ cosInner: 0.95,
113
+ cosOuter: 0.8,
114
+ castShadow: false,
115
+ lightViewProj: projectorMatrix,
116
+ mapSize: 512,
117
+ nearPlane: 0.1,
118
+ farPlane: 25,
119
+ shadowAtlasTile: -1,
120
+ projectorAsset: { kind: 'texture' } as never,
121
+ };
122
+ writeViewUbo(
123
+ queue,
124
+ {} as Buffer,
125
+ camera(),
126
+ {
127
+ kind: 'directional',
128
+ direction: vec3.create(0, -1, 0),
129
+ color: vec3.create(1, 1, 1),
130
+ intensity: 1,
131
+ },
132
+ { point: [], spot: [projector] } as never,
133
+ [projector],
134
+ undefined,
135
+ undefined,
136
+ 0,
137
+ );
138
+ const payload = new Float32Array(uploads[0]?.buffer ?? new ArrayBuffer(0));
139
+ expect(payload[132 + 12]).toBe(9);
140
+ });
141
+
101
142
  it('writes temporal current/previous projection after the spot-light lanes', () => {
102
143
  const { queue, uploads } = recordingQueue();
103
144
  writeViewUbo(
@@ -0,0 +1,73 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { selectPipelineLayoutForVariant } from '../../assembly/factory';
3
+ import { resolvePipelineGroup2Contract } from '../../pbr-pipeline';
4
+ import { projectDeviceOwnedBindGroup } from '../frame-lighting';
5
+ import { selectMaterialGroup2 } from '../main-pass-material';
6
+
7
+ const meshGroup = { label: 'pbr-mesh-bg' } as never;
8
+ const clusterGroup = { label: 'hdrp-unified-bg-group2' } as never;
9
+
10
+ describe('transparent material group 2 selection', () => {
11
+ it('uses the shared cluster layout only for standard materials', () => {
12
+ expect(selectMaterialGroup2(clusterGroup, meshGroup, 'cluster')).toBe(clusterGroup);
13
+ expect(selectMaterialGroup2(clusterGroup, meshGroup, 'mesh')).toBe(meshGroup);
14
+ });
15
+
16
+ it('returns null when a non-standard material lacks its ordinary mesh group', () => {
17
+ expect(selectMaterialGroup2(clusterGroup, null, 'mesh')).toBeNull();
18
+ });
19
+
20
+ it('resolves mesh, skin, and clustered skin from the composed shader artifact', () => {
21
+ expect(
22
+ resolvePipelineGroup2Contract('@group(2) @binding(0) var<storage> mesh: array<u32>;'),
23
+ ).toBe('mesh');
24
+ expect(
25
+ resolvePipelineGroup2Contract(
26
+ '@group(2) @binding(0) var<storage> mesh: array<u32>; @group(2) @binding(1) var<uniform> palette: mat4x4<f32>;',
27
+ ),
28
+ ).toBe('skin');
29
+ expect(
30
+ resolvePipelineGroup2Contract(
31
+ '@group(2) @binding(0) var<storage> mesh: array<u32>; @group(2) @binding(1) var<storage> palette: array<u32>; @group(2) @binding(4) var<storage> lights: array<u32>;',
32
+ ),
33
+ ).toBe('skin-cluster');
34
+ });
35
+
36
+ it('lets the resolved clustered contract select the unified group', () => {
37
+ expect(selectMaterialGroup2(clusterGroup, meshGroup, 'cluster')).toBe(clusterGroup);
38
+ expect(selectMaterialGroup2(clusterGroup, meshGroup, 'skin')).toBeNull();
39
+ // A skin-cluster PSO cannot consume the ordinary HDRP group. The caller
40
+ // must provide the dedicated skin bind group through the skin path; when
41
+ // it is absent, fail-stop instead of submitting a layout-invalid draw.
42
+ expect(selectMaterialGroup2(clusterGroup, meshGroup, 'skin-cluster')).toBeNull();
43
+ });
44
+
45
+ it('keeps a mesh contract on the mesh pipeline despite the HDRP variant key', () => {
46
+ const pbrPipelineLayout = { label: 'pbr-pl' } as never;
47
+ const hdrpPipelineLayout = { label: 'hdrp-pbr-pl' } as never;
48
+ expect(
49
+ selectPipelineLayoutForVariant(
50
+ {
51
+ pbrPipelineLayout,
52
+ hdrpPbrPipelineLayout: hdrpPipelineLayout,
53
+ pbrSkinPipelineLayout: null,
54
+ hdrpSkinPipelineLayout: null,
55
+ },
56
+ '',
57
+ 'pbr',
58
+ ),
59
+ ).toBe(pbrPipelineLayout);
60
+ });
61
+
62
+ it('rejects membership bindings from a retired device or layout', () => {
63
+ const deviceA = {} as never;
64
+ const deviceB = {} as never;
65
+ const layoutA = {} as never;
66
+ const layoutB = {} as never;
67
+ const bindGroup = {} as never;
68
+ const state = { device: deviceA, layout: layoutA, bindGroup };
69
+ expect(projectDeviceOwnedBindGroup(state, deviceA, layoutA)).toBe(bindGroup);
70
+ expect(projectDeviceOwnedBindGroup(state, deviceB, layoutA)).toBeNull();
71
+ expect(projectDeviceOwnedBindGroup(state, deviceA, layoutB)).toBeNull();
72
+ });
73
+ });
@@ -0,0 +1,40 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { fileURLToPath } from 'node:url';
3
+ import { describe, expect, it } from 'vitest';
4
+
5
+ const viewUboSrc = readFileSync(fileURLToPath(new URL('../view-ubo.ts', import.meta.url)), 'utf8');
6
+ const commonWgslSrc = readFileSync(
7
+ fileURLToPath(new URL('../../../..//shader/src/common.wgsl', import.meta.url)),
8
+ 'utf8',
9
+ );
10
+
11
+ describe('View UBO directional shadow ABI', () => {
12
+ it('keeps the 960 B payload inside the existing 1024 B slot', () => {
13
+ expect(viewUboSrc).toMatch(/VIEW_UNIFORM_BYTES\s*=\s*960/);
14
+ expect(viewUboSrc).toMatch(/VIEW_UNIFORM_SLOT_STRIDE\s*=\s*1024/);
15
+ expect(viewUboSrc).toMatch(/VIEW_PAYLOAD_FLOATS\s*=\s*240/);
16
+ });
17
+
18
+ it('packs all four cascade metrics into the existing vec4 lanes', () => {
19
+ expect(viewUboSrc).toMatch(/viewPayload\[108 \+ s \* 4 \+ lane\]/);
20
+ expect(viewUboSrc).not.toMatch(/viewPayload\[108 \+ s \* 4\]\s*=\s*lights\.splitPlanes/);
21
+ });
22
+
23
+ it('keeps bias at [126]/[127], directional filter at [128..131], and Spot at 132', () => {
24
+ expect(viewUboSrc).toMatch(/viewPayload\[126\]\s*=\s*lights\.depthBias/);
25
+ expect(viewUboSrc).toMatch(/viewPayload\[127\]\s*=\s*lights\.normalBias/);
26
+ expect(viewUboSrc).toMatch(/viewPayload\[128\]/);
27
+ expect(viewUboSrc).toMatch(/viewPayload\[129\]/);
28
+ expect(viewUboSrc).toMatch(/viewPayload\[130\]/);
29
+ expect(viewUboSrc).toMatch(/viewPayload\[131\]/);
30
+ expect(viewUboSrc).toMatch(/SPOT_LVP_BASE_FLOAT\s*=\s*132/);
31
+ expect(viewUboSrc).not.toMatch(/lights\.pcfKernelSize/);
32
+ });
33
+
34
+ it('declares one shared View binding with a vec4 directional filter carrier', () => {
35
+ const viewStruct = commonWgslSrc.match(/struct View \{[\s\S]*?\n\};/)?.[0] ?? '';
36
+ expect(viewStruct).toMatch(/directionalShadowFilter\s*:\s*vec4<f32>/);
37
+ expect(viewStruct).not.toMatch(/pcfKernelSize/);
38
+ expect(commonWgslSrc.match(/@group\(0\) @binding\(0\) var<uniform> view/g)).toHaveLength(1);
39
+ });
40
+ });