@forgeax/engine-render 0.1.21 → 0.1.23

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 (426) hide show
  1. package/README.md +150 -29
  2. package/dist/assembly/backend-contract.d.ts +13 -0
  3. package/dist/assembly/backend-contract.d.ts.map +1 -1
  4. package/dist/assembly/device-feature-admission.d.ts.map +1 -1
  5. package/dist/assembly/factory.d.ts +14 -6
  6. package/dist/assembly/factory.d.ts.map +1 -1
  7. package/dist/assembly/host-contract.d.ts +10 -0
  8. package/dist/assembly/host-contract.d.ts.map +1 -1
  9. package/dist/authoring.mjs +1 -1
  10. package/dist/{chunk-PCHBPYML.mjs → chunk-4L3P6DT5.mjs} +2 -2
  11. package/dist/chunk-4L3P6DT5.mjs.map +1 -0
  12. package/dist/{chunk-VFPLTKCU.mjs → chunk-4NPUASZR.mjs} +15 -4
  13. package/dist/chunk-4NPUASZR.mjs.map +1 -0
  14. package/dist/chunk-DIYW3M5D.mjs +5085 -0
  15. package/dist/chunk-DIYW3M5D.mjs.map +1 -0
  16. package/dist/chunk-FBC4R44J.mjs +1003 -0
  17. package/dist/chunk-FBC4R44J.mjs.map +1 -0
  18. package/dist/{chunk-FS2KNUQH.mjs → chunk-FF7K3FSO.mjs} +253 -489
  19. package/dist/chunk-FF7K3FSO.mjs.map +1 -0
  20. package/dist/chunk-HZUPSYW7.mjs +1104 -0
  21. package/dist/chunk-HZUPSYW7.mjs.map +1 -0
  22. package/dist/{chunk-6OPXDQ2E.mjs → chunk-QODA5UKU.mjs} +33 -294
  23. package/dist/chunk-QODA5UKU.mjs.map +1 -0
  24. package/dist/chunk-YALPS44A.mjs +296 -0
  25. package/dist/chunk-YALPS44A.mjs.map +1 -0
  26. package/dist/{chunk-5YADUCA7.mjs → chunk-ZUWKAZ2I.mjs} +585 -991
  27. package/dist/chunk-ZUWKAZ2I.mjs.map +1 -0
  28. package/dist/cluster-binner.d.ts +5 -0
  29. package/dist/cluster-binner.d.ts.map +1 -1
  30. package/dist/components/index.d.ts +3 -0
  31. package/dist/components/index.d.ts.map +1 -1
  32. package/dist/components/light-helpers.d.ts +4 -0
  33. package/dist/components/light-helpers.d.ts.map +1 -1
  34. package/dist/components/light-probe.d.ts +9 -0
  35. package/dist/components/light-probe.d.ts.map +1 -0
  36. package/dist/components/rect-area-light.d.ts +12 -0
  37. package/dist/components/rect-area-light.d.ts.map +1 -0
  38. package/dist/components/scene-instance.d.ts +4 -0
  39. package/dist/components/scene-instance.d.ts.map +1 -1
  40. package/dist/components/spot-light.d.ts +3 -0
  41. package/dist/components/spot-light.d.ts.map +1 -1
  42. package/dist/construct-renderer.mjs +10437 -10044
  43. package/dist/construct-renderer.mjs.map +1 -1
  44. package/dist/device/gpu-residency.d.ts +8 -1
  45. package/dist/device/gpu-residency.d.ts.map +1 -1
  46. package/dist/device/lifecycle-transaction.d.ts +2 -0
  47. package/dist/device/lifecycle-transaction.d.ts.map +1 -0
  48. package/dist/environment/lifecycle.d.ts.map +1 -1
  49. package/dist/errors/gpu-driven.d.ts +26 -0
  50. package/dist/errors/gpu-driven.d.ts.map +1 -0
  51. package/dist/errors/index.d.ts +1 -0
  52. package/dist/errors/index.d.ts.map +1 -1
  53. package/dist/errors/recover.d.ts +9 -1
  54. package/dist/errors/recover.d.ts.map +1 -1
  55. package/dist/errors/render.d.ts +26 -1
  56. package/dist/errors/render.d.ts.map +1 -1
  57. package/dist/extract/gpu-driven.d.ts +50 -0
  58. package/dist/extract/gpu-driven.d.ts.map +1 -0
  59. package/dist/extract/visibility.d.ts.map +1 -1
  60. package/dist/gpu-driven/batch-topology.d.ts +34 -0
  61. package/dist/gpu-driven/batch-topology.d.ts.map +1 -1
  62. package/dist/gpu-driven/prepared-draw.d.ts +63 -0
  63. package/dist/gpu-driven/prepared-draw.d.ts.map +1 -0
  64. package/dist/gpu-driven/production-raster.d.ts +98 -2
  65. package/dist/gpu-driven/production-raster.d.ts.map +1 -1
  66. package/dist/gpu-driven/view-gpu.d.ts +64 -1
  67. package/dist/gpu-driven/view-gpu.d.ts.map +1 -1
  68. package/dist/gpu-scene-schema.d.ts +14 -0
  69. package/dist/gpu-scene-schema.d.ts.map +1 -1
  70. package/dist/gpu-scene.d.ts +8 -1
  71. package/dist/gpu-scene.d.ts.map +1 -1
  72. package/dist/gpu-usage.d.ts +0 -1
  73. package/dist/gpu-usage.d.ts.map +1 -1
  74. package/dist/hdrp-buffers.d.ts +3 -3
  75. package/dist/hdrp-buffers.d.ts.map +1 -1
  76. package/dist/index.d.ts +12 -1
  77. package/dist/index.d.ts.map +1 -1
  78. package/dist/index.mjs +90 -5
  79. package/dist/index.mjs.map +1 -1
  80. package/dist/inspection-types.d.ts +163 -1
  81. package/dist/inspection-types.d.ts.map +1 -1
  82. package/dist/internal.d.ts +9 -0
  83. package/dist/internal.d.ts.map +1 -1
  84. package/dist/internal.mjs +23 -2
  85. package/dist/internal.mjs.map +1 -1
  86. package/dist/light-buffer-layout.d.ts +58 -149
  87. package/dist/light-buffer-layout.d.ts.map +1 -1
  88. package/dist/materials.d.ts.map +1 -1
  89. package/dist/pbr-pipeline.d.ts +26 -12
  90. package/dist/pbr-pipeline.d.ts.map +1 -1
  91. package/dist/pipeline/standard-forward-lane.d.ts.map +1 -1
  92. package/dist/pipeline/standard-lighting/graph.d.ts.map +1 -1
  93. package/dist/pipeline/standard-lighting/light-frame.d.ts +3 -3
  94. package/dist/pipeline/standard-lighting/light-frame.d.ts.map +1 -1
  95. package/dist/pipeline/standard-lighting/topology.d.ts +1 -1
  96. package/dist/pipeline/standard-lighting/topology.d.ts.map +1 -1
  97. package/dist/pipeline/standard-lighting/transport.d.ts.map +1 -1
  98. package/dist/pipeline/standard-pipeline.d.ts.map +1 -1
  99. package/dist/pipeline-builder.d.ts +2 -0
  100. package/dist/pipeline-builder.d.ts.map +1 -1
  101. package/dist/pipeline-spec.d.ts +1 -1
  102. package/dist/pipeline-spec.d.ts.map +1 -1
  103. package/dist/plugin.d.ts.map +1 -1
  104. package/dist/prepare/extended-lighting/ltc-resources.d.ts +31 -0
  105. package/dist/prepare/extended-lighting/ltc-resources.d.ts.map +1 -0
  106. package/dist/prepare/extended-lighting/resources.d.ts +72 -0
  107. package/dist/prepare/extended-lighting/resources.d.ts.map +1 -0
  108. package/dist/prepare/extended-lighting/spot-modifiers.d.ts +42 -0
  109. package/dist/prepare/extended-lighting/spot-modifiers.d.ts.map +1 -0
  110. package/dist/prepare/extended-lighting/state.d.ts +24 -0
  111. package/dist/prepare/extended-lighting/state.d.ts.map +1 -0
  112. package/dist/record/frame-lighting.d.ts +8 -6
  113. package/dist/record/frame-lighting.d.ts.map +1 -1
  114. package/dist/record/frame-snapshot.d.ts +50 -10
  115. package/dist/record/frame-snapshot.d.ts.map +1 -1
  116. package/dist/record/frame.d.ts +7 -3
  117. package/dist/record/frame.d.ts.map +1 -1
  118. package/dist/record/gpu-timing.d.ts +19 -7
  119. package/dist/record/gpu-timing.d.ts.map +1 -1
  120. package/dist/record/helpers.d.ts +0 -20
  121. package/dist/record/helpers.d.ts.map +1 -1
  122. package/dist/record/main-pass-geometry.d.ts +6 -1
  123. package/dist/record/main-pass-geometry.d.ts.map +1 -1
  124. package/dist/record/main-pass-material.d.ts +3 -1
  125. package/dist/record/main-pass-material.d.ts.map +1 -1
  126. package/dist/record/main-pass-sprite-draws.d.ts.map +1 -1
  127. package/dist/record/mesh-ssbo.d.ts.map +1 -1
  128. package/dist/record/render-context.d.ts +57 -6
  129. package/dist/record/render-context.d.ts.map +1 -1
  130. package/dist/record/shadow-pass.d.ts.map +1 -1
  131. package/dist/record/typed-frame-graph.d.ts +49 -2
  132. package/dist/record/typed-frame-graph.d.ts.map +1 -1
  133. package/dist/record/view-ubo.d.ts +1 -2
  134. package/dist/record/view-ubo.d.ts.map +1 -1
  135. package/dist/reflection/inspection.d.ts +10 -0
  136. package/dist/reflection/inspection.d.ts.map +1 -0
  137. package/dist/reflection/projection.d.ts +49 -0
  138. package/dist/reflection/projection.d.ts.map +1 -1
  139. package/dist/reflection/record-owner.d.ts +60 -0
  140. package/dist/reflection/record-owner.d.ts.map +1 -0
  141. package/dist/render-contract.d.ts +14 -8
  142. package/dist/render-contract.d.ts.map +1 -1
  143. package/dist/render-pipeline.d.ts +7 -0
  144. package/dist/render-pipeline.d.ts.map +1 -1
  145. package/dist/render-system-extract.d.ts +104 -16
  146. package/dist/render-system-extract.d.ts.map +1 -1
  147. package/dist/render-system.d.ts +12 -1
  148. package/dist/render-system.d.ts.map +1 -1
  149. package/dist/scene/probe-blend-record.d.ts +22 -0
  150. package/dist/scene/probe-blend-record.d.ts.map +1 -0
  151. package/dist/scene/probe-blend.d.ts +142 -0
  152. package/dist/scene/probe-blend.d.ts.map +1 -0
  153. package/dist/scene/render-scene-types.d.ts +0 -6
  154. package/dist/scene/render-scene-types.d.ts.map +1 -1
  155. package/dist/scene/render-scene.d.ts +103 -9
  156. package/dist/scene/render-scene.d.ts.map +1 -1
  157. package/dist/scene/visibility/bounds.d.ts +20 -0
  158. package/dist/scene/visibility/bounds.d.ts.map +1 -0
  159. package/dist/scene/visibility/budget.d.ts +16 -0
  160. package/dist/scene/visibility/budget.d.ts.map +1 -0
  161. package/dist/scene/visibility/facet.d.ts +73 -0
  162. package/dist/scene/visibility/facet.d.ts.map +1 -0
  163. package/dist/scene/visibility/gpu-frame-timing.d.ts +41 -0
  164. package/dist/scene/visibility/gpu-frame-timing.d.ts.map +1 -0
  165. package/dist/scene/visibility/gpu-lod.d.ts +46 -0
  166. package/dist/scene/visibility/gpu-lod.d.ts.map +1 -0
  167. package/dist/scene/visibility/inspection.d.ts +132 -0
  168. package/dist/scene/visibility/inspection.d.ts.map +1 -0
  169. package/dist/scene/visibility/lod-selector.d.ts +25 -0
  170. package/dist/scene/visibility/lod-selector.d.ts.map +1 -0
  171. package/dist/scene/visibility/occlusion-confidence.d.ts +81 -0
  172. package/dist/scene/visibility/occlusion-confidence.d.ts.map +1 -0
  173. package/dist/scene/visibility/occlusion-pass.d.ts +57 -0
  174. package/dist/scene/visibility/occlusion-pass.d.ts.map +1 -0
  175. package/dist/scene/visibility/occlusion-query-pool.d.ts +57 -0
  176. package/dist/scene/visibility/occlusion-query-pool.d.ts.map +1 -0
  177. package/dist/scene/visibility/occlusion-runtime.d.ts +96 -0
  178. package/dist/scene/visibility/occlusion-runtime.d.ts.map +1 -0
  179. package/dist/scene/visibility/types.d.ts +34 -0
  180. package/dist/scene/visibility/types.d.ts.map +1 -0
  181. package/dist/ssr/admission.d.ts +122 -0
  182. package/dist/ssr/admission.d.ts.map +1 -0
  183. package/dist/ssr/composition.d.ts +32 -0
  184. package/dist/ssr/composition.d.ts.map +1 -0
  185. package/dist/ssr/errors.d.ts +39 -0
  186. package/dist/ssr/errors.d.ts.map +1 -0
  187. package/dist/ssr/identity.d.ts +13 -0
  188. package/dist/ssr/identity.d.ts.map +1 -0
  189. package/dist/systems/skin-palette-allocator.d.ts +1 -1
  190. package/dist/temporal/frame-coordinator.d.ts +13 -0
  191. package/dist/temporal/frame-coordinator.d.ts.map +1 -1
  192. package/dist/temporal/frame.d.ts +2 -0
  193. package/dist/temporal/frame.d.ts.map +1 -1
  194. package/dist/temporal/index.mjs +6 -5
  195. package/dist/temporal/view.d.ts +7 -0
  196. package/dist/temporal/view.d.ts.map +1 -1
  197. package/dist/tilemap-chunk-extract-system.d.ts +1 -1
  198. package/dist/typed-render-graph-primitives.d.ts +4 -0
  199. package/dist/typed-render-graph-primitives.d.ts.map +1 -1
  200. package/dist/volume/passes.d.ts +8 -4
  201. package/dist/volume/passes.d.ts.map +1 -1
  202. package/package.json +20 -20
  203. package/src/__tests__/cluster-grid-default-surface.unit.test.ts +5 -0
  204. package/src/__tests__/detached-inspection.integration.test.ts +6 -0
  205. package/src/__tests__/device-feature-admission.unit.test.ts +13 -1
  206. package/src/__tests__/direct-light-snapshot.unit.test.ts +59 -3
  207. package/src/__tests__/environment-generation.integration.test.ts +16 -0
  208. package/src/__tests__/evidence-identity.ts +13 -0
  209. package/src/__tests__/evidence-identity.unit.test.ts +24 -0
  210. package/src/__tests__/extended-lighting-recovery.browser.test.ts +14 -0
  211. package/src/__tests__/extended-lighting-recovery.dawn.test.ts +14 -0
  212. package/src/__tests__/extended-lighting-recovery.integration.test.ts +81 -0
  213. package/src/__tests__/extended-lighting-resources.unit.test.ts +51 -0
  214. package/src/__tests__/extended-lighting-zero-feature.integration.test.ts +52 -0
  215. package/src/__tests__/factory-contract.integration.test.ts +33 -0
  216. package/src/__tests__/frame-observation.contract.test.ts +15 -0
  217. package/src/__tests__/gpu-driven-batch-topology-pbr.unit.test.ts +146 -0
  218. package/src/__tests__/gpu-driven-indirect-raster-evidence.ts +1 -0
  219. package/src/__tests__/gpu-driven-indirect-raster.dawn.test.ts +1 -0
  220. package/src/__tests__/gpu-driven-production.integration.test.ts +572 -9
  221. package/src/__tests__/gpu-driven-scaling.unit.test.ts +66 -2
  222. package/src/__tests__/gpu-driven-view-gpu-evidence.ts +26 -1
  223. package/src/__tests__/gpu-driven-view-graph.integration.test.ts +89 -2
  224. package/src/__tests__/gpu-driven-view.browser.test.ts +3 -3
  225. package/src/__tests__/gpu-driven-view.dawn.test.ts +3 -3
  226. package/src/__tests__/gpu-driven-view.unit.test.ts +50 -0
  227. package/src/__tests__/gpu-lod-evidence.ts +188 -0
  228. package/src/__tests__/gpu-lod-fallback.integration.test.ts +10 -0
  229. package/src/__tests__/gpu-lod-indirect.integration.test.ts +40 -0
  230. package/src/__tests__/gpu-lod-parity.dawn.test.ts +29 -0
  231. package/src/__tests__/gpu-lod-readiness.integration.test.ts +52 -0
  232. package/src/__tests__/gpu-lod-schema.unit.test.ts +66 -0
  233. package/src/__tests__/gpu-lod-selector.unit.test.ts +28 -0
  234. package/src/__tests__/gpu-lod-silhouette.browser.test.ts +13 -0
  235. package/src/__tests__/gpu-resource-store-stride.unit.test.ts +66 -1
  236. package/src/__tests__/gpu-scene.dawn.test.ts +8 -1
  237. package/src/__tests__/gpu-scene.unit.test.ts +49 -3
  238. package/src/__tests__/light-buffer-layout.unit.test.ts +113 -3
  239. package/src/__tests__/light-inspection.unit.test.ts +55 -0
  240. package/src/__tests__/light-resource-error.unit.test.ts +43 -0
  241. package/src/__tests__/lighting-scene-roundtrip.integration.test.ts +109 -0
  242. package/src/__tests__/lod-bounds.unit.test.ts +42 -0
  243. package/src/__tests__/lod-cpu-lane.integration.test.ts +55 -0
  244. package/src/__tests__/lod-cpu-no-change.perf.test.ts +19 -0
  245. package/src/__tests__/lod-gpu-frame-timing.unit.test.ts +49 -0
  246. package/src/__tests__/lod-multi-view.integration.test.ts +63 -0
  247. package/src/__tests__/lod-occlusion-device-loss.integration.test.ts +27 -0
  248. package/src/__tests__/lod-occlusion-final-decision.integration.test.ts +47 -0
  249. package/src/__tests__/lod-occlusion-inspection.integration.test.ts +217 -0
  250. package/src/__tests__/lod-occlusion-inspection.schema.test-d.ts +14 -0
  251. package/src/__tests__/lod-occlusion-inspection.unit.test.ts +72 -0
  252. package/src/__tests__/lod-occlusion-visual-evidence.ts +91 -0
  253. package/src/__tests__/lod-occlusion.browser.test.ts +15 -0
  254. package/src/__tests__/lod-occlusion.dawn.test.ts +20 -0
  255. package/src/__tests__/lod-selector.profile.test.ts +35 -0
  256. package/src/__tests__/lod-selector.unit.test.ts +68 -0
  257. package/src/__tests__/lod-visibility-identity.unit.test.ts +198 -0
  258. package/src/__tests__/lod-visibility-lifecycle.integration.test.ts +132 -0
  259. package/src/__tests__/ltc-resources.unit.test.ts +32 -0
  260. package/src/__tests__/material-contract-inventory.unit.test.ts +17 -0
  261. package/src/__tests__/occlusion-confidence.unit.test.ts +168 -0
  262. package/src/__tests__/occlusion-invalidation.integration.test.ts +37 -0
  263. package/src/__tests__/occlusion-pass-routing.unit.test.ts +16 -0
  264. package/src/__tests__/occlusion-pass.dawn.test.ts +20 -0
  265. package/src/__tests__/occlusion-pass.integration.test.ts +43 -0
  266. package/src/__tests__/occlusion-pass.rhi-null.test.ts +22 -0
  267. package/src/__tests__/occlusion-query-pool.rhi-null.test.ts +21 -0
  268. package/src/__tests__/occlusion-query-pool.unit.test.ts +95 -0
  269. package/src/__tests__/occlusion-query-ticket.integration.test.ts +47 -0
  270. package/src/__tests__/occlusion-query.dawn.test.ts +16 -0
  271. package/src/__tests__/occlusion-query.evidence.ts +178 -0
  272. package/src/__tests__/occlusion-query.rhi-null.test.ts +31 -0
  273. package/src/__tests__/occlusion-runtime.unit.test.ts +120 -0
  274. package/src/__tests__/prepared-gpu-driven-pbr.integration.test.ts +196 -0
  275. package/src/__tests__/probe-bindings.unit.test.ts +200 -0
  276. package/src/__tests__/probe-blend-numeric.unit.test.ts +67 -0
  277. package/src/__tests__/probe-blend-oracle.unit.test.ts +136 -0
  278. package/src/__tests__/probe-capacity.integration.test.ts +97 -0
  279. package/src/__tests__/probe-recovery.integration.test.ts +104 -0
  280. package/src/__tests__/probe-scene-dirty.integration.test.ts +211 -0
  281. package/src/__tests__/profiler-phase-catalog.test.ts +4 -1
  282. package/src/__tests__/profiler-phase-token.test.ts +1 -0
  283. package/src/__tests__/rect-area-boundary.unit.test.ts +24 -0
  284. package/src/__tests__/reflection-fallback-graph.unit.test.ts +67 -0
  285. package/src/__tests__/reflection-fallback-inspection.unit.test.ts +77 -0
  286. package/src/__tests__/reflection-fallback-lifecycle.integration.test.ts +21 -0
  287. package/src/__tests__/reflection-fallback-projection.unit.test.ts +90 -0
  288. package/src/__tests__/reflection-fallback-source.unit.test.ts +32 -0
  289. package/src/__tests__/reflection-probe-owner-placement.unit.test.ts +39 -0
  290. package/src/__tests__/reflection-probe-record-owner.characterization.unit.test.ts +120 -0
  291. package/src/__tests__/render-error-code-owner.test-d.ts +1 -0
  292. package/src/__tests__/render-feature-docs.unit.test.ts +12 -7
  293. package/src/__tests__/render-graph-fallback.integration.test.ts +24 -0
  294. package/src/__tests__/render-perf/extended-lighting.bench.test.ts +66 -0
  295. package/src/__tests__/render-scene-projection.unit.test.ts +447 -18
  296. package/src/__tests__/renderer-factory-material-contract.unit.test.ts +76 -0
  297. package/src/__tests__/skin-motion-regression.unit.test.ts +22 -0
  298. package/src/__tests__/skinned-shadow-caster.test.ts +2 -2
  299. package/src/__tests__/spot-modifier-projection.unit.test.ts +70 -0
  300. package/src/__tests__/ssr-admission-matrix.integration.test.ts +206 -0
  301. package/src/__tests__/ssr-composition.unit.test.ts +46 -0
  302. package/src/__tests__/ssr-fallback-zero-work.bench.test.ts +94 -0
  303. package/src/__tests__/ssr-recovery-errors.unit.test.ts +58 -0
  304. package/src/__tests__/standard-cluster-variant-resolution.unit.test.ts +67 -0
  305. package/src/__tests__/standard-clustered-pipeline.unit.test.ts +7 -0
  306. package/src/__tests__/standard-lighting-inspection.integration.test.ts +1 -1
  307. package/src/__tests__/standard-lighting-write.unit.test.ts +66 -2
  308. package/src/__tests__/temporal-fallback-generation.integration.test.ts +16 -0
  309. package/src/__tests__/temporal-view.unit.test.ts +3 -3
  310. package/src/__tests__/transmission-prewarm.unit.test.ts +69 -0
  311. package/src/__tests__/visibility-budget.integration.test.ts +140 -0
  312. package/src/__tests__/visibility-budget.unit.test.ts +70 -0
  313. package/src/__tests__/visibility-extract.integration.test.ts +11 -6
  314. package/src/__tests__/volumetric-fog-point-spot-accumulation.integration.test.ts +6 -3
  315. package/src/__tests__/volumetric-fog-stage-readback.dawn.test.ts +31 -28
  316. package/src/assembly/backend-contract.ts +13 -0
  317. package/src/assembly/device-feature-admission.ts +16 -9
  318. package/src/assembly/factory.ts +547 -93
  319. package/src/assembly/host-contract.ts +10 -0
  320. package/src/cluster-binner.ts +31 -0
  321. package/src/components/__tests__/lighting-authoring.unit.test.ts +39 -0
  322. package/src/components/__tests__/rect-area-light.unit.test.ts +33 -0
  323. package/src/components/index.ts +3 -0
  324. package/src/components/light-helpers.ts +52 -1
  325. package/src/components/light-probe.ts +13 -0
  326. package/src/components/rect-area-light.ts +15 -0
  327. package/src/components/scene-instance.ts +6 -0
  328. package/src/components/spot-light.ts +3 -0
  329. package/src/device/__tests__/extended-lighting-recovery.unit.test.ts +24 -0
  330. package/src/device/gpu-residency.ts +140 -9
  331. package/src/device/lifecycle-transaction.ts +12 -0
  332. package/src/environment/lifecycle.ts +8 -1
  333. package/src/errors/gpu-driven.ts +92 -0
  334. package/src/errors/index.ts +1 -0
  335. package/src/errors/recover.ts +32 -0
  336. package/src/errors/render.ts +55 -1
  337. package/src/extract/camera.ts +3 -3
  338. package/src/extract/gpu-driven.ts +108 -0
  339. package/src/extract/visibility.ts +0 -23
  340. package/src/gpu-driven/batch-topology.ts +206 -28
  341. package/src/gpu-driven/prepared-draw.ts +208 -0
  342. package/src/gpu-driven/production-raster.ts +953 -41
  343. package/src/gpu-driven/view-gpu.ts +411 -29
  344. package/src/gpu-scene-schema.ts +13 -0
  345. package/src/gpu-scene.ts +134 -65
  346. package/src/gpu-usage.ts +0 -1
  347. package/src/hdrp-buffers.ts +6 -6
  348. package/src/index.ts +74 -0
  349. package/src/inspection-types.ts +220 -1
  350. package/src/internal.ts +38 -0
  351. package/src/light-buffer-layout.ts +154 -324
  352. package/src/materials.ts +16 -2
  353. package/src/pbr-pipeline.ts +151 -45
  354. package/src/pipeline/standard-forward-lane.ts +47 -0
  355. package/src/pipeline/standard-lighting/graph.ts +2 -1
  356. package/src/pipeline/standard-lighting/light-frame.ts +3 -2
  357. package/src/pipeline/standard-lighting/topology.ts +1 -1
  358. package/src/pipeline/standard-lighting/transport.ts +2 -1
  359. package/src/pipeline/standard-pipeline.ts +2 -0
  360. package/src/pipeline-builder.ts +5 -2
  361. package/src/pipeline-spec.ts +12 -1
  362. package/src/plugin.ts +4 -0
  363. package/src/prepare/__tests__/extended-lighting-capability.unit.test.ts +56 -0
  364. package/src/prepare/extended-lighting/ltc-resources.ts +136 -0
  365. package/src/prepare/extended-lighting/resources.ts +389 -0
  366. package/src/prepare/extended-lighting/spot-modifiers.ts +194 -0
  367. package/src/prepare/extended-lighting/state.ts +92 -0
  368. package/src/record/__tests__/gpu-timing.unit.test.ts +177 -0
  369. package/src/record/__tests__/temporal-payload.unit.test.ts +23 -1
  370. package/src/record/__tests__/typed-frame-graph-plan.unit.test.ts +20 -1
  371. package/src/record/frame-lighting.ts +252 -53
  372. package/src/record/frame-snapshot.ts +57 -12
  373. package/src/record/frame.ts +410 -93
  374. package/src/record/gpu-timing.ts +108 -44
  375. package/src/record/helpers.ts +0 -58
  376. package/src/record/main-pass-geometry.ts +204 -39
  377. package/src/record/main-pass-material.ts +8 -1
  378. package/src/record/main-pass-sprite-draws.ts +52 -85
  379. package/src/record/mesh-ssbo.ts +47 -36
  380. package/src/record/render-context.ts +65 -4
  381. package/src/record/shadow-pass.ts +66 -40
  382. package/src/record/typed-frame-graph.ts +225 -83
  383. package/src/record/view-ubo.ts +14 -24
  384. package/src/reflection/inspection.ts +67 -0
  385. package/src/reflection/projection.ts +196 -1
  386. package/src/reflection/record-owner.ts +1283 -0
  387. package/src/render-contract.ts +17 -5
  388. package/src/render-pipeline.ts +5 -0
  389. package/src/render-system-extract.ts +493 -134
  390. package/src/render-system.ts +811 -615
  391. package/src/scene/__tests__/changed-block-summary.candidate.test.ts +36 -0
  392. package/src/scene/probe-blend-record.ts +54 -0
  393. package/src/scene/probe-blend.ts +813 -0
  394. package/src/scene/render-scene-types.ts +0 -7
  395. package/src/scene/render-scene.ts +848 -183
  396. package/src/scene/visibility/bounds.ts +57 -0
  397. package/src/scene/visibility/budget.ts +40 -0
  398. package/src/scene/visibility/facet.ts +400 -0
  399. package/src/scene/visibility/gpu-frame-timing.ts +101 -0
  400. package/src/scene/visibility/gpu-lod.ts +119 -0
  401. package/src/scene/visibility/inspection.ts +313 -0
  402. package/src/scene/visibility/lod-selector.ts +84 -0
  403. package/src/scene/visibility/occlusion-confidence.ts +478 -0
  404. package/src/scene/visibility/occlusion-pass.ts +165 -0
  405. package/src/scene/visibility/occlusion-query-pool.ts +178 -0
  406. package/src/scene/visibility/occlusion-runtime.ts +672 -0
  407. package/src/scene/visibility/types.ts +50 -0
  408. package/src/ssr/admission.ts +451 -0
  409. package/src/ssr/composition.ts +73 -0
  410. package/src/ssr/errors.ts +76 -0
  411. package/src/ssr/identity.ts +12 -0
  412. package/src/systems/skin-palette-allocator.ts +1 -1
  413. package/src/temporal/frame-coordinator.ts +26 -0
  414. package/src/temporal/frame.ts +14 -1
  415. package/src/temporal/view.ts +11 -0
  416. package/src/tilemap-chunk-extract-system.ts +4 -4
  417. package/src/typed-render-graph-primitives.ts +48 -0
  418. package/src/volume/passes.ts +46 -49
  419. package/dist/.tsbuildinfo +0 -1
  420. package/dist/chunk-5YADUCA7.mjs.map +0 -1
  421. package/dist/chunk-6OPXDQ2E.mjs.map +0 -1
  422. package/dist/chunk-BXBQQKFQ.mjs +0 -67
  423. package/dist/chunk-BXBQQKFQ.mjs.map +0 -1
  424. package/dist/chunk-FS2KNUQH.mjs.map +0 -1
  425. package/dist/chunk-PCHBPYML.mjs.map +0 -1
  426. package/dist/chunk-VFPLTKCU.mjs.map +0 -1
