@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
@@ -3,7 +3,7 @@
3
3
  //
4
4
  // Engine-internal phase: NOT registered to World schedule (AC-09);
5
5
  // `Renderer.draw([world], { cameraOwner: 0, resourceOwner: 0 })` invokes once per frame. See `Renderer` JSDoc in
6
- // `./renderer.ts` for the full error tier table (D-S4..D-S8) and AGENTS.md
6
+ // `./render-contract.ts` for the public error/frame contract and AGENTS.md
7
7
  // "ECS render bridge" section for the AI-user-facing contract.
8
8
  //
9
9
  // Stage carve-out (review round 1 finding #3 - 505 line cap fallback):
@@ -16,175 +16,108 @@
16
16
  // .invert` (charter proposition 5: no math reinvention; render-system.test.ts
17
17
  // asserts `/@forgeax\/engine-math/` shows up in render-system.ts source).
18
18
 
19
- import type { EntityHandle, World } from '@forgeax/engine-ecs';
20
- import type { VertexLayoutProjection } from '@forgeax/engine-geometry';
21
- import type { Profiler, RecorderSession } from '@forgeax/engine-profiler';
22
- import type {
23
- BindGroupEntry,
24
- BindGroupLayout,
25
- Buffer,
26
- ComputePipeline,
27
- PipelineLayout,
28
- RenderPipeline,
29
- RhiCanvasContext,
30
- RhiDevice,
31
- Sampler,
32
- TextureView,
33
- } from '@forgeax/engine-rhi';
19
+ import { resolveAssetHandle } from '@forgeax/engine-assets-runtime';
20
+ import type { World } from '@forgeax/engine-ecs';
21
+ import type { RenderReadLease } from '@forgeax/engine-ecs/projection';
22
+ import type { RecorderSession } from '@forgeax/engine-profiler';
23
+ import type { BindGroupEntry, BindGroupLayout, Buffer, RenderPipeline } from '@forgeax/engine-rhi';
34
24
  import { err, ok, type Result, RhiError } from '@forgeax/engine-rhi';
35
- import type { MaterialRuntimeArtifact } from '@forgeax/engine-shader';
36
25
  import {
26
+ derive,
37
27
  type Handle,
38
- type ImmutableParamSchemaProjection,
39
28
  type MaterialAsset,
40
- type MaterialRenderState,
41
29
  type MaterialTextureValue,
42
- type ParamSchemaEntry,
43
- type PassKind,
44
- type PrimitiveTopology,
30
+ type MeshAsset,
45
31
  type RenderPipelineAsset,
46
32
  RenderQueue,
47
33
  type SamplerAsset,
34
+ toShared,
48
35
  } from '@forgeax/engine-types';
49
- import type { RenderAssetPort } from './assets/render-asset-port';
50
- import { resolveRenderAsset } from './assets/resolve-render-asset';
51
36
  import { createClusterBinScratch } from './cluster-binner';
52
- import type { DynamicTextureStore } from './dynamic-texture-store';
53
- import type { EngineMetrics } from './engine-metrics';
54
37
  import {
55
- createEnvironmentGenerationState,
56
- environmentConsumerSnapshot,
57
- } from './environment/generation';
58
- import { isEnvironmentGpuBundle } from './environment/gpu-bundle';
59
- import { type EnvironmentInspection, inspectEnvironmentGeneration } from './environment/inspection';
60
- import { abandonEnvironmentDevice, disposeEnvironmentGeneration } from './environment/lifecycle';
61
- import {
62
- HdrpCapsInsufficientError,
63
38
  type ObservationUnavailableError,
39
+ PointsLinesMaterialUnsupportedError,
64
40
  type RenderError,
65
41
  } from './errors/render';
66
- import { resolveMaterialFromPort } from './extract/material-interface';
67
- import {
68
- createRenderFeatureContributionStaging,
69
- mergeRenderFeatureContributions,
70
- type RenderFeatureGraphComposition,
71
- type RenderFeatureGraphContribution,
72
- } from './features/graph-contribution';
73
42
  import {
74
- type RenderFeatureHost,
75
43
  type RenderFeaturePreparedGraphicsResolverInput,
76
44
  runRenderFeatureFrame,
77
45
  settlePreparedGraphicsCompletion,
78
46
  } from './features/host';
79
47
  import {
80
- createRenderFeatureGpuWorkResolver,
81
- type RenderFeatureGpuWorkResolver,
48
+ createRenderFeatureGpuWorkOwner,
49
+ type RenderFeatureGpuWorkOwner,
82
50
  } from './features/prepared-gpu-work';
83
- import { sceneTemporalCoverage } from './features/scene-temporal';
84
51
  import { resolveStandardRenderFeatureTargets } from './features/targets';
85
- import type { RenderFeaturePassContext } from './features/types';
86
52
  import {
87
53
  buildFullscreenPostProcessPass,
88
54
  DEPTH_MIN_PARAMS_BYTE_SIZE,
89
55
  entryHasDepthRead,
90
56
  type PostProcessShaderEntry,
91
57
  } from './fullscreen-post-process-pass';
58
+ import { GpuDrivenProduction } from './gpu-driven/production-raster';
59
+ import type { RenderSceneInspection } from './inspection-types';
60
+ import { admitPointsLines } from './points-lines/admission';
61
+ import { PointsLinesExpansionCache } from './points-lines/expansion-cache';
62
+ import {
63
+ inspectPointsLines,
64
+ type PointsLinesInspection,
65
+ type PointsLinesSourceError,
66
+ } from './points-lines/inspection';
92
67
  import {
93
- GpuDrivenProduction,
94
- type GpuDrivenProductionInspection,
95
- } from './gpu-driven/production-raster';
96
- import type { GpuBuffer } from './gpu-resource';
97
- import type { GpuResourceStore } from './gpu-resource-store';
68
+ createPointsLinesLanePreparationAdapter,
69
+ type PointsLinesLanePreparationAdapter,
70
+ } from './points-lines/prepare';
71
+ import { createPointsLinesLaneAdapter, type PointsLinesBackend } from './points-lines/record';
72
+ import type { PointsLinesRetainedSnapshot } from './points-lines/snapshot';
73
+ import type { PointsLinesRecordOwner, PointsLinesRecordSubmission } from './record/render-context';
74
+
75
+ export type { RenderSceneInspection } from './inspection-types';
76
+
77
+ import { deriveVertexLayoutProjection } from '@forgeax/engine-geometry';
98
78
  import {
99
- GPU_TEXTURE_USAGE_COPY_SRC,
100
- GPU_TEXTURE_USAGE_RENDER_ATTACHMENT,
101
- GPU_TEXTURE_USAGE_TEXTURE_BINDING,
102
- } from './gpu-texture-usage';
103
- import { GPU_BUFFER_USAGE_COPY_DST, GPU_BUFFER_USAGE_UNIFORM } from './gpu-usage';
104
- import { validateClusterGrid } from './hdrp-pipeline';
79
+ GPU_BUFFER_USAGE_COPY_DST,
80
+ GPU_BUFFER_USAGE_INDEX,
81
+ GPU_BUFFER_USAGE_UNIFORM,
82
+ GPU_BUFFER_USAGE_VERTEX,
83
+ } from './gpu-usage';
105
84
  import { assembleMaterialWithSkylightEntries } from './ibl/skylight-bind-group';
106
85
  import { disposeInstanceBuffers, disposeTransientInstanceBuffers } from './instance-buffer-cache';
107
- import type { RhiErrorListenerRegistry } from './lifecycle';
108
86
  import type { MeshMaterialBindingObservation } from './mesh-material-bindings';
109
- import {
110
- deriveParamSchemaForCensus,
111
- type ParamSchemaProjectionCensus,
112
- } from './param-schema-projection-census';
113
- import {
114
- PersistentRenderScene,
115
- type PersistentRenderSceneInspection,
116
- } from './persistent-render-scene';
117
- import { PipelineError } from './pipeline-errors';
118
- import {
119
- PostProcessError,
120
- type PostProcessError as PostProcessErrorType,
121
- } from './post-process-errors';
87
+ import { validateClusterGrid } from './pipeline/standard-pipeline';
88
+ import { DEFAULT_CLUSTER_GRID } from './pipeline/standard-profile';
89
+ import { PostProcessError } from './post-process-errors';
122
90
  import {
123
91
  createPreparedGraphicsResolver,
124
92
  type PreparedGraphicsResolver,
125
93
  } from './prepare/prepared-graphics-resolver';
126
- import { type RecordProfileRunner, type RenderFrameState, recordFrame } from './record';
127
- import { buildPerFrameBindGroups } from './record/frame-lighting';
128
94
  import {
129
95
  type FrameObservation,
130
96
  type FrameObservationOptions,
131
97
  observeCurrentFrame,
132
- } from './record/frame-observation';
98
+ type RecordProfileRunner,
99
+ recordFrame,
100
+ } from './record/frame';
101
+ import { buildPerFrameBindGroups } from './record/frame-lighting';
102
+ import type { RenderFrameState } from './record/frame-snapshot';
133
103
  import { applyParamSnapshotToUbo, residentTextureView } from './record/main-pass-material';
