@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
@@ -8,6 +8,7 @@
8
8
 
9
9
  import { mat4 } from '@forgeax/engine-math';
10
10
  import type { Buffer, RhiQueue } from '@forgeax/engine-rhi';
11
+ import type { DirectionalShadowQuality } from '../components/directional-shadow-filter';
11
12
  import type { FogFrame } from '../extract/environment';
12
13
  import type { PointsLinesStyle } from '../points-lines/snapshot';
13
14
  import type { CameraSnapshot } from '../render-contract';
@@ -16,9 +17,9 @@ import type {
16
17
  ExtractedLights,
17
18
  SpotLightSnapshot,
18
19
  } from '../render-system-extract';
20
+ import { SHADOW_ATLAS_DEFAULT_LAYERS } from '../shadow-atlas';
19
21
  import type { TemporalView as LegacyTemporalView } from '../temporal/temporal-view';
20
22
  import type { TemporalView } from '../temporal/view';
21
- import { clampPcfKernelSize } from './frame-snapshot';
22
23
  import { computeProjectionMatrix, computeViewMatrix } from './helpers';
23
24
 
24
25
  export const VIEW_UNIFORM_BYTES = 960;
@@ -29,13 +30,39 @@ export const POINTS_LINES_VIEW_BUFFER_SIZE =
29
30
  POINTS_LINES_VIEW_SLOT_STRIDE * POINTS_LINES_VIEW_SLOT_COUNT;
30
31
  export const VIEW_UNIFORM_SLOT_STRIDE = 1024;
31
32
  export const POINT_SHADOW_VIEW_SLOT_COUNT = 24;
33
+ export const CUBE_CAPTURE_VIEW_SLOT_BASE = 1 + POINT_SHADOW_VIEW_SLOT_COUNT;
34
+ export const REFLECTION_PROBE_VIEW_SLOT_BASE = CUBE_CAPTURE_VIEW_SLOT_BASE + 6;
35
+ export const REFLECTION_PROBE_VIEW_SLOT_COUNT = 16;
32
36
  export const VIEW_UNIFORM_BUFFER_SIZE =
33
- VIEW_UNIFORM_SLOT_STRIDE * (1 + POINT_SHADOW_VIEW_SLOT_COUNT);
37
+ VIEW_UNIFORM_SLOT_STRIDE * (REFLECTION_PROBE_VIEW_SLOT_BASE + REFLECTION_PROBE_VIEW_SLOT_COUNT);
34
38
 
35
39
  export function pointShadowViewOffset(layer: number, face: number): number {
36
40
  return VIEW_UNIFORM_SLOT_STRIDE * (1 + layer * 6 + face);
37
41
  }
38
42
 