@@ -1,50 +1,28 @@
1
- // @forgeax/engine-runtime - host-side packing helpers for the
2
- // PointLight + SpotLight std430 storage buffers + storage-buffer cap gate.
1
+ // @forgeax/engine-render - host-side DirectLightSlot ABI and buffer helpers.
3
2
  //
4
- // Owns the byte-frozen std430 layout (D-S2):
5
- //
6
- // PointLight (32 B / 8 floats):
7
- // [ 0..2 ] position vec3<f32>
8
- // [ 3 ] invRangeSquared f32 (Bevy color_inverse_square_range.w)
9
- // [ 4..6 ] color vec3<f32> (host-pre-multiplied: color * intensity)
10
- // [ 7 ] shadowAtlasLayer i32 (sentinel -1 = no shadow; 0..3 = atlas layer)
11
- // — feat-20260612-point-light-shadows-urp-hdrp M1 / T-M1-8 (D-2):
12
- // repurposes the prior `pointPadW` lane. The lane is written as
13
- // i32 via an Int32Array view of the same backing ArrayBuffer; the
14
- // shader reads `shadowAtlasLayer: i32` from byte 28..32 of struct
15
- // PointLight in common.wgsl. -1 (0xFFFFFFFF) is the no-shadow
16
- // sentinel shader path samples shadow only when layer >= 0.
17
- //
18
- // SpotLight (80 B / 20 floats):
19
- // [ 0..2 ] position vec3<f32>
20
- // [ 3 ] invRangeSquared f32
21
- // [ 4..6 ] color vec3<f32> (host-pre-multiplied)
22
- // [ 7 ] cosInner f32
23
- // [ 8..10] direction vec3<f32> (raw outgoing vector)
24
- // [ 11 ] cosOuter f32
25
- // [12] depthBias, [13] normalBias, [14] pcfKernelSize
26
- // [ 15 ] shadowAtlasTile i32 (sentinel -1 = unassigned/clipped; 0..3 = tile)
27
- // — feat-20260625-spot-light-shadow-mapping M2 / w8 (D-4):
28
- // SpotLight receiver controls occupy the 4th vec4 column. The tile
29
- // lane is written i32 via an Int32Array view of the same backing
30
- // ArrayBuffer; the shader reads `shadowAtlasTile: i32` from byte
31
- // 60..64 of struct SpotLight (common.wgsl). -1 (0xFFFFFFFF) is the
32
- // unassigned/clipped sentinel — shader samples shadow only when
33
- // tile >= 0. Mirrors point's slot-7 shadowAtlasLayer pattern.
34
- // [ 16 ] shadowIntensity f32 (default 1; [0,1] residual visibility)
35
- // [17..19] std430 tail pad (the struct rounds to the next 16 B boundary)
36
- //
37
- // Array header (16 B, std430 stride alignment):
38
- // [ 0 ] count u32 (number of valid slots in the trailing array)
39
- // [ 1..3 ] pad u32 = 0
3
+ // Point, Spot, and Rect snapshots share one five-row, 80-byte Cluster slot.
4
+ // The same packer is used by the Cluster producer and every Cluster consumer;
5
+ // this is the only local-light transport.
6
+
7
+ import { err, ok, type Result, RhiError } from '@forgeax/engine-rhi';
8
+ import type {
9
+ PointLightSnapshot,
10
+ RectAreaDirectLightSnapshot,
11
+ SpotLightSnapshot,
12
+ } from './render-system-extract';
13
+ // Owns the byte-frozen std430 layout (D-S2). Point, Spot, and Rect all use the
14
+ // same five-row DirectLightSlot; the kind and resource identities are carried
15
+ // by the metadata row. Point uses the angular lanes as neutral values. Spot
16
+ // uses row 3's x/y/z lanes for volume shadow receiver controls and w for the
17
+ // extended-lighting roll angle. Rect uses row 2/3 for its two tangent axes.
40
18
  //