134
- import type { MembershipTimingController } from './record/membership-timing';
135
- import {
136
- applyShadowSubmitMode,
137
- createShadowSubmitModeState,
138
- restoreShadowSubmitMode,
139
- type ShadowSubmitMode,
140
- type ShadowSubmitModeState,
141
- type ShadowSubmitResult,
142
- } from './record/shadow-submit-mode';
143
- import { retireCompiledFrameGraph } from './record/typed-frame-graph';
144
- // The forgeax-concept RenderPipeline (registrable / installable unit) - aliased to avoid
145
- // the name collision with the RHI opaque `RenderPipeline` handle imported above. The RHI
146
- // handle stays internal (requirements line 155); this concept type is the public surface.
147
- import type { RenderPipeline as RenderPipelineDef } from './render-pipeline';
148
- import type { RenderPipelineContext } from './render-pipeline-context';
149
- import type {
150
- CameraSnapshot,
151
- DispatchEntry,
152
- ExtractedFrame,
153
- MaterialSnapshotCachesByWorld,
154
- RenderableSnapshot,
155
- } from './render-system-extract';
156
- import { TaaHistoryStore } from './taa-history-store';
157
- import { TemporalFrameTransaction } from './temporal-frame-transaction';
158
- import { projectTemporalInspection, type TemporalInspection } from './temporal-inspection';
159
-
160
- export type RenderSceneInspection = PersistentRenderSceneInspection & {
161
- readonly gpuDriven: GpuDrivenProductionInspection;
162
- readonly temporal: TemporalInspection;
163
- readonly environment: EnvironmentInspection;
164
- };
165
-
104
+ import type { RenderSystemInternals } from './record/render-context';
166
105
  import {
167
- extractFrame,
168
- extractFrames,
169
- extractRenderableEntity,
170
- prepareExtractContext,
171
- refreshExtractedFrameCameras,
172
- refreshExtractedFrameLights,
173
- refreshExtractedFramePostProcessParams,
174
- refreshRenderableMaterialSnapshot,
175
- selectFog,
176
- } from './render-system-extract';
106
+ type RenderFeatureGraphCandidate,
107
+ resetRenderFeatureGraphState,
108
+ } from './record/typed-frame-graph';
177
109
  import {
110
+ type CameraSnapshot,
178
111
  type DrawOwnerOptions,
179
112
  RENDER_PHASE_CATALOG,
180
113
  type RenderPhase,
181
114
  type RenderPhaseSkipReason,
182
115
  type RenderRecordPhase,
183
- } from './renderer';
184
- import type { MaterialRenderProjection } from './renderer/material/assembly';
185
- import type { MaterialReadiness } from './renderer/material/pipeline-projection';
186
- import { resolveSurfaceFormatPair } from './surface-format';
187
- import type { SkinPaletteAllocator } from './systems/skin-palette-allocator';
116
+ } from './render-contract';
117
+ import type { DispatchEntry, ExtractedFrame, RenderableSnapshot } from './render-system-extract';
118
+ import { extractFrames } from './render-system-extract';
119
+ import { PersistentRenderScene } from './scene/render-scene';
120
+ import { resolveSsaoParameters } from './ssao-config';
188
121
  import {
189
122
  getTransparentSortConfig,
190
123
  TRANSPARENT_SORT_MODE_DISTANCE,
@@ -192,7 +125,24 @@ import {
192
125
  TRANSPARENT_SORT_MODE_LAYER_YZ,
193
126
  TRANSPARENT_SORT_MODE_LAYER_Z,
194
127
  } from './systems/transparent-sort-config';
195
- import { URP_PIPELINE_ID, urpPipeline } from './urp-pipeline';
128
+
129
+ export type {
130
+ _InternalRenderPipelineContext,
131
+ _StandardForwardSceneView,
132
+ PerPassResources,
133
+ PipelineState,
134
+ RenderSystemInternals,
135
+ RenderSystemRuntime,
136
+ SurfaceBackendKind,
137
+ SwapChainFormatPair,
138
+ } from './record/render-context';
139
+ export {
140
+ configureSurface,
141
+ MATERIAL_PER_ENTITY_STRIDE,
142
+ resolveSurfaceFormatPair,
143
+ STANDARD_PBR_UBO_SIZE,
144
+ selectSwapChainFormat,
145
+ } from './record/render-context';
196
146
 
197
147
  /**
198
148
  * Unified transparent-queue sub-sort covering all four
@@ -284,7 +234,7 @@ function sortTransparentDispatch(
284
234
  const footY = posY - pivotY * sizeY;
285
235
  if (mode === TRANSPARENT_SORT_MODE_LAYER_Y) return -footY;
286
236
  if (mode === TRANSPARENT_SORT_MODE_LAYER_YZ) return footY + cfg.yzAlpha * posZ;
287
- // Defensive fallback for any unknown mode that slips past setTransparentSortConfig.
237
+ // Defensive fallback for an unknown mode that slips past setTransparentSortConfig.
288
238
  return posZ;
289
239
  };
290
240
 
@@ -317,9 +267,6 @@ function sortTransparentDispatch(
317
267
  return result;
318
268
  }
319
269
 
320
- /** Dynamic-offset stride for one derived material UBO slot. */
321
- export const MATERIAL_PER_ENTITY_STRIDE = 512;
322
-
323
270
  /**
324
271
  * Engine-internal Extract / Prepare / Record driver; constructed by createRenderer.
325
272
  *
@@ -338,18 +285,13 @@ export const MATERIAL_PER_ENTITY_STRIDE = 512;
338
285
  */
339
286
  export interface RenderSystem {
340
287
  /** Returns true only when this invocation reached queue submission. */
341
- draw(worlds: readonly World[], opts: DrawOwnerOptions): boolean;
342
- /** Apply one renderer-owned shadow submission mode for one generation. */
343
- applyShadowSubmitMode(
344
- mode: ShadowSubmitMode,
345
- rendererGeneration: string,
346
- ): ShadowSubmitResult<ShadowSubmitModeState>;
347
- /** Restore the renderer-owned mode to the independent baseline. */
348
- restoreShadowSubmitMode(rendererGeneration: string): ShadowSubmitResult<ShadowSubmitModeState>;
349
- /** Read the generation-bound mode and exact lifecycle counters. */
350
- getShadowSubmitMode(): ShadowSubmitModeState;
288
+ draw(
289
+ worlds: readonly World[],
290
+ opts: DrawOwnerOptions,
291
+ renderReadLeases?: readonly RenderReadLease[],
292
+ ): boolean;
351
293
  /** Release renderer-owned persistent state for one detached World. */
352
- detachWorld(world: World): void;
294
+ detachScene(world: World): void;
353
295
  /** Release the profiler catalog contribution owned by this RenderSystem. */
354
296
  releaseProfilerCatalog(): void;
355
297
  observeCurrentFrame(
@@ -367,6 +309,8 @@ export interface RenderSystem {
367
309
  readonly visibilityStats: { explicitlyHidden: number };
368
310
  /** Persistent scene maintenance evidence from the ordinary single-World path. */
369
311
  readonly renderScene: RenderSceneInspection;
312
+ /** Retained Points/Lines authoring facts from the single scene projection. */
313
+ readonly pointsLinesSnapshots: readonly PointsLinesRetainedSnapshot[];
370
314
  /** Current mesh-slot provenance and active diagnostics from the last frame. */
371
315
  readonly meshMaterialBindings: readonly MeshMaterialBindingObservation[];
372
316
  /**
@@ -397,46 +341,10 @@ export interface RenderSystem {
397
341
  readonly createBindGroup: number;
398
342
  readonly keys: readonly string[];
399
343
  };
400
- /**
401
- * feat-20260615-pipeline-spec-ssot M5-T2: latest URP shadow-atlas
402
- * texture view resolved from the compiled typed frame graph. Returns `null` when no
403
- * frame has been drawn yet, or when the active pipeline does not
404
- * declare the `shadowDepth` color target. Read-only debug seam:
405
- * the engine itself reads the same view inline in `recordFrame`;
406
- * this getter exists so debug readback paths
407
- * (`Renderer.debugSampleShadowFactor`) read the same SSOT view as
408
- * the engine's record stage (D-2: graph owns the lifecycle; no
409
- * mirror slot on `pipelineState.perPassResources`).
410
- */
411
- // biome-ignore lint/suspicious/noExplicitAny: opaque RHI handle
412
- getCurrentShadowView(): any | null;
413
- /**
414
- * feat-20260601-customizable-render-pipeline-seam M1 / w7: register a render-pipeline
415
- * logic under `id`. Same-id re-register THROWS a PipelineError
416
- * (`'pipeline-already-registered'`) - programmer-error fail-fast, mirroring
417
- * ShaderRegistry.installMaterialArtifact. Engine builtins use the `forgeax::` prefix.
418
- */
419
- registerPipeline(id: string, impl: RenderPipelineDef): void;
420
- /**
421
- * feat-20260601-customizable-render-pipeline-seam M1 / w7 (D-19): install the pipeline
422
- * described by a `RenderPipelineAsset` POD. Looks up `pipelineId` in the registry; on an
423
- * unregistered id returns `Result.err(PipelineError{code:'pipeline-not-found'})`. On
424
- * success the next `draw` detects the install-epoch bump and rebuilds the memoized
425
- * per-frame graph (hot-swap). Takes the payload directly because installation happens at
426
- * boot/swap time before any World exists -- there is no handle to resolve.
427
- */
428
- installPipeline(asset: RenderPipelineAsset): Result<() => void, PipelineError>;
429
- /**
430
- * feat-20260604-resource-owning-render-graph-and-fullscreen-postpr M2 / w13:
431
- * fullscreen post-process shader registry, parallel to ShaderRegistry.installMaterialArtifact
432
- * (D-4: material shader handles 4-BGL / 12-float-vertex / depth / triangle-list, while
433
- * fullscreen post-process uses 0-vertex-buffer / no-depth / input-texture-BGL).
434
- * Same-id re-register THROWS PostProcessError ('post-process-already-registered').
435
- * Engine builtins use the `forgeax::` prefix.
436
- */
437
- readonly postProcess: {
438
- register(id: string, entry: PostProcessShaderEntry): () => void;
439
- };
344
+ /** Configure the sole Standard graph owner before the first frame. */
345
+ configureStandard(config: RenderPipelineAsset['config']): void;
346
+ /** Register one engine-owned post-process shader used by the Standard lane. */
347
+ registerBuiltinPostProcess(id: string, entry: PostProcessShaderEntry): () => void;
440
348
  /**
441
349
  * feat-20260612-rhi-destroy-renderer-dispose-gpu-lifecycle / M5 / w21:
442
350
  * release the per-RenderSystem frame-state GPU bookkeeping during
@@ -461,313 +369,11 @@ export interface RenderSystem {
461
369
  * Idempotent and graph-optional (no-op when nothing has compiled yet).
462
370
  */
463
371
  resetForRecover(): void;
464
- /** Recreate GPU-driven production state after internals.device is replaced. */
465
- restoreGpuDrivenForRecover(): void;
466
372
  /** Recreate post-process GPU parameter resources after a device rebuild. */
467
373
  restorePostProcessResources(): void;
468
374
  }
469
375
 
470
- /**
471
- * feat-20260601-customizable-render-pipeline-seam M2 / w10: the NARROW runtime-services
472
- * surface a `RenderPipeline` pass closure consumes (UE `FRDGBuilder` runtime half). Bundles
473
- * exactly the four `RenderSystemInternals` members the 9 urp closures reach
474
- * directly: the GPU `device`, the structured-error sink `errorRegistry`, and the two
475
- * per-MaterialShader pipeline-cache / param-schema lookups. `RenderPipelineContext.runtime`
476
- * is typed as this interface, NOT the full `RenderSystemInternals` - a pipeline author
477
- * cannot reach `context` / `getPipelineState` / `canvas` (charter P4: a
478
- * consistent narrow abstraction over the runtime, not the kitchen-sink). `internals`
479
- * itself is unreachable through the public ctx (the AC-08 oracle: `ctx.internals` is a
480
- * compile error).
481
- */
482
- export interface RenderSystemRuntime {
483
- readonly device: RhiDevice;
484
- readonly errorRegistry: RhiErrorListenerRegistry;
485
- /** @internal Emit directional shadow causal markers only for debug capture. */
486
- readonly shadowDebugObservation?: boolean | undefined;
487
- /** @internal Debug-only ParamSchema compatibility census; absent in production. */
488
- readonly paramSchemaProjectionCensus?: ParamSchemaProjectionCensus | undefined;
489
- /** @internal Depth-only fullscreen draw used to clear one invalid cascade tile. */
490
- readonly getDirectionalShadowTileClearPipeline?: () => RenderPipeline | null;
491
- /**
492
- * feat-20260622-s5-device-surface-self-heal-recover M2 / w8: health registry
493
- * threaded into the record stage so `recordFrame` can fire `internal-fault`
494
- * when surface reconfigure+retry both fail (A-IN-2).
495
- */
496
- readonly healthRegistry: import('./lifecycle').HealthListenerRegistry;
497
- /**
498
- * Cooked material consumption boundary. Runtime record code may obtain a
499
- * projection and content-addressed artifact here; it must not resolve an
500
- * authored parent chain or compile a missing specialization.
501
- */
502
- readonly getMaterialProjection?: (materialGuid: string) => MaterialRenderProjection | undefined;
503
- readonly getMaterialArtifact?: (specializationKey: string) => MaterialRuntimeArtifact | undefined;
504
- /** Production MaterialReady/Error lookup used by the record draw gate. */
505
- readonly getMaterialReadiness?: (materialGuid: string) => MaterialReadiness | undefined;
506
- // feat-20260523-shader-template-instance-split M9-T03 (D-PipelineBuilder):
507
- // per-MaterialShader pipeline cache lookup. Returns the cached pipeline
508
- // for `materialShaderId`, lazily building on first miss via
509
- // ShaderRegistry.findMaterialArtifact -> buildPipelineForMaterialShader.
510
- // Returns `null` when:
511
- // - the id is not registered in ShaderRegistry (authored materials skip
512
- // the affected draw; feature-only callers may use the engine path)
513
- // - the underlying async shader-module build is still pending (caller
514
- // falls back for one frame and retries on the next; mirrors the
515
- // `makeShaderDeviceAdapter` 1-frame-warmup idiom)
516
- // - the pipeline build itself fails (caller fires a structured
517
- // RhiError via errorRegistry; charter P3 explicit failure)
518
- readonly getMaterialShaderPipeline?: (
519
- materialShaderId: string,
520
- isHdr: boolean,
521
- renderState?: MaterialRenderState,
522
- // feat-20260604 M3 / w9: per-mesh topology selects a per-topology PSO
523
- // (WebGPU bakes topology into the immutable pipeline). Omitted resolves to
524
- // 'triangle-list' (AC-03 zero-regression).
525
- topology?: PrimitiveTopology,
526
- // feat-20260604 M5 / w15: strip topologies bake the mesh's index width into
527
- // primitive.stripIndexIndex. The record stage threads entry.mesh.indexFormat
528
- // here so a uint16-indexed strip gets a uint16 PSO (not a hardcoded uint32).
529
- // Ignored for list topologies (WebGPU spec: stripIndexFormat strip-only).
530
- indexFormat?: 'uint16' | 'uint32',
531
- // feat-20260609 M4 / w33: variantSet selects the per-variant WGSL from the
532
- // shader manifest. When non-empty, the pipeline builder resolves the matching
533
- // variant's composedWgsl instead of the boot-time default; the BGL is
534
- // derived from paramSchema via derive() in pbr-pipeline (M3 / w12-w13).
535
- // URP callers pass 'STORAGE_BUFFER_AVAILABLE=true'; HDRP callers pass
536
- // 'CLUSTER_FORWARD_AVAILABLE=true+STORAGE_BUFFER_AVAILABLE=true'. Omitted
537
- // (undefined / '') preserves backward-compatible behaviour (existing PSOs).
538
- variantSet?: string,
539
- // feat-20260609 M0 / T-002: passKind distinguishes forward (colour+DS)
540
- // from shadow-caster passes in the PSO cache key. Defaults to
541
- // 'forward' for backward compatibility (AC-06).
542
- passKind?: PassKind,
543
- // feat-20260611-fox-skinning-vertex-attribute-chain M4 / w16 (D-4):
544
- // pbr-skin layoutKind reads its 6-attribute / 72-byte vertex buffer
545
- // layout via `deriveVertexBufferLayout` (vertex-attribute-layout.ts SSOT).
546
- // Callers with a resolved `MeshAsset.attributes` pass it through so the
547
- // built PSO matches the mesh's real attribute set. Undefined falls
548
- // through to the synthesized 6-key sentinel inside buildPipelineContext;
549
- // non-skin layoutKinds ignore this parameter entirely.
550
- meshAttributes?: import('@forgeax/engine-types').VertexAttributeMap,
551
- // bug-20260615 M2 / m2-1: sampleCount drives the multisample descriptor
552
- // field in the pipeline builder — it is a CAMERA fact (per-frame
553
- // antialias setting). Default 1 preserves byte-identity of every
554
- // pre-M2 cache slot + descriptor.
555
- sampleCount?: number,
556
- // feat-20260625-refactor-sprite-as-transparent-mesh R2 fix-up:
557
- // LDR-color override for sub-passes whose attachment view is not the
558
- // sRGB swap-chain view (LDR sprite split sub-pass writes through the
559
- // storage / non-sRGB view of the same texture so premultiplied-alpha
560
- // blends correctly). Threaded into the cache key + PSO descriptor.
561
- // Undefined preserves the default sRGB view used by the geometry +
562
- // tonemap + skybox callers. Ignored when `isHdr=true` or
563
- // `passKind='shadow-caster'`.
564
- colorFormatOverride?: GPUTextureFormat,
565
- // Shader-declared UV count used by material pipeline cache aliases.
566
- shaderUvSetCount?: number,
567
- // `null` explicitly requests a color-only prepared-graphics pipeline;
568
- // omitted keeps the normal forward material depth attachment.
569
- depthFormatOverride?: GPUTextureFormat | null,
570
- // Prepared graphics may select one of the host-owned canonical vertex
571
- // layouts. Ordinary material callers leave this undefined.
572
- vertexLayout?: string,
573
- // Authored material GUID used to distinguish the publication-backed draw
574
- // path from feature-only pipeline callers.
575
- materialGuid?: string,
576
- // Fragment entry point override for shaders with distinct color-domain
577
- // entry points. Omitted keeps the shader's default `fs_main` entry.
578
- fragmentEntry?: string,
579
- /** Geometry-owned immutable projection carried to the renderer PSO seam. */
580
- vertexLayoutProjection?: VertexLayoutProjection,
581
- ) => RenderPipeline | null;
582
- readonly getMaterialShaderBindingContract?: (
583
- materialShaderId: string,
584
- ) => 'group-0' | 'group-0-resource' | 'view-only' | 'view-with-resource' | 'render-material';
585
- /**
586
- * feat-20260527 M2 / w7: schema lookup for material param overlay.
587
- * Returns the paramSchema for a registered material shader, or
588
- * `undefined` when the shader is not in the ShaderRegistry.
589
- * Record stage uses this instead of reading `MaterialSnapshot.paramSchema`.
590
- */
591
- readonly getParamSchema?: (materialShaderId: string) => readonly ParamSchemaEntry[] | undefined;
592
- /**
593
- * Runtime ParamSchema projection owner lookup. Registered shader consumers
594
- * must prefer this immutable projection over deriving the same schema again.
595
- * The optional seam keeps feature-only and legacy test runtimes compatible.
596
- */
597
- readonly getParamSchemaProjection?: (
598
- materialShaderId: string,
599
- ) => ImmutableParamSchemaProjection | undefined;
600
- /**
601
- * feat-20260621-learn-render-5-5-parallax M2 / w6 (D-1): the per-shader
602
- * material BindGroupLayout for the given material shader. A custom shader
603
- * declaring >3 user-region textures (e.g. parallax `heightTexture`) owns a
604
- * material BGL whose entry count + injection start differ from the built-in
605
- * 18-entry layout; the record stage must create the material bind group
606
- * against THIS layout so the entry count matches. Returns `undefined` when
607
- * the shader is unregistered or resolves to the shared built-in layout
608
- * (3-texture shaders), in which case the caller falls back to
609
- * `pipelineState.materialBindGroupLayout`.
610
- */
611
- readonly getMaterialBindGroupLayout?: (materialShaderId: string) => BindGroupLayout | undefined;
612
- /**
613
- * feat-20260527-sprite-nineslice M4 / w16 (D-5): per-Renderer metrics counter
614
- * surfaced through `renderer.metrics`. Record-stage soft-warns (e.g.
615
- * `nineslice.scale-too-small` when Transform.scale falls below the four
616
- * 9-slice corner anchors) call `runtime.metrics.increment(name)` instead of
617
- * a per-entity `console.warn` so AI users observe machine-readable counters
618
- * (charter P3 over P-flooded text logs). Each Renderer owns its own
619
- * EngineMetrics instance (D-5 candidate 1: multi-Renderer isolation).
620
- */
621
- readonly metrics: EngineMetrics;
622
- /** Optional Render-owned membership timing capability. */
623
- readonly membershipTiming?: MembershipTimingController | undefined;
624
- /**
625
- * feat-20260604-resource-owning-render-graph-and-fullscreen-postpr M3 / F-2 fix-up:
626
- * post-process registry lookup. Returns the `PostProcessShaderEntry` registered
627
- * via `renderer.postProcess.register(id, entry)`, or `undefined` when `id`
628
- * resolves to no entry. `addFullscreenPass`'s execute closure dispatches on this
629
- * to route to either the FXAA built-in path (id === 'fxaa') or the generic
630
- * `buildFullscreenPostProcessPass + createFullscreenBindGroup` path; an
631
- * unregistered id surfaces as a thrown `PostProcessError({code:'post-process-not-found'})`
632
- * fail-fast (charter P3 / pipeline-errors throw template).
633
- *
634
- * Optional so a custom RenderSystemRuntime fixture (test doubles) need not
635
- * supply it; the engine's own createRenderSystem populates it unconditionally.
636
- */
637
- readonly lookupPostProcess?: (id: string) => PostProcessShaderEntry | undefined;
638
- /**
639
- * feat-20260621-fullscreen-post-process-per-frame-uniform-params-l M-A2 / w8:
640
- * per-shader params UBO accessor. Returns the eager-created (at register time,
641
- * M-A1 / w5) GPU buffer for `id`, or `undefined` when the id has no params
642
- * (param-less consumer) / is unregistered. dispatchFullscreenPass reads this to
643
- * writeBuffer the per-frame snapshot bytes and bind the buffer at group(1)
644
- * binding(2). Optional so test fixtures need not supply it; createRenderSystem
645
- * populates it unconditionally (mirrors lookupPostProcess / getPostProcessPipeline).
646
- */
647
- readonly getPostProcessParamsBuffer?: (id: string) => Buffer | undefined;
648
- /**
649
- * feat-20260609-learn-render-4-5-framebuffers-demo-offscreen-rt-an M4 / T-10-a:
650
- * post-process render-pipeline lookup with eager-on-first-call build. Returns a
651
- * cached `RenderPipeline` (RHI handle) for `id`, lazily building on first call
652
- * via the closure passed through `RenderSystemInternals.buildPostProcessPipeline`.
653
- * Returns `null` when:
654
- * - the id is not registered in the post-process registry (caller skips the
655
- * pass — paired with the `'post-process-not-found'` throw site, this branch
656
- * is unreachable on the happy path)
657
- * - the underlying async shader-module compile is still pending (caller falls
658
- * back for one frame and retries on the next; mirrors the
659
- * `getMaterialShaderPipeline` 1-frame-warmup idiom)
660
- * - the pipeline build itself fails (caller skips the pass; the build path
661
- * fires a structured RhiError via errorRegistry; charter P3 explicit failure)
662
- *
663
- * Solves M1 CONCERN-1: the dispatcher in `dispatchFullscreenPass` previously
664
- * passed `pipeline=null` to `built.createHandle` because the per-frame execute
665
- * closure cannot await `createShaderModule` (async). With this lookup the
666
- * dispatcher reads the pipeline synchronously; warm-up costs one frame.
667
- *
668
- * `bgl` is the fullscreen input-texture BGL the dispatcher already built via
669
- * `buildFullscreenPostProcessPass`. The engine assumes a single static BGL
670
- * shape (FULLSCREEN_BGL_DESCRIPTOR) so the pipeline cache keys on `id` only;
671
- * subsequent calls reuse the cached pipeline regardless of which BGL identity
672
- * is threaded.
673
- *
674
- * `colorFormat` is the swap-chain / target color format the post-process
675
- * pipeline writes (`bgra8unorm-srgb` for swap-chain, the offscreen RT format
676
- * otherwise). Cache key is `id|colorFormat`.
677
- *
678
- * Optional so a custom RenderSystemRuntime fixture (test doubles) need not
679
- * supply it; the engine's own createRenderSystem populates it when
680
- * `RenderSystemInternals.buildPostProcessPipeline` is provided.
681
- */
682
- readonly getPostProcessPipeline?: (
683
- id: string,
684
- bgl: BindGroupLayout,
685
- colorFormat: GPUTextureFormat,
686
- ) => RenderPipeline | null;
687
- /**
688
- * feat-20260623-world-space-video-asset M4 / w16 (D-3): transient per-frame
689
- * texture store for VideoAsset sources. Independent of {@link gpuStore} — the
690
- * record stage routes a `MaterialSnapshot.videoTextureFields` field through
691
- * this store (per-frame copyExternalImageToTexture upload + current-frame view)
692
- * instead of `gpuStore.ensureResident` (whose static cache video must never
693
- * enter, AC-08). Optional so test fixtures that drive recordFrame manually
694
- * need not supply one — a video field then degrades to the default view
695
- * (charter P3). The production createRenderer always wires it.
696
- */
697
- readonly dynamicTextureStore?: DynamicTextureStore | undefined;
698
- }
699
-
700
- export interface RenderSystemInternals extends RenderSystemRuntime {
701
- readonly canvas: HTMLCanvasElement | OffscreenCanvas;
702
- /** Optional feature host supplied by the renderer assembly layer. */
703
- readonly featureHost?: RenderFeatureHost | undefined;
704
- readonly shaderModuleFactory?: import('./pipeline-builder').PipelineBuilderShaderModuleFactory;
705
- /** Optional host profiler capability for the existing Render path. */
706
- readonly profiler?: Profiler | undefined;
707
- // M6 / w41 (feat-20260510-rhi-resource-creation): the canvas context is the
708
- // forgeax `RhiCanvasContext` brand; render-system-record.ts uses
709
- // `getCurrentTexture()` -> `device.createTextureView(...)` (K-4 two-step
710
- // explicit form, charter proposition 5 consistent abstraction red line).
711
- readonly context: RhiCanvasContext | null;
712
- // feat-20260608-create-app-param-surface-trim / M1 / AC-02: the legacy
713
- // `clearColor` field is removed. The record stage reads clear color
714
- // straight from the Camera SoA (`camera.clearColor`, array<f32,4> as of
715
- // feat-20260709 M3); zero-Camera fallback uses
716
- // `ZERO_CAMERA_CLEAR_FALLBACK = [0, 0, 0, 1]`.
717
- readonly getPipelineState: () => PipelineState | null;
718
- readonly assets: RenderAssetPort;
719
- // feat-20260601-gpu-resource-store-extraction M1: GPU residency layer
720
- // extracted out of AssetRegistry. Record stage reads GPU texture / cubemap /
721
- // mesh handles through the store; CPU POD reads stay on `assets`.
722
- readonly gpuStore: GpuResourceStore;
723
- // feat-20260608-mesh-ssbo-dynamic-grow-l1-lift-1024-entity-cap M3 / T-M3-04:
724
- // mesh-SSBO grow hook + read-only state surface threaded into the record
725
- // stage so `ensureMeshSsboCapacity(internals, validatedOrdered.length)` can
726
- // bridge the recordFrame → growController seam without leaking the full
727
- // controller object into RenderSystemRuntime (the narrow ctx). Both fields
728
- // are optional for test fixtures that wire createRenderSystem manually
729
- // without a controller (legacy path returns ok:true and short-circuits);
730
- // the production createRenderer always sets both. The `| undefined`
731
- // suffix keeps both fields assignable from the WebGPU-internals carrier
732
- // whose own field is `?:` -- exactOptionalPropertyTypes ABI compat.
733
- readonly growMeshSsbo?:
734
- | ((neededSlots: number) =>
735
- | { readonly ok: true }
736
- | {
737
- readonly ok: false;
738
- readonly code: 'mesh-ssbo-ceiling-reached' | 'mesh-ssbo-capacity-exceeded';
739
- readonly degradedToSlotCount: number;
740
- })
741
- | undefined;
742
- readonly meshSsboState?: { readonly slotCount: number } | undefined;
743
- /**
744
- * feat-20260609 M4 / T-10-a: factory backing `getPostProcessPipeline`. Provided
745
- * by `createRenderer.ts` (it owns the shared `getShaderModuleAdapter` and the
746
- * fullscreen pipeline-layout builder); receives a registered
747
- * `PostProcessShaderEntry` + the fullscreen BGL the dispatcher already
748
- * composed + the target color format, returns a built `RenderPipeline`
749
- * (RHI handle) or `null` (shader still compiling / build failed). The first
750
- * call per id triggers the async shader compile; subsequent calls hit the
751
- * RenderSystem-level cache and never reach this factory.
752
- *
753
- * Optional so test fixtures (createRenderSystem with a stub
754
- * RenderSystemInternals) need not provide a real factory; without it
755
- * `getPostProcessPipeline` resolves to `undefined` on the runtime surface
756
- * and the dispatcher falls through to its existing `null`-skip branch.
757
- */
758
- readonly buildPostProcessPipeline?:
759
- | ((
760
- entry: PostProcessShaderEntry,
761
- bgl: BindGroupLayout,
762
- colorFormat: GPUTextureFormat,
763
- label: string,
764
- ) => RenderPipeline | null)
765
- | undefined;
766
- }
767
-
768
- function isStructuredRendererError(
769
- error: unknown,
770
- ): error is RhiError | RenderError | PostProcessErrorType {
376
+ function isStructuredRendererError(error: unknown): error is RhiError | RenderError {
771
377
  if (!(error instanceof Error)) return false;
772
378
  const structured = error as {
773
379
  readonly code?: unknown;
@@ -805,55 +411,6 @@ function reportPreparedGraphicsCompletionError(
805
411
  );
806
412
  }
807
413
 
808
- export interface RenderFeatureGraphRuntimeState {
809
- contributions: readonly RenderFeatureGraphContribution<RenderFeaturePassContext>[];
810
- topologySignature: string | undefined;
811
- composition:
812
- | RenderFeatureGraphComposition<RenderPipelineContext, RenderFeaturePassContext>
813
- | undefined;
814
- }
815
-
816
- const renderFeatureGraphStates = new WeakMap<
817
- RenderSystemInternals,
818
- RenderFeatureGraphRuntimeState
819
- >();
820
-
821
- export function getRenderFeatureGraphState(
822
- internals: RenderSystemInternals,
823
- ): RenderFeatureGraphRuntimeState {
824
- const existing = renderFeatureGraphStates.get(internals);
825
- if (existing !== undefined) return existing;
826
- const created: RenderFeatureGraphRuntimeState = {
827
- contributions: [],
828
- topologySignature: undefined,
829
- composition: undefined,
830
- };
831
- renderFeatureGraphStates.set(internals, created);
832
- return created;
833
- }
834
-
835
- function resetRenderFeatureGraphState(internals: RenderSystemInternals): void {
836
- const state = getRenderFeatureGraphState(internals);
837
- state.contributions = [];
838
- state.topologySignature = undefined;
839
- state.composition = undefined;
840
- }
841
-
842
- export function reportRenderFeatureGraphError(
843
- internals: RenderSystemInternals,
844
- error: RenderError,
845
- ): void {
846
- if (internals.featureHost !== undefined && 'detail' in error) {
847
- const detail = error.detail;
848
- if (detail !== undefined && 'featureIdentity' in detail && 'order' in detail) {
849
- const owned = internals.featureHost.recordError(detail.featureIdentity, error);
850
- internals.errorRegistry.fire(owned);
851
- return;
852
- }
853
- }
854
- internals.errorRegistry.fire(error);
855
- }
856
-
857
414
  function isPendingRenderFeaturePreparation(error: RenderError): boolean {
858
415
  return (
859
416
  error.code === 'render-feature-preparation-failed' &&
@@ -869,785 +426,272 @@ function makePreparedPipelinePendingError(): RhiError {
869
426
  });
870
427
  }
871
428
 
872
- export interface PipelineState {
873
- readonly format: string;
874
- // Color-attachment view format. May differ from `format` (canvas storage)
875
- // when an sRGB-encoding view is requested over a linear storage texture.
876
- // Backend-aware SSOT: selectSwapChainFormat in createRenderer.ts derives
877
- // both format and colorAttachmentFormat from storageBufferCapable
878
- // (Channel 2 UA-preferred bgra8unorm / Channel 3 GLES fallback rgba8unorm);
879
- // bug-20260612-webgpu-canvas-format-prefer-bgra supersedes the previous
880
- // module-level SWAP_CHAIN_VIEW_FORMAT constant (bug-20260519 / bug-20260610).
881
- // Equal to `format` for offscreen render targets.
882
- readonly colorAttachmentFormat: string;
883
-
884
- // Per-frame uniform / storage buffers + bind group layouts used to compose
885
- // the 3 BindGroups (view / material / mesh-array) the pbr.wgsl pipeline
886
- // expects (D-S2 + plan-strategy 1 architecture). The view + material UBOs
887
- // and the mesh SSBO are reused across frames; only the contents are
888
- // queue.writeBuffer-updated each draw([world], { cameraOwner: 0, resourceOwner: 0 }) invocation.
889
- readonly viewBindGroupLayout: BindGroupLayout;
890
- readonly materialBindGroupLayout: BindGroupLayout;
891
- readonly meshBindGroupLayout: BindGroupLayout;
892
- readonly viewUniformBuffer: Buffer;
893
- // feat-20260613-csm-cascaded-shadow-maps M5 / w28: per-pass shadow_caster
894
- // cascade-index UBO. 16 B (1 u32 + 12 B pad to satisfy the 16 B uniform
895
- // buffer alignment). The host writes `index = i` (0..3) before each
896
- // cascade's recordShadowPass submit; shadow_caster.wgsl reads it via
897
- // `@group(0) @binding(5) shadowCasterCascade.index` to pick the matching
898
- // `view.lightViewProj_X`. Forward shaders declare the binding (so the
899
- // shared view BGL accommodates both pipelines) but never reference it.
900
- readonly shadowCasterCascadeBuffer: Buffer;
901
- // feat-20260608-mesh-ssbo-dynamic-grow-l1-lift-1024-entity-cap M2 / T-M2-06:
902
- // materialUniformBuffer + meshStorageBuffer fields hold wrapper objects
903
- // (`{ buffer, sizeInBytes }`) instead of bare `Buffer` handles. The outer
904
- // wrapper-object identity is stable across grow events (research §F8 R1)
905
- // so PipelineState references survive grow without re-bind cycles; the
906
- // inner `.buffer` is replaced by `growMeshSsbo` (createRenderer.ts /
907
- // T-M2-05). Consumers must read `.buffer` to reach the underlying
908
- // `Buffer` handle (M3-04: `pipelineState.meshStorageBuffer.buffer` /
909
- // `.materialUniformBuffer.buffer`); using the wrapper directly as a
910
- // WeakMap chain key would bind the cache to the wrapper object identity
911
- // (which never changes) instead of the inner buffer identity (which DOES
912
- // change on grow), defeating the cache miss -> re-bind path (D-3
913
- // grep-gate guards this).
914
- readonly materialUniformBuffer: { readonly buffer: Buffer; readonly sizeInBytes: number };
915
- readonly meshStorageBuffer: { readonly buffer: Buffer; readonly sizeInBytes: number };
916
- // feat-20260519-light-casters-point-spot-pbr M3 / w20 (D-S1 + D-S2):
917
- // PointLight + SpotLight std430 storage buffers occupy two new
918
- // viewBindGroupLayout entries (binding 1 + 2) alongside the existing
919
- // viewUniformBuffer (binding 0). Each buffer carries a 16 B std430 header
920
- // (count u32 + 12 B pad) followed by a fixed 4-slot first-slice cap
921
- // (32 B per PointLight / 48 B per SpotLight; total 144 B / 208 B). The
922
- // record stage rewrites both buffers per frame via `queue.writeBuffer`
923
- // (D-S6 per-frame full rewrite); the bind-group layout entries exist
924
- // even when shaders do not yet reference them so the M4 pbr.wgsl
925
- // multi-light helper landing requires no further createRenderer
926
- // changes (charter P5 consistent abstraction; AC-04 c byte-frozen
927
- // layout). Cap-gate at createRenderer time
928
- // (`assertStorageBufferCap >= 4`) keeps the layout reachable before any
929
- // frame records (R-4 + plan-strategy 4 risk table).
930
- readonly pointLightsBuffer: Buffer;
931
- readonly spotLightsBuffer: Buffer;
932
- // feat-20260513-instanced-mesh M3 (T-M3-2 / T-M3-3): SSBO single-branch
933
- // record-stage. `meshStorageBuffer` carries one `entity_world` mat4 per
934
- // renderable (256-byte stride for dynamic-offset alignment); the
935
- // `instancesBindGroupLayout` + `identityInstanceBuffer` form the second
936
- // tier of the chained transform: shader composes
937
- // `world = entity_world * instances_local[instance_index]`. When a
938
- // renderable does not carry an `Instances` component the record stage
939
- // binds the shared 1-element identity-mat4 fallback (`drawIndexed
940
- // instanceCount=1`); when it does, the per-entity GPU storage buffer
941
- // owned by the record stage's `frameState.instanceBuffers` cache is
942
- // bound (`drawIndexed instanceCount=Instances.transforms.count / 16`).
943
- // feat-20260514 M3 / w15: the legacy
944
- // `AssetRegistry.createInstancedBuffer` triplet is gone — Instances data
945
- // flows entirely through the ECS-managed `array<f32>` field; the record
946
- // stage owns GPU storage buffer allocation + `LimitExceededDetail`
947
- // emit + `caps.storageBuffer` cap-gate.
948
- readonly instancesBindGroupLayout: BindGroupLayout;
949
- readonly identityInstanceBuffer: Buffer;
950
-
951
- // feat-20260515 M3 / T-M3-05 (research F-6 fix): default sampler +
952
- // fallback 1x1 white textureView seed the materialBindGroup binding=1 /
953
- // binding=2 entries when MaterialAsset.baseColorTexture is undefined.
954
- // The defaults follow research F-5 SSOT (linear min/mag/mipmap +
955
- // repeat) so AI users that don't supply a texture still get a sensible
956
- // sampling configuration without having to register a SamplerAsset
957
- // (charter F2 minimal surface).
958
- readonly defaultSampler: Sampler;
959
- // Nearest-neighbour (mag=nearest, min=nearest, mip=nearest, clamp-to-edge)
960
- // sampler used for sprite / tilemap materials so pixel-art tiles render sharp.
961
- readonly nearestSampler: Sampler;
962
- readonly fallbackTextureView: TextureView;
963
-
964
- // bug-20260519: BUILTIN_CUBE / BUILTIN_TRIANGLE migrated to 12-floats
965
- // (position + normal + uv + tangent) so a single (`unlit-procedural` /
966
- // `standard`) pipeline pair handles all renderables. The legacy
967
- // `unlitBuiltinPipeline` + zero-stride `unlitBuiltinDummyAttrBuffer`
968
- // (which hard-coded uv = (0,0) for BUILTIN cubes) are deleted.
969
- //
970
- // - `unlitPipeline` : unlit module + 12F vertex stride.
971
- // Routed to every entity whose
972
- // shader identity is `forgeax::default-unlit`.
973
- // - `standardPipeline` : pbr module + 12F vertex stride.
974
- // Routed to every entity carrying a
975
- // standard / GGX-PBR material.
976
- //
977
- // Both pipelines share the same 4-BindGroupLayout chain (view + material +
978
- // mesh-array + instances). The record stage selects between them on
979
- // `mat.materialShaderId` only (D-2 BUILTIN-vs-procedural distinction retired).
980
- // bug-20260519 D-3: nullable when manifest carries zero entries (Camera-
981
- // only / clear-pass-only path; D-1 + D-2 + plan-strategy section 1
982
- // mermaid). The render-time access point in `render-system-record.ts`
983
- // narrows on `=== null` and fires a structured `RhiError
984
- // shader-compile-failed` (charter P3 explicit failure; AC-03).
985
- readonly unlitPipeline: RenderPipeline | null;
986
- readonly standardPipeline: RenderPipeline | null;
987
- /**
988
- * feat-20260604-learn-render-4.10-anti-aliasing-msaa M2 / w8: count=4
989
- * multisample variants of the 7 geometry pipelines that write the screen
990
- * or HDR colour target. A pipeline's `multisample.count` must match the
991
- * sampleCount of the colour attachment it writes (WebGPU validation),
992
- * so a count=4 pass cannot reuse a count=1 pipeline -- the record stage
993
- * picks the `*Msaa` variant when the active camera carries
994
- * `antialias === 'msaa'`. Post-process + shadow pipelines stay single-
995
- * sample (AC-06) and have no MSAA variant. `null` when the base pipeline
996
- * is null (empty-manifest path).
997
- */
998
- readonly unlitPipelineMsaa: RenderPipeline | null;
999
- readonly standardPipelineMsaa: RenderPipeline | null;
1000
- // feat-20260625-refactor-sprite-as-transparent-mesh M3 / w14 (D-7 / AC-12):
1001
- // the four sprite-dedicated PSO fields are gone — sprite PSO now flows
1002
- // through `runtime.getMaterialShaderPipeline('forgeax::sprite', ...)`
1003
- // keyed on the premultiplied-alpha renderState the record stage
1004
- // assembles for any transparent material (plan-strategy D-7). Concept
1005
- // count -4 fields on PipelineState; sprite's "is a transparent mesh"
1006
- // story is told entirely by the generic cache + `material.transparent`
1007
- // (derived by the extract stage from `passes[0].renderState.blend !==
1008
- // undefined`, the post-feat-20260626-collapse SSOT).
1009
- readonly unlitPipelineHdrMsaa: RenderPipeline | null;
1010
- readonly standardPipelineHdrMsaa: RenderPipeline | null;
1011
- /**
1012
- * feat-20260523-shader-template-instance-split M9-T03 (D-PipelineBuilder):
1013
- * the shared 4-BindGroupLayout chain pipeline layout (`[view, material,
1014
- * mesh-array, instances]`) -- same handle the standard / unlit / sprite
1015
- * pipelines share. M9 exposes it on PipelineState so the per-MaterialShader
1016
- * pipeline cache (`getMaterialShaderPipeline`) can reuse it at lazy build
1017
- * time without re-running pbrLayouts construction (charter P4 consistent
1018
- * abstraction). `null` when the manifest is empty (Camera-only / clear-
1019
- * pass-only path; bug-20260519 D-3 nullable).
1020
- */
1021
- readonly pbrPipelineLayout: PipelineLayout | null;
1022
- /**
1023
- * feat-20260609-hdrp-cluster-fragment-ggx M4.5 / w36 (D-10 option A):
1024
- * per-variant PipelineLayout for HDRP. Mirrors `pbrPipelineLayout` but
1025
- * substitutes the HDRP unified 7-slot group(2) BGL
1026
- * (`createHdrpBindGroupLayoutDescriptor`) for the 1-slot pbr-mesh-array
1027
- * BGL. Built once at boot and reused for any HDRP variant PSO. `null`
1028
- * when the manifest is empty (parallel to `pbrPipelineLayout`) or when
1029
- * the boot-time createBindGroupLayout / createPipelineLayout fails (in
1030
- * which case `selectPipelineLayoutForVariant` falls back to the URP
1031
- * `pbrPipelineLayout` instead of hard-disabling the build path).
1032
- */
1033
- readonly hdrpPbrPipelineLayout: PipelineLayout | null;
1034
- /** WebGPU storage-path producer for ordered HDRP cluster memberships. */
1035
- readonly hdrpClusterMembershipPipeline: ComputePipeline | null;
1036
- /** Compute bind-group layout paired with the membership producer pipeline. */
1037
- readonly hdrpClusterMembershipBindGroupLayout: BindGroupLayout | null;
1038
- /**
1039
- * bug-20260611-skin-pipeline-layout: dedicated PipelineLayout for the
1040
- * `forgeax::pbr-skin` material shader. Mirrors `pbrPipelineLayout` but
1041
- * substitutes a 3-entry mesh-array BGL (binding 0 = meshes, bindings 1/2 =
1042
- * current/previous palettes) for standard PBR's 1-entry mesh-array BGL. Built once at boot
1043
- * by `buildPbrSkinLayouts` and selected by
1044
- * `selectPipelineLayoutForVariant` when the caller passes
1045
- * `LayoutKind = 'pbr-skin'`. `null` when manifest is empty or the skin
1046
- * shader is not registered (storage-buffer cap gate / register failure);
1047
- * `selectPipelineLayoutForVariant` returns `null` rather than silently
1048
- * falling back to URP layout (charter P3 explicit failure, mirroring the
1049
- * `hdrp-active-must-not-fallback-to-urp-pipeline` guard).
1050
- */
1051
- readonly pbrSkinPipelineLayout: PipelineLayout | null;
1052
- /**
1053
- * feat-20260611 R2 / M8 / w28 (IS-14): record-stage handle to the
1054
- * 3-binding `pbr-skin-mesh-array-bgl` returned by `buildPbrSkinLayouts`
1055
- * (binding 0 mesh-array UBO, bindings 1/2 current/previous palette UBOs;
1056
- * all `hasDynamicOffset: true`). The record stage builds a BG against this
1057
- * BGL for skin entries (`entry.source.skin !== undefined`) and binds it
1058
- * at group(2) when the entry's pipeline layout is `pbr-skin-pl`. The
1059
- * URP path keeps using `meshBindGroupLayout` (1-binding) at group(2),
1060
- * so `pbr-mesh-array-bgl` is unchanged. Stays `null` when
1061
- * `pbrSkinPipelineLayout` itself failed to build (charter P3 explicit
1062
- * failure -- skin path is hard-disabled, mirroring the pipeline-layout
1063
- * fallback in `selectPipelineLayoutForVariant`).
1064
- */
1065
- readonly pbrSkinMeshBindGroupLayout: BindGroupLayout | null;
1066
- /**
1067
- * feat-20260612-skin-palette-per-frame-upload M1 / m1-2: animator-ready
1068
- * skin-palette storage allocator. Replaces the prior identity-buffer
1069
- * stub (PR #353 / feat-20260611 R2 / M8 / w28 IS-14) -- a single 16320 B
1070
- * identity-seeded UBO shared by every skin entry. The allocator owns the
1071
- * lifecycle of the per-frame palette buffer (`createSkinPaletteAllocator`
1072
- * from `./systems/skin-palette-allocator`); the record stage reads the
1073
- * GPU buffer through `skinPaletteAllocator.buffer` and the extract stage
1074
- * (M2) calls `allocateSlice` + `writeJointPalette` to land animated
1075
- * palette data per skinned entity. Per plan-strategy D-1 candidate (b) the
1076
- * allocator is the single authoritative carrier -- no parallel fallback
1077
- * stub. `null` when `pbrSkinPipelineLayout` itself failed (skin path
1078
- * hard-disabled, mirroring the prior stub field's gating).
1079
- */
1080
- readonly skinPaletteAllocator: SkinPaletteAllocator | null;
1081
- // 1x1 white texture view (feat-20260518 M3 / w13 + AC-06): seeds the
1082
- // baseColorTexture (binding 2) and metallicRoughnessTexture (binding 4)
1083
- // entries when the schema-driven MaterialAsset values omit the optional textures.
1084
- // Reuses the same sampling-friendly default as `fallbackTextureView`
1085
- // (above); the alias keeps the new MVP code path declarative without
1086
- // renaming the existing field. Note: normalTexture (binding 6) does
1087
- // NOT use this view -- it has its own RG-encoded fallback below.
1088
- readonly defaultWhiteTextureView: TextureView;
1089
- // 1x1 RGBA8 (128,128,255,255) view dedicated to the normalTexture slot
1090
- // (binding 6). pbr.wgsl decodes sample.rg * 2 - 1 + z = sqrt(1-x^2-y^2),
1091
- // so RG=(128,128)=0.5 maps to tangent (0,0,1) -- zero perturbation when
1092
- // normalTexture is absent. White (255,255) would yield sqrt(1-2)=NaN
1093
- // under saturate (clamps to 0) -> tangent.z=0 -> N rotated into the
1094
- // tangent plane (severely wrong). Separate view because baseColor /
1095
- // metallicRoughness require white-on-missing, normal requires (0,0,1).
1096
- readonly defaultNormalTextureView: TextureView;
1097
-
1098
- // ── feat-20260519-tonemap-reinhard-mvp / M2 / T-M2.5 ──────────────────────
1099
- //
1100
- // HDR (rgba16float) variants of `unlitPipeline` / `standardPipeline`. AI
1101
- // users opt in via `Camera.tonemap === 'reinhard-extended'`; the record
1102
- // stage picks `unlitPipelineHdr` / `standardPipelineHdr` when the active
1103
- // camera's tonemap field is non-'none', and writes the geometry pass into
1104
- // `hdrColorView` instead of the swap-chain view. The post-process tonemap
1105
- // pass then reads `hdrColorView` and writes the final LDR pixels into the
1106
- // swap-chain view (charter P3 explicit failure: a wrong format combination
1107
- // would otherwise silently produce mis-tonemapped pixels).
1108
- //
1109
- // Both HDR pipelines share the same 4-BindGroupLayout chain as their
1110
- // sRGB siblings — only the colour-attachment format differs (D-2 +
1111
- // plan-strategy D-3).
1112
- //
1113
- // bug-20260519 D-3 nullable extension: when the renderer starts with a
1114
- // zero-entry shader manifest the entire HDR + tonemap-resource block in
1115
- // `createRenderer.ts` is skipped (Camera-only / clear-pass-only path),
1116
- // so these fields stay `null`. Record-stage HDR pipeline dispatch +
1117
- // fullscreen tonemap pass narrow on `=== null` and fire structured
1118
- // `shader-compile-failed` (charter P3 explicit failure; AC-03).
1119
- readonly unlitPipelineHdr: RenderPipeline | null;
1120
- readonly standardPipelineHdr: RenderPipeline | null;
1121
-
1122
- /**
1123
- * feat-20260520-directional-light-shadow-mapping M2 / w14 (D-1):
1124
- * 1x1 depth32float fallback bound at viewBindGroup binding(3) when no
1125
- * shadow RT exists (castShadow:false or allocation failed).
1126
- * Cleared to 1.0 (far plane) so comparison-sampler always returns fully lit.
1127
- */
1128
- readonly shadowFallbackTextureView: TextureView;
1129
-
1130
- /**
1131
- * feat-20260612-point-light-shadows-urp-hdrp Round-2 F-1: 1x1x6
1132
- * depth32float `texture_depth_cube_array` (layers=1) fallback bound at
1133
- * viewBindGroup binding(5) when no PointLightShadow snapshots are active
1134
- * (zero-shadow scene = AC-09 zero allocation -- the real cube_array atlas
1135
- * stays in `ShadowAtlas` and is only allocated when the extract stage
1136
- * sees its first PointLightShadow). Cleared to 1.0 so the
1137
- * comparison-sampler returns fully lit on the no-shadow path. The view
1138
- * dimension is `cube-array` to satisfy the BGL `viewDimension:
1139
- * 'cube-array'`; the underlying texture has 6 layers (one cube).
1140
- */
1141
- readonly shadowAtlasFallbackTextureView: TextureView;
1142
-
1143
- /**
1144
- * feat-20260612-point-light-shadows-urp-hdrp Round-2 F-1: 64 B uniform
1145
- * buffer carrying `array<vec4<f32>, 4>` -- one lane per shadow-casting
1146
- * point light slot. Lane N stores `(near, far, 1/(far-near), 0)` for the
1147
- * point light with `shadowAtlasLayer === N`. Lanes for non-shadow-casting
1148
- * slots stay zeroed; the WGSL sample path is gated by
1149
- * `PointLight.shadowAtlasLayer >= 0` so zeroed lanes are never read.
1150
- * Bound at viewBindGroup binding(6); written per frame from the
1151
- * `pointShadowSnapshots` array.
1152
- */
1153
- readonly shadowParamsBuffer: Buffer;
1154
-
1155
- // feat-20260625-spot-light-shadow-mapping w25 (scope-amend webkit-fallback):
1156
- // the per-spot fragment-read perspective lightViewProj matrices no longer
1157
- // occupy a dedicated `spotLightViewProjBuffer` / view-BG binding 9 — they fold
1158
- // into the View UBO tail (`view.spotLightViewProj`, bytes 528..784) written by
1159
- // render-system-record's viewPayload. The standalone uniform buffer pushed the
1160
- // WebGL2 fallback fragment uniform-buffer count to 12, over GLES 3.0's
1161
- // `max_uniform_buffers_per_shader_stage = 11`. Caster vertex channel (binding
1162
- // 7) is untouched: same-frame write contention only applies to casters (D-1).
1163
-
1164
- // ── feat-20260520-directional-light-shadow-mapping M2 / w15 (AC-12) ─────
1165
- //
1166
- // GPU shadow-factor probe pipeline. `debugSampleShadowFactor` packs N world
1167
- // positions into `shadowProbeInputBuf` (`array<vec4<f32>, PROBE_MAX_COUNT>`),
1168
- // writes the active lightSpaceMatrix into `shadowProbeLsmUbo`, builds a
1169
- // transient BindGroup binding the active shadow texture + comparison
1170
- // sampler, and renders 1 pixel per probe into `shadowProbeOutputTex`
1171
- // (1xN rgba32float). The fragment stage runs the same atlas-depth PCF path
1172
- // as `pbr.wgsl::evalDirectional()` and returns factor, center depth,
1173
- // cascade index, and receiver depth in the four channels. The 1-row staging
1174
- // buffer (1024 B = PROBE_MAX_COUNT * 16 B; already 256B-aligned) is mapped
1175
- // and the leading N vec4 records returned. Compiled once at pipeline build
1176
- // time; null when the shader manifest is empty (no probe possible).
1177
- readonly shadowProbePipeline: RenderPipeline | null;
1178
- readonly shadowProbeBindGroupLayout: BindGroupLayout | null;
1179
- readonly shadowProbeLsmUbo: Buffer | null;
1180
- readonly shadowProbeInputBuf: Buffer | null;
1181
- // biome-ignore lint/suspicious/noExplicitAny: opaque RHI handle (matches sibling fields)
1182
- readonly shadowProbeOutputTex: any | null;
1183
- readonly shadowProbeOutputView: TextureView | null;
1184
- readonly shadowProbeStagingBuf: Buffer | null;
1185
-
1186
- /**
1187
- * Fallback Skylight resource bundle (feat-20260520-skylight-ibl-cubemap
1188
- * M2 round-2 / t40, plan-strategy D-5). 1x1 all-zero rgba16float
1189
- * texture_cube * 2 (irradiance + prefilter) + 1x1 approximate rg16float
1190
- * brdfLut + intensity=0 16 B uniform buffer + 7-entry BindGroup compose
1191
- * the @group(4) skylight binding for the "no Skylight ECS entity"
1192
- * branch. The M4 round-2 recordFrame branch binds `bindGroup` when
1193
- * `skylightCount === 0` so `standardPipeline` / `standardPipelineHdr`
1194
- * (which keep a 4-slot pipeline layout per D-5 round-4) dispatch with
1195
- * ambient=0 -- physical convergence with D-4 (charter F1: AI users
1196
- * writing demos do not need a "is there a skylight?" branch).
1197
- *
1198
- * D-5 round-4: the fallback bundle no longer carries a stand-alone
1199
- * BindGroup. The PBR material BindGroupLayout now holds 14 entries
1200
- * (material 0..6 + Skylight 7..13) and the per-frame material
1201
- * BindGroup assembly site (render-system-record) merges Skylight
1202
- * resources at binding 7..13 -- active when a Skylight component
1203
- * exists, fallback identity when `skylightCount === 0`.
1204
- *
1205
- * bug-20260519 D-3 nullable extension: the empty-manifest path
1206
- * (Camera-only / clear-pass-only) skips the entire PBR pipeline build
1207
- * block; we still allocate the fallback bundle in the normal path,
1208
- * but tests / OOS paths that mock the manifest empty land here with
1209
- * `null` (charter P3 explicit failure: M4 record-stage gates on
1210
- * `skylightFallback !== null` before binding fallback resources at
1211
- * material BG entries 7..13).
1212
- */
1213
- // biome-ignore lint/suspicious/noExplicitAny: opaque SkylightFallback shape
1214
- readonly skylightFallback: any | null;
1215
-
1216
- /**
1217
- * feat-20260529-rendergraph-pass-abstraction M3 / w11 (D-2 + Finding 3):
1218
- * per-pass mutable texture cache slots extracted from the global
1219
- * PipelineState. These fields are the lazily-allocated texture attachments
1220
- * + pass-specific pipelines and bindgroups that belong to a single render
1221
- * pass (depth / shadow / tonemap / FXAA). M4 render-graph resource
1222
- * declarations will read these descriptors to derive transient/persistent
1223
- * resources; shadow probe (OOS-4) stays on PipelineState directly.
1224
- */
1225
- readonly perPassResources: PerPassResources;
1226
- }
429
+ type PointsLinesGpuResources = {
430
+ readonly vertexBuffer: Buffer;
431
+ readonly indexBuffer: Buffer;
432
+ };
433
+ type PointsLinesPreparationAdapter = PointsLinesLanePreparationAdapter<PointsLinesGpuResources>;
1227
434
 
1228
435
  /**
1229
- * Configure a canvas surface from the two PipelineState format fields, applying
1230
- * the WebGL2-fallback gate: native WebGPU uses the storage format plus an sRGB
1231
- * view format, while the wgpu WebGL2 surface uses its direct sRGB format and
1232
- * COLOR_TARGET-only usage because its GLES surface rejects COPY_SRC. The
1233
- * storage-buffer capability still controls TEXTURE_BINDING usage, but it does
1234
- * not determine whether native WebGPU supports viewFormats. Single SSOT for
1235
- * the configure descriptor consumed by both the
1236
- * lazy first-frame path (ensureContextConfigured in createRenderer.ts) and the
1237
- * F2 surface-outdated reconfigure-and-retry branch (render-system-record.ts) so
1238
- * the two cannot drift (architecture-principles #1 SSOT). Returns the configure
1239
- * Result; callers own the `state.perPassResources.configured` flag + the
1240
- * `__forgeaxSwapChainFormat` probe write (those differ per call site).
436
+ * The Standard renderer's single Points/Lines prepare owner.
437
+ *
438
+ * The retained snapshot is the identity boundary; this owner only resolves
439
+ * the source assets, runs admission, and publishes the existing preparation
440
+ * and record contracts to the main geometry loop. The expansion cache is
441
+ * shared across lanes and this owner keeps the one prepared vertex/index
442
+ * resource pair for each retained identity.
1241
443
  */
1242
- export function configureSurface(
1243
- context: RhiCanvasContext,
1244
- device: RhiDevice,
1245
- format: string,
1246
- colorAttachmentFormat: string,
1247
- ): Result<void, RhiError> {
1248
- const backendKind = device.caps?.backendKind ?? 'webgpu';
1249
- const isWebGl2 = backendKind === 'wgpu-webgl2';
1250
- const surfaceFormats = resolveSurfaceFormatPair(
1251
- backendKind,
1252
- format as GPUTextureFormat,
1253
- colorAttachmentFormat as GPUTextureFormat,
1254
- );
1255
- const supportsTextureBinding = device.caps?.storageBuffer ?? true;
1256
- return context.configure({
1257
- device,
1258
- format: surfaceFormats.storage,
1259
- alphaMode: isWebGl2 ? 'opaque' : 'premultiplied',
1260
- usage:
1261
- GPU_TEXTURE_USAGE_RENDER_ATTACHMENT |
1262
- (supportsTextureBinding ? GPU_TEXTURE_USAGE_TEXTURE_BINDING : 0) |
1263
- (isWebGl2 ? 0 : GPU_TEXTURE_USAGE_COPY_SRC),
1264
- ...(!isWebGl2 ? { viewFormats: [surfaceFormats.view] } : {}),
444
+ class StandardPointsLinesOwner implements PointsLinesRecordOwner {
445
+ private readonly cache = new PointsLinesExpansionCache();
446
+ private readonly preparations = new Map<string, PointsLinesPreparationAdapter>();
447
+ private readonly active = new Map<string, PointsLinesInspection>();
448
+ private readonly layoutProjection = deriveVertexLayoutProjection({
449
+ position: new Float32Array(0),
450
+ normal: new Float32Array(0),
451
+ uv: new Float32Array(0),
452
+ tangent: new Float32Array(0),
1265
453
  });
1266
- }
1267
454
 
1268
- /**
1269
- * feat-20260529-rendergraph-pass-abstraction M3 / w11 (D-2):
1270
- * per-pass mutable resource slots extracted from PipelineState per
1271
- * research Finding 3 fact-based grouping (depth / shadow / tonemap / fxaa).
1272
- * These fields are the lazily-allocated texture caches + pass-specific
1273
- * pipeline/bindgroup handles that belong to a single render pass.
1274
- * Mutable (non-readonly) because the record stage writes back after
1275
- * lazy-alloc on size drift.
1276
- */
1277
- export interface PerPassResources {
1278
- /**
1279
- * Per-frame depth attachment. Lazily allocated by
1280
- * `ensureContextConfigured` and recreated whenever the canvas resizes
1281
- * (see `ensureDepthTexture` in createRenderer.ts). Bound at the
1282
- * `beginRenderPass.depthStencilAttachment` slot with `depthLoadOp:'clear'`
1283
- * + `clearValue:1` so each frame starts with a far-plane depth buffer
1284
- * and the back-face / inside-of-cube fragments are correctly occluded
1285
- * (bug-20260519).
1286
- *
1287
- * `depthTextureView` is `null` until the canvas dimensions are known
1288
- * (first draw); the record stage skips the frame and fires
1289
- * `webgpu-runtime-error` if it stays null past configure.
1290
- */
1291
- // biome-ignore lint/suspicious/noExplicitAny: opaque RHI handle (matches sibling fields)
1292
- depthTexture: any | null;
1293
- // biome-ignore lint/suspicious/noExplicitAny: opaque RHI handle
1294
- depthTextureView: any | null;
1295
- depthTextureWidth: number;
1296
- depthTextureHeight: number;
1297
- configured: boolean;
1298
-
1299
- // ── feat-20260519-tonemap-reinhard-mvp / M2 / T-M2.5 ──────────────────────
1300
- // feat-20260621 M-A3 (D-5): the dedicated tonemap pipeline / BGL / sampler /
1301
- // params-UBO fields are deleted — the built-in tonemap now flows through the
1302
- // unified fullscreen post-process channel (`postProcess.register(
1303
- // 'forgeax::tonemap', { source, params })` + the per-frame PostProcessParams
1304
- // channel). dispatchFullscreenPass owns the pipeline (getPostProcessPipeline
1305
- // cache), the BGL + sampler (buildFullscreenPostProcessPass), and the params
1306
- // UBO (eager-created at register). Only the HDR colour/depth attachments below
1307
- // remain here — they belong to the geometry pass, not the tonemap pass.
1308
-
1309
- /**
1310
- * Lazy HDR colour + depth attachments for the opt-in tonemap path. Both
1311
- * are `null` until the first frame whose active camera carries
1312
- * `tonemap !== 'none'`; the record stage allocates them at the swap-chain
1313
- * size and re-creates on resize (mirrors `depthTexture` / `depthTextureView`
1314
- * idiom; AC-12).
1315
- *
1316
- * `hdrColor`'s format is `rgba16float` (AC-03(d)); the depth attachment is
1317
- * the same `depth24plus-stencil8` format the geometry pipelines already declare
1318
- * (`DEPTH_TEXTURE_FORMAT` SSOT in `createRenderer.ts`). When a camera
1319
- * downgrades to `tonemap === 'none'` between frames the textures stay
1320
- * allocated for cheap re-opt-in; full dispose happens via
1321
- * `Renderer.dispose()` -> browser GC (matches the `depthTexture` fix-f6
1322
- * placeholder).
1323
- */
1324
- // biome-ignore lint/suspicious/noExplicitAny: opaque RHI handle (matches sibling fields)
1325
- hdrColorTexture: any | null;
1326
- // biome-ignore lint/suspicious/noExplicitAny: opaque RHI handle
1327
- hdrColorView: any | null;
1328
- // biome-ignore lint/suspicious/noExplicitAny: opaque RHI handle
1329
- hdrDepthTexture: any | null;
1330
- // biome-ignore lint/suspicious/noExplicitAny: opaque RHI handle
1331
- hdrDepthView: any | null;
1332
- hdrTextureWidth: number;
1333
- hdrTextureHeight: number;
1334
- /**
1335
- * feat-20260604 M2 / w10: sampleCount the `hdrDepth` attachment was last
1336
- * allocated at (1 or 4). The HDR colour target is always sampled
1337
- * single-sample (`hdrColorView`); the depth pairs with the geometry colour
1338
- * target's sampleCount, which flips with `antialias === 'msaa'`. Tracking it
1339
- * lets the record stage reallocate depth when MSAA toggles between frames
1340
- * (a stale count=4 depth under a count=1 pipeline fails WebGPU validation).
1341
- */
1342
- hdrDepthSampleCount: number;
455
+ constructor(private readonly internals: RenderSystemInternals) {}
1343
456
 
1344
- // -- feat-20260528-fxaa-post-processing M2 / w7 ----------------------------------
1345
- //
1346
- // FXAA fullscreen post-process pipeline resources. Mirrors the tonemap
1347
- // pipeline shape: compiled at buildReadyWebGPU step 2 alongside the tonemap
1348
- // pipeline and stored on PipelineState for per-frame consumption in the
1349
- // record stage.
1350
- //
1351
- // D-2: 2-entry BGL (texture + sampler), no UBO. D-3: intermediate texture
1352
- // format = bgra8unorm (swap-chain storage format). D-7: antialias=0
1353
- // zero-overhead -- all FXAA fields stay null/0 until the first frame with
1354
- // antialias='fxaa'.
1355
- readonly fxaaPipeline: RenderPipeline | null;
1356
- readonly fxaaBindGroupLayout: BindGroupLayout | null;
1357
- readonly fxaaSampler: Sampler | null;
1358
-
1359
- /** Fixed two-attachment linear TAA resolve resources, owned by this renderer. */
1360
- readonly taaResolvePipeline: RenderPipeline | null;
1361
- readonly taaResolveBindGroupLayout: BindGroupLayout | null;
1362
- readonly taaResolveSampler: Sampler | null;
1363
- taaResolveParamsBuffer: Buffer | null;
1364
-
1365
- // ── feat-20260604-learn-render-4.10-anti-aliasing-msaa M2 / w7 ──────────
1366
- //
1367
- // MSAA (4x multisample) attachment slots. All `null`/0 until the first
1368
- // frame whose active camera carries `antialias === 'msaa'`; the record
1369
- // stage allocates them at the swap-chain size and re-creates on resize
1370
- // (mirrors the hdrColor size-drift idiom; D-1). When
1371
- // antialias is none/fxaa these stay null with zero allocation (C-9: MSAA
1372
- // is a per-Camera switch derived from `camera.antialias === 'msaa'`, D-6).
1373
- //
1374
- // LDR swap-chain path (tonemapActive=false):
1375
- // ONE count=4 multisample texture backs both the geometry and sprite
1376
- // sub-passes. Two views of that same texture are taken: an srgb view
1377
- // (msaaColorView, geometry pass) and an unorm view (msaaSpriteColorView,
1378
- // sprite-split sub-pass; F-1). The single texture's storage format is
1379
- // bgra8unorm, so the srgb and unorm views are both valid view formats of
1380
- // it (same trick as the non-MSAA split path). The srgb view resolves to
1381
- // the swap-chain srgb view; the unorm view resolves to the swap-chain
1382
- // unorm view. No second multisample texture is allocated.
1383
- // msaaDepth (depth24plus-stencil8, count=4) -> paired, never resolved (D-3).
1384
- //
1385
- // HDR path (tonemapActive=true): hdrColor/hdrDepth are themselves
1386
- // allocated count=4 (record stage); hdrColorResolve is the single-sample
1387
- // resolve output the tonemap/bloom passes sample (AC-03).
1388
- // biome-ignore lint/suspicious/noExplicitAny: opaque RHI handle (matches sibling fields)
1389
- msaaColorTexture: any | null;
1390
- // biome-ignore lint/suspicious/noExplicitAny: opaque RHI handle
1391
- msaaColorView: any | null;
1392
- // biome-ignore lint/suspicious/noExplicitAny: opaque RHI handle
1393
- msaaSpriteColorTexture: any | null;
1394
- // biome-ignore lint/suspicious/noExplicitAny: opaque RHI handle
1395
- msaaSpriteColorView: any | null;
1396
- // biome-ignore lint/suspicious/noExplicitAny: opaque RHI handle
1397
- msaaDepthTexture: any | null;
1398
- // biome-ignore lint/suspicious/noExplicitAny: opaque RHI handle
1399
- msaaDepthView: any | null;
1400
- msaaTextureWidth: number;
1401
- msaaTextureHeight: number;
1402
- // HDR path MSAA: the count=4 multisample rgba16float geometry target. The
1403
- // geometry + skybox passes write it; it resolves to the single-sample
1404
- // `hdrColorView` (which the tonemap / bloom passes keep sampling unchanged).
1405
- // Allocated alongside hdrColor when antialias='msaa' && tonemap!='none'.
1406
- // biome-ignore lint/suspicious/noExplicitAny: opaque RHI handle (HDR multisample target)
1407
- hdrColorMsaaTexture: any | null;
1408
- // biome-ignore lint/suspicious/noExplicitAny: opaque RHI handle
1409
- hdrColorMsaaView: any | null;
1410
-
1411
- // ── feat-20260531-skybox-env-background M3 / w15 ─────────────────────────
1412
- //
1413
- // Skybox fullscreen cubemap pipeline resources. Mirrors tonemap/fxaa
1414
- // construction: 4-entry BGL (texture_cube + sampler + View UBO + rotation UBO), fullscreen
1415
- // triangle vertex stage, cubemap-sample fragment stage. Skybox writes HDR to
1416
- // hdrColor rgba16float (targeted by the hdrColor render target); the tonemap
1417
- // pass later reads hdrColor and maps to the LDR swap-chain. Fields are null
1418
- // when the manifest has no skybox entry (D-7 optional, legacy manifests
1419
- // continue to boot).
1420
- readonly skyboxPipeline: RenderPipeline | null;
1421
- // feat-20260604 M2 / w8: count=4 multisample variant of the skybox pipeline.
1422
- // Used when the active camera carries antialias='msaa' (the skybox writes the
1423
- // count=4 hdrColorMsaa target alongside the main geometry pass). null when
1424
- // the base skybox pipeline is null (legacy manifest) or its MSAA build failed.
1425
- readonly skyboxPipelineMsaa: RenderPipeline | null;
1426
- readonly skyboxBindGroupLayout: BindGroupLayout | null;
1427
- readonly skyboxSampler: Sampler | null;
1428
- /** Stable 16 B quaternion UBO updated by the skybox record pass. */
1429
- readonly skyboxRotationBuffer: Buffer | null;
1430
-
1431
- /** Executable analytic-atmosphere producer and background resources. */
1432
- readonly atmosphereCubemapPipeline?: RenderPipeline | null;
1433
- readonly atmosphereCubemapBindGroupLayout?: BindGroupLayout | null;
1434
- readonly atmosphereIrradiancePipeline?: RenderPipeline | null;
1435
- readonly atmospherePrefilterPipeline?: RenderPipeline | null;
1436
- readonly atmosphereIblParamsBindGroupLayout?: BindGroupLayout | null;
1437
- readonly atmosphereIblSkyBindGroupLayout?: BindGroupLayout | null;
1438
- readonly atmosphereBackgroundPipeline?: RenderPipeline | null;
1439
- readonly atmosphereBackgroundBindGroupLayout?: BindGroupLayout | null;
1440
- readonly atmosphereParamsBuffer?: Buffer | null;
1441
- readonly atmosphereCubeVertexBuffer?: Buffer | null;
1442
-
1443
- // ── feat-20260520-directional-light-shadow-mapping M1c / w8 ────────────
1444
- //
1445
- // Shadow RT (depth32float) lazy-allocated per mapSize; recreated on mapSize
1446
- // drift. Used by the shadow depth pass (colorAttachments: []) and sampled
1447
- // by the main pass as @group(0) binding(3) in M2/M3. debugReadback copies
1448
- // the depth texture to a staging buffer for Inspector readback (D-2/D-5).
1449
- //
1450
- // feat-20260609 M4 / T-009: shadowCasterPipeline / shadowCasterPipelineLayout
1451
- // removed — shadow PSO now obtained via frameState.pipelineCache lookup
1452
- // (runtime.getMaterialShaderPipeline, passKind: 'shadow-caster'), same
1453
- // path as forward passes (charter P4 consistent abstraction).
1454
- // biome-ignore lint/suspicious/noExplicitAny: opaque RHI handle
1455
- shadowTexture: any | null;
1456
- shadowMapSize: number;
1457
- shadowCascadeCount: number;
1458
- /** Exact dimensions of the compiled directional cascade atlas. */
1459
- shadowAtlasWidth: number;
1460
- shadowAtlasHeight: number;
1461
- // biome-ignore lint/suspicious/noExplicitAny: opaque RHI handle
1462
- shadowSampler: any | null;
1463
- /**
1464
- * feat-20260520-directional-light-shadow-mapping M1c / w11 +
1465
- * feat-20260613-csm-cascaded-shadow-maps M5 / w28:
1466
- * latest cascade-0 lightViewProj mat4 (16 f32, col-major). Kept under
1467
- * the legacy field name as an Inspector backward-compat surface
1468
- * (`runtime.lights.directionalShadow.lightSpaceMatrix` JSON-RPC), per
1469
- * AGENTS.md §Change stance exception for external-visible wire
1470
- * protocols with known downstream consumers. Populated each frame
1471
- * from `lights.lightViewProj[0]`. Null before the first extract with
1472
- * an active shadow-casting DirectionalLight.
1473
- */
1474
- shadowLightSpaceMatrix: Float32Array | null;
1475
- /**
1476
- * feat-20260613-csm-cascaded-shadow-maps M5 / w28: full 4-cascade
1477
- * lightViewProj concatenation (4 × 16 = 64 f32, col-major) consumed by
1478
- * `debugSampleShadowFactor` so the probe can project against the selected
1479
- * cascade. Null before the first extract
1480
- * with an active shadow-casting DirectionalLight.
1481
- */
1482
- shadowCsmLightViewProj: Float32Array | null;
1483
- /**
1484
- * Producer-owned CSM receiver-selection facts. The debug probe must use the
1485
- * same view-space depth + split-plane selector as the main receiver path;
1486
- * keeping the two inputs together prevents a selector mirror from silently
1487
- * drifting at cascade boundaries.
1488
- */
1489
- shadowCsmSelection: {
1490
- readonly viewMatrix: Float32Array;
1491
- readonly splitPlanes: Float32Array;
1492
- readonly cascadeBlend: number;
1493
- } | null;
1494
- /** Producer-owned directional shadow comparison parameters for GPU probes. */
1495
- shadowLightDirection: Float32Array | null;
1496
- shadowDepthBias: number;
1497
- shadowNormalBias: number;
1498
- shadowPcfKernelSize: number;
1499
-
1500
- // ── feat-20260531-bloom-first-declarative-render-graph-pass w13/w16 ────
1501
- //
1502
- // Bloom post-processing chain: 4 passes (bright / blur-H / blur-V / composite)
1503
- // operating in HDR rgba16float domain. Pipeline handles assembled at
1504
- // buildReadyWebGPU (optional — legacy manifests without bloom.wgsl leave
1505
- // these null). Intermediate textures are graph-owned transient targets
1506
- // resolved per-frame; the bloom bind groups are identity-cached on those
1507
- // views (frameState.postProcessBgCache) and rebuild automatically when a
1508
- // resize retires the old textures.
1509
- //
1510
- // D-1: blur H/V share the same module, per-axis texelSize baked at UBO write.
1511
- // D-4: 3 distinct BGL layouts (bright/blur: 1-tex+UBO, composite: 2-tex+UBO).
1512
- // D-6: all intermediate textures use rgba16float format.
1513
- readonly bloomBrightPipeline: RenderPipeline | null;
1514
- readonly bloomBlurHPipeline: RenderPipeline | null;
1515
- readonly bloomBlurVPipeline: RenderPipeline | null;
1516
- readonly bloomCompositePipeline: RenderPipeline | null;
1517
- readonly bloomBrightBindGroupLayout: BindGroupLayout | null;
1518
- readonly bloomBlurBindGroupLayout: BindGroupLayout | null;
1519
- readonly bloomCompositeBindGroupLayout: BindGroupLayout | null;
1520
- readonly bloomSampler: Sampler | null;
1521
- readonly bloomBrightParamsBuffer: Buffer | null;
1522
- // bug-20260625: H and V blur passes need SEPARATE params UBOs. They run in
1523
- // the same frame encoder; with one shared buffer the later writeBuffer (V's
1524
- // texelSize=(0,1/h)) clobbers H's (texelSize=(1/w,0)) before the GPU runs the
1525
- // H pass, so both passes blurred vertically -> only vertical bloom, no
1526
- // horizontal spread. One buffer per axis keeps each pass's params intact.
1527
- readonly bloomBlurHParamsBuffer: Buffer | null;
1528
- readonly bloomBlurVParamsBuffer: Buffer | null;
1529
- readonly bloomCompositeParamsBuffer: Buffer | null;
1530
-
1531
- // biome-ignore lint/suspicious/noExplicitAny: opaque RHI handle
1532
- bloomBrightTexture: any | null;
1533
- // biome-ignore lint/suspicious/noExplicitAny: opaque RHI handle
1534
- bloomBrightView: any | null;
1535
- bloomBrightWidth: number;
1536
- bloomBrightHeight: number;
457
+ beginFrame(): void {
458
+ this.active.clear();
459
+ }
1537
460
 
1538
- // biome-ignore lint/suspicious/noExplicitAny: opaque RHI handle
1539
- bloomBlurHTexture: any | null;
1540
- // biome-ignore lint/suspicious/noExplicitAny: opaque RHI handle
1541
- bloomBlurHView: any | null;
1542
- bloomBlurHWidth: number;
1543
- bloomBlurHHeight: number;
461
+ prepare(
462
+ entry: import('./record/frame-snapshot').ValidatedRenderable,
463
+ clustered: boolean,
464
+ ): PointsLinesRecordSubmission | undefined {
465
+ const snapshot = entry.source.pointsLines;
466
+ if (snapshot === undefined || snapshot.component === undefined || entry.world === undefined) {
467
+ return undefined;
468
+ }
469
+ const key = `${snapshot.worldId}:${snapshot.entityKey}`;
470
+ const topology = snapshot.component === 'Points' ? 'point-list' : 'line-list';
471
+ const backend = this.backend();
472
+ const lane = backend === 'wgpu-webgl2' ? 'cpu-webgl2' : clustered ? 'clustered' : 'direct';
473
+ const meshResult = resolveAssetHandle<MeshAsset>(
474
+ entry.world,
475
+ toShared<'MeshAsset'>(entry.source.assetHandle),
476
+ );
477
+ const materialResult = resolveAssetHandle<MaterialAsset>(
478
+ entry.world,
479
+ toShared<'MaterialAsset'>(snapshot.materialHandle),
480
+ );
481
+ if (!meshResult.ok || !materialResult.ok) {
482
+ this.publishRefusal(
483
+ snapshot,
484
+ new PointsLinesMaterialUnsupportedError({
485
+ entity: snapshot.entityKey,
486
+ material: 'unresolved',
487
+ pass: 'forward',
488
+ module: 'asset-resolution',
489
+ reason: 'source MeshAsset or MaterialAsset could not be resolved',
490
+ }),
491
+ );
492
+ return undefined;
493
+ }
494
+ const admission = admitPointsLines({
495
+ entity: snapshot.entityKey,
496
+ mesh: meshResult.value,
497
+ material: materialResult.value,
498
+ ...(snapshot.style?.kind === 'points'
499
+ ? {
500
+ points: {
501
+ sizePx: snapshot.style.sizePx,
502
+ shape: snapshot.style.shape === 'circle' ? 1 : 0,
503
+ },
504
+ }
505
+ : { lines: { widthPx: snapshot.style?.widthPx ?? 1 } }),
506
+ });
507
+ if (!admission.ok) {
508
+ this.publishRefusal(snapshot, admission.error);
509
+ return undefined;
510
+ }
511
+ const geometryKey = this.cache.getOrCreate(snapshot, meshResult.value).key;
512
+ const prepKey = `${geometryKey}:${lane}:${backend}`;
513
+ let preparation = this.preparations.get(prepKey);
514
+ if (preparation === undefined) {
515
+ preparation = createPointsLinesLanePreparationAdapter(lane, backend, {
516
+ cache: this.cache,
517
+ adapter: {
518
+ create: (geometry) => {
519
+ const vertex = this.internals.device.createBuffer({
520
+ label: `points-lines-vertices:${geometryKey}`,
521
+ size: Math.max(4, geometry.vertices.byteLength),
522
+ usage: GPU_BUFFER_USAGE_VERTEX | GPU_BUFFER_USAGE_COPY_DST,
523
+ mappedAtCreation: false,
524
+ });
525
+ if (!vertex.ok) return err(vertex.error);
526
+ const index = this.internals.device.createBuffer({
527
+ label: `points-lines-indices:${geometryKey}`,
528
+ size: Math.max(4, geometry.indices.byteLength),
529
+ usage: GPU_BUFFER_USAGE_INDEX | GPU_BUFFER_USAGE_COPY_DST,
530
+ mappedAtCreation: false,
531
+ });
532
+ if (!index.ok) {
533
+ this.internals.device.destroyBuffer(vertex.value);
534
+ return err(index.error);
535
+ }
536
+ return ok({ vertexBuffer: vertex.value, indexBuffer: index.value });
537
+ },
538
+ upload: (resource, geometry) => {
539
+ if (geometry.vertices.byteLength > 0) {
540
+ const vertexWrite = this.internals.device.queue.writeBuffer(
541
+ resource.vertexBuffer,
542
+ 0,
543
+ geometry.vertices,
544
+ );
545
+ if (!vertexWrite.ok) return err(vertexWrite.error);
546
+ }
547
+ if (geometry.indices.byteLength > 0) {
548
+ const indexWrite = this.internals.device.queue.writeBuffer(
549
+ resource.indexBuffer,
550
+ 0,
551
+ geometry.indices,
552
+ );
553
+ if (!indexWrite.ok) return err(indexWrite.error);
554
+ }
555
+ return ok(geometry.derivedBytes);
556
+ },
557
+ validate: (_resource, geometry) =>
558
+ geometry.expandedVertexCount > 0 && geometry.expandedIndexCount > 0
559
+ ? ok(undefined)
560
+ : err(new Error('Points/Lines expansion contains no drawable triangles')),
561
+ destroy: (resource) => {
562
+ this.internals.device.destroyBuffer(resource.vertexBuffer);
563
+ this.internals.device.destroyBuffer(resource.indexBuffer);
564
+ },
565
+ },
566
+ });
567
+ this.preparations.set(prepKey, preparation);
568
+ }
569
+ const prepared = preparation.prepare(snapshot, meshResult.value);
570
+ if (!prepared.ok) {
571
+ const lkg = preparation.lastKnownGood();
572
+ if (lkg !== undefined) {
573
+ const lkgPlan = createPointsLinesLaneAdapter(lane, backend).createRecordPlan(
574
+ lkg.snapshot,
575
+ lkg.geometry,
576
+ );
577
+ const state = preparation.inspect();
578
+ this.active.set(
579
+ key,
580
+ inspectPointsLines({
581
+ snapshot,
582
+ topology,
583
+ lane,
584
+ pointCount: lkg.geometry.pointCount,
585
+ segmentCount: lkg.geometry.segmentCount,
586
+ sourceBytes: lkg.geometry.sourceBytes,
587
+ derivedBytes: lkg.geometry.derivedBytes,
588
+ cache: { hit: true, rebuilds: state.rebuilds, evictions: 0 },
589
+ drawCount: lkgPlan.drawCount,
590
+ uploadBytes: 0,
591
+ lastKnownGood: true,
592
+ refusal: {
593
+ code: prepared.error.code,
594
+ expected: prepared.error.expected,
595
+ hint: prepared.error.hint,
596
+ detail: prepared.error.detail,
597
+ generation: prepared.error.detail.generation,
598
+ lastKnownGood: true,
599
+ },
600
+ }),
601
+ );
602
+ return {
603
+ plan: lkgPlan,
604
+ vertexBuffer: lkg.resource.vertexBuffer,
605
+ indexBuffer: lkg.resource.indexBuffer,
606
+ layoutProjection: this.layoutProjection,
607
+ };
608
+ }
609
+ this.publishRefusal(snapshot, prepared.error);
610
+ return undefined;
611
+ }
612
+ const plan = createPointsLinesLaneAdapter(lane, backend).createRecordPlan(
613
+ snapshot,
614
+ prepared.value.geometry,
615
+ );
616
+ const state = preparation.inspect();
617
+ this.active.set(
618
+ key,
619
+ inspectPointsLines({
620
+ snapshot,
621
+ topology,
622
+ lane,
623
+ pointCount: prepared.value.geometry.pointCount,
624
+ segmentCount: prepared.value.geometry.segmentCount,
625
+ sourceBytes: prepared.value.geometry.sourceBytes,
626
+ derivedBytes: prepared.value.geometry.derivedBytes,
627
+ cache: {
628
+ hit: prepared.value.uploadedBytes === 0,
629
+ rebuilds: state.rebuilds,
630
+ evictions: 0,
631
+ },
632
+ drawCount: plan.drawCount,
633
+ uploadBytes: prepared.value.uploadedBytes,
634
+ lastKnownGood: prepared.value.lastKnownGood,
635
+ }),
636
+ );
637
+ return {
638
+ plan,
639
+ vertexBuffer: prepared.value.resource.vertexBuffer,
640
+ indexBuffer: prepared.value.resource.indexBuffer,
641
+ layoutProjection: this.layoutProjection,
642
+ };
643
+ }
1544
644
 
1545
- // biome-ignore lint/suspicious/noExplicitAny: opaque RHI handle
1546
- bloomBlurVTexture: any | null;
1547
- // biome-ignore lint/suspicious/noExplicitAny: opaque RHI handle
1548
- bloomBlurVView: any | null;
1549
- bloomBlurVWidth: number;
1550
- bloomBlurVHeight: number;
645
+ resetForDeviceLoss(): void {
646
+ for (const preparation of this.preparations.values()) preparation.resetForDeviceLoss();
647
+ this.active.clear();
648
+ }
1551
649
 
1552
- // ── feat-20260612-hdrp-ssao M6 / w26 + M8 / w37 + w38 ───────────────────
1553
- //
1554
- // SSAO post-processing chain: 2 passes (calc + blur) operating on
1555
- // half-resolution R8 single-channel textures. RenderPipeline handles
1556
- // assembled at buildReadyWebGPU (optional — manifests without
1557
- // hdrp-ssao.wgsl leave these null). Dedicated BGL (9 entries matching
1558
- // current WGSL @group(0) bindings 0-8) shared by both pipelines: calc
1559
- // and blur both bind all 9 entries; the blur uses ssaoRaw (binding 7) +
1560
- // ssaoSampler (binding 8) while the calc binds a 1x1 fallback view at
1561
- // 7 (the calc shader never samples ssaoRaw).
1562
- // plan-strategy D-A; D-D; D-E.
1563
- //
1564
- // Samplers (filtering + non-filtering depth) and the 1x1 ssaoRaw fallback
1565
- // view used by the calc pass are lazy-allocated inside the record closure
1566
- // on first frame and cached on the mutable slots below — keeps createRenderer
1567
- // free of additional state and matches bloom's lazy bind-group pattern.
1568
- readonly ssaoCalcPipeline: RenderPipeline | null;
1569
- readonly ssaoBlurPipeline: RenderPipeline | null;
1570
- readonly ssaoBgl: BindGroupLayout | null;
1571
- /** Filtering sampler shared by ssao_noise_sampler (binding 3) + ssaoSampler (binding 8). */
1572
- ssaoFilteringSampler: Sampler | null;
1573
- /** Non-filtering sampler dedicated to hdr_depth (binding 6); WebGPU validation. */
1574
- ssaoDepthSampler: Sampler | null;
1575
- /** 1x1 fallback view bound at ssaoRaw (binding 7) in the calc pass. */
1576
- // biome-ignore lint/suspicious/noExplicitAny: opaque RHI handle
1577
- ssaoFallbackRawView: any | null;
1578
- }
650
+ inspections(): readonly PointsLinesInspection[] {
651
+ return [...this.active.values()];
652
+ }
1579
653
 
1580
- export interface MeshGpuHandles {
1581
- /**
1582
- * Vertex buffer wrapped as a GpuBuffer (M-3 / w11). Consumers reach the
1583
- * raw RHI Buffer via `.handle` for `setVertexBuffer` etc.; `.destroy()`
1584
- * routes through the RHI shim's lifecycle SSOT (charter §F1 single-entry
1585
- * indexability + plan-strategy D-9 wrapper migration).
1586
- */
1587
- readonly vertexBuffer: GpuBuffer;
1588
- /**
1589
- * Index buffer wrapper, or `null` for a vertex-only mesh (no
1590
- * `MeshAsset.indices`). When `null` the record stage takes the non-indexed
1591
- * `pass.draw(vertexCount)` path and never calls `setIndexBuffer`. Gated
1592
- * on `indexed` below.
1593
- */
1594
- readonly indexBuffer: GpuBuffer | null;
1595
- /** Allocation byte size of the vertex buffer (mirrors GPUBuffer.size). */
1596
- readonly vboBytes: number;
1597
- /** Allocation byte size of the index buffer, or 0 when `indexBuffer` is null. */
1598
- readonly iboBytes: number;
1599
- readonly indexCount: number;
1600
- /**
1601
- * Index buffer format inferred from the source `MeshAsset.indices` typed
1602
- * array (`Uint16Array` -> `'uint16'`, `Uint32Array` -> `'uint32'`). The
1603
- * record stage threads this into `pass.setIndexBuffer(..., format)` so
1604
- * the GPU reads the correct stride per index. bug-20260519: the prior
1605
- * hard-coded `'uint16'` corrupted procedural meshes whose factories
1606
- * (`createBoxGeometry` etc) emit Uint32Array indices.
1607
- */
1608
- readonly indexFormat: 'uint16' | 'uint32';
1609
- /** Geometry-owned immutable projection carried through every GPU consumer. */
1610
- readonly layoutProjection: VertexLayoutProjection;
1611
- /**
1612
- * Vertex count is derived from the projection stride at upload time. The
1613
- * non-indexed draw path passes this to `pass.draw(vertexCount)`.
1614
- */
1615
- readonly vertexCount: number;
1616
- /**
1617
- * True when the mesh carries an index buffer (`MeshAsset.indices` present).
1618
- * `false` for vertex-only meshes. The record stage branches on this to pick
1619
- * `drawIndexed` vs `draw` and to gate `setIndexBuffer`.
1620
- */
1621
- readonly indexed: boolean;
1622
- /**
1623
- * Primitive topology of this mesh (first submesh's topology, default
1624
- * 'triangle-list'). WebGPU bakes topology into the immutable PSO, so the
1625
- * record stage threads this into `getMaterialShaderPipeline` to select a
1626
- * per-topology PSO (feat-20260604 M3 / w9).
1627
- *
1628
- * feat-20260608 M4 / w16: topology is per-submesh; this field reflects
1629
- * `submeshes[0].topology` for backward compat. Use `submeshes` field for
1630
- * per-submesh draw iteration.
1631
- */
1632
- readonly topology: PrimitiveTopology;
1633
- /**
1634
- * Submeshes from MeshAsset.submeshes, carried through from the GPU store
1635
- * so the record stage can iterate per-submesh drawIndexed (feat-20260608 M4 / w16).
1636
- */
1637
- readonly submeshes: readonly import('@forgeax/engine-types').Submesh[];
1638
- }
654
+ private backend(): PointsLinesBackend {
655
+ switch (this.internals.device.caps.backendKind) {
656
+ case 'wgpu-webgl2':
657
+ return 'wgpu-webgl2';
658
+ case 'null':
659
+ return 'null';
660
+ default:
661
+ return 'webgpu';
662
+ }
663
+ }
1639
664
 
1640
- const DIRECTIONAL_SHADOW_TILE_CLEAR_WGSL = /* wgsl */ `
1641
- @vertex
1642
- fn vs_main(@builtin(vertex_index) vertexIndex: u32) -> @builtin(position) vec4<f32> {
1643
- var positions = array<vec2<f32>, 3>(
1644
- vec2<f32>(-1.0, -1.0),
1645
- vec2<f32>(3.0, -1.0),
1646
- vec2<f32>(-1.0, 3.0),
1647
- );
1648
- return vec4<f32>(positions[vertexIndex], 1.0, 1.0);
665
+ private publishRefusal(
666
+ snapshot: PointsLinesRetainedSnapshot,
667
+ error: PointsLinesSourceError,
668
+ ): void {
669
+ this.active.set(
670
+ `${snapshot.worldId}:${snapshot.entityKey}`,
671
+ inspectPointsLines({
672
+ snapshot,
673
+ topology: snapshot.component === 'Points' ? 'point-list' : 'line-list',
674
+ lane: 'refused',
675
+ pointCount: 0,
676
+ segmentCount: 0,
677
+ sourceBytes: 0,
678
+ derivedBytes: 0,
679
+ cache: { hit: false, rebuilds: 0, evictions: 0 },
680
+ drawCount: 0,
681
+ uploadBytes: 0,
682
+ lastKnownGood: false,
683
+ refusal: {
684
+ code: error.code,
685
+ expected: error.expected,
686
+ hint: error.hint,
687
+ detail: error.detail,
688
+ generation: snapshot.meshGeneration,
689
+ lastKnownGood: false,
690
+ },
691
+ }),
692
+ );
693
+ }
1649
694
  }
1650
- `;
1651
695
 
1652
696
  export function createRenderSystem(internals: RenderSystemInternals): RenderSystem {
1653
697
  const phaseCatalogRegistration = internals.profiler?.registerPhaseCatalog(
@@ -1656,17 +700,15 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
1656
700
  );
1657
701
  let releaseProfilerCatalog =
1658
702
  phaseCatalogRegistration?.ok === true ? phaseCatalogRegistration.value : undefined;
1659
- // Material handles are scoped to a World. Keep resolved snapshots scoped to
1660
- // this RenderSystem and this World so Edit/Play handles can never collide;
1661
- // WeakMap also lets a dropped Play world release its cache with the world.
1662
- const materialSnapshotCachesByWorld: MaterialSnapshotCachesByWorld = new WeakMap();
703
+ let preparedWorlds: readonly World[] = [];
704
+ const pointsLinesOwner = new StandardPointsLinesOwner(internals);
1663
705
  const persistentRenderScene = new PersistentRenderScene({
1664
706
  getDevice: () => internals.device,
1665
707
  onGpuError: (error) => internals.errorRegistry.fire(error),
1666
- collectShadowDebugCandidates: () => internals.shadowDebugObservation === true,
708
+ onSharedRefMutation: (worldId, handle) => {
709
+ internals.gpuStore.invalidateMesh(handle, preparedWorlds[worldId] ?? worldId);
710
+ },
1667
711
  });
1668
- const taaHistoryStore = new TaaHistoryStore(internals.device);
1669
- const temporalFrameTransaction = new TemporalFrameTransaction(taaHistoryStore);
1670
712
  const gpuDrivenShaderFactory =
1671
713
  internals.shaderModuleFactory ??
1672
714
  ({
@@ -1679,89 +721,25 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
1679
721
  }),
1680
722
  ),
1681
723
  } satisfies import('./pipeline-builder').PipelineBuilderShaderModuleFactory);
1682
- let directionalShadowTileClear:
1683
- | { readonly device: RhiDevice; readonly pipeline: RenderPipeline }
1684
- | undefined;
1685
- const getDirectionalShadowTileClearPipeline = (): RenderPipeline | null => {
1686
- const device = internals.device;
1687
- if (directionalShadowTileClear?.device === device) {
1688
- return directionalShadowTileClear.pipeline;
1689
- }
1690
- const module = gpuDrivenShaderFactory.createShaderModule({
1691
- label: 'directional-shadow-tile-clear',
1692
- code: DIRECTIONAL_SHADOW_TILE_CLEAR_WGSL,
1693
- });
1694
- if (!module.ok) {
1695
- if (module.error.code !== 'rhi-not-available') internals.errorRegistry.fire(module.error);
1696
- return null;
1697
- }
1698
- const layout = device.createPipelineLayout({
1699
- label: 'directional-shadow-tile-clear-layout',
1700
- bindGroupLayouts: [],
1701
- });
1702
- if (!layout.ok) {
1703
- internals.errorRegistry.fire(layout.error);
1704
- return null;
1705
- }
1706
- const pipeline = device.createRenderPipeline({
1707
- label: 'directional-shadow-tile-clear',
1708
- layout: layout.value as unknown as GPUPipelineLayout,
1709
- vertex: {
1710
- module: module.value as unknown as GPUShaderModule,
1711
- entryPoint: 'vs_main',
1712
- buffers: [],
1713
- },
1714
- fragment: undefined,
1715
- primitive: { topology: 'triangle-list', cullMode: 'none' },
1716
- depthStencil: {
1717
- format: 'depth32float',
1718
- depthWriteEnabled: true,
1719
- depthCompare: 'always',
1720
- },
1721
- });
1722
- if (!pipeline.ok) {
1723
- internals.errorRegistry.fire(pipeline.error);
1724
- return null;
1725
- }
1726
- directionalShadowTileClear = { device, pipeline: pipeline.value };
1727
- return pipeline.value;
1728
- };
1729
- (
1730
- internals as unknown as {
1731
- getDirectionalShadowTileClearPipeline: typeof getDirectionalShadowTileClearPipeline;
1732
- }
1733
- ).getDirectionalShadowTileClearPipeline = getDirectionalShadowTileClearPipeline;
1734
724
  let gpuDrivenProduction = new GpuDrivenProduction(internals.device, gpuDrivenShaderFactory);
1735
- const featureGpuWork = new Map<string, RenderFeatureGpuWorkResolver>();
1736
- const getFeatureGpuWork = (featureIdentity: string): RenderFeatureGpuWorkResolver => {
1737
- const existing = featureGpuWork.get(featureIdentity);
1738
- if (existing !== undefined) return existing;
1739
- const created = createRenderFeatureGpuWorkResolver({
1740
- device: internals.device,
1741
- shaderModuleFactory:
1742
- internals.shaderModuleFactory ??
1743
- ({
1744
- createShaderModule: () =>
1745
- err(
1746
- new RhiError({
1747
- code: 'rhi-not-available',
1748
- expected: 'the renderer backend exposes shader module creation',
1749
- hint: 'construct the renderer through the backend pack before installing GPU features',
1750
- }),
1751
- ),
1752
- } satisfies import('./pipeline-builder').PipelineBuilderShaderModuleFactory),
1753
- generation: internals.featureHost?.preparedGeneration ?? 0,
1754
- featureIdentity,
1755
- });
1756
- featureGpuWork.set(featureIdentity, created);
1757
- return created;
1758
- };
725
+ const featureGpuWork: RenderFeatureGpuWorkOwner = createRenderFeatureGpuWorkOwner({
726
+ getDevice: () => internals.device,
727
+ getShaderModuleFactory: () =>
728
+ internals.shaderModuleFactory ??
729
+ ({
730
+ createShaderModule: () =>
731
+ err(
732
+ new RhiError({
733
+ code: 'rhi-not-available',
734
+ expected: 'the renderer backend exposes shader module creation',
735
+ hint: 'construct the renderer through the backend pack before activating GPU features',
736
+ }),
737
+ ),
738
+ } satisfies import('./pipeline-builder').PipelineBuilderShaderModuleFactory),
739
+ });
1759
740
  const disposeFeatureGpuWork = (): void => {
1760
- for (const resolver of featureGpuWork.values()) {
1761
- const disposed = resolver.dispose();
1762
- if (!disposed.ok) internals.errorRegistry.fire(disposed.error);
1763
- }
1764
- featureGpuWork.clear();
741
+ const disposed = featureGpuWork.dispose();
742
+ if (!disposed.ok) internals.errorRegistry.fire(disposed.error);
1765
743
  };
1766
744
  // Per-RenderSystem frame state: closure-internal frameNumber + the
1767
745
  // per-entity instance GPU buffer cache (feat-20260514 M3 / w15: the
@@ -1778,9 +756,6 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
1778
756
  // raw (material-handle-based, cross-world collision semantically correct).
1779
757
  const frameState: RenderFrameState = {
1780
758
  frameNumber: 0,
1781
- environmentGeneration: createEnvironmentGenerationState(0),
1782
- shadowSubmitModeState: createShadowSubmitModeState(),
1783
- pendingShadowCommandBuffers: [],
1784
759
  directionalShadowCache: null,
1785
760
  directionalShadowCacheRecorded: false,
1786
761
  compiledFrameGraph: null,
@@ -1790,6 +765,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
1790
765
  currentDirectionalShadowView: null,
1791
766
  currentSpotShadowView: null,
1792
767
  instanceBuffers: new Map(),
768
+ morphBuffers: new Map(),
1793
769
  hdrpClusterBinScratch: createClusterBinScratch(),
1794
770
  hdrpClusterGridScratch: null,
1795
771
  hdrpLightIndexListScratch: null,
@@ -1810,7 +786,6 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
1810
786
  // cleared (charter F1 minimal surface — the per-RenderSystem lifetime
1811
787
  // is the natural upper bound).
1812
788
  warnedMissingBaseColorTextureHandles: new Set<number>(),
1813
- reportedMaterialDiagnosticKeys: new Set<string>(),
1814
789
  // feat-20260527-sprite-nineslice M2 / w11 + M4 / w16 (AC-16): once-per-
1815
790
  // renderable guard for the runtime `nineslice.scale-too-small` metric
1816
791
  // counter (`runtime.metrics.increment(...)` in render-system-record.ts).
@@ -1842,14 +817,13 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
1842
817
  // WeakMap chain so resize-retired transient targets rebuild automatically.
1843
818
  postProcessBgCache: new WeakMap(),
1844
819
  // feat-20260601-customizable-render-pipeline-seam M1 / w7: installed-pipeline state.
1845
- // 0 = nothing installed yet (createRenderer dogfood installs the default before any
820
+ // 0 = nothing installed yet (createRenderer dogfood installs the default before a
1846
821
  // draw). activePipeline defaults to the built-in forward pipeline.
1847
822
  installedPipelineHandle: 0,
1848
- activePipelineId: URP_PIPELINE_ID,
1849
- activePipeline: urpPipeline,
823
+ activePipeline: internals.standardPipeline,
1850
824
  // feat-20260601 verify round 2: the standard forward pipeline installs with no config
1851
- // (its topology is frame-invariant). installPipeline overwrites this with the resolved
1852
- // asset's `config` on every swap so a custom pipeline reads its install-time config.
825
+ // (its topology is frame-invariant). Standard configuration overwrites this with the
826
+ // resolved asset config on every swap so the active graph reads its current config.
1853
827
  installedPipelineConfig: undefined,
1854
828
  // feat-20260608-cluster-lighting M2 / w10 + M5 / w20: HDRP active flag + once-per-frame
1855
829
  // warn dedup set for hdrp-light-budget-exceeded / hdrp-index-list-overflow.
@@ -1859,7 +833,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
1859
833
  // cube_array shadow atlas + per-frame snapshot list. Atlas is null until the
1860
834
  // first frame whose extracted lights.pointShadow is non-empty (zero-shadow
1861
835
  // scenes never allocate; AC-09); the snapshot list defaults to an empty
1862
- // tuple so the URP `addPointShadowPass` gate sees zero shadow lights as the
836
+ // tuple so the typed point-shadow graph sees zero shadow lights as the
1863
837
  // initial steady state.
1864
838
  pointShadowAtlas: null,
1865
839
  pointShadowSnapshots: [],
@@ -1917,52 +891,61 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
1917
891
  if (opened) endProfilePhase(session);
1918
892
  }
1919
893
  }
1920
- // Pipeline registry and current installation live in the RenderSystem closure
1921
- // (plan-strategy D-D: all real logic on the RenderSystem layer; the Renderer facade
1922
- // only forwards). The registry dedups same-id register (Map.has -> throw), mirroring
1923
- // ShaderRegistry.installMaterialArtifact.
1924
- const pipelineRegistry = new Map<string, RenderPipelineDef>();
1925
- const pipelineInstalls: Array<{
1926
- readonly token: object;
1927
- readonly id: string;
1928
- readonly pipeline: RenderPipelineDef;
1929
- readonly config: RenderPipelineAsset['config'];
1930
- readonly isHdrp: boolean;
1931
- }> = [];
1932
894
  let lastBuiltPipelineHandle = 0;
1933
- // Monotonic install epoch: bumped on every installPipeline call to brand the
895
+ // Monotonic configuration epoch: bumped on every Standard configuration change to brand the
1934
896
  // installed pipeline so `draw` can detect a swap and rebuild the per-frame
1935
- // graph. Replaces the prior raw-handle brand (D-19: installPipeline takes a
897
+ // graph. Replaces the prior raw-handle brand (D-19: the pipeline configuration takes a
1936
898
  // POD, no handle).
1937
899
  let installEpoch = 0;
1938
- // feat-20260604-resource-owning-render-graph-and-fullscreen-postpr M2 / w13:
1939
- // post-process shader registry (id -> PostProcessShaderEntry), parallel to pipelineRegistry.
1940
- // Dedups same-id register (Map.has -> throw), mirroring ShaderRegistry.installMaterialArtifact.
1941
- const postProcessRegistry = new Map<string, PostProcessShaderEntry>();
1942
- // CPU post-process declarations are the logical registry and remain live
1943
- // across recovery. Only device-bound buffers and pipelines are rebuilt.
900
+ // CPU post-process declarations are owned by the feature host and remain live
901
+ // across recovery. Only device-bound buffers and pipelines are rebuilt here.
1944
902
  // D-3 / D-8: per-shader params UBO resource table (id -> GPU Buffer).
1945
903
  // Eager-created at register time when entry.params is present (byteSize >= 16,
1946
904
  // defaultValue.length === byteSize); reused frame-to-frame via queue.writeBuffer.
1947
905
  const postProcessParamsBuffers = new Map<string, Buffer>();
1948
- // F-2 fix-up: expose registry lookup through the narrow runtime surface so
1949
- // addFullscreenPass's execute closure (in render-graph-primitives.ts) can
1950
- // resolve a registered shader id without reaching into RenderSystemInternals.
1951
- // Inject as a post-construction property on `internals` (typed as readonly
1952
- // optional on RenderSystemRuntime, so a one-time cast is the only path);
1953
- // every consumer that types `RenderPipelineContext.runtime` will see it.
906
+ const builtinPostProcessEntries = new Map<string, PostProcessShaderEntry>();
907
+ let activeFeaturePostProcessEntries: ReadonlyMap<string, PostProcessShaderEntry> = new Map();
1954
908
  const lookupPostProcess = (id: string): PostProcessShaderEntry | undefined =>
1955
- postProcessRegistry.get(id);
1956
- (internals as unknown as { lookupPostProcess: typeof lookupPostProcess }).lookupPostProcess =
1957
- lookupPostProcess;
909
+ builtinPostProcessEntries.get(id) ?? activeFeaturePostProcessEntries.get(id);
1958
910
  // feat-20260621 M-A2 / w8: expose the eager-created per-id params UBO through
1959
911
  // the narrow runtime surface so dispatchFullscreenPass can writeBuffer the
1960
912
  // per-frame snapshot + bind it at group(1) binding(2).
1961
913
  const getPostProcessParamsBuffer = (id: string): Buffer | undefined =>
1962
914
  postProcessParamsBuffers.get(id);
1963
- (
1964
- internals as unknown as { getPostProcessParamsBuffer: typeof getPostProcessParamsBuffer }
1965
- ).getPostProcessParamsBuffer = getPostProcessParamsBuffer;
915
+ const ensurePostProcessParamsResources = (
916
+ featureEntries: ReadonlyMap<string, PostProcessShaderEntry>,
917
+ ): void => {
918
+ const declarations = new Map<string, PostProcessShaderEntry>([
919
+ ...builtinPostProcessEntries.entries(),
920
+ ...featureEntries.entries(),
921
+ ]);
922
+ for (const [id, entry] of declarations) {
923
+ const byteSize =
924
+ entry.params?.byteSize ?? (entryHasDepthRead(entry) ? DEPTH_MIN_PARAMS_BYTE_SIZE : 0);
925
+ if (byteSize === 0 || postProcessParamsBuffers.has(id)) continue;
926
+ const created = internals.device.createBuffer({
927
+ label: `post-process-params-${id}`,
928
+ size: byteSize,
929
+ usage: GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST,
930
+ mappedAtCreation: false,
931
+ });
932
+ if (!created.ok) throw created.error;
933
+ const buffer = created.value;
934
+ if (entry.params !== undefined) {
935
+ const written = internals.device.queue.writeBuffer(buffer, 0, entry.params.defaultValue);
936
+ if (!written.ok) {
937
+ internals.device.destroyBuffer(buffer);
938
+ throw written.error;
939
+ }
940
+ }
941
+ postProcessParamsBuffers.set(id, buffer);
942
+ }
943
+ for (const [id, buffer] of postProcessParamsBuffers) {
944
+ if (declarations.has(id)) continue;
945
+ internals.device.destroyBuffer(buffer);
946
+ postProcessParamsBuffers.delete(id);
947
+ }
948
+ };
1966
949
  // feat-20260609 M4 / T-10-a: post-process pipeline cache (id|colorFormat -> RhiRenderPipeline).
1967
950
  // Solves CONCERN-1: dispatcher previously passed `pipeline=null` to
1968
951
  // built.createHandle because per-frame execute closures cannot await async
@@ -1978,7 +961,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
1978
961
  const key = `${id}|${colorFormat}`;
1979
962
  const cached = postProcessPipelineCache.get(key);
1980
963
  if (cached !== undefined) return cached;
1981
- const entry = postProcessRegistry.get(id);
964
+ const entry = lookupPostProcess(id);
1982
965
  if (entry === undefined) return null;
1983
966
  const factory = internals.buildPostProcessPipeline;
1984
967
  if (factory === undefined) return null;
@@ -1987,9 +970,11 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
1987
970
  postProcessPipelineCache.set(key, built);
1988
971
  return built;
1989
972
  };
1990
- (
1991
- internals as unknown as { getPostProcessPipeline: typeof getPostProcessPipeline }
1992
- ).getPostProcessPipeline = getPostProcessPipeline;
973
+ Object.assign(internals, {
974
+ lookupPostProcess,
975
+ getPostProcessParamsBuffer,
976
+ getPostProcessPipeline,
977
+ });
1993
978
  // w15 M5 (plan-strategy D-P4 / AC-07): per-frame dispatch counters. Reset
1994
979
  // on every `draw([world], { cameraOwner: 0, resourceOwner: 0 })` entry; bumped once per actual `pass.setPipeline`
1995
980
  // dispatch in render-system-record.ts. Two-way split mirrors the two
@@ -2007,12 +992,10 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2007
992
  createBindGroup: 0,
2008
993
  keys: [],
2009
994
  };
2010
- let preparedWorlds: readonly World[] = [];
2011
995
  const preparedPipelineIds = new WeakMap<object, string>();
2012
996
  const preparedMaterialPipelineShaders = new WeakMap<object, string>();
2013
997
  const preparedGroup0Pipelines = new WeakSet<object>();
2014
998
  const preparedViewOnlyPipelines = new WeakSet<object>();
2015
- const preparedViewWithResourcePipelines = new WeakSet<object>();
2016
999
  const preparedRenderMaterialPipelines = new WeakSet<object>();
2017
1000
  const preparedAssetHandles = new WeakMap<World, Map<string, number>>();
2018
1001
  const preparedHandle = <Brand extends string>(
@@ -2046,31 +1029,9 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2046
1029
  if (world === undefined || material?.kind !== 'material' || pipelineState === null) {
2047
1030
  return err(new Error('prepared material asset is unavailable'));
2048
1031
  }
2049
- const schemaProjection = internals.getParamSchemaProjection?.(materialShaderId);
2050
- const schema = schemaProjection?.schema ?? internals.getParamSchema?.(materialShaderId) ?? [];
1032
+ const schema = internals.getParamSchema?.(materialShaderId) ?? [];
2051
1033
  const values = material.values ?? {};
2052
- const derived =
2053
- schemaProjection?.derivedInterface ??
2054
- deriveParamSchemaForCensus(
2055
- schema,
2056
- internals.paramSchemaProjectionCensus,
2057
- 'prepared-material',
2058
- );
2059
- if (internals.paramSchemaProjectionCensus !== undefined && schemaProjection !== undefined) {
2060
- internals.paramSchemaProjectionCensus.record(
2061
- 'registered-projection-read',
2062
- 'prepared-material',
2063
- );
2064
- if (
2065
- schemaProjection.schema.length === 0 &&
2066
- Object.keys(values).some((name) => name.endsWith('Texture'))
2067
- ) {
2068
- internals.paramSchemaProjectionCensus.record(
2069
- 'empty-schema-legacy-inference',
2070
- 'prepared-material',
2071
- );
2072
- }
2073
- }
1034
+ const derived = derive(schema);
2074
1035
  const fields = [...derived.textureFieldNames];
2075
1036
  let buffer: Buffer | undefined;
2076
1037
  let payloadByteLength = 0;
@@ -2080,7 +1041,6 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2080
1041
  payload,
2081
1042
  schema,
2082
1043
  values as Readonly<Record<string, number | readonly number[]>>,
2083
- internals.paramSchemaProjectionCensus,
2084
1044
  );
2085
1045
  const created = internals.device.createBuffer({
2086
1046
  label: `prepared-material:${materialGuid}`,
@@ -2111,13 +1071,9 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2111
1071
  if (textureValue?.sampler !== undefined) {
2112
1072
  const handle = preparedHandle(world, String(textureValue.sampler), 'SamplerAsset');
2113
1073
  if (handle !== undefined) {
2114
- const asset = resolveRenderAsset<SamplerAsset>(world, handle);
1074
+ const asset = resolveAssetHandle<SamplerAsset>(world, handle);
2115
1075
  if (asset.ok) {
2116
- const resident = internals.gpuStore.ensureSamplerResident(
2117
- handle,
2118
- asset.value,
2119
- worldIndex,
2120
- );
1076
+ const resident = internals.gpuStore.ensureSamplerResident(handle, asset.value, world);
2121
1077
  if (resident.ok) sampler = resident.value;
2122
1078
  }
2123
1079
  }
@@ -2126,8 +1082,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2126
1082
  if (textureValue !== undefined) {
2127
1083
  const handle = preparedHandle(world, String(textureValue.texture), 'TextureAsset');
2128
1084
  if (handle !== undefined) {
2129
- view =
2130
- residentTextureView(world, internals.gpuStore, internals, handle, worldIndex) ?? view;
1085
+ view = residentTextureView(world, internals.gpuStore, internals, handle) ?? view;
2131
1086
  }
2132
1087
  }
2133
1088
  textureResources.push({ sampler, view });
@@ -2220,8 +1175,10 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2220
1175
  let lastMeshMaterialBindingFrame: ExtractedFrame | undefined;
2221
1176
  const preparedResolverFactory = (
2222
1177
  input: RenderFeaturePreparedGraphicsResolverInput,
2223
- ): PreparedGraphicsResolver =>
2224
- createPreparedGraphicsResolver({
1178
+ ): PreparedGraphicsResolver => {
1179
+ activeFeaturePostProcessEntries = input.fullscreenEffects;
1180
+ ensurePostProcessParamsResources(input.fullscreenEffects);
1181
+ return createPreparedGraphicsResolver({
2225
1182
  device: internals.device,
2226
1183
  featureIdentity: input.featureIdentity,
2227
1184
  generation: input.generation,
@@ -2229,9 +1186,11 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2229
1186
  featureOrder: input.order,
2230
1187
  lookup: input.lookup,
2231
1188
  resolveGpuBuffer: (reference) =>
2232
- getFeatureGpuWork(input.featureIdentity).resolveBuffer(reference),
1189
+ featureGpuWork.resolveBuffer(input.featureIdentity, reference),
2233
1190
  resolvePipeline: (descriptor) => {
2234
- const postProcessEntry = internals.lookupPostProcess?.(descriptor.shader);
1191
+ const postProcessEntry =
1192
+ input.fullscreenEffects.get(descriptor.shader) ??
1193
+ builtinPostProcessEntries.get(descriptor.shader);
2235
1194
  if (postProcessEntry !== undefined) {
2236
1195
  const fullscreen = buildFullscreenPostProcessPass(
2237
1196
  { device: internals.device, errorRegistry: internals.errorRegistry },
@@ -2286,8 +1245,6 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2286
1245
  preparedGroup0Pipelines.add(pipeline as object);
2287
1246
  } else if (bindingContract === 'view-only') {
2288
1247
  preparedViewOnlyPipelines.add(pipeline as object);
2289
- } else if (bindingContract === 'view-with-resource') {
2290
- preparedViewWithResourcePipelines.add(pipeline as object);
2291
1248
  } else {
2292
1249
  preparedRenderMaterialPipelines.add(pipeline as object);
2293
1250
  }
@@ -2305,6 +1262,9 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2305
1262
  ? err(new Error('prepared group-0 resource pipeline requires a scene target'))
2306
1263
  : ok(undefined);
2307
1264
  }
1265
+ if (bindingContract === 'view-and-scene-depth') {
1266
+ return ok(undefined);
1267
+ }
2308
1268
  if (preparedGroup0Pipelines.has(pipeline as object)) {
2309
1269
  const layout =
2310
1270
  (materialShaderId === undefined
@@ -2321,7 +1281,6 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2321
1281
  }
2322
1282
  if (
2323
1283
  preparedViewOnlyPipelines.has(pipeline as object) ||
2324
- preparedViewWithResourcePipelines.has(pipeline as object) ||
2325
1284
  (preparedRenderMaterialPipelines.has(pipeline as object) &&
2326
1285
  descriptor.values.group === 0) ||
2327
1286
  pipeline === internals.getPipelineState()?.unlitPipeline
@@ -2396,60 +1355,29 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2396
1355
  });
2397
1356
  },
2398
1357
  });
1358
+ };
2399
1359
  return {
2400
- applyShadowSubmitMode(
2401
- mode: ShadowSubmitMode,
2402
- rendererGeneration: string,
2403
- ): ShadowSubmitResult<ShadowSubmitModeState> {
2404
- const result = applyShadowSubmitMode(
2405
- frameState.shadowSubmitModeState,
2406
- mode,
2407
- rendererGeneration,
2408
- );
2409
- if (result.ok) frameState.shadowSubmitModeState = result.value;
2410
- return result;
2411
- },
2412
- restoreShadowSubmitMode(rendererGeneration: string): ShadowSubmitResult<ShadowSubmitModeState> {
2413
- const result = restoreShadowSubmitMode(frameState.shadowSubmitModeState, rendererGeneration);
2414
- if (result.ok) {
2415
- frameState.shadowSubmitModeState = result.value;
2416
- frameState.pendingShadowCommandBuffers = [];
2417
- }
2418
- return result;
2419
- },
2420
- getShadowSubmitMode(): ShadowSubmitModeState {
2421
- return frameState.shadowSubmitModeState;
2422
- },
2423
1360
  releaseProfilerCatalog(): void {
2424
1361
  releaseProfilerCatalog?.();
2425
1362
  releaseProfilerCatalog = undefined;
2426
1363
  },
2427
1364
  get renderScene(): RenderSceneInspection {
2428
- const temporal = projectTemporalInspection(temporalFrameTransaction.inspect());
2429
1365
  return {
2430
1366
  ...persistentRenderScene.inspect(),
2431
1367
  gpuDriven: gpuDrivenProduction.inspect(),
2432
- temporal,
2433
- environment: inspectEnvironmentGeneration(frameState.environmentGeneration, {
2434
- sourceWorldId: environmentConsumerSnapshot(frameState.environmentGeneration)?.source
2435
- ?.worldId,
2436
- sourceEntity: environmentConsumerSnapshot(frameState.environmentGeneration)?.source
2437
- ?.entity,
2438
- taa: {
2439
- activeSignature: temporal.environmentSignature,
2440
- publishCount: temporal.environmentPublishCount,
2441
- resetCount: temporal.environmentResetCount,
2442
- lastResetReason: temporal.resetReason,
2443
- },
2444
- }),
2445
1368
  };
2446
1369
  },
2447
- detachWorld(world: World): void {
2448
- taaHistoryStore.invalidate('world-detached');
1370
+ get pointsLinesSnapshots(): readonly PointsLinesRetainedSnapshot[] {
1371
+ return persistentRenderScene.pointsLinesSnapshots();
1372
+ },
1373
+ detachScene(world: World): void {
2449
1374
  persistentRenderScene.detach(world);
2450
- materialSnapshotCachesByWorld.delete(world);
2451
1375
  },
2452
- draw(worlds: readonly World[], opts: DrawOwnerOptions): boolean {
1376
+ draw(
1377
+ worlds: readonly World[],
1378
+ opts: DrawOwnerOptions,
1379
+ renderReadLeases?: readonly RenderReadLease[],
1380
+ ): boolean {
2453
1381
  preparedWorlds = worlds;
2454
1382
  const profileSession = internals.profiler?.activeSession();
2455
1383
  let ownsProfileFrame = false;
@@ -2462,6 +1390,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2462
1390
  }
2463
1391
  }
2464
1392
  try {
1393
+ pointsLinesOwner.beginFrame();
2465
1394
  // cameraOwner drives the surfaced cameras + frustum
2466
1395
  // cull; resourceOwner drives skylight/skybox/postProcess + per-world
2467
1396
  // record config.
@@ -2471,7 +1400,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2471
1400
  // replacement retires it atomically in ensureCompiledFrameGraph.
2472
1401
  if (frameState.installedPipelineHandle !== lastBuiltPipelineHandle) {
2473
1402
  // Feature contributions describe the active pipeline graph. Drop the
2474
- // old composition before the next frame re-runs feature contribution
1403
+ // old graph before the next frame re-runs feature contribution
2475
1404
  // so a hot-swap cannot reuse passes compiled for the retired pipeline.
2476
1405
  resetRenderFeatureGraphState(internals);
2477
1406
  lastBuiltPipelineHandle = frameState.installedPipelineHandle;
@@ -2502,183 +1431,35 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2502
1431
  // are resource-owner reads, not camera reads).
2503
1432
  const resourceWorld = worlds[resourceOwner] as World;
2504
1433
  const frame = runProfiledRenderPhase(profileSession, 'extract', () => {
2505
- const ordinaryWorld =
2506
- worlds.length === 1 && cameraOwner === 0 && resourceOwner === 0 ? worlds[0] : undefined;
2507
- if (ordinaryWorld === undefined) {
2508
- return persistentRenderScene.extractComposition(
2509
- worlds,
2510
- { cameraOwner, resourceOwner },
2511
- internals.assets.epoch,
2512
- () =>
2513
- extractFrames(
2514
- worlds,
2515
- { cameraOwner, resourceOwner },
2516
- internals.assets,
2517
- internals.getPipelineState(),
2518
- internals.gpuStore,
2519
- materialSnapshotCachesByWorld,
2520
- {
2521
- cull: 'none',
2522
- ...(internals.paramSchemaProjectionCensus === undefined
2523
- ? {}
2524
- : { paramSchemaProjectionCensus: internals.paramSchemaProjectionCensus }),
2525
- },
2526
- ),
2527
- (world, worldId, entity, currentFrame) => {
2528
- let materialSnapshotCache = materialSnapshotCachesByWorld.get(world);
2529
- if (materialSnapshotCache === undefined) {
2530
- materialSnapshotCache = new Map();
2531
- materialSnapshotCachesByWorld.set(world, materialSnapshotCache);
2532
- }
2533
- const visibility = currentFrame.featureVisibilitySnapshots.find(
2534
- (entry) => entry.world === world,
2535
- )?.snapshot ?? {
2536
- diagnostics: [],
2537
- hasAnyIntent: false,
2538
- hasAnyHiddenIntent: false,
2539
- get: () => undefined,
2540
- effective: () => 'visible' as const,
2541
- };
2542
- const extracted = extractRenderableEntity(world, entity, {
2543
- assets: internals.assets,
2544
- pipelineState: internals.getPipelineState(),
2545
- gpuStore: internals.gpuStore,
2546
- materialSnapshotCache,
2547
- ...(internals.paramSchemaProjectionCensus === undefined
2548
- ? {}
2549
- : { paramSchemaProjectionCensus: internals.paramSchemaProjectionCensus }),
2550
- cull: 'none',
2551
- cullCameras: undefined,
2552
- hierarchy: {
2553
- parentOf: new Map(),
2554
- diagnostics: [],
2555
- getParent: () => undefined,
2556
- },
2557
- visibility,
2558
- });
2559
- if (extracted === undefined) return undefined;
2560
- return {
2561
- snapshot:
2562
- extracted.snapshot.worldId === worldId
2563
- ? extracted.snapshot
2564
- : { ...extracted.snapshot, worldId },
2565
- candidateDispatch: extracted.candidateDispatch,
2566
- };
2567
- },
2568
- );
2569
- }
2570
- const pipelineState = internals.getPipelineState();
2571
- pipelineState?.skinPaletteAllocator?.resetForFrame();
2572
- let materialSnapshotCache = materialSnapshotCachesByWorld.get(ordinaryWorld);
2573
- if (materialSnapshotCache === undefined) {
2574
- materialSnapshotCache = new Map();
2575
- materialSnapshotCachesByWorld.set(ordinaryWorld, materialSnapshotCache);
2576
- }
2577
- return persistentRenderScene.extract(
2578
- ordinaryWorld,
2579
- internals.assets.epoch,
1434
+ return persistentRenderScene.extractComposition(
1435
+ worlds,
1436
+ { cameraOwner, resourceOwner },
1437
+ internals.assets.catalogEpoch,
2580
1438
  () =>
2581
- extractFrame(
2582
- ordinaryWorld,
2583
- prepareExtractContext(ordinaryWorld, {
2584
- assets: internals.assets,
2585
- pipelineState,
2586
- gpuStore: internals.gpuStore,
2587
- materialSnapshotCache,
2588
- ...(internals.paramSchemaProjectionCensus === undefined
2589
- ? {}
2590
- : { paramSchemaProjectionCensus: internals.paramSchemaProjectionCensus }),
2591
- cull: 'none',
2592
- }),
2593
- ),
2594
- (snapshot, materialHandle) =>
2595
- refreshRenderableMaterialSnapshot(
2596
- snapshot,
2597
- materialHandle,
2598
- ordinaryWorld,
1439
+ extractFrames(
1440
+ worlds,
1441
+ { cameraOwner, resourceOwner },
2599
1442
  internals.assets,
2600
- internals.gpuStore,
2601
- materialSnapshotCache,
2602
- internals.paramSchemaProjectionCensus,
1443
+ internals.getPipelineState(),
1444
+ persistentRenderScene.materialSnapshotCacheStore(),
1445
+ { cull: 'none' },
2603
1446
  ),
2604
- (current) => refreshExtractedFrameCameras(ordinaryWorld, current),
2605
- `${internals.gpuStore.meshResidencyEpoch}:${frameState.isHdrpActive ? 'hdrp' : 'urp'}`,
2606
- (current) => refreshExtractedFramePostProcessParams(ordinaryWorld, current),
2607
- (current) => refreshExtractedFrameLights(ordinaryWorld, current),
2608
- (entity: EntityHandle, currentFrame) =>
2609
- extractRenderableEntity(ordinaryWorld, entity, {
2610
- assets: internals.assets,
2611
- pipelineState,
2612
- gpuStore: internals.gpuStore,
2613
- materialSnapshotCache,
2614
- cull: 'none',
2615
- cullCameras: undefined,
2616
- hierarchy: {
2617
- parentOf: new Map(),
2618
- diagnostics: [],
2619
- getParent: () => undefined,
2620
- },
2621
- visibility: currentFrame.visibilitySnapshots[0] ?? {
2622
- diagnostics: [],
2623
- hasAnyIntent: false,
2624
- hasAnyHiddenIntent: false,
2625
- get: () => undefined,
2626
- effective: () => 'visible',
2627
- },
2628
- }),
1447
+ renderReadLeases,
2629
1448
  );
2630
1449
  });
2631
- const fogSelection = selectFog(resourceWorld, resourceOwner);
2632
- if (fogSelection.ok) {
2633
- persistentRenderScene.stageFogCandidate(fogSelection.value);
2634
- } else {
2635
- // Fog authoring failures are renderer-owned structured errors. Keep
2636
- // the retained LKG candidate while publishing the same error object
2637
- // through Renderer.onError for AI recovery.
2638
- internals.errorRegistry.fire(fogSelection.error);
2639
- persistentRenderScene.stageFogFailure({
2640
- code: fogSelection.error.code,
2641
- resourceOwner,
2642
- });
2643
- }
2644
1450
  const {
2645
1451
  cameras,
2646
1452
  lights,
2647
- renderables: extractedRenderables,
1453
+ renderables,
2648
1454
  dispatch,
2649
- environment,
1455
+ skylight,
1456
+ skylightCount,
1457
+ skybox,
1458
+ skyboxCount,
2650
1459
  frustumStats,
2651
1460
  visibilityStats,
2652
1461
  postProcessParams,
2653
1462
  } = frame;
2654
- const temporalProjection = persistentRenderScene.temporalProjection(worlds);
2655
- const renderables =
2656
- temporalProjection === undefined
2657
- ? extractedRenderables
2658
- : extractedRenderables.map((snapshot) => temporalProjection.withTemporal(snapshot));
2659
- const skinPaletteAllocator = internals.getPipelineState()?.skinPaletteAllocator;
2660
- if (skinPaletteAllocator !== null && skinPaletteAllocator !== undefined) {
2661
- for (const renderable of renderables) {
2662
- const skin = renderable.skin;
2663
- if (skin === undefined) continue;
2664
- skinPaletteAllocator.writePreviousJointPalette(
2665
- skin,
2666
- renderable.temporal?.previousSkin?.jointMatrices ?? skin.jointMatrices,
2667
- );
2668
- }
2669
- }
2670
- const gpuDrivenVisibilityScene =
2671
- worlds.length === 1 && cameraOwner === 0 && resourceOwner === 0
2672
- ? persistentRenderScene.gpuDrivenState(worlds[0])
2673
- : persistentRenderScene.compositionGpuDrivenState();
2674
- // Publish one main + three-cascade visibility result before record.
2675
- // The production batch topology joins these stable IDs once and both
2676
- // indirect-capable and direct-RHI executors consume that submission.
2677
- gpuDrivenProduction.prepareCpuVisibility({
2678
- scene: gpuDrivenVisibilityScene,
2679
- frame,
2680
- camera: cameras[0],
2681
- });
2682
1463
  if (lastMeshMaterialBindingFrame !== frame) {
2683
1464
  lastMeshMaterialBindingFrame = frame;
2684
1465
  lastMeshMaterialBindings = renderables.map((renderable) => ({
@@ -2731,13 +1512,13 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2731
1512
  preparedPipelineState === null || cameras[0] === undefined
2732
1513
  ? []
2733
1514
  : resolveStandardRenderFeatureTargets({
2734
- pipelineId: frameState.isHdrpActive ? 'forgeax::hdrp' : 'forgeax::urp',
2735
1515
  tonemap: cameras[0].tonemap,
2736
1516
  antialias: cameras[0].antialias,
2737
1517
  colorAttachmentFormat: preparedPipelineState.colorAttachmentFormat,
2738
1518
  storageBuffer: internals.device.caps.storageBuffer,
2739
1519
  multisample: internals.device.caps.backendKind !== 'wgpu-webgl2',
2740
1520
  });
1521
+ let featureGraphCandidate: RenderFeatureGraphCandidate | undefined;
2741
1522
  const preparedResourceBatches = runProfiledRenderPhase(profileSession, 'features', () => {
2742
1523
  if (internals.featureHost === undefined) return [];
2743
1524
  const featureFrame = runRenderFeatureFrame(internals.featureHost, {
@@ -2749,54 +1530,32 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2749
1530
  targets: featureTargets,
2750
1531
  generation: internals.featureHost?.preparedGeneration ?? 0,
2751
1532
  caps: internals.device.caps,
2752
- createContributionStaging: (
2753
- identity,
2754
- order,
2755
- validateGraphics,
2756
- resolveGraphics,
2757
- resolveGpuCompute,
2758
- ) =>
2759
- createRenderFeatureContributionStaging<RenderFeaturePassContext>(
2760
- identity,
2761
- order,
2762
- validateGraphics,
2763
- resolveGraphics,
2764
- resolveGpuCompute,
2765
- ),
2766
1533
  createPreparedGraphicsResolver: preparedResolverFactory,
2767
- ...(internals.getMaterialShaderBindingContract === undefined
2768
- ? {}
2769
- : { getMaterialShaderBindingContract: internals.getMaterialShaderBindingContract }),
2770
- resolveMaterialAsset: (material) => resolveMaterialFromPort(material, internals.assets),
2771
- getGpuWorkResolver: getFeatureGpuWork,
1534
+ gpuWork: featureGpuWork,
2772
1535
  });
1536
+ activeFeaturePostProcessEntries = featureFrame.fullscreenEffects;
1537
+ ensurePostProcessParamsResources(featureFrame.fullscreenEffects);
2773
1538
  lastVisibilityStats.explicitlyHidden = featureFrame.hiddenEntityReports.length;
2774
1539
  for (const featureError of featureFrame.errors) {
2775
1540
  if (!isPendingRenderFeaturePreparation(featureError)) {
2776
1541
  internals.errorRegistry.fire(featureError);
2777
1542
  }
2778
1543
  }
2779
- const featureGraphState = getRenderFeatureGraphState(internals);
2780
- const merged = mergeRenderFeatureContributions(featureFrame.contributions);
2781
- if (!merged.ok) {
2782
- reportRenderFeatureGraphError(internals, merged.error);
2783
- if (featureGraphState.composition !== undefined) {
2784
- retireCompiledFrameGraph(frameState, taaHistoryStore);
2785
- featureGraphState.composition = undefined;
2786
- }
2787
- featureGraphState.contributions = [];
2788
- featureGraphState.topologySignature = undefined;
2789
- } else {
2790
- const topologyChanged =
2791
- featureGraphState.topologySignature !== undefined &&
2792
- featureGraphState.topologySignature !== merged.value.topologySignature;
2793
- featureGraphState.contributions = featureFrame.contributions;
2794
- featureGraphState.topologySignature = merged.value.topologySignature;
2795
- if (topologyChanged) {
2796
- retireCompiledFrameGraph(frameState, taaHistoryStore);
2797
- featureGraphState.composition = undefined;
2798
- }
2799
- }
1544
+ featureGraphCandidate = {
1545
+ plans: featureFrame.plans,
1546
+ fullscreenEffects: featureFrame.fullscreenEffects,
1547
+ ...(featureFrame.preparedResourceBatches.length === 0
1548
+ ? {}
1549
+ : { preparedResourceKey: `frame-${frameState.frameNumber}` }),
1550
+ onRejected: () => {
1551
+ for (const batch of featureFrame.preparedResourceBatches) {
1552
+ const released = batch.release();
1553
+ if (!released.ok) {
1554
+ internals.errorRegistry.fire(released.error);
1555
+ }
1556
+ }
1557
+ },
1558
+ };
2800
1559
  return featureFrame.preparedResourceBatches;
2801
1560
  });
2802
1561
 
@@ -2809,11 +1568,6 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2809
1568
  const orderedDispatch = runProfiledRenderPhase(profileSession, 'sort', () =>
2810
1569
  sortTransparentDispatch(dispatch, resourceWorld, cameras, renderables),
2811
1570
  );
2812
- const temporalCoverage = sceneTemporalCoverage({
2813
- renderables,
2814
- dispatch: orderedDispatch,
2815
- features: getRenderFeatureGraphState(internals).contributions,
2816
- });
2817
1571
 
2818
1572
  // M3 / w26: single dispatch list replaces old three-bucket model.
2819
1573
  // Pass dispatch to recordFrame — the record stage iterates dispatch
@@ -2822,17 +1576,17 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2822
1576
  // feat-20260709-editor-world-partition ENGINE-fix-round2 (defect 2):
2823
1577
  // the record stage must resolve each renderable's mesh + material
2824
1578
  // textures against the world it was EXTRACTED from — never the single
2825
- // resourceWorld. PersistentRenderScene remaps extractFrames' temporary
2826
- // array index to renderer-local WorldRenderId before record; resolving
2827
- // everything against resourceWorld would make a shared mesh in the
2828
- // cameraOwner world
1579
+ // resourceWorld. The extract stage already resolves per-world
1580
+ // (extractFrames loops worlds[] and stamps RenderableSnapshot.worldId);
1581
+ // the record stage regressed to resolving everything against
1582
+ // resourceWorld, so a user-tier mesh living in the cameraOwner world
2829
1583
  // (e.g. an editor gizmo handle) resolves against resourceWorld's
2830
1584
  // sharedRefs — either a miss (asset-not-registered) or, when that slot
2831
- // is occupied by an unrelated payload (equirect), a
1585
+ // is occupied by an unrelated user-tier payload (equirect), a
2832
1586
  // wrong-kind resolve that throws in ensureResident. `worlds` is threaded
2833
- // so the record stage receives the stable WorldRenderId lookup.
2834
- // `resourceWorld` stays the singleton-resource owner (skybox equirect /
2835
- // transparent-sort config / video provider) — those ARE resource-owner reads.
1587
+ // so the record stage can index worlds[renderable.worldId]. `resourceWorld`
1588
+ // stays the singleton-resource owner (skybox equirect / transparent-sort
1589
+ // config / video provider) — those ARE resource-owner reads.
2836
1590
  const recordProfilePhase: RecordProfileRunner | undefined =
2837
1591
  profileSession === undefined || profileSession.detail === 'owner'
2838
1592
  ? undefined
@@ -2862,43 +1616,27 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2862
1616
  frameState,
2863
1617
  dispatchCounts,
2864
1618
  bindGroupCounts,
2865
- environment,
1619
+ skylight,
1620
+ skylightCount,
1621
+ skybox,
1622
+ skyboxCount,
2866
1623
  postProcessParams,
2867
1624
  worlds,
2868
1625
  recordProfilePhase,
2869
1626
  {
2870
1627
  owner: gpuDrivenProduction,
2871
- scene:
2872
- worlds.length === 1 && cameraOwner === 0 && resourceOwner === 0
2873
- ? persistentRenderScene.gpuDrivenState(worlds[0])
2874
- : persistentRenderScene.compositionGpuDrivenState(),
1628
+ scene: persistentRenderScene.compositionGpuDrivenState(),
2875
1629
  onCoverage: (ownsAll) => {
2876
1630
  const key = `${internals.gpuStore.meshResidencyEpoch}:${frameState.isHdrpActive ? 'hdrp' : 'urp'}`;
2877
- if (worlds.length === 1 && cameraOwner === 0 && resourceOwner === 0) {
2878
- const ordinaryWorld = worlds[0];
2879
- if (ordinaryWorld !== undefined) {
2880
- persistentRenderScene.setGpuDrivenCoverage(ordinaryWorld, key, ownsAll);
2881
- }
2882
- } else {
2883
- persistentRenderScene.setCompositionGpuDrivenCoverage(key, ownsAll);
2884
- }
1631
+ persistentRenderScene.setCompositionGpuDrivenCoverage(key, ownsAll);
2885
1632
  },
2886
1633
  },
2887
- persistentRenderScene.worldForRenderId,
2888
- temporalProjection === undefined
2889
- ? undefined
2890
- : {
2891
- transaction: temporalFrameTransaction,
2892
- projection: temporalProjection,
2893
- coverage: temporalCoverage,
2894
- ...(fogSelection.ok
2895
- ? { reactiveRevision: fogSelection.value.snapshot?.revision }
2896
- : {}),
2897
- onSceneSubmitted: (delta) => persistentRenderScene.stageTemporalSubmission(delta),
2898
- },
2899
- fogSelection.ok ? fogSelection.value.snapshot : persistentRenderScene.fogSnapshot(),
1634
+ renderReadLeases,
1635
+ featureGraphCandidate,
1636
+ pointsLinesOwner,
2900
1637
  ),
2901
1638
  );
1639
+ persistentRenderScene.setPointsLinesInspections(pointsLinesOwner.inspections());
2902
1640
  if (internals.featureHost !== undefined && preparedResourceBatches.length > 0) {
2903
1641
  const batches = preparedResourceBatches;
2904
1642
  if (submitted) {
@@ -2915,25 +1653,19 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2915
1653
  }
2916
1654
  }
2917
1655
  } catch (err) {
2918
- if (isStructuredRendererError(err)) {
2919
- internals.errorRegistry.fire(err);
2920
- } else {
2921
- const innerError =
2922
- err instanceof RhiError
2923
- ? err
2924
- : { code: 'unknown' as const, message: String(err), name: (err as Error)?.name };
2925
- internals.errorRegistry.fire(
2926
- new RhiError({
2927
- code: 'webgpu-runtime-error',
2928
- expected: 'RenderSystem to record one frame without an internal exception',
2929
- hint: 'inspect detail.error for the underlying cause; next frame will retry',
2930
- detail: { error: innerError },
2931
- }),
2932
- );
2933
- }
1656
+ const innerError =
1657
+ err instanceof RhiError
1658
+ ? err
1659
+ : { code: 'unknown' as const, message: String(err), name: (err as Error)?.name };
1660
+ internals.errorRegistry.fire(
1661
+ new RhiError({
1662
+ code: 'webgpu-runtime-error',
1663
+ expected: 'RenderSystem to record one frame without an internal exception',
1664
+ hint: 'inspect detail.error for the underlying cause; next frame will retry',
1665
+ detail: { error: innerError },
1666
+ }),
1667
+ );
2934
1668
  } finally {
2935
- if (submitted) persistentRenderScene.stageFogSubmission();
2936
- else persistentRenderScene.discardFogCandidate();
2937
1669
  if (ownsProfileFrame) {
2938
1670
  try {
2939
1671
  profileSession?.endFrame();
@@ -2958,158 +1690,102 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
2958
1690
  get perFramePassNames(): readonly string[] {
2959
1691
  return frameState.compiledFrameGraph?.inspect().passes.map((pass) => pass.name) ?? [];
2960
1692
  },
2961
- // biome-ignore lint/suspicious/noExplicitAny: opaque RHI handle
2962
- getCurrentShadowView(): any | null {
2963
- return frameState.currentDirectionalShadowView;
2964
- },
2965
- registerPipeline(id: string, impl: RenderPipelineDef): void {
2966
- if (pipelineRegistry.has(id)) {
2967
- throw new PipelineError({ code: 'pipeline-already-registered', detail: { id } });
2968
- }
2969
- pipelineRegistry.set(id, impl);
2970
- },
2971
- installPipeline(asset: RenderPipelineAsset): Result<() => void, PipelineError> {
2972
- const impl = pipelineRegistry.get(asset.pipelineId);
2973
- if (impl === undefined) {
2974
- return err(
2975
- new PipelineError({ code: 'pipeline-not-found', detail: { handle: installEpoch } }),
2976
- );
2977
- }
2978
- // feat-20260608-cluster-lighting M2 / w10: HDRP grid validation.
2979
- // When the pipelineId is 'forgeax::hdrp', validate clusterGrid dimensions
2980
- // before installing. Grid-invalid throws a standalone HdrpInstallError
2981
- // (NOT in RuntimeErrorCode; synchronous install-time config error).
2982
- //
2983
- // feat-20260608-cluster-lighting M5 / w20: HDRP caps gate.
2984
- // A zero storage-buffer ceiling is the supported WebGL2 downlevel path:
2985
- // HDRP's cluster light list is transported through a bounded uniform
2986
- // array. Partial storage support (1..3 slots) is still unsupported
2987
- // because it cannot satisfy either the clustered or the complete
2988
- // uniform fallback contract.
2989
- if (asset.pipelineId === 'forgeax::hdrp') {
2990
- const storageBuffersCap = internals.device.limits.maxStorageBuffersPerShaderStage;
2991
- if (storageBuffersCap > 0 && storageBuffersCap < 4) {
2992
- throw new HdrpCapsInsufficientError(
2993
- 'maxStorageBuffersPerShaderStage',
2994
- storageBuffersCap,
2995
- 4,
2996
- );
2997
- }
2998
- const grid = asset.config?.clusterGrid ?? { x: 16, y: 9, z: 24 };
1693
+ configureStandard(config: RenderPipelineAsset['config']): void {
1694
+ const profileConfig = internals.standardProfile;
1695
+ const resolvedConfig =
1696
+ profileConfig === undefined
1697
+ ? config
1698
+ : {
1699
+ ...(profileConfig.lighting === 'clustered'
1700
+ ? { clusterGrid: DEFAULT_CLUSTER_GRID }
1701
+ : {}),
1702
+ ...(profileConfig.ssao ? { ssao: { enabled: true } } : {}),
1703
+ ...config,
1704
+ };
1705
+ if (resolvedConfig?.clusterGrid !== undefined) {
1706
+ const grid = resolvedConfig.clusterGrid;
2999
1707
  const gridResult = validateClusterGrid(grid);
3000
1708
  if (!gridResult.ok) {
3001
1709
  throw gridResult.error;
3002
1710
  }
3003
1711
  }
3004
- const install = {
3005
- token: {},
3006
- id: asset.pipelineId,
3007
- pipeline: impl,
3008
- config: asset.config,
3009
- isHdrp: asset.pipelineId === 'forgeax::hdrp',
3010
- };
3011
- pipelineInstalls.push(install);
3012
- frameState.activePipeline = install.pipeline;
3013
- frameState.activePipelineId = asset.pipelineId;
3014
- // installPipeline no longer carries a handle (D-19: RenderPipelineAsset is
3015
- // installed as a POD at boot/swap time before any World exists). The
1712
+ if (resolvedConfig?.ssao?.enabled === true) {
1713
+ const ssaoResult = resolveSsaoParameters(resolvedConfig.ssao);
1714
+ if (!ssaoResult.ok) {
1715
+ throw ssaoResult.error;
1716
+ }
1717
+ }
1718
+ // Standard configuration no longer carries a handle (D-19: RenderPipelineAsset is
1719
+ // supplied as a POD at boot/swap time before a World exists). The
3016
1720
  // brand-number that `draw` compares to force a per-frame graph rebuild is
3017
1721
  // now a monotonic epoch bumped on every install -- distinct configs (and
3018
1722
  // even identical re-installs) trigger the rebuild, which is correct: install
3019
1723
  // is a rare boot/swap event, never a per-frame cost.
3020
1724
  installEpoch += 1;
3021
1725
  frameState.installedPipelineHandle = installEpoch;
3022
- frameState.isHdrpActive = install.isHdrp;
3023
- // The install-time config becomes part of the next typed topology key.
3024
- frameState.installedPipelineConfig = install.config;
3025
- let active = true;
3026
- return ok(() => {
3027
- if (!active) return;
3028
- active = false;
3029
- const index = pipelineInstalls.findIndex((candidate) => candidate.token === install.token);
3030
- if (index < 0) return;
3031
- const wasCurrent = index === pipelineInstalls.length - 1;
3032
- pipelineInstalls.splice(index, 1);
3033
- if (!wasCurrent) return;
3034
- const current = pipelineInstalls.at(-1);
3035
- frameState.activePipeline = current?.pipeline ?? urpPipeline;
3036
- frameState.activePipelineId = current?.id ?? URP_PIPELINE_ID;
3037
- frameState.isHdrpActive = current?.isHdrp ?? false;
3038
- frameState.installedPipelineConfig = current?.config;
3039
- installEpoch += 1;
3040
- frameState.installedPipelineHandle = installEpoch;
3041
- });
1726
+ frameState.isHdrpActive = resolvedConfig?.clusterGrid !== undefined;
1727
+ frameState.installedPipelineConfig = resolvedConfig;
3042
1728
  },
3043
- postProcess: {
3044
- register(id: string, entry: PostProcessShaderEntry): () => void {
3045
- if (postProcessRegistry.has(id)) {
1729
+ registerBuiltinPostProcess(id: string, entry: PostProcessShaderEntry): () => void {
1730
+ // D-3: eager-create params UBO at register time + fail-fast
1731
+ // byteSize / defaultValue validation (q5=A).
1732
+ let paramsBuffer: Buffer | undefined;
1733
+ try {
1734
+ if (entry.params !== undefined) {
1735
+ const { byteSize, defaultValue } = entry.params;
1736
+ if (byteSize < 16 || defaultValue.length !== byteSize) {
1737
+ throw new PostProcessError({
1738
+ code: 'params-size-mismatch',
1739
+ detail: { byteSize, actualLength: defaultValue.length },
1740
+ });
1741
+ }
1742
+ const paramsBufferResult = internals.device.createBuffer({
1743
+ label: `post-process-params-${id}`,
1744
+ size: byteSize,
1745
+ usage: GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST,
1746
+ mappedAtCreation: false,
1747
+ });
1748
+ if (!paramsBufferResult.ok) throw paramsBufferResult.error;
1749
+ paramsBuffer = paramsBufferResult.value;
1750
+ const writeResult = internals.device.queue.writeBuffer(paramsBuffer, 0, defaultValue);
1751
+ if (!writeResult.ok) throw writeResult.error;
1752
+ } else if (entryHasDepthRead(entry)) {
1753
+ const paramsBufferResult = internals.device.createBuffer({
1754
+ label: `post-process-params-${id}`,
1755
+ size: DEPTH_MIN_PARAMS_BYTE_SIZE,
1756
+ usage: GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST,
1757
+ mappedAtCreation: false,
1758
+ });
1759
+ if (!paramsBufferResult.ok) throw paramsBufferResult.error;
1760
+ paramsBuffer = paramsBufferResult.value;
1761
+ }
1762
+ if (builtinPostProcessEntries.has(id)) {
3046
1763
  throw new PostProcessError({
3047
1764
  code: 'post-process-already-registered',
3048
1765
  detail: { id },
3049
1766
  });
3050
1767
  }
3051
- // D-3: eager-create params UBO at register time + fail-fast
3052
- // byteSize / defaultValue validation (q5=A).
3053
- let paramsBuffer: Buffer | undefined;
3054
- try {
3055
- if (entry.params !== undefined) {
3056
- const { byteSize, defaultValue } = entry.params;
3057
- if (byteSize < 16 || defaultValue.length !== byteSize) {
3058
- throw new PostProcessError({
3059
- code: 'params-size-mismatch',
3060
- detail: { byteSize, actualLength: defaultValue.length },
3061
- });
3062
- }
3063
- const paramsBufferResult = internals.device.createBuffer({
3064
- label: `post-process-params-${id}`,
3065
- size: byteSize,
3066
- usage: GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST,
3067
- mappedAtCreation: false,
3068
- });
3069
- if (!paramsBufferResult.ok) throw paramsBufferResult.error;
3070
- paramsBuffer = paramsBufferResult.value;
3071
- const writeResult = internals.device.queue.writeBuffer(paramsBuffer, 0, defaultValue);
3072
- if (!writeResult.ok) throw writeResult.error;
3073
- } else if (entryHasDepthRead(entry)) {
3074
- const paramsBufferResult = internals.device.createBuffer({
3075
- label: `post-process-params-${id}`,
3076
- size: DEPTH_MIN_PARAMS_BYTE_SIZE,
3077
- usage: GPU_BUFFER_USAGE_UNIFORM | GPU_BUFFER_USAGE_COPY_DST,
3078
- mappedAtCreation: false,
3079
- });
3080
- if (!paramsBufferResult.ok) throw paramsBufferResult.error;
3081
- paramsBuffer = paramsBufferResult.value;
3082
- }
3083
- postProcessRegistry.set(id, entry);
3084
- if (paramsBuffer !== undefined) postProcessParamsBuffers.set(id, paramsBuffer);
3085
- } catch (cause) {
3086
- if (paramsBuffer !== undefined) internals.device.destroyBuffer(paramsBuffer);
3087
- throw cause;
3088
- }
1768
+ builtinPostProcessEntries.set(id, entry);
1769
+ if (paramsBuffer !== undefined) postProcessParamsBuffers.set(id, paramsBuffer);
3089
1770
  return () => {
3090
- if (postProcessRegistry.get(id) !== entry) return;
3091
- postProcessRegistry.delete(id);
3092
- const paramsBuffer = postProcessParamsBuffers.get(id);
3093
- if (paramsBuffer !== undefined) {
3094
- internals.device.destroyBuffer(paramsBuffer);
1771
+ if (builtinPostProcessEntries.get(id) === entry) {
1772
+ builtinPostProcessEntries.delete(id);
1773
+ }
1774
+ const current = postProcessParamsBuffers.get(id);
1775
+ if (current !== undefined) {
1776
+ internals.device.destroyBuffer(current);
3095
1777
  postProcessParamsBuffers.delete(id);
3096
1778
  }
3097
1779
  for (const key of postProcessPipelineCache.keys()) {
3098
1780
  if (key.startsWith(`${id}|`)) postProcessPipelineCache.delete(key);
3099
1781
  }
3100
1782
  };
3101
- },
1783
+ } catch (cause) {
1784
+ if (paramsBuffer !== undefined) internals.device.destroyBuffer(paramsBuffer);
1785
+ throw cause;
1786
+ }
3102
1787
  },
3103
1788
  disposeFrameState(): void {
3104
- const environmentHandles = [
3105
- frameState.environmentGeneration.active?.handle,
3106
- frameState.environmentGeneration.candidate?.handle,
3107
- ];
3108
- disposeEnvironmentGeneration(frameState.environmentGeneration);
3109
- for (const handle of environmentHandles) {
3110
- if (isEnvironmentGpuBundle(handle)) handle.destroy(internals.device);
3111
- }
3112
- temporalFrameTransaction.dispose();
3113
1789
  persistentRenderScene.dispose();
3114
1790
  gpuDrivenProduction.dispose();
3115
1791
  disposeFeatureGpuWork();
@@ -3121,17 +1797,15 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3121
1797
  // 6 steps).
3122
1798
  frameState.directionalShadowCache = null;
3123
1799
  frameState.directionalShadowCacheRecorded = false;
3124
- frameState.pendingShadowCommandBuffers = [];
3125
- frameState.shadowSubmitModeState = createShadowSubmitModeState();
3126
1800
  frameState.currentFrameObservationSource = undefined;
3127
1801
  frameState.currentDirectionalShadowView = null;
3128
1802
  frameState.currentSpotShadowView = null;
3129
1803
  const compiled = frameState.compiledFrameGraph;
3130
1804
  frameState.compiledFrameGraph = null;
3131
1805
  frameState.compiledFrameGraphTopologyKey = null;
3132
- if (compiled !== null) taaHistoryStore.retireGraph(compiled).catch(() => undefined);
1806
+ if (compiled !== null) compiled.retire().catch(() => undefined);
3133
1807
  for (const retired of frameState.retiredCompiledFrameGraphs) {
3134
- taaHistoryStore.retireGraph(retired).catch(() => undefined);
1808
+ retired.retire().catch(() => undefined);
3135
1809
  }
3136
1810
  frameState.retiredCompiledFrameGraphs.clear();
3137
1811
  // feat-20260619 M4 (D-6): pass errorRegistry to disposeInstanceBuffers
@@ -3139,6 +1813,15 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3139
1813
  // dispose error strategy).
3140
1814
  disposeInstanceBuffers(frameState.instanceBuffers, internals.errorRegistry);
3141
1815
  disposeTransientInstanceBuffers(frameState.transientInstanceBuffers, internals.errorRegistry);
1816
+ if (frameState.morphBuffers !== undefined) {
1817
+ for (const entry of frameState.morphBuffers.values()) {
1818
+ if (!entry.buffer.isDestroyed) {
1819
+ const result = entry.buffer.destroy();
1820
+ if (!result.ok) internals.errorRegistry.fire(result.error);
1821
+ }
1822
+ }
1823
+ frameState.morphBuffers.clear();
1824
+ }
3142
1825
  // feat-20260612-point-light-shadows-urp-hdrp M4 / T-M4-2: dispose the
3143
1826
  // cube_array shadow atlas owned by the RenderSystem closure. The atlas
3144
1827
  // is per-RenderSystem (= per Renderer) and is shared transparently
@@ -3148,46 +1831,31 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3148
1831
  frameState.pointShadowAtlas.dispose();
3149
1832
  frameState.pointShadowAtlas = null;
3150
1833
  }
3151
- const taaParams = internals.getPipelineState()?.perPassResources.taaResolveParamsBuffer;
3152
- if (taaParams !== null && taaParams !== undefined) {
3153
- internals.device.destroyBuffer(taaParams);
3154
- const pipelineState = internals.getPipelineState();
3155
- if (pipelineState !== null) pipelineState.perPassResources.taaResolveParamsBuffer = null;
3156
- }
3157
1834
  },
3158
1835
  resetForRecover(): void {
3159
- abandonEnvironmentDevice(
3160
- frameState.environmentGeneration,
3161
- frameState.environmentGeneration.deviceGeneration,
3162
- );
3163
- temporalFrameTransaction.resetForRecover();
3164
- const pipelineState = internals.getPipelineState();
3165
- if (pipelineState !== null) pipelineState.perPassResources.taaResolveParamsBuffer = null;
3166
1836
  persistentRenderScene.resetGpuForRecover();
3167
1837
  gpuDrivenProduction.dispose();
1838
+ gpuDrivenProduction = new GpuDrivenProduction(internals.device, gpuDrivenShaderFactory);
3168
1839
  disposeFeatureGpuWork();
3169
1840
  // feat-20260622-s5 M3 / B-2 / w18: recover() rebuild drops device-bound
3170
1841
  // state minted by the lost device. The active graph and per-entity caches
3171
1842
  // must be discarded, not merely marked for destruction: their opaque
3172
1843
  // handles cannot be used on the fresh device and the next draw must
3173
1844
  // lazily build a new graph from the preserved ECS / asset POD caches.
3174
- if (frameState.compiledFrameGraph !== null) {
3175
- taaHistoryStore.retireGraph(frameState.compiledFrameGraph).catch(() => undefined);
3176
- }
1845
+ frameState.compiledFrameGraph?.retire().catch(() => undefined);
3177
1846
  frameState.compiledFrameGraph = null;
3178
1847
  frameState.compiledFrameGraphTopologyKey = null;
3179
1848
  frameState.directionalShadowCache = null;
3180
1849
  frameState.directionalShadowCacheRecorded = false;
3181
- frameState.pendingShadowCommandBuffers = [];
3182
- frameState.shadowSubmitModeState = createShadowSubmitModeState();
3183
1850
  frameState.currentFrameObservationSource = undefined;
3184
1851
  frameState.currentDirectionalShadowView = null;
3185
1852
  frameState.currentSpotShadowView = null;
3186
1853
  for (const retired of frameState.retiredCompiledFrameGraphs) {
3187
- taaHistoryStore.retireGraph(retired).catch(() => undefined);
1854
+ retired.retire().catch(() => undefined);
3188
1855
  }
3189
1856
  frameState.retiredCompiledFrameGraphs.clear();
3190
1857
  frameState.instanceBuffers.clear();
1858
+ frameState.morphBuffers?.clear();
3191
1859
  frameState.transientInstanceBuffers = [];
3192
1860
  frameState.pointShadowAtlas = null;
3193
1861
  // Bind groups retain opaque handles from the lost device. WeakMap roots
@@ -3202,26 +1870,22 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3202
1870
  frameState.materialBgAssemblyCache.clear();
3203
1871
  frameState.singletonMaterialCache.clear();
3204
1872
  frameState.postProcessBgCache = new WeakMap();
3205
- // Post-process PSOs are cached outside frameState because the normal
1873
+ // Fullscreen PSOs are cached outside frameState because the normal
3206
1874
  // path reuses them across frames. They still carry opaque handles from
3207
1875
  // the lost device, so recovery must invalidate this cache alongside the
3208
- // post-process registry/UBOs below.
1876
+ // feature-host declarations and UBOs below.
3209
1877
  postProcessPipelineCache.clear();
3210
- // Logical declarations stay live so unregister remains authoritative
3211
- // while a replacement device is being requested.
1878
+ // Logical declarations stay live in the feature host while a replacement
1879
+ // device is being requested.
3212
1880
  postProcessParamsBuffers.clear();
3213
1881
  resetRenderFeatureGraphState(internals);
3214
1882
  },
3215
- restoreGpuDrivenForRecover(): void {
3216
- // resetForRecover() runs before Renderer.recover() acquires the
3217
- // replacement device. Rebinding here is deliberately a separate phase;
3218
- // constructing GpuDrivenProduction during reset would capture the lost
3219
- // device and make the first eligible post-recovery frame submit stale
3220
- // buffers/pipelines.
3221
- gpuDrivenProduction = new GpuDrivenProduction(internals.device, gpuDrivenShaderFactory);
3222
- },
3223
1883
  restorePostProcessResources(): void {
3224
- for (const [id, entry] of postProcessRegistry) {
1884
+ const declarations = [
1885
+ ...builtinPostProcessEntries.entries(),
1886
+ ...activeFeaturePostProcessEntries.entries(),
1887
+ ];
1888
+ for (const [id, entry] of declarations) {
3225
1889
  let buffer: Buffer | undefined;
3226
1890
  if (entry.params !== undefined) {
3227
1891
  const created = internals.device.createBuffer({
@@ -3247,7 +1911,7 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3247
1911
  if (!created.ok) throw created.error;
3248
1912
  buffer = created.value;
3249
1913
  }
3250
- if (buffer !== undefined && postProcessRegistry.get(id) === entry) {
1914
+ if (buffer !== undefined && lookupPostProcess(id) === entry) {
3251
1915
  postProcessParamsBuffers.set(id, buffer);
3252
1916
  } else if (buffer !== undefined) {
3253
1917
  internals.device.destroyBuffer(buffer);
@@ -3256,3 +1920,14 @@ export function createRenderSystem(internals: RenderSystemInternals): RenderSyst
3256
1920
  },
3257
1921
  };
3258
1922
  }
1923
+
1924
+ // @forgeax/engine-render - built-in record context extension.
1925
+ //
1926
+ // The public RenderPipelineContext is canonical in render-contract.ts. This
1927
+ // owner adds only concrete assembly state for built-in record closures.
1928
+
1929
+ /**
1930
+ * Package-private extension consumed by the built-in record closures only.
1931
+ * Custom pipeline authors see the leaf RenderPipelineContext contract and
1932
+ * never this surface.
1933
+ */