@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
@@ -1,3 +1,7 @@
1
+ import { addTargetCaptureGraphPasses, type CubeCaptureGraphState } from './target-capture-graph';
2
+
3
+ export type { CubeCaptureGraphState, CubeCaptureGraphWork } from './target-capture-graph';
4
+
1
5
  import { encodeMipmapLevel } from '@forgeax/engine-assets-runtime';
2
6
  import {
3
7
  type CompiledRenderGraph,
@@ -95,8 +99,6 @@ import type {
95
99
  import type { ExtractedLights, ExtractedVolumetricFog } from '../render-system-extract';
96
100
  import { SHADOW_ATLAS_DEFAULT_FACE_SIZE, SHADOW_ATLAS_DEFAULT_LAYERS } from '../shadow-atlas';
97
101
  import type { SsrAdmissionResult, SsrSpatialAdmission } from '../ssr/admission';
98
- import type { RenderTarget } from '../targets/contracts';
99
- import type { RenderTargetPhysical } from '../targets/physical';
100
102
  import {
101
103
  abortTemporalGpuSubmit,
102
104
  CLOUD_HISTORY_FORMATS,
@@ -607,19 +609,6 @@ export function createTemporalGraphRoster(input: {
607
609
  };
608
610
  }
609
611
 
610
- export interface CubeCaptureGraphWork {
611
- readonly target: RenderTarget;
612
- readonly faceIndex: number;
613
- readonly physical: RenderTargetPhysical;
614
- readonly faceCamera: import('../render-contract').CameraSnapshot;
615
- readonly viewBindGroupDynamicOffset: number;
616
- }
617
-
618
- export interface CubeCaptureGraphState {
619
- work: readonly CubeCaptureGraphWork[];
620
- reflectionProbes?: ReflectionProbeGraphState;
621
- }
622
-
623
612
  export interface ReflectionProbeFilterGraphWork {
624
613
  readonly probeIndex: number;
625
614
  readonly faceIndex: number;
@@ -666,195 +655,6 @@ export function boundedReflectionProbeFilterWork(
666
655
  return state.work.slice(0, Math.max(0, state.maxStepsPerFrame));
667
656
  }
668
657
 
669
- function renderTargetMipCount(work: CubeCaptureGraphWork): number {
670
- const { descriptor } = work.physical;
671
- return descriptor.mipLevels === 1
672
- ? 1
673
- : Math.floor(Math.log2(Math.max(descriptor.width, descriptor.height))) + 1;
674
- }
675
-
676
- function addCubeCaptureGraphPasses(
677
- builder: RenderGraphBuilder<RenderPipelineFrame>,
678
- state: CubeCaptureGraphState,
679
- ): Result<void, RenderGraphError> {
680
- for (let index = 0; index < state.work.length; index += 1) {
681
- const slot = index;
682
- const initial = state.work[slot];
683
- if (initial === undefined) continue;
684
- const current = (): CubeCaptureGraphWork => state.work[slot] ?? initial;
685
- const texture = builder.importTexture(
686
- `cube-capture.${slot}.texture`,
687
- {
688
- format: initial.physical.descriptor.format,
689
- size: {
690
- width: initial.physical.descriptor.width,
691
- height: initial.physical.descriptor.height,
692
- depthOrArrayLayers: initial.physical.descriptor.sampleCount === 4 ? 1 : 6,
693
- },
694
- mipLevelCount:
695
- initial.physical.descriptor.sampleCount === 4 ? 1 : renderTargetMipCount(initial),
696
- sampleCount: initial.physical.descriptor.sampleCount,
697
- dimension: '2d',
698
- usage: 0x10 | 0x04 | 0x01,
699
- },
700
- () => current().physical.colorTextures[current().faceIndex] ?? initial.physical.texture,
701
- );
702
- if (!texture.ok) return texture;
703
- const color = builder.importView(
704
- texture.value,
705
- {
706
- label: `cube-capture.${slot}.color`,
707
- dimension: '2d',
708
- baseMipLevel: 0,
709
- mipLevelCount: 1,
710
- baseArrayLayer: initial.physical.descriptor.sampleCount === 4 ? 0 : initial.faceIndex,
711
- arrayLayerCount: 1,
712
- },
713
- () =>
714
- current().physical.faceViews[current().faceIndex] ??
715
- initial.physical.faceViews[initial.faceIndex] ??
716
- initial.physical.view,
717
- );
718
- if (!color.ok) return color;
719
- const depthTexture = builder.importTexture(
720
- `cube-capture.${slot}.depth-texture`,
721
- {
722
- format: 'depth24plus-stencil8',
723
- size: {
724
- width: initial.physical.descriptor.width,
725
- height: initial.physical.descriptor.height,
726
- depthOrArrayLayers: initial.physical.descriptor.sampleCount === 4 ? 1 : 6,
727
- },
728
- mipLevelCount: 1,
729
- sampleCount: initial.physical.descriptor.sampleCount,
730
- dimension: '2d',
731
- usage: 0x10,
732
- },
733
- () => current().physical.depthTextures[current().faceIndex] as Texture,
734
- );
735
- if (!depthTexture.ok) return depthTexture;
736
- const depth = builder.importView(
737
- depthTexture.value,
738
- {
739
- label: `cube-capture.${slot}.depth`,
740
- dimension: '2d',
741
- baseArrayLayer: initial.physical.descriptor.sampleCount === 4 ? 0 : initial.faceIndex,
742
- arrayLayerCount: 1,
743
- },
744
- () => current().physical.depthViews[current().faceIndex] as TextureView,
745
- );
746
- if (!depth.ok) return depth;
747
- const resolveTexture =
748
- initial.physical.descriptor.sampleCount === 4
749
- ? builder.importTexture(
750
- `cube-capture.${slot}.resolve-texture`,
751
- {
752
- format: initial.physical.descriptor.format,
753
- size: {
754
- width: initial.physical.descriptor.width,
755
- height: initial.physical.descriptor.height,
756
- depthOrArrayLayers: 6,
757
- },
758
- mipLevelCount: renderTargetMipCount(initial),
759
- sampleCount: 1,
760
- dimension: '2d',
761
- usage: 0x10 | 0x04 | 0x01,
762
- },
763
- () =>
764
- current().physical.resolveTexture ??
765
- initial.physical.resolveTexture ??
766
- initial.physical.texture,
767
- )
768
- : undefined;
769
- if (resolveTexture !== undefined && !resolveTexture.ok) return resolveTexture;
770
- const resolve =
771
- resolveTexture === undefined
772
- ? undefined
773
- : builder.importView(
774
- resolveTexture.value,
775
- {
776
- label: `cube-capture.${slot}.resolve`,
777
- dimension: '2d',
778
- baseMipLevel: 0,
779
- mipLevelCount: 1,
780
- baseArrayLayer: initial.faceIndex,
781
- arrayLayerCount: 1,
782
- },
783
- () =>
784
- current().physical.resolveFaceViews[current().faceIndex] ??
785
- initial.physical.resolveFaceViews[initial.faceIndex] ??
786
- initial.physical.resolveView,
787
- );
788
- if (resolve !== undefined && !resolve.ok) return resolve;
789
- const added = builder.addRasterPass(`cube-capture-face.${slot}`, {
790
- accesses: [
791
- { resource: color.value, usage: 'color-attachment' },
792
- { resource: depth.value, usage: 'depth-stencil-write' },
793
- ...(resolve === undefined
794
- ? []
795
- : [{ resource: resolve.value, usage: 'color-attachment' as const }]),
796
- ],
797
- colorAttachments: [
798
- {
799
- view: color.value,
800
- ...(resolve === undefined ? {} : { resolveTarget: resolve.value }),
801
- loadOp: 'clear',
802
- storeOp: 'store',
803
- clearValue: () => {
804
- return { r: 0, g: 0, b: 0, a: 1 };
805
- },
806
- },
807
- ],
808
- depthStencilAttachment: {
809
- view: depth.value,
810
- depthClearValue: 1,
811
- depthLoadOp: 'clear',
812
- depthStoreOp: 'store',
813
- stencilClearValue: 0,
814
- stencilLoadOp: 'clear',
815
- stencilStoreOp: 'store',
816
- },
817
- encode: ({ pass, frame, resources }) => {
818
- const internal = frame as _InternalRenderPipelineContext;
819
- const captureColor = resources.textureView(color.value);
820
- if (!captureColor.ok) return;
821
- const captureDepth = resources.textureView(depth.value);
822
- if (!captureDepth.ok) return;
823
- const captureResolve =
824
- resolve === undefined ? undefined : resources.textureView(resolve.value);
825
- if (resolve !== undefined && (captureResolve === undefined || !captureResolve.ok)) return;
826
- const captureResolveView = captureResolve?.ok ? captureResolve.value : null;
827
- const captureContext = {
828
- ...internal,
829
- camera: current().faceCamera,
830
- // Cube faces are raw scene captures, not display-camera HDR output.
831
- // Select the PSO against the actual target attachment so an LDR
832
- // cube target is rendered by a compatible forward pipeline.
833
- tonemapActive: false,
834
- skyboxActive: false,
835
- transparentColorFormat: initial.physical.descriptor.format as GPUTextureFormat,
836
- msaaActive: initial.physical.descriptor.sampleCount === 4,
837
- viewBindGroupDynamicOffset: current().viewBindGroupDynamicOffset,
838
- geometryColorView: captureColor.value,
839
- geometryDepthView: captureDepth.value,
840
- geometryColorResolveView: captureResolveView,
841
- splitLdrSprite: false,
842
- ldrSpritePassView: null,
843
- };
844
- encodeMainPass(captureContext, pass, undefined, {
845
- colorViews: [captureColor.value],
846
- colorFormats: [initial.physical.descriptor.format as GPUTextureFormat],
847
- depthView: captureDepth.value,
848
- passKind: 'forward',
849
- clearColor: [0, 0, 0, 1],
850
- });
851
- },
852
- });
853
- if (!added.ok) return added;
854
- }
855
- return ok(undefined);
856
- }
857
-
858
658
  export function addReflectionProbeGraphPasses(
859
659
  builder: RenderGraphBuilder<RenderPipelineFrame>,
860
660
  state: ReflectionProbeGraphState,
@@ -2669,7 +2469,7 @@ export function ensureCompiledFrameGraph(
2669
2469
  // rotation must not invalidate the graph or its cached bind groups.
2670
2470
  occlusionQuery:
2671
2471
  occlusion === undefined ? undefined : { active: true, sampleCount: occlusion.sampleCount },
2672
- cubeCaptureSlots: cubeCaptureState?.work.length ?? 0,
2472
+ cubeCaptureSlots: cubeCaptureState?.work.map((work) => work.physical.descriptor),
2673
2473
  reflectionProbeSlots: cubeCaptureState?.reflectionProbes?.work.map((work) => [
2674
2474
  work.probeIndex,
2675
2475
  work.rawCaptureFace ?? -1,
@@ -2839,7 +2639,11 @@ export function ensureCompiledFrameGraph(
2839
2639
  ? {}
2840
2640
  : {
2841
2641
  projectGpuDrivenShadow: (identity) =>
2842
- gpuDriven.projectShadow?.(builder, identity) ?? ok(undefined),
2642
+ // A newly compiled graph must include the shadow compute passes
2643
+ // even when the producer cache hit. The graph owns its pass
2644
+ // list; importing only the cached view resources would reuse
2645
+ // stale indirect arguments after a topology replacement.
2646
+ gpuDriven.projectShadow?.(builder, identity, true) ?? ok(undefined),
2843
2647
  }),
2844
2648
  contributeShadowFeatures: () =>
2845
2649
  (featureShadows ??= projectRenderFeatureShadows(
@@ -2924,7 +2728,7 @@ export function ensureCompiledFrameGraph(
2924
2728
  if (!reflectionProbes.ok) return reflectionProbes;
2925
2729
  return cubeCaptureState === undefined
2926
2730
  ? ok(undefined)
2927
- : addCubeCaptureGraphPasses(builder, cubeCaptureState);
2731
+ : addTargetCaptureGraphPasses(builder, cubeCaptureState);
2928
2732
  },
2929
2733
  hasFeature: (identity) =>
2930
2734
  projectedFeaturePlans.value.some((execution) => execution.featureIdentity === identity),
@@ -30,7 +30,7 @@ export const POINTS_LINES_VIEW_BUFFER_SIZE =
30
30
  export const VIEW_UNIFORM_SLOT_STRIDE = 1024;
31
31
  export const POINT_SHADOW_VIEW_SLOT_COUNT = 24;
32
32
  export const CUBE_CAPTURE_VIEW_SLOT_BASE = 1 + POINT_SHADOW_VIEW_SLOT_COUNT;
33
- export const REFLECTION_PROBE_VIEW_SLOT_BASE = CUBE_CAPTURE_VIEW_SLOT_BASE + 6;
33
+ export const REFLECTION_PROBE_VIEW_SLOT_BASE = CUBE_CAPTURE_VIEW_SLOT_BASE + 6 + 1;
34
34
  export const REFLECTION_PROBE_VIEW_SLOT_COUNT = 16;
35
35
  export const VIEW_UNIFORM_BUFFER_SIZE =
36
36
  VIEW_UNIFORM_SLOT_STRIDE * (REFLECTION_PROBE_VIEW_SLOT_BASE + REFLECTION_PROBE_VIEW_SLOT_COUNT);
@@ -1,7 +1,7 @@
1
- import type { World } from '@forgeax/engine-ecs';
2
1
  import { vec3 } from '@forgeax/engine-math';
3
2
  import { type Buffer, RhiError } from '@forgeax/engine-rhi';
4
3
  import { GPU_BUFFER_USAGE_COPY_DST, GPU_BUFFER_USAGE_UNIFORM } from '../gpu-usage';
4
+ import type { RenderResourceScope } from '../publication/resource-scope';
5
5
  import type { SpotLightProjectorFrameContext, VolumetricFogFrameContext } from '../render-contract';
6
6
  import type { ExtractedLights, ExtractedVolumetricFog } from '../render-system-extract';
7
7
  import { VOLUMETRIC_FOG_PARAMS_BYTES } from '../volume/resources';
@@ -57,8 +57,8 @@ export function stageVolumetricFogParams(
57
57
  function prepareVolumetricFogMember(
58
58
  internals: RenderSystemInternals,
59
59
  frameState: RenderFrameState,
60
- world: World,
61
- worlds: readonly World[],
60
+ world: RenderResourceScope,
61
+ worlds: readonly RenderResourceScope[],
62
62
  fog: ExtractedVolumetricFog | undefined,
63
63
  lights: ExtractedLights,
64
64
  volumeCapability: boolean,
@@ -157,7 +157,7 @@ function prepareVolumetricFogMember(
157
157
  frameState.temporalFrame !== undefined && frameState.temporalFrame.resetReason === 'none'
158
158
  ? 1
159
159
  : 0;
160
- // World elapsed time is the sole authored-medium clock.
160
+ // RenderResourceScope elapsed time is the sole authored-medium clock.
161
161
  params[32] = fog.worldTimeSeconds ?? 0;
162
162
  return {
163
163
  densityTexture: resident.value.texture.handle,
@@ -180,8 +180,8 @@ function prepareVolumetricFogMember(
180
180
  export function prepareVolumetricFogFrame(
181
181
  internals: RenderSystemInternals,
182
182
  frameState: RenderFrameState,
183
- world: World,
184
- worlds: readonly World[],
183
+ world: RenderResourceScope,
184
+ worlds: readonly RenderResourceScope[],
185
185
  fog: ExtractedVolumetricFog | undefined,
186
186
  lights: ExtractedLights,
187
187
  volumeCapability: boolean,
@@ -47,11 +47,13 @@ import type { PipelineBuilderShaderModuleFactory } from '../pipeline-builder';
47
47
  import { standardTopologyVariantSet } from '../pipeline-spec';
48
48
  import { inspectPointShadow } from '../point-shadow-inspection';
49
49
  import type { PreparedGraphicsResolver } from '../prepare/prepared-graphics-resolver';
50
+ import type { RenderResourceScope } from '../publication/resource-scope';
50
51
  import { gpuDrivenMeshesForFrame, validateRenderables } from '../record/frame';
51
52
  import { prepareFrameLighting } from '../record/frame-lighting';
52
53
  import type { RenderFrameState, ValidatedRenderable } from '../record/frame-snapshot';
53
54
  import { resolveGeometryInstanceBuffer } from '../record/main-pass-geometry';
54
55
  import { computeSplitLdrSprite } from '../record/main-pass-sprite-draws';
56
+ import type { PreparedResolverCaches } from '../record/prepared-material-bindings';
55
57
  import { ensureProbeBlendRecordBuffer } from '../record/probe-blend-buffer';
56
58
  import {
57
59
  prepareRecoveryPipelineReadiness,
@@ -89,25 +91,16 @@ import type {
89
91
  RecoveryPointsLinesCandidate,
90
92
  } from './types';
91
93
 
92
- export interface PreparedResolverCaches {
93
- readonly preparedPipelineIds: WeakMap<object, string>;
94
- readonly preparedMaterialPipelineShaders: WeakMap<object, string>;
95
- readonly preparedGroup0Pipelines: WeakSet<object>;
96
- readonly preparedViewOnlyPipelines: WeakSet<object>;
97
- readonly preparedRenderMaterialPipelines: WeakSet<object>;
98
- readonly preparedAssetHandles: WeakMap<World, Map<string, number>>;
99
- }
100
-
101
94
  export interface RecoveryFrameSeed {
102
95
  readonly frame: ExtractedFrame;
103
- readonly worlds: readonly World[];
96
+ readonly worlds: readonly RenderResourceScope[];
104
97
  readonly cameraOwner: number;
105
98
  readonly resourceOwner: number;
106
99
  readonly width: number;
107
100
  readonly height: number;
108
101
  /** Last submitted Surface page is reused while the replacement device warms. */
109
102
  readonly surfaceDynamicInput?: SurfaceDynamicInputFrame;
110
- /** Last submitted World/App time snapshot for the Surface frame ABI. */
103
+ /** Last submitted RenderResourceScope/App time snapshot for the Surface frame ABI. */
111
104
  readonly frameTime?: number;
112
105
  /** Exact forward artifacts accepted by the submitted frame. */
113
106
  readonly materialArtifacts?: ReadonlyMap<string, MaterialShaderArtifact>;
@@ -144,7 +137,7 @@ export interface RenderSystemRecoveryDependencies {
144
137
  readonly createPreparedResolverFactory: (
145
138
  runtime: RenderSystemInternals,
146
139
  gpuWork: RenderFeatureGpuWorkOwner,
147
- worlds: readonly World[],
140
+ worlds: readonly RenderResourceScope[],
148
141
  caches: PreparedResolverCaches,
149
142
  ) => (input: RenderFeaturePreparedGraphicsResolverInput) => PreparedGraphicsResolver;
150
143
  readonly createPreparedResolverCaches: () => PreparedResolverCaches;
@@ -229,7 +222,7 @@ export function createRenderSystemRecovery(
229
222
  } = deps;
230
223
 
231
224
  // This is a detached CPU projection, retained only after queue submission.
232
- // It is not a World/lease cache and it never carries target or scheduler state.
225
+ // It is not a RenderResourceScope/lease cache and it never carries target or scheduler state.
233
226
  let lastSuccessfulFrameSeed: RecoveryFrameSeed | undefined;
234
227
 
235
228
  const createRecoveryCandidateFrameState = (): RenderFrameState => {
@@ -406,7 +399,7 @@ export function createRenderSystemRecovery(
406
399
  // A successful clear-only draw can have no display camera. There is no
407
400
  // frame graph to rehydrate in that case, but the device generation is
408
401
  // still recoverable; the next camera-bearing draw will build its graph
409
- // from the current World composition.
402
+ // from the current RenderResourceScope composition.
410
403
  if (camera === undefined) return { kind: 'no-seed' };
411
404
 
412
405
  const candidateFrameState = createRecoveryCandidateFrameState();
@@ -445,7 +438,6 @@ export function createRenderSystemRecovery(
445
438
  if (pendingOutput !== undefined) retireAutoExposureGpuResources(pendingOutput);
446
439
  candidateFrameState.pendingAutoExposureGpuResources = undefined;
447
440
  for (const work of setupWorks) work.discard();
448
- setupWorks.length = 0;
449
441
  const graph = candidateFrameState.compiledFrameGraph;
450
442
  candidateFrameState.compiledFrameGraph = null;
451
443
  candidateFrameState.perFrameGraph = null;
@@ -504,7 +496,7 @@ export function createRenderSystemRecovery(
504
496
  candidateFrameState,
505
497
  runtime.internals,
506
498
  camera,
507
- seed.worlds[seed.cameraOwner] as World,
499
+ seed.worlds[seed.cameraOwner] as RenderResourceScope,
508
500
  );
509
501
  if (!output.ok) return failed('candidate Standard output preparation failed', output.error);
510
502
  const gpuScene = persistentRenderScene.prepareRecoveryGpuDrivenCandidate(
@@ -514,7 +506,7 @@ export function createRenderSystemRecovery(
514
506
  candidateGpuDrivenScene = gpuScene.value;
515
507
  candidateValidated = validateRenderables(
516
508
  runtime.internals,
517
- seed.worlds[seed.cameraOwner] as World,
509
+ seed.worlds[seed.cameraOwner] as RenderResourceScope,
518
510
  seed.worlds,
519
511
  runtime.pipelineState,
520
512
  candidateFrameState,
@@ -731,7 +723,7 @@ export function createRenderSystemRecovery(
731
723
  cloudHistory: seed.frame.cloudLayer !== undefined,
732
724
  });
733
725
  const featureFrame = runRenderFeatureFrame(candidateFeatureHost, {
734
- worlds: seed.worlds,
726
+ worlds: seed.worlds.filter((world): world is World => !('resolveAsset' in world)),
735
727
  owner: seed.resourceOwner,
736
728
  frameNumber: candidateFrameState.frameNumber,
737
729
  selectedCamera: camera,
@@ -62,6 +62,7 @@ import type {
62
62
  } from './inspection-types.js';
63
63
  import type { InstanceCollectionInspection } from './instances.js';
64
64
  import type { RenderExtent } from './pipeline/render-extent.js';
65
+ import type { PublishedRenderFrameInput, RenderPublicationIdentity } from './publication/contract';
65
66
  import type { RenderSceneBounds } from './scene/render-scene-types.js';
66
67
  import type { SsrAdmissionIdentity } from './ssr/identity.js';
67
68
  import type {
@@ -446,7 +447,9 @@ export interface Renderer {
446
447
  dynamicGeometryReceipt(candidate: DynamicGeometryCandidate): DynamicGeometryReceipt | undefined;
447
448
  cancelDynamicGeometry(candidate: DynamicGeometryCandidate): Result<void, DynamicGeometryError>;
448
449
  retireDynamicGeometry(candidate: DynamicGeometryCandidate): Result<void, DynamicGeometryError>;
449
- draw(input: RenderFrameInput): RenderResult<FrameReceipt, RenderError>;
450
+ draw(
451
+ input: RenderFrameInput | PublishedRenderFrameInput,
452
+ ): RenderResult<FrameReceipt, RenderError>;
450
453
  /**
451
454
  * Arm one upcoming submitted frame for explicit color-domain observation.
452
455
  * Observation buffers are not created, copied, mapped, or retained until a
@@ -1062,6 +1065,8 @@ export type RenderPhaseSkipReason =
1062
1065
 
1063
1066
  /** First-version options bag (intentionally empty; reserved for v0.1). */
1064
1067
  export interface RendererOptions {
1068
+ /** Bind a source session before receiving any publication. */
1069
+ readonly publicationSource?: RenderPublicationIdentity;
1065
1070
  /** Producer-owned features installed by the renderer host. */
1066
1071
  readonly features?: readonly RenderFeature<unknown>[] | undefined;
1067
1072
  /** Standard profile consumed by the single renderer-owned pipeline. */
@@ -183,7 +183,7 @@ import {
183
183
  } from './render-system-extract';
184
184
  import { buildShadowFrusta } from './scene/shadow-visibility';
185
185
  import { SHADOW_ATLAS_DEFAULT_LAYERS } from './shadow-atlas';
186
- import type { SkinPaletteReceipt } from './systems/skin-palette-types';
186
+ import type { SkinPaletteReceipt, SkinPose } from './systems/skin-palette-types';
187
187
 
188
188
  function skinPaletteIdentity(world: World, entity: number): string {
189
189
  return `skin:${world.identity}:${entity}`;
@@ -2137,6 +2137,11 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
2137
2137
  if (asset.kind !== 'material') {
2138
2138
  materialSnap = defaultMaterialSnapshot(handleRaw);
2139
2139
  } else {
2140
+ // Program selection belongs to the shared source; RuntimeMaterialValue
2141
+ // only projects the numeric parameter content resolved below.
2142
+ const source = world.sharedRefs.resolve<string, Asset>(tagged);
2143
+ const programOwner =
2144
+ source.ok && source.value.kind === 'material' ? source.value : asset;
2140
2145
  // feat-20260529 M3 / w11: material parent chain inheritance via
2141
2146
  // read-through _materialWalk accessor (plan-strategy D-6).
2142
2147
  // The old direct asset.passes / asset.values read never
@@ -2186,7 +2191,7 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
2186
2191
  };
2187
2192
  try {
2188
2193
  materialProgramSelections = materialProgramSelectionsForMaterial(
2189
- asset,
2194
+ programOwner,
2190
2195
  assets,
2191
2196
  materialContext,
2192
2197
  );
@@ -2198,10 +2203,14 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
2198
2203
  // publication error instead of guessing from module names/order.
2199
2204
  if (isMissingMaterialProgram(error) && materialContext?.geometry === 'mesh') {
2200
2205
  try {
2201
- const skinnedSelections = materialProgramSelectionsForMaterial(asset, assets, {
2202
- ...materialContext,
2203
- geometry: 'skinned',
2204
- });
2206
+ const skinnedSelections = materialProgramSelectionsForMaterial(
2207
+ programOwner,
2208
+ assets,
2209
+ {
2210
+ ...materialContext,
2211
+ geometry: 'skinned',
2212
+ },
2213
+ );
2205
2214
  if (
2206
2215
  skinnedSelections?.some(materialProgramSelectionRequiresSkin) === true &&
2207
2216
  gpuDrivenMesh !== undefined
@@ -2238,7 +2247,7 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
2238
2247
  ]),
2239
2248
  );
2240
2249
  const materialSceneIndexProgramKeys = materialSceneIndexProgramKeysForMaterial(
2241
- asset,
2250
+ programOwner,
2242
2251
  assets,
2243
2252
  materialContext,
2244
2253
  );
@@ -2739,6 +2748,7 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
2739
2748
  // resolve / validate / write chain (D-9 reset already fired at
2740
2749
  // extractFrame entry; per-entity allocate + writeJointPalette here).
2741
2750
  let skinSlice: SkinPaletteReceipt | undefined;
2751
+ let skinPose: SkinPose | undefined;
2742
2752
  const skinIdentity = hasSkin ? skinPaletteIdentity(world, entity) : undefined;
2743
2753
  const releaseSkinIdentity = (): void => {
2744
2754
  if (skinIdentity !== undefined) {
@@ -2850,6 +2860,17 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
2850
2860
  });
2851
2861
  skinPaletteAllocator.writePersistentJointPalette(receipt, ibms, jointWorlds);
2852
2862
  skinSlice = receipt;
2863
+ } else {
2864
+ skinPose = {
2865
+ identity: skinPaletteIdentity(world, entity),
2866
+ generation: ((skeletonHandleRaw >>> 0) ^ skeleton.jointCount) >>> 0,
2867
+ jointCount: skeleton.jointCount,
2868
+ ...(hasFiniteOrderedLocalAabb(skeleton.bounds)
2869
+ ? { bounds: new Float32Array(skeleton.bounds) }
2870
+ : {}),
2871
+ inverseBindMatrices: ibms,
2872
+ jointWorlds,
2873
+ };
2853
2874
  }
2854
2875
  }
2855
2876
  }
@@ -2858,7 +2879,7 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
2858
2879
  // projection. Do not publish a renderable or its staged dispatch when
2859
2880
  // palette extraction was unavailable; the record stage must never
2860
2881
  // reinterpret a skinned vertex stream through an ordinary mesh group.
2861
- if (skinSlice === undefined && hasSkin) {
2882
+ if (skinSlice === undefined && skinPose === undefined && hasSkin) {
2862
2883
  releaseSkinIdentity();
2863
2884
  if (
2864
2885
  materialSnap.materialShaderId === 'forgeax::pbr-skin' ||
@@ -3128,6 +3149,7 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
3128
3149
  entityKey: entity,
3129
3150
  ...(authorVisible ? {} : { authorVisible: false }),
3130
3151
  ...(skinSlice !== undefined ? { skin: skinSlice } : {}),
3152
+ ...(skinPose === undefined ? {} : { skinPose }),
3131
3153
  ...(skin?.joints === undefined
3132
3154
  ? {}
3133
3155
  : { skinJointEntities: Array.from(skin.joints, (joint) => Number(joint)) }),
@@ -3136,21 +3158,24 @@ export function extractFrame(world: World, context: PreparedExtractContext): Ext
3136
3158
  ...(spriteInstancesSnap !== undefined ? { spriteInstances: spriteInstancesSnap } : {}),
3137
3159
  ...(pointsLines !== undefined ? { pointsLines } : {}),
3138
3160
  };
3139
- const gpuDrivenDraws = buildGpuDrivenDraws({
3140
- mesh: gpuDrivenMesh,
3141
- materials: materialsArr,
3142
- fallbackMaterial: materialSnap,
3143
- baseSnapshot: baseRenderable,
3144
- ...(getMaterialShaderArtifact === undefined ? {} : { getMaterialShaderArtifact }),
3145
- ...(lods === undefined
3146
- ? {}
3147
- : {
3148
- lodMeshes: lods.map((lod) => {
3149
- const lower = assets?.lookup<Asset>(lod.mesh);
3150
- return lower?.kind === 'mesh' ? lower : undefined;
3151
- }),
3152
- }),
3153
- });
3161
+ const gpuDrivenDraws =
3162
+ skinPose === undefined
3163
+ ? buildGpuDrivenDraws({
3164
+ mesh: gpuDrivenMesh,
3165
+ materials: materialsArr,
3166
+ fallbackMaterial: materialSnap,
3167
+ baseSnapshot: baseRenderable,
3168
+ ...(getMaterialShaderArtifact === undefined ? {} : { getMaterialShaderArtifact }),
3169
+ ...(lods === undefined
3170
+ ? {}
3171
+ : {
3172
+ lodMeshes: lods.map((lod) => {
3173
+ const lower = assets?.lookup<Asset>(lod.mesh);
3174
+ return lower?.kind === 'mesh' ? lower : undefined;
3175
+ }),
3176
+ }),
3177
+ })
3178
+ : [];
3154
3179
  for (const draw of gpuDrivenDraws) {
3155
3180
  if (draw.preparationError === undefined) continue;
3156
3181
  worldInternal._routeError(draw.preparationError, {
@@ -792,6 +792,8 @@ export interface RenderableSnapshot {
792
792
  * Absent (`undefined`) means the entity is not skinned.
793
793
  */
794
794
  readonly skin?: SkinPaletteReceipt;
795
+ /** Source pose waiting for the receiving Renderer to assign a palette. */
796
+ readonly skinPose?: import('./systems/skin-palette-types').SkinPose;
795
797
  /** Joint identities consumed by the retained scene dependency index. */
796
798
  readonly skinJointEntities?: readonly number[];
797
799
  /** Source pass ownership retained with the scene record across view changes. */
@@ -2360,9 +2362,16 @@ export function resolveMaterialSnapshot(
2360
2362
  if (!resolvedResult.ok) return defaultMaterialSnapshot(handleRaw);
2361
2363
  const resolved = resolvedResult.value;
2362
2364
  const allPasses = resolved.passes;
2363
- const materialProgramKeys = materialProgramKeysForMaterial(asset, assetsRef, materialContext);
2365
+ // Numeric World content changes values, never the immutable program owner.
2366
+ const source = world.sharedRefs.resolve<string, Asset>(tagged);
2367
+ const programOwner = source.ok && source.value.kind === 'material' ? source.value : asset;
2368
+ const materialProgramKeys = materialProgramKeysForMaterial(
2369
+ programOwner,
2370
+ assetsRef,
2371
+ materialContext,
2372
+ );
2364
2373
  const materialSceneIndexProgramKeys = materialSceneIndexProgramKeysForMaterial(
2365
- asset,
2374
+ programOwner,
2366
2375
  assetsRef,
2367
2376
  materialContext,
2368
2377
  );
@@ -1,4 +1,4 @@
1
- import type { World } from '@forgeax/engine-ecs';
1
+ import type { RenderResourceScope } from './publication/resource-scope';
2
2
  import type { RenderSystemInternals } from './record/render-context';
3
3
  import type { FramePresentation } from './render-contract';
4
4
  import type { DispatchEntry, RenderableSnapshot } from './render-system-extract';
@@ -20,7 +20,7 @@ export function projectFramePresentation(input: {
20
20
  readonly hasEnvironment: boolean;
21
21
  readonly submissionRenderables: readonly RenderableSnapshot[];
22
22
  readonly submissionDispatch: readonly DispatchEntry[];
23
- readonly preparedWorlds: readonly World[];
23
+ readonly preparedWorlds: readonly RenderResourceScope[];
24
24
  readonly internals: RenderSystemInternals;
25
25
  }): FramePresentationProjection {
26
26
  const displayRenderableIndices = new Set<number>();
@@ -6,6 +6,7 @@ import type {
6
6
  LodOcclusionInspectionRow,
7
7
  LodOcclusionWorldInspection,
8
8
  } from './inspection-types';
9
+ import type { RenderResourceScope } from './publication/resource-scope';
9
10
  import { worldEntityKey } from './record/frame-snapshot';
10
11
  import type { CameraSnapshot, DrawOwnerOptions } from './render-contract';
11
12
  import type { DispatchEntry, RenderableSnapshot } from './render-system-extract';
@@ -52,7 +53,7 @@ interface LodInspectionQuerySummary {
52
53
  }
53
54
 
54
55
  export function createLodWorldInspections(
55
- worlds: readonly World[],
56
+ worlds: readonly RenderResourceScope[],
56
57
  renderables: readonly RenderableSnapshot[],
57
58
  submittedRenderables: readonly RenderableSnapshot[],
58
59
  camera: CameraSnapshot,
@@ -164,7 +165,7 @@ export function createLodWorldInspections(
164
165
  /** Apply the configured stable sub-sort to transparent dispatch entries. */
165
166
  export function sortTransparentDispatch(
166
167
  dispatch: DispatchEntry[],
167
- world: World,
168
+ world: RenderResourceScope,
168
169
  cameras: readonly CameraSnapshot[],
169
170
  renderables: readonly RenderableSnapshot[],
170
171
  ): DispatchEntry[] {