@forgeax/engine-render 0.1.4 → 0.1.7

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 (979) hide show
  1. package/README.md +268 -640
  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 +214 -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 +11 -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-HCOGPHKS.mjs +93 -0
  54. package/dist/chunk-HCOGPHKS.mjs.map +1 -0
  55. package/dist/chunk-YEJIUW7D.mjs +1152 -0
  56. package/dist/chunk-YEJIUW7D.mjs.map +1 -0
  57. package/dist/components/__tests__/points-lines-admission.unit.test.d.ts +2 -0
  58. package/dist/components/__tests__/points-lines-admission.unit.test.d.ts.map +1 -0
  59. package/dist/components/__tests__/points-lines-schema.unit.test.d.ts +2 -0
  60. package/dist/components/__tests__/points-lines-schema.unit.test.d.ts.map +1 -0
  61. package/dist/components/__tests__/schema-contract.unit.test.d.ts +2 -0
  62. package/dist/components/__tests__/schema-contract.unit.test.d.ts.map +1 -0
  63. package/dist/components/glyph-text.d.ts +1 -1
  64. package/dist/components/glyph-text.d.ts.map +1 -1
  65. package/dist/components/index.d.ts +4 -2
  66. package/dist/components/index.d.ts.map +1 -1
  67. package/dist/components/instances.d.ts +1 -1
  68. package/dist/components/instances.d.ts.map +1 -1
  69. package/dist/components/light-helpers.d.ts +1 -1
  70. package/dist/components/light-helpers.d.ts.map +1 -1
  71. package/dist/components/lines.d.ts +5 -0
  72. package/dist/components/lines.d.ts.map +1 -0
  73. package/dist/components/mesh-filter.d.ts +7 -7
  74. package/dist/components/mesh-filter.d.ts.map +1 -1
  75. package/dist/components/points.d.ts +15 -0
  76. package/dist/components/points.d.ts.map +1 -0
  77. package/dist/components/scene-instance.d.ts +11 -11
  78. package/dist/components/skybox-background.d.ts +56 -0
  79. package/dist/components/skybox-background.d.ts.map +1 -0
  80. package/dist/components/skylight.d.ts +55 -0
  81. package/dist/components/skylight.d.ts.map +1 -0
  82. package/dist/components/sprite-animation.d.ts +2 -3
  83. package/dist/components/sprite-animation.d.ts.map +1 -1
  84. package/dist/components/sprite-instances.d.ts +1 -1
  85. package/dist/components/sprite-playback-mode.d.ts +6 -1
  86. package/dist/components/sprite-playback-mode.d.ts.map +1 -1
  87. package/dist/components/sprite-region-override.d.ts +2 -3
  88. package/dist/components/sprite-region-override.d.ts.map +1 -1
  89. package/dist/components/tile-layer.d.ts +5 -0
  90. package/dist/components/tile-layer.d.ts.map +1 -1
  91. package/dist/construct-renderer.d.ts +18 -1
  92. package/dist/construct-renderer.d.ts.map +1 -1
  93. package/dist/construct-renderer.mjs +27463 -4
  94. package/dist/construct-renderer.mjs.map +1 -1
  95. package/dist/debug-draw-glue.d.ts +2 -53
  96. package/dist/debug-draw-glue.d.ts.map +1 -1
  97. package/dist/device/device-scope.d.ts +83 -0
  98. package/dist/device/device-scope.d.ts.map +1 -0
  99. package/dist/{gpu-resource-store.d.ts → device/gpu-residency.d.ts} +91 -48
  100. package/dist/device/gpu-residency.d.ts.map +1 -0
  101. package/dist/device/resource-types.d.ts +8 -0
  102. package/dist/device/resource-types.d.ts.map +1 -0
  103. package/dist/engine-metrics.d.ts +2 -2
  104. package/dist/engine-metrics.d.ts.map +1 -1
  105. package/dist/errors/recover.d.ts +28 -1
  106. package/dist/errors/recover.d.ts.map +1 -1
  107. package/dist/errors/render.d.ts +320 -248
  108. package/dist/errors/render.d.ts.map +1 -1
  109. package/dist/features/fullscreen.d.ts +19 -0
  110. package/dist/features/fullscreen.d.ts.map +1 -0
  111. package/dist/features/host.d.ts +32 -26
  112. package/dist/features/host.d.ts.map +1 -1
  113. package/dist/features/morph/morph-feature.d.ts +39 -5
  114. package/dist/features/morph/morph-feature.d.ts.map +1 -1
  115. package/dist/features/plan.d.ts +182 -0
  116. package/dist/features/plan.d.ts.map +1 -0
  117. package/dist/features/prepared-gpu-work.d.ts +21 -17
  118. package/dist/features/prepared-gpu-work.d.ts.map +1 -1
  119. package/dist/features/prepared-graphics-store.d.ts +2 -1
  120. package/dist/features/prepared-graphics-store.d.ts.map +1 -1
  121. package/dist/features/prepared-graphics.d.ts +11 -21
  122. package/dist/features/prepared-graphics.d.ts.map +1 -1
  123. package/dist/features/render-graph-compute.d.ts +2 -2
  124. package/dist/features/render-graph-compute.d.ts.map +1 -1
  125. package/dist/features/render-graph-contribution.d.ts +4 -7
  126. package/dist/features/render-graph-contribution.d.ts.map +1 -1
  127. package/dist/features/render-graph-raster.d.ts +7 -3
  128. package/dist/features/render-graph-raster.d.ts.map +1 -1
  129. package/dist/features/render-graph-resources.d.ts +6 -6
  130. package/dist/features/render-graph-resources.d.ts.map +1 -1
  131. package/dist/features/targets.d.ts +5 -5
  132. package/dist/features/targets.d.ts.map +1 -1
  133. package/dist/features/types.d.ts +11 -190
  134. package/dist/features/types.d.ts.map +1 -1
  135. package/dist/features/vocabulary.d.ts +14 -0
  136. package/dist/features/vocabulary.d.ts.map +1 -0
  137. package/dist/fullscreen-post-process-pass.d.ts +4 -25
  138. package/dist/fullscreen-post-process-pass.d.ts.map +1 -1
  139. package/dist/glyph-text-layout-system.d.ts +3 -2
  140. package/dist/glyph-text-layout-system.d.ts.map +1 -1
  141. package/dist/gpu-driven/batch-topology.d.ts +6 -75
  142. package/dist/gpu-driven/batch-topology.d.ts.map +1 -1
  143. package/dist/gpu-driven/production-raster.d.ts +13 -112
  144. package/dist/gpu-driven/production-raster.d.ts.map +1 -1
  145. package/dist/gpu-driven/view-gpu.d.ts +10 -66
  146. package/dist/gpu-driven/view-gpu.d.ts.map +1 -1
  147. package/dist/gpu-resource.d.ts +3 -3
  148. package/dist/gpu-scene-schema.d.ts +1 -94
  149. package/dist/gpu-scene-schema.d.ts.map +1 -1
  150. package/dist/gpu-scene.d.ts +7 -33
  151. package/dist/gpu-scene.d.ts.map +1 -1
  152. package/dist/hdrp-buffers.d.ts +3 -53
  153. package/dist/hdrp-buffers.d.ts.map +1 -1
  154. package/dist/ibl/IblPipelineCache.d.ts +74 -180
  155. package/dist/ibl/IblPipelineCache.d.ts.map +1 -1
  156. package/dist/ibl/skylight-bind-group.d.ts +1 -1
  157. package/dist/index.d.ts +17 -43
  158. package/dist/index.d.ts.map +1 -1
  159. package/dist/index.mjs +167 -3
  160. package/dist/index.mjs.map +1 -1
  161. package/dist/inspection-types.d.ts +72 -0
  162. package/dist/inspection-types.d.ts.map +1 -0
  163. package/dist/instance-buffer-cache.d.ts +1 -4
  164. package/dist/instance-buffer-cache.d.ts.map +1 -1
  165. package/dist/internal.d.ts +1 -73
  166. package/dist/internal.d.ts.map +1 -1
  167. package/dist/internal.mjs +1 -43
  168. package/dist/internal.mjs.map +1 -1
  169. package/dist/lifecycle.d.ts +36 -2
  170. package/dist/lifecycle.d.ts.map +1 -1
  171. package/dist/materials.d.ts +0 -3
  172. package/dist/materials.d.ts.map +1 -1
  173. package/dist/mesh-material-bindings.d.ts +0 -1
  174. package/dist/mesh-material-bindings.d.ts.map +1 -1
  175. package/dist/pbr-pipeline.d.ts +77 -29
  176. package/dist/pbr-pipeline.d.ts.map +1 -1
  177. package/dist/pipeline/standard-forward-lane.d.ts +5 -0
  178. package/dist/pipeline/standard-forward-lane.d.ts.map +1 -0
  179. package/dist/pipeline/standard-pipeline.d.ts +31 -0
  180. package/dist/pipeline/standard-pipeline.d.ts.map +1 -0
  181. package/dist/pipeline/standard-profile.d.ts +40 -0
  182. package/dist/pipeline/standard-profile.d.ts.map +1 -0
  183. package/dist/pipeline-builder.d.ts +3 -3
  184. package/dist/pipeline-builder.d.ts.map +1 -1
  185. package/dist/pipeline-spec-types.d.ts +24 -0
  186. package/dist/pipeline-spec-types.d.ts.map +1 -0
  187. package/dist/pipeline-spec.d.ts +16 -155
  188. package/dist/pipeline-spec.d.ts.map +1 -1
  189. package/dist/plugin.d.ts +1 -14
  190. package/dist/plugin.d.ts.map +1 -1
  191. package/dist/points-lines/__tests__/bounds.unit.test.d.ts +2 -0
  192. package/dist/points-lines/__tests__/bounds.unit.test.d.ts.map +1 -0
  193. package/dist/points-lines/__tests__/cache-recovery.unit.test.d.ts +2 -0
  194. package/dist/points-lines/__tests__/cache-recovery.unit.test.d.ts.map +1 -0
  195. package/dist/points-lines/__tests__/inspection-lanes.integration.test.d.ts +2 -0
  196. package/dist/points-lines/__tests__/inspection-lanes.integration.test.d.ts.map +1 -0
  197. package/dist/points-lines/__tests__/inspection.unit.test.d.ts +2 -0
  198. package/dist/points-lines/__tests__/inspection.unit.test.d.ts.map +1 -0
  199. package/dist/points-lines/__tests__/lane-structural.integration.test.d.ts +2 -0
  200. package/dist/points-lines/__tests__/lane-structural.integration.test.d.ts.map +1 -0
  201. package/dist/points-lines/__tests__/points-lines.bench.d.ts +2 -0
  202. package/dist/points-lines/__tests__/points-lines.bench.d.ts.map +1 -0
  203. package/dist/points-lines/__tests__/raster-lane.integration.test.d.ts +2 -0
  204. package/dist/points-lines/__tests__/raster-lane.integration.test.d.ts.map +1 -0
  205. package/dist/points-lines/__tests__/snapshot.unit.test.d.ts +2 -0
  206. package/dist/points-lines/__tests__/snapshot.unit.test.d.ts.map +1 -0
  207. package/dist/points-lines/__tests__/view-ubo.unit.test.d.ts +2 -0
  208. package/dist/points-lines/__tests__/view-ubo.unit.test.d.ts.map +1 -0
  209. package/dist/points-lines/admission.d.ts +36 -0
  210. package/dist/points-lines/admission.d.ts.map +1 -0
  211. package/dist/points-lines/bounds.d.ts +8 -0
  212. package/dist/points-lines/bounds.d.ts.map +1 -0
  213. package/dist/points-lines/expansion-cache.d.ts +28 -0
  214. package/dist/points-lines/expansion-cache.d.ts.map +1 -0
  215. package/dist/points-lines/inspection.d.ts +53 -0
  216. package/dist/points-lines/inspection.d.ts.map +1 -0
  217. package/dist/points-lines/prepare.d.ts +67 -0
  218. package/dist/points-lines/prepare.d.ts.map +1 -0
  219. package/dist/points-lines/record.d.ts +60 -0
  220. package/dist/points-lines/record.d.ts.map +1 -0
  221. package/dist/points-lines/snapshot.d.ts +49 -0
  222. package/dist/points-lines/snapshot.d.ts.map +1 -0
  223. package/dist/post-process-errors.d.ts +18 -18
  224. package/dist/post-process-errors.d.ts.map +1 -1
  225. package/dist/prepare/prepared-graphics-resolver.d.ts +2 -5
  226. package/dist/prepare/prepared-graphics-resolver.d.ts.map +1 -1
  227. package/dist/record/__tests__/typed-frame-graph-plan.unit.test.d.ts +2 -0
  228. package/dist/record/__tests__/typed-frame-graph-plan.unit.test.d.ts.map +1 -0
  229. package/dist/record/frame-lighting.d.ts +7 -73
  230. package/dist/record/frame-lighting.d.ts.map +1 -1
  231. package/dist/record/frame-snapshot.d.ts +44 -44
  232. package/dist/record/frame-snapshot.d.ts.map +1 -1
  233. package/dist/record/frame-targets.d.ts +5 -38
  234. package/dist/record/frame-targets.d.ts.map +1 -1
  235. package/dist/record/frame.d.ts +64 -52
  236. package/dist/record/frame.d.ts.map +1 -1
  237. package/dist/record/helpers.d.ts +3 -2
  238. package/dist/record/helpers.d.ts.map +1 -1
  239. package/dist/record/main-pass-geometry.d.ts +12 -4
  240. package/dist/record/main-pass-geometry.d.ts.map +1 -1
  241. package/dist/record/main-pass-material.d.ts +90 -59
  242. package/dist/record/main-pass-material.d.ts.map +1 -1
  243. package/dist/record/main-pass-sprite-draws.d.ts +28 -3
  244. package/dist/record/main-pass-sprite-draws.d.ts.map +1 -1
  245. package/dist/record/main-pass.d.ts +1 -1
  246. package/dist/record/main-pass.d.ts.map +1 -1
  247. package/dist/record/mesh-ssbo.d.ts +276 -3
  248. package/dist/record/mesh-ssbo.d.ts.map +1 -1
  249. package/dist/record/render-context.d.ts +252 -0
  250. package/dist/record/render-context.d.ts.map +1 -0
  251. package/dist/record/shadow-pass.d.ts +15 -88
  252. package/dist/record/shadow-pass.d.ts.map +1 -1
  253. package/dist/record/skybox-post-pass.d.ts +1 -1
  254. package/dist/record/skybox-post-pass.d.ts.map +1 -1
  255. package/dist/record/typed-frame-graph.d.ts +29 -7
  256. package/dist/record/typed-frame-graph.d.ts.map +1 -1
  257. package/dist/record/view-ubo.d.ts +9 -3
  258. package/dist/record/view-ubo.d.ts.map +1 -1
  259. package/dist/render-contract.d.ts +424 -0
  260. package/dist/render-contract.d.ts.map +1 -0
  261. package/dist/render-data.d.ts.map +1 -1
  262. package/dist/render-graph-primitives.d.ts +14 -362
  263. package/dist/render-graph-primitives.d.ts.map +1 -1
  264. package/dist/render-pipeline.d.ts +25 -64
  265. package/dist/render-pipeline.d.ts.map +1 -1
  266. package/dist/render-system-extract.d.ts +110 -338
  267. package/dist/render-system-extract.d.ts.map +1 -1
  268. package/dist/render-system.d.ts +24 -713
  269. package/dist/render-system.d.ts.map +1 -1
  270. package/dist/scene/render-scene-types.d.ts +61 -0
  271. package/dist/scene/render-scene-types.d.ts.map +1 -0
  272. package/dist/scene/render-scene.d.ts +112 -0
  273. package/dist/scene/render-scene.d.ts.map +1 -0
  274. package/dist/scene-instances/post-spawn-resolve-joints.d.ts +2 -5
  275. package/dist/scene-instances/post-spawn-resolve-joints.d.ts.map +1 -1
  276. package/dist/ssao-buffers.d.ts +22 -3
  277. package/dist/ssao-buffers.d.ts.map +1 -1
  278. package/dist/systems/skin-palette-allocator.d.ts +2 -4
  279. package/dist/systems/skin-palette-allocator.d.ts.map +1 -1
  280. package/dist/systems/skin-palette-types.d.ts +14 -0
  281. package/dist/systems/skin-palette-types.d.ts.map +1 -0
  282. package/dist/systems/transparent-sort-config.d.ts +23 -2
  283. package/dist/systems/transparent-sort-config.d.ts.map +1 -1
  284. package/dist/tilemap-chunk-extract-system.d.ts +4 -3
  285. package/dist/tilemap-chunk-extract-system.d.ts.map +1 -1
  286. package/dist/typed-render-graph-primitives.d.ts +2 -4
  287. package/dist/typed-render-graph-primitives.d.ts.map +1 -1
  288. package/dist/typed-shadow-passes.d.ts +3 -3
  289. package/dist/typed-shadow-passes.d.ts.map +1 -1
  290. package/package.json +23 -23
  291. package/src/__tests__/authoring-path.unit.test.ts +9 -0
  292. package/src/__tests__/backend-selection-rhi-capture.test-d.ts +1 -1
  293. package/src/__tests__/callback-inference.test-d.ts +6 -3
  294. package/src/__tests__/cluster-grid-default-surface.unit.test.ts +7 -4
  295. package/src/__tests__/construct-renderer-identity.unit.test.ts +15 -2
  296. package/src/__tests__/device-scope-lifecycle.unit.test.ts +151 -0
  297. package/src/__tests__/device-scope-stale-matrix.unit.test.ts +42 -0
  298. package/src/__tests__/direct-extract-consumer-contract.test-d.ts +8 -19
  299. package/src/__tests__/direct-light-snapshot.unit.test.ts +3 -13
  300. package/src/__tests__/factory-contract.integration.test.ts +158 -209
  301. package/src/__tests__/fallback-row-stride-surface.unit.test.ts +1 -4
  302. package/src/__tests__/frame-observation.contract.test.ts +17 -8
  303. package/src/__tests__/fullscreen-feature-plan.unit.test.ts +32 -0
  304. package/src/__tests__/glyph-text-layout-recovery.unit.test.ts +6 -11
  305. package/src/__tests__/gpu-driven-indirect-raster-evidence.ts +21 -18
  306. package/src/__tests__/gpu-driven-production.integration.test.ts +32 -205
  307. package/src/__tests__/gpu-driven-scaling.unit.test.ts +4 -7
  308. package/src/__tests__/gpu-driven-view-gpu-evidence.ts +87 -8
  309. package/src/__tests__/gpu-driven-view-graph.integration.test.ts +170 -8
  310. package/src/{gpu-driven/view-reference.ts → __tests__/gpu-driven-view-reference.ts} +4 -3
  311. package/src/__tests__/gpu-driven-view.browser.test.ts +12 -1
  312. package/src/__tests__/gpu-driven-view.dawn.test.ts +12 -1
  313. package/src/__tests__/gpu-driven-view.unit.test.ts +11 -267
  314. package/src/__tests__/gpu-resource-store-stride.unit.test.ts +3 -3
  315. package/src/__tests__/gpu-scene-render-graph-gpu.ts +2 -3
  316. package/src/__tests__/gpu-scene.dawn.test.ts +2 -6
  317. package/src/__tests__/gpu-scene.unit.test.ts +10 -24
  318. package/src/__tests__/ibl/face-uniforms.unit.test.ts +46 -0
  319. package/src/__tests__/infinite-grid-no-vertex-probe.unit.test.ts +34 -66
  320. package/src/__tests__/ktx2-basis-gpu-consumer.dawn.test.ts +14 -5
  321. package/src/__tests__/light-buffer-layout.unit.test.ts +4 -49
  322. package/src/__tests__/material-cooked-projection.unit.test.ts +31 -58
  323. package/src/__tests__/material-snapshot-mutation.integration.test.ts +10 -15
  324. package/src/__tests__/material-static-dynamic-route.unit.test.ts +31 -50
  325. package/src/__tests__/material-stride-surface.unit.test.ts +2 -2
  326. package/src/__tests__/mesh-buffer-usage-surface.unit.test.ts +7 -8
  327. package/src/__tests__/morph-culling-reentry.dawn.test.ts +29 -38
  328. package/src/__tests__/morph-feature-gpu-work.integration.test.ts +45 -43
  329. package/src/__tests__/morph-feature-plan.unit.test.ts +53 -0
  330. package/src/__tests__/morph-visual-evidence.integration.test.ts +47 -33
  331. package/src/__tests__/nineslice-handle-surface.unit.test.ts +11 -6
  332. package/src/__tests__/post-process-error-policy-owner.unit.test.ts +6 -13
  333. package/src/__tests__/prepared-graphics-capability.unit.test.ts +6 -81
  334. package/src/__tests__/prepared-graphics-generation.unit.test.ts +3 -17
  335. package/src/__tests__/prepared-graphics-lifecycle.unit.test.ts +6 -74
  336. package/src/__tests__/prepared-graphics-resolver.unit.test.ts +1 -3
  337. package/src/__tests__/prepared-graphics-snapshot.unit.test.ts +2 -2
  338. package/src/__tests__/profiler-default-off.test.ts +1 -1
  339. package/src/__tests__/profiler-phase-catalog.test.ts +2 -2
  340. package/src/__tests__/profiler-phase-integration.test.ts +5 -13
  341. package/src/__tests__/profiler-phase-token.test.ts +3 -8
  342. package/src/__tests__/public-surface.test-d.ts +65 -3
  343. package/src/__tests__/recover-error-policy-owner.unit.test.ts +1 -21
  344. package/src/__tests__/render-error-code-owner.test-d.ts +24 -23
  345. package/src/__tests__/render-error-detail-ssot.test-d.ts +0 -95
  346. package/src/__tests__/render-error-exhaustive.test-d.ts +27 -1
  347. package/src/__tests__/render-feature-assembly.test-d.ts +8 -16
  348. package/src/__tests__/render-feature-context.test-d.ts +16 -40
  349. package/src/__tests__/render-feature-contract.test-d.ts +9 -18
  350. package/src/__tests__/render-feature-diagnostics.unit.test.ts +6 -8
  351. package/src/__tests__/render-feature-docs.unit.test.ts +8 -7
  352. package/src/__tests__/render-feature-gpu-work.integration.test.ts +14 -75
  353. package/src/__tests__/render-feature-plan.test-d.ts +161 -0
  354. package/src/__tests__/render-feature-plan.unit.test.ts +170 -0
  355. package/src/__tests__/render-feature-prepared-diagnostics.unit.test.ts +8 -19
  356. package/src/__tests__/render-feature-prepared-errors.test-d.ts +3 -6
  357. package/src/__tests__/render-feature-prepared-graphics-docs.unit.test.ts +10 -71
  358. package/src/__tests__/render-feature-prepared-graphics-isolation.unit.test.ts +11 -61
  359. package/src/__tests__/render-feature-prepared-graphics-negative.test-d.ts +20 -43
  360. package/src/__tests__/render-feature-prepared-graphics.test-d.ts +56 -78
  361. package/src/__tests__/render-feature-public-surface.test-d.ts +6 -24
  362. package/src/__tests__/render-feature-retry.unit.test.ts +7 -48
  363. package/src/__tests__/render-feature-scheduling.unit.test.ts +15 -27
  364. package/src/__tests__/render-feature-stage-failed-policy-owner.unit.test.ts +0 -9
  365. package/src/__tests__/render-feature-stage-fault-recovery.unit.test.ts +19 -109
  366. package/src/__tests__/render-feature-visibility-context.test-d.ts +1 -6
  367. package/src/__tests__/render-feature-visibility-context.unit.test.ts +1 -2
  368. package/src/__tests__/render-feature-zero-work.unit.test.ts +14 -20
  369. package/src/__tests__/render-scene-projection.unit.test.ts +73 -111
  370. package/src/__tests__/renderer-factory-material-contract.unit.test.ts +82 -95
  371. package/src/__tests__/renderer-lifecycle.integration.test.ts +49 -1
  372. package/src/__tests__/shader-stage-surface.unit.test.ts +14 -9
  373. package/src/__tests__/ssao-config.unit.test.ts +36 -0
  374. package/src/__tests__/ssao-kernel-surface.unit.test.ts +4 -5
  375. package/src/__tests__/standard-clustered-pipeline.unit.test.ts +28 -0
  376. package/src/__tests__/standard-descriptors.test-d.ts +22 -0
  377. package/src/__tests__/standard-pipeline.dawn.test.ts +61 -0
  378. package/src/__tests__/standard-pipeline.integration.test.ts +124 -0
  379. package/src/__tests__/standard-profile-oracle.unit.test.ts +62 -0
  380. package/src/__tests__/surface-configuration.unit.test.ts +1 -2
  381. package/src/__tests__/texture-usage-surface.unit.test.ts +3 -6
  382. package/src/__tests__/typed-pipeline-topology.unit.test.ts +40 -206
  383. package/src/__tests__/urp-pipeline-targets.unit.test.ts +1 -11
  384. package/src/__tests__/urp-pipeline.unit.test.ts +2 -2
  385. package/src/__tests__/vertex-color-pipeline.unit.test.ts +10 -2
  386. package/src/__tests__/vertex-layout-consumer.unit.test.ts +1 -1
  387. package/src/__tests__/visibility-diagnostics.integration.test.ts +7 -11
  388. package/src/__tests__/visibility-extract.integration.test.ts +20 -11
  389. package/src/__tests__/visibility-extract.perf.test.ts +66 -32
  390. package/src/__tests__/visibility-feature-dedup.integration.test.ts +2 -4
  391. package/src/__tests__/visibility-hidden-report.unit.test.ts +1 -2
  392. package/src/__tests__/visibility-instances.integration.test.ts +7 -11
  393. package/src/__tests__/visibility-multi-world.integration.test.ts +38 -6
  394. package/src/__tests__/visibility-producer-matrix.integration.test.ts +18 -41
  395. package/src/__tests__/visibility-public-surface.test-d.ts +10 -9
  396. package/src/__tests__/visibility-resolver.unit.test.ts +2 -1
  397. package/src/__tests__/visibility-resource-short-circuit.unit.test.ts +80 -0
  398. package/src/__tests__/visibility-snapshot-contract.unit.test.ts +2 -1
  399. package/src/__tests__/vocabulary.unit.test.ts +1 -1
  400. package/src/assembly/backend-contract.ts +57 -0
  401. package/src/{renderer/renderer-factory.ts → assembly/factory.ts} +1741 -2699
  402. package/src/assembly/host-contract.ts +101 -0
  403. package/src/{renderer → assembly}/material/assembly.ts +2 -7
  404. package/src/assembly/material/pipeline-projection.ts +63 -0
  405. package/src/authoring.ts +15 -22
  406. package/src/components/__tests__/points-lines-admission.unit.test.ts +219 -0
  407. package/src/components/__tests__/points-lines-schema.unit.test.ts +56 -0
  408. package/src/components/__tests__/schema-contract.unit.test.ts +10 -0
  409. package/src/components/glyph-text.ts +3 -2
  410. package/src/components/index.ts +14 -9
  411. package/src/components/instances.ts +4 -5
  412. package/src/components/light-helpers.ts +1 -1
  413. package/src/components/lines.ts +18 -0
  414. package/src/components/mesh-filter.ts +11 -10
  415. package/src/components/points.ts +40 -0
  416. package/src/components/post-process-params.ts +3 -3
  417. package/src/components/scene-instance.ts +12 -12
  418. package/src/components/skybox-background.ts +103 -0
  419. package/src/components/{environment.ts → skylight.ts} +2 -110
  420. package/src/components/sprite-animation.ts +3 -4
  421. package/src/components/sprite-instances.ts +2 -2
  422. package/src/components/sprite-playback-mode.ts +7 -1
  423. package/src/components/sprite-region-override.ts +2 -3
  424. package/src/components/tile-layer.ts +8 -2
  425. package/src/construct-renderer.ts +67 -4
  426. package/src/debug-draw-glue.ts +8 -129
  427. package/src/device/device-scope.ts +306 -0
  428. package/src/{gpu-resource-store.ts → device/gpu-residency.ts} +540 -639
  429. package/src/device/resource-types.ts +20 -0
  430. package/src/engine-metrics.ts +12 -19
  431. package/src/errors/recover.ts +45 -3
  432. package/src/errors/render.ts +469 -429
  433. package/src/features/fullscreen.ts +46 -0
  434. package/src/features/host.ts +575 -309
  435. package/src/features/morph/morph-feature.ts +195 -105
  436. package/src/features/plan.ts +447 -0
  437. package/src/features/prepared-gpu-work.ts +72 -15
  438. package/src/features/prepared-graphics-store.ts +20 -6
  439. package/src/features/prepared-graphics.ts +12 -71
  440. package/src/features/render-graph-compute.ts +14 -5
  441. package/src/features/render-graph-contribution.ts +43 -63
  442. package/src/features/render-graph-raster.ts +32 -8
  443. package/src/features/render-graph-resources.ts +25 -23
  444. package/src/features/targets.ts +8 -21
  445. package/src/features/types.ts +39 -227
  446. package/src/features/vocabulary.ts +24 -0
  447. package/src/fullscreen-post-process-pass.ts +14 -57
  448. package/src/glyph-text-layout-system.ts +70 -96
  449. package/src/gpu-driven/batch-topology.ts +24 -202
  450. package/src/gpu-driven/production-raster.ts +99 -1109
  451. package/src/gpu-driven/view-gpu.ts +46 -192
  452. package/src/gpu-resource.ts +4 -4
  453. package/src/gpu-scene-schema.ts +11 -99
  454. package/src/gpu-scene.ts +231 -364
  455. package/src/hdrp-buffers.ts +12 -221
  456. package/src/ibl/IblPipelineCache.ts +471 -711
  457. package/src/ibl/skylight-bind-group.ts +4 -4
  458. package/src/index.ts +75 -217
  459. package/src/inspection-types.ts +80 -0
  460. package/src/instance-buffer-cache.ts +1 -29
  461. package/src/internal.ts +6 -85
  462. package/src/lifecycle.ts +64 -19
  463. package/src/materials.ts +7 -29
  464. package/src/mesh-material-bindings.ts +0 -1
  465. package/src/pbr-pipeline.ts +441 -53
  466. package/src/{urp-pipeline.ts → pipeline/standard-forward-lane.ts} +38 -149
  467. package/src/{hdrp-pipeline.ts → pipeline/standard-pipeline.ts} +221 -191
  468. package/src/pipeline/standard-profile.ts +65 -0
  469. package/src/pipeline-builder.ts +60 -64
  470. package/src/pipeline-spec-types.ts +28 -0
  471. package/src/pipeline-spec.ts +72 -609
  472. package/src/plugin.ts +7 -75
  473. package/src/points-lines/__tests__/bounds.unit.test.ts +21 -0
  474. package/src/points-lines/__tests__/cache-recovery.unit.test.ts +157 -0
  475. package/src/points-lines/__tests__/inspection-lanes.integration.test.ts +146 -0
  476. package/src/points-lines/__tests__/inspection.unit.test.ts +173 -0
  477. package/src/points-lines/__tests__/lane-structural.integration.test.ts +122 -0
  478. package/src/points-lines/__tests__/points-lines.bench.ts +400 -0
  479. package/src/points-lines/__tests__/raster-lane.integration.test.ts +44 -0
  480. package/src/points-lines/__tests__/snapshot.unit.test.ts +101 -0
  481. package/src/points-lines/__tests__/view-ubo.unit.test.ts +73 -0
  482. package/src/points-lines/admission.ts +238 -0
  483. package/src/points-lines/bounds.ts +23 -0
  484. package/src/points-lines/expansion-cache.ts +215 -0
  485. package/src/points-lines/inspection.ts +97 -0
  486. package/src/points-lines/prepare.ts +177 -0
  487. package/src/points-lines/record.ts +125 -0
  488. package/src/points-lines/snapshot.ts +130 -0
  489. package/src/post-process-errors.ts +31 -33
  490. package/src/prepare/prepared-graphics-resolver.ts +5 -4
  491. package/src/record/__tests__/material-slot-plan.unit.test.ts +1 -33
  492. package/src/record/__tests__/typed-frame-graph-plan.unit.test.ts +41 -0
  493. package/src/record/__tests__/video-texture-view.unit.test.ts +1 -1
  494. package/src/record/frame-lighting.ts +88 -197
  495. package/src/record/frame-snapshot.ts +45 -60
  496. package/src/record/frame-targets.ts +8 -243
  497. package/src/record/frame.ts +521 -797
  498. package/src/record/helpers.ts +6 -11
  499. package/src/record/main-pass-geometry.ts +198 -147
  500. package/src/record/main-pass-material.ts +517 -190
  501. package/src/record/main-pass-sprite-draws.ts +199 -118
  502. package/src/record/main-pass.ts +166 -144
  503. package/src/record/mesh-ssbo.ts +517 -38
  504. package/src/record/render-context.ts +384 -0
  505. package/src/record/shadow-pass.ts +154 -1843
  506. package/src/record/skybox-post-pass.ts +14 -15
  507. package/src/record/typed-frame-graph.ts +224 -262
  508. package/src/record/view-ubo.ts +53 -37
  509. package/src/render-contract.ts +582 -0
  510. package/src/render-data.ts +3 -3
  511. package/src/render-graph-primitives.ts +76 -851
  512. package/src/render-pipeline.ts +104 -188
  513. package/src/render-system-extract.ts +1114 -1732
  514. package/src/render-system.ts +614 -1939
  515. package/src/scene/render-scene-types.ts +67 -0
  516. package/src/scene/render-scene.ts +1087 -0
  517. package/src/scene-instances/post-spawn-resolve-joints.ts +4 -7
  518. package/src/ssao-buffers.ts +167 -8
  519. package/src/systems/skin-palette-allocator.ts +21 -64
  520. package/src/systems/skin-palette-types.ts +14 -0
  521. package/src/systems/transparent-sort-config.ts +25 -3
  522. package/src/tilemap-chunk-extract-system.ts +53 -70
  523. package/src/typed-render-graph-primitives.ts +35 -24
  524. package/src/typed-shadow-passes.ts +26 -56
  525. package/dist/__tests__/analytic-sky-contract.test.d.ts +0 -2
  526. package/dist/__tests__/analytic-sky-contract.test.d.ts.map +0 -1
  527. package/dist/__tests__/analytic-sky-falsify.test.d.ts +0 -2
  528. package/dist/__tests__/analytic-sky-falsify.test.d.ts.map +0 -1
  529. package/dist/__tests__/assets-runtime-handoff.integration.test.d.ts +0 -2
  530. package/dist/__tests__/assets-runtime-handoff.integration.test.d.ts.map +0 -1
  531. package/dist/__tests__/backend-selection.integration.test.d.ts +0 -2
  532. package/dist/__tests__/backend-selection.integration.test.d.ts.map +0 -1
  533. package/dist/__tests__/camera-temporal.test-d.d.ts +0 -2
  534. package/dist/__tests__/camera-temporal.test-d.d.ts.map +0 -1
  535. package/dist/__tests__/camera-temporal.test.d.ts +0 -2
  536. package/dist/__tests__/camera-temporal.test.d.ts.map +0 -1
  537. package/dist/__tests__/directional-shadow-probe-atlas-layout.unit.test.d.ts +0 -2
  538. package/dist/__tests__/directional-shadow-probe-atlas-layout.unit.test.d.ts.map +0 -1
  539. package/dist/__tests__/environment-inspection.test.d.ts +0 -2
  540. package/dist/__tests__/environment-inspection.test.d.ts.map +0 -1
  541. package/dist/__tests__/fog-ai-user.fixture.d.ts +0 -2
  542. package/dist/__tests__/fog-ai-user.fixture.d.ts.map +0 -1
  543. package/dist/__tests__/fog-custom-render-feature.fixture.d.ts +0 -16
  544. package/dist/__tests__/fog-custom-render-feature.fixture.d.ts.map +0 -1
  545. package/dist/__tests__/fog-custom-render-feature.integration.test.d.ts +0 -2
  546. package/dist/__tests__/fog-custom-render-feature.integration.test.d.ts.map +0 -1
  547. package/dist/__tests__/fog-inspection.integration.test.d.ts +0 -2
  548. package/dist/__tests__/fog-inspection.integration.test.d.ts.map +0 -1
  549. package/dist/__tests__/fog-owner-state.integration.test.d.ts +0 -2
  550. package/dist/__tests__/fog-owner-state.integration.test.d.ts.map +0 -1
  551. package/dist/__tests__/fog-pipeline-topology.integration.test.d.ts +0 -2
  552. package/dist/__tests__/fog-pipeline-topology.integration.test.d.ts.map +0 -1
  553. package/dist/__tests__/fog-public-surface.integration.test.d.ts +0 -2
  554. package/dist/__tests__/fog-public-surface.integration.test.d.ts.map +0 -1
  555. package/dist/__tests__/fog-ray-contract.integration.test.d.ts +0 -2
  556. package/dist/__tests__/fog-ray-contract.integration.test.d.ts.map +0 -1
  557. package/dist/__tests__/fog-source-absence.integration.test.d.ts +0 -2
  558. package/dist/__tests__/fog-source-absence.integration.test.d.ts.map +0 -1
  559. package/dist/__tests__/fog-temporal.integration.test.d.ts +0 -2
  560. package/dist/__tests__/fog-temporal.integration.test.d.ts.map +0 -1
  561. package/dist/__tests__/frame-chain.unit.test.d.ts +0 -2
  562. package/dist/__tests__/frame-chain.unit.test.d.ts.map +0 -1
  563. package/dist/__tests__/gpu-scene-field-type-owner.test-d.d.ts +0 -2
  564. package/dist/__tests__/gpu-scene-field-type-owner.test-d.d.ts.map +0 -1
  565. package/dist/__tests__/hdrp-pipeline.unit.test.d.ts +0 -2
  566. package/dist/__tests__/hdrp-pipeline.unit.test.d.ts.map +0 -1
  567. package/dist/__tests__/ibl/ibl-fallback.unit.test.d.ts +0 -2
  568. package/dist/__tests__/ibl/ibl-fallback.unit.test.d.ts.map +0 -1
  569. package/dist/__tests__/material-derived-error-stop-draw.integration.test.d.ts +0 -2
  570. package/dist/__tests__/material-derived-error-stop-draw.integration.test.d.ts.map +0 -1
  571. package/dist/__tests__/material-derived-payload.integration.test.d.ts +0 -2
  572. package/dist/__tests__/material-derived-payload.integration.test.d.ts.map +0 -1
  573. package/dist/__tests__/materials.unit.test.d.ts +0 -2
  574. package/dist/__tests__/materials.unit.test.d.ts.map +0 -1
  575. package/dist/__tests__/param-schema-projection-census.unit.test.d.ts +0 -2
  576. package/dist/__tests__/param-schema-projection-census.unit.test.d.ts.map +0 -1
  577. package/dist/__tests__/persistent-render-scene.unit.test.d.ts +0 -2
  578. package/dist/__tests__/persistent-render-scene.unit.test.d.ts.map +0 -1
  579. package/dist/__tests__/pipeline-error-policy-owner.unit.test.d.ts +0 -2
  580. package/dist/__tests__/pipeline-error-policy-owner.unit.test.d.ts.map +0 -1
  581. package/dist/__tests__/pipeline-pass-trace.test.d.ts +0 -2
  582. package/dist/__tests__/pipeline-pass-trace.test.d.ts.map +0 -1
  583. package/dist/__tests__/pipeline-stage-owner.test-d.d.ts +0 -2
  584. package/dist/__tests__/pipeline-stage-owner.test-d.d.ts.map +0 -1
  585. package/dist/__tests__/plugin.test.d.ts +0 -2
  586. package/dist/__tests__/plugin.test.d.ts.map +0 -1
  587. package/dist/__tests__/prepared-graphics-backend-matrix.unit.test.d.ts +0 -2
  588. package/dist/__tests__/prepared-graphics-backend-matrix.unit.test.d.ts.map +0 -1
  589. package/dist/__tests__/prepared-graphics-resolver-recording.unit.test.d.ts +0 -2
  590. package/dist/__tests__/prepared-graphics-resolver-recording.unit.test.d.ts.map +0 -1
  591. package/dist/__tests__/public-pipeline-id.test-d.d.ts +0 -2
  592. package/dist/__tests__/public-pipeline-id.test-d.d.ts.map +0 -1
  593. package/dist/__tests__/render-dispatch-membership.unit.test.d.ts +0 -2
  594. package/dist/__tests__/render-dispatch-membership.unit.test.d.ts.map +0 -1
  595. package/dist/__tests__/render-feature-construction.unit.test.d.ts +0 -2
  596. package/dist/__tests__/render-feature-construction.unit.test.d.ts.map +0 -1
  597. package/dist/__tests__/render-feature-error-code-owner.test-d.d.ts +0 -2
  598. package/dist/__tests__/render-feature-error-code-owner.test-d.d.ts.map +0 -1
  599. package/dist/__tests__/render-feature-gpu-buffer-usage-owner.test-d.d.ts +0 -2
  600. package/dist/__tests__/render-feature-gpu-buffer-usage-owner.test-d.d.ts.map +0 -1
  601. package/dist/__tests__/render-feature-graph.integration.test.d.ts +0 -2
  602. package/dist/__tests__/render-feature-graph.integration.test.d.ts.map +0 -1
  603. package/dist/__tests__/render-feature-graphics-staging.unit.test.d.ts +0 -2
  604. package/dist/__tests__/render-feature-graphics-staging.unit.test.d.ts.map +0 -1
  605. package/dist/__tests__/render-feature-order.unit.test.d.ts +0 -2
  606. package/dist/__tests__/render-feature-order.unit.test.d.ts.map +0 -1
  607. package/dist/__tests__/render-feature-prepared-graphics-recording.unit.test.d.ts +0 -2
  608. package/dist/__tests__/render-feature-prepared-graphics-recording.unit.test.d.ts.map +0 -1
  609. package/dist/__tests__/render-feature-staging.unit.test.d.ts +0 -2
  610. package/dist/__tests__/render-feature-staging.unit.test.d.ts.map +0 -1
  611. package/dist/__tests__/renderer-render-scene-public.test-d.d.ts +0 -2
  612. package/dist/__tests__/renderer-render-scene-public.test-d.d.ts.map +0 -1
  613. package/dist/__tests__/scene-temporal-coverage.unit.test.d.ts +0 -2
  614. package/dist/__tests__/scene-temporal-coverage.unit.test.d.ts.map +0 -1
  615. package/dist/__tests__/sun-snapshot-consumers.test.d.ts +0 -2
  616. package/dist/__tests__/sun-snapshot-consumers.test.d.ts.map +0 -1
  617. package/dist/__tests__/surface-backend-kind-owner.test-d.d.ts +0 -2
  618. package/dist/__tests__/surface-backend-kind-owner.test-d.d.ts.map +0 -1
  619. package/dist/__tests__/taa-history-store.unit.test.d.ts +0 -2
  620. package/dist/__tests__/taa-history-store.unit.test.d.ts.map +0 -1
  621. package/dist/__tests__/temporal-frame-transaction.integration.test.d.ts +0 -2
  622. package/dist/__tests__/temporal-frame-transaction.integration.test.d.ts.map +0 -1
  623. package/dist/__tests__/temporal-inspection.unit.test.d.ts +0 -2
  624. package/dist/__tests__/temporal-inspection.unit.test.d.ts.map +0 -1
  625. package/dist/assets/__tests__/asset-decoders.unit.test.d.ts +0 -2
  626. package/dist/assets/__tests__/asset-decoders.unit.test.d.ts.map +0 -1
  627. package/dist/assets/__tests__/mipmap-generator.unit.test.d.ts +0 -2
  628. package/dist/assets/__tests__/mipmap-generator.unit.test.d.ts.map +0 -1
  629. package/dist/assets/__tests__/render-asset-port.unit.test.d.ts +0 -2
  630. package/dist/assets/__tests__/render-asset-port.unit.test.d.ts.map +0 -1
  631. package/dist/assets/__tests__/resolve-render-asset.unit.test.d.ts +0 -2
  632. package/dist/assets/__tests__/resolve-render-asset.unit.test.d.ts.map +0 -1
  633. package/dist/chunk-DJIMFYX6.mjs +0 -1439
  634. package/dist/chunk-DJIMFYX6.mjs.map +0 -1
  635. package/dist/chunk-F2SIOCRM.mjs +0 -7423
  636. package/dist/chunk-F2SIOCRM.mjs.map +0 -1
  637. package/dist/chunk-JMSB7AR4.mjs +0 -3
  638. package/dist/chunk-JMSB7AR4.mjs.map +0 -1
  639. package/dist/chunk-KHI6K4C6.mjs +0 -659
  640. package/dist/chunk-KHI6K4C6.mjs.map +0 -1
  641. package/dist/chunk-PORZOL2V.mjs +0 -569
  642. package/dist/chunk-PORZOL2V.mjs.map +0 -1
  643. package/dist/chunk-UOJGMDGP.mjs +0 -29913
  644. package/dist/chunk-UOJGMDGP.mjs.map +0 -1
  645. package/dist/components/__tests__/atmosphere-red.test.d.ts +0 -2
  646. package/dist/components/__tests__/atmosphere-red.test.d.ts.map +0 -1
  647. package/dist/components/__tests__/atmosphere.test.d.ts +0 -2
  648. package/dist/components/__tests__/atmosphere.test.d.ts.map +0 -1
  649. package/dist/components/__tests__/fog.test-d.d.ts +0 -2
  650. package/dist/components/__tests__/fog.test-d.d.ts.map +0 -1
  651. package/dist/components/__tests__/fog.test.d.ts +0 -2
  652. package/dist/components/__tests__/fog.test.d.ts.map +0 -1
  653. package/dist/components/atmosphere.d.ts +0 -18
  654. package/dist/components/atmosphere.d.ts.map +0 -1
  655. package/dist/components/environment.d.ts +0 -115
  656. package/dist/components/environment.d.ts.map +0 -1
  657. package/dist/deferred-lighting.d.ts +0 -13
  658. package/dist/deferred-lighting.d.ts.map +0 -1
  659. package/dist/dynamic-texture-store.d.ts +0 -42
  660. package/dist/dynamic-texture-store.d.ts.map +0 -1
  661. package/dist/environment/__tests__/failure-injection.test.d.ts +0 -2
  662. package/dist/environment/__tests__/failure-injection.test.d.ts.map +0 -1
  663. package/dist/environment/__tests__/generation-red.test.d.ts +0 -2
  664. package/dist/environment/__tests__/generation-red.test.d.ts.map +0 -1
  665. package/dist/environment/__tests__/generation.test.d.ts +0 -2
  666. package/dist/environment/__tests__/generation.test.d.ts.map +0 -1
  667. package/dist/environment/__tests__/graph-transaction.test.d.ts +0 -2
  668. package/dist/environment/__tests__/graph-transaction.test.d.ts.map +0 -1
  669. package/dist/environment/__tests__/inspection-red.test.d.ts +0 -2
  670. package/dist/environment/__tests__/inspection-red.test.d.ts.map +0 -1
  671. package/dist/environment/__tests__/lifecycle-evidence.test.d.ts +0 -2
  672. package/dist/environment/__tests__/lifecycle-evidence.test.d.ts.map +0 -1
  673. package/dist/environment/__tests__/pipeline-topology.test.d.ts +0 -2
  674. package/dist/environment/__tests__/pipeline-topology.test.d.ts.map +0 -1
  675. package/dist/environment/__tests__/readiness.test.d.ts +0 -2
  676. package/dist/environment/__tests__/readiness.test.d.ts.map +0 -1
  677. package/dist/environment/__tests__/recovery.test.d.ts +0 -2
  678. package/dist/environment/__tests__/recovery.test.d.ts.map +0 -1
  679. package/dist/environment/__tests__/resize-dispose.test.d.ts +0 -2
  680. package/dist/environment/__tests__/resize-dispose.test.d.ts.map +0 -1
  681. package/dist/environment/__tests__/signature-red.test.d.ts +0 -2
  682. package/dist/environment/__tests__/signature-red.test.d.ts.map +0 -1
  683. package/dist/environment/__tests__/signature.test.d.ts +0 -2
  684. package/dist/environment/__tests__/signature.test.d.ts.map +0 -1
  685. package/dist/environment/descriptors.d.ts +0 -2
  686. package/dist/environment/descriptors.d.ts.map +0 -1
  687. package/dist/environment/extracted-environment.d.ts +0 -53
  688. package/dist/environment/extracted-environment.d.ts.map +0 -1
  689. package/dist/environment/generation.d.ts +0 -62
  690. package/dist/environment/generation.d.ts.map +0 -1
  691. package/dist/environment/gpu-bundle.d.ts +0 -23
  692. package/dist/environment/gpu-bundle.d.ts.map +0 -1
  693. package/dist/environment/graph.d.ts +0 -108
  694. package/dist/environment/graph.d.ts.map +0 -1
  695. package/dist/environment/inspection.d.ts +0 -48
  696. package/dist/environment/inspection.d.ts.map +0 -1
  697. package/dist/environment/lifecycle.d.ts +0 -22
  698. package/dist/environment/lifecycle.d.ts.map +0 -1
  699. package/dist/environment/readiness.d.ts +0 -29
  700. package/dist/environment/readiness.d.ts.map +0 -1
  701. package/dist/environment/signature.d.ts +0 -70
  702. package/dist/environment/signature.d.ts.map +0 -1
  703. package/dist/extract/__tests__/directional-csm-domain.unit.test.d.ts +0 -2
  704. package/dist/extract/__tests__/directional-csm-domain.unit.test.d.ts.map +0 -1
  705. package/dist/extract/__tests__/environment-source.test.d.ts +0 -2
  706. package/dist/extract/__tests__/environment-source.test.d.ts.map +0 -1
  707. package/dist/extract/__tests__/schema-readers.unit.test.d.ts +0 -2
  708. package/dist/extract/__tests__/schema-readers.unit.test.d.ts.map +0 -1
  709. package/dist/extract/directional-csm.d.ts +0 -129
  710. package/dist/extract/directional-csm.d.ts.map +0 -1
  711. package/dist/extract/index.d.ts +0 -5
  712. package/dist/extract/index.d.ts.map +0 -1
  713. package/dist/extract/material-interface.d.ts +0 -56
  714. package/dist/extract/material-interface.d.ts.map +0 -1
  715. package/dist/extract/schema-readers.d.ts +0 -20
  716. package/dist/extract/schema-readers.d.ts.map +0 -1
  717. package/dist/features/graph-contribution.d.ts +0 -64
  718. package/dist/features/graph-contribution.d.ts.map +0 -1
  719. package/dist/features/index.d.ts +0 -21
  720. package/dist/features/index.d.ts.map +0 -1
  721. package/dist/features/morph/index.d.ts +0 -3
  722. package/dist/features/morph/index.d.ts.map +0 -1
  723. package/dist/features/morph/morph-bounds.d.ts +0 -42
  724. package/dist/features/morph/morph-bounds.d.ts.map +0 -1
  725. package/dist/features/scene-temporal.d.ts +0 -45
  726. package/dist/features/scene-temporal.d.ts.map +0 -1
  727. package/dist/features/taa-resolve.d.ts +0 -21
  728. package/dist/features/taa-resolve.d.ts.map +0 -1
  729. package/dist/frame-chain.d.ts +0 -14
  730. package/dist/frame-chain.d.ts.map +0 -1
  731. package/dist/gpu-driven/view-reference.d.ts.map +0 -1
  732. package/dist/gpu-resource-store.d.ts.map +0 -1
  733. package/dist/hdrp-cluster-membership.d.ts +0 -2
  734. package/dist/hdrp-cluster-membership.d.ts.map +0 -1
  735. package/dist/hdrp-pipeline.d.ts +0 -36
  736. package/dist/hdrp-pipeline.d.ts.map +0 -1
  737. package/dist/ibl/cubemap-projection.d.ts +0 -23
  738. package/dist/ibl/cubemap-projection.d.ts.map +0 -1
  739. package/dist/ibl/face-uniforms.d.ts +0 -65
  740. package/dist/ibl/face-uniforms.d.ts.map +0 -1
  741. package/dist/mesh-update-errors.d.ts +0 -71
  742. package/dist/mesh-update-errors.d.ts.map +0 -1
  743. package/dist/mipmap-generator.d.ts +0 -39
  744. package/dist/mipmap-generator.d.ts.map +0 -1
  745. package/dist/param-schema-projection-census.d.ts +0 -18
  746. package/dist/param-schema-projection-census.d.ts.map +0 -1
  747. package/dist/persistent-render-scene.d.ts +0 -158
  748. package/dist/persistent-render-scene.d.ts.map +0 -1
  749. package/dist/pipeline/index.d.ts +0 -11
  750. package/dist/pipeline/index.d.ts.map +0 -1
  751. package/dist/pipeline-errors.d.ts +0 -110
  752. package/dist/pipeline-errors.d.ts.map +0 -1
  753. package/dist/prepare/__tests__/schema-readers.unit.test.d.ts +0 -2
  754. package/dist/prepare/__tests__/schema-readers.unit.test.d.ts.map +0 -1
  755. package/dist/prepare/index.d.ts +0 -4
  756. package/dist/prepare/index.d.ts.map +0 -1
  757. package/dist/prepare/schema-readers.d.ts +0 -11
  758. package/dist/prepare/schema-readers.d.ts.map +0 -1
  759. package/dist/record/__tests__/environment-record.test.d.ts +0 -2
  760. package/dist/record/__tests__/environment-record.test.d.ts.map +0 -1
  761. package/dist/record/__tests__/frame-targets.unit.test.d.ts +0 -2
  762. package/dist/record/__tests__/frame-targets.unit.test.d.ts.map +0 -1
  763. package/dist/record/__tests__/membership-timing.unit.test.d.ts +0 -2
  764. package/dist/record/__tests__/membership-timing.unit.test.d.ts.map +0 -1
  765. package/dist/record/__tests__/render-submission.integration.test.d.ts +0 -2
  766. package/dist/record/__tests__/render-submission.integration.test.d.ts.map +0 -1
  767. package/dist/record/__tests__/render-submission.perf.test.d.ts +0 -2
  768. package/dist/record/__tests__/render-submission.perf.test.d.ts.map +0 -1
  769. package/dist/record/__tests__/shadow-submit-mode.unit.test.d.ts +0 -2
  770. package/dist/record/__tests__/shadow-submit-mode.unit.test.d.ts.map +0 -1
  771. package/dist/record/__tests__/view-ubo-layout.unit.test.d.ts +0 -2
  772. package/dist/record/__tests__/view-ubo-layout.unit.test.d.ts.map +0 -1
  773. package/dist/record/frame-observation.d.ts +0 -38
  774. package/dist/record/frame-observation.d.ts.map +0 -1
  775. package/dist/record/index.d.ts +0 -15
  776. package/dist/record/index.d.ts.map +0 -1
  777. package/dist/record/main-pass-skin.d.ts +0 -46
  778. package/dist/record/main-pass-skin.d.ts.map +0 -1
  779. package/dist/record/main-pass-sprite.d.ts +0 -69
  780. package/dist/record/main-pass-sprite.d.ts.map +0 -1
  781. package/dist/record/material-payload.d.ts +0 -43
  782. package/dist/record/material-payload.d.ts.map +0 -1
  783. package/dist/record/material-slot-plan.d.ts +0 -26
  784. package/dist/record/material-slot-plan.d.ts.map +0 -1
  785. package/dist/record/membership-timing.d.ts +0 -232
  786. package/dist/record/membership-timing.d.ts.map +0 -1
  787. package/dist/record/shadow-caster-uniform.d.ts +0 -8
  788. package/dist/record/shadow-caster-uniform.d.ts.map +0 -1
  789. package/dist/record/shadow-submit-mode.d.ts +0 -66
  790. package/dist/record/shadow-submit-mode.d.ts.map +0 -1
  791. package/dist/render-dispatch-membership.d.ts +0 -47
  792. package/dist/render-dispatch-membership.d.ts.map +0 -1
  793. package/dist/render-pipeline-context.d.ts +0 -289
  794. package/dist/render-pipeline-context.d.ts.map +0 -1
  795. package/dist/render-pipeline-target.d.ts +0 -16
  796. package/dist/render-pipeline-target.d.ts.map +0 -1
  797. package/dist/render-scene-projection.d.ts +0 -174
  798. package/dist/render-scene-projection.d.ts.map +0 -1
  799. package/dist/render-system-fold.d.ts +0 -266
  800. package/dist/render-system-fold.d.ts.map +0 -1
  801. package/dist/renderer/asset-service.d.ts +0 -10
  802. package/dist/renderer/asset-service.d.ts.map +0 -1
  803. package/dist/renderer/backend-selection.d.ts.map +0 -1
  804. package/dist/renderer/environment-classify.d.ts +0 -6
  805. package/dist/renderer/environment-classify.d.ts.map +0 -1
  806. package/dist/renderer/environment-error.d.ts +0 -13
  807. package/dist/renderer/environment-error.d.ts.map +0 -1
  808. package/dist/renderer/material/pipeline-projection.d.ts +0 -91
  809. package/dist/renderer/material/pipeline-projection.d.ts.map +0 -1
  810. package/dist/renderer/renderer-factory.d.ts +0 -289
  811. package/dist/renderer/renderer-factory.d.ts.map +0 -1
  812. package/dist/renderer.d.ts +0 -771
  813. package/dist/renderer.d.ts.map +0 -1
  814. package/dist/scene-utils/collect-subtree.d.ts +0 -11
  815. package/dist/scene-utils/collect-subtree.d.ts.map +0 -1
  816. package/dist/ssao-data.d.ts +0 -34
  817. package/dist/ssao-data.d.ts.map +0 -1
  818. package/dist/surface-format.d.ts +0 -10
  819. package/dist/surface-format.d.ts.map +0 -1
  820. package/dist/taa-history-store.d.ts +0 -88
  821. package/dist/taa-history-store.d.ts.map +0 -1
  822. package/dist/temporal-coverage.d.ts +0 -35
  823. package/dist/temporal-coverage.d.ts.map +0 -1
  824. package/dist/temporal-frame-transaction.d.ts +0 -92
  825. package/dist/temporal-frame-transaction.d.ts.map +0 -1
  826. package/dist/temporal-inspection.d.ts +0 -57
  827. package/dist/temporal-inspection.d.ts.map +0 -1
  828. package/dist/temporal-view.d.ts +0 -43
  829. package/dist/temporal-view.d.ts.map +0 -1
  830. package/dist/tilemap/resolve-tileset-runtime.d.ts +0 -23
  831. package/dist/tilemap/resolve-tileset-runtime.d.ts.map +0 -1
  832. package/dist/urp-pipeline.d.ts +0 -4
  833. package/dist/urp-pipeline.d.ts.map +0 -1
  834. package/src/__tests__/analytic-sky-contract.test.ts +0 -27
  835. package/src/__tests__/analytic-sky-falsify-carriers.mjs +0 -43
  836. package/src/__tests__/analytic-sky-falsify.test.ts +0 -94
  837. package/src/__tests__/assets-runtime-handoff.integration.test.ts +0 -36
  838. package/src/__tests__/backend-selection.integration.test.ts +0 -11
  839. package/src/__tests__/camera-temporal.test-d.ts +0 -8
  840. package/src/__tests__/camera-temporal.test.ts +0 -188
  841. package/src/__tests__/directional-shadow-probe-atlas-layout.unit.test.ts +0 -18
  842. package/src/__tests__/environment-inspection.test.ts +0 -45
  843. package/src/__tests__/fog-ai-user.fixture.ts +0 -350
  844. package/src/__tests__/fog-custom-render-feature.fixture.ts +0 -33
  845. package/src/__tests__/fog-custom-render-feature.integration.test.ts +0 -76
  846. package/src/__tests__/fog-inspection.integration.test.ts +0 -127
  847. package/src/__tests__/fog-owner-state.integration.test.ts +0 -112
  848. package/src/__tests__/fog-pipeline-topology.integration.test.ts +0 -93
  849. package/src/__tests__/fog-public-surface.integration.test.ts +0 -60
  850. package/src/__tests__/fog-ray-contract.integration.test.ts +0 -73
  851. package/src/__tests__/fog-source-absence.integration.test.ts +0 -51
  852. package/src/__tests__/fog-temporal.integration.test.ts +0 -115
  853. package/src/__tests__/frame-chain.unit.test.ts +0 -26
  854. package/src/__tests__/gpu-scene-field-type-owner.test-d.ts +0 -27
  855. package/src/__tests__/hdrp-pipeline.unit.test.ts +0 -100
  856. package/src/__tests__/ibl/ibl-fallback.unit.test.ts +0 -23
  857. package/src/__tests__/material-derived-error-stop-draw.integration.test.ts +0 -69
  858. package/src/__tests__/material-derived-payload.integration.test.ts +0 -164
  859. package/src/__tests__/materials.unit.test.ts +0 -25
  860. package/src/__tests__/param-schema-projection-census.unit.test.ts +0 -40
  861. package/src/__tests__/persistent-render-scene.unit.test.ts +0 -1311
  862. package/src/__tests__/pipeline-error-policy-owner.unit.test.ts +0 -167
  863. package/src/__tests__/pipeline-pass-trace.test.ts +0 -92
  864. package/src/__tests__/pipeline-stage-owner.test-d.ts +0 -13
  865. package/src/__tests__/plugin.test.ts +0 -25
  866. package/src/__tests__/prepared-graphics-backend-matrix.unit.test.ts +0 -129
  867. package/src/__tests__/prepared-graphics-resolver-recording.unit.test.ts +0 -180
  868. package/src/__tests__/public-pipeline-id.test-d.ts +0 -6
  869. package/src/__tests__/render-dispatch-membership.unit.test.ts +0 -117
  870. package/src/__tests__/render-feature-construction.unit.test.ts +0 -124
  871. package/src/__tests__/render-feature-error-code-owner.test-d.ts +0 -27
  872. package/src/__tests__/render-feature-gpu-buffer-usage-owner.test-d.ts +0 -46
  873. package/src/__tests__/render-feature-graph.integration.test.ts +0 -205
  874. package/src/__tests__/render-feature-graphics-staging.unit.test.ts +0 -200
  875. package/src/__tests__/render-feature-order.unit.test.ts +0 -66
  876. package/src/__tests__/render-feature-prepared-graphics-recording.unit.test.ts +0 -107
  877. package/src/__tests__/render-feature-staging.unit.test.ts +0 -94
  878. package/src/__tests__/renderer-render-scene-public.test-d.ts +0 -27
  879. package/src/__tests__/scene-temporal-coverage.unit.test.ts +0 -259
  880. package/src/__tests__/sun-snapshot-consumers.test.ts +0 -35
  881. package/src/__tests__/surface-backend-kind-owner.test-d.ts +0 -42
  882. package/src/__tests__/taa-history-store.unit.test.ts +0 -196
  883. package/src/__tests__/temporal-frame-transaction.integration.test.ts +0 -537
  884. package/src/__tests__/temporal-inspection.unit.test.ts +0 -138
  885. package/src/assets/__tests__/asset-decoders.unit.test.ts +0 -92
  886. package/src/assets/__tests__/mipmap-generator.unit.test.ts +0 -52
  887. package/src/assets/__tests__/render-asset-port.unit.test.ts +0 -54
  888. package/src/assets/__tests__/resolve-render-asset.unit.test.ts +0 -47
  889. package/src/components/__tests__/atmosphere-red.test.ts +0 -51
  890. package/src/components/__tests__/atmosphere.test.ts +0 -79
  891. package/src/components/__tests__/fog.test-d.ts +0 -49
  892. package/src/components/__tests__/fog.test.ts +0 -60
  893. package/src/components/atmosphere.ts +0 -64
  894. package/src/deferred-lighting.ts +0 -92
  895. package/src/dynamic-texture-store.ts +0 -111
  896. package/src/environment/README.md +0 -76
  897. package/src/environment/__tests__/failure-injection.test.ts +0 -50
  898. package/src/environment/__tests__/generation-red.test.ts +0 -64
  899. package/src/environment/__tests__/generation.test.ts +0 -37
  900. package/src/environment/__tests__/graph-transaction.test.ts +0 -85
  901. package/src/environment/__tests__/inspection-red.test.ts +0 -53
  902. package/src/environment/__tests__/lifecycle-evidence.test.ts +0 -40
  903. package/src/environment/__tests__/pipeline-topology.test.ts +0 -33
  904. package/src/environment/__tests__/readiness.test.ts +0 -42
  905. package/src/environment/__tests__/recovery.test.ts +0 -62
  906. package/src/environment/__tests__/resize-dispose.test.ts +0 -30
  907. package/src/environment/__tests__/signature-red.test.ts +0 -45
  908. package/src/environment/__tests__/signature.test.ts +0 -60
  909. package/src/environment/descriptors.ts +0 -7
  910. package/src/environment/extracted-environment.ts +0 -239
  911. package/src/environment/generation.ts +0 -158
  912. package/src/environment/gpu-bundle.ts +0 -144
  913. package/src/environment/graph.ts +0 -588
  914. package/src/environment/inspection.ts +0 -107
  915. package/src/environment/lifecycle.ts +0 -124
  916. package/src/environment/readiness.ts +0 -76
  917. package/src/environment/signature.ts +0 -185
  918. package/src/extract/__tests__/directional-csm-domain.unit.test.ts +0 -391
  919. package/src/extract/__tests__/environment-source.test.ts +0 -105
  920. package/src/extract/__tests__/schema-readers.unit.test.ts +0 -16
  921. package/src/extract/directional-csm.ts +0 -959
  922. package/src/extract/index.ts +0 -10
  923. package/src/extract/material-interface.ts +0 -604
  924. package/src/extract/schema-readers.ts +0 -27
  925. package/src/features/graph-contribution.ts +0 -577
  926. package/src/features/index.ts +0 -100
  927. package/src/features/morph/index.ts +0 -24
  928. package/src/features/morph/morph-bounds.ts +0 -123
  929. package/src/features/scene-temporal.ts +0 -274
  930. package/src/features/taa-resolve.ts +0 -244
  931. package/src/frame-chain.ts +0 -21
  932. package/src/hdrp-cluster-membership.ts +0 -70
  933. package/src/ibl/cubemap-projection.ts +0 -60
  934. package/src/ibl/face-uniforms.ts +0 -150
  935. package/src/mesh-update-errors.ts +0 -113
  936. package/src/mipmap-generator.ts +0 -244
  937. package/src/param-schema-projection-census.ts +0 -73
  938. package/src/persistent-render-scene.ts +0 -1752
  939. package/src/pipeline/index.ts +0 -40
  940. package/src/pipeline-errors.ts +0 -172
  941. package/src/prepare/__tests__/schema-readers.unit.test.ts +0 -16
  942. package/src/prepare/index.ts +0 -8
  943. package/src/prepare/schema-readers.ts +0 -20
  944. package/src/record/__tests__/environment-record.test.ts +0 -35
  945. package/src/record/__tests__/frame-targets.unit.test.ts +0 -54
  946. package/src/record/__tests__/membership-timing.unit.test.ts +0 -524
  947. package/src/record/__tests__/render-submission.integration.test.ts +0 -76
  948. package/src/record/__tests__/render-submission.perf.test.ts +0 -141
  949. package/src/record/__tests__/shadow-submit-mode.unit.test.ts +0 -88
  950. package/src/record/__tests__/view-ubo-layout.unit.test.ts +0 -101
  951. package/src/record/frame-observation.ts +0 -122
  952. package/src/record/index.ts +0 -15
  953. package/src/record/main-pass-skin.ts +0 -51
  954. package/src/record/main-pass-sprite.ts +0 -166
  955. package/src/record/material-payload.ts +0 -292
  956. package/src/record/material-slot-plan.ts +0 -68
  957. package/src/record/membership-timing.ts +0 -1012
  958. package/src/record/shadow-caster-uniform.ts +0 -37
  959. package/src/record/shadow-submit-mode.ts +0 -277
  960. package/src/render-dispatch-membership.ts +0 -155
  961. package/src/render-pipeline-context.ts +0 -332
  962. package/src/render-pipeline-target.ts +0 -96
  963. package/src/render-scene-projection.ts +0 -1738
  964. package/src/render-system-fold.ts +0 -531
  965. package/src/renderer/asset-service.ts +0 -32
  966. package/src/renderer/backend-selection.ts +0 -121
  967. package/src/renderer/environment-classify.ts +0 -30
  968. package/src/renderer/environment-error.ts +0 -19
  969. package/src/renderer/material/pipeline-projection.ts +0 -140
  970. package/src/renderer.ts +0 -940
  971. package/src/scene-utils/collect-subtree.ts +0 -40
  972. package/src/ssao-data.ts +0 -106
  973. package/src/surface-format.ts +0 -37
  974. package/src/taa-history-store.ts +0 -429
  975. package/src/temporal-coverage.ts +0 -106
  976. package/src/temporal-frame-transaction.ts +0 -301
  977. package/src/temporal-inspection.ts +0 -194
  978. package/src/temporal-view.ts +0 -174
  979. 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';
