@forgeax/engine-render 0.1.20 → 0.1.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (645) hide show
  1. package/README.md +365 -29
  2. package/dist/assembly/backend-contract.d.ts +13 -0
  3. package/dist/assembly/backend-contract.d.ts.map +1 -1
  4. package/dist/assembly/device-feature-admission.d.ts +18 -0
  5. package/dist/assembly/device-feature-admission.d.ts.map +1 -0
  6. package/dist/assembly/directional-shadow-inspection.d.ts +38 -0
  7. package/dist/assembly/directional-shadow-inspection.d.ts.map +1 -0
  8. package/dist/assembly/factory.d.ts +30 -19
  9. package/dist/assembly/factory.d.ts.map +1 -1
  10. package/dist/assembly/host-contract.d.ts +19 -0
  11. package/dist/assembly/host-contract.d.ts.map +1 -1
  12. package/dist/assembly/render-target-host.d.ts +46 -0
  13. package/dist/assembly/render-target-host.d.ts.map +1 -0
  14. package/dist/assets/render-asset-port.d.ts.map +1 -1
  15. package/dist/authoring.mjs +4 -1
  16. package/dist/authoring.mjs.map +1 -1
  17. package/dist/capture/cube-views.d.ts +21 -0
  18. package/dist/capture/cube-views.d.ts.map +1 -0
  19. package/dist/capture/scheduler.d.ts +54 -0
  20. package/dist/capture/scheduler.d.ts.map +1 -0
  21. package/dist/{chunk-PCHBPYML.mjs → chunk-4L3P6DT5.mjs} +2 -2
  22. package/dist/chunk-4L3P6DT5.mjs.map +1 -0
  23. package/dist/{chunk-VFPLTKCU.mjs → chunk-4NPUASZR.mjs} +15 -4
  24. package/dist/chunk-4NPUASZR.mjs.map +1 -0
  25. package/dist/chunk-DIYW3M5D.mjs +5085 -0
  26. package/dist/chunk-DIYW3M5D.mjs.map +1 -0
  27. package/dist/chunk-FBC4R44J.mjs +1003 -0
  28. package/dist/chunk-FBC4R44J.mjs.map +1 -0
  29. package/dist/{chunk-CDTWTHZR.mjs → chunk-FF7K3FSO.mjs} +474 -168
  30. package/dist/chunk-FF7K3FSO.mjs.map +1 -0
  31. package/dist/chunk-HZUPSYW7.mjs +1104 -0
  32. package/dist/chunk-HZUPSYW7.mjs.map +1 -0
  33. package/dist/{chunk-TBQJOK2L.mjs → chunk-QODA5UKU.mjs} +321 -269
  34. package/dist/chunk-QODA5UKU.mjs.map +1 -0
  35. package/dist/chunk-YALPS44A.mjs +296 -0
  36. package/dist/chunk-YALPS44A.mjs.map +1 -0
  37. package/dist/{chunk-4XINDLAY.mjs → chunk-ZUWKAZ2I.mjs} +1963 -2178
  38. package/dist/chunk-ZUWKAZ2I.mjs.map +1 -0
  39. package/dist/cluster-binner.d.ts +5 -0
  40. package/dist/cluster-binner.d.ts.map +1 -1
  41. package/dist/components/camera.d.ts +1 -0
  42. package/dist/components/camera.d.ts.map +1 -1
  43. package/dist/components/cube-camera.d.ts +20 -0
  44. package/dist/components/cube-camera.d.ts.map +1 -0
  45. package/dist/components/directional-light.d.ts +11 -2
  46. package/dist/components/directional-light.d.ts.map +1 -1
  47. package/dist/components/directional-shadow-filter.d.ts +25 -0
  48. package/dist/components/directional-shadow-filter.d.ts.map +1 -0
  49. package/dist/components/index.d.ts +5 -0
  50. package/dist/components/index.d.ts.map +1 -1
  51. package/dist/components/light-helpers.d.ts +4 -0
  52. package/dist/components/light-helpers.d.ts.map +1 -1
  53. package/dist/components/light-probe.d.ts +9 -0
  54. package/dist/components/light-probe.d.ts.map +1 -0
  55. package/dist/components/rect-area-light.d.ts +12 -0
  56. package/dist/components/rect-area-light.d.ts.map +1 -0
  57. package/dist/components/reflection-probe.d.ts +17 -0
  58. package/dist/components/reflection-probe.d.ts.map +1 -0
  59. package/dist/components/scene-instance.d.ts +4 -0
  60. package/dist/components/scene-instance.d.ts.map +1 -1
  61. package/dist/components/spot-light.d.ts +3 -0
  62. package/dist/components/spot-light.d.ts.map +1 -1
  63. package/dist/construct-renderer.mjs +14836 -9620
  64. package/dist/construct-renderer.mjs.map +1 -1
  65. package/dist/device/gpu-residency.d.ts +8 -1
  66. package/dist/device/gpu-residency.d.ts.map +1 -1
  67. package/dist/device/lifecycle-transaction.d.ts +2 -0
  68. package/dist/device/lifecycle-transaction.d.ts.map +1 -0
  69. package/dist/environment/frame.d.ts.map +1 -1
  70. package/dist/environment/lifecycle.d.ts +5 -0
  71. package/dist/environment/lifecycle.d.ts.map +1 -1
  72. package/dist/errors/gpu-driven.d.ts +26 -0
  73. package/dist/errors/gpu-driven.d.ts.map +1 -0
  74. package/dist/errors/index.d.ts +1 -0
  75. package/dist/errors/index.d.ts.map +1 -1
  76. package/dist/errors/recover.d.ts +9 -1
  77. package/dist/errors/recover.d.ts.map +1 -1
  78. package/dist/errors/render.d.ts +166 -93
  79. package/dist/errors/render.d.ts.map +1 -1
  80. package/dist/extract/directional-shadow-projection.d.ts +23 -0
  81. package/dist/extract/directional-shadow-projection.d.ts.map +1 -0
  82. package/dist/extract/environment.d.ts +14 -0
  83. package/dist/extract/environment.d.ts.map +1 -1
  84. package/dist/extract/gpu-driven.d.ts +50 -0
  85. package/dist/extract/gpu-driven.d.ts.map +1 -0
  86. package/dist/extract/visibility.d.ts.map +1 -1
  87. package/dist/features/fullscreen.d.ts.map +1 -1
  88. package/dist/features/targets.d.ts.map +1 -1
  89. package/dist/features/types.d.ts +10 -0
  90. package/dist/features/types.d.ts.map +1 -1
  91. package/dist/fullscreen-post-process-pass.d.ts +13 -2
  92. package/dist/fullscreen-post-process-pass.d.ts.map +1 -1
  93. package/dist/gpu-driven/batch-topology.d.ts +34 -0
  94. package/dist/gpu-driven/batch-topology.d.ts.map +1 -1
  95. package/dist/gpu-driven/prepared-draw.d.ts +63 -0
  96. package/dist/gpu-driven/prepared-draw.d.ts.map +1 -0
  97. package/dist/gpu-driven/production-raster.d.ts +97 -2
  98. package/dist/gpu-driven/production-raster.d.ts.map +1 -1
  99. package/dist/gpu-driven/view-gpu.d.ts +64 -1
  100. package/dist/gpu-driven/view-gpu.d.ts.map +1 -1
  101. package/dist/gpu-scene-schema.d.ts +14 -0
  102. package/dist/gpu-scene-schema.d.ts.map +1 -1
  103. package/dist/gpu-scene.d.ts +8 -1
  104. package/dist/gpu-scene.d.ts.map +1 -1
  105. package/dist/gpu-usage.d.ts +0 -1
  106. package/dist/gpu-usage.d.ts.map +1 -1
  107. package/dist/hdrp-buffers.d.ts +21 -19
  108. package/dist/hdrp-buffers.d.ts.map +1 -1
  109. package/dist/ibl/IblPipelineCache.d.ts +4 -0
  110. package/dist/ibl/IblPipelineCache.d.ts.map +1 -1
  111. package/dist/ibl/kernel-cache.d.ts +36 -0
  112. package/dist/ibl/kernel-cache.d.ts.map +1 -0
  113. package/dist/index.d.ts +39 -3
  114. package/dist/index.d.ts.map +1 -1
  115. package/dist/index.mjs +91 -6
  116. package/dist/index.mjs.map +1 -1
  117. package/dist/inspection-types.d.ts +214 -1
  118. package/dist/inspection-types.d.ts.map +1 -1
  119. package/dist/instances-derived-bounds.d.ts +50 -0
  120. package/dist/instances-derived-bounds.d.ts.map +1 -0
  121. package/dist/internal.d.ts +9 -0
  122. package/dist/internal.d.ts.map +1 -1
  123. package/dist/internal.mjs +23 -2
  124. package/dist/internal.mjs.map +1 -1
  125. package/dist/light-buffer-layout.d.ts +58 -140
  126. package/dist/light-buffer-layout.d.ts.map +1 -1
  127. package/dist/materials.d.ts.map +1 -1
  128. package/dist/mesh-material-bindings.d.ts +54 -0
  129. package/dist/mesh-material-bindings.d.ts.map +1 -1
  130. package/dist/pbr-pipeline.d.ts +38 -14
  131. package/dist/pbr-pipeline.d.ts.map +1 -1
  132. package/dist/pipeline/standard-forward-lane.d.ts +2 -1
  133. package/dist/pipeline/standard-forward-lane.d.ts.map +1 -1
  134. package/dist/pipeline/standard-lighting/errors.d.ts +4 -0
  135. package/dist/pipeline/standard-lighting/errors.d.ts.map +1 -0
  136. package/dist/pipeline/standard-lighting/graph.d.ts +24 -0
  137. package/dist/pipeline/standard-lighting/graph.d.ts.map +1 -0
  138. package/dist/pipeline/standard-lighting/inspection.d.ts +38 -0
  139. package/dist/pipeline/standard-lighting/inspection.d.ts.map +1 -0
  140. package/dist/pipeline/standard-lighting/layout.d.ts +28 -0
  141. package/dist/pipeline/standard-lighting/layout.d.ts.map +1 -0
  142. package/dist/pipeline/standard-lighting/light-frame.d.ts +33 -0
  143. package/dist/pipeline/standard-lighting/light-frame.d.ts.map +1 -0
  144. package/dist/pipeline/standard-lighting/membership.d.ts +14 -0
  145. package/dist/pipeline/standard-lighting/membership.d.ts.map +1 -0
  146. package/dist/pipeline/standard-lighting/prepare.d.ts +16 -0
  147. package/dist/pipeline/standard-lighting/prepare.d.ts.map +1 -0
  148. package/dist/pipeline/standard-lighting/topology.d.ts +45 -0
  149. package/dist/pipeline/standard-lighting/topology.d.ts.map +1 -0
  150. package/dist/pipeline/standard-lighting/transport.d.ts +36 -0
  151. package/dist/pipeline/standard-lighting/transport.d.ts.map +1 -0
  152. package/dist/pipeline/standard-pipeline.d.ts +4 -13
  153. package/dist/pipeline/standard-pipeline.d.ts.map +1 -1
  154. package/dist/pipeline/standard-post.d.ts.map +1 -1
  155. package/dist/pipeline/standard-profile.d.ts +5 -26
  156. package/dist/pipeline/standard-profile.d.ts.map +1 -1
  157. package/dist/pipeline-builder.d.ts +2 -0
  158. package/dist/pipeline-builder.d.ts.map +1 -1
  159. package/dist/pipeline-spec.d.ts +40 -7
  160. package/dist/pipeline-spec.d.ts.map +1 -1
  161. package/dist/plugin.d.ts.map +1 -1
  162. package/dist/point-shadow-inspection.d.ts +21 -0
  163. package/dist/point-shadow-inspection.d.ts.map +1 -0
  164. package/dist/prepare/extended-lighting/ltc-resources.d.ts +31 -0
  165. package/dist/prepare/extended-lighting/ltc-resources.d.ts.map +1 -0
  166. package/dist/prepare/extended-lighting/resources.d.ts +72 -0
  167. package/dist/prepare/extended-lighting/resources.d.ts.map +1 -0
  168. package/dist/prepare/extended-lighting/spot-modifiers.d.ts +42 -0
  169. package/dist/prepare/extended-lighting/spot-modifiers.d.ts.map +1 -0
  170. package/dist/prepare/extended-lighting/state.d.ts +24 -0
  171. package/dist/prepare/extended-lighting/state.d.ts.map +1 -0
  172. package/dist/record/frame-lighting.d.ts +33 -36
  173. package/dist/record/frame-lighting.d.ts.map +1 -1
  174. package/dist/record/frame-snapshot.d.ts +78 -24
  175. package/dist/record/frame-snapshot.d.ts.map +1 -1
  176. package/dist/record/frame-targets.d.ts +5 -0
  177. package/dist/record/frame-targets.d.ts.map +1 -1
  178. package/dist/record/frame.d.ts +16 -6
  179. package/dist/record/frame.d.ts.map +1 -1
  180. package/dist/record/gpu-pass-timing/contract.d.ts +96 -0
  181. package/dist/record/gpu-pass-timing/contract.d.ts.map +1 -0
  182. package/dist/record/gpu-pass-timing/errors.d.ts +18 -0
  183. package/dist/record/gpu-pass-timing/errors.d.ts.map +1 -0
  184. package/dist/record/gpu-pass-timing/index.d.ts +8 -0
  185. package/dist/record/gpu-pass-timing/index.d.ts.map +1 -0
  186. package/dist/record/gpu-pass-timing/parser.d.ts +11 -0
  187. package/dist/record/gpu-pass-timing/parser.d.ts.map +1 -0
  188. package/dist/record/gpu-pass-timing/session.d.ts +43 -0
  189. package/dist/record/gpu-pass-timing/session.d.ts.map +1 -0
  190. package/dist/record/gpu-timing.d.ts +20 -3
  191. package/dist/record/gpu-timing.d.ts.map +1 -1
  192. package/dist/record/helpers.d.ts +0 -20
  193. package/dist/record/helpers.d.ts.map +1 -1
  194. package/dist/record/main-pass-geometry.d.ts +6 -1
  195. package/dist/record/main-pass-geometry.d.ts.map +1 -1
  196. package/dist/record/main-pass-material.d.ts +25 -2
  197. package/dist/record/main-pass-material.d.ts.map +1 -1
  198. package/dist/record/main-pass-sprite-draws.d.ts.map +1 -1
  199. package/dist/record/main-pass.d.ts +2 -0
  200. package/dist/record/main-pass.d.ts.map +1 -1
  201. package/dist/record/mesh-ssbo.d.ts.map +1 -1
  202. package/dist/record/render-context.d.ts +98 -6
  203. package/dist/record/render-context.d.ts.map +1 -1
  204. package/dist/record/shadow-pass.d.ts.map +1 -1
  205. package/dist/record/skybox-post-pass.d.ts.map +1 -1
  206. package/dist/record/typed-frame-graph.d.ts +119 -4
  207. package/dist/record/typed-frame-graph.d.ts.map +1 -1
  208. package/dist/record/view-ubo.d.ts +7 -5
  209. package/dist/record/view-ubo.d.ts.map +1 -1
  210. package/dist/reflection/filter.d.ts +22 -0
  211. package/dist/reflection/filter.d.ts.map +1 -0
  212. package/dist/reflection/gpu-table.d.ts +25 -0
  213. package/dist/reflection/gpu-table.d.ts.map +1 -0
  214. package/dist/reflection/inspection.d.ts +10 -0
  215. package/dist/reflection/inspection.d.ts.map +1 -0
  216. package/dist/reflection/projection.d.ts +140 -0
  217. package/dist/reflection/projection.d.ts.map +1 -0
  218. package/dist/reflection/record-owner.d.ts +60 -0
  219. package/dist/reflection/record-owner.d.ts.map +1 -0
  220. package/dist/render-contract.d.ts +62 -14
  221. package/dist/render-contract.d.ts.map +1 -1
  222. package/dist/render-graph-primitives.d.ts +1 -0
  223. package/dist/render-graph-primitives.d.ts.map +1 -1
  224. package/dist/render-pipeline.d.ts +49 -2
  225. package/dist/render-pipeline.d.ts.map +1 -1
  226. package/dist/render-system-extract.d.ts +160 -31
  227. package/dist/render-system-extract.d.ts.map +1 -1
  228. package/dist/render-system.d.ts +28 -2
  229. package/dist/render-system.d.ts.map +1 -1
  230. package/dist/renderer.d.ts +43 -0
  231. package/dist/renderer.d.ts.map +1 -0
  232. package/dist/scene/probe-blend-record.d.ts +22 -0
  233. package/dist/scene/probe-blend-record.d.ts.map +1 -0
  234. package/dist/scene/probe-blend.d.ts +142 -0
  235. package/dist/scene/probe-blend.d.ts.map +1 -0
  236. package/dist/scene/render-scene-types.d.ts +0 -6
  237. package/dist/scene/render-scene-types.d.ts.map +1 -1
  238. package/dist/scene/render-scene.d.ts +117 -9
  239. package/dist/scene/render-scene.d.ts.map +1 -1
  240. package/dist/scene/visibility/bounds.d.ts +20 -0
  241. package/dist/scene/visibility/bounds.d.ts.map +1 -0
  242. package/dist/scene/visibility/budget.d.ts +16 -0
  243. package/dist/scene/visibility/budget.d.ts.map +1 -0
  244. package/dist/scene/visibility/facet.d.ts +73 -0
  245. package/dist/scene/visibility/facet.d.ts.map +1 -0
  246. package/dist/scene/visibility/gpu-frame-timing.d.ts +41 -0
  247. package/dist/scene/visibility/gpu-frame-timing.d.ts.map +1 -0
  248. package/dist/scene/visibility/gpu-lod.d.ts +46 -0
  249. package/dist/scene/visibility/gpu-lod.d.ts.map +1 -0
  250. package/dist/scene/visibility/inspection.d.ts +132 -0
  251. package/dist/scene/visibility/inspection.d.ts.map +1 -0
  252. package/dist/scene/visibility/lod-selector.d.ts +25 -0
  253. package/dist/scene/visibility/lod-selector.d.ts.map +1 -0
  254. package/dist/scene/visibility/occlusion-confidence.d.ts +81 -0
  255. package/dist/scene/visibility/occlusion-confidence.d.ts.map +1 -0
  256. package/dist/scene/visibility/occlusion-pass.d.ts +57 -0
  257. package/dist/scene/visibility/occlusion-pass.d.ts.map +1 -0
  258. package/dist/scene/visibility/occlusion-query-pool.d.ts +57 -0
  259. package/dist/scene/visibility/occlusion-query-pool.d.ts.map +1 -0
  260. package/dist/scene/visibility/occlusion-runtime.d.ts +96 -0
  261. package/dist/scene/visibility/occlusion-runtime.d.ts.map +1 -0
  262. package/dist/scene/visibility/types.d.ts +34 -0
  263. package/dist/scene/visibility/types.d.ts.map +1 -0
  264. package/dist/shadow-atlas.d.ts +3 -3
  265. package/dist/shadow-atlas.d.ts.map +1 -1
  266. package/dist/ssao-buffers.d.ts +3 -0
  267. package/dist/ssao-buffers.d.ts.map +1 -1
  268. package/dist/ssr/admission.d.ts +122 -0
  269. package/dist/ssr/admission.d.ts.map +1 -0
  270. package/dist/ssr/composition.d.ts +32 -0
  271. package/dist/ssr/composition.d.ts.map +1 -0
  272. package/dist/ssr/errors.d.ts +39 -0
  273. package/dist/ssr/errors.d.ts.map +1 -0
  274. package/dist/ssr/identity.d.ts +13 -0
  275. package/dist/ssr/identity.d.ts.map +1 -0
  276. package/dist/systems/skin-palette-allocator.d.ts +1 -1
  277. package/dist/targets/contracts.d.ts +66 -0
  278. package/dist/targets/contracts.d.ts.map +1 -0
  279. package/dist/targets/graph-projection.d.ts +29 -0
  280. package/dist/targets/graph-projection.d.ts.map +1 -0
  281. package/dist/targets/material-source.d.ts +34 -0
  282. package/dist/targets/material-source.d.ts.map +1 -0
  283. package/dist/targets/owner.d.ts +32 -0
  284. package/dist/targets/owner.d.ts.map +1 -0
  285. package/dist/targets/physical.d.ts +18 -0
  286. package/dist/targets/physical.d.ts.map +1 -0
  287. package/dist/targets/readback.d.ts +39 -0
  288. package/dist/targets/readback.d.ts.map +1 -0
  289. package/dist/temporal/frame-coordinator.d.ts +13 -0
  290. package/dist/temporal/frame-coordinator.d.ts.map +1 -1
  291. package/dist/temporal/frame.d.ts +2 -0
  292. package/dist/temporal/frame.d.ts.map +1 -1
  293. package/dist/temporal/index.mjs +6 -5
  294. package/dist/temporal/view.d.ts +7 -0
  295. package/dist/temporal/view.d.ts.map +1 -1
  296. package/dist/tilemap-chunk-extract-system.d.ts +1 -1
  297. package/dist/typed-render-graph-primitives.d.ts +5 -1
  298. package/dist/typed-render-graph-primitives.d.ts.map +1 -1
  299. package/dist/typed-shadow-passes.d.ts +1 -1
  300. package/dist/typed-shadow-passes.d.ts.map +1 -1
  301. package/dist/volume/passes.d.ts +9 -5
  302. package/dist/volume/passes.d.ts.map +1 -1
  303. package/package.json +20 -20
  304. package/src/__tests__/bloom-exact-zero.unit.test.ts +48 -19
  305. package/src/__tests__/bloom-lifecycle.integration.test.ts +41 -13
  306. package/src/__tests__/camera-target-consumption.unit.test.ts +53 -0
  307. package/src/__tests__/cluster-binner.unit.test.ts +35 -0
  308. package/src/__tests__/cluster-grid-default-surface.unit.test.ts +12 -2
  309. package/src/__tests__/cube-camera-scheduler.unit.test.ts +70 -0
  310. package/src/__tests__/cube-camera-single-submit.perf.test.ts +75 -0
  311. package/src/__tests__/cube-camera-view-matrices.unit.test.ts +41 -0
  312. package/src/__tests__/cube-camera-vocabulary.unit.test.ts +47 -0
  313. package/src/__tests__/cube-camera.integration.test.ts +42 -0
  314. package/src/__tests__/detached-inspection.integration.test.ts +6 -0
  315. package/src/__tests__/device-feature-admission.unit.test.ts +85 -0
  316. package/src/__tests__/direct-light-snapshot.unit.test.ts +62 -6
  317. package/src/__tests__/docs-gate-green.unit.test.ts +38 -0
  318. package/src/__tests__/environment-generation.integration.test.ts +16 -0
  319. package/src/__tests__/evidence-identity.ts +13 -0
  320. package/src/__tests__/evidence-identity.unit.test.ts +24 -0
  321. package/src/__tests__/extended-lighting-recovery.browser.test.ts +14 -0
  322. package/src/__tests__/extended-lighting-recovery.dawn.test.ts +14 -0
  323. package/src/__tests__/extended-lighting-recovery.integration.test.ts +81 -0
  324. package/src/__tests__/extended-lighting-resources.unit.test.ts +51 -0
  325. package/src/__tests__/extended-lighting-zero-feature.integration.test.ts +52 -0
  326. package/src/__tests__/factory-contract.integration.test.ts +288 -29
  327. package/src/__tests__/fog-component.unit.test.ts +67 -0
  328. package/src/__tests__/fog.integration.test.ts +86 -0
  329. package/src/__tests__/frame-observation.contract.test.ts +15 -0
  330. package/src/__tests__/fullscreen-feature-plan.unit.test.ts +37 -0
  331. package/src/__tests__/gpu-driven-batch-topology-pbr.unit.test.ts +146 -0
  332. package/src/__tests__/gpu-driven-indirect-raster-evidence.ts +10 -8
  333. package/src/__tests__/gpu-driven-indirect-raster.dawn.test.ts +18 -1
  334. package/src/__tests__/gpu-driven-production.integration.test.ts +579 -17
  335. package/src/__tests__/gpu-driven-scaling.unit.test.ts +66 -2
  336. package/src/__tests__/gpu-driven-view-gpu-evidence.ts +26 -1
  337. package/src/__tests__/gpu-driven-view-graph.integration.test.ts +101 -6
  338. package/src/__tests__/gpu-driven-view.browser.test.ts +3 -3
  339. package/src/__tests__/gpu-driven-view.dawn.test.ts +3 -3
  340. package/src/__tests__/gpu-driven-view.unit.test.ts +50 -0
  341. package/src/__tests__/gpu-lod-evidence.ts +188 -0
  342. package/src/__tests__/gpu-lod-fallback.integration.test.ts +10 -0
  343. package/src/__tests__/gpu-lod-indirect.integration.test.ts +40 -0
  344. package/src/__tests__/gpu-lod-parity.dawn.test.ts +29 -0
  345. package/src/__tests__/gpu-lod-readiness.integration.test.ts +52 -0
  346. package/src/__tests__/gpu-lod-schema.unit.test.ts +66 -0
  347. package/src/__tests__/gpu-lod-selector.unit.test.ts +28 -0
  348. package/src/__tests__/gpu-lod-silhouette.browser.test.ts +13 -0
  349. package/src/__tests__/gpu-pass-timing-browser-runner.ts +281 -0
  350. package/src/__tests__/gpu-pass-timing-coexistence.integration.test.ts +80 -0
  351. package/src/__tests__/gpu-pass-timing-contract.unit.test.ts +243 -0
  352. package/src/__tests__/gpu-pass-timing-exact-zero.unit.test.ts +131 -0
  353. package/src/__tests__/gpu-pass-timing-lifecycle.integration.test.ts +230 -0
  354. package/src/__tests__/gpu-pass-timing-observation.unit.test.ts +233 -0
  355. package/src/__tests__/gpu-pass-timing-session.unit.test.ts +438 -0
  356. package/src/__tests__/gpu-pass-timing.browser.test.ts +26 -0
  357. package/src/__tests__/gpu-pass-timing.rhi-null.unit.test.ts +125 -0
  358. package/src/__tests__/gpu-resource-store-stride.unit.test.ts +66 -1
  359. package/src/__tests__/gpu-scene.dawn.test.ts +8 -1
  360. package/src/__tests__/gpu-scene.unit.test.ts +49 -3
  361. package/src/__tests__/hdrp-group2-visibility.unit.test.ts +24 -0
  362. package/src/__tests__/instances-culling.integration.test.ts +94 -0
  363. package/src/__tests__/instances-derived-bounds.unit.test.ts +118 -0
  364. package/src/__tests__/light-buffer-layout.unit.test.ts +113 -3
  365. package/src/__tests__/light-inspection.unit.test.ts +55 -0
  366. package/src/__tests__/light-resource-error.unit.test.ts +43 -0
  367. package/src/__tests__/lighting-scene-roundtrip.integration.test.ts +109 -0
  368. package/src/__tests__/lod-bounds.unit.test.ts +42 -0
  369. package/src/__tests__/lod-cpu-lane.integration.test.ts +55 -0
  370. package/src/__tests__/lod-cpu-no-change.perf.test.ts +19 -0
  371. package/src/__tests__/lod-gpu-frame-timing.unit.test.ts +49 -0
  372. package/src/__tests__/lod-multi-view.integration.test.ts +63 -0
  373. package/src/__tests__/lod-occlusion-device-loss.integration.test.ts +27 -0
  374. package/src/__tests__/lod-occlusion-final-decision.integration.test.ts +47 -0
  375. package/src/__tests__/lod-occlusion-inspection.integration.test.ts +217 -0
  376. package/src/__tests__/lod-occlusion-inspection.schema.test-d.ts +14 -0
  377. package/src/__tests__/lod-occlusion-inspection.unit.test.ts +72 -0
  378. package/src/__tests__/lod-occlusion-visual-evidence.ts +91 -0
  379. package/src/__tests__/lod-occlusion.browser.test.ts +15 -0
  380. package/src/__tests__/lod-occlusion.dawn.test.ts +20 -0
  381. package/src/__tests__/lod-selector.profile.test.ts +35 -0
  382. package/src/__tests__/lod-selector.unit.test.ts +68 -0
  383. package/src/__tests__/lod-visibility-identity.unit.test.ts +198 -0
  384. package/src/__tests__/lod-visibility-lifecycle.integration.test.ts +132 -0
  385. package/src/__tests__/ltc-resources.unit.test.ts +32 -0
  386. package/src/__tests__/material-contract-inventory.unit.test.ts +17 -0
  387. package/src/__tests__/material-observation.integration.test.ts +101 -0
  388. package/src/__tests__/occlusion-confidence.unit.test.ts +168 -0
  389. package/src/__tests__/occlusion-invalidation.integration.test.ts +37 -0
  390. package/src/__tests__/occlusion-pass-routing.unit.test.ts +16 -0
  391. package/src/__tests__/occlusion-pass.dawn.test.ts +20 -0
  392. package/src/__tests__/occlusion-pass.integration.test.ts +43 -0
  393. package/src/__tests__/occlusion-pass.rhi-null.test.ts +22 -0
  394. package/src/__tests__/occlusion-query-pool.rhi-null.test.ts +21 -0
  395. package/src/__tests__/occlusion-query-pool.unit.test.ts +95 -0
  396. package/src/__tests__/occlusion-query-ticket.integration.test.ts +47 -0
  397. package/src/__tests__/occlusion-query.dawn.test.ts +16 -0
  398. package/src/__tests__/occlusion-query.evidence.ts +178 -0
  399. package/src/__tests__/occlusion-query.rhi-null.test.ts +31 -0
  400. package/src/__tests__/occlusion-runtime.unit.test.ts +120 -0
  401. package/src/__tests__/pbr-view-layout.unit.test.ts +16 -0
  402. package/src/__tests__/point-shadow-inspection.unit.test.ts +60 -0
  403. package/src/__tests__/prepared-gpu-driven-pbr.integration.test.ts +196 -0
  404. package/src/__tests__/probe-bindings.unit.test.ts +200 -0
  405. package/src/__tests__/probe-blend-numeric.unit.test.ts +67 -0
  406. package/src/__tests__/probe-blend-oracle.unit.test.ts +136 -0
  407. package/src/__tests__/probe-capacity.integration.test.ts +97 -0
  408. package/src/__tests__/probe-recovery.integration.test.ts +104 -0
  409. package/src/__tests__/probe-scene-dirty.integration.test.ts +211 -0
  410. package/src/__tests__/profiler-phase-catalog.test.ts +4 -1
  411. package/src/__tests__/profiler-phase-token.test.ts +1 -0
  412. package/src/__tests__/public-surface.test-d.ts +10 -0
  413. package/src/__tests__/rect-area-boundary.unit.test.ts +24 -0
  414. package/src/__tests__/reflection-fallback-graph.unit.test.ts +67 -0
  415. package/src/__tests__/reflection-fallback-inspection.unit.test.ts +77 -0
  416. package/src/__tests__/reflection-fallback-lifecycle.integration.test.ts +21 -0
  417. package/src/__tests__/reflection-fallback-projection.unit.test.ts +90 -0
  418. package/src/__tests__/reflection-fallback-source.unit.test.ts +32 -0
  419. package/src/__tests__/reflection-probe-authoring.unit.test.ts +71 -0
  420. package/src/__tests__/reflection-probe-filter.unit.test.ts +54 -0
  421. package/src/__tests__/reflection-probe-ibl-topology.unit.test.ts +45 -0
  422. package/src/__tests__/reflection-probe-owner-placement.unit.test.ts +39 -0
  423. package/src/__tests__/reflection-probe-pass-encode.integration.test.ts +101 -0
  424. package/src/__tests__/reflection-probe-performance.perf.test.ts +60 -0
  425. package/src/__tests__/reflection-probe-record-owner.characterization.unit.test.ts +120 -0
  426. package/src/__tests__/reflection-probe-recovery.integration.test.ts +55 -0
  427. package/src/__tests__/reflection-probe-selection.unit.test.ts +46 -0
  428. package/src/__tests__/render-error-code-owner.test-d.ts +19 -3
  429. package/src/__tests__/render-feature-docs.unit.test.ts +12 -7
  430. package/src/__tests__/render-graph-fallback.integration.test.ts +24 -0
  431. package/src/__tests__/render-perf/extended-lighting.bench.test.ts +66 -0
  432. package/src/__tests__/render-scene-projection.unit.test.ts +447 -18
  433. package/src/__tests__/render-target-admission.unit.test.ts +80 -0
  434. package/src/__tests__/render-target-graph-projection.unit.test.ts +102 -0
  435. package/src/__tests__/render-target-inspection.unit.test.ts +52 -0
  436. package/src/__tests__/render-target-material-source.test-d.ts +30 -0
  437. package/src/__tests__/render-target-material-source.unit.test.ts +97 -0
  438. package/src/__tests__/render-target-owner-placement.unit.test.ts +203 -0
  439. package/src/__tests__/render-target-owner.unit.test.ts +122 -0
  440. package/src/__tests__/render-target-probe-soak.perf.test.ts +94 -0
  441. package/src/__tests__/render-target-public-schema.json +107 -0
  442. package/src/__tests__/render-target-public-schema.unit.test.ts +91 -0
  443. package/src/__tests__/render-target-public-surface.test-d.ts +83 -0
  444. package/src/__tests__/render-target-readback.integration.test.ts +90 -0
  445. package/src/__tests__/render-target-recovery.integration.test.ts +85 -0
  446. package/src/__tests__/renderer-factory-material-contract.unit.test.ts +124 -0
  447. package/src/__tests__/renderer-lifecycle.integration.test.ts +14 -0
  448. package/src/__tests__/shader-stage-surface.unit.test.ts +19 -4
  449. package/src/__tests__/skin-motion-regression.unit.test.ts +154 -0
  450. package/src/__tests__/skinned-shadow-caster.test.ts +13 -4
  451. package/src/__tests__/spot-modifier-projection.unit.test.ts +70 -0
  452. package/src/__tests__/ssr-admission-matrix.integration.test.ts +206 -0
  453. package/src/__tests__/ssr-composition.unit.test.ts +46 -0
  454. package/src/__tests__/ssr-fallback-zero-work.bench.test.ts +94 -0
  455. package/src/__tests__/ssr-recovery-errors.unit.test.ts +58 -0
  456. package/src/__tests__/standard-cluster-capability.integration.test.ts +63 -0
  457. package/src/__tests__/standard-cluster-transport.unit.test.ts +121 -0
  458. package/src/__tests__/standard-cluster-variant-resolution.unit.test.ts +299 -0
  459. package/src/__tests__/standard-clustered-pipeline.unit.test.ts +15 -5
  460. package/src/__tests__/standard-lighting-inspection.integration.test.ts +138 -0
  461. package/src/__tests__/standard-lighting-ssot.unit.test.ts +18 -0
  462. package/src/__tests__/standard-lighting-write.unit.test.ts +441 -0
  463. package/src/__tests__/standard-lighting.integration.test.ts +67 -0
  464. package/src/__tests__/standard-pipeline.dawn.test.ts +206 -5
  465. package/src/__tests__/standard-pipeline.integration.test.ts +181 -150
  466. package/src/__tests__/standard-probe-binding.unit.test.ts +25 -0
  467. package/src/__tests__/standard-profile-oracle.unit.test.ts +23 -38
  468. package/src/__tests__/temporal-fallback-generation.integration.test.ts +16 -0
  469. package/src/__tests__/temporal-view.unit.test.ts +3 -3
  470. package/src/__tests__/transmission-prewarm.unit.test.ts +69 -0
  471. package/src/__tests__/typed-pipeline-topology.unit.test.ts +395 -14
  472. package/src/__tests__/urp-pipeline-targets.unit.test.ts +2 -2
  473. package/src/__tests__/visibility-budget.integration.test.ts +140 -0
  474. package/src/__tests__/visibility-budget.unit.test.ts +70 -0
  475. package/src/__tests__/visibility-extract.integration.test.ts +11 -6
  476. package/src/__tests__/visibility-resolver.unit.test.ts +15 -0
  477. package/src/__tests__/volumetric-fog-point-spot-accumulation.integration.test.ts +6 -3
  478. package/src/__tests__/volumetric-fog-stage-readback.dawn.test.ts +31 -28
  479. package/src/assembly/backend-contract.ts +13 -0
  480. package/src/assembly/device-feature-admission.ts +80 -0
  481. package/src/assembly/directional-shadow-inspection.ts +122 -0
  482. package/src/assembly/factory.ts +1411 -366
  483. package/src/assembly/host-contract.ts +35 -0
  484. package/src/assembly/render-target-host.ts +847 -0
  485. package/src/assets/asset-decoders.ts +1 -1
  486. package/src/assets/render-asset-port.ts +7 -1
  487. package/src/capture/cube-views.ts +69 -0
  488. package/src/capture/scheduler.ts +265 -0
  489. package/src/cluster-binner.ts +31 -0
  490. package/src/components/__tests__/lighting-authoring.unit.test.ts +39 -0
  491. package/src/components/__tests__/rect-area-light.unit.test.ts +33 -0
  492. package/src/components/__tests__/schema-contract.unit.test.ts +12 -0
  493. package/src/components/__tests__/schema-parity.unit.test.ts +9 -0
  494. package/src/components/camera.ts +4 -0
  495. package/src/components/cube-camera.ts +52 -0
  496. package/src/components/directional-light.ts +22 -2
  497. package/src/components/directional-shadow-filter.ts +57 -0
  498. package/src/components/index.ts +5 -0
  499. package/src/components/light-helpers.ts +133 -8
  500. package/src/components/light-probe.ts +13 -0
  501. package/src/components/rect-area-light.ts +15 -0
  502. package/src/components/reflection-probe.ts +49 -0
  503. package/src/components/scene-instance.ts +6 -0
  504. package/src/components/spot-light.ts +3 -0
  505. package/src/device/__tests__/extended-lighting-recovery.unit.test.ts +24 -0
  506. package/src/device/gpu-residency.ts +140 -9
  507. package/src/device/lifecycle-transaction.ts +12 -0
  508. package/src/environment/frame.ts +6 -24
  509. package/src/environment/lifecycle.ts +20 -1
  510. package/src/errors/gpu-driven.ts +92 -0
  511. package/src/errors/index.ts +1 -0
  512. package/src/errors/recover.ts +32 -0
  513. package/src/errors/render.ts +341 -143
  514. package/src/extract/camera.ts +3 -3
  515. package/src/extract/directional-shadow-projection.ts +61 -0
  516. package/src/extract/environment.ts +57 -0
  517. package/src/extract/gpu-driven.ts +108 -0
  518. package/src/extract/visibility.ts +2 -1
  519. package/src/features/fullscreen.ts +3 -0
  520. package/src/features/targets.ts +4 -1
  521. package/src/features/types.ts +10 -0
  522. package/src/fullscreen-post-process-pass.ts +43 -2
  523. package/src/gpu-driven/batch-topology.ts +207 -28
  524. package/src/gpu-driven/prepared-draw.ts +208 -0
  525. package/src/gpu-driven/production-raster.ts +953 -43
  526. package/src/gpu-driven/view-gpu.ts +411 -29
  527. package/src/gpu-scene-schema.ts +13 -0
  528. package/src/gpu-scene.ts +140 -64
  529. package/src/gpu-usage.ts +0 -1
  530. package/src/hdrp-buffers.ts +169 -63
  531. package/src/ibl/IblPipelineCache.ts +11 -0
  532. package/src/ibl/kernel-cache.ts +88 -0
  533. package/src/index.ts +222 -0
  534. package/src/inspection-types.ts +276 -1
  535. package/src/instances-derived-bounds.ts +171 -0
  536. package/src/internal.ts +38 -0
  537. package/src/light-buffer-layout.ts +155 -310
  538. package/src/materials.ts +16 -2
  539. package/src/mesh-material-bindings.ts +132 -0
  540. package/src/pbr-pipeline.ts +255 -60
  541. package/src/pipeline/standard-forward-lane.ts +93 -16
  542. package/src/pipeline/standard-lighting/errors.ts +6 -0
  543. package/src/pipeline/standard-lighting/graph.ts +158 -0
  544. package/src/pipeline/standard-lighting/inspection.ts +88 -0
  545. package/src/pipeline/standard-lighting/layout.ts +31 -0
  546. package/src/pipeline/standard-lighting/light-frame.ts +35 -0
  547. package/src/pipeline/standard-lighting/membership.ts +43 -0
  548. package/src/pipeline/standard-lighting/prepare.ts +37 -0
  549. package/src/pipeline/standard-lighting/topology.ts +120 -0
  550. package/src/pipeline/standard-lighting/transport.ts +78 -0
  551. package/src/pipeline/standard-pipeline.ts +124 -193
  552. package/src/pipeline/standard-post.ts +26 -7
  553. package/src/pipeline/standard-profile.ts +17 -47
  554. package/src/pipeline-builder.ts +5 -2
  555. package/src/pipeline-spec.ts +95 -21
  556. package/src/plugin.ts +4 -0
  557. package/src/point-shadow-inspection.ts +55 -0
  558. package/src/prepare/__tests__/extended-lighting-capability.unit.test.ts +56 -0
  559. package/src/prepare/extended-lighting/ltc-resources.ts +136 -0
  560. package/src/prepare/extended-lighting/resources.ts +389 -0
  561. package/src/prepare/extended-lighting/spot-modifiers.ts +194 -0
  562. package/src/prepare/extended-lighting/state.ts +92 -0
  563. package/src/record/__tests__/gpu-timing.unit.test.ts +177 -0
  564. package/src/record/__tests__/temporal-payload.unit.test.ts +64 -1
  565. package/src/record/__tests__/transparent-group2-selection.unit.test.ts +73 -0
  566. package/src/record/__tests__/typed-frame-graph-plan.unit.test.ts +20 -1
  567. package/src/record/__tests__/view-ubo-layout.unit.test.ts +40 -0
  568. package/src/record/frame-lighting.ts +591 -452
  569. package/src/record/frame-snapshot.ts +92 -39
  570. package/src/record/frame-targets.ts +17 -0
  571. package/src/record/frame.ts +662 -158
  572. package/src/record/gpu-pass-timing/contract.ts +180 -0
  573. package/src/record/gpu-pass-timing/errors.ts +34 -0
  574. package/src/record/gpu-pass-timing/index.ts +37 -0
  575. package/src/record/gpu-pass-timing/parser.ts +97 -0
  576. package/src/record/gpu-pass-timing/session.ts +938 -0
  577. package/src/record/gpu-timing.ts +112 -39
  578. package/src/record/helpers.ts +0 -58
  579. package/src/record/main-pass-geometry.ts +324 -119
  580. package/src/record/main-pass-material.ts +103 -10
  581. package/src/record/main-pass-sprite-draws.ts +99 -103
  582. package/src/record/main-pass.ts +71 -15
  583. package/src/record/mesh-ssbo.ts +47 -36
  584. package/src/record/render-context.ts +136 -3
  585. package/src/record/shadow-pass.ts +83 -68
  586. package/src/record/skybox-post-pass.ts +20 -4
  587. package/src/record/typed-frame-graph.ts +1034 -43
  588. package/src/record/view-ubo.ts +72 -47
  589. package/src/reflection/filter.ts +81 -0
  590. package/src/reflection/gpu-table.ts +44 -0
  591. package/src/reflection/inspection.ts +67 -0
  592. package/src/reflection/projection.ts +476 -0
  593. package/src/reflection/record-owner.ts +1283 -0
  594. package/src/render-contract.ts +93 -10
  595. package/src/render-graph-primitives.ts +9 -4
  596. package/src/render-pipeline.ts +122 -2
  597. package/src/render-system-extract.ts +1041 -225
  598. package/src/render-system.ts +1310 -85
  599. package/src/renderer.ts +222 -0
  600. package/src/scene/__tests__/changed-block-summary.candidate.test.ts +36 -0
  601. package/src/scene/probe-blend-record.ts +54 -0
  602. package/src/scene/probe-blend.ts +813 -0
  603. package/src/scene/render-scene-types.ts +0 -7
  604. package/src/scene/render-scene.ts +942 -171
  605. package/src/scene/visibility/bounds.ts +57 -0
  606. package/src/scene/visibility/budget.ts +40 -0
  607. package/src/scene/visibility/facet.ts +400 -0
  608. package/src/scene/visibility/gpu-frame-timing.ts +101 -0
  609. package/src/scene/visibility/gpu-lod.ts +119 -0
  610. package/src/scene/visibility/inspection.ts +313 -0
  611. package/src/scene/visibility/lod-selector.ts +84 -0
  612. package/src/scene/visibility/occlusion-confidence.ts +478 -0
  613. package/src/scene/visibility/occlusion-pass.ts +165 -0
  614. package/src/scene/visibility/occlusion-query-pool.ts +178 -0
  615. package/src/scene/visibility/occlusion-runtime.ts +672 -0
  616. package/src/scene/visibility/types.ts +50 -0
  617. package/src/shadow-atlas.ts +5 -5
  618. package/src/ssao-buffers.ts +9 -2
  619. package/src/ssr/admission.ts +451 -0
  620. package/src/ssr/composition.ts +73 -0
  621. package/src/ssr/errors.ts +76 -0
  622. package/src/ssr/identity.ts +12 -0
  623. package/src/systems/skin-palette-allocator.ts +1 -1
  624. package/src/targets/contracts.ts +182 -0
  625. package/src/targets/graph-projection.ts +169 -0
  626. package/src/targets/material-source.ts +143 -0
  627. package/src/targets/owner.ts +214 -0
  628. package/src/targets/physical.ts +18 -0
  629. package/src/targets/readback.ts +153 -0
  630. package/src/temporal/frame-coordinator.ts +26 -0
  631. package/src/temporal/frame.ts +14 -1
  632. package/src/temporal/view.ts +11 -0
  633. package/src/tilemap-chunk-extract-system.ts +4 -4
  634. package/src/transmission/__tests__/standard-transmission.dawn.test.ts +59 -20
  635. package/src/typed-render-graph-primitives.ts +53 -3
  636. package/src/typed-shadow-passes.ts +46 -40
  637. package/src/volume/passes.ts +61 -58
  638. package/dist/.tsbuildinfo +0 -1
  639. package/dist/chunk-4XINDLAY.mjs.map +0 -1
  640. package/dist/chunk-CDTWTHZR.mjs.map +0 -1
  641. package/dist/chunk-M2MP4ZOU.mjs +0 -73
  642. package/dist/chunk-M2MP4ZOU.mjs.map +0 -1
  643. package/dist/chunk-PCHBPYML.mjs.map +0 -1
  644. package/dist/chunk-TBQJOK2L.mjs.map +0 -1
  645. package/dist/chunk-VFPLTKCU.mjs.map +0 -1
