@forgeax/engine-render 0.1.21 → 0.1.24

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 (584) hide show
  1. package/README.md +281 -38
  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/bundler-contract.d.ts +9 -0
  5. package/dist/assembly/bundler-contract.d.ts.map +1 -0
  6. package/dist/assembly/device-feature-admission.d.ts +8 -0
  7. package/dist/assembly/device-feature-admission.d.ts.map +1 -1
  8. package/dist/assembly/extended-lighting-fallback.d.ts +13 -0
  9. package/dist/assembly/extended-lighting-fallback.d.ts.map +1 -0
  10. package/dist/assembly/factory.d.ts +7 -235
  11. package/dist/assembly/factory.d.ts.map +1 -1
  12. package/dist/assembly/host-contract.d.ts +10 -0
  13. package/dist/assembly/host-contract.d.ts.map +1 -1
  14. package/dist/assembly/material/assembly.d.ts +3 -2
  15. package/dist/assembly/material/assembly.d.ts.map +1 -1
  16. package/dist/assembly/material/standard-layer-projection.d.ts +10 -0
  17. package/dist/assembly/material/standard-layer-projection.d.ts.map +1 -0
  18. package/dist/assembly/material-shader-policy.d.ts +168 -0
  19. package/dist/assembly/material-shader-policy.d.ts.map +1 -0
  20. package/dist/assembly/material-shader-prewarm.d.ts +21 -0
  21. package/dist/assembly/material-shader-prewarm.d.ts.map +1 -0
  22. package/dist/assembly/mesh-ssbo-grow.d.ts +88 -0
  23. package/dist/assembly/mesh-ssbo-grow.d.ts.map +1 -0
  24. package/dist/assembly/recovery/device-loss-fanout.d.ts +14 -0
  25. package/dist/assembly/recovery/device-loss-fanout.d.ts.map +1 -0
  26. package/dist/assembly/recovery/generation.d.ts +46 -0
  27. package/dist/assembly/recovery/generation.d.ts.map +1 -0
  28. package/dist/assembly/recovery/recovery-attempt.d.ts +63 -0
  29. package/dist/assembly/recovery/recovery-attempt.d.ts.map +1 -0
  30. package/dist/assembly/recovery/renderer-recover.d.ts +115 -0
  31. package/dist/assembly/recovery/renderer-recover.d.ts.map +1 -0
  32. package/dist/assembly/renderer-facade.d.ts +10 -0
  33. package/dist/assembly/renderer-facade.d.ts.map +1 -0
  34. package/dist/assembly/renderer-frame-transaction.d.ts +17 -0
  35. package/dist/assembly/renderer-frame-transaction.d.ts.map +1 -1
  36. package/dist/assembly/renderer-helpers.d.ts +19 -0
  37. package/dist/assembly/renderer-helpers.d.ts.map +1 -0
  38. package/dist/assembly/renderer-lifecycle.d.ts +34 -0
  39. package/dist/assembly/renderer-lifecycle.d.ts.map +1 -1
  40. package/dist/assembly/shader-prewarm-policy.d.ts +15 -0
  41. package/dist/assembly/shader-prewarm-policy.d.ts.map +1 -0
  42. package/dist/assembly/webgpu-ready-contract.d.ts +9 -0
  43. package/dist/assembly/webgpu-ready-contract.d.ts.map +1 -0
  44. package/dist/assembly/webgpu-ready.d.ts +55 -0
  45. package/dist/assembly/webgpu-ready.d.ts.map +1 -0
  46. package/dist/assembly/webgpu-renderer.d.ts +152 -0
  47. package/dist/assembly/webgpu-renderer.d.ts.map +1 -0
  48. package/dist/authoring.mjs +1 -1
  49. package/dist/{chunk-PCHBPYML.mjs → chunk-GE3SDD72.mjs} +37 -5
  50. package/dist/chunk-GE3SDD72.mjs.map +1 -0
  51. package/dist/chunk-KZFTK6DO.mjs +1191 -0
  52. package/dist/chunk-KZFTK6DO.mjs.map +1 -0
  53. package/dist/{chunk-FS2KNUQH.mjs → chunk-Q752SHI5.mjs} +332 -568
  54. package/dist/chunk-Q752SHI5.mjs.map +1 -0
  55. package/dist/{chunk-6OPXDQ2E.mjs → chunk-RR5L4SRS.mjs} +33 -294
  56. package/dist/chunk-RR5L4SRS.mjs.map +1 -0
  57. package/dist/chunk-TFH5I7JJ.mjs +296 -0
  58. package/dist/chunk-TFH5I7JJ.mjs.map +1 -0
  59. package/dist/chunk-UQYS7FRM.mjs +1003 -0
  60. package/dist/chunk-UQYS7FRM.mjs.map +1 -0
  61. package/dist/chunk-YKB5DQV7.mjs +345 -0
  62. package/dist/chunk-YKB5DQV7.mjs.map +1 -0
  63. package/dist/{chunk-5YADUCA7.mjs → chunk-YRUYGA4A.mjs} +1189 -1217
  64. package/dist/chunk-YRUYGA4A.mjs.map +1 -0
  65. package/dist/chunk-Z3YYH2MB.mjs +5082 -0
  66. package/dist/chunk-Z3YYH2MB.mjs.map +1 -0
  67. package/dist/cluster-binner.d.ts +5 -0
  68. package/dist/cluster-binner.d.ts.map +1 -1
  69. package/dist/components/index.d.ts +3 -0
  70. package/dist/components/index.d.ts.map +1 -1
  71. package/dist/components/light-helpers.d.ts +4 -0
  72. package/dist/components/light-helpers.d.ts.map +1 -1
  73. package/dist/components/light-probe.d.ts +9 -0
  74. package/dist/components/light-probe.d.ts.map +1 -0
  75. package/dist/components/rect-area-light.d.ts +12 -0
  76. package/dist/components/rect-area-light.d.ts.map +1 -0
  77. package/dist/components/scene-instance.d.ts +4 -0
  78. package/dist/components/scene-instance.d.ts.map +1 -1
  79. package/dist/components/spot-light.d.ts +3 -0
  80. package/dist/components/spot-light.d.ts.map +1 -1
  81. package/dist/construct-renderer.mjs +27276 -23232
  82. package/dist/construct-renderer.mjs.map +1 -1
  83. package/dist/device/device-scope.d.ts +2 -0
  84. package/dist/device/device-scope.d.ts.map +1 -1
  85. package/dist/device/gpu-residency.d.ts +32 -5
  86. package/dist/device/gpu-residency.d.ts.map +1 -1
  87. package/dist/device/lifecycle-transaction.d.ts +2 -0
  88. package/dist/device/lifecycle-transaction.d.ts.map +1 -0
  89. package/dist/environment/frame.d.ts +5 -0
  90. package/dist/environment/frame.d.ts.map +1 -1
  91. package/dist/environment/inspection.d.ts +7 -0
  92. package/dist/environment/inspection.d.ts.map +1 -1
  93. package/dist/environment/lifecycle.d.ts +20 -2
  94. package/dist/environment/lifecycle.d.ts.map +1 -1
  95. package/dist/errors/gpu-driven.d.ts +26 -0
  96. package/dist/errors/gpu-driven.d.ts.map +1 -0
  97. package/dist/errors/index.d.ts +1 -0
  98. package/dist/errors/index.d.ts.map +1 -1
  99. package/dist/errors/recover.d.ts +67 -3
  100. package/dist/errors/recover.d.ts.map +1 -1
  101. package/dist/errors/render.d.ts +39 -1
  102. package/dist/errors/render.d.ts.map +1 -1
  103. package/dist/extract/contracts.d.ts +899 -0
  104. package/dist/extract/contracts.d.ts.map +1 -0
  105. package/dist/extract/gpu-driven.d.ts +50 -0
  106. package/dist/extract/gpu-driven.d.ts.map +1 -0
  107. package/dist/extract/visibility.d.ts.map +1 -1
  108. package/dist/features/host.d.ts +2 -0
  109. package/dist/features/host.d.ts.map +1 -1
  110. package/dist/features/prepared-gpu-work.d.ts +3 -0
  111. package/dist/features/prepared-gpu-work.d.ts.map +1 -1
  112. package/dist/features/prepared-graphics-store.d.ts +2 -0
  113. package/dist/features/prepared-graphics-store.d.ts.map +1 -1
  114. package/dist/gpu-driven/batch-topology.d.ts +34 -0
  115. package/dist/gpu-driven/batch-topology.d.ts.map +1 -1
  116. package/dist/gpu-driven/prepared-draw.d.ts +63 -0
  117. package/dist/gpu-driven/prepared-draw.d.ts.map +1 -0
  118. package/dist/gpu-driven/production-raster.d.ts +126 -2
  119. package/dist/gpu-driven/production-raster.d.ts.map +1 -1
  120. package/dist/gpu-driven/view-gpu.d.ts +64 -1
  121. package/dist/gpu-driven/view-gpu.d.ts.map +1 -1
  122. package/dist/gpu-scene-schema.d.ts +14 -0
  123. package/dist/gpu-scene-schema.d.ts.map +1 -1
  124. package/dist/gpu-scene.d.ts +9 -1
  125. package/dist/gpu-scene.d.ts.map +1 -1
  126. package/dist/gpu-usage.d.ts +0 -1
  127. package/dist/gpu-usage.d.ts.map +1 -1
  128. package/dist/hdrp-buffers.d.ts +3 -3
  129. package/dist/hdrp-buffers.d.ts.map +1 -1
  130. package/dist/ibl/IblPipelineCache.d.ts +7 -6
  131. package/dist/ibl/IblPipelineCache.d.ts.map +1 -1
  132. package/dist/ibl/skylight-bind-group.d.ts +7 -1
  133. package/dist/ibl/skylight-bind-group.d.ts.map +1 -1
  134. package/dist/index.d.ts +13 -2
  135. package/dist/index.d.ts.map +1 -1
  136. package/dist/index.mjs +90 -5
  137. package/dist/index.mjs.map +1 -1
  138. package/dist/inspection-types.d.ts +163 -1
  139. package/dist/inspection-types.d.ts.map +1 -1
  140. package/dist/internal.d.ts +9 -0
  141. package/dist/internal.d.ts.map +1 -1
  142. package/dist/internal.mjs +23 -2
  143. package/dist/internal.mjs.map +1 -1
  144. package/dist/light-buffer-layout.d.ts +60 -148
  145. package/dist/light-buffer-layout.d.ts.map +1 -1
  146. package/dist/materials.d.ts +34 -10
  147. package/dist/materials.d.ts.map +1 -1
  148. package/dist/mesh-material-bindings.d.ts +56 -0
  149. package/dist/mesh-material-bindings.d.ts.map +1 -1
  150. package/dist/occlusion-proxy.d.ts +4 -0
  151. package/dist/occlusion-proxy.d.ts.map +1 -0
  152. package/dist/pbr-pipeline.d.ts +58 -21
  153. package/dist/pbr-pipeline.d.ts.map +1 -1
  154. package/dist/pipeline/standard-forward-lane.d.ts.map +1 -1
  155. package/dist/pipeline/standard-lighting/graph.d.ts.map +1 -1
  156. package/dist/pipeline/standard-lighting/light-frame.d.ts +3 -3
  157. package/dist/pipeline/standard-lighting/light-frame.d.ts.map +1 -1
  158. package/dist/pipeline/standard-lighting/topology.d.ts +1 -1
  159. package/dist/pipeline/standard-lighting/topology.d.ts.map +1 -1
  160. package/dist/pipeline/standard-lighting/transport.d.ts.map +1 -1
  161. package/dist/pipeline/standard-pipeline.d.ts.map +1 -1
  162. package/dist/pipeline-builder.d.ts +2 -0
  163. package/dist/pipeline-builder.d.ts.map +1 -1
  164. package/dist/pipeline-spec.d.ts +1 -1
  165. package/dist/pipeline-spec.d.ts.map +1 -1
  166. package/dist/plugin.d.ts.map +1 -1
  167. package/dist/points-lines/prepare.d.ts +3 -0
  168. package/dist/points-lines/prepare.d.ts.map +1 -1
  169. package/dist/points-lines/standard-owner.d.ts +33 -0
  170. package/dist/points-lines/standard-owner.d.ts.map +1 -0
  171. package/dist/prepare/extended-lighting/ltc-resources.d.ts +31 -0
  172. package/dist/prepare/extended-lighting/ltc-resources.d.ts.map +1 -0
  173. package/dist/prepare/extended-lighting/resources.d.ts +72 -0
  174. package/dist/prepare/extended-lighting/resources.d.ts.map +1 -0
  175. package/dist/prepare/extended-lighting/spot-modifiers.d.ts +42 -0
  176. package/dist/prepare/extended-lighting/spot-modifiers.d.ts.map +1 -0
  177. package/dist/prepare/extended-lighting/state.d.ts +24 -0
  178. package/dist/prepare/extended-lighting/state.d.ts.map +1 -0
  179. package/dist/record/frame-lighting.d.ts +8 -6
  180. package/dist/record/frame-lighting.d.ts.map +1 -1
  181. package/dist/record/frame-snapshot.d.ts +55 -10
  182. package/dist/record/frame-snapshot.d.ts.map +1 -1
  183. package/dist/record/frame-targets.d.ts.map +1 -1
  184. package/dist/record/frame.d.ts +21 -5
  185. package/dist/record/frame.d.ts.map +1 -1
  186. package/dist/record/gpu-timing.d.ts +19 -7
  187. package/dist/record/gpu-timing.d.ts.map +1 -1
  188. package/dist/record/helpers.d.ts +0 -20
  189. package/dist/record/helpers.d.ts.map +1 -1
  190. package/dist/record/main-pass-geometry.d.ts +20 -2
  191. package/dist/record/main-pass-geometry.d.ts.map +1 -1
  192. package/dist/record/main-pass-material.d.ts +10 -18
  193. package/dist/record/main-pass-material.d.ts.map +1 -1
  194. package/dist/record/main-pass-sprite-draws.d.ts.map +1 -1
  195. package/dist/record/main-pass.d.ts.map +1 -1
  196. package/dist/record/mesh-ssbo.d.ts +11 -3
  197. package/dist/record/mesh-ssbo.d.ts.map +1 -1
  198. package/dist/record/recovery-pipeline.d.ts +30 -0
  199. package/dist/record/recovery-pipeline.d.ts.map +1 -0
  200. package/dist/record/render-context.d.ts +82 -9
  201. package/dist/record/render-context.d.ts.map +1 -1
  202. package/dist/record/shadow-pass.d.ts.map +1 -1
  203. package/dist/record/typed-frame-graph.d.ts +49 -2
  204. package/dist/record/typed-frame-graph.d.ts.map +1 -1
  205. package/dist/record/view-ubo.d.ts +1 -2
  206. package/dist/record/view-ubo.d.ts.map +1 -1
  207. package/dist/recovery/render-system-candidate.d.ts +65 -0
  208. package/dist/recovery/render-system-candidate.d.ts.map +1 -0
  209. package/dist/reflection/inspection.d.ts +10 -0
  210. package/dist/reflection/inspection.d.ts.map +1 -0
  211. package/dist/reflection/projection.d.ts +49 -0
  212. package/dist/reflection/projection.d.ts.map +1 -1
  213. package/dist/reflection/record-owner.d.ts +60 -0
  214. package/dist/reflection/record-owner.d.ts.map +1 -0
  215. package/dist/render-contract.d.ts +78 -15
  216. package/dist/render-contract.d.ts.map +1 -1
  217. package/dist/render-pipeline.d.ts +7 -0
  218. package/dist/render-pipeline.d.ts.map +1 -1
  219. package/dist/render-system-extract-tail.d.ts +12 -0
  220. package/dist/render-system-extract-tail.d.ts.map +1 -0
  221. package/dist/render-system-extract.d.ts +252 -30
  222. package/dist/render-system-extract.d.ts.map +1 -1
  223. package/dist/render-system.d.ts +140 -5
  224. package/dist/render-system.d.ts.map +1 -1
  225. package/dist/scene/probe-blend-record.d.ts +22 -0
  226. package/dist/scene/probe-blend-record.d.ts.map +1 -0
  227. package/dist/scene/probe-blend.d.ts +142 -0
  228. package/dist/scene/probe-blend.d.ts.map +1 -0
  229. package/dist/scene/render-scene-types.d.ts +0 -6
  230. package/dist/scene/render-scene-types.d.ts.map +1 -1
  231. package/dist/scene/render-scene.d.ts +122 -9
  232. package/dist/scene/render-scene.d.ts.map +1 -1
  233. package/dist/scene/visibility/bounds.d.ts +20 -0
  234. package/dist/scene/visibility/bounds.d.ts.map +1 -0
  235. package/dist/scene/visibility/budget.d.ts +16 -0
  236. package/dist/scene/visibility/budget.d.ts.map +1 -0
  237. package/dist/scene/visibility/facet.d.ts +73 -0
  238. package/dist/scene/visibility/facet.d.ts.map +1 -0
  239. package/dist/scene/visibility/gpu-frame-timing.d.ts +41 -0
  240. package/dist/scene/visibility/gpu-frame-timing.d.ts.map +1 -0
  241. package/dist/scene/visibility/gpu-lod.d.ts +46 -0
  242. package/dist/scene/visibility/gpu-lod.d.ts.map +1 -0
  243. package/dist/scene/visibility/inspection.d.ts +132 -0
  244. package/dist/scene/visibility/inspection.d.ts.map +1 -0
  245. package/dist/scene/visibility/lod-selector.d.ts +25 -0
  246. package/dist/scene/visibility/lod-selector.d.ts.map +1 -0
  247. package/dist/scene/visibility/occlusion-confidence.d.ts +81 -0
  248. package/dist/scene/visibility/occlusion-confidence.d.ts.map +1 -0
  249. package/dist/scene/visibility/occlusion-pass.d.ts +57 -0
  250. package/dist/scene/visibility/occlusion-pass.d.ts.map +1 -0
  251. package/dist/scene/visibility/occlusion-query-pool.d.ts +57 -0
  252. package/dist/scene/visibility/occlusion-query-pool.d.ts.map +1 -0
  253. package/dist/scene/visibility/occlusion-runtime.d.ts +96 -0
  254. package/dist/scene/visibility/occlusion-runtime.d.ts.map +1 -0
  255. package/dist/scene/visibility/types.d.ts +34 -0
  256. package/dist/scene/visibility/types.d.ts.map +1 -0
  257. package/dist/ssr/admission.d.ts +122 -0
  258. package/dist/ssr/admission.d.ts.map +1 -0
  259. package/dist/ssr/composition.d.ts +32 -0
  260. package/dist/ssr/composition.d.ts.map +1 -0
  261. package/dist/ssr/errors.d.ts +39 -0
  262. package/dist/ssr/errors.d.ts.map +1 -0
  263. package/dist/ssr/identity.d.ts +13 -0
  264. package/dist/ssr/identity.d.ts.map +1 -0
  265. package/dist/systems/skin-palette-allocator.d.ts +1 -1
  266. package/dist/temporal/frame-coordinator.d.ts +13 -0
  267. package/dist/temporal/frame-coordinator.d.ts.map +1 -1
  268. package/dist/temporal/frame.d.ts +2 -0
  269. package/dist/temporal/frame.d.ts.map +1 -1
  270. package/dist/temporal/index.mjs +6 -5
  271. package/dist/temporal/taa-history-store.d.ts +15 -1
  272. package/dist/temporal/taa-history-store.d.ts.map +1 -1
  273. package/dist/temporal/transaction.d.ts +5 -0
  274. package/dist/temporal/transaction.d.ts.map +1 -1
  275. package/dist/temporal/view.d.ts +7 -0
  276. package/dist/temporal/view.d.ts.map +1 -1
  277. package/dist/tilemap-chunk-extract-system.d.ts +1 -1
  278. package/dist/typed-render-graph-primitives.d.ts +4 -0
  279. package/dist/typed-render-graph-primitives.d.ts.map +1 -1
  280. package/dist/volume/passes.d.ts +8 -4
  281. package/dist/volume/passes.d.ts.map +1 -1
  282. package/package.json +20 -20
  283. package/src/__tests__/cluster-grid-default-surface.unit.test.ts +5 -0
  284. package/src/__tests__/detached-inspection.integration.test.ts +6 -0
  285. package/src/__tests__/device-feature-admission.unit.test.ts +19 -1
  286. package/src/__tests__/device-loss-fanout.unit.test.ts +107 -0
  287. package/src/__tests__/device-scope-lifecycle.unit.test.ts +21 -0
  288. package/src/__tests__/direct-light-snapshot.unit.test.ts +59 -3
  289. package/src/__tests__/environment-generation.integration.test.ts +39 -0
  290. package/src/__tests__/evidence-identity.ts +13 -0
  291. package/src/__tests__/evidence-identity.unit.test.ts +24 -0
  292. package/src/__tests__/expose-renderer-errors.unit.test.ts +34 -0
  293. package/src/__tests__/extended-lighting-recovery.browser.test.ts +14 -0
  294. package/src/__tests__/extended-lighting-recovery.dawn.test.ts +14 -0
  295. package/src/__tests__/extended-lighting-recovery.integration.test.ts +81 -0
  296. package/src/__tests__/extended-lighting-resources.unit.test.ts +51 -0
  297. package/src/__tests__/extended-lighting-zero-feature.integration.test.ts +52 -0
  298. package/src/__tests__/factory-contract.integration.test.ts +154 -6
  299. package/src/__tests__/fallback-row-stride-surface.unit.test.ts +4 -1
  300. package/src/__tests__/frame-observation.contract.test.ts +15 -0
  301. package/src/__tests__/gpu-driven-batch-topology-pbr.unit.test.ts +146 -0
  302. package/src/__tests__/gpu-driven-indirect-raster-evidence.ts +1 -0
  303. package/src/__tests__/gpu-driven-indirect-raster.dawn.test.ts +1 -0
  304. package/src/__tests__/gpu-driven-production.integration.test.ts +572 -9
  305. package/src/__tests__/gpu-driven-scaling.unit.test.ts +66 -2
  306. package/src/__tests__/gpu-driven-view-gpu-evidence.ts +26 -1
  307. package/src/__tests__/gpu-driven-view-graph.integration.test.ts +89 -2
  308. package/src/__tests__/gpu-driven-view.browser.test.ts +3 -3
  309. package/src/__tests__/gpu-driven-view.dawn.test.ts +3 -3
  310. package/src/__tests__/gpu-driven-view.unit.test.ts +50 -0
  311. package/src/__tests__/gpu-lod-evidence.ts +188 -0
  312. package/src/__tests__/gpu-lod-fallback.integration.test.ts +10 -0
  313. package/src/__tests__/gpu-lod-indirect.integration.test.ts +40 -0
  314. package/src/__tests__/gpu-lod-parity.dawn.test.ts +29 -0
  315. package/src/__tests__/gpu-lod-readiness.integration.test.ts +52 -0
  316. package/src/__tests__/gpu-lod-schema.unit.test.ts +66 -0
  317. package/src/__tests__/gpu-lod-selector.unit.test.ts +28 -0
  318. package/src/__tests__/gpu-lod-silhouette.browser.test.ts +13 -0
  319. package/src/__tests__/gpu-resource-store-stride.unit.test.ts +66 -1
  320. package/src/__tests__/gpu-scene.dawn.test.ts +8 -1
  321. package/src/__tests__/gpu-scene.unit.test.ts +49 -3
  322. package/src/__tests__/ibl/face-uniforms.unit.test.ts +2 -0
  323. package/src/__tests__/ibl-residency-publication.unit.test.ts +113 -0
  324. package/src/__tests__/light-buffer-layout.unit.test.ts +113 -3
  325. package/src/__tests__/light-inspection.unit.test.ts +55 -0
  326. package/src/__tests__/light-resource-error.unit.test.ts +43 -0
  327. package/src/__tests__/lighting-scene-roundtrip.integration.test.ts +109 -0
  328. package/src/__tests__/lod-bounds.unit.test.ts +42 -0
  329. package/src/__tests__/lod-cpu-lane.integration.test.ts +55 -0
  330. package/src/__tests__/lod-cpu-no-change.perf.test.ts +19 -0
  331. package/src/__tests__/lod-gpu-frame-timing.unit.test.ts +49 -0
  332. package/src/__tests__/lod-multi-view.integration.test.ts +63 -0
  333. package/src/__tests__/lod-occlusion-device-loss.integration.test.ts +27 -0
  334. package/src/__tests__/lod-occlusion-final-decision.integration.test.ts +47 -0
  335. package/src/__tests__/lod-occlusion-inspection.integration.test.ts +217 -0
  336. package/src/__tests__/lod-occlusion-inspection.schema.test-d.ts +14 -0
  337. package/src/__tests__/lod-occlusion-inspection.unit.test.ts +72 -0
  338. package/src/__tests__/lod-occlusion-visual-evidence.ts +91 -0
  339. package/src/__tests__/lod-occlusion.browser.test.ts +15 -0
  340. package/src/__tests__/lod-occlusion.dawn.test.ts +20 -0
  341. package/src/__tests__/lod-selector.profile.test.ts +35 -0
  342. package/src/__tests__/lod-selector.unit.test.ts +68 -0
  343. package/src/__tests__/lod-visibility-identity.unit.test.ts +198 -0
  344. package/src/__tests__/lod-visibility-lifecycle.integration.test.ts +132 -0
  345. package/src/__tests__/ltc-resources.unit.test.ts +32 -0
  346. package/src/__tests__/material-contract-inventory.unit.test.ts +41 -9
  347. package/src/__tests__/material-cooked-projection.unit.test.ts +19 -1
  348. package/src/__tests__/material-static-dynamic-route.unit.test.ts +29 -0
  349. package/src/__tests__/material-stride-surface.unit.test.ts +7 -3
  350. package/src/__tests__/materials-standard-contract.unit.test.ts +104 -0
  351. package/src/__tests__/mesh-buffer-usage-surface.unit.test.ts +1 -1
  352. package/src/__tests__/occlusion-confidence.unit.test.ts +168 -0
  353. package/src/__tests__/occlusion-invalidation.integration.test.ts +37 -0
  354. package/src/__tests__/occlusion-pass-routing.unit.test.ts +16 -0
  355. package/src/__tests__/occlusion-pass.dawn.test.ts +20 -0
  356. package/src/__tests__/occlusion-pass.integration.test.ts +43 -0
  357. package/src/__tests__/occlusion-pass.rhi-null.test.ts +22 -0
  358. package/src/__tests__/occlusion-query-pool.rhi-null.test.ts +21 -0
  359. package/src/__tests__/occlusion-query-pool.unit.test.ts +95 -0
  360. package/src/__tests__/occlusion-query-ticket.integration.test.ts +47 -0
  361. package/src/__tests__/occlusion-query.dawn.test.ts +16 -0
  362. package/src/__tests__/occlusion-query.evidence.ts +178 -0
  363. package/src/__tests__/occlusion-query.rhi-null.test.ts +31 -0
  364. package/src/__tests__/occlusion-runtime.unit.test.ts +120 -0
  365. package/src/__tests__/physical-clearcoat.integration.test.ts +89 -0
  366. package/src/__tests__/prepared-gpu-driven-pbr.integration.test.ts +196 -0
  367. package/src/__tests__/prepared-graphics-pipeline-warmup.unit.test.ts +1 -1
  368. package/src/__tests__/probe-bindings.unit.test.ts +200 -0
  369. package/src/__tests__/probe-blend-numeric.unit.test.ts +67 -0
  370. package/src/__tests__/probe-blend-oracle.unit.test.ts +136 -0
  371. package/src/__tests__/probe-capacity.integration.test.ts +97 -0
  372. package/src/__tests__/probe-recovery.integration.test.ts +104 -0
  373. package/src/__tests__/probe-scene-dirty.integration.test.ts +211 -0
  374. package/src/__tests__/profiler-phase-catalog.test.ts +4 -1
  375. package/src/__tests__/profiler-phase-token.test.ts +1 -0
  376. package/src/__tests__/recover-error-policy-owner.unit.test.ts +6 -3
  377. package/src/__tests__/recovery-candidate-prepare.contract.test.ts +140 -0
  378. package/src/__tests__/rect-area-boundary.unit.test.ts +24 -0
  379. package/src/__tests__/reflection-fallback-graph.unit.test.ts +67 -0
  380. package/src/__tests__/reflection-fallback-inspection.unit.test.ts +77 -0
  381. package/src/__tests__/reflection-fallback-lifecycle.integration.test.ts +21 -0
  382. package/src/__tests__/reflection-fallback-projection.unit.test.ts +90 -0
  383. package/src/__tests__/reflection-fallback-source.unit.test.ts +32 -0
  384. package/src/__tests__/reflection-probe-owner-placement.unit.test.ts +39 -0
  385. package/src/__tests__/reflection-probe-record-owner.characterization.unit.test.ts +120 -0
  386. package/src/__tests__/render-error-code-owner.test-d.ts +1 -0
  387. package/src/__tests__/render-feature-docs.unit.test.ts +12 -7
  388. package/src/__tests__/render-graph-fallback.integration.test.ts +24 -0
  389. package/src/__tests__/render-perf/extended-lighting.bench.test.ts +66 -0
  390. package/src/__tests__/render-scene-projection.unit.test.ts +447 -18
  391. package/src/__tests__/renderer-factory-material-contract.unit.test.ts +160 -17
  392. package/src/__tests__/renderer-frame-generation-fence.integration.test.ts +47 -0
  393. package/src/__tests__/renderer-generation-assembly.unit.test.ts +77 -0
  394. package/src/__tests__/renderer-generation-failure.unit.test.ts +108 -0
  395. package/src/__tests__/renderer-producer-root-matrix.integration.test.ts +33 -0
  396. package/src/__tests__/renderer-recovery-capability-matrix.unit.test.ts +59 -0
  397. package/src/__tests__/renderer-recovery-contract.unit.test.ts +73 -0
  398. package/src/__tests__/renderer-recovery-lifecycle.unit.test.ts +216 -0
  399. package/src/__tests__/renderer-target-history-recovery.integration.test.ts +80 -0
  400. package/src/__tests__/shader-manifest-fixture.ts +13 -16
  401. package/src/__tests__/shader-stage-surface.unit.test.ts +1 -1
  402. package/src/__tests__/skin-motion-regression.unit.test.ts +22 -0
  403. package/src/__tests__/skinned-shadow-caster.test.ts +2 -2
  404. package/src/__tests__/spot-modifier-projection.unit.test.ts +70 -0
  405. package/src/__tests__/ssr-admission-matrix.integration.test.ts +206 -0
  406. package/src/__tests__/ssr-composition.unit.test.ts +46 -0
  407. package/src/__tests__/ssr-fallback-zero-work.bench.test.ts +94 -0
  408. package/src/__tests__/ssr-recovery-errors.unit.test.ts +58 -0
  409. package/src/__tests__/standard-cluster-variant-resolution.unit.test.ts +67 -0
  410. package/src/__tests__/standard-clustered-pipeline.unit.test.ts +7 -0
  411. package/src/__tests__/standard-layer-projection.unit.test.ts +23 -0
  412. package/src/__tests__/standard-lighting-inspection.integration.test.ts +1 -1
  413. package/src/__tests__/standard-lighting-write.unit.test.ts +66 -2
  414. package/src/__tests__/temporal-fallback-generation.integration.test.ts +16 -0
  415. package/src/__tests__/temporal-view.unit.test.ts +3 -3
  416. package/src/__tests__/transmission-prewarm.unit.test.ts +111 -32
  417. package/src/__tests__/visibility-budget.integration.test.ts +140 -0
  418. package/src/__tests__/visibility-budget.unit.test.ts +70 -0
  419. package/src/__tests__/visibility-extract.integration.test.ts +11 -6
  420. package/src/__tests__/volumetric-fog-point-spot-accumulation.integration.test.ts +6 -3
  421. package/src/__tests__/volumetric-fog-stage-readback.dawn.test.ts +31 -28
  422. package/src/__tests__/volumetric-fog-topology.unit.test.ts +3 -1
  423. package/src/assembly/backend-contract.ts +13 -0
  424. package/src/assembly/bundler-contract.ts +9 -0
  425. package/src/assembly/device-feature-admission.ts +26 -9
  426. package/src/assembly/extended-lighting-fallback.ts +216 -0
  427. package/src/assembly/factory.ts +81 -9634
  428. package/src/assembly/host-contract.ts +10 -0
  429. package/src/assembly/material/assembly.ts +18 -2
  430. package/src/assembly/material/standard-layer-projection.ts +16 -0
  431. package/src/assembly/material-shader-policy.ts +854 -0
  432. package/src/assembly/material-shader-prewarm.ts +88 -0
  433. package/src/assembly/mesh-ssbo-grow.ts +270 -0
  434. package/src/assembly/recovery/device-loss-fanout.ts +89 -0
  435. package/src/assembly/recovery/generation.ts +141 -0
  436. package/src/assembly/recovery/recovery-attempt.ts +800 -0
  437. package/src/assembly/recovery/renderer-recover.ts +906 -0
  438. package/src/assembly/renderer-facade.ts +342 -0
  439. package/src/assembly/renderer-frame-transaction.ts +45 -0
  440. package/src/assembly/renderer-helpers.ts +99 -0
  441. package/src/assembly/renderer-lifecycle.ts +159 -0
  442. package/src/assembly/shader-prewarm-policy.ts +157 -0
  443. package/src/assembly/webgpu-ready-contract.ts +14 -0
  444. package/src/assembly/webgpu-ready.ts +4079 -0
  445. package/src/assembly/webgpu-renderer.ts +4095 -0
  446. package/src/cluster-binner.ts +31 -0
  447. package/src/components/__tests__/lighting-authoring.unit.test.ts +39 -0
  448. package/src/components/__tests__/rect-area-light.unit.test.ts +33 -0
  449. package/src/components/camera.ts +1 -1
  450. package/src/components/index.ts +3 -0
  451. package/src/components/light-helpers.ts +52 -1
  452. package/src/components/light-probe.ts +13 -0
  453. package/src/components/rect-area-light.ts +15 -0
  454. package/src/components/scene-instance.ts +6 -0
  455. package/src/components/spot-light.ts +3 -0
  456. package/src/debug-draw-glue.ts +1 -1
  457. package/src/device/__tests__/extended-lighting-recovery.unit.test.ts +24 -0
  458. package/src/device/device-scope.ts +35 -6
  459. package/src/device/gpu-residency.ts +334 -35
  460. package/src/device/lifecycle-transaction.ts +12 -0
  461. package/src/environment/frame.ts +6 -0
  462. package/src/environment/inspection.ts +10 -0
  463. package/src/environment/lifecycle.ts +122 -9
  464. package/src/errors/gpu-driven.ts +92 -0
  465. package/src/errors/index.ts +1 -0
  466. package/src/errors/recover.ts +108 -5
  467. package/src/errors/render.ts +72 -1
  468. package/src/extract/camera.ts +3 -3
  469. package/src/extract/contracts.ts +974 -0
  470. package/src/extract/gpu-driven.ts +108 -0
  471. package/src/extract/visibility.ts +0 -23
  472. package/src/features/host.ts +16 -0
  473. package/src/features/prepared-gpu-work.ts +20 -2
  474. package/src/features/prepared-graphics-store.ts +17 -0
  475. package/src/gpu-driven/batch-topology.ts +206 -28
  476. package/src/gpu-driven/prepared-draw.ts +208 -0
  477. package/src/gpu-driven/production-raster.ts +1046 -39
  478. package/src/gpu-driven/view-gpu.ts +411 -29
  479. package/src/gpu-scene-schema.ts +13 -0
  480. package/src/gpu-scene.ts +137 -67
  481. package/src/gpu-usage.ts +0 -1
  482. package/src/hdrp-buffers.ts +6 -6
  483. package/src/ibl/IblPipelineCache.ts +134 -50
  484. package/src/ibl/skylight-bind-group.ts +26 -0
  485. package/src/index.ts +83 -1
  486. package/src/inspection-types.ts +220 -1
  487. package/src/internal.ts +38 -0
  488. package/src/light-buffer-layout.ts +157 -324
  489. package/src/materials.ts +278 -47
  490. package/src/mesh-material-bindings.ts +59 -0
  491. package/src/occlusion-proxy.ts +64 -0
  492. package/src/pbr-pipeline.ts +290 -60
  493. package/src/pipeline/standard-forward-lane.ts +47 -0
  494. package/src/pipeline/standard-lighting/graph.ts +2 -1
  495. package/src/pipeline/standard-lighting/light-frame.ts +3 -2
  496. package/src/pipeline/standard-lighting/topology.ts +1 -1
  497. package/src/pipeline/standard-lighting/transport.ts +2 -1
  498. package/src/pipeline/standard-pipeline.ts +2 -0
  499. package/src/pipeline-builder.ts +5 -2
  500. package/src/pipeline-spec.ts +12 -1
  501. package/src/plugin.ts +4 -0
  502. package/src/points-lines/__tests__/cache-recovery.unit.test.ts +22 -0
  503. package/src/points-lines/prepare.ts +9 -0
  504. package/src/points-lines/standard-owner.ts +403 -0
  505. package/src/prepare/__tests__/extended-lighting-capability.unit.test.ts +56 -0
  506. package/src/prepare/extended-lighting/ltc-resources.ts +136 -0
  507. package/src/prepare/extended-lighting/resources.ts +389 -0
  508. package/src/prepare/extended-lighting/spot-modifiers.ts +194 -0
  509. package/src/prepare/extended-lighting/state.ts +92 -0
  510. package/src/record/__tests__/frame-targets-recovery.unit.test.ts +60 -0
  511. package/src/record/__tests__/gpu-timing.unit.test.ts +177 -0
  512. package/src/record/__tests__/main-pass-geometry.unit.test.ts +26 -1
  513. package/src/record/__tests__/mesh-ssbo.unit.test.ts +38 -0
  514. package/src/record/__tests__/recovery-pipeline.unit.test.ts +124 -0
  515. package/src/record/__tests__/sprite-material-abi.unit.test.ts +28 -13
  516. package/src/record/__tests__/standard-pbr-ubo-layout.unit.test.ts +47 -4
  517. package/src/record/__tests__/temporal-payload.unit.test.ts +29 -28
  518. package/src/record/__tests__/typed-frame-graph-plan.unit.test.ts +21 -2
  519. package/src/record/frame-lighting.ts +252 -53
  520. package/src/record/frame-snapshot.ts +72 -12
  521. package/src/record/frame-targets.ts +11 -1
  522. package/src/record/frame.ts +416 -95
  523. package/src/record/gpu-timing.ts +108 -44
  524. package/src/record/helpers.ts +1 -59
  525. package/src/record/main-pass-geometry.ts +434 -44
  526. package/src/record/main-pass-material.ts +324 -210
  527. package/src/record/main-pass-sprite-draws.ts +58 -92
  528. package/src/record/main-pass.ts +94 -6
  529. package/src/record/mesh-ssbo.ts +79 -46
  530. package/src/record/recovery-pipeline.ts +695 -0
  531. package/src/record/render-context.ts +99 -7
  532. package/src/record/shadow-pass.ts +66 -40
  533. package/src/record/typed-frame-graph.ts +270 -84
  534. package/src/record/view-ubo.ts +17 -27
  535. package/src/recovery/render-system-candidate.ts +839 -0
  536. package/src/reflection/inspection.ts +67 -0
  537. package/src/reflection/projection.ts +196 -1
  538. package/src/reflection/record-owner.ts +1283 -0
  539. package/src/render-contract.ts +82 -12
  540. package/src/render-pipeline.ts +5 -0
  541. package/src/render-system-extract-tail.ts +2983 -0
  542. package/src/render-system-extract.ts +290 -2784
  543. package/src/render-system.ts +1597 -1192
  544. package/src/scene/__tests__/changed-block-summary.candidate.test.ts +36 -0
  545. package/src/scene/probe-blend-record.ts +54 -0
  546. package/src/scene/probe-blend.ts +813 -0
  547. package/src/scene/render-scene-types.ts +0 -7
  548. package/src/scene/render-scene.ts +972 -184
  549. package/src/scene/visibility/bounds.ts +57 -0
  550. package/src/scene/visibility/budget.ts +40 -0
  551. package/src/scene/visibility/facet.ts +400 -0
  552. package/src/scene/visibility/gpu-frame-timing.ts +101 -0
  553. package/src/scene/visibility/gpu-lod.ts +119 -0
  554. package/src/scene/visibility/inspection.ts +313 -0
  555. package/src/scene/visibility/lod-selector.ts +84 -0
  556. package/src/scene/visibility/occlusion-confidence.ts +478 -0
  557. package/src/scene/visibility/occlusion-pass.ts +165 -0
  558. package/src/scene/visibility/occlusion-query-pool.ts +178 -0
  559. package/src/scene/visibility/occlusion-runtime.ts +672 -0
  560. package/src/scene/visibility/types.ts +50 -0
  561. package/src/ssr/admission.ts +451 -0
  562. package/src/ssr/composition.ts +73 -0
  563. package/src/ssr/errors.ts +76 -0
  564. package/src/ssr/identity.ts +12 -0
  565. package/src/systems/skin-palette-allocator.ts +1 -1
  566. package/src/temporal/frame-coordinator.ts +30 -1
  567. package/src/temporal/frame.ts +14 -1
  568. package/src/temporal/taa-history-store.ts +46 -2
  569. package/src/temporal/temporal-view.ts +1 -1
  570. package/src/temporal/transaction.ts +9 -0
  571. package/src/temporal/view.ts +11 -0
  572. package/src/tilemap-chunk-extract-system.ts +5 -5
  573. package/src/transmission/__tests__/standard-transmission.dawn.test.ts +19 -12
  574. package/src/typed-render-graph-primitives.ts +48 -0
  575. package/src/volume/passes.ts +46 -49
  576. package/dist/.tsbuildinfo +0 -1
  577. package/dist/chunk-5YADUCA7.mjs.map +0 -1
  578. package/dist/chunk-6OPXDQ2E.mjs.map +0 -1
  579. package/dist/chunk-BXBQQKFQ.mjs +0 -67
  580. package/dist/chunk-BXBQQKFQ.mjs.map +0 -1
  581. package/dist/chunk-FS2KNUQH.mjs.map +0 -1
  582. package/dist/chunk-PCHBPYML.mjs.map +0 -1
  583. package/dist/chunk-VFPLTKCU.mjs +0 -192
  584. package/dist/chunk-VFPLTKCU.mjs.map +0 -1
