@forgeax/engine-render 0.1.20 → 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 (372) 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 +21 -18
  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-4XINDLAY.mjs → chunk-5YADUCA7.mjs} +1552 -1361
  21. package/dist/chunk-5YADUCA7.mjs.map +1 -0
  22. package/dist/{chunk-TBQJOK2L.mjs → chunk-6OPXDQ2E.mjs} +364 -51
  23. package/dist/chunk-6OPXDQ2E.mjs.map +1 -0
  24. package/dist/{chunk-M2MP4ZOU.mjs → chunk-BXBQQKFQ.mjs} +3 -9
  25. package/dist/chunk-BXBQQKFQ.mjs.map +1 -0
  26. package/dist/{chunk-CDTWTHZR.mjs → chunk-FS2KNUQH.mjs} +548 -6
  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/construct-renderer.mjs +5826 -1003
  42. package/dist/construct-renderer.mjs.map +1 -1
  43. package/dist/environment/frame.d.ts.map +1 -1
  44. package/dist/environment/lifecycle.d.ts +5 -0
  45. package/dist/environment/lifecycle.d.ts.map +1 -1
  46. package/dist/errors/render.d.ts +141 -93
  47. package/dist/errors/render.d.ts.map +1 -1
  48. package/dist/extract/directional-shadow-projection.d.ts +23 -0
  49. package/dist/extract/directional-shadow-projection.d.ts.map +1 -0
  50. package/dist/extract/environment.d.ts +14 -0
  51. package/dist/extract/environment.d.ts.map +1 -1
  52. package/dist/extract/visibility.d.ts.map +1 -1
  53. package/dist/features/fullscreen.d.ts.map +1 -1
  54. package/dist/features/targets.d.ts.map +1 -1
  55. package/dist/features/types.d.ts +10 -0
  56. package/dist/features/types.d.ts.map +1 -1
  57. package/dist/fullscreen-post-process-pass.d.ts +13 -2
  58. package/dist/fullscreen-post-process-pass.d.ts.map +1 -1
  59. package/dist/gpu-driven/batch-topology.d.ts.map +1 -1
  60. package/dist/gpu-driven/production-raster.d.ts +0 -1
  61. package/dist/gpu-driven/production-raster.d.ts.map +1 -1
  62. package/dist/gpu-scene.d.ts.map +1 -1
  63. package/dist/hdrp-buffers.d.ts +19 -17
  64. package/dist/hdrp-buffers.d.ts.map +1 -1
  65. package/dist/ibl/IblPipelineCache.d.ts +4 -0
  66. package/dist/ibl/IblPipelineCache.d.ts.map +1 -1
  67. package/dist/ibl/kernel-cache.d.ts +36 -0
  68. package/dist/ibl/kernel-cache.d.ts.map +1 -0
  69. package/dist/index.d.ts +28 -3
  70. package/dist/index.d.ts.map +1 -1
  71. package/dist/index.mjs +4 -4
  72. package/dist/index.mjs.map +1 -1
  73. package/dist/inspection-types.d.ts +51 -0
  74. package/dist/inspection-types.d.ts.map +1 -1
  75. package/dist/instances-derived-bounds.d.ts +50 -0
  76. package/dist/instances-derived-bounds.d.ts.map +1 -0
  77. package/dist/internal.mjs +1 -1
  78. package/dist/light-buffer-layout.d.ts +10 -1
  79. package/dist/light-buffer-layout.d.ts.map +1 -1
  80. package/dist/mesh-material-bindings.d.ts +54 -0
  81. package/dist/mesh-material-bindings.d.ts.map +1 -1
  82. package/dist/pbr-pipeline.d.ts +14 -4
  83. package/dist/pbr-pipeline.d.ts.map +1 -1
  84. package/dist/pipeline/standard-forward-lane.d.ts +2 -1
  85. package/dist/pipeline/standard-forward-lane.d.ts.map +1 -1
  86. package/dist/pipeline/standard-lighting/errors.d.ts +4 -0
  87. package/dist/pipeline/standard-lighting/errors.d.ts.map +1 -0
  88. package/dist/pipeline/standard-lighting/graph.d.ts +24 -0
  89. package/dist/pipeline/standard-lighting/graph.d.ts.map +1 -0
  90. package/dist/pipeline/standard-lighting/inspection.d.ts +38 -0
  91. package/dist/pipeline/standard-lighting/inspection.d.ts.map +1 -0
  92. package/dist/pipeline/standard-lighting/layout.d.ts +28 -0
  93. package/dist/pipeline/standard-lighting/layout.d.ts.map +1 -0
  94. package/dist/pipeline/standard-lighting/light-frame.d.ts +33 -0
  95. package/dist/pipeline/standard-lighting/light-frame.d.ts.map +1 -0
  96. package/dist/pipeline/standard-lighting/membership.d.ts +14 -0
  97. package/dist/pipeline/standard-lighting/membership.d.ts.map +1 -0
  98. package/dist/pipeline/standard-lighting/prepare.d.ts +16 -0
  99. package/dist/pipeline/standard-lighting/prepare.d.ts.map +1 -0
  100. package/dist/pipeline/standard-lighting/topology.d.ts +45 -0
  101. package/dist/pipeline/standard-lighting/topology.d.ts.map +1 -0
  102. package/dist/pipeline/standard-lighting/transport.d.ts +36 -0
  103. package/dist/pipeline/standard-lighting/transport.d.ts.map +1 -0
  104. package/dist/pipeline/standard-pipeline.d.ts +4 -13
  105. package/dist/pipeline/standard-pipeline.d.ts.map +1 -1
  106. package/dist/pipeline/standard-post.d.ts.map +1 -1
  107. package/dist/pipeline/standard-profile.d.ts +5 -26
  108. package/dist/pipeline/standard-profile.d.ts.map +1 -1
  109. package/dist/pipeline-spec.d.ts +40 -7
  110. package/dist/pipeline-spec.d.ts.map +1 -1
  111. package/dist/point-shadow-inspection.d.ts +21 -0
  112. package/dist/point-shadow-inspection.d.ts.map +1 -0
  113. package/dist/record/frame-lighting.d.ts +27 -32
  114. package/dist/record/frame-lighting.d.ts.map +1 -1
  115. package/dist/record/frame-snapshot.d.ts +28 -14
  116. package/dist/record/frame-snapshot.d.ts.map +1 -1
  117. package/dist/record/frame-targets.d.ts +5 -0
  118. package/dist/record/frame-targets.d.ts.map +1 -1
  119. package/dist/record/frame.d.ts +10 -4
  120. package/dist/record/frame.d.ts.map +1 -1
  121. package/dist/record/gpu-pass-timing/contract.d.ts +96 -0
  122. package/dist/record/gpu-pass-timing/contract.d.ts.map +1 -0
  123. package/dist/record/gpu-pass-timing/errors.d.ts +18 -0
  124. package/dist/record/gpu-pass-timing/errors.d.ts.map +1 -0
  125. package/dist/record/gpu-pass-timing/index.d.ts +8 -0
  126. package/dist/record/gpu-pass-timing/index.d.ts.map +1 -0
  127. package/dist/record/gpu-pass-timing/parser.d.ts +11 -0
  128. package/dist/record/gpu-pass-timing/parser.d.ts.map +1 -0
  129. package/dist/record/gpu-pass-timing/session.d.ts +43 -0
  130. package/dist/record/gpu-pass-timing/session.d.ts.map +1 -0
  131. package/dist/record/gpu-timing.d.ts +8 -3
  132. package/dist/record/gpu-timing.d.ts.map +1 -1
  133. package/dist/record/main-pass-geometry.d.ts.map +1 -1
  134. package/dist/record/main-pass-material.d.ts +22 -1
  135. package/dist/record/main-pass-material.d.ts.map +1 -1
  136. package/dist/record/main-pass-sprite-draws.d.ts.map +1 -1
  137. package/dist/record/main-pass.d.ts +2 -0
  138. package/dist/record/main-pass.d.ts.map +1 -1
  139. package/dist/record/render-context.d.ts +43 -2
  140. package/dist/record/render-context.d.ts.map +1 -1
  141. package/dist/record/shadow-pass.d.ts.map +1 -1
  142. package/dist/record/skybox-post-pass.d.ts.map +1 -1
  143. package/dist/record/typed-frame-graph.d.ts +72 -4
  144. package/dist/record/typed-frame-graph.d.ts.map +1 -1
  145. package/dist/record/view-ubo.d.ts +7 -4
  146. package/dist/record/view-ubo.d.ts.map +1 -1
  147. package/dist/reflection/filter.d.ts +22 -0
  148. package/dist/reflection/filter.d.ts.map +1 -0
  149. package/dist/reflection/gpu-table.d.ts +25 -0
  150. package/dist/reflection/gpu-table.d.ts.map +1 -0
  151. package/dist/reflection/projection.d.ts +91 -0
  152. package/dist/reflection/projection.d.ts.map +1 -0
  153. package/dist/render-contract.d.ts +49 -7
  154. package/dist/render-contract.d.ts.map +1 -1
  155. package/dist/render-graph-primitives.d.ts +1 -0
  156. package/dist/render-graph-primitives.d.ts.map +1 -1
  157. package/dist/render-pipeline.d.ts +42 -2
  158. package/dist/render-pipeline.d.ts.map +1 -1
  159. package/dist/render-system-extract.d.ts +58 -17
  160. package/dist/render-system-extract.d.ts.map +1 -1
  161. package/dist/render-system.d.ts +17 -2
  162. package/dist/render-system.d.ts.map +1 -1
  163. package/dist/renderer.d.ts +43 -0
  164. package/dist/renderer.d.ts.map +1 -0
  165. package/dist/scene/render-scene.d.ts +14 -0
  166. package/dist/scene/render-scene.d.ts.map +1 -1
  167. package/dist/shadow-atlas.d.ts +3 -3
  168. package/dist/shadow-atlas.d.ts.map +1 -1
  169. package/dist/ssao-buffers.d.ts +3 -0
  170. package/dist/ssao-buffers.d.ts.map +1 -1
  171. package/dist/targets/contracts.d.ts +66 -0
  172. package/dist/targets/contracts.d.ts.map +1 -0
  173. package/dist/targets/graph-projection.d.ts +29 -0
  174. package/dist/targets/graph-projection.d.ts.map +1 -0
  175. package/dist/targets/material-source.d.ts +34 -0
  176. package/dist/targets/material-source.d.ts.map +1 -0
  177. package/dist/targets/owner.d.ts +32 -0
  178. package/dist/targets/owner.d.ts.map +1 -0
  179. package/dist/targets/physical.d.ts +18 -0
  180. package/dist/targets/physical.d.ts.map +1 -0
  181. package/dist/targets/readback.d.ts +39 -0
  182. package/dist/targets/readback.d.ts.map +1 -0
  183. package/dist/temporal/index.mjs +3 -3
  184. package/dist/typed-render-graph-primitives.d.ts +1 -1
  185. package/dist/typed-render-graph-primitives.d.ts.map +1 -1
  186. package/dist/typed-shadow-passes.d.ts +1 -1
  187. package/dist/typed-shadow-passes.d.ts.map +1 -1
  188. package/dist/volume/passes.d.ts +2 -2
  189. package/dist/volume/passes.d.ts.map +1 -1
  190. package/package.json +20 -20
  191. package/src/__tests__/bloom-exact-zero.unit.test.ts +48 -19
  192. package/src/__tests__/bloom-lifecycle.integration.test.ts +41 -13
  193. package/src/__tests__/camera-target-consumption.unit.test.ts +53 -0
  194. package/src/__tests__/cluster-binner.unit.test.ts +35 -0
  195. package/src/__tests__/cluster-grid-default-surface.unit.test.ts +7 -2
  196. package/src/__tests__/cube-camera-scheduler.unit.test.ts +70 -0
  197. package/src/__tests__/cube-camera-single-submit.perf.test.ts +75 -0
  198. package/src/__tests__/cube-camera-view-matrices.unit.test.ts +41 -0
  199. package/src/__tests__/cube-camera-vocabulary.unit.test.ts +47 -0
  200. package/src/__tests__/cube-camera.integration.test.ts +42 -0
  201. package/src/__tests__/device-feature-admission.unit.test.ts +73 -0
  202. package/src/__tests__/direct-light-snapshot.unit.test.ts +3 -3
  203. package/src/__tests__/docs-gate-green.unit.test.ts +38 -0
  204. package/src/__tests__/factory-contract.integration.test.ts +255 -29
  205. package/src/__tests__/fog-component.unit.test.ts +67 -0
  206. package/src/__tests__/fog.integration.test.ts +86 -0
  207. package/src/__tests__/fullscreen-feature-plan.unit.test.ts +37 -0
  208. package/src/__tests__/gpu-driven-indirect-raster-evidence.ts +10 -9
  209. package/src/__tests__/gpu-driven-indirect-raster.dawn.test.ts +17 -1
  210. package/src/__tests__/gpu-driven-production.integration.test.ts +16 -17
  211. package/src/__tests__/gpu-driven-view-graph.integration.test.ts +12 -4
  212. package/src/__tests__/gpu-pass-timing-browser-runner.ts +281 -0
  213. package/src/__tests__/gpu-pass-timing-coexistence.integration.test.ts +80 -0
  214. package/src/__tests__/gpu-pass-timing-contract.unit.test.ts +243 -0
  215. package/src/__tests__/gpu-pass-timing-exact-zero.unit.test.ts +131 -0
  216. package/src/__tests__/gpu-pass-timing-lifecycle.integration.test.ts +230 -0
  217. package/src/__tests__/gpu-pass-timing-observation.unit.test.ts +233 -0
  218. package/src/__tests__/gpu-pass-timing-session.unit.test.ts +438 -0
  219. package/src/__tests__/gpu-pass-timing.browser.test.ts +26 -0
  220. package/src/__tests__/gpu-pass-timing.rhi-null.unit.test.ts +125 -0
  221. package/src/__tests__/hdrp-group2-visibility.unit.test.ts +24 -0
  222. package/src/__tests__/instances-culling.integration.test.ts +94 -0
  223. package/src/__tests__/instances-derived-bounds.unit.test.ts +118 -0
  224. package/src/__tests__/material-observation.integration.test.ts +101 -0
  225. package/src/__tests__/pbr-view-layout.unit.test.ts +16 -0
  226. package/src/__tests__/point-shadow-inspection.unit.test.ts +60 -0
  227. package/src/__tests__/public-surface.test-d.ts +10 -0
  228. package/src/__tests__/reflection-probe-authoring.unit.test.ts +71 -0
  229. package/src/__tests__/reflection-probe-filter.unit.test.ts +54 -0
  230. package/src/__tests__/reflection-probe-ibl-topology.unit.test.ts +45 -0
  231. package/src/__tests__/reflection-probe-pass-encode.integration.test.ts +101 -0
  232. package/src/__tests__/reflection-probe-performance.perf.test.ts +60 -0
  233. package/src/__tests__/reflection-probe-recovery.integration.test.ts +55 -0
  234. package/src/__tests__/reflection-probe-selection.unit.test.ts +46 -0
  235. package/src/__tests__/render-error-code-owner.test-d.ts +18 -3
  236. package/src/__tests__/render-feature-docs.unit.test.ts +1 -1
  237. package/src/__tests__/render-target-admission.unit.test.ts +80 -0
  238. package/src/__tests__/render-target-graph-projection.unit.test.ts +102 -0
  239. package/src/__tests__/render-target-inspection.unit.test.ts +52 -0
  240. package/src/__tests__/render-target-material-source.test-d.ts +30 -0
  241. package/src/__tests__/render-target-material-source.unit.test.ts +97 -0
  242. package/src/__tests__/render-target-owner-placement.unit.test.ts +203 -0
  243. package/src/__tests__/render-target-owner.unit.test.ts +122 -0
  244. package/src/__tests__/render-target-probe-soak.perf.test.ts +94 -0
  245. package/src/__tests__/render-target-public-schema.json +107 -0
  246. package/src/__tests__/render-target-public-schema.unit.test.ts +91 -0
  247. package/src/__tests__/render-target-public-surface.test-d.ts +83 -0
  248. package/src/__tests__/render-target-readback.integration.test.ts +90 -0
  249. package/src/__tests__/render-target-recovery.integration.test.ts +85 -0
  250. package/src/__tests__/renderer-factory-material-contract.unit.test.ts +48 -0
  251. package/src/__tests__/renderer-lifecycle.integration.test.ts +14 -0
  252. package/src/__tests__/shader-stage-surface.unit.test.ts +19 -4
  253. package/src/__tests__/skin-motion-regression.unit.test.ts +132 -0
  254. package/src/__tests__/skinned-shadow-caster.test.ts +11 -2
  255. package/src/__tests__/standard-cluster-capability.integration.test.ts +63 -0
  256. package/src/__tests__/standard-cluster-transport.unit.test.ts +121 -0
  257. package/src/__tests__/standard-cluster-variant-resolution.unit.test.ts +232 -0
  258. package/src/__tests__/standard-clustered-pipeline.unit.test.ts +8 -5
  259. package/src/__tests__/standard-lighting-inspection.integration.test.ts +138 -0
  260. package/src/__tests__/standard-lighting-ssot.unit.test.ts +18 -0
  261. package/src/__tests__/standard-lighting-write.unit.test.ts +377 -0
  262. package/src/__tests__/standard-lighting.integration.test.ts +67 -0
  263. package/src/__tests__/standard-pipeline.dawn.test.ts +206 -5
  264. package/src/__tests__/standard-pipeline.integration.test.ts +181 -150
  265. package/src/__tests__/standard-probe-binding.unit.test.ts +25 -0
  266. package/src/__tests__/standard-profile-oracle.unit.test.ts +23 -38
  267. package/src/__tests__/typed-pipeline-topology.unit.test.ts +395 -14
  268. package/src/__tests__/urp-pipeline-targets.unit.test.ts +2 -2
  269. package/src/__tests__/visibility-resolver.unit.test.ts +15 -0
  270. package/src/assembly/device-feature-admission.ts +73 -0
  271. package/src/assembly/directional-shadow-inspection.ts +122 -0
  272. package/src/assembly/factory.ts +877 -286
  273. package/src/assembly/host-contract.ts +25 -0
  274. package/src/assembly/render-target-host.ts +847 -0
  275. package/src/assets/asset-decoders.ts +1 -1
  276. package/src/assets/render-asset-port.ts +7 -1
  277. package/src/capture/cube-views.ts +69 -0
  278. package/src/capture/scheduler.ts +265 -0
  279. package/src/components/__tests__/schema-contract.unit.test.ts +12 -0
  280. package/src/components/__tests__/schema-parity.unit.test.ts +9 -0
  281. package/src/components/camera.ts +4 -0
  282. package/src/components/cube-camera.ts +52 -0
  283. package/src/components/directional-light.ts +22 -2
  284. package/src/components/directional-shadow-filter.ts +57 -0
  285. package/src/components/index.ts +2 -0
  286. package/src/components/light-helpers.ts +81 -7
  287. package/src/components/reflection-probe.ts +49 -0
  288. package/src/environment/frame.ts +6 -24
  289. package/src/environment/lifecycle.ts +12 -0
  290. package/src/errors/render.ts +286 -142
  291. package/src/extract/directional-shadow-projection.ts +61 -0
  292. package/src/extract/environment.ts +57 -0
  293. package/src/extract/visibility.ts +25 -1
  294. package/src/features/fullscreen.ts +3 -0
  295. package/src/features/targets.ts +4 -1
  296. package/src/features/types.ts +10 -0
  297. package/src/fullscreen-post-process-pass.ts +43 -2
  298. package/src/gpu-driven/batch-topology.ts +1 -0
  299. package/src/gpu-driven/production-raster.ts +1 -3
  300. package/src/gpu-scene.ts +8 -1
  301. package/src/hdrp-buffers.ts +165 -59
  302. package/src/ibl/IblPipelineCache.ts +11 -0
  303. package/src/ibl/kernel-cache.ts +88 -0
  304. package/src/index.ts +148 -0
  305. package/src/inspection-types.ts +56 -0
  306. package/src/instances-derived-bounds.ts +171 -0
  307. package/src/light-buffer-layout.ts +20 -5
  308. package/src/mesh-material-bindings.ts +132 -0
  309. package/src/pbr-pipeline.ts +106 -17
  310. package/src/pipeline/standard-forward-lane.ts +46 -16
  311. package/src/pipeline/standard-lighting/errors.ts +6 -0
  312. package/src/pipeline/standard-lighting/graph.ts +157 -0
  313. package/src/pipeline/standard-lighting/inspection.ts +88 -0
  314. package/src/pipeline/standard-lighting/layout.ts +31 -0
  315. package/src/pipeline/standard-lighting/light-frame.ts +34 -0
  316. package/src/pipeline/standard-lighting/membership.ts +43 -0
  317. package/src/pipeline/standard-lighting/prepare.ts +37 -0
  318. package/src/pipeline/standard-lighting/topology.ts +120 -0
  319. package/src/pipeline/standard-lighting/transport.ts +77 -0
  320. package/src/pipeline/standard-pipeline.ts +122 -193
  321. package/src/pipeline/standard-post.ts +26 -7
  322. package/src/pipeline/standard-profile.ts +17 -47
  323. package/src/pipeline-spec.ts +84 -21
  324. package/src/point-shadow-inspection.ts +55 -0
  325. package/src/record/__tests__/temporal-payload.unit.test.ts +42 -1
  326. package/src/record/__tests__/transparent-group2-selection.unit.test.ts +73 -0
  327. package/src/record/__tests__/view-ubo-layout.unit.test.ts +40 -0
  328. package/src/record/frame-lighting.ts +367 -427
  329. package/src/record/frame-snapshot.ts +35 -27
  330. package/src/record/frame-targets.ts +17 -0
  331. package/src/record/frame.ts +261 -74
  332. package/src/record/gpu-pass-timing/contract.ts +180 -0
  333. package/src/record/gpu-pass-timing/errors.ts +34 -0
  334. package/src/record/gpu-pass-timing/index.ts +37 -0
  335. package/src/record/gpu-pass-timing/parser.ts +97 -0
  336. package/src/record/gpu-pass-timing/session.ts +938 -0
  337. package/src/record/gpu-timing.ts +21 -12
  338. package/src/record/main-pass-geometry.ts +120 -80
  339. package/src/record/main-pass-material.ts +95 -9
  340. package/src/record/main-pass-sprite-draws.ts +54 -25
  341. package/src/record/main-pass.ts +71 -15
  342. package/src/record/render-context.ts +72 -0
  343. package/src/record/shadow-pass.ts +17 -28
  344. package/src/record/skybox-post-pass.ts +20 -4
  345. package/src/record/typed-frame-graph.ts +893 -44
  346. package/src/record/view-ubo.ts +63 -28
  347. package/src/reflection/filter.ts +81 -0
  348. package/src/reflection/gpu-table.ts +44 -0
  349. package/src/reflection/projection.ts +281 -0
  350. package/src/render-contract.ts +76 -5
  351. package/src/render-graph-primitives.ts +9 -4
  352. package/src/render-pipeline.ts +117 -2
  353. package/src/render-system-extract.ts +557 -100
  354. package/src/render-system.ts +1095 -66
  355. package/src/renderer.ts +222 -0
  356. package/src/scene/render-scene.ts +115 -9
  357. package/src/shadow-atlas.ts +5 -5
  358. package/src/ssao-buffers.ts +9 -2
  359. package/src/targets/contracts.ts +182 -0
  360. package/src/targets/graph-projection.ts +169 -0
  361. package/src/targets/material-source.ts +143 -0
  362. package/src/targets/owner.ts +214 -0
  363. package/src/targets/physical.ts +18 -0
  364. package/src/targets/readback.ts +153 -0
  365. package/src/transmission/__tests__/standard-transmission.dawn.test.ts +59 -20
  366. package/src/typed-render-graph-primitives.ts +5 -3
  367. package/src/typed-shadow-passes.ts +46 -40
  368. package/src/volume/passes.ts +15 -9
  369. package/dist/chunk-4XINDLAY.mjs.map +0 -1
  370. package/dist/chunk-CDTWTHZR.mjs.map +0 -1
  371. package/dist/chunk-M2MP4ZOU.mjs.map +0 -1
  372. package/dist/chunk-TBQJOK2L.mjs.map +0 -1