41
19
  // AC anchor: requirements AC-04 (c) + C-7 (16 B alignment); plan-strategy
42
20
  // section 2 D-S2 (byte-frozen layout); research Finding 2 (Bevy
43
21
  // invRangeSquared naming) + Finding 9 (Bevy storage buffer pattern).
44
22
  //
45
- // Cap gate: maxStorageBuffersPerShaderStage >= 4 (M3 occupies 3 storage
46
- // entries: meshSSBO + pointLightsBuffer + spotLightsBuffer; default WebGPU
47
- // minimum is 8, requirements assumption A-7 + plan R-4). The closed
23
+ // Cap gate: maxStorageBuffersPerShaderStage >= 4 (Cluster occupies four
24
+ // storage entries: meshSSBO + light_data + cluster_grid + light_index_list;
25
+ // default WebGPU minimum is 8, requirements assumption A-7 + plan R-4). The closed
48
26
  // `RhiErrorCode` union has no `'rhi-not-supported'` member; the
49
27
  // plan-strategy phrasing collapses onto the spec-aligned `'limit-exceeded'`
50
28
  // arm which already documents "input value exceeded device.limits.<name>"
@@ -52,213 +30,22 @@
52
30
  // keeps `RhiErrorCode` count at 18 (AGENTS.md error model evolution
