@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
@@ -0,0 +1,134 @@
1
+ import { AssetRegistry, RuntimeMaterialValue } from '@forgeax/engine-assets-runtime';
2
+ import { World } from '@forgeax/engine-ecs';
3
+ import type { BindGroupLayout } from '@forgeax/engine-rhi';
4
+ import { ShaderRegistry } from '@forgeax/engine-shader';
5
+ import { type MaterialAsset, ok } from '@forgeax/engine-types';
6
+ import { expect, it } from 'vitest';
7
+ import { renderPublicationTransfers } from '../../publication/contract';
8
+ import { createRenderPublisher } from '../../publication/publisher';
9
+ import { RenderPublicationReceiver } from '../../publication/receiver';
10
+ import { preparedMaterialBindings } from '../prepared-material-bindings';
11
+ import type { RenderSystemInternals } from '../render-context';
12
+
13
+ it('uploads accepted values on World and published-reader paths without mutating the base', () => {
14
+ const world = new World();
15
+ const assets = new AssetRegistry(new ShaderRegistry({ manifestUrl: undefined }));
16
+ const guid = '12345678-1234-1234-1234-123456789abc';
17
+ const base: MaterialAsset = { kind: 'material', values: { roughness: 0.8 } };
18
+ assets.catalog(guid, base);
19
+ const handle = world.internSharedRef('MaterialAsset', base);
20
+ const content = world
21
+ .spawn({
22
+ component: RuntimeMaterialValue,
23
+ data: { asset: handle, parameter: 'roughness', kind: 0, value: [0.25] },
24
+ })
25
+ .unwrap();
26
+ const uploads: number[] = [];
27
+ let destroys = 0;
28
+ const runtime = {
29
+ assets,
30
+ getParamSchema: () => [{ name: 'roughness', type: 'f32' }],
31
+ getPipelineState: () => ({
32
+ defaultSampler: {},
33
+ skylightFallback: {
34
+ irradianceView: {},
35
+ prefilterView: {},
36
+ brdfLutView: {},
37
+ sampler: {},
38
+ intensityBuffer: {},
39
+ },
40
+ }),
41
+ device: {
42
+ caps: { storageBuffer: false },
43
+ limits: { maxSampledTexturesPerShaderStage: 16 },
44
+ createBuffer: () => ok({}),
45
+ destroyBuffer: () => {
46
+ destroys++;
47
+ return ok(undefined);
48
+ },
49
+ createBindGroup: () => ok({}),
50
+ queue: {
51
+ writeBuffer: (_buffer: unknown, _offset: number, bytes: Uint8Array) => {
52
+ uploads.push(new Float32Array(bytes.buffer, bytes.byteOffset, 1)[0] ?? NaN);
53
+ return ok(undefined);
54
+ },
55
+ },
56
+ },
57
+ } as unknown as RenderSystemInternals;
58
+ const identity = { source: 'material-values', epoch: 1 };
59
+ const publisher = createRenderPublisher(world, assets, identity, undefined, [
60
+ {
61
+ identity: 'material-consumer',
62
+ extract: () => ok({}),
63
+ assetDependencies: () => [guid],
64
+ plan: () => ok({ resources: [], passes: [] }),
65
+ },
66
+ ]);
67
+ const receiver = new RenderPublicationReceiver(identity);
68
+ const publish = () => {
69
+ const candidate = publisher.prepare(0).unwrap();
70
+ const packet = structuredClone(candidate.packet);
71
+ candidate.accept();
72
+ const resources = receiver.accept(packet).unwrap().resources;
73
+ publisher.recycle(packet.revision, renderPublicationTransfers(packet)).unwrap();
74
+ return resources;
75
+ };
76
+ const reader = publish();
77
+ for (const source of [world, reader]) {
78
+ const bindings = preparedMaterialBindings(
79
+ runtime,
80
+ [source],
81
+ 'shader',
82
+ 0,
83
+ guid,
84
+ {} as BindGroupLayout,
85
+ ).unwrap();
86
+ if ('release' in bindings) bindings.release?.();
87
+ }
88
+ expect(uploads).toEqual([0.25, 0.25]);
89
+ expect(base.values?.roughness).toBe(0.8);
90
+ const other = new World();
91
+ const otherHandle = other.internSharedRef('MaterialAsset', base);
92
+ const otherContent = other
93
+ .spawn({
94
+ component: RuntimeMaterialValue,
95
+ data: {
96
+ asset: otherHandle,
97
+ parameter: 'roughness',
98
+ kind: 0,
99
+ value: [0.6],
100
+ },
101
+ })
102
+ .unwrap();
103
+ for (const [owner, expected] of [
104
+ [1, 0.6],
105
+ [0, 0.25],
106
+ ] as const) {
107
+ const bindings = preparedMaterialBindings(
108
+ runtime,
109
+ [world, other],
110
+ 'shader',
111
+ owner,
112
+ guid,
113
+ {} as BindGroupLayout,
114
+ ).unwrap();
115
+ expect(uploads.at(-1)).toBeCloseTo(expected);
116
+ if ('release' in bindings) bindings.release?.();
117
+ }
118
+ other.despawn(otherContent).unwrap();
119
+ world.despawn(content).unwrap();
120
+ for (const source of [world, publish()]) {
121
+ const restored = preparedMaterialBindings(
122
+ runtime,
123
+ [source],
124
+ 'shader',
125
+ 0,
126
+ guid,
127
+ {} as BindGroupLayout,
128
+ ).unwrap();
129
+ expect(uploads.at(-1)).toBeCloseTo(0.8);
130
+ if ('release' in restored) restored.release?.();
131
+ }
132
+ expect(destroys).toBe(6);
133
+ publisher.dispose();
134
+ });
@@ -72,22 +72,21 @@ describe('sprite Standard material ABI', () => {
72
72
  prefilterView: {} as TextureView,
73
73
  prefilterSampler: sampler,
74
74
  brdfLutView: {} as TextureView,
75
- brdfLutSampler: sampler,
76
75
  intensityBuffer: {} as Buffer,
77
76
  });
