@forgeax/engine-render 0.1.34 → 0.1.35

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 (243) hide show
  1. package/README.md +52 -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/authoring.mjs +2 -4
  12. package/dist/authoring.mjs.map +1 -1
  13. package/dist/chunk-3XUBWRY2.mjs +1842 -0
  14. package/dist/chunk-3XUBWRY2.mjs.map +1 -0
  15. package/dist/{chunk-UI5SFODX.mjs → chunk-G6HDNGY7.mjs} +2329 -1776
  16. package/dist/chunk-G6HDNGY7.mjs.map +1 -0
  17. package/dist/{chunk-HEB564WF.mjs → chunk-LM5IE7GF.mjs} +4 -4
  18. package/dist/{chunk-HEB564WF.mjs.map → chunk-LM5IE7GF.mjs.map} +1 -1
  19. package/dist/{chunk-MKDIT2EM.mjs → chunk-LNIUIKQ7.mjs} +143 -246
  20. package/dist/chunk-LNIUIKQ7.mjs.map +1 -0
  21. package/dist/chunk-MXTWKZO4.mjs +3268 -0
  22. package/dist/chunk-MXTWKZO4.mjs.map +1 -0
  23. package/dist/{chunk-ODZWR6JQ.mjs → chunk-MZ4C7EHF.mjs} +2042 -27
  24. package/dist/chunk-MZ4C7EHF.mjs.map +1 -0
  25. package/dist/chunk-RIPAEGI6.mjs +48 -0
  26. package/dist/chunk-RIPAEGI6.mjs.map +1 -0
  27. package/dist/{chunk-OCDLEI6I.mjs → chunk-SGPUJW3F.mjs} +117 -5
  28. package/dist/chunk-SGPUJW3F.mjs.map +1 -0
  29. package/dist/{chunk-ELJFJ2BU.mjs → chunk-X2KA6WHM.mjs} +29 -4
  30. package/dist/chunk-X2KA6WHM.mjs.map +1 -0
  31. package/dist/{chunk-BLXGONR2.mjs → chunk-XSFB7XXQ.mjs} +51 -4
  32. package/dist/chunk-XSFB7XXQ.mjs.map +1 -0
  33. package/dist/cloud/frame-context.d.ts +2 -2
  34. package/dist/cloud/frame-context.d.ts.map +1 -1
  35. package/dist/construct-renderer.mjs +810 -2073
  36. package/dist/construct-renderer.mjs.map +1 -1
  37. package/dist/device/gpu-residency.d.ts +19 -19
  38. package/dist/device/gpu-residency.d.ts.map +1 -1
  39. package/dist/errors/render.d.ts +3 -2
  40. package/dist/errors/render.d.ts.map +1 -1
  41. package/dist/extract/material-context.d.ts +7 -0
  42. package/dist/extract/material-context.d.ts.map +1 -0
  43. package/dist/features/host.d.ts +5 -0
  44. package/dist/features/host.d.ts.map +1 -1
  45. package/dist/features/plan.d.ts +2 -0
  46. package/dist/features/plan.d.ts.map +1 -1
  47. package/dist/features/types.d.ts +4 -0
  48. package/dist/features/types.d.ts.map +1 -1
  49. package/dist/glyph-text-layout-system.d.ts +1 -1
  50. package/dist/glyph-text-layout-system.d.ts.map +1 -1
  51. package/dist/gpu-driven/shadow-ownership.d.ts +3 -3
  52. package/dist/gpu-driven/shadow-ownership.d.ts.map +1 -1
  53. package/dist/ibl/frame-resources.d.ts.map +1 -1
  54. package/dist/ibl/skylight-bind-group.d.ts +4 -4
  55. package/dist/ibl/skylight-bind-group.d.ts.map +1 -1
  56. package/dist/index.d.ts +3 -0
  57. package/dist/index.d.ts.map +1 -1
  58. package/dist/index.mjs +530 -9
  59. package/dist/index.mjs.map +1 -1
  60. package/dist/instances.d.ts +3 -4
  61. package/dist/instances.d.ts.map +1 -1
  62. package/dist/internal.mjs +7 -8
  63. package/dist/internal.mjs.map +1 -1
  64. package/dist/pbr-pipeline.d.ts +3 -3
  65. package/dist/pbr-pipeline.d.ts.map +1 -1
  66. package/dist/pipeline/standard-output/lut-gpu.d.ts +2 -2
  67. package/dist/pipeline/standard-output/lut-gpu.d.ts.map +1 -1
  68. package/dist/pipeline/standard-output/resources.d.ts +2 -2
  69. package/dist/pipeline/standard-output/resources.d.ts.map +1 -1
  70. package/dist/publication/contract.d.ts +77 -0
  71. package/dist/publication/contract.d.ts.map +1 -0
  72. package/dist/publication/dependencies.d.ts +7 -0
  73. package/dist/publication/dependencies.d.ts.map +1 -0
  74. package/dist/publication/prepare-geometry.d.ts +9 -0
  75. package/dist/publication/prepare-geometry.d.ts.map +1 -0
  76. package/dist/publication/programs.d.ts +5 -0
  77. package/dist/publication/programs.d.ts.map +1 -0
  78. package/dist/publication/publisher.d.ts +33 -0
  79. package/dist/publication/publisher.d.ts.map +1 -0
  80. package/dist/publication/receiver.d.ts +29 -0
  81. package/dist/publication/receiver.d.ts.map +1 -0
  82. package/dist/publication/resource-scope.d.ts +18 -0
  83. package/dist/publication/resource-scope.d.ts.map +1 -0
  84. package/dist/publication/targets.d.ts +33 -0
  85. package/dist/publication/targets.d.ts.map +1 -0
  86. package/dist/publication/video.d.ts +5 -0
  87. package/dist/publication/video.d.ts.map +1 -0
  88. package/dist/record/fold-buckets.d.ts +3 -3
  89. package/dist/record/fold-buckets.d.ts.map +1 -1
  90. package/dist/record/frame-snapshot.d.ts +6 -6
  91. package/dist/record/frame-snapshot.d.ts.map +1 -1
  92. package/dist/record/frame.d.ts +5 -4
  93. package/dist/record/frame.d.ts.map +1 -1
  94. package/dist/record/helpers.d.ts +2 -2
  95. package/dist/record/helpers.d.ts.map +1 -1
  96. package/dist/record/main-pass-geometry.d.ts +2 -2
  97. package/dist/record/main-pass-geometry.d.ts.map +1 -1
  98. package/dist/record/main-pass-material.d.ts +7 -9
  99. package/dist/record/main-pass-material.d.ts.map +1 -1
  100. package/dist/record/main-pass-sprite-draws.d.ts +3 -16
  101. package/dist/record/main-pass-sprite-draws.d.ts.map +1 -1
  102. package/dist/record/main-pass.d.ts.map +1 -1
  103. package/dist/record/prepared-material-bindings.d.ts +3 -3
  104. package/dist/record/prepared-material-bindings.d.ts.map +1 -1
  105. package/dist/record/render-context.d.ts +2 -2
  106. package/dist/record/render-context.d.ts.map +1 -1
  107. package/dist/record/sprite-instance-buffer.d.ts +31 -0
  108. package/dist/record/sprite-instance-buffer.d.ts.map +1 -0
  109. package/dist/record/target-capture-graph.d.ts +19 -0
  110. package/dist/record/target-capture-graph.d.ts.map +1 -0
  111. package/dist/record/typed-frame-graph.d.ts +2 -13
  112. package/dist/record/typed-frame-graph.d.ts.map +1 -1
  113. package/dist/record/view-ubo.d.ts.map +1 -1
  114. package/dist/record/volume-params.d.ts +2 -2
  115. package/dist/record/volume-params.d.ts.map +1 -1
  116. package/dist/recovery/render-system-candidate.d.ts +5 -5
  117. package/dist/recovery/render-system-candidate.d.ts.map +1 -1
  118. package/dist/render-contract.d.ts +4 -1
  119. package/dist/render-contract.d.ts.map +1 -1
  120. package/dist/render-system-extract-tail.d.ts.map +1 -1
  121. package/dist/render-system-extract.d.ts +2 -0
  122. package/dist/render-system-extract.d.ts.map +1 -1
  123. package/dist/render-system-presentation.d.ts +2 -2
  124. package/dist/render-system-presentation.d.ts.map +1 -1
  125. package/dist/render-system-projections.d.ts +3 -2
  126. package/dist/render-system-projections.d.ts.map +1 -1
  127. package/dist/render-system-residency.d.ts +2 -2
  128. package/dist/render-system-residency.d.ts.map +1 -1
  129. package/dist/render-system.d.ts +3 -7
  130. package/dist/render-system.d.ts.map +1 -1
  131. package/dist/scene/render-scene.d.ts +8 -4
  132. package/dist/scene/render-scene.d.ts.map +1 -1
  133. package/dist/scene/render-source.d.ts +42 -0
  134. package/dist/scene/render-source.d.ts.map +1 -0
  135. package/dist/scene/source-systems.d.ts +6 -0
  136. package/dist/scene/source-systems.d.ts.map +1 -0
  137. package/dist/systems/skin-palette-allocator.d.ts.map +1 -1
  138. package/dist/systems/skin-palette-types.d.ts +10 -0
  139. package/dist/systems/skin-palette-types.d.ts.map +1 -1
  140. package/dist/systems/transparent-dispatch.d.ts +2 -2
  141. package/dist/systems/transparent-dispatch.d.ts.map +1 -1
  142. package/dist/systems/transparent-sort-config.d.ts +2 -1
  143. package/dist/systems/transparent-sort-config.d.ts.map +1 -1
  144. package/dist/temporal/index.mjs +6 -7
  145. package/package.json +21 -21
  146. package/src/__tests__/auto-exposure-transaction.integration.test.ts +4 -9
  147. package/src/__tests__/device-feature-admission.unit.test.ts +3 -2
  148. package/src/__tests__/gpu-driven-pbr.dawn.test.ts +3 -4
  149. package/src/__tests__/ktx2-basis-gpu-consumer.dawn.test.ts +4 -1
  150. package/src/__tests__/main-pass-material-medium-selection.unit.test.ts +0 -1
  151. package/src/__tests__/materials-standard-contract.unit.test.ts +0 -1
  152. package/src/__tests__/physical-clearcoat.integration.test.ts +23 -23
  153. package/src/__tests__/physical-material-budget.unit.test.ts +40 -0
  154. package/src/__tests__/render-error-code-owner.test-d.ts +1 -0
  155. package/src/__tests__/renderer-factory-material-contract.unit.test.ts +1 -1
  156. package/src/assembly/device-feature-admission.ts +2 -2
  157. package/src/assembly/host-contract.ts +5 -2
  158. package/src/assembly/recovery/renderer-recover.ts +9 -0
  159. package/src/assembly/render-target-host.ts +57 -47
  160. package/src/assembly/renderer-facade.ts +3 -1
  161. package/src/assembly/webgpu-ready.ts +1 -1
  162. package/src/assembly/webgpu-renderer.ts +89 -61
  163. package/src/cloud/frame-context.ts +2 -2
  164. package/src/device/gpu-residency.ts +26 -25
  165. package/src/errors/render.ts +3 -1
  166. package/src/extract/material-context.ts +21 -0
  167. package/src/features/host.ts +13 -5
  168. package/src/features/plan.ts +2 -0
  169. package/src/features/types.ts +4 -0
  170. package/src/glyph-text-layout-system.ts +2 -2
  171. package/src/gpu-driven/shadow-ownership.ts +3 -3
  172. package/src/ibl/frame-resources.ts +0 -1
  173. package/src/ibl/skylight-bind-group.ts +13 -25
  174. package/src/index.ts +9 -0
  175. package/src/instances.ts +9 -6
  176. package/src/pbr-pipeline.ts +11 -17
  177. package/src/pipeline/standard-output/lut-gpu.ts +3 -3
  178. package/src/pipeline/standard-output/resources.ts +2 -2
  179. package/src/publication/__tests__/derived-geometry.integration.test.ts +177 -0
  180. package/src/publication/__tests__/features.integration.test.ts +122 -0
  181. package/src/publication/__tests__/programs.unit.test.ts +25 -0
  182. package/src/publication/__tests__/publication.integration.test.ts +370 -0
  183. package/src/publication/__tests__/targets.integration.test.ts +120 -0
  184. package/src/publication/__tests__/video.browser.test.ts +50 -0
  185. package/src/publication/contract.ts +104 -0
  186. package/src/publication/dependencies.ts +54 -0
  187. package/src/publication/prepare-geometry.ts +94 -0
  188. package/src/publication/programs.ts +45 -0
  189. package/src/publication/publisher.ts +618 -0
  190. package/src/publication/receiver.ts +297 -0
  191. package/src/publication/resource-scope.ts +31 -0
  192. package/src/publication/targets.ts +182 -0
  193. package/src/publication/video.ts +33 -0
  194. package/src/record/__tests__/material-bg-cache.unit.test.ts +15 -1
  195. package/src/record/__tests__/sprite-material-abi.unit.test.ts +8 -10
  196. package/src/record/__tests__/video-texture-view.unit.test.ts +9 -9
  197. package/src/record/fold-buckets.ts +8 -5
  198. package/src/record/frame-snapshot.ts +6 -6
  199. package/src/record/frame.ts +42 -22
  200. package/src/record/helpers.ts +2 -2
  201. package/src/record/main-pass-geometry.ts +21 -2
  202. package/src/record/main-pass-material.ts +37 -33
  203. package/src/record/main-pass-sprite-draws.ts +6 -152
  204. package/src/record/main-pass.ts +8 -7
  205. package/src/record/prepared-material-bindings.ts +11 -6
  206. package/src/record/render-context.ts +2 -2
  207. package/src/record/shadow-pass.ts +0 -3
  208. package/src/record/sprite-instance-buffer.ts +155 -0
  209. package/src/record/target-capture-graph.ts +258 -0
  210. package/src/record/typed-frame-graph.ts +6 -206
  211. package/src/record/view-ubo.ts +1 -1
  212. package/src/record/volume-params.ts +6 -6
  213. package/src/recovery/render-system-candidate.ts +10 -10
  214. package/src/render-contract.ts +6 -1
  215. package/src/render-system-extract-tail.ts +33 -17
  216. package/src/render-system-extract.ts +2 -0
  217. package/src/render-system-presentation.ts +2 -2
  218. package/src/render-system-projections.ts +3 -2
  219. package/src/render-system-residency.ts +2 -2
  220. package/src/render-system.ts +82 -53
  221. package/src/scene/render-scene.ts +110 -100
  222. package/src/scene/render-source.ts +90 -0
  223. package/src/scene/source-systems.ts +33 -0
  224. package/src/systems/skin-palette-allocator.ts +1 -0
  225. package/src/systems/skin-palette-types.ts +11 -0
  226. package/src/systems/transparent-dispatch.ts +2 -2
  227. package/src/systems/transparent-sort-config.ts +3 -1
  228. package/dist/chunk-BLXGONR2.mjs.map +0 -1
  229. package/dist/chunk-ELJFJ2BU.mjs.map +0 -1
  230. package/dist/chunk-ILQ5MEZF.mjs +0 -32
  231. package/dist/chunk-ILQ5MEZF.mjs.map +0 -1
  232. package/dist/chunk-MKDIT2EM.mjs.map +0 -1
  233. package/dist/chunk-OCDLEI6I.mjs.map +0 -1
  234. package/dist/chunk-ODZWR6JQ.mjs.map +0 -1
  235. package/dist/chunk-RXZ3RGOU.mjs +0 -53
  236. package/dist/chunk-RXZ3RGOU.mjs.map +0 -1
  237. package/dist/chunk-SHFUFJ5N.mjs +0 -1781
  238. package/dist/chunk-SHFUFJ5N.mjs.map +0 -1
  239. package/dist/chunk-UI5SFODX.mjs.map +0 -1
  240. package/dist/chunk-UINHLCZA.mjs +0 -2389
  241. package/dist/chunk-UINHLCZA.mjs.map +0 -1
  242. package/dist/chunk-WEX45HKH.mjs +0 -1524
  243. package/dist/chunk-WEX45HKH.mjs.map +0 -1