@@ -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
  }
@@ -0,0 +1,108 @@
1
+ import type { MaterialShaderArtifact } from '@forgeax/engine-shader';
2
+ import type { MeshAsset } from '@forgeax/engine-types';
3
+ import {
4
+ type GpuDrivenDrawRange,
5
+ type PreparedGpuDrivenDraw,
6
+ type PrepareGpuDrivenDrawInput,
7
+ prepareGpuDrivenDraw,
8
+ } from '../gpu-driven/prepared-draw';
9
+ import type { MaterialSnapshot } from '../render-system-extract';
10
+
11
+ export {
12
+ GpuDrivenPreparationError,
13
+ type GpuDrivenPreparationErrorCode,
14
+ type GpuDrivenPreparationErrorDetail,
15
+ type GpuDrivenPreparationReason,
16
+ } from '../errors/gpu-driven';
17
+ export {
18
+ type GpuDrivenDrawRange,
19
+ type GpuDrivenGeometryReceipt,
20
+ type GpuDrivenSkinReceipt,
21
+ type PreparedGpuDrivenDraw,
22
+ type PrepareGpuDrivenDrawInput,
23
+ prepareGpuDrivenDraw,
24
+ } from '../gpu-driven/prepared-draw';
25
+
26
+ export interface GpuDrivenDrawSnapshot {
27
+ readonly kind: GpuDrivenDrawRange['kind'];
28
+ readonly first: GpuDrivenDrawRange['first'];
29
+ readonly count: GpuDrivenDrawRange['count'];
30
+ readonly baseVertex: GpuDrivenDrawRange['baseVertex'];
31
+ readonly materialSlot: number;
32
+ readonly topology: GpuDrivenDrawRange['topology'];
33
+ readonly pipelineClass: string;
34
+ readonly materialResourceClass: string;
35
+ /** Producer-owned prepared ABI facts used by the PBR GPU lane. */
36
+ readonly prepared?: PreparedGpuDrivenDraw;
37
+ }
38
+
39
+ /**
40
+ * Resolves prepared facts once at extract time; record never re-reads assets.
41
+ * The resolver surfaces UV, reflection, vertex, alpha, resource, and
42
+ * generation failures as GpuDrivenPreparationError before recording.
43
+ */
44
+ export function resolvePreparedGpuDrivenDraw(input: PrepareGpuDrivenDrawInput):
45
+ | { readonly ok: true; readonly value: PreparedGpuDrivenDraw }
46
+ | {
47
+ readonly ok: false;
48
+ readonly error: import('../errors/gpu-driven').GpuDrivenPreparationError;
49
+ } {
50
+ return prepareGpuDrivenDraw(input);
51
+ }
52
+
53
+ /** Keeps the artifact type visible at this owner boundary for contract checks. */
54
+ export type PreparedGpuDrivenArtifact = MaterialShaderArtifact;
55
+
56
+ /**
57
+ * Derives the resource identity consumed by the existing GPU-driven batch
58
+ * owner. The extract stage is the only place that resolves material values;
59
+ * this helper keeps the batch adapter from re-reading assets later.
60
+ */
61
+ export function gpuDrivenMaterialResourceClass(material: MaterialSnapshot): string {
62
+ const textures = [...(material.textureHandles?.entries() ?? [])]
63
+ .map(([name, handle]) => [name, Number(handle)] as const)
64
+ .sort(([left], [right]) => left.localeCompare(right));
65
+ const samplers = [...(material.samplerHandles?.entries() ?? [])]
66
+ .map(([name, handle]) => [name, Number(handle)] as const)
67
+ .sort(([left], [right]) => left.localeCompare(right));
68
+ return JSON.stringify({
69
+ textures,
70
+ samplers,
71
+ video: [...(material.videoTextureFields?.keys() ?? [])].sort(),
72
+ });
73
+ }
74
+
75
+ /**
76
+ * Projects already-resolved mesh facts into the existing GPU-driven draw
77
+ * snapshot. No World or AssetRegistry access is permitted at this boundary.
78
+ */
79
+ export function buildGpuDrivenDraws(input: {
80
+ readonly submeshes: readonly MeshAsset['submeshes'][number][];
81
+ readonly indexed: boolean;
82
+ readonly materials: readonly MaterialSnapshot[];
83
+ readonly fallbackMaterial: MaterialSnapshot;
84
+ readonly prepare?: (
85
+ draw: GpuDrivenDrawSnapshot,
86
+ material: MaterialSnapshot,
87
+ ) => PreparedGpuDrivenDraw | undefined;
88
+ }): GpuDrivenDrawSnapshot[] {
89
+ let nonIndexedFirst = 0;
90
+ return input.submeshes.flatMap((submesh) => {
91
+ const drawMaterial = input.materials[submesh.materialSlot] ?? input.fallbackMaterial;
92
+ const first = input.indexed ? submesh.indexOffset : nonIndexedFirst;
93
+ nonIndexedFirst += submesh.vertexCount;
94
+ if (drawMaterial.transparent === true) return [];
95
+ const draw: GpuDrivenDrawSnapshot = {
96
+ kind: input.indexed ? ('indexed' as const) : ('non-indexed' as const),
97
+ first,
98
+ count: input.indexed ? submesh.indexCount : submesh.vertexCount,
99
+ baseVertex: 0,
100
+ materialSlot: submesh.materialSlot,
101
+ topology: submesh.topology,
102
+ pipelineClass: `${drawMaterial.materialShaderId ?? 'forgeax::default-unlit'}|${submesh.topology}|${JSON.stringify(drawMaterial.renderState ?? null)}`,
103
+ materialResourceClass: gpuDrivenMaterialResourceClass(drawMaterial),
104
+ };
105
+ const prepared = input.prepare?.(draw, drawMaterial);
106
+ return [prepared === undefined ? draw : { ...draw, prepared }];
107
+ });
108
+ }
@@ -123,7 +123,7 @@ export function resolveVisibility(
123
123
  };