53
31
  // contract: minor add-only; w20a explicitly forbids count drift).
54
32
 
55
- import { err, ok, type Result, RhiError } from '@forgeax/engine-rhi';
56
- import type { PointLightSnapshot, SpotLightSnapshot } from './render-system-extract';
57
-
58
- // ── HDRP LightSlot cluster-forward unified struct ─────────────────────────────
59
- // feat-20260608-cluster-lighting M4 / w16.
60
- //
61
- // LightSlot is the single std430 struct shared by point and spot lights in the
62
- // HDRP cluster-forward path. Field layout is byte-frozen (AC-11 double-sided
63
- // lock with WGSL hdrp-cluster-forward.wgsl LightSlot struct):
64
- //
65
- // [ 0..2 ] position vec3<f32>
66
- // [ 3 ] invRangeSquared f32
67
- // [ 4..6 ] color vec3<f32> (host pre-multiplied: color * intensity)
68
- // [ 7 ] cosInner f32 (point: 1.0)
69
- // [ 8..10] direction vec3<f32> (point: vec3(0))
70
- // [ 11 ] cosOuter f32 (point: 0.0)
71
- // [ 12 ] kind u32 (POINT = 0, SPOT = 1)
72
- // [ 13 ] shadow identity i32 (point layer / spot tile, -1 sentinel)
73
- // [14] point near / spot shadowIntensity f32
74
- // [15] point far / spot pcfKernelSize f32
75
- //
76
- // Design: research Finding 1 (Bevy GpuClusteredLight 152B -> forgeax 64B),
77
- // plan-strategy D-light-slot (kind bit-tag discriminant).
78
- //
79
- // The existing URP PointLight (32B) pack function remains unchanged; the URP
80
- // SpotLight grows from 64B to 80B to carry the independent shadow visibility
81
- // factor without stealing the tile or PCF lanes. packLightSlot remains the
82
- // compact 64B HDRP owner and uses a spot-only pad lane for the same value.
83
-
84
- /** Byte size of one HDRP LightSlot in std430 (16 x f32). AC-11 absolute-value lock. */
85
- export const BYTES_PER_LIGHT_SLOT = 64;
86
-
87
- /**
88
- * LightSlot kind discriminant — closed enum matching WGSL `KIND_POINT` / `KIND_SPOT`.
89
- * Values are u32 bit-tags (not sequential); AC-12 exhaustive switch.
90
- */
91
- export const LightSlotKind = {
92
- POINT: 0,
93
- SPOT: 1,
94
- } as const;
95
- export type LightSlotKind = (typeof LightSlotKind)[keyof typeof LightSlotKind];
96
-
97
- /**
98
- * Byte-offset layout of one LightSlot in std430 (16 f32 = 64 B).
99
- *
100
- * Mirrors the WGSL `LightSlot` struct field order byte-for-byte.
101
- * All offsets verified by w14 lightslot-layout.test.ts.
102
- *
103
- * feat-20260612-point-light-shadows-urp-hdrp M4 / T-M4-4 (plan-strategy §D-8):
104
- * the prior u32x3 pad lanes at byte 52..64 carry shadow-side data on the HDRP
105
- * path. `kind_and_pad.x = kind` (u32), `.y = shadow identity` (i32, sentinel
106
- * -1 = no shadow). For point lights `.z/.w` carry near/far (f32 via bitcast);
107
- * for spot lights `.z` carries shadowIntensity and `.w` carries the PCF
108
- * kernel width (f32 via bitcast). The shader reads these lanes through
109
- * `bitcast<f32>(...)` to
110
- * keep the WGSL struct as `vec4<u32>` (alignment + naga_oil unchanged).
111
- */
112
- export const LIGHTSLOT_LAYOUT = {
113
- byteSize: BYTES_PER_LIGHT_SLOT,
114
- positionOffset: 0,
115
- invRangeSquaredOffset: 12,
116
- colorOffset: 16,
117
- cosInnerOffset: 28,
118
- directionOffset: 32,
119
- cosOuterOffset: 44,
120
- kindOffset: 48,
121
- shadowPayloadOffset: 52,
122
- shadowAtlasLayerOffset: 52,
123
- shadowAtlasTileOffset: 52,
124
- shadowNearOffset: 56,
125
- shadowFarOffset: 60,
126
- spotPcfKernelSizeOffset: 60,
127
- floatCount: 16,
128
- vec4Count: 4,
129
- } as const;
130
-
131
33
  /**
132
34
  * Minimum storage-buffer count needed by the M3 record stage.
133
35
  *
134
36
  * 1. Mesh SSBO (entity_world + normalMatrix; per-renderable dynamic
135
37
  * offset, 256 B stride).
136
- * 2. PointLight std430 array (this feat).
137
- * 3. SpotLight std430 array (this feat).
38
+ * 2. Cluster light_data SSBO.
39
+ * 3. Cluster membership grid SSBO.
40
+ * 4. Cluster light-index-list SSBO.
138
41
  *
139
- * Plan R-4 + plan-strategy section 4 risk table: assert >= 4 to keep the
140
- * head-room margin for todo-125 (cluster expansion lifts to 4-5 entries).
42
+ * Cluster needs four storage-backed resources in the Standard bind group.
43
+ * This is the complete local-light transport.
141
44
  */