@@ -1,10 +1,13 @@
1
1
  import { HANDLE_CUBE } from '@forgeax/engine-assets-runtime';
2
2
  import { World } from '@forgeax/engine-ecs';
3
3
  import { deriveVertexLayoutProjection } from '@forgeax/engine-geometry';
4
+ import { vec3 } from '@forgeax/engine-math';
4
5
  import { RenderGraphBuilder } from '@forgeax/engine-render-graph';
6
+ import type { BindGroup } from '@forgeax/engine-rhi';
5
7
  import { type RhiNullDevice, rhi } from '@forgeax/engine-rhi-null';
6
8
  import { Transform } from '@forgeax/engine-scene';
7
9
  import {
10
+ createStandardPbrArtifactReceipt,
8
11
  DEFAULT_STANDARD_PBR_PARAM_SCHEMA,
9
12
  DEFAULT_UNLIT_PARAM_SCHEMA,
10
13
  } from '@forgeax/engine-shader';
@@ -14,7 +17,12 @@ import { createRenderer as constructRenderer } from '../assembly/factory';
14
17
  import { Camera, MeshFilter, MeshRenderer, PointLight } from '../components';
15
18
  import type { MeshGpuHandles } from '../device/gpu-residency';
16
19
  import { BatchTopology } from '../gpu-driven/batch-topology';
