@forgeax/engine-render 0.1.19 → 0.1.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (452) hide show
  1. package/README.md +215 -0
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/assembly/device-feature-admission.d.ts +18 -0
  4. package/dist/assembly/device-feature-admission.d.ts.map +1 -0
  5. package/dist/assembly/directional-shadow-inspection.d.ts +38 -0
  6. package/dist/assembly/directional-shadow-inspection.d.ts.map +1 -0
  7. package/dist/assembly/factory.d.ts +34 -20
  8. package/dist/assembly/factory.d.ts.map +1 -1
  9. package/dist/assembly/host-contract.d.ts +9 -0
  10. package/dist/assembly/host-contract.d.ts.map +1 -1
  11. package/dist/assembly/render-target-host.d.ts +46 -0
  12. package/dist/assembly/render-target-host.d.ts.map +1 -0
  13. package/dist/assets/render-asset-port.d.ts.map +1 -1
  14. package/dist/authoring.mjs +3 -0
  15. package/dist/authoring.mjs.map +1 -1
  16. package/dist/capture/cube-views.d.ts +21 -0
  17. package/dist/capture/cube-views.d.ts.map +1 -0
  18. package/dist/capture/scheduler.d.ts +54 -0
  19. package/dist/capture/scheduler.d.ts.map +1 -0
  20. package/dist/{chunk-RJRUS55L.mjs → chunk-5YADUCA7.mjs} +1896 -1624
  21. package/dist/chunk-5YADUCA7.mjs.map +1 -0
  22. package/dist/{chunk-LOSO4CTV.mjs → chunk-6OPXDQ2E.mjs} +423 -46
  23. package/dist/chunk-6OPXDQ2E.mjs.map +1 -0
  24. package/dist/{chunk-IPXCABEP.mjs → chunk-BXBQQKFQ.mjs} +3 -9
  25. package/dist/chunk-BXBQQKFQ.mjs.map +1 -0
  26. package/dist/chunk-FS2KNUQH.mjs +1294 -0
  27. package/dist/chunk-FS2KNUQH.mjs.map +1 -0
  28. package/dist/components/camera.d.ts +1 -0
  29. package/dist/components/camera.d.ts.map +1 -1
  30. package/dist/components/cube-camera.d.ts +20 -0
  31. package/dist/components/cube-camera.d.ts.map +1 -0
  32. package/dist/components/directional-light.d.ts +11 -2
  33. package/dist/components/directional-light.d.ts.map +1 -1
  34. package/dist/components/directional-shadow-filter.d.ts +25 -0
  35. package/dist/components/directional-shadow-filter.d.ts.map +1 -0
  36. package/dist/components/index.d.ts +2 -0
  37. package/dist/components/index.d.ts.map +1 -1
  38. package/dist/components/light-helpers.d.ts.map +1 -1
  39. package/dist/components/reflection-probe.d.ts +17 -0
  40. package/dist/components/reflection-probe.d.ts.map +1 -0
  41. package/dist/components/spot-light.d.ts +12 -0
  42. package/dist/components/spot-light.d.ts.map +1 -1
  43. package/dist/construct-renderer.mjs +8552 -1612
  44. package/dist/construct-renderer.mjs.map +1 -1
  45. package/dist/device/gpu-residency.d.ts +19 -2
  46. package/dist/device/gpu-residency.d.ts.map +1 -1
  47. package/dist/environment/frame.d.ts.map +1 -1
  48. package/dist/environment/lifecycle.d.ts +5 -0
  49. package/dist/environment/lifecycle.d.ts.map +1 -1
  50. package/dist/errors/render.d.ts +209 -93
  51. package/dist/errors/render.d.ts.map +1 -1
  52. package/dist/extract/directional-shadow-projection.d.ts +23 -0
  53. package/dist/extract/directional-shadow-projection.d.ts.map +1 -0
  54. package/dist/extract/environment.d.ts +14 -0
  55. package/dist/extract/environment.d.ts.map +1 -1
  56. package/dist/extract/visibility.d.ts.map +1 -1
  57. package/dist/features/fullscreen.d.ts.map +1 -1
  58. package/dist/features/targets.d.ts.map +1 -1
  59. package/dist/features/types.d.ts +10 -0
  60. package/dist/features/types.d.ts.map +1 -1
  61. package/dist/fullscreen-post-process-pass.d.ts +13 -2
  62. package/dist/fullscreen-post-process-pass.d.ts.map +1 -1
  63. package/dist/gpu-driven/batch-topology.d.ts.map +1 -1
  64. package/dist/gpu-driven/production-raster.d.ts +0 -1
  65. package/dist/gpu-driven/production-raster.d.ts.map +1 -1
  66. package/dist/gpu-scene.d.ts.map +1 -1
  67. package/dist/gpu-usage.d.ts +2 -0
  68. package/dist/gpu-usage.d.ts.map +1 -1
  69. package/dist/hdrp-buffers.d.ts +19 -17
  70. package/dist/hdrp-buffers.d.ts.map +1 -1
  71. package/dist/ibl/IblPipelineCache.d.ts +4 -0
  72. package/dist/ibl/IblPipelineCache.d.ts.map +1 -1
  73. package/dist/ibl/kernel-cache.d.ts +36 -0
  74. package/dist/ibl/kernel-cache.d.ts.map +1 -0
  75. package/dist/index.d.ts +35 -4
  76. package/dist/index.d.ts.map +1 -1
  77. package/dist/index.mjs +4 -4
  78. package/dist/index.mjs.map +1 -1
  79. package/dist/inspection-types.d.ts +51 -0
  80. package/dist/inspection-types.d.ts.map +1 -1
  81. package/dist/instances-derived-bounds.d.ts +50 -0
  82. package/dist/instances-derived-bounds.d.ts.map +1 -0
  83. package/dist/internal.mjs +1 -1
  84. package/dist/light-buffer-layout.d.ts +30 -13
  85. package/dist/light-buffer-layout.d.ts.map +1 -1
  86. package/dist/mesh-material-bindings.d.ts +54 -0
  87. package/dist/mesh-material-bindings.d.ts.map +1 -1
  88. package/dist/pbr-pipeline.d.ts +20 -7
  89. package/dist/pbr-pipeline.d.ts.map +1 -1
  90. package/dist/pipeline/standard-forward-lane.d.ts +2 -1
  91. package/dist/pipeline/standard-forward-lane.d.ts.map +1 -1
  92. package/dist/pipeline/standard-lighting/errors.d.ts +4 -0
  93. package/dist/pipeline/standard-lighting/errors.d.ts.map +1 -0
  94. package/dist/pipeline/standard-lighting/graph.d.ts +24 -0
  95. package/dist/pipeline/standard-lighting/graph.d.ts.map +1 -0
  96. package/dist/pipeline/standard-lighting/inspection.d.ts +38 -0
  97. package/dist/pipeline/standard-lighting/inspection.d.ts.map +1 -0
  98. package/dist/pipeline/standard-lighting/layout.d.ts +28 -0
  99. package/dist/pipeline/standard-lighting/layout.d.ts.map +1 -0
  100. package/dist/pipeline/standard-lighting/light-frame.d.ts +33 -0
  101. package/dist/pipeline/standard-lighting/light-frame.d.ts.map +1 -0
  102. package/dist/pipeline/standard-lighting/membership.d.ts +14 -0
  103. package/dist/pipeline/standard-lighting/membership.d.ts.map +1 -0
  104. package/dist/pipeline/standard-lighting/prepare.d.ts +16 -0
  105. package/dist/pipeline/standard-lighting/prepare.d.ts.map +1 -0
  106. package/dist/pipeline/standard-lighting/topology.d.ts +45 -0
  107. package/dist/pipeline/standard-lighting/topology.d.ts.map +1 -0
  108. package/dist/pipeline/standard-lighting/transport.d.ts +36 -0
  109. package/dist/pipeline/standard-lighting/transport.d.ts.map +1 -0
  110. package/dist/pipeline/standard-pipeline.d.ts +4 -13
  111. package/dist/pipeline/standard-pipeline.d.ts.map +1 -1
  112. package/dist/pipeline/standard-post.d.ts.map +1 -1
  113. package/dist/pipeline/standard-profile.d.ts +12 -20
  114. package/dist/pipeline/standard-profile.d.ts.map +1 -1
  115. package/dist/pipeline-spec.d.ts +40 -7
  116. package/dist/pipeline-spec.d.ts.map +1 -1
  117. package/dist/point-shadow-inspection.d.ts +21 -0
  118. package/dist/point-shadow-inspection.d.ts.map +1 -0
  119. package/dist/record/frame-lighting.d.ts +29 -32
  120. package/dist/record/frame-lighting.d.ts.map +1 -1
  121. package/dist/record/frame-snapshot.d.ts +65 -15
  122. package/dist/record/frame-snapshot.d.ts.map +1 -1
  123. package/dist/record/frame-targets.d.ts +5 -0
  124. package/dist/record/frame-targets.d.ts.map +1 -1
  125. package/dist/record/frame.d.ts +12 -5
  126. package/dist/record/frame.d.ts.map +1 -1
  127. package/dist/record/gpu-pass-timing/contract.d.ts +96 -0
  128. package/dist/record/gpu-pass-timing/contract.d.ts.map +1 -0
  129. package/dist/record/gpu-pass-timing/errors.d.ts +18 -0
  130. package/dist/record/gpu-pass-timing/errors.d.ts.map +1 -0
  131. package/dist/record/gpu-pass-timing/index.d.ts +8 -0
  132. package/dist/record/gpu-pass-timing/index.d.ts.map +1 -0
  133. package/dist/record/gpu-pass-timing/parser.d.ts +11 -0
  134. package/dist/record/gpu-pass-timing/parser.d.ts.map +1 -0
  135. package/dist/record/gpu-pass-timing/session.d.ts +43 -0
  136. package/dist/record/gpu-pass-timing/session.d.ts.map +1 -0
  137. package/dist/record/gpu-timing.d.ts +37 -0
  138. package/dist/record/gpu-timing.d.ts.map +1 -0
  139. package/dist/record/main-pass-geometry.d.ts.map +1 -1
  140. package/dist/record/main-pass-material.d.ts +23 -2
  141. package/dist/record/main-pass-material.d.ts.map +1 -1
  142. package/dist/record/main-pass-sprite-draws.d.ts.map +1 -1
  143. package/dist/record/main-pass.d.ts +2 -0
  144. package/dist/record/main-pass.d.ts.map +1 -1
  145. package/dist/record/render-context.d.ts +48 -3
  146. package/dist/record/render-context.d.ts.map +1 -1
  147. package/dist/record/shadow-pass.d.ts.map +1 -1
  148. package/dist/record/skybox-post-pass.d.ts.map +1 -1
  149. package/dist/record/typed-frame-graph.d.ts +81 -5
  150. package/dist/record/typed-frame-graph.d.ts.map +1 -1
  151. package/dist/record/view-ubo.d.ts +7 -4
  152. package/dist/record/view-ubo.d.ts.map +1 -1
  153. package/dist/reflection/filter.d.ts +22 -0
  154. package/dist/reflection/filter.d.ts.map +1 -0
  155. package/dist/reflection/gpu-table.d.ts +25 -0
  156. package/dist/reflection/gpu-table.d.ts.map +1 -0
  157. package/dist/reflection/projection.d.ts +91 -0
  158. package/dist/reflection/projection.d.ts.map +1 -0
  159. package/dist/render-contract.d.ts +114 -7
  160. package/dist/render-contract.d.ts.map +1 -1
  161. package/dist/render-data.d.ts +10 -6
  162. package/dist/render-data.d.ts.map +1 -1
  163. package/dist/render-graph-primitives.d.ts +1 -0
  164. package/dist/render-graph-primitives.d.ts.map +1 -1
  165. package/dist/render-pipeline.d.ts +78 -2
  166. package/dist/render-pipeline.d.ts.map +1 -1
  167. package/dist/render-system-extract.d.ts +113 -20
  168. package/dist/render-system-extract.d.ts.map +1 -1
  169. package/dist/render-system.d.ts +24 -3
  170. package/dist/render-system.d.ts.map +1 -1
  171. package/dist/renderer-inspect.d.ts +29 -0
  172. package/dist/renderer-inspect.d.ts.map +1 -0
  173. package/dist/renderer.d.ts +43 -0
  174. package/dist/renderer.d.ts.map +1 -0
  175. package/dist/scene/render-scene.d.ts +14 -0
  176. package/dist/scene/render-scene.d.ts.map +1 -1
  177. package/dist/shadow-atlas.d.ts +3 -3
  178. package/dist/shadow-atlas.d.ts.map +1 -1
  179. package/dist/ssao-buffers.d.ts +3 -0
  180. package/dist/ssao-buffers.d.ts.map +1 -1
  181. package/dist/targets/contracts.d.ts +66 -0
  182. package/dist/targets/contracts.d.ts.map +1 -0
  183. package/dist/targets/graph-projection.d.ts +29 -0
  184. package/dist/targets/graph-projection.d.ts.map +1 -0
  185. package/dist/targets/material-source.d.ts +34 -0
  186. package/dist/targets/material-source.d.ts.map +1 -0
  187. package/dist/targets/owner.d.ts +32 -0
  188. package/dist/targets/owner.d.ts.map +1 -0
  189. package/dist/targets/physical.d.ts +18 -0
  190. package/dist/targets/physical.d.ts.map +1 -0
  191. package/dist/targets/readback.d.ts +39 -0
  192. package/dist/targets/readback.d.ts.map +1 -0
  193. package/dist/temporal/frame.d.ts +44 -0
  194. package/dist/temporal/frame.d.ts.map +1 -0
  195. package/dist/temporal/index.mjs +3 -3
  196. package/dist/typed-render-graph-primitives.d.ts +6 -1
  197. package/dist/typed-render-graph-primitives.d.ts.map +1 -1
  198. package/dist/typed-shadow-atlas.d.ts +10 -0
  199. package/dist/typed-shadow-atlas.d.ts.map +1 -0
  200. package/dist/typed-shadow-passes.d.ts +1 -1
  201. package/dist/typed-shadow-passes.d.ts.map +1 -1
  202. package/dist/volume/capability.d.ts +57 -0
  203. package/dist/volume/capability.d.ts.map +1 -0
  204. package/dist/volume/component.d.ts +61 -0
  205. package/dist/volume/component.d.ts.map +1 -0
  206. package/dist/volume/extract.d.ts +11 -0
  207. package/dist/volume/extract.d.ts.map +1 -0
  208. package/dist/volume/inspection.d.ts +38 -0
  209. package/dist/volume/inspection.d.ts.map +1 -0
  210. package/dist/volume/passes.d.ts +38 -0
  211. package/dist/volume/passes.d.ts.map +1 -0
  212. package/dist/volume/recovery.d.ts +38 -0
  213. package/dist/volume/recovery.d.ts.map +1 -0
  214. package/dist/volume/resources.d.ts +57 -0
  215. package/dist/volume/resources.d.ts.map +1 -0
  216. package/dist/volume/temporal.d.ts +35 -0
  217. package/dist/volume/temporal.d.ts.map +1 -0
  218. package/package.json +20 -20
  219. package/src/__tests__/bloom-exact-zero.unit.test.ts +48 -19
  220. package/src/__tests__/bloom-lifecycle.integration.test.ts +41 -13
  221. package/src/__tests__/camera-target-consumption.unit.test.ts +53 -0
  222. package/src/__tests__/cluster-binner.unit.test.ts +35 -0
  223. package/src/__tests__/cluster-grid-default-surface.unit.test.ts +7 -2
  224. package/src/__tests__/cube-camera-scheduler.unit.test.ts +70 -0
  225. package/src/__tests__/cube-camera-single-submit.perf.test.ts +75 -0
  226. package/src/__tests__/cube-camera-view-matrices.unit.test.ts +41 -0
  227. package/src/__tests__/cube-camera-vocabulary.unit.test.ts +47 -0
  228. package/src/__tests__/cube-camera.integration.test.ts +42 -0
  229. package/src/__tests__/device-feature-admission.unit.test.ts +73 -0
  230. package/src/__tests__/direct-light-snapshot.unit.test.ts +3 -3
  231. package/src/__tests__/docs-gate-green.unit.test.ts +38 -0
  232. package/src/__tests__/factory-contract.integration.test.ts +255 -29
  233. package/src/__tests__/fog-component.unit.test.ts +67 -0
  234. package/src/__tests__/fog.integration.test.ts +86 -0
  235. package/src/__tests__/fullscreen-feature-plan.unit.test.ts +37 -0
  236. package/src/__tests__/gpu-driven-indirect-raster-evidence.ts +10 -9
  237. package/src/__tests__/gpu-driven-indirect-raster.dawn.test.ts +17 -1
  238. package/src/__tests__/gpu-driven-production.integration.test.ts +16 -17
  239. package/src/__tests__/gpu-driven-view-graph.integration.test.ts +12 -4
  240. package/src/__tests__/gpu-pass-timing-browser-runner.ts +281 -0
  241. package/src/__tests__/gpu-pass-timing-coexistence.integration.test.ts +80 -0
  242. package/src/__tests__/gpu-pass-timing-contract.unit.test.ts +243 -0
  243. package/src/__tests__/gpu-pass-timing-exact-zero.unit.test.ts +131 -0
  244. package/src/__tests__/gpu-pass-timing-lifecycle.integration.test.ts +230 -0
  245. package/src/__tests__/gpu-pass-timing-observation.unit.test.ts +233 -0
  246. package/src/__tests__/gpu-pass-timing-session.unit.test.ts +438 -0
  247. package/src/__tests__/gpu-pass-timing.browser.test.ts +26 -0
  248. package/src/__tests__/gpu-pass-timing.rhi-null.unit.test.ts +125 -0
  249. package/src/__tests__/hdrp-group2-visibility.unit.test.ts +24 -0
  250. package/src/__tests__/instances-culling.integration.test.ts +94 -0
  251. package/src/__tests__/instances-derived-bounds.unit.test.ts +118 -0
  252. package/src/__tests__/ktx2-basis-gpu-consumer.dawn.test.ts +14 -14
  253. package/src/__tests__/material-observation.integration.test.ts +101 -0
  254. package/src/__tests__/mesh-buffer-usage-surface.unit.test.ts +1 -1
  255. package/src/__tests__/pbr-view-layout.unit.test.ts +16 -0
  256. package/src/__tests__/point-shadow-inspection.unit.test.ts +60 -0
  257. package/src/__tests__/public-surface.test-d.ts +10 -0
  258. package/src/__tests__/reflection-probe-authoring.unit.test.ts +71 -0
  259. package/src/__tests__/reflection-probe-filter.unit.test.ts +54 -0
  260. package/src/__tests__/reflection-probe-ibl-topology.unit.test.ts +45 -0
  261. package/src/__tests__/reflection-probe-pass-encode.integration.test.ts +101 -0
  262. package/src/__tests__/reflection-probe-performance.perf.test.ts +60 -0
  263. package/src/__tests__/reflection-probe-recovery.integration.test.ts +55 -0
  264. package/src/__tests__/reflection-probe-selection.unit.test.ts +46 -0
  265. package/src/__tests__/render-error-code-owner.test-d.ts +23 -3
  266. package/src/__tests__/render-feature-docs.unit.test.ts +1 -1
  267. package/src/__tests__/render-target-admission.unit.test.ts +80 -0
  268. package/src/__tests__/render-target-graph-projection.unit.test.ts +102 -0
  269. package/src/__tests__/render-target-inspection.unit.test.ts +52 -0
  270. package/src/__tests__/render-target-material-source.test-d.ts +30 -0
  271. package/src/__tests__/render-target-material-source.unit.test.ts +97 -0
  272. package/src/__tests__/render-target-owner-placement.unit.test.ts +203 -0
  273. package/src/__tests__/render-target-owner.unit.test.ts +122 -0
  274. package/src/__tests__/render-target-probe-soak.perf.test.ts +94 -0
  275. package/src/__tests__/render-target-public-schema.json +107 -0
  276. package/src/__tests__/render-target-public-schema.unit.test.ts +91 -0
  277. package/src/__tests__/render-target-public-surface.test-d.ts +83 -0
  278. package/src/__tests__/render-target-readback.integration.test.ts +90 -0
  279. package/src/__tests__/render-target-recovery.integration.test.ts +85 -0
  280. package/src/__tests__/renderer-factory-material-contract.unit.test.ts +76 -0
  281. package/src/__tests__/renderer-frame-transaction.integration.test.ts +1 -1
  282. package/src/__tests__/renderer-lifecycle.integration.test.ts +14 -0
  283. package/src/__tests__/shader-stage-surface.unit.test.ts +19 -4
  284. package/src/__tests__/skin-motion-regression.unit.test.ts +132 -0
  285. package/src/__tests__/skinned-shadow-caster.test.ts +32 -2
  286. package/src/__tests__/standard-cluster-capability.integration.test.ts +63 -0
  287. package/src/__tests__/standard-cluster-transport.unit.test.ts +121 -0
  288. package/src/__tests__/standard-cluster-variant-resolution.unit.test.ts +232 -0
  289. package/src/__tests__/standard-clustered-pipeline.unit.test.ts +8 -5
  290. package/src/__tests__/standard-lighting-inspection.integration.test.ts +138 -0
  291. package/src/__tests__/standard-lighting-ssot.unit.test.ts +18 -0
  292. package/src/__tests__/standard-lighting-write.unit.test.ts +377 -0
  293. package/src/__tests__/standard-lighting.integration.test.ts +67 -0
  294. package/src/__tests__/standard-pipeline.dawn.test.ts +206 -5
  295. package/src/__tests__/standard-pipeline.integration.test.ts +181 -150
  296. package/src/__tests__/standard-probe-binding.unit.test.ts +25 -0
  297. package/src/__tests__/standard-profile-oracle.unit.test.ts +23 -36
  298. package/src/__tests__/typed-pipeline-topology.unit.test.ts +395 -14
  299. package/src/__tests__/urp-pipeline-targets.unit.test.ts +2 -2
  300. package/src/__tests__/visibility-resolver.unit.test.ts +15 -0
  301. package/src/__tests__/volume-optics-oracle.unit.test.ts +85 -0
  302. package/src/__tests__/volumetric-fog-budget-recovery.integration.test.ts +655 -0
  303. package/src/__tests__/volumetric-fog-component.unit.test.ts +71 -0
  304. package/src/__tests__/volumetric-fog-composite-stage-readback.dawn.test.ts +376 -0
  305. package/src/__tests__/volumetric-fog-errors.unit.test.ts +51 -0
  306. package/src/__tests__/volumetric-fog-inspection.unit.test.ts +77 -0
  307. package/src/__tests__/volumetric-fog-light-capability.unit.test.ts +19 -0
  308. package/src/__tests__/volumetric-fog-light-consumers.integration.test.ts +18 -0
  309. package/src/__tests__/volumetric-fog-light-errors.unit.test.ts +17 -0
  310. package/src/__tests__/volumetric-fog-light-topology.unit.test.ts +21 -0
  311. package/src/__tests__/volumetric-fog-light-transaction.integration.test.ts +53 -0
  312. package/src/__tests__/volumetric-fog-m9-watcher.integration.test.ts +27 -0
  313. package/src/__tests__/volumetric-fog-point-light-owner.integration.test.ts +44 -0
  314. package/src/__tests__/volumetric-fog-point-spot-accumulation.integration.test.ts +49 -0
  315. package/src/__tests__/volumetric-fog-point-spot-contract.unit.test.ts +28 -0
  316. package/src/__tests__/volumetric-fog-point-spot-lkg.integration.test.ts +65 -0
  317. package/src/__tests__/volumetric-fog-point-spot-selection.integration.test.ts +49 -0
  318. package/src/__tests__/volumetric-fog-point-spot-temporal.unit.test.ts +55 -0
  319. package/src/__tests__/volumetric-fog-projector-contract.unit.test.ts +22 -0
  320. package/src/__tests__/volumetric-fog-projector-inspection.unit.test.ts +12 -0
  321. package/src/__tests__/volumetric-fog-projector-surface-volume.integration.test.ts +11 -0
  322. package/src/__tests__/volumetric-fog-projector-transaction.integration.test.ts +26 -0
  323. package/src/__tests__/volumetric-fog-recovery.integration.test.ts +47 -0
  324. package/src/__tests__/volumetric-fog-selected-light-resolution.integration.test.ts +63 -0
  325. package/src/__tests__/volumetric-fog-spot-closure.integration.test.ts +26 -0
  326. package/src/__tests__/volumetric-fog-spot-contract.test-d.ts +21 -0
  327. package/src/__tests__/volumetric-fog-spot-contract.unit.test.ts +27 -0
  328. package/src/__tests__/volumetric-fog-spot-frame-optics.integration.test.ts +14 -0
  329. package/src/__tests__/volumetric-fog-spot-optics.integration.test.ts +29 -0
  330. package/src/__tests__/volumetric-fog-stage-readback.dawn.test.ts +550 -0
  331. package/src/__tests__/volumetric-fog-temporal-selected-light.unit.test.ts +33 -0
  332. package/src/__tests__/volumetric-fog-temporal.unit.test.ts +45 -0
  333. package/src/__tests__/volumetric-fog-topology.unit.test.ts +237 -0
  334. package/src/__tests__/volumetric-fog-transparent.integration.test.ts +27 -0
  335. package/src/assembly/device-feature-admission.ts +73 -0
  336. package/src/assembly/directional-shadow-inspection.ts +122 -0
  337. package/src/assembly/factory.ts +1093 -246
  338. package/src/assembly/host-contract.ts +25 -0
  339. package/src/assembly/render-target-host.ts +847 -0
  340. package/src/assets/asset-decoders.ts +1 -1
  341. package/src/assets/render-asset-port.ts +7 -1
  342. package/src/capture/cube-views.ts +69 -0
  343. package/src/capture/scheduler.ts +265 -0
  344. package/src/components/__tests__/schema-contract.unit.test.ts +12 -0
  345. package/src/components/__tests__/schema-parity.unit.test.ts +9 -0
  346. package/src/components/camera.ts +4 -0
  347. package/src/components/cube-camera.ts +52 -0
  348. package/src/components/directional-light.ts +22 -2
  349. package/src/components/directional-shadow-filter.ts +57 -0
  350. package/src/components/index.ts +2 -0
  351. package/src/components/light-helpers.ts +85 -7
  352. package/src/components/reflection-probe.ts +49 -0
  353. package/src/components/spot-light.ts +22 -3
  354. package/src/device/gpu-residency.ts +168 -58
  355. package/src/environment/frame.ts +6 -24
  356. package/src/environment/lifecycle.ts +12 -0
  357. package/src/errors/render.ts +405 -143
  358. package/src/extract/directional-shadow-projection.ts +61 -0
  359. package/src/extract/environment.ts +57 -0
  360. package/src/extract/visibility.ts +25 -1
  361. package/src/features/fullscreen.ts +3 -0
  362. package/src/features/targets.ts +4 -1
  363. package/src/features/types.ts +10 -0
  364. package/src/fullscreen-post-process-pass.ts +43 -2
  365. package/src/gpu-driven/batch-topology.ts +1 -0
  366. package/src/gpu-driven/production-raster.ts +1 -3
  367. package/src/gpu-scene.ts +8 -1
  368. package/src/gpu-usage.ts +2 -0
  369. package/src/hdrp-buffers.ts +165 -59
  370. package/src/ibl/IblPipelineCache.ts +11 -0
  371. package/src/ibl/kernel-cache.ts +88 -0
  372. package/src/index.ts +186 -1
  373. package/src/inspection-types.ts +56 -0
  374. package/src/instances-derived-bounds.ts +171 -0
  375. package/src/light-buffer-layout.ts +74 -26
  376. package/src/mesh-material-bindings.ts +132 -0
  377. package/src/pbr-pipeline.ts +139 -27
  378. package/src/pipeline/standard-forward-lane.ts +65 -15
  379. package/src/pipeline/standard-lighting/errors.ts +6 -0
  380. package/src/pipeline/standard-lighting/graph.ts +157 -0
  381. package/src/pipeline/standard-lighting/inspection.ts +88 -0
  382. package/src/pipeline/standard-lighting/layout.ts +31 -0
  383. package/src/pipeline/standard-lighting/light-frame.ts +34 -0
  384. package/src/pipeline/standard-lighting/membership.ts +43 -0
  385. package/src/pipeline/standard-lighting/prepare.ts +37 -0
  386. package/src/pipeline/standard-lighting/topology.ts +120 -0
  387. package/src/pipeline/standard-lighting/transport.ts +77 -0
  388. package/src/pipeline/standard-pipeline.ts +141 -192
  389. package/src/pipeline/standard-post.ts +26 -7
  390. package/src/pipeline/standard-profile.ts +23 -30
  391. package/src/pipeline-spec.ts +84 -21
  392. package/src/point-shadow-inspection.ts +55 -0
  393. package/src/record/__tests__/temporal-payload.unit.test.ts +42 -1
  394. package/src/record/__tests__/transparent-group2-selection.unit.test.ts +73 -0
  395. package/src/record/__tests__/view-ubo-layout.unit.test.ts +40 -0
  396. package/src/record/frame-lighting.ts +470 -516
  397. package/src/record/frame-snapshot.ts +76 -28
  398. package/src/record/frame-targets.ts +23 -2
  399. package/src/record/frame.ts +1169 -79
  400. package/src/record/gpu-pass-timing/contract.ts +180 -0
  401. package/src/record/gpu-pass-timing/errors.ts +34 -0
  402. package/src/record/gpu-pass-timing/index.ts +37 -0
  403. package/src/record/gpu-pass-timing/parser.ts +97 -0
  404. package/src/record/gpu-pass-timing/session.ts +938 -0
  405. package/src/record/gpu-timing.ts +233 -0
  406. package/src/record/main-pass-geometry.ts +120 -80
  407. package/src/record/main-pass-material.ts +101 -11
  408. package/src/record/main-pass-sprite-draws.ts +54 -25
  409. package/src/record/main-pass.ts +71 -15
  410. package/src/record/render-context.ts +77 -0
  411. package/src/record/shadow-pass.ts +104 -66
  412. package/src/record/skybox-post-pass.ts +20 -4
  413. package/src/record/typed-frame-graph.ts +1016 -32
  414. package/src/record/view-ubo.ts +63 -28
  415. package/src/reflection/filter.ts +81 -0
  416. package/src/reflection/gpu-table.ts +44 -0
  417. package/src/reflection/projection.ts +281 -0
  418. package/src/render-contract.ts +147 -4
  419. package/src/render-data.ts +50 -22
  420. package/src/render-graph-primitives.ts +9 -4
  421. package/src/render-pipeline.ts +161 -2
  422. package/src/render-system-extract.ts +886 -103
  423. package/src/render-system.ts +1174 -66
  424. package/src/renderer-inspect.ts +66 -0
  425. package/src/renderer.ts +222 -0
  426. package/src/scene/render-scene.ts +117 -9
  427. package/src/shadow-atlas.ts +5 -5
  428. package/src/ssao-buffers.ts +9 -2
  429. package/src/targets/contracts.ts +182 -0
  430. package/src/targets/graph-projection.ts +169 -0
  431. package/src/targets/material-source.ts +143 -0
  432. package/src/targets/owner.ts +214 -0
  433. package/src/targets/physical.ts +18 -0
  434. package/src/targets/readback.ts +153 -0
  435. package/src/temporal/frame.ts +128 -0
  436. package/src/transmission/__tests__/standard-transmission.dawn.test.ts +61 -23
  437. package/src/typed-render-graph-primitives.ts +34 -2
  438. package/src/typed-shadow-atlas.ts +17 -0
  439. package/src/typed-shadow-passes.ts +47 -40
  440. package/src/volume/capability.ts +173 -0
  441. package/src/volume/component.ts +110 -0
  442. package/src/volume/extract.ts +21 -0
  443. package/src/volume/inspection.ts +78 -0
  444. package/src/volume/passes.ts +686 -0
  445. package/src/volume/recovery.ts +86 -0
  446. package/src/volume/resources.ts +236 -0
  447. package/src/volume/temporal.ts +89 -0
  448. package/dist/chunk-IPXCABEP.mjs.map +0 -1
  449. package/dist/chunk-LOSO4CTV.mjs.map +0 -1
  450. package/dist/chunk-RJRUS55L.mjs.map +0 -1
  451. package/dist/chunk-YTQ5C7YQ.mjs +0 -584
  452. package/dist/chunk-YTQ5C7YQ.mjs.map +0 -1