142
45
  export const STORAGE_BUFFER_MIN_REQUIRED = 4;
143
46
 
144
- /** Byte size of one PointLight std430 slot (vec4 * 2). */
145
- export const POINT_LIGHT_STD430_BYTES = 32;
146
- /**
147
- * Byte size of one SpotLight std430 slot (five 16-byte columns).
148
- *
149
- * feat-20260625-spot-light-shadow-mapping M2 / w8 (D-4) grew 48 -> 64 for
150
- * `shadowAtlasTile`; the visual-parity contract then adds a fifth column for
151
- * `shadowIntensity` rather than overloading the tile/PCF lanes.
152
- */
153
- export const SPOT_LIGHT_STD430_BYTES = 80;
154
-
155
- /** Sentinel value for SpotLight slot[15] meaning "no shadow / clipped". */
156
- export const SPOT_LIGHT_SHADOW_TILE_SENTINEL = -1;
157
- /** Sentinel for a projector-only SpotLight with no shadow atlas tile. */
158
- export const SPOT_LIGHT_PROJECTOR_ONLY_SENTINEL = -2;
159
- /**
160
- * High-bit marker carried by the shared LightSlot identity lane when this
161
- * SpotLight is the one projector selected for the frame. The lower 30 bits
162
- * retain the shadow-atlas tile (0..3), or zero for projector-only lights, so
163
- * one 64-byte slot carries both shadow and projector identity without growing
164
- * the cluster UBO or inventing a second light list.
165
- */
166
- export const SPOT_LIGHT_PROJECTOR_FLAG = 0x40000000;
167
- export const SPOT_LIGHT_TILE_MASK = SPOT_LIGHT_PROJECTOR_FLAG - 1;
168
- /** Byte size of the array header (count u32 + 12 B pad to 16 B stride). */
169
- export const LIGHT_ARRAY_HEADER_BYTES = 16;
170
- /** Maximum number of slots packed in the first-slice cap layout (D-S3). */
171
- export const LIGHT_ARRAY_MAX_SLOTS = 4;
172
-
173
- /** Sentinel value for PointLight slot[7] / LightSlot pad meaning "no shadow". */
174
- export const POINT_LIGHT_SHADOW_LAYER_SENTINEL = -1;
175
-
176
- /**
177
- * Pack one PointLightSnapshot into 8 floats / 32 B byte-for-byte std430.
178
- *
179
- * Returns a fresh `Float32Array` per call; `Float32Array.byteLength === 32`
180
- * by construction (Float32Array(8).buffer.byteLength === 32). Slot 7
181
- * (byte 28..32) carries `shadowAtlasLayer: i32` (sentinel -1 = no shadow,
182
- * 0..3 = cube_array atlas layer index for shadow casters). The lane is
183
- * written through an Int32Array view of the same backing ArrayBuffer so the
184
- * shader's `i32` read at byte 28..32 of `struct PointLight` (common.wgsl)
185
- * sees the correct two's-complement bits (-1 = 0xFFFFFFFF).
186
- */
187
- export function packPointLight(snap: PointLightSnapshot): Float32Array {
188
- const out = new Float32Array(8);
189
- out[0] = snap.position[0] ?? 0;
190
- out[1] = snap.position[1] ?? 0;
191
- out[2] = snap.position[2] ?? 0;
192
- out[3] = snap.invRangeSquared;
193
- out[4] = snap.color[0] ?? 0;
194
- out[5] = snap.color[1] ?? 0;
195
- out[6] = snap.color[2] ?? 0;
196
- // Slot 7: shadowAtlasLayer i32 via Int32Array view (sentinel -1 default).
197
- const i32 = new Int32Array(out.buffer);
198
- i32[7] = snap.shadowAtlasLayer ?? POINT_LIGHT_SHADOW_LAYER_SENTINEL;
199
- return out;
200
- }
201
-
202
- /**
203
- * Pack one SpotLightSnapshot into 20 floats (80 B byte-for-byte std430).
204
- *
205
- * cosInner / cosOuter ride the otherwise-padding `.w` lanes of the
206
- * `color` and `direction` vec4 columns (AC-04 c + plan D-S2 16 B alignment
207
- * via packing rather than padding rows). Slot 15 carries `shadowAtlasTile: i32`
208
- * (sentinel -1 = unassigned/clipped, 0..3 = atlas tile) written through an
209
- * Int32Array view so the shader's `i32` read at byte 60..64 of struct SpotLight
210
- * (common.wgsl) sees the correct two's-complement bits (-1 = 0xFFFFFFFF).
211
- * Slots 12..14 carry the shared receiver controls; slot 15 remains the i32
212
- * shadow tile sentinel; slot 16 carries shadowIntensity and slots 17..19 are
213
- * alignment tail padding. Surface and volume consumers therefore share one
214
- * extracted shadow configuration.
215
- */
216
- export function packSpotLight(snap: SpotLightSnapshot): Float32Array {
217
- const out = new Float32Array(20);
218
- out[0] = snap.position[0] ?? 0;
219
- out[1] = snap.position[1] ?? 0;
220
- out[2] = snap.position[2] ?? 0;
221
- out[3] = snap.invRangeSquared;
222
- out[4] = snap.color[0] ?? 0;
223
- out[5] = snap.color[1] ?? 0;
224
- out[6] = snap.color[2] ?? 0;
225
- out[7] = snap.cosInner;
226
- out[8] = snap.direction[0] ?? 0;
227
- out[9] = snap.direction[1] ?? 0;
228
- out[10] = snap.direction[2] ?? 0;
229
- out[11] = snap.cosOuter;
230
- out[12] = snap.depthBias ?? 0.005;
231
- out[13] = snap.normalBias ?? 0.05;
232
- out[14] = snap.pcfKernelSize ?? 3;
233
- // Slot 15: shadowAtlasTile i32 via Int32Array view (sentinel -1 default).
234
- const i32 = new Int32Array(out.buffer);
235
- i32[15] =
236
- snap.shadowAtlasTile >= 0
237
- ? snap.shadowAtlasTile
238
- : snap.projectorAsset !== undefined && snap.lightViewProj !== undefined
239
- ? SPOT_LIGHT_PROJECTOR_ONLY_SENTINEL
240
- : SPOT_LIGHT_SHADOW_TILE_SENTINEL;
241
- // Slot 16 begins the fifth vec4 column. The remaining tail lanes stay zero
242
- // so the std430 array stride is the explicit 80-byte contract.
243
- out[16] = snap.shadowIntensity ?? 1;
244
- return out;
245
- }
246
-
247
- /**
248
- * Pack the 16-byte std430 array header (count u32 + 12 B pad) for the
249
- * point/spot light storage buffers.
250
- *
251
- * Returns a fresh `ArrayBuffer` so callers can either re-view it as
252
- * `Uint8Array` for `queue.writeBuffer` or as `Uint32Array` for in-place
253
- * inspection.
254
- */
255
- export function packLightArrayHeader(count: number): ArrayBuffer {
256
- const buf = new ArrayBuffer(LIGHT_ARRAY_HEADER_BYTES);
257
- const u32 = new Uint32Array(buf);
258
- u32[0] = count >>> 0;
259
- // Slots 1..3 stay zero (12 B pad to 16 B stride alignment).
260
- return buf;
261
- }
47
+ /** Byte size of one Point/Spot/Rect DirectLightSlot. */
48
+ export const BYTES_PER_DIRECT_LIGHT_SLOT = 80;
262
49
 