78
77
  expect(merged[15]).toMatchObject({
79
78
  binding: 15,
80
79
  resource: { kind: 'textureView' },
81
80
  });
82
- expect(merged[22]).toMatchObject({
83
- binding: 22,
81
+ expect(merged[21]).toMatchObject({
82
+ binding: 21,
84
83
  resource: { kind: 'sampler', value: sampler },
85
84
  });
86
- expect(merged[23]).toMatchObject({
87
- binding: 23,
85
+ expect(merged[22]).toMatchObject({
86
+ binding: 22,
88
87
  resource: { kind: 'textureView' },
89
88
  });
90
- expect(merged).toHaveLength(25);
89
+ expect(merged).toHaveLength(24);
91
90
  expect(merged.find((entry) => entry.binding === 47)?.resource.kind).toBe('textureView');
92
91
 
93
92
  const complete = [
@@ -97,7 +96,7 @@ describe('sprite Standard material ABI', () => {
97
96
  resource: { kind: 'buffer' as const, value: { buffer: {} as Buffer } },
98
97
  },
99
98
  ];
100
- expect(complete).toHaveLength(26);
99
+ expect(complete).toHaveLength(25);
101
100
  expect(complete.find((entry) => entry.binding === 46)).toMatchObject({
102
101
  binding: 46,
103
102
  resource: { kind: 'buffer' },
@@ -112,13 +111,12 @@ describe('sprite Standard material ABI', () => {
112
111
  prefilterView: {} as TextureView,
113
112
  prefilterSampler: sampler,
114
113
  brdfLutView: {} as TextureView,
115
- brdfLutSampler: sampler,
116
114
  intensityBuffer: {} as Buffer,
117
115
  },
118
116
  { sampler, backdropView: backdrop },
119
117
  );
120
- expect(active[23]).toMatchObject({
121
- binding: 23,
118
+ expect(active[22]).toMatchObject({
119
+ binding: 22,
122
120
  resource: { kind: 'textureView', value: backdrop },
123
121
  });
124
122
  });
@@ -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 {
@@ -57,7 +58,7 @@ import {
57
58
  resolveDepthOfFieldFrameParams,
58
59
  } from '../features/depth-of-field/depth-of-field-params';
59
60
  import type { GpuDrivenProduction, PreparedGpuDrivenFrame } from '../gpu-driven/production-raster';
60
- import type { ShadowViewIdentity } from '../gpu-driven/shadow-views';
61
+ import { type ShadowViewIdentity, shadowViewIdentityKey } from '../gpu-driven/shadow-views';
61
62
  import type { GpuDrivenLodSubmitIdentity } from '../gpu-driven/view-gpu';
62
63
  import { GPU_TEXTURE_USAGE_COPY_SRC } from '../gpu-texture-usage';
63
64
  import { GPU_BUFFER_USAGE_COPY_DST, GPU_BUFFER_USAGE_MAP_READ } from '../gpu-usage';
@@ -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
  }
@@ -467,8 +484,42 @@ function shadowPlanes(matrix: Float32Array): Float32Array {
467
484
  return frustum.fromViewProjection(frustum.create(), matrix as never);
468
485
  }
469
486
 
470
- function shadowViewToken(matrix: Float32Array): string {
471
- return Array.from(matrix).join(',');
487
+ function shadowViewPlanesByView(lights: ExtractedLights): ReadonlyMap<string, Float32Array> {
488
+ const views = new Map<string, Float32Array>();
489
+ for (const [index, matrix] of (lights.lightViewProj ?? []).entries()) {
490
+ views.set(shadowViewIdentityKey({ kind: 'directional', index }), shadowPlanes(matrix));
491
+ }
492
+ for (const [index, point] of lights.pointShadow.entries()) {
493
+ for (let face = 0; face < 6; face += 1) {
494
+ views.set(
495
+ shadowViewIdentityKey({ kind: 'point', index, face }),
496
+ shadowPlanes(point.shadowMatrices.subarray(face * 16, face * 16 + 16)),
497
+ );
498
+ }
499
+ }
500
+ let spotIndex = 0;
501
+ for (const spot of lights.spot) {
502
+ if (spot.lightViewProj === undefined) continue;
503
+ views.set(
504
+ shadowViewIdentityKey({ kind: 'spot', index: spotIndex }),
505
+ shadowPlanes(spot.lightViewProj),
506
+ );
507
+ spotIndex += 1;
508
+ }
509
+ return views;
510
+ }
511
+
512
+ function shadowViewToken(
513
+ matrix: Float32Array,
514
+ targetSize: number | undefined,
515
+ graphGeneration: number,
516
+ ): string {
517
+ // A newly accepted graph imports a new resource projection and must refresh
518
+ // the shadow compute inputs once. The graph-generation component is a
519
+ // one-shot resource fence; it is deliberately excluded from the GPU
520
+ // producer's topology signature so that this refresh cannot rebuild the
521
+ // graph indefinitely.
522
+ return `${Array.from(matrix).join(',')}|target:${targetSize}|graph:${graphGeneration}`;
472
523
  }
473
524
 
474
525
  function halfFloatToNumber(bits: number): number {
@@ -840,8 +891,8 @@ function resolveVolumetricFogHistoryContext(
840
891
  */
841
892
  function prepareSpotLightProjectorFrame(
842
893
  internals: RenderSystemInternals,
843
- world: World,
844
- worlds: readonly World[],
894
+ world: RenderResourceScope,
895
+ worlds: readonly RenderResourceScope[],
845
896
  lights: ExtractedLights,
846
897
  fog: ExtractedVolumetricFog | undefined,
847
898
  ): SpotLightProjectorFrameContext | undefined {
@@ -982,15 +1033,15 @@ interface FrameClockSample {
982
1033
 
983
1034
  /** Read the render sample clock from the camera's composition world once per frame. */
984
1035
  function frameClockSample(
985
- world: World,
986
- worlds: readonly World[],
1036
+ world: RenderResourceScope,
1037
+ worlds: readonly RenderResourceScope[],
987
1038
  camera: CameraSnapshot,
988
1039
  sampleTimeSeconds?: number,
989
1040
  ): FrameClockSample {
990
1041
  const owner = worlds[camera.worldId ?? 0] ?? world;
991
1042
  const invalidSampleTime = sampleTimeSeconds !== undefined && !Number.isFinite(sampleTimeSeconds);
992
1043
  try {
993
- const clock = owner.getResource(Time);
1044
+ const clock = renderTime(owner);
994
1045
  return {
995
1046
  deltaSeconds: Number.isFinite(clock.delta) ? Math.max(0, clock.delta) : 0,
996
1047
  // Keep the raw host sample even when ECS accepted only a clamped
@@ -1007,7 +1058,7 @@ function frameClockSample(
1007
1058
  invalidSampleTime,
1008
1059
  };
1009
1060
  } catch {
1010
- // Test and recovery callers may provide a minimal World-shaped owner. An
1061
+ // Test and recovery callers may provide a minimal RenderResourceScope-shaped owner. An
1011
1062
  // absent clock is an invalid temporal pair and therefore safely resets.
1012
1063
  return {
1013
1064
  deltaSeconds: 0,
@@ -1372,7 +1423,7 @@ function updateVolumetricFogInspection(
1372
1423
 
1373
1424
  export function recordFrame(
1374
1425
  internals: RenderSystemInternals,
1375
- world: World,
1426
+ world: RenderResourceScope,
1376
1427
  cameras: CameraSnapshot[],
1377
1428
  lights: ExtractedLights,
1378
1429
  renderables: readonly RenderableSnapshot[],
@@ -1392,7 +1443,7 @@ export function recordFrame(
1392
1443
  // the per-world extract stage). Optional + defaulting to `[world]` keeps the
1393
1444
  // pre-split single-world identity path (worldId always 0) byte-for-byte and
1394
1445
  // the existing unit-test callers (which pass a single mock world) valid.
1395
- worlds: readonly World[] = [world],
1446
+ worlds: readonly RenderResourceScope[] = [world],
1396
1447
  profilePhase?: RecordProfileRunner,
1397
1448
  gpuDriven?: {
1398
1449
  readonly owner: GpuDrivenProduction;
@@ -1931,7 +1982,7 @@ export function recordFrame(
1931
1982
  shadowViews.push({
1932
1983
  identity: { kind: 'directional', index },
1933
1984
  planes: shadowPlanes(matrix),
1934
- cacheToken: `${shadowViewToken(matrix)}|target:${graphShadowMapSize}:${frameState.graphGeneration}`,
1985
+ cacheToken: shadowViewToken(matrix, graphShadowMapSize, frameState.graphGeneration),
1935
1986
  });
1936
1987
  }
1937
1988
  for (const [index, point] of lights.pointShadow.entries()) {
@@ -1940,7 +1991,7 @@ export function recordFrame(
1940
1991
  shadowViews.push({
1941
1992
  identity: { kind: 'point', index, face },
1942
1993
  planes: shadowPlanes(matrix),
1943
- cacheToken: `${shadowViewToken(matrix)}|target:${graphShadowMapSize}:${frameState.graphGeneration}`,
1994
+ cacheToken: shadowViewToken(matrix, graphShadowMapSize, frameState.graphGeneration),
1944
1995
  });
1945
1996
  }
1946
1997
  }
@@ -1950,7 +2001,11 @@ export function recordFrame(
1950
2001
  shadowViews.push({
1951
2002
  identity: { kind: 'spot', index: shadowSpotIndex },
1952
2003
  planes: shadowPlanes(spot.lightViewProj),
1953
- cacheToken: `${shadowViewToken(spot.lightViewProj)}|target:${graphShadowMapSize}:${frameState.graphGeneration}`,
2004
+ cacheToken: shadowViewToken(
2005
+ spot.lightViewProj,
2006
+ graphShadowMapSize,
2007
+ frameState.graphGeneration,
2008
+ ),
1954
2009
  });
1955
2010
  shadowSpotIndex += 1;
1956
2011
  }
@@ -2270,7 +2325,7 @@ export function recordFrame(
2270
2325
  activeVolumetricFogSignature = resolvedHistory.signature;
2271
2326
  const previousTime = frameState.volumetricFogHistorySignature?.worldTimeSeconds;
2272
2327
  const currentTime = resolvedHistory.signature.worldTimeSeconds;
2273
- // Adjacent UBO lanes: validity, World time, elapsed time since accepted history.
2328
+ // Adjacent UBO lanes: validity, RenderResourceScope time, elapsed time since accepted history.
2274
2329
  const historyParams = new Float32Array([
2275
2330
  activeVolumetricFogContext.historyValid ? 1 : 0,
2276
2331
  currentTime ?? 0,
@@ -2710,6 +2765,7 @@ export function recordFrame(
2710
2765
  gpuDrivenShadowDrawKeysByView: rasterGpuDriven.shadowDrawKeysByView,
2711
2766
  }),
2712
2767
  }),
2768
+ shadowViewPlanesByView: shadowViewPlanesByView(lights),
2713
2769
  ...(frameGpuDriven?.scene === undefined
2714
2770
  ? {}
2715
2771
  : { gpuDrivenSceneMaterialBuffer: frameGpuDriven.scene.scene.materialBuffer }),
@@ -2842,7 +2898,7 @@ export function recordFrame(
2842
2898
  // the picture authority: an identity frame publishes an identity
2843
2899
  // mapping, while a failed enable/disable candidate retains the previous
2844
2900
  // mapping together with its old picture. Never pair an LKG graph with
2845
- // the live World's newer camera component.
2901
+ // the live RenderResourceScope's newer camera component.
2846
2902
  // The exact mapping used to populate the accepted graph's UBO is the
2847
2903
  // only mapping promoted with its receipt. This keeps an identity frame,
2848
2904
  // a retained barrel LKG and a normal nonzero frame on one context.
@@ -2855,7 +2911,10 @@ export function recordFrame(
2855
2911
  // prior tick into the next topology.
2856
2912
  acknowledgeRenderFeatureSubmission(getRenderFeatureGraphState(internals).plans);
2857
2913
  }
2858
- if (cubeCapture !== undefined && cubeCaptureWork.length > 0) {
2914
+ if (
2915
+ cubeCapture !== undefined &&
2916
+ cubeCaptureWork.some((work) => work.physical.descriptor.shape === 'cube')
2917
+ ) {
2859
2918
  const completed = cubeCapture.scheduler.completeSubmission(submitted, completion);
2860
2919
  if (!completed.ok) internals.errorRegistry.fire(completed.error);
2861
2920
  }
@@ -2980,7 +3039,7 @@ function readWorldState(lease: RenderReadLease): DirectionalShadowWorldState {
2980
3039
 
2981
3040
  function sameWorldState(
2982
3041
  cached: DirectionalShadowCache,
2983
- worlds: readonly World[],
3042
+ worlds: readonly RenderResourceScope[],
2984
3043
  leases: readonly RenderReadLease[] | undefined,
2985
3044
  ): boolean {
2986
3045
  if (leases === undefined || leases.length !== worlds.length) return false;
@@ -2999,7 +3058,7 @@ function sameWorldState(
2999
3058
  changes = lease.readChanges(cachedState.version);
3000
3059
  } catch {
3001
3060
  // Treat malformed or foreign version evidence as stale and rebuild the
3002
- // atlas rather than reaching through World for a second mutation clock.
3061
+ // atlas rather than reaching through RenderResourceScope for a second mutation clock.
3003
3062
  return false;
3004
3063
  }
3005
3064
  if (
@@ -3047,7 +3106,7 @@ function sameDirectionalShadowQuality(
3047
3106
  function prepareDirectionalShadowCache(
3048
3107
  internals: RenderSystemInternals,
3049
3108
  frameState: RenderFrameState,
3050
- worlds: readonly World[],
3109
+ worlds: readonly RenderResourceScope[],
3051
3110
  leases: readonly RenderReadLease[] | undefined,
3052
3111
  lights: ExtractedLights,
3053
3112
  shadowMapSize: number | undefined,
@@ -3102,7 +3161,7 @@ function prepareDirectionalShadowCache(
3102
3161
  if (leases === undefined || leases.length !== worlds.length) {
3103
3162
  // Direct record-stage test harnesses may omit the host-owned leases. They
3104
3163
  // still exercise the real shadow encoder, but without the ECS read owner
3105
- // they must conservatively rebuild rather than inspect World internals.
3164
+ // they must conservatively rebuild rather than inspect RenderResourceScope internals.
3106
3165
  return { reuse: false, next: null };
3107
3166
  }
3108
3167
  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 {