@forgeax/engine-render 0.1.32 → 0.1.34

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 (624) hide show
  1. package/README.md +1374 -142
  2. package/dist/assembly/analytic-fog-registration.d.ts +3 -0
  3. package/dist/assembly/analytic-fog-registration.d.ts.map +1 -0
  4. package/dist/assembly/backend-contract.d.ts +2 -0
  5. package/dist/assembly/backend-contract.d.ts.map +1 -1
  6. package/dist/assembly/barrel-distortion-inspection.d.ts +3 -0
  7. package/dist/assembly/barrel-distortion-inspection.d.ts.map +1 -0
  8. package/dist/assembly/canvas-draw-guard.d.ts +3 -0
  9. package/dist/assembly/canvas-draw-guard.d.ts.map +1 -0
  10. package/dist/assembly/cloud-shadow-entry.d.ts +4 -0
  11. package/dist/assembly/cloud-shadow-entry.d.ts.map +1 -0
  12. package/dist/assembly/dynamic-geometry-host.d.ts +4 -1
  13. package/dist/assembly/dynamic-geometry-host.d.ts.map +1 -1
  14. package/dist/assembly/dynamic-geometry-runtime.d.ts +1 -1
  15. package/dist/assembly/dynamic-geometry-runtime.d.ts.map +1 -1
  16. package/dist/assembly/factory.d.ts +1 -1
  17. package/dist/assembly/factory.d.ts.map +1 -1
  18. package/dist/assembly/fxaa-registration.d.ts +4 -0
  19. package/dist/assembly/fxaa-registration.d.ts.map +1 -0
  20. package/dist/assembly/host-contract.d.ts +3 -3
  21. package/dist/assembly/host-contract.d.ts.map +1 -1
  22. package/dist/assembly/lod-observation.d.ts +4 -0
  23. package/dist/assembly/lod-observation.d.ts.map +1 -0
  24. package/dist/assembly/material/assembly.d.ts.map +1 -1
  25. package/dist/assembly/material/pipeline-helpers.d.ts +5 -0
  26. package/dist/assembly/material/pipeline-helpers.d.ts.map +1 -1
  27. package/dist/assembly/material-shader-policy.d.ts +16 -2
  28. package/dist/assembly/material-shader-policy.d.ts.map +1 -1
  29. package/dist/assembly/material-shader-prewarm.d.ts.map +1 -1
  30. package/dist/assembly/motion-blur-registration.d.ts +3 -0
  31. package/dist/assembly/motion-blur-registration.d.ts.map +1 -0
  32. package/dist/assembly/recovery/device-loss-fanout.d.ts.map +1 -1
  33. package/dist/assembly/recovery/renderer-recover.d.ts +2 -0
  34. package/dist/assembly/recovery/renderer-recover.d.ts.map +1 -1
  35. package/dist/assembly/render-feature-post-processes.d.ts +6 -0
  36. package/dist/assembly/render-feature-post-processes.d.ts.map +1 -0
  37. package/dist/assembly/renderer-facade.d.ts.map +1 -1
  38. package/dist/assembly/renderer-feature-inspection.d.ts +1 -1
  39. package/dist/assembly/renderer-feature-inspection.d.ts.map +1 -1
  40. package/dist/assembly/renderer-frame-transaction.d.ts +12 -0
  41. package/dist/assembly/renderer-frame-transaction.d.ts.map +1 -1
  42. package/dist/assembly/renderer-inspection.d.ts +11 -0
  43. package/dist/assembly/renderer-inspection.d.ts.map +1 -1
  44. package/dist/assembly/temporal-post-process-prewarm.d.ts +13 -0
  45. package/dist/assembly/temporal-post-process-prewarm.d.ts.map +1 -0
  46. package/dist/assembly/webgpu-pbr-ready.d.ts +1 -0
  47. package/dist/assembly/webgpu-pbr-ready.d.ts.map +1 -1
  48. package/dist/assembly/webgpu-ready-contract.d.ts +4 -3
  49. package/dist/assembly/webgpu-ready-contract.d.ts.map +1 -1
  50. package/dist/assembly/webgpu-ready-per-pass.d.ts +39 -0
  51. package/dist/assembly/webgpu-ready-per-pass.d.ts.map +1 -0
  52. package/dist/assembly/webgpu-ready.d.ts +2 -2
  53. package/dist/assembly/webgpu-ready.d.ts.map +1 -1
  54. package/dist/assembly/webgpu-renderer-bootstrap.d.ts.map +1 -1
  55. package/dist/assembly/webgpu-renderer-observation-owner.d.ts +35 -0
  56. package/dist/assembly/webgpu-renderer-observation-owner.d.ts.map +1 -0
  57. package/dist/assembly/webgpu-renderer.d.ts +1 -1
  58. package/dist/assembly/webgpu-renderer.d.ts.map +1 -1
  59. package/dist/authoring.mjs +1 -1
  60. package/dist/barrel-distortion.d.ts +47 -0
  61. package/dist/barrel-distortion.d.ts.map +1 -0
  62. package/dist/bloom-admission.d.ts +13 -0
  63. package/dist/bloom-admission.d.ts.map +1 -0
  64. package/dist/{chunk-PA5DRJNI.mjs → chunk-ELJFJ2BU.mjs} +2 -2
  65. package/dist/chunk-ELJFJ2BU.mjs.map +1 -0
  66. package/dist/{chunk-JCIRYQ4V.mjs → chunk-HEB564WF.mjs} +10 -4
  67. package/dist/chunk-HEB564WF.mjs.map +1 -0
  68. package/dist/{chunk-JRIPCMYC.mjs → chunk-MKDIT2EM.mjs} +1584 -375
  69. package/dist/chunk-MKDIT2EM.mjs.map +1 -0
  70. package/dist/{chunk-S665NJNF.mjs → chunk-OCDLEI6I.mjs} +3 -3
  71. package/dist/{chunk-S665NJNF.mjs.map → chunk-OCDLEI6I.mjs.map} +1 -1
  72. package/dist/chunk-ODZWR6JQ.mjs +4927 -0
  73. package/dist/chunk-ODZWR6JQ.mjs.map +1 -0
  74. package/dist/{chunk-DRR7D3KX.mjs → chunk-SHFUFJ5N.mjs} +192 -18
  75. package/dist/chunk-SHFUFJ5N.mjs.map +1 -0
  76. package/dist/{chunk-4NNZQCL3.mjs → chunk-UI5SFODX.mjs} +223 -277
  77. package/dist/chunk-UI5SFODX.mjs.map +1 -0
  78. package/dist/{chunk-GWCEMDVR.mjs → chunk-UINHLCZA.mjs} +787 -80
  79. package/dist/chunk-UINHLCZA.mjs.map +1 -0
  80. package/dist/{chunk-6TFBXLRJ.mjs → chunk-WEX45HKH.mjs} +222 -337
  81. package/dist/chunk-WEX45HKH.mjs.map +1 -0
  82. package/dist/cloud/density.d.ts +55 -0
  83. package/dist/cloud/density.d.ts.map +1 -0
  84. package/dist/cloud/extract.d.ts +25 -0
  85. package/dist/cloud/extract.d.ts.map +1 -0
  86. package/dist/cloud/feature.d.ts +57 -0
  87. package/dist/cloud/feature.d.ts.map +1 -0
  88. package/dist/cloud/frame-context.d.ts +8 -0
  89. package/dist/cloud/frame-context.d.ts.map +1 -0
  90. package/dist/cloud/inspection.d.ts +59 -0
  91. package/dist/cloud/inspection.d.ts.map +1 -0
  92. package/dist/cloud/optics.d.ts +56 -0
  93. package/dist/cloud/optics.d.ts.map +1 -0
  94. package/dist/cloud/parameters.d.ts +73 -0
  95. package/dist/cloud/parameters.d.ts.map +1 -0
  96. package/dist/cloud/resources.d.ts +60 -0
  97. package/dist/cloud/resources.d.ts.map +1 -0
  98. package/dist/cloud/shadow.d.ts +42 -0
  99. package/dist/cloud/shadow.d.ts.map +1 -0
  100. package/dist/cloud/temporal.d.ts +74 -0
  101. package/dist/cloud/temporal.d.ts.map +1 -0
  102. package/dist/components/atmosphere.d.ts +2 -0
  103. package/dist/components/atmosphere.d.ts.map +1 -1
  104. package/dist/components/barrel-distortion.d.ts +13 -0
  105. package/dist/components/barrel-distortion.d.ts.map +1 -0
  106. package/dist/components/camera.d.ts +20 -4
  107. package/dist/components/camera.d.ts.map +1 -1
  108. package/dist/components/cloud-layer.d.ts +29 -0
  109. package/dist/components/cloud-layer.d.ts.map +1 -0
  110. package/dist/components/index.d.ts +2 -0
  111. package/dist/components/index.d.ts.map +1 -1
  112. package/dist/components/instances.d.ts +20 -6
  113. package/dist/components/instances.d.ts.map +1 -1
  114. package/dist/components/mesh-filter.d.ts +6 -5
  115. package/dist/components/mesh-filter.d.ts.map +1 -1
  116. package/dist/components/motion-blur.d.ts +1 -0
  117. package/dist/components/motion-blur.d.ts.map +1 -1
  118. package/dist/construct-renderer.d.ts +1 -1
  119. package/dist/construct-renderer.d.ts.map +1 -1
  120. package/dist/construct-renderer.mjs +9751 -3528
  121. package/dist/construct-renderer.mjs.map +1 -1
  122. package/dist/device/gpu-residency.d.ts +6 -0
  123. package/dist/device/gpu-residency.d.ts.map +1 -1
  124. package/dist/dynamic-geometry.d.ts +5 -1
  125. package/dist/dynamic-geometry.d.ts.map +1 -1
  126. package/dist/environment/analytic-fog.d.ts +6 -0
  127. package/dist/environment/analytic-fog.d.ts.map +1 -0
  128. package/dist/errors/cloud.d.ts +59 -0
  129. package/dist/errors/cloud.d.ts.map +1 -0
  130. package/dist/errors/render.d.ts +23 -5
  131. package/dist/errors/render.d.ts.map +1 -1
  132. package/dist/examples/single-layer-medium-surface.d.ts +47 -0
  133. package/dist/examples/single-layer-medium-surface.d.ts.map +1 -0
  134. package/dist/extract/camera.d.ts.map +1 -1
  135. package/dist/extract/contracts.d.ts +12 -1
  136. package/dist/extract/contracts.d.ts.map +1 -1
  137. package/dist/extract/environment.d.ts +2 -0
  138. package/dist/extract/environment.d.ts.map +1 -1
  139. package/dist/features/barrel-distortion.d.ts +29 -0
  140. package/dist/features/barrel-distortion.d.ts.map +1 -0
  141. package/dist/features/error-reporting.d.ts +17 -0
  142. package/dist/features/error-reporting.d.ts.map +1 -0
  143. package/dist/features/host.d.ts +15 -0
  144. package/dist/features/host.d.ts.map +1 -1
  145. package/dist/features/motion-blur/motion-blur-feature.d.ts +20 -2
  146. package/dist/features/motion-blur/motion-blur-feature.d.ts.map +1 -1
  147. package/dist/features/motion-blur/motion-blur-params.d.ts +21 -0
  148. package/dist/features/motion-blur/motion-blur-params.d.ts.map +1 -1
  149. package/dist/features/motion-blur/motion-blur-runtime.d.ts +54 -0
  150. package/dist/features/motion-blur/motion-blur-runtime.d.ts.map +1 -0
  151. package/dist/features/plan.d.ts +15 -4
  152. package/dist/features/plan.d.ts.map +1 -1
  153. package/dist/features/prepared-gpu-work.d.ts +9 -5
  154. package/dist/features/prepared-gpu-work.d.ts.map +1 -1
  155. package/dist/features/render-graph-compute.d.ts.map +1 -1
  156. package/dist/features/render-graph-contribution.d.ts +5 -0
  157. package/dist/features/render-graph-contribution.d.ts.map +1 -1
  158. package/dist/features/render-graph-raster.d.ts +1 -0
  159. package/dist/features/render-graph-raster.d.ts.map +1 -1
  160. package/dist/features/targets.d.ts +2 -0
  161. package/dist/features/targets.d.ts.map +1 -1
  162. package/dist/features/types.d.ts +44 -0
  163. package/dist/features/types.d.ts.map +1 -1
  164. package/dist/fullscreen-post-process-pass.d.ts +15 -7
  165. package/dist/fullscreen-post-process-pass.d.ts.map +1 -1
  166. package/dist/gpu-dirty-ranges.d.ts +11 -0
  167. package/dist/gpu-dirty-ranges.d.ts.map +1 -0
  168. package/dist/gpu-driven/batch-topology.d.ts +17 -0
  169. package/dist/gpu-driven/batch-topology.d.ts.map +1 -1
  170. package/dist/gpu-driven/material-artifacts.d.ts +6 -0
  171. package/dist/gpu-driven/material-artifacts.d.ts.map +1 -1
  172. package/dist/gpu-driven/prepared-draw.d.ts +7 -0
  173. package/dist/gpu-driven/prepared-draw.d.ts.map +1 -1
  174. package/dist/gpu-driven/production-raster-material.d.ts +178 -0
  175. package/dist/gpu-driven/production-raster-material.d.ts.map +1 -0
  176. package/dist/gpu-driven/production-raster.d.ts +50 -4
  177. package/dist/gpu-driven/production-raster.d.ts.map +1 -1
  178. package/dist/gpu-driven/resource-allocation.d.ts +31 -0
  179. package/dist/gpu-driven/resource-allocation.d.ts.map +1 -0
  180. package/dist/gpu-driven/shadow-ownership.d.ts +14 -0
  181. package/dist/gpu-driven/shadow-ownership.d.ts.map +1 -0
  182. package/dist/gpu-driven/view-gpu.d.ts +35 -2
  183. package/dist/gpu-driven/view-gpu.d.ts.map +1 -1
  184. package/dist/gpu-scene.d.ts +11 -1
  185. package/dist/gpu-scene.d.ts.map +1 -1
  186. package/dist/hdrp-buffers.d.ts +2 -2
  187. package/dist/hdrp-buffers.d.ts.map +1 -1
  188. package/dist/ibl/skylight-bind-group.d.ts +10 -1
  189. package/dist/ibl/skylight-bind-group.d.ts.map +1 -1
  190. package/dist/index.d.ts +31 -8
  191. package/dist/index.d.ts.map +1 -1
  192. package/dist/index.mjs +258 -8
  193. package/dist/index.mjs.map +1 -1
  194. package/dist/inspection-types.d.ts +178 -3
  195. package/dist/inspection-types.d.ts.map +1 -1
  196. package/dist/instance-buffer-cache.d.ts +4 -4
  197. package/dist/instance-buffer-cache.d.ts.map +1 -1
  198. package/dist/instances.d.ts +49 -94
  199. package/dist/instances.d.ts.map +1 -1
  200. package/dist/internal.mjs +7 -7
  201. package/dist/pbr-pipeline.d.ts +19 -1
  202. package/dist/pbr-pipeline.d.ts.map +1 -1
  203. package/dist/pipeline/single-layer-medium-passes.d.ts +73 -0
  204. package/dist/pipeline/single-layer-medium-passes.d.ts.map +1 -0
  205. package/dist/pipeline/standard-forward-lane.d.ts.map +1 -1
  206. package/dist/pipeline/standard-output/graph.d.ts.map +1 -1
  207. package/dist/pipeline/standard-output/types.d.ts +4 -2
  208. package/dist/pipeline/standard-output/types.d.ts.map +1 -1
  209. package/dist/pipeline/standard-pipeline.d.ts.map +1 -1
  210. package/dist/pipeline/standard-post.d.ts +28 -19
  211. package/dist/pipeline/standard-post.d.ts.map +1 -1
  212. package/dist/pipeline-builder.d.ts +1 -1
  213. package/dist/pipeline-builder.d.ts.map +1 -1
  214. package/dist/pipeline-spec.d.ts +2 -2
  215. package/dist/plugin.d.ts.map +1 -1
  216. package/dist/prepare/prepared-graphics-resolver.d.ts +2 -0
  217. package/dist/prepare/prepared-graphics-resolver.d.ts.map +1 -1
  218. package/dist/record/barrel-distortion-frame.d.ts +15 -0
  219. package/dist/record/barrel-distortion-frame.d.ts.map +1 -0
  220. package/dist/record/dynamic-geometry-consumption.d.ts +10 -2
  221. package/dist/record/dynamic-geometry-consumption.d.ts.map +1 -1
  222. package/dist/record/fold-buckets.d.ts +95 -0
  223. package/dist/record/fold-buckets.d.ts.map +1 -0
  224. package/dist/record/fold-instance-buffer.d.ts +6 -0
  225. package/dist/record/fold-instance-buffer.d.ts.map +1 -0
  226. package/dist/record/frame-lighting.d.ts +1 -0
  227. package/dist/record/frame-lighting.d.ts.map +1 -1
  228. package/dist/record/frame-snapshot.d.ts +40 -3
  229. package/dist/record/frame-snapshot.d.ts.map +1 -1
  230. package/dist/record/frame-targets.d.ts.map +1 -1
  231. package/dist/record/frame.d.ts +14 -62
  232. package/dist/record/frame.d.ts.map +1 -1
  233. package/dist/record/gpu-draw-receipts.d.ts +11 -0
  234. package/dist/record/gpu-draw-receipts.d.ts.map +1 -0
  235. package/dist/record/gpu-timing.d.ts +8 -0
  236. package/dist/record/gpu-timing.d.ts.map +1 -1
  237. package/dist/record/main-pass-geometry.d.ts +21 -5
  238. package/dist/record/main-pass-geometry.d.ts.map +1 -1
  239. package/dist/record/main-pass-material.d.ts +7 -1
  240. package/dist/record/main-pass-material.d.ts.map +1 -1
  241. package/dist/record/main-pass-sprite-draws.d.ts.map +1 -1
  242. package/dist/record/main-pass.d.ts +13 -2
  243. package/dist/record/main-pass.d.ts.map +1 -1
  244. package/dist/record/mesh-ssbo.d.ts +6 -5
  245. package/dist/record/mesh-ssbo.d.ts.map +1 -1
  246. package/dist/record/motion-blur-receipt.d.ts +11 -0
  247. package/dist/record/motion-blur-receipt.d.ts.map +1 -0
  248. package/dist/record/probe-blend-buffer.d.ts +16 -0
  249. package/dist/record/probe-blend-buffer.d.ts.map +1 -0
  250. package/dist/record/recovery-pipeline.d.ts.map +1 -1
  251. package/dist/record/render-context.d.ts +34 -31
  252. package/dist/record/render-context.d.ts.map +1 -1
  253. package/dist/record/shadow-pass.d.ts +2 -0
  254. package/dist/record/shadow-pass.d.ts.map +1 -1
  255. package/dist/record/skybox-post-pass.d.ts +6 -4
  256. package/dist/record/skybox-post-pass.d.ts.map +1 -1
  257. package/dist/record/typed-frame-graph.d.ts +22 -2
  258. package/dist/record/typed-frame-graph.d.ts.map +1 -1
  259. package/dist/record/view-ubo.d.ts +11 -3
  260. package/dist/record/view-ubo.d.ts.map +1 -1
  261. package/dist/record/volume-params.d.ts +9 -0
  262. package/dist/record/volume-params.d.ts.map +1 -0
  263. package/dist/recovery/render-system-candidate.d.ts +10 -0
  264. package/dist/recovery/render-system-candidate.d.ts.map +1 -1
  265. package/dist/reflection/record-owner.d.ts.map +1 -1
  266. package/dist/render-contract.d.ts +66 -8
  267. package/dist/render-contract.d.ts.map +1 -1
  268. package/dist/render-graph-primitives.d.ts.map +1 -1
  269. package/dist/render-pipeline.d.ts +106 -3
  270. package/dist/render-pipeline.d.ts.map +1 -1
  271. package/dist/render-system-extract-tail.d.ts +3 -2
  272. package/dist/render-system-extract-tail.d.ts.map +1 -1
  273. package/dist/render-system-extract.d.ts +24 -10
  274. package/dist/render-system-extract.d.ts.map +1 -1
  275. package/dist/render-system-presentation.d.ts +23 -0
  276. package/dist/render-system-presentation.d.ts.map +1 -0
  277. package/dist/render-system-producer-roots.d.ts +10 -0
  278. package/dist/render-system-producer-roots.d.ts.map +1 -0
  279. package/dist/render-system-residency.d.ts +12 -0
  280. package/dist/render-system-residency.d.ts.map +1 -0
  281. package/dist/render-system.d.ts +26 -15
  282. package/dist/render-system.d.ts.map +1 -1
  283. package/dist/scene/probe-blend.d.ts +27 -0
  284. package/dist/scene/probe-blend.d.ts.map +1 -1
  285. package/dist/scene/render-scene.d.ts +14 -5
  286. package/dist/scene/render-scene.d.ts.map +1 -1
  287. package/dist/ssao-buffers.d.ts +9 -0
  288. package/dist/ssao-buffers.d.ts.map +1 -1
  289. package/dist/surface/dynamic-input.d.ts +133 -0
  290. package/dist/surface/dynamic-input.d.ts.map +1 -0
  291. package/dist/surface/gpu-driven.d.ts +41 -0
  292. package/dist/surface/gpu-driven.d.ts.map +1 -0
  293. package/dist/surface/submission-observation.d.ts +136 -0
  294. package/dist/surface/submission-observation.d.ts.map +1 -0
  295. package/dist/systems/skin-palette-allocator.d.ts.map +1 -1
  296. package/dist/temporal/frame-coordinator.d.ts.map +1 -1
  297. package/dist/temporal/frame.d.ts +7 -0
  298. package/dist/temporal/frame.d.ts.map +1 -1
  299. package/dist/temporal/gpu.d.ts +13 -1
  300. package/dist/temporal/gpu.d.ts.map +1 -1
  301. package/dist/temporal/index.mjs +5 -5
  302. package/dist/temporal/inspection.d.ts.map +1 -1
  303. package/dist/temporal/standard-scene-data.d.ts.map +1 -1
  304. package/dist/temporal/target-coverage-attachment.d.ts.map +1 -1
  305. package/dist/temporal/view.d.ts +6 -1
  306. package/dist/temporal/view.d.ts.map +1 -1
  307. package/dist/transmission/single-layer-medium.d.ts +89 -0
  308. package/dist/transmission/single-layer-medium.d.ts.map +1 -0
  309. package/dist/typed-render-graph-primitives.d.ts +24 -9
  310. package/dist/typed-render-graph-primitives.d.ts.map +1 -1
  311. package/dist/volume/component.d.ts +9 -5
  312. package/dist/volume/component.d.ts.map +1 -1
  313. package/dist/volume/extract.d.ts +1 -1
  314. package/dist/volume/extract.d.ts.map +1 -1
  315. package/dist/volume/inspection.d.ts +2 -0
  316. package/dist/volume/inspection.d.ts.map +1 -1
  317. package/dist/volume/passes.d.ts +4 -1
  318. package/dist/volume/passes.d.ts.map +1 -1
  319. package/dist/volume/resources.d.ts.map +1 -1
  320. package/dist/volume/temporal.d.ts +3 -1
  321. package/dist/volume/temporal.d.ts.map +1 -1
  322. package/package.json +22 -21
  323. package/src/__tests__/analytic-fog-graph.unit.test.ts +199 -0
  324. package/src/__tests__/analytic-fog-optics.unit.test.ts +38 -0
  325. package/src/__tests__/atmosphere-daylight.dawn.test.ts +72 -0
  326. package/src/__tests__/barrel-distortion-lkg.integration.test.ts +353 -0
  327. package/src/__tests__/barrel-distortion.dawn.test.ts +41 -0
  328. package/src/__tests__/barrel-distortion.unit.test.ts +364 -0
  329. package/src/__tests__/bloom-exact-zero.unit.test.ts +112 -18
  330. package/src/__tests__/bloom-lifecycle.integration.test.ts +17 -4
  331. package/src/__tests__/construct-renderer-identity.unit.test.ts +49 -0
  332. package/src/__tests__/detached-recovery-graph.unit.test.ts +277 -0
  333. package/src/__tests__/device-loss-fanout.unit.test.ts +36 -1
  334. package/src/__tests__/docs-gate-green.unit.test.ts +112 -1
  335. package/src/__tests__/dynamic-resolution-plugin.integration.test.ts +20 -0
  336. package/src/__tests__/environment-fog-lanes.integration.test.ts +14 -0
  337. package/src/__tests__/environment-selection.unit.test.ts +12 -0
  338. package/src/__tests__/factory-contract.integration.test.ts +626 -113
  339. package/src/__tests__/feature-depth-input.dawn.test.ts +129 -2
  340. package/src/__tests__/frame-camera-selection.unit.test.ts +39 -1
  341. package/src/__tests__/frame-plan-contract.unit.test.ts +2 -0
  342. package/src/__tests__/fullscreen-feature-plan.unit.test.ts +14 -3
  343. package/src/__tests__/gpu-dirty-ranges.unit.test.ts +22 -0
  344. package/src/__tests__/gpu-driven-baseline.characterization.test.ts +16 -3
  345. package/src/__tests__/gpu-driven-batch-topology-pbr.unit.test.ts +12 -0
  346. package/src/__tests__/gpu-driven-indirect-raster-evidence.ts +2 -1
  347. package/src/__tests__/gpu-driven-material-artifacts.unit.test.ts +143 -0
  348. package/src/__tests__/gpu-driven-pbr.dawn.test.ts +2 -2
  349. package/src/__tests__/gpu-driven-production.integration.test.ts +482 -3
  350. package/src/__tests__/gpu-driven-shadow-views.dawn.test.ts +7 -6
  351. package/src/__tests__/gpu-driven-shadow-views.integration.test.ts +1 -1
  352. package/src/__tests__/gpu-driven-view-gpu-evidence.ts +103 -4
  353. package/src/__tests__/gpu-driven-view-graph.integration.test.ts +18 -2
  354. package/src/__tests__/gpu-driven-view.browser.test.ts +5 -8
  355. package/src/__tests__/gpu-driven-view.dawn.test.ts +6 -2
  356. package/src/__tests__/gpu-driven-view.unit.test.ts +1 -1
  357. package/src/__tests__/gpu-pass-timing-lifecycle.integration.test.ts +1 -0
  358. package/src/__tests__/gpu-pass-timing.browser.test.ts +6 -4
  359. package/src/__tests__/gpu-pass-timing.rhi-null.unit.test.ts +1 -0
  360. package/src/__tests__/gpu-resource-store-stride.unit.test.ts +12 -0
  361. package/src/__tests__/gpu-scene.unit.test.ts +210 -9
  362. package/src/__tests__/instance-allocation-failure.unit.test.ts +57 -0
  363. package/src/__tests__/instances-culling.integration.test.ts +10 -36
  364. package/src/__tests__/instances-store.unit.test.ts +105 -238
  365. package/src/__tests__/instances-world-ownership.integration.test.ts +62 -0
  366. package/src/__tests__/main-pass-material-medium-selection.unit.test.ts +146 -0
  367. package/src/__tests__/material-shader-prewarm.unit.test.ts +59 -0
  368. package/src/__tests__/material-snapshot-mutation.integration.test.ts +39 -1
  369. package/src/__tests__/mesh-submission-lifetime.unit.test.ts +31 -0
  370. package/src/__tests__/morph-culling-reentry.dawn.test.ts +5 -5
  371. package/src/__tests__/morph-visual-evidence.integration.test.ts +8 -7
  372. package/src/__tests__/motion-blur-compute.dawn.test.ts +589 -0
  373. package/src/__tests__/motion-blur.pixel.integration.test.ts +2 -2
  374. package/src/__tests__/nineslice-handle-surface.unit.test.ts +4 -4
  375. package/src/__tests__/pbr-view-layout.unit.test.ts +9 -0
  376. package/src/__tests__/physical-clearcoat.integration.test.ts +58 -0
  377. package/src/__tests__/prepared-gpu-driven-pbr.integration.test.ts +21 -0
  378. package/src/__tests__/prepared-graphics-capability.unit.test.ts +66 -2
  379. package/src/__tests__/prepared-graphics-lifecycle.unit.test.ts +86 -0
  380. package/src/__tests__/probe-bindings.unit.test.ts +582 -1
  381. package/src/__tests__/probe-scene-dirty.integration.test.ts +28 -1
  382. package/src/__tests__/public-surface.test-d.ts +8 -0
  383. package/src/__tests__/receipt-observation-retirement.unit.test.ts +65 -0
  384. package/src/__tests__/recovery-candidate-prepare.contract.test.ts +69 -0
  385. package/src/__tests__/render-error-code-owner.test-d.ts +9 -0
  386. package/src/__tests__/render-feature-diagnostics.unit.test.ts +15 -2
  387. package/src/__tests__/render-feature-error-reporting.unit.test.ts +44 -0
  388. package/src/__tests__/render-feature-prepared-diagnostics.unit.test.ts +10 -1
  389. package/src/__tests__/render-feature-retry.unit.test.ts +12 -1
  390. package/src/__tests__/render-feature-stage-fault-recovery.unit.test.ts +23 -0
  391. package/src/__tests__/render-scene-mixed-updates.integration.test.ts +145 -19
  392. package/src/__tests__/render-scene-projection.unit.test.ts +36 -11
  393. package/src/__tests__/render-scene-temporal-retry.unit.test.ts +37 -6
  394. package/src/__tests__/render-target-probe-soak.perf.test.ts +2 -2
  395. package/src/__tests__/render-temporal-environment-consumer.browser.test.ts +3 -3
  396. package/src/__tests__/render-temporal-environment-consumer.dawn.test.ts +5 -2
  397. package/src/__tests__/renderer-factory-material-contract.unit.test.ts +41 -7
  398. package/src/__tests__/renderer-frame-generation-fence.integration.test.ts +16 -0
  399. package/src/__tests__/renderer-frame-transaction.integration.test.ts +33 -11
  400. package/src/__tests__/resource-allocation.unit.test.ts +123 -0
  401. package/src/__tests__/shader-manifest-fixture.ts +4 -4
  402. package/src/__tests__/single-layer-medium-passes.unit.test.ts +275 -0
  403. package/src/__tests__/skin-motion-regression.unit.test.ts +70 -1
  404. package/src/__tests__/skin-palette-recycling.dawn.test.ts +97 -0
  405. package/src/__tests__/skinned-shadow-caster.test.ts +17 -0
  406. package/src/__tests__/ssr-carrier-public-contract.unit.test.ts +21 -0
  407. package/src/__tests__/ssr-gpu-dispatch.browser.test.ts +4 -4
  408. package/src/__tests__/ssr-gpu-dispatch.dawn.test.ts +2 -2
  409. package/src/__tests__/ssr-gpu-dispatch.ts +1 -1
  410. package/src/__tests__/ssr-receiver-coverage.dawn.test.ts +6 -2
  411. package/src/__tests__/standard-lighting-write.unit.test.ts +2 -1
  412. package/src/__tests__/standard-output-chain-contract.unit.test.ts +28 -0
  413. package/src/__tests__/standard-pbr-artifact-assembly.unit.test.ts +46 -1
  414. package/src/__tests__/standard-pipeline.dawn.test.ts +1 -1
  415. package/src/__tests__/standard-pipeline.integration.test.ts +200 -9
  416. package/src/__tests__/surface-dynamic-buffer-lifecycle.unit.test.ts +506 -0
  417. package/src/__tests__/surface-submission-observation.unit.test.ts +812 -0
  418. package/src/__tests__/temporal-history-lifecycle.unit.test.ts +32 -0
  419. package/src/__tests__/temporal-performance.integration.test.ts +3 -3
  420. package/src/__tests__/temporal-reset-matrix.unit.test.ts +10 -0
  421. package/src/__tests__/temporal-view.unit.test.ts +2 -1
  422. package/src/__tests__/typed-pipeline-topology.unit.test.ts +20 -4
  423. package/src/__tests__/visibility-instances.integration.test.ts +7 -19
  424. package/src/__tests__/visibility-producer-matrix.integration.test.ts +9 -20
  425. package/src/__tests__/volume-optics-oracle.unit.test.ts +7 -3
  426. package/src/__tests__/volumetric-fog-budget-recovery.integration.test.ts +189 -15
  427. package/src/__tests__/volumetric-fog-errors.unit.test.ts +4 -3
  428. package/src/__tests__/volumetric-fog-multiple.unit.test.ts +55 -0
  429. package/src/__tests__/volumetric-fog-spot-frame-optics.integration.test.ts +1 -1
  430. package/src/__tests__/volumetric-fog-stage-readback.dawn.test.ts +558 -68
  431. package/src/__tests__/volumetric-fog-temporal.unit.test.ts +27 -0
  432. package/src/__tests__/volumetric-fog-topology.unit.test.ts +23 -5
  433. package/src/assembly/analytic-fog-registration.ts +14 -0
  434. package/src/assembly/backend-contract.ts +30 -0
  435. package/src/assembly/barrel-distortion-inspection.ts +22 -0
  436. package/src/assembly/canvas-draw-guard.ts +11 -0
  437. package/src/assembly/cloud-shadow-entry.ts +22 -0
  438. package/src/assembly/dynamic-geometry-host.ts +65 -37
  439. package/src/assembly/dynamic-geometry-runtime.ts +7 -1
  440. package/src/assembly/factory.ts +2 -30
  441. package/src/assembly/fxaa-registration.ts +17 -0
  442. package/src/assembly/host-contract.ts +3 -3
  443. package/src/assembly/lod-observation.ts +12 -0
  444. package/src/assembly/material/assembly.ts +25 -13
  445. package/src/assembly/material/pipeline-helpers.ts +53 -12
  446. package/src/assembly/material-shader-policy.ts +55 -0
  447. package/src/assembly/material-shader-prewarm.ts +21 -10
  448. package/src/assembly/motion-blur-registration.ts +17 -0
  449. package/src/assembly/recovery/device-loss-fanout.ts +13 -0
  450. package/src/assembly/recovery/renderer-recover.ts +41 -18
  451. package/src/assembly/render-feature-post-processes.ts +54 -0
  452. package/src/assembly/renderer-facade.ts +6 -1
  453. package/src/assembly/renderer-feature-inspection.ts +8 -2
  454. package/src/assembly/renderer-frame-transaction.ts +23 -0
  455. package/src/assembly/renderer-inspection.ts +28 -0
  456. package/src/assembly/skin-palette-owner.ts +1 -1
  457. package/src/assembly/temporal-post-process-prewarm.ts +40 -0
  458. package/src/assembly/webgpu-pbr-ready.ts +9 -1
  459. package/src/assembly/webgpu-ready-contract.ts +8 -3
  460. package/src/assembly/webgpu-ready-per-pass.ts +104 -0
  461. package/src/assembly/webgpu-ready.ts +296 -306
  462. package/src/assembly/webgpu-renderer-bootstrap.ts +0 -12
  463. package/src/assembly/webgpu-renderer-observation-owner.ts +222 -0
  464. package/src/assembly/webgpu-renderer.ts +228 -281
  465. package/src/assembly/webgpu-vertex-layouts.ts +1 -1
  466. package/src/barrel-distortion.ts +247 -0
  467. package/src/bloom-admission.ts +21 -0
  468. package/src/cloud/__tests__/cloud-density-parity.dawn.test.ts +173 -0
  469. package/src/cloud/__tests__/cloud-layer-framebuffer-y.dawn.test.ts +265 -0
  470. package/src/cloud/__tests__/cloud-layer.browser.test.ts +432 -0
  471. package/src/cloud/__tests__/cloud-layer.unit.test.ts +634 -0
  472. package/src/cloud/density.ts +600 -0
  473. package/src/cloud/extract.ts +110 -0
  474. package/src/cloud/feature.ts +1810 -0
  475. package/src/cloud/frame-context.ts +42 -0
  476. package/src/cloud/inspection.ts +130 -0
  477. package/src/cloud/optics.ts +285 -0
  478. package/src/cloud/parameters.ts +255 -0
  479. package/src/cloud/resources.ts +122 -0
  480. package/src/cloud/shadow.ts +167 -0
  481. package/src/cloud/temporal.ts +226 -0
  482. package/src/components/__tests__/motion-blur.unit.test.ts +1 -0
  483. package/src/components/atmosphere.ts +4 -0
  484. package/src/components/barrel-distortion.ts +46 -0
  485. package/src/components/camera.ts +66 -10
  486. package/src/components/cloud-layer.ts +47 -0
  487. package/src/components/index.ts +2 -0
  488. package/src/components/instances.ts +18 -45
  489. package/src/components/mesh-filter.ts +11 -9
  490. package/src/components/motion-blur.ts +1 -0
  491. package/src/components/sprite-instances.ts +1 -1
  492. package/src/construct-renderer.ts +1 -1
  493. package/src/device/gpu-residency.ts +56 -12
  494. package/src/dynamic-geometry.ts +12 -1
  495. package/src/environment/analytic-fog.ts +44 -0
  496. package/src/environment/background.ts +2 -2
  497. package/src/errors/cloud.ts +103 -0
  498. package/src/errors/render.ts +58 -8
  499. package/src/examples/single-layer-medium-surface.ts +59 -0
  500. package/src/extract/camera.ts +35 -8
  501. package/src/extract/contracts.ts +12 -0
  502. package/src/extract/environment.ts +4 -0
  503. package/src/features/barrel-distortion.ts +172 -0
  504. package/src/features/error-reporting.ts +98 -0
  505. package/src/features/host.ts +138 -24
  506. package/src/features/motion-blur/__tests__/motion-blur-feature.unit.test.ts +111 -1
  507. package/src/features/motion-blur/__tests__/motion-blur-params.unit.test.ts +58 -3
  508. package/src/features/motion-blur/motion-blur-feature.ts +1519 -15
  509. package/src/features/motion-blur/motion-blur-params.ts +70 -2
  510. package/src/features/motion-blur/motion-blur-runtime.ts +176 -0
  511. package/src/features/plan.ts +33 -8
  512. package/src/features/prepared-gpu-work.ts +86 -35
  513. package/src/features/render-graph-compute.ts +13 -5
  514. package/src/features/render-graph-contribution.ts +11 -0
  515. package/src/features/render-graph-raster.ts +45 -3
  516. package/src/features/targets.ts +66 -0
  517. package/src/features/types.ts +47 -0
  518. package/src/fullscreen-post-process-pass.ts +41 -9
  519. package/src/gpu-dirty-ranges.ts +48 -0
  520. package/src/gpu-driven/batch-topology.ts +53 -0
  521. package/src/gpu-driven/material-artifacts.ts +35 -32
  522. package/src/gpu-driven/prepared-draw.ts +27 -14
  523. package/src/gpu-driven/production-raster-material.ts +1894 -0
  524. package/src/gpu-driven/production-raster.ts +904 -787
  525. package/src/gpu-driven/resource-allocation.ts +142 -0
  526. package/src/gpu-driven/shadow-ownership.ts +71 -0
  527. package/src/gpu-driven/view-gpu.ts +181 -10
  528. package/src/gpu-scene.ts +259 -101
  529. package/src/hdrp-buffers.ts +4 -3
  530. package/src/ibl/skylight-bind-group.ts +49 -7
  531. package/src/index.ts +228 -9
  532. package/src/inspection-types.ts +222 -2
  533. package/src/instance-buffer-cache.ts +4 -4
  534. package/src/instances.ts +151 -492
  535. package/src/pbr-pipeline.ts +219 -13
  536. package/src/pipeline/single-layer-medium-passes.ts +267 -0
  537. package/src/pipeline/standard-forward-lane.ts +313 -57
  538. package/src/pipeline/standard-output/graph.ts +8 -2
  539. package/src/pipeline/standard-output/types.ts +12 -1
  540. package/src/pipeline/standard-pipeline.ts +291 -18
  541. package/src/pipeline/standard-post.ts +397 -102
  542. package/src/pipeline-builder.ts +1 -1
  543. package/src/pipeline-spec.ts +5 -5
  544. package/src/plugin.ts +4 -0
  545. package/src/points-lines/__tests__/points-lines.bench.ts +17 -8
  546. package/src/points-lines/__tests__/view-ubo.unit.test.ts +2 -1
  547. package/src/prepare/prepared-graphics-resolver.ts +2 -0
  548. package/src/record/__tests__/main-pass-geometry.unit.test.ts +30 -0
  549. package/src/record/__tests__/sprite-material-abi.unit.test.ts +1 -1
  550. package/src/record/__tests__/temporal-payload.unit.test.ts +3 -2
  551. package/src/record/__tests__/transparent-group2-selection.unit.test.ts +11 -0
  552. package/src/record/__tests__/typed-frame-graph-plan.unit.test.ts +1 -0
  553. package/src/record/__tests__/view-ubo-layout.unit.test.ts +4 -3
  554. package/src/record/barrel-distortion-frame.ts +110 -0
  555. package/src/record/dynamic-geometry-consumption.ts +39 -9
  556. package/src/record/fold-buckets.ts +852 -0
  557. package/src/record/fold-instance-buffer.ts +51 -0
  558. package/src/record/frame-lighting.ts +36 -2
  559. package/src/record/frame-snapshot.ts +44 -4
  560. package/src/record/frame-targets.ts +10 -6
  561. package/src/record/frame.ts +373 -1099
  562. package/src/record/gpu-draw-receipts.ts +38 -0
  563. package/src/record/gpu-timing.ts +48 -5
  564. package/src/record/main-pass-geometry.ts +281 -83
  565. package/src/record/main-pass-material.ts +66 -4
  566. package/src/record/main-pass-sprite-draws.ts +13 -66
  567. package/src/record/main-pass.ts +156 -20
  568. package/src/record/mesh-ssbo.ts +43 -6
  569. package/src/record/motion-blur-receipt.ts +91 -0
  570. package/src/record/probe-blend-buffer.ts +180 -0
  571. package/src/record/recovery-pipeline.ts +35 -3
  572. package/src/record/render-context.ts +51 -31
  573. package/src/record/shadow-pass.ts +36 -2
  574. package/src/record/skybox-post-pass.ts +150 -200
  575. package/src/record/typed-frame-graph.ts +824 -44
  576. package/src/record/view-ubo.ts +26 -3
  577. package/src/record/volume-params.ts +225 -0
  578. package/src/recovery/render-system-candidate.ts +257 -52
  579. package/src/reflection/record-owner.ts +7 -3
  580. package/src/render-contract.ts +71 -8
  581. package/src/render-graph-primitives.ts +7 -1
  582. package/src/render-pipeline.ts +136 -1
  583. package/src/render-system-extract-tail.ts +72 -60
  584. package/src/render-system-extract.ts +117 -87
  585. package/src/render-system-presentation.ts +56 -0
  586. package/src/render-system-producer-roots.ts +96 -0
  587. package/src/render-system-residency.ts +159 -0
  588. package/src/render-system.ts +601 -482
  589. package/src/scene/probe-blend.ts +96 -4
  590. package/src/scene/render-scene.ts +431 -281
  591. package/src/ssao-buffers.ts +95 -2
  592. package/src/surface/__tests__/dynamic-input.unit.test.ts +374 -0
  593. package/src/surface/__tests__/gpu-driven.unit.test.ts +87 -0
  594. package/src/surface/dynamic-input.ts +658 -0
  595. package/src/surface/gpu-driven.ts +147 -0
  596. package/src/surface/submission-observation.ts +517 -0
  597. package/src/systems/skin-palette-allocator.ts +4 -1
  598. package/src/temporal/__tests__/temporal-frame-transaction.unit.test.ts +36 -0
  599. package/src/temporal/frame-coordinator.ts +8 -1
  600. package/src/temporal/frame.ts +20 -0
  601. package/src/temporal/gpu.ts +101 -17
  602. package/src/temporal/inspection.ts +7 -1
  603. package/src/temporal/standard-scene-data.ts +5 -0
  604. package/src/temporal/target-coverage-attachment.ts +4 -0
  605. package/src/temporal/view.ts +13 -2
  606. package/src/transmission/__tests__/single-layer-medium.unit.test.ts +149 -0
  607. package/src/transmission/__tests__/standard-transmission.dawn.test.ts +12 -11
  608. package/src/transmission/single-layer-medium.ts +322 -0
  609. package/src/typed-render-graph-primitives.ts +191 -50
  610. package/src/volume/component.ts +10 -10
  611. package/src/volume/extract.ts +11 -5
  612. package/src/volume/inspection.ts +3 -0
  613. package/src/volume/passes.ts +95 -8
  614. package/src/volume/resources.ts +2 -1
  615. package/src/volume/temporal.ts +11 -0
  616. package/dist/chunk-4NNZQCL3.mjs.map +0 -1
  617. package/dist/chunk-6TFBXLRJ.mjs.map +0 -1
  618. package/dist/chunk-DRR7D3KX.mjs.map +0 -1
  619. package/dist/chunk-GWCEMDVR.mjs.map +0 -1
  620. package/dist/chunk-JCIRYQ4V.mjs.map +0 -1
  621. package/dist/chunk-JRIPCMYC.mjs.map +0 -1
  622. package/dist/chunk-LULCA3OF.mjs +0 -1713
  623. package/dist/chunk-LULCA3OF.mjs.map +0 -1
  624. package/dist/chunk-PA5DRJNI.mjs.map +0 -1
