@forgeax/engine-render 0.1.34 → 0.1.36

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 (273) hide show
  1. package/README.md +69 -3
  2. package/dist/assembly/device-feature-admission.d.ts +2 -2
  3. package/dist/assembly/host-contract.d.ts +3 -2
  4. package/dist/assembly/host-contract.d.ts.map +1 -1
  5. package/dist/assembly/recovery/renderer-recover.d.ts.map +1 -1
  6. package/dist/assembly/render-target-host.d.ts +5 -0
  7. package/dist/assembly/render-target-host.d.ts.map +1 -1
  8. package/dist/assembly/renderer-facade.d.ts.map +1 -1
  9. package/dist/assembly/webgpu-renderer.d.ts +1 -0
  10. package/dist/assembly/webgpu-renderer.d.ts.map +1 -1
  11. package/dist/assembly/webgpu-vertex-layouts.d.ts.map +1 -1
  12. package/dist/authoring.mjs +2 -4
  13. package/dist/authoring.mjs.map +1 -1
  14. package/dist/chunk-2OZAS3WU.mjs +3489 -0
  15. package/dist/chunk-2OZAS3WU.mjs.map +1 -0
  16. package/dist/{chunk-UI5SFODX.mjs → chunk-BZQ4CDRF.mjs} +2338 -1991
  17. package/dist/chunk-BZQ4CDRF.mjs.map +1 -0
  18. package/dist/{chunk-MKDIT2EM.mjs → chunk-CJOV2XLO.mjs} +216 -261
  19. package/dist/chunk-CJOV2XLO.mjs.map +1 -0
  20. package/dist/chunk-D6WSBXJ2.mjs +1842 -0
  21. package/dist/chunk-D6WSBXJ2.mjs.map +1 -0
  22. package/dist/{chunk-HEB564WF.mjs → chunk-DRF6OULX.mjs} +4 -4
  23. package/dist/{chunk-HEB564WF.mjs.map → chunk-DRF6OULX.mjs.map} +1 -1
  24. package/dist/{chunk-OCDLEI6I.mjs → chunk-OB5WAFYH.mjs} +117 -5
  25. package/dist/chunk-OB5WAFYH.mjs.map +1 -0
  26. package/dist/{chunk-ODZWR6JQ.mjs → chunk-OEYKKMXG.mjs} +2042 -27
  27. package/dist/chunk-OEYKKMXG.mjs.map +1 -0
  28. package/dist/chunk-RIPAEGI6.mjs +48 -0
  29. package/dist/chunk-RIPAEGI6.mjs.map +1 -0
  30. package/dist/{chunk-ELJFJ2BU.mjs → chunk-X2KA6WHM.mjs} +29 -4
  31. package/dist/chunk-X2KA6WHM.mjs.map +1 -0
  32. package/dist/{chunk-BLXGONR2.mjs → chunk-XSFB7XXQ.mjs} +51 -4
  33. package/dist/chunk-XSFB7XXQ.mjs.map +1 -0
  34. package/dist/cloud/frame-context.d.ts +2 -2
  35. package/dist/cloud/frame-context.d.ts.map +1 -1
  36. package/dist/construct-renderer.mjs +936 -2140
  37. package/dist/construct-renderer.mjs.map +1 -1
  38. package/dist/device/device-scope.d.ts.map +1 -1
  39. package/dist/device/gpu-residency.d.ts +19 -19
  40. package/dist/device/gpu-residency.d.ts.map +1 -1
  41. package/dist/errors/render.d.ts +3 -2
  42. package/dist/errors/render.d.ts.map +1 -1
  43. package/dist/extract/material-context.d.ts +7 -0
  44. package/dist/extract/material-context.d.ts.map +1 -0
  45. package/dist/features/host.d.ts +5 -0
  46. package/dist/features/host.d.ts.map +1 -1
  47. package/dist/features/particle-mesh-layout.d.ts +19 -0
  48. package/dist/features/particle-mesh-layout.d.ts.map +1 -0
  49. package/dist/features/plan.d.ts +2 -0
  50. package/dist/features/plan.d.ts.map +1 -1
  51. package/dist/features/types.d.ts +8 -0
  52. package/dist/features/types.d.ts.map +1 -1
  53. package/dist/glyph-text-layout-system.d.ts +1 -1
  54. package/dist/glyph-text-layout-system.d.ts.map +1 -1
  55. package/dist/gpu-driven/production-raster.d.ts +1 -1
  56. package/dist/gpu-driven/production-raster.d.ts.map +1 -1
  57. package/dist/gpu-driven/shadow-ownership.d.ts +3 -3
  58. package/dist/gpu-driven/shadow-ownership.d.ts.map +1 -1
  59. package/dist/gpu-driven/shadow-views.d.ts +1 -1
  60. package/dist/gpu-driven/shadow-views.d.ts.map +1 -1
  61. package/dist/gpu-driven/view-gpu.d.ts +1 -1
  62. package/dist/gpu-driven/view-gpu.d.ts.map +1 -1
  63. package/dist/ibl/frame-resources.d.ts.map +1 -1
  64. package/dist/ibl/skylight-bind-group.d.ts +4 -4
  65. package/dist/ibl/skylight-bind-group.d.ts.map +1 -1
  66. package/dist/index.d.ts +3 -0
  67. package/dist/index.d.ts.map +1 -1
  68. package/dist/index.mjs +542 -9
  69. package/dist/index.mjs.map +1 -1
  70. package/dist/instances.d.ts +3 -4
  71. package/dist/instances.d.ts.map +1 -1
  72. package/dist/internal.mjs +7 -8
  73. package/dist/internal.mjs.map +1 -1
  74. package/dist/pbr-pipeline.d.ts +3 -3
  75. package/dist/pbr-pipeline.d.ts.map +1 -1
  76. package/dist/pipeline/standard-output/lut-gpu.d.ts +2 -2
  77. package/dist/pipeline/standard-output/lut-gpu.d.ts.map +1 -1
  78. package/dist/pipeline/standard-output/resources.d.ts +2 -2
  79. package/dist/pipeline/standard-output/resources.d.ts.map +1 -1
  80. package/dist/publication/contract.d.ts +77 -0
  81. package/dist/publication/contract.d.ts.map +1 -0
  82. package/dist/publication/dependencies.d.ts +7 -0
  83. package/dist/publication/dependencies.d.ts.map +1 -0
  84. package/dist/publication/prepare-geometry.d.ts +9 -0
  85. package/dist/publication/prepare-geometry.d.ts.map +1 -0
  86. package/dist/publication/programs.d.ts +5 -0
  87. package/dist/publication/programs.d.ts.map +1 -0
  88. package/dist/publication/publisher.d.ts +34 -0
  89. package/dist/publication/publisher.d.ts.map +1 -0
  90. package/dist/publication/receiver.d.ts +29 -0
  91. package/dist/publication/receiver.d.ts.map +1 -0
  92. package/dist/publication/resource-scope.d.ts +18 -0
  93. package/dist/publication/resource-scope.d.ts.map +1 -0
  94. package/dist/publication/targets.d.ts +33 -0
  95. package/dist/publication/targets.d.ts.map +1 -0
  96. package/dist/publication/video.d.ts +5 -0
  97. package/dist/publication/video.d.ts.map +1 -0
  98. package/dist/record/fold-buckets.d.ts +3 -3
  99. package/dist/record/fold-buckets.d.ts.map +1 -1
  100. package/dist/record/frame-snapshot.d.ts +6 -6
  101. package/dist/record/frame-snapshot.d.ts.map +1 -1
  102. package/dist/record/frame.d.ts +5 -4
  103. package/dist/record/frame.d.ts.map +1 -1
  104. package/dist/record/helpers.d.ts +2 -2
  105. package/dist/record/helpers.d.ts.map +1 -1
  106. package/dist/record/main-pass-geometry.d.ts +2 -2
  107. package/dist/record/main-pass-geometry.d.ts.map +1 -1
  108. package/dist/record/main-pass-material.d.ts +7 -9
  109. package/dist/record/main-pass-material.d.ts.map +1 -1
  110. package/dist/record/main-pass-sprite-draws.d.ts +3 -16
  111. package/dist/record/main-pass-sprite-draws.d.ts.map +1 -1
  112. package/dist/record/main-pass.d.ts.map +1 -1
  113. package/dist/record/prepared-material-bindings.d.ts +2 -3
  114. package/dist/record/prepared-material-bindings.d.ts.map +1 -1
  115. package/dist/record/render-context.d.ts +4 -2
  116. package/dist/record/render-context.d.ts.map +1 -1
  117. package/dist/record/shadow-pass.d.ts +2 -2
  118. package/dist/record/shadow-pass.d.ts.map +1 -1
  119. package/dist/record/sprite-instance-buffer.d.ts +31 -0
  120. package/dist/record/sprite-instance-buffer.d.ts.map +1 -0
  121. package/dist/record/target-capture-graph.d.ts +19 -0
  122. package/dist/record/target-capture-graph.d.ts.map +1 -0
  123. package/dist/record/typed-frame-graph.d.ts +2 -13
  124. package/dist/record/typed-frame-graph.d.ts.map +1 -1
  125. package/dist/record/view-ubo.d.ts.map +1 -1
  126. package/dist/record/volume-params.d.ts +2 -2
  127. package/dist/record/volume-params.d.ts.map +1 -1
  128. package/dist/recovery/render-system-candidate.d.ts +5 -12
  129. package/dist/recovery/render-system-candidate.d.ts.map +1 -1
  130. package/dist/render-contract.d.ts +4 -1
  131. package/dist/render-contract.d.ts.map +1 -1
  132. package/dist/render-system-extract-tail.d.ts.map +1 -1
  133. package/dist/render-system-extract.d.ts +2 -0
  134. package/dist/render-system-extract.d.ts.map +1 -1
  135. package/dist/render-system-presentation.d.ts +2 -2
  136. package/dist/render-system-presentation.d.ts.map +1 -1
  137. package/dist/render-system-projections.d.ts +3 -2
  138. package/dist/render-system-projections.d.ts.map +1 -1
  139. package/dist/render-system-residency.d.ts +2 -2
  140. package/dist/render-system-residency.d.ts.map +1 -1
  141. package/dist/render-system.d.ts +3 -7
  142. package/dist/render-system.d.ts.map +1 -1
  143. package/dist/scene/render-scene.d.ts +8 -4
  144. package/dist/scene/render-scene.d.ts.map +1 -1
  145. package/dist/scene/render-source.d.ts +42 -0
  146. package/dist/scene/render-source.d.ts.map +1 -0
  147. package/dist/scene/source-systems.d.ts +6 -0
  148. package/dist/scene/source-systems.d.ts.map +1 -0
  149. package/dist/systems/skin-palette-allocator.d.ts.map +1 -1
  150. package/dist/systems/skin-palette-types.d.ts +10 -0
  151. package/dist/systems/skin-palette-types.d.ts.map +1 -1
  152. package/dist/systems/transparent-dispatch.d.ts +2 -2
  153. package/dist/systems/transparent-dispatch.d.ts.map +1 -1
  154. package/dist/systems/transparent-sort-config.d.ts +2 -1
  155. package/dist/systems/transparent-sort-config.d.ts.map +1 -1
  156. package/dist/temporal/index.mjs +6 -7
  157. package/dist/typed-shadow-passes.d.ts.map +1 -1
  158. package/package.json +21 -21
  159. package/src/__tests__/auto-exposure-transaction.integration.test.ts +4 -9
  160. package/src/__tests__/device-feature-admission.unit.test.ts +3 -2
  161. package/src/__tests__/device-scope-lifecycle.unit.test.ts +26 -0
  162. package/src/__tests__/gpu-driven-pbr.dawn.test.ts +3 -4
  163. package/src/__tests__/gpu-driven-production.integration.test.ts +18 -1
  164. package/src/__tests__/gpu-driven-shadow-views.unit.test.ts +23 -0
  165. package/src/__tests__/ktx2-basis-gpu-consumer.dawn.test.ts +4 -1
  166. package/src/__tests__/main-pass-material-medium-selection.unit.test.ts +0 -1
  167. package/src/__tests__/material-vertex-texture.browser.test.ts +7 -0
  168. package/src/__tests__/material-vertex-texture.dawn.test.ts +7 -0
  169. package/src/__tests__/material-vertex-texture.fixture.ts +31 -0
  170. package/src/__tests__/materials-standard-contract.unit.test.ts +0 -1
  171. package/src/__tests__/physical-clearcoat.integration.test.ts +23 -23
  172. package/src/__tests__/physical-material-budget.unit.test.ts +40 -0
  173. package/src/__tests__/recovery-candidate-prepare.contract.test.ts +5 -0
  174. package/src/__tests__/render-error-code-owner.test-d.ts +1 -0
  175. package/src/__tests__/renderer-factory-material-contract.unit.test.ts +107 -5
  176. package/src/__tests__/runtime-material-program.integration.test.ts +79 -0
  177. package/src/__tests__/vertex-layouts-ssot.unit.test.ts +23 -3
  178. package/src/assembly/device-feature-admission.ts +2 -2
  179. package/src/assembly/host-contract.ts +5 -2
  180. package/src/assembly/recovery/renderer-recover.ts +9 -0
  181. package/src/assembly/render-target-host.ts +57 -47
  182. package/src/assembly/renderer-facade.ts +3 -1
  183. package/src/assembly/webgpu-ready.ts +1 -1
  184. package/src/assembly/webgpu-renderer.ts +101 -70
  185. package/src/assembly/webgpu-vertex-layouts.ts +2 -9
  186. package/src/cloud/frame-context.ts +2 -2
  187. package/src/device/device-scope.ts +3 -0
  188. package/src/device/gpu-residency.ts +26 -25
  189. package/src/errors/render.ts +3 -1
  190. package/src/extract/material-context.ts +21 -0
  191. package/src/features/host.ts +13 -5
  192. package/src/features/particle-mesh-layout.ts +25 -0
  193. package/src/features/plan.ts +2 -0
  194. package/src/features/types.ts +8 -0
  195. package/src/glyph-text-layout-system.ts +2 -2
  196. package/src/gpu-driven/production-raster.ts +9 -3
  197. package/src/gpu-driven/shadow-ownership.ts +3 -3
  198. package/src/gpu-driven/shadow-views.ts +27 -2
  199. package/src/gpu-driven/view-gpu.ts +5 -0
  200. package/src/ibl/frame-resources.ts +0 -1
  201. package/src/ibl/skylight-bind-group.ts +13 -25
  202. package/src/index.ts +9 -0
  203. package/src/instances.ts +9 -6
  204. package/src/pbr-pipeline.ts +15 -18
  205. package/src/pipeline/standard-output/lut-gpu.ts +3 -3
  206. package/src/pipeline/standard-output/resources.ts +2 -2
  207. package/src/publication/__tests__/derived-geometry.integration.test.ts +177 -0
  208. package/src/publication/__tests__/features.integration.test.ts +173 -0
  209. package/src/publication/__tests__/programs.unit.test.ts +25 -0
  210. package/src/publication/__tests__/publication.integration.test.ts +415 -0
  211. package/src/publication/__tests__/targets.integration.test.ts +120 -0
  212. package/src/publication/__tests__/video.browser.test.ts +50 -0
  213. package/src/publication/contract.ts +104 -0
  214. package/src/publication/dependencies.ts +54 -0
  215. package/src/publication/prepare-geometry.ts +94 -0
  216. package/src/publication/programs.ts +45 -0
  217. package/src/publication/publisher.ts +645 -0
  218. package/src/publication/receiver.ts +297 -0
  219. package/src/publication/resource-scope.ts +31 -0
  220. package/src/publication/targets.ts +182 -0
  221. package/src/publication/video.ts +33 -0
  222. package/src/record/__tests__/material-bg-cache.unit.test.ts +15 -1
  223. package/src/record/__tests__/prepared-runtime-material.integration.test.ts +134 -0
  224. package/src/record/__tests__/sprite-material-abi.unit.test.ts +8 -10
  225. package/src/record/__tests__/video-texture-view.unit.test.ts +9 -9
  226. package/src/record/fold-buckets.ts +8 -5
  227. package/src/record/frame-snapshot.ts +6 -6
  228. package/src/record/frame.ts +87 -28
  229. package/src/record/helpers.ts +2 -2
  230. package/src/record/main-pass-geometry.ts +21 -2
  231. package/src/record/main-pass-material.ts +37 -33
  232. package/src/record/main-pass-sprite-draws.ts +6 -152
  233. package/src/record/main-pass.ts +8 -7
  234. package/src/record/prepared-material-bindings.ts +16 -37
  235. package/src/record/render-context.ts +4 -2
  236. package/src/record/shadow-pass.ts +65 -7
  237. package/src/record/sprite-instance-buffer.ts +155 -0
  238. package/src/record/target-capture-graph.ts +258 -0
  239. package/src/record/typed-frame-graph.ts +11 -207
  240. package/src/record/view-ubo.ts +1 -1
  241. package/src/record/volume-params.ts +6 -6
  242. package/src/recovery/render-system-candidate.ts +10 -18
  243. package/src/render-contract.ts +6 -1
  244. package/src/render-system-extract-tail.ts +48 -23
  245. package/src/render-system-extract.ts +11 -2
  246. package/src/render-system-presentation.ts +2 -2
  247. package/src/render-system-projections.ts +3 -2
  248. package/src/render-system-residency.ts +2 -2
  249. package/src/render-system.ts +82 -54
  250. package/src/scene/render-scene.ts +110 -100
  251. package/src/scene/render-source.ts +90 -0
  252. package/src/scene/source-systems.ts +33 -0
  253. package/src/systems/skin-palette-allocator.ts +1 -0
  254. package/src/systems/skin-palette-types.ts +11 -0
  255. package/src/systems/transparent-dispatch.ts +2 -2
  256. package/src/systems/transparent-sort-config.ts +3 -1
  257. package/src/typed-shadow-passes.ts +36 -5
  258. package/dist/chunk-BLXGONR2.mjs.map +0 -1
  259. package/dist/chunk-ELJFJ2BU.mjs.map +0 -1
  260. package/dist/chunk-ILQ5MEZF.mjs +0 -32
  261. package/dist/chunk-ILQ5MEZF.mjs.map +0 -1
  262. package/dist/chunk-MKDIT2EM.mjs.map +0 -1
  263. package/dist/chunk-OCDLEI6I.mjs.map +0 -1
  264. package/dist/chunk-ODZWR6JQ.mjs.map +0 -1
  265. package/dist/chunk-RXZ3RGOU.mjs +0 -53
  266. package/dist/chunk-RXZ3RGOU.mjs.map +0 -1
  267. package/dist/chunk-SHFUFJ5N.mjs +0 -1781
  268. package/dist/chunk-SHFUFJ5N.mjs.map +0 -1
  269. package/dist/chunk-UI5SFODX.mjs.map +0 -1
  270. package/dist/chunk-UINHLCZA.mjs +0 -2389
  271. package/dist/chunk-UINHLCZA.mjs.map +0 -1
  272. package/dist/chunk-WEX45HKH.mjs +0 -1524
  273. package/dist/chunk-WEX45HKH.mjs.map +0 -1