@@ -1,8 +1,8 @@
1
1
  import { type DynamicTextureDevice, DynamicTextureStore } from '@forgeax/engine-assets-runtime';
2
2
  import { World } from '@forgeax/engine-ecs';
3
3
  import {
4
- VIDEO_ELEMENT_PROVIDER_KEY,
5
- type VideoElementProvider,
4
+ VIDEO_SOURCE_PROVIDER_KEY,
5
+ type VideoSourceProvider,
6
6
  } from '@forgeax/engine-graphics-extras';
7
7
  import type { Result, RhiError, Texture, TextureView } from '@forgeax/engine-rhi';
8
8
  import { ok } from '@forgeax/engine-rhi';
@@ -53,16 +53,16 @@ describe('videoTextureView provider-loss recovery', () => {
53
53
  const store = makeStore(probe);
54
54
  const errors: { readonly code: string }[] = [];
55
55
  const runtime = runtimeWithErrors((error) => errors.push(error));
56
- const provider: VideoElementProvider = {
57
- getElement: vi.fn(() => VIDEO),
56
+ const provider: VideoSourceProvider = {
57
+ getSource: vi.fn(() => VIDEO),
58
58
  };
59
- world.insertResource(VIDEO_ELEMENT_PROVIDER_KEY, provider);
59
+ world.insertResource(VIDEO_SOURCE_PROVIDER_KEY, provider);
60
60
 
61
61
  const lkg = videoTextureView(world, store, runtime, ENTITY, CLIP, false);
62
62
  expect(lkg).toBeDefined();
63
63
  expect(probe.copies).toBe(1);
64
64
 
65
- world.removeResource(VIDEO_ELEMENT_PROVIDER_KEY);
65
+ world.removeResource(VIDEO_SOURCE_PROVIDER_KEY);
66
66
  expect(videoTextureView(world, store, runtime, ENTITY, CLIP, false)).toBe(lkg);
67
67
  expect(videoTextureView(world, store, runtime, ENTITY, CLIP, false)).toBe(lkg);
68
68
  expect(videoTextureView(world, store, runtime, ENTITY, CLIP, false)).toBe(lkg);
@@ -70,19 +70,19 @@ describe('videoTextureView provider-loss recovery', () => {
70
70
  expect(errors[0]?.code).toBe('video-upload-unsupported');
71
71
  expect(probe.copies).toBe(1);
72
72
 
73
- world.insertResource(VIDEO_ELEMENT_PROVIDER_KEY, provider);
73
+ world.insertResource(VIDEO_SOURCE_PROVIDER_KEY, provider);
74
74
  expect(videoTextureView(world, store, runtime, ENTITY, CLIP, false)).toBe(lkg);
75
75
  expect(videoTextureView(world, store, runtime, ENTITY, CLIP, false)).toBe(lkg);
76
76
  expect(errors).toHaveLength(1);
77
77
  expect(probe.copies).toBe(3);
78
78
 
79
- world.removeResource(VIDEO_ELEMENT_PROVIDER_KEY);
79
+ world.removeResource(VIDEO_SOURCE_PROVIDER_KEY);
80
80
  expect(videoTextureView(world, store, runtime, ENTITY, CLIP, false)).toBe(lkg);
81
81
  expect(videoTextureView(world, store, runtime, ENTITY, CLIP, false)).toBe(lkg);
82
82
  expect(errors).toHaveLength(2);
83
83
  expect(probe.copies).toBe(3);
84
84
 
85
- world.insertResource(VIDEO_ELEMENT_PROVIDER_KEY, provider);
85
+ world.insertResource(VIDEO_SOURCE_PROVIDER_KEY, provider);
86
86
  expect(videoTextureView(world, store, runtime, ENTITY, CLIP, false)).toBe(lkg);
87
87
  expect(errors).toHaveLength(2);
88
88
  expect(probe.copies).toBe(4);
@@ -1,5 +1,4 @@
1
1
  import { HANDLE_NINESLICE_QUAD, resolveAssetHandle } from '@forgeax/engine-assets-runtime';
2
- import type { World } from '@forgeax/engine-ecs';
3
2
  import { deriveVertexCount } from '@forgeax/engine-geometry';
4
3
  import { RhiError } from '@forgeax/engine-rhi';
5
4
  import type { MaterialRenderState, MeshAsset } from '@forgeax/engine-types';
@@ -9,6 +8,8 @@ import { materialBindingKey } from '../gpu-driven/material-bindings';
9
8
  import { GpuBuffer } from '../gpu-resource';
10
9
  import { GPU_BUFFER_USAGE_COPY_DST, GPU_BUFFER_USAGE_VERTEX } from '../gpu-usage';
11
10
  import { disposeTransientInstanceBuffers } from '../instance-buffer-cache';
11
+ import type { RenderResourceScope } from '../publication/resource-scope';
12
+ import { renderAssetByGuid } from '../publication/resource-scope';
12
13
  import type { DispatchEntry, MaterialSnapshot, RenderableSnapshot } from '../render-system-extract';
13
14
  import {
14
15
  getTransparentSortConfig,
@@ -151,14 +152,14 @@ function limitRowsToMeshSsboCapacity<T>(rows: readonly T[], availableSlots: numb
151
152
  */
152
153
  export function validateRenderables(
153
154
  internals: RenderSystemInternals,
154
- world: World,
155
+ world: RenderResourceScope,
155
156
  // feat-20260709-editor-world-partition ENGINE-fix-round2 (defect 2): the full
156
157
  // worlds[] list. Each renderable's mesh handle is a user-tier slot in its OWN
157
158
  // world's sharedRefs; resolving against a foreign world either misses
158
159
  // (asset-not-registered) or resolves the wrong slot payload. `world` (the
159
160
  // resource-owner) is retained as the fallback for renderables whose worldId
160
161
  // is out of range (defensive; extractFrames always stamps a valid index).
161
- worlds: readonly World[],
162
+ worlds: readonly RenderResourceScope[],
162
163
  pipelineState: PipelineState,
163
164
  frameState: RenderFrameState,
164
165
  renderables: readonly RenderableSnapshot[],
@@ -226,7 +227,9 @@ export function validateRenderables(
226
227
  // frames hit the O(1) cache.
227
228
  const meshAssetHandle = toShared<'MeshAsset'>(r.assetHandle);
228
229
  let meshHandles = internals.gpuStore.getMeshGpuHandles(meshAssetHandle, renderableWorld);
229
- const lodMeshes = r.lods?.map((lod) => internals.assets.lookup<MeshAsset>(lod.mesh));
230
+ const lodMeshes = r.lods?.map((lod) =>
231
+ renderAssetByGuid<MeshAsset>(renderableWorld, internals.assets, lod.mesh),
232
+ );
230
233
  const resolvedLodMeshes =
231
234
  lodMeshes?.every((lod): lod is MeshAsset => lod?.kind === 'mesh') === true
232
235
  ? lodMeshes
@@ -793,7 +796,7 @@ export function cleanPerFrameCaches(
793
796
  * @internal
794
797
  */
795
798
  export function computeFoldBuckets(
796
- world: World,
799
+ world: RenderResourceScope,
797
800
  frameState: RenderFrameState,
798
801
  transparentDispatch: readonly DispatchEntry[],
799
802
  renderables: readonly RenderableSnapshot[],
@@ -1,7 +1,7 @@
1
+ import type { RenderResourceScope } from '../publication/resource-scope';
1
2
  // @forgeax/engine-runtime - RenderSystem record stage: frame-snapshot.
2
3
  // Extracted from render-system-record.ts (feat-20260704 M3/w17, pure move).
3
4
 
4
- import type { World } from '@forgeax/engine-ecs';
5
5
  import type { RenderReadVersion } from '@forgeax/engine-ecs/projection';
6
6
  import { vec3 } from '@forgeax/engine-math';
7
7
  import type {
@@ -867,8 +867,8 @@ export interface BloomFrameReceipts {
867
867
  */
868
868
  export interface ValidatedRenderable {
869
869
  readonly source: RenderableSnapshot;
870
- /** World that owns the renderable's asset handles and material snapshots. */
871
- readonly world?: World;
870
+ /** RenderResourceScope that owns the renderable's asset handles and material snapshots. */
871
+ readonly world?: RenderResourceScope;
872
872
  readonly mesh: MeshGpuHandles;
873
873
  readonly renderableIndex: number;
874
874
  readonly renderState: MaterialRenderState | undefined;
@@ -900,7 +900,7 @@ export interface ValidatedRenderable {
900
900
  * @internal
901
901
  */
902
902
  export interface DirectionalShadowCache {
903
- readonly worlds: readonly World[];
903
+ readonly worlds: readonly RenderResourceScope[];
904
904
  readonly worldStateTokens: readonly DirectionalShadowWorldState[];
905
905
  readonly assetCatalogEpoch: number;
906
906
  readonly pipelineHandle: number;
@@ -916,8 +916,8 @@ export interface DirectionalShadowCache {
916
916
  * ECS state that can change the contents of a directional shadow map.
917
917
  *
918
918
  * The ECS-owned RenderReadLease is the only render-facing mutation boundary.
919
- * Its version is intentionally conservative: any published World change
920
- * invalidates the cached atlas, while render never reaches through World for
919
+ * Its version is intentionally conservative: any published RenderResourceScope change
920
+ * invalidates the cached atlas, while render never reaches through RenderResourceScope for
921
921
  * component-clock storage.
922
922
  */
923
923
  export interface DirectionalShadowWorldState {
@@ -5,6 +5,8 @@ import {
5
5
  requiresProbeBlendRecord,
6
6
  } from '../gpu-driven/material-artifacts';
7
7
  import { projectMaterialBindingClasses } from '../gpu-driven/material-bindings';
8
+ import type { RenderResourceScope } from '../publication/resource-scope';
9
+ import { renderAssetByGuid, renderTime } from '../publication/resource-scope';
8
10
  import {
9
11
  buildDispatchPlan,
10
12
  cleanPerFrameCaches,
@@ -16,7 +18,6 @@ import { uploadMaterialUniforms } from './material-uniforms';
16
18
  // Extracted from render-system-record.ts (feat-20260704 M3/w17, pure move).
17
19
 
18
20
  import { resolveAssetHandle } from '@forgeax/engine-assets-runtime';
19
- import { Time, type World } from '@forgeax/engine-ecs';
20
21
  import type { RenderReadLease } from '@forgeax/engine-ecs/projection';
21
22
  import { frustum, mat4, vec3 } from '@forgeax/engine-math';
22
23
  import {
@@ -144,11 +145,14 @@ export type FrameObservationReadback = (
144
145
  ) => Promise<Result<Uint8Array, Error>>;
145
146
 
146
147
  /**
147
- * Fold World identity into the renderer-owned validation token. The GPU
148
+ * Fold RenderResourceScope identity into the renderer-owned validation token. The GPU
148
149
  * production owner receives only this opaque key; ECS identity and ordering
149
- * stay in the record owner where the World list is authoritative.
150
+ * stay in the record owner where the RenderResourceScope list is authoritative.
150
151
  */
151
- function residencyValidationCacheKey(world: World, worlds: readonly World[]): string {
152
+ function residencyValidationCacheKey(
153
+ world: RenderResourceScope,
154
+ worlds: readonly RenderResourceScope[],
155
+ ): string {
152
156
  return [world.identity, ...worlds.map((candidate) => candidate.identity)].join('\u001f');
153
157
  }
154
158
  function renderableIdentity(snapshot: Pick<RenderableSnapshot, 'worldId' | 'entityKey'>): string {
@@ -327,7 +331,7 @@ import {
327
331
  /**
328
332
  * Resolve the GPU mesh view used by the optional production raster before its
329
333
  * filtering pass runs. The result is keyed by the retained RenderScene slot,
330
- * which preserves the World namespace already used by gpuStore. User meshes
334
+ * which preserves the RenderResourceScope namespace already used by gpuStore. User meshes
331
335
  * are pull-resident during record; relying only on the boot-time builtin map
332
336
  * would make the first frame classify every imported mesh as a CPU fallback
333
337
  * and would hide its LOD ranges from the GPU cull. Builtins are the only
@@ -336,7 +340,7 @@ import {
336
340
  export function gpuDrivenMeshesForFrame(
337
341
  internals: RenderSystemInternals,
338
342
  pipelineState: PipelineState,
339
- worlds: readonly World[],
343
+ worlds: readonly RenderResourceScope[],
340
344
  scene: PersistentGpuDrivenState | undefined,
341
345
  ): ReadonlyMap<number, MeshGpuHandles> {
342
346
  const meshes = new Map<number, MeshGpuHandles>();
@@ -357,7 +361,7 @@ export function gpuDrivenMeshesForFrame(
357
361
  const asset = resolveAssetHandle<MeshAsset>(world, sharedHandle);
358
362
  if (asset.ok) {
359
363
  const lodMeshes = slot.snapshot.lods?.map((lod) =>
360
- internals.assets.lookup<MeshAsset>(lod.mesh),
364
+ renderAssetByGuid<MeshAsset>(world, internals.assets, lod.mesh),
361
365
  );
362
366
  const resolvedLodMeshes =
363
367
  lodMeshes?.every((lod): lod is MeshAsset => lod?.kind === 'mesh') === true
@@ -382,6 +386,7 @@ export type RecordProfileRunner = <T>(phase: RenderRecordPhase, action: () => T)
382
386
  export interface CubeCaptureFrameInput {
383
387
  readonly captureDispatch?: readonly DispatchEntry[];
384
388
  readonly snapshots: readonly CubeCameraSnapshot[];
389
+ readonly auxiliaryCameras: readonly CameraSnapshot[];
385
390
  readonly scheduler: import('../capture/scheduler').CubeCaptureScheduler;
386
391
  readonly state: CubeCaptureGraphState;
387
392
  readonly reflectionProbes?: import('./render-context').ReflectionProbeRecordState;
@@ -437,6 +442,18 @@ function prepareCubeCapture(
437
442
  (CUBE_CAPTURE_VIEW_SLOT_BASE + physicalWork.length) * VIEW_UNIFORM_SLOT_STRIDE,
438
443
  });
439
444
  }
445
+ for (const camera of input.auxiliaryCameras) {
446
+ if (camera.target === undefined) continue;
447
+ const physical = internals.getRenderTargetPhysical?.(camera.target);
448
+ if (physical?.descriptor.shape !== '2d') continue;
449
+ physicalWork.push({
450
+ target: camera.target,
451
+ faceIndex: 0,
452
+ physical,
453
+ faceCamera: camera,
454
+ viewBindGroupDynamicOffset: (CUBE_CAPTURE_VIEW_SLOT_BASE + 6) * VIEW_UNIFORM_SLOT_STRIDE,
455
+ });
456
+ }
440
457
  input.state.work = physicalWork;
441
458
  return physicalWork;
442
459
  }
@@ -840,8 +857,8 @@ function resolveVolumetricFogHistoryContext(
840
857
  */
841
858
  function prepareSpotLightProjectorFrame(
842
859
  internals: RenderSystemInternals,
843
- world: World,
844
- worlds: readonly World[],
860
+ world: RenderResourceScope,
861
+ worlds: readonly RenderResourceScope[],
845
862
  lights: ExtractedLights,
846
863
  fog: ExtractedVolumetricFog | undefined,
847
864
  ): SpotLightProjectorFrameContext | undefined {
@@ -982,15 +999,15 @@ interface FrameClockSample {
982
999
 
983
1000
  /** Read the render sample clock from the camera's composition world once per frame. */
984
1001
  function frameClockSample(
985
- world: World,
986
- worlds: readonly World[],
1002
+ world: RenderResourceScope,
1003
+ worlds: readonly RenderResourceScope[],
987
1004
  camera: CameraSnapshot,
988
1005
  sampleTimeSeconds?: number,
989
1006
  ): FrameClockSample {
990
1007
  const owner = worlds[camera.worldId ?? 0] ?? world;
991
1008
  const invalidSampleTime = sampleTimeSeconds !== undefined && !Number.isFinite(sampleTimeSeconds);
992
1009
  try {
993
- const clock = owner.getResource(Time);
1010
+ const clock = renderTime(owner);
994
1011
  return {
995
1012
  deltaSeconds: Number.isFinite(clock.delta) ? Math.max(0, clock.delta) : 0,
996
1013
  // Keep the raw host sample even when ECS accepted only a clamped
@@ -1007,7 +1024,7 @@ function frameClockSample(
1007
1024
  invalidSampleTime,
1008
1025
  };
1009
1026
  } catch {
1010
- // Test and recovery callers may provide a minimal World-shaped owner. An
1027
+ // Test and recovery callers may provide a minimal RenderResourceScope-shaped owner. An
1011
1028
  // absent clock is an invalid temporal pair and therefore safely resets.
1012
1029
  return {
1013
1030
  deltaSeconds: 0,
@@ -1372,7 +1389,7 @@ function updateVolumetricFogInspection(
1372
1389
 
1373
1390
  export function recordFrame(
1374
1391
  internals: RenderSystemInternals,
1375
- world: World,
1392
+ world: RenderResourceScope,
1376
1393
  cameras: CameraSnapshot[],
1377
1394
  lights: ExtractedLights,
1378
1395
  renderables: readonly RenderableSnapshot[],
@@ -1392,7 +1409,7 @@ export function recordFrame(
1392
1409
  // the per-world extract stage). Optional + defaulting to `[world]` keeps the
1393
1410
  // pre-split single-world identity path (worldId always 0) byte-for-byte and
1394
1411
  // the existing unit-test callers (which pass a single mock world) valid.
1395
- worlds: readonly World[] = [world],
1412
+ worlds: readonly RenderResourceScope[] = [world],
1396
1413
  profilePhase?: RecordProfileRunner,
1397
1414
  gpuDriven?: {
1398
1415
  readonly owner: GpuDrivenProduction;
@@ -2270,7 +2287,7 @@ export function recordFrame(
2270
2287
  activeVolumetricFogSignature = resolvedHistory.signature;
2271
2288
  const previousTime = frameState.volumetricFogHistorySignature?.worldTimeSeconds;
2272
2289
  const currentTime = resolvedHistory.signature.worldTimeSeconds;
2273
- // Adjacent UBO lanes: validity, World time, elapsed time since accepted history.
2290
+ // Adjacent UBO lanes: validity, RenderResourceScope time, elapsed time since accepted history.
2274
2291
  const historyParams = new Float32Array([
2275
2292
  activeVolumetricFogContext.historyValid ? 1 : 0,
2276
2293
  currentTime ?? 0,
@@ -2842,7 +2859,7 @@ export function recordFrame(
2842
2859
  // the picture authority: an identity frame publishes an identity
2843
2860
  // mapping, while a failed enable/disable candidate retains the previous
2844
2861
  // mapping together with its old picture. Never pair an LKG graph with
2845
- // the live World's newer camera component.
2862
+ // the live RenderResourceScope's newer camera component.
2846
2863
  // The exact mapping used to populate the accepted graph's UBO is the
2847
2864
  // only mapping promoted with its receipt. This keeps an identity frame,
2848
2865
  // a retained barrel LKG and a normal nonzero frame on one context.
@@ -2855,7 +2872,10 @@ export function recordFrame(
2855
2872
  // prior tick into the next topology.
2856
2873
  acknowledgeRenderFeatureSubmission(getRenderFeatureGraphState(internals).plans);
2857
2874
  }
2858
- if (cubeCapture !== undefined && cubeCaptureWork.length > 0) {
2875
+ if (
2876
+ cubeCapture !== undefined &&
2877
+ cubeCaptureWork.some((work) => work.physical.descriptor.shape === 'cube')
2878
+ ) {
2859
2879
  const completed = cubeCapture.scheduler.completeSubmission(submitted, completion);
2860
2880
  if (!completed.ok) internals.errorRegistry.fire(completed.error);
2861
2881
  }
@@ -2980,7 +3000,7 @@ function readWorldState(lease: RenderReadLease): DirectionalShadowWorldState {
2980
3000
 
2981
3001
  function sameWorldState(
2982
3002
  cached: DirectionalShadowCache,
2983
- worlds: readonly World[],
3003
+ worlds: readonly RenderResourceScope[],
2984
3004
  leases: readonly RenderReadLease[] | undefined,
2985
3005
  ): boolean {
2986
3006
  if (leases === undefined || leases.length !== worlds.length) return false;
@@ -2999,7 +3019,7 @@ function sameWorldState(
2999
3019
  changes = lease.readChanges(cachedState.version);
3000
3020
  } catch {
3001
3021
  // Treat malformed or foreign version evidence as stale and rebuild the
3002
- // atlas rather than reaching through World for a second mutation clock.
3022
+ // atlas rather than reaching through RenderResourceScope for a second mutation clock.
3003
3023
  return false;
3004
3024
  }
3005
3025
  if (
@@ -3047,7 +3067,7 @@ function sameDirectionalShadowQuality(
3047
3067
  function prepareDirectionalShadowCache(
3048
3068
  internals: RenderSystemInternals,
3049
3069
  frameState: RenderFrameState,
3050
- worlds: readonly World[],
3070
+ worlds: readonly RenderResourceScope[],
3051
3071
  leases: readonly RenderReadLease[] | undefined,
3052
3072
  lights: ExtractedLights,
3053
3073
  shadowMapSize: number | undefined,
@@ -3102,7 +3122,7 @@ function prepareDirectionalShadowCache(
3102
3122
  if (leases === undefined || leases.length !== worlds.length) {
3103
3123
  // Direct record-stage test harnesses may omit the host-owned leases. They
3104
3124
  // still exercise the real shadow encoder, but without the ECS read owner
3105
- // they must conservatively rebuild rather than inspect World internals.
3125
+ // they must conservatively rebuild rather than inspect RenderResourceScope internals.
3106
3126
  return { reuse: false, next: null };
3107
3127
  }
3108
3128
  const worldStateTokens = leases.map(readWorldState);
@@ -1,8 +1,8 @@
1
+ import type { RenderResourceScope } from '../publication/resource-scope';
1
2
  // @forgeax/engine-runtime - RenderSystem record stage: helpers.
2
3
  // Extracted from render-system-record.ts (feat-20260704 M3/w17, pure move).
3
4
 
4
5
  import { resolveAssetHandle } from '@forgeax/engine-assets-runtime';
5
- import type { World } from '@forgeax/engine-ecs';
6
6
  import { type Mat4, mat4 } from '@forgeax/engine-math';
7
7
  import type { EquirectAsset } from '@forgeax/engine-types';
8
8
  import { toShared } from '@forgeax/engine-types';
@@ -140,7 +140,7 @@ export function warnMultiSkybox(
140
140
  */
141
141
  export function driveLazyEquirectProjection(
142
142
  internals: RenderSystemInternals,
143
- world: World,
143
+ world: RenderResourceScope,
144
144
  frameState: Pick<RenderFrameState, 'firedEquirectProjectionFailedHandles'>,
145
145
  equirectHandle: number,
146
146
  ): void {
@@ -1,8 +1,9 @@
1
+ import type { RenderResourceScope } from '../publication/resource-scope';
2
+ import { resolveSpriteInstancesBuffer } from './sprite-instance-buffer';
1
3
  // @forgeax/engine-runtime - RenderSystem record stage: main-pass geometry draws.
2
4
  // feat-20260704 M5/w31: further-split from main-pass.ts (AC-05 <=1500 lines/file).
3
5
  // recordGeometryDraws + resolveGeometryInstanceBuffer, moved verbatim.
4
6
 
5
- import type { World } from '@forgeax/engine-ecs';
6
7
  import {
7
8
  type BindGroup,
8
9
  type Buffer,
@@ -495,7 +496,7 @@ export function recordGeometryDraws(
495
496
  materialSlot: number,
496
497
  submeshMaterial: MaterialSnapshot,
497
498
  entityKey: number,
498
- materialWorld: World,
499
+ materialWorld: RenderResourceScope,
499
500
  materialShaderId?: string,
500
501
  ) => BindGroup,
501
502
  passKind: PassKind = 'forward',
@@ -1916,6 +1917,24 @@ export function resolveGeometryInstanceBuffer(
1916
1917
  ): readonly GeometryInstanceDraw[] | null {
1917
1918
  const { runtime, pipelineState, frameState } = c;
1918
1919
  const inst = entry.source.instances;
1920
+ if (entry.source.spriteInstances !== undefined) {
1921
+ const { buffer, count } = resolveSpriteInstancesBuffer(
1922
+ c,
1923
+ entry,
1924
+ pipelineState.identityInstanceBuffer,
1925
+ 0,
1926
+ );
1927
+ return count === 0
1928
+ ? []
1929
+ : [
1930
+ {
1931
+ instanceBuffer: buffer,
1932
+ instanceBindGroup: resolveGeometryInstancesBindGroup(c, buffer, undefined),
1933
+ probeOffset: -1,
1934
+ instanceCount: count,
1935
+ },
1936
+ ];
1937
+ }
1919
1938
  const probeBinding =
1920
1939
  !useProbeBlend ||
1921
1940
  !runtime.device.caps.storageBuffer ||
@@ -1,14 +1,16 @@
1
1
  import { transmissionBackdropAvailable } from '../assembly/device-feature-admission';
2
2
  import { getOrCreateIblCache } from '../ibl/IblPipelineCache';
3
+ import type { RenderResourceScope } from '../publication/resource-scope';
3
4
  import type { _InternalRenderPipelineContext } from './render-context';
4
5
  // @forgeax/engine-runtime - RenderSystem record stage: main-pass-material.
5
6
  // Extracted from render-system-record.ts (feat-20260704 M3/w17, pure move).
6
7
 
7
8
  import { resolveAssetHandle } from '@forgeax/engine-assets-runtime';
8
- import type { EntityHandle, World } from '@forgeax/engine-ecs';
9
+ import type { EntityHandle } from '@forgeax/engine-ecs';
9
10
  import {
10
- VIDEO_ELEMENT_PROVIDER_KEY,
11
- type VideoElementProvider,
11
+ VIDEO_SOURCE_PROVIDER_KEY,
12
+ type VideoSourceProvider,
13
+ videoSourceExtent,
12
14
  } from '@forgeax/engine-graphics-extras';
13
15
  import {
14
16
  type BindGroup,
@@ -261,11 +263,11 @@ function materialDiagnosticsEnabled(): boolean {
261
263
  }
262
264
 
263
265
  export function residentTextureView(
264
- world: World,
266
+ world: RenderResourceScope,
265
267
  store: GpuResidencyCache,
266
268
  runtime: RenderSystemRuntime,
267
269
  handle: Handle<'TextureAsset', 'shared'>,
268
- worldId: World | number = world,
270
+ worldId: RenderResourceScope | number = world,
269
271
  ): TextureView | undefined {
270
272
  const podRes = resolveAssetHandle<TextureAsset>(world, handle);
271
273
  const diagnosticsEnabled = materialDiagnosticsEnabled();
@@ -323,11 +325,11 @@ export function residentTextureView(
323
325
  }
324
326
 
325
327
  function residentSampler(
326
- world: World,
328
+ world: RenderResourceScope,
327
329
  store: GpuResidencyCache,
328
330
  runtime: RenderSystemRuntime,
329
331
  handle: Handle<'SamplerAsset', 'shared'>,
330
- worldId: World | number = world,
332
+ worldId: RenderResourceScope | number = world,
331
333
  ): Sampler | undefined {
332
334
  const podRes = resolveAssetHandle<SamplerAsset>(world, handle);
333
335
  if (!podRes.ok) return undefined;
@@ -344,7 +346,7 @@ function residentSampler(
344
346
  // transient DynamicTextureStore, NOT the static `residentTextureView` /
345
347
  // `ensureResident` cache (video never enters that switch; AC-08).
346
348
  //
347
- // Per frame: ask the host-registered VideoElementProvider (World Resource,
349
+ // Per frame: ask the host-registered VideoSourceProvider (RenderResourceScope Resource,
348
350
  // D-1) for this entity's HTMLVideoElement, upload its current frame via
349
351
  // `store.uploadFrame` (copyExternalImageToTexture), and return the resulting
350
352
  // view. When the provider is absent / returns no element / the element has no
@@ -404,7 +406,7 @@ function clearVideoUploadFailureEpisode(
404
406
  }
405
407
 
406
408
  export function videoTextureView(
407
- world: World,
409
+ world: RenderResourceScope,
408
410
  store: DynamicTextureStore | undefined,
409
411
  runtime: RenderSystemRuntime,
410
412
  entityKey: number,
@@ -412,10 +414,14 @@ export function videoTextureView(
412
414
  highPerfAvailable: boolean,
413
415
  ): TextureView | undefined {
414
416
  if (store === undefined) return undefined;
415
- const provider = world.hasResource(VIDEO_ELEMENT_PROVIDER_KEY)
416
- ? world.getResource<VideoElementProvider>(VIDEO_ELEMENT_PROVIDER_KEY)
417
- : undefined;
418
- const element = provider?.getElement(entityKey as EntityHandle, clip);
417
+ const provider =
418
+ !('resolveAsset' in world) && world.hasResource(VIDEO_SOURCE_PROVIDER_KEY)
419
+ ? world.getResource<VideoSourceProvider>(VIDEO_SOURCE_PROVIDER_KEY)
420
+ : undefined;
421
+ const element =
422
+ 'resolveAsset' in world
423
+ ? world.videoFrame(entityKey, clip)
424
+ : provider?.getSource(entityKey as EntityHandle, clip);
419
425
  // AC-10 double-miss: a VideoPlayer entity can reach NEITHER upload path this
420
426
  // frame — no host HTMLVideoElement (general copyExternalImageToTexture path)
421
427
  // AND no high-perf GPUExternalTexture path. This is the genuine "this backend
@@ -441,13 +447,9 @@ export function videoTextureView(
441
447
  clearVideoUploadFailureEpisode(store, entityKey, clip);
442
448
  return store.getView(clip);
443
449
  }
444
- const width = element.videoWidth;
445
- const height = element.videoHeight;
446
- // Metadata dimensions can be non-zero before the decoder has produced a
447
- // current frame. WebGPU rejects copyExternalImageToTexture in that window
448
- // because the video element has no back resource yet; keep the previous
449
- // frame/default view until HAVE_CURRENT_DATA (2) is reached.
450
- if (width <= 0 || height <= 0 || element.readyState < 2) return store.getView(clip);
450
+ const extent = videoSourceExtent(element);
451
+ if (extent === undefined) return store.getView(clip);
452
+ const { width, height } = extent;
451
453
  const uploaded = store.uploadFrame(clip, element, width, height);
452
454
  if (uploaded === undefined) return store.getView(clip);
453
455
  if (!uploaded.ok) {
@@ -592,7 +594,7 @@ function writeNumericParamValue(
592
594
  export function applyMaterialTextureUvScales(
593
595
  payload: MaterialUboPayload,
594
596
  material: MaterialSnapshot,
595
- world: World,
597
+ world: RenderResourceScope,
596
598
  ): void {
597
599
  const f32 = materialUboFloatView(payload);
598
600
  const coordinateSchema = materialCoordinateSchema(material);
@@ -940,14 +942,14 @@ export function applyParamSchemaDefaultsToUbo(
940
942
  export interface PerSubmeshMaterialBgDeps {
941
943
  readonly runtime: RenderSystemRuntime;
942
944
  readonly pipelineState: PipelineState;
943
- readonly world: World;
945
+ readonly world: RenderResourceScope;
944
946
  readonly store: GpuResidencyCache;
945
947
  readonly materialSlice: number;
946
948
  readonly videoHighPerfAvailable: boolean;
947
949
  readonly skylightResources: SkylightBindGroupResources;
948
950
  readonly resolveRenderTargetTextureSource?: RenderSystemRuntime['resolveRenderTargetTextureSource'];
949
951
  readonly resolveReflectionProbeResources?: (
950
- materialWorld: World,
952
+ materialWorld: RenderResourceScope,
951
953
  entityKey: number,
952
954
  ) => { readonly resources: SkylightBindGroupResources; readonly probeIndex: number } | undefined;
953
955
  readonly materialBgShared: Map<string, WeakMap<object, unknown>>;
@@ -979,7 +981,7 @@ function recordIblMaterialBinding(
979
981
  if (receipt === undefined) return;
980
982
  const skylightBindingStart = entries.findIndex((entry) => entry.binding === iblStart);
981
983
  if (skylightBindingStart < 0) return;
982
- const iblEntries = entries.slice(skylightBindingStart, skylightBindingStart + 7);
984
+ const iblEntries = entries.slice(skylightBindingStart, skylightBindingStart + 6);
983
985
  const projected: IblBindingEntryInspection[] = [];
984
986
  for (const entry of iblEntries) {
985
987
  if (entry.resource.kind === 'externalTexture') continue;
@@ -1018,7 +1020,15 @@ export function isMaterialBgAssemblyCacheHit(
1018
1020
  sceneMaterialBuffer?: Buffer,
1019
1021
  surfaceRawDepthView?: TextureView | null,
1020
1022
  ): cached is MaterialBgAssemblyCacheEntry {
1021
- if (transmissionBackdropView != null || surfaceRawDepthView != null) return false;
1023
+ // Logical target sources keep their identity when a resize promotes a new
1024
+ // physical view. Resolve those views each frame; the lower binding cache is
1025
+ // keyed by the actual GPU resources and still reuses unchanged bindings.
1026
+ if (
1027
+ transmissionBackdropView != null ||
1028
+ surfaceRawDepthView != null ||
1029
+ (material.textureSources?.size ?? 0) > 0
1030
+ )
1031
+ return false;
1022
1032
  return (
1023
1033
  cached?.material === material &&
1024
1034
  cached.materialResourceEpoch === materialResourceEpoch &&
@@ -1031,7 +1041,6 @@ export function isMaterialBgAssemblyCacheHit(
1031
1041
  cached.skylightResources.skylightPrefilterView === skylightResources.skylightPrefilterView &&
1032
1042
  cached.skylightResources.prefilterSampler === skylightResources.prefilterSampler &&
1033
1043
  cached.skylightResources.brdfLutView === skylightResources.brdfLutView &&
1034
- cached.skylightResources.brdfLutSampler === skylightResources.brdfLutSampler &&
1035
1044
  cached.skylightResources.intensityBuffer === skylightResources.intensityBuffer
1036
1045
  );
1037
1046
  }
@@ -1040,7 +1049,7 @@ export function buildPerSubmeshMaterialBg(
1040
1049
  deps: PerSubmeshMaterialBgDeps,
1041
1050
  submeshMaterial: MaterialSnapshot,
1042
1051
  entityKey: number,
1043
- materialWorld: World = deps.world,
1052
+ materialWorld: RenderResourceScope = deps.world,
1044
1053
  materialShaderId: string | undefined = submeshMaterial.materialShaderId,
1045
1054
  ): BindGroup {
1046
1055
  const {
@@ -1175,10 +1184,6 @@ export function buildPerSubmeshMaterialBg(
1175
1184
  },
1176
1185
  {
1177
1186
  binding: 6 + smUserRegionFields.length * 2,
1178
- resource: { kind: 'sampler', value: effectiveSkylightResources.brdfLutSampler },
1179
- },
1180
- {
1181
- binding: 7 + smUserRegionFields.length * 2,
1182
1187
  resource: {
1183
1188
  kind: 'buffer',
1184
1189
  value: { buffer: effectiveSkylightResources.intensityBuffer },
@@ -1429,6 +1434,7 @@ export function buildPerSubmeshMaterialBg(
1429
1434
  if (
1430
1435
  smMaterialHandle !== undefined &&
1431
1436
  !smHasVideoFields &&
1437
+ (submeshMaterial.textureSources?.size ?? 0) === 0 &&
1432
1438
  materialResourcesResident &&
1433
1439
  !hasActiveTransmissionBackdrop
1434
1440
  ) {
@@ -1532,7 +1538,6 @@ export function resolveMaterialSkylight(
1532
1538
  prefilterView: activeViews.pref,
1533
1539
  prefilterSampler: skylightFallback.sampler,
1534
1540
  brdfLutView: activeViews.brdf,
1535
- brdfLutSampler: skylightFallback.sampler,
1536
1541
  intensityBuffer: skylightFallback.intensityBuffer,
1537
1542
  }
1538
1543
  : {
@@ -1541,7 +1546,6 @@ export function resolveMaterialSkylight(
1541
1546
  prefilterView: skylightFallback.prefilterView,
1542
1547
  prefilterSampler: skylightFallback.sampler,
1543
1548
  brdfLutView: skylightFallback.brdfLutView,
1544
- brdfLutSampler: skylightFallback.sampler,
1545
1549
  intensityBuffer: skylightFallback.intensityBuffer,
1546
1550
  };
1547
1551
  return { skylightResources, activeViews };