@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
@@ -6,7 +6,7 @@ import {
6
6
  DynamicTextureStore,
7
7
  } from '@forgeax/engine-assets-runtime';
8
8
  import { audioLoader } from '@forgeax/engine-audio-webaudio';
9
- import { Update, type World } from '@forgeax/engine-ecs';
9
+ import type { World } from '@forgeax/engine-ecs';
10
10
  import { createRenderReadLease, type RenderReadLease } from '@forgeax/engine-ecs/projection';
11
11
  import {
12
12
  DEFAULT_VERTEX_ATTRIBUTE_MAP,
@@ -21,7 +21,6 @@ import type {
21
21
  RhiDevice,
22
22
  } from '@forgeax/engine-rhi';
23
23
  import { err, ok, RhiError, validateDrawArgs } from '@forgeax/engine-rhi';
24
- import { registerPropagateTransforms } from '@forgeax/engine-scene';
25
24
  import {
26
25
  findVariantByKey,
27
26
  type MaterialShaderEntry,
@@ -39,6 +38,11 @@ import type {
39
38
  PrimitiveTopology,
40
39
  VertexAttributeMap,
41
40
  } from '@forgeax/engine-types';
41
+ import { type PublishedRenderFrameInput, RenderPublicationError } from '../publication/contract';
42
+ import { installPublicationPrograms } from '../publication/programs';
43
+ import { type PreparedRenderPublication, RenderPublicationReceiver } from '../publication/receiver';
44
+ import { RenderPublicationTargetReceiver } from '../publication/targets';
45
+ import { registerRenderSourceSystems } from '../scene/source-systems';
42
46
  import { transmissionBackdropAvailable } from './device-feature-admission';
43
47
 
44
48
  export type { MaterialShaderManifestEntry } from '@forgeax/engine-shader';
@@ -71,7 +75,6 @@ import {
71
75
  type PostProcessShaderEntry,
72
76
  postProcessShaderModuleLabel,
73
77
  } from '../fullscreen-post-process-pass';
74
- import { glyphTextLayoutSystem } from '../glyph-text-layout-system';
75
78
  import { GPU_SHADER_STAGE_FRAGMENT, GPU_SHADER_STAGE_VERTEX } from '../gpu-stage';
76
79
  import { type BloomInspection, emptyBloomInspection } from '../inspection-types';
77
80
  import { DeviceScope } from '../lifecycle';
@@ -155,7 +158,6 @@ import {
155
158
  import { createGpuPassTimingObservationStore, observeGpuPassTimingDisabled } from '../renderer.js';
156
159
  import { postSpawnResolveJoints } from '../scene-instances/post-spawn-resolve-joints';
157
160
  import type { RenderTarget } from '../targets/contracts';
158
- import { tilemapChunkExtractSystem } from '../tilemap-chunk-extract-system';
159
161
  import type { TypedFrameObservationCapture } from '../typed-render-graph-primitives';
160
162
  import { registerAnalyticFogPostProcess } from './analytic-fog-registration';
161
163
  import type { RhiBackendPack } from './backend-contract';
@@ -303,7 +305,6 @@ export type { WebGPUOutcome, WebGPURendererInternals } from './webgpu-renderer-c
303
305
  export { disposeObservationCaptureSet } from './webgpu-renderer-observation';
304
306
 
305
307
  /** Bundler injection and the WebGPU renderer construction entry points. */
306
- let nextDerivedSystemId = 0;
307
308
  export async function tryCreateWebGPURenderer(
308
309
  canvas: HTMLCanvasElement | OffscreenCanvas,
309
310
  options: RendererOptions | undefined,
@@ -315,7 +316,12 @@ export async function tryCreateWebGPURenderer(
315
316
  async function makeWebGPURenderer(
316
317
  internals: WebGPURendererInternals,
317
318
  ): Promise<RendererAssemblyImplementation> {
319
+ let publicationTargets: RenderPublicationTargetReceiver | undefined;
318
320
  let disposed = false;
321
+ const publicationReceiver =
322
+ internals.options?.publicationSource === undefined
323
+ ? undefined
324
+ : new RenderPublicationReceiver(internals.options.publicationSource);
319
325
  const observeTiming = observeGpuPassTimingDisabled;
320
326
  const frameContinuations = new Set<ContinuationTerminator>();
321
327
  type RecoveryInspection = RenderInspection['recovery'];
@@ -564,6 +570,8 @@ async function makeWebGPURenderer(
564
570
  const getShader = (): ShaderCatalog => {
565
571
  const state = candidateShaderState ?? activeShaderState;
566
572
  if (state.shaderInstance === null) {
573
+ const activeCatalog = activeShaderState.shaderInstance;
574
+ // Reuse validated manifest CPU rows; candidate preparation selects variants.
567
575
  // feat-20260608-create-app-param-surface-trim / M2 / D-2 q5-A:
568
576
  // shaderManifestUrl moved to BundlerOptions (third arg). The fallback
569
577
  // literal '/shaders/manifest.json' stays here so the LO 1.1
@@ -572,39 +580,20 @@ async function makeWebGPURenderer(
572
580
  // the zero-entry opt-in: explicitly passing
573
581
  // `bundler: { shaderManifestUrl: undefined }` retains the
574
582
  // old "no manifest fetched" mode used by tests / camera-only worlds.
575
- const bundler = internals.bundler;
576
- const explicitUrl = bundler?.shaderManifestUrl;
577
- state.shaderInstance = new ShaderCatalog({
578
- device: getShaderModuleAdapter(),
579
- manifestUrl:
580
- bundler !== undefined && 'shaderManifestUrl' in bundler
581
- ? explicitUrl
582
- : '/shaders/manifest.json',
583
- });
583
+ state.shaderInstance =
584
+ state === candidateShaderState && activeCatalog !== null
585
+ ? activeCatalog.forkForDevice(getShaderModuleAdapter())
586
+ : new ShaderCatalog({
587
+ device: getShaderModuleAdapter(),
588
+ manifestUrl:
589
+ internals.bundler !== undefined && 'shaderManifestUrl' in internals.bundler
590
+ ? internals.bundler.shaderManifestUrl
591
+ : '/shaders/manifest.json',
592
+ });
584
593
  }
585
594
  return state.shaderInstance;
586
595
  };
587
- // D-S9: AssetRegistry instance shared with RenderSystem. RenderSystem
588
- // looks up MeshFilter.assetHandle here per frame; buildReadyWebGPU step 3
589
- // uploads the registry's builtin mesh geometry to GPU buffers so the
590
- // RenderSystem can route MeshFilter.assetHandle to a real (vbo, ibo) pair.
591
- // feat-20260527 M1 / w1: ShaderCatalog is now eager-constructed and
592
- // constructor-injected into AssetRegistry (plan-strategy D-1). The
593
- // ShaderCatalogDevice adapter (shared between the catalog and per-MaterialShader
594
- // pipeline cache) is already available at this point.
595
596
  const shaderCatalog = getShader();
596
- // bug-20260601-hello-tonemap-material-register M1 (plan-strategy D-1):
597
- // All material-shader entries are now registered from the manifest
598
- // BEFORE the renderer is returned, so `register<MaterialAsset>` referencing
599
- // an engine shader succeeds without waiting for `renderer.initialization`.
600
- // Host-injected ImportTransport remains a constructor-only dependency.
601
- // feat-20260705-runtime-tier2-decomposition M1 / w10 (D-1 / D-2): the sole
602
- // production assembly point injects (a) the post-spawn hook
603
- // `postSpawnResolveJoints` (auto-wire Skin.joints; D-1) and the audio
604
- // catalog-entry loader. The latter stays at this assembly boundary so
605
- // assets-runtime remains independent of the concrete Web Audio backend.
606
- // videoLoader is wired internally from graphics-extras; the full registry has
607
- // 11 engine kinds. This is the only `new AssetRegistry(...)` call in the repo.
608
597
  const assets = new AssetRegistry(
609
598
  shaderCatalog,
610
599
  internals.importTransport,
@@ -735,7 +724,9 @@ async function makeWebGPURenderer(
735
724
  gpuStore.configureGpuDevice(
736
725
  internals.device,
737
726
  packShaderFactory,
738
- (world, pod) => {
727
+ (world, pod, source) => {
728
+ // The accepted equirect identity already owns this renderer-local GPU projection.
729
+ if ('resolveAsset' in world) return ok(source);
739
730
  const handle = world.allocSharedRef('EquirectAsset', pod);
740
731
  return ok(handle);
741
732
  },
@@ -2562,7 +2553,6 @@ async function makeWebGPURenderer(
2562
2553
  currentGeneration: () => activeDeviceScope.generation,
2563
2554
  renderSystem,
2564
2555
  });
2565
- const derivedSystemNames = new Map<World, string>();
2566
2556
  const transformReleases = new Map<World, () => void>();
2567
2557
  const attachmentOwner = {};
2568
2558
  let frameId = 0;
@@ -2599,6 +2589,13 @@ async function makeWebGPURenderer(
2599
2589
  renderSystem.setSurfaceDynamicInput(frame);
2600
2590
  },
2601
2591
  attach(world: World): RenderResult<RenderWorldLease, RenderError> {
2592
+ if (publicationReceiver !== undefined)
2593
+ return err(
2594
+ new RenderPublicationError({
2595
+ reason: 'identity',
2596
+ subject: 'publication Renderer has no World attachment',
2597
+ }),
2598
+ );
2602
2599
  const attached = this.attachScene(world);
2603
2600
  if (!attached.ok) {
2604
2601
  return err(new RendererContractFailureError('attach', attached.error.hint));
@@ -2850,23 +2847,7 @@ async function makeWebGPURenderer(
2850
2847
  }
2851
2848
  if (attachedWorlds.has(world)) return ok(undefined);
2852
2849
  try {
2853
- const releaseTransforms = registerPropagateTransforms(world);
2854
- const derivedSystemName = `renderDerived:${++nextDerivedSystemId}`;
2855
- const derived = world.addSystem(Update, {
2856
- name: derivedSystemName,
2857
- queries: [],
2858
- fn: (world) => {
2859
- tilemapChunkExtractSystem(world, (guid) => assets.lookup(guid));
2860
- const glyphResult = glyphTextLayoutSystem(world, gpuStore);
2861
- if (!glyphResult.ok) throw glyphResult.error;
2862
- },
2863
- });
2864
- if (!derived.ok) {
2865
- releaseTransforms();
2866
- throw new Error(`failed to register ${derivedSystemName}: ${derived.error.code}`);
2867
- }
2868
- transformReleases.set(world, releaseTransforms);
2869
- derivedSystemNames.set(world, derivedSystemName);
2850
+ transformReleases.set(world, registerRenderSourceSystems(world, assets, gpuStore));
2870
2851
  attachedWorlds.add(world);
2871
2852
  return ok(undefined);
2872
2853
  } catch (cause) {
@@ -2897,11 +2878,6 @@ async function makeWebGPURenderer(
2897
2878
  }
2898
2879
  }
2899
2880
  renderSystem.detachScene(world);
2900
- const derivedSystemName = derivedSystemNames.get(world);
2901
- if (derivedSystemName !== undefined) {
2902
- world.removeSystem(Update, derivedSystemName);
2903
- derivedSystemNames.delete(world);
2904
- }
2905
2881
  transformReleases.get(world)?.();
2906
2882
  transformReleases.delete(world);
2907
2883
  },
@@ -3105,17 +3081,43 @@ async function makeWebGPURenderer(
3105
3081
  : ok(result.value);
3106
3082
  },
3107
3083
  draw(
3108
- worldsOrRequest: readonly World[] | RenderFrameInput,
3084
+ worldsOrRequest: readonly World[] | RenderFrameInput | PublishedRenderFrameInput,
3109
3085
  options?: DrawOwnerOptions,
3110
3086
  ): Result<void | FrameReceipt, RhiError | RenderError> {
3111
3087
  const isFrameRequest = !Array.isArray(worldsOrRequest);
3112
- const frameRequest = isFrameRequest ? (worldsOrRequest as RenderFrameInput) : undefined;
3088
+ const publicationRequest =
3089
+ isFrameRequest && 'publication' in worldsOrRequest
3090
+ ? (worldsOrRequest as PublishedRenderFrameInput)
3091
+ : undefined;
3092
+ const frameRequest =
3093
+ isFrameRequest && publicationRequest === undefined
3094
+ ? (worldsOrRequest as RenderFrameInput)
3095
+ : undefined;
3096
+ let publication: PreparedRenderPublication | undefined;
3097
+ if (publicationRequest !== undefined) {
3098
+ if (publicationReceiver === undefined)
3099
+ return err(
3100
+ new RenderPublicationError({
3101
+ reason: 'identity',
3102
+ subject: 'RendererOptions.publicationSource',
3103
+ }),
3104
+ );
3105
+ } else if (publicationReceiver !== undefined) {
3106
+ return err(
3107
+ new RenderPublicationError({
3108
+ reason: 'identity',
3109
+ subject: 'a publication-bound renderer cannot consume local World leases',
3110
+ }),
3111
+ );
3112
+ }
3113
3113
  const worlds: readonly World[] =
3114
3114
  frameRequest !== undefined
3115
3115
  ? frameRequest.leases
3116
3116
  .map((lease) => attachedLeases.get(lease))
3117
3117
  .filter((world): world is World => world !== undefined)
3118
- : (worldsOrRequest as readonly World[]);
3118
+ : publicationRequest === undefined
3119
+ ? (worldsOrRequest as readonly World[])
3120
+ : [];
3119
3121
  const readLeases: readonly RenderReadLease[] | undefined =
3120
3122
  frameRequest !== undefined
3121
3123
  ? frameRequest.leases
@@ -3170,7 +3172,14 @@ async function makeWebGPURenderer(
3170
3172
  ? {}
3171
3173
  : { geometryLane: frameRequest.geometryLane }),
3172
3174
  }
3173
- : (options ?? { cameraOwner: 0, resourceOwner: 0 });
3175
+ : publicationRequest === undefined
3176
+ ? (options ?? { cameraOwner: 0, resourceOwner: 0 })
3177
+ : {
3178
+ cameraOwner: 0,
3179
+ resourceOwner: 0,
3180
+ sampleTimeSeconds: publicationRequest.publication.sampleTimeSeconds,
3181
+ temporalReset: publicationRequest.publication.temporalReset,
3182
+ };
3174
3183
  // feat-20260612-rhi-destroy-renderer-dispose-gpu-lifecycle / M5 / w21
3175
3184
  // (plan-strategy D-1, D-8): post-dispose the renderer is dead. AI
3176
3185
  // users observing `result.ok === false && err.code === 'rhi-not-
@@ -3246,7 +3255,8 @@ async function makeWebGPURenderer(
3246
3255
  // are non-exclusive: an empty array short-circuits to empty-worlds. The
3247
3256
  // check is defensive against JS callers passing a non-array despite the
3248
3257
  // compile-time World[] type (red-window migration safety).
3249
- const worldCount = Array.isArray(worlds) ? worlds.length : 0;
3258
+ const worldCount =
3259
+ publicationRequest === undefined ? (Array.isArray(worlds) ? worlds.length : 0) : 1;
3250
3260
  if (worldCount === 0 && internals.device.caps.backendKind === 'null') {
3251
3261
  return ok(undefined);
3252
3262
  }
@@ -3268,12 +3278,27 @@ async function makeWebGPURenderer(
3268
3278
  internals.errorRegistry,
3269
3279
  );
3270
3280
  if (!contextConfigured.ok) return contextConfigured;
3271
- renderTargetHost.beginFrame();
3272
3281
  // w24 — facade-level try/catch produces Result.err on unexpected throw
3273
3282
  // (D-P6 dual-channel preserved: per-stage RhiError continues to fan out
3274
3283
  // through onError separately; the facade Result is the synchronous
3275
3284
  // summary AI users can ignore or branch on).
3276
3285
  try {
3286
+ if (publicationRequest !== undefined && publicationReceiver !== undefined) {
3287
+ const accepted = publicationReceiver.accept(publicationRequest.publication);
3288
+ if (!accepted.ok) return accepted;
3289
+ publicationTargets ??= new RenderPublicationTargetReceiver(renderTargetHost);
3290
+ publication = {
3291
+ ...publicationTargets.apply(accepted.value),
3292
+ ...(publicationRequest.onFeatureSourceSubmitted === undefined
3293
+ ? {}
3294
+ : {
3295
+ onFeatureSourceSubmitted: publicationRequest.onFeatureSourceSubmitted,
3296
+ }),
3297
+ };
3298
+ installPublicationPrograms(assets, publicationRequest.publication.programs);
3299
+ }
3300
+ renderTargetHost.beginFrame();
3301
+
3277
3302
  const timingHost = internals as WebGPURendererInternals & {
3278
3303
  gpuPassTimingFrameIdentity?:
3279
3304
  | {
@@ -3311,7 +3336,13 @@ async function makeWebGPURenderer(
3311
3336
  graphGeneration: 0,
3312
3337
  };
3313
3338
  }
3314
- const submitted = renderSystem.draw(worlds, drawOptions, readLeases, volumeTimingCapture);
3339
+ const submitted = renderSystem.draw(
3340
+ worlds,
3341
+ drawOptions,
3342
+ readLeases,
3343
+ volumeTimingCapture,
3344
+ publication,
3345
+ );
3315
3346
  timingHost.observationFrameId = undefined;
3316
3347
  timingHost.observationCaptureDomains = undefined;
3317
3348
  timingHost.gpuPassTimingFrameIdentity = undefined;
@@ -3510,6 +3541,10 @@ async function makeWebGPURenderer(
3510
3541
  });
3511
3542
  internals.errorRegistry.fire(e);
3512
3543
  return err(e);
3544
+ } finally {
3545
+ // Native video pixels have been consumed by synchronous queue copy calls.
3546
+ // Release them on every success/failure path, including direct publication users.
3547
+ publicationReceiver?.releaseVideoFrames();
3513
3548
  }
3514
3549
  },
3515
3550
  async observe(
@@ -3752,6 +3787,7 @@ async function makeWebGPURenderer(
3752
3787
  disposed = true;
3753
3788
  for (const world of attachedWorlds) dynamicGeometryHost.invalidateDynamicGeometryWorld(world);
3754
3789
  dynamicGeometry.dispose();
3790
+ publicationReceiver?.dispose();
3755
3791
  for (const continuation of frameContinuations) {
3756
3792
  continuation.terminate({ code: 'disposed' });
3757
3793
  }
@@ -3762,11 +3798,6 @@ async function makeWebGPURenderer(
3762
3798
  }
3763
3799
  for (const world of attachedWorlds) {
3764
3800
  try {
3765
- const derivedSystemName = derivedSystemNames.get(world);
3766
- if (derivedSystemName !== undefined) {
3767
- world.removeSystem(Update, derivedSystemName);
3768
- derivedSystemNames.delete(world);
3769
- }
3770
3801
  transformReleases.get(world)?.();
3771
3802
  transformReleases.delete(world);
3772
3803
  } catch (cause) {
@@ -8,6 +8,7 @@ import {
8
8
  type VertexLayoutProjection,
9
9
  } from '@forgeax/engine-geometry';
10
10
  import type { VertexAttributeMap } from '@forgeax/engine-types';
11
+ import { PARTICLE_MESH_VERTEX_BUFFER } from '../features/particle-mesh-layout';
11
12
  import { RENDER_FEATURE_VERTEX_LAYOUTS } from '../features/prepared-graphics';
12
13
 
13
14
  type FeatureFloatVertexFormat = 'float32x2' | 'float32x3' | 'float32x4';
@@ -101,14 +102,6 @@ const MESH_GEOMETRY_MATERIAL_INSTANCE_SPEC = {
101
102
  export const PREPARED_INSTANCE_VERTEX_ATTRS = buildMeshAttributeMapForUvSets(2);
102
103
  export const PREPARED_MATERIAL_INSTANCE_VERTEX_ATTRS = SKIN_VERTEX_ATTRIBUTE_MAP;
103
104
 
104
- function canonicalMeshVertexBufferLayout(): GPUVertexBufferLayout {
105
- const [buffer] = toGpuVertexBufferLayouts(deriveVertexBufferLayout(DEFAULT_VERTEX_ATTRIBUTE_MAP));
106
- if (buffer === undefined) {
107
- throw new Error('The canonical mesh vertex layout must contain one vertex buffer');
108
- }
109
- return { ...buffer, stepMode: 'vertex' };
110
- }
111
-
112
105
  export const POSITION_SIZE_COLOR_INSTANCE_VERTEX_BUFFERS = [
113
106
  defineFeatureVertexBuffer(POSITION_SIZE_COLOR_INSTANCE_SPEC),
114
107
  ] satisfies readonly GPUVertexBufferLayout[];
@@ -127,7 +120,7 @@ export const TOPOLOGY_SEGMENT_INSTANCE_VERTEX_BUFFERS = [
127
120
  * particle transform/material data rather than MeshAsset attributes.
128
121
  */
129
122
  export const MESH_GEOMETRY_MATERIAL_INSTANCE_VERTEX_BUFFERS = [
130
- canonicalMeshVertexBufferLayout(),
123
+ PARTICLE_MESH_VERTEX_BUFFER,
131
124
  defineFeatureVertexBuffer(MESH_GEOMETRY_MATERIAL_INSTANCE_SPEC),
132
125
  ] satisfies readonly GPUVertexBufferLayout[];
133
126
 
@@ -1,12 +1,12 @@
1
- import type { World } from '@forgeax/engine-ecs';
2
1
  import type { RenderFeatureFrameContext, RenderFeatureViewContext } from '../features/types';
2
+ import type { RenderResourceScope } from '../publication/resource-scope';
3
3
  import type { CameraSnapshot } from '../render-contract';
4
4
  import type { ExtractedCloudLayer } from './extract';
5
5
 
6
6
  /** Project the stable camera/world facts consumed by the cloud feature owner. */
7
7
  export function createCloudFeatureView(
8
8
  camera: CameraSnapshot | undefined,
9
- worlds: readonly World[],
9
+ worlds: readonly RenderResourceScope[],
10
10
  cameraOwner: number,
11
11
  width: number,
12
12
  height: number,
@@ -176,6 +176,7 @@ export class DeviceScope {
176
176
  this.clearChildren();
177
177
  this._clearResources();
178
178
  this.lifecycleState = 'retired';
179
+ this.parent?.detachChild(this);
179
180
  }
180
181
 
181
182
  abandon(): void {
@@ -183,6 +184,7 @@ export class DeviceScope {
183
184
  this.lifecycleState = 'abandoned';
184
185
  this.clearChildren();
185
186
  this._clearResources();
187
+ this.parent?.detachChild(this);
186
188
  }
187
189
 
188
190
  dispose(): void {
@@ -190,6 +192,7 @@ export class DeviceScope {
190
192
  this.lifecycleState = 'disposed';
191
193
  this.clearChildren();
192
194
  this._clearResources();
195
+ this.parent?.detachChild(this);
193
196
  }
194
197
 
195
198
  async replace(
@@ -1,3 +1,4 @@
1
+ import type { RenderResourceScope } from '../publication/resource-scope';
1
2
  import { type MeshResidencyLease, MeshResidencyLifetime } from './mesh-residency-lifetime';
2
3
  // @forgeax/engine-runtime - GpuResidencyCache
3
4
  // (feat-20260601-device/gpu-residency-extraction M1; M2 added the deriveRenderData
@@ -37,7 +38,6 @@ import {
37
38
  type MipmapShaderModuleFactory,
38
39
  prepareMipmaps,
39
40
  } from '@forgeax/engine-assets-runtime';
40
- import type { World } from '@forgeax/engine-ecs';
41
41
  import { deriveVertexCount, type VertexLayoutProjection } from '@forgeax/engine-geometry';
42
42
  import { halfFloat } from '@forgeax/engine-math';
43
43
  import {
@@ -288,8 +288,9 @@ function makeImageError<C extends ImageErrorCode>(
288
288
 
289
289
  /** Cube-POD register-relay injected by the wire layer (D-3). */
290
290
  type RegisterCube = (
291
- world: World,
291
+ world: RenderResourceScope,
292
292
  pod: EquirectAsset,
293
+ source: Handle<'EquirectAsset', 'shared'>,
293
294
  ) => Result<Handle<'EquirectAsset', 'shared'>, AssetError>;
294
295
 
295
296
  // feat-20260612-rhi-destroy-renderer-dispose-gpu-lifecycle / M-3 / w11:
@@ -607,16 +608,16 @@ export class GpuResidencyCache {
607
608
  }
608
609
 
609
610
  /**
610
- * Compose a cache key from the raw handle slot and its owning World. The
611
+ * Compose a cache key from the raw handle slot and its owning RenderResourceScope. The
611
612
  * draw-time world index is only a position in one frame's `worlds[]`; it is
612
- * not an asset identity and can be reused by a later World. World objects
613
+ * not an asset identity and can be reused by a later RenderResourceScope. RenderResourceScope objects
613
614
  * therefore receive a renderer-local namespace, while numeric callers keep
614
615
  * the legacy namespace for low-level tests and built-in upload paths.
615
616
  */
616
- private readonly worldNamespaces = new WeakMap<World, number>();
617
+ private readonly worldNamespaces = new WeakMap<RenderResourceScope, number>();
617
618
  private nextWorldNamespace = 1;
618
619
 
619
- private worldKey(slot: number, world: World | number): string | number {
620
+ private worldKey(slot: number, world: RenderResourceScope | number): string | number {
620
621
  if (typeof world === 'number') {
621
622
  return world === 0 ? slot : world * 0x1000000 + slot;
622
623
  }
@@ -809,7 +810,7 @@ export class GpuResidencyCache {
809
810
  */
810
811
  evictTexture(
811
812
  handle: Handle<'TextureAsset', 'shared'>,
812
- worldId: World | number = 0,
813
+ worldId: RenderResourceScope | number = 0,
813
814
  ): { freed: number; errors: RhiError[] } {
814
815
  const id = this.worldKey(handleSlot(handle), worldId);
815
816
  const entry = this.textureGpuHandles.get(id);
@@ -835,7 +836,7 @@ export class GpuResidencyCache {
835
836
 
836
837
  evictMesh(
837
838
  handle: Handle<'MeshAsset', 'shared'>,
838
- worldId: World | number = 0,
839
+ worldId: RenderResourceScope | number = 0,
839
840
  ): { freed: number; errors: RhiError[] } {
840
841
  const id = this.worldKey(Number(handle), worldId);
841
842
  const entry = this.meshGpuHandles.get(id);
@@ -1000,7 +1001,7 @@ export class GpuResidencyCache {
1000
1001
  */
1001
1002
  _getTextureGpuTexture(
1002
1003
  handle: Handle<'TextureAsset', 'shared'>,
1003
- worldId: World | number = 0,
1004
+ worldId: RenderResourceScope | number = 0,
1004
1005
  ): GpuTexture | undefined {
1005
1006
  return this.textureGpuHandles.get(this.worldKey(handleSlot(handle), worldId))?.texture;
1006
1007
  }
@@ -1011,7 +1012,7 @@ export class GpuResidencyCache {
1011
1012
  */
1012
1013
  getTextureGpuView(
1013
1014
  handle: Handle<'TextureAsset', 'shared'>,
1014
- worldId: World | number = 0,
1015
+ worldId: RenderResourceScope | number = 0,
1015
1016
  ): TextureView | undefined {
1016
1017
  return this.textureGpuHandles.get(this.worldKey(handleSlot(handle), worldId))?.view;
1017
1018
  }
@@ -1024,7 +1025,7 @@ export class GpuResidencyCache {
1024
1025
  ensureSamplerResident(
1025
1026
  handle: Handle<'SamplerAsset', 'shared'>,
1026
1027
  pod: SamplerAsset,
1027
- worldId: World | number = 0,
1028
+ worldId: RenderResourceScope | number = 0,
1028
1029
  ): Result<Sampler, RhiError> {
1029
1030
  const key = this.worldKey(handleSlot(handle), worldId);
1030
1031
  const existing = this.samplerGpuHandles.get(key);
@@ -1093,7 +1094,7 @@ export class GpuResidencyCache {
1093
1094
  */
1094
1095
  getMeshGpuHandles(
1095
1096
  handle: Handle<'MeshAsset', 'shared'>,
1096
- worldId: World | number = 0,
1097
+ worldId: RenderResourceScope | number = 0,
1097
1098
  ): MeshGpuHandles | undefined {
1098
1099
  return this.meshGpuHandles.get(this.worldKey(Number(handle), worldId));
1099
1100
  }
@@ -1101,7 +1102,7 @@ export class GpuResidencyCache {
1101
1102
  /** Candidate leases retain the concrete allocation even after cache invalidation. */
1102
1103
  retainMeshResidency(
1103
1104
  handle: Handle<'MeshAsset', 'shared'>,
1104
- worldId: World | number = 0,
1105
+ worldId: RenderResourceScope | number = 0,
1105
1106
  ): MeshResidencyLease | undefined {
1106
1107
  const key = this.worldKey(Number(handle), worldId);
1107
1108
  const entry = this.meshGpuHandles.get(key);
@@ -1114,7 +1115,7 @@ export class GpuResidencyCache {
1114
1115
  }
1115
1116
 
1116
1117
  /** In-place author changes immediately miss the cache; old submissions keep their allocation. */
1117
- invalidateMesh(handle: number, worldId: World | number = 0): void {
1118
+ invalidateMesh(handle: number, worldId: RenderResourceScope | number = 0): void {
1118
1119
  const key = this.worldKey(handle, worldId);
1119
1120
  const entry = this.meshGpuHandles.get(key);
1120
1121
  if (entry !== undefined) this.retireMeshEntry(key, entry);
@@ -1137,18 +1138,18 @@ export class GpuResidencyCache {
1137
1138
  ensureResident(
1138
1139
  handle: Handle<'MeshAsset', 'shared'>,
1139
1140
  pod: TypesMeshAsset,
1140
- worldId?: World | number,
1141
+ worldId?: RenderResourceScope | number,
1141
1142
  lodMeshes?: readonly TypesMeshAsset[],
1142
1143
  ): Result<MeshGpuHandles, RhiError | AssetError | ImageError>;
1143
1144
  ensureResident(
1144
1145
  handle: Handle<'TextureAsset', 'shared'>,
1145
1146
  pod: TextureAsset,
1146
- worldId?: World | number,
1147
+ worldId?: RenderResourceScope | number,
1147
1148
  ): Result<TextureGpuEntry, RhiError | AssetError | ImageError>;
1148
1149
  ensureResident(
1149
1150
  handle: Handle<'MeshAsset', 'shared'> | Handle<'TextureAsset', 'shared'>,
1150
1151
  pod: TypesMeshAsset | TextureAsset,
1151
- worldId: World | number = 0,
1152
+ worldId: RenderResourceScope | number = 0,
1152
1153
  lodMeshes: readonly TypesMeshAsset[] = [],
1153
1154
  ): Result<TextureGpuEntry | MeshGpuHandles, RhiError | AssetError | ImageError> {
1154
1155
  const id = handleSlot(handle);
@@ -1218,17 +1219,17 @@ export class GpuResidencyCache {
1218
1219
  prepareResidentForRecovery(
1219
1220
  handle: Handle<'MeshAsset', 'shared'>,
1220
1221
  pod: TypesMeshAsset,
1221
- worldId?: World | number,
1222
+ worldId?: RenderResourceScope | number,
1222
1223
  ): Result<RecoveryResidencyPreparation, RhiError | AssetError | ImageError>;
1223
1224
  prepareResidentForRecovery(
1224
1225
  handle: Handle<'TextureAsset', 'shared'>,
1225
1226
  pod: TextureAsset,
1226
- worldId?: World | number,
1227
+ worldId?: RenderResourceScope | number,
1227
1228
  ): Result<RecoveryResidencyPreparation, RhiError | AssetError | ImageError>;
1228
1229
  prepareResidentForRecovery(
1229
1230
  handle: Handle<'MeshAsset', 'shared'> | Handle<'TextureAsset', 'shared'>,
1230
1231
  pod: TypesMeshAsset | TextureAsset,
1231
- worldId: World | number = 0,
1232
+ worldId: RenderResourceScope | number = 0,
1232
1233
  ): Result<RecoveryResidencyPreparation, RhiError | AssetError | ImageError> {
1233
1234
  const id = handleSlot(handle);
1234
1235
  if (pod.kind === 'mesh') {
@@ -1316,7 +1317,7 @@ export class GpuResidencyCache {
1316
1317
  handle: Handle<'TextureAsset', 'shared'>,
1317
1318
  pod: TextureAsset,
1318
1319
  decoded: DecodedImage,
1319
- worldId: World | number = 0,
1320
+ worldId: RenderResourceScope | number = 0,
1320
1321
  ): Promise<Result<void, AssetError | ImageError | RhiError>> {
1321
1322
  const device = this.gpuDevice;
1322
1323
  const projected = deriveRenderDataTexture(pod);
@@ -1392,7 +1393,7 @@ export class GpuResidencyCache {
1392
1393
  pod: TextureAsset,
1393
1394
  decoded: DecodedImage,
1394
1395
  renderData: TextureRenderData,
1395
- worldId: World | number = 0,
1396
+ worldId: RenderResourceScope | number = 0,
1396
1397
  ): Result<TextureGpuEntry, AssetError | ImageError | RhiError> {
1397
1398
  const device = this.gpuDevice;
1398
1399
  const prepared = this.prepareTextureUpload(handle, pod, decoded, renderData);
@@ -1654,7 +1655,7 @@ export class GpuResidencyCache {
1654
1655
  * retries by inferring "no entry => try again".
1655
1656
  */
1656
1657
  async _uploadCubemapFromEquirect(
1657
- world: World,
1658
+ world: RenderResourceScope,
1658
1659
  sourceHandle: Handle<'EquirectAsset', 'shared'>,
1659
1660
  sourcePod: EquirectAsset,
1660
1661
  ): Promise<Result<Handle<'EquirectAsset', 'shared'>, AssetError | RhiError>> {
@@ -1999,7 +2000,7 @@ export class GpuResidencyCache {
1999
2000
  view: cubeView,
2000
2001
  faceViews,
2001
2002
  });
2002
- const regResult = registerCube(world, cubeAsset);
2003
+ const regResult = registerCube(world, cubeAsset, sourceHandle);
2003
2004
  if (!regResult.ok) return recordFailed(regResult.error);
2004
2005
  const cubeHandle = regResult.value;
2005
2006
  const cubeId = handleSlot(cubeHandle);
@@ -2252,7 +2253,7 @@ export class GpuResidencyCache {
2252
2253
  handle: Handle<'MeshAsset', 'shared'>,
2253
2254
  newVertices: Float32Array,
2254
2255
  newIndices: Uint16Array,
2255
- worldId: World | number = 0,
2256
+ worldId: RenderResourceScope | number = 0,
2256
2257
  submeshes?: readonly Submesh[],
2257
2258
  ): void {
2258
2259
  const id = this.worldKey(handleSlot(handle), worldId);
@@ -1,3 +1,4 @@
1
+ import type { RenderPublicationError } from '../publication/contract';
1
2
  // @forgeax/engine-render -- render cluster error classes.
2
3
  //
3
4
  // feat-20260704-runtime-tier1-decomposition M2 / w8 (D-3): the monolithic
@@ -562,7 +563,7 @@ export class PointShadowAtlasBoundsViolationError extends Error {
562
563
  * Fired by the per-frame record stage (`videoTextureView`) when a VideoPlayer
563
564
  * entity can reach neither video upload path this frame: the general
564
565
  * `copyExternalImageToTexture` path (no host HTMLVideoElement resolved via
565
- * `VideoElementProvider`) AND the high-perf `GPUExternalTexture` path
566
+ * `VideoSourceProvider`) AND the high-perf `GPUExternalTexture` path
566
567
  * (capability absent). The engine surfaces this explicit failure rather than
567
568
  * silently rendering a stale/garbage texture (charter P3, plan-strategy D-6).
568
569
  * - `.code = 'video-upload-unsupported'`
@@ -1980,6 +1981,7 @@ export type RenderErrorCode = RenderError['code'];
1980
1981
  * `RenderErrorCode` discriminant on `.code`.
1981
1982
  */
1982
1983
  export type RenderError =
1984
+ | RenderPublicationError
1983
1985
  | LifecycleConstructionError
1984
1986
  | AutoExposureError
1985
1987
  | RendererExpectedOperationError
@@ -0,0 +1,21 @@
1
+ import type { MaterialCookProgramContext } from '@forgeax/engine-pack/material-cook';
2
+ import type { RhiCaps } from '@forgeax/engine-rhi';
3
+
4
+ /** The receiving device selects the immutable material program domain. */
5
+ export function renderMaterialContext(caps: Pick<RhiCaps, 'backendKind' | 'storageBuffer'>): {
6
+ readonly materialContext?: MaterialCookProgramContext;
7
+ } {
8
+ if (caps.backendKind === 'null') return {};
9
+ return {
10
+ materialContext: {
11
+ backend: caps.backendKind === 'wgpu-webgl2' ? 'webgl2' : caps.backendKind,
12
+ capability: caps.storageBuffer ? 'storage-buffer' : 'uniform-fallback',
13
+ pipeline: 'forward',
14
+ geometry: 'mesh',
15
+ pass: 'forward',
16
+ profile: 'forgeax-material-wgsl-v1',
17
+ toolchain: 'naga-oil',
18
+ instrumentation: 'none',
19
+ },
20
+ };
21
+ }