@forgeax/engine-render 0.1.23 → 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 (271) hide show
  1. package/README.md +131 -9
  2. package/dist/assembly/bundler-contract.d.ts +9 -0
  3. package/dist/assembly/bundler-contract.d.ts.map +1 -0
  4. package/dist/assembly/device-feature-admission.d.ts +8 -0
  5. package/dist/assembly/device-feature-admission.d.ts.map +1 -1
  6. package/dist/assembly/extended-lighting-fallback.d.ts +13 -0
  7. package/dist/assembly/extended-lighting-fallback.d.ts.map +1 -0
  8. package/dist/assembly/factory.d.ts +7 -243
  9. package/dist/assembly/factory.d.ts.map +1 -1
  10. package/dist/assembly/material/assembly.d.ts +3 -2
  11. package/dist/assembly/material/assembly.d.ts.map +1 -1
  12. package/dist/assembly/material/standard-layer-projection.d.ts +10 -0
  13. package/dist/assembly/material/standard-layer-projection.d.ts.map +1 -0
  14. package/dist/assembly/material-shader-policy.d.ts +168 -0
  15. package/dist/assembly/material-shader-policy.d.ts.map +1 -0
  16. package/dist/assembly/material-shader-prewarm.d.ts +21 -0
  17. package/dist/assembly/material-shader-prewarm.d.ts.map +1 -0
  18. package/dist/assembly/mesh-ssbo-grow.d.ts +88 -0
  19. package/dist/assembly/mesh-ssbo-grow.d.ts.map +1 -0
  20. package/dist/assembly/recovery/device-loss-fanout.d.ts +14 -0
  21. package/dist/assembly/recovery/device-loss-fanout.d.ts.map +1 -0
  22. package/dist/assembly/recovery/generation.d.ts +46 -0
  23. package/dist/assembly/recovery/generation.d.ts.map +1 -0
  24. package/dist/assembly/recovery/recovery-attempt.d.ts +63 -0
  25. package/dist/assembly/recovery/recovery-attempt.d.ts.map +1 -0
  26. package/dist/assembly/recovery/renderer-recover.d.ts +115 -0
  27. package/dist/assembly/recovery/renderer-recover.d.ts.map +1 -0
  28. package/dist/assembly/renderer-facade.d.ts +10 -0
  29. package/dist/assembly/renderer-facade.d.ts.map +1 -0
  30. package/dist/assembly/renderer-frame-transaction.d.ts +17 -0
  31. package/dist/assembly/renderer-frame-transaction.d.ts.map +1 -1
  32. package/dist/assembly/renderer-helpers.d.ts +19 -0
  33. package/dist/assembly/renderer-helpers.d.ts.map +1 -0
  34. package/dist/assembly/renderer-lifecycle.d.ts +34 -0
  35. package/dist/assembly/renderer-lifecycle.d.ts.map +1 -1
  36. package/dist/assembly/shader-prewarm-policy.d.ts +15 -0
  37. package/dist/assembly/shader-prewarm-policy.d.ts.map +1 -0
  38. package/dist/assembly/webgpu-ready-contract.d.ts +9 -0
  39. package/dist/assembly/webgpu-ready-contract.d.ts.map +1 -0
  40. package/dist/assembly/webgpu-ready.d.ts +55 -0
  41. package/dist/assembly/webgpu-ready.d.ts.map +1 -0
  42. package/dist/assembly/webgpu-renderer.d.ts +152 -0
  43. package/dist/assembly/webgpu-renderer.d.ts.map +1 -0
  44. package/dist/authoring.mjs +1 -1
  45. package/dist/{chunk-4L3P6DT5.mjs → chunk-GE3SDD72.mjs} +37 -5
  46. package/dist/chunk-GE3SDD72.mjs.map +1 -0
  47. package/dist/{chunk-HZUPSYW7.mjs → chunk-KZFTK6DO.mjs} +102 -15
  48. package/dist/chunk-KZFTK6DO.mjs.map +1 -0
  49. package/dist/{chunk-FF7K3FSO.mjs → chunk-Q752SHI5.mjs} +89 -89
  50. package/dist/{chunk-FF7K3FSO.mjs.map → chunk-Q752SHI5.mjs.map} +1 -1
  51. package/dist/{chunk-QODA5UKU.mjs → chunk-RR5L4SRS.mjs} +2 -2
  52. package/dist/chunk-RR5L4SRS.mjs.map +1 -0
  53. package/dist/{chunk-YALPS44A.mjs → chunk-TFH5I7JJ.mjs} +47 -47
  54. package/dist/chunk-TFH5I7JJ.mjs.map +1 -0
  55. package/dist/{chunk-FBC4R44J.mjs → chunk-UQYS7FRM.mjs} +57 -57
  56. package/dist/chunk-UQYS7FRM.mjs.map +1 -0
  57. package/dist/{chunk-4NPUASZR.mjs → chunk-YKB5DQV7.mjs} +161 -19
  58. package/dist/chunk-YKB5DQV7.mjs.map +1 -0
  59. package/dist/{chunk-ZUWKAZ2I.mjs → chunk-YRUYGA4A.mjs} +615 -237
  60. package/dist/chunk-YRUYGA4A.mjs.map +1 -0
  61. package/dist/{chunk-DIYW3M5D.mjs → chunk-Z3YYH2MB.mjs} +2210 -2213
  62. package/dist/chunk-Z3YYH2MB.mjs.map +1 -0
  63. package/dist/construct-renderer.mjs +19841 -16190
  64. package/dist/construct-renderer.mjs.map +1 -1
  65. package/dist/device/device-scope.d.ts +2 -0
  66. package/dist/device/device-scope.d.ts.map +1 -1
  67. package/dist/device/gpu-residency.d.ts +24 -4
  68. package/dist/device/gpu-residency.d.ts.map +1 -1
  69. package/dist/environment/frame.d.ts +5 -0
  70. package/dist/environment/frame.d.ts.map +1 -1
  71. package/dist/environment/inspection.d.ts +7 -0
  72. package/dist/environment/inspection.d.ts.map +1 -1
  73. package/dist/environment/lifecycle.d.ts +20 -2
  74. package/dist/environment/lifecycle.d.ts.map +1 -1
  75. package/dist/errors/recover.d.ts +58 -2
  76. package/dist/errors/recover.d.ts.map +1 -1
  77. package/dist/errors/render.d.ts +13 -0
  78. package/dist/errors/render.d.ts.map +1 -1
  79. package/dist/extract/contracts.d.ts +899 -0
  80. package/dist/extract/contracts.d.ts.map +1 -0
  81. package/dist/features/host.d.ts +2 -0
  82. package/dist/features/host.d.ts.map +1 -1
  83. package/dist/features/prepared-gpu-work.d.ts +3 -0
  84. package/dist/features/prepared-gpu-work.d.ts.map +1 -1
  85. package/dist/features/prepared-graphics-store.d.ts +2 -0
  86. package/dist/features/prepared-graphics-store.d.ts.map +1 -1
  87. package/dist/gpu-driven/production-raster.d.ts +28 -0
  88. package/dist/gpu-driven/production-raster.d.ts.map +1 -1
  89. package/dist/gpu-scene.d.ts +1 -0
  90. package/dist/gpu-scene.d.ts.map +1 -1
  91. package/dist/ibl/IblPipelineCache.d.ts +7 -6
  92. package/dist/ibl/IblPipelineCache.d.ts.map +1 -1
  93. package/dist/ibl/skylight-bind-group.d.ts +7 -1
  94. package/dist/ibl/skylight-bind-group.d.ts.map +1 -1
  95. package/dist/index.d.ts +1 -1
  96. package/dist/index.d.ts.map +1 -1
  97. package/dist/index.mjs +6 -6
  98. package/dist/internal.mjs +6 -6
  99. package/dist/light-buffer-layout.d.ts +3 -0
  100. package/dist/light-buffer-layout.d.ts.map +1 -1
  101. package/dist/materials.d.ts +34 -10
  102. package/dist/materials.d.ts.map +1 -1
  103. package/dist/mesh-material-bindings.d.ts +56 -0
  104. package/dist/mesh-material-bindings.d.ts.map +1 -1
  105. package/dist/occlusion-proxy.d.ts +4 -0
  106. package/dist/occlusion-proxy.d.ts.map +1 -0
  107. package/dist/pbr-pipeline.d.ts +32 -9
  108. package/dist/pbr-pipeline.d.ts.map +1 -1
  109. package/dist/points-lines/prepare.d.ts +3 -0
  110. package/dist/points-lines/prepare.d.ts.map +1 -1
  111. package/dist/points-lines/standard-owner.d.ts +33 -0
  112. package/dist/points-lines/standard-owner.d.ts.map +1 -0
  113. package/dist/record/frame-snapshot.d.ts +5 -0
  114. package/dist/record/frame-snapshot.d.ts.map +1 -1
  115. package/dist/record/frame-targets.d.ts.map +1 -1
  116. package/dist/record/frame.d.ts +14 -2
  117. package/dist/record/frame.d.ts.map +1 -1
  118. package/dist/record/main-pass-geometry.d.ts +14 -1
  119. package/dist/record/main-pass-geometry.d.ts.map +1 -1
  120. package/dist/record/main-pass-material.d.ts +7 -17
  121. package/dist/record/main-pass-material.d.ts.map +1 -1
  122. package/dist/record/main-pass.d.ts.map +1 -1
  123. package/dist/record/mesh-ssbo.d.ts +11 -3
  124. package/dist/record/mesh-ssbo.d.ts.map +1 -1
  125. package/dist/record/recovery-pipeline.d.ts +30 -0
  126. package/dist/record/recovery-pipeline.d.ts.map +1 -0
  127. package/dist/record/render-context.d.ts +26 -4
  128. package/dist/record/render-context.d.ts.map +1 -1
  129. package/dist/record/typed-frame-graph.d.ts +1 -1
  130. package/dist/record/typed-frame-graph.d.ts.map +1 -1
  131. package/dist/recovery/render-system-candidate.d.ts +65 -0
  132. package/dist/recovery/render-system-candidate.d.ts.map +1 -0
  133. package/dist/render-contract.d.ts +64 -7
  134. package/dist/render-contract.d.ts.map +1 -1
  135. package/dist/render-system-extract-tail.d.ts +12 -0
  136. package/dist/render-system-extract-tail.d.ts.map +1 -0
  137. package/dist/render-system-extract.d.ts +156 -22
  138. package/dist/render-system-extract.d.ts.map +1 -1
  139. package/dist/render-system.d.ts +128 -4
  140. package/dist/render-system.d.ts.map +1 -1
  141. package/dist/scene/render-scene.d.ts +19 -0
  142. package/dist/scene/render-scene.d.ts.map +1 -1
  143. package/dist/temporal/frame-coordinator.d.ts.map +1 -1
  144. package/dist/temporal/index.mjs +6 -6
  145. package/dist/temporal/taa-history-store.d.ts +15 -1
  146. package/dist/temporal/taa-history-store.d.ts.map +1 -1
  147. package/dist/temporal/transaction.d.ts +5 -0
  148. package/dist/temporal/transaction.d.ts.map +1 -1
  149. package/package.json +20 -20
  150. package/src/__tests__/device-feature-admission.unit.test.ts +6 -0
  151. package/src/__tests__/device-loss-fanout.unit.test.ts +107 -0
  152. package/src/__tests__/device-scope-lifecycle.unit.test.ts +21 -0
  153. package/src/__tests__/environment-generation.integration.test.ts +23 -0
  154. package/src/__tests__/expose-renderer-errors.unit.test.ts +34 -0
  155. package/src/__tests__/factory-contract.integration.test.ts +122 -7
  156. package/src/__tests__/fallback-row-stride-surface.unit.test.ts +4 -1
  157. package/src/__tests__/ibl/face-uniforms.unit.test.ts +2 -0
  158. package/src/__tests__/ibl-residency-publication.unit.test.ts +113 -0
  159. package/src/__tests__/material-contract-inventory.unit.test.ts +24 -9
  160. package/src/__tests__/material-cooked-projection.unit.test.ts +19 -1
  161. package/src/__tests__/material-static-dynamic-route.unit.test.ts +29 -0
  162. package/src/__tests__/material-stride-surface.unit.test.ts +7 -3
  163. package/src/__tests__/materials-standard-contract.unit.test.ts +104 -0
  164. package/src/__tests__/mesh-buffer-usage-surface.unit.test.ts +1 -1
  165. package/src/__tests__/physical-clearcoat.integration.test.ts +89 -0
  166. package/src/__tests__/prepared-gpu-driven-pbr.integration.test.ts +1 -1
  167. package/src/__tests__/prepared-graphics-pipeline-warmup.unit.test.ts +1 -1
  168. package/src/__tests__/recover-error-policy-owner.unit.test.ts +6 -3
  169. package/src/__tests__/recovery-candidate-prepare.contract.test.ts +140 -0
  170. package/src/__tests__/renderer-factory-material-contract.unit.test.ts +85 -18
  171. package/src/__tests__/renderer-frame-generation-fence.integration.test.ts +47 -0
  172. package/src/__tests__/renderer-generation-assembly.unit.test.ts +77 -0
  173. package/src/__tests__/renderer-generation-failure.unit.test.ts +108 -0
  174. package/src/__tests__/renderer-producer-root-matrix.integration.test.ts +33 -0
  175. package/src/__tests__/renderer-recovery-capability-matrix.unit.test.ts +59 -0
  176. package/src/__tests__/renderer-recovery-contract.unit.test.ts +73 -0
  177. package/src/__tests__/renderer-recovery-lifecycle.unit.test.ts +216 -0
  178. package/src/__tests__/renderer-target-history-recovery.integration.test.ts +80 -0
  179. package/src/__tests__/shader-manifest-fixture.ts +13 -16
  180. package/src/__tests__/shader-stage-surface.unit.test.ts +1 -1
  181. package/src/__tests__/standard-layer-projection.unit.test.ts +23 -0
  182. package/src/__tests__/transmission-prewarm.unit.test.ts +95 -85
  183. package/src/__tests__/volumetric-fog-topology.unit.test.ts +3 -1
  184. package/src/assembly/bundler-contract.ts +9 -0
  185. package/src/assembly/device-feature-admission.ts +15 -5
  186. package/src/assembly/extended-lighting-fallback.ts +216 -0
  187. package/src/assembly/factory.ts +81 -10088
  188. package/src/assembly/material/assembly.ts +18 -2
  189. package/src/assembly/material/standard-layer-projection.ts +16 -0
  190. package/src/assembly/material-shader-policy.ts +854 -0
  191. package/src/assembly/material-shader-prewarm.ts +88 -0
  192. package/src/assembly/mesh-ssbo-grow.ts +270 -0
  193. package/src/assembly/recovery/device-loss-fanout.ts +89 -0
  194. package/src/assembly/recovery/generation.ts +141 -0
  195. package/src/assembly/recovery/recovery-attempt.ts +800 -0
  196. package/src/assembly/recovery/renderer-recover.ts +906 -0
  197. package/src/assembly/renderer-facade.ts +342 -0
  198. package/src/assembly/renderer-frame-transaction.ts +45 -0
  199. package/src/assembly/renderer-helpers.ts +99 -0
  200. package/src/assembly/renderer-lifecycle.ts +159 -0
  201. package/src/assembly/shader-prewarm-policy.ts +157 -0
  202. package/src/assembly/webgpu-ready-contract.ts +14 -0
  203. package/src/assembly/webgpu-ready.ts +4079 -0
  204. package/src/assembly/webgpu-renderer.ts +4095 -0
  205. package/src/components/camera.ts +1 -1
  206. package/src/debug-draw-glue.ts +1 -1
  207. package/src/device/device-scope.ts +35 -6
  208. package/src/device/gpu-residency.ts +194 -26
  209. package/src/environment/frame.ts +6 -0
  210. package/src/environment/inspection.ts +10 -0
  211. package/src/environment/lifecycle.ts +114 -8
  212. package/src/errors/recover.ts +76 -5
  213. package/src/errors/render.ts +17 -0
  214. package/src/extract/contracts.ts +974 -0
  215. package/src/features/host.ts +16 -0
  216. package/src/features/prepared-gpu-work.ts +20 -2
  217. package/src/features/prepared-graphics-store.ts +17 -0
  218. package/src/gpu-driven/production-raster.ts +97 -2
  219. package/src/gpu-scene.ts +3 -2
  220. package/src/ibl/IblPipelineCache.ts +134 -50
  221. package/src/ibl/skylight-bind-group.ts +26 -0
  222. package/src/index.ts +9 -1
  223. package/src/light-buffer-layout.ts +3 -0
  224. package/src/materials.ts +262 -45
  225. package/src/mesh-material-bindings.ts +59 -0
  226. package/src/occlusion-proxy.ts +64 -0
  227. package/src/pbr-pipeline.ts +139 -15
  228. package/src/points-lines/__tests__/cache-recovery.unit.test.ts +22 -0
  229. package/src/points-lines/prepare.ts +9 -0
  230. package/src/points-lines/standard-owner.ts +403 -0
  231. package/src/record/__tests__/frame-targets-recovery.unit.test.ts +60 -0
  232. package/src/record/__tests__/main-pass-geometry.unit.test.ts +26 -1
  233. package/src/record/__tests__/mesh-ssbo.unit.test.ts +38 -0
  234. package/src/record/__tests__/recovery-pipeline.unit.test.ts +124 -0
  235. package/src/record/__tests__/sprite-material-abi.unit.test.ts +28 -13
  236. package/src/record/__tests__/standard-pbr-ubo-layout.unit.test.ts +47 -4
  237. package/src/record/__tests__/temporal-payload.unit.test.ts +22 -43
  238. package/src/record/__tests__/typed-frame-graph-plan.unit.test.ts +1 -1
  239. package/src/record/frame-snapshot.ts +15 -0
  240. package/src/record/frame-targets.ts +11 -1
  241. package/src/record/frame.ts +6 -2
  242. package/src/record/helpers.ts +1 -1
  243. package/src/record/main-pass-geometry.ts +230 -5
  244. package/src/record/main-pass-material.ts +316 -209
  245. package/src/record/main-pass-sprite-draws.ts +6 -7
  246. package/src/record/main-pass.ts +94 -6
  247. package/src/record/mesh-ssbo.ts +32 -10
  248. package/src/record/recovery-pipeline.ts +695 -0
  249. package/src/record/render-context.ts +35 -4
  250. package/src/record/typed-frame-graph.ts +45 -1
  251. package/src/record/view-ubo.ts +3 -3
  252. package/src/recovery/render-system-candidate.ts +839 -0
  253. package/src/render-contract.ts +65 -7
  254. package/src/render-system-extract-tail.ts +2983 -0
  255. package/src/render-system-extract.ts +124 -2977
  256. package/src/render-system.ts +868 -659
  257. package/src/scene/render-scene.ts +124 -1
  258. package/src/temporal/frame-coordinator.ts +4 -1
  259. package/src/temporal/taa-history-store.ts +46 -2
  260. package/src/temporal/temporal-view.ts +1 -1
  261. package/src/temporal/transaction.ts +9 -0
  262. package/src/tilemap-chunk-extract-system.ts +1 -1
  263. package/src/transmission/__tests__/standard-transmission.dawn.test.ts +19 -12
  264. package/dist/chunk-4L3P6DT5.mjs.map +0 -1
  265. package/dist/chunk-4NPUASZR.mjs.map +0 -1
  266. package/dist/chunk-DIYW3M5D.mjs.map +0 -1
  267. package/dist/chunk-FBC4R44J.mjs.map +0 -1
  268. package/dist/chunk-HZUPSYW7.mjs.map +0 -1
  269. package/dist/chunk-QODA5UKU.mjs.map +0 -1
  270. package/dist/chunk-YALPS44A.mjs.map +0 -1
  271. package/dist/chunk-ZUWKAZ2I.mjs.map +0 -1
