@forgeax/engine-render 0.1.4 → 0.1.6

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 (918) hide show
  1. package/README.md +130 -641
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/__tests__/device-scope-lifecycle.unit.test.d.ts +2 -0
  4. package/dist/__tests__/device-scope-lifecycle.unit.test.d.ts.map +1 -0
  5. package/dist/__tests__/device-scope-stale-matrix.unit.test.d.ts +2 -0
  6. package/dist/__tests__/device-scope-stale-matrix.unit.test.d.ts.map +1 -0
  7. package/dist/__tests__/fullscreen-feature-plan.unit.test.d.ts +2 -0
  8. package/dist/__tests__/fullscreen-feature-plan.unit.test.d.ts.map +1 -0
  9. package/dist/__tests__/gpu-driven-indirect-raster-evidence.d.ts.map +1 -1
  10. package/dist/__tests__/gpu-driven-view-gpu-evidence.d.ts +6 -0
  11. package/dist/__tests__/gpu-driven-view-gpu-evidence.d.ts.map +1 -1
  12. package/dist/{gpu-driven/view-reference.d.ts → __tests__/gpu-driven-view-reference.d.ts} +4 -4
  13. package/dist/__tests__/gpu-driven-view-reference.d.ts.map +1 -0
  14. package/dist/__tests__/gpu-scene-render-graph-gpu.d.ts.map +1 -1
  15. package/dist/__tests__/ibl/face-uniforms.unit.test.d.ts +2 -0
  16. package/dist/__tests__/ibl/face-uniforms.unit.test.d.ts.map +1 -0
  17. package/dist/__tests__/morph-feature-plan.unit.test.d.ts +2 -0
  18. package/dist/__tests__/morph-feature-plan.unit.test.d.ts.map +1 -0
  19. package/dist/__tests__/render-feature-plan.test-d.d.ts +2 -0
  20. package/dist/__tests__/render-feature-plan.test-d.d.ts.map +1 -0
  21. package/dist/__tests__/render-feature-plan.unit.test.d.ts +2 -0
  22. package/dist/__tests__/render-feature-plan.unit.test.d.ts.map +1 -0
  23. package/dist/__tests__/ssao-config.unit.test.d.ts +2 -0
  24. package/dist/__tests__/ssao-config.unit.test.d.ts.map +1 -0
  25. package/dist/__tests__/standard-clustered-pipeline.unit.test.d.ts +2 -0
  26. package/dist/__tests__/standard-clustered-pipeline.unit.test.d.ts.map +1 -0
  27. package/dist/__tests__/standard-descriptors.test-d.d.ts +2 -0
  28. package/dist/__tests__/standard-descriptors.test-d.d.ts.map +1 -0
  29. package/dist/__tests__/standard-pipeline.dawn.test.d.ts +2 -0
  30. package/dist/__tests__/standard-pipeline.dawn.test.d.ts.map +1 -0
  31. package/dist/__tests__/standard-pipeline.integration.test.d.ts +2 -0
  32. package/dist/__tests__/standard-pipeline.integration.test.d.ts.map +1 -0
  33. package/dist/__tests__/standard-profile-oracle.unit.test.d.ts +2 -0
  34. package/dist/__tests__/standard-profile-oracle.unit.test.d.ts.map +1 -0
  35. package/dist/__tests__/visibility-resource-short-circuit.unit.test.d.ts +2 -0
  36. package/dist/__tests__/visibility-resource-short-circuit.unit.test.d.ts.map +1 -0
  37. package/dist/{renderer/backend-selection.d.ts → assembly/backend-contract.d.ts} +15 -4
  38. package/dist/assembly/backend-contract.d.ts.map +1 -0
  39. package/dist/assembly/factory.d.ts +207 -0
  40. package/dist/assembly/factory.d.ts.map +1 -0
  41. package/dist/assembly/host-contract.d.ts +84 -0
  42. package/dist/assembly/host-contract.d.ts.map +1 -0
  43. package/dist/{renderer → assembly}/material/assembly.d.ts +0 -1
  44. package/dist/{renderer → assembly}/material/assembly.d.ts.map +1 -1
  45. package/dist/assembly/material/pipeline-projection.d.ts +30 -0
  46. package/dist/assembly/material/pipeline-projection.d.ts.map +1 -0
  47. package/dist/authoring.d.ts +10 -11
  48. package/dist/authoring.d.ts.map +1 -1
  49. package/dist/authoring.mjs +25 -3
  50. package/dist/authoring.mjs.map +1 -1
  51. package/dist/chunk-CQXZC5XI.mjs +182 -0
  52. package/dist/chunk-CQXZC5XI.mjs.map +1 -0
  53. package/dist/chunk-QC4VW3XJ.mjs +79 -0
  54. package/dist/chunk-QC4VW3XJ.mjs.map +1 -0
  55. package/dist/chunk-SCRSAK5B.mjs +480 -0
  56. package/dist/chunk-SCRSAK5B.mjs.map +1 -0
  57. package/dist/components/__tests__/schema-contract.unit.test.d.ts +2 -0
  58. package/dist/components/__tests__/schema-contract.unit.test.d.ts.map +1 -0
  59. package/dist/components/glyph-text.d.ts +1 -1
  60. package/dist/components/glyph-text.d.ts.map +1 -1
  61. package/dist/components/index.d.ts +2 -2
  62. package/dist/components/index.d.ts.map +1 -1
  63. package/dist/components/instances.d.ts +1 -1
  64. package/dist/components/instances.d.ts.map +1 -1
  65. package/dist/components/light-helpers.d.ts +1 -1
  66. package/dist/components/light-helpers.d.ts.map +1 -1
  67. package/dist/components/mesh-filter.d.ts +7 -7
  68. package/dist/components/mesh-filter.d.ts.map +1 -1
  69. package/dist/components/scene-instance.d.ts +11 -11
  70. package/dist/components/skybox-background.d.ts +56 -0
  71. package/dist/components/skybox-background.d.ts.map +1 -0
  72. package/dist/components/skylight.d.ts +55 -0
  73. package/dist/components/skylight.d.ts.map +1 -0
  74. package/dist/components/sprite-animation.d.ts +2 -3
  75. package/dist/components/sprite-animation.d.ts.map +1 -1
  76. package/dist/components/sprite-instances.d.ts +1 -1
  77. package/dist/components/sprite-playback-mode.d.ts +6 -1
  78. package/dist/components/sprite-playback-mode.d.ts.map +1 -1
  79. package/dist/components/sprite-region-override.d.ts +2 -3
  80. package/dist/components/sprite-region-override.d.ts.map +1 -1
  81. package/dist/components/tile-layer.d.ts +5 -0
  82. package/dist/components/tile-layer.d.ts.map +1 -1
  83. package/dist/construct-renderer.d.ts +18 -1
  84. package/dist/construct-renderer.d.ts.map +1 -1
  85. package/dist/construct-renderer.mjs +27008 -4
  86. package/dist/construct-renderer.mjs.map +1 -1
  87. package/dist/debug-draw-glue.d.ts +2 -53
  88. package/dist/debug-draw-glue.d.ts.map +1 -1
  89. package/dist/device/device-scope.d.ts +83 -0
  90. package/dist/device/device-scope.d.ts.map +1 -0
  91. package/dist/{gpu-resource-store.d.ts → device/gpu-residency.d.ts} +91 -48
  92. package/dist/device/gpu-residency.d.ts.map +1 -0
  93. package/dist/device/resource-types.d.ts +8 -0
  94. package/dist/device/resource-types.d.ts.map +1 -0
  95. package/dist/engine-metrics.d.ts +2 -2
  96. package/dist/engine-metrics.d.ts.map +1 -1
  97. package/dist/errors/recover.d.ts +28 -1
  98. package/dist/errors/recover.d.ts.map +1 -1
  99. package/dist/errors/render.d.ts +249 -258
  100. package/dist/errors/render.d.ts.map +1 -1
  101. package/dist/features/fullscreen.d.ts +19 -0
  102. package/dist/features/fullscreen.d.ts.map +1 -0
  103. package/dist/features/host.d.ts +32 -26
  104. package/dist/features/host.d.ts.map +1 -1
  105. package/dist/features/morph/morph-feature.d.ts +39 -5
  106. package/dist/features/morph/morph-feature.d.ts.map +1 -1
  107. package/dist/features/plan.d.ts +182 -0
  108. package/dist/features/plan.d.ts.map +1 -0
  109. package/dist/features/prepared-gpu-work.d.ts +21 -17
  110. package/dist/features/prepared-gpu-work.d.ts.map +1 -1
  111. package/dist/features/prepared-graphics-store.d.ts +2 -1
  112. package/dist/features/prepared-graphics-store.d.ts.map +1 -1
  113. package/dist/features/prepared-graphics.d.ts +11 -21
  114. package/dist/features/prepared-graphics.d.ts.map +1 -1
  115. package/dist/features/render-graph-compute.d.ts +2 -2
  116. package/dist/features/render-graph-compute.d.ts.map +1 -1
  117. package/dist/features/render-graph-contribution.d.ts +4 -7
  118. package/dist/features/render-graph-contribution.d.ts.map +1 -1
  119. package/dist/features/render-graph-raster.d.ts +2 -2
  120. package/dist/features/render-graph-raster.d.ts.map +1 -1
  121. package/dist/features/render-graph-resources.d.ts +6 -6
  122. package/dist/features/render-graph-resources.d.ts.map +1 -1
  123. package/dist/features/targets.d.ts +5 -5
  124. package/dist/features/targets.d.ts.map +1 -1
  125. package/dist/features/types.d.ts +11 -190
  126. package/dist/features/types.d.ts.map +1 -1
  127. package/dist/features/vocabulary.d.ts +14 -0
  128. package/dist/features/vocabulary.d.ts.map +1 -0
  129. package/dist/fullscreen-post-process-pass.d.ts +4 -25
  130. package/dist/fullscreen-post-process-pass.d.ts.map +1 -1
  131. package/dist/glyph-text-layout-system.d.ts +3 -2
  132. package/dist/glyph-text-layout-system.d.ts.map +1 -1
  133. package/dist/gpu-driven/batch-topology.d.ts +6 -75
  134. package/dist/gpu-driven/batch-topology.d.ts.map +1 -1
  135. package/dist/gpu-driven/production-raster.d.ts +13 -112
  136. package/dist/gpu-driven/production-raster.d.ts.map +1 -1
  137. package/dist/gpu-driven/view-gpu.d.ts +10 -66
  138. package/dist/gpu-driven/view-gpu.d.ts.map +1 -1
  139. package/dist/gpu-resource.d.ts +3 -3
  140. package/dist/gpu-scene-schema.d.ts +1 -94
  141. package/dist/gpu-scene-schema.d.ts.map +1 -1
  142. package/dist/gpu-scene.d.ts +7 -33
  143. package/dist/gpu-scene.d.ts.map +1 -1
  144. package/dist/hdrp-buffers.d.ts +3 -53
  145. package/dist/hdrp-buffers.d.ts.map +1 -1
  146. package/dist/ibl/IblPipelineCache.d.ts +74 -180
  147. package/dist/ibl/IblPipelineCache.d.ts.map +1 -1
  148. package/dist/ibl/skylight-bind-group.d.ts +1 -1
  149. package/dist/index.d.ts +13 -43
  150. package/dist/index.d.ts.map +1 -1
  151. package/dist/index.mjs +165 -3
  152. package/dist/index.mjs.map +1 -1
  153. package/dist/inspection-types.d.ts +69 -0
  154. package/dist/inspection-types.d.ts.map +1 -0
  155. package/dist/instance-buffer-cache.d.ts +1 -4
  156. package/dist/instance-buffer-cache.d.ts.map +1 -1
  157. package/dist/internal.d.ts +1 -73
  158. package/dist/internal.d.ts.map +1 -1
  159. package/dist/internal.mjs +1 -43
  160. package/dist/internal.mjs.map +1 -1
  161. package/dist/lifecycle.d.ts +36 -2
  162. package/dist/lifecycle.d.ts.map +1 -1
  163. package/dist/materials.d.ts +0 -3
  164. package/dist/materials.d.ts.map +1 -1
  165. package/dist/mesh-material-bindings.d.ts +0 -1
  166. package/dist/mesh-material-bindings.d.ts.map +1 -1
  167. package/dist/pbr-pipeline.d.ts +75 -29
  168. package/dist/pbr-pipeline.d.ts.map +1 -1
  169. package/dist/pipeline/standard-forward-lane.d.ts +5 -0
  170. package/dist/pipeline/standard-forward-lane.d.ts.map +1 -0
  171. package/dist/pipeline/standard-pipeline.d.ts +31 -0
  172. package/dist/pipeline/standard-pipeline.d.ts.map +1 -0
  173. package/dist/pipeline/standard-profile.d.ts +40 -0
  174. package/dist/pipeline/standard-profile.d.ts.map +1 -0
  175. package/dist/pipeline-builder.d.ts +3 -3
  176. package/dist/pipeline-builder.d.ts.map +1 -1
  177. package/dist/pipeline-spec-types.d.ts +24 -0
  178. package/dist/pipeline-spec-types.d.ts.map +1 -0
  179. package/dist/pipeline-spec.d.ts +16 -155
  180. package/dist/pipeline-spec.d.ts.map +1 -1
  181. package/dist/plugin.d.ts +1 -14
  182. package/dist/plugin.d.ts.map +1 -1
  183. package/dist/post-process-errors.d.ts +18 -18
  184. package/dist/post-process-errors.d.ts.map +1 -1
  185. package/dist/prepare/prepared-graphics-resolver.d.ts +2 -5
  186. package/dist/prepare/prepared-graphics-resolver.d.ts.map +1 -1
  187. package/dist/record/__tests__/typed-frame-graph-plan.unit.test.d.ts +2 -0
  188. package/dist/record/__tests__/typed-frame-graph-plan.unit.test.d.ts.map +1 -0
  189. package/dist/record/frame-lighting.d.ts +7 -73
  190. package/dist/record/frame-lighting.d.ts.map +1 -1
  191. package/dist/record/frame-snapshot.d.ts +44 -44
  192. package/dist/record/frame-snapshot.d.ts.map +1 -1
  193. package/dist/record/frame-targets.d.ts +5 -38
  194. package/dist/record/frame-targets.d.ts.map +1 -1
  195. package/dist/record/frame.d.ts +64 -52
  196. package/dist/record/frame.d.ts.map +1 -1
  197. package/dist/record/helpers.d.ts +3 -2
  198. package/dist/record/helpers.d.ts.map +1 -1
  199. package/dist/record/main-pass-geometry.d.ts +5 -4
  200. package/dist/record/main-pass-geometry.d.ts.map +1 -1
  201. package/dist/record/main-pass-material.d.ts +90 -59
  202. package/dist/record/main-pass-material.d.ts.map +1 -1
  203. package/dist/record/main-pass-sprite-draws.d.ts +28 -3
  204. package/dist/record/main-pass-sprite-draws.d.ts.map +1 -1
  205. package/dist/record/main-pass.d.ts +1 -1
  206. package/dist/record/main-pass.d.ts.map +1 -1
  207. package/dist/record/mesh-ssbo.d.ts +276 -3
  208. package/dist/record/mesh-ssbo.d.ts.map +1 -1
  209. package/dist/record/render-context.d.ts +235 -0
  210. package/dist/record/render-context.d.ts.map +1 -0
  211. package/dist/record/shadow-pass.d.ts +15 -88
  212. package/dist/record/shadow-pass.d.ts.map +1 -1
  213. package/dist/record/skybox-post-pass.d.ts +1 -1
  214. package/dist/record/skybox-post-pass.d.ts.map +1 -1
  215. package/dist/record/typed-frame-graph.d.ts +29 -7
  216. package/dist/record/typed-frame-graph.d.ts.map +1 -1
  217. package/dist/record/view-ubo.d.ts +3 -3
  218. package/dist/record/view-ubo.d.ts.map +1 -1
  219. package/dist/render-contract.d.ts +416 -0
  220. package/dist/render-contract.d.ts.map +1 -0
  221. package/dist/render-data.d.ts.map +1 -1
  222. package/dist/render-graph-primitives.d.ts +14 -362
  223. package/dist/render-graph-primitives.d.ts.map +1 -1
  224. package/dist/render-pipeline.d.ts +25 -64
  225. package/dist/render-pipeline.d.ts.map +1 -1
  226. package/dist/render-system-extract.d.ts +107 -338
  227. package/dist/render-system-extract.d.ts.map +1 -1
  228. package/dist/render-system.d.ts +21 -713
  229. package/dist/render-system.d.ts.map +1 -1
  230. package/dist/scene/render-scene-types.d.ts +58 -0
  231. package/dist/scene/render-scene-types.d.ts.map +1 -0
  232. package/dist/scene/render-scene.d.ts +105 -0
  233. package/dist/scene/render-scene.d.ts.map +1 -0
  234. package/dist/scene-instances/post-spawn-resolve-joints.d.ts +2 -5
  235. package/dist/scene-instances/post-spawn-resolve-joints.d.ts.map +1 -1
  236. package/dist/ssao-buffers.d.ts +22 -3
  237. package/dist/ssao-buffers.d.ts.map +1 -1
  238. package/dist/systems/skin-palette-allocator.d.ts +2 -4
  239. package/dist/systems/skin-palette-allocator.d.ts.map +1 -1
  240. package/dist/systems/skin-palette-types.d.ts +14 -0
  241. package/dist/systems/skin-palette-types.d.ts.map +1 -0
  242. package/dist/systems/transparent-sort-config.d.ts +23 -2
  243. package/dist/systems/transparent-sort-config.d.ts.map +1 -1
  244. package/dist/tilemap-chunk-extract-system.d.ts +4 -3
  245. package/dist/tilemap-chunk-extract-system.d.ts.map +1 -1
  246. package/dist/typed-render-graph-primitives.d.ts +2 -4
  247. package/dist/typed-render-graph-primitives.d.ts.map +1 -1
  248. package/dist/typed-shadow-passes.d.ts +3 -3
  249. package/dist/typed-shadow-passes.d.ts.map +1 -1
  250. package/package.json +23 -23
  251. package/src/__tests__/backend-selection-rhi-capture.test-d.ts +1 -1
  252. package/src/__tests__/callback-inference.test-d.ts +6 -3
  253. package/src/__tests__/cluster-grid-default-surface.unit.test.ts +7 -4
  254. package/src/__tests__/construct-renderer-identity.unit.test.ts +15 -2
  255. package/src/__tests__/device-scope-lifecycle.unit.test.ts +151 -0
  256. package/src/__tests__/device-scope-stale-matrix.unit.test.ts +42 -0
  257. package/src/__tests__/direct-extract-consumer-contract.test-d.ts +8 -19
  258. package/src/__tests__/direct-light-snapshot.unit.test.ts +3 -13
  259. package/src/__tests__/factory-contract.integration.test.ts +158 -209
  260. package/src/__tests__/fallback-row-stride-surface.unit.test.ts +1 -4
  261. package/src/__tests__/frame-observation.contract.test.ts +17 -8
  262. package/src/__tests__/fullscreen-feature-plan.unit.test.ts +32 -0
  263. package/src/__tests__/glyph-text-layout-recovery.unit.test.ts +6 -11
  264. package/src/__tests__/gpu-driven-indirect-raster-evidence.ts +9 -16
  265. package/src/__tests__/gpu-driven-production.integration.test.ts +32 -205
  266. package/src/__tests__/gpu-driven-scaling.unit.test.ts +4 -7
  267. package/src/__tests__/gpu-driven-view-gpu-evidence.ts +87 -8
  268. package/src/__tests__/gpu-driven-view-graph.integration.test.ts +170 -8
  269. package/src/{gpu-driven/view-reference.ts → __tests__/gpu-driven-view-reference.ts} +4 -3
  270. package/src/__tests__/gpu-driven-view.browser.test.ts +12 -1
  271. package/src/__tests__/gpu-driven-view.dawn.test.ts +12 -1
  272. package/src/__tests__/gpu-driven-view.unit.test.ts +11 -267
  273. package/src/__tests__/gpu-resource-store-stride.unit.test.ts +3 -3
  274. package/src/__tests__/gpu-scene-render-graph-gpu.ts +2 -3
  275. package/src/__tests__/gpu-scene.dawn.test.ts +2 -6
  276. package/src/__tests__/gpu-scene.unit.test.ts +10 -24
  277. package/src/__tests__/ibl/face-uniforms.unit.test.ts +46 -0
  278. package/src/__tests__/infinite-grid-no-vertex-probe.unit.test.ts +34 -66
  279. package/src/__tests__/ktx2-basis-gpu-consumer.dawn.test.ts +14 -5
  280. package/src/__tests__/light-buffer-layout.unit.test.ts +4 -49
  281. package/src/__tests__/material-cooked-projection.unit.test.ts +2 -51
  282. package/src/__tests__/material-snapshot-mutation.integration.test.ts +10 -15
  283. package/src/__tests__/material-static-dynamic-route.unit.test.ts +4 -23
  284. package/src/__tests__/material-stride-surface.unit.test.ts +2 -2
  285. package/src/__tests__/mesh-buffer-usage-surface.unit.test.ts +7 -8
  286. package/src/__tests__/morph-culling-reentry.dawn.test.ts +29 -38
  287. package/src/__tests__/morph-feature-gpu-work.integration.test.ts +45 -43
  288. package/src/__tests__/morph-feature-plan.unit.test.ts +53 -0
  289. package/src/__tests__/morph-visual-evidence.integration.test.ts +47 -33
  290. package/src/__tests__/nineslice-handle-surface.unit.test.ts +11 -6
  291. package/src/__tests__/post-process-error-policy-owner.unit.test.ts +6 -13
  292. package/src/__tests__/prepared-graphics-capability.unit.test.ts +6 -81
  293. package/src/__tests__/prepared-graphics-generation.unit.test.ts +3 -17
  294. package/src/__tests__/prepared-graphics-lifecycle.unit.test.ts +6 -74
  295. package/src/__tests__/prepared-graphics-resolver.unit.test.ts +1 -3
  296. package/src/__tests__/prepared-graphics-snapshot.unit.test.ts +2 -2
  297. package/src/__tests__/profiler-default-off.test.ts +1 -1
  298. package/src/__tests__/profiler-phase-catalog.test.ts +2 -2
  299. package/src/__tests__/profiler-phase-integration.test.ts +5 -13
  300. package/src/__tests__/profiler-phase-token.test.ts +3 -8
  301. package/src/__tests__/public-surface.test-d.ts +65 -3
  302. package/src/__tests__/recover-error-policy-owner.unit.test.ts +1 -21
  303. package/src/__tests__/render-error-code-owner.test-d.ts +18 -23
  304. package/src/__tests__/render-error-detail-ssot.test-d.ts +0 -95
  305. package/src/__tests__/render-error-exhaustive.test-d.ts +8 -1
  306. package/src/__tests__/render-feature-assembly.test-d.ts +8 -16
  307. package/src/__tests__/render-feature-context.test-d.ts +16 -40
  308. package/src/__tests__/render-feature-contract.test-d.ts +9 -18
  309. package/src/__tests__/render-feature-diagnostics.unit.test.ts +6 -8
  310. package/src/__tests__/render-feature-docs.unit.test.ts +8 -7
  311. package/src/__tests__/render-feature-gpu-work.integration.test.ts +14 -75
  312. package/src/__tests__/render-feature-plan.test-d.ts +161 -0
  313. package/src/__tests__/render-feature-plan.unit.test.ts +170 -0
  314. package/src/__tests__/render-feature-prepared-diagnostics.unit.test.ts +8 -19
  315. package/src/__tests__/render-feature-prepared-errors.test-d.ts +3 -6
  316. package/src/__tests__/render-feature-prepared-graphics-docs.unit.test.ts +10 -71
  317. package/src/__tests__/render-feature-prepared-graphics-isolation.unit.test.ts +11 -61
  318. package/src/__tests__/render-feature-prepared-graphics-negative.test-d.ts +20 -43
  319. package/src/__tests__/render-feature-prepared-graphics.test-d.ts +56 -78
  320. package/src/__tests__/render-feature-public-surface.test-d.ts +6 -24
  321. package/src/__tests__/render-feature-retry.unit.test.ts +7 -48
  322. package/src/__tests__/render-feature-scheduling.unit.test.ts +15 -27
  323. package/src/__tests__/render-feature-stage-failed-policy-owner.unit.test.ts +0 -9
  324. package/src/__tests__/render-feature-stage-fault-recovery.unit.test.ts +19 -109
  325. package/src/__tests__/render-feature-visibility-context.test-d.ts +1 -6
  326. package/src/__tests__/render-feature-visibility-context.unit.test.ts +1 -2
  327. package/src/__tests__/render-feature-zero-work.unit.test.ts +14 -20
  328. package/src/__tests__/render-scene-projection.unit.test.ts +73 -111
  329. package/src/__tests__/renderer-factory-material-contract.unit.test.ts +44 -95
  330. package/src/__tests__/renderer-lifecycle.integration.test.ts +49 -1
  331. package/src/__tests__/shader-stage-surface.unit.test.ts +14 -9
  332. package/src/__tests__/ssao-config.unit.test.ts +36 -0
  333. package/src/__tests__/ssao-kernel-surface.unit.test.ts +4 -5
  334. package/src/__tests__/standard-clustered-pipeline.unit.test.ts +28 -0
  335. package/src/__tests__/standard-descriptors.test-d.ts +22 -0
  336. package/src/__tests__/standard-pipeline.dawn.test.ts +61 -0
  337. package/src/__tests__/standard-pipeline.integration.test.ts +124 -0
  338. package/src/__tests__/standard-profile-oracle.unit.test.ts +62 -0
  339. package/src/__tests__/surface-configuration.unit.test.ts +1 -2
  340. package/src/__tests__/texture-usage-surface.unit.test.ts +3 -6
  341. package/src/__tests__/typed-pipeline-topology.unit.test.ts +40 -206
  342. package/src/__tests__/urp-pipeline-targets.unit.test.ts +1 -11
  343. package/src/__tests__/urp-pipeline.unit.test.ts +2 -2
  344. package/src/__tests__/vertex-color-pipeline.unit.test.ts +10 -2
  345. package/src/__tests__/vertex-layout-consumer.unit.test.ts +1 -1
  346. package/src/__tests__/visibility-diagnostics.integration.test.ts +7 -11
  347. package/src/__tests__/visibility-extract.integration.test.ts +20 -11
  348. package/src/__tests__/visibility-extract.perf.test.ts +66 -32
  349. package/src/__tests__/visibility-feature-dedup.integration.test.ts +2 -4
  350. package/src/__tests__/visibility-hidden-report.unit.test.ts +1 -2
  351. package/src/__tests__/visibility-instances.integration.test.ts +7 -11
  352. package/src/__tests__/visibility-multi-world.integration.test.ts +38 -6
  353. package/src/__tests__/visibility-producer-matrix.integration.test.ts +18 -41
  354. package/src/__tests__/visibility-public-surface.test-d.ts +10 -9
  355. package/src/__tests__/visibility-resolver.unit.test.ts +2 -1
  356. package/src/__tests__/visibility-resource-short-circuit.unit.test.ts +80 -0
  357. package/src/__tests__/visibility-snapshot-contract.unit.test.ts +2 -1
  358. package/src/__tests__/vocabulary.unit.test.ts +1 -1
  359. package/src/assembly/backend-contract.ts +57 -0
  360. package/src/{renderer/renderer-factory.ts → assembly/factory.ts} +1663 -2690
  361. package/src/assembly/host-contract.ts +101 -0
  362. package/src/{renderer → assembly}/material/assembly.ts +0 -2
  363. package/src/assembly/material/pipeline-projection.ts +63 -0
  364. package/src/authoring.ts +14 -22
  365. package/src/components/__tests__/schema-contract.unit.test.ts +10 -0
  366. package/src/components/glyph-text.ts +3 -2
  367. package/src/components/index.ts +7 -9
  368. package/src/components/instances.ts +4 -5
  369. package/src/components/light-helpers.ts +1 -1
  370. package/src/components/mesh-filter.ts +11 -10
  371. package/src/components/post-process-params.ts +3 -3
  372. package/src/components/scene-instance.ts +12 -12
  373. package/src/components/skybox-background.ts +103 -0
  374. package/src/components/{environment.ts → skylight.ts} +2 -110
  375. package/src/components/sprite-animation.ts +3 -4
  376. package/src/components/sprite-instances.ts +2 -2
  377. package/src/components/sprite-playback-mode.ts +7 -1
  378. package/src/components/sprite-region-override.ts +2 -3
  379. package/src/components/tile-layer.ts +8 -2
  380. package/src/construct-renderer.ts +67 -4
  381. package/src/debug-draw-glue.ts +8 -129
  382. package/src/device/device-scope.ts +306 -0
  383. package/src/{gpu-resource-store.ts → device/gpu-residency.ts} +540 -639
  384. package/src/device/resource-types.ts +20 -0
  385. package/src/engine-metrics.ts +12 -19
  386. package/src/errors/recover.ts +45 -3
  387. package/src/errors/render.ts +357 -460
  388. package/src/features/fullscreen.ts +46 -0
  389. package/src/features/host.ts +575 -309
  390. package/src/features/morph/morph-feature.ts +195 -105
  391. package/src/features/plan.ts +447 -0
  392. package/src/features/prepared-gpu-work.ts +72 -15
  393. package/src/features/prepared-graphics-store.ts +20 -6
  394. package/src/features/prepared-graphics.ts +12 -71
  395. package/src/features/render-graph-compute.ts +14 -5
  396. package/src/features/render-graph-contribution.ts +43 -63
  397. package/src/features/render-graph-raster.ts +11 -6
  398. package/src/features/render-graph-resources.ts +25 -23
  399. package/src/features/targets.ts +8 -21
  400. package/src/features/types.ts +39 -227
  401. package/src/features/vocabulary.ts +24 -0
  402. package/src/fullscreen-post-process-pass.ts +14 -57
  403. package/src/glyph-text-layout-system.ts +70 -96
  404. package/src/gpu-driven/batch-topology.ts +24 -202
  405. package/src/gpu-driven/production-raster.ts +93 -1108
  406. package/src/gpu-driven/view-gpu.ts +46 -192
  407. package/src/gpu-resource.ts +4 -4
  408. package/src/gpu-scene-schema.ts +11 -99
  409. package/src/gpu-scene.ts +231 -364
  410. package/src/hdrp-buffers.ts +12 -221
  411. package/src/ibl/IblPipelineCache.ts +471 -711
  412. package/src/ibl/skylight-bind-group.ts +4 -4
  413. package/src/index.ts +58 -217
  414. package/src/inspection-types.ts +76 -0
  415. package/src/instance-buffer-cache.ts +1 -29
  416. package/src/internal.ts +6 -85
  417. package/src/lifecycle.ts +64 -19
  418. package/src/materials.ts +7 -29
  419. package/src/mesh-material-bindings.ts +0 -1
  420. package/src/pbr-pipeline.ts +434 -53
  421. package/src/{urp-pipeline.ts → pipeline/standard-forward-lane.ts} +38 -149
  422. package/src/{hdrp-pipeline.ts → pipeline/standard-pipeline.ts} +221 -191
  423. package/src/pipeline/standard-profile.ts +65 -0
  424. package/src/pipeline-builder.ts +60 -64
  425. package/src/pipeline-spec-types.ts +28 -0
  426. package/src/pipeline-spec.ts +72 -609
  427. package/src/plugin.ts +3 -75
  428. package/src/post-process-errors.ts +31 -33
  429. package/src/prepare/prepared-graphics-resolver.ts +5 -4
  430. package/src/record/__tests__/material-slot-plan.unit.test.ts +1 -33
  431. package/src/record/__tests__/typed-frame-graph-plan.unit.test.ts +41 -0
  432. package/src/record/__tests__/video-texture-view.unit.test.ts +1 -1
  433. package/src/record/frame-lighting.ts +74 -195
  434. package/src/record/frame-snapshot.ts +45 -60
  435. package/src/record/frame-targets.ts +8 -243
  436. package/src/record/frame.ts +509 -796
  437. package/src/record/helpers.ts +6 -11
  438. package/src/record/main-pass-geometry.ts +106 -147
  439. package/src/record/main-pass-material.ts +517 -190
  440. package/src/record/main-pass-sprite-draws.ts +198 -117
  441. package/src/record/main-pass.ts +162 -143
  442. package/src/record/mesh-ssbo.ts +517 -38
  443. package/src/record/render-context.ts +364 -0
  444. package/src/record/shadow-pass.ts +138 -1838
  445. package/src/record/skybox-post-pass.ts +12 -14
  446. package/src/record/typed-frame-graph.ts +215 -254
  447. package/src/record/view-ubo.ts +6 -37
  448. package/src/render-contract.ts +571 -0
  449. package/src/render-data.ts +3 -3
  450. package/src/render-graph-primitives.ts +76 -851
  451. package/src/render-pipeline.ts +104 -188
  452. package/src/render-system-extract.ts +1048 -1729
  453. package/src/render-system.ts +330 -1957
  454. package/src/scene/render-scene-types.ts +64 -0
  455. package/src/scene/render-scene.ts +1032 -0
  456. package/src/scene-instances/post-spawn-resolve-joints.ts +4 -7
  457. package/src/ssao-buffers.ts +167 -8
  458. package/src/systems/skin-palette-allocator.ts +21 -64
  459. package/src/systems/skin-palette-types.ts +14 -0
  460. package/src/systems/transparent-sort-config.ts +25 -3
  461. package/src/tilemap-chunk-extract-system.ts +53 -70
  462. package/src/typed-render-graph-primitives.ts +35 -24
  463. package/src/typed-shadow-passes.ts +26 -56
  464. package/dist/__tests__/analytic-sky-contract.test.d.ts +0 -2
  465. package/dist/__tests__/analytic-sky-contract.test.d.ts.map +0 -1
  466. package/dist/__tests__/analytic-sky-falsify.test.d.ts +0 -2
  467. package/dist/__tests__/analytic-sky-falsify.test.d.ts.map +0 -1
  468. package/dist/__tests__/assets-runtime-handoff.integration.test.d.ts +0 -2
  469. package/dist/__tests__/assets-runtime-handoff.integration.test.d.ts.map +0 -1
  470. package/dist/__tests__/backend-selection.integration.test.d.ts +0 -2
  471. package/dist/__tests__/backend-selection.integration.test.d.ts.map +0 -1
  472. package/dist/__tests__/camera-temporal.test-d.d.ts +0 -2
  473. package/dist/__tests__/camera-temporal.test-d.d.ts.map +0 -1
  474. package/dist/__tests__/camera-temporal.test.d.ts +0 -2
  475. package/dist/__tests__/camera-temporal.test.d.ts.map +0 -1
  476. package/dist/__tests__/directional-shadow-probe-atlas-layout.unit.test.d.ts +0 -2
  477. package/dist/__tests__/directional-shadow-probe-atlas-layout.unit.test.d.ts.map +0 -1
  478. package/dist/__tests__/environment-inspection.test.d.ts +0 -2
  479. package/dist/__tests__/environment-inspection.test.d.ts.map +0 -1
  480. package/dist/__tests__/fog-ai-user.fixture.d.ts +0 -2
  481. package/dist/__tests__/fog-ai-user.fixture.d.ts.map +0 -1
  482. package/dist/__tests__/fog-custom-render-feature.fixture.d.ts +0 -16
  483. package/dist/__tests__/fog-custom-render-feature.fixture.d.ts.map +0 -1
  484. package/dist/__tests__/fog-custom-render-feature.integration.test.d.ts +0 -2
  485. package/dist/__tests__/fog-custom-render-feature.integration.test.d.ts.map +0 -1
  486. package/dist/__tests__/fog-inspection.integration.test.d.ts +0 -2
  487. package/dist/__tests__/fog-inspection.integration.test.d.ts.map +0 -1
  488. package/dist/__tests__/fog-owner-state.integration.test.d.ts +0 -2
  489. package/dist/__tests__/fog-owner-state.integration.test.d.ts.map +0 -1
  490. package/dist/__tests__/fog-pipeline-topology.integration.test.d.ts +0 -2
  491. package/dist/__tests__/fog-pipeline-topology.integration.test.d.ts.map +0 -1
  492. package/dist/__tests__/fog-public-surface.integration.test.d.ts +0 -2
  493. package/dist/__tests__/fog-public-surface.integration.test.d.ts.map +0 -1
  494. package/dist/__tests__/fog-ray-contract.integration.test.d.ts +0 -2
  495. package/dist/__tests__/fog-ray-contract.integration.test.d.ts.map +0 -1
  496. package/dist/__tests__/fog-source-absence.integration.test.d.ts +0 -2
  497. package/dist/__tests__/fog-source-absence.integration.test.d.ts.map +0 -1
  498. package/dist/__tests__/fog-temporal.integration.test.d.ts +0 -2
  499. package/dist/__tests__/fog-temporal.integration.test.d.ts.map +0 -1
  500. package/dist/__tests__/frame-chain.unit.test.d.ts +0 -2
  501. package/dist/__tests__/frame-chain.unit.test.d.ts.map +0 -1
  502. package/dist/__tests__/gpu-scene-field-type-owner.test-d.d.ts +0 -2
  503. package/dist/__tests__/gpu-scene-field-type-owner.test-d.d.ts.map +0 -1
  504. package/dist/__tests__/hdrp-pipeline.unit.test.d.ts +0 -2
  505. package/dist/__tests__/hdrp-pipeline.unit.test.d.ts.map +0 -1
  506. package/dist/__tests__/ibl/ibl-fallback.unit.test.d.ts +0 -2
  507. package/dist/__tests__/ibl/ibl-fallback.unit.test.d.ts.map +0 -1
  508. package/dist/__tests__/material-derived-error-stop-draw.integration.test.d.ts +0 -2
  509. package/dist/__tests__/material-derived-error-stop-draw.integration.test.d.ts.map +0 -1
  510. package/dist/__tests__/material-derived-payload.integration.test.d.ts +0 -2
  511. package/dist/__tests__/material-derived-payload.integration.test.d.ts.map +0 -1
  512. package/dist/__tests__/materials.unit.test.d.ts +0 -2
  513. package/dist/__tests__/materials.unit.test.d.ts.map +0 -1
  514. package/dist/__tests__/param-schema-projection-census.unit.test.d.ts +0 -2
  515. package/dist/__tests__/param-schema-projection-census.unit.test.d.ts.map +0 -1
  516. package/dist/__tests__/persistent-render-scene.unit.test.d.ts +0 -2
  517. package/dist/__tests__/persistent-render-scene.unit.test.d.ts.map +0 -1
  518. package/dist/__tests__/pipeline-error-policy-owner.unit.test.d.ts +0 -2
  519. package/dist/__tests__/pipeline-error-policy-owner.unit.test.d.ts.map +0 -1
  520. package/dist/__tests__/pipeline-pass-trace.test.d.ts +0 -2
  521. package/dist/__tests__/pipeline-pass-trace.test.d.ts.map +0 -1
  522. package/dist/__tests__/pipeline-stage-owner.test-d.d.ts +0 -2
  523. package/dist/__tests__/pipeline-stage-owner.test-d.d.ts.map +0 -1
  524. package/dist/__tests__/plugin.test.d.ts +0 -2
  525. package/dist/__tests__/plugin.test.d.ts.map +0 -1
  526. package/dist/__tests__/prepared-graphics-backend-matrix.unit.test.d.ts +0 -2
  527. package/dist/__tests__/prepared-graphics-backend-matrix.unit.test.d.ts.map +0 -1
  528. package/dist/__tests__/prepared-graphics-resolver-recording.unit.test.d.ts +0 -2
  529. package/dist/__tests__/prepared-graphics-resolver-recording.unit.test.d.ts.map +0 -1
  530. package/dist/__tests__/public-pipeline-id.test-d.d.ts +0 -2
  531. package/dist/__tests__/public-pipeline-id.test-d.d.ts.map +0 -1
  532. package/dist/__tests__/render-dispatch-membership.unit.test.d.ts +0 -2
  533. package/dist/__tests__/render-dispatch-membership.unit.test.d.ts.map +0 -1
  534. package/dist/__tests__/render-feature-construction.unit.test.d.ts +0 -2
  535. package/dist/__tests__/render-feature-construction.unit.test.d.ts.map +0 -1
  536. package/dist/__tests__/render-feature-error-code-owner.test-d.d.ts +0 -2
  537. package/dist/__tests__/render-feature-error-code-owner.test-d.d.ts.map +0 -1
  538. package/dist/__tests__/render-feature-gpu-buffer-usage-owner.test-d.d.ts +0 -2
  539. package/dist/__tests__/render-feature-gpu-buffer-usage-owner.test-d.d.ts.map +0 -1
  540. package/dist/__tests__/render-feature-graph.integration.test.d.ts +0 -2
  541. package/dist/__tests__/render-feature-graph.integration.test.d.ts.map +0 -1
  542. package/dist/__tests__/render-feature-graphics-staging.unit.test.d.ts +0 -2
  543. package/dist/__tests__/render-feature-graphics-staging.unit.test.d.ts.map +0 -1
  544. package/dist/__tests__/render-feature-order.unit.test.d.ts +0 -2
  545. package/dist/__tests__/render-feature-order.unit.test.d.ts.map +0 -1
  546. package/dist/__tests__/render-feature-prepared-graphics-recording.unit.test.d.ts +0 -2
  547. package/dist/__tests__/render-feature-prepared-graphics-recording.unit.test.d.ts.map +0 -1
  548. package/dist/__tests__/render-feature-staging.unit.test.d.ts +0 -2
  549. package/dist/__tests__/render-feature-staging.unit.test.d.ts.map +0 -1
  550. package/dist/__tests__/renderer-render-scene-public.test-d.d.ts +0 -2
  551. package/dist/__tests__/renderer-render-scene-public.test-d.d.ts.map +0 -1
  552. package/dist/__tests__/scene-temporal-coverage.unit.test.d.ts +0 -2
  553. package/dist/__tests__/scene-temporal-coverage.unit.test.d.ts.map +0 -1
  554. package/dist/__tests__/sun-snapshot-consumers.test.d.ts +0 -2
  555. package/dist/__tests__/sun-snapshot-consumers.test.d.ts.map +0 -1
  556. package/dist/__tests__/surface-backend-kind-owner.test-d.d.ts +0 -2
  557. package/dist/__tests__/surface-backend-kind-owner.test-d.d.ts.map +0 -1
  558. package/dist/__tests__/taa-history-store.unit.test.d.ts +0 -2
  559. package/dist/__tests__/taa-history-store.unit.test.d.ts.map +0 -1
  560. package/dist/__tests__/temporal-frame-transaction.integration.test.d.ts +0 -2
  561. package/dist/__tests__/temporal-frame-transaction.integration.test.d.ts.map +0 -1
  562. package/dist/__tests__/temporal-inspection.unit.test.d.ts +0 -2
  563. package/dist/__tests__/temporal-inspection.unit.test.d.ts.map +0 -1
  564. package/dist/assets/__tests__/asset-decoders.unit.test.d.ts +0 -2
  565. package/dist/assets/__tests__/asset-decoders.unit.test.d.ts.map +0 -1
  566. package/dist/assets/__tests__/mipmap-generator.unit.test.d.ts +0 -2
  567. package/dist/assets/__tests__/mipmap-generator.unit.test.d.ts.map +0 -1
  568. package/dist/assets/__tests__/render-asset-port.unit.test.d.ts +0 -2
  569. package/dist/assets/__tests__/render-asset-port.unit.test.d.ts.map +0 -1
  570. package/dist/assets/__tests__/resolve-render-asset.unit.test.d.ts +0 -2
  571. package/dist/assets/__tests__/resolve-render-asset.unit.test.d.ts.map +0 -1
  572. package/dist/chunk-DJIMFYX6.mjs +0 -1439
  573. package/dist/chunk-DJIMFYX6.mjs.map +0 -1
  574. package/dist/chunk-F2SIOCRM.mjs +0 -7423
  575. package/dist/chunk-F2SIOCRM.mjs.map +0 -1
  576. package/dist/chunk-JMSB7AR4.mjs +0 -3
  577. package/dist/chunk-JMSB7AR4.mjs.map +0 -1
  578. package/dist/chunk-KHI6K4C6.mjs +0 -659
  579. package/dist/chunk-KHI6K4C6.mjs.map +0 -1
  580. package/dist/chunk-PORZOL2V.mjs +0 -569
  581. package/dist/chunk-PORZOL2V.mjs.map +0 -1
  582. package/dist/chunk-UOJGMDGP.mjs +0 -29913
  583. package/dist/chunk-UOJGMDGP.mjs.map +0 -1
  584. package/dist/components/__tests__/atmosphere-red.test.d.ts +0 -2
  585. package/dist/components/__tests__/atmosphere-red.test.d.ts.map +0 -1
  586. package/dist/components/__tests__/atmosphere.test.d.ts +0 -2
  587. package/dist/components/__tests__/atmosphere.test.d.ts.map +0 -1
  588. package/dist/components/__tests__/fog.test-d.d.ts +0 -2
  589. package/dist/components/__tests__/fog.test-d.d.ts.map +0 -1
  590. package/dist/components/__tests__/fog.test.d.ts +0 -2
  591. package/dist/components/__tests__/fog.test.d.ts.map +0 -1
  592. package/dist/components/atmosphere.d.ts +0 -18
  593. package/dist/components/atmosphere.d.ts.map +0 -1
  594. package/dist/components/environment.d.ts +0 -115
  595. package/dist/components/environment.d.ts.map +0 -1
  596. package/dist/deferred-lighting.d.ts +0 -13
  597. package/dist/deferred-lighting.d.ts.map +0 -1
  598. package/dist/dynamic-texture-store.d.ts +0 -42
  599. package/dist/dynamic-texture-store.d.ts.map +0 -1
  600. package/dist/environment/__tests__/failure-injection.test.d.ts +0 -2
  601. package/dist/environment/__tests__/failure-injection.test.d.ts.map +0 -1
  602. package/dist/environment/__tests__/generation-red.test.d.ts +0 -2
  603. package/dist/environment/__tests__/generation-red.test.d.ts.map +0 -1
  604. package/dist/environment/__tests__/generation.test.d.ts +0 -2
  605. package/dist/environment/__tests__/generation.test.d.ts.map +0 -1
  606. package/dist/environment/__tests__/graph-transaction.test.d.ts +0 -2
  607. package/dist/environment/__tests__/graph-transaction.test.d.ts.map +0 -1
  608. package/dist/environment/__tests__/inspection-red.test.d.ts +0 -2
  609. package/dist/environment/__tests__/inspection-red.test.d.ts.map +0 -1
  610. package/dist/environment/__tests__/lifecycle-evidence.test.d.ts +0 -2
  611. package/dist/environment/__tests__/lifecycle-evidence.test.d.ts.map +0 -1
  612. package/dist/environment/__tests__/pipeline-topology.test.d.ts +0 -2
  613. package/dist/environment/__tests__/pipeline-topology.test.d.ts.map +0 -1
  614. package/dist/environment/__tests__/readiness.test.d.ts +0 -2
  615. package/dist/environment/__tests__/readiness.test.d.ts.map +0 -1
  616. package/dist/environment/__tests__/recovery.test.d.ts +0 -2
  617. package/dist/environment/__tests__/recovery.test.d.ts.map +0 -1
  618. package/dist/environment/__tests__/resize-dispose.test.d.ts +0 -2
  619. package/dist/environment/__tests__/resize-dispose.test.d.ts.map +0 -1
  620. package/dist/environment/__tests__/signature-red.test.d.ts +0 -2
  621. package/dist/environment/__tests__/signature-red.test.d.ts.map +0 -1
  622. package/dist/environment/__tests__/signature.test.d.ts +0 -2
  623. package/dist/environment/__tests__/signature.test.d.ts.map +0 -1
  624. package/dist/environment/descriptors.d.ts +0 -2
  625. package/dist/environment/descriptors.d.ts.map +0 -1
  626. package/dist/environment/extracted-environment.d.ts +0 -53
  627. package/dist/environment/extracted-environment.d.ts.map +0 -1
  628. package/dist/environment/generation.d.ts +0 -62
  629. package/dist/environment/generation.d.ts.map +0 -1
  630. package/dist/environment/gpu-bundle.d.ts +0 -23
  631. package/dist/environment/gpu-bundle.d.ts.map +0 -1
  632. package/dist/environment/graph.d.ts +0 -108
  633. package/dist/environment/graph.d.ts.map +0 -1
  634. package/dist/environment/inspection.d.ts +0 -48
  635. package/dist/environment/inspection.d.ts.map +0 -1
  636. package/dist/environment/lifecycle.d.ts +0 -22
  637. package/dist/environment/lifecycle.d.ts.map +0 -1
  638. package/dist/environment/readiness.d.ts +0 -29
  639. package/dist/environment/readiness.d.ts.map +0 -1
  640. package/dist/environment/signature.d.ts +0 -70
  641. package/dist/environment/signature.d.ts.map +0 -1
  642. package/dist/extract/__tests__/directional-csm-domain.unit.test.d.ts +0 -2
  643. package/dist/extract/__tests__/directional-csm-domain.unit.test.d.ts.map +0 -1
  644. package/dist/extract/__tests__/environment-source.test.d.ts +0 -2
  645. package/dist/extract/__tests__/environment-source.test.d.ts.map +0 -1
  646. package/dist/extract/__tests__/schema-readers.unit.test.d.ts +0 -2
  647. package/dist/extract/__tests__/schema-readers.unit.test.d.ts.map +0 -1
  648. package/dist/extract/directional-csm.d.ts +0 -129
  649. package/dist/extract/directional-csm.d.ts.map +0 -1
  650. package/dist/extract/index.d.ts +0 -5
  651. package/dist/extract/index.d.ts.map +0 -1
  652. package/dist/extract/material-interface.d.ts +0 -56
  653. package/dist/extract/material-interface.d.ts.map +0 -1
  654. package/dist/extract/schema-readers.d.ts +0 -20
  655. package/dist/extract/schema-readers.d.ts.map +0 -1
  656. package/dist/features/graph-contribution.d.ts +0 -64
  657. package/dist/features/graph-contribution.d.ts.map +0 -1
  658. package/dist/features/index.d.ts +0 -21
  659. package/dist/features/index.d.ts.map +0 -1
  660. package/dist/features/morph/index.d.ts +0 -3
  661. package/dist/features/morph/index.d.ts.map +0 -1
  662. package/dist/features/morph/morph-bounds.d.ts +0 -42
  663. package/dist/features/morph/morph-bounds.d.ts.map +0 -1
  664. package/dist/features/scene-temporal.d.ts +0 -45
  665. package/dist/features/scene-temporal.d.ts.map +0 -1
  666. package/dist/features/taa-resolve.d.ts +0 -21
  667. package/dist/features/taa-resolve.d.ts.map +0 -1
  668. package/dist/frame-chain.d.ts +0 -14
  669. package/dist/frame-chain.d.ts.map +0 -1
  670. package/dist/gpu-driven/view-reference.d.ts.map +0 -1
  671. package/dist/gpu-resource-store.d.ts.map +0 -1
  672. package/dist/hdrp-cluster-membership.d.ts +0 -2
  673. package/dist/hdrp-cluster-membership.d.ts.map +0 -1
  674. package/dist/hdrp-pipeline.d.ts +0 -36
  675. package/dist/hdrp-pipeline.d.ts.map +0 -1
  676. package/dist/ibl/cubemap-projection.d.ts +0 -23
  677. package/dist/ibl/cubemap-projection.d.ts.map +0 -1
  678. package/dist/ibl/face-uniforms.d.ts +0 -65
  679. package/dist/ibl/face-uniforms.d.ts.map +0 -1
  680. package/dist/mesh-update-errors.d.ts +0 -71
  681. package/dist/mesh-update-errors.d.ts.map +0 -1
  682. package/dist/mipmap-generator.d.ts +0 -39
  683. package/dist/mipmap-generator.d.ts.map +0 -1
  684. package/dist/param-schema-projection-census.d.ts +0 -18
  685. package/dist/param-schema-projection-census.d.ts.map +0 -1
  686. package/dist/persistent-render-scene.d.ts +0 -158
  687. package/dist/persistent-render-scene.d.ts.map +0 -1
  688. package/dist/pipeline/index.d.ts +0 -11
  689. package/dist/pipeline/index.d.ts.map +0 -1
  690. package/dist/pipeline-errors.d.ts +0 -110
  691. package/dist/pipeline-errors.d.ts.map +0 -1
  692. package/dist/prepare/__tests__/schema-readers.unit.test.d.ts +0 -2
  693. package/dist/prepare/__tests__/schema-readers.unit.test.d.ts.map +0 -1
  694. package/dist/prepare/index.d.ts +0 -4
  695. package/dist/prepare/index.d.ts.map +0 -1
  696. package/dist/prepare/schema-readers.d.ts +0 -11
  697. package/dist/prepare/schema-readers.d.ts.map +0 -1
  698. package/dist/record/__tests__/environment-record.test.d.ts +0 -2
  699. package/dist/record/__tests__/environment-record.test.d.ts.map +0 -1
  700. package/dist/record/__tests__/frame-targets.unit.test.d.ts +0 -2
  701. package/dist/record/__tests__/frame-targets.unit.test.d.ts.map +0 -1
  702. package/dist/record/__tests__/membership-timing.unit.test.d.ts +0 -2
  703. package/dist/record/__tests__/membership-timing.unit.test.d.ts.map +0 -1
  704. package/dist/record/__tests__/render-submission.integration.test.d.ts +0 -2
  705. package/dist/record/__tests__/render-submission.integration.test.d.ts.map +0 -1
  706. package/dist/record/__tests__/render-submission.perf.test.d.ts +0 -2
  707. package/dist/record/__tests__/render-submission.perf.test.d.ts.map +0 -1
  708. package/dist/record/__tests__/shadow-submit-mode.unit.test.d.ts +0 -2
  709. package/dist/record/__tests__/shadow-submit-mode.unit.test.d.ts.map +0 -1
  710. package/dist/record/__tests__/view-ubo-layout.unit.test.d.ts +0 -2
  711. package/dist/record/__tests__/view-ubo-layout.unit.test.d.ts.map +0 -1
  712. package/dist/record/frame-observation.d.ts +0 -38
  713. package/dist/record/frame-observation.d.ts.map +0 -1
  714. package/dist/record/index.d.ts +0 -15
  715. package/dist/record/index.d.ts.map +0 -1
  716. package/dist/record/main-pass-skin.d.ts +0 -46
  717. package/dist/record/main-pass-skin.d.ts.map +0 -1
  718. package/dist/record/main-pass-sprite.d.ts +0 -69
  719. package/dist/record/main-pass-sprite.d.ts.map +0 -1
  720. package/dist/record/material-payload.d.ts +0 -43
  721. package/dist/record/material-payload.d.ts.map +0 -1
  722. package/dist/record/material-slot-plan.d.ts +0 -26
  723. package/dist/record/material-slot-plan.d.ts.map +0 -1
  724. package/dist/record/membership-timing.d.ts +0 -232
  725. package/dist/record/membership-timing.d.ts.map +0 -1
  726. package/dist/record/shadow-caster-uniform.d.ts +0 -8
  727. package/dist/record/shadow-caster-uniform.d.ts.map +0 -1
  728. package/dist/record/shadow-submit-mode.d.ts +0 -66
  729. package/dist/record/shadow-submit-mode.d.ts.map +0 -1
  730. package/dist/render-dispatch-membership.d.ts +0 -47
  731. package/dist/render-dispatch-membership.d.ts.map +0 -1
  732. package/dist/render-pipeline-context.d.ts +0 -289
  733. package/dist/render-pipeline-context.d.ts.map +0 -1
  734. package/dist/render-pipeline-target.d.ts +0 -16
  735. package/dist/render-pipeline-target.d.ts.map +0 -1
  736. package/dist/render-scene-projection.d.ts +0 -174
  737. package/dist/render-scene-projection.d.ts.map +0 -1
  738. package/dist/render-system-fold.d.ts +0 -266
  739. package/dist/render-system-fold.d.ts.map +0 -1
  740. package/dist/renderer/asset-service.d.ts +0 -10
  741. package/dist/renderer/asset-service.d.ts.map +0 -1
  742. package/dist/renderer/backend-selection.d.ts.map +0 -1
  743. package/dist/renderer/environment-classify.d.ts +0 -6
  744. package/dist/renderer/environment-classify.d.ts.map +0 -1
  745. package/dist/renderer/environment-error.d.ts +0 -13
  746. package/dist/renderer/environment-error.d.ts.map +0 -1
  747. package/dist/renderer/material/pipeline-projection.d.ts +0 -91
  748. package/dist/renderer/material/pipeline-projection.d.ts.map +0 -1
  749. package/dist/renderer/renderer-factory.d.ts +0 -289
  750. package/dist/renderer/renderer-factory.d.ts.map +0 -1
  751. package/dist/renderer.d.ts +0 -771
  752. package/dist/renderer.d.ts.map +0 -1
  753. package/dist/scene-utils/collect-subtree.d.ts +0 -11
  754. package/dist/scene-utils/collect-subtree.d.ts.map +0 -1
  755. package/dist/ssao-data.d.ts +0 -34
  756. package/dist/ssao-data.d.ts.map +0 -1
  757. package/dist/surface-format.d.ts +0 -10
  758. package/dist/surface-format.d.ts.map +0 -1
  759. package/dist/taa-history-store.d.ts +0 -88
  760. package/dist/taa-history-store.d.ts.map +0 -1
  761. package/dist/temporal-coverage.d.ts +0 -35
  762. package/dist/temporal-coverage.d.ts.map +0 -1
  763. package/dist/temporal-frame-transaction.d.ts +0 -92
  764. package/dist/temporal-frame-transaction.d.ts.map +0 -1
  765. package/dist/temporal-inspection.d.ts +0 -57
  766. package/dist/temporal-inspection.d.ts.map +0 -1
  767. package/dist/temporal-view.d.ts +0 -43
  768. package/dist/temporal-view.d.ts.map +0 -1
  769. package/dist/tilemap/resolve-tileset-runtime.d.ts +0 -23
  770. package/dist/tilemap/resolve-tileset-runtime.d.ts.map +0 -1
  771. package/dist/urp-pipeline.d.ts +0 -4
  772. package/dist/urp-pipeline.d.ts.map +0 -1
  773. package/src/__tests__/analytic-sky-contract.test.ts +0 -27
  774. package/src/__tests__/analytic-sky-falsify-carriers.mjs +0 -43
  775. package/src/__tests__/analytic-sky-falsify.test.ts +0 -94
  776. package/src/__tests__/assets-runtime-handoff.integration.test.ts +0 -36
  777. package/src/__tests__/backend-selection.integration.test.ts +0 -11
  778. package/src/__tests__/camera-temporal.test-d.ts +0 -8
  779. package/src/__tests__/camera-temporal.test.ts +0 -188
  780. package/src/__tests__/directional-shadow-probe-atlas-layout.unit.test.ts +0 -18
  781. package/src/__tests__/environment-inspection.test.ts +0 -45
  782. package/src/__tests__/fog-ai-user.fixture.ts +0 -350
  783. package/src/__tests__/fog-custom-render-feature.fixture.ts +0 -33
  784. package/src/__tests__/fog-custom-render-feature.integration.test.ts +0 -76
  785. package/src/__tests__/fog-inspection.integration.test.ts +0 -127
  786. package/src/__tests__/fog-owner-state.integration.test.ts +0 -112
  787. package/src/__tests__/fog-pipeline-topology.integration.test.ts +0 -93
  788. package/src/__tests__/fog-public-surface.integration.test.ts +0 -60
  789. package/src/__tests__/fog-ray-contract.integration.test.ts +0 -73
  790. package/src/__tests__/fog-source-absence.integration.test.ts +0 -51
  791. package/src/__tests__/fog-temporal.integration.test.ts +0 -115
  792. package/src/__tests__/frame-chain.unit.test.ts +0 -26
  793. package/src/__tests__/gpu-scene-field-type-owner.test-d.ts +0 -27
  794. package/src/__tests__/hdrp-pipeline.unit.test.ts +0 -100
  795. package/src/__tests__/ibl/ibl-fallback.unit.test.ts +0 -23
  796. package/src/__tests__/material-derived-error-stop-draw.integration.test.ts +0 -69
  797. package/src/__tests__/material-derived-payload.integration.test.ts +0 -164
  798. package/src/__tests__/materials.unit.test.ts +0 -25
  799. package/src/__tests__/param-schema-projection-census.unit.test.ts +0 -40
  800. package/src/__tests__/persistent-render-scene.unit.test.ts +0 -1311
  801. package/src/__tests__/pipeline-error-policy-owner.unit.test.ts +0 -167
  802. package/src/__tests__/pipeline-pass-trace.test.ts +0 -92
  803. package/src/__tests__/pipeline-stage-owner.test-d.ts +0 -13
  804. package/src/__tests__/plugin.test.ts +0 -25
  805. package/src/__tests__/prepared-graphics-backend-matrix.unit.test.ts +0 -129
  806. package/src/__tests__/prepared-graphics-resolver-recording.unit.test.ts +0 -180
  807. package/src/__tests__/public-pipeline-id.test-d.ts +0 -6
  808. package/src/__tests__/render-dispatch-membership.unit.test.ts +0 -117
  809. package/src/__tests__/render-feature-construction.unit.test.ts +0 -124
  810. package/src/__tests__/render-feature-error-code-owner.test-d.ts +0 -27
  811. package/src/__tests__/render-feature-gpu-buffer-usage-owner.test-d.ts +0 -46
  812. package/src/__tests__/render-feature-graph.integration.test.ts +0 -205
  813. package/src/__tests__/render-feature-graphics-staging.unit.test.ts +0 -200
  814. package/src/__tests__/render-feature-order.unit.test.ts +0 -66
  815. package/src/__tests__/render-feature-prepared-graphics-recording.unit.test.ts +0 -107
  816. package/src/__tests__/render-feature-staging.unit.test.ts +0 -94
  817. package/src/__tests__/renderer-render-scene-public.test-d.ts +0 -27
  818. package/src/__tests__/scene-temporal-coverage.unit.test.ts +0 -259
  819. package/src/__tests__/sun-snapshot-consumers.test.ts +0 -35
  820. package/src/__tests__/surface-backend-kind-owner.test-d.ts +0 -42
  821. package/src/__tests__/taa-history-store.unit.test.ts +0 -196
  822. package/src/__tests__/temporal-frame-transaction.integration.test.ts +0 -537
  823. package/src/__tests__/temporal-inspection.unit.test.ts +0 -138
  824. package/src/assets/__tests__/asset-decoders.unit.test.ts +0 -92
  825. package/src/assets/__tests__/mipmap-generator.unit.test.ts +0 -52
  826. package/src/assets/__tests__/render-asset-port.unit.test.ts +0 -54
  827. package/src/assets/__tests__/resolve-render-asset.unit.test.ts +0 -47
  828. package/src/components/__tests__/atmosphere-red.test.ts +0 -51
  829. package/src/components/__tests__/atmosphere.test.ts +0 -79
  830. package/src/components/__tests__/fog.test-d.ts +0 -49
  831. package/src/components/__tests__/fog.test.ts +0 -60
  832. package/src/components/atmosphere.ts +0 -64
  833. package/src/deferred-lighting.ts +0 -92
  834. package/src/dynamic-texture-store.ts +0 -111
  835. package/src/environment/README.md +0 -76
  836. package/src/environment/__tests__/failure-injection.test.ts +0 -50
  837. package/src/environment/__tests__/generation-red.test.ts +0 -64
  838. package/src/environment/__tests__/generation.test.ts +0 -37
  839. package/src/environment/__tests__/graph-transaction.test.ts +0 -85
  840. package/src/environment/__tests__/inspection-red.test.ts +0 -53
  841. package/src/environment/__tests__/lifecycle-evidence.test.ts +0 -40
  842. package/src/environment/__tests__/pipeline-topology.test.ts +0 -33
  843. package/src/environment/__tests__/readiness.test.ts +0 -42
  844. package/src/environment/__tests__/recovery.test.ts +0 -62
  845. package/src/environment/__tests__/resize-dispose.test.ts +0 -30
  846. package/src/environment/__tests__/signature-red.test.ts +0 -45
  847. package/src/environment/__tests__/signature.test.ts +0 -60
  848. package/src/environment/descriptors.ts +0 -7
  849. package/src/environment/extracted-environment.ts +0 -239
  850. package/src/environment/generation.ts +0 -158
  851. package/src/environment/gpu-bundle.ts +0 -144
  852. package/src/environment/graph.ts +0 -588
  853. package/src/environment/inspection.ts +0 -107
  854. package/src/environment/lifecycle.ts +0 -124
  855. package/src/environment/readiness.ts +0 -76
  856. package/src/environment/signature.ts +0 -185
  857. package/src/extract/__tests__/directional-csm-domain.unit.test.ts +0 -391
  858. package/src/extract/__tests__/environment-source.test.ts +0 -105
  859. package/src/extract/__tests__/schema-readers.unit.test.ts +0 -16
  860. package/src/extract/directional-csm.ts +0 -959
  861. package/src/extract/index.ts +0 -10
  862. package/src/extract/material-interface.ts +0 -604
  863. package/src/extract/schema-readers.ts +0 -27
  864. package/src/features/graph-contribution.ts +0 -577
  865. package/src/features/index.ts +0 -100
  866. package/src/features/morph/index.ts +0 -24
  867. package/src/features/morph/morph-bounds.ts +0 -123
  868. package/src/features/scene-temporal.ts +0 -274
  869. package/src/features/taa-resolve.ts +0 -244
  870. package/src/frame-chain.ts +0 -21
  871. package/src/hdrp-cluster-membership.ts +0 -70
  872. package/src/ibl/cubemap-projection.ts +0 -60
  873. package/src/ibl/face-uniforms.ts +0 -150
  874. package/src/mesh-update-errors.ts +0 -113
  875. package/src/mipmap-generator.ts +0 -244
  876. package/src/param-schema-projection-census.ts +0 -73
  877. package/src/persistent-render-scene.ts +0 -1752
  878. package/src/pipeline/index.ts +0 -40
  879. package/src/pipeline-errors.ts +0 -172
  880. package/src/prepare/__tests__/schema-readers.unit.test.ts +0 -16
  881. package/src/prepare/index.ts +0 -8
  882. package/src/prepare/schema-readers.ts +0 -20
  883. package/src/record/__tests__/environment-record.test.ts +0 -35
  884. package/src/record/__tests__/frame-targets.unit.test.ts +0 -54
  885. package/src/record/__tests__/membership-timing.unit.test.ts +0 -524
  886. package/src/record/__tests__/render-submission.integration.test.ts +0 -76
  887. package/src/record/__tests__/render-submission.perf.test.ts +0 -141
  888. package/src/record/__tests__/shadow-submit-mode.unit.test.ts +0 -88
  889. package/src/record/__tests__/view-ubo-layout.unit.test.ts +0 -101
  890. package/src/record/frame-observation.ts +0 -122
  891. package/src/record/index.ts +0 -15
  892. package/src/record/main-pass-skin.ts +0 -51
  893. package/src/record/main-pass-sprite.ts +0 -166
  894. package/src/record/material-payload.ts +0 -292
  895. package/src/record/material-slot-plan.ts +0 -68
  896. package/src/record/membership-timing.ts +0 -1012
  897. package/src/record/shadow-caster-uniform.ts +0 -37
  898. package/src/record/shadow-submit-mode.ts +0 -277
  899. package/src/render-dispatch-membership.ts +0 -155
  900. package/src/render-pipeline-context.ts +0 -332
  901. package/src/render-pipeline-target.ts +0 -96
  902. package/src/render-scene-projection.ts +0 -1738
  903. package/src/render-system-fold.ts +0 -531
  904. package/src/renderer/asset-service.ts +0 -32
  905. package/src/renderer/backend-selection.ts +0 -121
  906. package/src/renderer/environment-classify.ts +0 -30
  907. package/src/renderer/environment-error.ts +0 -19
  908. package/src/renderer/material/pipeline-projection.ts +0 -140
  909. package/src/renderer.ts +0 -940
  910. package/src/scene-utils/collect-subtree.ts +0 -40
  911. package/src/ssao-data.ts +0 -106
  912. package/src/surface-format.ts +0 -37
  913. package/src/taa-history-store.ts +0 -429
  914. package/src/temporal-coverage.ts +0 -106
  915. package/src/temporal-frame-transaction.ts +0 -301
  916. package/src/temporal-inspection.ts +0 -194
  917. package/src/temporal-view.ts +0 -174
  918. package/src/tilemap/resolve-tileset-runtime.ts +0 -72