@@ -0,0 +1,125 @@
1
+ import { RenderGraphBuilder } from '@forgeax/engine-render-graph';
2
+ import type { RhiCommandEncoder } from '@forgeax/engine-rhi';
3
+ import { type RhiNullDevice, rhi } from '@forgeax/engine-rhi-null';
4
+ import { ok } from '@forgeax/engine-types';
5
+ import { describe, expect, it } from 'vitest';
6
+ import { createGpuPassTimingSession } from '../record/gpu-pass-timing/session.js';
7
+ import type { FrameReceipt } from '../render-contract.js';
8
+ import { createGpuPassTimingObservationStore } from '../renderer.js';
9
+
10
+ async function createNullDevice(): Promise<RhiNullDevice> {
11
+ const adapter = (await rhi.requestAdapter()).unwrap();
12
+ return (await adapter.requestDevice()).unwrap() as RhiNullDevice;
13
+ }
14
+
15
+ function receipt(frameId: number): FrameReceipt {
16
+ return {
17
+ frameId,
18
+ deviceGeneration: 1,
19
+ completed: Promise.resolve(ok(undefined)),
20
+ } as FrameReceipt;
21
+ }
22
+
23
+ describe('GPU pass timing RhiNull refusal and exact-zero evidence', () => {
24
+ it('keeps the real RhiNull draw path free of timing resources and commands', async () => {
25
+ const device = await createNullDevice();
26
+ const graph = new RenderGraphBuilder<{ readonly encoder: RhiCommandEncoder }>();
27
+ const output = graph
28
+ .createTexture('output', { format: 'rgba8unorm', size: { width: 1, height: 1 } })
29
+ .unwrap();
30
+ const outputView = graph.view(output).unwrap();
31
+ graph
32
+ .addRasterPass('scene', {
33
+ accesses: [{ resource: outputView, usage: 'color-attachment' }],
34
+ colorAttachments: [{ view: outputView, loadOp: 'clear', storeOp: 'store' }],
35
+ encode: ({ pass }) => pass.draw(3, 1, 0, 0),
36
+ })
37
+ .unwrap();
38
+
39
+ const compiled = graph.compile({ device, surfaceSize: { width: 1, height: 1 } }).unwrap();
40
+ const querySetsBefore = device.bookkeeper
41
+ .allRecords()
42
+ .filter((record) => record.kind === 'QuerySet');
43
+ const encoder = device.createCommandEncoder({ label: 'rhi-null-off' }).unwrap();
44
+ expect(compiled.execute({ encoder }).ok).toBe(true);
45
+ const commandBuffer = encoder.finish().unwrap();
46
+ expect(device.queue.submit([commandBuffer]).ok).toBe(true);
47
+
48
+ expect(device.totalDrawCount).toBe(1);
49
+ expect(device.framePassNames).toEqual(['scene']);
50
+ expect(
51
+ device.bookkeeper.allRecords().filter((record) => record.kind === 'QuerySet'),
52
+ ).toHaveLength(querySetsBefore.length);
53
+ expect(
54
+ device.bookkeeper.allRecords().filter((record) => record.kind === 'Buffer'),
55
+ ).toHaveLength(0);
56
+ expect((await compiled.retire()).ok).toBe(true);
57
+ });
58
+
59
+ it('refuses opt-in before allocation and keeps repeated recovery attempts side-effect free', async () => {
60
+ const device = await createNullDevice();
61
+ const before = device.bookkeeper.recordCount();
62
+ const first = createGpuPassTimingSession(device, { retentionFrames: 8 });
63
+ const second = createGpuPassTimingSession(device, { retentionFrames: 8 });
64
+
65
+ expect(first).toMatchObject({
66
+ ok: false,
67
+ error: { code: 'timestamp-period-unavailable' },
68
+ });
69
+ expect(second).toMatchObject({
70
+ ok: false,
71
+ error: { code: 'timestamp-period-unavailable' },
72
+ });
73
+ expect(device.bookkeeper.recordCount()).toBe(before);
74
+ expect(device.bookkeeper.allRecords().some((record) => record.kind === 'QuerySet')).toBe(false);
75
+ });
76
+
77
+ it('reports unavailable observations without tick or duration fields', async () => {
78
+ const store = createGpuPassTimingObservationStore({
79
+ retentionFrames: 8,
80
+ currentDeviceGeneration: () => 1,
81
+ });
82
+ const current = receipt(1);
83
+ store.register(current, async () => ({
84
+ status: 'unavailable',
85
+ reason: {
86
+ code: 'timestamp-query-unsupported',
87
+ expected: 'the active device exposes timestamp-query',
88
+ hint: 'use a backend that supports timestamp-query',
89
+ detail: {},
90
+ },
91
+ capability: { timestampQuery: false, timestampPeriodNanoseconds: null },
92
+ }));
93
+
94
+ const observed = await store.observe(current, { include: ['timings'] });
95
+ expect(observed.ok).toBe(true);
96
+ if (!observed.ok) return;
97
+ expect(observed.value.timings).toMatchObject({
98
+ status: 'unavailable',
99
+ reason: { code: 'timestamp-query-unsupported' },
100
+ });
101
+ expect(observed.value.timings).not.toHaveProperty('tick');
102
+ expect(observed.value.timings).not.toHaveProperty('durationNanoseconds');
103
+ expect(JSON.stringify(observed.value)).not.toContain('durationNanoseconds');
104
+ });
105
+
106
+ it('marks a RhiNull artifact as refused rather than manufacturing GPU evidence', async () => {
107
+ const validatorModulePath = '../../bench/gpu-pass-timing/validator.js';
108
+ const { validateGpuPassTimingReport } = await import(validatorModulePath);
109
+ const refused = validateGpuPassTimingReport({
110
+ schemaVersion: '1.0',
111
+ benchmark: 'render-gpu-pass-timing',
112
+ source: { sourceHead: '0'.repeat(40), package: '@forgeax/engine-render' },
113
+ runner: { name: 'rhi-null-test', version: '1', os: 'test', browser: null },
114
+ backend: {
115
+ kind: 'null',
116
+ adapter: 'RhiNull',
117
+ driver: 'none',
118
+ browser: 'none',
119
+ realGpu: false,
120
+ },
121
+ });
122
+
123
+ expect(refused).toMatchObject({ ok: false, error: { code: 'artifact-not-real-gpu' } });
124
+ });
125
+ });
@@ -0,0 +1,24 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { GPU_SHADER_STAGE_FRAGMENT, GPU_SHADER_STAGE_VERTEX } from '../gpu-stage';
3
+ import {
4
+ createHdrpBindGroupLayoutDescriptor,
5
+ createHdrpSkinBindGroupLayoutDescriptor,
6
+ } from '../pbr-pipeline';
7
+
8
+ describe('HDRP group(2) mesh visibility contract', () => {
9
+ it('exposes the mesh window to both vertex and fragment stages', () => {
10
+ const descriptor = createHdrpBindGroupLayoutDescriptor();
11
+ const mesh = descriptor.entries?.find((entry) => entry.binding === 0);
12
+ expect(mesh?.visibility).toBe(GPU_SHADER_STAGE_VERTEX | GPU_SHADER_STAGE_FRAGMENT);
13
+ });
14
+
15
+ it('keeps skin palettes vertex-only while sharing mesh visibility', () => {
16
+ const descriptor = createHdrpSkinBindGroupLayoutDescriptor();
17
+ const mesh = descriptor.entries?.find((entry) => entry.binding === 0);
18
+ const currentPalette = descriptor.entries?.find((entry) => entry.binding === 1);
19
+ const previousPalette = descriptor.entries?.find((entry) => entry.binding === 2);
20
+ expect(mesh?.visibility).toBe(GPU_SHADER_STAGE_VERTEX | GPU_SHADER_STAGE_FRAGMENT);
21
+ expect(currentPalette?.visibility).toBe(GPU_SHADER_STAGE_VERTEX);
22
+ expect(previousPalette?.visibility).toBe(GPU_SHADER_STAGE_VERTEX);
23
+ });
24
+ });
@@ -0,0 +1,94 @@
1
+ import { HANDLE_CUBE } from '@forgeax/engine-assets-runtime';
2
+ import { World } from '@forgeax/engine-ecs';
3
+ import { registerPropagateTransforms, Transform } from '@forgeax/engine-scene';
4
+ import { describe, expect, it } from 'vitest';
5
+ import { Camera } from '../components/camera';
6
+ import { Instances } from '../components/instances';
7
+ import { MeshFilter } from '../components/mesh-filter';
8
+ import { MeshRenderer } from '../components/mesh-renderer';
9
+ import { GPU_DRIVEN_VIEW_WGSL } from '../gpu-driven/view-gpu';
10
+ import { extractFrames } from '../render-system-extract';
11
+
12
+ const IDENTITY = new Float32Array([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);
13
+
14
+ function translated(x: number): Float32Array {
15
+ const matrix = new Float32Array(IDENTITY);
16
+ matrix[12] = x;
17
+ return matrix;
18
+ }
19
+
20
+ function spawnCamera(world: World): void {
21
+ world
22
+ .spawn(
23
+ { component: Transform, data: { pos: [0, 0, 5] } },
24
+ { component: Camera, data: { fov: Math.PI / 4, aspect: 1, near: 0.1, far: 100 } },
25
+ )
26
+ .unwrap();
27
+ }
28
+
29
+ function spawnInstancedCube(world: World, entityX: number, instanceX: number) {
30
+ return world
31
+ .spawn(
32
+ { component: Transform, data: { pos: [entityX, 0, 0] } },
33
+ { component: MeshFilter, data: { assetHandle: HANDLE_CUBE } },
34
+ { component: MeshRenderer, data: {} },
35
+ { component: Instances, data: { transforms: translated(instanceX) } },
36
+ )
37
+ .unwrap();
38
+ }
39
+
40
+ describe('Instances CPU/GPU culling ownership', () => {
41
+ it('keeps an instance visible when the entity origin is outside the camera frustum', () => {
42
+ const world = new World();
43
+ registerPropagateTransforms(world);
44
+ spawnCamera(world);
45
+ const entity = spawnInstancedCube(world, 20, -20);
46
+ world.update(0).unwrap();
47
+
48
+ const frame = extractFrames([world], 0);
49
+
50
+ expect(frame.renderables.map((renderable) => renderable.entityKey)).toContain(entity);
51
+ expect(frame.frustumStats).toEqual({ culled: 0, total: 1 });
52
+ });
53
+
54
+ it('recomputes the union after entity and instance transforms change', () => {
55
+ const world = new World();
56
+ registerPropagateTransforms(world);
57
+ spawnCamera(world);
58
+ const entity = spawnInstancedCube(world, 20, -20);
59
+ world.update(0).unwrap();
60
+ expect(extractFrames([world], 0).renderables).toHaveLength(1);
61
+
62
+ world.set(entity, Transform, { pos: [40, 0, 0] }).unwrap();
63
+ world.set(entity, Instances, { transforms: translated(-40) }).unwrap();
64
+ world.update(0).unwrap();
65
+
66
+ const frame = extractFrames([world], 0);
67
+ expect(frame.renderables).toHaveLength(1);
68
+ expect(frame.renderables[0]?.instances?.instanceCount).toBe(1);
69
+ });
70
+
71
+ it('does not make an empty instance array visible or feed it to the GPU as one identity instance', () => {
72
+ const world = new World();
73
+ registerPropagateTransforms(world);
74
+ spawnCamera(world);
75
+ const entity = world
76
+ .spawn(
77
+ { component: Transform, data: { pos: [20, 0, 0] } },
78
+ { component: MeshFilter, data: { assetHandle: HANDLE_CUBE } },
79
+ { component: MeshRenderer, data: {} },
80
+ { component: Instances, data: { transforms: new Float32Array() } },
81
+ )
82
+ .unwrap();
83
+ world.update(0).unwrap();
84
+
85
+ const frame = extractFrames([world], 0);
86
+ expect(
87
+ frame.renderables.find((renderable) => renderable.entityKey === entity)?.instances,
88
+ ).toMatchObject({ instanceCount: 0 });
89
+ expect(GPU_DRIVEN_VIEW_WGSL).toContain('candidate.instanceOrdinal');
90
+ expect(GPU_DRIVEN_VIEW_WGSL).toContain(
91
+ 'currentWorld * transforms[instance.transformIndex].currentWorld',
92
+ );
93
+ });
94
+ });
@@ -0,0 +1,118 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { describe, expect, it } from 'vitest';
3
+ import { deriveInstancesUnionBounds, InstanceBoundsCache } from '../instances-derived-bounds';
4
+
5
+ const IDENTITY = new Float32Array([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);
6
+
7
+ function translated(x: number, y = 0, z = 0): Float32Array {
8
+ const matrix = new Float32Array(IDENTITY);
9
+ matrix[12] = x;
10
+ matrix[13] = y;
11
+ matrix[14] = z;
12
+ return matrix;
13
+ }
14
+
15
+ const meshAabb = new Float32Array([-1, -1, -1, 1, 1, 1]);
16
+ const entityWorld = new Float32Array(IDENTITY);
17
+
18
+ describe('Instances renderer-derived union bounds', () => {
19
+ it('unions every transformed mesh corner in entity world space', () => {
20
+ expect(
21
+ deriveInstancesUnionBounds({
22
+ meshAabb,
23
+ entityWorld,
24
+ transforms: new Float32Array([...IDENTITY, ...translated(5, 2, -3)]),
25
+ }),
26
+ ).toEqual(new Float32Array([-1, -1, -4, 6, 3, 1]));
27
+ });
28
+
29
+ it('does not manufacture a visible bound for empty or malformed input', () => {
30
+ expect(
31
+ deriveInstancesUnionBounds({ meshAabb, entityWorld, transforms: new Float32Array() }),
32
+ ).toBeUndefined();
33
+ expect(
34
+ deriveInstancesUnionBounds({
35
+ meshAabb,
36
+ entityWorld,
37
+ transforms: new Float32Array([...IDENTITY, 1, 2]),
38
+ }),
39
+ ).toBeUndefined();
40
+ expect(
41
+ deriveInstancesUnionBounds({
42
+ meshAabb,
43
+ entityWorld,
44
+ transforms: new Float32Array([...IDENTITY.slice(0, 15), Number.NaN]),
45
+ }),
46
+ ).toBeUndefined();
47
+ });
48
+
49
+ it('invalidates one cached projection when mesh, entity, or matrix generation changes', () => {
50
+ const cache = new InstanceBoundsCache();
51
+ const input = {
52
+ entityKey: 7,
53
+ meshGeneration: 1,
54
+ transformGeneration: 1,
55
+ matrixGeneration: 1,
56
+ meshAabb,
57
+ entityWorld,
58
+ transforms: new Float32Array(IDENTITY),
59
+ };
60
+ const first = cache.get(input);
61
+ expect(first).toEqual(meshAabb);
62
+ expect(cache.get(input)).toBe(first);
63
+
64
+ const matrixChanged = cache.get({ ...input, matrixGeneration: 2, transforms: translated(9) });
65
+ expect(matrixChanged).not.toBe(first);
66
+ expect(matrixChanged?.[0]).toBe(8);
67
+
68
+ const entityChanged = cache.get({
69
+ ...input,
70
+ transformGeneration: 2,
71
+ entityWorld: translated(10),
72
+ });
73
+ expect(entityChanged).not.toBe(first);
74
+ expect(entityChanged?.[0]).toBe(9);
75
+
76
+ const meshChanged = cache.get({
77
+ ...input,
78
+ meshGeneration: 2,
79
+ meshAabb: new Float32Array([-2, -1, -1, 2, 1, 1]),
80
+ });
81
+ expect(meshChanged).not.toBe(first);
82
+ expect(meshChanged).toEqual(new Float32Array([-2, -1, -1, 2, 1, 1]));
83
+
84
+ const reused = cache.get({ ...input, meshGeneration: 3 });
85
+ expect(reused).not.toBe(first);
86
+ });
87
+
88
+ it('does not alias equal entity keys from different worlds in one composite scene', () => {
89
+ const cache = new InstanceBoundsCache();
90
+ const first = cache.get({
91
+ worldId: 1,
92
+ entityKey: 7,
93
+ meshGeneration: 1,
94
+ transformGeneration: 1,
95
+ matrixGeneration: 1,
96
+ meshAabb,
97
+ entityWorld,
98
+ transforms: new Float32Array(IDENTITY),
99
+ });
100
+ const second = cache.get({
101
+ worldId: 2,
102
+ entityKey: 7,
103
+ meshGeneration: 1,
104
+ transformGeneration: 1,
105
+ matrixGeneration: 1,
106
+ meshAabb,
107
+ entityWorld: translated(20),
108
+ transforms: new Float32Array(IDENTITY),
109
+ });
110
+ expect(second).not.toBe(first);
111
+ expect(second?.[0]).toBe(19);
112
+ });
113
+
114
+ it('keeps bounds renderer-derived; the public Instances schema has no bounds field', () => {
115
+ const source = readFileSync(new URL('../components/instances.ts', import.meta.url), 'utf8');
116
+ expect(source).not.toMatch(/\bbounds\s*:/);
117
+ });
118
+ });
@@ -343,12 +343,11 @@ async function textureFor(
343
343
  selected,
344
344
  texture: {
345
345
  kind: 'texture',
346
- width: mip.width,
347
- height: mip.height,
346
+ shape: { viewDimension: '2d', extent: { width: mip.width, height: mip.height } },
348
347
  format: selected,
349
348
  data: mip.data,
350
349
  colorSpace: input.colorSpace,
351
- mipmap: false,
350
+ mips: { kind: 'none' },
352
351
  },
353
352
  };
354
353
  }
@@ -377,12 +376,11 @@ async function textureFor(
377
376
  selected,
378
377
  texture: {
379
378
  kind: 'texture',
380
- width: mip.width,
381
- height: mip.height,
379
+ shape: { viewDimension: '2d', extent: { width: mip.width, height: mip.height } },
382
380
  format: selected,
383
381
  data: mip.data,
384
382
  colorSpace: input.colorSpace,
385
- mipmap: false,
383
+ mips: { kind: 'none' },
386
384
  },
387
385
  };
388
386
  }