@@ -138,49 +138,33 @@ export class TemporalFrameSubmitError extends Error {
138
138
  /**
139
139
  * Detail for `RuntimeErrorCode 'shadow-invalid-config'`.
140
140
  *
141
- * Emitted by `DirectionalLight.validate` shadow-field path when a field value violates
142
- * runtime constraints (e.g. mapSize < 1, cascadeCount not in {1..4}).
143
- * AI users access `.detail.field` / `.detail.value` / `.detail.min` /
144
- * `.detail.max` via property access — no string parsing.
145
- *
146
- * `max` is `undefined` for lower-bound-only validations (e.g. mapSize < 1).
147
- * feat-20260613-csm: max added for upper-bound cascade validations
148
- * (cascadeCount max=4, splitLambda max=1, cascadeBlend max=0.5).
141
+ * The four fields are the only recovery surface: AI users read the invalid
142
+ * field, actual value, structured bound, and reason without parsing prose.
149
143
  */
144
+ export type ShadowInvalidConfigBound =
145
+ | { readonly kind: 'range'; readonly min: number; readonly max: number }
146
+ | { readonly kind: 'lower-bound'; readonly operator: '>' | '>='; readonly value: number }
147
+ | { readonly kind: 'allowed-values'; readonly values: readonly number[] };
148
+
150
149
  export interface ShadowInvalidConfigDetail {
151
150
  readonly field: string;
152
- readonly value: number;
153
- readonly min: number;
154
- readonly max?: number;
151
+ readonly actual: number;
152
+ readonly bound: ShadowInvalidConfigBound;
153
+ readonly reason: string;
155
154
  }
156
155
 
157
156
  /**
158
157
  * Structured error for shadow component config validation failures.
159
158
  *
160
- * Emitted by `DirectionalLight.validate()` shadow-field path (mapSize / cascadeCount /
161
- * splitLambda / cascadeBlend / shadowDistance) and `PointLightShadow.validate()`
162
- * (mapSize / farPlane > nearPlane / pcfKernelSize); both shadow component
163
- * types share this single error class so `switch (err.code)` on
164
- * `RuntimeErrorCode` only needs one branch (charter P4 — closed-union SSOT).
165
- * Four-field surface per AGENTS.md error model:
159
+ * Emitted by Directional, Point, and Spot shadow validation. All three
160
+ * component owners share this error class so `switch (err.code)` on
161
+ * `RuntimeErrorCode` needs one branch (charter P4 closed-union SSOT).
162
+ * Four-field surface:
166
163
  * - `.code = 'shadow-invalid-config'` (closed RuntimeErrorCode)
167
164
  * - `.expected` — expected-state description (programmatic predicate form)
168
165
  * - `.hint` — actionable recovery guidance (imperative; AI users paste into
169
- * spawn calls). Integer-typed fields (e.g. `cascadeCount`, `pcfKernelSize`)
170
- * get an "integer in [min, max]" hint; otherwise "[min, max]" range or
171
- * "<comparator> min".
172
- * - `.detail = { field, value, min, max? }` — structured values (charter P4)
173
- *
174
- * 4th constructor parameter is a union over the two range shapes:
175
- * - `number` — `max` for a closed `[min, max]` range (CSM cascadeCount /
176
- * splitLambda / cascadeBlend). hint uses "in [min, max]"; integer-typed
177
- * fields get "integer in [min, max]".
178
- * - `'>' | '>='` — comparator for an open lower-bound predicate. `'>'` is
179
- * used when `min` carries dynamic context (e.g. `farPlane > nearPlane` —
180
- * the surfaced hint reads "must be > nearPlane" so AI users setting
181
- * farPlane=nearPlane don't re-fail the spawn loop with the wrong cue).
182
- * - `undefined` — defaults to `'>='`. Callsites for mapSize < 1 validation
183
- * stay backward-compatible (3-arg form).
166
+ * spawn calls).
167
+ * - `.detail = { field, actual, bound, reason }` structured values.
184
168
  */
185
169
  export class ShadowInvalidConfigError extends Error {
186
170
  readonly code = 'shadow-invalid-config' as const;
@@ -188,35 +172,53 @@ export class ShadowInvalidConfigError extends Error {
188
172
  readonly hint: string;
189
173
  readonly detail: ShadowInvalidConfigDetail;
190
174
 
191
- constructor(field: string, value: number, min: number, maxOrComparator?: number | '>' | '>=') {
192
- const isComparator = maxOrComparator === '>' || maxOrComparator === '>=';
193
- const max = !isComparator && typeof maxOrComparator === 'number' ? maxOrComparator : undefined;
194
- const comparator: '>' | '>=' = isComparator ? (maxOrComparator as '>' | '>=') : '>=';
195
- // Integer-typed fields get an "integer in [min, max]" hint when a range
196
- // is supplied. Expand this set as new integer-typed shadow fields land.
197
- const isInteger = field === 'cascadeCount' || field === 'pcfKernelSize';
198
- // pcfKernelSize must be odd (kernel is symmetric around the center tap).
199
- // Naming "odd" in the hint stops an AI retry from looping min->min+1 (4->6).
200
- const isOdd = field === 'pcfKernelSize';
201
- const hint =
202
- max !== undefined
203
- ? isInteger
204
- ? `set ${field} to an integer in [${min}, ${max}]; got ${value}`
205
- : `set ${field} to a value in [${min}, ${max}]; got ${value}`
206
- : isOdd
207
- ? `set ${field} to an odd integer ${comparator} ${min}; got ${value}`
208
- : `set ${field} to a value ${comparator} ${min}; got ${value}`;
209
- const expected =
210
- max !== undefined ? `${field} in [${min}, ${max}]` : `${field} ${comparator} ${min}`;
211
- super(
212
- max !== undefined
213
- ? `shadow component .${field} must be in [${min}, ${max}], got ${value}`
214
- : `shadow component .${field} must be ${comparator} ${min}, got ${value}`,
215
- );
175
+ constructor(
176
+ field: string,
177
+ actual: number,
178
+ minOrBound: number | ShadowInvalidConfigBound,
179
+ maxOrComparator?: number | '>' | '>=',
180
+ reason?: string,
181
+ ) {
182
+ const bound: ShadowInvalidConfigBound =
183
+ typeof minOrBound === 'number'
184
+ ? typeof maxOrComparator === 'number'
185
+ ? { kind: 'range', min: minOrBound, max: maxOrComparator }
186
+ : {
187
+ kind: 'lower-bound',
188
+ operator: maxOrComparator === '>' ? '>' : '>=',
189
+ value: minOrBound,
190
+ }
191
+ : minOrBound;
192
+ const resolvedReason = reason ?? shadowBoundReason(bound);
193
+ const expected = shadowBoundExpected(field, bound);
194
+ const hint = `set ${field} to ${resolvedReason}; got ${actual}`;
195
+ super(`shadow component .${field} is invalid: ${resolvedReason}; got ${actual}`);
216
196
  this.name = 'ShadowInvalidConfigError';
217
197
  this.hint = hint;
218
198
  this.expected = expected;
219
- this.detail = { field, value, min, ...(max !== undefined ? { max } : {}) };
199
+ this.detail = { field, actual, bound, reason: resolvedReason };
200
+ }
201
+ }
202
+
203
+ function shadowBoundExpected(field: string, bound: ShadowInvalidConfigBound): string {
204
+ switch (bound.kind) {
205
+ case 'range':
206
+ return `${field} in [${bound.min}, ${bound.max}]`;
207
+ case 'lower-bound':
208
+ return `${field} ${bound.operator} ${bound.value}`;
209
+ case 'allowed-values':
210
+ return `${field} in {${bound.values.join(', ')}}`;
211
+ }
212
+ }
213
+
214
+ function shadowBoundReason(bound: ShadowInvalidConfigBound): string {
215
+ switch (bound.kind) {
216
+ case 'range':
217
+ return `a value in [${bound.min}, ${bound.max}]`;
218
+ case 'lower-bound':
219
+ return `a value ${bound.operator} ${bound.value}`;
220
+ case 'allowed-values':
221
+ return `one of [${bound.values.join(', ')}]`;
220
222
  }
221
223
  }
222
224
 
@@ -270,133 +272,125 @@ export class EquirectProjectionFailedError extends Error {
270
272
  }
271
273
  }
272
274
 
273
- // ── HdrpLightBudgetExceededError ───────────────────────────────────────────
275
+ // ── StandardLightBudgetExceededError ──────────────────────────────────────
274
276
 
275
- /**
276
- * Detail for `RuntimeErrorCode 'hdrp-light-budget-exceeded'`.
277
- *
278
- * Emitted per-frame (once-per-frame fire) when light count exceeds HDRP budget.
279
- */
280
- export interface HdrpLightBudgetExceededDetail {
277
+ /** The finite local-light budget that a Standard frame agreed to admit. */
278
+ export interface StandardLightBudgetExceededDetail {
281
279
  readonly actual: number;
282
280
  readonly budget: number;
283
281
  }
284
282
 
285
283
  /**
286
- * Structured error for HDRP light budget exceeded (AC-06/AC-07).
287
- *
288
- * Emitted per-frame in recordFrame when the total punctual light count
289
- * exceeds the HDRP budget (256). Fail-soft: fire once per frame, truncate to 256.
290
- * - `.code = 'hdrp-light-budget-exceeded'` (closed RuntimeErrorCode)
291
- * - `.expected` — 'light count <= 256'
292
- * - `.hint` — 'reduce world light count or increase budget (OOS-hdrp-larger-budget)'
293
- * - `.detail = { actual, budget }`
284
+ * Structured refusal for a Standard frame whose finite local-light set is
285
+ * outside the selected profile budget. The frame is rejected as a whole; the
286
+ * record owner must not slice the input or submit a partial membership set.
294
287
  */
295
- export class HdrpLightBudgetExceededError extends Error {
296
- readonly code = 'hdrp-light-budget-exceeded' as const;
288
+ export class StandardLightBudgetExceededError extends Error {
289
+ readonly code = 'standard-light-budget-exceeded' as const;
297
290
  readonly expected: string;
298
291
  readonly hint: string;
299
- readonly detail: HdrpLightBudgetExceededDetail;
292
+ readonly detail: StandardLightBudgetExceededDetail;
300
293
 
301
294
  constructor(actual: number, budget: number) {
302
295
  const expected = `light count <= ${budget}`;
303
296
  const hint =
304
- `${actual} lights exceed HDRP budget ${budget}; ` +
305
- 'truncating to 256. Reduce light count or wait for OOS-hdrp-larger-budget';
306
- super(`HDRP light budget exceeded: ${actual} > ${budget}`);
307
- this.name = 'HdrpLightBudgetExceededError';
297
+ `${actual} finite local lights exceed Standard budget ${budget}; ` +
298
+ 'reduce the local-light set or select a profile with a supported budget';
299
+ super(`Standard light budget exceeded: ${actual} > ${budget}`);
300
+ this.name = 'StandardLightBudgetExceededError';
308
301
  this.expected = expected;
309
302
  this.hint = hint;
310
303
  this.detail = { actual, budget };
311
304
  }
312
305
  }
313
306
 
314
- // ── HdrpIndexListOverflowError ─────────────────────────────────────────────
307
+ // ── StandardClusterIndexOverflowError ──────────────────────────────────────
315
308
 
316
- /**
317
- * Detail for `RuntimeErrorCode 'hdrp-index-list-overflow'`.
318
- *
319
- * Emitted per-frame (once-per-frame fire) when the cluster binner overflows
320
- * the light index list capacity (65536). Upgraded from ClusterBinError.
321
- */
322
- export interface HdrpIndexListOverflowDetail {
309
+ /** Cluster membership entries that could not fit the single shared index list. */
310
+ export interface StandardClusterIndexOverflowDetail {
323
311
  readonly actual: number;
324
312
  readonly capacity: number;
325
313
  }
326
314
 
327
315
  /**
328
- * Structured error for HDRP cluster binner index list overflow (AC-24).
329
- *
330
- * Emitted per-frame in recordFrame when the CPU binner returns
331
- * `ClusterBinError('index-overflow')`. Fail-soft: fire once per frame,
332
- * continue rendering with what fit.
333
- * - `.code = 'hdrp-index-list-overflow'` (closed RuntimeErrorCode)
334
- * - `.expected` — 'light index list entries <= 65536'
335
- * - `.hint` — 'reduce lights, shrink cluster grid, or increase capacity'
336
- * - `.detail = { actual, capacity }`
316
+ * Structured refusal for a membership producer that exceeded the shared
317
+ * Cluster index-list capacity. No truncated list is uploaded and no graph is
318
+ * submitted for the rejected frame.
337
319
  */
338
- export class HdrpIndexListOverflowError extends Error {
339
- readonly code = 'hdrp-index-list-overflow' as const;
320
+ export class StandardClusterIndexOverflowError extends Error {
321
+ readonly code = 'standard-cluster-index-overflow' as const;
340
322
  readonly expected: string;
341
323
  readonly hint: string;
342
- readonly detail: HdrpIndexListOverflowDetail;
324
+ readonly detail: StandardClusterIndexOverflowDetail;
343
325
 
344
326
  constructor(actual: number, capacity: number) {
345
327
  const expected = `light index list entries <= ${capacity}`;
346
328
  const hint =
347
329
  `cluster binner overflow: writeCount ${actual} exceeds capacity ${capacity}; ` +
348
- 'reduce lights, shrink cluster grid, or increase LIGHT_INDEX_LIST_CAPACITY';
349
- super(`HDRP index list overflow: ${actual} > ${capacity}`);
350
- this.name = 'HdrpIndexListOverflowError';
330
+ 'reduce finite local lights or choose a supported cluster layout';
331
+ super(`Standard cluster index list overflow: ${actual} > ${capacity}`);
332
+ this.name = 'StandardClusterIndexOverflowError';
351
333
  this.expected = expected;
352
334
  this.hint = hint;
353
335
  this.detail = { actual, capacity };
354
336
  }
355
337
  }
356
338
 
357
- // ── HdrpDeferredCapsInsufficientError ──────────────────────────────────────
339
+ // ── StandardClusterTransportUnavailableError ───────────────────────────────
358
340
 
359
- /**
360
- * Detail for `RuntimeErrorCode 'hdrp-deferred-caps-insufficient'`.
361
- *
362
- * Emitted when the Standard clustered lane has fewer than 4 color attachments,
363
- * meaning the deferred path cannot allocate 3 g-buffer color targets + depth.
364
- */
365
- export interface HdrpDeferredCapsInsufficientDetail {
366
- readonly actual: number;
367
- readonly expected: number;
341
+ export interface StandardClusterTransportUnavailableDetail {
342
+ readonly requested: number;
343
+ readonly admitted: 0;
368
344
  }
369
345
 
370
346
  /**
371
- * Structured error for deferred-path capability gate failure.
372
- *
373
- * Emitted during Standard clustered lane validation synchronous throw.
374
- * - `.code = 'hdrp-deferred-caps-insufficient'` (closed RuntimeErrorCode)
375
- * - `.expected` — 'maxColorAttachments >= 4'
376
- * - `.hint` — actionable guidance to upgrade browser or select URP
377
- * - `.detail = { actual, expected }`
378
- *
379
- * @see plan-strategy D-5 (capability validation, no silent fallback)
347
+ * Structured capability refusal. Standard never turns an unproven transport
348
+ * into a smaller direct-light lane; the caller receives the exact requested
349
+ * local-light count and an admitted count of zero.
380
350
  */
381
- export class HdrpDeferredCapsInsufficientError extends Error {
382
- readonly code = 'hdrp-deferred-caps-insufficient' as const;
383
- readonly expected: string;
351
+ export class StandardClusterTransportUnavailableError extends Error {
352
+ readonly code = 'standard-cluster-transport-unavailable' as const;
353
+ readonly expected = 'all requested local lights have a proven Cluster transport';
384
354
  readonly hint: string;
385
- readonly detail: HdrpDeferredCapsInsufficientDetail;
355
+ readonly detail: StandardClusterTransportUnavailableDetail;
386
356
 
387
- constructor(actual: number) {
388
- const _expected = 4;
389
- const _expectedStr = `maxColorAttachments >= ${_expected}`;
390
- const hint =
391
- `WebGPU maxColorAttachments = ${actual} (need >= ${_expected}). ` +
392
- 'Upgrade browser to latest Chrome/Edge/Safari, or use URP (forgeax::urp) instead of HDRP.';
393
- super(
394
- `HDRP deferred caps insufficient: maxColorAttachments = ${actual} (need >= ${_expected})`,
395
- );
396
- this.name = 'HdrpDeferredCapsInsufficientError';
397
- this.expected = _expectedStr;
357
+ constructor(
358
+ requested: number,
359
+ hint = 'enable a proven Cluster storage transport before drawing',
360
+ ) {
361
+ super(`Standard Cluster transport unavailable for ${requested} local lights`);
362
+ this.name = 'StandardClusterTransportUnavailableError';
398
363
  this.hint = hint;
399
- this.detail = { actual, expected: _expected };
364
+ this.detail = { requested, admitted: 0 };
365
+ }
366
+ }
367
+
368
+ // ── StandardProfileInvalidError ───────────────────────────────────────────
369
+
370
+ export interface StandardProfileInvalidDetail {
371
+ readonly field: string;
372
+ readonly actual: unknown;
373
+ }
374
+
375
+ /** Structured validation failure for the single Standard profile surface. */
376
+ export class StandardProfileInvalidError extends Error {
377
+ readonly code = 'standard-profile-invalid' as const;
378
+ readonly expected: string;
379
+ readonly hint: string;
380
+ readonly detail: StandardProfileInvalidDetail;
381
+
382
+ constructor(message: string, detail?: StandardProfileInvalidDetail) {
383
+ super(message);
384
+ this.name = 'StandardProfileInvalidError';
385
+ this.detail = detail ?? { field: 'profile', actual: message };
386
+ if (this.detail.field === 'clusterGrid') {
387
+ this.expected = 'clusterGrid.{x,y,z} each positive integer in [1, 64]';
388
+ this.hint =
389
+ 'set clusterGrid.x, clusterGrid.y, and clusterGrid.z to positive integers in [1, 64]';
390
+ } else {
391
+ this.expected = 'StandardProfile contains only supported fields and values';
392
+ this.hint = 'repair the Standard profile fields and retry setProfile';
393
+ }
400
394
  }
401
395
  }
402
396
 
@@ -452,7 +446,7 @@ export interface PointShadowAtlasBoundsViolationDetail {
452
446
  * - `.code = 'point-shadow-atlas-bounds-violation'`
453
447
  * - `.expected` — `0 <= layer < layers && 0 <= face < 6`
454
448
  * - `.hint` — clamp `shadowAtlasLayer` or face index before calling
455
- * `faceView`; the cap on layers is `PointLightShadow` cardinality (4)
449
+ * `faceView`; the layer cap is the renderer-owned ShadowAtlas capacity
456
450
  * - `.detail = { axis, value, max }` — discriminates layer vs face
457
451
  */
458
452
  export class PointShadowAtlasBoundsViolationError extends Error {
@@ -1531,6 +1525,149 @@ export class PointsLinesPrepareFailedError extends Error {
1531
1525
  }
1532
1526
  }
1533
1527
 
1528
+ // -- RenderTarget errors ------------------------------------------------------
1529
+
1530
+ export interface RenderTargetDescriptorInvalidDetail {
1531
+ readonly field: string;
1532
+ readonly value: unknown;
1533
+ readonly expected: string;
1534
+ }
1535
+
1536
+ export class RenderTargetDescriptorInvalidError extends Error {
1537
+ readonly code = 'render-target-descriptor-invalid' as const;
1538
+ readonly expected: string;
1539
+ readonly hint: string;
1540
+ readonly detail: RenderTargetDescriptorInvalidDetail;
1541
+
1542
+ constructor(detail: RenderTargetDescriptorInvalidDetail) {
1543
+ super(`render-target-descriptor-invalid: ${detail.field} does not satisfy ${detail.expected}`);
1544
+ this.name = 'RenderTargetDescriptorInvalidError';
1545
+ this.expected = detail.expected;
1546
+ this.hint = `set RenderTargetDescriptor.${detail.field} to the expected value, then retry create or resize`;
1547
+ this.detail = detail;
1548
+ }
1549
+ }
1550
+
1551
+ export interface RenderTargetCapabilityMissingDetail {
1552
+ readonly operation: 'create' | 'resize' | 'source' | 'readback';
1553
+ readonly requested: string;
1554
+ readonly capability: string;
1555
+ readonly actual: string;
1556
+ }
1557
+
1558
+ export class RenderTargetCapabilityMissingError extends Error {
1559
+ readonly code = 'render-target-capability-missing' as const;
1560
+ readonly expected: string;
1561
+ readonly hint: string;
1562
+ readonly detail: RenderTargetCapabilityMissingDetail;
1563
+
1564
+ constructor(detail: RenderTargetCapabilityMissingDetail) {
1565
+ super(
1566
+ `render-target-capability-missing: ${detail.capability} does not admit ${detail.requested}`,
1567
+ );
1568
+ this.name = 'RenderTargetCapabilityMissingError';
1569
+ this.expected = `${detail.capability} admits ${detail.requested}`;
1570
+ this.hint = `choose an admitted target descriptor or use a backend providing ${detail.capability}`;
1571
+ this.detail = detail;
1572
+ }
1573
+ }
1574
+
1575
+ export type RenderTargetStateInvalidReason =
1576
+ | 'foreign-renderer'
1577
+ | 'destroyed'
1578
+ | 'uninitialized'
1579
+ | 'generation-mismatch'
1580
+ | 'readback-without-intent';
1581
+
1582
+ export interface RenderTargetStateInvalidDetail {
1583
+ readonly operation: 'inspect' | 'resize' | 'source' | 'readback' | 'destroy';
1584
+ readonly reason: RenderTargetStateInvalidReason;
1585
+ readonly state: 'uninitialized' | 'candidate' | 'active' | 'rebuilding' | 'destroyed';
1586
+ readonly generation: number;
1587
+ }
1588
+
1589
+ export class RenderTargetStateInvalidError extends Error {
1590
+ readonly code = 'render-target-state-invalid' as const;
1591
+ readonly expected: string;
1592
+ readonly hint: string;
1593
+ readonly detail: RenderTargetStateInvalidDetail;
1594
+
1595
+ constructor(detail: RenderTargetStateInvalidDetail) {
1596
+ super(`render-target-state-invalid: ${detail.operation} cannot use ${detail.reason}`);
1597
+ this.name = 'RenderTargetStateInvalidError';
1598
+ this.expected = `target state permits ${detail.operation}`;
1599
+ this.hint =
1600
+ 'use the Renderer that created the target, then initialize, recover, or stop using the destroyed token';
1601
+ this.detail = detail;
1602
+ }
1603
+ }
1604
+
1605
+ export interface RenderTargetOperationFailedDetail {
1606
+ readonly operation: 'create' | 'resize' | 'source' | 'readback' | 'destroy' | 'recover';
1607
+ readonly stage: 'allocation' | 'compile' | 'write' | 'submit' | 'copy' | 'retire';
1608
+ readonly generation: number;
1609
+ readonly cause: unknown;
1610
+ readonly recovery: 'retry' | 'recover' | 'retain-last-known-good';
1611
+ }
1612
+
1613
+ export class RenderTargetOperationFailedError extends Error {
1614
+ readonly code = 'render-target-operation-failed' as const;
1615
+ readonly expected: string;
1616
+ readonly hint: string;
1617
+ readonly detail: RenderTargetOperationFailedDetail;
1618
+
1619
+ constructor(detail: RenderTargetOperationFailedDetail) {
1620
+ super(`render-target-operation-failed: ${detail.operation} failed during ${detail.stage}`);
1621
+ this.name = 'RenderTargetOperationFailedError';
1622
+ this.expected = `${detail.operation} completes ${detail.stage}`;
1623
+ this.hint =
1624
+ 'inspect detail.cause, then retry, recover the Renderer, or retain the last-known-good generation';
1625
+ this.detail = detail;
1626
+ }
1627
+ }
1628
+
1629
+ export interface ReflectionProbeBudgetExceededDetail {
1630
+ readonly actual: number;
1631
+ readonly budget: number;
1632
+ }
1633
+
1634
+ export class ReflectionProbeBudgetExceededError extends Error {
1635
+ readonly code = 'reflection-probe-budget-exceeded' as const;
1636
+ readonly expected: string;
1637
+ readonly hint: string;
1638
+ readonly detail: ReflectionProbeBudgetExceededDetail;
1639
+
1640
+ constructor(actual: number, budget: number) {
1641
+ super(`reflection-probe-budget-exceeded: ${actual} > ${budget}`);
1642
+ this.name = 'ReflectionProbeBudgetExceededError';
1643
+ this.expected = `reflection probe work <= ${budget}`;
1644
+ this.hint = 'reduce probe updates for this frame or raise the RenderProfile probe budget';
1645
+ this.detail = { actual, budget };
1646
+ }
1647
+ }
1648
+
1649
+ export interface RenderIntentInvalidDetail {
1650
+ readonly component: 'CubeCamera' | 'ReflectionProbe';
1651
+ readonly field: 'updateIntent';
1652
+ readonly value: number;
1653
+ readonly allowed: readonly [0, 1, 2];
1654
+ }
1655
+
1656
+ export class RenderIntentInvalidError extends Error {
1657
+ readonly code = 'render-intent-invalid' as const;
1658
+ readonly expected: string;
1659
+ readonly hint: string;
1660
+ readonly detail: RenderIntentInvalidDetail;
1661
+
1662
+ constructor(component: RenderIntentInvalidDetail['component'], value: number) {
1663
+ super(`render-intent-invalid: ${component}.updateIntent is not an admitted value`);
1664
+ this.name = 'RenderIntentInvalidError';
1665
+ this.expected = `${component}.updateIntent is one of 0, 1, or 2`;
1666
+ this.hint = `set ${component}.updateIntent to 0, 1, or 2`;
1667
+ this.detail = { component, field: 'updateIntent', value, allowed: [0, 1, 2] };
1668
+ }
1669
+ }
1670
+
1534
1671
  export interface VolumeOwnerConflictDetail {
1535
1672
  readonly ownerCount: number;
1536
1673
  }
@@ -1652,9 +1789,10 @@ export type RenderError =
1652
1789
  | TaaUnavailableError
1653
1790
  | ShadowInvalidConfigError
1654
1791
  | EquirectProjectionFailedError
1655
- | HdrpLightBudgetExceededError
1656
- | HdrpIndexListOverflowError
1657
- | HdrpDeferredCapsInsufficientError
1792
+ | StandardLightBudgetExceededError
1793
+ | StandardClusterIndexOverflowError
1794
+ | StandardClusterTransportUnavailableError
1795
+ | StandardProfileInvalidError
1658
1796
  | PointShadowAtlasUninitializedError
1659
1797
  | PointShadowAtlasBoundsViolationError
1660
1798
  | VideoUploadUnsupportedError
@@ -1683,5 +1821,11 @@ export type RenderError =
1683
1821
  | PointsLinesMaterialUnsupportedError
1684
1822
  | PointsLinesBudgetExceededError
1685
1823
  | PointsLinesPrepareFailedError
1824
+ | RenderTargetDescriptorInvalidError
1825
+ | RenderTargetCapabilityMissingError
1826
+ | RenderTargetStateInvalidError
1827
+ | RenderTargetOperationFailedError
1828
+ | ReflectionProbeBudgetExceededError
1829
+ | RenderIntentInvalidError
1686
1830
  | ProjectorBindingError
1687
1831
  | VolumeError;
@@ -0,0 +1,61 @@
1
+ // @forgeax/engine-render - Directional CSM projection owner.
2
+
3
+ import type { DirectionalShadowQuality } from '../components/directional-shadow-filter';
4
+ import { directionalShadowQualityFromF32 } from '../components/directional-shadow-filter';
5
+
6
+ /** Light-space bounds produced by one existing orthographic CSM fit. */
7
+ export interface DirectionalShadowCascadeFit {
8
+ readonly split: number;
9
+ readonly minX: number;
10
+ readonly maxX: number;
11
+ readonly minY: number;
12
+ readonly maxY: number;
13
+ readonly minZ: number;
14
+ readonly maxZ: number;
15
+ }
16
+
17
+ /** Host projection sent to the shared View UBO. */
18
+ export interface DirectionalShadowProjection {
19
+ readonly splitPlanes: Float32Array;
20
+ readonly directionalShadowQuality: DirectionalShadowQuality;
21
+ }
22
+
23
+ /**
24
+ * Project accepted Directional author facts and the existing CSM fit into the
25
+ * shared split vec4 lanes. `.x` is the split, `.y` is world units per texel,
26
+ * `.z` is the light-space depth span, and `.w` is reserved for the ABI.
27
+ */
28
+ export function projectDirectionalShadow(
29
+ fits: readonly DirectionalShadowCascadeFit[],
30
+ mapSize: number,
31
+ shadowFilter: number,
32
+ shadowAngularRadius: number,
33
+ maxPenumbraTexels: number,
34
+ ): DirectionalShadowProjection | undefined {
35
+ if (!Number.isFinite(mapSize) || mapSize < 1) return undefined;
36
+ const directionalShadowQuality = directionalShadowQualityFromF32(
37
+ shadowFilter,
38
+ shadowAngularRadius,
39
+ maxPenumbraTexels,
40
+ );
41
+ if (directionalShadowQuality === undefined) return undefined;
42
+
43
+ const splitPlanes = new Float32Array(16);
44
+ for (let index = 0; index < fits.length && index < 4; index += 1) {
45
+ const fit = fits[index];
46
+ if (fit === undefined) continue;
47
+ const width = Math.abs(fit.maxX - fit.minX);
48
+ const height = Math.abs(fit.maxY - fit.minY);
49
+ const depth = Math.abs(fit.maxZ - fit.minZ);
50
+ const worldUnitsPerTexel = Math.max(width, height, Number.EPSILON) / mapSize;
51
+ const lightDepthWorldSpan = Math.max(depth, Number.EPSILON);
52
+ if (!Number.isFinite(worldUnitsPerTexel) || !Number.isFinite(lightDepthWorldSpan)) {
53
+ return undefined;
54
+ }
55
+ const lane = index * 4;
56
+ splitPlanes[lane] = fit.split;
57
+ splitPlanes[lane + 1] = worldUnitsPerTexel;
58
+ splitPlanes[lane + 2] = lightDepthWorldSpan;
59
+ }
60
+ return { splitPlanes, directionalShadowQuality };
61
+ }
@@ -76,6 +76,20 @@ export type EnvironmentSource =
76
76
 
77
77
  export interface FogFrame extends FogCandidate {}
78
78
 
79
+ export interface FogParameterIssue {
80
+ readonly field: string;
81
+ readonly value: number;
82
+ readonly expected: string;
83
+ }
84
+
85
+ /** Structured failure retained when an invalid Fog update falls back to LKG. */
86
+ export interface FogSelectionFailure {
87
+ readonly code: string;
88
+ readonly expected: string;
89
+ readonly hint: string;
90
+ readonly detail: Readonly<Record<string, unknown>>;
91
+ }
92
+
79
93
  export interface EnvironmentFrame {
80
94
  readonly source: EnvironmentSource;
81
95
  /** Descriptor of the real environment resource adopted for this frame. */
@@ -103,6 +117,49 @@ function freezeFog(candidate: FogCandidate): FogFrame {
103
117
  });
104
118
  }
105
119
 
120
+ /** Validate the bounded Fog domain once for every frame-selection producer. */
121
+ export function validateFogParameters(candidate: FogCandidate): FogParameterIssue | undefined {
122
+ if (candidate.color.length !== 3) {
123
+ return {
124
+ field: 'color',
125
+ value: candidate.color.length,
126
+ expected: 'exactly three finite channels in [0, 1]',
127
+ };
128
+ }
129
+ for (let index = 0; index < candidate.color.length; index += 1) {
130
+ const value = candidate.color[index];
131
+ if (value === undefined || !Number.isFinite(value) || value < 0 || value > 1) {
132
+ return {
133
+ field: `color[${index}]`,
134
+ value: value ?? Number.NaN,
135
+ expected: 'a finite number in [0, 1]',
136
+ };
137
+ }
138
+ }
139
+ if (!Number.isFinite(candidate.density) || candidate.density < 0) {
140
+ return { field: 'density', value: candidate.density, expected: 'a finite number >= 0' };
141
+ }
142
+ if (!Number.isFinite(candidate.heightFalloff) || candidate.heightFalloff < 0) {
143
+ return {
144
+ field: 'heightFalloff',
145
+ value: candidate.heightFalloff,
146
+ expected: 'a finite number >= 0',
147
+ };
148
+ }
149
+ if (
150
+ !Number.isFinite(candidate.maxOpacity) ||
151
+ candidate.maxOpacity < 0 ||
152
+ candidate.maxOpacity > 1
153
+ ) {
154
+ return {
155
+ field: 'maxOpacity',
156
+ value: candidate.maxOpacity,
157
+ expected: 'a finite number in [0, 1]',
158
+ };
159
+ }
160
+ return undefined;
161
+ }
162
+
106
163
  function freezeAtmosphere(parameters: AtmosphereParameters): AtmosphereParameters {
107
164
  return Object.freeze({ ...parameters });
108
165
  }