@@ -1,74 +1,47 @@
1
1
  // @forgeax/engine-runtime - RenderSystem record stage: frame.
2
2
  // Extracted from render-system-record.ts (feat-20260704 M3/w17, pure move).
3
3
 
4
+ import { HANDLE_NINESLICE_QUAD, resolveAssetHandle } from '@forgeax/engine-assets-runtime';
4
5
  import type { World } from '@forgeax/engine-ecs';
5
- import { createWorldProjection } from '@forgeax/engine-ecs/projection';
6
- import { type RhiCommandEncoder, RhiError, type TextureView } from '@forgeax/engine-rhi';
7
- import type { MaterialRenderState, MeshAsset } from '@forgeax/engine-types';
8
- import { toShared } from '@forgeax/engine-types';
9
- import { resolveRenderAsset } from '../assets/resolve-render-asset';
10
- import type { EngineMetrics } from '../engine-metrics';
11
- import type { ExtractedEnvironment } from '../environment/extracted-environment';
12
- import {
13
- beginEnvironmentGeneration,
14
- environmentConsumerSnapshot,
15
- publishEnvironmentGeneration,
16
- } from '../environment/generation';
17
- import { createEnvironmentGpuBundle } from '../environment/gpu-bundle';
18
- import type { EnvironmentTransactionAttempt } from '../environment/graph';
6
+ import type { RenderReadLease } from '@forgeax/engine-ecs/projection';
19
7
  import {
20
- completeEnvironmentQueue,
21
- destroyCompletedEnvironmentGenerations,
22
- resizeEnvironmentGeneration,
23
- } from '../environment/lifecycle';
8
+ type CurrentFrameObservationLease,
9
+ createCurrentFrameObservationLease,
10
+ } from '@forgeax/engine-render-graph';
24
11
  import {
25
- deriveEnvironmentBundleDescriptors,
26
- environmentBundleBytes,
27
- environmentSignature,
28
- } from '../environment/signature';
29
- import { buildSubmissionCandidateMembership } from '../gpu-driven/batch-topology';
12
+ err,
13
+ ok,
14
+ type Result,
15
+ type RhiCommandEncoder,
16
+ RhiError,
17
+ type TextureFormat,
18
+ type TextureView,
19
+ } from '@forgeax/engine-rhi';
20
+ import type { MaterialRenderState, MeshAsset } from '@forgeax/engine-types';
21
+ import { handleSlot, toShared } from '@forgeax/engine-types';
22
+ import type { MeshGpuHandles } from '../device/gpu-residency';
23
+ import { ObservationUnavailableError, type ObservationUnavailableReason } from '../errors/render';
30
24
  import type { GpuDrivenProduction, PreparedGpuDrivenFrame } from '../gpu-driven/production-raster';
