@forgeax/engine-render 0.1.27 → 0.1.29

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 (655) hide show
  1. package/README.md +842 -124
  2. package/dist/assembly/atmosphere-shader-prewarm.d.ts +6 -0
  3. package/dist/assembly/atmosphere-shader-prewarm.d.ts.map +1 -0
  4. package/dist/assembly/dynamic-geometry-host.d.ts +52 -0
  5. package/dist/assembly/dynamic-geometry-host.d.ts.map +1 -0
  6. package/dist/assembly/dynamic-geometry-runtime.d.ts +20 -0
  7. package/dist/assembly/dynamic-geometry-runtime.d.ts.map +1 -0
  8. package/dist/assembly/factory.d.ts +3 -1
  9. package/dist/assembly/factory.d.ts.map +1 -1
  10. package/dist/assembly/gpu-driven-owner.d.ts +10 -0
  11. package/dist/assembly/gpu-driven-owner.d.ts.map +1 -0
  12. package/dist/assembly/host-contract.d.ts +19 -0
  13. package/dist/assembly/host-contract.d.ts.map +1 -1
  14. package/dist/assembly/material/assembly.d.ts +7 -0
  15. package/dist/assembly/material/assembly.d.ts.map +1 -1
  16. package/dist/assembly/material-shader-policy.d.ts +3 -1
  17. package/dist/assembly/material-shader-policy.d.ts.map +1 -1
  18. package/dist/assembly/recovery/failure-location.d.ts +12 -0
  19. package/dist/assembly/recovery/failure-location.d.ts.map +1 -0
  20. package/dist/assembly/recovery/renderer-recover.d.ts +6 -4
  21. package/dist/assembly/recovery/renderer-recover.d.ts.map +1 -1
  22. package/dist/assembly/renderer-facade.d.ts.map +1 -1
  23. package/dist/assembly/renderer-feature-inspection.d.ts +5 -0
  24. package/dist/assembly/renderer-feature-inspection.d.ts.map +1 -0
  25. package/dist/assembly/renderer-frame-transaction.d.ts +19 -0
  26. package/dist/assembly/renderer-frame-transaction.d.ts.map +1 -1
  27. package/dist/assembly/renderer-helpers.d.ts +3 -0
  28. package/dist/assembly/renderer-helpers.d.ts.map +1 -1
  29. package/dist/assembly/renderer-inspection.d.ts +4 -0
  30. package/dist/assembly/renderer-inspection.d.ts.map +1 -1
  31. package/dist/assembly/shader-prewarm-policy.d.ts +6 -0
  32. package/dist/assembly/shader-prewarm-policy.d.ts.map +1 -1
  33. package/dist/assembly/skin-palette-owner.d.ts +8 -0
  34. package/dist/assembly/skin-palette-owner.d.ts.map +1 -0
  35. package/dist/assembly/ssr-shader-prewarm.d.ts +6 -0
  36. package/dist/assembly/ssr-shader-prewarm.d.ts.map +1 -0
  37. package/dist/assembly/webgpu-device-loss.d.ts +15 -0
  38. package/dist/assembly/webgpu-device-loss.d.ts.map +1 -0
  39. package/dist/assembly/webgpu-pbr-ready.d.ts +38 -0
  40. package/dist/assembly/webgpu-pbr-ready.d.ts.map +1 -0
  41. package/dist/assembly/webgpu-ready-mipmap.d.ts +11 -0
  42. package/dist/assembly/webgpu-ready-mipmap.d.ts.map +1 -0
  43. package/dist/assembly/webgpu-ready.d.ts +7 -3
  44. package/dist/assembly/webgpu-ready.d.ts.map +1 -1
  45. package/dist/assembly/webgpu-renderer-bootstrap.d.ts +10 -0
  46. package/dist/assembly/webgpu-renderer-bootstrap.d.ts.map +1 -0
  47. package/dist/assembly/webgpu-renderer-contract.d.ts +62 -0
  48. package/dist/assembly/webgpu-renderer-contract.d.ts.map +1 -0
  49. package/dist/assembly/webgpu-renderer-guards.d.ts +9 -0
  50. package/dist/assembly/webgpu-renderer-guards.d.ts.map +1 -0
  51. package/dist/assembly/webgpu-renderer-observation.d.ts +6 -0
  52. package/dist/assembly/webgpu-renderer-observation.d.ts.map +1 -0
  53. package/dist/assembly/webgpu-renderer-recovery-failure.d.ts +12 -0
  54. package/dist/assembly/webgpu-renderer-recovery-failure.d.ts.map +1 -0
  55. package/dist/assembly/webgpu-renderer.d.ts +7 -141
  56. package/dist/assembly/webgpu-renderer.d.ts.map +1 -1
  57. package/dist/assembly/webgpu-vertex-layouts.d.ts +28 -84
  58. package/dist/assembly/webgpu-vertex-layouts.d.ts.map +1 -1
  59. package/dist/authoring.mjs +3 -3
  60. package/dist/{chunk-ADHHVYLW.mjs → chunk-33XZFTQU.mjs} +245 -39
  61. package/dist/chunk-33XZFTQU.mjs.map +1 -0
  62. package/dist/{chunk-GF523LHF.mjs → chunk-BLXGONR2.mjs} +30 -12
  63. package/dist/chunk-BLXGONR2.mjs.map +1 -0
  64. package/dist/{chunk-KM2NOX2I.mjs → chunk-BQVQOQJH.mjs} +958 -254
  65. package/dist/chunk-BQVQOQJH.mjs.map +1 -0
  66. package/dist/{chunk-HKXTW355.mjs → chunk-BTWH7BP4.mjs} +386 -17
  67. package/dist/chunk-BTWH7BP4.mjs.map +1 -0
  68. package/dist/{chunk-TMIASV2N.mjs → chunk-FUODTTIA.mjs} +3934 -676
  69. package/dist/chunk-FUODTTIA.mjs.map +1 -0
  70. package/dist/{chunk-FJ6P52EE.mjs → chunk-ILQ5MEZF.mjs} +3 -17
  71. package/dist/chunk-ILQ5MEZF.mjs.map +1 -0
  72. package/dist/{chunk-E34VL5VI.mjs → chunk-JQTSEI3Z.mjs} +3 -3
  73. package/dist/{chunk-E34VL5VI.mjs.map → chunk-JQTSEI3Z.mjs.map} +1 -1
  74. package/dist/{chunk-GE3SDD72.mjs → chunk-NICXSHAT.mjs} +16 -38
  75. package/dist/chunk-NICXSHAT.mjs.map +1 -0
  76. package/dist/{chunk-ZZ4YQ474.mjs → chunk-NRNXBLIJ.mjs} +577 -7
  77. package/dist/chunk-NRNXBLIJ.mjs.map +1 -0
  78. package/dist/{chunk-JDWARUOI.mjs → chunk-PA5DRJNI.mjs} +17 -3
  79. package/dist/chunk-PA5DRJNI.mjs.map +1 -0
  80. package/dist/{chunk-XDP6XE5V.mjs → chunk-S4DNKQYQ.mjs} +1118 -446
  81. package/dist/chunk-S4DNKQYQ.mjs.map +1 -0
  82. package/dist/components/camera.d.ts +62 -2
  83. package/dist/components/camera.d.ts.map +1 -1
  84. package/dist/components/index.d.ts +1 -0
  85. package/dist/components/index.d.ts.map +1 -1
  86. package/dist/components/instances.d.ts +6 -20
  87. package/dist/components/instances.d.ts.map +1 -1
  88. package/dist/components/scene-instance.d.ts +2 -1
  89. package/dist/components/scene-instance.d.ts.map +1 -1
  90. package/dist/components/screen-space-reflection.d.ts +14 -0
  91. package/dist/components/screen-space-reflection.d.ts.map +1 -0
  92. package/dist/construct-renderer.mjs +13805 -7186
  93. package/dist/construct-renderer.mjs.map +1 -1
  94. package/dist/device/gpu-residency.d.ts +11 -17
  95. package/dist/device/gpu-residency.d.ts.map +1 -1
  96. package/dist/device/mesh-residency-lifetime.d.ts +21 -0
  97. package/dist/device/mesh-residency-lifetime.d.ts.map +1 -0
  98. package/dist/dynamic-geometry.d.ts +125 -0
  99. package/dist/dynamic-geometry.d.ts.map +1 -0
  100. package/dist/environment/background.d.ts +6 -0
  101. package/dist/environment/background.d.ts.map +1 -0
  102. package/dist/errors/gpu-driven.d.ts +6 -3
  103. package/dist/errors/gpu-driven.d.ts.map +1 -1
  104. package/dist/errors/render.d.ts +7 -4
  105. package/dist/errors/render.d.ts.map +1 -1
  106. package/dist/extract/camera.d.ts.map +1 -1
  107. package/dist/extract/gpu-driven.d.ts +33 -3
  108. package/dist/extract/gpu-driven.d.ts.map +1 -1
  109. package/dist/features/host.d.ts +23 -1
  110. package/dist/features/host.d.ts.map +1 -1
  111. package/dist/features/noise-texture.d.ts +17 -0
  112. package/dist/features/noise-texture.d.ts.map +1 -0
  113. package/dist/features/plan.d.ts +83 -2
  114. package/dist/features/plan.d.ts.map +1 -1
  115. package/dist/features/prepared-gpu-work.d.ts +37 -4
  116. package/dist/features/prepared-gpu-work.d.ts.map +1 -1
  117. package/dist/features/prepared-graphics-store.d.ts.map +1 -1
  118. package/dist/features/prepared-graphics.d.ts +5 -0
  119. package/dist/features/prepared-graphics.d.ts.map +1 -1
  120. package/dist/features/render-graph-compute.d.ts +5 -3
  121. package/dist/features/render-graph-compute.d.ts.map +1 -1
  122. package/dist/features/render-graph-contribution.d.ts +11 -3
  123. package/dist/features/render-graph-contribution.d.ts.map +1 -1
  124. package/dist/features/render-graph-raster.d.ts +19 -3
  125. package/dist/features/render-graph-raster.d.ts.map +1 -1
  126. package/dist/features/types.d.ts +11 -0
  127. package/dist/features/types.d.ts.map +1 -1
  128. package/dist/fullscreen-post-process-pass.d.ts +2 -0
  129. package/dist/fullscreen-post-process-pass.d.ts.map +1 -1
  130. package/dist/gpu-driven/batch-topology.d.ts +34 -1
  131. package/dist/gpu-driven/batch-topology.d.ts.map +1 -1
  132. package/dist/gpu-driven/pbr-program.d.ts +10 -0
  133. package/dist/gpu-driven/pbr-program.d.ts.map +1 -0
  134. package/dist/gpu-driven/prepared-draw.d.ts.map +1 -1
  135. package/dist/gpu-driven/production-raster.d.ts +144 -23
  136. package/dist/gpu-driven/production-raster.d.ts.map +1 -1
  137. package/dist/gpu-driven/shadow-views.d.ts +126 -0
  138. package/dist/gpu-driven/shadow-views.d.ts.map +1 -0
  139. package/dist/gpu-driven/view-gpu.d.ts +18 -2
  140. package/dist/gpu-driven/view-gpu.d.ts.map +1 -1
  141. package/dist/gpu-scene-schema.d.ts +2 -8
  142. package/dist/gpu-scene-schema.d.ts.map +1 -1
  143. package/dist/gpu-scene.d.ts +14 -0
  144. package/dist/gpu-scene.d.ts.map +1 -1
  145. package/dist/gpu-texture-usage.d.ts +1 -0
  146. package/dist/gpu-texture-usage.d.ts.map +1 -1
  147. package/dist/hdrp-buffers.d.ts +6 -0
  148. package/dist/hdrp-buffers.d.ts.map +1 -1
  149. package/dist/ibl/frame-resources.d.ts +8 -0
  150. package/dist/ibl/frame-resources.d.ts.map +1 -0
  151. package/dist/ibl/skylight-bind-group.d.ts.map +1 -1
  152. package/dist/index.d.ts +21 -8
  153. package/dist/index.d.ts.map +1 -1
  154. package/dist/index.mjs +8 -7
  155. package/dist/index.mjs.map +1 -1
  156. package/dist/inspection-types.d.ts +164 -0
  157. package/dist/inspection-types.d.ts.map +1 -1
  158. package/dist/instances-derived-bounds.d.ts +11 -0
  159. package/dist/instances-derived-bounds.d.ts.map +1 -1
  160. package/dist/instances.d.ts +89 -44
  161. package/dist/instances.d.ts.map +1 -1
  162. package/dist/internal.d.ts +3 -0
  163. package/dist/internal.d.ts.map +1 -1
  164. package/dist/internal.mjs +118 -10
  165. package/dist/internal.mjs.map +1 -1
  166. package/dist/lifecycle.d.ts +2 -1
  167. package/dist/lifecycle.d.ts.map +1 -1
  168. package/dist/material-row.d.ts +5 -0
  169. package/dist/material-row.d.ts.map +1 -0
  170. package/dist/materials.d.ts +15 -0
  171. package/dist/materials.d.ts.map +1 -1
  172. package/dist/pbr-pipeline.d.ts +21 -5
  173. package/dist/pbr-pipeline.d.ts.map +1 -1
  174. package/dist/pipeline/standard-forward-lane.d.ts.map +1 -1
  175. package/dist/pipeline/standard-output/auto-exposure/capability.d.ts +12 -0
  176. package/dist/pipeline/standard-output/auto-exposure/capability.d.ts.map +1 -0
  177. package/dist/pipeline/standard-output/auto-exposure/gpu.d.ts +70 -0
  178. package/dist/pipeline/standard-output/auto-exposure/gpu.d.ts.map +1 -0
  179. package/dist/pipeline/standard-output/auto-exposure/graph.d.ts +75 -0
  180. package/dist/pipeline/standard-output/auto-exposure/graph.d.ts.map +1 -0
  181. package/dist/pipeline/standard-output/auto-exposure/inspection.d.ts +111 -0
  182. package/dist/pipeline/standard-output/auto-exposure/inspection.d.ts.map +1 -0
  183. package/dist/pipeline/standard-output/auto-exposure/oracle.d.ts +16 -0
  184. package/dist/pipeline/standard-output/auto-exposure/oracle.d.ts.map +1 -0
  185. package/dist/pipeline/standard-output/auto-exposure/preset.d.ts +14 -0
  186. package/dist/pipeline/standard-output/auto-exposure/preset.d.ts.map +1 -0
  187. package/dist/pipeline/standard-output/auto-exposure/state.d.ts +46 -0
  188. package/dist/pipeline/standard-output/auto-exposure/state.d.ts.map +1 -0
  189. package/dist/pipeline/standard-output/color-transform.d.ts +27 -0
  190. package/dist/pipeline/standard-output/color-transform.d.ts.map +1 -0
  191. package/dist/pipeline/standard-output/graph.d.ts +15 -0
  192. package/dist/pipeline/standard-output/graph.d.ts.map +1 -0
  193. package/dist/pipeline/standard-output/lut-admission.d.ts +29 -0
  194. package/dist/pipeline/standard-output/lut-admission.d.ts.map +1 -0
  195. package/dist/pipeline/standard-output/lut-gpu.d.ts +48 -0
  196. package/dist/pipeline/standard-output/lut-gpu.d.ts.map +1 -0
  197. package/dist/pipeline/standard-output/lut-state.d.ts +68 -0
  198. package/dist/pipeline/standard-output/lut-state.d.ts.map +1 -0
  199. package/dist/pipeline/standard-output/resources.d.ts +10 -0
  200. package/dist/pipeline/standard-output/resources.d.ts.map +1 -0
  201. package/dist/pipeline/standard-output/types.d.ts +39 -0
  202. package/dist/pipeline/standard-output/types.d.ts.map +1 -0
  203. package/dist/pipeline/standard-pipeline.d.ts.map +1 -1
  204. package/dist/pipeline/standard-post.d.ts +2 -2
  205. package/dist/pipeline/standard-post.d.ts.map +1 -1
  206. package/dist/pipeline-builder.d.ts +2 -1
  207. package/dist/pipeline-builder.d.ts.map +1 -1
  208. package/dist/pipeline-spec-types.d.ts +4 -0
  209. package/dist/pipeline-spec-types.d.ts.map +1 -1
  210. package/dist/pipeline-spec.d.ts +1 -9
  211. package/dist/pipeline-spec.d.ts.map +1 -1
  212. package/dist/points-lines/standard-owner.d.ts +1 -1
  213. package/dist/points-lines/standard-owner.d.ts.map +1 -1
  214. package/dist/prepare/prepared-graphics-resolver.d.ts +6 -1
  215. package/dist/prepare/prepared-graphics-resolver.d.ts.map +1 -1
  216. package/dist/record/dynamic-geometry-consumption.d.ts +15 -0
  217. package/dist/record/dynamic-geometry-consumption.d.ts.map +1 -0
  218. package/dist/record/frame-lighting.d.ts +3 -3
  219. package/dist/record/frame-lighting.d.ts.map +1 -1
  220. package/dist/record/frame-snapshot.d.ts +106 -5
  221. package/dist/record/frame-snapshot.d.ts.map +1 -1
  222. package/dist/record/frame-targets.d.ts.map +1 -1
  223. package/dist/record/frame.d.ts +39 -4
  224. package/dist/record/frame.d.ts.map +1 -1
  225. package/dist/record/main-pass-geometry.d.ts +7 -8
  226. package/dist/record/main-pass-geometry.d.ts.map +1 -1
  227. package/dist/record/main-pass-material.d.ts +38 -1
  228. package/dist/record/main-pass-material.d.ts.map +1 -1
  229. package/dist/record/main-pass-sprite-draws.d.ts +2 -10
  230. package/dist/record/main-pass-sprite-draws.d.ts.map +1 -1
  231. package/dist/record/main-pass.d.ts +12 -2
  232. package/dist/record/main-pass.d.ts.map +1 -1
  233. package/dist/record/mesh-ssbo.d.ts +15 -6
  234. package/dist/record/mesh-ssbo.d.ts.map +1 -1
  235. package/dist/record/prepared-material-bindings.d.ts +19 -0
  236. package/dist/record/prepared-material-bindings.d.ts.map +1 -0
  237. package/dist/record/recovery-pipeline.d.ts +2 -9
  238. package/dist/record/recovery-pipeline.d.ts.map +1 -1
  239. package/dist/record/render-context.d.ts +76 -26
  240. package/dist/record/render-context.d.ts.map +1 -1
  241. package/dist/record/shadow-pass.d.ts +62 -7
  242. package/dist/record/shadow-pass.d.ts.map +1 -1
  243. package/dist/record/skybox-post-pass.d.ts +1 -1
  244. package/dist/record/skybox-post-pass.d.ts.map +1 -1
  245. package/dist/record/typed-frame-graph.d.ts +35 -7
  246. package/dist/record/typed-frame-graph.d.ts.map +1 -1
  247. package/dist/record/view-ubo.d.ts +1 -0
  248. package/dist/record/view-ubo.d.ts.map +1 -1
  249. package/dist/recovery/render-system-candidate.d.ts +10 -2
  250. package/dist/recovery/render-system-candidate.d.ts.map +1 -1
  251. package/dist/recovery/render-system-roots.d.ts +11 -0
  252. package/dist/recovery/render-system-roots.d.ts.map +1 -0
  253. package/dist/recovery/types.d.ts +76 -0
  254. package/dist/recovery/types.d.ts.map +1 -0
  255. package/dist/reflection/record-owner.d.ts +0 -2
  256. package/dist/reflection/record-owner.d.ts.map +1 -1
  257. package/dist/render-contract.d.ts +185 -30
  258. package/dist/render-contract.d.ts.map +1 -1
  259. package/dist/render-graph-primitives.d.ts +1 -0
  260. package/dist/render-graph-primitives.d.ts.map +1 -1
  261. package/dist/render-pipeline.d.ts +64 -5
  262. package/dist/render-pipeline.d.ts.map +1 -1
  263. package/dist/render-system-extract-tail.d.ts +7 -5
  264. package/dist/render-system-extract-tail.d.ts.map +1 -1
  265. package/dist/render-system-extract.d.ts +101 -41
  266. package/dist/render-system-extract.d.ts.map +1 -1
  267. package/dist/render-system-projections.d.ts +22 -0
  268. package/dist/render-system-projections.d.ts.map +1 -0
  269. package/dist/render-system.d.ts +34 -77
  270. package/dist/render-system.d.ts.map +1 -1
  271. package/dist/scene/render-scene-types.d.ts +14 -7
  272. package/dist/scene/render-scene-types.d.ts.map +1 -1
  273. package/dist/scene/render-scene.d.ts +29 -11
  274. package/dist/scene/render-scene.d.ts.map +1 -1
  275. package/dist/scene/shadow-visibility.d.ts +5 -0
  276. package/dist/scene/shadow-visibility.d.ts.map +1 -0
  277. package/dist/ssr/admission.d.ts +70 -1
  278. package/dist/ssr/admission.d.ts.map +1 -1
  279. package/dist/ssr/compose.d.ts +13 -0
  280. package/dist/ssr/compose.d.ts.map +1 -0
  281. package/dist/ssr/errors.d.ts +35 -0
  282. package/dist/ssr/errors.d.ts.map +1 -1
  283. package/dist/ssr/graph.d.ts +60 -0
  284. package/dist/ssr/graph.d.ts.map +1 -0
  285. package/dist/ssr/history.d.ts +97 -0
  286. package/dist/ssr/history.d.ts.map +1 -0
  287. package/dist/ssr/hiz.d.ts +37 -0
  288. package/dist/ssr/hiz.d.ts.map +1 -0
  289. package/dist/ssr/inspection.d.ts +42 -0
  290. package/dist/ssr/inspection.d.ts.map +1 -0
  291. package/dist/ssr/resources.d.ts +58 -0
  292. package/dist/ssr/resources.d.ts.map +1 -0
  293. package/dist/ssr/temporal.d.ts +38 -0
  294. package/dist/ssr/temporal.d.ts.map +1 -0
  295. package/dist/systems/skin-palette-allocator.d.ts +32 -10
  296. package/dist/systems/skin-palette-allocator.d.ts.map +1 -1
  297. package/dist/systems/skin-palette-types.d.ts +15 -0
  298. package/dist/systems/skin-palette-types.d.ts.map +1 -1
  299. package/dist/systems/transparent-dispatch.d.ts +9 -0
  300. package/dist/systems/transparent-dispatch.d.ts.map +1 -0
  301. package/dist/targets/material-source.d.ts +7 -0
  302. package/dist/targets/material-source.d.ts.map +1 -1
  303. package/dist/temporal/frame-coordinator.d.ts +7 -0
  304. package/dist/temporal/frame-coordinator.d.ts.map +1 -1
  305. package/dist/temporal/gpu.d.ts +8 -0
  306. package/dist/temporal/gpu.d.ts.map +1 -1
  307. package/dist/temporal/history.d.ts.map +1 -1
  308. package/dist/temporal/index.mjs +6 -6
  309. package/dist/temporal/inspection.d.ts.map +1 -1
  310. package/dist/temporal/standard-scene-data.d.ts +2 -1
  311. package/dist/temporal/standard-scene-data.d.ts.map +1 -1
  312. package/dist/temporal/temporal-view.d.ts +2 -0
  313. package/dist/temporal/temporal-view.d.ts.map +1 -1
  314. package/dist/typed-render-graph-primitives.d.ts +35 -1
  315. package/dist/typed-render-graph-primitives.d.ts.map +1 -1
  316. package/dist/typed-shadow-passes.d.ts +3 -1
  317. package/dist/typed-shadow-passes.d.ts.map +1 -1
  318. package/dist/volume/temporal.d.ts +6 -0
  319. package/dist/volume/temporal.d.ts.map +1 -1
  320. package/package.json +21 -20
  321. package/src/__tests__/auto-exposure-gpu-evidence.ts +291 -0
  322. package/src/__tests__/auto-exposure-inspection.unit.test.ts +80 -0
  323. package/src/__tests__/auto-exposure-transaction.integration.test.ts +99 -0
  324. package/src/__tests__/auto-exposure.browser.test.ts +33 -0
  325. package/src/__tests__/auto-exposure.dawn.test.ts +33 -0
  326. package/src/__tests__/detached-inspection.integration.test.ts +5 -0
  327. package/src/__tests__/device-scope-lifecycle.unit.test.ts +18 -0
  328. package/src/__tests__/device-scope-stale-matrix.unit.test.ts +18 -0
  329. package/src/__tests__/docs-gate-green.unit.test.ts +3 -3
  330. package/src/__tests__/dynamic-geometry-host.unit.test.ts +1123 -0
  331. package/src/__tests__/dynamic-geometry.unit.test.ts +233 -0
  332. package/src/__tests__/factory-contract.integration.test.ts +73 -22
  333. package/src/__tests__/fallback-row-stride-surface.unit.test.ts +19 -0
  334. package/src/__tests__/feature-depth-input.dawn.test.ts +121 -0
  335. package/src/__tests__/feature-noise.unit.test.ts +38 -0
  336. package/src/__tests__/fixtures/taa-resolve-pinned-baseline.wgsl +301 -0
  337. package/src/__tests__/frame-camera-selection.unit.test.ts +131 -0
  338. package/src/__tests__/frame-plan-contract.unit.test.ts +24 -0
  339. package/src/__tests__/frame-targets.unit.test.ts +24 -0
  340. package/src/__tests__/fullscreen-feature-plan.unit.test.ts +3 -0
  341. package/src/__tests__/gpu-driven-baseline.characterization.test.ts +79 -0
  342. package/src/__tests__/gpu-driven-batch-topology-pbr.unit.test.ts +287 -6
  343. package/src/__tests__/gpu-driven-indirect-raster-evidence.ts +122 -142
  344. package/src/__tests__/gpu-driven-indirect-raster.browser.test.ts +10 -1
  345. package/src/__tests__/gpu-driven-lod-cache.unit.test.ts +264 -0
  346. package/src/__tests__/gpu-driven-mesh-world-identity.unit.test.ts +349 -0
  347. package/src/__tests__/gpu-driven-pbr.dawn.test.ts +1417 -0
  348. package/src/__tests__/gpu-driven-pbr.integration.test.ts +21 -0
  349. package/src/__tests__/gpu-driven-production-fixture.ts +96 -0
  350. package/src/__tests__/gpu-driven-production.integration.test.ts +1075 -93
  351. package/src/__tests__/gpu-driven-scaling.unit.test.ts +1 -3
  352. package/src/__tests__/gpu-driven-shadow-views.browser.test.ts +63 -0
  353. package/src/__tests__/gpu-driven-shadow-views.dawn.test.ts +294 -0
  354. package/src/__tests__/gpu-driven-shadow-views.integration.test.ts +97 -0
  355. package/src/__tests__/gpu-driven-shadow-views.unit.test.ts +168 -0
  356. package/src/__tests__/gpu-driven-skin-contract.test-d.ts +11 -0
  357. package/src/__tests__/gpu-driven-view-gpu-evidence.ts +72 -20
  358. package/src/__tests__/gpu-driven-view-graph.integration.test.ts +30 -9
  359. package/src/__tests__/gpu-driven-view.unit.test.ts +159 -52
  360. package/src/__tests__/gpu-pass-timing.browser.test.ts +6 -1
  361. package/src/__tests__/gpu-resource-store-stride.unit.test.ts +42 -0
  362. package/src/__tests__/gpu-scene-render-graph-gpu.ts +19 -5
  363. package/src/__tests__/gpu-scene.dawn.test.ts +40 -13
  364. package/src/__tests__/gpu-scene.unit.test.ts +559 -33
  365. package/src/__tests__/instances-culling.integration.test.ts +36 -10
  366. package/src/__tests__/instances-derived-bounds.unit.test.ts +54 -0
  367. package/src/__tests__/instances-store.unit.test.ts +242 -39
  368. package/src/__tests__/ktx2-basis-gpu-consumer.dawn.test.ts +6 -0
  369. package/src/__tests__/lighting-scene-roundtrip.integration.test.ts +13 -16
  370. package/src/__tests__/lut-lkg-transaction.integration.test.ts +122 -0
  371. package/src/__tests__/material-contract-inventory.unit.test.ts +5 -6
  372. package/src/__tests__/material-dispatch-shader-id.unit.test.ts +51 -0
  373. package/src/__tests__/material-lane-selection.unit.test.ts +60 -0
  374. package/src/__tests__/material-numeric-upload.unit.test.ts +53 -0
  375. package/src/__tests__/material-texture-handles.unit.test.ts +18 -0
  376. package/src/__tests__/material-texture-source.unit.test.ts +87 -0
  377. package/src/__tests__/materials-authoring-validation.unit.test.ts +41 -0
  378. package/src/__tests__/materials-standard-contract.unit.test.ts +52 -2
  379. package/src/__tests__/mesh-buffer-usage-surface.unit.test.ts +4 -0
  380. package/src/__tests__/mesh-submission-lifetime.unit.test.ts +53 -0
  381. package/src/__tests__/normal-fallback.dawn.test.ts +122 -0
  382. package/src/__tests__/particle-material-input-layout.unit.test.ts +104 -0
  383. package/src/__tests__/physical-clearcoat.integration.test.ts +37 -3
  384. package/src/__tests__/pipeline-texture-specialization.unit.test.ts +138 -0
  385. package/src/__tests__/point-shadow-inspection.unit.test.ts +2 -0
  386. package/src/__tests__/prepared-gpu-driven-pbr-contract.test-d.ts +31 -0
  387. package/src/__tests__/prepared-gpu-driven-pbr-contract.unit.test.ts +39 -0
  388. package/src/__tests__/prepared-gpu-driven-pbr.integration.test.ts +35 -4
  389. package/src/__tests__/prepared-graphics-capability.unit.test.ts +21 -0
  390. package/src/__tests__/prepared-graphics-pipeline-warmup.unit.test.ts +6 -2
  391. package/src/__tests__/profiler-phase-catalog.test.ts +5 -0
  392. package/src/__tests__/public-surface.test-d.ts +3 -0
  393. package/src/__tests__/recovery-candidate-prepare.contract.test.ts +30 -12
  394. package/src/__tests__/reflection-fallback-async-publication.unit.test.ts +104 -0
  395. package/src/__tests__/reflection-probe-record-owner.characterization.unit.test.ts +2 -1
  396. package/src/__tests__/render-error-code-owner.test-d.ts +4 -0
  397. package/src/__tests__/render-error-detail-ssot.test-d.ts +22 -0
  398. package/src/__tests__/render-error-exhaustive.test-d.ts +15 -0
  399. package/src/__tests__/render-feature-gpu-work.integration.test.ts +285 -2
  400. package/src/__tests__/render-feature-plan.unit.test.ts +245 -0
  401. package/src/__tests__/render-feature-shadow-order.unit.test.ts +228 -0
  402. package/src/__tests__/render-feature-zero-work.unit.test.ts +35 -1
  403. package/src/__tests__/render-graph-fallback.integration.test.ts +2 -2
  404. package/src/__tests__/render-owner-cohesion.unit.test.ts +55 -0
  405. package/src/__tests__/render-scene-mixed-updates.integration.test.ts +426 -0
  406. package/src/__tests__/render-scene-projection.unit.test.ts +914 -23
  407. package/src/__tests__/render-scene-resource-updates.integration.test.ts +214 -0
  408. package/src/__tests__/render-scene-skin-lifecycle.integration.test.ts +173 -0
  409. package/src/__tests__/render-scene-temporal-retry.unit.test.ts +133 -0
  410. package/src/__tests__/render-target-material-source.test-d.ts +27 -3
  411. package/src/__tests__/renderer-factory-material-contract.unit.test.ts +61 -1
  412. package/src/__tests__/renderer-frame-transaction.integration.test.ts +923 -5
  413. package/src/__tests__/shader-manifest-fixture.ts +39 -0
  414. package/src/__tests__/skin-motion-regression.unit.test.ts +0 -33
  415. package/src/__tests__/skin-palette-persistent.integration.test.ts +28 -0
  416. package/src/__tests__/skin-palette-persistent.unit.test.ts +247 -0
  417. package/src/__tests__/skinned-shadow-caster.test.ts +118 -14
  418. package/src/__tests__/specular-environment-scale.dawn.test.ts +156 -0
  419. package/src/__tests__/ssr-admission-matrix.integration.test.ts +7 -1
  420. package/src/__tests__/ssr-confidence-compose.unit.test.ts +84 -0
  421. package/src/__tests__/ssr-coordinate-oracle.unit.test.ts +28 -0
  422. package/src/__tests__/ssr-format-probe-gating.integration.test.ts +82 -0
  423. package/src/__tests__/ssr-gpu-dispatch.browser.test.ts +91 -0
  424. package/src/__tests__/ssr-gpu-dispatch.dawn.test.ts +112 -0
  425. package/src/__tests__/ssr-gpu-dispatch.ts +946 -0
  426. package/src/__tests__/ssr-graph-topology.unit.test.ts +104 -0
  427. package/src/__tests__/ssr-history-filter.dawn.test.ts +198 -0
  428. package/src/__tests__/ssr-hit-color.dawn.test.ts +273 -0
  429. package/src/__tests__/ssr-hiz-odd-reduction.dawn.test.ts +569 -0
  430. package/src/__tests__/ssr-hiz-rescue.dawn.test.ts +279 -0
  431. package/src/__tests__/ssr-hiz.browser.test.ts +150 -0
  432. package/src/__tests__/ssr-hiz.dawn.test.ts +150 -0
  433. package/src/__tests__/ssr-hiz.unit.test.ts +56 -0
  434. package/src/__tests__/ssr-inspection.unit.test.ts +105 -0
  435. package/src/__tests__/ssr-memory-budget.unit.test.ts +45 -0
  436. package/src/__tests__/ssr-performance-sequence-aggregation.unit.test.ts +15 -0
  437. package/src/__tests__/ssr-public-surface.test-d.ts +37 -0
  438. package/src/__tests__/ssr-receiver-coverage.dawn.test.ts +387 -0
  439. package/src/__tests__/ssr-reflection-mip-reducer.dawn.test.ts +309 -0
  440. package/src/__tests__/ssr-reprojection-probe.ts +90 -0
  441. package/src/__tests__/ssr-roughness-mip.dawn.test.ts +163 -0
  442. package/src/__tests__/ssr-shader-prewarm.unit.test.ts +59 -0
  443. package/src/__tests__/ssr-spatial.integration.test.ts +141 -0
  444. package/src/__tests__/ssr-stable-lattice.dawn.test.ts +475 -0
  445. package/src/__tests__/ssr-temporal-history.unit.test.ts +202 -0
  446. package/src/__tests__/ssr-texel-crossing.dawn.test.ts +448 -0
  447. package/src/__tests__/ssr-zero-work.integration.test.ts +87 -0
  448. package/src/__tests__/standard-cluster-variant-resolution.unit.test.ts +44 -12
  449. package/src/__tests__/standard-clustered-pipeline.unit.test.ts +29 -0
  450. package/src/__tests__/standard-output-chain-contract.unit.test.ts +99 -0
  451. package/src/__tests__/standard-output-chain.integration.test.ts +73 -0
  452. package/src/__tests__/standard-output-gpu-evidence.ts +205 -0
  453. package/src/__tests__/standard-output.browser.test.ts +15 -0
  454. package/src/__tests__/standard-output.dawn.test.ts +15 -0
  455. package/src/__tests__/standard-pbr-artifact-assembly.unit.test.ts +38 -0
  456. package/src/__tests__/standard-pipeline.integration.test.ts +320 -3
  457. package/src/__tests__/standard-profile-oracle.unit.test.ts +22 -0
  458. package/src/__tests__/standard-surface-lighting.unit.test.ts +41 -0
  459. package/src/__tests__/standard-texture-specialization.dawn.test.ts +141 -0
  460. package/src/__tests__/surface-adjacent-regressions.integration.test.ts +6 -3
  461. package/src/__tests__/taa-coverage-edge.dawn.test.ts +193 -0
  462. package/src/__tests__/taa-history-clipping.dawn.test.ts +328 -0
  463. package/src/__tests__/taa-neighborhood-equivalence.dawn.test.ts +778 -0
  464. package/src/__tests__/taa-stability-feedback.browser.test.ts +23 -0
  465. package/src/__tests__/taa-stability-feedback.dawn.test.ts +68 -0
  466. package/src/__tests__/taa-stability-feedback.ts +341 -0
  467. package/src/__tests__/temporal-history-lifecycle.unit.test.ts +49 -7
  468. package/src/__tests__/temporal-reset-matrix.unit.test.ts +9 -0
  469. package/src/__tests__/temporal-transaction.integration.test.ts +28 -0
  470. package/src/__tests__/texture-usage-surface.unit.test.ts +4 -2
  471. package/src/__tests__/typed-pipeline-topology.unit.test.ts +109 -4
  472. package/src/__tests__/typed-scene-pass-material-cache.integration.test.ts +126 -0
  473. package/src/__tests__/vertex-layouts-ssot.unit.test.ts +47 -0
  474. package/src/__tests__/visibility-extract.perf.test.ts +20 -8
  475. package/src/__tests__/visibility-instances.integration.test.ts +19 -7
  476. package/src/__tests__/visibility-producer-matrix.integration.test.ts +20 -9
  477. package/src/assembly/atmosphere-shader-prewarm.ts +38 -0
  478. package/src/assembly/dynamic-geometry-host.ts +887 -0
  479. package/src/assembly/dynamic-geometry-runtime.ts +71 -0
  480. package/src/assembly/factory.ts +4 -0
  481. package/src/assembly/gpu-driven-owner.ts +29 -0
  482. package/src/assembly/host-contract.ts +36 -0
  483. package/src/assembly/material/assembly.ts +38 -0
  484. package/src/assembly/material-shader-policy.ts +71 -28
  485. package/src/assembly/recovery/failure-location.ts +62 -0
  486. package/src/assembly/recovery/renderer-recover.ts +19 -17
  487. package/src/assembly/renderer-facade.ts +29 -0
  488. package/src/assembly/renderer-feature-inspection.ts +13 -0
  489. package/src/assembly/renderer-frame-transaction.ts +63 -0
  490. package/src/assembly/renderer-helpers.ts +26 -0
  491. package/src/assembly/renderer-inspection.ts +6 -0
  492. package/src/assembly/shader-prewarm-policy.ts +32 -0
  493. package/src/assembly/skin-palette-owner.ts +21 -0
  494. package/src/assembly/ssr-shader-prewarm.ts +41 -0
  495. package/src/assembly/webgpu-device-loss.ts +75 -0
  496. package/src/assembly/webgpu-pbr-ready.ts +232 -0
  497. package/src/assembly/webgpu-ready-mipmap.ts +26 -0
  498. package/src/assembly/webgpu-ready.ts +142 -121
  499. package/src/assembly/webgpu-renderer-bootstrap.ts +85 -0
  500. package/src/assembly/webgpu-renderer-contract.ts +69 -0
  501. package/src/assembly/webgpu-renderer-guards.ts +32 -0
  502. package/src/assembly/webgpu-renderer-observation.ts +43 -0
  503. package/src/assembly/webgpu-renderer-recovery-failure.ts +62 -0
  504. package/src/assembly/webgpu-renderer.ts +675 -614
  505. package/src/assembly/webgpu-vertex-layouts.ts +233 -87
  506. package/src/components/__tests__/camera-exposure.contract.unit.test.ts +153 -0
  507. package/src/components/camera.ts +287 -7
  508. package/src/components/index.ts +1 -0
  509. package/src/components/instances.ts +45 -18
  510. package/src/components/scene-instance.ts +2 -1
  511. package/src/components/screen-space-reflection.ts +21 -0
  512. package/src/components/sprite-animation.ts +3 -3
  513. package/src/components/sprite-instances.ts +1 -1
  514. package/src/device/gpu-residency.ts +96 -93
  515. package/src/device/mesh-residency-lifetime.ts +73 -0
  516. package/src/dynamic-geometry.ts +736 -0
  517. package/src/environment/background.ts +263 -0
  518. package/src/errors/gpu-driven.ts +39 -5
  519. package/src/errors/render.ts +27 -3
  520. package/src/extract/camera.ts +26 -1
  521. package/src/extract/gpu-driven.ts +185 -10
  522. package/src/features/host.ts +237 -19
  523. package/src/features/noise-texture.ts +91 -0
  524. package/src/features/plan.ts +553 -24
  525. package/src/features/prepared-gpu-work.ts +631 -49
  526. package/src/features/prepared-graphics-store.ts +31 -0
  527. package/src/features/prepared-graphics.ts +5 -0
  528. package/src/features/render-graph-compute.ts +41 -8
  529. package/src/features/render-graph-contribution.ts +141 -3
  530. package/src/features/render-graph-raster.ts +143 -30
  531. package/src/features/types.ts +11 -0
  532. package/src/fullscreen-post-process-pass.ts +3 -0
  533. package/src/gpu-driven/batch-topology.ts +424 -102
  534. package/src/gpu-driven/pbr-program.ts +31 -0
  535. package/src/gpu-driven/prepared-draw.ts +29 -4
  536. package/src/gpu-driven/production-raster.ts +2267 -694
  537. package/src/gpu-driven/shadow-views.ts +539 -0
  538. package/src/gpu-driven/view-gpu.ts +188 -41
  539. package/src/gpu-scene-schema.ts +52 -11
  540. package/src/gpu-scene.ts +186 -99
  541. package/src/gpu-texture-usage.ts +1 -0
  542. package/src/hdrp-buffers.ts +72 -36
  543. package/src/ibl/frame-resources.ts +38 -0
  544. package/src/ibl/skylight-bind-group.ts +5 -4
  545. package/src/index.ts +125 -1
  546. package/src/inspection-types.ts +191 -0
  547. package/src/instances-derived-bounds.ts +258 -34
  548. package/src/instances.ts +459 -150
  549. package/src/internal.ts +22 -0
  550. package/src/lifecycle.ts +2 -0
  551. package/src/material-row.ts +145 -0
  552. package/src/materials.ts +44 -8
  553. package/src/pbr-pipeline.ts +159 -17
  554. package/src/pipeline/standard-forward-lane.ts +52 -24
  555. package/src/pipeline/standard-output/__tests__/lut-admission.unit.test.ts +103 -0
  556. package/src/pipeline/standard-output/auto-exposure/__tests__/capability.unit.test.ts +40 -0
  557. package/src/pipeline/standard-output/auto-exposure/__tests__/gpu.contract.unit.test.ts +125 -0
  558. package/src/pipeline/standard-output/auto-exposure/__tests__/graph.contract.unit.test.ts +94 -0
  559. package/src/pipeline/standard-output/auto-exposure/__tests__/oracle.test.ts +108 -0
  560. package/src/pipeline/standard-output/auto-exposure/__tests__/preset.test.ts +26 -0
  561. package/src/pipeline/standard-output/auto-exposure/capability.ts +38 -0
  562. package/src/pipeline/standard-output/auto-exposure/gpu.ts +567 -0
  563. package/src/pipeline/standard-output/auto-exposure/graph.ts +221 -0
  564. package/src/pipeline/standard-output/auto-exposure/inspection.ts +231 -0
  565. package/src/pipeline/standard-output/auto-exposure/oracle.ts +291 -0
  566. package/src/pipeline/standard-output/auto-exposure/preset.ts +28 -0
  567. package/src/pipeline/standard-output/auto-exposure/state.ts +156 -0
  568. package/src/pipeline/standard-output/color-transform.ts +124 -0
  569. package/src/pipeline/standard-output/graph.ts +139 -0
  570. package/src/pipeline/standard-output/lut-admission.ts +121 -0
  571. package/src/pipeline/standard-output/lut-gpu.ts +399 -0
  572. package/src/pipeline/standard-output/lut-state.ts +176 -0
  573. package/src/pipeline/standard-output/resources.ts +121 -0
  574. package/src/pipeline/standard-output/types.ts +58 -0
  575. package/src/pipeline/standard-pipeline.ts +192 -59
  576. package/src/pipeline/standard-post.ts +186 -27
  577. package/src/pipeline-builder.ts +16 -6
  578. package/src/pipeline-spec-types.ts +4 -0
  579. package/src/pipeline-spec.ts +37 -29
  580. package/src/points-lines/standard-owner.ts +6 -14
  581. package/src/prepare/prepared-graphics-resolver.ts +9 -2
  582. package/src/record/__tests__/material-slot-plan.unit.test.ts +48 -0
  583. package/src/record/__tests__/mesh-ssbo.unit.test.ts +40 -1
  584. package/src/record/__tests__/recovery-pipeline.unit.test.ts +2 -29
  585. package/src/record/__tests__/sprite-material-abi.unit.test.ts +36 -5
  586. package/src/record/__tests__/typed-frame-graph-plan.unit.test.ts +45 -0
  587. package/src/record/__tests__/view-ubo-layout.unit.test.ts +5 -0
  588. package/src/record/dynamic-geometry-consumption.ts +59 -0
  589. package/src/record/frame-lighting.ts +12 -11
  590. package/src/record/frame-snapshot.ts +133 -23
  591. package/src/record/frame-targets.ts +9 -0
  592. package/src/record/frame.ts +727 -133
  593. package/src/record/main-pass-geometry.ts +162 -98
  594. package/src/record/main-pass-material.ts +115 -26
  595. package/src/record/main-pass-sprite-draws.ts +18 -107
  596. package/src/record/main-pass.ts +78 -8
  597. package/src/record/material-uniforms.ts +1 -1
  598. package/src/record/mesh-ssbo.ts +59 -15
  599. package/src/record/prepared-material-bindings.ts +234 -0
  600. package/src/record/recovery-pipeline.ts +59 -136
  601. package/src/record/render-context.ts +92 -22
  602. package/src/record/shadow-pass.ts +1231 -209
  603. package/src/record/skybox-post-pass.ts +14 -3
  604. package/src/record/typed-frame-graph.ts +1006 -53
  605. package/src/record/view-ubo.ts +11 -0
  606. package/src/recovery/render-system-candidate.ts +90 -66
  607. package/src/recovery/render-system-roots.ts +113 -0
  608. package/src/recovery/types.ts +77 -0
  609. package/src/reflection/record-owner.ts +26 -54
  610. package/src/render-contract.ts +219 -25
  611. package/src/render-graph-primitives.ts +6 -2
  612. package/src/render-pipeline.ts +70 -1
  613. package/src/render-system-extract-tail.ts +409 -160
  614. package/src/render-system-extract.ts +376 -102
  615. package/src/render-system-projections.ts +241 -0
  616. package/src/render-system.ts +692 -791
  617. package/src/scene/render-scene-types.ts +13 -4
  618. package/src/scene/render-scene.ts +569 -355
  619. package/src/scene/shadow-visibility.ts +21 -0
  620. package/src/ssr/admission.ts +380 -14
  621. package/src/ssr/compose.ts +144 -0
  622. package/src/ssr/errors.ts +48 -0
  623. package/src/ssr/graph.ts +777 -0
  624. package/src/ssr/history.ts +436 -0
  625. package/src/ssr/hiz.ts +124 -0
  626. package/src/ssr/inspection.ts +124 -0
  627. package/src/ssr/resources.ts +202 -0
  628. package/src/ssr/temporal.ts +164 -0
  629. package/src/systems/skin-palette-allocator.ts +495 -148
  630. package/src/systems/skin-palette-types.ts +17 -0
  631. package/src/systems/transparent-dispatch.ts +90 -0
  632. package/src/targets/material-source.ts +34 -0
  633. package/src/temporal/coverage.ts +1 -1
  634. package/src/temporal/frame-coordinator.ts +23 -2
  635. package/src/temporal/gpu.ts +74 -12
  636. package/src/temporal/history.ts +3 -1
  637. package/src/temporal/inspection.ts +2 -1
  638. package/src/temporal/standard-scene-data.ts +3 -1
  639. package/src/temporal/temporal-view.ts +3 -0
  640. package/src/transmission/__tests__/standard-transmission.dawn.test.ts +204 -139
  641. package/src/typed-render-graph-primitives.ts +266 -74
  642. package/src/typed-shadow-passes.ts +122 -12
  643. package/src/volume/temporal.ts +10 -0
  644. package/dist/chunk-ADHHVYLW.mjs.map +0 -1
  645. package/dist/chunk-FJ6P52EE.mjs.map +0 -1
  646. package/dist/chunk-GE3SDD72.mjs.map +0 -1
  647. package/dist/chunk-GF523LHF.mjs.map +0 -1
  648. package/dist/chunk-HKXTW355.mjs.map +0 -1
  649. package/dist/chunk-JDWARUOI.mjs.map +0 -1
  650. package/dist/chunk-KM2NOX2I.mjs.map +0 -1
  651. package/dist/chunk-TMIASV2N.mjs.map +0 -1
  652. package/dist/chunk-XDP6XE5V.mjs.map +0 -1
  653. package/dist/chunk-ZZ4YQ474.mjs.map +0 -1
  654. package/src/__tests__/instances-world-ownership.integration.test.ts +0 -62
  655. package/src/scene/__tests__/changed-block-summary.candidate.test.ts +0 -36