124
124
  };
125
125
 
126
- return {
126
+ const snapshot: VisibilitySnapshot = {
127
127
  diagnostics: hierarchy.diagnostics,
128
128
  hasAnyIntent: intentCount > 0,
129
129
  hasAnyHiddenIntent,
@@ -136,4 +136,5 @@ export function resolveVisibility(
136
136
  return resolveEntity?.(entity)?.effective ?? 'visible';
137
137
  },
138
138
  };
139
+ return snapshot;
139
140
  }
@@ -28,6 +28,9 @@ export function createFullscreenRenderFeature(
28
28
  const resourceName = `fullscreen.${options.identity.replace(/[^a-z0-9.-]/gi, '-').toLowerCase()}`;
29
29
  return Object.freeze({
30
30
  identity: options.identity,
31
+ requiredFullscreenPostProcesses: Object.freeze([
32
+ Object.freeze({ identity: options.identity, source: options.source }),
33
+ ]),
31
34
  extract: (_context: RenderFeatureExtractContext) => ok(undefined),
32
35
  plan: (_data: undefined, _context: RenderFeaturePlanContext) =>
33
36
  ok({
@@ -57,8 +57,11 @@ export function resolveStandardRenderFeatureTargets(input: {
57
57
  readonly colorAttachmentFormat: TextureFormat;
58
58
  }): readonly RenderFeatureTargetHandle[] {
59
59
  const sampleCount: 1 | 4 = input.antialias === 'msaa' && input.multisample ? 4 : 1;
60
+ // Standard always exposes the scene to features as a linear float target;
61
+ // the shared Output Transform owns the eventual RGBA8 write for every
62
+ // antialias mode, including no-AA/WebGL2.
60
63
  const linearLdr =
61
- (input.tonemap === 'none' && input.storageBuffer) ||
64
+ input.tonemap === 'none' ||
62
65
  input.antialias === 'fxaa' ||
63
66
  (input.antialias === 'msaa' && input.multisample);
64
67
  const colorFormat =
@@ -98,6 +98,16 @@ export interface RenderFeature<FrameData> {
98
98
  * the same lazy module cache used by prepared graphics.
99
99
  */
100
100
  readonly requiredMaterialShaders?: readonly string[];
101
+ /**
102
+ * Fullscreen program sources that must be module-ready before the first
103
+ * frame. The renderer seeds the same lazy post-process module cache used by
104
+ * the typed graph, so a synchronous frame driver cannot observe a cleared
105
+ * target while a boot-installed effect is compiling.
106
+ */
107
+ readonly requiredFullscreenPostProcesses?: readonly {
108
+ readonly identity: string;
109
+ readonly source: string;
110
+ }[];
101
111
  extract(context: RenderFeatureExtractContext): Result<FrameData, RenderError>;
102
112
  plan(data: FrameData, context: RenderFeaturePlanContext): Result<RenderFeaturePlan, RenderError>;
103
113
  }
@@ -67,7 +67,8 @@ export type PostProcessReadSampleType = 'depth';
67
67
  * no paramSchema (params are passed inline via a {type, value} struct — plan-strategy D-4).
68
68
  *
69
69
  * `params` carries the WGSL struct definition + default value. When `params` is
70
- * `undefined`, the post-process shader has no UBO (e.g. FXAA — Finding M2-1).
70
+ * `undefined`, the post-process shader has no params UBO. Special graph-owned
71
+ * passes such as FXAA may still register an internal policy UBO separately.
71
72
  *
72
73
  * `reads` declares graph resource keys this pass samples as input texture(s).
73
74
  * If omitted, the pass samples the swap-chain color attachment (default path).
@@ -81,7 +82,7 @@ export interface PostProcessShaderEntry {
81
82
  /** Composed WGSL source for the fragment stage (post-naga_oil). */
82
83
  readonly source: string;
83
84
  /**
84
- * Params UBO schema. When undefined, the shader has no uniform buffer (e.g. FXAA)
85
+ * Params UBO schema. When undefined, the generic shader has no uniform buffer
85
86
  * and the BGL degrades to 2 entries (texture@0 + sampler@1). When present, the
86
87
  * primitive eager-creates a params UBO (at register, sized `byteSize`) and binds
87
88
  * it at bindgroup(1) binding(2) as part of a 3-entry BGL
@@ -104,6 +105,46 @@ export interface PostProcessShaderEntry {
104
105
  readonly reads?: readonly (string | PostProcessReadEntry)[] | undefined;
105
106
  }
106
107
 
108
+ function postProcessSourceDigest(source: string): string {
109
+ let first = 0x811c9dc5;
110
+ let second = 0x9e3779b9;
111
+ for (let index = 0; index < source.length; index += 1) {
112
+ const code = source.charCodeAt(index);
113
+ first = Math.imul(first ^ code, 0x01000193);
114
+ second = Math.imul(second ^ (code + index), 0x01000193);
115
+ }
116
+ return `${source.length.toString(16)}-${(first >>> 0).toString(16).padStart(8, '0')}-${(second >>> 0).toString(16).padStart(8, '0')}`;
117
+ }
118
+
119
+ /** Content-addressed PSO label shared by the post-process pipeline factory. */
120
+ export function postProcessShaderPipelineLabel(identity: string, source: string): string {
121
+ return `post-process-${identity}-pso-${postProcessSourceDigest(source)}`;
122
+ }
123
+
124
+ /** Content-addressed shader-module label shared by prewarm and lazy paths. */
125
+ export function postProcessShaderModuleLabel(identity: string, source: string): string {
126
+ return `${postProcessShaderPipelineLabel(identity, source)}-module`;
127
+ }
128
+
129
+ /**
130
+ * Stable declaration identity used by graph admission and the renderer's
131
+ * device-bound pipeline cache. Shader source, bind-group-affecting params,
132
+ * and graph reads must move together when a feature reuses its identity.
133
+ */
134
+ export function postProcessShaderEntrySignature(entry: PostProcessShaderEntry): string {
135
+ return JSON.stringify({
136
+ source: entry.source,
137
+ params:
138
+ entry.params === undefined
139
+ ? undefined
140
+ : {
141
+ byteSize: entry.params.byteSize,
142
+ defaultValue: Array.from(entry.params.defaultValue),
143
+ },
144
+ reads: entry.reads,
145
+ });
146
+ }
147
+
107
148
  /** Whether a feature binding opts into the temporal multi-read contract. */
108
149
  export function isTemporalFullscreenBinding(values: Readonly<Record<string, unknown>>): boolean {
109
150
  return (
@@ -1,6 +1,10 @@
1
1
  import type { PrimitiveTopology } from '@forgeax/engine-types';
2
2
  import type { BatchTopologyInspection } from '../inspection-types';
3
+ import type { CameraSnapshot } from '../render-contract';
3
4
  import type { RenderSceneApplyResult, RenderSceneSlot } from '../scene/render-scene-types';
5
+ import { type GpuLodRow, selectGpuLod } from '../scene/visibility/gpu-lod';
6
+ import { projectedHeight as measureProjectedHeight } from '../scene/visibility/lod-selector';
7
+ import type { PreparedGpuDrivenDraw } from './prepared-draw';
4
8
 
5
9
  export type { BatchTopologyInspection } from '../inspection-types';
6
10
 
@@ -14,6 +18,12 @@ export interface GpuDrivenBatchKey {
14
18
  readonly topology: PrimitiveTopology;
15
19
  readonly pipelineClass: string;
16
20
  readonly materialResourceClass: string;
21
+ /** Stable producer-owned identity; topology never derives this from a shader name. */
22
+ readonly preparedIdentity?: string;
23
+ /** Resource identity projected by the prepared material contract. */
24
+ readonly resourceIdentity?: string;
25
+ /** Closed admission lane for the prepared Standard PBR draw. */
26
+ readonly admission?: 'opaque' | 'alpha-mask';
17
27
  }
18
28
 
19
29
  export interface GpuDrivenCandidate {
@@ -21,6 +31,16 @@ export interface GpuDrivenCandidate {
21
31
  readonly generation: number;
22
32
  readonly drawItemIndex: number;
23
33
  readonly instanceOrdinal: number;
34
+ /** Absolute coverage rows uploaded to the GPU selector; root is implicit. */
35
+ readonly lodCoverages?: readonly number[];
36
+ readonly lodHysteresis?: number;
37
+ readonly projectedHeight?: number;
38
+ readonly lodRanges?: readonly {
39
+ readonly first: number;
40
+ readonly count: number;
41
+ readonly baseVertex: number;
42
+ }[];
43
+ readonly prepared?: PreparedGpuDrivenDraw;
24
44
  }
25
45
 
26
46
  export interface GpuDrivenBatch {
@@ -40,6 +60,32 @@ export interface SubmissionPlan {
40
60
  readonly visibleCapacity: number;
41
61
  }
42
62
 
63
+ export interface GpuLodCandidateInput {
64
+ readonly candidate: GpuDrivenCandidate;
65
+ readonly rows: readonly GpuLodRow[];
66
+ readonly projectedHeight: number;
67
+ readonly previousLevel: number;
68
+ readonly historyValid: boolean;
69
+ }
70
+
71
+ export interface GpuLodCompactCandidate {
72
+ readonly candidate: GpuDrivenCandidate;
73
+ readonly level: number;
74
+ readonly confidence: number;
75
+ }
76
+
77
+ /** Selects one level per stable topology candidate without mutating membership. */
78
+ export function compactGpuLodCandidates(
79
+ inputs: readonly GpuLodCandidateInput[],
80
+ ): readonly GpuLodCompactCandidate[] {
81
+ return Object.freeze(
82
+ inputs.map((input) => {
83
+ const selection = selectGpuLod(input.rows, input);
84
+ return Object.freeze({ candidate: input.candidate, ...selection });
85
+ }),
86
+ );
87
+ }
88
+
43
89
  interface MutableBatch {
44
90
  readonly batchId: number;
45
91
  readonly generation: number;
@@ -53,29 +99,100 @@ function eligibleKeys(slot: RenderSceneSlot): readonly GpuDrivenBatchKey[] {
53
99
  if (
54
100
  draws === undefined ||
55
101
  draws.length === 0 ||
102
+ snapshot.instances?.instanceCount === 0 ||
56
103
  snapshot.localAabb === undefined ||
57
104
  snapshot.skin !== undefined ||
105
+ snapshot.morph !== undefined ||
58
106
  snapshot.spriteInstances !== undefined
59
107
  ) {
60
108
  return [];
61
109
  }
62
- return draws.map((draw) => ({
63
- assetHandle: snapshot.assetHandle,
64
- drawKind: draw.kind,
65
- first: draw.first,
66
- count: draw.count,
67
- baseVertex: draw.baseVertex,
68
- materialSlot: draw.materialSlot,
69
- topology: draw.topology,
70
- pipelineClass: draw.pipelineClass,
71
- materialResourceClass: draw.materialResourceClass,
72
- }));
110
+ return draws.flatMap((draw) => {
111
+ const prepared = draw.prepared;
112
+ const material = snapshot.materials[draw.materialSlot] ?? snapshot.material;
113
+ if (prepared !== undefined) {
114
+ const isStandardPbr =
115
+ (material.materialShaderId === 'forgeax::default-standard-pbr' &&
116
+ prepared.identity.material === 'forgeax::default-standard-pbr') ||
117
+ (material.materialShaderId === 'forgeax::default-standard-pbr-skin' &&
118
+ prepared.identity.material === 'forgeax::default-standard-pbr-skin');
119
+ const isAlphaBlend =
120
+ material.transparent === true || material.renderState?.blend !== undefined;
121
+ if (!isStandardPbr || isAlphaBlend || prepared.identity.deformation !== 'rigid') return [];
122
+ const preparedIdentity = `${prepared.identity.material}|${prepared.identity.geometry}|${prepared.identity.deformation}`;
123
+ const admission =
124
+ typeof material.paramSnapshot?.alphaCutoff === 'number' ? 'alpha-mask' : 'opaque';
125
+ return [
126
+ {
127
+ assetHandle: snapshot.assetHandle,
128
+ drawKind: draw.kind,
129
+ first: prepared.first,
130
+ count: prepared.count,
131
+ baseVertex: prepared.baseVertex,
132
+ materialSlot: draw.materialSlot,
133
+ topology: prepared.topology,
134
+ pipelineClass: preparedIdentity,
135
+ materialResourceClass: draw.materialResourceClass,
136
+ preparedIdentity,
137
+ resourceIdentity: draw.materialResourceClass,
138
+ admission,
139
+ },
140
+ ];
141
+ }
142
+ return [
143
+ {
144
+ assetHandle: snapshot.assetHandle,
145
+ drawKind: draw.kind,
146
+ first: draw.first,
147
+ count: draw.count,
148
+ baseVertex: draw.baseVertex,
149
+ materialSlot: draw.materialSlot,
150
+ topology: draw.topology,
151
+ pipelineClass: draw.pipelineClass,
152
+ materialResourceClass: draw.materialResourceClass,
153
+ },
154
+ ];
155
+ });
73
156
  }
74
157
 
75
158
  function keyText(key: GpuDrivenBatchKey): string {
76
159
  return JSON.stringify(key);
77
160
  }
78
161
 
162
+ /**
163
+ * A GPU indirect draw has one index/vertex range for the whole batch. LOD
164
+ * selection is per candidate, so sharing a batch between LOD candidates would
165
+ * let `finalizeView` pick the first candidate's range for every visible
166
+ * instance. Keep LOD candidates in single-candidate batches; ordinary meshes
167
+ * retain the existing compatibility batching path.
168
+ */
169
+ function candidateBatchText(
170
+ key: GpuDrivenBatchKey,
171
+ primitiveIndex: number,
172
+ drawItemIndex: number,
173
+ instanceOrdinal: number,
174
+ hasLod: boolean,
175
+ ): string {
176
+ return hasLod
177
+ ? `${keyText(key)}|lod:${primitiveIndex}:${drawItemIndex}:${instanceOrdinal}`
178
+ : keyText(key);
179
+ }
180
+
181
+ function membershipTexts(slot: RenderSceneSlot): readonly string[] {
182
+ const keys = eligibleKeys(slot);
183
+ const instanceCount = slot.snapshot.instances?.instanceCount ?? 1;
184
+ const hasLod = (slot.snapshot.lods?.length ?? 0) > 0;
185
+ const texts: string[] = [];
186
+ for (let drawItemIndex = 0; drawItemIndex < keys.length; drawItemIndex += 1) {
187
+ const key = keys[drawItemIndex];
188
+ if (key === undefined) continue;
189
+ for (let instanceOrdinal = 0; instanceOrdinal < instanceCount; instanceOrdinal += 1) {
190
+ texts.push(candidateBatchText(key, slot.slot, drawItemIndex, instanceOrdinal, hasLod));
191
+ }
192
+ }
193
+ return texts;
194
+ }
195
+
79
196
  function alignInstanceBase(value: number): number {
80
197
  // Raster binds each batch's visible-index and compact-transform segment at
81
198
  // a static storage-buffer offset. WebGPU requires those offsets to satisfy
@@ -85,6 +202,42 @@ function alignInstanceBase(value: number): number {
85
202
  return Math.ceil(value / 64) * 64;
86
203
  }
87
204
 
205
+ export function projectedHeightForCandidate(slot: RenderSceneSlot, camera: CameraSnapshot): number {
206
+ const aabb = slot.snapshot.localAabb;
207
+ if (aabb === undefined || aabb.length < 6) return Number.NaN;
208
+ const halfX = Math.abs((aabb[3] ?? 0) - (aabb[0] ?? 0)) * 0.5;
209
+ const halfY = Math.abs((aabb[4] ?? 0) - (aabb[1] ?? 0)) * 0.5;
210
+ const halfZ = Math.abs((aabb[5] ?? 0) - (aabb[2] ?? 0)) * 0.5;
211
+ const world = slot.snapshot.transform.world;
212
+ // Transform the local AABB's half extents through the absolute value of the
213
+ // world 3x3. This is the conservative world-space sphere bound for every
214
+ // rotation and signed/non-uniform scale, and keeps CPU projection aligned
215
+ // with the world transform uploaded to the GPU selector.
216
+ const worldHalfX =
217
+ Math.abs(world[0] ?? 0) * halfX +
218
+ Math.abs(world[4] ?? 0) * halfY +
219
+ Math.abs(world[8] ?? 0) * halfZ;
220
+ const worldHalfY =
221
+ Math.abs(world[1] ?? 0) * halfX +
222
+ Math.abs(world[5] ?? 0) * halfY +
223
+ Math.abs(world[9] ?? 0) * halfZ;
224
+ const worldHalfZ =
225
+ Math.abs(world[2] ?? 0) * halfX +
226
+ Math.abs(world[6] ?? 0) * halfY +
227
+ Math.abs(world[10] ?? 0) * halfZ;
228
+ const radius = Math.hypot(worldHalfX, worldHalfY, worldHalfZ);
229
+ const dx = (world[12] ?? 0) - (camera.position[0] ?? 0);
230
+ const dy = (world[13] ?? 0) - (camera.position[1] ?? 0);
231
+ const dz = (world[14] ?? 0) - (camera.position[2] ?? 0);
232
+ return measureProjectedHeight({
233
+ radius,
234
+ depth: Math.hypot(dx, dy, dz),
235
+ projection: camera.projection,
236
+ fov: camera.fov,
237
+ orthoHeight: Math.abs(camera.orthoTop - camera.orthoBottom),
238
+ });
239
+ }
240
+
88
241
  /** Stable compatibility grouping; per-view visibility never mutates this owner. */
89
242
  export class BatchTopology {
90
243
  private readonly batches = new Map<string, MutableBatch>();
@@ -119,15 +272,17 @@ export class BatchTopology {
119
272
  changed = this.remove(slot.slot) || changed;
120
273
  changed = this.add(slot) || changed;
121
274
  }
275
+ // Most updated slots are transform-only. A full renderable update can
276
+ // still carry a changed material/geometry/draw topology, so compare the
277
+ // stable batch memberships and patch only when that key actually changed.
278
+ // This keeps moving entities on the zero-work transform lane while making
279
+ // same-entity resource edits visible to the GPU-driven owner.
122
280
  for (const slot of delta.updatedSlots) {
123
- const instanceCount = slot.snapshot.instances?.instanceCount ?? 1;
124
- const nextTexts = eligibleKeys(slot).flatMap((key) =>
125
- Array.from({ length: instanceCount }, () => keyText(key)),
126
- );
127
- const current = this.membershipByPrimitive.get(slot.slot) ?? [];
281
+ const previous = this.membershipByPrimitive.get(slot.slot) ?? [];
282
+ const next = membershipTexts(slot);
128
283
  if (
129
- current.length === nextTexts.length &&
130
- current.every((membership, index) => membership.batchText === nextTexts[index])
284
+ previous.length === next.length &&
285
+ previous.every((membership, index) => membership.batchText === next[index])
131
286
  ) {
132
287
  continue;
133
288
  }
@@ -198,26 +353,50 @@ export class BatchTopology {
198
353
  this.ineligiblePrimitives.delete(slot.slot);
199
354
  const memberships: Array<{ batchText: string; candidateKey: string }> = [];
200
355
  const instanceCount = slot.snapshot.instances?.instanceCount ?? 1;
356
+ const hasLod = (slot.snapshot.lods?.length ?? 0) > 0;
201
357
  for (let drawItemIndex = 0; drawItemIndex < keys.length; drawItemIndex += 1) {
202
358
  const key = keys[drawItemIndex];
203
359
  if (key === undefined) continue;
204
- const text = keyText(key);
205
- let batch = this.batches.get(text);
206
- if (batch === undefined) {
207
- const reused = this.freeBatchIds.pop();
208
- const batchId = reused ?? this.generationByBatchId.length;
209
- const generation = reused === undefined ? 0 : (this.generationByBatchId[batchId] ?? -1) + 1;
210
- this.generationByBatchId[batchId] = generation;
211
- batch = { batchId, generation, key, candidates: new Map() };
212
- this.batches.set(text, batch);
213
- }
214
360
  for (let instanceOrdinal = 0; instanceOrdinal < instanceCount; instanceOrdinal += 1) {
361
+ const text = candidateBatchText(key, slot.slot, drawItemIndex, instanceOrdinal, hasLod);
362
+ let batch = this.batches.get(text);
363
+ if (batch === undefined) {
364
+ const reused = this.freeBatchIds.pop();
365
+ const batchId = reused ?? this.generationByBatchId.length;
366
+ const generation =
367
+ reused === undefined ? 0 : (this.generationByBatchId[batchId] ?? -1) + 1;
368
+ this.generationByBatchId[batchId] = generation;
369
+ batch = { batchId, generation, key, candidates: new Map() };
370
+ this.batches.set(text, batch);
371
+ }
215
372
  const candidateKey = `${slot.slot}:${drawItemIndex}:${instanceOrdinal}`;
373
+ const draw = slot.snapshot.gpuDrivenDraws?.[drawItemIndex];
374
+ const prepared = draw?.prepared;
216
375
  batch.candidates.set(candidateKey, {
217
376
  primitiveIndex: slot.slot,
218
377
  generation: slot.generation,
219
378
  drawItemIndex,
220
379
  instanceOrdinal,
380
+ ...(draw?.projectedHeight === undefined ? {} : { projectedHeight: draw.projectedHeight }),
381
+ ...(slot.snapshot.lods === undefined
382
+ ? {}
383
+ : {
384
+ lodCoverages: [1, ...slot.snapshot.lods.map((lod) => lod.screenCoverage)],
385
+ ...(slot.snapshot.gpuDrivenDraws?.[drawItemIndex]?.lodRanges === undefined
386
+ ? {}
387
+ : {
388
+ lodRanges: slot.snapshot.gpuDrivenDraws[drawItemIndex]
389
+ ?.lodRanges as readonly {
390
+ readonly first: number;
391
+ readonly count: number;
392
+ readonly baseVertex: number;
393
+ }[],
394
+ }),
395
+ ...(slot.snapshot.lodHysteresis === undefined
396
+ ? {}
397
+ : { lodHysteresis: slot.snapshot.lodHysteresis }),
398
+ }),
399
+ ...(prepared === undefined ? {} : { prepared }),
221
400
  });
222
401
  memberships.push({ batchText: text, candidateKey });
223
402
  }