31
- import type { CpuMultiViewVisibilityResult } from '../gpu-driven/view-gpu';
25
+ import { GpuBuffer } from '../gpu-resource';
26
+ import { GPU_BUFFER_USAGE_COPY_DST, GPU_BUFFER_USAGE_VERTEX } from '../gpu-usage';
32
27
  import { disposeTransientInstanceBuffers } from '../instance-buffer-cache';
33
- import type { PersistentGpuDrivenState } from '../persistent-render-scene';
34
- import type { _InternalRenderPipelineContext } from '../render-pipeline-context';
35
- import type { RenderSceneProjection, RenderSceneSubmissionDelta } from '../render-scene-projection';
36
- import type { MeshGpuHandles, RenderSystemInternals } from '../render-system';
28
+ import type { CameraSnapshot, RenderRecordPhase } from '../render-contract';
37
29
  import type {
38
- CameraSnapshot,
39
30
  DispatchEntry,
40
31
  ExtractedLights,
41
- FogSnapshot,
42
32
  MaterialSnapshot,
43
33
  RenderableSnapshot,
34
+ SkyboxSnapshot,
35
+ SkylightSnapshot,
44
36
  } from '../render-system-extract';
45
- // feat-20260622-chunk-gpu-instancing-sprite-tilemap M1 / w4 (D-1 record-stage
46
- // fold operator). Pure linear-scan helper that groups transparent-dispatch
47
- // entries with equal (Layer.value, sortKey, materialHandle) into FoldBucket
48
- // descriptors. The drawIndexed swap (1 instanced draw per bucket vs N
49
- // per-entity draws) hooks into the sprite-pass dispatch loop below
50
- // (w4-record-swap), using {@link buildFoldDispatchPlan} to translate
51
- // renderableIndex-keyed buckets into validatedOrdered-index-keyed
52
- // head/skip maps.
53
- import {
54
- buildFoldDispatchPlan,
55
- evaluateFoldBucketUniformCap,
56
- type FoldBucket,
57
- type FoldDispatchPlan,
58
- foldDispatchBuckets,
59
- incrementFoldedDrawsMetric,
60
- } from '../render-system-fold';
61
- import type { RenderRecordPhase } from '../renderer';
37
+ import type { PersistentGpuDrivenState } from '../scene/render-scene';
62
38
  import {
63
39
  getTransparentSortConfig,
64
40
  TRANSPARENT_SORT_MODE_LAYER_Y,
65
41
  TRANSPARENT_SORT_MODE_LAYER_Z,
66
42
  } from '../systems/transparent-sort-config';