263
50
  /**
264
51
  * Cap gate at createRenderer time: check whether the device has enough
@@ -275,101 +62,144 @@ export function packLightArrayHeader(count: number): ArrayBuffer {
275
62
  *
276
63
  * Uniform-fallback path decision (plan D-5): `cap === 0` is the wgpu
277
64
  * WebGL2 backend signal (`downlevel_webgl2_defaults().limits.
278
- * maxStorageBuffersPerShaderStage = 0`). Uniform fallback covers pointLight
279
- * + spotLight + meshes + instances + skin palette — every storage-buffer
280
- * entry in the PBR pipeline switches to `uniform`.
65
+ * maxStorageBuffersPerShaderStage = 0`). It remains a mesh/instance fallback;
66
+ * local-light frames require the single Cluster storage transport.
281
67
  */
68
+ /** Closed direct-light kind carried by the unified five-row slot. */
69
+ export const DirectLightSlotKind = {
70
+ POINT: 0,
71
+ SPOT: 1,
72
+ RECT_AREA: 2,
73
+ } as const;
74
+ export type DirectLightSlotKind = 'point' | 'spot' | 'rect-area';
75
+
76
+ /** Metadata identity used for absent shadow, IES, and Cookie resources. */
77
+ export const DIRECT_LIGHT_SLOT_METADATA_SENTINEL = 0xffffffff;
282
78
  /**
283
- * Optional shadow info attached to a HDRP LightSlot for point lights with a
284
- * companion `PointLightShadow` component. Sentinel `layer = -1` (the default
285
- * when the snapshot has no shadow) makes the shader skip cube_array sampling
286
- * and stay on the unshadowed `evalPoint` path.
287
- *
288
- * `near` / `far` ride the otherwise-pad lanes (`kind_and_pad.zw` in the WGSL
289
- * struct, byte 56..64) and feed the depth-ref reconstruction in
290
- * `evalPointShadowed`. Spot lights (and shadow-less point lights) leave these
291
- * lanes zero per std430 init.
79
+ * SpotLight projector marker carried in the shared shadow identity word. The
80
+ * lower 30 bits retain the selected shadow tile; projector-only spots use tile
81
+ * zero because their matrix is deliberately published in View lane zero.
292
82
  */