17
- import { GpuDrivenProduction } from '../gpu-driven/production-raster';
20
+ import {
21
+ adaptStandardPbrFrameResources,
22
+ GpuDrivenProduction,
23
+ projectStandardPbrScene,
24
+ } from '../gpu-driven/production-raster';
25
+ import { GPU_DRIVEN_VIEW_WGSL } from '../gpu-driven/view-gpu';
18
26
  import { GpuBuffer } from '../gpu-resource';
19
27
  import { GpuScene } from '../gpu-scene';
20
28
  import {
@@ -23,11 +31,13 @@ import {
23
31
  GPU_BUFFER_USAGE_UNIFORM,
24
32
  GPU_BUFFER_USAGE_VERTEX,
25
33
  } from '../gpu-usage';
26
- import { makeZeroCameraFallbackSnapshot } from '../record/frame-snapshot';
34
+ import { makeZeroCameraFallbackSnapshot, worldEntityKey } from '../record/frame-snapshot';
27
35
  import type { RenderPipelineFrame } from '../render-pipeline';
28
36
  import type { MaterialSnapshot, RenderableSnapshot } from '../render-system-extract';
29
37
  import { RenderScene } from '../scene/render-scene';
30
38
 
39
+ const STANDARD_PBR_RECEIPT = createStandardPbrArtifactReceipt();
40
+
31
41
  const EMPTY_RESOURCE_CLASS = JSON.stringify({ textures: [], samplers: [], video: [] });
32
42
  const OUTPUT_TRANSFORM_WGSL =
33
43
  '@vertex fn vs_main(@builtin(vertex_index) i: u32) -> @builtin(position) vec4<f32> { ' +
@@ -126,7 +136,7 @@ function snapshot(entityKey: number, assetHandle: number): RenderableSnapshot {
126
136
  };
127
137
  }