@@ -133,6 +133,8 @@ export interface RenderFeatureStageEvent {
133
133
  }
134
134
 
135
135
  export interface RenderFeatureFrameInput {
136
+ readonly publishedFeatures?: readonly { readonly identity: string; readonly data: unknown }[];
137
+ readonly onFeatureSourceSubmitted?: (identity: string, feedback: unknown) => void;
136
138
  readonly worlds: readonly import('@forgeax/engine-ecs').World[];
137
139
  readonly owner: number;
138
140
  readonly frameNumber: number;
@@ -1454,7 +1456,10 @@ export function runRenderFeatureFrame(
1454
1456
  const extracted = invokeStage(
1455
1457
  slot,
1456
1458
  'extract',
1457
- () => slot.feature.extract(extractContext),
1459
+ () => {
1460
+ const published = input.publishedFeatures?.find((row) => row.identity === identity);
1461
+ return published === undefined ? slot.feature.extract(extractContext) : ok(published.data);
1462
+ },
1458
1463
  stageEvents,
1459
1464
  errors,
1460
1465
  );
@@ -1655,11 +1660,14 @@ export function runRenderFeatureFrame(
1655
1660
  continue;
1656
1661
  }
1657
1662
  planExecutionProjections.set(plannedFrame, projected.value);
1658
- if (feature.onFrameSubmitted !== undefined) {
1663
+ if (feature.onFrameSubmitted !== undefined || feature.onSourceFrameSubmitted !== undefined) {
1659
1664
  submissionSensitivePlans.add(plannedFrame);
1660
- submissionAcknowledgments.set(plannedFrame, () =>
1661
- feature.onFrameSubmitted?.(extracted.value, projected.value),
1662
- );
1665
+ submissionAcknowledgments.set(plannedFrame, () => {
1666
+ feature.onFrameSubmitted?.(extracted.value, projected.value);
1667
+ if (input.publishedFeatures?.some((row) => row.identity === identity))
1668
+ input.onFeatureSourceSubmitted?.(identity, declared.value.sourceFeedback);
1669
+ else feature.onSourceFrameSubmitted?.(extracted.value, declared.value.sourceFeedback);
1670
+ });
1663
1671
  }
1664
1672
  plans.push(plannedFrame);
1665
1673
  if (feature.onFrameAborted !== undefined) {
@@ -0,0 +1,25 @@
1
+ import { deriveVertexLayoutProjection } from '@forgeax/engine-geometry';
2
+
3
+ // Mesh attributes keep geometry's canonical packing. Only locations change:
4
+ // the particle instance occupies 4..9 and custom material inputs occupy 10..13.
5
+ export const PARTICLE_MESH_DEFAULTS = {
6
+ position: [0, 0, 0],
7
+ normal: [0, 0, 1],
8
+ uv: [0, 0],
9
+ tangent: [1, 0, 0, 1],
10
+ uv1: [0, 0],
11
+ color: [1, 1, 1, 1],
12
+ } as const;
13
+ export const PARTICLE_MESH_GEOMETRY = deriveVertexLayoutProjection(
14
+ Object.fromEntries(Object.keys(PARTICLE_MESH_DEFAULTS).map((key) => [key, new Float32Array()])),
15
+ );
16
+
17
+ export const PARTICLE_MESH_VERTEX_BUFFER = {
18
+ arrayStride: PARTICLE_MESH_GEOMETRY.arrayStride,
19
+ stepMode: 'vertex' as const,
20
+ attributes: PARTICLE_MESH_GEOMETRY.attributes.map(({ key, shaderLocation, offset, format }) => ({
21
+ shaderLocation: key === 'color' ? 14 : key === 'uv1' ? 15 : shaderLocation,
22
+ offset,
23
+ format,
24
+ })),
25
+ };
@@ -237,6 +237,8 @@ export type RenderFeaturePassDeclaration =
237
237
  * access is derived from these roles rather than duplicated as reads/writes.
238
238
  */
239
239
  export interface RenderFeaturePlan {
240
+ /** Source acknowledgment facts; never part of GPU resource or graph identity. */
241
+ readonly sourceFeedback?: unknown;
240
242
  readonly resources: readonly RenderFeatureResourceDeclaration[];
241
243
  readonly passes: readonly RenderFeaturePassDeclaration[];
242
244
  }
@@ -171,6 +171,8 @@ export interface RenderFeature<FrameData> {
171
171
  readonly source: string;
172
172
  }[];
173
173
  extract(context: RenderFeatureExtractContext): Result<FrameData, RenderError>;
174
+ /** GUID roots needed by the declarative plan; the publisher carries their asset closure. */
175
+ assetDependencies?(data: FrameData): readonly string[];
174
176
  plan(data: FrameData, context: RenderFeaturePlanContext): Result<RenderFeaturePlan, RenderError>;
175
177
  /**
176
178
  * Consume frame-owned producer state only after the compiled graph reached
@@ -178,6 +180,12 @@ export interface RenderFeature<FrameData> {
178
180
  * extracted frame and never during declarative planning.
179
181
  */
180
182
  onFrameSubmitted?(data: FrameData, submission?: RenderFeatureSubmission): void;
183
+ /**
184
+ * Consume source-owned intents after submission; feedback must be structured-cloneable.
185
+ * A sealed successor may have been extracted before this acknowledgment. Ordered
186
+ * intent producers must identify already-submitted work instead of replaying it.
187
+ */
188
+ onSourceFrameSubmitted?(data: FrameData, feedback: unknown): void;
181
189
  /**
182
190
  * Discard frame-owned producer state when graph admission or submission is
183
191
  * rejected. This is the symmetric recovery path for onFrameSubmitted.
@@ -158,7 +158,7 @@ interface GlyphTextData {
158
158
  */
159
159
  export function glyphTextLayoutSystem(
160
160
  world: World,
161
- gpuStore: GpuResidencyCache,
161
+ gpuStore: Pick<GpuResidencyCache, 'updateMesh'>,
162
162
  ): Result<void, TextError> {
163
163
  resetFontConcurrency();
164
164
 
@@ -200,7 +200,7 @@ function collectGlyphEntities(world: World): EntityHandle[] {
200
200
  */
201
201
  function processEntity(
202
202
  world: World,
203
- gpuStore: GpuResidencyCache,
203
+ gpuStore: Pick<GpuResidencyCache, 'updateMesh'>,
204
204
  entity: EntityHandle,
205
205
  ): TextError | null {
206
206
  const gtRes = world.get(entity, GlyphText);
@@ -1359,6 +1359,7 @@ export interface PreparedGpuDrivenFrame {
1359
1359
  readonly projectShadow?: (
1360
1360
  graph: RenderGraphBuilder<RenderPipelineFrame>,
1361
1361
  identity: ShadowViewIdentity,
1362
+ forceCompute?: boolean,
1362
1363
  ) => Result<ShadowViewProjection | undefined, RenderGraphError>;
1363
1364
  readonly updateShadowViews?: (
1364
1365
  views: readonly Omit<ShadowViewUpdateInput, 'sourcePlan' | 'scene'>[],
@@ -2572,7 +2573,12 @@ export class GpuDrivenProduction {
2572
2573
  ?.inspect()
2573
2574
  .map(
2574
2575
  (entry) =>
2575
- `${shadowViewIdentityKey(entry.identity)}:${entry.cache}:${entry.generation}:${entry.sourceRevision}:${entry.resourceGeneration}`,
2576
+ // Cache decisions and logical update generations are frame-local
2577
+ // producer state. They must not feed the compiled graph key: a
2578
+ // graph replacement may intentionally invalidate a shadow view
2579
+ // once without creating a graph-rebuild feedback loop. Resource
2580
+ // and source-plan generations remain real graph dependencies.
2581
+ `${shadowViewIdentityKey(entry.identity)}:${entry.sourceRevision}:${entry.resourceGeneration}`,
2576
2582
  )
2577
2583
  .join('|') ?? '';
2578
2584
  const production = this;
@@ -2689,10 +2695,10 @@ export class GpuDrivenProduction {
2689
2695
  frameNumber,
2690
2696
  ),
2691
2697
  }),
2692
- projectShadow: (graph, identity) => {
2698
+ projectShadow: (graph, identity, forceCompute = false) => {
2693
2699
  if (!shadowViewPool.isActive(identity) || shadowViewPool.submission(identity) === undefined)
2694
2700
  return ok(undefined);
2695
- return shadowViewPool.project(graph, identity);
2701
+ return shadowViewPool.project(graph, identity, forceCompute);
2696
2702
  },
2697
2703
  updateShadowViews: (views) =>
2698
2704
  shadowFiltered.plan.batches.length > 0
@@ -1,5 +1,5 @@
1
- import type { World } from '@forgeax/engine-ecs';
2
1
  import { gpuDrivenShadowDrawKey } from '../extract/gpu-driven';
2
+ import type { RenderResourceScope } from '../publication/resource-scope';
3
3
  import { worldEntityKey } from '../record/frame-snapshot';
4
4
  import type { ShadowCasterMembership } from '../render-system-extract';
5
5
 
@@ -7,12 +7,12 @@ const WORLD_ENTITY_KEY_STRIDE = 4294967296;
7
7
 
8
8
  /**
9
9
  * Extract emits world/entity keys in the transient `worlds[]` index domain,
10
- * while PersistentRenderScene owns stable renderer-local World keys for the
10
+ * while PersistentRenderScene owns stable renderer-local RenderResourceScope keys for the
11
11
  * retained GPU scene. Rebase ShadowCaster ownership at this orchestration
12
12
  * boundary before both GPU projection and CPU residual recording consume it.
13
13
  */
14
14
  export function rebaseShadowCasterOwnership(
15
- worlds: readonly World[],
15
+ worlds: readonly RenderResourceScope[],
16
16
  stableWorldKeys: readonly number[],
17
17
  entityKeys: ReadonlySet<number>,
18
18
  drawKeys: ReadonlySet<string>,
@@ -395,14 +395,39 @@ export class ShadowViewStatePool {
395
395
  project<FrameCtx extends RenderGraphFrame>(
396
396
  builder: RenderGraphBuilder<FrameCtx>,
397
397
  identity: ShadowViewIdentity,
398
+ forceCompute = false,
398
399
  ): Result<ShadowViewProjection, RenderGraphError> {
399
400
  const record = this.records.get(identityKey(identity));
400
401
  if (record === undefined) return err(graphStateError(identity));
401
402
  const cacheHit = record.cache === 'hit' && !record.invalidated;
403
+ // A graph replacement must carry the shadow compute passes even when the
404
+ // view itself is a cache hit. The graph owns the pass list, so omitting
405
+ // compute work while importing a replacement graph would leave its
406
+ // indirect arguments stale. The normal pool contract still skips work on
407
+ // a cache hit; graph projection opts in explicitly at that boundary.
408
+ const includeCompute = forceCompute || !cacheHit;
409
+ let forceComputePending = forceCompute;
410
+ let lastFrame: FrameCtx | undefined;
411
+ let executeForFrame = false;
412
+ const executeCompute = (frame: FrameCtx): boolean => {
413
+ if (frame !== lastFrame) {
414
+ lastFrame = frame;
415
+ const currentRecord = this.records.get(identityKey(record.identity));
416
+ executeForFrame =
417
+ forceComputePending ||
418
+ currentRecord === undefined ||
419
+ currentRecord.cache !== 'hit' ||
420
+ currentRecord.invalidated;
421
+ forceComputePending = false;
422
+ }
423
+ return executeForFrame;
424
+ };
402
425
  const projected = record.view.addPasses(
403
426
  builder,
404
427
  shadowViewLabelPrefix(record.identity),
405
- !cacheHit,
428
+ includeCompute,
429
+ undefined,
430
+ includeCompute ? executeCompute : undefined,
406
431
  );
407
432
  if (!projected.ok) return projected;
408
433
  return ok({
@@ -411,7 +436,7 @@ export class ShadowViewStatePool {
411
436
  generation: record.generation,
412
437
  plan: record.plan,
413
438
  view: record.view,
414
- passNames: cacheHit ? Object.freeze([]) : passNames(record.identity),
439
+ passNames: includeCompute ? passNames(record.identity) : Object.freeze([]),
415
440
  graphResources: projected.value,
416
441
  });
417
442
  }
@@ -982,6 +982,7 @@ export class GpuDrivenView {
982
982
  labelPrefix = 'gpu-driven',
983
983
  includeCompute = true,
984
984
  surfaceSubmissionObservation?: () => SurfaceSubmissionCandidate | undefined,
985
+ executeIf?: (frame: FrameCtx) => boolean,
985
986
  ): Result<GpuDrivenViewGraphResources, RenderGraphError> {
986
987
  const buffers = this.buffers;
987
988
  const bindGroup = this.bindGroup;
@@ -1099,6 +1100,7 @@ export class GpuDrivenView {
1099
1100
  { resource: view.value, usage: 'uniform-read' },
1100
1101
  { resource: counters.value, usage: 'storage-read-write' },
1101
1102
  ],
1103
+ ...(executeIf === undefined ? {} : { executeIf }),
1102
1104
  encode: ({ pass }) => {
1103
1105
  pass.setPipeline(this.resetPipeline);
1104
1106
  pass.setBindGroup(0, bindGroup);
@@ -1116,6 +1118,7 @@ export class GpuDrivenView {
1116
1118
  { resource: counters.value, usage: 'storage-read-write' },
1117
1119
  { resource: visible.value, usage: 'storage-read-write' },
1118
1120
  ],
1121
+ ...(executeIf === undefined ? {} : { executeIf }),
1119
1122
  encode: ({ pass }) => {
1120
1123
  pass.setPipeline(this.cullPipeline);
1121
1124
  pass.setBindGroup(0, bindGroup);
@@ -1130,6 +1133,7 @@ export class GpuDrivenView {
1130
1133
  { resource: counters.value, usage: 'storage-read' },
1131
1134
  { resource: indirect.value, usage: 'storage-read-write' },
1132
1135
  ],
1136
+ ...(executeIf === undefined ? {} : { executeIf }),
1133
1137
  encode: ({ pass }) => {
1134
1138
  pass.setPipeline(this.finalizePipeline);
1135
1139
  pass.setBindGroup(0, bindGroup);
@@ -1148,6 +1152,7 @@ export class GpuDrivenView {
1148
1152
  { resource: indirect.value, usage: 'copy-src' },
1149
1153
  { resource: lodReadback.value, usage: 'copy-dst' },
1150
1154
  ],
1155
+ ...(executeIf === undefined ? {} : { executeIf }),
1151
1156
  encode: ({ encoder, resources }) => {
1152
1157
  // A cached graph may run while an observer is awaiting mapAsync on the
1153
1158
  // previous copy. WebGPU forbids submitting a copy into a
@@ -32,7 +32,6 @@ export function resolveFrameSkylightResources(
32
32
  prefilterView: resident?.prefilterView ?? fallback.prefilterView,
33
33
  prefilterSampler: fallback.sampler,
34
34
  brdfLutView: resident?.brdfLutView ?? fallback.brdfLutView,
35
- brdfLutSampler: fallback.sampler,
36
35
  intensityBuffer: fallback.intensityBuffer,
37
36
  });
38
37
  }
@@ -1,20 +1,20 @@
1
1
  // skylight-bind-group.ts -- Skylight resources merged into @group(1) PBR
2
- // material BGL (binding 7..13) + fallback identity resource bundle. The
2
+ // material BGL (binding 7..12) + fallback identity resource bundle. The
3
3
  // complete Standard material group appends transmission entries.
4
4
  //
5
5
  // Plan-strategy D-5 (round-4 REVISED): the round-2 stand-alone @group(4)
6
6
  // Skylight BindGroupLayout collided with WebGPU's default maxBindGroups=4
7
7
  // limit, blocking pbr-pl pipeline-layout creation in chrome-beta. Round-4
8
- // rewrites the contract: Skylight 7 entries are **appended** to the
9
- // existing PBR material BindGroupLayout at bindings 7..13; pipeline layout
8
+ // rewrites the contract: Skylight 6 entries are **appended** to the
9
+ // existing PBR material BindGroupLayout at bindings 7..12; pipeline layout
10
10
  // stays at 4 slots `[view, material, mesh, instances]`; unlit pipeline is
11
11
  // untouched (it uses its own material BGL).
12
12
  //
13
13
  // Surface (M2 round-4 / t40 amend):
14
14
  // - mergeSkylightIntoMaterialBgl(materialBglEntries): given the existing
15
15
  // 7 PBR material BindGroupLayout entries (binding 0..6), returns the
16
- // merged 14-entry array with Skylight resources at binding 7..13
17
- // [irrTex, irrSampler, prefTex, prefSampler, brdfTex, brdfSampler,
16
+ // merged 13-entry array with Skylight resources at binding 7..12
17
+ // [irrTex, irrSampler, prefTex, prefSampler, brdfTex,
18
18
  // uniform { intensity: f32 }]. The merge is a pure function -- caller
19
19
  // (createRenderer) is responsible for passing the result to
20
20
  // `device.createBindGroupLayout`.
@@ -124,13 +124,13 @@ export interface SkylightQueue {
124
124
  */
125
125
  export interface SkylightBindGroupResources {
126
126
  readonly irradianceView: TextureView;
127
+ /** Shared by diffuse irradiance and the BRDF lookup table. */
127
128
  readonly irradianceSampler: Sampler;
128
129
  readonly prefilterView: TextureView;
129
130
  /** Present only when the per-draw prefilter slot was replaced by a local probe. */
130
131
  readonly skylightPrefilterView?: TextureView;
131
132
  readonly prefilterSampler: Sampler;
132
133
  readonly brdfLutView: TextureView;
133
- readonly brdfLutSampler: Sampler;
134
134
  readonly intensityBuffer: Buffer;
135
135
  }
136
136
 
@@ -143,7 +143,7 @@ export interface SkylightBindGroupResources {
143
143
  *
144
144
  * No stand-alone `bindGroup` field: the round-2 stand-alone Skylight BG
145
145
  * is gone (D-5 round-4); the fallback resources flow into the PBR
146
- * material BG at binding 7..13.
146
+ * material BG at binding 7..12.
147
147
  */
148
148
  export interface SkylightFallback {
149
149
  readonly irradianceTexture: Texture;
@@ -159,7 +159,7 @@ export interface SkylightFallback {
159
159
  // ─── Pure merger: BindGroupLayout entries (D-5 round-4) ─────────────────────
160
160
 
161
161
  /**
162
- * Append the 7 Skylight BindGroupLayout entries after the existing PBR
162
+ * Append the 6 Skylight BindGroupLayout entries after the existing PBR
163
163
  * user-region entries. Pure function -- the
164
164
  * caller passes the result to `device.createBindGroupLayout`. Charter P4
165
165
  * consistent abstraction: one BGL holds material + Skylight together,
@@ -208,16 +208,10 @@ export function mergeSkylightIntoMaterialBgl(
208
208
  visibility: GPU_SHADER_STAGE_FRAGMENT,
209
209
  texture: { sampleType: 'float', viewDimension: '2d' },
210
210
  } as BglEntry);
211
- // binding 12: brdfLutSampler
211
+ // binding 12: uniform { intensity: f32 }
212
212
  merged.push({
213
213
  binding: skylightBindingStart + 5,
214
214
  visibility: GPU_SHADER_STAGE_FRAGMENT,
215
- sampler: { type: 'filtering' },
216
- } as BglEntry);
217
- // binding 13: uniform { intensity: f32 }
218
- merged.push({
219
- binding: skylightBindingStart + 6,
220
- visibility: GPU_SHADER_STAGE_FRAGMENT,
221
215
  buffer: { type: 'uniform' },
222
216
  } as BglEntry);
223
217
  return merged;
@@ -226,7 +220,7 @@ export function mergeSkylightIntoMaterialBgl(
226
220
  // ─── Pure merger: BindGroupEntry values (assembly site) ─────────────────────
227
221
 
228
222
  /**
229
- * Append the 7 Skylight BindGroupEntry resources (binding 7..13) onto the
223
+ * Append the 6 Skylight BindGroupEntry resources (binding 7..12) onto the
230
224
  * existing PBR material BindGroupEntry values (binding 0..6). The caller
231
225
  * passes the result to `device.createBindGroup` as part of the merged
232
226
  * material BG.
@@ -301,10 +295,6 @@ export function assembleMaterialWithSkylightEntries(
301
295
  },
302
296
  {
303
297
  binding: iblStart + 5,
304
- resource: { kind: 'sampler', value: skylight.brdfLutSampler },
305
- },
306
- {
307
- binding: iblStart + 6,
308
298
  resource: { kind: 'buffer', value: { buffer: skylight.intensityBuffer } },
309
299
  },
310
300
  ];
@@ -413,11 +403,9 @@ export function createSkylightFallback(
413
403
  device: SkylightDevice,
414
404
  queue: SkylightQueue,
415
405
  ): SkylightFallback {
416
- // One sampler shared across the three texture slots. The PBR material
417
- // BindGroup still declares 3 separate sampler bindings (D-5 round-4
418
- // ordering at binding 8/10/12), so we pass the same handle three times
419
- // in assembleMaterialWithSkylightEntries; WebGPU spec permits sampler
420
- // reuse across BindGroupEntry slots.
406
+ // Diffuse IBL and the BRDF LUT share one binding; prefilter remains a
407
+ // separate binding so per-draw reflection probes can replace it. Both
408
+ // bindings use this linear-clamp sampler for the global environment.
421
409
  const samplerResult = device.createSampler({
422
410
  label: 'skylight-fallback-sampler',
423
411
  magFilter: 'linear',
package/src/index.ts CHANGED
@@ -560,6 +560,15 @@ export {
560
560
  type PointsStyleInput,
561
561
  } from './points-lines/admission.js';
562
562
  export type { PointsLinesInspection } from './points-lines/inspection.js';
563
+ export {
564
+ type PublishedRenderFrameInput,
565
+ type RenderPublication,
566
+ RenderPublicationError,
567
+ type RenderPublicationIdentity,
568
+ renderPublicationTransfers,
569
+ } from './publication/contract';
570
+ export { createRenderPublisher, type RenderPublicationCandidate } from './publication/publisher';
571
+ export { RenderPublicationTargetOwner, type RenderTargetAuthoring } from './publication/targets';
563
572
  /**
564
573
  * Public bounded GPU pass facts. Use the single `gpuPassTiming` opt-in,
565
574
  * `draw()` receipt, and `observe(receipt, { include: ['timings'] })` route.
package/src/instances.ts CHANGED
@@ -1,4 +1,3 @@
1
- import type { World } from '@forgeax/engine-ecs';
2
1
  import { InstanceTransformsStrideMismatchError } from '@forgeax/engine-ecs/projection';
3
2
  import type { RhiErrorCode } from '@forgeax/engine-rhi';
4
3
 
@@ -93,7 +92,7 @@ export function instanceUploadRangesForResident(
93
92
  }
94
93
 
95
94
  interface Projection extends InstanceCollectionSnapshot {
96
- readonly world: World;
95
+ readonly world: object;
97
96
  readonly entity: number;
98
97
  }
99
98
  interface Residency {
@@ -112,11 +111,15 @@ interface Residency {
112
111
  export class InstanceProjectionStore {
113
112
  private nextId = 1;
114
113
  private nextGeneration = 1;
115
- private identities = new WeakMap<World, Map<number, InstanceCollectionId>>();
114
+ private identities = new WeakMap<object, Map<number, InstanceCollectionId>>();
116
115
  private readonly records = new Map<InstanceCollectionId, Projection>();
117
116
  private readonly residency = new Map<InstanceCollectionId, Residency>();
118
117
 
119
- project(world: World, entity: number, transforms: ArrayLike<number>): InstanceCollectionSnapshot {
118
+ project(
119
+ world: object,
120
+ entity: number,
121
+ transforms: ArrayLike<number>,
122
+ ): InstanceCollectionSnapshot {
120
123
  const collectionId =
121
124
  this.identities.get(world)?.get(entity) ?? (this.nextId++ as InstanceCollectionId);
122
125
  const previous = this.records.get(collectionId);
@@ -147,7 +150,7 @@ export class InstanceProjectionStore {
147
150
 
148
151
  /** Called only after the owning RenderScene accepts its prepared changes. */
149
152
  accept(
150
- world: World,
153
+ world: object,
151
154
  entity: number,
152
155
  snapshot: {
153
156
  readonly collectionId?: InstanceCollectionId;
@@ -175,7 +178,7 @@ export class InstanceProjectionStore {
175
178
  });
176
179
  }
177
180
 
178
- release(world: World, entity: number): void {
181
+ release(world: object, entity: number): void {
179
182
  const entities = this.identities.get(world);
180
183
  const id = entities?.get(entity);
181
184
  if (id === undefined) return;
@@ -6,7 +6,7 @@
6
6
  // material BGL grows from the derived user region by appending the 7
7
7
  // Skylight resources via `mergeSkylightIntoMaterialBgl`. The
8
8
  // unlit pipeline keeps its 7-entry material BGL (no Skylight binding
9
- // 7..13 contamination) so unlit demos don't carry IBL state.
9
+ // 7..12 contamination) so unlit demos don't carry IBL state.
10
10
  // - charter P4: same pipeline layout shape drives Skylight present +
11
11
  // absent paths -- AI users do not branch on Skylight existence.
12
12
  // - feat-20260520-skylight-ibl-cubemap M4 / t59 (round-4): the
@@ -158,7 +158,10 @@ export function buildPbrMaterialUserRegionEntries(
158
158
  let entries = derived.bglEntries.map(
159
159
  (entry): GPUBindGroupLayoutEntry => ({
160
160
  binding: entry.binding,
161
- visibility: entry.visibility,
161
+ visibility:
162
+ entry.texture !== undefined || entry.sampler !== undefined
163
+ ? GPU_SHADER_STAGE_VERTEX | GPU_SHADER_STAGE_FRAGMENT
164
+ : entry.visibility,
162
165
  ...(entry.buffer === undefined ? {} : { buffer: entry.buffer }),
163
166
  ...(entry.sampler === undefined ? {} : { sampler: entry.sampler }),
164
167
  ...(entry.texture === undefined ? {} : { texture: entry.texture }),
@@ -209,8 +212,8 @@ export function buildPbrMaterialUserRegionEntries(
209
212
  // (group(0) bindings 3..7); this kind is the seam for
210
213
  // each per-material shadow override surface a future feat
211
214
  // wires onto group(1).
212
- // - 'ibl' the 7 IBL / Skylight entries (irradiance / prefilter
213
- // cube + brdfLut 2d + 3 samplers + intensity uniform).
215
+ // - 'ibl' the 6 IBL / Skylight entries (irradiance / prefilter
216
+ // cube + brdfLut 2d + 2 samplers + intensity uniform).
214
217
  // Used by `buildPbrPipelineLayouts` after the user-region
215
218
  // user paramSchema entries are emitted.
216
219
  // - 'lightmap' the 4 emissive + occlusion entries (sampler + texture
@@ -224,7 +227,7 @@ export function buildPbrMaterialUserRegionEntries(
224
227
  // placeholders because their shaders do not read these slots.
225
228
  export type InjectionKind = keyof typeof INJECTION_KIND_LENGTHS;
226
229
 
227
- const IBL_INJECTION_LENGTH = 7;
230
+ const IBL_INJECTION_LENGTH = 6;
228
231
  const LIGHTMAP_INJECTION_LENGTH = 4;
229
232
  const SHADOW_INJECTION_LENGTH = 2;
230
233
  const TRANSMISSION_INJECTION_LENGTH = 2;
@@ -283,16 +286,10 @@ export function appendInjection(
283
286
  visibility: GPU_SHADER_STAGE_FRAGMENT,
284
287
  texture: { sampleType: 'float', viewDimension: '2d' },
285
288
  },
286
- // binding start+5: brdfLutSampler
289
+ // binding start+5: uniform { intensity: f32 }
287
290
  {
288
291
  binding: start + 5,
289
292
  visibility: GPU_SHADER_STAGE_FRAGMENT,
290
- sampler: { type: 'filtering' },
291
- },
292
- // binding start+6: uniform { intensity: f32 }
293
- {
294
- binding: start + 6,
295
- visibility: GPU_SHADER_STAGE_FRAGMENT,
296
293
  buffer: { type: 'uniform' },
297
294
  },
298
295
  ];
@@ -1119,13 +1116,13 @@ export function pbrSkinMeshDynamicOffsets(
1119
1116
 
1120
1117
  /**
1121
1118
  * Build a stand-alone 7-entry unlit material BGL. Round-4 D-5 keeps unlit
1122
- * material BG isolated from Skylight binding 7..13 -- unlit demos do not
1119
+ * material BG isolated from Skylight binding 7..12 -- unlit demos do not
1123
1120
  * pay for IBL state. The unlit pipeline still binds material at slot 1,
1124
1121
  * just with a 7-entry layout.
1125
1122
  *
1126
1123
  * Note: at the moment the runtime still routes both unlit + standard
1127
1124
  * through a single 14-entry pipeline layout; the unlit material BG
1128
- * carries fallback identity resources at binding 7..13. This factory is
1125
+ * carries fallback identity resources at binding 7..12. This factory is
1129
1126
  * exported as a future-proof seam for the moment when unlit demos own
1130
1127
  * their own pipeline layout (t57 (e) test pins the contract today so the
1131
1128
  * eventual split has a green target).
@@ -1241,7 +1238,7 @@ export const SPRITE_PASS_PER_INSTANCE_REGION_VARIANT_SET = '';
1241
1238
  *
1242
1239
  * Three groups by derivation source:
1243
1240
  * 1. Shader-derived (paramSchema reflection + injection chain):
1244
- * - `'pbr-material-merged'` — derived user region + Skylight 7 + transmission 2
1241
+ * - `'pbr-material-merged'` — derived user region + Skylight 6 + transmission 2
1245
1242
  * - `'unlit-material'` — 7 entries: base PBR material only (no inject)
1246
1243
  * - `'hdrp-7-slot'` — 7 entries (binding 0 + 3..8): HDRP cluster + SSAO group(2) BGL
1247
1244
  * 2. Caps-driven literal shapes (no shader):
@@ -1638,11 +1635,11 @@ export function buildBindGroupLayoutDescriptor(
1638
1635
  }
1639
1636
  case 'pbr-material-merged': {
1640
1637
  // Material BGL: per-shader user-region (derive(paramSchema).bglEntries)
1641
- // + IBL injection (7) + transmission injection (2). The user-region
1638
+ // + IBL injection (6) + transmission injection (2). The user-region
1642
1639
  // size is the only variable; injection start = userRegion.length so a
1643
1640
  // custom schema shifts IBL/transmission by its sampler/texture pairs.
1644
- // The built-in standard-PBR schema is 17 + 7 + 2 = 26 entries;
1645
- // clearcoat texture pairs are derived from the effective root schema.
1641
+ // The built-in standard-PBR user region has 15 entries;
1642
+ // scene rows follow injection, and physical texture pairs follow the effective root schema.
1646
1643
  //
1647
1644
  // Schema source priority (D-1): explicit materialParamSchema option >
1648
1645
  // registry lookup of spec.shader.id > built-in standard-PBR fallback.
@@ -1,6 +1,5 @@
1
1
  import type { AssetRegistry } from '@forgeax/engine-assets-runtime';
2
2
  import { resolveAssetHandle } from '@forgeax/engine-assets-runtime';
3
- import type { World } from '@forgeax/engine-ecs';
4
3
  import type { RenderGraphBuilder, RenderGraphError } from '@forgeax/engine-render-graph';
5
4
  import type {
6
5
  BindGroup,
@@ -13,6 +12,7 @@ import type {
13
12
  } from '@forgeax/engine-rhi';
14
13
  import { err, type Handle, ok, type Result, type TextureAsset } from '@forgeax/engine-types';
15
14
  import type { GpuResidencyCache, TextureGpuEntry } from '../../device/gpu-residency';
15
+ import type { RenderResourceScope } from '../../publication/resource-scope';
16
16
  import type { RenderPipelineFrame, RenderPipelineTarget } from '../../render-pipeline';
17
17
  import {
18
18
  admitStandardColorLut,
@@ -41,7 +41,7 @@ export interface StandardLutGraphProjection {
41
41
  }
42
42
 
43
43
  export interface StandardLutGpuPreparationInput {
44
- readonly world: World;
44
+ readonly world: RenderResourceScope;
45
45
  readonly handle: Handle<'TextureAsset', 'shared'>;
46
46
  readonly strength: number;
47
47
  readonly assets: AssetRegistry;
@@ -125,7 +125,7 @@ export function prepareStandardLutGpu(
125
125
  if (!asset.ok) {
126
126
  return failure(
127
127
  'standard-lut-residency-failed',
128
- 'the authored TextureAsset handle resolves in the active World',
128
+ 'the authored TextureAsset handle resolves in the active RenderResourceScope',
129
129
  'register or rebuild the TextureAsset, then retry the same camera generation',
130
130
  causeDetail(asset.error),
131
131
  );
@@ -1,6 +1,6 @@
1
- import type { World } from '@forgeax/engine-ecs';
2
1
  import { err, ok, type Result, RhiError } from '@forgeax/engine-rhi';
3
2
  import { toShared } from '@forgeax/engine-types';
3
+ import type { RenderResourceScope } from '../../publication/resource-scope';
4
4
  import type { RenderFrameState } from '../../record/frame-snapshot';
5
5
  import type { RenderSystemInternals } from '../../record/render-context';
6
6
  import type { CameraSnapshot } from '../../render-contract';
@@ -17,7 +17,7 @@ export function prepareStandardOutputResources(
17
17
  state: RenderFrameState,
18
18
  internals: RenderSystemInternals,
19
19
  camera: CameraSnapshot | undefined,
20
- world: World,
20
+ world: RenderResourceScope,
21
21
  ): Result<void, RhiError> {
22
22
  const output = camera?.output;
23
23
  if (output?.exposure.kind === 'auto') {