293
- export interface PointShadowSlotInfo {
294
- readonly shadowAtlasLayer: number;
295
- readonly near: number;
296
- readonly far: number;
83
+ export const DIRECT_LIGHT_SLOT_PROJECTOR_FLAG = 0x40000000;
84
+ export const DIRECT_LIGHT_SLOT_TILE_MASK = DIRECT_LIGHT_SLOT_PROJECTOR_FLAG - 1;
85
+ /** Byte size of the five-row Point/Spot/Rect transport slot. */
86
+ export const DIRECT_LIGHT_SLOT_FLOAT_COUNT =
87
+ BYTES_PER_DIRECT_LIGHT_SLOT / Float32Array.BYTES_PER_ELEMENT;
88
+
89
+ /** Five vec4 rows shared by host packing and the WGSL direct-light consumer. */
90
+ export const DIRECT_LIGHT_SLOT_LAYOUT = {
91
+ byteSize: BYTES_PER_DIRECT_LIGHT_SLOT,
92
+ rowByteOffsets: [0, 16, 32, 48, 64],
93
+ positionOffset: 0,
94
+ rangeOffset: 12,
95
+ colorOffset: 16,
96
+ firstAngleOffset: 28,
97
+ primaryAxisOffset: 32,
98
+ secondAngleOffset: 44,
99
+ auxiliaryAxisOffset: 48,
100
+ metadataByteOffset: 64,
101
+ kindByteOffset: 64,
102
+ shadowByteOffset: 68,
103
+ iesProfileByteOffset: 72,
104
+ cookieByteOffset: 76,
105
+ /** Spot-only roll angle stored in the fourth row's w lane. */
106
+ rollDegByteOffset: 60,
107
+ floatCount: DIRECT_LIGHT_SLOT_FLOAT_COUNT,
108
+ vec4Count: 5,
109
+ } as const;
110
+
111
+ type DirectLightModifierMetadata = {
112
+ readonly iesProfileSlice?: number;
113
+ readonly cookieSlice?: number;
114
+ };
115
+
116
+ export type DirectLightSnapshot =
117
+ | (PointLightSnapshot & DirectLightModifierMetadata)
118
+ | (SpotLightSnapshot & DirectLightModifierMetadata)
119
+ | (RectAreaDirectLightSnapshot & DirectLightModifierMetadata);
120
+
121
+ function directLightMetadata(value: number | undefined): number {
122
+ return value === undefined ? DIRECT_LIGHT_SLOT_METADATA_SENTINEL : value >>> 0;
297
123
  }
298
124
 
299
125
  /**
300
- * Pack one PointLightSnapshot or SpotLightSnapshot into a 64-byte LightSlot
301
- * (16 floats, std430). Point lights fill `cosInner=1.0`, `direction=vec3(0)`,
302
- * `cosOuter=0.0`, `kind=POINT(0)`. Spot lights fill all fields verbatim;
303
- * `direction` is already normalized by extract and this owner never repeats
304
- * that operation.
305
- *
306
- * feat-20260612-point-light-shadows-urp-hdrp M4 / T-M4-4 (plan-strategy §D-8):
307
- * the optional point `shadow` info threads through to bytes 52..64 as
308
- * `(shadowAtlasLayer i32, near f32, far f32)`. Spot snapshots write their
309
- * `shadowAtlasTile` directly into the same identity lane, put shadowIntensity
310
- * in the `.z` lane, and the PCF kernel width in `.w`. The shader reads `near`/`far`
311
- * through `bitcast<f32>(kind_and_pad.zw)` so the WGSL struct stays
312
- * `kind_and_pad: vec4<u32>` (alignment + naga_oil compose unchanged). When
313
- * `shadow` is undefined (no PointLightShadow) the lanes default to
314
- * `(layer = -1, 0, 0)` -- shader gates on `layer >= 0` so unshadowed lights
315
- * stay on the `evaluate_point_light` path. Spot lights write their tile or -1.
126
+ * Pack one Point, Spot, or Rect snapshot into the unified five-row slot.
316
127
  *
317
- * Returns a fresh `Float32Array(16)` per call.
128
+ * Row 0 is position plus inverse range. Row 1 is color plus the first
129
+ * angular/size fact. Row 2 is the primary axis plus the second angular/size
130
+ * fact. Row 3 is the auxiliary Rect axis. Row 4 is four u32 identities.
318
131
  */
319
- export function packLightSlot(
320
- snap: PointLightSnapshot | SpotLightSnapshot,
321
- shadow?: PointShadowSlotInfo,
132
+ export function packDirectLightSlot(
133
+ snapshot: DirectLightSnapshot,
322
134
  projectorSelected?: boolean,
323
135
  ): Float32Array {
324
- const out = new Float32Array(16);
325
- // position (vec3<f32> + invRangeSquared at lane .w)
326
- out[0] = snap.position[0] ?? 0;
327
- out[1] = snap.position[1] ?? 0;
328
- out[2] = snap.position[2] ?? 0;
329
- out[3] = snap.invRangeSquared;
330
- // color (vec3<f32> + cosInner at lane .w)
331
- out[4] = snap.color[0] ?? 0;
332
- out[5] = snap.color[1] ?? 0;
333
- out[6] = snap.color[2] ?? 0;
334
- out[7] = snap.kind === 'point' ? 1.0 : snap.cosInner;
335
- // direction (vec3<f32> + cosOuter at lane .w)
336
- out[8] = snap.kind === 'point' ? 0 : (snap.direction[0] ?? 0);
337
- out[9] = snap.kind === 'point' ? 0 : (snap.direction[1] ?? 0);
338
- out[10] = snap.kind === 'point' ? 0 : (snap.direction[2] ?? 0);
339
- out[11] = snap.kind === 'point' ? 0.0 : snap.cosOuter;
340
- // Kind is a raw u32 discriminant at byte 48. Do not assign through the
341
- // Float32Array: SPOT=1 must be 0x00000001, not f32(1.0) bits.
342
- const u32 = new Uint32Array(out.buffer);
343
- u32[12] = snap.kind === 'point' ? LightSlotKind.POINT : LightSlotKind.SPOT;
344
- // Lanes 13..15 carry one kind-discriminated shadow payload. Point keeps
345
- // layer/near/far; spot writes its snapshot tile, shadowIntensity, and PCF
346
- // kernel width.
347
- const i32 = new Int32Array(out.buffer);
348
- if (snap.kind === 'point' && shadow !== undefined) {
349
- i32[13] = shadow.shadowAtlasLayer;
350
- out[14] = shadow.near;
351
- out[15] = shadow.far;
352
- } else if (snap.kind === 'spot') {
353
- // The optional argument is explicit at the record owner. Keeping the
354
- // source-derived fallback preserves standalone packing fixtures while the
355
- // production path marks only the accepted projector identity.
356
- const hasProjector =
357
- projectorSelected ?? (snap.projectorAsset !== undefined && snap.lightViewProj !== undefined);
358
- const tile = snap.shadowAtlasTile >= 0 ? snap.shadowAtlasTile : 0;
359
- i32[13] = hasProjector
360
- ? SPOT_LIGHT_PROJECTOR_FLAG | (tile & SPOT_LIGHT_TILE_MASK)
361
- : snap.shadowAtlasTile >= 0
362
- ? snap.shadowAtlasTile
363
- : SPOT_LIGHT_SHADOW_TILE_SENTINEL;
364
- out[14] = snap.shadowIntensity ?? 1;
365
- // HDRP has no separate 80-byte SpotLight payload. Reuse the otherwise
366
- // unused spot `.w` lane so the cluster path consumes the same authored PCF
367
- // width as the URP and volume paths.
368
- out[15] = snap.pcfKernelSize ?? 3;
136
+ const out = new Float32Array(DIRECT_LIGHT_SLOT_FLOAT_COUNT);
137
+ out[0] = snapshot.position[0] ?? 0;
138
+ out[1] = snapshot.position[1] ?? 0;
139
+ out[2] = snapshot.position[2] ?? 0;
140
+ out[3] = snapshot.invRangeSquared;
141
+ out[4] = snapshot.color[0] ?? 0;
142
+ out[5] = snapshot.color[1] ?? 0;
143
+ out[6] = snapshot.color[2] ?? 0;
144
+
145
+ if (snapshot.kind === 'point') {
146
+ out[7] = 1;
147
+ } else if (snapshot.kind === 'spot') {
148
+ out[7] = snapshot.cosInner;
149
+ out[8] = snapshot.direction[0] ?? 0;
150
+ out[9] = snapshot.direction[1] ?? 0;
151
+ out[10] = snapshot.direction[2] ?? 0;
152
+ out[11] = snapshot.cosOuter;
153
+ // Row 3 is the Rect auxiliary axis for RectArea lights. Spot uses x/y/z
154
+ // for its receiver controls and w for roll; the surface direct-light
155
+ // owner only consumes w, while the volume owner consumes the full row.
156
+ out[12] = snapshot.depthBias ?? 0.005;
157
+ out[13] = snapshot.normalBias ?? 0.05;
158
+ out[14] = snapshot.shadowIntensity ?? 1;
159
+ out[15] = snapshot.rollDeg ?? 0;
369
160
  } else {
370
- i32[13] = POINT_LIGHT_SHADOW_LAYER_SENTINEL;
371
- // out[14] / out[15] remain zero (Float32Array zero-init).
161
+ out[7] = snapshot.halfWidth;
162
+ out[8] = snapshot.axisY[0] ?? 0;
163
+ out[9] = snapshot.axisY[1] ?? 0;
164
+ out[10] = snapshot.axisY[2] ?? 0;
165
+ out[11] = snapshot.halfHeight;
166
+ out[12] = snapshot.axisX[0] ?? 0;
167
+ out[13] = snapshot.axisX[1] ?? 0;
168
+ out[14] = snapshot.axisX[2] ?? 0;
372
169
  }
170
+
171
+ const metadata = new Uint32Array(out.buffer);
172
+ metadata[16] =
173
+ snapshot.kind === 'point'
174
+ ? DirectLightSlotKind.POINT
175
+ : snapshot.kind === 'spot'
176
+ ? DirectLightSlotKind.SPOT
177
+ : DirectLightSlotKind.RECT_AREA;
178
+ const spotProjectorSelected =
179
+ snapshot.kind === 'spot' &&
180
+ (projectorSelected ??
181
+ (snapshot.projectorAsset !== undefined && snapshot.lightViewProj !== undefined));
182
+ const spotShadowMetadata =
183
+ snapshot.kind === 'spot'
184
+ ? spotProjectorSelected
185
+ ? DIRECT_LIGHT_SLOT_PROJECTOR_FLAG |
186
+ ((snapshot.shadowAtlasTile >= 0 ? snapshot.shadowAtlasTile : 0) &
187
+ DIRECT_LIGHT_SLOT_TILE_MASK)
188
+ : directLightMetadata(snapshot.shadowAtlasTile)
189
+ : undefined;
190
+ metadata[17] = directLightMetadata(
191
+ snapshot.kind === 'point'
192
+ ? snapshot.shadowAtlasLayer
193
+ : snapshot.kind === 'spot'
194
+ ? spotShadowMetadata
195
+ : undefined,
196
+ );
197
+ metadata[18] = directLightMetadata(snapshot.iesProfileSlice);
198
+ metadata[19] = directLightMetadata(
199
+ spotProjectorSelected && snapshot.kind === 'spot'
200
+ ? snapshot.projectorSlice
201
+ : snapshot.cookieSlice,
202
+ );
373
203
  return out;
374
204
  }
375
205
 
@@ -383,8 +213,8 @@ export function assertStorageBufferCap(cap: number): Result<boolean, RhiError> {
383
213
  return err(
384
214
  new RhiError({
385
215
  code: 'limit-exceeded',
386
- expected: `device.limits.maxStorageBuffersPerShaderStage >= ${STORAGE_BUFFER_MIN_REQUIRED}, or exactly 0 for uniform-fallback path`,
387
- hint: `device.limits.maxStorageBuffersPerShaderStage = ${cap} < ${STORAGE_BUFFER_MIN_REQUIRED} and != 0. WebGPU spec default minimum is 8; this may be a downlevel adapter with partial storage support. Wait for todo-125 multi-light-pack which collapses point + spot into a single cluster storage buffer (1 entry instead of 2) for 2-3 buffer environments.`,
216
+ expected: `device.limits.maxStorageBuffersPerShaderStage >= ${STORAGE_BUFFER_MIN_REQUIRED}, or exactly 0 for the mesh-only fallback`,
217
+ hint: `device.limits.maxStorageBuffersPerShaderStage = ${cap} < ${STORAGE_BUFFER_MIN_REQUIRED} and != 0. WebGPU spec default minimum is 8; this adapter cannot run the unified Cluster local-light path.`,
388
218
  detail: {
389
219
  maxStorageBufferBindingSize: cap,
390
220
  requestedBytes: STORAGE_BUFFER_MIN_REQUIRED,
package/src/materials.ts CHANGED
@@ -130,6 +130,16 @@ function pass(
130
130
  };
131
131
  }
132
132
 
133
+ function shadowCasterRenderState(
134
+ renderState: MaterialRenderState | undefined,
135
+ ): MaterialRenderState | undefined {
136
+ if (renderState?.cullMode === undefined && renderState?.frontFace === undefined) return undefined;
137
+ return {
138
+ ...(renderState.cullMode === undefined ? {} : { cullMode: renderState.cullMode }),
139
+ ...(renderState.frontFace === undefined ? {} : { frontFace: renderState.frontFace }),
140
+ };
141
+ }
142
+
133
143
  interface UnlitOpts {
134
144
  /** Asset-side authored color space. Defaults to sRGB. */
135
145
  readonly colorSpace?: MaterialColorSpace;
@@ -150,7 +160,9 @@ function unlit(rgba: readonly [number, number, number, number], opts?: UnlitOpts
150
160
  const passes: [MaterialPass, ...MaterialPass[]] = [
151
161
  pass('forward', UNLIT_MODULE, opts?.renderState, undefined, opts?.queue),
152
162
  ];
153
- if (opts?.castShadow !== false) passes.push(pass('shadow-caster', UNLIT_MODULE, undefined));
163
+ if (opts?.castShadow !== false) {
164
+ passes.push(pass('shadow-caster', UNLIT_MODULE, shadowCasterRenderState(opts?.renderState)));
165
+ }
154
166
  return {
155
167
  kind: 'material',
156
168
  ...(opts?.colorSpace === undefined || opts.colorSpace === 'srgb'
@@ -292,7 +304,9 @@ function standard(opts: StandardOpts): MaterialAsset {
292
304
  if (transmission === 0) {
293
305
  passes.push(pass('deferred', STANDARD_MODULE, opts.renderState, 'fs_gbuffer', opts.queue));
294
306
  }
295
- if (opts.castShadow !== false) passes.push(pass('shadow-caster', STANDARD_MODULE, undefined));
307
+ if (opts.castShadow !== false) {
308
+ passes.push(pass('shadow-caster', STANDARD_MODULE, shadowCasterRenderState(opts.renderState)));
309
+ }
296
310
  return {
297
311
  kind: 'material',
298
312
  ...(opts.colorSpace === undefined || opts.colorSpace === 'srgb'