@@ -2,8 +2,11 @@ import * as render from '@forgeax/engine-render';
2
2
  import {
3
3
  type EngineEnvironmentError,
4
4
  constructRendererHost as internalConstructRendererHost,
5
+ loadRhiPack as loadRenderRhiPack,
5
6
  type RendererHostAssembly,
7
+ type RhiBackendPack,
6
8
  } from '@forgeax/engine-render/internal/construct-renderer';
9
+ import { rhi } from '@forgeax/engine-rhi-null';
7
10
  import { describe, expect, it } from 'vitest';
8
11
  import type { RenderError } from '../errors/render';
9
12
  import type { RenderResult } from '../render-contract';
@@ -21,4 +24,50 @@ describe('construct-renderer internal entry', () => {
21
24
  >;
22
25
  expect(null as unknown as Return satisfies Expected).toBeNull();
23
26
  });
27
+
28
+ it('projects backend fields with Render explicit-only instrumentation policy', () => {
29
+ const createShaderModule = async (..._args: unknown[]) => ({
30
+ ok: true as const,
31
+ value: {} as never,
32
+ });
33
+ const createShaderModuleImmediate = (..._args: unknown[]) => ({
34
+ ok: true as const,
35
+ value: {} as never,
36
+ });
37
+ const translateErrorEventToRhiError = (_event: unknown) => ({
38
+ ok: false as const,
39
+ error: {} as never,
40
+ });
41
+ const rawDeviceAccessor = (_device: unknown) => undefined;
42
+ const moduleInstrumentation: RhiBackendPack['instrumentation'] = {
43
+ onFrameBoundary: () => undefined,
44
+ };
45
+ const explicitInstrumentation: RhiBackendPack['instrumentation'] = {
46
+ onDeviceLost: () => undefined,
47
+ };
48
+ const module = {
49
+ rhi,
50
+ createShaderModule,
51
+ createShaderModuleImmediate,
52
+ translateErrorEventToRhiError,
53
+ _internal_getRawDevice: rawDeviceAccessor,
54
+ instrumentation: moduleInstrumentation,
55
+ };
56
+
57
+ const implicitPack = loadRenderRhiPack(module);
58
+ const explicitPack = loadRenderRhiPack(module, explicitInstrumentation);
59
+ expect(Object.hasOwn(implicitPack, 'instrumentation')).toBe(false);
60
+ expect(explicitPack.instrumentation).toBe(explicitInstrumentation);
61
+ expect(explicitPack.rhi).toBe(rhi);
62
+ expect(explicitPack.createShaderModule).toBe(createShaderModule);
63
+ expect(explicitPack.createShaderModuleImmediate).toBe(createShaderModuleImmediate);
64
+ expect(explicitPack.translateErrorEventToRhiError).toBe(translateErrorEventToRhiError);
65
+ expect(explicitPack._internal_getRawDevice).toBe(rawDeviceAccessor);
66
+
67
+ const absentPack = loadRenderRhiPack({ rhi });
68
+ expect(Object.hasOwn(absentPack, 'createShaderModule')).toBe(false);
69
+ expect(Object.hasOwn(absentPack, 'createShaderModuleImmediate')).toBe(false);
70
+ expect(Object.hasOwn(absentPack, 'translateErrorEventToRhiError')).toBe(false);
71
+ expect(Object.hasOwn(absentPack, '_internal_getRawDevice')).toBe(false);
72
+ });
24
73
  });