@@ -392,6 +390,8 @@ async function readbackTexture(
392
390
  texture: TextureAsset,
393
391
  gpuTexture: { readonly handle: Texture },
394
392
  ): Promise<Uint8Array> {
393
+ const width = texture.shape.extent.width;
394
+ const height = texture.shape.extent.height;
395
395
  const hdr = texture.format === 'rgba16float' || texture.format.startsWith('bc6h');
396
396
  const outputFormat: GPUTextureFormat = hdr ? 'rgba16float' : 'rgba8unorm-srgb';
397
397
  const outputBytesPerPixel = hdr ? 8 : 4;
@@ -447,7 +447,7 @@ async function readbackTexture(
447
447
  });
448
448
  if (!bindings.ok) throw new Error(`${bindings.error.code}:${bindings.error.hint}`);
449
449
  const normalizedTexture = device.createTexture({
450
- size: { width: texture.width, height: texture.height, depthOrArrayLayers: 1 },
450
+ size: { width, height, depthOrArrayLayers: 1 },
451
451
  format: outputFormat,
452
452
  usage: 0x10 | 0x01,
453
453
  } as never);
@@ -457,7 +457,7 @@ async function readbackTexture(
457
457
  if (!normalizedView.ok)
458
458
  throw new Error(`${normalizedView.error.code}:${normalizedView.error.hint}`);
459
459
  const readback = device.createBuffer({
460
- size: 256 * texture.height,
460
+ size: 256 * height,
461
461
  usage: GPU_BUFFER_USAGE_COPY_DST | GPU_BUFFER_USAGE_MAP_READ,
462
462
  });
463
463
  if (!readback.ok) throw new Error(`${readback.error.code}:${readback.error.hint}`);
@@ -483,9 +483,9 @@ async function readbackTexture(
483
483
  buffer: readback.value as unknown as GPUBuffer,
484
484
  offset: 0,
485
485
  bytesPerRow: 256,
486
- rowsPerImage: texture.height,
486
+ rowsPerImage: height,
487
487
  },
488
- { width: texture.width, height: texture.height, depthOrArrayLayers: 1 },
488
+ { width, height, depthOrArrayLayers: 1 },
489
489
  );