67
- import type { TemporalCoverageSummary } from '../temporal-coverage';
68
- import type { TemporalFrameAttempt, TemporalFrameTransaction } from '../temporal-frame-transaction';
69
43
  import {
70
44
  buildPerFrameBindGroups,
71
- createEnvironmentConsumerSnapshot,
72
45
  prepareFrameLighting,
73
46
  resolveSkyboxActive,
74
47
  warnZeroLightStandard,
@@ -78,16 +51,139 @@ import {
78
51
  } from './frame-lighting';
79
52
  import {
80
53
  type BindGroupCounts,
81
- clampPcfKernelSize,
82
54
  type DirectionalShadowCache,
83
55
  type DirectionalShadowWorldState,
84
56
  type DispatchCounts,
57
+ type FrameObservationSource,
85
58
  makeZeroCameraFallbackSnapshot,
86
- type RasterItemKey,
87
59
  type RenderFrameState,
88
60
  type ValidatedRenderable,
89
61
  worldEntityKey,
90
62
  } from './frame-snapshot';
63
+ // feat-20260622-chunk-gpu-instancing-sprite-tilemap M1 / w4 (D-1 record-stage
64
+ // fold operator). Pure linear-scan helper that groups transparent-dispatch
65
+ // entries with equal (Layer.value, sortKey, materialHandle) into FoldBucket
66
+ // descriptors. The drawIndexed swap (1 instanced draw per bucket vs N
67
+ // per-entity draws) hooks into the sprite-pass dispatch loop below
68
+ // (w4-record-swap), using {@link buildFoldDispatchPlan} to translate
69
+ // renderableIndex-keyed buckets into validatedOrdered-index-keyed
70
+ // head/skip maps.
71
+ import {
72
+ buildFoldDispatchPlan,
73
+ evaluateFoldBucketUniformCap,
74
+ type FoldBucket,
75
+ type FoldDispatchPlan,
76
+ foldDispatchBuckets,
77
+ incrementFoldedDrawsMetric,
78
+ } from './mesh-ssbo';
79
+ import type {
80
+ _InternalRenderPipelineContext,
81
+ PipelineState,
82
+ RenderSystemInternals,
83
+ } from './render-context';
84
+
85
+ export type { FrameObservationSource } from './frame-snapshot';
86
+
87
+ export type FrameObservationReadback = (
88
+ lease: CurrentFrameObservationLease,
89
+ ) => Promise<Result<Uint8Array, Error>>;
90
+
91
+ export interface FrameObservationOptions {
92
+ readonly semantic: 'linear-hdr';
93
+ readonly readback: FrameObservationReadback;
94
+ }
95
+
96
+ export interface FrameObservationMetadata {
97
+ readonly format: TextureFormat;
98
+ readonly size: { readonly width: number; readonly height: number };
99
+ readonly usage: number;
100
+ readonly sample: number;
101
+ readonly frameId: number;
102
+ readonly lifetime: { readonly frameId: number; readonly state: 'active' | 'retired' };
103
+ readonly pipelineId: 'forgeax::standard';
104
+ readonly backendId: string;
105
+ }
106
+
107
+ export interface FrameObservation {
108
+ readonly bytes: Uint8Array;
109
+ readonly metadata: FrameObservationMetadata;
110
+ }
111
+
112
+ function unavailable(
113
+ reason: ObservationUnavailableReason,
114
+ hint: string,
115
+ ): Result<never, ObservationUnavailableError> {
116
+ return err(new ObservationUnavailableError(reason, hint));
117
+ }
118
+
119
+ function mapLeaseFailure(code: string, hint: string): Result<never, ObservationUnavailableError> {
120
+ let reason: ObservationUnavailableReason = 'resource';
121
+ if (code === 'observation-stale') reason = 'stale';
122
+ else if (code === 'observation-invalid-format') reason = 'format';
123
+ else if (code === 'observation-missing-copy-src') reason = 'copy-src';
124
+ return unavailable(reason, hint);
125
+ }
126
+
127
+ export async function observeCurrentFrame(
128
+ options: FrameObservationOptions,
129
+ source: FrameObservationSource | undefined,
130
+ currentFrameId: number,
131
+ ): Promise<Result<FrameObservation, ObservationUnavailableError>> {
132
+ if (options.semantic !== 'linear-hdr') {
133
+ return unavailable('identity', 'request the producer-owned linear-hdr semantic');
134
+ }
135
+ if (source === undefined) {
136
+ return unavailable(
137
+ 'no-frame',
138
+ 'draw a frame through the Standard producer before requesting observation',
139
+ );
140
+ }
141
+ if (source.pipelineId !== 'forgeax::standard' || source.backendId.length === 0) {
142
+ return unavailable(
143
+ 'identity',
144
+ 'observe a producer with explicit pipeline and backend identity',
145
+ );
146
+ }
147
+
148
+ const leaseResult = createCurrentFrameObservationLease(
149
+ { ...source.descriptor, texture: source.texture, frameId: source.frameId },
150
+ currentFrameId,
151
+ );
152
+ if (!leaseResult.ok) {
153
+ return mapLeaseFailure(leaseResult.error.code, leaseResult.error.hint);
154
+ }
155
+
156
+ let bytesResult: Result<Uint8Array, Error>;
157
+ try {
158
+ bytesResult = await options.readback(leaseResult.value);
159
+ } catch (cause) {
160
+ const message = cause instanceof Error ? cause.message : String(cause);
161
+ return unavailable('readback-failed', `retry the current-frame readback after: ${message}`);
162
+ }
163
+ if (!bytesResult.ok) {
164
+ return unavailable('readback-failed', bytesResult.error.message);
165
+ }
166
+
167
+ const activeResult = leaseResult.value.beginReadback();
168
+ if (!activeResult.ok) {
169
+ return mapLeaseFailure(activeResult.error.code, activeResult.error.hint);
170
+ }
171
+
172
+ return ok({
173
+ bytes: bytesResult.value,
174
+ metadata: {
175
+ format: leaseResult.value.descriptor.format,
176
+ size: leaseResult.value.descriptor.size,
177
+ usage: leaseResult.value.descriptor.usage,
178
+ sample: source.descriptor.sample,
179
+ frameId: source.frameId,
180
+ lifetime: leaseResult.value.lifetime,
181
+ pipelineId: source.pipelineId,
182
+ backendId: source.backendId,
183
+ },
184
+ });
185
+ }
186
+
91
187
  import {
92
188
  acquireSwapChainTarget,
93
189
  graphExecutionPhase,
@@ -102,62 +198,89 @@ import {
102
198
  warnMultiSkybox,
103
199
  warnMultiSkylight,
104
200
  } from './helpers';
105
- import { computeSplitLdrSprite } from './main-pass-sprite';
106
- import {
107
- buildMaterialSlotPlan,
108
- findRenderablePrefixForSlotCapacity,
109
- materialSlotCountForPrefix,
110
- } from './material-slot-plan';
201
+ import { computeSplitLdrSprite } from './main-pass-sprite-draws';
111
202
  import { cleanPerEntityCache, ensureMeshSsboCapacity, uploadMeshSsboBatch } from './mesh-ssbo';
112
- import { writeShadowCasterUniforms } from './shadow-caster-uniform';
113
- import { ensureCompiledFrameGraph, executeCompiledFrameGraph } from './typed-frame-graph';
203
+ import { writeShadowCasterUniforms } from './shadow-pass';
204
+ import {
205
+ ensureCompiledFrameGraph,
206
+ executeCompiledFrameGraph,
207
+ type RenderFeatureGraphCandidate,
208
+ } from './typed-frame-graph';
114
209
  import { writeViewUbo } from './view-ubo';
115
210
 
116
- export type RecordProfileRunner = <T>(phase: RenderRecordPhase, action: () => T) => T;
117
-
118
- const EMPTY_RASTER_ITEM_KEYS: ReadonlySet<RasterItemKey> = new Set<RasterItemKey>();
119
-
120
- type ShadowSceneSlot = PersistentGpuDrivenState['slots'][number];
211
+ /**
212
+ * A frame-local material table. Repeated snapshot objects share one UBO slot;
213
+ * each renderable keeps only the slot index used by each authored material.
214
+ */
215
+ export interface MaterialSlotPlan<T extends object> {
216
+ readonly slotIndices: readonly (readonly number[])[];
217
+ readonly slots: readonly T[];
218
+ /** Index of the first renderable that referenced each slot. */
219
+ readonly slotOwners: readonly number[];
220
+ }
121
221
 
122
- // PersistentRenderScene publishes the production O(1) stable-id join. The
123
- // compatibility path below deliberately rebuilds its small index per token
124
- // calculation: custom/test doubles may mutate a slots array in place, so an
125
- // identity cache would be able to return a stale slot and create a false hit.
126
- function shadowSlotIndex(slots: readonly ShadowSceneSlot[]): ReadonlyMap<number, ShadowSceneSlot> {
127
- const index = new Map<number, ShadowSceneSlot>();
128
- for (const slot of slots) index.set(slot.slot, slot);
129
- return index;
222
+ /**
223
+ * Intern identical material snapshots into one frame-local slot table.
224
+ * Snapshot identity is the extract layer's invalidation token, so this keeps
225
+ * the same correctness boundary while avoiding duplicate payload assembly and
226
+ * upload for repeated scene instances.
227
+ */
228
+ export function buildMaterialSlotPlan<T extends object>(
229
+ materialGroups: readonly (readonly T[])[],
230
+ ): MaterialSlotPlan<T> {
231
+ const slotByMaterial = new Map<T, number>();
232
+ const slots: T[] = [];
233
+ const slotOwners: number[] = [];
234
+ const slotIndices = materialGroups.map((materials, ownerIndex) =>
235
+ materials.map((material) => {
236
+ const cached = slotByMaterial.get(material);
237
+ if (cached !== undefined) return cached;
238
+ const slot = slots.length;
239
+ slots.push(material);
240
+ slotOwners.push(ownerIndex);
241
+ slotByMaterial.set(material, slot);
242
+ return slot;
243
+ }),
244
+ );
245
+ return { slotIndices, slots, slotOwners };
130
246
  }
131
247
 
132
- function sortedUniqueStableIds(values: readonly number[]): number[] {
133
- const sorted = [...values].sort((left, right) => left - right);
134
- let write = 0;
135
- for (const value of sorted) {
136
- if (write > 0 && sorted[write - 1] === value) continue;
137
- sorted[write] = value;
138
- write += 1;
248
+ /**
249
+ * Return the largest complete renderable prefix supported by a shared
250
+ * mesh/material capacity. Mesh rows consume one slot per renderable; material
251
+ * rows use the deduplicated slot indices assigned in first-seen order.
252
+ */
253
+ export function findRenderablePrefixForSlotCapacity(
254
+ materialSlotIndices: readonly (readonly number[])[],
255
+ slotCapacity: number,
256
+ ): number {
257
+ const capacity = Math.max(0, Math.floor(slotCapacity));
258
+ const maxRenderableCount = Math.min(materialSlotIndices.length, capacity);
259
+ let renderableCount = 0;
260
+ while (renderableCount < maxRenderableCount) {
261
+ const slots = materialSlotIndices[renderableCount] ?? [];
262
+ if (slots.some((slot) => slot >= capacity)) break;
263
+ renderableCount += 1;
139
264
  }
140
- sorted.length = write;
141
- return sorted;
265
+ return renderableCount;
142
266
  }
143
267
 
144
- function visibilityCandidateIndices(
145
- visibility: CpuMultiViewVisibilityResult | undefined,
146
- kind: 'main' | 'cascade',
147
- cascadeIndex?: number,
148
- ): ReadonlySet<number> | undefined {
149
- const view = visibility?.views.find(
150
- (candidate) =>
151
- candidate.kind === kind && (kind === 'main' || candidate.cascadeIndex === cascadeIndex),
152
- );
153
- if (view === undefined) return undefined;
154
- return new Set(
155
- view.compactVisible.flatMap((candidate) =>
156
- candidate.candidateIndex === undefined ? [] : [candidate.candidateIndex],
157
- ),
158
- );
268
+ /** Count material slots reachable from a prefix of a first-seen slot plan. */
269
+ export function materialSlotCountForPrefix(
270
+ materialSlotIndices: readonly (readonly number[])[],
271
+ renderableCount: number,
272
+ ): number {
273
+ let maxSlot = -1;
274
+ for (let index = 0; index < renderableCount; index += 1) {
275
+ for (const slot of materialSlotIndices[index] ?? []) maxSlot = Math.max(maxSlot, slot);
276
+ }
277
+ return maxSlot + 1;
159
278
  }
160
279
 
280
+ export type RecordProfileRunner = <T>(phase: RenderRecordPhase, action: () => T) => T;
281
+
282
+ const EMPTY_ENTITY_KEYS: ReadonlySet<number> = new Set<number>();
283
+
161
284
  function runRecordProfilePhase<T>(
162
285
  runner: RecordProfileRunner | undefined,
163
286
  phase: RenderRecordPhase,
@@ -166,119 +289,6 @@ function runRecordProfilePhase<T>(
166
289
  return runner === undefined ? action() : runner(phase, action);
167
290
  }
168
291
 
169
- const ANALYTIC_SKY_QUALITY = Object.freeze({ skySize: 128, irradianceSize: 32, prefilterMips: 5 });
170
- const ANALYTIC_SKY_ALGORITHM = 'preetham-perez-1999-r1';
171
-
172
- function beginProductionEnvironmentGeneration(
173
- internals: RenderSystemInternals,
174
- frameState: RenderFrameState,
175
- environment: ExtractedEnvironment,
176
- ): EnvironmentTransactionAttempt | undefined {
177
- if (environment.kind !== 'atmosphere') {
178
- const state = frameState.environmentGeneration;
179
- const oldHandles = [state.active?.handle, state.candidate?.handle].filter(
180
- (handle): handle is NonNullable<typeof handle> => handle !== undefined,
181
- );
182
- if (oldHandles.length > 0) {
183
- resizeEnvironmentGeneration(state);
184
- void internals.device.queue.onSubmittedWorkDone().then(() => {
185
- for (const handle of oldHandles) {
186
- if (typeof handle === 'object' && handle !== null && 'destroy' in handle) {
187
- (handle as { readonly destroy: (device: typeof internals.device) => unknown }).destroy(
188
- internals.device,
189
- );
190
- }
191
- }
192
- });
193
- }
194
- return undefined;
195
- }
196
- const quality = ANALYTIC_SKY_QUALITY;
197
- const signature = environmentSignature({
198
- atmosphere: {
199
- turbidity: environment.atmosphere.turbidity,
200
- rayleigh: environment.atmosphere.rayleigh,
201
- mieCoefficient: environment.atmosphere.mieCoefficient,
202
- mieDirectionalG: environment.atmosphere.mieDirectionalG,
203
- sunAngularRadius: environment.atmosphere.sunAngularRadius,
204
- },
205
- sun: {
206
- direction: environment.atmosphere.sun.direction,
207
- color: environment.atmosphere.sun.color,
208
- illuminance: environment.atmosphere.sun.illuminance,
209
- },
210
- quality,
211
- algorithmRevision: ANALYTIC_SKY_ALGORITHM,
212
- worldId: environment.worldId,
213
- entity: environment.atmosphere.entity,
214
- deviceGeneration:
215
- typeof frameState.environmentGeneration.deviceGeneration === 'number'
216
- ? frameState.environmentGeneration.deviceGeneration
217
- : 0,
218
- outputFormat: 'rgba16float',
219
- });
220
- const state = frameState.environmentGeneration;
221
- if (state.active?.signature === signature) {
222
- return undefined;
223
- }
224
- if (state.candidate !== undefined) return undefined;
225
- const descriptors = deriveEnvironmentBundleDescriptors(quality, 'rgba16float');
226
- const attempt = beginEnvironmentGeneration(state, {
227
- signature,
228
- bytes: environmentBundleBytes(descriptors),
229
- });
230
- const bundle = createEnvironmentGpuBundle(internals.device, descriptors);
231
- if (!bundle.ok) {
232
- attempt.fail('readiness');
233
- internals.errorRegistry.fire(bundle.error);
234
- return undefined;
235
- }
236
- const candidateSnapshot = createEnvironmentConsumerSnapshot({
237
- generation: attempt.generation,
238
- signature: String(attempt.desired.signature),
239
- direction: [
240
- environment.atmosphere.sun.direction[0] ?? 0,
241
- environment.atmosphere.sun.direction[1] ?? 0,
242
- environment.atmosphere.sun.direction[2] ?? 0,
243
- ],
244
- color: [
245
- environment.atmosphere.sun.color[0] ?? 0,
246
- environment.atmosphere.sun.color[1] ?? 0,
247
- environment.atmosphere.sun.color[2] ?? 0,
248
- ],
249
- illuminance: environment.atmosphere.sun.illuminance,
250
- atmosphere: {
251
- turbidity: environment.atmosphere.turbidity,
252
- rayleigh: environment.atmosphere.rayleigh,
253
- mieCoefficient: environment.atmosphere.mieCoefficient,
254
- mieDirectionalG: environment.atmosphere.mieDirectionalG,
255
- sunAngularRadius: environment.atmosphere.sunAngularRadius,
256
- },
257
- source: { worldId: environment.worldId, entity: environment.atmosphere.entity },
258
- });
259
- const candidate = attempt.complete(bundle.value, candidateSnapshot);
260
- const queueCompletion = `analytic-sky:${candidate.generation}:${frameState.frameNumber}`;
261
- let settled = false;
262
- const abandonCandidate = (stage: 'execute' | 'finish' | 'submit'): void => {
263
- if (settled) return;
264
- settled = true;
265
- attempt.fail(stage);
266
- bundle.value.destroy(internals.device);
267
- };
268
- return {
269
- abort: abandonCandidate,
270
- publish: () => {
271
- if (settled) return;
272
- settled = true;
273
- publishEnvironmentGeneration(state, candidate, queueCompletion);
274
- void internals.device.queue.onSubmittedWorkDone().then(() => {
275
- completeEnvironmentQueue(state, queueCompletion);
276
- destroyCompletedEnvironmentGenerations(state, internals.device, queueCompletion);
277
- });
278
- },
279
- };
280
- }
281
-
282
292
  export function recordFrame(
283
293
  internals: RenderSystemInternals,
284
294
  world: World,
@@ -289,14 +299,18 @@ export function recordFrame(
289
299
  frameState: RenderFrameState,
290
300
  dispatchCounts: DispatchCounts,
291
301
  bindGroupCounts: BindGroupCounts,
292
- environment: ExtractedEnvironment,
302
+ skylight: SkylightSnapshot | undefined,
303
+ skylightCount: number,
304
+ skybox: SkyboxSnapshot | undefined,
305
+ skyboxCount: number,
293
306
  postProcessParams: ReadonlyMap<string, Uint8Array>,
294
307
  // feat-20260709-editor-world-partition ENGINE-fix-round2 (defect 2): the
295
- // The ordered worlds[] remains the shadow-composition input. Renderer-owned
296
- // snapshots use stable WorldRenderId values, so production passes the
297
- // worldForRenderId lookup below instead of treating those ids as indexes.
298
- // Direct unit callers without that lookup retain the historical array-index
299
- // behavior.
308
+ // full worlds[] list passed to `renderer.draw`, indexed by
309
+ // RenderableSnapshot.worldId so the record stage resolves each renderable's
310
+ // mesh + material textures against the world it was extracted from (mirroring
311
+ // the per-world extract stage). Optional + defaulting to `[world]` keeps the
312
+ // pre-split single-world identity path (worldId always 0) byte-for-byte and
313
+ // the existing unit-test callers (which pass a single mock world) valid.
300
314
  worlds: readonly World[] = [world],
301
315
  profilePhase?: RecordProfileRunner,
302
316
  gpuDriven?: {
@@ -304,21 +318,9 @@ export function recordFrame(
304
318
  readonly scene: PersistentGpuDrivenState | undefined;
305
319
  readonly onCoverage: (ownsAll: boolean) => void;
306
320
  },
307
- worldForRenderId?: (worldId: number) => World | undefined,
308
- temporal?: {
309
- readonly transaction: TemporalFrameTransaction;
310
- readonly projection: RenderSceneProjection;
311
- readonly coverage: TemporalCoverageSummary;
312
- readonly reactiveRevision?: number | undefined;
313
- readonly onSceneSubmitted?: ((delta: RenderSceneSubmissionDelta) => void) | undefined;
314
- },
315
- fog?: FogSnapshot | undefined,
321
+ renderReadLeases?: readonly RenderReadLease[],
322
+ featureGraphCandidate?: RenderFeatureGraphCandidate,
316
323
  ): boolean {
317
- const skylight =
318
- environment.kind === 'none' || environment.kind === 'image' ? environment.skylight : undefined;
319
- const skylightCount = environment.skylightCount;
320
- const skybox = environment.kind === 'image' ? environment.skybox : undefined;
321
- const skyboxCount = environment.skyboxCount;
322
324
  // The `try / finally` wrapper advances `frameState.frameNumber` exactly
323
325
  // once per `recordFrame` invocation regardless of which early-return
324
326
  // branch is taken (camera missing, cap exceeded, pipeline pending,
@@ -338,9 +340,6 @@ export function recordFrame(
338
340
  // (no Camera = no scene), so geometry submission is a no-op and only
339
341
  // the swap-chain clear lands.
340
342
  let activeCameras = cameras;
341
- let temporalAttempt: TemporalFrameAttempt | undefined;
342
- let environmentAttempt: EnvironmentTransactionAttempt | undefined;
343
- let frameSubmitted = false;
344
343
  if (activeCameras.length === 0) {
345
344
  internals.errorRegistry.fire(
346
345
  new RhiError({
@@ -369,11 +368,6 @@ export function recordFrame(
369
368
  frameState.currentFrameObservationSource = undefined;
370
369
  frameState.currentDirectionalShadowView = null;
371
370
  frameState.currentSpotShadowView = null;
372
- environmentAttempt = beginProductionEnvironmentGeneration(internals, frameState, environment);
373
- // `beginProductionEnvironmentGeneration` owns the producer snapshot and
374
- // updates the active snapshot only after a successful submit. This keeps
375
- // direct light/CSM consumers on the previous LKG while a replacement is
376
- // still a candidate.
377
371
 
378
372
  // Record-stage fold operator linear scan (groups transparent-sort entries
379
373
  // into fold buckets + records the fold-eligible count metric). Extracted to
@@ -401,7 +395,7 @@ export function recordFrame(
401
395
  // no Skylight (skylight === undefined)
402
396
  // AND 0 direct light (totalLightCount === 0)
403
397
  // AND StandardMaterial (renderables.some materialShaderId !== 'forgeax::default-unlit')
404
- // All three true -> black + warn. Any one false -> no warn.
398
+ // All three true -> black + warn. A single false -> no warn.
405
399
  //
406
400
  // Multi-Skylight warn (F-4 nit + feat-20260630 M3 / w19): >1 Skylight
407
401
  // entity -> warn ONCE per RenderSystem lifetime (not per frame), naming the
@@ -454,8 +448,7 @@ export function recordFrame(
454
448
  spotLights,
455
449
  profilePhase,
456
450
  pipelineState.hdrpClusterMembershipPipeline !== null &&
457
- internals.device.caps?.compute === true &&
458
- internals.membershipTiming?.usesCpuControl() !== true,
451
+ internals.device.caps?.compute === true,
459
452
  pipelineState.hdrpClusterMembershipBindGroupLayout,
460
453
  );
461
454
  });
@@ -467,8 +460,6 @@ export function recordFrame(
467
460
  return { foldBuckets, light };
468
461
  });
469
462
 
470
- void activeCameras; // referenced below
471
-
472
463
  // Case E (this commit): 0 renderables = legitimate scene (LO §1.1
473
464
  // hello-window minimum semantic). Mirrors the Case C softening for
474
465
  // 0 DirectionalLight (line 134 above; D-Q7). recordFrame() falls
@@ -493,40 +484,13 @@ export function recordFrame(
493
484
  const effectiveSpotShadowMapSize = resolveSpotShadowMapSize(internals, lights);
494
485
  const graphShadowMapSize = effectiveShadowMapSize ?? effectiveSpotShadowMapSize;
495
486
 
496
- const validated = runRecordProfilePhase(profilePhase, 'record/validation', () =>
497
- validateRenderables(
498
- internals,
499
- world,
500
- worlds,
501
- renderables,
502
- transparentDispatch,
503
- worldForRenderId,
504
- ),
505
- );
506
- const residentMeshes = new Map<number, MeshGpuHandles>();
507
- const meshWorlds = new Map<number, number>();
508
- const ambiguousMeshHandles = new Set<number>();
509
- for (const entry of validated) {
510
- const handle = entry.source.assetHandle;
511
- if (ambiguousMeshHandles.has(handle)) continue;
512
- const priorWorld = meshWorlds.get(handle);
513
- if (priorWorld !== undefined && priorWorld !== entry.source.worldId) {
514
- residentMeshes.delete(handle);
515
- ambiguousMeshHandles.add(handle);
516
- continue;
517
- }
518
- meshWorlds.set(handle, entry.source.worldId);
519
- residentMeshes.set(handle, entry.mesh);
520
- }
521
-
522
487
  let preparedGpuDriven: PreparedGpuDrivenFrame | undefined;
523
488
  if (gpuDriven !== undefined) {
524
489
  const prepared = gpuDriven.owner.prepare({
525
490
  scene: gpuDriven.scene,
526
491
  camera,
527
- meshes: residentMeshes,
492
+ meshes: pipelineState.meshes,
528
493
  viewBindGroupLayout: pipelineState.viewBindGroupLayout,
529
- materialBindGroupLayout: pipelineState.materialBindGroupLayout,
530
494
  meshResidencyEpoch: internals.gpuStore.meshResidencyEpoch,
531
495
  hdrp: frameState.isHdrpActive,
532
496
  });
@@ -534,22 +498,16 @@ export function recordFrame(
534
498
  else if (prepared.error.code !== 'rhi-not-available')
535
499
  internals.errorRegistry.fire(prepared.error);
536
500
  }
537
- const cpuVisibility = gpuDriven?.owner.inspect().cpuVisibility;
538
- const multiViewSubmissions = gpuDriven?.owner.multiViewSubmissions() ?? [];
539
501
 
540
- // The GPU lane owns only the primary opaque raster today. Shadow membership
541
- // is owned by the batch-aligned multi-view submissions below; the existing
542
- // shadow encoder consumes them through a one-way direct-RHI adapter.
502
+ // The GPU lane owns only the primary opaque raster today. Directional,
503
+ // point, and spot shadow views still consume the CPU-validated draw plan.
543
504
  const cpuShadowCastersActive =
544
505
  (effectiveShadowMapSize ?? 0) > 0 ||
545
506
  lights.pointShadow.length > 0 ||
546
507
  lights.spot.some((spot) => spot.castShadow);
547
- const gpuOwnsAllPrimaryRaster = preparedGpuDriven?.ownsAllForwardItems === true;
508
+ const gpuOwnsAllPrimaryRaster = preparedGpuDriven?.ownsAllRenderables === true;
548
509
  gpuDriven?.onCoverage(gpuOwnsAllPrimaryRaster && !cpuShadowCastersActive);
549
510
 
550
- const activeEnvironmentSignature =
551
- frameState.environmentGeneration.candidate?.signature ??
552
- frameState.environmentGeneration.active?.signature;
553
511
  const preflightGraph = runRecordProfilePhase(profilePhase, 'record/render-graph', () =>
554
512
  ensureCompiledFrameGraph(
555
513
  internals,
@@ -561,26 +519,10 @@ export function recordFrame(
561
519
  Math.max(1, internals.canvas.height),
562
520
  graphShadowMapSize,
563
521
  preparedGpuDriven,
564
- temporal?.transaction,
522
+ featureGraphCandidate,
565
523
  ),
566
524
  );
567
525
  if (preflightGraph === null) return false;
568
- temporalAttempt = temporal?.transaction.begin({
569
- camera,
570
- surfaceWidth: Math.max(1, internals.canvas.width),
571
- surfaceHeight: Math.max(1, internals.canvas.height),
572
- projection: temporal.projection,
573
- visible: renderables,
574
- coverage: temporal.coverage,
575
- ...(Object.hasOwn(temporal, 'reactiveRevision')
576
- ? { reactiveRevision: temporal.reactiveRevision }
577
- : {}),
578
- environmentSignature:
579
- typeof activeEnvironmentSignature === 'string' ? activeEnvironmentSignature : undefined,
580
- ...(temporal.onSceneSubmitted === undefined
581
- ? {}
582
- : { onSceneSubmitted: temporal.onSceneSubmitted }),
583
- });
584
526
 
585
527
  // Acquire the swap-chain texture + colour view + target dimensions (with
586
528
  // one reconfigure-and-retry on surface-outdated). Extracted to
@@ -611,29 +553,6 @@ export function recordFrame(
611
553
 
612
554
  pipelineState.perPassResources.shadowMapSize = graphShadowMapSize ?? 0;
613
555
  pipelineState.perPassResources.shadowCascadeCount = lights.cascadeCount ?? 0;
614
- pipelineState.perPassResources.shadowDepthBias = lights.depthBias ?? 0.005;
615
- pipelineState.perPassResources.shadowNormalBias = lights.normalBias ?? 0.05;
616
- pipelineState.perPassResources.shadowPcfKernelSize = clampPcfKernelSize(lights.pcfKernelSize);
617
- const environmentSunDirection = environmentConsumerSnapshot(frameState.environmentGeneration)
618
- ?.csm.direction;
619
- const environmentShadowDirection =
620
- environmentSunDirection === undefined
621
- ? undefined
622
- : [
623
- -(environmentSunDirection[0] ?? 0),
624
- -(environmentSunDirection[1] ?? 0),
625
- -(environmentSunDirection[2] ?? 0),
626
- ];
627
- pipelineState.perPassResources.shadowLightDirection =
628
- environmentShadowDirection !== undefined
629
- ? new Float32Array(environmentShadowDirection)
630
- : lights.directional === undefined
631
- ? null
632
- : new Float32Array([
633
- lights.directional.direction[0] ?? 0,
634
- lights.directional.direction[1] ?? 0,
635
- lights.directional.direction[2] ?? 0,
636
- ]);
637
556
  const firstDirectionalShadowMatrix = lights.lightViewProj?.[0];
638
557
  pipelineState.perPassResources.shadowLightSpaceMatrix =
639
558
  firstDirectionalShadowMatrix === undefined
@@ -655,7 +574,6 @@ export function recordFrame(
655
574
  : {
656
575
  viewMatrix: new Float32Array(computeViewMatrix(camera)),
657
576
  splitPlanes: new Float32Array(lights.splitPlanes),
658
- cascadeBlend: lights.cascadeBlend ?? 0,
659
577
  };
660
578
  }
661
579
 
@@ -667,9 +585,6 @@ export function recordFrame(
667
585
  camera,
668
586
  skybox,
669
587
  );
670
- const atmosphereActive =
671
- frameState.environmentGeneration.active !== undefined ||
672
- frameState.environmentGeneration.candidate !== undefined;
673
588
 
674
589
  // feat-20260604-learn-render-4.10-anti-aliasing-msaa M2 / w9 (D-6, C-9):
675
590
  // MSAA is a per-Camera switch derived from `camera.antialias`, never
@@ -703,13 +618,27 @@ export function recordFrame(
703
618
  // build a renderableIndex -> renderState map from dispatch entries
704
619
  // so each ValidatedRenderable carries its per-material renderState
705
620
  // override without an O(n^2) back-scan in the draw loop.
706
- const gpuOwnedItemKeys = preparedGpuDriven?.ownedItemKeys ?? EMPTY_RASTER_ITEM_KEYS;
707
- gpuDriven?.owner.recordCpuValidation(validated, gpuOwnedItemKeys);
621
+ const gpuOwnedEntityKeys = preparedGpuDriven?.entityKeys ?? EMPTY_ENTITY_KEYS;
622
+ const validated = runRecordProfilePhase(profilePhase, 'record/validation', () =>
623
+ gpuOwnsAllPrimaryRaster && !cpuShadowCastersActive
624
+ ? []
625
+ : validateRenderables(
626
+ internals,
627
+ world,
628
+ worlds,
629
+ pipelineState,
630
+ frameState,
631
+ renderables,
632
+ transparentDispatch,
633
+ cpuShadowCastersActive ? EMPTY_ENTITY_KEYS : gpuOwnedEntityKeys,
634
+ ),
635
+ );
636
+ gpuDriven?.owner.recordCpuValidation(validated, gpuOwnedEntityKeys);
708
637
 
709
638
  // Per-frame cache clean-up: drop per-entity BG cache entries + instance
710
639
  // buffers whose entityKey is absent from the current validated set (despawn
711
640
  // eviction). Extracted to cleanPerFrameCaches (M3/w18).
712
- cleanPerFrameCaches(internals, frameState, validated);
641
+ cleanPerFrameCaches(internals, frameState, validated, gpuOwnedEntityKeys);
713
642
 
714
643
  // Dispatch-ordered render plan: reorder validated renderables to dispatch
715
644
  // order, run the mesh-SSBO capacity gate (graceful truncation), and build
@@ -718,44 +647,6 @@ export function recordFrame(
718
647
  buildDispatchPlan(internals, validated, transparentDispatch, foldBuckets),
719
648
  );
720
649
  const validatedOrdered = dispatchPlan.validatedOrdered;
721
- // Build the candidate-index owner once at the frame boundary. Directional
722
- // CPU fallback consumes the same multi-view membership contract as the
723
- // indirect path and must not rediscover selected casters by scanning the
724
- // complete dispatch for every cascade.
725
- const validatedByCandidateIndex = new Map<number, ValidatedRenderable>();
726
- const validatedIndexByCandidateIndex = new Map<number, number>();
727
- for (let validatedIndex = 0; validatedIndex < validatedOrdered.length; validatedIndex += 1) {
728
- const entry = validatedOrdered[validatedIndex];
729
- if (entry === undefined) continue;
730
- const candidateIndex = entry.source.candidateIndex ?? entry.renderableIndex;
731
- if (!validatedByCandidateIndex.has(candidateIndex)) {
732
- validatedByCandidateIndex.set(candidateIndex, entry);
733
- validatedIndexByCandidateIndex.set(candidateIndex, validatedIndex);
734
- }
735
- }
736
- // Build the compatibility shadow geometry owner once. Directional CSM
737
- // uses its compact per-cascade submission; point/spot fallback passes
738
- // need the main-view domain and the original validated slot for their
739
- // dynamic mesh offset. Keeping both facts here removes a full
740
- // validatedOrdered walk from every point face and spot tile while
741
- // preserving the existing overflow-conservative behavior.
742
- const mainSubmission = multiViewSubmissions.find((submission) => submission.kind === 'main');
743
- const mainShadowCandidateIndices =
744
- mainSubmission === undefined || mainSubmission.overflow
745
- ? undefined
746
- : mainSubmission.candidateIndices;
747
- const validatedShadowEntries = Object.freeze(
748
- validatedOrdered.flatMap((entry, validatedIndex) => {
749
- const candidateIndex = entry.source.candidateIndex ?? entry.renderableIndex;
750
- if (
751
- mainShadowCandidateIndices !== undefined &&
752
- !mainShadowCandidateIndices.has(candidateIndex)
753
- ) {
754
- return [];
755
- }
756
- return [{ entry, validatedIndex }];
757
- }),
758
- );
759
650
  const foldDispatchPlan = dispatchPlan.foldDispatchPlan;
760
651
  const materialSlotIndices = dispatchPlan.materialSlotIndices;
761
652
  const materialSlots = dispatchPlan.materialSlots;
@@ -794,12 +685,8 @@ export function recordFrame(
794
685
  // (clear-pass-only) path only when neither condition is met.
795
686
  // feat-20260531-skybox-env-background M2 / w6: gate relaxed from
796
687
  // `validatedOrdered.length > 0` to include skybox-only frames
797
- // (plan-strategy D-3, R-3). Feature-only passes (for example VFX) also
798
- // consume the View UBO through their group(0) contract even when the
799
- // scene validation list is empty; keep that payload authoritative before
800
- // the graph executes those passes.
801
- const renderFeaturesActive = (internals.featureHost?.size ?? 0) > 0;
802
- if (validatedOrdered.length > 0 || skyboxActive || atmosphereActive || renderFeaturesActive) {
688
+ // (plan-strategy D-3, R-3).
689
+ if (validatedOrdered.length > 0 || skyboxActive) {
803
690
  runRecordProfilePhase(profilePhase, 'record/uploads', () => {
804
691
  // View UBO + CSM/spot-shadow matrix pack: assembled + uploaded in one
805
692
  // queue.writeBuffer round-trip. Extracted to view-ubo.ts (M3/w18) so
@@ -811,8 +698,6 @@ export function recordFrame(
811
698
  light,
812
699
  lights,
813
700
  frameState.spotShadowSnapshots,
814
- temporalAttempt?.view,
815
- fog,
816
701
  );
817
702
  writeShadowCasterUniforms(
818
703
  internals.device.queue,
@@ -834,12 +719,11 @@ export function recordFrame(
834
719
  pipelineState.meshStorageBuffer,
835
720
  validatedOrdered,
836
721
  foldDispatchPlan,
837
- temporalAttempt?.reactiveRevisionChanged ?? false,
838
722
  );
839
723
  });
840
724
  }
841
725
 
842
- const preparedDirectionalShadowCache = runRecordProfilePhase(
726
+ const directionalShadowCache = runRecordProfilePhase(
843
727
  profilePhase,
844
728
  'record/scene-state/directional-shadow-cache',
845
729
  () =>
@@ -847,21 +731,12 @@ export function recordFrame(
847
731
  internals,
848
732
  frameState,
849
733
  worlds,
734
+ renderReadLeases,
850
735
  lights,
851
736
  effectiveShadowMapSize,
852
737
  validatedOrdered,
853
- cpuVisibility,
854
- gpuDriven?.scene,
855
738
  ),
856
739
  );
857
- const directionalShadowTileClearPipeline =
858
- internals.getDirectionalShadowTileClearPipeline?.() ?? null;
859
- const directionalShadowCache =
860
- !preparedDirectionalShadowCache.reuse &&
861
- preparedDirectionalShadowCache.hitMask !== 0 &&
862
- directionalShadowTileClearPipeline === null
863
- ? { ...preparedDirectionalShadowCache, hitMask: 0 }
864
- : preparedDirectionalShadowCache;
865
740
  frameState.directionalShadowCacheRecorded = false;
866
741
 
867
742
  const encoderResult = internals.device.createCommandEncoder({ label: 'render-system-frame' });
@@ -898,29 +773,13 @@ export function recordFrame(
898
773
  // errorRegistry + shader-cache lookups) surfaces (`internals` itself satisfies
899
774
  // `RenderSystemRuntime` so `runtime: internals` is a zero-cost reference). The
900
775
  // 0-consumed `skyboxCount` residual is dropped.
901
- const csmPlan = lights.directionalCsm;
902
- const mainMembership = visibilityCandidateIndices(cpuVisibility, 'main');
903
- const mainDispatch =
904
- mainMembership === undefined
905
- ? transparentDispatch
906
- : transparentDispatch.filter((entry) => {
907
- const source = renderables[entry.renderableIndex];
908
- const candidateIndex = source?.candidateIndex ?? entry.renderableIndex;
909
- return mainMembership.has(candidateIndex);
910
- });
911
-
912
776
  const passCtx: _InternalRenderPipelineContext = {
913
777
  assets: internals.assets,
914
778
  world,
915
- gpuDrivenItemKeys: gpuOwnedItemKeys,
916
- gpuDrivenOwnsAllForwardItems: gpuOwnsAllPrimaryRaster,
917
- validatedByCandidateIndex,
918
- validatedIndexByCandidateIndex,
919
- validatedShadowEntries,
779
+ gpuDrivenEntityKeys: gpuOwnedEntityKeys,
920
780
  store: internals.gpuStore,
921
781
  pipelineState,
922
782
  runtime: internals,
923
- temporalFrame: temporalAttempt,
924
783
  encoder,
925
784
  view,
926
785
  clear,
@@ -949,10 +808,7 @@ export function recordFrame(
949
808
  geometryColorResolveView,
950
809
  ldrSpriteColorView,
951
810
  postProcessParams,
952
- dispatch: mainDispatch,
953
- shadowDispatch: transparentDispatch,
954
- directionalCsmPlan: csmPlan,
955
- shadowDebugObservation: internals.shadowDebugObservation,
811
+ dispatch: transparentDispatch,
956
812
  hdrpClusterBindGroup,
957
813
  hdrpClusterMembershipBindGroup,
958
814
  foldDispatchPlan,
@@ -962,12 +818,9 @@ export function recordFrame(
962
818
  materialSlotCount,
963
819
  materialBgAssemblyCache: frameState.materialBgAssemblyCache,
964
820
  directionalShadowCacheReuse: directionalShadowCache.reuse,
965
- directionalShadowCacheTokens: directionalShadowCache.cascadeTokens,
966
- directionalShadowCacheHitMask: directionalShadowCache.hitMask,
967
- multiViewSubmissions,
968
821
  ...(profilePhase !== undefined ? { profilePhase } : {}),
969
822
  };
970
- frameSubmitted = runRecordProfilePhase(profilePhase, 'record/graph-execute', () =>
823
+ const submitted = runRecordProfilePhase(profilePhase, 'record/graph-execute', () =>
971
824
  executeCompiledFrameGraph(
972
825
  internals,
973
826
  frameState,
@@ -976,19 +829,15 @@ export function recordFrame(
976
829
  profilePhase === undefined
977
830
  ? undefined
978
831
  : (pass, encode) => profilePhase(graphExecutionPhase(pass.name), encode),
979
- temporalAttempt,
980
- environmentAttempt,
981
832
  ),
982
833
  );
983
- if (frameSubmitted && !directionalShadowCache.reuse) {
834
+ if (submitted && !directionalShadowCache.reuse) {
984
835
  frameState.directionalShadowCache = frameState.directionalShadowCacheRecorded
985
836
  ? directionalShadowCache.next
986
837
  : null;
987
838
  }
988
- return frameSubmitted;
839
+ return submitted;
989
840
  } finally {
990
- temporalAttempt?.abort();
991
- if (!frameSubmitted) environmentAttempt?.abort('execute');
992
841
  frameState.frameNumber += 1;
993
842
  // feat-20260608-cluster-lighting M5 / w22: clear HDRP once-per-frame fired
994
843
  // set so the next frame re-fires if the condition persists.
@@ -999,294 +848,63 @@ export function recordFrame(
999
848
  interface DirectionalShadowCacheDecision {
1000
849
  readonly reuse: boolean;
1001
850
  readonly next: DirectionalShadowCache | null;
1002
- readonly cascadeTokens: readonly string[];
1003
- readonly hitMask: number;
1004
- }
1005
-
1006
- export type DirectionalShadowCacheMissReason =
1007
- | 'no-entry'
1008
- | 'shadow-map-size'
1009
- | 'cascade-count'
1010
- | 'pipeline'
1011
- | 'graph-topology'
1012
- | 'scene-mode'
1013
- | 'asset-catalog'
1014
- | 'mesh-residency'
1015
- | 'world-state'
1016
- | 'cascade-token';
1017
-
1018
- function observeDirectionalShadowTokenBuild(
1019
- metrics: EngineMetrics,
1020
- enabled: boolean,
1021
- built: { readonly tokens: readonly string[]; readonly productionSceneToken: boolean },
1022
- ): void {
1023
- if (!enabled || built.tokens.length === 0) return;
1024
- metrics.increment('shadow.csm.token-build');
1025
- metrics.increment(
1026
- built.productionSceneToken
1027
- ? 'shadow.csm.token-production-scene'
1028
- : 'shadow.csm.token-validated-fallback',
1029
- );
1030
- for (const _token of built.tokens) metrics.increment('shadow.csm.token-cascade');
1031
- }
1032
-
1033
- function observeDirectionalShadowCacheDecision(
1034
- metrics: EngineMetrics,
1035
- enabled: boolean,
1036
- decision: Pick<DirectionalShadowCacheDecision, 'reuse' | 'cascadeTokens' | 'hitMask'>,
1037
- missReason: DirectionalShadowCacheMissReason | undefined,
1038
- ): void {
1039
- if (!enabled || decision.cascadeTokens.length === 0) return;
1040
- metrics.increment(decision.reuse ? 'shadow.csm.cache.reuse' : 'shadow.csm.cache.refresh');
1041
- if (decision.hitMask !== 0 && !decision.reuse) {
1042
- metrics.increment('shadow.csm.cache.partial-hit');
1043
- }
1044
- for (let index = 0; index < decision.cascadeTokens.length; index += 1) {
1045
- metrics.increment(
1046
- (decision.hitMask & (1 << index)) !== 0
1047
- ? 'shadow.csm.cache.cascade-hit'
1048
- : 'shadow.csm.cache.cascade-miss',
1049
- );
1050
- }
1051
- if (missReason !== undefined) metrics.increment(`shadow.csm.cache.miss.${missReason}`);
1052
851
  }
1053
852
 
1054
- function classifyDirectionalShadowCacheMiss(input: {
1055
- readonly cached: DirectionalShadowCache | null;
1056
- readonly built: { readonly productionSceneToken: boolean };
1057
- readonly shadowMapSize: number | undefined;
1058
- readonly cascadeCount: number | undefined;
1059
- readonly pipelineHandle: number;
1060
- readonly graphTopologyKey: string | null;
1061
- readonly assetCatalogEpoch: number;
1062
- readonly meshResidencyEpoch: number;
1063
- readonly worldStateMatches: boolean;
1064
- }): DirectionalShadowCacheMissReason {
1065
- const cached = input.cached;
1066
- if (cached === null) return 'no-entry';
1067
- if (cached.shadowMapSize !== input.shadowMapSize) return 'shadow-map-size';
1068
- if (cached.cascadeCount !== input.cascadeCount) return 'cascade-count';
1069
- if (cached.pipelineHandle !== input.pipelineHandle) return 'pipeline';
1070
- if (cached.graphTopologyKey !== input.graphTopologyKey) return 'graph-topology';
1071
- if (cached.productionSceneToken !== input.built.productionSceneToken) return 'scene-mode';
1072
- if (cached.assetCatalogEpoch !== input.assetCatalogEpoch) return 'asset-catalog';
1073
- if (cached.meshResidencyEpoch !== input.meshResidencyEpoch) return 'mesh-residency';
1074
- if (!input.built.productionSceneToken && !input.worldStateMatches) return 'world-state';
1075
- return 'cascade-token';
1076
- }
1077
-
1078
- /**
1079
- * @internal Pure cache decision owner used by recordFrame and its semantic
1080
- * invalidation tests. Keeping the comparison separate makes the dangerous
1081
- * path explicit: a hit is only reusable when every cascade token matches and
1082
- * all frame/resource owners agree; a forced redraw is represented by
1083
- * `reuse:false` even when one cascade happens to match.
1084
- */
1085
- export function evaluateDirectionalShadowCache(input: {
1086
- readonly cached: DirectionalShadowCache | null;
1087
- readonly cascadeTokens: readonly string[];
1088
- readonly productionSceneToken: boolean;
1089
- readonly shadowMapSize: number | undefined;
1090
- readonly cascadeCount: number | undefined;
1091
- readonly pipelineHandle: number;
1092
- readonly graphTopologyKey: string | null;
1093
- readonly assetCatalogEpoch: number;
1094
- readonly meshResidencyEpoch: number;
1095
- readonly worldStateMatches: boolean;
1096
- }): {
1097
- readonly reuse: boolean;
1098
- readonly hitMask: number;
1099
- readonly missReason: DirectionalShadowCacheMissReason | undefined;
1100
- } {
1101
- const cached = input.cached;
1102
- const commonHit =
1103
- cached !== null &&
1104
- cached.shadowMapSize === input.shadowMapSize &&
1105
- cached.cascadeCount === input.cascadeCount &&
1106
- cached.pipelineHandle === input.pipelineHandle &&
1107
- cached.graphTopologyKey === input.graphTopologyKey &&
1108
- cached.productionSceneToken === input.productionSceneToken &&
1109
- cached.assetCatalogEpoch === input.assetCatalogEpoch &&
1110
- cached.meshResidencyEpoch === input.meshResidencyEpoch &&
1111
- (input.productionSceneToken || input.worldStateMatches);
1112
- let hitMask = 0;
1113
- if (commonHit && cached !== null) {
1114
- for (let index = 0; index < input.cascadeTokens.length; index += 1) {
1115
- if (cached.cascadeTokens[index] === input.cascadeTokens[index]) hitMask |= 1 << index;
1116
- }
1117
- }
1118
- const reuse = input.cascadeTokens.length > 0 && hitMask === (1 << input.cascadeTokens.length) - 1;
853
+ function readWorldState(lease: RenderReadLease): DirectionalShadowWorldState {
1119
854
  return {
1120
- reuse,
1121
- hitMask,
1122
- missReason: reuse
1123
- ? undefined
1124
- : classifyDirectionalShadowCacheMiss({
1125
- cached,
1126
- built: { productionSceneToken: input.productionSceneToken },
1127
- shadowMapSize: input.shadowMapSize,
1128
- cascadeCount: input.cascadeCount,
1129
- pipelineHandle: input.pipelineHandle,
1130
- graphTopologyKey: input.graphTopologyKey,
1131
- assetCatalogEpoch: input.assetCatalogEpoch,
1132
- meshResidencyEpoch: input.meshResidencyEpoch,
1133
- worldStateMatches: input.worldStateMatches,
1134
- }),
855
+ worldIdentity: lease.worldIdentity,
856
+ changeCursor: lease.inspectCursor(),
1135
857
  };
1136
858
  }
1137
859
 
1138
- const SHADOW_TOKEN_NUMBER = new DataView(new ArrayBuffer(8));
1139
-
1140
- function hashShadowTokenByte(hash: number, value: number): number {
1141
- return Math.imul((hash ^ value) >>> 0, 16_777_619) >>> 0;
1142
- }
1143
-
1144
- function hashShadowTokenNumber(hash: number, value: number | undefined): number {
1145
- SHADOW_TOKEN_NUMBER.setFloat64(0, value ?? Number.NaN, true);
1146
- let next = hash;
1147
- for (let index = 0; index < 8; index += 1) {
1148
- next = hashShadowTokenByte(next, SHADOW_TOKEN_NUMBER.getUint8(index));
1149
- }
1150
- return next;
1151
- }
1152
-
1153
- function hashShadowTokenString(hash: number, value: string): number {
1154
- let next = hash;
1155
- for (let index = 0; index < value.length; index += 1) {
1156
- const code = value.charCodeAt(index);
1157
- next = hashShadowTokenByte(next, code & 0xff);
1158
- next = hashShadowTokenByte(next, code >>> 8);
1159
- }
1160
- return next;
1161
- }
1162
-
1163
- function hashShadowTokenNumbers(hash: number, values: ArrayLike<number>): number {
1164
- let next = hash;
1165
- for (let index = 0; index < values.length; index += 1) {
1166
- next = hashShadowTokenNumber(next, values[index]);
1167
- }
1168
- return next;
1169
- }
1170
-
1171
- /** @internal Deterministic cascade-local invalidation token producer. */
1172
- export function buildCascadeLocalCacheTokens(input: {
1173
- readonly lights: ExtractedLights;
1174
- readonly visibility: CpuMultiViewVisibilityResult | undefined;
1175
- readonly scene: PersistentGpuDrivenState | undefined;
1176
- readonly validated: readonly ValidatedRenderable[];
1177
- }): { readonly tokens: readonly string[]; readonly productionSceneToken: boolean } {
1178
- const plan = input.lights.directionalCsm;
1179
- if (plan === undefined) return { tokens: [], productionSceneToken: false };
1180
- const productionSceneToken = input.visibility !== undefined && input.scene !== undefined;
1181
- // PersistentRenderScene already owns O(1) slot lookup. The fallback map is
1182
- // retained only for older test/custom state doubles that do not expose the
1183
- // projection owner; it is intentionally not identity-cached because those
1184
- // doubles may mutate their slots array in place.
1185
- const slotByStableId =
1186
- input.scene === undefined || input.scene.projection !== undefined
1187
- ? undefined
1188
- : shadowSlotIndex(input.scene.slots);
1189
- const submissionMembership =
1190
- input.scene === undefined ? undefined : buildSubmissionCandidateMembership(input.scene.plan);
1191
- const cascadeCount = Math.min(input.lights.cascadeCount ?? 0, plan.cascades.length);
1192
- const tokens: string[] = [];
1193
- for (let cascadeIndex = 0; cascadeIndex < cascadeCount; cascadeIndex += 1) {
1194
- const cascade = plan.cascades[cascadeIndex];
1195
- if (cascade === undefined) continue;
1196
- let hash = 2_166_136_261;
1197
- hash = hashShadowTokenString(hash, 'csm-cache-v2');
1198
- hash = hashShadowTokenNumber(hash, cascadeIndex);
1199
- hash = hashShadowTokenNumber(hash, cascade.revision);
1200
- hash = hashShadowTokenNumbers(hash, cascade.lightViewProj);
1201
- hash = hashShadowTokenNumbers(hash, cascade.clipLightBounds);
1202
- hash = hashShadowTokenNumbers(hash, cascade.casterBroadphaseWorldAabb);
1203
- hash = hashShadowTokenNumber(hash, cascade.rasterViewport.x);
1204
- hash = hashShadowTokenNumber(hash, cascade.rasterViewport.y);
1205
- hash = hashShadowTokenNumber(hash, cascade.rasterViewport.w);
1206
- hash = hashShadowTokenNumber(hash, cascade.rasterViewport.h);
1207
-
1208
- const visibilityView = input.visibility?.views.find(
1209
- (view) => view.kind === 'cascade' && view.cascadeIndex === cascadeIndex,
1210
- );
1211
- if (
1212
- productionSceneToken &&
1213
- visibilityView !== undefined &&
1214
- input.scene !== undefined &&
1215
- (slotByStableId !== undefined || input.scene.projection !== undefined)
1216
- ) {
1217
- const visibleStableIds = sortedUniqueStableIds(visibilityView.stableIds);
1218
- for (const stableId of visibleStableIds) {
1219
- const slot = input.scene.projection?.slotByIndex(stableId) ?? slotByStableId?.get(stableId);
1220
- if (slot === undefined) continue;
1221
- const revisions = slot.fact.revisions;
1222
- hash = hashShadowTokenNumber(hash, stableId);
1223
- hash = hashShadowTokenNumber(hash, slot.generation);
1224
- hash = hashShadowTokenNumber(hash, revisions.transform);
1225
- hash = hashShadowTokenNumber(hash, revisions.geometry);
1226
- hash = hashShadowTokenNumber(hash, revisions.material);
1227
- hash = hashShadowTokenNumber(hash, revisions.eligibility);
1228
- hash = hashShadowTokenNumber(hash, revisions.space);
1229
- hash = hashShadowTokenNumber(hash, revisions.batch);
1230
- hash = hashShadowTokenNumber(hash, revisions.resource);
1231
- }
1232
- const visibleBatchIds = new Set<number>();
1233
- if (submissionMembership !== undefined) {
1234
- for (const stableId of visibleStableIds) {
1235
- for (const entry of submissionMembership.byPrimitiveIndex.get(stableId) ?? []) {
1236
- visibleBatchIds.add(entry.batchId);
1237
- }
1238
- }
1239
- // SubmissionPlan owns a stable numeric batch order. Normalize the
1240
- // token input explicitly instead of inheriting Set insertion order
1241
- // from primitive/cascade traversal; a reorder must be a conservative
1242
- // miss at most, never a semantic token change by accident.
1243
- for (const batchId of [...visibleBatchIds].sort((left, right) => left - right)) {
1244
- const batch = submissionMembership.byBatchId.get(batchId);
1245
- if (batch === undefined) continue;
1246
- hash = hashShadowTokenNumber(hash, batch.batchId);
1247
- hash = hashShadowTokenNumber(hash, batch.generation);
1248
- hash = hashShadowTokenNumber(hash, batch.key.resourceRevision);
1249
- hash = hashShadowTokenNumber(hash, batch.key.materialRevision);
1250
- hash = hashShadowTokenString(hash, batch.key.pipelineClass);
1251
- hash = hashShadowTokenString(hash, batch.key.materialClass);
1252
- hash = hashShadowTokenString(hash, batch.key.materialResourceClass);
1253
- }
1254
- }
1255
- } else {
1256
- hash = hashShadowTokenNumbers(hash, cascade.casterRenderableIndices);
1257
- const casterMembership = new Set(cascade.casterRenderableIndices);
1258
- for (const entry of input.validated) {
1259
- const candidateIndex = entry.source.candidateIndex ?? entry.renderableIndex;
1260
- if (!casterMembership.has(candidateIndex)) continue;
1261
- const revisions = entry.source.sceneFact?.revisions;
1262
- hash = hashShadowTokenNumber(hash, candidateIndex);
1263
- hash = hashShadowTokenNumber(hash, revisions?.transform);
1264
- hash = hashShadowTokenNumber(hash, revisions?.geometry);
1265
- hash = hashShadowTokenNumber(hash, revisions?.material);
1266
- hash = hashShadowTokenNumber(hash, revisions?.eligibility);
1267
- hash = hashShadowTokenNumber(hash, revisions?.space);
1268
- hash = hashShadowTokenNumber(hash, revisions?.batch);
1269
- hash = hashShadowTokenNumber(hash, revisions?.resource);
1270
- }
1271
- }
1272
- tokens.push(`csm-${cascadeIndex}-${hash.toString(16).padStart(8, '0')}`);
860
+ function sameWorldState(
861
+ cached: DirectionalShadowCache,
862
+ worlds: readonly World[],
863
+ leases: readonly RenderReadLease[] | undefined,
864
+ ): boolean {
865
+ if (leases === undefined || leases.length !== worlds.length) return false;
866
+ if (cached.worlds.length !== worlds.length || cached.worldStateTokens.length !== leases.length) {
867
+ return false;
1273
868
  }
1274
- return { tokens: Object.freeze(tokens), productionSceneToken };
1275
- }
1276
-
1277
- function snapshotWorldState(world: World): DirectionalShadowWorldState {
1278
- return { projection: createWorldProjection(world) };
1279
- }
1280
-
1281
- function sameWorldState(cached: DirectionalShadowCache, worlds: readonly World[]): boolean {
1282
- if (cached.worlds.length !== worlds.length) return false;
1283
869
  for (let index = 0; index < worlds.length; index += 1) {
1284
870
  const world = worlds[index];
1285
- if (world === undefined || cached.worlds[index] !== world) return false;
871
+ const lease = leases[index];
872
+ if (world === undefined || lease === undefined || cached.worlds[index] !== world) return false;
1286
873
  const cachedState = cached.worldStateTokens[index];
1287
874
  if (cachedState === undefined) return false;
1288
- const evidence = cachedState.projection.poll();
1289
- if (evidence.status !== 'delta' || evidence.changes.length > 0) return false;
875
+ if (cachedState.worldIdentity !== lease.worldIdentity) return false;
876
+ let changes: ReturnType<RenderReadLease['readChanges']>;
877
+ try {
878
+ changes = lease.readChanges(cachedState.changeCursor);
879
+ } catch {
880
+ // A lease may have evicted an old cursor after another owner consumed
881
+ // its bounded cursor window. Treat that as stale render evidence and
882
+ // rebuild the atlas from the current lease rather than reaching into
883
+ // World for a second mutation clock.
884
+ return false;
885
+ }
886
+ if (changes.status !== 'ok') return false;
887
+ if (changes.world.records.length !== 0 || changes.sharedRefs.records.length !== 0) {
888
+ return false;
889
+ }
890
+ }
891
+ return true;
892
+ }
893
+
894
+ function sameLightViewProj(
895
+ cached: DirectionalShadowCache,
896
+ current: readonly Float32Array[],
897
+ ): boolean {
898
+ if (cached.lightViewProj.length !== current.length) return false;
899
+ for (let matrixIndex = 0; matrixIndex < current.length; matrixIndex += 1) {
900
+ const previous = cached.lightViewProj[matrixIndex];
901
+ const next = current[matrixIndex];
902
+ if (previous === undefined || next === undefined || previous.length !== next.length) {
903
+ return false;
904
+ }
905
+ for (let valueIndex = 0; valueIndex < next.length; valueIndex += 1) {
906
+ if (previous[valueIndex] !== next[valueIndex]) return false;
907
+ }
1290
908
  }
1291
909
  return true;
1292
910
  }
@@ -1295,21 +913,13 @@ function prepareDirectionalShadowCache(
1295
913
  internals: RenderSystemInternals,
1296
914
  frameState: RenderFrameState,
1297
915
  worlds: readonly World[],
916
+ leases: readonly RenderReadLease[] | undefined,
1298
917
  lights: ExtractedLights,
1299
918
  shadowMapSize: number | undefined,
1300
919
  validatedOrdered: readonly ValidatedRenderable[],
1301
- visibility: CpuMultiViewVisibilityResult | undefined,
1302
- scene: PersistentGpuDrivenState | undefined,
1303
920
  ): DirectionalShadowCacheDecision {
1304
- const observeShadowCensus = internals.shadowDebugObservation === true;
1305
921
  const cascadeCount = lights.cascadeCount;
1306
- const built = buildCascadeLocalCacheTokens({
1307
- lights,
1308
- visibility,
1309
- scene,
1310
- validated: validatedOrdered,
1311
- });
1312
- observeDirectionalShadowTokenBuild(internals.metrics, observeShadowCensus, built);
922
+ const lightViewProj = lights.lightViewProj;
1313
923
  if (
1314
924
  frameState.compiledFrameGraph === null ||
1315
925
  frameState.compiledFrameGraphTopologyKey === null ||
@@ -1317,87 +927,49 @@ function prepareDirectionalShadowCache(
1317
927
  shadowMapSize <= 0 ||
1318
928
  cascadeCount === undefined ||
1319
929
  cascadeCount <= 0 ||
1320
- built.tokens.length === 0 ||
930
+ lightViewProj === undefined ||
1321
931
  validatedOrdered.length === 0
1322
932
  ) {
1323
- if (observeShadowCensus && built.tokens.length > 0) {
1324
- internals.metrics.increment('shadow.csm.cache.precondition');
1325
- }
1326
- return { reuse: false, next: null, cascadeTokens: built.tokens, hitMask: 0 };
933
+ return { reuse: false, next: null };
1327
934
  }
1328
935
 
1329
936
  const cached = frameState.directionalShadowCache;
1330
- const assetCatalogEpoch = internals.assets.epoch;
937
+ const assetCatalogEpoch = internals.assets.catalogEpoch;
1331
938
  const meshResidencyEpoch = internals.gpuStore.meshResidencyEpoch;
1332
- const evaluation = evaluateDirectionalShadowCache({
1333
- cached,
1334
- cascadeTokens: built.tokens,
1335
- productionSceneToken: built.productionSceneToken,
1336
- shadowMapSize,
1337
- cascadeCount,
1338
- pipelineHandle: frameState.installedPipelineHandle,
1339
- graphTopologyKey: frameState.compiledFrameGraphTopologyKey,
1340
- assetCatalogEpoch,
1341
- meshResidencyEpoch,
1342
- worldStateMatches:
1343
- cached !== null ? built.productionSceneToken || sameWorldState(cached, worlds) : false,
1344
- });
1345
- if (evaluation.reuse) {
1346
- const decision = {
1347
- reuse: true,
1348
- next: null,
1349
- cascadeTokens: built.tokens,
1350
- hitMask: evaluation.hitMask,
1351
- };
1352
- observeDirectionalShadowCacheDecision(
1353
- internals.metrics,
1354
- observeShadowCensus,
1355
- decision,
1356
- undefined,
1357
- );
1358
- return decision;
939
+ if (
940
+ cached !== null &&
941
+ cached.shadowMapSize === shadowMapSize &&
942
+ cached.cascadeCount === cascadeCount &&
943
+ cached.pipelineHandle === frameState.installedPipelineHandle &&
944
+ cached.graphTopologyKey === frameState.compiledFrameGraphTopologyKey &&
945
+ cached.assetCatalogEpoch === assetCatalogEpoch &&
946
+ cached.meshResidencyEpoch === meshResidencyEpoch &&
947
+ sameWorldState(cached, worlds, leases) &&
948
+ sameLightViewProj(cached, lightViewProj)
949
+ ) {
950
+ return { reuse: true, next: null };
1359
951
  }
1360
952
 
1361
- const decision = {
1362
- reuse: false,
1363
- next: null,
1364
- cascadeTokens: built.tokens,
1365
- hitMask: evaluation.hitMask,
1366
- };
1367
- observeDirectionalShadowCacheDecision(
1368
- internals.metrics,
1369
- observeShadowCensus,
1370
- decision,
1371
- observeShadowCensus ? evaluation.missReason : undefined,
1372
- );
1373
-
1374
- const worldStateTokens = worlds.map((world) => snapshotWorldState(world));
1375
- if (worldStateTokens.some((state) => state === null)) {
1376
- // A world that does not expose the ECS mutation tokens cannot safely
1377
- // participate in a static-shadow reuse decision. Keep the conservative
1378
- // path for custom/test worlds instead of collapsing token indexes with a
1379
- // filter and risking a false-positive match.
1380
- if (observeShadowCensus) {
1381
- internals.metrics.increment('shadow.csm.cache.world-state-unavailable');
1382
- }
1383
- return decision;
953
+ if (leases === undefined || leases.length !== worlds.length) {
954
+ // Direct record-stage test harnesses may omit the host-owned leases. They
955
+ // still exercise the real shadow encoder, but without the ECS read owner
956
+ // they must conservatively rebuild rather than inspect World internals.
957
+ return { reuse: false, next: null };
1384
958
  }
959
+ const worldStateTokens = leases.map(readWorldState);
1385
960
 
1386
961
  return {
1387
962
  reuse: false,
1388
- cascadeTokens: built.tokens,
1389
- hitMask: evaluation.hitMask,
1390
963
  next: {
1391
964
  worlds: worlds.slice(),
1392
- worldStateTokens,
965
+ worldStateTokens: worldStateTokens as DirectionalShadowWorldState[],
1393
966
  assetCatalogEpoch,
1394
967
  pipelineHandle: frameState.installedPipelineHandle,
1395
968
  graphTopologyKey: frameState.compiledFrameGraphTopologyKey,
1396
969
  shadowMapSize,
1397
970
  cascadeCount,
971
+ lightViewProj: lightViewProj.map((matrix) => new Float32Array(matrix)),
1398
972
  meshResidencyEpoch,
1399
- cascadeTokens: built.tokens,
1400
- productionSceneToken: built.productionSceneToken,
1401
973
  },
1402
974
  };
1403
975
  }
@@ -1417,15 +989,17 @@ function validateRenderables(
1417
989
  internals: RenderSystemInternals,
1418
990
  world: World,
1419
991
  // feat-20260709-editor-world-partition ENGINE-fix-round2 (defect 2): the full
1420
- // Each renderable's mesh handle is a slot in its OWN world's
1421
- // sharedRefs; resolving against a foreign world either misses or resolves
1422
- // the wrong slot payload. Production resolves stable WorldRenderId through
1423
- // the renderer-owned lookup. Direct tests without that lookup retain the
1424
- // historical ordered-array fallback.
992
+ // worlds[] list. Each renderable's mesh handle is a user-tier slot in its OWN
993
+ // world's sharedRefs; resolving against a foreign world either misses
994
+ // (asset-not-registered) or resolves the wrong slot payload. `world` (the
995
+ // resource-owner) is retained as the fallback for renderables whose worldId
996
+ // is out of range (defensive; extractFrames always stamps a valid index).
1425
997
  worlds: readonly World[],
998
+ pipelineState: PipelineState,
999
+ frameState: RenderFrameState,
1426
1000
  renderables: readonly RenderableSnapshot[],
1427
1001
  transparentDispatch: readonly DispatchEntry[],
1428
- worldForRenderId?: (worldId: number) => World | undefined,
1002
+ excludedEntityKeys: ReadonlySet<number> = new Set<number>(),
1429
1003
  ): ValidatedRenderable[] {
1430
1004
  // bug-20260527-renderstate-pipeline-dispatch-gap D-4:
1431
1005
  // build a renderableIndex -> renderState map from dispatch entries
@@ -1451,23 +1025,17 @@ function validateRenderables(
1451
1025
  for (let rIdx = 0; rIdx < renderables.length; rIdx++) {
1452
1026
  const r = renderables[rIdx];
1453
1027
  if (r === undefined) continue;
1028
+ if (excludedEntityKeys.has(worldEntityKey(r.worldId, r.entityKey))) continue;
1454
1029
  // feat-20260709-editor-world-partition ENGINE-fix-round2 (defect 2): resolve
1455
- // this renderable's mesh against the world it was EXTRACTED from — NOT the
1456
- // single resource-owner `world`.
1457
- const renderableWorld =
1458
- worldForRenderId === undefined ? (worlds[r.worldId] ?? world) : worldForRenderId(r.worldId);
1459
- if (renderableWorld === undefined) {
1460
- internals.errorRegistry.fire(
1461
- new RhiError({
1462
- code: 'asset-not-registered',
1463
- expected: `attached renderer WorldRenderId ${r.worldId}`,
1464
- hint: 'keep every World passed to renderer.draw attached until its frame retires',
1465
- detail: { assetHandle: r.assetHandle },
1466
- }),
1467
- );
1468
- continue;
1469
- }
1470
- const assetRes = resolveRenderAsset<MeshAsset>(
1030
+ // this renderable's mesh against the world it was EXTRACTED from
1031
+ // (worlds[r.worldId]) — NOT the single resource-owner `world`. Builtin mesh
1032
+ // slots (< BUILTIN_BASE) resolve process-statically regardless of world, so
1033
+ // the per-world pick only matters for user-tier handles, but selecting it
1034
+ // unconditionally keeps a single code path. Falls back to the resource-owner
1035
+ // world if worldId is out of range (defensive; extractFrames always stamps
1036
+ // a valid index into worlds[]).
1037
+ const renderableWorld = worlds[r.worldId] ?? world;
1038
+ const assetRes = resolveAssetHandle<MeshAsset>(
1471
1039
  renderableWorld,
1472
1040
  toShared<'MeshAsset'>(r.assetHandle),
1473
1041
  );
@@ -1475,20 +1043,28 @@ function validateRenderables(
1475
1043
  internals.errorRegistry.fire(
1476
1044
  new RhiError({
1477
1045
  code: 'asset-not-registered',
1478
- expected: 'MeshFilter.assetHandle in the owning World sharedRefs',
1479
- hint: 'allocate or intern the MeshAsset in the same World before assigning MeshFilter.assetHandle',
1046
+ expected: 'MeshFilter.assetHandle in AssetRegistry',
1047
+ hint: 'use HANDLE_CUBE / HANDLE_TRIANGLE imports; custom mesh register path: feat-future-asset-system',
1480
1048
  detail: { assetHandle: r.assetHandle },
1481
1049
  }),
1482
1050
  );
1483
1051
  continue;
1484
1052
  }
1053
+ // feat-20260601-device/gpu-residency-extraction M1 (D-1): builtin meshes
1054
+ // (slots through HANDLE_NINESLICE_QUAD) keep the createRenderer step-3
1055
+ // direct-upload + `pipelineState.meshes` path -- they are NOT routed
1056
+ // through `ensureResident`. User-registered meshes pull through the store
1057
+ // on first access (the register->upload push was severed in this M1);
1058
+ // the POD fetched above (assetRes.value) is passed in, store holds no
1059
+ // registry ref (D-2). A first-access miss builds the GPU buffers; later
1060
+ // frames hit the O(1) cache.
1485
1061
  const meshAssetHandle = toShared<'MeshAsset'>(r.assetHandle);
1486
- let meshHandles = internals.gpuStore.getMeshGpuHandles(meshAssetHandle, r.worldId);
1487
- if (meshHandles === undefined) {
1062
+ let meshHandles = internals.gpuStore.getMeshGpuHandles(meshAssetHandle, renderableWorld);
1063
+ if (meshHandles === undefined && r.assetHandle > handleSlot(HANDLE_NINESLICE_QUAD)) {
1488
1064
  const residentRes = internals.gpuStore.ensureResident(
1489
1065
  meshAssetHandle,
1490
1066
  assetRes.value,
1491
- r.worldId,
1067
+ renderableWorld,
1492
1068
  );
1493
1069
  if (residentRes.ok) {
1494
1070
  meshHandles = residentRes.value;
@@ -1496,20 +1072,71 @@ function validateRenderables(
1496
1072
  internals.errorRegistry.fire(residentRes.error);
1497
1073
  }
1498
1074
  }
1075
+ meshHandles = meshHandles ?? pipelineState.meshes.get(r.assetHandle);
1499
1076
  if (meshHandles === undefined) {
1500
1077
  internals.errorRegistry.fire(
1501
1078
  new RhiError({
1502
1079
  code: 'asset-not-registered',
1503
1080
  expected: 'GPU mesh buffers uploaded for assetHandle',
1504
- hint: 'await renderer.ready before draw; keep the MeshAsset live in the renderable World',
1081
+ hint: 'await renderer.initialization before draw([world], { cameraOwner: 0, resourceOwner: 0 }); ensure AssetRegistry.configureGpuDevice ran so user meshes are uploaded',
1505
1082
  detail: { assetHandle: r.assetHandle },
1506
1083
  }),
1507
1084
  );
1508
1085
  continue;
1509
1086
  }
1087
+ // feat-20260527-sprite-nineslice M2 / w11 (plan-strategy section D-2):
1088
+ // sprite branch with non-zero `slicesAndMode` (post-w12 paramSnapshot
1089
+ // entry name) overrides the user-supplied mesh handle (typically
1090
+ // HANDLE_QUAD = 3) with the 16-vertex / 54-index HANDLE_NINESLICE_QUAD
1091
+ // (id=5) topology so the vertex shader sees the 4x4 grid required for
1092
+ // 9-region anchor mapping. Default slicesAndMode ([0, 0, 0, 0]) keeps
1093
+ // the legacy HANDLE_QUAD path; a flip from zero to non-zero on the
1094
+ // same entity routes here per-frame so AI users can toggle 9-slice
1095
+ // on the fly without re-spawning the entity (charter F1 minimum
1096
+ // surface). The HANDLE_NINESLICE_QUAD GPU buffers are seeded by
1097
+ // createRenderer step-3.
1098
+ //
1099
+ // feat-20260625-refactor-sprite-as-transparent-mesh M3 / w13: judgement
1100
+ // key migrated from `shadingModel === 'sprite'` to
1101
+ // `materialShaderId === 'forgeax::sprite'` (plan-strategy D-10); slices
1102
+ // sourced from `paramSnapshot.slicesAndMode` (post-w12 UBO-aligned
1103
+ // overlay path).
1104
+ //
1105
+ // feat-20260624-sprite-lit-shading-model-pure-2d-lighting M1' / t7:
1106
+ // sprite-lit shares the sprite paramSchema (5 fields, t4 mirror) so
1107
+ // the 9-slices mesh swap applies identically.
1108
+ let effectiveMeshHandles = meshHandles;
1109
+ if (
1110
+ r.material.materialShaderId === 'forgeax::sprite' ||
1111
+ r.material.materialShaderId === 'forgeax::sprite-lit'
1112
+ ) {
1113
+ const slicesArr = r.material.paramSnapshot?.slicesAndMode as readonly number[] | undefined;
1114
+ if (
1115
+ slicesArr !== undefined &&
1116
+ slicesArr.length >= 4 &&
1117
+ (slicesArr[0] !== 0 || slicesArr[1] !== 0 || slicesArr[2] !== 0 || slicesArr[3] !== 0)
1118
+ ) {
1119
+ const nineSliceHandles = pipelineState.meshes.get(handleSlot(HANDLE_NINESLICE_QUAD));
1120
+ if (nineSliceHandles !== undefined) {
1121
+ effectiveMeshHandles = nineSliceHandles;
1122
+ }
1123
+ }
1124
+ }
1125
+ if (r.morph !== undefined) {
1126
+ const morphed = prepareMorphMesh(
1127
+ internals,
1128
+ frameState,
1129
+ worldEntityKey(r.worldId, r.entityKey),
1130
+ effectiveMeshHandles,
1131
+ assetRes.value,
1132
+ r.morph,
1133
+ );
1134
+ if (morphed !== undefined) effectiveMeshHandles = morphed;
1135
+ }
1510
1136
  validated.push({
1511
1137
  source: r,
1512
- mesh: meshHandles,
1138
+ world: renderableWorld,
1139
+ mesh: effectiveMeshHandles,
1513
1140
  renderableIndex: rIdx,
1514
1141
  renderState: renderStateByRenderableIdx.get(rIdx),
1515
1142
  variantSet: variantSetByRenderableIdx.get(rIdx),
@@ -1519,6 +1146,81 @@ function validateRenderables(
1519
1146
  return validated;
1520
1147
  }
1521
1148
 
1149
+ function prepareMorphMesh(
1150
+ internals: RenderSystemInternals,
1151
+ frameState: RenderFrameState,
1152
+ cacheKey: number,
1153
+ base: MeshGpuHandles,
1154
+ mesh: MeshAsset,
1155
+ morph: RenderableSnapshot['morph'],
1156
+ ): MeshGpuHandles | undefined {
1157
+ if (morph === undefined || frameState.morphBuffers === undefined) return undefined;
1158
+ const targets = mesh.morphTargets;
1159
+ if (targets === undefined || targets.length !== morph.targetCount) return undefined;
1160
+ if (base.layoutProjection.arrayStride !== 48 || base.vertexCount <= 0) return undefined;
1161
+ const stride = mesh.vertices.length / base.vertexCount;
1162
+ if (!Number.isInteger(stride) || stride < 3) return undefined;
1163
+
1164
+ let active = false;
1165
+ for (const weight of morph.weights) {
1166
+ if (weight !== 0) {
1167
+ active = true;
1168
+ break;
1169
+ }
1170
+ }
1171
+ if (!active) {
1172
+ frameState.morphBuffers.get(cacheKey)?.buffer.destroy();
1173
+ frameState.morphBuffers.delete(cacheKey);
1174
+ return undefined;
1175
+ }
1176
+
1177
+ const vertices = new Float32Array(mesh.vertices);
1178
+ for (let targetIndex = 0; targetIndex < targets.length; targetIndex += 1) {
1179
+ const weight = morph.weights[targetIndex] ?? 0;
1180
+ const positions = targets[targetIndex]?.position;
1181
+ if (positions === undefined || positions.length !== base.vertexCount * 3) return undefined;
1182
+ for (let vertex = 0; vertex < base.vertexCount; vertex += 1) {
1183
+ const vertexBase = vertex * stride;
1184
+ const positionBase = vertex * 3;
1185
+ vertices[vertexBase] = (vertices[vertexBase] ?? 0) + (positions[positionBase] ?? 0) * weight;
1186
+ vertices[vertexBase + 1] =
1187
+ (vertices[vertexBase + 1] ?? 0) + (positions[positionBase + 1] ?? 0) * weight;
1188
+ vertices[vertexBase + 2] =
1189
+ (vertices[vertexBase + 2] ?? 0) + (positions[positionBase + 2] ?? 0) * weight;
1190
+ }
1191
+ }
1192
+
1193
+ let cached = frameState.morphBuffers.get(cacheKey);
1194
+ if (cached === undefined || cached.byteLength !== vertices.byteLength) {
1195
+ if (cached !== undefined && !cached.buffer.isDestroyed) {
1196
+ const destroyed = cached.buffer.destroy();
1197
+ if (!destroyed.ok) internals.errorRegistry.fire(destroyed.error);
1198
+ }
1199
+ const created = internals.device.createBuffer({
1200
+ label: `morph-${cacheKey}-vbo`,
1201
+ size: vertices.byteLength,
1202
+ usage: GPU_BUFFER_USAGE_VERTEX | GPU_BUFFER_USAGE_COPY_DST,
1203
+ mappedAtCreation: false,
1204
+ });
1205
+ if (!created.ok) {
1206
+ internals.errorRegistry.fire(created.error);
1207
+ frameState.morphBuffers.delete(cacheKey);
1208
+ return undefined;
1209
+ }
1210
+ cached = {
1211
+ buffer: new GpuBuffer(internals.device, created.value),
1212
+ byteLength: vertices.byteLength,
1213
+ };
1214
+ frameState.morphBuffers.set(cacheKey, cached);
1215
+ }
1216
+ const written = internals.device.queue.writeBuffer(cached.buffer.handle, 0, vertices);
1217
+ if (!written.ok) {
1218
+ internals.errorRegistry.fire(written.error);
1219
+ return undefined;
1220
+ }
1221
+ return { ...base, vertexBuffer: cached.buffer };
1222
+ }
1223
+
1522
1224
  /**
1523
1225
  * feat-20260704 M3/w18: build the dispatch-ordered render plan, extracted
1524
1226
  * verbatim from `recordFrame`. (1) M3/w26 dispatch-ordered reorder: reorder
@@ -1575,7 +1277,7 @@ function buildDispatchPlan(
1575
1277
  ordered.push(v);
1576
1278
  }
1577
1279
  }
1578
- // Append any renderables not in the dispatch list (e.g. default-material entities)
1280
+ // Append renderables not in the dispatch list (e.g. default-material entities)
1579
1281
  for (const v of validated) {
1580
1282
  if (!seen.has(v.renderableIndex)) {
1581
1283
  ordered.push(v);
@@ -1610,7 +1312,7 @@ function buildDispatchPlan(
1610
1312
  //
1611
1313
  // bug-20260609: feat-20260608 M5 amend made the material UBO indexed by
1612
1314
  // cumulative *material-slot* count (one slot per submesh material),
1613
- // which is >= entity count once any entity carries `materials.length>1`.
1315
+ // which is >= entity count once an entity carries `materials.length>1`.
1614
1316
  // The mesh + material buffer pair share `slotCount` (single allocator),
1615
1317
  // so we size against the larger of the two requirements: entity count
1616
1318
  // (mesh-SSBO consumer) vs cumulative material-slot count (material-UBO
@@ -1721,7 +1423,7 @@ function buildDispatchPlan(
1721
1423
  // Singleton buckets (mode-bypass under D-5, or non-foldable under
1722
1424
  // mode 0) carry `bucketSize === 1` and never enter `headBuckets`, so
1723
1425
  // the per-entity drawIndexed path correctly does not count.
1724
- // SSOT helper lives in `render-system-fold.ts` — engine code never
1426
+ // SSOT helper lives in this record owner — engine code never
1725
1427
  // hardcodes the metric key string.
1726
1428
  incrementFoldedDrawsMetric(foldDispatchPlan, internals.metrics);
1727
1429
  }
@@ -1798,6 +1500,17 @@ function cleanPerFrameCaches(
1798
1500
  frameState.instanceBuffers.delete(key);
1799
1501
  }
1800
1502
  }
1503
+
1504
+ if (frameState.morphBuffers !== undefined) {
1505
+ for (const [key, entry] of frameState.morphBuffers.entries()) {
1506
+ if (validatedEntityKeys.has(key)) continue;
1507
+ if (!entry.buffer.isDestroyed) {
1508
+ const result = entry.buffer.destroy();
1509
+ if (!result.ok) internals.errorRegistry.fire(result.error);
1510
+ }
1511
+ frameState.morphBuffers.delete(key);
1512
+ }
1513
+ }
1801
1514
  }
1802
1515
 
1803
1516
  /**