114
- import { writeViewUbo } from './view-ubo';
115
-
116
- export type RecordProfileRunner = <T>(phase: RenderRecordPhase, action: () => T) => T;
117
-
118
- const EMPTY_RASTER_ITEM_KEYS: ReadonlySet<RasterItemKey> = new Set<RasterItemKey>();
203
+ import { writeShadowCasterUniforms } from './shadow-pass';
204
+ import {
205
+ ensureCompiledFrameGraph,
206
+ executeCompiledFrameGraph,
207
+ type RenderFeatureGraphCandidate,
208
+ } from './typed-frame-graph';
209
+ import { writePointsLinesViewUbo, writeViewUbo } from './view-ubo';
119
210
 
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,10 @@ 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,
323
+ pointsLinesOwner?: import('./render-context').PointsLinesRecordOwner,
316
324
  ): 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
325
  // The `try / finally` wrapper advances `frameState.frameNumber` exactly
323
326
  // once per `recordFrame` invocation regardless of which early-return
324
327
  // branch is taken (camera missing, cap exceeded, pipeline pending,
@@ -338,9 +341,6 @@ export function recordFrame(
338
341
  // (no Camera = no scene), so geometry submission is a no-op and only
339
342
  // the swap-chain clear lands.
340
343
  let activeCameras = cameras;
341
- let temporalAttempt: TemporalFrameAttempt | undefined;
342
- let environmentAttempt: EnvironmentTransactionAttempt | undefined;
343
- let frameSubmitted = false;
344
344
  if (activeCameras.length === 0) {
345
345
  internals.errorRegistry.fire(
346
346
  new RhiError({
@@ -369,11 +369,6 @@ export function recordFrame(
369
369
  frameState.currentFrameObservationSource = undefined;
370
370
  frameState.currentDirectionalShadowView = null;
371
371
  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
372
 
378
373
  // Record-stage fold operator linear scan (groups transparent-sort entries
379
374
  // into fold buckets + records the fold-eligible count metric). Extracted to
@@ -401,7 +396,7 @@ export function recordFrame(
401
396
  // no Skylight (skylight === undefined)
402
397
  // AND 0 direct light (totalLightCount === 0)
403
398
  // AND StandardMaterial (renderables.some materialShaderId !== 'forgeax::default-unlit')
404
- // All three true -> black + warn. Any one false -> no warn.
399
+ // All three true -> black + warn. A single false -> no warn.
405
400
  //
406
401
  // Multi-Skylight warn (F-4 nit + feat-20260630 M3 / w19): >1 Skylight
407
402
  // entity -> warn ONCE per RenderSystem lifetime (not per frame), naming the
@@ -454,8 +449,7 @@ export function recordFrame(
454
449
  spotLights,
455
450
  profilePhase,
456
451
  pipelineState.hdrpClusterMembershipPipeline !== null &&
457
- internals.device.caps?.compute === true &&
458
- internals.membershipTiming?.usesCpuControl() !== true,
452
+ internals.device.caps?.compute === true,
459
453
  pipelineState.hdrpClusterMembershipBindGroupLayout,
460
454
  );
461
455
  });
@@ -467,8 +461,6 @@ export function recordFrame(
467
461
  return { foldBuckets, light };
468
462
  });
469
463
 
470
- void activeCameras; // referenced below
471
-
472
464
  // Case E (this commit): 0 renderables = legitimate scene (LO §1.1
473
465
  // hello-window minimum semantic). Mirrors the Case C softening for
474
466
  // 0 DirectionalLight (line 134 above; D-Q7). recordFrame() falls
@@ -493,40 +485,13 @@ export function recordFrame(
493
485
  const effectiveSpotShadowMapSize = resolveSpotShadowMapSize(internals, lights);
494
486
  const graphShadowMapSize = effectiveShadowMapSize ?? effectiveSpotShadowMapSize;
495
487
 
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
488
  let preparedGpuDriven: PreparedGpuDrivenFrame | undefined;
523
489
  if (gpuDriven !== undefined) {
524
490
  const prepared = gpuDriven.owner.prepare({
525
491
  scene: gpuDriven.scene,
526
492
  camera,
527
- meshes: residentMeshes,
493
+ meshes: pipelineState.meshes,
528
494
  viewBindGroupLayout: pipelineState.viewBindGroupLayout,
529
- materialBindGroupLayout: pipelineState.materialBindGroupLayout,
530
495
  meshResidencyEpoch: internals.gpuStore.meshResidencyEpoch,
531
496
  hdrp: frameState.isHdrpActive,
532
497
  });
@@ -534,22 +499,16 @@ export function recordFrame(
534
499
  else if (prepared.error.code !== 'rhi-not-available')
535
500
  internals.errorRegistry.fire(prepared.error);
536
501
  }
537
- const cpuVisibility = gpuDriven?.owner.inspect().cpuVisibility;
538
- const multiViewSubmissions = gpuDriven?.owner.multiViewSubmissions() ?? [];
539
502
 
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.
503
+ // The GPU lane owns only the primary opaque raster today. Directional,
504
+ // point, and spot shadow views still consume the CPU-validated draw plan.
543
505
  const cpuShadowCastersActive =
544
506
  (effectiveShadowMapSize ?? 0) > 0 ||
545
507
  lights.pointShadow.length > 0 ||
546
508
  lights.spot.some((spot) => spot.castShadow);
547
- const gpuOwnsAllPrimaryRaster = preparedGpuDriven?.ownsAllForwardItems === true;
509
+ const gpuOwnsAllPrimaryRaster = preparedGpuDriven?.ownsAllRenderables === true;
548
510
  gpuDriven?.onCoverage(gpuOwnsAllPrimaryRaster && !cpuShadowCastersActive);
549
511
 
550
- const activeEnvironmentSignature =
551
- frameState.environmentGeneration.candidate?.signature ??
552
- frameState.environmentGeneration.active?.signature;
553
512
  const preflightGraph = runRecordProfilePhase(profilePhase, 'record/render-graph', () =>
554
513
  ensureCompiledFrameGraph(
555
514
  internals,
@@ -561,26 +520,10 @@ export function recordFrame(
561
520
  Math.max(1, internals.canvas.height),
562
521
  graphShadowMapSize,
563
522
  preparedGpuDriven,
564
- temporal?.transaction,
523
+ featureGraphCandidate,
565
524
  ),
566
525
  );
567
526
  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
527
 
585
528
  // Acquire the swap-chain texture + colour view + target dimensions (with
586
529
  // one reconfigure-and-retry on surface-outdated). Extracted to
@@ -611,29 +554,6 @@ export function recordFrame(
611
554
 
612
555
  pipelineState.perPassResources.shadowMapSize = graphShadowMapSize ?? 0;
613
556
  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
557
  const firstDirectionalShadowMatrix = lights.lightViewProj?.[0];
638
558
  pipelineState.perPassResources.shadowLightSpaceMatrix =
639
559
  firstDirectionalShadowMatrix === undefined
@@ -655,7 +575,6 @@ export function recordFrame(
655
575
  : {
656
576
  viewMatrix: new Float32Array(computeViewMatrix(camera)),
657
577
  splitPlanes: new Float32Array(lights.splitPlanes),
658
- cascadeBlend: lights.cascadeBlend ?? 0,
659
578
  };
660
579
  }
661
580
 
@@ -667,9 +586,6 @@ export function recordFrame(
667
586
  camera,
668
587
  skybox,
669
588
  );
670
- const atmosphereActive =
671
- frameState.environmentGeneration.active !== undefined ||
672
- frameState.environmentGeneration.candidate !== undefined;
673
589
 
674
590
  // feat-20260604-learn-render-4.10-anti-aliasing-msaa M2 / w9 (D-6, C-9):
675
591
  // MSAA is a per-Camera switch derived from `camera.antialias`, never
@@ -703,13 +619,27 @@ export function recordFrame(
703
619
  // build a renderableIndex -> renderState map from dispatch entries
704
620
  // so each ValidatedRenderable carries its per-material renderState
705
621
  // 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);
622
+ const gpuOwnedEntityKeys = preparedGpuDriven?.entityKeys ?? EMPTY_ENTITY_KEYS;
623
+ const validated = runRecordProfilePhase(profilePhase, 'record/validation', () =>
624
+ gpuOwnsAllPrimaryRaster && !cpuShadowCastersActive
625
+ ? []
626
+ : validateRenderables(
627
+ internals,
628
+ world,
629
+ worlds,
630
+ pipelineState,
631
+ frameState,
632
+ renderables,
633
+ transparentDispatch,
634
+ cpuShadowCastersActive ? EMPTY_ENTITY_KEYS : gpuOwnedEntityKeys,
635
+ ),
636
+ );
637
+ gpuDriven?.owner.recordCpuValidation(validated, gpuOwnedEntityKeys);
708
638
 
709
639
  // Per-frame cache clean-up: drop per-entity BG cache entries + instance
710
640
  // buffers whose entityKey is absent from the current validated set (despawn
711
641
  // eviction). Extracted to cleanPerFrameCaches (M3/w18).
712
- cleanPerFrameCaches(internals, frameState, validated);
642
+ cleanPerFrameCaches(internals, frameState, validated, gpuOwnedEntityKeys);
713
643
 
714
644
  // Dispatch-ordered render plan: reorder validated renderables to dispatch
715
645
  // order, run the mesh-SSBO capacity gate (graceful truncation), and build
@@ -718,44 +648,6 @@ export function recordFrame(
718
648
  buildDispatchPlan(internals, validated, transparentDispatch, foldBuckets),
719
649
  );
720
650
  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
651
  const foldDispatchPlan = dispatchPlan.foldDispatchPlan;
760
652
  const materialSlotIndices = dispatchPlan.materialSlotIndices;
761
653
  const materialSlots = dispatchPlan.materialSlots;
@@ -794,12 +686,8 @@ export function recordFrame(
794
686
  // (clear-pass-only) path only when neither condition is met.
795
687
  // feat-20260531-skybox-env-background M2 / w6: gate relaxed from
796
688
  // `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) {
689
+ // (plan-strategy D-3, R-3).
690
+ if (validatedOrdered.length > 0 || skyboxActive) {
803
691
  runRecordProfilePhase(profilePhase, 'record/uploads', () => {
804
692
  // View UBO + CSM/spot-shadow matrix pack: assembled + uploaded in one
805
693
  // queue.writeBuffer round-trip. Extracted to view-ubo.ts (M3/w18) so
@@ -811,9 +699,16 @@ export function recordFrame(
811
699
  light,
812
700
  lights,
813
701
  frameState.spotShadowSnapshots,
814
- temporalAttempt?.view,
815
- fog,
816
702
  );
703
+ if (pipelineState.pointsLinesViewBuffer !== undefined) {
704
+ writePointsLinesViewUbo(
705
+ internals.device.queue,
706
+ pipelineState.pointsLinesViewBuffer,
707
+ camera,
708
+ targetW,
709
+ targetH,
710
+ );
711
+ }
817
712
  writeShadowCasterUniforms(
818
713
  internals.device.queue,
819
714
  pipelineState.shadowCasterCascadeBuffer,
@@ -834,12 +729,11 @@ export function recordFrame(
834
729
  pipelineState.meshStorageBuffer,
835
730
  validatedOrdered,
836
731
  foldDispatchPlan,
837
- temporalAttempt?.reactiveRevisionChanged ?? false,
838
732
  );
839
733
  });
840
734
  }
841
735
 
842
- const preparedDirectionalShadowCache = runRecordProfilePhase(
736
+ const directionalShadowCache = runRecordProfilePhase(
843
737
  profilePhase,
844
738
  'record/scene-state/directional-shadow-cache',
845
739
  () =>
@@ -847,21 +741,12 @@ export function recordFrame(
847
741
  internals,
848
742
  frameState,
849
743
  worlds,
744
+ renderReadLeases,
850
745
  lights,
851
746
  effectiveShadowMapSize,
852
747
  validatedOrdered,
853
- cpuVisibility,
854
- gpuDriven?.scene,
855
748
  ),
856
749
  );
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
750
  frameState.directionalShadowCacheRecorded = false;
866
751
 
867
752
  const encoderResult = internals.device.createCommandEncoder({ label: 'render-system-frame' });
@@ -898,29 +783,13 @@ export function recordFrame(
898
783
  // errorRegistry + shader-cache lookups) surfaces (`internals` itself satisfies
899
784
  // `RenderSystemRuntime` so `runtime: internals` is a zero-cost reference). The
900
785
  // 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
786
  const passCtx: _InternalRenderPipelineContext = {
913
787
  assets: internals.assets,
914
788
  world,
915
- gpuDrivenItemKeys: gpuOwnedItemKeys,
916
- gpuDrivenOwnsAllForwardItems: gpuOwnsAllPrimaryRaster,
917
- validatedByCandidateIndex,
918
- validatedIndexByCandidateIndex,
919
- validatedShadowEntries,
789
+ gpuDrivenEntityKeys: gpuOwnedEntityKeys,
920
790
  store: internals.gpuStore,
921
791
  pipelineState,
922
792
  runtime: internals,
923
- temporalFrame: temporalAttempt,
924
793
  encoder,
925
794
  view,
926
795
  clear,
@@ -949,10 +818,7 @@ export function recordFrame(
949
818
  geometryColorResolveView,
950
819
  ldrSpriteColorView,
951
820
  postProcessParams,
952
- dispatch: mainDispatch,
953
- shadowDispatch: transparentDispatch,
954
- directionalCsmPlan: csmPlan,
955
- shadowDebugObservation: internals.shadowDebugObservation,
821
+ dispatch: transparentDispatch,
956
822
  hdrpClusterBindGroup,
957
823
  hdrpClusterMembershipBindGroup,
958
824
  foldDispatchPlan,
@@ -960,14 +826,12 @@ export function recordFrame(
960
826
  materialSlots,
961
827
  materialSlotOwners,
962
828
  materialSlotCount,
829
+ pointsLines: pointsLinesOwner,
963
830
  materialBgAssemblyCache: frameState.materialBgAssemblyCache,
964
831
  directionalShadowCacheReuse: directionalShadowCache.reuse,
965
- directionalShadowCacheTokens: directionalShadowCache.cascadeTokens,
966
- directionalShadowCacheHitMask: directionalShadowCache.hitMask,
967
- multiViewSubmissions,
968
832
  ...(profilePhase !== undefined ? { profilePhase } : {}),
969
833
  };
970
- frameSubmitted = runRecordProfilePhase(profilePhase, 'record/graph-execute', () =>
834
+ const submitted = runRecordProfilePhase(profilePhase, 'record/graph-execute', () =>
971
835
  executeCompiledFrameGraph(
972
836
  internals,
973
837
  frameState,
@@ -976,19 +840,15 @@ export function recordFrame(
976
840
  profilePhase === undefined
977
841
  ? undefined
978
842
  : (pass, encode) => profilePhase(graphExecutionPhase(pass.name), encode),
979
- temporalAttempt,
980
- environmentAttempt,
981
843
  ),
982
844
  );
983
- if (frameSubmitted && !directionalShadowCache.reuse) {
845
+ if (submitted && !directionalShadowCache.reuse) {
984
846
  frameState.directionalShadowCache = frameState.directionalShadowCacheRecorded
985
847
  ? directionalShadowCache.next
986
848
  : null;
987
849
  }
988
- return frameSubmitted;
850
+ return submitted;
989
851
  } finally {
990
- temporalAttempt?.abort();
991
- if (!frameSubmitted) environmentAttempt?.abort('execute');
992
852
  frameState.frameNumber += 1;
993
853
  // feat-20260608-cluster-lighting M5 / w22: clear HDRP once-per-frame fired
994
854
  // set so the next frame re-fires if the condition persists.
@@ -999,294 +859,63 @@ export function recordFrame(
999
859
  interface DirectionalShadowCacheDecision {
1000
860
  readonly reuse: boolean;
1001
861
  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
862
  }
1053
863
 
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;
864
+ function readWorldState(lease: RenderReadLease): DirectionalShadowWorldState {
1119
865
  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
- }),
866
+ worldIdentity: lease.worldIdentity,
867
+ changeCursor: lease.inspectCursor(),
1135
868
  };
1136
869
  }
1137
870
 
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')}`);
871
+ function sameWorldState(
872
+ cached: DirectionalShadowCache,
873
+ worlds: readonly World[],
874
+ leases: readonly RenderReadLease[] | undefined,
875
+ ): boolean {
876
+ if (leases === undefined || leases.length !== worlds.length) return false;
877
+ if (cached.worlds.length !== worlds.length || cached.worldStateTokens.length !== leases.length) {
878
+ return false;
1273
879
  }
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
880
  for (let index = 0; index < worlds.length; index += 1) {
1284
881
  const world = worlds[index];
1285
- if (world === undefined || cached.worlds[index] !== world) return false;
882
+ const lease = leases[index];
883
+ if (world === undefined || lease === undefined || cached.worlds[index] !== world) return false;
1286
884
  const cachedState = cached.worldStateTokens[index];
1287
885
  if (cachedState === undefined) return false;
1288
- const evidence = cachedState.projection.poll();
1289
- if (evidence.status !== 'delta' || evidence.changes.length > 0) return false;
886
+ if (cachedState.worldIdentity !== lease.worldIdentity) return false;
887
+ let changes: ReturnType<RenderReadLease['readChanges']>;
888
+ try {
889
+ changes = lease.readChanges(cachedState.changeCursor);
890
+ } catch {
891
+ // A lease may have evicted an old cursor after another owner consumed
892
+ // its bounded cursor window. Treat that as stale render evidence and
893
+ // rebuild the atlas from the current lease rather than reaching into
894
+ // World for a second mutation clock.
895
+ return false;
896
+ }
897
+ if (changes.status !== 'ok') return false;
898
+ if (changes.world.records.length !== 0 || changes.sharedRefs.records.length !== 0) {
899
+ return false;
900
+ }
901
+ }
902
+ return true;
903
+ }
904
+
905
+ function sameLightViewProj(
906
+ cached: DirectionalShadowCache,
907
+ current: readonly Float32Array[],
908
+ ): boolean {
909
+ if (cached.lightViewProj.length !== current.length) return false;
910
+ for (let matrixIndex = 0; matrixIndex < current.length; matrixIndex += 1) {
911
+ const previous = cached.lightViewProj[matrixIndex];
912
+ const next = current[matrixIndex];
913
+ if (previous === undefined || next === undefined || previous.length !== next.length) {
914
+ return false;
915
+ }
916
+ for (let valueIndex = 0; valueIndex < next.length; valueIndex += 1) {
917
+ if (previous[valueIndex] !== next[valueIndex]) return false;
918
+ }
1290
919
  }
1291
920
  return true;
1292
921
  }
@@ -1295,21 +924,13 @@ function prepareDirectionalShadowCache(
1295
924
  internals: RenderSystemInternals,
1296
925
  frameState: RenderFrameState,
1297
926
  worlds: readonly World[],
927
+ leases: readonly RenderReadLease[] | undefined,
1298
928
  lights: ExtractedLights,
1299
929
  shadowMapSize: number | undefined,
1300
930
  validatedOrdered: readonly ValidatedRenderable[],
1301
- visibility: CpuMultiViewVisibilityResult | undefined,
1302
- scene: PersistentGpuDrivenState | undefined,
1303
931
  ): DirectionalShadowCacheDecision {
1304
- const observeShadowCensus = internals.shadowDebugObservation === true;
1305
932
  const cascadeCount = lights.cascadeCount;
1306
- const built = buildCascadeLocalCacheTokens({
1307
- lights,
1308
- visibility,
1309
- scene,
1310
- validated: validatedOrdered,
1311
- });
1312
- observeDirectionalShadowTokenBuild(internals.metrics, observeShadowCensus, built);
933
+ const lightViewProj = lights.lightViewProj;
1313
934
  if (
1314
935
  frameState.compiledFrameGraph === null ||
1315
936
  frameState.compiledFrameGraphTopologyKey === null ||
@@ -1317,87 +938,49 @@ function prepareDirectionalShadowCache(
1317
938
  shadowMapSize <= 0 ||
1318
939
  cascadeCount === undefined ||
1319
940
  cascadeCount <= 0 ||
1320
- built.tokens.length === 0 ||
941
+ lightViewProj === undefined ||
1321
942
  validatedOrdered.length === 0
1322
943
  ) {
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 };
944
+ return { reuse: false, next: null };
1327
945
  }
1328
946
 
1329
947
  const cached = frameState.directionalShadowCache;
1330
- const assetCatalogEpoch = internals.assets.epoch;
948
+ const assetCatalogEpoch = internals.assets.catalogEpoch;
1331
949
  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;
950
+ if (
951
+ cached !== null &&
952
+ cached.shadowMapSize === shadowMapSize &&
953
+ cached.cascadeCount === cascadeCount &&
954
+ cached.pipelineHandle === frameState.installedPipelineHandle &&
955
+ cached.graphTopologyKey === frameState.compiledFrameGraphTopologyKey &&
956
+ cached.assetCatalogEpoch === assetCatalogEpoch &&
957
+ cached.meshResidencyEpoch === meshResidencyEpoch &&
958
+ sameWorldState(cached, worlds, leases) &&
959
+ sameLightViewProj(cached, lightViewProj)
960
+ ) {
961
+ return { reuse: true, next: null };
1359
962
  }
1360
963
 
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;
964
+ if (leases === undefined || leases.length !== worlds.length) {
965
+ // Direct record-stage test harnesses may omit the host-owned leases. They
966
+ // still exercise the real shadow encoder, but without the ECS read owner
967
+ // they must conservatively rebuild rather than inspect World internals.
968
+ return { reuse: false, next: null };
1384
969
  }
970
+ const worldStateTokens = leases.map(readWorldState);
1385
971
 
1386
972
  return {
1387
973
  reuse: false,
1388
- cascadeTokens: built.tokens,
1389
- hitMask: evaluation.hitMask,
1390
974
  next: {
1391
975
  worlds: worlds.slice(),
1392
- worldStateTokens,
976
+ worldStateTokens: worldStateTokens as DirectionalShadowWorldState[],
1393
977
  assetCatalogEpoch,
1394
978
  pipelineHandle: frameState.installedPipelineHandle,
1395
979
  graphTopologyKey: frameState.compiledFrameGraphTopologyKey,
1396
980
  shadowMapSize,
1397
981
  cascadeCount,
982
+ lightViewProj: lightViewProj.map((matrix) => new Float32Array(matrix)),
1398
983
  meshResidencyEpoch,
1399
- cascadeTokens: built.tokens,
1400
- productionSceneToken: built.productionSceneToken,
1401
984
  },
1402
985
  };
1403
986
  }
@@ -1417,15 +1000,17 @@ function validateRenderables(
1417
1000
  internals: RenderSystemInternals,
1418
1001
  world: World,
1419
1002
  // 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.
1003
+ // worlds[] list. Each renderable's mesh handle is a user-tier slot in its OWN
1004
+ // world's sharedRefs; resolving against a foreign world either misses
1005
+ // (asset-not-registered) or resolves the wrong slot payload. `world` (the
1006
+ // resource-owner) is retained as the fallback for renderables whose worldId
1007
+ // is out of range (defensive; extractFrames always stamps a valid index).
1425
1008
  worlds: readonly World[],
1009
+ pipelineState: PipelineState,
1010
+ frameState: RenderFrameState,
1426
1011
  renderables: readonly RenderableSnapshot[],
1427
1012
  transparentDispatch: readonly DispatchEntry[],
1428
- worldForRenderId?: (worldId: number) => World | undefined,
1013
+ excludedEntityKeys: ReadonlySet<number> = new Set<number>(),
1429
1014
  ): ValidatedRenderable[] {
1430
1015
  // bug-20260527-renderstate-pipeline-dispatch-gap D-4:
1431
1016
  // build a renderableIndex -> renderState map from dispatch entries
@@ -1451,23 +1036,17 @@ function validateRenderables(
1451
1036
  for (let rIdx = 0; rIdx < renderables.length; rIdx++) {
1452
1037
  const r = renderables[rIdx];
1453
1038
  if (r === undefined) continue;
1039
+ if (excludedEntityKeys.has(worldEntityKey(r.worldId, r.entityKey))) continue;
1454
1040
  // 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>(
1041
+ // this renderable's mesh against the world it was EXTRACTED from
1042
+ // (worlds[r.worldId]) — NOT the single resource-owner `world`. Builtin mesh
1043
+ // slots (< BUILTIN_BASE) resolve process-statically regardless of world, so
1044
+ // the per-world pick only matters for user-tier handles, but selecting it
1045
+ // unconditionally keeps a single code path. Falls back to the resource-owner
1046
+ // world if worldId is out of range (defensive; extractFrames always stamps
1047
+ // a valid index into worlds[]).
1048
+ const renderableWorld = worlds[r.worldId] ?? world;
1049
+ const assetRes = resolveAssetHandle<MeshAsset>(
1471
1050
  renderableWorld,
1472
1051
  toShared<'MeshAsset'>(r.assetHandle),
1473
1052
  );
@@ -1475,20 +1054,28 @@ function validateRenderables(
1475
1054
  internals.errorRegistry.fire(
1476
1055
  new RhiError({
1477
1056
  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',
1057
+ expected: 'MeshFilter.assetHandle in AssetRegistry',
1058
+ hint: 'use HANDLE_CUBE / HANDLE_TRIANGLE imports; custom mesh register path: feat-future-asset-system',
1480
1059
  detail: { assetHandle: r.assetHandle },
1481
1060
  }),
1482
1061
  );
1483
1062
  continue;
1484
1063
  }
1064
+ // feat-20260601-device/gpu-residency-extraction M1 (D-1): builtin meshes
1065
+ // (slots through HANDLE_NINESLICE_QUAD) keep the createRenderer step-3
1066
+ // direct-upload + `pipelineState.meshes` path -- they are NOT routed
1067
+ // through `ensureResident`. User-registered meshes pull through the store
1068
+ // on first access (the register->upload push was severed in this M1);
1069
+ // the POD fetched above (assetRes.value) is passed in, store holds no
1070
+ // registry ref (D-2). A first-access miss builds the GPU buffers; later
1071
+ // frames hit the O(1) cache.
1485
1072
  const meshAssetHandle = toShared<'MeshAsset'>(r.assetHandle);
1486
- let meshHandles = internals.gpuStore.getMeshGpuHandles(meshAssetHandle, r.worldId);
1487
- if (meshHandles === undefined) {
1073
+ let meshHandles = internals.gpuStore.getMeshGpuHandles(meshAssetHandle, renderableWorld);
1074
+ if (meshHandles === undefined && r.assetHandle > handleSlot(HANDLE_NINESLICE_QUAD)) {
1488
1075
  const residentRes = internals.gpuStore.ensureResident(
1489
1076
  meshAssetHandle,
1490
1077
  assetRes.value,
1491
- r.worldId,
1078
+ renderableWorld,
1492
1079
  );
1493
1080
  if (residentRes.ok) {
1494
1081
  meshHandles = residentRes.value;
@@ -1496,20 +1083,71 @@ function validateRenderables(
1496
1083
  internals.errorRegistry.fire(residentRes.error);
1497
1084
  }
1498
1085
  }
1086
+ meshHandles = meshHandles ?? pipelineState.meshes.get(r.assetHandle);
1499
1087
  if (meshHandles === undefined) {
1500
1088
  internals.errorRegistry.fire(
1501
1089
  new RhiError({
1502
1090
  code: 'asset-not-registered',
1503
1091
  expected: 'GPU mesh buffers uploaded for assetHandle',
1504
- hint: 'await renderer.ready before draw; keep the MeshAsset live in the renderable World',
1092
+ hint: 'await renderer.initialization before draw([world], { cameraOwner: 0, resourceOwner: 0 }); ensure AssetRegistry.configureGpuDevice ran so user meshes are uploaded',
1505
1093
  detail: { assetHandle: r.assetHandle },
1506
1094
  }),
1507
1095
  );
1508
1096
  continue;
1509
1097
  }
1098
+ // feat-20260527-sprite-nineslice M2 / w11 (plan-strategy section D-2):
1099
+ // sprite branch with non-zero `slicesAndMode` (post-w12 paramSnapshot
1100
+ // entry name) overrides the user-supplied mesh handle (typically
1101
+ // HANDLE_QUAD = 3) with the 16-vertex / 54-index HANDLE_NINESLICE_QUAD
1102
+ // (id=5) topology so the vertex shader sees the 4x4 grid required for
1103
+ // 9-region anchor mapping. Default slicesAndMode ([0, 0, 0, 0]) keeps
1104
+ // the legacy HANDLE_QUAD path; a flip from zero to non-zero on the
1105
+ // same entity routes here per-frame so AI users can toggle 9-slice
1106
+ // on the fly without re-spawning the entity (charter F1 minimum
1107
+ // surface). The HANDLE_NINESLICE_QUAD GPU buffers are seeded by
1108
+ // createRenderer step-3.
1109
+ //
1110
+ // feat-20260625-refactor-sprite-as-transparent-mesh M3 / w13: judgement
1111
+ // key migrated from `shadingModel === 'sprite'` to
1112
+ // `materialShaderId === 'forgeax::sprite'` (plan-strategy D-10); slices
1113
+ // sourced from `paramSnapshot.slicesAndMode` (post-w12 UBO-aligned
1114
+ // overlay path).
1115
+ //
1116
+ // feat-20260624-sprite-lit-shading-model-pure-2d-lighting M1' / t7:
1117
+ // sprite-lit shares the sprite paramSchema (5 fields, t4 mirror) so
1118
+ // the 9-slices mesh swap applies identically.
1119
+ let effectiveMeshHandles = meshHandles;
1120
+ if (
1121
+ r.material.materialShaderId === 'forgeax::sprite' ||
1122
+ r.material.materialShaderId === 'forgeax::sprite-lit'
1123
+ ) {
1124
+ const slicesArr = r.material.paramSnapshot?.slicesAndMode as readonly number[] | undefined;
1125
+ if (
1126
+ slicesArr !== undefined &&
1127
+ slicesArr.length >= 4 &&
1128
+ (slicesArr[0] !== 0 || slicesArr[1] !== 0 || slicesArr[2] !== 0 || slicesArr[3] !== 0)
1129
+ ) {
1130
+ const nineSliceHandles = pipelineState.meshes.get(handleSlot(HANDLE_NINESLICE_QUAD));
1131
+ if (nineSliceHandles !== undefined) {
1132
+ effectiveMeshHandles = nineSliceHandles;
1133
+ }
1134
+ }
1135
+ }
1136
+ if (r.morph !== undefined) {
1137
+ const morphed = prepareMorphMesh(
1138
+ internals,
1139
+ frameState,
1140
+ worldEntityKey(r.worldId, r.entityKey),
1141
+ effectiveMeshHandles,
1142
+ assetRes.value,
1143
+ r.morph,
1144
+ );
1145
+ if (morphed !== undefined) effectiveMeshHandles = morphed;
1146
+ }
1510
1147
  validated.push({
1511
1148
  source: r,
1512
- mesh: meshHandles,
1149
+ world: renderableWorld,
1150
+ mesh: effectiveMeshHandles,
1513
1151
  renderableIndex: rIdx,
1514
1152
  renderState: renderStateByRenderableIdx.get(rIdx),
1515
1153
  variantSet: variantSetByRenderableIdx.get(rIdx),
@@ -1519,6 +1157,81 @@ function validateRenderables(
1519
1157
  return validated;
1520
1158
  }
1521
1159
 
1160
+ function prepareMorphMesh(
1161
+ internals: RenderSystemInternals,
1162
+ frameState: RenderFrameState,
1163
+ cacheKey: number,
1164
+ base: MeshGpuHandles,
1165
+ mesh: MeshAsset,
1166
+ morph: RenderableSnapshot['morph'],
1167
+ ): MeshGpuHandles | undefined {
1168
+ if (morph === undefined || frameState.morphBuffers === undefined) return undefined;
1169
+ const targets = mesh.morphTargets;
1170
+ if (targets === undefined || targets.length !== morph.targetCount) return undefined;
1171
+ if (base.layoutProjection.arrayStride !== 48 || base.vertexCount <= 0) return undefined;
1172
+ const stride = mesh.vertices.length / base.vertexCount;
1173
+ if (!Number.isInteger(stride) || stride < 3) return undefined;
1174
+
1175
+ let active = false;
1176
+ for (const weight of morph.weights) {
1177
+ if (weight !== 0) {
1178
+ active = true;
1179
+ break;
1180
+ }
1181
+ }
1182
+ if (!active) {
1183
+ frameState.morphBuffers.get(cacheKey)?.buffer.destroy();
1184
+ frameState.morphBuffers.delete(cacheKey);
1185
+ return undefined;
1186
+ }
1187
+
1188
+ const vertices = new Float32Array(mesh.vertices);
1189
+ for (let targetIndex = 0; targetIndex < targets.length; targetIndex += 1) {
1190
+ const weight = morph.weights[targetIndex] ?? 0;
1191
+ const positions = targets[targetIndex]?.position;
1192
+ if (positions === undefined || positions.length !== base.vertexCount * 3) return undefined;
1193
+ for (let vertex = 0; vertex < base.vertexCount; vertex += 1) {
1194
+ const vertexBase = vertex * stride;
1195
+ const positionBase = vertex * 3;
1196
+ vertices[vertexBase] = (vertices[vertexBase] ?? 0) + (positions[positionBase] ?? 0) * weight;
1197
+ vertices[vertexBase + 1] =
1198
+ (vertices[vertexBase + 1] ?? 0) + (positions[positionBase + 1] ?? 0) * weight;
1199
+ vertices[vertexBase + 2] =
1200
+ (vertices[vertexBase + 2] ?? 0) + (positions[positionBase + 2] ?? 0) * weight;
1201
+ }
1202
+ }
1203
+
1204
+ let cached = frameState.morphBuffers.get(cacheKey);
1205
+ if (cached === undefined || cached.byteLength !== vertices.byteLength) {
1206
+ if (cached !== undefined && !cached.buffer.isDestroyed) {
1207
+ const destroyed = cached.buffer.destroy();
1208
+ if (!destroyed.ok) internals.errorRegistry.fire(destroyed.error);
1209
+ }
1210
+ const created = internals.device.createBuffer({
1211
+ label: `morph-${cacheKey}-vbo`,
1212
+ size: vertices.byteLength,
1213
+ usage: GPU_BUFFER_USAGE_VERTEX | GPU_BUFFER_USAGE_COPY_DST,
1214
+ mappedAtCreation: false,
1215
+ });
1216
+ if (!created.ok) {
1217
+ internals.errorRegistry.fire(created.error);
1218
+ frameState.morphBuffers.delete(cacheKey);
1219
+ return undefined;
1220
+ }
1221
+ cached = {
1222
+ buffer: new GpuBuffer(internals.device, created.value),
1223
+ byteLength: vertices.byteLength,
1224
+ };
1225
+ frameState.morphBuffers.set(cacheKey, cached);
1226
+ }
1227
+ const written = internals.device.queue.writeBuffer(cached.buffer.handle, 0, vertices);
1228
+ if (!written.ok) {
1229
+ internals.errorRegistry.fire(written.error);
1230
+ return undefined;
1231
+ }
1232
+ return { ...base, vertexBuffer: cached.buffer };
1233
+ }
1234
+
1522
1235
  /**
1523
1236
  * feat-20260704 M3/w18: build the dispatch-ordered render plan, extracted
1524
1237
  * verbatim from `recordFrame`. (1) M3/w26 dispatch-ordered reorder: reorder
@@ -1575,7 +1288,7 @@ function buildDispatchPlan(
1575
1288
  ordered.push(v);
1576
1289
  }
1577
1290
  }
1578
- // Append any renderables not in the dispatch list (e.g. default-material entities)
1291
+ // Append renderables not in the dispatch list (e.g. default-material entities)
1579
1292
  for (const v of validated) {
1580
1293
  if (!seen.has(v.renderableIndex)) {
1581
1294
  ordered.push(v);
@@ -1610,7 +1323,7 @@ function buildDispatchPlan(
1610
1323
  //
1611
1324
  // bug-20260609: feat-20260608 M5 amend made the material UBO indexed by
1612
1325
  // cumulative *material-slot* count (one slot per submesh material),
1613
- // which is >= entity count once any entity carries `materials.length>1`.
1326
+ // which is >= entity count once an entity carries `materials.length>1`.
1614
1327
  // The mesh + material buffer pair share `slotCount` (single allocator),
1615
1328
  // so we size against the larger of the two requirements: entity count
1616
1329
  // (mesh-SSBO consumer) vs cumulative material-slot count (material-UBO
@@ -1721,7 +1434,7 @@ function buildDispatchPlan(
1721
1434
  // Singleton buckets (mode-bypass under D-5, or non-foldable under
1722
1435
  // mode 0) carry `bucketSize === 1` and never enter `headBuckets`, so
1723
1436
  // the per-entity drawIndexed path correctly does not count.
1724
- // SSOT helper lives in `render-system-fold.ts` — engine code never
1437
+ // SSOT helper lives in this record owner — engine code never
1725
1438
  // hardcodes the metric key string.
1726
1439
  incrementFoldedDrawsMetric(foldDispatchPlan, internals.metrics);
1727
1440
  }
@@ -1798,6 +1511,17 @@ function cleanPerFrameCaches(
1798
1511
  frameState.instanceBuffers.delete(key);
1799
1512
  }
1800
1513
  }
1514
+
1515
+ if (frameState.morphBuffers !== undefined) {
1516
+ for (const [key, entry] of frameState.morphBuffers.entries()) {
1517
+ if (validatedEntityKeys.has(key)) continue;
1518
+ if (!entry.buffer.isDestroyed) {
1519
+ const result = entry.buffer.destroy();
1520
+ if (!result.ok) internals.errorRegistry.fire(result.error);
1521
+ }
1522
+ frameState.morphBuffers.delete(key);
1523
+ }
1524
+ }
1801
1525
  }
1802
1526
 
1803
1527
  /**