43
+ /** Project the accepted Directional quality union into the frozen View ABI. */
44
+ function directionalShadowFilterCarrier(
45
+ quality: DirectionalShadowQuality | undefined,
46
+ ): readonly [number, number, number, number] {
47
+ if (quality === undefined) return [0, 0, 0, 0];
48
+ if (quality.kind === 'pcss') {
49
+ return [
50
+ quality.preset === 'medium' ? 4 : 5,
51
+ quality.angularRadiusRadians,
52
+ quality.maxPenumbraTexels,
53
+ 0,
54
+ ];
55
+ }
56
+ switch (quality.kernel) {
57
+ case 1:
58
+ return [1, 0, 0, 0];
59
+ case 3:
60
+ return [2, 0, 0, 0];
61
+ case 5:
62
+ return [3, 0, 0, 0];
63
+ }
64
+ }
65
+
39
66
  export function writePointsLinesViewUbo(
40
67
  queue: RhiQueue,
41
68
  buffer: Buffer,
@@ -95,10 +122,10 @@ export function writePointsLinesViewUbo(
95
122
  * [24..26] cameraPos, [28..43] lightViewProj0 (was lightSpaceMatrix),
96
123
  * [44..59] inverseViewProj, [60..75] lightViewProj1,
97
124
  * [76..91] lightViewProj2, [92..107] lightViewProj3,
98
- * [108]/[112]/[116]/[120] splitPlanes (vec4 stride),
125
+ * [108..123] splitPlanes (vec4 lanes: split/world texel/depth span/reserved),
99
126
  * [124] cascadeCount, [125] cascadeBlend,
100
- * [126] depthBias, [127] normalBias, [128] pcfKernelSize (feat-20260621
101
- * M3 / m3-t2-t3), [129..131] align pad,
127
+ * [126] depthBias, [127] normalBias,
128
+ * [128..131] directionalShadowFilter (profile/radius/max penumbra/reserved),
102
129
  * [132..195] spotLightViewProj array<mat4x4<f32>, 4> (feat-20260625 w25:
103
130
  * folded from standalone binding 9 to fix WebGL2 fragment uniform-buffer
104
131
  * overflow; lane N = spot with shadowAtlasTile === N, 16 f32 / lane,
@@ -113,9 +140,13 @@ export function writeViewUbo(
113
140
  light: DirectionalLightSnapshot,
114
141
  lights: ExtractedLights,
115
142
  spotShadowSnapshots: readonly SpotLightSnapshot[],
116
- fogOrTemporal?: FogFrame | TemporalView | LegacyTemporalView,
143
+ fogOrTemporalOrOffset?: FogFrame | TemporalView | LegacyTemporalView | number,
117
144
  temporal?: TemporalView,
145
+ projectorSpotIndex?: number,
118
146
  ): void {
147
+ const byteOffset = typeof fogOrTemporalOrOffset === 'number' ? fogOrTemporalOrOffset : 0;
148
+ const fogOrTemporal =
149
+ typeof fogOrTemporalOrOffset === 'number' ? undefined : fogOrTemporalOrOffset;
119
150
  const isTemporal =
120
151
  fogOrTemporal !== undefined &&
121
152
  ('currentJitterUv' in fogOrTemporal || 'currentUnjitteredViewProjection' in fogOrTemporal);
@@ -185,38 +216,39 @@ export function writeViewUbo(
185
216
  viewPayload[228] = camera.near;
186
217
  viewPayload[229] = camera.far;
187
218
  viewPayload[230] = camera.projection === 'orthographic' ? 1 : 0;
188
- // splitPlanes at [108], [112], [116], [120] (vec4 stride = 4 floats).
219
+ // splitPlanes at [108..123] (four vec4 lanes: split/world texel/depth span/reserved).
189
220
  if (lights.splitPlanes !== undefined) {
190
221
  for (let s = 0; s < 4; s++) {
191
- viewPayload[108 + s * 4] = lights.splitPlanes[s] ?? 0;
222
+ for (let lane = 0; lane < 4; lane++) {
223
+ viewPayload[108 + s * 4 + lane] = lights.splitPlanes[s * 4 + lane] ?? 0;
224
+ }
192
225
  }
193
226
  }
194
227
  // cascadeCount / cascadeBlend at [124..125].
195
228
  viewPayload[124] = lights.cascadeCount ?? 0;
196
229
  viewPayload[125] = lights.cascadeBlend ?? 0;
197
- // feat-20260621-merge-directionallightshadow-into-directionallight M3 /
198
- // m3-t2: shadow bias + PCF kernel width from the merged DirectionalLight
199
- // land in the formerly-free tail pad at floats [126/127/128] (bytes
200
- // 504/508/512). VIEW_PAYLOAD_FLOATS / VIEW_UBO_BYTES are unchanged --
201
- // the WGSL View struct (common.wgsl) appends matching f32 at the same
202
- // slots; the host tail pad shrinks 88 B -> 64 B, rest stays zero.
203
- // pcfKernelSize is host-clamped to the nearest valid odd kernel {1,3,5}
204
- // (cap 5, matching lighting-directional.wgsl MAX_PCF_HALF=2 from the
205
- // merged 5.3-production-shadow-demos AC-14 variant-free loop) so the
206
- // per-iteration radius clip has a fixed, legal bound; undefined
207
- // (no cast-shadow / no shadow fields) defaults to 3.
230
+ // M1 directional shadow carrier: bias remains at [126]/[127], while the
231
+ // accepted quality union occupies the existing four-float tail [128..131].
232
+ // Point and Spot pcfKernelSize stay in their own snapshot paths and never
233
+ // enter this Directional View projection.
208
234
  viewPayload[126] = lights.depthBias ?? 0.005;
209
235
  viewPayload[127] = lights.normalBias ?? 0.05;
210
- viewPayload[128] = clampPcfKernelSize(lights.pcfKernelSize);
236
+ const filterCarrier = directionalShadowFilterCarrier(lights.directionalShadowQuality);
237
+ viewPayload[128] = filterCarrier[0] ?? 0;
238
+ viewPayload[129] = filterCarrier[1] ?? 0;
239
+ viewPayload[130] = filterCarrier[2] ?? 0;
240
+ viewPayload[131] = filterCarrier[3] ?? 0;
211
241
 
212
242
  // feat-20260625-spot-light-shadow-mapping w25 (scope-amend webkit-fallback):
213
243
  // spotLightViewProj array<mat4x4<f32>, 4> at floats [132..195] (byte 528,
214
- // 16 B-aligned after pcfKernelSize). Lane N = the spot with
244
+ // 16 B-aligned after directionalShadowFilter). Lane N = the spot with
215
245
  // `shadowAtlasTile === N` (cap = 4); the perspective matrix was already
216
246
  // computed by the extract stage (SpotLightSnapshot.lightViewProj) — record
217
- // never recomputes it (Derive). Lanes for non-shadow / clipped spots (tile
218
- // < 0 or lightViewProj undefined) stay zeroed; the WGSL sample path gates
219
- // on `SpotLight.shadowAtlasTile >= 0` so zeroed lanes are never read.
247
+ // never recomputes it (Derive). The accepted projector is identified by its
248
+ // spot index, not by a negative shadow tile: a projector-only spot publishes
249
+ // its matrix in lane 0 so the shader never samples a zero matrix. Shadowed
250
+ // projector spots keep their shadow tile lane and carry the projector bit in
251
+ // the LightSlot identity word.
220
252
  // Folded from the former standalone binding 9 UBO to keep the WebGL2
221
253
  // fallback fragment uniform-buffer count <= 11 (GLES 3.0).
222
254
  {
@@ -228,10 +260,12 @@ export function writeViewUbo(
228
260
  const ss = spotSnaps[i];
229
261
  if (ss === undefined) continue;
230
262
  const tile = ss.shadowAtlasTile;
231
- if (tile < 0 || tile >= SPOT_LVP_LANE_COUNT) continue;
232
263
  const lvp = ss.lightViewProj;
233
264
  if (lvp === undefined) continue;
234
- const base = SPOT_LVP_BASE_FLOAT + tile * SPOT_LVP_FLOATS_PER_LANE;
265
+ const lane =
266
+ tile >= 0 && tile < SPOT_LVP_LANE_COUNT ? tile : i === projectorSpotIndex ? 0 : -1;
267
+ if (lane < 0) continue;
268
+ const base = SPOT_LVP_BASE_FLOAT + lane * SPOT_LVP_FLOATS_PER_LANE;
235
269
  for (let f = 0; f < SPOT_LVP_FLOATS_PER_LANE; f++) {
236
270
  viewPayload[base + f] = lvp[f] ?? 0;
237
271
  }
@@ -250,11 +284,12 @@ export function writeViewUbo(
250
284
  viewPayload[237] = fog.maxOpacity;
251
285
  }
252
286
 
253
- const viewUploadResult = queue.writeBuffer(viewUniformBuffer, 0, viewPayload);
287
+ const viewUploadResult = queue.writeBuffer(viewUniformBuffer, byteOffset, viewPayload);
254
288
  if (!viewUploadResult.ok) throw viewUploadResult.error;
255
289
 
256
290
  for (const snapshot of lights.pointShadow ?? []) {
257
- if (snapshot.shadowAtlasLayer < 0 || snapshot.shadowAtlasLayer >= 4) continue;
291
+ if (snapshot.shadowAtlasLayer < 0 || snapshot.shadowAtlasLayer >= SHADOW_ATLAS_DEFAULT_LAYERS)
292
+ continue;
258
293
  for (let face = 0; face < 6; face += 1) {
259
294
  const facePayload = viewPayload.slice();
260
295
  const matrix = snapshot.shadowMatrices.subarray(face * 16, (face + 1) * 16);
@@ -0,0 +1,81 @@
1
+ export interface ProbeFilterState {
2
+ readonly probeIndex: number;
3
+ readonly faceCount: number;
4
+ readonly mipCount: number;
5
+ readonly cursor: number;
6
+ readonly activeGeneration: number;
7
+ }
8
+
9
+ export function createProbeFilterState(input: {
10
+ readonly probeIndex: number;
11
+ readonly faceCount?: number;
12
+ readonly mipCount: number;
13
+ readonly activeGeneration?: number;
14
+ }): ProbeFilterState {
15
+ return {
16
+ probeIndex: input.probeIndex,
17
+ faceCount: input.faceCount ?? 6,
18
+ mipCount: input.mipCount,
19
+ cursor: 0,
20
+ activeGeneration: input.activeGeneration ?? 0,
21
+ };
22
+ }
23
+
24
+ export function advanceProbeFilter(
25
+ state: ProbeFilterState,
26
+ ):
27
+ | { readonly probeIndex: number; readonly faceIndex: number; readonly mipLevel: number }
28
+ | undefined {
29
+ const total = state.faceCount * state.mipCount;
30
+ if (state.cursor >= total) return undefined;
31
+ return {
32
+ probeIndex: state.probeIndex,
33
+ faceIndex: state.cursor % state.faceCount,
34
+ mipLevel: Math.floor(state.cursor / state.faceCount),
35
+ };
36
+ }
37
+
38
+ export function commitProbeFilterStep(state: ProbeFilterState, success: boolean): ProbeFilterState {
39
+ if (!success) return state;
40
+ const nextCursor = state.cursor + 1;
41
+ const complete = nextCursor >= state.faceCount * state.mipCount;
42
+ return {
43
+ ...state,
44
+ cursor: nextCursor,
45
+ activeGeneration: complete ? state.activeGeneration + 1 : state.activeGeneration,
46
+ };
47
+ }
48
+
49
+ export function probeFilterIsSteady(state: ProbeFilterState): boolean {
50
+ return state.cursor >= state.faceCount * state.mipCount;
51
+ }
52
+
53
+ function normalize(vector: readonly [number, number, number]): [number, number, number] {
54
+ const length = Math.hypot(vector[0], vector[1], vector[2]);
55
+ if (length === 0) return [0, 0, 1];
56
+ return [vector[0] / length, vector[1] / length, vector[2] / length];
57
+ }
58
+
59
+ export function boxProjectReflectionDirection(
60
+ direction: readonly [number, number, number],
61
+ boxCenter: readonly [number, number, number],
62
+ halfExtents: readonly [number, number, number],
63
+ point: readonly [number, number, number],
64
+ ): [number, number, number] {
65
+ const ray = normalize(direction);
66
+ let distance = Number.POSITIVE_INFINITY;
67
+ for (let axis = 0; axis < 3; axis += 1) {
68
+ const component = ray[axis] ?? 0;
69
+ if (component === 0) continue;
70
+ const extent = halfExtents[axis] ?? 0;
71
+ const edge = component > 0 ? (boxCenter[axis] ?? 0) + extent : (boxCenter[axis] ?? 0) - extent;
72
+ const candidate = (edge - (point[axis] ?? 0)) / component;
73
+ if (candidate > 0) distance = Math.min(distance, candidate);
74
+ }
75
+ if (!Number.isFinite(distance)) return ray;
76
+ return normalize([
77
+ (point[0] ?? 0) + ray[0] * distance - (boxCenter[0] ?? 0),
78
+ (point[1] ?? 0) + ray[1] * distance - (boxCenter[1] ?? 0),
79
+ (point[2] ?? 0) + ray[2] * distance - (boxCenter[2] ?? 0),
80
+ ]);
81
+ }
@@ -0,0 +1,44 @@
1
+ import type { Buffer, Sampler, TextureView } from '@forgeax/engine-rhi';
2
+
3
+ export interface ReflectionProbeTableRow {
4
+ readonly primitiveKey?: string;
5
+ readonly index: number;
6
+ readonly worldId: number;
7
+ readonly entityKey: number;
8
+ readonly center: readonly [number, number, number];
9
+ readonly halfExtents: readonly [number, number, number];
10
+ readonly intensity: number;
11
+ readonly generation: number;
12
+ /** Renderer-owned filtered cube used by the Standard material lane. */
13
+ readonly filteredView?: TextureView;
14
+ /** Renderer-owned sampler paired with filteredView. */
15
+ readonly sampler?: Sampler;
16
+ /** Per-probe uniform payload; keeps MaterialAsset ABI unchanged. */
17
+ readonly uniformBuffer?: Buffer;
18
+ }
19
+
20
+ export interface ReflectionProbeTable {
21
+ readonly rows: readonly ReflectionProbeTableRow[];
22
+ readonly cpuIndexByPrimitive: (primitiveKey: string) => number | undefined;
23
+ readonly gpuIndexByPrimitive: (primitiveKey: string) => number | undefined;
24
+ }
25
+
26
+ export function buildReflectionProbeTable(
27
+ rows: readonly ReflectionProbeTableRow[],
28
+ ): ReflectionProbeTable {
29
+ const ordered = rows.slice().sort((a, b) => a.index - b.index);
30
+ const indices = new Map<string, number>();
31
+ for (const row of ordered) {
32
+ if (row.primitiveKey !== undefined) indices.set(row.primitiveKey, row.index);
33
+ }
34
+ const indexFor = (primitiveKey: string): number | undefined => indices.get(primitiveKey);
35
+ return {
36
+ rows: Object.freeze(ordered),
37
+ cpuIndexByPrimitive: indexFor,
38
+ gpuIndexByPrimitive: indexFor,
39
+ };
40
+ }
41
+
42
+ export function reflectionProbeTableBytes(table: ReflectionProbeTable): number {
43
+ return table.rows.length * 48;
44
+ }
@@ -0,0 +1,281 @@
1
+ import type { ReflectionProbeUpdateIntent } from '../components/reflection-probe';
2
+
3
+ export const REFLECTION_PROBE_BYTES_PER_TEXEL = 8;
4
+ export const DEFAULT_REFLECTION_PROBE_LIMITS = Object.freeze({
5
+ maxProbes: 16,
6
+ maxResolution: 256,
7
+ maxBytes: 64 * 1024 * 1024,
8
+ });
9
+
10
+ export interface ReflectionProbeInput {
11
+ readonly halfExtents: readonly [number, number, number];
12
+ readonly priority: number;
13
+ readonly intensity: number;
14
+ readonly resolution: number;
15
+ }
16
+
17
+ export interface ReflectionProbeFact extends ReflectionProbeInput {
18
+ readonly worldId: number;
19
+ readonly entityKey: number;
20
+ readonly center: readonly [number, number, number];
21
+ readonly revision: number;
22
+ readonly updateIntent?: ReflectionProbeUpdateIntent;
23
+ readonly invalidationVersion?: number;
24
+ }
25
+
26
+ export interface ReflectionProbeAdmissionLimits {
27
+ readonly maxProbes?: number;
28
+ readonly maxResolution?: number;
29
+ readonly maxBytes?: number;
30
+ }
31
+
32
+ export interface ReflectionProbeInvalidInput {
33
+ readonly code: 'reflection-probe-input-invalid';
34
+ readonly field: string;
35
+ readonly value: unknown;
36
+ readonly expected: string;
37
+ }
38
+
39
+ export interface ReflectionProbeBudgetFailure {
40
+ readonly code: 'reflection-probe-budget-exceeded';
41
+ readonly requested: number;
42
+ readonly activeCount: number;
43
+ readonly activeBytes: number;
44
+ readonly maxProbes: number;
45
+ readonly maxBytes: number;
46
+ }
47
+
48
+ export type ReflectionProbeAdmission =
49
+ | { readonly ok: true; readonly acceptedBytes: number }
50
+ | { readonly ok: false; readonly error: ReflectionProbeBudgetFailure };
51
+
52
+ export function validateReflectionProbeInput(
53
+ input: ReflectionProbeInput,
54
+ ): { readonly ok: true } | { readonly ok: false; readonly error: ReflectionProbeInvalidInput } {
55
+ for (let index = 0; index < input.halfExtents.length; index += 1) {
56
+ const value = input.halfExtents[index];
57
+ if (value === undefined || !Number.isFinite(value) || value <= 0) {
58
+ return {
59
+ ok: false,
60
+ error: {
61
+ code: 'reflection-probe-input-invalid',
62
+ field: `halfExtents[${index}]`,
63
+ value,
64
+ expected: 'a finite number greater than zero',
65
+ },
66
+ };
67
+ }
68
+ }
69
+ if (!Number.isFinite(input.priority)) {
70
+ return {
71
+ ok: false,
72
+ error: {
73
+ code: 'reflection-probe-input-invalid',
74
+ field: 'priority',
75
+ value: input.priority,
76
+ expected: 'a finite number',
77
+ },
78
+ };
79
+ }
80
+ if (!Number.isFinite(input.intensity) || input.intensity < 0) {
81
+ return {
82
+ ok: false,
83
+ error: {
84
+ code: 'reflection-probe-input-invalid',
85
+ field: 'intensity',
86
+ value: input.intensity,
87
+ expected: 'a finite number greater than or equal to zero',
88
+ },
89
+ };
90
+ }
91
+ if (!Number.isInteger(input.resolution) || input.resolution <= 0) {
92
+ return {
93
+ ok: false,
94
+ error: {
95
+ code: 'reflection-probe-input-invalid',
96
+ field: 'resolution',
97
+ value: input.resolution,
98
+ expected: 'a positive integer',
99
+ },
100
+ };
101
+ }
102
+ return { ok: true };
103
+ }
104
+
105
+ export function estimateReflectionProbeBytes(
106
+ resolution: number,
107
+ limits: ReflectionProbeAdmissionLimits = DEFAULT_REFLECTION_PROBE_LIMITS,
108
+ ): number {
109
+ const maxResolution = limits.maxResolution ?? DEFAULT_REFLECTION_PROBE_LIMITS.maxResolution;
110
+ const clamped = Math.min(resolution, maxResolution);
111
+ let mipTexels = 0;
112
+ for (let size = clamped; size >= 1; size = Math.floor(size / 2)) {
113
+ mipTexels += size * size;
114
+ }
115
+ return mipTexels * 6 * REFLECTION_PROBE_BYTES_PER_TEXEL;
116
+ }
117
+
118
+ export function admitReflectionProbe(
119
+ input: ReflectionProbeInput,
120
+ current: { readonly acceptedCount: number; readonly acceptedBytes: number },
121
+ limits: ReflectionProbeAdmissionLimits = DEFAULT_REFLECTION_PROBE_LIMITS,
122
+ ): ReflectionProbeAdmission {
123
+ const maxProbes = limits.maxProbes ?? DEFAULT_REFLECTION_PROBE_LIMITS.maxProbes;
124
+ const maxBytes = limits.maxBytes ?? DEFAULT_REFLECTION_PROBE_LIMITS.maxBytes;
125
+ const maxResolution = limits.maxResolution ?? DEFAULT_REFLECTION_PROBE_LIMITS.maxResolution;
126
+ const requested = estimateReflectionProbeBytes(input.resolution, { maxResolution });
127
+ if (
128
+ input.resolution > maxResolution ||
129
+ current.acceptedCount >= maxProbes ||
130
+ current.acceptedBytes + requested > maxBytes
131
+ ) {
132
+ return {
133
+ ok: false,
134
+ error: {
135
+ code: 'reflection-probe-budget-exceeded',
136
+ requested,
137
+ activeCount: current.acceptedCount,
138
+ activeBytes: current.acceptedBytes,
139
+ maxProbes,
140
+ maxBytes,
141
+ },
142
+ };
143
+ }
144
+ return { ok: true, acceptedBytes: current.acceptedBytes + requested };
145
+ }
146
+
147
+ export interface ReflectionProbeSelection {
148
+ readonly kind: 'probe';
149
+ readonly worldId: number;
150
+ readonly entityKey: number;
151
+ readonly normalizedDistance: number;
152
+ }
153
+
154
+ export interface SkylightSelection {
155
+ readonly kind: 'skylight';
156
+ }
157
+
158
+ export type ReflectionProbeSelectionResult = ReflectionProbeSelection | SkylightSelection;
159
+
160
+ function contains(fact: ReflectionProbeFact, point: readonly [number, number, number]): boolean {
161
+ return fact.halfExtents.every((extent, axis) => {
162
+ const center = fact.center[axis] ?? 0;
163
+ const coordinate = point[axis] ?? 0;
164
+ return Math.abs(coordinate - center) <= extent;
165
+ });
166
+ }
167
+
168
+ function normalizedDistance(
169
+ fact: ReflectionProbeFact,
170
+ point: readonly [number, number, number],
171
+ ): number {
172
+ let squared = 0;
173
+ for (let axis = 0; axis < 3; axis += 1) {
174
+ const delta = (point[axis] ?? 0) - (fact.center[axis] ?? 0);
175
+ const extent = fact.halfExtents[axis] ?? 1;
176
+ squared += (delta / extent) ** 2;
177
+ }
178
+ return squared;
179
+ }
180
+
181
+ function isBetter(
182
+ candidate: ReflectionProbeSelection,
183
+ current: ReflectionProbeSelection,
184
+ facts: readonly ReflectionProbeFact[],
185
+ ): boolean {
186
+ const candidateFact = facts.find(
187
+ (fact) => fact.worldId === candidate.worldId && fact.entityKey === candidate.entityKey,
188
+ );
189
+ const currentFact = facts.find(
190
+ (fact) => fact.worldId === current.worldId && fact.entityKey === current.entityKey,
191
+ );
192
+ if (candidateFact === undefined || currentFact === undefined) return false;
193
+ if (candidateFact.priority !== currentFact.priority)
194
+ return candidateFact.priority > currentFact.priority;
195
+ if (candidate.normalizedDistance !== current.normalizedDistance) {
196
+ return candidate.normalizedDistance < current.normalizedDistance;
197
+ }
198
+ return (
199
+ candidate.worldId < current.worldId ||
200
+ (candidate.worldId === current.worldId && candidate.entityKey < current.entityKey)
201
+ );
202
+ }
203
+
204
+ export function selectReflectionProbe(
205
+ facts: readonly ReflectionProbeFact[],
206
+ primitiveCenter: readonly [number, number, number],
207
+ ): ReflectionProbeSelectionResult {
208
+ let selected: ReflectionProbeSelection | undefined;
209
+ for (const fact of facts) {
210
+ if (!contains(fact, primitiveCenter)) continue;
211
+ const candidate: ReflectionProbeSelection = {
212
+ kind: 'probe',
213
+ worldId: fact.worldId,
214
+ entityKey: fact.entityKey,
215
+ normalizedDistance: normalizedDistance(fact, primitiveCenter),
216
+ };
217
+ if (selected === undefined || isBetter(candidate, selected, facts)) selected = candidate;
218
+ }
219
+ return selected ?? { kind: 'skylight' };
220
+ }
221
+
222
+ export interface ReflectionProbePrimitive {
223
+ readonly worldId: number;
224
+ readonly entityKey: number;
225
+ readonly center: readonly [number, number, number];
226
+ }
227
+
228
+ export interface ReflectionProbeProjectionSnapshot {
229
+ readonly revision: number;
230
+ readonly facts: readonly ReflectionProbeFact[];
231
+ readonly selected: ReadonlyMap<string, ReflectionProbeSelectionResult>;
232
+ readonly scannedPrimitives: number;
233
+ }
234
+
235
+ function primitiveKey(worldId: number, entityKey: number): string {
236
+ return `${worldId}:${entityKey}`;
237
+ }
238
+
239
+ export class ReflectionProbeProjection {
240
+ private revision = 0;
241
+ private facts: readonly ReflectionProbeFact[] = [];
242
+ private selected = new Map<string, ReflectionProbeSelectionResult>();
243
+ private signature = '';
244
+ private scannedPrimitives = 0;
245
+
246
+ update(
247
+ facts: readonly ReflectionProbeFact[],
248
+ primitives: readonly ReflectionProbePrimitive[],
249
+ ): ReflectionProbeProjectionSnapshot {
250
+ const nextFacts = facts
251
+ .slice()
252
+ .sort((a, b) => a.worldId - b.worldId || a.entityKey - b.entityKey);
253
+ const signature = JSON.stringify({ facts: nextFacts, primitives });
254
+ if (signature === this.signature) return this.snapshot();
255
+ this.signature = signature;
256
+ this.facts = nextFacts;
257
+ this.selected = new Map();
258
+ for (const primitive of primitives) {
259
+ this.selected.set(
260
+ primitiveKey(primitive.worldId, primitive.entityKey),
261
+ selectReflectionProbe(nextFacts, primitive.center),
262
+ );
263
+ }
264
+ this.scannedPrimitives = primitives.length;
265
+ this.revision += 1;
266
+ return this.snapshot();
267
+ }
268
+
269
+ snapshot(): ReflectionProbeProjectionSnapshot {
270
+ return {
271
+ revision: this.revision,
272
+ facts: this.facts,
273
+ selected: this.selected,
274
+ scannedPrimitives: this.scannedPrimitives,
275
+ };
276
+ }
277
+
278
+ selection(worldId: number, entityKey: number): ReflectionProbeSelectionResult {
279
+ return this.selected.get(primitiveKey(worldId, entityKey)) ?? { kind: 'skylight' };
280
+ }
281
+ }