@@ -0,0 +1,277 @@
1
+ import type { RhiCommandEncoder, RhiDevice } from '@forgeax/engine-rhi';
2
+ import { rhi } from '@forgeax/engine-rhi-null';
3
+ import { ok } from '@forgeax/engine-types';
4
+ import { describe, expect, it, vi } from 'vitest';
5
+ import { RhiErrorListenerRegistry } from '../lifecycle';
6
+ import type { RenderFrameState } from '../record/frame-snapshot';
7
+ import { makeZeroCameraFallbackSnapshot } from '../record/frame-snapshot';
8
+ import type { PipelineState, RenderSystemInternals } from '../record/render-context';
9
+ import {
10
+ ensureCompiledFrameGraph,
11
+ executeCompiledFrameGraph,
12
+ inspectRenderGraphGenerationAllocation,
13
+ retire as retireCompiledGraph,
14
+ settleBarrelDistortionGraphCandidate,
15
+ shareRenderGraphGenerationAllocationOwner,
16
+ } from '../record/typed-frame-graph';
17
+ import type { RenderPipeline } from '../render-pipeline';
18
+ import type { ExtractedLights } from '../render-system-extract';
19
+
20
+ function createRecoveryGraphHarness() {
21
+ let failBuild = false;
22
+ const activePipeline: RenderPipeline = {
23
+ build: ({ graph }) => {
24
+ if (failBuild) return { ok: false, error: { code: 'injected-build-failure' } } as never;
25
+ const buffer = graph.createBuffer('recovery-graph-bytes', { size: 16 });
26
+ if (!buffer.ok) return buffer;
27
+ return graph.addCopyPass('recovery-graph-seed', {
28
+ accesses: [{ resource: buffer.value, usage: 'copy-dst' }],
29
+ encode: () => undefined,
30
+ });
31
+ },
32
+ };
33
+ const frameState = {
34
+ compiledFrameGraph: null,
35
+ compiledFrameGraphTopologyKey: null,
36
+ retiredCompiledFrameGraphs: new Set(),
37
+ activePipeline,
38
+ installedPipelineConfig: undefined,
39
+ } as unknown as RenderFrameState;
40
+ const errorRegistry = new RhiErrorListenerRegistry();
41
+ let device: RhiDevice | undefined;
42
+ const internals = {
43
+ get device() {
44
+ return device as RhiDevice;
45
+ },
46
+ errorRegistry,
47
+ } as unknown as RenderSystemInternals;
48
+ const pipelineState = {
49
+ format: 'rgba8unorm',
50
+ colorAttachmentFormat: 'rgba8unorm',
51
+ } as unknown as PipelineState;
52
+ const lights = {
53
+ cascadeCount: undefined,
54
+ pointShadow: [],
55
+ spot: [],
56
+ } as unknown as ExtractedLights;
57
+ return {
58
+ activePipeline,
59
+ frameState,
60
+ internals,
61
+ pipelineState,
62
+ lights,
63
+ setDevice(nextDevice: RhiDevice) {
64
+ device = nextDevice;
65
+ },
66
+ compile() {
67
+ return ensureCompiledFrameGraph(
68
+ internals,
69
+ frameState,
70
+ pipelineState,
71
+ makeZeroCameraFallbackSnapshot(),
72
+ lights,
73
+ 1,
74
+ 1,
75
+ undefined,
76
+ );
77
+ },
78
+ accept() {
79
+ settleBarrelDistortionGraphCandidate(frameState, true);
80
+ },
81
+ setFail(value: boolean) {
82
+ failBuild = value;
83
+ },
84
+ };
85
+ }
86
+
87
+ describe('detached recovery graph execution boundary', () => {
88
+ it('keeps topology inspection while rejecting detached LKG and then retires it on replacement', async () => {
89
+ const device = (await (await rhi.requestAdapter()).unwrap().requestDevice()).unwrap();
90
+ const harness = createRecoveryGraphHarness();
91
+ harness.setDevice(device);
92
+
93
+ const candidate = harness.compile();
94
+ expect(candidate).not.toBeNull();
95
+ if (candidate === null) return;
96
+ harness.frameState.compiledFrameGraphTopologyKey = null;
97
+ harness.frameState.perFrameGraph = null;
98
+ harness.setFail(true);
99
+ expect(harness.compile()).toBeNull();
100
+ expect(harness.frameState.compiledFrameGraph).toBe(candidate);
101
+
102
+ harness.setFail(false);
103
+ const replacement = harness.compile();
104
+ expect(replacement).not.toBeNull();
105
+ expect(replacement).not.toBe(candidate);
106
+ expect(harness.frameState.compiledFrameGraph).toBe(replacement);
107
+ expect(harness.frameState.compiledFrameGraphTopologyKey).not.toBeNull();
108
+ expect(harness.frameState.retiredCompiledFrameGraphs.has(candidate)).toBe(false);
109
+ harness.accept();
110
+ expect(harness.frameState.retiredCompiledFrameGraphs.has(candidate)).toBe(true);
111
+
112
+ harness.setFail(true);
113
+ expect(harness.compile()).toBe(replacement);
114
+ });
115
+
116
+ it('does not execute or submit a detached graph', () => {
117
+ const execute = vi.fn(() => ok(undefined));
118
+ const submit = vi.fn(() => ok(undefined));
119
+ const frameState = {
120
+ compiledFrameGraph: { execute },
121
+ compiledFrameGraphTopologyKey: null,
122
+ retiredCompiledFrameGraphs: new Set(),
123
+ } as unknown as RenderFrameState;
124
+ const internals = {
125
+ errorRegistry: { fire: vi.fn() },
126
+ device: { queue: { submit } },
127
+ } as never;
128
+
129
+ const submitted = executeCompiledFrameGraph(
130
+ internals,
131
+ frameState,
132
+ {} as never,
133
+ {} as RhiCommandEncoder,
134
+ );
135
+
136
+ expect(submitted).toBe(false);
137
+ expect(execute).not.toHaveBeenCalled();
138
+ expect(submit).not.toHaveBeenCalled();
139
+ });
140
+
141
+ it('projects active candidate overlap and graph-generation peak bytes', async () => {
142
+ const device = (await (await rhi.requestAdapter()).unwrap().requestDevice()).unwrap();
143
+ const harness = createRecoveryGraphHarness();
144
+ harness.setDevice(device);
145
+
146
+ const first = harness.compile();
147
+ expect(first).not.toBeNull();
148
+ if (first === null) return;
149
+ harness.frameState.compiledFrameGraphTopologyKey = null;
150
+ const replacement = harness.compile();
151
+ expect(replacement).not.toBeNull();
152
+ if (replacement === null) return;
153
+ // The old graph remains live until the replacement submission is accepted.
154
+ expect(inspectRenderGraphGenerationAllocation(harness.frameState).liveBytes).toBe(32);
155
+ harness.accept();
156
+
157
+ const allocation = inspectRenderGraphGenerationAllocation(harness.frameState);
158
+ expect(allocation.availability).toBe('complete');
159
+ expect(allocation.entries).toHaveLength(2);
160
+ expect(
161
+ allocation.entries.find((entry) => entry.generation === replacement.inspect().generation)
162
+ ?.roles,
163
+ ).toEqual(expect.arrayContaining(['active']));
164
+ expect(
165
+ allocation.entries.find((entry) => entry.generation === first.inspect().generation)?.roles,
166
+ ).toEqual(expect.arrayContaining(['retiring']));
167
+ expect(allocation.liveBytes).toBe(16);
168
+ expect(allocation.pendingRetirementBytes).toBe(16);
169
+ expect(allocation.peakBytes).toBe(32);
170
+ });
171
+
172
+ it('records graph-generation overlap without an allocation inspection sample', async () => {
173
+ const device = (await (await rhi.requestAdapter()).unwrap().requestDevice()).unwrap();
174
+ const harness = createRecoveryGraphHarness();
175
+ harness.setDevice(device);
176
+
177
+ const first = harness.compile();
178
+ expect(first).not.toBeNull();
179
+ if (first === null) return;
180
+
181
+ // Force two ordinary graph replacements. Do not call the allocation
182
+ // inspection between them: the renderer owner must observe compile and
183
+ // retirement events itself rather than treating inspect() as a sampler.
184
+ harness.frameState.compiledFrameGraphTopologyKey = null;
185
+ const second = harness.compile();
186
+ expect(second).not.toBeNull();
187
+ if (second === null) return;
188
+ harness.accept();
189
+ await new Promise<void>((resolve) => setTimeout(resolve, 0));
190
+ harness.frameState.compiledFrameGraphTopologyKey = null;
191
+ const third = harness.compile();
192
+ expect(third).not.toBeNull();
193
+ if (third === null) return;
194
+ harness.accept();
195
+
196
+ // RHI-null retires the old generations on its queue fence. The first
197
+ // inspection therefore sees one live graph, while peakBytes retains the
198
+ // live + retiring overlap observed during replacement.
199
+ await new Promise<void>((resolve) => setTimeout(resolve, 0));
200
+ const allocation = inspectRenderGraphGenerationAllocation(harness.frameState);
201
+ expect(allocation.liveBytes).toBe(16);
202
+ expect(allocation.pendingRetirementBytes).toBe(0);
203
+ expect(allocation.peakBytes).toBe(32);
204
+ expect(allocation.entries).toHaveLength(1);
205
+ expect(allocation.entries[0]?.generation).toBe(third.inspect().generation);
206
+ });
207
+
208
+ it('keeps a failed graph retirement visible in the renderer generation ledger', async () => {
209
+ const device = (await (await rhi.requestAdapter()).unwrap().requestDevice()).unwrap();
210
+ const harness = createRecoveryGraphHarness();
211
+ harness.setDevice(device);
212
+
213
+ const first = harness.compile();
214
+ expect(first).not.toBeNull();
215
+ if (first === null) return;
216
+ (device as unknown as { destroyBuffer: RhiDevice['destroyBuffer'] }).destroyBuffer = () =>
217
+ ({ ok: false, error: { code: 'webgpu-runtime-error' } }) as never;
218
+ harness.frameState.compiledFrameGraphTopologyKey = null;
219
+ const replacement = harness.compile();
220
+ expect(replacement).not.toBeNull();
221
+ if (replacement === null) return;
222
+ harness.accept();
223
+ await new Promise<void>((resolve) => setTimeout(resolve, 0));
224
+
225
+ const allocation = inspectRenderGraphGenerationAllocation(harness.frameState);
226
+ const failed = allocation.entries.find(
227
+ (entry) => entry.generation === first.inspect().generation,
228
+ );
229
+ expect(failed?.retirement).toBe('failed');
230
+ expect(failed?.allocation.pendingRetirementBytes).toBe(16);
231
+ expect(allocation.failedRetirementCount).toBe(1);
232
+ expect(allocation.failedRetirementBytes).toBe(16);
233
+ });
234
+
235
+ it('keeps a detached recovery candidate failure in the shared owner', async () => {
236
+ const device = (await (await rhi.requestAdapter()).unwrap().requestDevice()).unwrap();
237
+ const harness = createRecoveryGraphHarness();
238
+ harness.setDevice(device);
239
+ const active = harness.compile();
240
+ expect(active).not.toBeNull();
241
+ if (active === null) return;
242
+
243
+ const candidateFrameState = {
244
+ ...harness.frameState,
245
+ compiledFrameGraph: null,
246
+ compiledFrameGraphTopologyKey: null,
247
+ retiredCompiledFrameGraphs: new Set(),
248
+ } as unknown as RenderFrameState;
249
+ shareRenderGraphGenerationAllocationOwner(harness.frameState, candidateFrameState);
250
+ const candidate = ensureCompiledFrameGraph(
251
+ harness.internals,
252
+ candidateFrameState,
253
+ harness.pipelineState,
254
+ makeZeroCameraFallbackSnapshot(),
255
+ harness.lights,
256
+ 1,
257
+ 1,
258
+ undefined,
259
+ );
260
+ expect(candidate).not.toBeNull();
261
+ if (candidate === null) return;
262
+
263
+ (device as unknown as { destroyBuffer: RhiDevice['destroyBuffer'] }).destroyBuffer = () =>
264
+ ({ ok: false, error: { code: 'webgpu-runtime-error' } }) as never;
265
+ retireCompiledGraph(candidateFrameState, candidate);
266
+ await new Promise<void>((resolve) => setTimeout(resolve, 0));
267
+
268
+ const allocation = inspectRenderGraphGenerationAllocation(harness.frameState);
269
+ const failed = allocation.entries.find(
270
+ (entry) => entry.generation === candidate.inspect().generation,
271
+ );
272
+ expect(failed?.retirement).toBe('failed');
273
+ expect(failed?.roles).toEqual(expect.arrayContaining(['retiring']));
274
+ expect(allocation.failedRetirementCount).toBe(1);
275
+ expect(allocation.failedRetirementBytes).toBe(16);
276
+ });
277
+ });
@@ -1,4 +1,4 @@
1
- import type { RhiDevice } from '@forgeax/engine-rhi';
1
+ import { err, type RhiDevice, RhiError } from '@forgeax/engine-rhi';
2
2
  import { rhi } from '@forgeax/engine-rhi-null';