128
138
 
129
- function mesh(device: RhiNullDevice, indexed = true): MeshGpuHandles {
139
+ function mesh(device: RhiNullDevice, indexed = true, withLod = false): MeshGpuHandles {
130
140
  const vertex = device
131
141
  .createBuffer({
132
142
  size: 144,
@@ -168,10 +178,401 @@ function mesh(device: RhiNullDevice, indexed = true): MeshGpuHandles {
168
178
  topology: 'triangle-list',
169
179
  },
170
180
  ],
181
+ ...(withLod
182
+ ? {
183
+ lodRanges: [
184
+ [{ first: 0, count: 3, baseVertex: 0 }],
185
+ [{ first: 3, count: 1, baseVertex: 0 }],
186
+ ],
187
+ }
188
+ : {}),
171
189
  };
172
190
  }
173
191
 
174
192
  describe('GPU-driven production projection', () => {
193
+ it('retains suppressed LOD candidates for selector accounting while admitting only active draws', async () => {
194
+ const adapter = (await rhi.requestAdapter()).unwrap();
195
+ const device = (await adapter.requestDevice()).unwrap() as RhiNullDevice;
196
+ const shader = (await rhi.createShaderModule(device, { code: 'synthetic' })).unwrap();
197
+ const viewLayout = device
198
+ .createBindGroupLayout({
199
+ entries: [{ binding: 0, visibility: 0x1, buffer: { type: 'uniform' } }],
200
+ })
201
+ .unwrap();
202
+ const projection = new RenderScene();
203
+ const lodMeshGuid = '00000000-0000-7000-8000-000000000001' as never;
204
+ const makeLodSnapshot = (entityKey: number): RenderableSnapshot => {
205
+ const base = snapshot(entityKey, 3);
206
+ const draw = base.gpuDrivenDraws?.[0];
207
+ if (draw === undefined) throw new Error('LOD test snapshot has no draw');
208
+ return {
209
+ ...base,
210
+ lods: [{ mesh: lodMeshGuid, screenCoverage: 0.5 }],
211
+ gpuDrivenDraws: [{ ...draw, lodRanges: [{ first: 3, count: 1, baseVertex: 0 }] }],
212
+ };
213
+ };
214
+ const delta = projection.apply([
215
+ { kind: 'create', snapshot: makeLodSnapshot(1) },
216
+ { kind: 'create', snapshot: makeLodSnapshot(2) },
217
+ ]);
218
+ const availability = GpuScene.create(device, 2).unwrap();
219
+ expect(availability.status).toBe('available');
220
+ if (availability.status !== 'available') return;
221
+ availability.scene.sync(delta).unwrap();
222
+ const topology = new BatchTopology();
223
+ topology.rebuild(projection.slotsSnapshot());
224
+ const production = new GpuDrivenProduction(device, {
225
+ createShaderModule: () => ok(shader),
226
+ });
227
+ const prepared = production
228
+ .prepare({
229
+ scene: {
230
+ scene: availability.scene,
231
+ plan: topology.plan(),
232
+ slots: projection.slotsSnapshot(),
233
+ },
234
+ camera: makeZeroCameraFallbackSnapshot(),
235
+ meshes: new Map([[3, mesh(device, true, true)]]),
236
+ viewBindGroupLayout: viewLayout,
237
+ meshResidencyEpoch: 1,
238
+ hdrp: false,
239
+ activeEntityKeys: new Set([worldEntityKey(0, 1)]),
240
+ activeEntityRevision: 1,
241
+ })
242
+ .unwrap();
243
+ expect(prepared?.entityKeys).toEqual(new Set([worldEntityKey(0, 1)]));
244
+ // Both candidates are uploaded to the selector (2 * 304 bytes), while
245
+ // only the active entity is admitted into the compacted draw stream.
246
+ expect(production.inspect()).toMatchObject({
247
+ candidateUploadBytes: 2 * 304,
248
+ batchCount: 1,
249
+ indirectDrawCount: 1,
250
+ });
251
+ if (prepared === undefined) return;
252
+ const projected = prepared
253
+ .project(new RenderGraphBuilder<RenderPipelineFrame>(), 'rgba8unorm', 1)
254
+ .unwrap();
255
+ expect(projected.accesses.length).toBeGreaterThan(0);
256
+ expect(production.inspect()).toMatchObject({
257
+ batchBindGroupCreates: 1,
258
+ indirectDrawCount: 1,
259
+ });
260
+ expect(production.inspect().indirectDrawCount).toBeGreaterThan(0);
261
+ expect(production.inspect().indirectDrawCount).toBeLessThanOrEqual(
262
+ production.inspect().batchCount,
263
+ );
264
+
265
+ const suppressed = production
266
+ .prepare({
267
+ scene: {
268
+ scene: availability.scene,
269
+ plan: topology.plan(),
270
+ slots: projection.slotsSnapshot(),
271
+ },
272
+ camera: makeZeroCameraFallbackSnapshot(),
273
+ meshes: new Map([[3, mesh(device, true, true)]]),
274
+ viewBindGroupLayout: viewLayout,
275
+ meshResidencyEpoch: 1,
276
+ hdrp: false,
277
+ activeEntityKeys: new Set(),
278
+ activeEntityRevision: 2,
279
+ })
280
+ .unwrap();
281
+ expect(suppressed?.topologySignature).not.toBe(prepared.topologySignature);
282
+ expect(production.inspect()).toMatchObject({
283
+ batchCount: 1,
284
+ indirectDrawCount: 0,
285
+ });
286
+
287
+ const restored = production
288
+ .prepare({
289
+ scene: {
290
+ scene: availability.scene,
291
+ plan: topology.plan(),
292
+ slots: projection.slotsSnapshot(),
293
+ },
294
+ camera: makeZeroCameraFallbackSnapshot(),
295
+ meshes: new Map([[3, mesh(device, true, true)]]),
296
+ viewBindGroupLayout: viewLayout,
297
+ meshResidencyEpoch: 1,
298
+ hdrp: false,
299
+ activeEntityKeys: new Set([worldEntityKey(0, 1)]),
300
+ activeEntityRevision: 3,
301
+ })
302
+ .unwrap();
303
+ expect(restored?.topologySignature).not.toBe(suppressed?.topologySignature);
304
+ expect(production.inspect()).toMatchObject({
305
+ batchCount: 1,
306
+ indirectDrawCount: 1,
307
+ });
308
+ expect(GPU_DRIVEN_VIEW_WGSL.indexOf('atomicAdd(&counters[lodCounterIndex')).toBeLessThan(
309
+ GPU_DRIVEN_VIEW_WGSL.indexOf('if (candidate.submitAdmission == 0u)'),
310
+ );
311
+ expect(GPU_DRIVEN_VIEW_WGSL.indexOf('if (candidate.submitAdmission == 0u)')).toBeLessThan(
312
+ GPU_DRIVEN_VIEW_WGSL.indexOf('atomicAdd(&counters[counterIndex(candidate.batchIndex)], 1u)'),
313
+ );
314
+ production.dispose();
315
+ availability.scene.dispose();
316
+ });
317
+
318
+ it('keeps a suppressed-only batch in selector telemetry without raster resources', async () => {
319
+ const adapter = (await rhi.requestAdapter()).unwrap();
320
+ const device = (await adapter.requestDevice()).unwrap() as RhiNullDevice;
321
+ const shader = (await rhi.createShaderModule(device, { code: 'synthetic' })).unwrap();
322
+ const viewLayout = device
323
+ .createBindGroupLayout({
324
+ entries: [{ binding: 0, visibility: 0x1, buffer: { type: 'uniform' } }],
325
+ })
326
+ .unwrap();
327
+ const projection = new RenderScene();
328
+ const candidate = snapshot(1, 3);
329
+ const delta = projection.apply([{ kind: 'create', snapshot: candidate }]);
330
+ const availability = GpuScene.create(device, 1).unwrap();
331
+ expect(availability.status).toBe('available');
332
+ if (availability.status !== 'available') return;
333
+ availability.scene.sync(delta).unwrap();
334
+ const topology = new BatchTopology();
335
+ topology.rebuild(projection.slotsSnapshot());
336
+ const production = new GpuDrivenProduction(device, {
337
+ createShaderModule: () => ok(shader),
338
+ });
339
+ const prepared = production
340
+ .prepare({
341
+ scene: {
342
+ scene: availability.scene,
343
+ plan: topology.plan(),
344
+ slots: projection.slotsSnapshot(),
345
+ },
346
+ camera: makeZeroCameraFallbackSnapshot(),
347
+ meshes: new Map([[3, mesh(device)]]),
348
+ viewBindGroupLayout: viewLayout,
349
+ meshResidencyEpoch: 1,
350
+ hdrp: false,
351
+ activeEntityKeys: new Set(),
352
+ activeEntityRevision: 1,
353
+ })
354
+ .unwrap();
355
+ expect(prepared).toBeDefined();
356
+ expect(production.requiresSceneRows()).toBe(true);
357
+ expect(production.inspect()).toMatchObject({
358
+ batchCount: 1,
359
+ indirectDrawCount: 0,
360
+ batchBindGroupCreates: 0,
361
+ });
362
+ if (prepared === undefined) return;
363
+ prepared.project(new RenderGraphBuilder<RenderPipelineFrame>(), 'rgba8unorm', 1).unwrap();
364
+ expect(production.inspect()).toMatchObject({
365
+ batchCount: 1,
366
+ indirectDrawCount: 0,
367
+ batchBindGroupCreates: 0,
368
+ });
369
+ production.dispose();
370
+ availability.scene.dispose();
371
+ });
372
+
373
+ it('keeps GPU batches World-homogeneous for same-submit counter attribution', async () => {
374
+ const adapter = (await rhi.requestAdapter()).unwrap();
375
+ const device = (await adapter.requestDevice()).unwrap() as RhiNullDevice;
376
+ const shader = (await rhi.createShaderModule(device, { code: 'synthetic' })).unwrap();
377
+ const viewLayout = device
378
+ .createBindGroupLayout({
379
+ entries: [{ binding: 0, visibility: 0x1, buffer: { type: 'uniform' } }],
380
+ })
381
+ .unwrap();
382
+ const projection = new RenderScene();
383
+ const first = snapshot(1, 3);
384
+ const second = { ...snapshot(2, 3), worldId: 1 };
385
+ const delta = projection.apply([
386
+ { kind: 'create', snapshot: first },
387
+ { kind: 'create', snapshot: second },
388
+ ]);
389
+ const availability = GpuScene.create(device, 2).unwrap();
390
+ expect(availability.status).toBe('available');
391
+ if (availability.status !== 'available') return;
392
+ availability.scene.sync(delta).unwrap();
393
+ const topology = new BatchTopology();
394
+ topology.rebuild(projection.slotsSnapshot());
395
+ const production = new GpuDrivenProduction(device, {
396
+ createShaderModule: () => ok(shader),
397
+ });
398
+ const prepared = production
399
+ .prepare({
400
+ scene: {
401
+ scene: availability.scene,
402
+ plan: topology.plan(),
403
+ slots: projection.slotsSnapshot(),
404
+ },
405
+ camera: makeZeroCameraFallbackSnapshot(),
406
+ meshes: new Map([[3, mesh(device)]]),
407
+ viewBindGroupLayout: viewLayout,
408
+ meshResidencyEpoch: 1,
409
+ hdrp: false,
410
+ })
411
+ .unwrap();
412
+ expect(prepared).toBeDefined();
413
+ expect(production.inspect()).toMatchObject({ batchCount: 2, indirectDrawCount: 2 });
414
+ production.dispose();
415
+ availability.scene.dispose();
416
+ });
417
+
418
+ it('uses renderer-local World keys for CPU validation telemetry', async () => {
419
+ const adapter = (await rhi.requestAdapter()).unwrap();
420
+ const device = (await adapter.requestDevice()).unwrap() as RhiNullDevice;
421
+ const shader = (await rhi.createShaderModule(device, { code: 'synthetic' })).unwrap();
422
+ const production = new GpuDrivenProduction(device, {
423
+ createShaderModule: () => ok(shader),
424
+ });
425
+ const gpuLodRow = {
426
+ ...snapshot(7, 3),
427
+ worldId: 1,
428
+ lods: [{ mesh: '00000000-0000-7000-8000-000000000001' as never, screenCoverage: 0.5 }],
429
+ };
430
+ const plainFallbackRow = { ...snapshot(2, 3), worldId: 0 };
431
+ const gpuOwned = new Set([worldEntityKey(101, gpuLodRow.entityKey)]);
432
+ const worldKeys = [202, 101];
433
+
434
+ // A GPU-owned row is removed from validation before this telemetry hook;
435
+ // the remaining plain row must still be counted as the one CPU fallback.
436
+ production.recordCpuValidation([{ source: plainFallbackRow }], gpuOwned, worldKeys);
437
+ expect(production.inspect()).toMatchObject({
438
+ cpuFallbackDrawItems: 1,
439
+ validatedGpuOwnedRows: 0,
440
+ });
441
+
442
+ // Prove the same renderer-local namespace recognizes the GPU row when a
443
+ // diagnostic caller supplies it directly, even though its extracted
444
+ // RenderableSnapshot still carries the reordered array index.
445
+ production.recordCpuValidation([{ source: gpuLodRow }], gpuOwned, worldKeys);
446
+ expect(production.inspect()).toMatchObject({
447
+ cpuFallbackDrawItems: 1,
448
+ validatedGpuOwnedRows: 1,
449
+ });
450
+ production.dispose();
451
+ });
452
+
453
+ it('coalesces LOD candidates that share the selected geometry range', async () => {
454
+ const adapter = (await rhi.requestAdapter()).unwrap();
455
+ const device = (await adapter.requestDevice()).unwrap() as RhiNullDevice;
456
+ const shader = (await rhi.createShaderModule(device, { code: 'synthetic' })).unwrap();
457
+ const viewLayout = device
458
+ .createBindGroupLayout({
459
+ entries: [{ binding: 0, visibility: 0x1, buffer: { type: 'uniform' } }],
460
+ })
461
+ .unwrap();
462
+ const projection = new RenderScene();
463
+ const lodMeshGuid = '00000000-0000-7000-8000-000000000001' as never;
464
+ const makeLodSnapshot = (entityKey: number): RenderableSnapshot => {
465
+ const base = snapshot(entityKey, 3);
466
+ const draw = base.gpuDrivenDraws?.[0];
467
+ if (draw === undefined) throw new Error('LOD test snapshot has no draw');
468
+ return {
469
+ ...base,
470
+ lods: [{ mesh: lodMeshGuid, screenCoverage: 0.5 }],
471
+ gpuDrivenDraws: [{ ...draw, lodRanges: [{ first: 3, count: 1, baseVertex: 0 }] }],
472
+ };
473
+ };
474
+ const delta = projection.apply([
475
+ { kind: 'create', snapshot: makeLodSnapshot(1) },
476
+ { kind: 'create', snapshot: makeLodSnapshot(2) },
477
+ ]);
478
+ const availability = GpuScene.create(device, 2).unwrap();
479
+ expect(availability.status).toBe('available');
480
+ if (availability.status !== 'available') return;
481
+ availability.scene.sync(delta).unwrap();
482
+ const topology = new BatchTopology();
483
+ topology.rebuild(projection.slotsSnapshot());
484
+ const production = new GpuDrivenProduction(device, {
485
+ createShaderModule: () => ok(shader),
486
+ });
487
+ const prepared = production
488
+ .prepare({
489
+ scene: {
490
+ scene: availability.scene,
491
+ plan: topology.plan(),
492
+ slots: projection.slotsSnapshot(),
493
+ },
494
+ camera: { ...makeZeroCameraFallbackSnapshot(), position: vec3.create(0, 0, 5) },
495
+ meshes: new Map([[3, mesh(device, true, true)]]),
496
+ viewBindGroupLayout: viewLayout,
497
+ meshResidencyEpoch: 1,
498
+ hdrp: false,
499
+ })
500
+ .unwrap();
501
+ expect(prepared).toBeDefined();
502
+ expect(production.inspect()).toMatchObject({
503
+ batchCount: 1,
504
+ indirectDrawCount: 1,
505
+ candidateUploadBytes: 2 * 304,
506
+ });
507
+ production.dispose();
508
+ availability.scene.dispose();
509
+ });
510
+
511
+ it('projects scene rows from the receipt-selected scene-index entry', () => {
512
+ const result = projectStandardPbrScene({
513
+ artifact: {
514
+ material: 'forgeax::default-standard-pbr',
515
+ pass: 'forward',
516
+ wgsl: 'standard-pbr',
517
+ layoutIdentity: STANDARD_PBR_RECEIPT.reflection.layoutIdentity,
518
+ bindings: [],
519
+ deps: [],
520
+ vertexInputs: STANDARD_PBR_RECEIPT.vertexInputs.map((input) => ({ ...input })),
521
+ receipt: STANDARD_PBR_RECEIPT,
522
+ },
523
+ candidates: [
524
+ {
525
+ primitiveIndex: 3,
526
+ world: Float32Array.from([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 2, 3, 4, 1]),
527
+ },
528
+ ],
529
+ });
530
+ expect(result.ok).toBe(true);
531
+ if (!result.ok) return;
532
+ expect(result.value.entryPoint).toBe('vs_scene_index');
533
+ expect(result.value.rows[0]).toBe(1);
534
+ expect(result.value.rows[12]).toBe(2);
535
+ expect(result.value.rows[13]).toBe(3);
536
+ expect(result.value.rows[14]).toBe(4);
537
+ });
538
+
539
+ it('adapts the four existing Standard PBR groups from one receipt', () => {
540
+ const groups = {
541
+ view: { id: 'view' },
542
+ material: { id: 'material' },
543
+ mesh: { id: 'mesh' },
544
+ instances: { id: 'instances' },
545
+ } as unknown as {
546
+ readonly view: BindGroup;
547
+ readonly material: BindGroup;
548
+ readonly mesh: BindGroup;
549
+ readonly instances: BindGroup;
550
+ };
551
+ const result = adaptStandardPbrFrameResources({
552
+ artifact: {
553
+ material: 'forgeax::default-standard-pbr',
554
+ pass: 'forward',
555
+ wgsl: 'standard-pbr',
556
+ layoutIdentity: STANDARD_PBR_RECEIPT.reflection.layoutIdentity,
557
+ bindings: [],
558
+ deps: [],
559
+ vertexInputs: STANDARD_PBR_RECEIPT.vertexInputs.map((input) => ({ ...input })),
560
+ receipt: STANDARD_PBR_RECEIPT,
561
+ },
562
+ ...groups,
563
+ });
564
+ expect(result.ok).toBe(true);
565
+ if (!result.ok) return;
566
+ expect(result.value.entryPoint).toBe('vs_scene_index');
567
+ expect(result.value.bindGroups).toEqual([
568
+ groups.view,
569
+ groups.material,
570
+ groups.mesh,
571
+ groups.instances,
572
+ ]);
573
+ expect(result.value.resourceSlots).toEqual(STANDARD_PBR_RECEIPT.resourceSlots);
574
+ });
575
+
175
576
  it('projects aligned multi-batch compute work into one indirect raster pass', async () => {
176
577
  const adapter = (await rhi.requestAdapter()).unwrap();
177
578
  const device = (await adapter.requestDevice()).unwrap() as RhiNullDevice;
@@ -194,7 +595,7 @@ describe('GPU-driven production projection', () => {
194
595
  const firstDraw = first.gpuDrivenDraws?.[0];
195
596
  expect(firstDraw).toBeDefined();
196
597
  if (firstDraw === undefined) return;
197
- const second = snapshot(2, 4);
598
+ const second = { ...snapshot(2, 4), worldId: 1 };
198
599
  const secondDraw = second.gpuDrivenDraws?.[0];
199
600
  expect(secondDraw).toBeDefined();
200
601
  if (secondDraw === undefined) return;
@@ -252,16 +653,17 @@ describe('GPU-driven production projection', () => {
252
653
  meshes,
253
654
  viewBindGroupLayout: viewLayout,
254
655
  meshResidencyEpoch: 1,
656
+ hdrp: false,
255
657
  })
256
658
  .unwrap();
257
659
  expect(prepared).toBeDefined();
258
660
  if (prepared === undefined) return;
259
- expect([...prepared.entityKeys]).toEqual([1, 2]);
661
+ expect([...prepared.entityKeys]).toEqual([worldEntityKey(0, 1), worldEntityKey(1, 2)]);
260
662
  expect(production.inspect()).toMatchObject({
261
663
  gpuOwnedSnapshotsMaterialized: 2,
262
664
  filteredPlanBuilds: 1,
263
- candidateUploadBytes: 84,
264
- batchUploadBytes: 84,
665
+ candidateUploadBytes: 912,
666
+ batchUploadBytes: 96,
265
667
  viewBindGroupCreates: 1,
266
668
  });
267
669
 
@@ -276,6 +678,7 @@ describe('GPU-driven production projection', () => {
276
678
  meshes,
277
679
  viewBindGroupLayout: viewLayout,
278
680
  meshResidencyEpoch: 1,
681
+ hdrp: false,
279
682
  })
280
683
  .unwrap();
281
684
  expect(stable?.topologySignature).toBe(prepared.topologySignature);
@@ -287,10 +690,40 @@ describe('GPU-driven production projection', () => {
287
690
  viewConstantsUploadBytes: 112,
288
691
  viewBindGroupCreates: 0,
289
692
  batchBindGroupCreates: 0,
693
+ indirectDrawCount: 3,
290
694
  });
291
695
 
696
+ // The scene topology is unchanged, but projectedHeight is camera-owned
697
+ // input to the GPU LOD selector. A moved camera must rebuild the filtered
698
+ // candidate plan instead of reusing the previous view's heights.
699
+ const movedCamera = {
700
+ ...makeZeroCameraFallbackSnapshot(),
701
+ position: vec3.create(0, 0, 6),
702
+ };
703
+ const moved = production
704
+ .prepare({
705
+ scene: {
706
+ scene: availability.scene,
707
+ plan: topology.plan(),
708
+ slots: projection.slotsSnapshot(),
709
+ },
710
+ camera: movedCamera,
711
+ meshes,
712
+ viewBindGroupLayout: viewLayout,
713
+ meshResidencyEpoch: 1,
714
+ hdrp: false,
715
+ })
716
+ .unwrap();
717
+ expect(moved).toBeDefined();
718
+ expect(production.inspect()).toMatchObject({
719
+ filteredPlanBuilds: 1,
720
+ candidateUploadBytes: 912,
721
+ batchUploadBytes: 96,
722
+ });
723
+ if (moved === undefined) return;
724
+
292
725
  const graph = new RenderGraphBuilder<RenderPipelineFrame>();
293
- const gpu = prepared.project(graph, 'rgba8unorm', 1).unwrap();
726
+ const gpu = moved.project(graph, 'rgba8unorm', 1).unwrap();
294
727
  const color = graph
295
728
  .createTexture('gpu-driven-production-color', {
296
729
  format: 'rgba8unorm',
@@ -328,6 +761,54 @@ describe('GPU-driven production projection', () => {
328
761
  ]);
329
762
  expect(device.totalDispatchCount).toBe(3);
330
763
  expect(device.totalDrawCount).toBe(3);
764
+ expect(production.inspect().indirectDrawCount).toBe(3);
765
+ expect(production.inspect().indirectDrawCount).toBeLessThanOrEqual(
766
+ production.inspect().batchCount,
767
+ );
768
+
769
+ // The facet projection is the same admission set consumed by the GPU
770
+ // candidate upload. A suppressed entity must disappear from the indirect
771
+ // plan without making the remaining entity fall back to CPU validation.
772
+ const activeOnly = production
773
+ .prepare({
774
+ scene: {
775
+ scene: availability.scene,
776
+ plan: topology.plan(),
777
+ slots: projection.slotsSnapshot(),
778
+ },
779
+ camera: movedCamera,
780
+ meshes,
781
+ viewBindGroupLayout: viewLayout,
782
+ meshResidencyEpoch: 1,
783
+ hdrp: false,
784
+ activeEntityKeys: new Set([worldEntityKey(0, 1)]),
785
+ activeEntityRevision: 1,
786
+ })
787
+ .unwrap();
788
+ expect(activeOnly?.entityKeys).toEqual(new Set([1]));
789
+ expect(activeOnly?.ownsAllRenderables).toBe(true);
790
+
791
+ const changedActive = production
792
+ .prepare({
793
+ scene: {
794
+ scene: availability.scene,
795
+ plan: topology.plan(),
796
+ slots: projection.slotsSnapshot(),
797
+ },
798
+ camera: movedCamera,
799
+ meshes,
800
+ viewBindGroupLayout: viewLayout,
801
+ meshResidencyEpoch: 1,
802
+ hdrp: false,
803
+ activeEntityKeys: new Set([worldEntityKey(1, 2)]),
804
+ activeEntityRevision: 2,
805
+ })
806
+ .unwrap();
807
+ expect(changedActive?.entityKeys).toEqual(new Set([worldEntityKey(1, 2)]));
808
+ // Production inspection counters are per-prepare-frame facts. The changed
809
+ // revision must trigger exactly one rebuild for this frame, not accumulate
810
+ // the prior frame's count.
811
+ expect(production.inspect().filteredPlanBuilds).toBe(1);
331
812
 
332
813
  const replacement = GpuScene.create(device, 2).unwrap();
333
814
  expect(replacement.status).toBe('available');
@@ -344,6 +825,7 @@ describe('GPU-driven production projection', () => {
344
825
  meshes,
345
826
  viewBindGroupLayout: viewLayout,
346
827
  meshResidencyEpoch: 1,
828
+ hdrp: false,
347
829
  })
348
830
  .unwrap();
349
831
  expect(replacementPrepared?.topologySignature).not.toBe(prepared.topologySignature);
@@ -353,6 +835,75 @@ describe('GPU-driven production projection', () => {
353
835
  availability.scene.dispose();
354
836
  });
355
837
 
838
+ it('keeps an ineligible production topology empty while the camera moves', async () => {
839
+ const adapter = (await rhi.requestAdapter()).unwrap();
840
+ const device = (await adapter.requestDevice()).unwrap() as RhiNullDevice;
841
+ const shader = (await rhi.createShaderModule(device, { code: 'synthetic' })).unwrap();
842
+ const viewLayout = device
843
+ .createBindGroupLayout({
844
+ entries: [{ binding: 0, visibility: 0x1, buffer: { type: 'uniform' } }],
845
+ })
846
+ .unwrap();
847
+ const base = snapshot(1, 3);
848
+ const material = {
849
+ ...base.material,
850
+ materialShaderId: 'forgeax::default-standard-pbr',
851
+ };
852
+ const projection = new RenderScene();
853
+ const delta = projection.apply([
854
+ { kind: 'create', snapshot: { ...base, material, materials: [material] } },
855
+ ]);
856
+ const availability = GpuScene.create(device, 1).unwrap();
857
+ expect(availability.status).toBe('available');
858
+ if (availability.status !== 'available') return;
859
+ availability.scene.sync(delta).unwrap();
860
+ const topology = new BatchTopology();
861
+ topology.rebuild(projection.slotsSnapshot());
862
+ const production = new GpuDrivenProduction(device, {
863
+ createShaderModule: () => ok(shader),
864
+ });
865
+ const input = {
866
+ scene: {
867
+ scene: availability.scene,
868
+ plan: topology.plan(),
869
+ slots: projection.slotsSnapshot(),
870
+ },
871
+ meshes: new Map([[3, mesh(device)]]),
872
+ viewBindGroupLayout: viewLayout,
873
+ hdrp: false,
874
+ } as const;
875
+
876
+ expect(
877
+ production
878
+ .prepare({
879
+ ...input,
880
+ camera: makeZeroCameraFallbackSnapshot(),
881
+ meshResidencyEpoch: 1,
882
+ })
883
+ .unwrap(),
884
+ ).toBeUndefined();
885
+ expect(production.requiresSceneRows()).toBe(false);
886
+ expect(production.inspect().filteredPlanBuilds).toBe(1);
887
+ expect(
888
+ production
889
+ .prepare({
890
+ ...input,
891
+ camera: { ...makeZeroCameraFallbackSnapshot(), position: vec3.create(0, 0, 6) },
892
+ meshResidencyEpoch: 2,
893
+ })
894
+ .unwrap(),
895
+ ).toBeUndefined();
896
+ expect(production.requiresSceneRows()).toBe(false);
897
+ expect(production.inspect()).toMatchObject({
898
+ filteredPlanBuilds: 0,
899
+ gpuOwnedSnapshotsMaterialized: 0,
900
+ gpuOwnedEntityCount: 0,
901
+ });
902
+
903
+ production.dispose();
904
+ availability.scene.dispose();
905
+ });
906
+
356
907
  it('activates inside the renderer-owned clustered Standard graph and removes the CPU draw', async () => {
357
908
  const canvas = {
358
909
  width: 1,
@@ -401,7 +952,19 @@ describe('GPU-driven production projection', () => {
401
952
  expect(renderer.perFramePassNames).toEqual(
402
953
  expect.arrayContaining(['cluster-membership-producer', 'main', 'output-transform']),
403
954
  );
404
- expect(device.totalDrawCount).toBeGreaterThan(0);
955
+ expect(renderer.perFramePassNames).toContain('output-transform');
956
+ // Storage-buffer LDR frames keep the scene linear until the one output
957
+ // transform writes the encoded surface; the GPU-driven indirect raster
958
+ // draw and that fullscreen pass are the only draws in this topology.
959
+ // A plain mesh has no LOD relation, so it stays on the CPU visibility
960
+ // path for occlusion while GPU-driven raster + output-transform remain
961
+ // the only draws in this graph.
962
+ expect(device.totalDrawCount).toBe(2);
963
+ expect(renderer.inspect().lodOcclusion).toMatchObject({
964
+ fallback: { active: false },
965
+ degradation: { active: false },
966
+ pagePressure: { used: 0, capacity: 3 * 4096 },
967
+ });
405
968
  expect(renderer.renderScene).toMatchObject({
406
969
  worldEntitiesScanned: 0,
407
970
  fullRebuilds: 1,