@forgeax/engine-render 0.1.21 → 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 (426) hide show
  1. package/README.md +150 -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.map +1 -1
  5. package/dist/assembly/factory.d.ts +14 -6
  6. package/dist/assembly/factory.d.ts.map +1 -1
  7. package/dist/assembly/host-contract.d.ts +10 -0
  8. package/dist/assembly/host-contract.d.ts.map +1 -1
  9. package/dist/authoring.mjs +1 -1
  10. package/dist/{chunk-PCHBPYML.mjs → chunk-4L3P6DT5.mjs} +2 -2
  11. package/dist/chunk-4L3P6DT5.mjs.map +1 -0
  12. package/dist/{chunk-VFPLTKCU.mjs → chunk-4NPUASZR.mjs} +15 -4
  13. package/dist/chunk-4NPUASZR.mjs.map +1 -0
  14. package/dist/chunk-DIYW3M5D.mjs +5085 -0
  15. package/dist/chunk-DIYW3M5D.mjs.map +1 -0
  16. package/dist/chunk-FBC4R44J.mjs +1003 -0
  17. package/dist/chunk-FBC4R44J.mjs.map +1 -0
  18. package/dist/{chunk-FS2KNUQH.mjs → chunk-FF7K3FSO.mjs} +253 -489
  19. package/dist/chunk-FF7K3FSO.mjs.map +1 -0
  20. package/dist/chunk-HZUPSYW7.mjs +1104 -0
  21. package/dist/chunk-HZUPSYW7.mjs.map +1 -0
  22. package/dist/{chunk-6OPXDQ2E.mjs → chunk-QODA5UKU.mjs} +33 -294
  23. package/dist/chunk-QODA5UKU.mjs.map +1 -0
  24. package/dist/chunk-YALPS44A.mjs +296 -0
  25. package/dist/chunk-YALPS44A.mjs.map +1 -0
  26. package/dist/{chunk-5YADUCA7.mjs → chunk-ZUWKAZ2I.mjs} +585 -991
  27. package/dist/chunk-ZUWKAZ2I.mjs.map +1 -0
  28. package/dist/cluster-binner.d.ts +5 -0
  29. package/dist/cluster-binner.d.ts.map +1 -1
  30. package/dist/components/index.d.ts +3 -0
  31. package/dist/components/index.d.ts.map +1 -1
  32. package/dist/components/light-helpers.d.ts +4 -0
  33. package/dist/components/light-helpers.d.ts.map +1 -1
  34. package/dist/components/light-probe.d.ts +9 -0
  35. package/dist/components/light-probe.d.ts.map +1 -0
  36. package/dist/components/rect-area-light.d.ts +12 -0
  37. package/dist/components/rect-area-light.d.ts.map +1 -0
  38. package/dist/components/scene-instance.d.ts +4 -0
  39. package/dist/components/scene-instance.d.ts.map +1 -1
  40. package/dist/components/spot-light.d.ts +3 -0
  41. package/dist/components/spot-light.d.ts.map +1 -1
  42. package/dist/construct-renderer.mjs +10437 -10044
  43. package/dist/construct-renderer.mjs.map +1 -1
  44. package/dist/device/gpu-residency.d.ts +8 -1
  45. package/dist/device/gpu-residency.d.ts.map +1 -1
  46. package/dist/device/lifecycle-transaction.d.ts +2 -0
  47. package/dist/device/lifecycle-transaction.d.ts.map +1 -0
  48. package/dist/environment/lifecycle.d.ts.map +1 -1
  49. package/dist/errors/gpu-driven.d.ts +26 -0
  50. package/dist/errors/gpu-driven.d.ts.map +1 -0
  51. package/dist/errors/index.d.ts +1 -0
  52. package/dist/errors/index.d.ts.map +1 -1
  53. package/dist/errors/recover.d.ts +9 -1
  54. package/dist/errors/recover.d.ts.map +1 -1
  55. package/dist/errors/render.d.ts +26 -1
  56. package/dist/errors/render.d.ts.map +1 -1
  57. package/dist/extract/gpu-driven.d.ts +50 -0
  58. package/dist/extract/gpu-driven.d.ts.map +1 -0
  59. package/dist/extract/visibility.d.ts.map +1 -1
  60. package/dist/gpu-driven/batch-topology.d.ts +34 -0
  61. package/dist/gpu-driven/batch-topology.d.ts.map +1 -1
  62. package/dist/gpu-driven/prepared-draw.d.ts +63 -0
  63. package/dist/gpu-driven/prepared-draw.d.ts.map +1 -0
  64. package/dist/gpu-driven/production-raster.d.ts +98 -2
  65. package/dist/gpu-driven/production-raster.d.ts.map +1 -1
  66. package/dist/gpu-driven/view-gpu.d.ts +64 -1
  67. package/dist/gpu-driven/view-gpu.d.ts.map +1 -1
  68. package/dist/gpu-scene-schema.d.ts +14 -0
  69. package/dist/gpu-scene-schema.d.ts.map +1 -1
  70. package/dist/gpu-scene.d.ts +8 -1
  71. package/dist/gpu-scene.d.ts.map +1 -1
  72. package/dist/gpu-usage.d.ts +0 -1
  73. package/dist/gpu-usage.d.ts.map +1 -1
  74. package/dist/hdrp-buffers.d.ts +3 -3
  75. package/dist/hdrp-buffers.d.ts.map +1 -1
  76. package/dist/index.d.ts +12 -1
  77. package/dist/index.d.ts.map +1 -1
  78. package/dist/index.mjs +90 -5
  79. package/dist/index.mjs.map +1 -1
  80. package/dist/inspection-types.d.ts +163 -1
  81. package/dist/inspection-types.d.ts.map +1 -1
  82. package/dist/internal.d.ts +9 -0
  83. package/dist/internal.d.ts.map +1 -1
  84. package/dist/internal.mjs +23 -2
  85. package/dist/internal.mjs.map +1 -1
  86. package/dist/light-buffer-layout.d.ts +58 -149
  87. package/dist/light-buffer-layout.d.ts.map +1 -1
  88. package/dist/materials.d.ts.map +1 -1
  89. package/dist/pbr-pipeline.d.ts +26 -12
  90. package/dist/pbr-pipeline.d.ts.map +1 -1
  91. package/dist/pipeline/standard-forward-lane.d.ts.map +1 -1
  92. package/dist/pipeline/standard-lighting/graph.d.ts.map +1 -1
  93. package/dist/pipeline/standard-lighting/light-frame.d.ts +3 -3
  94. package/dist/pipeline/standard-lighting/light-frame.d.ts.map +1 -1
  95. package/dist/pipeline/standard-lighting/topology.d.ts +1 -1
  96. package/dist/pipeline/standard-lighting/topology.d.ts.map +1 -1
  97. package/dist/pipeline/standard-lighting/transport.d.ts.map +1 -1
  98. package/dist/pipeline/standard-pipeline.d.ts.map +1 -1
  99. package/dist/pipeline-builder.d.ts +2 -0
  100. package/dist/pipeline-builder.d.ts.map +1 -1
  101. package/dist/pipeline-spec.d.ts +1 -1
  102. package/dist/pipeline-spec.d.ts.map +1 -1
  103. package/dist/plugin.d.ts.map +1 -1
  104. package/dist/prepare/extended-lighting/ltc-resources.d.ts +31 -0
  105. package/dist/prepare/extended-lighting/ltc-resources.d.ts.map +1 -0
  106. package/dist/prepare/extended-lighting/resources.d.ts +72 -0
  107. package/dist/prepare/extended-lighting/resources.d.ts.map +1 -0
  108. package/dist/prepare/extended-lighting/spot-modifiers.d.ts +42 -0
  109. package/dist/prepare/extended-lighting/spot-modifiers.d.ts.map +1 -0
  110. package/dist/prepare/extended-lighting/state.d.ts +24 -0
  111. package/dist/prepare/extended-lighting/state.d.ts.map +1 -0
  112. package/dist/record/frame-lighting.d.ts +8 -6
  113. package/dist/record/frame-lighting.d.ts.map +1 -1
  114. package/dist/record/frame-snapshot.d.ts +50 -10
  115. package/dist/record/frame-snapshot.d.ts.map +1 -1
  116. package/dist/record/frame.d.ts +7 -3
  117. package/dist/record/frame.d.ts.map +1 -1
  118. package/dist/record/gpu-timing.d.ts +19 -7
  119. package/dist/record/gpu-timing.d.ts.map +1 -1
  120. package/dist/record/helpers.d.ts +0 -20
  121. package/dist/record/helpers.d.ts.map +1 -1
  122. package/dist/record/main-pass-geometry.d.ts +6 -1
  123. package/dist/record/main-pass-geometry.d.ts.map +1 -1
  124. package/dist/record/main-pass-material.d.ts +3 -1
  125. package/dist/record/main-pass-material.d.ts.map +1 -1
  126. package/dist/record/main-pass-sprite-draws.d.ts.map +1 -1
  127. package/dist/record/mesh-ssbo.d.ts.map +1 -1
  128. package/dist/record/render-context.d.ts +57 -6
  129. package/dist/record/render-context.d.ts.map +1 -1
  130. package/dist/record/shadow-pass.d.ts.map +1 -1
  131. package/dist/record/typed-frame-graph.d.ts +49 -2
  132. package/dist/record/typed-frame-graph.d.ts.map +1 -1
  133. package/dist/record/view-ubo.d.ts +1 -2
  134. package/dist/record/view-ubo.d.ts.map +1 -1
  135. package/dist/reflection/inspection.d.ts +10 -0
  136. package/dist/reflection/inspection.d.ts.map +1 -0
  137. package/dist/reflection/projection.d.ts +49 -0
  138. package/dist/reflection/projection.d.ts.map +1 -1
  139. package/dist/reflection/record-owner.d.ts +60 -0
  140. package/dist/reflection/record-owner.d.ts.map +1 -0
  141. package/dist/render-contract.d.ts +14 -8
  142. package/dist/render-contract.d.ts.map +1 -1
  143. package/dist/render-pipeline.d.ts +7 -0
  144. package/dist/render-pipeline.d.ts.map +1 -1
  145. package/dist/render-system-extract.d.ts +104 -16
  146. package/dist/render-system-extract.d.ts.map +1 -1
  147. package/dist/render-system.d.ts +12 -1
  148. package/dist/render-system.d.ts.map +1 -1
  149. package/dist/scene/probe-blend-record.d.ts +22 -0
  150. package/dist/scene/probe-blend-record.d.ts.map +1 -0
  151. package/dist/scene/probe-blend.d.ts +142 -0
  152. package/dist/scene/probe-blend.d.ts.map +1 -0
  153. package/dist/scene/render-scene-types.d.ts +0 -6
  154. package/dist/scene/render-scene-types.d.ts.map +1 -1
  155. package/dist/scene/render-scene.d.ts +103 -9
  156. package/dist/scene/render-scene.d.ts.map +1 -1
  157. package/dist/scene/visibility/bounds.d.ts +20 -0
  158. package/dist/scene/visibility/bounds.d.ts.map +1 -0
  159. package/dist/scene/visibility/budget.d.ts +16 -0
  160. package/dist/scene/visibility/budget.d.ts.map +1 -0
  161. package/dist/scene/visibility/facet.d.ts +73 -0
  162. package/dist/scene/visibility/facet.d.ts.map +1 -0
  163. package/dist/scene/visibility/gpu-frame-timing.d.ts +41 -0
  164. package/dist/scene/visibility/gpu-frame-timing.d.ts.map +1 -0
  165. package/dist/scene/visibility/gpu-lod.d.ts +46 -0
  166. package/dist/scene/visibility/gpu-lod.d.ts.map +1 -0
  167. package/dist/scene/visibility/inspection.d.ts +132 -0
  168. package/dist/scene/visibility/inspection.d.ts.map +1 -0
  169. package/dist/scene/visibility/lod-selector.d.ts +25 -0
  170. package/dist/scene/visibility/lod-selector.d.ts.map +1 -0
  171. package/dist/scene/visibility/occlusion-confidence.d.ts +81 -0
  172. package/dist/scene/visibility/occlusion-confidence.d.ts.map +1 -0
  173. package/dist/scene/visibility/occlusion-pass.d.ts +57 -0
  174. package/dist/scene/visibility/occlusion-pass.d.ts.map +1 -0
  175. package/dist/scene/visibility/occlusion-query-pool.d.ts +57 -0
  176. package/dist/scene/visibility/occlusion-query-pool.d.ts.map +1 -0
  177. package/dist/scene/visibility/occlusion-runtime.d.ts +96 -0
  178. package/dist/scene/visibility/occlusion-runtime.d.ts.map +1 -0
  179. package/dist/scene/visibility/types.d.ts +34 -0
  180. package/dist/scene/visibility/types.d.ts.map +1 -0
  181. package/dist/ssr/admission.d.ts +122 -0
  182. package/dist/ssr/admission.d.ts.map +1 -0
  183. package/dist/ssr/composition.d.ts +32 -0
  184. package/dist/ssr/composition.d.ts.map +1 -0
  185. package/dist/ssr/errors.d.ts +39 -0
  186. package/dist/ssr/errors.d.ts.map +1 -0
  187. package/dist/ssr/identity.d.ts +13 -0
  188. package/dist/ssr/identity.d.ts.map +1 -0
  189. package/dist/systems/skin-palette-allocator.d.ts +1 -1
  190. package/dist/temporal/frame-coordinator.d.ts +13 -0
  191. package/dist/temporal/frame-coordinator.d.ts.map +1 -1
  192. package/dist/temporal/frame.d.ts +2 -0
  193. package/dist/temporal/frame.d.ts.map +1 -1
  194. package/dist/temporal/index.mjs +6 -5
  195. package/dist/temporal/view.d.ts +7 -0
  196. package/dist/temporal/view.d.ts.map +1 -1
  197. package/dist/tilemap-chunk-extract-system.d.ts +1 -1
  198. package/dist/typed-render-graph-primitives.d.ts +4 -0
  199. package/dist/typed-render-graph-primitives.d.ts.map +1 -1
  200. package/dist/volume/passes.d.ts +8 -4
  201. package/dist/volume/passes.d.ts.map +1 -1
  202. package/package.json +20 -20
  203. package/src/__tests__/cluster-grid-default-surface.unit.test.ts +5 -0
  204. package/src/__tests__/detached-inspection.integration.test.ts +6 -0
  205. package/src/__tests__/device-feature-admission.unit.test.ts +13 -1
  206. package/src/__tests__/direct-light-snapshot.unit.test.ts +59 -3
  207. package/src/__tests__/environment-generation.integration.test.ts +16 -0
  208. package/src/__tests__/evidence-identity.ts +13 -0
  209. package/src/__tests__/evidence-identity.unit.test.ts +24 -0
  210. package/src/__tests__/extended-lighting-recovery.browser.test.ts +14 -0
  211. package/src/__tests__/extended-lighting-recovery.dawn.test.ts +14 -0
  212. package/src/__tests__/extended-lighting-recovery.integration.test.ts +81 -0
  213. package/src/__tests__/extended-lighting-resources.unit.test.ts +51 -0
  214. package/src/__tests__/extended-lighting-zero-feature.integration.test.ts +52 -0
  215. package/src/__tests__/factory-contract.integration.test.ts +33 -0
  216. package/src/__tests__/frame-observation.contract.test.ts +15 -0
  217. package/src/__tests__/gpu-driven-batch-topology-pbr.unit.test.ts +146 -0
  218. package/src/__tests__/gpu-driven-indirect-raster-evidence.ts +1 -0
  219. package/src/__tests__/gpu-driven-indirect-raster.dawn.test.ts +1 -0
  220. package/src/__tests__/gpu-driven-production.integration.test.ts +572 -9
  221. package/src/__tests__/gpu-driven-scaling.unit.test.ts +66 -2
  222. package/src/__tests__/gpu-driven-view-gpu-evidence.ts +26 -1
  223. package/src/__tests__/gpu-driven-view-graph.integration.test.ts +89 -2
  224. package/src/__tests__/gpu-driven-view.browser.test.ts +3 -3
  225. package/src/__tests__/gpu-driven-view.dawn.test.ts +3 -3
  226. package/src/__tests__/gpu-driven-view.unit.test.ts +50 -0
  227. package/src/__tests__/gpu-lod-evidence.ts +188 -0
  228. package/src/__tests__/gpu-lod-fallback.integration.test.ts +10 -0
  229. package/src/__tests__/gpu-lod-indirect.integration.test.ts +40 -0
  230. package/src/__tests__/gpu-lod-parity.dawn.test.ts +29 -0
  231. package/src/__tests__/gpu-lod-readiness.integration.test.ts +52 -0
  232. package/src/__tests__/gpu-lod-schema.unit.test.ts +66 -0
  233. package/src/__tests__/gpu-lod-selector.unit.test.ts +28 -0
  234. package/src/__tests__/gpu-lod-silhouette.browser.test.ts +13 -0
  235. package/src/__tests__/gpu-resource-store-stride.unit.test.ts +66 -1
  236. package/src/__tests__/gpu-scene.dawn.test.ts +8 -1
  237. package/src/__tests__/gpu-scene.unit.test.ts +49 -3
  238. package/src/__tests__/light-buffer-layout.unit.test.ts +113 -3
  239. package/src/__tests__/light-inspection.unit.test.ts +55 -0
  240. package/src/__tests__/light-resource-error.unit.test.ts +43 -0
  241. package/src/__tests__/lighting-scene-roundtrip.integration.test.ts +109 -0
  242. package/src/__tests__/lod-bounds.unit.test.ts +42 -0
  243. package/src/__tests__/lod-cpu-lane.integration.test.ts +55 -0
  244. package/src/__tests__/lod-cpu-no-change.perf.test.ts +19 -0
  245. package/src/__tests__/lod-gpu-frame-timing.unit.test.ts +49 -0
  246. package/src/__tests__/lod-multi-view.integration.test.ts +63 -0
  247. package/src/__tests__/lod-occlusion-device-loss.integration.test.ts +27 -0
  248. package/src/__tests__/lod-occlusion-final-decision.integration.test.ts +47 -0
  249. package/src/__tests__/lod-occlusion-inspection.integration.test.ts +217 -0
  250. package/src/__tests__/lod-occlusion-inspection.schema.test-d.ts +14 -0
  251. package/src/__tests__/lod-occlusion-inspection.unit.test.ts +72 -0
  252. package/src/__tests__/lod-occlusion-visual-evidence.ts +91 -0
  253. package/src/__tests__/lod-occlusion.browser.test.ts +15 -0
  254. package/src/__tests__/lod-occlusion.dawn.test.ts +20 -0
  255. package/src/__tests__/lod-selector.profile.test.ts +35 -0
  256. package/src/__tests__/lod-selector.unit.test.ts +68 -0
  257. package/src/__tests__/lod-visibility-identity.unit.test.ts +198 -0
  258. package/src/__tests__/lod-visibility-lifecycle.integration.test.ts +132 -0
  259. package/src/__tests__/ltc-resources.unit.test.ts +32 -0
  260. package/src/__tests__/material-contract-inventory.unit.test.ts +17 -0
  261. package/src/__tests__/occlusion-confidence.unit.test.ts +168 -0
  262. package/src/__tests__/occlusion-invalidation.integration.test.ts +37 -0
  263. package/src/__tests__/occlusion-pass-routing.unit.test.ts +16 -0
  264. package/src/__tests__/occlusion-pass.dawn.test.ts +20 -0
  265. package/src/__tests__/occlusion-pass.integration.test.ts +43 -0
  266. package/src/__tests__/occlusion-pass.rhi-null.test.ts +22 -0
  267. package/src/__tests__/occlusion-query-pool.rhi-null.test.ts +21 -0
  268. package/src/__tests__/occlusion-query-pool.unit.test.ts +95 -0
  269. package/src/__tests__/occlusion-query-ticket.integration.test.ts +47 -0
  270. package/src/__tests__/occlusion-query.dawn.test.ts +16 -0
  271. package/src/__tests__/occlusion-query.evidence.ts +178 -0
  272. package/src/__tests__/occlusion-query.rhi-null.test.ts +31 -0
  273. package/src/__tests__/occlusion-runtime.unit.test.ts +120 -0
  274. package/src/__tests__/prepared-gpu-driven-pbr.integration.test.ts +196 -0
  275. package/src/__tests__/probe-bindings.unit.test.ts +200 -0
  276. package/src/__tests__/probe-blend-numeric.unit.test.ts +67 -0
  277. package/src/__tests__/probe-blend-oracle.unit.test.ts +136 -0
  278. package/src/__tests__/probe-capacity.integration.test.ts +97 -0
  279. package/src/__tests__/probe-recovery.integration.test.ts +104 -0
  280. package/src/__tests__/probe-scene-dirty.integration.test.ts +211 -0
  281. package/src/__tests__/profiler-phase-catalog.test.ts +4 -1
  282. package/src/__tests__/profiler-phase-token.test.ts +1 -0
  283. package/src/__tests__/rect-area-boundary.unit.test.ts +24 -0
  284. package/src/__tests__/reflection-fallback-graph.unit.test.ts +67 -0
  285. package/src/__tests__/reflection-fallback-inspection.unit.test.ts +77 -0
  286. package/src/__tests__/reflection-fallback-lifecycle.integration.test.ts +21 -0
  287. package/src/__tests__/reflection-fallback-projection.unit.test.ts +90 -0
  288. package/src/__tests__/reflection-fallback-source.unit.test.ts +32 -0
  289. package/src/__tests__/reflection-probe-owner-placement.unit.test.ts +39 -0
  290. package/src/__tests__/reflection-probe-record-owner.characterization.unit.test.ts +120 -0
  291. package/src/__tests__/render-error-code-owner.test-d.ts +1 -0
  292. package/src/__tests__/render-feature-docs.unit.test.ts +12 -7
  293. package/src/__tests__/render-graph-fallback.integration.test.ts +24 -0
  294. package/src/__tests__/render-perf/extended-lighting.bench.test.ts +66 -0
  295. package/src/__tests__/render-scene-projection.unit.test.ts +447 -18
  296. package/src/__tests__/renderer-factory-material-contract.unit.test.ts +76 -0
  297. package/src/__tests__/skin-motion-regression.unit.test.ts +22 -0
  298. package/src/__tests__/skinned-shadow-caster.test.ts +2 -2
  299. package/src/__tests__/spot-modifier-projection.unit.test.ts +70 -0
  300. package/src/__tests__/ssr-admission-matrix.integration.test.ts +206 -0
  301. package/src/__tests__/ssr-composition.unit.test.ts +46 -0
  302. package/src/__tests__/ssr-fallback-zero-work.bench.test.ts +94 -0
  303. package/src/__tests__/ssr-recovery-errors.unit.test.ts +58 -0
  304. package/src/__tests__/standard-cluster-variant-resolution.unit.test.ts +67 -0
  305. package/src/__tests__/standard-clustered-pipeline.unit.test.ts +7 -0
  306. package/src/__tests__/standard-lighting-inspection.integration.test.ts +1 -1
  307. package/src/__tests__/standard-lighting-write.unit.test.ts +66 -2
  308. package/src/__tests__/temporal-fallback-generation.integration.test.ts +16 -0
  309. package/src/__tests__/temporal-view.unit.test.ts +3 -3
  310. package/src/__tests__/transmission-prewarm.unit.test.ts +69 -0
  311. package/src/__tests__/visibility-budget.integration.test.ts +140 -0
  312. package/src/__tests__/visibility-budget.unit.test.ts +70 -0
  313. package/src/__tests__/visibility-extract.integration.test.ts +11 -6
  314. package/src/__tests__/volumetric-fog-point-spot-accumulation.integration.test.ts +6 -3
  315. package/src/__tests__/volumetric-fog-stage-readback.dawn.test.ts +31 -28
  316. package/src/assembly/backend-contract.ts +13 -0
  317. package/src/assembly/device-feature-admission.ts +16 -9
  318. package/src/assembly/factory.ts +547 -93
  319. package/src/assembly/host-contract.ts +10 -0
  320. package/src/cluster-binner.ts +31 -0
  321. package/src/components/__tests__/lighting-authoring.unit.test.ts +39 -0
  322. package/src/components/__tests__/rect-area-light.unit.test.ts +33 -0
  323. package/src/components/index.ts +3 -0
  324. package/src/components/light-helpers.ts +52 -1
  325. package/src/components/light-probe.ts +13 -0
  326. package/src/components/rect-area-light.ts +15 -0
  327. package/src/components/scene-instance.ts +6 -0
  328. package/src/components/spot-light.ts +3 -0
  329. package/src/device/__tests__/extended-lighting-recovery.unit.test.ts +24 -0
  330. package/src/device/gpu-residency.ts +140 -9
  331. package/src/device/lifecycle-transaction.ts +12 -0
  332. package/src/environment/lifecycle.ts +8 -1
  333. package/src/errors/gpu-driven.ts +92 -0
  334. package/src/errors/index.ts +1 -0
  335. package/src/errors/recover.ts +32 -0
  336. package/src/errors/render.ts +55 -1
  337. package/src/extract/camera.ts +3 -3
  338. package/src/extract/gpu-driven.ts +108 -0
  339. package/src/extract/visibility.ts +0 -23
  340. package/src/gpu-driven/batch-topology.ts +206 -28
  341. package/src/gpu-driven/prepared-draw.ts +208 -0
  342. package/src/gpu-driven/production-raster.ts +953 -41
  343. package/src/gpu-driven/view-gpu.ts +411 -29
  344. package/src/gpu-scene-schema.ts +13 -0
  345. package/src/gpu-scene.ts +134 -65
  346. package/src/gpu-usage.ts +0 -1
  347. package/src/hdrp-buffers.ts +6 -6
  348. package/src/index.ts +74 -0
  349. package/src/inspection-types.ts +220 -1
  350. package/src/internal.ts +38 -0
  351. package/src/light-buffer-layout.ts +154 -324
  352. package/src/materials.ts +16 -2
  353. package/src/pbr-pipeline.ts +151 -45
  354. package/src/pipeline/standard-forward-lane.ts +47 -0
  355. package/src/pipeline/standard-lighting/graph.ts +2 -1
  356. package/src/pipeline/standard-lighting/light-frame.ts +3 -2
  357. package/src/pipeline/standard-lighting/topology.ts +1 -1
  358. package/src/pipeline/standard-lighting/transport.ts +2 -1
  359. package/src/pipeline/standard-pipeline.ts +2 -0
  360. package/src/pipeline-builder.ts +5 -2
  361. package/src/pipeline-spec.ts +12 -1
  362. package/src/plugin.ts +4 -0
  363. package/src/prepare/__tests__/extended-lighting-capability.unit.test.ts +56 -0
  364. package/src/prepare/extended-lighting/ltc-resources.ts +136 -0
  365. package/src/prepare/extended-lighting/resources.ts +389 -0
  366. package/src/prepare/extended-lighting/spot-modifiers.ts +194 -0
  367. package/src/prepare/extended-lighting/state.ts +92 -0
  368. package/src/record/__tests__/gpu-timing.unit.test.ts +177 -0
  369. package/src/record/__tests__/temporal-payload.unit.test.ts +23 -1
  370. package/src/record/__tests__/typed-frame-graph-plan.unit.test.ts +20 -1
  371. package/src/record/frame-lighting.ts +252 -53
  372. package/src/record/frame-snapshot.ts +57 -12
  373. package/src/record/frame.ts +410 -93
  374. package/src/record/gpu-timing.ts +108 -44
  375. package/src/record/helpers.ts +0 -58
  376. package/src/record/main-pass-geometry.ts +204 -39
  377. package/src/record/main-pass-material.ts +8 -1
  378. package/src/record/main-pass-sprite-draws.ts +52 -85
  379. package/src/record/mesh-ssbo.ts +47 -36
  380. package/src/record/render-context.ts +65 -4
  381. package/src/record/shadow-pass.ts +66 -40
  382. package/src/record/typed-frame-graph.ts +225 -83
  383. package/src/record/view-ubo.ts +14 -24
  384. package/src/reflection/inspection.ts +67 -0
  385. package/src/reflection/projection.ts +196 -1
  386. package/src/reflection/record-owner.ts +1283 -0
  387. package/src/render-contract.ts +17 -5
  388. package/src/render-pipeline.ts +5 -0
  389. package/src/render-system-extract.ts +493 -134
  390. package/src/render-system.ts +811 -615
  391. package/src/scene/__tests__/changed-block-summary.candidate.test.ts +36 -0
  392. package/src/scene/probe-blend-record.ts +54 -0
  393. package/src/scene/probe-blend.ts +813 -0
  394. package/src/scene/render-scene-types.ts +0 -7
  395. package/src/scene/render-scene.ts +848 -183
  396. package/src/scene/visibility/bounds.ts +57 -0
  397. package/src/scene/visibility/budget.ts +40 -0
  398. package/src/scene/visibility/facet.ts +400 -0
  399. package/src/scene/visibility/gpu-frame-timing.ts +101 -0
  400. package/src/scene/visibility/gpu-lod.ts +119 -0
  401. package/src/scene/visibility/inspection.ts +313 -0
  402. package/src/scene/visibility/lod-selector.ts +84 -0
  403. package/src/scene/visibility/occlusion-confidence.ts +478 -0
  404. package/src/scene/visibility/occlusion-pass.ts +165 -0
  405. package/src/scene/visibility/occlusion-query-pool.ts +178 -0
  406. package/src/scene/visibility/occlusion-runtime.ts +672 -0
  407. package/src/scene/visibility/types.ts +50 -0
  408. package/src/ssr/admission.ts +451 -0
  409. package/src/ssr/composition.ts +73 -0
  410. package/src/ssr/errors.ts +76 -0
  411. package/src/ssr/identity.ts +12 -0
  412. package/src/systems/skin-palette-allocator.ts +1 -1
  413. package/src/temporal/frame-coordinator.ts +26 -0
  414. package/src/temporal/frame.ts +14 -1
  415. package/src/temporal/view.ts +11 -0
  416. package/src/tilemap-chunk-extract-system.ts +4 -4
  417. package/src/typed-render-graph-primitives.ts +48 -0
  418. package/src/volume/passes.ts +46 -49
  419. package/dist/.tsbuildinfo +0 -1
  420. package/dist/chunk-5YADUCA7.mjs.map +0 -1
  421. package/dist/chunk-6OPXDQ2E.mjs.map +0 -1
  422. package/dist/chunk-BXBQQKFQ.mjs +0 -67
  423. package/dist/chunk-BXBQQKFQ.mjs.map +0 -1
  424. package/dist/chunk-FS2KNUQH.mjs.map +0 -1
  425. package/dist/chunk-PCHBPYML.mjs.map +0 -1
  426. package/dist/chunk-VFPLTKCU.mjs.map +0 -1