3
3
  import { describe, expect, it } from 'vitest';
4
4
  import { attachDeviceLostFanout } from '../assembly/recovery/device-loss-fanout';
@@ -20,6 +20,41 @@ function deferred<T>(): {
20
20
  }
21
21
 
22
22
  describe('device loss fan-out generation fence', () => {
23
+ it('forwards replacement-device validation errors and fences retired devices', () => {
24
+ let current = 0;
25
+ const errorRegistry = new RhiErrorListenerRegistry();
26
+ const errors: unknown[] = [];
27
+ errorRegistry.add((error) => errors.push(error));
28
+ const failure = new RhiError({
29
+ code: 'webgpu-runtime-error',
30
+ expected: 'valid GPU work',
31
+ hint: 'repair the pipeline',
32
+ });
33
+ const native: { onuncapturederror?: (event: unknown) => void } = {};
34
+ attachDeviceLostFanout(
35
+ { lost: new Promise(() => undefined) } as unknown as RhiDevice,
36
+ {
37
+ rhi,
38
+ _internal_getRawDevice: () => native,
39
+ translateErrorEventToRhiError: () => err(failure),
40
+ },
41
+ {
42
+ lostRegistry: new LostListenerRegistry(),
43
+ errorRegistry,
44
+ healthRegistry: new HealthListenerRegistry(),
45
+ generation: 1,
46
+ currentGeneration: () => current,
47
+ },
48
+ );
49
+ expect(native.onuncapturederror).toBeTypeOf('function');
50
+ current = 1;
51
+ native.onuncapturederror?.({});
52
+ expect(errors).toEqual([failure]);
53
+ current = 2;
54
+ native.onuncapturederror?.({});
55
+ expect(errors).toEqual([failure]);
56
+ });
57
+
23
58
  it('counts a late old-generation notification without changing active health', async () => {
24
59
  const lost = deferred<{ readonly reason: 'unknown'; readonly message: string }>();
25
60
  const lostRegistry = new LostListenerRegistry();
@@ -1,22 +1,133 @@
1
1
  import { readFileSync } from 'node:fs';
2
+ import path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import ts from 'typescript';
2
5
  import { describe, expect, it } from 'vitest';
3
6
 
4
7
  function read(relativePath: string): string {
5
8
  return readFileSync(new URL(relativePath, import.meta.url), 'utf8');
6
9
  }
7
10
 
11
+ function publicPackagePaths(): ts.MapLike<string[]> {
12
+ const paths: ts.MapLike<string[]> = {};
13
+ for (const packageName of [
14
+ '@forgeax/engine-assets-runtime',
15
+ '@forgeax/engine-ecs',
16
+ '@forgeax/engine-geometry',
17
+ '@forgeax/engine-render',
18
+ '@forgeax/engine-scene',
19
+ '@forgeax/engine-types',
20
+ ]) {
21
+ const packageDirectory = packageName.slice('@forgeax/engine-'.length);
22
+ paths[packageName] = [`packages/${packageDirectory}/src/index.ts`];
23
+ }
24
+ return paths;
25
+ }
26
+
8
27
  const renderReadme = read('../../README.md');
9
28
  const assetsReadme = read('../../../assets-runtime/README.md');
10
29
  const instancesSource = read('../components/instances.ts');
30
+ const repositoryRoot = fileURLToPath(new URL('../../../../', import.meta.url));
31
+
32
+ function readWaterConsumerCodeBlock(): string {
33
+ const marker = renderReadme.indexOf('export async function drawProbeLitWater(');
34
+ const blockStart = renderReadme.lastIndexOf('```ts', marker);
35
+ const blockEnd = renderReadme.indexOf('```', marker);
36
+ expect(marker).toBeGreaterThanOrEqual(0);
37
+ expect(blockStart).toBeGreaterThanOrEqual(0);
38
+ expect(blockEnd).toBeGreaterThan(blockStart);
39
+ return renderReadme.slice(blockStart + '```ts'.length, blockEnd);
40
+ }
41
+
42
+ function semanticDiagnosticsFor(sourceText: string): readonly ts.Diagnostic[] {
43
+ const virtualFile = path.join(repositoryRoot, 'packages/render/.docs-gate/water-consumer.ts');
44
+ const options: ts.CompilerOptions = {
45
+ allowImportingTsExtensions: true,
46
+ baseUrl: repositoryRoot,
47
+ ignoreDeprecations: '6.0',
48
+ module: ts.ModuleKind.ESNext,
49
+ moduleResolution: ts.ModuleResolutionKind.Bundler,
50
+ noEmit: true,
51
+ paths: publicPackagePaths(),
52
+ skipLibCheck: true,
53
+ strict: true,
54
+ target: ts.ScriptTarget.ES2022,
55
+ types: ['node'],
56
+ };
57
+ const defaultHost = ts.createCompilerHost(options, true);
58
+ const host: ts.CompilerHost = {
59
+ ...defaultHost,
60
+ fileExists: (fileName) => fileName === virtualFile || defaultHost.fileExists(fileName),
61
+ readFile: (fileName) =>
62
+ fileName === virtualFile ? sourceText : defaultHost.readFile(fileName),
63
+ getSourceFile: (fileName, languageVersion, onError, shouldCreateNewSourceFile) =>
64
+ fileName === virtualFile
65
+ ? ts.createSourceFile(fileName, sourceText, languageVersion, true, ts.ScriptKind.TS)
66
+ : defaultHost.getSourceFile(fileName, languageVersion, onError, shouldCreateNewSourceFile),
67
+ };
68
+ const program = ts.createProgram([virtualFile], options, host);
69
+ const sourceFile = program.getSourceFile(virtualFile);
70
+ expect(sourceFile).toBeDefined();
71
+ return [
72
+ ...program.getOptionsDiagnostics(),
73
+ ...program.getSyntacticDiagnostics(sourceFile),
74
+ ...program.getSemanticDiagnostics(sourceFile),
75
+ ];
76
+ }
77
+
78
+ function formatDiagnostics(diagnostics: readonly ts.Diagnostic[]): string[] {
79
+ return diagnostics.map((diagnostic) =>
80
+ ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n'),
81
+ );
82
+ }
11
83
 
12
84
  describe('M4 material and Instances documentation contract', () => {
85
+ it('semantically checks the complete public water consumer code block', () => {
86
+ const snippet = readWaterConsumerCodeBlock();
87
+ const recovery = snippet.indexOf('const recoverAndRepublish = async');
88
+ expect(recovery).toBeGreaterThanOrEqual(0);
89
+ const diagnostics = semanticDiagnosticsFor(snippet);
90
+ expect(formatDiagnostics(diagnostics)).toEqual([]);
91
+ expect(snippet).not.toContain('renderer.initialization');
92
+ expect(snippet).toContain('const attachment = renderer.attach(world);');
93
+ expect(snippet).toContain('if (!attachment.ok) throw attachment.error;');
94
+ expect(snippet).toContain('const attached = attachment.value;');
95
+ for (const consumer of [
96
+ 'renderer.inspect().frame.deviceGeneration',
97
+ 'renderer.attach(world)',
98
+ '.draw({',
99
+ ]) {
100
+ expect(snippet.indexOf(consumer)).toBeGreaterThanOrEqual(0);
101
+ }
102
+ for (const recoveryFact of [
103
+ "renderer.state() !== 'device-lost'",
104
+ 'await renderer.recover()',
105
+ 'page.reconfigureDevice(nextDeviceGeneration)',
106
+ 'const previousRanges = ranges',
107
+ 'ranges = reserveFreshRanges()',
108
+ 'projectionRevision += 1',
109
+ 'publishDynamicInput(world.getResource(Time).elapsed)',
110
+ 'submitted = await submitAndObserve()',
111
+ 'const inspection = renderer.inspect()',
112
+ 'submitted.deviceGeneration !== publishedDeviceGeneration',
113
+ ]) {
114
+ expect(snippet.indexOf(recoveryFact, recovery)).toBeGreaterThanOrEqual(recovery);
115
+ }
116
+ expect(snippet).toContain(
117
+ 'renderer.setSurfaceDynamicInput({ page, ranges, projectionRevision, frameTime: now })',
118
+ );
119
+ expect(snippet).toContain('retry the same lease after the page and ranges are revalidated');
120
+ expect(snippet).not.toContain('commitUpload(');
121
+ expect(snippet).not.toContain('consume(');
122
+ }, 30_000);
123
+
13
124
  it('keeps resident observation and derived-bounds recovery discoverable', () => {
14
125
  for (const token of [
15
126
  'renderer.inspect().meshMaterialBindings[]',
16
127
  '`ready`, `pending`, `failed`, or `last-known-good`',
17
128
  'nearest structured preparation',
18
129
  'does not keep a second readiness ledger',
19
- '`Instances` is a lightweight ECS association',
130
+ '`Instances.transforms` holds packed column-major mat4 values in World-managed',
20
131
  'derives CPU union bounds',
21
132
  'conservative no-cull result',
22
133
  'mesh AABB',
@@ -69,13 +69,33 @@ describe('DynamicResolution render component registration', () => {
69
69
  shutterAngle: 120,
70
70
  maxRadiusPixels: 12,
71
71
  sampleCount: 6,
72
+ targetFps: 60,
72
73
  });
73
74
  expect(extractFrames([world], 0).cameras[0]?.motionBlur).toEqual({
74
75
  shutterAngle: 120,
75
76
  maxRadiusPixels: 12,
76
77
  sampleCount: 6,
78
+ targetFps: 60,
77
79
  });
78
80
 
79
81
  await context.fiber.dispose();
80
82
  });
83
+
84
+ it('routes invalid MotionBlur data through the structured extraction error', async () => {
85
+ const world = new World();
86
+ const context = await createWorldContext(world, [renderComponentsPlugin(), scenePlugin()]);
87
+ world
88
+ .spawn(
89
+ { component: Transform, data: {} },
90
+ { component: Camera, data: { antialias: ANTIALIAS_TAA } },
91
+ { component: MotionBlur, data: { targetFps: 59.5 } },
92
+ )
93
+ .unwrap();
94
+ world.update(1 / 60).unwrap();
95
+
96
+ expect(() => extractCameraSnapshots(world)).toThrowError(
97
+ expect.objectContaining({ code: 'motion-blur-invalid-params' }),
98
+ );
99
+ await context.fiber.dispose();
100
+ });
81
101
  });
@@ -20,6 +20,8 @@ const frame = selectEnvironment({
20
20
  mieCoefficient: 0.005,
21
21
  mieDirectionalG: 0.8,
22
22
  sunAngularRadius: 0.004675,
23
+ circumsolarStrength: 1,
24
+ circumsolarWidth: 1,
23
25
  },
24
26
  },
25
27
  ],
@@ -48,6 +50,8 @@ describe('Environment and Fog lane graph contract', () => {
48
50
  mieCoefficient: 0.006,
49
51
  mieDirectionalG: 0.7,
50
52
  sunAngularRadius: 0.01,
53
+ circumsolarStrength: 1.4,
54
+ circumsolarWidth: 1.6,
51
55
  },
52
56
  })
53
57
  .unwrap();
@@ -70,6 +74,8 @@ describe('Environment and Fog lane graph contract', () => {
70
74
  expect(extracted.environment.source.atmosphere?.mieCoefficient).toBeCloseTo(0.006);
71
75
  expect(extracted.environment.source.atmosphere?.mieDirectionalG).toBeCloseTo(0.7);
72
76
  expect(extracted.environment.source.atmosphere?.sunAngularRadius).toBeCloseTo(0.01);
77
+ expect(extracted.environment.source.atmosphere?.circumsolarStrength).toBeCloseTo(1.4);
78
+ expect(extracted.environment.source.atmosphere?.circumsolarWidth).toBeCloseTo(1.6);
73
79
  const signature = JSON.parse(extracted.environment.signature) as {
74
80
  environments: [{ atmosphere: Record<string, number> }];
75
81
  suns: [{ direction: [number, number, number] }];
@@ -79,6 +85,8 @@ describe('Environment and Fog lane graph contract', () => {
79
85
  expect(signature.environments[0].atmosphere.mieCoefficient).toBeCloseTo(0.006);
80
86
  expect(signature.environments[0].atmosphere.mieDirectionalG).toBeCloseTo(0.7);
81
87
  expect(signature.environments[0].atmosphere.sunAngularRadius).toBeCloseTo(0.01);
88
+ expect(signature.environments[0].atmosphere.circumsolarStrength).toBeCloseTo(1.4);
89
+ expect(signature.environments[0].atmosphere.circumsolarWidth).toBeCloseTo(1.6);
82
90
  expect(signature.suns[0].direction).toEqual([0, 1, 0]);
83
91
  });
84
92
 
@@ -162,6 +170,8 @@ describe('Environment and Fog lane graph contract', () => {
162
170
  mieCoefficient: 0.005,
163
171
  mieDirectionalG: 0.8,
164
172
  sunAngularRadius: 0.004675,
173
+ circumsolarStrength: 1,
174
+ circumsolarWidth: 1,
165
175
  },
166
176
  })
167
177
  .unwrap();
@@ -190,6 +200,8 @@ describe('Environment and Fog lane graph contract', () => {
190
200
  mieCoefficient: 0.005,
191
201
  mieDirectionalG: 0.8,
192
202
  sunAngularRadius: 0.004675,
203
+ circumsolarStrength: 1,
204
+ circumsolarWidth: 1,
193
205
  })
194
206
  .unwrap();
195
207
  const invalid = extractFrames([world], 0);
@@ -203,6 +215,8 @@ describe('Environment and Fog lane graph contract', () => {
203
215
  mieCoefficient: 0.006,
204
216
  mieDirectionalG: 0.7,
205
217
  sunAngularRadius: 0.01,
218
+ circumsolarStrength: 1,
219
+ circumsolarWidth: 1,
206
220
  })
207
221
  .unwrap();
208
222
  const corrected = extractFrames([world], 0);
@@ -7,6 +7,7 @@ import type {
7
7
  EnvironmentCandidate,
8
8
  FogCandidate,
9
9
  } from '../extract/environment';
10
+ import { SunCardinalityError } from '../index';
10
11
 
11
12
  const atmosphereParameters: AtmosphereParameters = {
12
13
  turbidity: 2,
@@ -14,6 +15,8 @@ const atmosphereParameters: AtmosphereParameters = {
14
15
  mieCoefficient: 0.005,
15
16
  mieDirectionalG: 0.8,
16
17
  sunAngularRadius: 0.004675,
18
+ circumsolarStrength: 1,
19
+ circumsolarWidth: 1,
17
20
  };
18
21
 
19
22
  const image = (entityKey: number, sourceKey: string): EnvironmentCandidate => ({
@@ -97,6 +100,7 @@ describe('Environment and Fog selection (M2)', () => {
97
100
  expect(result.error.expected).toContain('sun');
98
101
  expect(result.error.hint).toContain('sun');
99
102
  expect(result.error.detail).toMatchObject({ field, value });
103
+ expect(result.error).toBeInstanceOf(SunCardinalityError);
100
104
  });
101
105
 
102
106
  it('rejects invalid Fog parameters at the field owner', () => {
@@ -114,6 +118,8 @@ describe('Environment and Fog selection (M2)', () => {
114
118
  ['mieCoefficient', { mieCoefficient: -0.001 }],
115
119
  ['mieDirectionalG', { mieDirectionalG: 1.1 }],
116
120
  ['sunAngularRadius', { sunAngularRadius: Number.POSITIVE_INFINITY }],
121
+ ['circumsolarStrength', { circumsolarStrength: -0.001 }],
122
+ ['circumsolarWidth', { circumsolarWidth: 0.1 }],
117
123
  ] as const)('rejects invalid Atmosphere.%s with field-owned detail', (field, override) => {
118
124
  const result = selection(
119
125
  [
@@ -141,6 +147,8 @@ describe('Environment and Fog selection (M2)', () => {
141
147
  ['mieCoefficient', 0, 100],
142
148
  ['mieDirectionalG', 0, 0.999],
143
149
  ['sunAngularRadius', 0, 1],
150
+ ['circumsolarStrength', 0, 4],
151
+ ['circumsolarWidth', 0.25, 4],
144
152
  ] as const)('accepts the documented %s boundaries', (field, minimum, maximum) => {
145
153
  for (const value of [minimum, maximum]) {
146
154
  const result = selection(
@@ -165,6 +173,8 @@ describe('Environment and Fog selection (M2)', () => {
165
173
  ['mieCoefficient', [-Number.MIN_VALUE, Number.POSITIVE_INFINITY]],
166
174
  ['mieDirectionalG', [-Number.MIN_VALUE, 1]],
167
175
  ['sunAngularRadius', [-Number.MIN_VALUE, Number.POSITIVE_INFINITY]],
176
+ ['circumsolarStrength', [-Number.MIN_VALUE, 4.001]],
177
+ ['circumsolarWidth', [0.249, 4.001]],
168
178
  ] as const)('rejects %s values outside the shared shader domain', (field, values) => {
169
179
  for (const value of values) {
170
180
  const result = selection(
@@ -207,6 +217,8 @@ describe('Environment and Fog selection (M2)', () => {
207
217
  expect(baseline.value.source).toMatchObject({ atmosphere: atmosphereParameters });
208
218
  expect(baseline.value.signature).not.toBe(changed.value.signature);
209
219
  expect(baseline.value.signature).toContain('sunAngularRadius');
220
+ expect(baseline.value.signature).toContain('circumsolarStrength');
221
+ expect(baseline.value.signature).toContain('circumsolarWidth');
210
222
  });
211
223
 
212
224
  it('keeps Fog orthogonal and produces identical direct/clustered frame facts', () => {