490
490
  const command = encoder.value.finish();
491
491
  if (!command.ok) throw new Error(`${command.error.code}:${command.error.hint}`);
@@ -497,11 +497,11 @@ async function readbackTexture(
497
497
  const range = mapped.value.getMappedRange();
498
498
  if (!range.ok) throw new Error(`${range.error.code}:${range.error.hint}`);
499
499
  const mappedBytes = new Uint8Array(range.value);
500
- const bytes = new Uint8Array(texture.width * texture.height * outputBytesPerPixel);
501
- for (let row = 0; row < texture.height; row += 1) {
500
+ const bytes = new Uint8Array(width * height * outputBytesPerPixel);
501
+ for (let row = 0; row < height; row += 1) {
502
502
  bytes.set(
503
- mappedBytes.subarray(row * 256, row * 256 + texture.width * outputBytesPerPixel),
504
- row * texture.width * outputBytesPerPixel,
503
+ mappedBytes.subarray(row * 256, row * 256 + width * outputBytesPerPixel),
504
+ row * width * outputBytesPerPixel,
505
505
  );
506
506
  }
507
507
  mapped.value.unmap();
@@ -0,0 +1,101 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import {
3
+ projectMeshMaterialBindingObservation,
4
+ summarizeMeshMaterialBindings,
5
+ } from '../mesh-material-bindings';
6
+
7
+ const bindings = [
8
+ { handle: 11, source: 'renderer-override' as const },
9
+ { handle: 12, source: 'mesh-default' as const },
10
+ { handle: 0, source: 'engine-default' as const },
11
+ ];
12
+
13
+ const failure = {
14
+ code: 'material-specialization-stale-generation',
15
+ expected: 'a stable material publication generation',
16
+ hint: 'retry the producer-owned material rebuild',
17
+ detail: { material: 'mat-a', dependencies: ['texture/a'] },
18
+ };
19
+
20
+ describe('resident MeshMaterialBindingObservation', () => {
21
+ it('projects sampler, readiness, mip, and nearest preparation failure without producer fields', () => {
22
+ const observation = projectMeshMaterialBindingObservation({
23
+ worldId: 2,
24
+ entityKey: 9,
25
+ bindings,
26
+ diagnostics: [],
27
+ residency: [
28
+ {
29
+ readiness: 'ready',
30
+ samplers: [{ handle: 101, resident: true }],
31
+ textures: [{ handle: 201, mipLevelCount: 4 }],
32
+ },
33
+ {
34
+ readiness: 'pending',
35
+ samplers: [{ handle: 102, resident: false }],
36
+ textures: [{ handle: 202, mipLevelCount: 1 }],
37
+ },
38
+ {
39
+ readiness: 'last-known-good',
40
+ samplers: [{ handle: 103, resident: true }],
41
+ textures: [{ handle: 203, mipLevelCount: 2 }],
42
+ preparationFailure: failure,
43
+ },
44
+ ],
45
+ });
46
+
47
+ expect(observation).toMatchObject({
48
+ worldId: 2,
49
+ entityKey: 9,
50
+ bindings,
51
+ residency: [
52
+ { readiness: 'ready', textures: [{ mipLevelCount: 4 }] },
53
+ { readiness: 'pending' },
54
+ { readiness: 'last-known-good', preparationFailure: failure },
55
+ ],
56
+ });
57
+ expect(observation).not.toHaveProperty('materialAsset');
58
+ expect(observation).not.toHaveProperty('producerReadiness');
59
+ });
60
+
61
+ it('recomputes aggregate counts from the retained observations', () => {
62
+ const first = projectMeshMaterialBindingObservation({
63
+ worldId: 0,
64
+ entityKey: 1,
65
+ bindings,
66
+ diagnostics: [],
67
+ residency: [
68
+ { readiness: 'ready', samplers: [], textures: [] },
69
+ { readiness: 'failed', samplers: [], textures: [], preparationFailure: failure },
70
+ { readiness: 'pending', samplers: [], textures: [] },
71
+ ],
72
+ });
73
+ expect(summarizeMeshMaterialBindings([first])).toEqual({
74
+ total: 3,
75
+ ready: 1,
76
+ pending: 1,
77
+ failed: 1,
78
+ lastKnownGood: 0,
79
+ textureCount: 0,
80
+ samplerCount: 0,
81
+ });
82
+
83
+ const rebuilt = projectMeshMaterialBindingObservation({
84
+ ...first,
85
+ residency: [
86
+ { readiness: 'ready', samplers: [], textures: [] },
87
+ { readiness: 'last-known-good', samplers: [], textures: [], preparationFailure: failure },
88
+ { readiness: 'ready', samplers: [], textures: [] },
89
+ ],
90
+ });
91
+ expect(summarizeMeshMaterialBindings([rebuilt])).toEqual({
92
+ total: 3,
93
+ ready: 2,
94
+ pending: 0,
95
+ failed: 0,
96
+ lastKnownGood: 1,
97
+ textureCount: 0,
98
+ samplerCount: 0,
99
+ });
100
+ });
101
+ });
@@ -71,7 +71,7 @@ describe('render buffer usage owner', () => {
71
71
  expect(GPU_BUFFER_USAGE_UNIFORM).toBe(0x40);
72
72
  expect(GPU_BUFFER_USAGE_STORAGE).toBe(0x80);
73
73
  expect(GPU_BUFFER_USAGE_MAP_READ).toBe(0x01);
74
- expect(ownerSource.match(/export const GPU_BUFFER_USAGE_/g)).toHaveLength(8);
74
+ expect(ownerSource.match(/export const GPU_BUFFER_USAGE_/g)).toHaveLength(9);
75
75
  });
76
76
 
77
77
  it('routes mesh descriptor, update, and bootstrap consumers through the owner', () => {
@@ -0,0 +1,16 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { buildPbrViewBglEntries } from '../pbr-pipeline';
3
+
4
+ describe('PBR view bind-group layout', () => {
5
+ it('allows capture passes to select a per-face View UBO slot', () => {
6
+ const entries = buildPbrViewBglEntries({ storageBuffer: true });
7
+ const binding0 = entries[0];
8
+ expect(binding0?.binding).toBe(0);
9
+ expect(binding0?.buffer).toEqual({ type: 'uniform', hasDynamicOffset: true });
10
+ expect(
11
+ entries
12
+ .filter((entry) => entry.buffer?.hasDynamicOffset === true)
13
+ .map((entry) => entry.binding),
14
+ ).toEqual([0, 10]);
15
+ });
16
+ });
@@ -0,0 +1,60 @@
1
+ import { vec3 } from '@forgeax/engine-math';
2
+ import { describe, expect, it } from 'vitest';
3
+ import { inspectPointShadow } from '../point-shadow-inspection';
4
+ import type { PointShadowSnapshot } from '../render-system-extract';
5
+ import { SHADOW_ATLAS_DEFAULT_LAYERS } from '../shadow-atlas';
6
+
7
+ function snapshots(...layers: number[]): PointShadowSnapshot[] {
8
+ return layers.map((shadowAtlasLayer, entity) => ({
9
+ entity,
10
+ position: vec3.create(0, 0, 0),
11
+ mapSize: 512,
12
+ nearPlane: 0.1,
13
+ farPlane: 25,
14
+ shadowAtlasLayer,
15
+ shadowMatrices: new Float32Array(96),
16
+ }));
17
+ }
18
+
19
+ describe('point-shadow inspection', () => {
20
+ it('derives requested, admitted, shadowed, and occupancy from one layer projection', () => {
21
+ expect(inspectPointShadow(snapshots(0, 1, 2, 3, -1), SHADOW_ATLAS_DEFAULT_LAYERS)).toEqual({
22
+ status: 'over-budget',
23
+ requested: SHADOW_ATLAS_DEFAULT_LAYERS + 1,
24
+ admitted: SHADOW_ATLAS_DEFAULT_LAYERS,
25
+ shadowed: SHADOW_ATLAS_DEFAULT_LAYERS,
26
+ shadowAtlasOccupancy: SHADOW_ATLAS_DEFAULT_LAYERS,
27
+ shadowAtlasCapacity: SHADOW_ATLAS_DEFAULT_LAYERS,
28
+ });
29
+ });
30
+
31
+ it('reports disabled shadow production without inventing a fallback admission', () => {
32
+ expect(inspectPointShadow([], SHADOW_ATLAS_DEFAULT_LAYERS)).toEqual({
33
+ status: 'inactive',
34
+ requested: 0,
35
+ admitted: 0,
36
+ shadowed: 0,
37
+ shadowAtlasOccupancy: 0,
38
+ shadowAtlasCapacity: SHADOW_ATLAS_DEFAULT_LAYERS,
39
+ });
40
+ });
41
+
42
+ it('treats an invalid layer as non-admitted and keeps the request visible', () => {
43
+ expect(inspectPointShadow(snapshots(-1, 7), SHADOW_ATLAS_DEFAULT_LAYERS)).toMatchObject({
44
+ status: 'over-budget',
45
+ requested: 2,
46
+ admitted: 0,
47
+ shadowed: 0,
48
+ shadowAtlasOccupancy: 0,
49
+ });
50
+ });
51
+
52
+ it('does not call a partially admitted request ready', () => {
53
+ expect(inspectPointShadow(snapshots(0, -1), SHADOW_ATLAS_DEFAULT_LAYERS)).toMatchObject({
54
+ status: 'over-budget',
55
+ requested: 2,
56
+ admitted: 1,
57
+ shadowed: 1,
58
+ });
59
+ });
60
+ });
@@ -1,9 +1,13 @@
1
1
  import type {
2
2
  FrameObservationRequest,
3
3
  FrameReceipt,
4
+ RenderError as PublicRenderError,
5
+ ReflectionProbeInspection,
6
+ ReflectionProbeSelectionInspection,
4
7
  RendererState,
5
8
  RenderFrameInput,
6
9
  RenderInspection,
10
+ RenderIntentInvalidDetail,
7
11
  RenderWorldLease,
8
12
  } from '@forgeax/engine-render';
9
13
  import {
@@ -15,6 +19,7 @@ import {
15
19
  visibilityStateFromU32,
16
20
  } from '@forgeax/engine-render';
17
21
  import { GlyphText, Tilemap } from '@forgeax/engine-render/authoring';
22
+ import { expectTypeOf } from 'vitest';
18
23
 
19
24
  void Camera;
20
25
  void Atmosphere;
@@ -52,6 +57,11 @@ const alive: RendererState = 'alive';
52
57
  void alive;
53
58
 
54
59
  declare const inspection: RenderInspection;
60
+ const reflectionInspection: ReflectionProbeInspection = inspection.reflectionProbes;
61
+ const reflectionSelection: ReflectionProbeSelectionInspection = reflectionInspection.selection;
62
+ void reflectionSelection;
63
+ type IntentError = Extract<PublicRenderError, { readonly code: 'render-intent-invalid' }>;
64
+ expectTypeOf<IntentError['detail']>().toEqualTypeOf<RenderIntentInvalidDetail>();
55
65
  const backendKind: RenderInspection['capabilities']['backendKind'] =
56
66
  inspection.capabilities.backendKind;
57
67
  void backendKind;