@@ -5,14 +5,12 @@ import {
5
5
  } from '@forgeax/engine-assets-runtime';
6
6
  import type { EntityHandle, World } from '@forgeax/engine-ecs';
7
7
  import {
8
- InstanceTransformsStrideMismatchError,
9
8
  routeWorldError,
10
9
  SpawnLightInvalidBoundsError,
11
10
  SpriteInstancesCountMismatchError,
12
11
  SpriteInstancesMutuallyExclusiveWithInstancesError,
13
12
  SpriteInstancesRequiresSpriteShaderError,
14
13
  } from '@forgeax/engine-ecs/projection';
15
- import { deriveVertexLayoutProjection } from '@forgeax/engine-geometry';
16
14
  import { box3, frustum, type Mat4, mat4, type Vec3, vec3 } from '@forgeax/engine-math';
17
15
  import { AssetGuid } from '@forgeax/engine-pack/guid';
18
16
  import type { MaterialCookProgramContext } from '@forgeax/engine-pack/material-cook';
@@ -39,6 +37,7 @@ import {
39
37
  ASSET_ERROR_HINTS,
40
38
  AssetError,
41
39
  derive,
40
+ err,
42
41
  handleSlot,
43
42
  materialValuesToLinearRuntime,
44
43
  toShared,
@@ -81,16 +80,23 @@ import {
81
80
  import { selectEnvironment } from './environment/frame';
82
81
  import { MaterialSkinAttrMissingError, SkinMaterialMismatchError } from './errors/render';
83
82
  import type { EnvironmentCandidate, EnvironmentFrame } from './extract/environment';
84
- import { buildGpuDrivenDraws, resolvePreparedGpuDrivenDraw } from './extract/gpu-driven';
83
+ import {
84
+ buildGpuDrivenDraws,
85
+ gpuDrivenShadowDrawKey,
86
+ gpuDrivenSourceDrawItemIndex,
87
+ } from './extract/gpu-driven';
85
88
  import type { VisibilitySnapshot } from './extract/visibility';
86
89
  import type {
87
90
  RenderFeatureHiddenEntityReport,
88
91
  RenderFeatureWorldVisibilitySnapshot,
89
92
  } from './features/types';
90
93
  import { ensureGlyphMeshMaterialSlots } from './glyph-text-layout-system';
91
- import type { GpuDrivenGeometryReceipt } from './gpu-driven/prepared-draw';
92
- import type { InstanceProjectionStore } from './instances';
93
- import { deriveInstancesUnionBounds, fingerprintNumericArray } from './instances-derived-bounds';
94
+ import {
95
+ InstanceCollectionError,
96
+ type InstanceCollectionId,
97
+ type InstanceCollectionStore,
98
+ } from './instances';
99
+ import { deriveInstancesUnionBounds } from './instances-derived-bounds';
94
100
  import {
95
101
  type MeshMaterialBindingDiagnostic,
96
102
  type MeshMaterialBindingSource,
@@ -101,6 +107,7 @@ import { expandPointsLinesBounds } from './points-lines/bounds';
101
107
  import type { PointsLinesRetainedSnapshot, PointsLinesStyle } from './points-lines/snapshot';
102
108
  import { COOKIE_SLICE_CAPACITY, IES_SLICE_CAPACITY } from './prepare/extended-lighting/resources';
103
109
  import { prepareCookieProjection } from './prepare/extended-lighting/spot-modifiers';
110
+ import { worldEntityKey } from './record/frame-snapshot';
104
111
  import { STANDARD_OUTPUT_TRANSFORM_FEATURE_ID } from './render-contract';
105
112
  import type {
106
113
  CsmCameraData,
@@ -116,12 +123,15 @@ import type {
116
123
  MaterialSnapshot,
117
124
  MaterialSnapshotCache,
118
125
  MaterialSnapshotCachesByWorld,
126
+ MaterialTextureSourceCache,
127
+ MaterialTextureSourceStats,
119
128
  MorphSnapshot,
120
129
  PointLightSnapshot,
121
130
  PointShadowSnapshot,
122
131
  PreparedExtractContext,
123
132
  RectAreaDirectLightSnapshot,
124
133
  RenderableSnapshot,
134
+ ShadowCasterMembership,
125
135
  SkyboxSnapshot,
126
136
  SkylightSnapshot,
127
137
  SpotLightSnapshot,
@@ -134,6 +144,8 @@ import {
134
144
  BUILTIN_USER_REGION_TEXTURE_FIELDS,
135
145
  buildPointShadowMatrices,
136
146
  buildRectAreaWorldFrame,
147
+ collectAuthoredMaterialSamplerFields,
148
+ collectAuthoredMaterialTextureFields,
137
149
  collectCubeCameraSnapshots,
138
150
  collectMaterialTextureCoordinates,
139
151
  collectMaterialTextureSamplers,
@@ -150,8 +162,10 @@ import {
150
162
  materialNormalScale,
151
163
  materialParamSchemaForMaterial,
152
164
  materialProgramKeysForMaterial,
165
+ materialStandardTextureMask,
153
166
  materialTextureFields,
154
167
  materialTextureRef,
168
+ materialTextureSourceFields,
155
169
  pipelineRenderState,
156
170
  prepareExtractContext,
157
171
  pssmSplit,
@@ -163,12 +177,19 @@ import {
163
177
  Severity,
164
178
  selectCameraRoles,
165
179
  selectFogFrame,
180
+ shadowCasterCpuReason,
166
181
  sortDispatchByQueue,
167
182
  storeMaterialSnapshot,
168
183
  tonemapParams,
169
184
  } from './render-system-extract';
185
+ import { buildShadowFrusta } from './scene/shadow-visibility';
170
186
  import { SHADOW_ATLAS_DEFAULT_LAYERS } from './shadow-atlas';
171
- import type { SkinPaletteSlice } from './systems/skin-palette-types';
187
+ import type { SkinPaletteReceipt } from './systems/skin-palette-types';
188
+
189
+ function skinPaletteIdentity(world: World, entity: number): string {
190
+ return `skin:${world.identity}:${entity}`;
191
+ }
192
+
172
193
  export function extractFrames(
173
194
  worlds: readonly World[],
174
195
  owner: number | ExtractFramesOwner,
@@ -178,13 +199,19 @@ export function extractFrames(
178
199
  options: {
179
200
  readonly materialContext?: MaterialCookProgramContext;
180
201
  readonly cull?: 'normal' | 'none';
181
- readonly renderables?: 'full' | 'none';
182
- readonly renderableEntitiesByWorld?: readonly (ReadonlySet<number> | undefined)[];
202
+ readonly renderables?:
203
+ | 'full'
204
+ | 'none'
205
+ | {
206
+ readonly kind: 'partial';
207
+ readonly entitiesByWorld: readonly (ReadonlySet<number> | undefined)[];
208
+ };
183
209
  readonly retainHidden?: boolean;
184
210
  readonly getMaterialShaderArtifact?: (
185
211
  materialShaderId: string,
212
+ vertexColorAvailable?: boolean,
186
213
  ) => MaterialShaderArtifact | undefined;
187
- readonly instanceCollections?: InstanceProjectionStore;
214
+ readonly instanceCollections?: InstanceCollectionStore;
188
215
  } = {},
189
216
  ): ExtractedFrame {
190
217
  // w4: normalize the owner argument. A bare number is the legacy single-owner
@@ -193,6 +220,12 @@ export function extractFrames(
193
220
  // single-owner behaviour (w1 contract combination 2).
194
221
  const cameraOwner = typeof owner === 'number' ? owner : owner.cameraOwner;
195
222
  const resourceOwner = typeof owner === 'number' ? owner : owner.resourceOwner;
223
+ const cameraEntityKey = typeof owner === 'number' ? undefined : owner.cameraEntityKey;
224
+ const renderableRequest = options.renderables ?? 'full';
225
+ const renderableMode = typeof renderableRequest === 'object' ? 'full' : renderableRequest;
226
+ const partialEntitiesByWorld =
227
+ typeof renderableRequest === 'object' ? renderableRequest.entitiesByWorld : undefined;
228
+ const retainHidden = options.retainHidden ?? typeof renderableRequest === 'object';
196
229
 
197
230
  // ── D-2: frame-level side effects live here ────────────────────────────
198
231
  //
@@ -201,7 +234,14 @@ export function extractFrames(
201
234
  // extract runs, so sequential per-world allocation yields non-overlapping
202
235
  // palette slices (AC-08).
203
236
  const skinPaletteAllocator = pipelineState?.skinPaletteAllocator ?? null;
204
- if (skinPaletteAllocator !== null) {
237
+ // A partial request is still normalized to the producer's full renderable
238
+ // query, but it is not a full allocator reconciliation. beginFrame/endFrame
239
+ // mark every persistent identity not visited by this selected subset as
240
+ // detached; doing that here would release untouched palettes. Only the
241
+ // explicit full request is allowed to reconcile the identity set.
242
+ const reconcileSkinFrame = renderableRequest === 'full';
243
+ if (skinPaletteAllocator !== null && reconcileSkinFrame) {
244
+ skinPaletteAllocator.beginFrame();
205
245
  skinPaletteAllocator.resetForFrame();
206
246
  }
207
247
 
@@ -237,6 +277,13 @@ export function extractFrames(
237
277
  const world = worlds[wi];
238
278
  if (world === undefined || failedWorlds.has(world)) continue;
239
279
  try {
280
+ if (skinPaletteAllocator !== null && partialEntitiesByWorld !== undefined) {
281
+ for (const entityKey of partialEntitiesByWorld[wi] ?? []) {
282
+ if (!world.hasComponent(entityKey as EntityHandle, Skin)) {
283
+ skinPaletteAllocator.releasePersistentSlice(skinPaletteIdentity(world, entityKey));
284
+ }
285
+ }
286
+ }
240
287
  const isCameraOwner = wi === cameraOwner;
241
288
  const cameraOwnerFrame = framesByWorld.get(cameraOwner);
242
289
  const prepared = prepareExtractContext(world, {
@@ -258,11 +305,13 @@ export function extractFrames(
258
305
  ? {}
259
306
  : { materialContext: options.materialContext }),
260
307
  cull: options.cull === 'none' ? 'none' : isCameraOwner ? 'self' : 'external',
261
- renderables: options.renderables ?? 'full',
262
- ...(options.renderableEntitiesByWorld?.[wi] === undefined
308
+ renderables: renderableMode,
309
+ ...(isCameraOwner && cameraEntityKey !== undefined ? { cameraEntityKey } : {}),
310
+ ...(partialEntitiesByWorld?.[wi] === undefined
263
311
  ? {}
264
- : { renderableEntities: options.renderableEntitiesByWorld[wi] }),
265
- retainHidden: options.retainHidden ?? false,
312
+ : { renderableEntities: partialEntitiesByWorld[wi] }),
313
+ retainHidden,
314
+ worldId: wi,
266
315
  ...(options.getMaterialShaderArtifact === undefined
267
316
  ? {}
268
317
  : { getMaterialShaderArtifact: options.getMaterialShaderArtifact }),
@@ -287,6 +336,13 @@ export function extractFrames(
287
336
  }
288
337
  }
289
338
 
339
+ // Reconcile persistent palette identities from the extracted producer
340
+ // projection. Identities absent after a detach are fence-retired by the
341
+ // allocator; surviving identities keep their stable slices.
342
+ if (skinPaletteAllocator !== null && reconcileSkinFrame) {
343
+ skinPaletteAllocator.endFrame();
344
+ }
345
+
290
346
  // Restore the caller's world order after the camera-owner-first extraction.
291
347
  for (let wi = 0; wi < worlds.length; wi++) {
292
348
  const frame = framesByWorld.get(wi);
@@ -300,6 +356,9 @@ export function extractFrames(
300
356
  // AC-04: renderables — concat by worlds[] order, stamp worldId.
301
357
  const renderables: RenderableSnapshot[] = [];
302
358
  const dispatchEntries: DispatchEntry[] = [];
359
+ const shadowCasterEntityKeys = new Set<number>();
360
+ const shadowCasterDrawKeys = new Set<string>();
361
+ const shadowCasterMembership = new Map<string, ShadowCasterMembership>();
303
362
  const visibilitySnapshots: VisibilitySnapshot[] = [];
304
363
  const featureVisibilitySnapshots: RenderFeatureWorldVisibilitySnapshot[] = [];
305
364
  const hiddenEntityReports: RenderFeatureHiddenEntityReport[] = [];
@@ -330,6 +389,18 @@ export function extractFrames(
330
389
  for (const d of f.dispatch) {
331
390
  dispatchEntries.push({ ...d, renderableIndex: (d.renderableIndex ?? 0) + base });
332
391
  }
392
+ for (const key of f.shadowCasterEntityKeys) shadowCasterEntityKeys.add(key);
393
+ for (const key of f.shadowCasterDrawKeys) shadowCasterDrawKeys.add(key);
394
+ for (const membership of f.shadowCasterMembership ?? []) {
395
+ const rebased = { ...membership, renderableIndex: membership.renderableIndex + base };
396
+ const key = gpuDrivenShadowDrawKey(
397
+ rebased.worldEntity,
398
+ rebased.materialHandle,
399
+ rebased.drawItemIndex,
400
+ rebased.passIndex,
401
+ );
402
+ shadowCasterMembership.set(key, rebased);
403
+ }
333
404
  }
334
405
 
335
406
  // Stable sort dispatch by queue value.
@@ -359,11 +430,14 @@ export function extractFrames(
359
430
  // camera may live in different worlds (editor super-composite).
360
431
  let directionalCsmConfig: DirectionalCsmConfig | undefined;
361
432
  let directionalCsmDirection: Vec3 | undefined;
362
- for (const f of succeededFrames) {
363
- for (const p of f.lights.point) point.push(p);
364
- for (const s of f.lights.spot) spot.push(s);
433
+ for (let fi = 0; fi < succeededFrames.length; fi += 1) {
434
+ const f = succeededFrames[fi];
435
+ const wId = succeededIndices[fi] ?? 0;
436
+ if (f === undefined) continue;
437
+ for (const p of f.lights.point) point.push({ ...p, worldId: wId });
438
+ for (const s of f.lights.spot) spot.push({ ...s, worldId: wId });
365
439
  for (const r of f.lights.rect) rect.push(r);
366
- for (const ps of f.lights.pointShadow) pointShadow.push(ps);
440
+ for (const ps of f.lights.pointShadow) pointShadow.push({ ...ps, worldId: wId });
367
441
  if (directional === undefined && f.lights.directional !== undefined) {
368
442
  directional = f.lights.directional;
369
443
  // Carry CSM shadow fields from the first-hit directional's world.
@@ -393,9 +467,10 @@ export function extractFrames(
393
467
  if (succeededIndices[fi] === cameraOwner) cameraOwnerFrame = succeededFrames[fi];
394
468
  if (succeededIndices[fi] === resourceOwner) resourceOwnerFrame = succeededFrames[fi];
395
469
  }
396
- const cameras = cameraOwnerFrame !== undefined ? [...cameraOwnerFrame.cameras] : [];
470
+ const cameras =
471
+ cameraOwnerFrame?.cameras.map((camera) => ({ ...camera, worldId: cameraOwner })) ?? [];
397
472
  const auxiliaryCameras =
398
- cameraOwnerFrame !== undefined ? [...cameraOwnerFrame.auxiliaryCameras] : [];
473
+ cameraOwnerFrame?.auxiliaryCameras.map((camera) => ({ ...camera, worldId: cameraOwner })) ?? [];
399
474
  const cubeCameras = cameraOwnerFrame !== undefined ? [...cameraOwnerFrame.cubeCameras] : [];
400
475
 
401
476
  // bug-20260710-editor-cross-world-shadow: RECOMPUTE directional CSM matrices
@@ -460,6 +535,7 @@ export function extractFrames(
460
535
  directionalCsmConfig,
461
536
  directionalCsmDirection,
462
537
  };
538
+
463
539
  const volumetricFog = resourceOwnerFrame?.volumetricFog;
464
540
  const skylight = resourceOwnerFrame?.skylight;
465
541
  const skylightCount = resourceOwnerFrame?.skylightCount ?? 0;
@@ -468,7 +544,10 @@ export function extractFrames(
468
544
  const fog = resourceOwnerFrame?.fog;
469
545
  const fogFailure = resourceOwnerFrame?.fogFailure;
470
546
  const environment = resourceOwnerFrame?.environment;
471
- const lightProbes = succeededFrames.flatMap((frame) => frame.lightProbes ?? []);
547
+ const lightProbes = succeededFrames.flatMap((frame, index) => {
548
+ const worldId = succeededIndices[index] ?? 0;
549
+ return (frame.lightProbes ?? []).map((probe) => ({ ...probe, worldId }));
550
+ });
472
551
  // User-authored PostProcessParams entities are singleton scene resources, so
473
552
  // they come from the resourceOwner world (holistic snapshot selection).
474
553
  const postProcessParams = new Map(resourceOwnerFrame?.postProcessParams);
@@ -507,35 +586,30 @@ export function extractFrames(
507
586
  const visibilityStats = {
508
587
  explicitlyHidden: succeededFrames.reduce((s, f) => s + f.visibilityStats.explicitlyHidden, 0),
509
588
  };
589
+ const materialTextureSources: MaterialTextureSourceStats = {
590
+ sourceFieldsVisited: 0,
591
+ numericSharedRefProbes: 0,
592
+ sourceCacheHits: 0,
593
+ sourceCacheMisses: 0,
594
+ producerRoutes: {},
595
+ };
596
+ for (const frame of succeededFrames) {
597
+ const stats = frame.materialTextureSources;
598
+ if (stats === undefined) continue;
599
+ materialTextureSources.sourceFieldsVisited += stats.sourceFieldsVisited;
600
+ materialTextureSources.numericSharedRefProbes += stats.numericSharedRefProbes;
601
+ materialTextureSources.sourceCacheHits += stats.sourceCacheHits;
602
+ materialTextureSources.sourceCacheMisses += stats.sourceCacheMisses;
603
+ for (const [route, count] of Object.entries(stats.producerRoutes)) {
604
+ materialTextureSources.producerRoutes[route] =
605
+ (materialTextureSources.producerRoutes[route] ?? 0) + count;
606
+ }
607
+ }
510
608
  const reflectionProbes = succeededFrames.flatMap((f, index) => {
511
609
  const worldId = succeededIndices[index] ?? 0;
512
610
  return (f.reflectionProbes ?? []).map((probe) => ({ ...probe, worldId }));
513
611
  });
514
612
 
515
- if (options.renderables !== 'none' && options.renderableEntitiesByWorld === undefined) {
516
- options.instanceCollections?.retain(
517
- new Set(
518
- renderables.flatMap((row) =>
519
- row.instances?.collectionId === undefined ? [] : [row.instances.collectionId],
520
- ),
521
- ),
522
- );
523
- if (
524
- skinPaletteAllocator !== null &&
525
- typeof skinPaletteAllocator.retainSliceKeys === 'function'
526
- ) {
527
- skinPaletteAllocator.retainSliceKeys(
528
- new Set(
529
- renderables.flatMap((row) =>
530
- row.skin === undefined
531
- ? []
532
- : [`${worlds[row.worldId]?.identity ?? row.worldId}:${row.entityKey}`],
533
- ),
534
- ),
535
- );
536
- }
537
- }
538
-
539
613
  return {
540
614
  cameras,
541
615
  auxiliaryCameras,
@@ -547,6 +621,11 @@ export function extractFrames(
547
621
  : { volumetricFog: { ...volumetricFog, worldId: resourceOwner } }),
548
622
  renderables,
549
623
  dispatch: dispatchEntries,
624
+ shadowCasterEntityKeys,
625
+ shadowCasterDrawKeys,
626
+ ...(shadowCasterMembership.size === 0
627
+ ? {}
628
+ : { shadowCasterMembership: [...shadowCasterMembership.values()] }),
550
629
  skylight,
551
630
  skylightCount,
552
631
  lightProbes,
@@ -558,6 +637,7 @@ export function extractFrames(
558
637
  environmentReady: resourceOwnerFrame !== undefined && environment !== undefined,
559
638
  frustumStats,
560
639
  visibilityStats,
640
+ materialTextureSources,
561
641
  postProcessParams,
562
642
  visibilitySnapshots,
563
643
  featureVisibilitySnapshots,
@@ -656,6 +736,14 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
656
736
  const fogFailure = fogState(world).failure;
657
737
  const visibility = context.visibility.hasAnyHiddenIntent ? context.visibility : undefined;
658
738
  const skinPaletteAllocator = pipelineState?.skinPaletteAllocator ?? null;
739
+ const materialTextureSourceStats: MaterialTextureSourceStats = {
740
+ sourceFieldsVisited: 0,
741
+ numericSharedRefProbes: 0,
742
+ sourceCacheHits: 0,
743
+ sourceCacheMisses: 0,
744
+ producerRoutes: {},
745
+ };
746
+ const materialTextureSourceCache: MaterialTextureSourceCache = new Map();
659
747
 
660
748
  const directionalLightQuery = world.query({ read: [DirectionalLight] }).unwrap();
661
749
 
@@ -673,7 +761,7 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
673
761
  const worldInternal = createWorldInternalView(world);
674
762
  let volumetricFog = extractVolumeSnapshot(world, assets, worldInternal);
675
763
 
676
- const cameraRoles = selectCameraRoles(world);
764
+ const cameraRoles = selectCameraRoles(world, context.cameraEntityKey);
677
765
  const cameras = [...cameraRoles.display];
678
766
  const auxiliaryCameras = cameraRoles.auxiliary;
679
767
  const cubeCameras = collectCubeCameraSnapshots(world);
@@ -784,6 +872,7 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
784
872
  pointSnapshots.push({
785
873
  kind: 'point',
786
874
  entity: entityId,
875
+ worldId: context.worldId,
787
876
  position,
788
877
  color: vec3.create(
789
878
  (p.color[0] ?? 1) * intensity,
@@ -979,6 +1068,7 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
979
1068
  spotSnapshots.push({
980
1069
  kind: 'spot',
981
1070
  entity: row.entity,
1071
+ worldId: context.worldId,
982
1072
  // D-6: position reflects world transform; direction stays sourced
983
1073
  // from SpotLight.direction (NOT rotated by the parent).
984
1074
  position,
@@ -1274,10 +1364,13 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
1274
1364
  }
1275
1365
  pointShadowSnapshots.push({
1276
1366
  entity: row.entity,
1367
+ worldId: context.worldId,
1277
1368
  position: lightPos,
1278
1369
  mapSize,
1279
1370
  nearPlane,
1280
1371
  farPlane,
1372
+ depthBias: ps.depthBias,
1373
+ normalBias: ps.normalBias,
1281
1374
  shadowAtlasLayer: layer,
1282
1375
  shadowMatrices: packed,
1283
1376
  });
@@ -1329,6 +1422,12 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
1329
1422
  directionalCsmDirection,
1330
1423
  };
1331
1424
 
1425
+ // A caster may be outside the display camera while still contributing to a
1426
+ // light's shadow map. Keep the light-space visibility projection alongside
1427
+ // camera culling so the direct extraction path and persistent composition
1428
+ // retain the same shadow-only rule.
1429
+ const shadowFrusta = cullMode === 'none' ? [] : buildShadowFrusta(lights);
1430
+
1332
1431
  // feat-20260520-skylight-ibl-cubemap M4 / t26+t27: query Skylight entities.
1333
1432
  // First archetype hit wins (mirrors DirectionalLight pattern); multi-Skylight
1334
1433
  // warn in record stage (t27) uses skylightCount.
@@ -1384,6 +1483,7 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
1384
1483
  const transform = row.get(GlobalTransform);
1385
1484
  lightProbes.push({
1386
1485
  identity: String(row.entity),
1486
+ worldId: context.worldId,
1387
1487
  position: [transform.world[12] ?? 0, transform.world[13] ?? 0, transform.world[14] ?? 0],
1388
1488
  radius: probe.radius,
1389
1489
  irradiance: new Float32Array(probe.irradiance),
@@ -1459,22 +1559,27 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
1459
1559
  directionalDirection[1] ?? 0,
1460
1560
  directionalDirection[2] ?? 0,
1461
1561
  );
1562
+ const sunIntensity = directional?.intensity ?? 0;
1563
+ const sunColor =
1564
+ sunIntensity > 0 && directional !== undefined
1565
+ ? ([
1566
+ (directional.color[0] ?? 0) / sunIntensity,
1567
+ (directional.color[1] ?? 0) / sunIntensity,
1568
+ (directional.color[2] ?? 0) / sunIntensity,
1569
+ ] as [number, number, number])
1570
+ : ([0, 0, 0] as [number, number, number]);
1462
1571
  const sun =
1463
- directional === undefined || directionalLength <= 1e-6
1572
+ directional === undefined || directional.entity === undefined || directionalLength <= 1e-6
1464
1573
  ? []
1465
1574
  : [
1466
1575
  {
1467
- entityKey: 0,
1576
+ entityKey: directional.entity,
1468
1577
  direction: [
1469
1578
  -(directionalDirection?.[0] ?? 0) / directionalLength,
1470
1579
  -(directionalDirection?.[1] ?? 0) / directionalLength,
1471
1580
  -(directionalDirection?.[2] ?? 0) / directionalLength,
1472
1581
  ] as [number, number, number],
1473
- color: [
1474
- directional.color[0] ?? 0,
1475
- directional.color[1] ?? 0,
1476
- directional.color[2] ?? 0,
1477
- ] as [number, number, number],
1582
+ color: sunColor,
1478
1583
  intensity: directional.intensity,
1479
1584
  },
1480
1585
  ];
@@ -1561,18 +1666,26 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
1561
1666
  // (plan-strategy D-3). Entries built per-entity per-pass inside the
1562
1667
  // archetype walk, then sorted by queue at the end.
1563
1668
  let dispatch: DispatchEntry[] = [];
1669
+ const shadowMembershipEntries: ShadowCasterMembership[] = [];
1564
1670
  // Keep the derived material snapshot and its resolved passes local to this
1565
1671
  // extraction. Shared handles are immutable inputs for the frame, while
1566
1672
  // dispatch entries still need to be rebuilt for each entity.
1567
1673
  const materialSnapshotCache: MaterialSnapshotCache = new Map();
1674
+ // A collection can be associated with more than one entity. Snapshot its
1675
+ // renderer-owned bytes once per extracted frame so culling and every
1676
+ // submission lane consume the same detached revision without repeat copies.
1677
+ const instanceSnapshotCache = new Map<
1678
+ InstanceCollectionId,
1679
+ ReturnType<InstanceCollectionStore['snapshot']>
1680
+ >();
1568
1681
  const skinnedMaterialSnapshotCache: MaterialSnapshotCache = new Map();
1569
1682
  // tweak-20260611 M1: MeshRenderer renderable archetype walk routes
1570
1683
  // through one World-owned Query. K-2 sniffing scheme B
1571
1684
  // (`row.get(X) !== undefined` edge sniff) replaces the prior
1572
1685
  // `arch.components.some` row-internal back-door. K-3 invariant: the
1573
1686
  // variable-length array reads (`MeshRenderer.materials`, SpriteInstances
1574
- // arrays) still flow through `_getArrayView`; explicit Instances bytes also come
1575
- // from World, before projection into renderer-owned snapshots.
1687
+ // arrays) still flow through `_getArrayView`; explicit Instances bytes come
1688
+ // from the renderer-owned collection store.
1576
1689
  //
1577
1690
  // archVersion remains the content fingerprint for SpriteInstances' managed
1578
1691
  // arrays; explicit Instances use the collection revision below and never
@@ -1643,7 +1756,8 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
1643
1756
  // are not materialised merely to answer a boolean question.
1644
1757
  const hasTransform = row.has(Transform);
1645
1758
  const meshFilter = row.get(MeshFilter);
1646
- const hasInstances = row.has(Instances);
1759
+ const instancesData = row.get(Instances);
1760
+ const hasInstances = instancesData !== undefined;
1647
1761
  const skin = row.get(Skin);
1648
1762
  const morphWeightsView = worldInternal._getArrayView(row.entity, MorphWeights, 'weights');
1649
1763
  const hasMeshFilter = meshFilter !== undefined;
@@ -1760,9 +1874,7 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
1760
1874
  () => 'renderer-override',
1761
1875
  );
1762
1876
  let materialBindingDiagnostics: MeshMaterialBindingDiagnostic[] = [];
1763
- let gpuDrivenSubmeshes: readonly MeshAsset['submeshes'][number][] = [];
1764
- let gpuDrivenIndexed = false;
1765
- let gpuDrivenGeometry: GpuDrivenGeometryReceipt | undefined;
1877
+ let gpuDrivenMesh: MeshAsset | undefined;
1766
1878
 
1767
1879
  // Resolve instance overrides against mesh-owned slot defaults once.
1768
1880
  const fAssetHandleVal = fAssetHandle;
@@ -1777,19 +1889,7 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
1777
1889
  if (meshRes.ok && meshRes.value.kind === 'mesh') {
1778
1890
  const meshAsset = meshRes.value as MeshAsset;
1779
1891
  const guid = (meshRes.value as { guid?: string }).guid ?? '<no-guid>';
1780
- gpuDrivenSubmeshes = meshAsset.submeshes;
1781
- gpuDrivenIndexed = meshAsset.indices !== undefined;
1782
- const layoutProjection = deriveVertexLayoutProjection(meshAsset.attributes);
1783
- gpuDrivenGeometry = {
1784
- identity: `${guid}:${layoutProjection.digest}`,
1785
- vertexInputs: layoutProjection.attributes.map((attribute) => ({
1786
- semantic: attribute.key,
1787
- location: attribute.shaderLocation,
1788
- format: attribute.format,
1789
- })),
1790
- topology: meshAsset.submeshes[0]?.topology ?? 'triangle-list',
1791
- indexed: gpuDrivenIndexed,
1792
- };
1892
+ gpuDrivenMesh = meshAsset;
1793
1893
  // extractFrames is also a public read path and can run before the
1794
1894
  // Renderer.draw pre-render stage. GlyphText proves the derived mesh
1795
1895
  // owns exactly one Default slot; arbitrary meshes still fail closed
@@ -2241,6 +2341,16 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
2241
2341
  ? assets.materialShaderTextureFieldNames(firstPassShader)
2242
2342
  : undefined,
2243
2343
  ) ?? BUILTIN_USER_REGION_TEXTURE_FIELDS;
2344
+ const authoredTextureFields = collectAuthoredMaterialTextureFields(
2345
+ resolved.values,
2346
+ firstPassShader,
2347
+ materialParamSchema.length > 0 ? materialParamSchema : undefined,
2348
+ assets,
2349
+ );
2350
+ const authoredSamplerFields = collectAuthoredMaterialSamplerFields(
2351
+ resolved.values,
2352
+ authoredTextureFields,
2353
+ );
2244
2354
  const textureHandles = new Map<string, Handle<'TextureAsset', 'shared'>>();
2245
2355
  const videoTextureFields = new Map<string, Handle<'VideoAsset', 'shared'>>();
2246
2356
  for (const field of userRegionFields) {
@@ -2274,7 +2384,15 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
2274
2384
  pv.occlusionTexture,
2275
2385
  );
2276
2386
  const textureCoordinates = collectMaterialTextureCoordinates(pv);
2277
- const textureSources = collectMaterialTextureSources(pv, world);
2387
+ const textureSources = collectMaterialTextureSources(
2388
+ pv,
2389
+ world,
2390
+ materialTextureSourceFields(
2391
+ materialParamSchema.length > 0 ? materialParamSchema : undefined,
2392
+ ),
2393
+ materialTextureSourceStats,
2394
+ materialTextureSourceCache,
2395
+ );
2278
2396
  const emissivePv = pv.emissive as readonly number[] | undefined;
2279
2397
  // feat-20260625 M2 / w6: first-pass transparency flag folds into
2280
2398
  // MaterialSnapshot.transparent so the record stage can drive the
@@ -2370,8 +2488,15 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
2370
2488
  renderState: pipelineRenderState(allPasses[0]?.renderState),
2371
2489
  paramSnapshot: paramSnap,
2372
2490
  ...(materialParamSchema.length > 0 && { materialParamSchema }),
2491
+ standardTextureMask: materialStandardTextureMask(
2492
+ resolved.parameters,
2493
+ firstPassShader,
2494
+ pv,
2495
+ ),
2373
2496
  ...(textureCoordinates.size > 0 && { textureCoordinates }),
2497
+ ...(authoredTextureFields === undefined ? {} : { authoredTextureFields }),
2374
2498
  ...(samplerHandles.size > 0 && { samplerHandles }),
2499
+ ...(authoredSamplerFields === undefined ? {} : { authoredSamplerFields }),
2375
2500
  ...(textureHandles.size > 0 && { textureHandles }),
2376
2501
  ...(textureSources.size > 0 && { textureSources }),
2377
2502
  ...(videoTextureFields.size > 0 && { videoTextureFields }),
@@ -2497,7 +2622,13 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
2497
2622
  // T-21 placeholder ({0,0} discriminator-only sentinel) with full
2498
2623
  // resolve / validate / write chain (D-9 reset already fired at
2499
2624
  // extractFrame entry; per-entity allocate + writeJointPalette here).
2500
- let skinSlice: SkinPaletteSlice | undefined;
2625
+ let skinSlice: SkinPaletteReceipt | undefined;
2626
+ const skinIdentity = hasSkin ? skinPaletteIdentity(world, entity) : undefined;
2627
+ const releaseSkinIdentity = (): void => {
2628
+ if (skinIdentity !== undefined) {
2629
+ skinPaletteAllocator?.releasePersistentSlice(skinIdentity);
2630
+ }
2631
+ };
2501
2632
  if (hasSkin) {
2502
2633
  const skeletonHandleRaw = skinSkeletonView;
2503
2634
  if (
@@ -2512,7 +2643,7 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
2512
2643
  severity: Severity.Error,
2513
2644
  systemName: 'RenderSystem.extract (skin-instances-coexist)',
2514
2645
  });
2515
-
2646
+ releaseSkinIdentity();
2516
2647
  continue;
2517
2648
  }
2518
2649
  // (a) Resolve skeleton asset; on failure -> skeleton-resolve-failed.
@@ -2523,13 +2654,17 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
2523
2654
  severity: Severity.Error,
2524
2655
  systemName: 'RenderSystem.extract (skeleton-resolve-failed)',
2525
2656
  });
2657
+ releaseSkinIdentity();
2526
2658
  continue;
2527
2659
  }
2528
2660
  const skeleton = skeletonRes.value;
2529
2661
  // (b) Reuse the Skin row already read for its skeleton handle;
2530
2662
  // the old path repeated a second whole-row world.get here.
2531
2663
  const skinJoints = skin?.joints;
2532
- if (skinJoints === undefined) continue;
2664
+ if (skinJoints === undefined) {
2665
+ releaseSkinIdentity();
2666
+ continue;
2667
+ }
2533
2668
  const jointsLength = skinJoints.length;
2534
2669
  if (jointsLength !== skeleton.jointCount) {
2535
2670
  worldInternal._routeError(
@@ -2539,6 +2674,7 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
2539
2674
  systemName: 'RenderSystem.extract (joint-count-mismatch)',
2540
2675
  },
2541
2676
  );
2677
+ releaseSkinIdentity();
2542
2678
  continue;
2543
2679
  }
2544
2680
  // (c) Resolve only each joint's GlobalTransform.world column. The
@@ -2569,6 +2705,7 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
2569
2705
  severity: Severity.Error,
2570
2706
  systemName: 'RenderSystem.extract (joint-entity-dangling)',
2571
2707
  });
2708
+ releaseSkinIdentity();
2572
2709
  continue;
2573
2710
  }
2574
2711
  // (d) Slice the IBM flat Float32Array into per-joint Float32Arrays.
@@ -2578,29 +2715,25 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
2578
2715
  for (let jIdx = 0; jIdx < skeleton.jointCount; jIdx++) {
2579
2716
  ibms[jIdx] = ibmFlat.subarray(jIdx * 16, jIdx * 16 + 16);
2580
2717
  }
2581
- // (e) Allocate slice + write palette via the allocator (D-9 reset
2582
- // already fired at extractFrame entry). When the pipelineState
2583
- // surface is absent (test fixtures that pass undefined) the
2584
- // hasSkin segment is skipped silently bind-pose equivalent.
2718
+ // (e) Reuse the persistent producer-owned slice for this stable
2719
+ // world/entity identity. Missing producer bounds remain explicit:
2720
+ // the receipt carries no invented bind-pose AABB and the shadow
2721
+ // admission lane can route the caster to CPU.
2585
2722
  if (skinPaletteAllocator !== null) {
2586
- // Keep the narrow extraction seam compatible with older test or
2587
- // host-owned allocator doubles while the real renderer uses the
2588
- // stable-key path. The fallback is frame-local and therefore
2589
- // cannot affect production allocators that implement the new
2590
- // method.
2591
- const slice =
2592
- typeof skinPaletteAllocator.allocateSliceFor === 'function'
2593
- ? skinPaletteAllocator.allocateSliceFor(
2594
- `${world.identity}:${entity}`,
2595
- skeleton.jointCount,
2596
- )
2597
- : skinPaletteAllocator.allocateSlice(skeleton.jointCount);
2598
- skinPaletteAllocator.writeJointPalette(slice, ibms, jointWorlds);
2599
- skinSlice = {
2600
- jointCount: slice.jointCount,
2601
- byteOffset: slice.byteOffset,
2602
- buffer: slice.buffer,
2603
- };
2723
+ const identity = skinPaletteIdentity(world, entity);
2724
+ const generation = (skeletonHandleRaw >>> 0) ^ skeleton.jointCount;
2725
+ const bounds = hasFiniteOrderedLocalAabb(skeleton.bounds)
2726
+ ? new Float32Array(skeleton.bounds)
2727
+ : undefined;
2728
+ skinPaletteAllocator.observePersistentJoints(identity, ibms, jointWorlds);
2729
+ const receipt = skinPaletteAllocator.allocatePersistentSlice({
2730
+ identity,
2731
+ generation: generation >>> 0,
2732
+ jointCount: skeleton.jointCount,
2733
+ ...(bounds === undefined ? {} : { bounds }),
2734
+ });
2735
+ skinPaletteAllocator.writePersistentJointPalette(receipt, ibms, jointWorlds);
2736
+ skinSlice = receipt;
2604
2737
  }
2605
2738
  }
2606
2739
  }
@@ -2609,9 +2742,12 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
2609
2742
  // projection. Do not publish a renderable or its staged dispatch when
2610
2743
  // palette extraction was unavailable; the record stage must never
2611
2744
  // reinterpret a skinned vertex stream through an ordinary mesh group.
2612
- if (materialSnap.materialShaderId === 'forgeax::pbr-skin' && skinSlice === undefined) {
2613
- pendingDispatch.length = 0;
2614
- continue;
2745
+ if (skinSlice === undefined && hasSkin) {
2746
+ releaseSkinIdentity();
2747
+ if (materialSnap.materialShaderId === 'forgeax::pbr-skin') {
2748
+ pendingDispatch.length = 0;
2749
+ continue;
2750
+ }
2615
2751
  }
2616
2752
 
2617
2753
  // feat-20260601 D-3: read the resolved world mat4 (propagateTransforms
@@ -2622,7 +2758,10 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
2622
2758
  // tweak-20260611 M1 / K-3: `_getArrayView` call survives untouched;
2623
2759
  // only the `entity` source switched to `bundle.Entity.self[i]`.
2624
2760
  const worldView = worldInternal._getArrayView(entity, GlobalTransform, 'world');
2625
- if (worldView === undefined) continue;
2761
+ if (worldView === undefined) {
2762
+ releaseSkinIdentity();
2763
+ continue;
2764
+ }
2626
2765
  const worldMat = new Float32Array(worldView);
2627
2766
  const transformSnap: TransformSnapshot = { world: worldMat };
2628
2767
  // feat-20260608 M5 amend / w11-a: per-submesh `materials[]` array
@@ -2654,6 +2793,8 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
2654
2793
  ...context.materialContext,
2655
2794
  geometry: hasSkin ? 'skinned' : context.materialContext.geometry,
2656
2795
  },
2796
+ materialTextureSourceStats,
2797
+ materialTextureSourceCache,
2657
2798
  ),
2658
2799
  );
2659
2800
  }
@@ -2778,7 +2919,7 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
2778
2919
  lods = meshAsset.lods;
2779
2920
  lodHysteresis = meshAsset.lodHysteresis;
2780
2921
  morph = morphSnapshotFor(meshAsset, morphWeightsView);
2781
- const meshAabb = meshAsset.aabb;
2922
+ const meshAabb = skinSlice?.bounds ?? meshAsset.aabb;
2782
2923
  // Morph targets can expand the authored bounds. Keep the
2783
2924
  // specialized lane conservative until a target-aware bounds
2784
2925
  // projection is available; a false-positive draw is preferable
@@ -2805,7 +2946,7 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
2805
2946
  pointsLinesComponent === undefined
2806
2947
  ? undefined
2807
2948
  : ({
2808
- worldId: 0,
2949
+ worldId: context.worldId,
2809
2950
  entityKey: entity,
2810
2951
  component: pointsLinesComponent,
2811
2952
  meshHandle: assetHandleRaw,
@@ -2828,30 +2969,48 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
2828
2969
  // make the cull and draw disagree within one frame).
2829
2970
  let instancesSnap: InstancesSnapshot | undefined;
2830
2971
  if (hasInstances) {
2831
- const transforms =
2832
- worldInternal._getArrayView(entity, Instances, 'transforms') ?? new Float32Array();
2833
- if (transforms.length % 16 !== 0) {
2834
- worldInternal._routeError(
2835
- new InstanceTransformsStrideMismatchError(transforms.length),
2836
- {
2837
- severity: Severity.Error,
2838
- systemName: 'RenderSystem.extract (Instances stride)',
2839
- },
2840
- );
2972
+ const collectionId = instancesData?.collectionId as InstanceCollectionId | undefined;
2973
+ let collectionResult =
2974
+ collectionId !== undefined ? instanceSnapshotCache.get(collectionId) : undefined;
2975
+ if (collectionResult === undefined) {
2976
+ collectionResult =
2977
+ collectionId !== undefined && collectionId > 0 && instanceCollections !== undefined
2978
+ ? instanceCollections.snapshot(collectionId)
2979
+ : err(
2980
+ new InstanceCollectionError(
2981
+ 'instance-collection-invalid',
2982
+ 'Instances.collectionId references a live renderer-owned collection',
2983
+ 'create the collection through renderer.instances before attaching Instances',
2984
+ {
2985
+ operation: 'inspect',
2986
+ expected: 'a positive collectionId with a live renderer owner',
2987
+ actual: Number(collectionId ?? 0),
2988
+ ...(collectionId === undefined ? {} : { collectionId }),
2989
+ },
2990
+ ),
2991
+ );
2992
+ if (collectionId !== undefined)
2993
+ instanceSnapshotCache.set(collectionId, collectionResult);
2994
+ }
2995
+ if (collectionResult === undefined) continue;
2996
+ if (!collectionResult.ok) {
2997
+ worldInternal._routeError(collectionResult.error, {
2998
+ severity: Severity.Error,
2999
+ systemName: 'RenderSystem.extract (Instances collection)',
3000
+ });
2841
3001
  continue;
2842
3002
  }
2843
- const projected = instanceCollections?.project(world, entity, transforms);
2844
3003
  instancesSnap = {
2845
- transforms: projected?.transforms ?? new Float32Array(transforms),
2846
- instanceCount: transforms.length / 16,
3004
+ transforms: collectionResult.value.transforms,
3005
+ instanceCount: collectionResult.value.count,
3006
+ collectionId: collectionResult.value.collectionId,
3007
+ revision: collectionResult.value.revision,
3008
+ capacity: collectionResult.value.capacity,
3009
+ dirtyRanges: collectionResult.value.dirtyRanges,
2847
3010
  cacheKey: entity,
2848
- archVersion: projected === undefined ? fingerprintNumericArray(transforms) : 0,
2849
- ...(projected === undefined
2850
- ? {}
2851
- : {
2852
- collectionId: projected.collectionId,
2853
- revision: projected.revision,
2854
- }),
3011
+ // Explicit collection revisions supersede ECS archetype content
3012
+ // hashes; retain zero as the legacy sprite-only field value.
3013
+ archVersion: 0,
2855
3014
  };
2856
3015
  }
2857
3016
 
@@ -2865,7 +3024,7 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
2865
3024
  materials: materialsArr,
2866
3025
  materialBindingSources,
2867
3026
  materialBindingDiagnostics,
2868
- worldId: 0,
3027
+ worldId: context.worldId,
2869
3028
  entityKey: entity,
2870
3029
  ...(authorVisible ? {} : { authorVisible: false }),
2871
3030
  ...(skinSlice !== undefined ? { skin: skinSlice } : {}),
@@ -2878,34 +3037,27 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
2878
3037
  ...(pointsLines !== undefined ? { pointsLines } : {}),
2879
3038
  };
2880
3039
  const gpuDrivenDraws = buildGpuDrivenDraws({
2881
- submeshes: gpuDrivenSubmeshes,
2882
- indexed: gpuDrivenIndexed,
3040
+ mesh: gpuDrivenMesh,
2883
3041
  materials: materialsArr,
2884
3042
  fallbackMaterial: materialSnap,
2885
- ...(gpuDrivenGeometry === undefined || getMaterialShaderArtifact === undefined
3043
+ baseSnapshot: baseRenderable,
3044
+ ...(getMaterialShaderArtifact === undefined ? {} : { getMaterialShaderArtifact }),
3045
+ ...(lods === undefined
2886
3046
  ? {}
2887
3047
  : {
2888
- prepare(draw, drawMaterial) {
2889
- const materialShaderId = drawMaterial.materialShaderId;
2890
- if (materialShaderId === undefined) return undefined;
2891
- const artifact = getMaterialShaderArtifact(materialShaderId);
2892
- if (artifact?.receipt === undefined) return undefined;
2893
- const geometry: GpuDrivenGeometryReceipt = {
2894
- ...gpuDrivenGeometry,
2895
- topology: draw.topology,
2896
- indexed: draw.kind === 'indexed',
2897
- };
2898
- const result = resolvePreparedGpuDrivenDraw({
2899
- snapshot: { ...baseRenderable, material: drawMaterial },
2900
- artifact,
2901
- geometry,
2902
- generation: artifact.receipt.generation,
2903
- draw,
2904
- });
2905
- return result.ok ? result.value : undefined;
2906
- },
3048
+ lodMeshes: lods.map((lod) => {
3049
+ const lower = assets?.lookup<Asset>(lod.mesh);
3050
+ return lower?.kind === 'mesh' ? lower : undefined;
3051
+ }),
2907
3052
  }),
2908
3053
  });
3054
+ for (const draw of gpuDrivenDraws) {
3055
+ if (draw.preparationError === undefined) continue;
3056
+ worldInternal._routeError(draw.preparationError, {
3057
+ severity: Severity.Error,
3058
+ systemName: `RenderSystem.extract (gpu-driven-preparation:${draw.preparationError.code})`,
3059
+ });
3060
+ }
2909
3061
  const renderable =
2910
3062
  morph === undefined && gpuDrivenDraws.length > 0
2911
3063
  ? { ...baseRenderable, gpuDrivenDraws }
@@ -2975,13 +3127,81 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
2975
3127
  }
2976
3128
  if (!visible) {
2977
3129
  frustumCulled += 1;
2978
- continue;
3130
+ const shadowVisible =
3131
+ shadowFrusta.length > 0 &&
3132
+ pendingDispatch.some((entry) => entry.tags.LightMode === 'ShadowCaster') &&
3133
+ shadowFrusta.some((lightPlanes) =>
3134
+ frustum.intersectsBox(lightPlanes, worldAabb as box3.Box3Like),
3135
+ );
3136
+ if (!shadowVisible) continue;
3137
+ // Shadow-only renderables stay in the detached snapshot for the
3138
+ // depth passes, but must not leak their forward/transparent
3139
+ // dispatch entries into the main camera submission.
3140
+ for (let i = pendingDispatch.length - 1; i >= 0; i -= 1) {
3141
+ if (pendingDispatch[i]?.tags.LightMode !== 'ShadowCaster') {
3142
+ pendingDispatch.splice(i, 1);
3143
+ }
3144
+ }
2979
3145
  }
2980
3146
  }
2981
3147
  }
2982
3148
 
3149
+ const localShadowMembershipEntries: ShadowCasterMembership[] = [];
3150
+ if (gpuDrivenMesh !== undefined) {
3151
+ const worldEntity = worldEntityKey(context.worldId, entity);
3152
+ for (const [drawItemIndex, submesh] of gpuDrivenMesh.submeshes.entries()) {
3153
+ if (submesh.topology !== 'triangle-list' && submesh.topology !== 'triangle-strip') {
3154
+ continue;
3155
+ }
3156
+ const material = materialsArr[submesh.materialSlot] ?? materialSnap;
3157
+ const materialHandle = material.materialHandle ?? -1;
3158
+ const shadowEntries = pendingDispatch.filter(
3159
+ (entry) =>
3160
+ entry.tags.LightMode === 'ShadowCaster' && entry.materialHandle === materialHandle,
3161
+ );
3162
+ const draw = renderable.gpuDrivenDraws?.find(
3163
+ (candidate, compactIndex) =>
3164
+ gpuDrivenSourceDrawItemIndex(candidate, compactIndex) === drawItemIndex,
3165
+ );
3166
+ const cpuReason = shadowCasterCpuReason(
3167
+ material,
3168
+ draw,
3169
+ shadowEntries,
3170
+ morph,
3171
+ skinSlice,
3172
+ );
3173
+ for (const entry of shadowEntries) {
3174
+ const membership: ShadowCasterMembership = {
3175
+ worldEntity,
3176
+ renderableIndex: renderables.length,
3177
+ drawItemIndex,
3178
+ materialHandle,
3179
+ passIndex: entry.passIndex,
3180
+ ...(entry.materialShaderId === undefined
3181
+ ? {}
3182
+ : { materialShaderId: entry.materialShaderId }),
3183
+ ...(entry.renderState === undefined ? {} : { renderState: entry.renderState }),
3184
+ ...(cpuReason === undefined ? {} : { cpuReason }),
3185
+ };
3186
+ shadowMembershipEntries.push(membership);
3187
+ localShadowMembershipEntries.push(membership);
3188
+ }
3189
+ }
3190
+ }
3191
+ const shadowCasterPasses = localShadowMembershipEntries.map((membership) => ({
3192
+ drawItemIndex: membership.drawItemIndex,
3193
+ materialHandle: membership.materialHandle,
3194
+ passIndex: membership.passIndex,
3195
+ ...(membership.materialShaderId === undefined
3196
+ ? {}
3197
+ : { materialShaderId: membership.materialShaderId }),
3198
+ ...(membership.renderState === undefined ? {} : { renderState: membership.renderState }),
3199
+ ...(membership.cpuReason === undefined ? {} : { cpuReason: membership.cpuReason }),
3200
+ }));
3201
+ const renderableWithShadowPasses =
3202
+ shadowCasterPasses.length === 0 ? renderable : { ...renderable, shadowCasterPasses };
2983
3203
  flushPendingDispatch(pendingDispatch);
2984
- renderables.push(renderable);
3204
+ renderables.push(renderableWithShadowPasses);
2985
3205
  }
2986
3206
 
2987
3207
  // feat-20260520-2d-sprite-layer-mvp M-3 / w22 + w25: finalise the
@@ -2998,6 +3218,28 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
2998
3218
  // M3 / w26: sort dispatch entries by queue (ascending, stable sort)
2999
3219
  // per plan-strategy D-3.
3000
3220
  dispatch = sortDispatchByQueue(dispatch);
3221
+ const shadowCasterEntityKeys = new Set<number>();
3222
+ const shadowCasterDrawKeys = new Set<string>();
3223
+ const shadowCasterMembership = new Map<string, ShadowCasterMembership>();
3224
+ for (const entry of dispatch) {
3225
+ if (entry.tags.LightMode !== 'ShadowCaster') continue;
3226
+ const renderable = renderables[entry.renderableIndex];
3227
+ if (renderable !== undefined) {
3228
+ const worldEntity = worldEntityKey(renderable.worldId, renderable.entityKey);
3229
+ shadowCasterEntityKeys.add(worldEntity);
3230
+ }
3231
+ }
3232
+ for (const membership of shadowMembershipEntries) {
3233
+ const key = gpuDrivenShadowDrawKey(
3234
+ membership.worldEntity,
3235
+ membership.materialHandle,
3236
+ membership.drawItemIndex,
3237
+ membership.passIndex,
3238
+ );
3239
+ shadowCasterDrawKeys.add(key);
3240
+ shadowCasterMembership.set(key, membership);
3241
+ shadowCasterEntityKeys.add(membership.worldEntity);
3242
+ }
3001
3243
 
3002
3244
  // D-1: collect PostProcessParams entities into Map<shaderId, Uint8Array>.
3003
3245
  // Last-one-wins when multiple entities bear the same shader id (mirrors
@@ -3041,6 +3283,11 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
3041
3283
  ...(volumetricFog === undefined ? {} : { volumetricFog }),
3042
3284
  renderables,
3043
3285
  dispatch,
3286
+ shadowCasterEntityKeys,
3287
+ shadowCasterDrawKeys,
3288
+ ...(shadowCasterMembership.size === 0
3289
+ ? {}
3290
+ : { shadowCasterMembership: [...shadowCasterMembership.values()] }),
3044
3291
  skylight,
3045
3292
  skylightCount,
3046
3293
  lightProbes,
@@ -3050,6 +3297,7 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
3050
3297
  ...(fogFailure === undefined ? {} : { fogFailure }),
3051
3298
  frustumStats: { culled: frustumCulled, total: frustumTotal },
3052
3299
  visibilityStats: { explicitlyHidden: explicitlyHidden.size },
3300
+ materialTextureSources: materialTextureSourceStats,
3053
3301
  postProcessParams,
3054
3302
  visibilitySnapshots: [context.visibility],
3055
3303
  featureVisibilitySnapshots: [{ world, snapshot: context.visibility }],
@@ -3057,17 +3305,18 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
3057
3305
  };
3058
3306
  }
3059
3307
 
3308
+ /** Immutable author-independent default; extraction retains its producer identity. */
3309
+ const DEFAULT_MATERIAL_SNAPSHOT: MaterialSnapshot = Object.freeze({
3310
+ baseColor: vec3.create(0.5, 0.5, 0.5),
3311
+ metallic: 0,
3312
+ roughness: 1,
3313
+ materialHandle: 0,
3314
+ });
3315
+
3060
3316
  export function defaultMaterialSnapshot(materialHandle = 0): MaterialSnapshot {
3061
- // Mid-grey unlit fallback (D-Q7 case B + extract-stage missing-spec),
3062
- // matching the pre-w6 visual outcome where the record stage's force-cast
3063
- // read of `firstMaterial.baseColorTexture` returned undefined and the
3064
- // unlit fallback shader was selected.
3065
- return {
3066
- baseColor: vec3.create(0.5, 0.5, 0.5),
3067
- metallic: 0,
3068
- roughness: 1,
3069
- materialHandle,
3070
- };
3317
+ return materialHandle === 0
3318
+ ? DEFAULT_MATERIAL_SNAPSHOT
3319
+ : { ...DEFAULT_MATERIAL_SNAPSHOT, materialHandle };
3071
3320
  }
3072
3321
 
3073
3322
  function identityProjection(): Float32Array {