@@ -10,7 +10,7 @@ import {
10
10
  RhiError,
11
11
  type RhiRenderPassEncoder,
12
12
  } from '@forgeax/engine-rhi';
13
- import type { PassKind, PrimitiveTopology } from '@forgeax/engine-types';
13
+ import type { ParamSchemaEntry, PassKind, PrimitiveTopology } from '@forgeax/engine-types';
14
14
  import { GpuBuffer } from '../gpu-resource';
15
15
  import {
16
16
  GPU_BUFFER_USAGE_COPY_DST,
@@ -22,8 +22,12 @@ import type { InstanceBufferCacheEntry } from '../instance-buffer-cache';
22
22
  import { SPRITE_PREMULTIPLIED_ALPHA_BLEND } from '../materials';
23
23
  import {
24
24
  createPbrSkinMeshBindGroupEntries,
25
+ isCanonicalStandardPbrMaterialShader,
25
26
  isStandardPbrMaterialShader,
27
+ isStandardPbrSkinMaterialShader,
28
+ materialBindGroupLayoutIdentity,
26
29
  pbrSkinMeshDynamicOffsets,
30
+ physicalTextureFields,
27
31
  SKIN_MATERIAL_SHADER_ID,
28
32
  } from '../pbr-pipeline';
29
33
  import {
@@ -41,7 +45,7 @@ import {
41
45
  PROBE_BLEND_RECORD_STRIDE,
42
46
  probeBlendRecordOffset,
43
47
  } from '../scene/probe-blend-record';
44
- import { worldEntityKey } from './frame-snapshot';
48
+ import { getOpaqueResourceIdentity, worldEntityKey } from './frame-snapshot';
45
49
  import {
46
50
  geometryRenderStateForTopology,
47
51
  isEntityFullyTransparent,
@@ -75,6 +79,18 @@ type GeometryBindingState = {
75
79
  probeOffset: number;
76
80
  };
77
81
 
82
+ function materialDiagnosticsEnabled(): boolean {
83
+ if (typeof globalThis !== 'object' || globalThis === null || !('process' in globalThis)) {
84
+ return false;
85
+ }
86
+ const processValue = (
87
+ globalThis as {
88
+ readonly process?: { readonly env?: Record<string, string | undefined> };
89
+ }
90
+ ).process;
91
+ return processValue?.env?.FORGEAX_MATERIAL_DIAGNOSTICS === '1';
92
+ }
93
+
78
94
  type MaterialPipelineLookup = {
79
95
  readonly materialShaderId: string;
80
96
  readonly isHdrTarget: boolean;
@@ -94,6 +110,121 @@ type GeometryRecordProfileSegment = 'material-bind-groups' | 'pipeline-selection
94
110
 
95
111
  type GeometryRenderState = ReturnType<typeof geometryRenderStateForTopology>;
96
112
 
113
+ /**
114
+ * Select the transmission shader axis from authored material data, not the
115
+ * canonical boot schema. The latter reserves transmission fields for the
116
+ * shared Standard ABI even when a material remains base-only.
117
+ */
118
+ export function requestsStandardTransmissionVariant(
119
+ material: Pick<MaterialSnapshot, 'materialShaderId' | 'paramSnapshot'>,
120
+ ): boolean {
121
+ return (
122
+ material.materialShaderId === 'forgeax::default-standard-pbr' &&
123
+ material.paramSnapshot?.transmission !== undefined
124
+ );
125
+ }
126
+
127
+ function recordIblPipelineBinding(
128
+ c: _InternalRenderPipelineContext,
129
+ material: MaterialSnapshot,
130
+ pipeline: RenderPipeline,
131
+ materialGroup: BindGroup,
132
+ ): void {
133
+ const receipt = c.frameState.iblBindingInspection;
134
+ if (receipt === undefined || !isStandardPbrMaterialShader(material.materialShaderId)) return;
135
+ const materialBgl =
136
+ c.runtime.getMaterialBindGroupLayout?.(
137
+ material.materialShaderId ?? 'forgeax::default-standard-pbr',
138
+ ) ?? c.pipelineState.materialBindGroupLayout;
139
+ c.frameState.iblBindingInspection = {
140
+ ...receipt,
141
+ pipeline: {
142
+ pipelineIdentity: getOpaqueResourceIdentity(pipeline as object),
143
+ effectiveMaterialLayoutIdentity: effectiveMaterialLayoutIdentity(
144
+ material.materialShaderId ?? 'forgeax::default-standard-pbr',
145
+ material.materialParamSchema,
146
+ ),
147
+ materialBglIdentity: getOpaqueResourceIdentity(materialBgl as object),
148
+ bindGroupIdentity: getOpaqueResourceIdentity(materialGroup as object),
149
+ drawFrameId: c.frameState.frameNumber,
150
+ },
151
+ };
152
+ const materialReceipt = c.frameState.iblBindingInspection.material;
153
+ const errors: string[] = [];
154
+ if (
155
+ materialReceipt !== undefined &&
156
+ materialReceipt.materialBglIdentity !== getOpaqueResourceIdentity(materialBgl as object)
157
+ ) {
158
+ errors.push('material-pipeline-bgl-identity-mismatch');
159
+ }
160
+ if (
161
+ materialReceipt !== undefined &&
162
+ materialReceipt.bindGroupIdentity !== getOpaqueResourceIdentity(materialGroup as object)
163
+ ) {
164
+ errors.push('material-bind-group-identity-mismatch');
165
+ }
166
+ if (materialReceipt !== undefined) {
167
+ const expectedResourceIdentities = [
168
+ receipt.resources.irradiance.viewIdentity,
169
+ receipt.resources.irradiance.samplerIdentity,
170
+ receipt.resources.prefilter.viewIdentity,
171
+ receipt.resources.prefilter.samplerIdentity,
172
+ receipt.resources.brdfLut.viewIdentity,
173
+ receipt.resources.brdfLut.samplerIdentity,
174
+ receipt.resources.intensityBufferIdentity,
175
+ ];
176
+ const actualResourceIdentities = materialReceipt.entries
177
+ .filter((entry) => entry.binding >= materialReceipt.skylightBindingStart)
178
+ .slice(0, expectedResourceIdentities.length)
179
+ .map((entry) => entry.resourceIdentity);
180
+ if (
181
+ actualResourceIdentities.length !== expectedResourceIdentities.length ||
182
+ actualResourceIdentities.some(
183
+ (identity, index) => identity !== expectedResourceIdentities[index],
184
+ )
185
+ ) {
186
+ errors.push('material-ibl-resource-identity-mismatch');
187
+ }
188
+ }
189
+ c.frameState.iblBindingInspection = {
190
+ ...c.frameState.iblBindingInspection,
191
+ status: errors.length === 0 ? 'binding-chain-consistent' : 'binding-chain-mismatch',
192
+ errors,
193
+ };
194
+ }
195
+
196
+ const SHARED_BOOT_MATERIAL_SHADER_IDS = new Set([
197
+ 'forgeax::default-unlit',
198
+ 'forgeax::default-shadow-caster',
199
+ 'forgeax::sprite',
200
+ 'forgeax::sprite-lit',
201
+ 'forgeax::default-sprite',
202
+ 'forgeax::msdf-text',
203
+ 'forgeax::default-standard-pbr',
204
+ 'forgeax::pbr-skin',
205
+ 'forgeax::default-standard-pbr-skin',
206
+ ]);
207
+
208
+ /**
209
+ * Returns the layout identity that can distinguish a material PSO in the
210
+ * record cache. Shared boot shaders keep the boot layout identity even when a
211
+ * manifest exposes a schema; Standard PBR only opts into a schema-derived
212
+ * identity when authored clearcoat texture bindings extend that layout.
213
+ */
214
+ export function effectiveMaterialLayoutIdentity(
215
+ materialShaderId: string,
216
+ materialParamSchema?: readonly ParamSchemaEntry[],
217
+ ): string | undefined {
218
+ if (materialParamSchema === undefined) return undefined;
219
+ const hasStandardPhysicalMaps =
220
+ isStandardPbrMaterialShader(materialShaderId) &&
221
+ physicalTextureFields(materialParamSchema).length > 0;
222
+ if (SHARED_BOOT_MATERIAL_SHADER_IDS.has(materialShaderId) && !hasStandardPhysicalMaps) {
223
+ return undefined;
224
+ }
225
+ return materialBindGroupLayoutIdentity(materialShaderId, materialParamSchema);
226
+ }
227
+
97
228
  /**
98
229
  * Resolve the depth contract for a geometry pass without changing the base
99
230
  * material state. Temporal scene-data shares the main pass depth attachment:
@@ -228,6 +359,7 @@ export function recordGeometryDraws(
228
359
  viewBindGroup,
229
360
  viewBindGroupDynamicOffset = 0,
230
361
  } = c;
362
+ const diagnosticsEnabled = materialDiagnosticsEnabled();
231
363
  const colorFormatOverride = c.transparentColorFormat as GPUTextureFormat | undefined;
232
364
  // The prepared Standard topology is the frame authority. A bind-group
233
365
  // identity is only a resource result; using it to infer the shader lane
@@ -587,7 +719,22 @@ export function recordGeometryDraws(
587
719
  // and walks 0, 1536, 3072, ... on the storage path.
588
720
  const skinAllocator = pipelineState.skinPaletteAllocator;
589
721
  const skinSlice = entry.source.skin;
590
- const clusteredSkin = clusteredLighting;
722
+ // Authored Standard skin aliases deliberately publish the physical root
723
+ // with only their STORAGE_BUFFER capability axis. Their composed WGSL is
724
+ // the direct-lighting/palette (`skin`) ABI, even when the frame's
725
+ // canonical Standard lane uses clustered lighting. Only the canonical
726
+ // engine skin artifact can consume the HDRP unified skin group(2) layout;
727
+ // selecting it from the frame topology alone would bind
728
+ // `hdrp-skin-unified-bg-group2` to a three-entry URP skin PSO.
729
+ const clusteredSkin =
730
+ clusteredLighting &&
731
+ entry.source.materials.every((material) => {
732
+ const shaderId = material.materialShaderId;
733
+ return (
734
+ !isStandardPbrSkinMaterialShader(shaderId) ||
735
+ isCanonicalStandardPbrMaterialShader(shaderId)
736
+ );
737
+ });
591
738
  const skinMeshBindGroupLayout = clusteredSkin
592
739
  ? pipelineState.hdrpSkinMeshBindGroupLayout
593
740
  : pipelineState.pbrSkinMeshBindGroupLayout;
@@ -734,6 +881,19 @@ export function recordGeometryDraws(
734
881
  // the async PSO compile resolves the cache hits and the next frame
735
882
  // routes the skin BG + skin pipeline together. Mirrors the uniform
736
883
  // null skip-draw shape (M6-T1, charter P3 explicit failure).
884
+ if (diagnosticsEnabled) {
885
+ console.error(
886
+ `[render-material] skin draw skipped: ${JSON.stringify({
887
+ entityKey: entry.source.entityKey,
888
+ materialHandle: entry.source.material.materialHandle,
889
+ reason: 'skin-pipeline-unavailable',
890
+ skinResourcesReady: skinResources !== null,
891
+ skinPsoReady: skinPsoProbe !== null,
892
+ paletteBufferReady: skinSlice?.buffer !== undefined,
893
+ paletteByteOffset: skinSlice?.byteOffset,
894
+ })}`,
895
+ );
896
+ }
737
897
  continue;
738
898
  }
739
899
  // feat-20260520-2d-sprite-layer-mvp M-3 / w25 (@fallback sprite
@@ -828,6 +988,26 @@ export function recordGeometryDraws(
828
988
  // injection, and Skylight merge; deduped cross-entity via the
829
989
  // shaderId-outer `materialBgShared` cache.
830
990
  const materialSlot = materialSlotIndices[i]?.[matSlotIdx] ?? materialSlotIndices[i]?.[0] ?? 0;
991
+ const processValue = (
992
+ globalThis as {
993
+ process?: { env?: Record<string, string | undefined> };
994
+ }
995
+ ).process;
996
+ if (
997
+ processValue?.env?.FORGEAX_MATERIAL_PIPELINE_DIAGNOSTICS === '1' &&
998
+ (submeshMaterial.materialShaderId === 'forgeax::default-standard-pbr' ||
999
+ submeshMaterial.materialShaderId === 'forgeax::pbr-skin')
1000
+ ) {
1001
+ console.error(
1002
+ `[render-material] draw receipt: ${JSON.stringify({
1003
+ entityIndex: entry.renderableIndex,
1004
+ materialSlot,
1005
+ materialHandle: submeshMaterial.materialHandle,
1006
+ clearcoat: submeshMaterial.paramSnapshot?.clearcoat,
1007
+ shader: submeshMaterial.materialShaderId,
1008
+ })}`,
1009
+ );
1010
+ }
831
1011
  perSubmeshBg =
832
1012
  profilePhase === undefined
833
1013
  ? resolveMaterialBindGroup(
@@ -849,7 +1029,9 @@ export function recordGeometryDraws(
849
1029
  const smTopology = sm.topology;
850
1030
  const smMaterialShaderId =
851
1031
  entry.source.skin !== undefined
852
- ? SKIN_MATERIAL_SHADER_ID
1032
+ ? isStandardPbrSkinMaterialShader(submeshMaterial.materialShaderId)
1033
+ ? submeshMaterial.materialShaderId
1034
+ : SKIN_MATERIAL_SHADER_ID
853
1035
  : submeshMaterial.materialShaderId;
854
1036
  const probeBlendAvailable =
855
1037
  probeBlendRecordAvailable && isStandardPbrMaterialShader(smMaterialShaderId);
@@ -930,6 +1112,11 @@ export function recordGeometryDraws(
930
1112
  hasVertexColor,
931
1113
  probeBlendAvailable,
932
1114
  );
1115
+ const transmissionAvailable = requestsStandardTransmissionVariant(submeshMaterial);
1116
+ const materialCapabilityVariantSet =
1117
+ transmissionAvailable && !capabilityVariantSet.includes('TRANSMISSION_AVAILABLE=')
1118
+ ? `${capabilityVariantSet === '' ? '' : `${capabilityVariantSet}+`}TRANSMISSION_AVAILABLE=true`
1119
+ : capabilityVariantSet;
933
1120
  // Sprite's base request must retain the boot-selected artifact. Only
934
1121
  // SpriteInstances uses the explicit empty key for the
935
1122
  // PER_INSTANCE_REGION=true variant; synthesizing the generic PBR
@@ -940,7 +1127,7 @@ export function recordGeometryDraws(
940
1127
  ? ''
941
1128
  : undefined
942
1129
  : entry.variantSet === undefined
943
- ? capabilityVariantSet
1130
+ ? materialCapabilityVariantSet
944
1131
  : capabilityVariantSet === ''
945
1132
  ? entry.variantSet
946
1133
  : `${capabilityVariantSet}+${entry.variantSet}`;
@@ -973,6 +1160,16 @@ export function recordGeometryDraws(
973
1160
  }
974
1161
 
975
1162
  if (smPipelineHandle === null) {
1163
+ if (diagnosticsEnabled) {
1164
+ console.error(
1165
+ `[render-material] draw skipped: ${JSON.stringify({
1166
+ entityKey: entry.source.entityKey,
1167
+ materialHandle: submeshMaterial.materialHandle,
1168
+ shader: smMaterialShaderId,
1169
+ reason: 'pipeline-unavailable',
1170
+ })}`,
1171
+ );
1172
+ }
976
1173
  continue;
977
1174
  }
978
1175
  // The probe record is an ABI extension of the built-in Standard PBR
@@ -1003,6 +1200,31 @@ export function recordGeometryDraws(
1003
1200
  if (selectedGroup === null) continue;
1004
1201
  group2BindGroup = selectedGroup;
1005
1202
  }
1203
+ if (diagnosticsEnabled && submeshMaterial.textureHandles !== undefined) {
1204
+ console.error(
1205
+ `[render-material] draw submitted: ${JSON.stringify({
1206
+ entityKey: entry.source.entityKey,
1207
+ materialHandle: submeshMaterial.materialHandle,
1208
+ shader: smMaterialShaderId,
1209
+ textureHandles: [...submeshMaterial.textureHandles.entries()].map(
1210
+ ([field, handle]) => ({
1211
+ field,
1212
+ handle,
1213
+ }),
1214
+ ),
1215
+ skin: isSkinEntry
1216
+ ? {
1217
+ resourcesReady: skinResources !== null,
1218
+ psoReady: skinPsoProbe !== null,
1219
+ paletteBufferReady: skinSlice?.buffer !== undefined,
1220
+ paletteByteOffset: skinSlice?.byteOffset,
1221
+ dynamicOffsets: [...group2DynamicOffsets],
1222
+ }
1223
+ : undefined,
1224
+ pipelineReady: true,
1225
+ })}`,
1226
+ );
1227
+ }
1006
1228
  pass.setBindGroup(2, group2BindGroup, group2DynamicOffsets);
1007
1229
 
1008
1230
  if (profilePhase === undefined) {
@@ -1034,6 +1256,9 @@ export function recordGeometryDraws(
1034
1256
  ),
1035
1257
  );
1036
1258
  }
1259
+ if (diagnosticsEnabled && perSubmeshBg !== null) {
1260
+ recordIblPipelineBinding(c, submeshMaterial, smPipelineHandle, perSubmeshBg);
1261
+ }
1037
1262
  }
1038
1263
  }
1039
1264
  }