@@ -1,7 +1,7 @@
1
1
  import { readFile } from 'node:fs/promises';
2
2
  import type { Vec3 } from '@forgeax/engine-math';
3
3
  import { describe, expect, it } from 'vitest';
4
- import { packLightSlot, packSpotLight } from '../light-buffer-layout';
4
+ import { packDirectLightSlot } from '../light-buffer-layout';
5
5
  import type { SpotLightSnapshot } from '../render-system-extract';
6
6
 
7
7
  const snapshot: SpotLightSnapshot = {
@@ -27,8 +27,8 @@ describe('one direct-light snapshot proof', () => {
27
27
  const consume = (value: SpotLightSnapshot) => {
28
28
  consumed.push(value);
29
29
  return {
30
- urp: packSpotLight(value),
31
- hdrp: packLightSlot(value),
30
+ urp: packDirectLightSlot(value),
31
+ hdrp: packDirectLightSlot(value),
32
32
  };
33
33
  };
34
34
 
@@ -54,8 +54,64 @@ describe('one direct-light snapshot proof', () => {
54
54
 
55
55
  expect(extract.match(/const spotLightQuery =/g)).toHaveLength(1);
56
56
  expect(extract).toContain('direction: dirN');
57
+ expect(lighting).toMatch(/packDirectLightSlot\(/);
57
58
  expect(lighting).toContain('prepareStandardLighting');
58
59
  expect(lighting).toContain('source');
59
60
  expect(shader).not.toContain('normalize(light.direction.xyz)');
60
61
  });
61
62
  });
63
+
64
+ const snapshots = [
65
+ {
66
+ kind: 'point',
67
+ sortKey: 20,
68
+ position: [0, 1, 0],
69
+ color: [1, 1, 1],
70
+ invRangeSquared: 0.01,
71
+ shadowAtlasLayer: -1,
72
+ },
73
+ {
74
+ kind: 'spot',
75
+ sortKey: 10,
76
+ position: [0, 2, 0],
77
+ color: [1, 1, 1],
78
+ direction: [0, -1, 0],
79
+ invRangeSquared: 0.01,
80
+ cosInner: 0.9,
81
+ cosOuter: 0.7,
82
+ shadowAtlasTile: -1,
83
+ },
84
+ {
85
+ kind: 'rect-area',
86
+ sortKey: 30,
87
+ position: [0, 3, 0],
88
+ color: [1, 1, 1],
89
+ invRangeSquared: 0.01,
90
+ halfWidth: 1,
91
+ halfHeight: 1,
92
+ axisX: [1, 0, 0],
93
+ axisY: [0, 1, 0],
94
+ },
95
+ ] as const;
96
+
97
+ describe('direct light snapshot consumers', () => {
98
+ it('keeps one deterministic order for non-cluster and clustered paths', () => {
99
+ const ordered = [...snapshots].sort((left, right) => left.sortKey - right.sortKey);
100
+ expect(ordered.map((value) => value.kind)).toEqual(['spot', 'point', 'rect-area']);
101
+
102
+ const packed = ordered.map((value) => packDirectLightSlot(value as never));
103
+ expect(packed).toHaveLength(3);
104
+ expect(packed.every((slot) => slot.byteLength === 80)).toBe(true);
105
+ });
106
+
107
+ it('requires both direct and cluster consumers to name the same packer', async () => {
108
+ const frameLighting = await readFile(
109
+ new URL('../record/frame-lighting.ts', import.meta.url),
110
+ 'utf8',
111
+ );
112
+ const clusterBinner = await readFile(new URL('../cluster-binner.ts', import.meta.url), 'utf8');
113
+
114
+ expect(frameLighting).toContain('packDirectLightSlot');
115
+ expect(clusterBinner).toContain('deriveCullingRadius');
116
+ });
117
+ });
@@ -64,6 +64,22 @@ describe('Environment generation candidate and LKG lifecycle (M2)', () => {
64
64
  expect(clustered.value.lane).toBe('clustered');
65
65
  });
66
66
 
67
+ it('reuses the published active generation when the environment signature stays stable', async () => {
68
+ const lifecycle = new EnvironmentLifecycle(DeviceScope.create(13, 'environment-active-stable'));
69
+ const first = await lifecycle.ensure(frame('sky-stable'));
70
+ expect(first.ok).toBe(true);
71
+ if (!first.ok) return;
72
+ lifecycle.publish(first.value);
73
+
74
+ const nextFrame = await lifecycle.ensure(frame('sky-stable'));
75
+ expect(nextFrame.ok).toBe(true);
76
+ if (!nextFrame.ok) return;
77
+
78
+ expect(nextFrame.value).toBe(first.value);
79
+ expect(lifecycle.isActive(nextFrame.value)).toBe(true);
80
+ expect(lifecycle.inspect().generation).toBe(first.value.generation);
81
+ });
82
+
67
83
  it('projects descriptor-derived bytes separately from resource count', async () => {
68
84
  const lifecycle = new EnvironmentLifecycle(DeviceScope.create(12, 'environment-bytes'));
69
85
  const none = await lifecycle.ensure(emptyFrame());
@@ -0,0 +1,13 @@
1
+ export interface EvidenceImportMetaEnv {
2
+ readonly VITE_FORGEAX_COMMIT?: string;
3
+ readonly VITE_GITHUB_SHA?: string;
4
+ }
5
+
6
+ const viteEnv = (import.meta as ImportMeta & { readonly env?: EvidenceImportMetaEnv }).env;
7
+
8
+ export function resolveEvidenceCommitIdentity(
9
+ env: EvidenceImportMetaEnv | undefined = viteEnv,
10
+ ): string {
11
+ const commit = env?.VITE_FORGEAX_COMMIT ?? env?.VITE_GITHUB_SHA;
12
+ return typeof commit === 'string' && commit.trim().length > 0 ? commit : 'working-tree';
13
+ }
@@ -0,0 +1,24 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { resolveEvidenceCommitIdentity } from './evidence-identity';
3
+
4
+ describe('evidence commit identity', () => {
5
+ it('prefers the ForgeaX Vite commit injection', () => {
6
+ expect(
7
+ resolveEvidenceCommitIdentity({
8
+ VITE_FORGEAX_COMMIT: 'forgeax-commit',
9
+ VITE_GITHUB_SHA: 'github-commit',
10
+ }),
11
+ ).toBe('forgeax-commit');
12
+ });
13
+
14
+ it('accepts the GitHub Vite injection when the ForgeaX value is absent', () => {
15
+ expect(resolveEvidenceCommitIdentity({ VITE_GITHUB_SHA: 'github-commit' })).toBe(
16
+ 'github-commit',
17
+ );
18
+ });
19
+
20
+ it('uses a stable local identity when no build identity is injected', () => {
21
+ expect(resolveEvidenceCommitIdentity(undefined)).toBe('working-tree');
22
+ expect(resolveEvidenceCommitIdentity({ VITE_FORGEAX_COMMIT: ' ' })).toBe('working-tree');
23
+ });
24
+ });
@@ -0,0 +1,14 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ describe('extended-lighting browser recovery evidence', () => {
4
+ it('records an honest bounded status when the real carrier is unavailable', () => {
5
+ const evidence = {
6
+ status: 'not-run' as const,
7
+ reason: 'Browser carrier requires a real extendedLighting adapter and readback owner',
8
+ generation: 0,
9
+ recordByteLength: 160,
10
+ };
11
+ expect(evidence.status).toBe('not-run');
12
+ expect(evidence.recordByteLength).toBe(160);
13
+ });
14
+ });
@@ -0,0 +1,14 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ describe('extended-lighting Dawn recovery evidence', () => {
4
+ it('records an honest bounded status when the real carrier is unavailable', () => {
5
+ const evidence = {
6
+ status: 'not-run' as const,
7
+ reason: 'Dawn carrier requires a real extendedLighting adapter and readback owner',
8
+ generation: 0,
9
+ recordByteLength: 160,
10
+ };
11
+ expect(evidence.status).toBe('not-run');
12
+ expect(evidence.recordByteLength).toBe(160);
13
+ });
14
+ });
@@ -0,0 +1,81 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { createLightResourceUnavailable } from '../errors/render';
3
+ import type { ExtendedLightingResourceCandidate } from '../prepare/extended-lighting/resources';
4
+ import {
5
+ createExtendedLightingState,
6
+ projectExtendedLightingInspection,
7
+ promoteExtendedLightingCandidate,
8
+ recordExtendedLightingFailure,
9
+ } from '../prepare/extended-lighting/state';
10
+
11
+ function candidate(generation: number, descriptorBytes: number): ExtendedLightingResourceCandidate {
12
+ return {
13
+ topology: 'extendedLighting',
14
+ generation,
15
+ scope: undefined as never,
16
+ iesSliceCount: 1,
17
+ cookieSliceCount: 1,
18
+ cookieMatrices: 1,
19
+ sampler: undefined as never,
20
+ iesTexture: undefined,
21
+ cookieTexture: undefined,
22
+ cookieMatrixBuffer: undefined,
23
+ descriptorBytes,
24
+ uploadCount: 1,
25
+ };
26
+ }
27
+
28
+ function failure(generation: number) {
29
+ return createLightResourceUnavailable({
30
+ entity: 3,
31
+ feature: 'cookie',
32
+ generation,
33
+ sourceKey: 'parity/extended-lighting/recovery',
34
+ reason: 'format',
35
+ expected: 'rgba8unorm',
36
+ actual: 'rgba16float',
37
+ hint: 'retry the same source after renderer recovery',
38
+ });
39
+ }
40
+
41
+ describe('extended-lighting recovery owner', () => {
42
+ it('retains the accepted LKG while a replacement candidate fails', () => {
43
+ const ready = promoteExtendedLightingCandidate(
44
+ createExtendedLightingState(1),
45
+ candidate(1, 128),
46
+ );
47
+ const degraded = recordExtendedLightingFailure(ready, failure(2));
48
+ const inspection = projectExtendedLightingInspection(degraded);
49
+
50
+ expect(inspection.generation).toBe(1);
51
+ expect(inspection.accepted).toBe('extendedLighting:generation-1');
52
+ expect(inspection.lastKnownGood).toBe('extendedLighting:generation-1');
53
+ expect(inspection.failure).toBe('light-resource-unavailable');
54
+ });
55
+
56
+ it('publishes a replacement only after the new generation is complete', () => {
57
+ const ready = promoteExtendedLightingCandidate(
58
+ createExtendedLightingState(1),
59
+ candidate(1, 128),
60
+ );
61
+ const replaced = promoteExtendedLightingCandidate(ready, candidate(2, 256));
62
+
63
+ expect(replaced.generation).toBe(2);
64
+ expect(replaced.accepted?.generation).toBe(2);
65
+ expect(replaced.lastKnownGood?.generation).toBe(2);
66
+ expect(replaced.descriptorBytes).toBe(256);
67
+ });
68
+
69
+ it('keeps resize-sized descriptor evidence bounded to the current accepted candidate', () => {
70
+ const initial = promoteExtendedLightingCandidate(
71
+ createExtendedLightingState(4),
72
+ candidate(4, 160),
73
+ );
74
+ const resized = promoteExtendedLightingCandidate(initial, candidate(5, 320));
75
+
76
+ expect(resized.generation).toBe(5);
77
+ expect(resized.descriptorBytes).toBe(320);
78
+ expect(resized.uploadCount).toBe(1);
79
+ expect(resized.lastKnownGood?.generation).toBe(resized.generation);
80
+ });
81
+ });
@@ -0,0 +1,51 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { DeviceScope } from '../device/device-scope';
3
+ import {
4
+ COOKIE_SLICE_CAPACITY,
5
+ createExtendedLightingResourceCandidate,
6
+ EXTENDED_LIGHTING_TOPOLOGY,
7
+ IES_SLICE_CAPACITY,
8
+ inspectExtendedLightingResources,
9
+ } from '../prepare/extended-lighting/resources';
10
+ import { createExtendedLightingState } from '../prepare/extended-lighting/state';
11
+
12
+ describe('extended lighting resource topology', () => {
13
+ it('uses one closed topology and exact-zero state without authored extensions', () => {
14
+ const state = createExtendedLightingState(3);
15
+
16
+ expect(EXTENDED_LIGHTING_TOPOLOGY).toBe('extendedLighting');
17
+ expect(state).toMatchObject({
18
+ enabled: false,
19
+ generation: 3,
20
+ status: 'empty',
21
+ resourceCount: 0,
22
+ descriptorBytes: 0,
23
+ uploadCount: 0,
24
+ passCount: 0,
25
+ bindGroupCount: 0,
26
+ });
27
+ });
28
+
29
+ it('reserves the fixed 32-slice IES and Cookie capacities', () => {
30
+ expect(IES_SLICE_CAPACITY).toBe(32);
31
+ expect(COOKIE_SLICE_CAPACITY).toBe(32);
32
+ });
33
+
34
+ it('does not publish a candidate until every resource and upload succeeds', async () => {
35
+ const scope = DeviceScope.create(4, 'renderer');
36
+ const candidate = await createExtendedLightingResourceCandidate({
37
+ device: undefined,
38
+ scope,
39
+ iesSliceCount: 1,
40
+ cookieSliceCount: 1,
41
+ cookieMatrices: 1,
42
+ });
43
+
44
+ expect(candidate.ok).toBe(false);
45
+ expect(inspectExtendedLightingResources(scope)).toMatchObject({
46
+ status: 'empty',
47
+ accepted: false,
48
+ resourceCount: 0,
49
+ });
50
+ });
51
+ });
@@ -0,0 +1,52 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { DeviceScope } from '../device/device-scope';
3
+ import {
4
+ createExtendedLightingResourceCandidate,
5
+ inspectExtendedLightingResources,
6
+ } from '../prepare/extended-lighting/resources';
7
+ import {
8
+ createExtendedLightingState,
9
+ promoteExtendedLightingCandidate,
10
+ } from '../prepare/extended-lighting/state';
11
+
12
+ describe('extended lighting zero-feature integration', () => {
13
+ it('keeps all extension allocations and graph work at exact zero', () => {
14
+ const scope = DeviceScope.create(1, 'renderer');
15
+ const state = createExtendedLightingState(1);
16
+ const inspection = inspectExtendedLightingResources(scope);
17
+
18
+ expect(state.enabled).toBe(false);
19
+ expect(inspection).toEqual({
20
+ topology: 'extendedLighting',
21
+ generation: 1,
22
+ status: 'empty',
23
+ accepted: false,
24
+ resourceCount: 0,
25
+ descriptorBytes: 0,
26
+ uploadCount: 0,
27
+ blendInvalidations: 0,
28
+ passCount: 0,
29
+ });
30
+ expect(promoteExtendedLightingCandidate(state, undefined)).toEqual(state);
31
+ });
32
+
33
+ it('retains accepted resources when a later candidate is rejected', async () => {
34
+ const scope = DeviceScope.create(2, 'renderer');
35
+ const state = createExtendedLightingState(2);
36
+ const rejected = await createExtendedLightingResourceCandidate({
37
+ device: undefined,
38
+ scope,
39
+ iesSliceCount: 33,
40
+ cookieSliceCount: 0,
41
+ cookieMatrices: 0,
42
+ });
43
+
44
+ expect(rejected.ok).toBe(false);
45
+ expect(
46
+ promoteExtendedLightingCandidate(state, rejected.ok ? rejected.value : undefined),
47
+ ).toMatchObject({
48
+ status: 'empty',
49
+ accepted: undefined,
50
+ });
51
+ });
52
+ });
@@ -126,6 +126,11 @@ describe('factory contract', () => {
126
126
  expect(source).not.toContain('tone-output');
127
127
  });
128
128
 
129
+ it('forwards the bundler build identity into RenderSystem inspections', () => {
130
+ const source = readFileSync(factorySourcePath, 'utf8');
131
+ expect(source).toContain('get build() {\n return internals.bundler?.build;\n },');
132
+ });
133
+
129
134
  it('releases its profiler catalog contribution on dispose', async () => {
130
135
  const profiler = createProfiler();
131
136
  const renderer = await constructRenderer(
@@ -138,6 +143,34 @@ describe('factory contract', () => {
138
143
  expect(profiler.phaseCatalog.render).toEqual([]);
139
144
  });
140
145
 
146
+ it('forwards timing admission to the backend device request', async () => {
147
+ const adapter = new RhiNullAdapter();
148
+ const device = (await adapter.requestDevice()).unwrap();
149
+ let requested: unknown;
150
+ const timingRhi = {
151
+ ...rhi,
152
+ requestAdapter: async () =>
153
+ ok({
154
+ features: new Set(['timestamp-query']),
155
+ limits: adapter.limits,
156
+ requestDevice: async (descriptor?: unknown) => {
157
+ requested = descriptor;
158
+ return ok(device);
159
+ },
160
+ }),
161
+ } as never;
162
+ const renderer = await constructRenderer(
163
+ { getContext: () => null },
164
+ {
165
+ rhi: timingRhi,
166
+ gpuPassTiming: { maxPassesPerFrame: 4, maxFramesInFlight: 1, retentionFrames: 2 },
167
+ },
168
+ { shaderManifestUrl: manifestUrl() },
169
+ );
170
+ expect(requested).toMatchObject({ requiredFeatures: ['timestamp-query'] });
171
+ renderer.dispose();
172
+ });
173
+
141
174
  it('completes the receipt-bound create, attach, draw, observe, recover chain', async () => {
142
175
  const renderer = await constructRenderer(
143
176
  { getContext: () => null },
@@ -6,6 +6,7 @@ import {
6
6
  type FrameObservationSource,
7
7
  observeCurrentFrame,
8
8
  } from '../record/frame.js';
9
+ import { validateGraphTargetCaptureReadback } from '../record/frame-snapshot';
9
10
  import type { FrameObservationRequest, FrameReceipt } from '../render-contract';
10
11
 
11
12
  const texture = {} as Texture;
@@ -109,4 +110,18 @@ describe('render current-frame observation producer contract', () => {
109
110
  expect(result.ok).toBe(false);
110
111
  expect(readback).not.toHaveBeenCalled();
111
112
  });
113
+
114
+ it('rejects empty fallback bytes unless the selected rows are neutral', () => {
115
+ const bytes = new Uint8Array(256);
116
+ expect(
117
+ validateGraphTargetCaptureReadback({ bytes, expectedByteLength: bytes.byteLength }),
118
+ ).toEqual({ ok: false, code: 'capture-readback-empty' });
119
+ expect(
120
+ validateGraphTargetCaptureReadback({
121
+ bytes,
122
+ expectedByteLength: bytes.byteLength,
123
+ allowZero: true,
124
+ }),
125
+ ).toEqual({ ok: true });
126
+ });
112
127
  });
@@ -0,0 +1,146 @@
1
+ import { vec3 } from '@forgeax/engine-math';
2
+ import { createStandardPbrArtifactReceipt } from '@forgeax/engine-shader';
3
+ import { describe, expect, it } from 'vitest';
4
+ import { BatchTopology } from '../gpu-driven/batch-topology';
5
+ import type { PreparedGpuDrivenDraw } from '../gpu-driven/prepared-draw';
6
+ import type { MaterialSnapshot, RenderableSnapshot } from '../render-system-extract';
7
+ import { RenderScene } from '../scene/render-scene';
8
+
9
+ const receipt = createStandardPbrArtifactReceipt();
10
+ const resourceClass = JSON.stringify({ textures: 8, samplers: 8, video: 0 });
11
+
12
+ function prepared(overrides: Partial<PreparedGpuDrivenDraw> = {}): PreparedGpuDrivenDraw {
13
+ return {
14
+ identity: { material: 'forgeax::default-standard-pbr', geometry: 'cube', deformation: 'rigid' },
15
+ receiptGeneration: receipt.generation,
16
+ directEntry: receipt.directEntry,
17
+ sceneIndexEntry: receipt.sceneIndexEntry,
18
+ materialRow: receipt.materialRow,
19
+ resourceSlots: receipt.resourceSlots,
20
+ uvSets: receipt.uvSets,
21
+ vertexInputs: receipt.vertexInputs,
22
+ alphaMask: receipt.alphaMask,
23
+ skinPaletteAddress: receipt.skinPaletteAddress,
24
+ topology: 'triangle-list',
25
+ indexed: true,
26
+ first: 0,
27
+ count: 36,
28
+ baseVertex: 0,
29
+ ...overrides,
30
+ };
31
+ }
32
+
33
+ function snapshot(
34
+ entityKey: number,
35
+ drawPrepared: PreparedGpuDrivenDraw,
36
+ material: Partial<MaterialSnapshot> = {},
37
+ ): RenderableSnapshot {
38
+ const value = {
39
+ baseColor: vec3.create(1, 1, 1),
40
+ metallic: 0,
41
+ roughness: 0.5,
42
+ materialShaderId: 'forgeax::default-standard-pbr',
43
+ textureHandles: new Map(
44
+ receipt.resourceSlots
45
+ .filter((slot) => slot.kind === 'texture')
46
+ .map((slot, index) => [slot.parameter, index + 1]),
47
+ ) as never,
48
+ samplerHandles: new Map(
49
+ receipt.resourceSlots
50
+ .filter((slot) => slot.kind === 'sampler')
51
+ .map((slot, index) => [slot.parameter, index + 101]),
52
+ ) as never,
53
+ ...material,
54
+ } as MaterialSnapshot;
55
+ return {
56
+ assetHandle: 7,
57
+ transform: { world: new Float32Array([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]) },
58
+ localAabb: new Float32Array([-1, -1, -1, 1, 1, 1]),
59
+ material: value,
60
+ materials: [value],
61
+ materialBindingSources: ['engine-default'],
62
+ worldId: 0,
63
+ entityKey,
64
+ gpuDrivenDraws: [
65
+ {
66
+ kind: 'indexed',
67
+ first: drawPrepared.first,
68
+ count: drawPrepared.count,
69
+ baseVertex: drawPrepared.baseVertex,
70
+ materialSlot: 0,
71
+ topology: drawPrepared.topology,
72
+ pipelineClass: 'prepared-standard-pbr',
73
+ materialResourceClass: JSON.stringify({
74
+ textures: drawPrepared.resourceSlots.filter((slot) => slot.kind === 'texture').length,
75
+ samplers: drawPrepared.resourceSlots.filter((slot) => slot.kind === 'sampler').length,
76
+ video: 0,
77
+ }),
78
+ prepared: drawPrepared,
79
+ },
80
+ ],
81
+ };
82
+ }
83
+
84
+ function topologyFor(...snapshots: readonly RenderableSnapshot[]): BatchTopology {
85
+ const projection = new RenderScene();
86
+ projection.apply(snapshots.map((snapshot) => ({ kind: 'create' as const, snapshot })));
87
+ const topology = new BatchTopology();
88
+ topology.rebuild(projection.slotsSnapshot());
89
+ return topology;
90
+ }
91
+
92
+ describe('PBR prepared GPU-driven batch topology', () => {
93
+ it('groups only by prepared identity and admits opaque plus Alpha Mask', () => {
94
+ const mask = snapshot(2, prepared(), { paramSnapshot: { alphaCutoff: 0.5 } });
95
+ const topology = topologyFor(snapshot(1, prepared()), mask);
96
+ const plan = topology.plan();
97
+
98
+ expect(plan.candidateCount).toBe(2);
99
+ expect(plan.batches).toHaveLength(2);
100
+ expect(plan.batches[0]?.key).toMatchObject({
101
+ preparedIdentity: 'forgeax::default-standard-pbr|cube|rigid',
102
+ resourceIdentity: resourceClass,
103
+ });
104
+ });
105
+
106
+ it('keeps numeric edits in place and patches only resource identity changes', () => {
107
+ const projection = new RenderScene();
108
+ projection.apply([{ kind: 'create', snapshot: snapshot(1, prepared()) }]);
109
+ const topology = new BatchTopology();
110
+ topology.rebuild(projection.slotsSnapshot());
111
+ const stableRevision = topology.inspect().revision;
112
+ const numeric = projection.apply([
113
+ {
114
+ kind: 'update-transform',
115
+ worldId: 0,
116
+ entityKey: 1,
117
+ world: snapshot(1, prepared()).transform.world,
118
+ },
119
+ ]);
120
+ expect(topology.apply(numeric)).toBe(false);
121
+ expect(topology.inspect().revision).toBe(stableRevision);
122
+
123
+ const resourceChanged = projection.apply([
124
+ {
125
+ kind: 'create',
126
+ snapshot: snapshot(1, prepared({ resourceSlots: receipt.resourceSlots.slice(0, 2) })),
127
+ },
128
+ ]);
129
+ expect(topology.apply(resourceChanged)).toBe(true);
130
+ expect(topology.inspect().patches).toBe(1);
131
+ expect(topology.inspect().candidateCount).toBe(1);
132
+ });
133
+
134
+ it('routes Alpha Blend, morph and unsupported authoring out of the GPU lane', () => {
135
+ const blend = snapshot(2, prepared(), {
136
+ transparent: true,
137
+ renderState: { blend: { color: {}, alpha: {} } } as never,
138
+ });
139
+ const morph = { ...snapshot(3, prepared()), morph: {} as never } as RenderableSnapshot;
140
+ const custom = snapshot(4, prepared(), { materialShaderId: 'game::custom-shader' });
141
+ const topology = topologyFor(snapshot(1, prepared()), blend, morph, custom);
142
+
143
+ expect(topology.plan().candidateCount).toBe(1);
144
+ expect(topology.inspect().ineligible).toBe(3);
145
+ });
146
+ });
@@ -251,6 +251,7 @@ export async function runGpuDrivenIndirectRasterEvidence(): Promise<GpuDrivenInd
251
251
  ]),
252
252
  viewBindGroupLayout: viewLayout,
253
253
  meshResidencyEpoch: 1,
254
+ hdrp: false,
254
255
  })
255
256
  .unwrap();
256
257
  if (prepared === undefined) throw new Error('GPU-driven production lane inactive');
@@ -25,6 +25,7 @@ describe('GPU-driven indirect raster Dawn', () => {
25
25
  'gpu-driven.view-reset',
26
26
  'gpu-driven.frustum-compact',
27
27
  'gpu-driven.finalize-indirect',
28
+ 'gpu-driven.lod-selection-readback',
28
29
  'gpu-driven.opaque-indirect',
29
30
  'gpu-driven.raster-readback',
30
31
  ]);