@forgeax/engine-render 0.1.21 → 0.1.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (584) hide show
  1. package/README.md +281 -38
  2. package/dist/assembly/backend-contract.d.ts +13 -0
  3. package/dist/assembly/backend-contract.d.ts.map +1 -1
  4. package/dist/assembly/bundler-contract.d.ts +9 -0
  5. package/dist/assembly/bundler-contract.d.ts.map +1 -0
  6. package/dist/assembly/device-feature-admission.d.ts +8 -0
  7. package/dist/assembly/device-feature-admission.d.ts.map +1 -1
  8. package/dist/assembly/extended-lighting-fallback.d.ts +13 -0
  9. package/dist/assembly/extended-lighting-fallback.d.ts.map +1 -0
  10. package/dist/assembly/factory.d.ts +7 -235
  11. package/dist/assembly/factory.d.ts.map +1 -1
  12. package/dist/assembly/host-contract.d.ts +10 -0
  13. package/dist/assembly/host-contract.d.ts.map +1 -1
  14. package/dist/assembly/material/assembly.d.ts +3 -2
  15. package/dist/assembly/material/assembly.d.ts.map +1 -1
  16. package/dist/assembly/material/standard-layer-projection.d.ts +10 -0
  17. package/dist/assembly/material/standard-layer-projection.d.ts.map +1 -0
  18. package/dist/assembly/material-shader-policy.d.ts +168 -0
  19. package/dist/assembly/material-shader-policy.d.ts.map +1 -0
  20. package/dist/assembly/material-shader-prewarm.d.ts +21 -0
  21. package/dist/assembly/material-shader-prewarm.d.ts.map +1 -0
  22. package/dist/assembly/mesh-ssbo-grow.d.ts +88 -0
  23. package/dist/assembly/mesh-ssbo-grow.d.ts.map +1 -0
  24. package/dist/assembly/recovery/device-loss-fanout.d.ts +14 -0
  25. package/dist/assembly/recovery/device-loss-fanout.d.ts.map +1 -0
  26. package/dist/assembly/recovery/generation.d.ts +46 -0
  27. package/dist/assembly/recovery/generation.d.ts.map +1 -0
  28. package/dist/assembly/recovery/recovery-attempt.d.ts +63 -0
  29. package/dist/assembly/recovery/recovery-attempt.d.ts.map +1 -0
  30. package/dist/assembly/recovery/renderer-recover.d.ts +115 -0
  31. package/dist/assembly/recovery/renderer-recover.d.ts.map +1 -0
  32. package/dist/assembly/renderer-facade.d.ts +10 -0
  33. package/dist/assembly/renderer-facade.d.ts.map +1 -0
  34. package/dist/assembly/renderer-frame-transaction.d.ts +17 -0
  35. package/dist/assembly/renderer-frame-transaction.d.ts.map +1 -1
  36. package/dist/assembly/renderer-helpers.d.ts +19 -0
  37. package/dist/assembly/renderer-helpers.d.ts.map +1 -0
  38. package/dist/assembly/renderer-lifecycle.d.ts +34 -0
  39. package/dist/assembly/renderer-lifecycle.d.ts.map +1 -1
  40. package/dist/assembly/shader-prewarm-policy.d.ts +15 -0
  41. package/dist/assembly/shader-prewarm-policy.d.ts.map +1 -0
  42. package/dist/assembly/webgpu-ready-contract.d.ts +9 -0
  43. package/dist/assembly/webgpu-ready-contract.d.ts.map +1 -0
  44. package/dist/assembly/webgpu-ready.d.ts +55 -0
  45. package/dist/assembly/webgpu-ready.d.ts.map +1 -0
  46. package/dist/assembly/webgpu-renderer.d.ts +152 -0
  47. package/dist/assembly/webgpu-renderer.d.ts.map +1 -0
  48. package/dist/authoring.mjs +1 -1
  49. package/dist/{chunk-PCHBPYML.mjs → chunk-GE3SDD72.mjs} +37 -5
  50. package/dist/chunk-GE3SDD72.mjs.map +1 -0
  51. package/dist/chunk-KZFTK6DO.mjs +1191 -0
  52. package/dist/chunk-KZFTK6DO.mjs.map +1 -0
  53. package/dist/{chunk-FS2KNUQH.mjs → chunk-Q752SHI5.mjs} +332 -568
  54. package/dist/chunk-Q752SHI5.mjs.map +1 -0
  55. package/dist/{chunk-6OPXDQ2E.mjs → chunk-RR5L4SRS.mjs} +33 -294
  56. package/dist/chunk-RR5L4SRS.mjs.map +1 -0
  57. package/dist/chunk-TFH5I7JJ.mjs +296 -0
  58. package/dist/chunk-TFH5I7JJ.mjs.map +1 -0
  59. package/dist/chunk-UQYS7FRM.mjs +1003 -0
  60. package/dist/chunk-UQYS7FRM.mjs.map +1 -0
  61. package/dist/chunk-YKB5DQV7.mjs +345 -0
  62. package/dist/chunk-YKB5DQV7.mjs.map +1 -0
  63. package/dist/{chunk-5YADUCA7.mjs → chunk-YRUYGA4A.mjs} +1189 -1217
  64. package/dist/chunk-YRUYGA4A.mjs.map +1 -0
  65. package/dist/chunk-Z3YYH2MB.mjs +5082 -0
  66. package/dist/chunk-Z3YYH2MB.mjs.map +1 -0
  67. package/dist/cluster-binner.d.ts +5 -0
  68. package/dist/cluster-binner.d.ts.map +1 -1
  69. package/dist/components/index.d.ts +3 -0
  70. package/dist/components/index.d.ts.map +1 -1
  71. package/dist/components/light-helpers.d.ts +4 -0
  72. package/dist/components/light-helpers.d.ts.map +1 -1
  73. package/dist/components/light-probe.d.ts +9 -0
  74. package/dist/components/light-probe.d.ts.map +1 -0
  75. package/dist/components/rect-area-light.d.ts +12 -0
  76. package/dist/components/rect-area-light.d.ts.map +1 -0
  77. package/dist/components/scene-instance.d.ts +4 -0
  78. package/dist/components/scene-instance.d.ts.map +1 -1
  79. package/dist/components/spot-light.d.ts +3 -0
  80. package/dist/components/spot-light.d.ts.map +1 -1
  81. package/dist/construct-renderer.mjs +27276 -23232
  82. package/dist/construct-renderer.mjs.map +1 -1
  83. package/dist/device/device-scope.d.ts +2 -0
  84. package/dist/device/device-scope.d.ts.map +1 -1
  85. package/dist/device/gpu-residency.d.ts +32 -5
  86. package/dist/device/gpu-residency.d.ts.map +1 -1
  87. package/dist/device/lifecycle-transaction.d.ts +2 -0
  88. package/dist/device/lifecycle-transaction.d.ts.map +1 -0
  89. package/dist/environment/frame.d.ts +5 -0
  90. package/dist/environment/frame.d.ts.map +1 -1
  91. package/dist/environment/inspection.d.ts +7 -0
  92. package/dist/environment/inspection.d.ts.map +1 -1
  93. package/dist/environment/lifecycle.d.ts +20 -2
  94. package/dist/environment/lifecycle.d.ts.map +1 -1
  95. package/dist/errors/gpu-driven.d.ts +26 -0
  96. package/dist/errors/gpu-driven.d.ts.map +1 -0
  97. package/dist/errors/index.d.ts +1 -0
  98. package/dist/errors/index.d.ts.map +1 -1
  99. package/dist/errors/recover.d.ts +67 -3
  100. package/dist/errors/recover.d.ts.map +1 -1
  101. package/dist/errors/render.d.ts +39 -1
  102. package/dist/errors/render.d.ts.map +1 -1
  103. package/dist/extract/contracts.d.ts +899 -0
  104. package/dist/extract/contracts.d.ts.map +1 -0
  105. package/dist/extract/gpu-driven.d.ts +50 -0
  106. package/dist/extract/gpu-driven.d.ts.map +1 -0
  107. package/dist/extract/visibility.d.ts.map +1 -1
  108. package/dist/features/host.d.ts +2 -0
  109. package/dist/features/host.d.ts.map +1 -1
  110. package/dist/features/prepared-gpu-work.d.ts +3 -0
  111. package/dist/features/prepared-gpu-work.d.ts.map +1 -1
  112. package/dist/features/prepared-graphics-store.d.ts +2 -0
  113. package/dist/features/prepared-graphics-store.d.ts.map +1 -1
  114. package/dist/gpu-driven/batch-topology.d.ts +34 -0
  115. package/dist/gpu-driven/batch-topology.d.ts.map +1 -1
  116. package/dist/gpu-driven/prepared-draw.d.ts +63 -0
  117. package/dist/gpu-driven/prepared-draw.d.ts.map +1 -0
  118. package/dist/gpu-driven/production-raster.d.ts +126 -2
  119. package/dist/gpu-driven/production-raster.d.ts.map +1 -1
  120. package/dist/gpu-driven/view-gpu.d.ts +64 -1
  121. package/dist/gpu-driven/view-gpu.d.ts.map +1 -1
  122. package/dist/gpu-scene-schema.d.ts +14 -0
  123. package/dist/gpu-scene-schema.d.ts.map +1 -1
  124. package/dist/gpu-scene.d.ts +9 -1
  125. package/dist/gpu-scene.d.ts.map +1 -1
  126. package/dist/gpu-usage.d.ts +0 -1
  127. package/dist/gpu-usage.d.ts.map +1 -1
  128. package/dist/hdrp-buffers.d.ts +3 -3
  129. package/dist/hdrp-buffers.d.ts.map +1 -1
  130. package/dist/ibl/IblPipelineCache.d.ts +7 -6
  131. package/dist/ibl/IblPipelineCache.d.ts.map +1 -1
  132. package/dist/ibl/skylight-bind-group.d.ts +7 -1
  133. package/dist/ibl/skylight-bind-group.d.ts.map +1 -1
  134. package/dist/index.d.ts +13 -2
  135. package/dist/index.d.ts.map +1 -1
  136. package/dist/index.mjs +90 -5
  137. package/dist/index.mjs.map +1 -1
  138. package/dist/inspection-types.d.ts +163 -1
  139. package/dist/inspection-types.d.ts.map +1 -1
  140. package/dist/internal.d.ts +9 -0
  141. package/dist/internal.d.ts.map +1 -1
  142. package/dist/internal.mjs +23 -2
  143. package/dist/internal.mjs.map +1 -1
  144. package/dist/light-buffer-layout.d.ts +60 -148
  145. package/dist/light-buffer-layout.d.ts.map +1 -1
  146. package/dist/materials.d.ts +34 -10
  147. package/dist/materials.d.ts.map +1 -1
  148. package/dist/mesh-material-bindings.d.ts +56 -0
  149. package/dist/mesh-material-bindings.d.ts.map +1 -1
  150. package/dist/occlusion-proxy.d.ts +4 -0
  151. package/dist/occlusion-proxy.d.ts.map +1 -0
  152. package/dist/pbr-pipeline.d.ts +58 -21
  153. package/dist/pbr-pipeline.d.ts.map +1 -1
  154. package/dist/pipeline/standard-forward-lane.d.ts.map +1 -1
  155. package/dist/pipeline/standard-lighting/graph.d.ts.map +1 -1
  156. package/dist/pipeline/standard-lighting/light-frame.d.ts +3 -3
  157. package/dist/pipeline/standard-lighting/light-frame.d.ts.map +1 -1
  158. package/dist/pipeline/standard-lighting/topology.d.ts +1 -1
  159. package/dist/pipeline/standard-lighting/topology.d.ts.map +1 -1
  160. package/dist/pipeline/standard-lighting/transport.d.ts.map +1 -1
  161. package/dist/pipeline/standard-pipeline.d.ts.map +1 -1
  162. package/dist/pipeline-builder.d.ts +2 -0
  163. package/dist/pipeline-builder.d.ts.map +1 -1
  164. package/dist/pipeline-spec.d.ts +1 -1
  165. package/dist/pipeline-spec.d.ts.map +1 -1
  166. package/dist/plugin.d.ts.map +1 -1
  167. package/dist/points-lines/prepare.d.ts +3 -0
  168. package/dist/points-lines/prepare.d.ts.map +1 -1
  169. package/dist/points-lines/standard-owner.d.ts +33 -0
  170. package/dist/points-lines/standard-owner.d.ts.map +1 -0
  171. package/dist/prepare/extended-lighting/ltc-resources.d.ts +31 -0
  172. package/dist/prepare/extended-lighting/ltc-resources.d.ts.map +1 -0
  173. package/dist/prepare/extended-lighting/resources.d.ts +72 -0
  174. package/dist/prepare/extended-lighting/resources.d.ts.map +1 -0
  175. package/dist/prepare/extended-lighting/spot-modifiers.d.ts +42 -0
  176. package/dist/prepare/extended-lighting/spot-modifiers.d.ts.map +1 -0
  177. package/dist/prepare/extended-lighting/state.d.ts +24 -0
  178. package/dist/prepare/extended-lighting/state.d.ts.map +1 -0
  179. package/dist/record/frame-lighting.d.ts +8 -6
  180. package/dist/record/frame-lighting.d.ts.map +1 -1
  181. package/dist/record/frame-snapshot.d.ts +55 -10
  182. package/dist/record/frame-snapshot.d.ts.map +1 -1
  183. package/dist/record/frame-targets.d.ts.map +1 -1
  184. package/dist/record/frame.d.ts +21 -5
  185. package/dist/record/frame.d.ts.map +1 -1
  186. package/dist/record/gpu-timing.d.ts +19 -7
  187. package/dist/record/gpu-timing.d.ts.map +1 -1
  188. package/dist/record/helpers.d.ts +0 -20
  189. package/dist/record/helpers.d.ts.map +1 -1
  190. package/dist/record/main-pass-geometry.d.ts +20 -2
  191. package/dist/record/main-pass-geometry.d.ts.map +1 -1
  192. package/dist/record/main-pass-material.d.ts +10 -18
  193. package/dist/record/main-pass-material.d.ts.map +1 -1
  194. package/dist/record/main-pass-sprite-draws.d.ts.map +1 -1
  195. package/dist/record/main-pass.d.ts.map +1 -1
  196. package/dist/record/mesh-ssbo.d.ts +11 -3
  197. package/dist/record/mesh-ssbo.d.ts.map +1 -1
  198. package/dist/record/recovery-pipeline.d.ts +30 -0
  199. package/dist/record/recovery-pipeline.d.ts.map +1 -0
  200. package/dist/record/render-context.d.ts +82 -9
  201. package/dist/record/render-context.d.ts.map +1 -1
  202. package/dist/record/shadow-pass.d.ts.map +1 -1
  203. package/dist/record/typed-frame-graph.d.ts +49 -2
  204. package/dist/record/typed-frame-graph.d.ts.map +1 -1
  205. package/dist/record/view-ubo.d.ts +1 -2
  206. package/dist/record/view-ubo.d.ts.map +1 -1
  207. package/dist/recovery/render-system-candidate.d.ts +65 -0
  208. package/dist/recovery/render-system-candidate.d.ts.map +1 -0
  209. package/dist/reflection/inspection.d.ts +10 -0
  210. package/dist/reflection/inspection.d.ts.map +1 -0
  211. package/dist/reflection/projection.d.ts +49 -0
  212. package/dist/reflection/projection.d.ts.map +1 -1
  213. package/dist/reflection/record-owner.d.ts +60 -0
  214. package/dist/reflection/record-owner.d.ts.map +1 -0
  215. package/dist/render-contract.d.ts +78 -15
  216. package/dist/render-contract.d.ts.map +1 -1
  217. package/dist/render-pipeline.d.ts +7 -0
  218. package/dist/render-pipeline.d.ts.map +1 -1
  219. package/dist/render-system-extract-tail.d.ts +12 -0
  220. package/dist/render-system-extract-tail.d.ts.map +1 -0
  221. package/dist/render-system-extract.d.ts +252 -30
  222. package/dist/render-system-extract.d.ts.map +1 -1
  223. package/dist/render-system.d.ts +140 -5
  224. package/dist/render-system.d.ts.map +1 -1
  225. package/dist/scene/probe-blend-record.d.ts +22 -0
  226. package/dist/scene/probe-blend-record.d.ts.map +1 -0
  227. package/dist/scene/probe-blend.d.ts +142 -0
  228. package/dist/scene/probe-blend.d.ts.map +1 -0
  229. package/dist/scene/render-scene-types.d.ts +0 -6
  230. package/dist/scene/render-scene-types.d.ts.map +1 -1
  231. package/dist/scene/render-scene.d.ts +122 -9
  232. package/dist/scene/render-scene.d.ts.map +1 -1
  233. package/dist/scene/visibility/bounds.d.ts +20 -0
  234. package/dist/scene/visibility/bounds.d.ts.map +1 -0
  235. package/dist/scene/visibility/budget.d.ts +16 -0
  236. package/dist/scene/visibility/budget.d.ts.map +1 -0
  237. package/dist/scene/visibility/facet.d.ts +73 -0
  238. package/dist/scene/visibility/facet.d.ts.map +1 -0
  239. package/dist/scene/visibility/gpu-frame-timing.d.ts +41 -0
  240. package/dist/scene/visibility/gpu-frame-timing.d.ts.map +1 -0
  241. package/dist/scene/visibility/gpu-lod.d.ts +46 -0
  242. package/dist/scene/visibility/gpu-lod.d.ts.map +1 -0
  243. package/dist/scene/visibility/inspection.d.ts +132 -0
  244. package/dist/scene/visibility/inspection.d.ts.map +1 -0
  245. package/dist/scene/visibility/lod-selector.d.ts +25 -0
  246. package/dist/scene/visibility/lod-selector.d.ts.map +1 -0
  247. package/dist/scene/visibility/occlusion-confidence.d.ts +81 -0
  248. package/dist/scene/visibility/occlusion-confidence.d.ts.map +1 -0
  249. package/dist/scene/visibility/occlusion-pass.d.ts +57 -0
  250. package/dist/scene/visibility/occlusion-pass.d.ts.map +1 -0
  251. package/dist/scene/visibility/occlusion-query-pool.d.ts +57 -0
  252. package/dist/scene/visibility/occlusion-query-pool.d.ts.map +1 -0
  253. package/dist/scene/visibility/occlusion-runtime.d.ts +96 -0
  254. package/dist/scene/visibility/occlusion-runtime.d.ts.map +1 -0
  255. package/dist/scene/visibility/types.d.ts +34 -0
  256. package/dist/scene/visibility/types.d.ts.map +1 -0
  257. package/dist/ssr/admission.d.ts +122 -0
  258. package/dist/ssr/admission.d.ts.map +1 -0
  259. package/dist/ssr/composition.d.ts +32 -0
  260. package/dist/ssr/composition.d.ts.map +1 -0
  261. package/dist/ssr/errors.d.ts +39 -0
  262. package/dist/ssr/errors.d.ts.map +1 -0
  263. package/dist/ssr/identity.d.ts +13 -0
  264. package/dist/ssr/identity.d.ts.map +1 -0
  265. package/dist/systems/skin-palette-allocator.d.ts +1 -1
  266. package/dist/temporal/frame-coordinator.d.ts +13 -0
  267. package/dist/temporal/frame-coordinator.d.ts.map +1 -1
  268. package/dist/temporal/frame.d.ts +2 -0
  269. package/dist/temporal/frame.d.ts.map +1 -1
  270. package/dist/temporal/index.mjs +6 -5
  271. package/dist/temporal/taa-history-store.d.ts +15 -1
  272. package/dist/temporal/taa-history-store.d.ts.map +1 -1
  273. package/dist/temporal/transaction.d.ts +5 -0
  274. package/dist/temporal/transaction.d.ts.map +1 -1
  275. package/dist/temporal/view.d.ts +7 -0
  276. package/dist/temporal/view.d.ts.map +1 -1
  277. package/dist/tilemap-chunk-extract-system.d.ts +1 -1
  278. package/dist/typed-render-graph-primitives.d.ts +4 -0
  279. package/dist/typed-render-graph-primitives.d.ts.map +1 -1
  280. package/dist/volume/passes.d.ts +8 -4
  281. package/dist/volume/passes.d.ts.map +1 -1
  282. package/package.json +20 -20
  283. package/src/__tests__/cluster-grid-default-surface.unit.test.ts +5 -0
  284. package/src/__tests__/detached-inspection.integration.test.ts +6 -0
  285. package/src/__tests__/device-feature-admission.unit.test.ts +19 -1
  286. package/src/__tests__/device-loss-fanout.unit.test.ts +107 -0
  287. package/src/__tests__/device-scope-lifecycle.unit.test.ts +21 -0
  288. package/src/__tests__/direct-light-snapshot.unit.test.ts +59 -3
  289. package/src/__tests__/environment-generation.integration.test.ts +39 -0
  290. package/src/__tests__/evidence-identity.ts +13 -0
  291. package/src/__tests__/evidence-identity.unit.test.ts +24 -0
  292. package/src/__tests__/expose-renderer-errors.unit.test.ts +34 -0
  293. package/src/__tests__/extended-lighting-recovery.browser.test.ts +14 -0
  294. package/src/__tests__/extended-lighting-recovery.dawn.test.ts +14 -0
  295. package/src/__tests__/extended-lighting-recovery.integration.test.ts +81 -0
  296. package/src/__tests__/extended-lighting-resources.unit.test.ts +51 -0
  297. package/src/__tests__/extended-lighting-zero-feature.integration.test.ts +52 -0
  298. package/src/__tests__/factory-contract.integration.test.ts +154 -6
  299. package/src/__tests__/fallback-row-stride-surface.unit.test.ts +4 -1
  300. package/src/__tests__/frame-observation.contract.test.ts +15 -0
  301. package/src/__tests__/gpu-driven-batch-topology-pbr.unit.test.ts +146 -0
  302. package/src/__tests__/gpu-driven-indirect-raster-evidence.ts +1 -0
  303. package/src/__tests__/gpu-driven-indirect-raster.dawn.test.ts +1 -0
  304. package/src/__tests__/gpu-driven-production.integration.test.ts +572 -9
  305. package/src/__tests__/gpu-driven-scaling.unit.test.ts +66 -2
  306. package/src/__tests__/gpu-driven-view-gpu-evidence.ts +26 -1
  307. package/src/__tests__/gpu-driven-view-graph.integration.test.ts +89 -2
  308. package/src/__tests__/gpu-driven-view.browser.test.ts +3 -3
  309. package/src/__tests__/gpu-driven-view.dawn.test.ts +3 -3
  310. package/src/__tests__/gpu-driven-view.unit.test.ts +50 -0
  311. package/src/__tests__/gpu-lod-evidence.ts +188 -0
  312. package/src/__tests__/gpu-lod-fallback.integration.test.ts +10 -0
  313. package/src/__tests__/gpu-lod-indirect.integration.test.ts +40 -0
  314. package/src/__tests__/gpu-lod-parity.dawn.test.ts +29 -0
  315. package/src/__tests__/gpu-lod-readiness.integration.test.ts +52 -0
  316. package/src/__tests__/gpu-lod-schema.unit.test.ts +66 -0
  317. package/src/__tests__/gpu-lod-selector.unit.test.ts +28 -0
  318. package/src/__tests__/gpu-lod-silhouette.browser.test.ts +13 -0
  319. package/src/__tests__/gpu-resource-store-stride.unit.test.ts +66 -1
  320. package/src/__tests__/gpu-scene.dawn.test.ts +8 -1
  321. package/src/__tests__/gpu-scene.unit.test.ts +49 -3
  322. package/src/__tests__/ibl/face-uniforms.unit.test.ts +2 -0
  323. package/src/__tests__/ibl-residency-publication.unit.test.ts +113 -0
  324. package/src/__tests__/light-buffer-layout.unit.test.ts +113 -3
  325. package/src/__tests__/light-inspection.unit.test.ts +55 -0
  326. package/src/__tests__/light-resource-error.unit.test.ts +43 -0
  327. package/src/__tests__/lighting-scene-roundtrip.integration.test.ts +109 -0
  328. package/src/__tests__/lod-bounds.unit.test.ts +42 -0
  329. package/src/__tests__/lod-cpu-lane.integration.test.ts +55 -0
  330. package/src/__tests__/lod-cpu-no-change.perf.test.ts +19 -0
  331. package/src/__tests__/lod-gpu-frame-timing.unit.test.ts +49 -0
  332. package/src/__tests__/lod-multi-view.integration.test.ts +63 -0
  333. package/src/__tests__/lod-occlusion-device-loss.integration.test.ts +27 -0
  334. package/src/__tests__/lod-occlusion-final-decision.integration.test.ts +47 -0
  335. package/src/__tests__/lod-occlusion-inspection.integration.test.ts +217 -0
  336. package/src/__tests__/lod-occlusion-inspection.schema.test-d.ts +14 -0
  337. package/src/__tests__/lod-occlusion-inspection.unit.test.ts +72 -0
  338. package/src/__tests__/lod-occlusion-visual-evidence.ts +91 -0
  339. package/src/__tests__/lod-occlusion.browser.test.ts +15 -0
  340. package/src/__tests__/lod-occlusion.dawn.test.ts +20 -0
  341. package/src/__tests__/lod-selector.profile.test.ts +35 -0
  342. package/src/__tests__/lod-selector.unit.test.ts +68 -0
  343. package/src/__tests__/lod-visibility-identity.unit.test.ts +198 -0
  344. package/src/__tests__/lod-visibility-lifecycle.integration.test.ts +132 -0
  345. package/src/__tests__/ltc-resources.unit.test.ts +32 -0
  346. package/src/__tests__/material-contract-inventory.unit.test.ts +41 -9
  347. package/src/__tests__/material-cooked-projection.unit.test.ts +19 -1
  348. package/src/__tests__/material-static-dynamic-route.unit.test.ts +29 -0
  349. package/src/__tests__/material-stride-surface.unit.test.ts +7 -3
  350. package/src/__tests__/materials-standard-contract.unit.test.ts +104 -0
  351. package/src/__tests__/mesh-buffer-usage-surface.unit.test.ts +1 -1
  352. package/src/__tests__/occlusion-confidence.unit.test.ts +168 -0
  353. package/src/__tests__/occlusion-invalidation.integration.test.ts +37 -0
  354. package/src/__tests__/occlusion-pass-routing.unit.test.ts +16 -0
  355. package/src/__tests__/occlusion-pass.dawn.test.ts +20 -0
  356. package/src/__tests__/occlusion-pass.integration.test.ts +43 -0
  357. package/src/__tests__/occlusion-pass.rhi-null.test.ts +22 -0
  358. package/src/__tests__/occlusion-query-pool.rhi-null.test.ts +21 -0
  359. package/src/__tests__/occlusion-query-pool.unit.test.ts +95 -0
  360. package/src/__tests__/occlusion-query-ticket.integration.test.ts +47 -0
  361. package/src/__tests__/occlusion-query.dawn.test.ts +16 -0
  362. package/src/__tests__/occlusion-query.evidence.ts +178 -0
  363. package/src/__tests__/occlusion-query.rhi-null.test.ts +31 -0
  364. package/src/__tests__/occlusion-runtime.unit.test.ts +120 -0
  365. package/src/__tests__/physical-clearcoat.integration.test.ts +89 -0
  366. package/src/__tests__/prepared-gpu-driven-pbr.integration.test.ts +196 -0
  367. package/src/__tests__/prepared-graphics-pipeline-warmup.unit.test.ts +1 -1
  368. package/src/__tests__/probe-bindings.unit.test.ts +200 -0
  369. package/src/__tests__/probe-blend-numeric.unit.test.ts +67 -0
  370. package/src/__tests__/probe-blend-oracle.unit.test.ts +136 -0
  371. package/src/__tests__/probe-capacity.integration.test.ts +97 -0
  372. package/src/__tests__/probe-recovery.integration.test.ts +104 -0
  373. package/src/__tests__/probe-scene-dirty.integration.test.ts +211 -0
  374. package/src/__tests__/profiler-phase-catalog.test.ts +4 -1
  375. package/src/__tests__/profiler-phase-token.test.ts +1 -0
  376. package/src/__tests__/recover-error-policy-owner.unit.test.ts +6 -3
  377. package/src/__tests__/recovery-candidate-prepare.contract.test.ts +140 -0
  378. package/src/__tests__/rect-area-boundary.unit.test.ts +24 -0
  379. package/src/__tests__/reflection-fallback-graph.unit.test.ts +67 -0
  380. package/src/__tests__/reflection-fallback-inspection.unit.test.ts +77 -0
  381. package/src/__tests__/reflection-fallback-lifecycle.integration.test.ts +21 -0
  382. package/src/__tests__/reflection-fallback-projection.unit.test.ts +90 -0
  383. package/src/__tests__/reflection-fallback-source.unit.test.ts +32 -0
  384. package/src/__tests__/reflection-probe-owner-placement.unit.test.ts +39 -0
  385. package/src/__tests__/reflection-probe-record-owner.characterization.unit.test.ts +120 -0
  386. package/src/__tests__/render-error-code-owner.test-d.ts +1 -0
  387. package/src/__tests__/render-feature-docs.unit.test.ts +12 -7
  388. package/src/__tests__/render-graph-fallback.integration.test.ts +24 -0
  389. package/src/__tests__/render-perf/extended-lighting.bench.test.ts +66 -0
  390. package/src/__tests__/render-scene-projection.unit.test.ts +447 -18
  391. package/src/__tests__/renderer-factory-material-contract.unit.test.ts +160 -17
  392. package/src/__tests__/renderer-frame-generation-fence.integration.test.ts +47 -0
  393. package/src/__tests__/renderer-generation-assembly.unit.test.ts +77 -0
  394. package/src/__tests__/renderer-generation-failure.unit.test.ts +108 -0
  395. package/src/__tests__/renderer-producer-root-matrix.integration.test.ts +33 -0
  396. package/src/__tests__/renderer-recovery-capability-matrix.unit.test.ts +59 -0
  397. package/src/__tests__/renderer-recovery-contract.unit.test.ts +73 -0
  398. package/src/__tests__/renderer-recovery-lifecycle.unit.test.ts +216 -0
  399. package/src/__tests__/renderer-target-history-recovery.integration.test.ts +80 -0
  400. package/src/__tests__/shader-manifest-fixture.ts +13 -16
  401. package/src/__tests__/shader-stage-surface.unit.test.ts +1 -1
  402. package/src/__tests__/skin-motion-regression.unit.test.ts +22 -0
  403. package/src/__tests__/skinned-shadow-caster.test.ts +2 -2
  404. package/src/__tests__/spot-modifier-projection.unit.test.ts +70 -0
  405. package/src/__tests__/ssr-admission-matrix.integration.test.ts +206 -0
  406. package/src/__tests__/ssr-composition.unit.test.ts +46 -0
  407. package/src/__tests__/ssr-fallback-zero-work.bench.test.ts +94 -0
  408. package/src/__tests__/ssr-recovery-errors.unit.test.ts +58 -0
  409. package/src/__tests__/standard-cluster-variant-resolution.unit.test.ts +67 -0
  410. package/src/__tests__/standard-clustered-pipeline.unit.test.ts +7 -0
  411. package/src/__tests__/standard-layer-projection.unit.test.ts +23 -0
  412. package/src/__tests__/standard-lighting-inspection.integration.test.ts +1 -1
  413. package/src/__tests__/standard-lighting-write.unit.test.ts +66 -2
  414. package/src/__tests__/temporal-fallback-generation.integration.test.ts +16 -0
  415. package/src/__tests__/temporal-view.unit.test.ts +3 -3
  416. package/src/__tests__/transmission-prewarm.unit.test.ts +111 -32
  417. package/src/__tests__/visibility-budget.integration.test.ts +140 -0
  418. package/src/__tests__/visibility-budget.unit.test.ts +70 -0
  419. package/src/__tests__/visibility-extract.integration.test.ts +11 -6
  420. package/src/__tests__/volumetric-fog-point-spot-accumulation.integration.test.ts +6 -3
  421. package/src/__tests__/volumetric-fog-stage-readback.dawn.test.ts +31 -28
  422. package/src/__tests__/volumetric-fog-topology.unit.test.ts +3 -1
  423. package/src/assembly/backend-contract.ts +13 -0
  424. package/src/assembly/bundler-contract.ts +9 -0
  425. package/src/assembly/device-feature-admission.ts +26 -9
  426. package/src/assembly/extended-lighting-fallback.ts +216 -0
  427. package/src/assembly/factory.ts +81 -9634
  428. package/src/assembly/host-contract.ts +10 -0
  429. package/src/assembly/material/assembly.ts +18 -2
  430. package/src/assembly/material/standard-layer-projection.ts +16 -0
  431. package/src/assembly/material-shader-policy.ts +854 -0
  432. package/src/assembly/material-shader-prewarm.ts +88 -0
  433. package/src/assembly/mesh-ssbo-grow.ts +270 -0
  434. package/src/assembly/recovery/device-loss-fanout.ts +89 -0
  435. package/src/assembly/recovery/generation.ts +141 -0
  436. package/src/assembly/recovery/recovery-attempt.ts +800 -0
  437. package/src/assembly/recovery/renderer-recover.ts +906 -0
  438. package/src/assembly/renderer-facade.ts +342 -0
  439. package/src/assembly/renderer-frame-transaction.ts +45 -0
  440. package/src/assembly/renderer-helpers.ts +99 -0
  441. package/src/assembly/renderer-lifecycle.ts +159 -0
  442. package/src/assembly/shader-prewarm-policy.ts +157 -0
  443. package/src/assembly/webgpu-ready-contract.ts +14 -0
  444. package/src/assembly/webgpu-ready.ts +4079 -0
  445. package/src/assembly/webgpu-renderer.ts +4095 -0
  446. package/src/cluster-binner.ts +31 -0
  447. package/src/components/__tests__/lighting-authoring.unit.test.ts +39 -0
  448. package/src/components/__tests__/rect-area-light.unit.test.ts +33 -0
  449. package/src/components/camera.ts +1 -1
  450. package/src/components/index.ts +3 -0
  451. package/src/components/light-helpers.ts +52 -1
  452. package/src/components/light-probe.ts +13 -0
  453. package/src/components/rect-area-light.ts +15 -0
  454. package/src/components/scene-instance.ts +6 -0
  455. package/src/components/spot-light.ts +3 -0
  456. package/src/debug-draw-glue.ts +1 -1
  457. package/src/device/__tests__/extended-lighting-recovery.unit.test.ts +24 -0
  458. package/src/device/device-scope.ts +35 -6
  459. package/src/device/gpu-residency.ts +334 -35
  460. package/src/device/lifecycle-transaction.ts +12 -0
  461. package/src/environment/frame.ts +6 -0
  462. package/src/environment/inspection.ts +10 -0
  463. package/src/environment/lifecycle.ts +122 -9
  464. package/src/errors/gpu-driven.ts +92 -0
  465. package/src/errors/index.ts +1 -0
  466. package/src/errors/recover.ts +108 -5
  467. package/src/errors/render.ts +72 -1
  468. package/src/extract/camera.ts +3 -3
  469. package/src/extract/contracts.ts +974 -0
  470. package/src/extract/gpu-driven.ts +108 -0
  471. package/src/extract/visibility.ts +0 -23
  472. package/src/features/host.ts +16 -0
  473. package/src/features/prepared-gpu-work.ts +20 -2
  474. package/src/features/prepared-graphics-store.ts +17 -0
  475. package/src/gpu-driven/batch-topology.ts +206 -28
  476. package/src/gpu-driven/prepared-draw.ts +208 -0
  477. package/src/gpu-driven/production-raster.ts +1046 -39
  478. package/src/gpu-driven/view-gpu.ts +411 -29
  479. package/src/gpu-scene-schema.ts +13 -0
  480. package/src/gpu-scene.ts +137 -67
  481. package/src/gpu-usage.ts +0 -1
  482. package/src/hdrp-buffers.ts +6 -6
  483. package/src/ibl/IblPipelineCache.ts +134 -50
  484. package/src/ibl/skylight-bind-group.ts +26 -0
  485. package/src/index.ts +83 -1
  486. package/src/inspection-types.ts +220 -1
  487. package/src/internal.ts +38 -0
  488. package/src/light-buffer-layout.ts +157 -324
  489. package/src/materials.ts +278 -47
  490. package/src/mesh-material-bindings.ts +59 -0
  491. package/src/occlusion-proxy.ts +64 -0
  492. package/src/pbr-pipeline.ts +290 -60
  493. package/src/pipeline/standard-forward-lane.ts +47 -0
  494. package/src/pipeline/standard-lighting/graph.ts +2 -1
  495. package/src/pipeline/standard-lighting/light-frame.ts +3 -2
  496. package/src/pipeline/standard-lighting/topology.ts +1 -1
  497. package/src/pipeline/standard-lighting/transport.ts +2 -1
  498. package/src/pipeline/standard-pipeline.ts +2 -0
  499. package/src/pipeline-builder.ts +5 -2
  500. package/src/pipeline-spec.ts +12 -1
  501. package/src/plugin.ts +4 -0
  502. package/src/points-lines/__tests__/cache-recovery.unit.test.ts +22 -0
  503. package/src/points-lines/prepare.ts +9 -0
  504. package/src/points-lines/standard-owner.ts +403 -0
  505. package/src/prepare/__tests__/extended-lighting-capability.unit.test.ts +56 -0
  506. package/src/prepare/extended-lighting/ltc-resources.ts +136 -0
  507. package/src/prepare/extended-lighting/resources.ts +389 -0
  508. package/src/prepare/extended-lighting/spot-modifiers.ts +194 -0
  509. package/src/prepare/extended-lighting/state.ts +92 -0
  510. package/src/record/__tests__/frame-targets-recovery.unit.test.ts +60 -0
  511. package/src/record/__tests__/gpu-timing.unit.test.ts +177 -0
  512. package/src/record/__tests__/main-pass-geometry.unit.test.ts +26 -1
  513. package/src/record/__tests__/mesh-ssbo.unit.test.ts +38 -0
  514. package/src/record/__tests__/recovery-pipeline.unit.test.ts +124 -0
  515. package/src/record/__tests__/sprite-material-abi.unit.test.ts +28 -13
  516. package/src/record/__tests__/standard-pbr-ubo-layout.unit.test.ts +47 -4
  517. package/src/record/__tests__/temporal-payload.unit.test.ts +29 -28
  518. package/src/record/__tests__/typed-frame-graph-plan.unit.test.ts +21 -2
  519. package/src/record/frame-lighting.ts +252 -53
  520. package/src/record/frame-snapshot.ts +72 -12
  521. package/src/record/frame-targets.ts +11 -1
  522. package/src/record/frame.ts +416 -95
  523. package/src/record/gpu-timing.ts +108 -44
  524. package/src/record/helpers.ts +1 -59
  525. package/src/record/main-pass-geometry.ts +434 -44
  526. package/src/record/main-pass-material.ts +324 -210
  527. package/src/record/main-pass-sprite-draws.ts +58 -92
  528. package/src/record/main-pass.ts +94 -6
  529. package/src/record/mesh-ssbo.ts +79 -46
  530. package/src/record/recovery-pipeline.ts +695 -0
  531. package/src/record/render-context.ts +99 -7
  532. package/src/record/shadow-pass.ts +66 -40
  533. package/src/record/typed-frame-graph.ts +270 -84
  534. package/src/record/view-ubo.ts +17 -27
  535. package/src/recovery/render-system-candidate.ts +839 -0
  536. package/src/reflection/inspection.ts +67 -0
  537. package/src/reflection/projection.ts +196 -1
  538. package/src/reflection/record-owner.ts +1283 -0
  539. package/src/render-contract.ts +82 -12
  540. package/src/render-pipeline.ts +5 -0
  541. package/src/render-system-extract-tail.ts +2983 -0
  542. package/src/render-system-extract.ts +290 -2784
  543. package/src/render-system.ts +1597 -1192
  544. package/src/scene/__tests__/changed-block-summary.candidate.test.ts +36 -0
  545. package/src/scene/probe-blend-record.ts +54 -0
  546. package/src/scene/probe-blend.ts +813 -0
  547. package/src/scene/render-scene-types.ts +0 -7
  548. package/src/scene/render-scene.ts +972 -184
  549. package/src/scene/visibility/bounds.ts +57 -0
  550. package/src/scene/visibility/budget.ts +40 -0
  551. package/src/scene/visibility/facet.ts +400 -0
  552. package/src/scene/visibility/gpu-frame-timing.ts +101 -0
  553. package/src/scene/visibility/gpu-lod.ts +119 -0
  554. package/src/scene/visibility/inspection.ts +313 -0
  555. package/src/scene/visibility/lod-selector.ts +84 -0
  556. package/src/scene/visibility/occlusion-confidence.ts +478 -0
  557. package/src/scene/visibility/occlusion-pass.ts +165 -0
  558. package/src/scene/visibility/occlusion-query-pool.ts +178 -0
  559. package/src/scene/visibility/occlusion-runtime.ts +672 -0
  560. package/src/scene/visibility/types.ts +50 -0
  561. package/src/ssr/admission.ts +451 -0
  562. package/src/ssr/composition.ts +73 -0
  563. package/src/ssr/errors.ts +76 -0
  564. package/src/ssr/identity.ts +12 -0
  565. package/src/systems/skin-palette-allocator.ts +1 -1
  566. package/src/temporal/frame-coordinator.ts +30 -1
  567. package/src/temporal/frame.ts +14 -1
  568. package/src/temporal/taa-history-store.ts +46 -2
  569. package/src/temporal/temporal-view.ts +1 -1
  570. package/src/temporal/transaction.ts +9 -0
  571. package/src/temporal/view.ts +11 -0
  572. package/src/tilemap-chunk-extract-system.ts +5 -5
  573. package/src/transmission/__tests__/standard-transmission.dawn.test.ts +19 -12
  574. package/src/typed-render-graph-primitives.ts +48 -0
  575. package/src/volume/passes.ts +46 -49
  576. package/dist/.tsbuildinfo +0 -1
  577. package/dist/chunk-5YADUCA7.mjs.map +0 -1
  578. package/dist/chunk-6OPXDQ2E.mjs.map +0 -1
  579. package/dist/chunk-BXBQQKFQ.mjs +0 -67
  580. package/dist/chunk-BXBQQKFQ.mjs.map +0 -1
  581. package/dist/chunk-FS2KNUQH.mjs.map +0 -1
  582. package/dist/chunk-PCHBPYML.mjs.map +0 -1
  583. package/dist/chunk-VFPLTKCU.mjs +0 -192
  584. package/dist/chunk-VFPLTKCU.mjs.map +0 -1
@@ -20,8 +20,8 @@ import {
20
20
  import {
21
21
  DEFAULT_MSDF_TEXT_PARAM_SCHEMA,
22
22
  DEFAULT_SPRITE_PARAM_SCHEMA,
23
- DEFAULT_STANDARD_PBR_PARAM_SCHEMA,
24
23
  DEFAULT_UNLIT_PARAM_SCHEMA,
24
+ STANDARD_PIPELINE_PARAM_SCHEMA,
25
25
  } from '@forgeax/engine-shader';
26
26
  import type {
27
27
  Handle,
@@ -38,10 +38,16 @@ import { VideoUploadUnsupportedError } from '../errors/render';
38
38
  import {
39
39
  assembleMaterialWithSkylightEntries,
40
40
  type SkylightBindGroupResources,
41
+ type TextureInjectionResource,
41
42
  type TransmissionBindGroupResources,
42
43
  } from '../ibl/skylight-bind-group';
44
+ import type { IblBindingEntryInspection } from '../mesh-material-bindings';
43
45
  import type { PipelineGroup2Contract } from '../pbr-pipeline';
44
- import { isStandardPbrMaterialShader } from '../pbr-pipeline';
46
+ import {
47
+ isCanonicalStandardPbrMaterialShader,
48
+ isStandardPbrMaterialShader,
49
+ physicalTextureFields,
50
+ } from '../pbr-pipeline';
45
51
  import { deriveTextureExtent } from '../render-data';
46
52
  import type { MaterialSnapshot } from '../render-system-extract';
47
53
  import type { RenderTargetTextureSource } from '../targets/contracts';
@@ -50,7 +56,12 @@ import {
50
56
  resolveRenderTargetMaterialSource,
51
57
  } from '../targets/material-source';
52
58
  import type { StandardReflectionProbeBinding } from './frame-lighting';
53
- import type { BindGroupCounts, MaterialBgAssemblyCacheEntry } from './frame-snapshot';
59
+ import type {
60
+ BindGroupCounts,
61
+ MaterialBgAssemblyCacheEntry,
62
+ RenderFrameState,
63
+ } from './frame-snapshot';
64
+ import { getOpaqueResourceIdentity } from './frame-snapshot';
54
65
  import { extractEntryResourceHandle, getOrCreatePerEntity } from './mesh-ssbo';
55
66
  import {
56
67
  type PipelineState,
@@ -232,6 +243,18 @@ function isImageError(error: unknown): error is ImageError {
232
243
  return typeof code === 'string' && code.startsWith('image-');
233
244
  }
234
245
 
246
+ function materialDiagnosticsEnabled(): boolean {
247
+ if (typeof globalThis !== 'object' || globalThis === null || !('process' in globalThis)) {
248
+ return false;
249
+ }
250
+ const processValue = (
251
+ globalThis as {
252
+ readonly process?: { readonly env?: Record<string, string | undefined> };
253
+ }
254
+ ).process;
255
+ return processValue?.env?.FORGEAX_MATERIAL_DIAGNOSTICS === '1';
256
+ }
257
+
235
258
  export function residentTextureView(
236
259
  world: World,
237
260
  store: GpuResidencyCache,
@@ -240,7 +263,15 @@ export function residentTextureView(
240
263
  worldId: World | number = world,
241
264
  ): TextureView | undefined {
242
265
  const podRes = resolveAssetHandle<TextureAsset>(world, handle);
243
- if (!podRes.ok) return undefined;
266
+ const diagnosticsEnabled = materialDiagnosticsEnabled();
267
+ if (!podRes.ok) {
268
+ if (diagnosticsEnabled) {
269
+ console.error(
270
+ `[render-material] texture resolve failed: ${JSON.stringify({ handle: handleSlot(handle), error: podRes.error })}`,
271
+ );
272
+ }
273
+ return undefined;
274
+ }
244
275
  const residentRes = store.ensureResident(handle, podRes.value, worldId);
245
276
  if (!residentRes.ok) {
246
277
  // Preserve the concrete asset-capability boundary on the renderer error
@@ -249,9 +280,41 @@ export function residentTextureView(
249
280
  if (residentRes.error instanceof RhiError || isImageError(residentRes.error)) {
250
281
  runtime.errorRegistry.fire(residentRes.error);
251
282
  }
283
+ if (diagnosticsEnabled) {
284
+ console.error(
285
+ `[render-material] texture residency failed: ${JSON.stringify({
286
+ handle: handleSlot(handle),
287
+ pod: {
288
+ kind: podRes.value.kind,
289
+ shape: podRes.value.shape,
290
+ format: podRes.value.format,
291
+ dataByteLength: podRes.value.data.byteLength,
292
+ mips: podRes.value.mips,
293
+ },
294
+ error: residentRes.error,
295
+ })}`,
296
+ );
297
+ }
252
298
  return undefined;
253
299
  }
254
- return store.getTextureGpuView(handle, worldId);
300
+ const view = store.getTextureGpuView(handle, worldId);
301
+ if (diagnosticsEnabled) {
302
+ console.error(
303
+ `[render-material] texture residency ready: ${JSON.stringify({
304
+ handle: handleSlot(handle),
305
+ pod: {
306
+ kind: podRes.value.kind,
307
+ shape: podRes.value.shape,
308
+ format: podRes.value.format,
309
+ dataByteLength: podRes.value.data.byteLength,
310
+ mips: podRes.value.mips,
311
+ },
312
+ receipt: 'receipt' in residentRes.value ? residentRes.value.receipt : undefined,
313
+ viewReady: view !== undefined,
314
+ })}`,
315
+ );
316
+ }
317
+ return view;
255
318
  }
256
319
 
257
320
  function residentSampler(
@@ -398,7 +461,6 @@ export const BUILTIN_USER_REGION_TEXTURE_FIELDS: readonly string[] = [
398
461
  'baseColorTexture',
399
462
  'metallicRoughnessTexture',
400
463
  'normalTexture',
401
- 'specularTintTexture',
402
464
  'emissiveTexture',
403
465
  'occlusionTexture',
404
466
  'transmissionTexture',
@@ -406,21 +468,6 @@ export const BUILTIN_USER_REGION_TEXTURE_FIELDS: readonly string[] = [
406
468
  ];
407
469
 
408
470
  const LEGACY_MATERIAL_TEXTURE_SCALE_OFFSET = 80;
409
- // The Standard schema owns five transmission numeric fields between
410
- // normalScale and the engine-owned coordinate records. Keep this fallback
411
- // aligned with derive(DEFAULT_STANDARD_PBR_PARAM_SCHEMA): the first record is
412
- // at byte 128, not the pre-transmission byte 96.
413
- const STANDARD_PBR_TEXTURE_SCALE_OFFSET = 128;
414
- const MATERIAL_TEXTURE_SCALE_FIELDS = [
415
- 'baseColorTexture',
416
- 'metallicRoughnessTexture',
417
- 'normalTexture',
418
- 'specularTintTexture',
419
- 'emissiveTexture',
420
- 'occlusionTexture',
421
- 'transmissionTexture',
422
- 'thicknessTexture',
423
- ] as const;
424
471
  const LEGACY_MATERIAL_TEXTURE_SCALE_FIELDS = [
425
472
  'baseColorTexture',
426
473
  'metallicRoughnessTexture',
@@ -428,19 +475,20 @@ const LEGACY_MATERIAL_TEXTURE_SCALE_FIELDS = [
428
475
  'emissiveTexture',
429
476
  'occlusionTexture',
430
477
  ] as const;
431
- const STANDARD_PBR_TEXTURE_COORDINATE_OFFSET = 128;
432
- const STANDARD_PBR_TEXTURE_COORDINATE_STRIDE = 8;
433
478
  const DEFAULT_LEGACY_TEXTURE_SCALES = new Float32Array([1, 1, 1, 1, 1, 1, 1, 1, 1, 1]);
434
- const DEFAULT_STANDARD_TEXTURE_COORDINATES = new Float32Array([
435
- 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0,
436
- 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1,
437
- ]);
438
479
 
439
480
  export interface StandardReflectionProbeMaterialProjection {
440
481
  readonly probeIndex: number | undefined;
441
482
  readonly fallbackToSkylight: boolean;
442
483
  }
443
484
 
485
+ /** Keeps the fallback output as a same-pass, linear-HDR material projection. */
486
+ export function standardPbrFallbackDemand(
487
+ projection: StandardReflectionProbeMaterialProjection,
488
+ ): boolean {
489
+ return projection.probeIndex !== undefined || projection.fallbackToSkylight;
490
+ }
491
+
444
492
  /** Projects the selected scene row into the Standard material lane. */
445
493
  export function projectReflectionProbeMaterialBinding(
446
494
  binding: StandardReflectionProbeBinding,
@@ -529,13 +577,10 @@ export function applyMaterialTextureUvScales(
529
577
  }
530
578
  return;
531
579
  }
532
- // Standard PBR has a schema-derived 128-byte authored region before the
533
- // engine-owned UV records. Sprite, sprite-lit, unlit, and text keep their
534
- // own legacy tail positions because their WGSL layouts differ.
535
- const textureScaleOffset = isStandardPbrMaterialShader(material.materialShaderId)
536
- ? STANDARD_PBR_TEXTURE_SCALE_OFFSET
537
- : LEGACY_MATERIAL_TEXTURE_SCALE_OFFSET;
538
- const isStandardPbr = textureScaleOffset === STANDARD_PBR_TEXTURE_SCALE_OFFSET;
580
+ // Unknown legacy material paths retain their historical packed scale tail.
581
+ // Every registered Standard/material-schema path returns above and is
582
+ // therefore fully schema-driven (including physical texture coordinates).
583
+ const textureScaleOffset = LEGACY_MATERIAL_TEXTURE_SCALE_OFFSET;
539
584
  const hasTextureMetadata =
540
585
  material.textureCoordinates !== undefined ||
541
586
  material.textureHandles !== undefined ||
@@ -546,15 +591,10 @@ export function applyMaterialTextureUvScales(
546
591
  material.emissiveTexture !== undefined ||
547
592
  material.occlusionTexture !== undefined;
548
593
  if (!hasTextureMetadata) {
549
- f32.set(
550
- isStandardPbr ? DEFAULT_STANDARD_TEXTURE_COORDINATES : DEFAULT_LEGACY_TEXTURE_SCALES,
551
- textureScaleOffset / 4,
552
- );
594
+ f32.set(DEFAULT_LEGACY_TEXTURE_SCALES, textureScaleOffset / 4);
553
595
  return;
554
596
  }
555
- const fields = isStandardPbr
556
- ? MATERIAL_TEXTURE_SCALE_FIELDS
557
- : LEGACY_MATERIAL_TEXTURE_SCALE_FIELDS;
597
+ const fields = LEGACY_MATERIAL_TEXTURE_SCALE_FIELDS;
558
598
  for (let index = 0; index < fields.length; index++) {
559
599
  const field = fields[index];
560
600
  if (field === undefined) continue;
@@ -563,39 +603,19 @@ export function applyMaterialTextureUvScales(
563
603
  handle === undefined ? undefined : resolveAssetHandle<TextureAsset>(world, handle);
564
604
  const texture = resolved?.ok === true ? resolved.value : undefined;
565
605
  const [u, v] = materialTextureUvScale(texture);
566
- if (isStandardPbr) {
567
- const coordinates = material.textureCoordinates?.get(field);
568
- const transform = coordinates?.transform;
569
- const offset =
570
- STANDARD_PBR_TEXTURE_COORDINATE_OFFSET / 4 + index * STANDARD_PBR_TEXTURE_COORDINATE_STRIDE;
571
- f32[offset] = transform?.offset?.[0] ?? 0;
572
- f32[offset + 1] = transform?.offset?.[1] ?? 0;
573
- f32[offset + 2] = transform?.scale?.[0] ?? 1;
574
- f32[offset + 3] = transform?.scale?.[1] ?? 1;
575
- f32[offset + 4] = coordinates?.set ?? 0;
576
- f32[offset + 5] = transform?.rotation ?? 0;
577
- f32[offset + 6] = u;
578
- f32[offset + 7] = v;
579
- } else {
580
- const offset = textureScaleOffset / 4 + index * 2;
581
- f32[offset] = u;
582
- f32[offset + 1] = v;
583
- }
606
+ const offset = textureScaleOffset / 4 + index * 2;
607
+ f32[offset] = u;
608
+ f32[offset + 1] = v;
584
609
  }
585
610
  }
586
611
 
587
612
  function materialCoordinateSchema(
588
613
  material: MaterialSnapshot,
589
614
  ): readonly ParamSchemaEntry[] | undefined {
590
- if (
591
- isStandardPbrMaterialShader(material.materialShaderId) &&
592
- material.paramSnapshot !== undefined
593
- ) {
594
- return DEFAULT_STANDARD_PBR_PARAM_SCHEMA;
595
- }
596
615
  if (material.materialParamSchema !== undefined && material.materialParamSchema.length > 0) {
597
616
  return material.materialParamSchema;
598
617
  }
618
+ if (isStandardPbrMaterialShader(material.materialShaderId)) return STANDARD_PIPELINE_PARAM_SCHEMA;
599
619
  switch (material.materialShaderId) {
600
620
  case 'forgeax::default-unlit':
601
621
  return DEFAULT_UNLIT_PARAM_SCHEMA;
@@ -645,6 +665,13 @@ export function defaultViewForUserRegionField(
645
665
  }
646
666
  if (field === 'normalTexture') return pipelineState.defaultNormalTextureView;
647
667
  if (field === 'baseColorTexture') return pipelineState.fallbackTextureView;
668
+ if (field === 'anisotropyTexture') {
669
+ // The flat-normal fallback encodes a zero tangent direction and unit B
670
+ // strength; the shader's zero-vector guard selects its neutral (1, 0)
671
+ // axis. This keeps an absent anisotropy map neutral even on hosts that do
672
+ // not allocate the optional dedicated identity texture.
673
+ return pipelineState.defaultAnisotropyTextureView ?? pipelineState.defaultNormalTextureView;
674
+ }
648
675
  return pipelineState.defaultWhiteTextureView;
649
676
  }
650
677
 
@@ -662,7 +689,7 @@ export function defaultViewForUserRegionField(
662
689
  * `renderableIndex` per RenderSystem lifetime via the `seenIndices` Set
663
690
  * (the same warn-once anchor pattern used for missing-texture sprites).
664
691
  *
665
- * @param transformWorld The entity's resolved Transform.world mat4 (16 floats column-major).
692
+ * @param transformWorld The entity's resolved GlobalTransform.world mat4 (16 floats column-major).
666
693
  * @param slices The four anchor distances `[left, top, right, bottom]`
667
694
  * (sentinel `bottom < 0` for tile mode is consumed via abs()).
668
695
  * @param renderableIndex The entity index into the validated renderables list.
@@ -691,17 +718,7 @@ export function detectNineSliceScaleTooSmall(
691
718
  }
692
719
  }
693
720
 
694
- /**
695
- * Build the 384-byte Material UBO payload for a PBR / unlit material entry
696
- * (feat-20260527-sprite-nineslice M2 / w11; D-7 regression-net helper).
697
- *
698
- * Byte-for-byte equivalent to the legacy hard-coded PBR write path; each
699
- * deviation is caught by `render-system-record-pbr-ubo-stable.test.ts`.
700
- * The schema-driven paramSnapshot overlay (feat-20260523 M9-T05; AC-14)
701
- * positions vec4 / f32 slots onto std140 [slot0 vec4, slot1 f32 metallic,
702
- * slot1 f32 roughness] mirroring the engine-shipped default-standard-pbr
703
- * Material struct.
704
- */
721
+ /** Build one max-sized, schema-driven Standard material UBO payload. */
705
722
  export function buildPbrMaterialUboPayload(material: MaterialSnapshot): Uint8Array {
706
723
  const buf = new Uint8Array(STANDARD_PBR_UBO_SIZE);
707
724
  writePbrMaterialUboPayload(buf, material);
@@ -711,11 +728,10 @@ export function buildPbrMaterialUboPayload(material: MaterialSnapshot): Uint8Arr
711
728
  /**
712
729
  * Write the standard material payload into caller-owned storage.
713
730
  *
714
- * The allocating wrapper above remains the byte-stable test/helper surface,
715
- * while the frame recorder reuses one scratch slot for all materials. A
716
- * scene with thousands of submeshes otherwise allocates one 384-byte
717
- * Uint8Array per material per frame, creating avoidable GC pressure without
718
- * changing GPU-visible bytes.
731
+ * The allocating wrapper above remains the helper/test surface, while the
732
+ * frame recorder reuses one scratch slot for all materials. Field offsets are
733
+ * read from the material's admitted schema; no Standard field offset table is
734
+ * maintained in the record stage.
719
735
  */
720
736
  export function writePbrMaterialUboPayload(
721
737
  buf: Uint8Array | Float32Array,
@@ -728,114 +744,52 @@ export function writePbrMaterialUboPayload(
728
744
  }
729
745
  buf.fill(0);
730
746
  const f32 = materialUboFloatView(buf);
731
- // Layout (issue-1: channelMap split per D-8) -- byte-equivalent to the
732
- // post-split sidecar paramSchema for default-standard-pbr (12 numeric
733
- // entries packed std140 into one merged UBO at binding(0)).
734
- // f32[0..3] baseColor (offset 0, vec4)
735
- // f32[4] metallic (offset 16)
736
- // f32[5] roughness (offset 20)
737
- // f32[6] metallicChannel (offset 24) -- glTF 2.0 default = B = 2
738
- // f32[7] roughnessChannel (offset 28) -- glTF 2.0 default = G = 1
739
- // f32[8] aoChannel (offset 32) -- glTF 2.0 default = R = 0
740
- // f32[9] extraChannel (offset 36) -- reserved = 0
741
- // (offset 40..47 implicit pad: vec3 align=16)
742
- // f32[12..14] emissive (offset 48, vec3)
743
- // f32[15] emissiveIntensity (offset 60)
744
- // f32[16] occlusionStrength (offset 64)
745
- // f32[17] alphaCutoff (offset 68)
746
- // f32[18] clearcoat (offset 72)
747
- // f32[19] clearcoatRoughness (offset 76)
748
- // f32[20..22] specularTint (offset 80, vec3)
749
- // f32[23] normalScale (offset 92)
750
- // f32[24] transmission (offset 96)
751
- // f32[25] ior (offset 100)
752
- // f32[26] thickness (offset 104)
753
- // f32[28..30] attenuationColor (offset 112, vec3)
754
- // f32[31] attenuationDistance(offset 124)
755
- // f32[32..95] engine-owned texture coordinate records (offset 128)
756
- f32[0] = material.baseColor[0] ?? 0;
757
- f32[1] = material.baseColor[1] ?? 0;
758
- f32[2] = material.baseColor[2] ?? 0;
759
- f32[3] = 1;
760
- f32[4] = material.metallic;
761
- f32[5] = material.roughness;
762
- // channelMap default = (B, G, R, _) per AGENTS.md + glTF 2.0
763
- // KHR_materials_pbrSpecularGlossiness ARM packing -- now 4 independent
764
- // f32 selectors; fragment casts each to u32 at the pick site.
765
- f32[6] = 2; // metallicChannel <- B
766
- f32[7] = 1; // roughnessChannel <- G
767
- f32[8] = 0; // aoChannel <- R
768
- f32[9] = 0; // extraChannel <- reserved
769
- // emissive vec3 + emissiveIntensity (offset 48..63)
770
- const em = material.emissive;
771
- f32[12] = em?.[0] ?? 0;
772
- f32[13] = em?.[1] ?? 0;
773
- f32[14] = em?.[2] ?? 0;
774
- f32[15] = material.emissiveIntensity ?? 0;
775
- // occlusionStrength (offset 64)
776
- f32[16] = material.occlusionStrength ?? 1;
777
- // alphaCutoff (offset 68).
778
- const alphaCutoff = material.paramSnapshot?.alphaCutoff;
779
- f32[17] = typeof alphaCutoff === 'number' ? alphaCutoff : 0;
780
- f32[18] = material.clearcoat ?? 0;
781
- f32[19] = material.clearcoatRoughness ?? 0.5;
782
- const specularTint = material.specularTint;
783
- f32[20] = specularTint?.[0] ?? 1;
784
- f32[21] = specularTint?.[1] ?? 1;
785
- f32[22] = specularTint?.[2] ?? 1;
786
- const snapshotNumber = (name: string, fallback: number): number => {
787
- const value = material.paramSnapshot?.[name];
788
- return typeof value === 'number' ? value : fallback;
789
- };
790
- const snapshotVec3 = (
791
- name: string,
792
- fallback: readonly [number, number, number],
793
- ): readonly [number, number, number] => {
794
- const value = material.paramSnapshot?.[name];
795
- return Array.isArray(value)
796
- ? [value[0] ?? fallback[0], value[1] ?? fallback[1], value[2] ?? fallback[2]]
797
- : fallback;
747
+ const schema =
748
+ material.materialParamSchema === undefined || material.materialParamSchema.length === 0
749
+ ? STANDARD_PIPELINE_PARAM_SCHEMA
750
+ : material.materialParamSchema;
751
+ const snapshot = material.paramSnapshot;
752
+ const fallback = (name: string, entry: (typeof schema)[number]): unknown => {
753
+ switch (name) {
754
+ case 'baseColor':
755
+ return [
756
+ material.baseColor[0] ?? 1,
757
+ material.baseColor[1] ?? 1,
758
+ material.baseColor[2] ?? 1,
759
+ 1,
760
+ ];
761
+ case 'metallic':
762
+ return material.metallic;
763
+ case 'roughness':
764
+ return material.roughness;
765
+ case 'specularColor':
766
+ return material.specularColor ?? [1, 1, 1];
767
+ case 'normalScale':
768
+ return material.normalScale ?? 1;
769
+ case 'emissive':
770
+ return material.emissive ?? [0, 0, 0];
771
+ case 'emissiveIntensity':
772
+ return material.emissiveIntensity ?? 0;
773
+ case 'occlusionStrength':
774
+ return material.occlusionStrength ?? 1;
775
+ default:
776
+ return entry.default;
777
+ }
798
778
  };
799
- f32[23] = material.normalScale ?? snapshotNumber('normalScale', 1);
800
- f32[24] = snapshotNumber('transmission', 0);
801
- f32[25] = snapshotNumber('ior', 1.5);
802
- f32[26] = snapshotNumber('thickness', 0);
803
- const attenuationColor = snapshotVec3('attenuationColor', [1, 1, 1]);
804
- f32[28] = attenuationColor[0];
805
- f32[29] = attenuationColor[1];
806
- f32[30] = attenuationColor[2];
807
- f32[31] = snapshotNumber('attenuationDistance', 0);
808
- // Schema-driven paramSnapshot overlay (feat-20260523 M9-T05, AC-14):
809
- // for user-shaders with a paramSnapshot, project the first vec4/color
810
- // entry onto slot 0 and the first two f32 entries onto slot 1's first
811
- // two floats. The default-standard-pbr path lands on the same offsets
812
- // via the explicit fields above, so the overlay is a no-op for the
813
- // engine's stock PBR shader (charter P4 + R-H regression fence).
814
- const paramSnap = material.paramSnapshot;
815
- if (paramSnap !== undefined) {
816
- const f32Snap = (name: string): number | undefined => {
817
- const v = paramSnap[name];
818
- return typeof v === 'number' ? v : undefined;
819
- };
820
- const colorSnap = (name: string): readonly number[] | undefined => {
821
- const v = paramSnap[name];
822
- return Array.isArray(v) && v.every((x) => typeof x === 'number')
823
- ? (v as readonly number[])
824
- : undefined;
825
- };
826
- // Walk schema in declared order, fill positional UBO slots.
827
- const materialShaderId = material.materialShaderId;
828
- // Note: schema lookup is the caller's job (it owns the runtime ref);
829
- // when the helper has no access to runtime, paramSnap is consumed
830
- // positionally without a schema walk. The inline caller (recordFrame)
831
- // still performs the schema-driven walk and writes the overlay before
832
- // queueing this payload, so this helper is only the byte-stable
833
- // baseline. To preserve byte-for-byte equivalence in the helper-only
834
- // unit-test path, the overlay pass is skipped when the helper is
835
- // called without an external schema reference.
836
- void materialShaderId;
837
- void f32Snap;
838
- void colorSnap;
779
+ for (const entry of derivedParamSchema(schema).uboLayout.entries) {
780
+ const value =
781
+ snapshot?.[entry.name] ??
782
+ fallback(entry.name, schema.find((item) => item.name === entry.name) ?? entry);
783
+ const offset = entry.offset / 4;
784
+ const width = entry.size / 4;
785
+ if (typeof value === 'number') {
786
+ f32[offset] = value;
787
+ } else if (Array.isArray(value)) {
788
+ for (let index = 0; index < Math.min(width, value.length); index += 1) {
789
+ const component = value[index];
790
+ if (typeof component === 'number') f32[offset + index] = component;
791
+ }
792
+ }
839
793
  }
840
794
  }
841
795
 
@@ -884,13 +838,7 @@ export function applyParamSnapshotToUbo(
884
838
  if (paramSchema === undefined) return;
885
839
  if (paramSnapshot === undefined) return;
886
840
  const f32 = materialUboFloatView(payload);
887
- const effectiveSchema =
888
- Object.hasOwn(paramSnapshot, 'transmission') &&
889
- Object.hasOwn(paramSnapshot, 'ior') &&
890
- Object.hasOwn(paramSnapshot, 'thickness')
891
- ? DEFAULT_STANDARD_PBR_PARAM_SCHEMA
892
- : paramSchema;
893
- const { uboLayout } = derivedParamSchema(effectiveSchema);
841
+ const { uboLayout } = derivedParamSchema(paramSchema);
894
842
  for (const entry of uboLayout.entries) {
895
843
  const value = paramSnapshot[entry.name];
896
844
  if (value === undefined) continue;
@@ -955,6 +903,46 @@ export interface PerSubmeshMaterialBgDeps {
955
903
  /** Active transmission backdrop; undefined/null means layout placeholder. */
956
904
  readonly transmissionBackdropView?: TextureView | null;
957
905
  readonly bindGroupCounts: BindGroupCounts;
906
+ readonly frameState: RenderFrameState;
907
+ }
908
+
909
+ function recordIblMaterialBinding(
910
+ frameState: RenderFrameState,
911
+ materialBgl: BindGroupLayout,
912
+ bindGroup: BindGroup,
913
+ entries: readonly BindGroupEntry[],
914
+ iblStart: number,
915
+ cache: 'hit' | 'miss',
916
+ ): void {
917
+ const receipt = frameState.iblBindingInspection;
918
+ if (receipt === undefined) return;
919
+ const skylightBindingStart = entries.findIndex((entry) => entry.binding === iblStart);
920
+ if (skylightBindingStart < 0) return;
921
+ const iblEntries = entries.slice(skylightBindingStart, skylightBindingStart + 7);
922
+ const projected: IblBindingEntryInspection[] = [];
923
+ for (const entry of iblEntries) {
924
+ if (entry.resource.kind === 'externalTexture') continue;
925
+ const value =
926
+ entry.resource.kind === 'buffer' ? entry.resource.value.buffer : entry.resource.value;
927
+ if (typeof value !== 'object' || value === null) continue;
928
+ projected.push({
929
+ binding: entry.binding,
930
+ kind: entry.resource.kind,
931
+ resourceIdentity: getOpaqueResourceIdentity(value as object),
932
+ });
933
+ }
934
+ frameState.iblBindingInspection = {
935
+ ...receipt,
936
+ material: {
937
+ bindGroupIdentity: getOpaqueResourceIdentity(bindGroup as object),
938
+ materialBglIdentity: getOpaqueResourceIdentity(materialBgl as object),
939
+ cache,
940
+ skylightBindingStart: iblEntries[0]?.binding ?? 0,
941
+ entries: projected,
942
+ reflectionBindings: projected.map((entry) => entry.binding),
943
+ },
944
+ errors: [],
945
+ };
958
946
  }
959
947
 
960
948
  /** @internal Exported only so buffer-generation invalidation stays regression-tested. */
@@ -1002,6 +990,7 @@ export function buildPerSubmeshMaterialBg(
1002
990
  materialBgAssemblyCache,
1003
991
  transmissionBackdropView,
1004
992
  bindGroupCounts,
993
+ frameState,
1005
994
  } = deps;
1006
995
  const effectiveSkylightResources =
1007
996
  resolveReflectionProbeResources?.(materialWorld, entityKey) ?? skylightResources;
@@ -1010,10 +999,31 @@ export function buildPerSubmeshMaterialBg(
1010
999
  smMaterialHandle === undefined ? undefined : `${materialWorld.identity}:${smMaterialHandle}`;
1011
1000
  const smHasVideoFields = (submeshMaterial.videoTextureFields?.size ?? 0) > 0;
1012
1001
  const smShaderId = submeshMaterial.materialShaderId;
1002
+ // The cooked shader schema owns the resource ABI. A material snapshot may
1003
+ // carry only the authored values (for example a clearcoat-only root omits
1004
+ // the standard emissive/occlusion maps), while the Standard template still
1005
+ // declares those slots and expects neutral fallbacks to be bound.
1006
+ const smSchema =
1007
+ (smShaderId !== undefined ? runtime.getParamSchema?.(smShaderId) : undefined) ??
1008
+ submeshMaterial.materialParamSchema;
1013
1009
  const smPerShaderBgl =
1014
- smShaderId !== undefined ? runtime.getMaterialBindGroupLayout?.(smShaderId) : undefined;
1010
+ smShaderId !== undefined
1011
+ ? runtime.getMaterialBindGroupLayout?.(smShaderId, smSchema)
1012
+ : undefined;
1015
1013
  const smMaterialBgl = smPerShaderBgl ?? pipelineState.materialBindGroupLayout;
1014
+ const standardMaterial = isStandardPbrMaterialShader(smShaderId);
1015
+ const physicalFields =
1016
+ standardMaterial && smSchema !== undefined ? physicalTextureFields(smSchema) : [];
1017
+ // Standard's built-in shader owns the canonical seven-pair user region even
1018
+ // when the authored root only declares a subset. Physical maps are appended
1019
+ // after the reserved IBL/transmission tail, so compacting this list would
1020
+ // shift the shader's IBL bindings and invalidate the render pipeline.
1021
+ const smUserRegionFields =
1022
+ smPerShaderBgl === undefined || isCanonicalStandardPbrMaterialShader(smShaderId)
1023
+ ? BUILTIN_USER_REGION_TEXTURE_FIELDS
1024
+ : userRegionTextureFieldOrder(smSchema).filter((field) => !physicalFields.includes(field));
1016
1025
  const hasActiveTransmissionBackdrop = transmissionBackdropView != null;
1026
+ const diagnosticsEnabled = materialDiagnosticsEnabled();
1017
1027
  if (smMaterialHandle !== undefined && !smHasVideoFields && !hasActiveTransmissionBackdrop) {
1018
1028
  const cached =
1019
1029
  materialCacheKey === undefined ? undefined : materialBgAssemblyCache.get(materialCacheKey);
@@ -1028,21 +1038,48 @@ export function buildPerSubmeshMaterialBg(
1028
1038
  transmissionBackdropView,
1029
1039
  )
1030
1040
  ) {
1041
+ if (diagnosticsEnabled) {
1042
+ recordIblMaterialBinding(
1043
+ frameState,
1044
+ smMaterialBgl,
1045
+ cached.bindGroup,
1046
+ [
1047
+ {
1048
+ binding: 1 + smUserRegionFields.length * 2,
1049
+ resource: { kind: 'textureView', value: skylightResources.irradianceView },
1050
+ },
1051
+ {
1052
+ binding: 2 + smUserRegionFields.length * 2,
1053
+ resource: { kind: 'sampler', value: skylightResources.irradianceSampler },
1054
+ },
1055
+ {
1056
+ binding: 3 + smUserRegionFields.length * 2,
1057
+ resource: { kind: 'textureView', value: skylightResources.prefilterView },
1058
+ },
1059
+ {
1060
+ binding: 4 + smUserRegionFields.length * 2,
1061
+ resource: { kind: 'sampler', value: skylightResources.prefilterSampler },
1062
+ },
1063
+ {
1064
+ binding: 5 + smUserRegionFields.length * 2,
1065
+ resource: { kind: 'textureView', value: skylightResources.brdfLutView },
1066
+ },
1067
+ {
1068
+ binding: 6 + smUserRegionFields.length * 2,
1069
+ resource: { kind: 'sampler', value: skylightResources.brdfLutSampler },
1070
+ },
1071
+ {
1072
+ binding: 7 + smUserRegionFields.length * 2,
1073
+ resource: { kind: 'buffer', value: { buffer: skylightResources.intensityBuffer } },
1074
+ },
1075
+ ],
1076
+ 1 + smUserRegionFields.length * 2,
1077
+ 'hit',
1078
+ );
1079
+ }
1031
1080
  return cached.bindGroup;
1032
1081
  }
1033
1082
  }
1034
- const smSchema =
1035
- submeshMaterial.materialParamSchema ??
1036
- (smShaderId !== undefined ? runtime.getParamSchema?.(smShaderId) : undefined);
1037
- // The shipped PBR shaders always own the eight texture pairs declared by the
1038
- // standard shader artifact. The runtime PBR BGL exposes those pairs directly;
1039
- // emissive and occlusion are not duplicated in an engine injection region.
1040
- // Keep the record projection on the shader-owned layout so IBL starts at
1041
- // binding 17 rather than colliding with transmissionSampler.
1042
- const smUserRegionFields =
1043
- smPerShaderBgl === undefined || isStandardPbrMaterialShader(smShaderId)
1044
- ? BUILTIN_USER_REGION_TEXTURE_FIELDS
1045
- : userRegionTextureFieldOrder(smSchema);
1046
1083
  let materialResourcesResident = true;
1047
1084
  const smSamplerForField = (field: string | undefined): Sampler => {
1048
1085
  const handle = field === undefined ? undefined : submeshMaterial.samplerHandles?.get(field);
@@ -1125,6 +1162,38 @@ export function buildPerSubmeshMaterialBg(
1125
1162
  },
1126
1163
  );
1127
1164
  }
1165
+ // Physical Standard maps are deliberately appended after the stable IBL /
1166
+ // transmission tail. Keep their resource projection separate from the
1167
+ // user-region loop so the slot order is the same as
1168
+ // `appendTextureInjection(afterTransmission, physicalFields)` in the
1169
+ // layout owner.
1170
+ const physicalTextureInjections: TextureInjectionResource[] = [];
1171
+ for (let slot = 0; slot < physicalFields.length; slot += 1) {
1172
+ const field = physicalFields[slot];
1173
+ if (field === undefined) continue;
1174
+ let view = defaultViewForUserRegionField(field, pipelineState, smSchema);
1175
+ const targetSource = materialRenderTargetSourceForField(
1176
+ submeshMaterial,
1177
+ field,
1178
+ resolveRenderTargetTextureSource,
1179
+ );
1180
+ if (targetSource !== undefined) {
1181
+ if (targetSource.textureView !== undefined) view = targetSource.textureView;
1182
+ else materialResourcesResident = false;
1183
+ } else {
1184
+ const handle = submeshMaterial.textureHandles?.get(field);
1185
+ if (handle !== undefined) {
1186
+ const resident = residentTextureView(materialWorld, store, runtime, handle);
1187
+ if (resident !== undefined) view = resident;
1188
+ else materialResourcesResident = false;
1189
+ }
1190
+ }
1191
+ physicalTextureInjections.push({
1192
+ slot,
1193
+ sampler: smSamplerForField(field),
1194
+ view,
1195
+ });
1196
+ }
1128
1197
  // The backdrop is a full mip-chain view owned by the pipeline. Reuse the
1129
1198
  // pipeline's existing filtering/clamp sampler for screen-space sampling;
1130
1199
  // the material default sampler intentionally keeps repeat addressing for
@@ -1140,6 +1209,7 @@ export function buildPerSubmeshMaterialBg(
1140
1209
  sampler: transmissionSampler,
1141
1210
  ...(transmissionBackdropView === undefined ? {} : { backdropView: transmissionBackdropView }),
1142
1211
  } satisfies TransmissionBindGroupResources,
1212
+ physicalTextureInjections,
1143
1213
  );
1144
1214
  const createBindGroup = (): BindGroup => {
1145
1215
  const result = runtime.device.createBindGroup({
@@ -1150,16 +1220,60 @@ export function buildPerSubmeshMaterialBg(
1150
1220
  if (!result.ok) throw result.error;
1151
1221
  return result.value;
1152
1222
  };
1223
+ const materialCacheHandles = smMergedEntries.map((entry) => extractEntryResourceHandle(entry));
1224
+ if (
1225
+ diagnosticsEnabled &&
1226
+ submeshMaterial.textureHandles === undefined &&
1227
+ smMaterialHandle !== undefined
1228
+ ) {
1229
+ console.error(
1230
+ `[render-material] scalar material cache handles: ${JSON.stringify({
1231
+ entityKey,
1232
+ materialHandle: smMaterialHandle,
1233
+ shader: smShaderId,
1234
+ paramSnapshot: submeshMaterial.paramSnapshot,
1235
+ handleTypes: materialCacheHandles.map((handle) => typeof handle),
1236
+ })}`,
1237
+ );
1238
+ }
1239
+ if (
1240
+ diagnosticsEnabled &&
1241
+ materialCacheHandles.some((handle) => typeof handle !== 'object' || handle === null)
1242
+ ) {
1243
+ console.error(
1244
+ `[render-material] invalid material cache handle: ${JSON.stringify({
1245
+ entityKey,
1246
+ materialHandle: smMaterialHandle,
1247
+ shader: smShaderId,
1248
+ entries: smMergedEntries.map((entry, index) => ({
1249
+ index,
1250
+ binding: entry.binding,
1251
+ kind: entry.resource.kind,
1252
+ handleType: typeof materialCacheHandles[index],
1253
+ })),
1254
+ })}`,
1255
+ );
1256
+ }
1153
1257
  const smBindGroup = hasActiveTransmissionBackdrop
1154
1258
  ? createBindGroup()
1155
1259
  : getOrCreatePerEntity(
1156
1260
  materialBgShared,
1157
1261
  smShaderId ?? '',
1158
- smMergedEntries.map((e) => extractEntryResourceHandle(e)),
1262
+ materialCacheHandles,
1159
1263
  'material-shared',
1160
1264
  createBindGroup,
1161
1265
  bindGroupCounts,
1162
1266
  );
1267
+ if (diagnosticsEnabled) {
1268
+ recordIblMaterialBinding(
1269
+ frameState,
1270
+ smMaterialBgl,
1271
+ smBindGroup,
1272
+ smMergedEntries,
1273
+ smBaseEntries.length,
1274
+ 'miss',
1275
+ );
1276
+ }
1163
1277
  if (
1164
1278
  smMaterialHandle !== undefined &&
1165
1279
  !smHasVideoFields &&