@forgeax/engine-app 0.1.3 → 0.1.6

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 (145) hide show
  1. package/README.md +20 -14
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/__tests__/create-app-error-retention.integration.test.d.ts +2 -0
  4. package/dist/__tests__/create-app-error-retention.integration.test.d.ts.map +1 -0
  5. package/dist/__tests__/create-app-physics-plugin-failure.integration.test.d.ts +2 -0
  6. package/dist/__tests__/create-app-physics-plugin-failure.integration.test.d.ts.map +1 -0
  7. package/dist/animation-asset-lookup.d.ts +1 -2
  8. package/dist/animation-asset-lookup.d.ts.map +1 -1
  9. package/dist/create-app.d.ts +31 -10
  10. package/dist/create-app.d.ts.map +1 -1
  11. package/dist/engine-worker-runtime.mjs +72 -129
  12. package/dist/engine-worker-runtime.mjs.map +1 -1
  13. package/dist/errors.d.ts +3 -12
  14. package/dist/errors.d.ts.map +1 -1
  15. package/dist/execution/attached-world-swap.d.ts +1 -12
  16. package/dist/execution/attached-world-swap.d.ts.map +1 -1
  17. package/dist/execution/bootstrap-url.d.ts +5 -0
  18. package/dist/execution/bootstrap-url.d.ts.map +1 -0
  19. package/dist/execution/control.d.ts +0 -2
  20. package/dist/execution/control.d.ts.map +1 -1
  21. package/dist/execution/engine-worker.d.ts +1 -0
  22. package/dist/execution/engine-worker.d.ts.map +1 -1
  23. package/dist/execution/host-controller.d.ts.map +1 -1
  24. package/dist/execution/index.d.ts +1 -1
  25. package/dist/execution/index.d.ts.map +1 -1
  26. package/dist/execution/protocol.d.ts +4 -1
  27. package/dist/execution/protocol.d.ts.map +1 -1
  28. package/dist/execution/types.d.ts +17 -1
  29. package/dist/execution/types.d.ts.map +1 -1
  30. package/dist/game-context.d.ts +1 -1
  31. package/dist/game-context.d.ts.map +1 -1
  32. package/dist/index.d.ts +4 -2
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.mjs +332 -366
  35. package/dist/index.mjs.map +1 -1
  36. package/dist/internal/assembled-engine-profile.d.ts.map +1 -1
  37. package/dist/internal/assets-world-plugin.d.ts +3 -17
  38. package/dist/internal/assets-world-plugin.d.ts.map +1 -1
  39. package/dist/internal/browser-remote-bridge.d.ts +1 -2
  40. package/dist/internal/browser-remote-bridge.d.ts.map +1 -1
  41. package/dist/internal/browser-rhi-debug-runtime.d.ts +1 -1
  42. package/dist/internal/browser-rhi-debug-runtime.d.ts.map +1 -1
  43. package/dist/internal/debug-draw.d.ts +9 -0
  44. package/dist/internal/debug-draw.d.ts.map +1 -0
  45. package/dist/internal/engine-profile-common.d.ts +2 -2
  46. package/dist/internal/engine-profile-common.d.ts.map +1 -1
  47. package/dist/internal/frame-loop.d.ts +7 -4
  48. package/dist/internal/frame-loop.d.ts.map +1 -1
  49. package/dist/internal/main-engine-profile.d.ts +4 -0
  50. package/dist/internal/main-engine-profile.d.ts.map +1 -1
  51. package/dist/internal/remote-serve-flag.d.ts +3 -7
  52. package/dist/internal/remote-serve-flag.d.ts.map +1 -1
  53. package/dist/internal/rhi-capture.d.ts +1 -1
  54. package/dist/internal/rhi-capture.d.ts.map +1 -1
  55. package/dist/internal/worker-engine-profile.d.ts +2 -0
  56. package/dist/internal/worker-engine-profile.d.ts.map +1 -1
  57. package/dist/renderer-plugin.d.ts +25 -0
  58. package/dist/renderer-plugin.d.ts.map +1 -0
  59. package/dist/tool-preview/bootstrap.d.ts.map +1 -1
  60. package/dist/types.d.ts +21 -61
  61. package/dist/types.d.ts.map +1 -1
  62. package/package.json +25 -26
  63. package/src/__tests__/callback-inference.test-d.ts +7 -3
  64. package/src/__tests__/consumer-migration.unit.test.ts +13 -0
  65. package/src/__tests__/create-app-error-retention.integration.test.ts +125 -0
  66. package/src/__tests__/create-app-features.test.ts +21 -10
  67. package/src/__tests__/create-app-listener-sync.test.ts +23 -19
  68. package/src/__tests__/create-app-physics-plugin-failure.integration.test.ts +142 -0
  69. package/src/__tests__/create-app-stop.test.ts +4 -12
  70. package/src/__tests__/create-app.test-d.ts +12 -11
  71. package/src/__tests__/create-app.test.ts +1 -60
  72. package/src/__tests__/create-renderer-error.integration.test.ts +3 -2
  73. package/src/__tests__/create-renderer-lifecycle.integration.test.ts +10 -5
  74. package/src/__tests__/draw-source-injected-world-update-visibility.test.ts +7 -13
  75. package/src/__tests__/draw-source-single-world-regression.test.ts +23 -24
  76. package/src/__tests__/engine-worker-bootstrap-channel.unit.test.ts +8 -0
  77. package/src/__tests__/engine-worker-world-swap.unit.test.ts +15 -22
  78. package/src/__tests__/errors-pointer-lock-failed.test.ts +4 -7
  79. package/src/__tests__/execution-bootstrap-isolation.unit.test.ts +5 -6
  80. package/src/__tests__/execution-frame-order.unit.test.ts +2 -2
  81. package/src/__tests__/execution-frame-protocol.unit.test.ts +3 -4
  82. package/src/__tests__/execution-public-api.test-d.ts +4 -0
  83. package/src/__tests__/frame-loop-time-elapsed.test.ts +1 -1
  84. package/src/__tests__/frame-loop-time.unit.test.ts +1 -1
  85. package/src/__tests__/frame-loop-world-array.test.ts +82 -73
  86. package/src/__tests__/plugin-profiles.unit.test.ts +3 -3
  87. package/src/__tests__/profiler-default-off.test.ts +1 -1
  88. package/src/__tests__/profiler-frame-token.test.ts +17 -30
  89. package/src/__tests__/profiler-phase-integration.test.ts +15 -27
  90. package/src/__tests__/surface-handoff.test.ts +1 -4
  91. package/src/__tests__/visibility-remote.integration.test.ts +2 -5
  92. package/src/__tests__/worker-execution-stop.test.ts +64 -7
  93. package/src/animation-asset-lookup.ts +2 -2
  94. package/src/create-app.ts +197 -296
  95. package/src/errors.ts +27 -39
  96. package/src/execution/attached-world-swap.ts +4 -38
  97. package/src/execution/bootstrap-url.ts +25 -0
  98. package/src/execution/control.ts +3 -7
  99. package/src/execution/engine-worker-runtime.ts +23 -40
  100. package/src/execution/engine-worker.ts +2 -0
  101. package/src/execution/host-controller.ts +8 -2
  102. package/src/execution/index.ts +1 -0
  103. package/src/execution/protocol.ts +4 -1
  104. package/src/execution/types.ts +18 -1
  105. package/src/game-context.ts +1 -1
  106. package/src/index.ts +11 -4
  107. package/src/internal/assembled-engine-profile.ts +3 -11
  108. package/src/internal/assets-world-plugin.ts +7 -53
  109. package/src/internal/browser-remote-bridge.ts +12 -10
  110. package/src/internal/browser-rhi-debug-runtime.ts +1 -1
  111. package/src/internal/debug-draw.ts +38 -0
  112. package/src/internal/engine-profile-common.ts +2 -3
  113. package/src/internal/frame-loop.ts +77 -63
  114. package/src/internal/main-engine-profile.ts +21 -17
  115. package/src/internal/remote-serve-flag.ts +5 -14
  116. package/src/internal/rhi-capture.ts +1 -1
  117. package/src/internal/worker-engine-profile.ts +10 -10
  118. package/src/renderer-plugin.ts +96 -0
  119. package/src/tool-preview/bootstrap.ts +1 -14
  120. package/src/types.ts +22 -77
  121. package/src/vite-env.d.ts +0 -2
  122. package/dist/__tests__/app-rebuild.test.d.ts +0 -2
  123. package/dist/__tests__/app-rebuild.test.d.ts.map +0 -1
  124. package/dist/__tests__/asset-registry-types.test-d.d.ts +0 -2
  125. package/dist/__tests__/asset-registry-types.test-d.d.ts.map +0 -1
  126. package/dist/__tests__/assets-runtime-assembly.integration.test.d.ts +0 -2
  127. package/dist/__tests__/assets-runtime-assembly.integration.test.d.ts.map +0 -1
  128. package/dist/__tests__/draw-source-headless-full-chain.test.d.ts +0 -2
  129. package/dist/__tests__/draw-source-headless-full-chain.test.d.ts.map +0 -1
  130. package/dist/__tests__/ecs-import.unit.test.d.ts +0 -2
  131. package/dist/__tests__/ecs-import.unit.test.d.ts.map +0 -1
  132. package/dist/__tests__/execution-world-health-owner.test-d.d.ts +0 -2
  133. package/dist/__tests__/execution-world-health-owner.test-d.d.ts.map +0 -1
  134. package/dist/__tests__/remote-serve-flag.test.d.ts +0 -2
  135. package/dist/__tests__/remote-serve-flag.test.d.ts.map +0 -1
  136. package/dist/assets-runtime-assembly.d.ts +0 -19
  137. package/dist/assets-runtime-assembly.d.ts.map +0 -1
  138. package/src/__tests__/app-rebuild.test.ts +0 -31
  139. package/src/__tests__/asset-registry-types.test-d.ts +0 -16
  140. package/src/__tests__/assets-runtime-assembly.integration.test.ts +0 -68
  141. package/src/__tests__/draw-source-headless-full-chain.test.ts +0 -226
  142. package/src/__tests__/ecs-import.unit.test.ts +0 -40
  143. package/src/__tests__/execution-world-health-owner.test-d.ts +0 -9
  144. package/src/__tests__/remote-serve-flag.test.ts +0 -24
  145. package/src/assets-runtime-assembly.ts +0 -70
package/dist/index.mjs CHANGED
@@ -1,20 +1,20 @@
1
1
  import { Update, World, createWorldContext, componentDefinition } from '@forgeax/engine-ecs';
2
2
  export { Context } from '@forgeax/engine-plugin';
3
- import * as engineRuntimeModule from '@forgeax/engine-runtime';
4
- import { defaultAssetDecoderContributions, createRenderer, EngineEnvironmentError } from '@forgeax/engine-runtime';
5
- import { err, ok } from '@forgeax/engine-types';
6
- import { createAssetRegistry, createCatalogSource } from '@forgeax/engine-assets-runtime';
7
- import { getAssetRegistryResolver } from '@forgeax/engine-assets-runtime/internal';
8
- import { loadRhiPack, createDebugDrawOnReady, releaseDebugDraw } from '@forgeax/engine-render/internal';
3
+ export { createFullscreenRenderFeature } from '@forgeax/engine-render/authoring';
4
+ import { Camera, perspective, renderComponentsPlugin, CAMERA_PROJECTION_PERSPECTIVE } from '@forgeax/engine-render';
9
5
  import { RhiError } from '@forgeax/engine-rhi';
10
- import { Camera, perspective, ownedRendererPlugin, renderComponentsPlugin, rendererPlugin, CAMERA_PROJECTION_PERSPECTIVE } from '@forgeax/engine-render';
6
+ import { attachRecorder, decodeTape, openReplay, decodeToRgba8, buildTapeIndex, createRhiDebugError } from '@forgeax/engine-rhi-debug';
7
+ import * as engineRuntimeModule from '@forgeax/engine-runtime';
8
+ import { EngineEnvironmentError } from '@forgeax/engine-runtime';
9
+ import { loadRhiPack, constructRuntimeRendererHost } from '@forgeax/engine-runtime/internal/renderer-host';
10
+ import { ok, err } from '@forgeax/engine-types';
11
11
  import { createHostAudioConsumer } from '@forgeax/engine-audio-webaudio';
12
12
  import { INPUT_BACKEND_KEY, InputSet, InputFrameStartScan, attachBrowserInputBackend, inputBackendPlugin, ownedInputBackendPlugin, INPUT_MAP_KEY } from '@forgeax/engine-input';
13
13
  import { componentSchema } from '@forgeax/engine-ecs/internal';
14
14
  import { animationPayloadsPlugin, animationRuntimePlugin } from '@forgeax/engine-animation';
15
15
  import { Transform, scenePlugin } from '@forgeax/engine-scene';
16
16
  import { statePlugin } from '@forgeax/engine-state';
17
- import { decodeTape, openReplay, decodeToRgba8, attachRecorder, buildTapeIndex, createRhiDebugError } from '@forgeax/engine-rhi-debug';
17
+ import { createDebugDraw } from '@forgeax/engine-debug-draw';
18
18
  import { createProfiler } from '@forgeax/engine-profiler';
19
19
  import { isSerializableValue, createPreviewArtifactManifest, validatePreviewArtifactManifest, createArtifactRef, snapshotStaleError } from '@forgeax/engine-tool-runtime';
20
20
 
@@ -96,10 +96,7 @@ function serializeError(error) {
96
96
  }
97
97
  async function installBrowserRemoteBridge(deps) {
98
98
  const { world, renderer, assets, rhiCapture, profiler, execution, port } = deps;
99
- const mod = await import(
100
- /* @vite-ignore */
101
- '@forgeax/engine-remote/execute'
102
- );
99
+ const mod = await import('@forgeax/engine-remote/execute');
103
100
  const executeScript = mod.executeScript;
104
101
  const importModule = createEcsImportModule((specifier) => {
105
102
  if (specifier === "@forgeax/engine-runtime")
@@ -219,66 +216,11 @@ var init_browser_remote_bridge = __esm({
219
216
  init_ecs_import();
220
217
  }
221
218
  });
222
- function assembleAssetRuntime(registry, contributions) {
223
- const leases = [];
224
- const overrides = new Set(contributions.map((contribution) => contribution.kind.kind));
225
- const defaults = defaultAssetDecoderContributions ?? [];
226
- const assembledContributions = [
227
- ...defaults.filter((contribution) => !overrides.has(contribution.kind.kind)),
228
- ...contributions
229
- ];
230
- try {
231
- const installedKinds = /* @__PURE__ */ new Set();
232
- for (const contribution of assembledContributions) {
233
- if (installedKinds.has(contribution.kind.kind)) {
234
- throw new TypeError(`duplicate decoder kind "${contribution.kind.kind}"`);
235
- }
236
- installedKinds.add(contribution.kind.kind);
237
- leases.push(
238
- registry.installDecoder(
239
- contribution.kind,
240
- contribution.decoder
241
- )
242
- );
243
- }
244
- } catch (cause) {
245
- for (const lease of leases) lease.dispose();
246
- const failed = assembledContributions[leases.length];
247
- return err({
248
- code: "asset-assembly-failed",
249
- expected: "each owner decoder contribution to install exactly once in the realm registry",
250
- hint: "remove the conflicting owner contribution and assemble the current realm again",
251
- detail: { kind: failed?.kind.kind ?? "unknown", cause }
252
- });
253
- }
254
- let disposed = false;
255
- return ok({
256
- registry,
257
- leases: Object.freeze([...leases]),
258
- dispose() {
259
- if (disposed) return;
260
- disposed = true;
261
- for (const lease of leases) lease.dispose();
262
- }
263
- });
264
- }
265
219
 
266
220
  // src/animation-asset-lookup.ts
267
221
  function createAnimationPayloadLookup(source) {
268
222
  return (guid) => source?.lookup(guid);
269
223
  }
270
- function syncCameraAspect(world, canvasW, canvasH) {
271
- if (canvasW <= 0 || canvasH <= 0) return;
272
- const aspect = canvasW / canvasH;
273
- const query = world.query({ with: [Camera] }).unwrap();
274
- for (const row of query) {
275
- const camera = world.get(row.entity, Camera);
276
- if (!camera.ok) continue;
277
- if (camera.value.autoAspect !== true) continue;
278
- if (camera.value.projection !== CAMERA_PROJECTION_PERSPECTIVE) continue;
279
- world.set(row.entity, Camera, { aspect });
280
- }
281
- }
282
224
 
283
225
  // src/errors.ts
284
226
  function summarizeCause(cause) {
@@ -341,10 +283,6 @@ var appErrorPolicy = {
341
283
  expected: "canvas.isConnected === true at createApp(canvas) entry",
342
284
  hint: "append the canvas to the document tree before calling createApp(canvas), or use the assemble entry createApp({ renderer, world }) when the host already manages canvas lifetime"
343
285
  },
344
- "app-invalid-canvas-pixel-ratio": {
345
- expected: "maxCanvasPixelRatio is finite and greater than 0",
346
- hint: "omit maxCanvasPixelRatio to keep the device pixel ratio, or pass a finite value > 0 from the Host policy"
347
- },
348
286
  "app-frame-step-invalid": {
349
287
  expected: "stepFrame(deltaSeconds) runs only while the App is paused and deltaSeconds is finite and non-negative",
350
288
  hint: "pause the App before deterministic stepping and pass an explicit finite delta; resume after the bounded step sequence completes"
@@ -740,20 +678,17 @@ function createLocalExecutionControl(initial, providers = {}) {
740
678
  world: providers.world?.() ?? current.world,
741
679
  audio: executionAudioReport(providers.audio?.())
742
680
  }),
743
- rebuild: async () => {
744
- if (providers.rebuild !== void 0) return providers.rebuild();
745
- return err(
746
- new AppError({
747
- code: "app-execution-rebuild-failed",
748
- expected: APP_EXPECTED["app-execution-rebuild-failed"],
749
- hint: APP_ERROR_HINTS["app-execution-rebuild-failed"],
750
- detail: {
751
- worldIdentity: current.world.identity,
752
- cause: new Error("This local assembly has no bootstrap module.")
753
- }
754
- })
755
- );
756
- }
681
+ rebuild: async () => err(
682
+ new AppError({
683
+ code: "app-execution-rebuild-failed",
684
+ expected: APP_EXPECTED["app-execution-rebuild-failed"],
685
+ hint: APP_ERROR_HINTS["app-execution-rebuild-failed"],
686
+ detail: {
687
+ worldIdentity: current.world.identity,
688
+ cause: new Error("This local assembly has no bootstrap module.")
689
+ }
690
+ })
691
+ )
757
692
  };
758
693
  }
759
694
  var ENGINE_WORKER_CAPABILITIES = [
@@ -828,6 +763,25 @@ function selectExecutionTier(input) {
828
763
  sharedEvidencePassed: input.sharedEvidencePassed
829
764
  });
830
765
  }
766
+ function normalizeExecutionBootstrapUrl(bootstrap) {
767
+ const moduleUrl = typeof bootstrap === "string" ? bootstrap : bootstrap.href;
768
+ try {
769
+ return ok(new URL(bootstrap, globalThis.location?.href).href);
770
+ } catch (cause) {
771
+ return err(
772
+ new AppError({
773
+ code: "app-execution-bootstrap-failed",
774
+ expected: APP_EXPECTED["app-execution-bootstrap-failed"],
775
+ hint: APP_ERROR_HINTS["app-execution-bootstrap-failed"],
776
+ detail: {
777
+ phase: "prepare",
778
+ moduleUrl,
779
+ cause: cause instanceof TypeError ? cause : new TypeError(String(cause))
780
+ }
781
+ })
782
+ );
783
+ }
784
+ }
831
785
 
832
786
  // src/internal/error-fanout.ts
833
787
  var ErrorFanoutRegistry = class {
@@ -977,6 +931,7 @@ async function startEngineWorker(options) {
977
931
  bootstrapUrl: options.bootstrapUrl,
978
932
  ...options.bootstrapData === void 0 ? {} : { bootstrapData: options.bootstrapData },
979
933
  ...options.bootstrapPort === void 0 ? {} : { bootstrapPort: options.bootstrapPort },
934
+ ...options.assetCatalog === void 0 ? {} : { assetCatalog: options.assetCatalog },
980
935
  ...options.shaderManifestUrl !== void 0 ? { shaderManifestUrl: options.shaderManifestUrl } : {},
981
936
  ...options.time !== void 0 ? { time: options.time } : {},
982
937
  tier: options.tier
@@ -1047,7 +1002,7 @@ var FrameCreditLedger = class {
1047
1002
  nextFrameId = 1;
1048
1003
  inFlight = null;
1049
1004
  completed = 0;
1050
- issue(deltaSeconds, sampleInput, canvasSize) {
1005
+ issue(deltaSeconds, sampleInput, canvasSize = { width: 0, height: 0 }) {
1051
1006
  if (this.inFlight !== null) return void 0;
1052
1007
  const frameId = this.nextFrameId;
1053
1008
  this.nextFrameId += 1;
@@ -1109,15 +1064,18 @@ function runtimeError(message) {
1109
1064
  async function createWorkerExecutionApp(options) {
1110
1065
  const executionOptions = options.appOptions.execution;
1111
1066
  if (executionOptions === void 0) throw new Error("execution options are required");
1112
- const syncCanvas = options.syncCanvas;
1113
- const bootstrapUrl = new URL(executionOptions.bootstrap, globalThis.location?.href).href;
1067
+ const normalizedBootstrap = normalizeExecutionBootstrapUrl(executionOptions.bootstrap);
1068
+ if (!normalizedBootstrap.ok) return normalizedBootstrap;
1069
+ const bootstrapUrl = normalizedBootstrap.value;
1114
1070
  const startupTimeoutMs = executionOptions.startupTimeoutMs ?? 1e4;
1115
1071
  const frameTimeoutMs = executionOptions.frameTimeoutMs ?? 2e3;
1072
+ const syncCanvas = options.syncCanvas;
1116
1073
  const started = await startEngineWorker({
1117
1074
  canvas: options.canvas,
1118
1075
  bootstrapUrl,
1119
1076
  ...executionOptions.bootstrapData === void 0 ? {} : { bootstrapData: executionOptions.bootstrapData },
1120
1077
  ...executionOptions.bootstrapPort === void 0 ? {} : { bootstrapPort: executionOptions.bootstrapPort },
1078
+ ...executionOptions.assetCatalog === void 0 ? {} : { assetCatalog: executionOptions.assetCatalog },
1121
1079
  ...options.bundler?.shaderManifestUrl !== void 0 ? { shaderManifestUrl: options.bundler.shaderManifestUrl } : {},
1122
1080
  ...options.appOptions.time !== void 0 ? { time: options.appOptions.time } : {},
1123
1081
  timeoutMs: startupTimeoutMs,
@@ -1453,61 +1411,99 @@ async function createWorkerExecutionApp(options) {
1453
1411
  return ok(app);
1454
1412
  }
1455
1413
 
1456
- // src/internal/assets-world-plugin.ts
1457
- var RENDER_ASSET_PORT_RESOURCE_KEY = "RenderAssetPort";
1458
- var ASSET_REGISTRY_RESOURCE_KEY = "AssetRegistry";
1459
- function rendererAssetsPlugin() {
1414
+ // src/renderer-plugin.ts
1415
+ function createRenderFeatureHost(host) {
1460
1416
  return {
1461
- name: "renderer-assets",
1462
- inject: ["renderer"],
1463
- provide: "assets",
1417
+ async installFeature(feature) {
1418
+ const installed = await host.installRenderFeature(feature);
1419
+ if (!installed.ok) return installed;
1420
+ let released = false;
1421
+ return {
1422
+ ok: true,
1423
+ value: {
1424
+ release: async () => {
1425
+ if (released) return { ok: true, value: void 0 };
1426
+ released = true;
1427
+ return host.uninstallRenderFeature(feature);
1428
+ }
1429
+ }
1430
+ };
1431
+ }
1432
+ };
1433
+ }
1434
+ function rendererPlugin(renderer) {
1435
+ return {
1436
+ name: "renderer",
1437
+ provide: "renderer",
1464
1438
  apply(ctx) {
1465
- if (ctx.renderer === void 0) throw new Error("assets require the renderer service");
1466
- ctx.provide("assets", ctx.renderer.assets);
1439
+ ctx.provide("renderer", renderer);
1467
1440
  }
1468
1441
  };
1469
1442
  }
1470
- function assetRegistryPlugin(assembly) {
1443
+ function ownedRendererPlugin(renderer) {
1471
1444
  return {
1472
- name: "asset-registry",
1473
- provide: "assetRegistry",
1445
+ name: "renderer",
1446
+ provide: "renderer",
1474
1447
  apply(ctx) {
1475
- ctx.provide("assetRegistry", assembly.registry);
1448
+ ctx.provide("renderer", renderer);
1449
+ ctx.effect(() => () => renderer.dispose(), "render/renderer");
1450
+ }
1451
+ };
1452
+ }
1453
+ function renderFeatureHostPlugin(host) {
1454
+ return {
1455
+ name: "render-feature-host",
1456
+ provide: "renderFeatureHost",
1457
+ apply(ctx) {
1458
+ ctx.provide("renderFeatureHost", host);
1459
+ }
1460
+ };
1461
+ }
1462
+ function renderFeaturePlugin(feature) {
1463
+ return {
1464
+ name: `render-feature:${feature.identity}`,
1465
+ inject: ["renderFeatureHost"],
1466
+ async apply(ctx) {
1467
+ if (ctx.renderFeatureHost === void 0) {
1468
+ throw new Error("render-feature host capability is unavailable in this App realm");
1469
+ }
1470
+ const installed = await ctx.renderFeatureHost.installFeature(feature);
1471
+ if (!installed.ok) throw installed.error;
1476
1472
  ctx.effect(
1477
- () => () => {
1478
- assembly.dispose();
1479
- assembly.registry.dispose();
1473
+ () => async () => {
1474
+ const removed = await installed.value.release();
1475
+ if (!removed.ok) throw removed.error;
1480
1476
  },
1481
- "assets/registry"
1477
+ `render-feature/${feature.identity}`
1482
1478
  );
1483
1479
  }
1484
1480
  };
1485
1481
  }
1486
- function assetsWorldPlugin() {
1482
+
1483
+ // src/internal/assets-world-plugin.ts
1484
+ var ASSET_REGISTRY_RESOURCE_KEY = "AssetRegistry";
1485
+ function rendererAssetsPlugin(assets) {
1487
1486
  return {
1488
- name: "assets-world",
1489
- inject: ["world", "assets"],
1487
+ name: "renderer-assets",
1488
+ inject: ["renderer"],
1489
+ provide: "assets",
1490
1490
  apply(ctx) {
1491
- ctx.effect(() => {
1492
- ctx.world.insertResource(RENDER_ASSET_PORT_RESOURCE_KEY, ctx.assets);
1493
- return () => {
1494
- ctx.world.removeResource(RENDER_ASSET_PORT_RESOURCE_KEY);
1495
- };
1496
- }, "assets/world-render-projection");
1491
+ if (assets !== void 0) ctx.provide("assets", assets);
1497
1492
  }
1498
1493
  };
1499
1494
  }
1500
- function assetRegistryWorldPlugin() {
1495
+ function assetsWorldPlugin() {
1501
1496
  return {
1502
- name: "asset-registry-world",
1503
- inject: ["world", "assetRegistry"],
1497
+ name: "assets-world",
1498
+ inject: ["world", "assets"],
1504
1499
  apply(ctx) {
1500
+ if (ctx.assets === void 0) return;
1505
1501
  ctx.effect(() => {
1506
- ctx.world.insertResource(ASSET_REGISTRY_RESOURCE_KEY, ctx.assetRegistry);
1502
+ ctx.world.insertResource(ASSET_REGISTRY_RESOURCE_KEY, ctx.assets);
1507
1503
  return () => {
1508
1504
  ctx.world.removeResource(ASSET_REGISTRY_RESOURCE_KEY);
1509
1505
  };
1510
- }, "assets/world-registry");
1506
+ }, "assets/world-resource");
1511
1507
  }
1512
1508
  };
1513
1509
  }
@@ -1516,8 +1512,7 @@ function assetRegistryWorldPlugin() {
1516
1512
  function assembledEngineProfile(options) {
1517
1513
  return [
1518
1514
  rendererPlugin(options.renderer),
1519
- rendererAssetsPlugin(),
1520
- ...options.assetAssembly === void 0 ? [] : [assetRegistryPlugin(options.assetAssembly), assetRegistryWorldPlugin()],
1515
+ rendererAssetsPlugin(options.assets),
1521
1516
  assetsWorldPlugin(),
1522
1517
  ...options.extensions ?? []
1523
1518
  ];
@@ -1680,7 +1675,6 @@ function resolveCaf(opts) {
1680
1675
  }
1681
1676
  function createFrameLoop(opts) {
1682
1677
  const { world, renderer } = opts;
1683
- const primaryDrawWorlds = [world];
1684
1678
  const phaseCatalogRegistration = opts.profiler?.registerPhaseCatalog("app", APP_PHASE_CATALOG);
1685
1679
  let releasePhaseCatalog = phaseCatalogRegistration?.ok === true ? phaseCatalogRegistration.value : void 0;
1686
1680
  let drawSource = opts.drawSource;
@@ -1692,53 +1686,50 @@ function createFrameLoop(opts) {
1692
1686
  let pendingFrameId = 0;
1693
1687
  let profilerFrameId = 0;
1694
1688
  let profilerCaptureId;
1695
- let primaryAttached = false;
1696
- let activeInjectedWorlds;
1697
- let nextInjectedWorlds;
1689
+ const leases = /* @__PURE__ */ new Map();
1698
1690
  function attachPrimary(fireError) {
1699
- if (primaryAttached) return;
1691
+ if (leases.has(world)) return;
1700
1692
  try {
1701
- const result = renderer.attachWorld(world);
1702
- primaryAttached = result.ok;
1703
- if (!result.ok) fireError(result.error);
1693
+ const result = renderer.attach(world);
1694
+ if (result.ok) leases.set(world, result.value);
1695
+ else fireError(result.error);
1704
1696
  } catch (cause) {
1705
1697
  fireError(makeWorldUpdateError(cause));
1706
1698
  }
1707
1699
  }
1708
1700
  function syncInjectedAttachments(worlds, fireError) {
1709
1701
  if (worlds === void 0) {
1710
- if (activeInjectedWorlds !== void 0) {
1711
- for (const attached of activeInjectedWorlds) renderer.detachWorld(attached);
1712
- activeInjectedWorlds.clear();
1702
+ for (const [attached, lease] of leases) {
1703
+ if (attached !== world) {
1704
+ lease.dispose();
1705
+ leases.delete(attached);
1706
+ }
1713
1707
  }
1714
1708
  return void 0;
1715
1709
  }
1716
- const next = nextInjectedWorlds ?? /* @__PURE__ */ new Set();
1717
- nextInjectedWorlds = next;
1718
- next.clear();
1719
- const active = activeInjectedWorlds;
1710
+ const next = /* @__PURE__ */ new Set();
1720
1711
  for (const candidate of worlds) {
1721
1712
  if (candidate === world || next.has(candidate)) continue;
1722
- if (active?.has(candidate) === true) {
1713
+ if (leases.has(candidate)) {
1723
1714
  next.add(candidate);
1724
1715
  continue;
1725
1716
  }
1726
1717
  try {
1727
- const result = renderer.attachWorld(candidate);
1728
- if (result.ok) next.add(candidate);
1729
- else fireError?.(result.error);
1718
+ const result = renderer.attach(candidate);
1719
+ if (result.ok) {
1720
+ leases.set(candidate, result.value);
1721
+ next.add(candidate);
1722
+ } else fireError?.(result.error);
1730
1723
  } catch (cause) {
1731
1724
  fireError?.(makeWorldUpdateError(cause));
1732
1725
  }
1733
1726
  }
1734
- if (active !== void 0) {
1735
- for (const attached of active) {
1736
- if (!next.has(attached)) renderer.detachWorld(attached);
1727
+ for (const [attached, lease] of leases) {
1728
+ if (attached !== world && !next.has(attached)) {
1729
+ lease.dispose();
1730
+ leases.delete(attached);
1737
1731
  }
1738
- active.clear();
1739
1732
  }
1740
- activeInjectedWorlds = next;
1741
- nextInjectedWorlds = active;
1742
1733
  return next;
1743
1734
  }
1744
1735
  function releaseInjectedAttachments() {
@@ -1746,9 +1737,11 @@ function createFrameLoop(opts) {
1746
1737
  }
1747
1738
  function releaseAttachments() {
1748
1739
  releaseInjectedAttachments();
1749
- if (!primaryAttached) return;
1750
- renderer.detachWorld(world);
1751
- primaryAttached = false;
1740
+ const lease = leases.get(world);
1741
+ if (lease !== void 0) {
1742
+ lease.dispose();
1743
+ leases.delete(world);
1744
+ }
1752
1745
  }
1753
1746
  function runProfiledPhase(session, phase, action) {
1754
1747
  const opened = beginPhase(session, phase);
@@ -1799,7 +1792,7 @@ function createFrameLoop(opts) {
1799
1792
  });
1800
1793
  const attachedInjectedWorlds = syncInjectedAttachments(injected?.worlds, reportError);
1801
1794
  const updatedWorlds = injected === void 0 ? void 0 : /* @__PURE__ */ new Set();
1802
- if (updatedWorlds !== void 0 && primaryUpdated && primaryAttached) {
1795
+ if (updatedWorlds !== void 0 && primaryUpdated && leases.has(world)) {
1803
1796
  updatedWorlds.add(world);
1804
1797
  }
1805
1798
  runProfiledPhase(session, "world-update-injected", () => {
@@ -1816,27 +1809,34 @@ function createFrameLoop(opts) {
1816
1809
  });
1817
1810
  runProfiledPhase(session, "renderer-draw", () => {
1818
1811
  try {
1819
- const profileOptions = profileFrame === void 0 ? {} : { profileFrame };
1820
1812
  let drawResult;
1821
1813
  if (injected === void 0) {
1822
- if (!primaryUpdated || !primaryAttached) return;
1823
- drawResult = renderer.draw(primaryDrawWorlds, {
1824
- cameraOwner: 0,
1825
- resourceOwner: 0,
1826
- ...profileOptions
1814
+ const primaryLease = leases.get(world);
1815
+ if (!primaryUpdated || primaryLease === void 0) return;
1816
+ drawResult = renderer.draw({
1817
+ leases: [primaryLease],
1818
+ camera: { lease: primaryLease },
1819
+ environment: { lease: primaryLease },
1820
+ ...profileFrame === void 0 ? {} : { profileFrame }
1827
1821
  });
1828
1822
  } else {
1829
1823
  if (updatedWorlds === void 0) return;
1830
- const readyWorlds = injected.worlds.filter((candidate) => updatedWorlds.has(candidate));
1824
+ const readyEntries = injected.worlds.filter((candidate) => updatedWorlds.has(candidate)).map((candidate) => ({ candidate, lease: leases.get(candidate) })).filter(
1825
+ (entry) => entry.lease !== void 0
1826
+ );
1827
+ const readyLeases = readyEntries.map((entry) => entry.lease);
1831
1828
  const cameraWorld = injected.worlds[injected.cameraOwner];
1832
- const resourceWorld = injected.worlds[injected.resourceOwner];
1833
- const cameraOwner = cameraWorld === void 0 ? -1 : readyWorlds.indexOf(cameraWorld);
1834
- const resourceOwner = resourceWorld === void 0 ? -1 : readyWorlds.indexOf(resourceWorld);
1835
- if (readyWorlds.length === 0 || cameraOwner < 0 || resourceOwner < 0) return;
1836
- drawResult = renderer.draw(readyWorlds, {
1837
- cameraOwner,
1838
- resourceOwner,
1839
- ...profileOptions
1829
+ const environmentWorld = injected.worlds[injected.resourceOwner];
1830
+ if (cameraWorld === void 0 || environmentWorld === void 0) return;
1831
+ const cameraLease = leases.get(cameraWorld);
1832
+ const environmentLease = leases.get(environmentWorld);
1833
+ if (readyLeases.length === 0 || cameraLease === void 0 || environmentLease === void 0)
1834
+ return;
1835
+ drawResult = renderer.draw({
1836
+ leases: readyLeases,
1837
+ camera: { lease: cameraLease },
1838
+ environment: { lease: environmentLease },
1839
+ ...profileFrame === void 0 ? {} : { profileFrame }
1840
1840
  });
1841
1841
  }
1842
1842
  if (drawResult !== void 0) {
@@ -1851,14 +1851,14 @@ function createFrameLoop(opts) {
1851
1851
  });
1852
1852
  });
1853
1853
  endFrame(session);
1854
+ try {
1855
+ opts.debugRhi?.onFrameEnd();
1856
+ } catch {
1857
+ }
1854
1858
  return frameError === void 0 ? ok(void 0) : err(frameError);
1855
1859
  }
1856
1860
  function tick() {
1857
1861
  if (state !== "running") return;
1858
- if (renderer.health?.().reason === "device-lost") {
1859
- pendingFrameId = raf(tick);
1860
- return;
1861
- }
1862
1862
  const timestamp = now();
1863
1863
  const deltaSeconds = (timestamp - lastTimestamp) / 1e3;
1864
1864
  lastTimestamp = timestamp;
@@ -2066,17 +2066,53 @@ function inputPlugin() {
2066
2066
  };
2067
2067
  }
2068
2068
 
2069
+ // ../render/src/record/render-context.ts
2070
+ function selectSwapChainFormat(storageBufferCapable) {
2071
+ if (!storageBufferCapable) return { storage: "rgba8unorm", view: "rgba8unorm-srgb" };
2072
+ const nav = globalThis.navigator;
2073
+ const gpu = nav?.gpu;
2074
+ const getPreferred = gpu?.getPreferredCanvasFormat;
2075
+ if (gpu !== void 0 && typeof getPreferred === "function") {
2076
+ const storage = getPreferred.call(gpu);
2077
+ const view = storage === "rgba8unorm" ? "rgba8unorm-srgb" : storage === "bgra8unorm" ? "bgra8unorm-srgb" : storage;
2078
+ return { storage, view };
2079
+ }
2080
+ return {
2081
+ storage: "rgba8unorm",
2082
+ view: "rgba8unorm-srgb",
2083
+ fallbackReason: "preferred-canvas-format-missing"
2084
+ };
2085
+ }
2086
+
2087
+ // src/internal/debug-draw.ts
2088
+ async function createDebugDrawOnReady(context, format) {
2089
+ const ready = await context.initialization;
2090
+ if (!ready.ok) throw ready.error;
2091
+ const resolvedFormat = selectSwapChainFormat(context.device.caps.storageBuffer).view;
2092
+ const result = await createDebugDraw({
2093
+ device: context.device,
2094
+ queue: context.device.queue,
2095
+ createShaderModule: context._internal_createShaderModule,
2096
+ format: resolvedFormat
2097
+ });
2098
+ if (!result.ok) throw result.error;
2099
+ context._internal_setRenderOverlay(result.value);
2100
+ return result.value;
2101
+ }
2102
+ function releaseDebugDraw(context, debugDraw) {
2103
+ context._internal_setRenderOverlay(void 0);
2104
+ debugDraw.destroy();
2105
+ }
2106
+
2069
2107
  // src/internal/main-engine-profile.ts
2070
- function debugDrawPlugin(onReady) {
2108
+ function debugDrawPlugin(context, onReady) {
2071
2109
  return {
2072
2110
  name: "debug-draw",
2073
- inject: ["renderer"],
2074
2111
  async apply(ctx) {
2075
- if (ctx.renderer === void 0) throw new Error("debug draw requires the renderer service");
2076
2112
  try {
2077
- const debugDraw = await createDebugDrawOnReady(ctx.renderer);
2113
+ const debugDraw = await createDebugDrawOnReady(context);
2078
2114
  onReady(debugDraw);
2079
- ctx.effect(() => () => releaseDebugDraw(debugDraw), "render/debug-draw");
2115
+ ctx.effect(() => () => releaseDebugDraw(context, debugDraw), "render/debug-draw");
2080
2116
  } catch {
2081
2117
  }
2082
2118
  }
@@ -2086,8 +2122,8 @@ function mainEngineProfile(options) {
2086
2122
  return [
2087
2123
  ownedRendererPlugin(options.renderer),
2088
2124
  renderComponentsPlugin(),
2089
- rendererAssetsPlugin(),
2090
- ...options.assetAssembly === void 0 ? [] : [assetRegistryPlugin(options.assetAssembly), assetRegistryWorldPlugin()],
2125
+ ...options.rendererFeatureHost === void 0 ? [] : [renderFeatureHostPlugin(options.rendererFeatureHost)],
2126
+ rendererAssetsPlugin(options.assets),
2091
2127
  assetsWorldPlugin(),
2092
2128
  ...options.input === void 0 ? [] : [
2093
2129
  options.inputDispose === void 0 ? inputBackendPlugin(options.input) : ownedInputBackendPlugin(options.input, options.inputDispose)
@@ -2096,7 +2132,7 @@ function mainEngineProfile(options) {
2096
2132
  animationPayloadsPlugin(options.animationPayloads),
2097
2133
  animationRuntimePlugin(),
2098
2134
  statePlugin(),
2099
- ...options.onDebugDrawReady === void 0 ? [] : [debugDrawPlugin(options.onDebugDrawReady)],
2135
+ ...options.onDebugDrawReady === void 0 || options.rendererDebugDrawHost === void 0 ? [] : [debugDrawPlugin(options.rendererDebugDrawHost, options.onDebugDrawReady)],
2100
2136
  ...options.input === void 0 ? [] : [inputPlugin()],
2101
2137
  ...options.inputMap === void 0 ? [] : [inputMapPlugin(options.inputMap)],
2102
2138
  ...options.extensions ?? []
@@ -2104,8 +2140,7 @@ function mainEngineProfile(options) {
2104
2140
  }
2105
2141
 
2106
2142
  // src/internal/remote-serve-flag.ts
2107
- function resolveRemoteServeFlag(isDev, processEnv, isBrowser = false) {
2108
- if (isBrowser) return false;
2143
+ function resolveRemoteServeFlag(isDev, processEnv) {
2109
2144
  if (isDev === true) return true;
2110
2145
  if (processEnv?.FORGEAX_ENGINE_REMOTE_SERVE === "1") return true;
2111
2146
  return false;
@@ -2292,38 +2327,10 @@ function resolveRhiDebugFlag(importMetaEnv, processEnv) {
2292
2327
  }
2293
2328
 
2294
2329
  // src/create-app.ts
2295
- var DEFAULT_ASSET_CATALOG_PATH = "pack-index.json";
2296
- function defaultAssetCatalogUrl() {
2297
- const base = typeof document !== "undefined" ? document.baseURI : globalThis.location?.href;
2298
- return base === void 0 ? DEFAULT_ASSET_CATALOG_PATH : new URL(DEFAULT_ASSET_CATALOG_PATH, base).href;
2299
- }
2300
- function assembleAppAssetRuntime(catalog, contributions) {
2301
- const registry = createAssetRegistry({
2302
- catalog: catalog ?? createCatalogSource({ url: defaultAssetCatalogUrl() })
2303
- });
2304
- const assembled = assembleAssetRuntime(registry, contributions ?? []);
2305
- if (!assembled.ok) registry.dispose();
2306
- return assembled;
2307
- }
2308
2330
  function makeAppError2(code, expected, hint, detail) {
2309
2331
  return new AppError({ code, expected, hint, detail });
2310
2332
  }
2311
- function normalizeExecutionBootstrapUrl(bootstrap) {
2312
- const moduleUrl = typeof bootstrap === "string" ? bootstrap : bootstrap.href;
2313
- try {
2314
- return ok(new URL(bootstrap, globalThis.location?.href).href);
2315
- } catch (cause) {
2316
- return err(
2317
- makeAppError2(
2318
- "app-execution-bootstrap-failed",
2319
- APP_EXPECTED["app-execution-bootstrap-failed"],
2320
- APP_ERROR_HINTS["app-execution-bootstrap-failed"],
2321
- { phase: "prepare", moduleUrl, cause }
2322
- )
2323
- );
2324
- }
2325
- }
2326
- function canvasAspectPlugin(canvas, maxCanvasPixelRatio) {
2333
+ function canvasAspectPlugin(canvas) {
2327
2334
  return {
2328
2335
  name: "canvas-aspect",
2329
2336
  inject: ["world"],
@@ -2333,7 +2340,7 @@ function canvasAspectPlugin(canvas, maxCanvasPixelRatio) {
2333
2340
  name: "app-sync-camera-aspect",
2334
2341
  queries: [],
2335
2342
  fn: () => {
2336
- syncCanvasDrawingBuffer(canvas, maxCanvasPixelRatio);
2343
+ syncCanvasDrawingBuffer(canvas);
2337
2344
  syncCameraAspect(ctx.world, canvas.width, canvas.height);
2338
2345
  }
2339
2346
  };
@@ -2358,17 +2365,6 @@ function createApp(arg, opts, bundler) {
2358
2365
  return createAppFromAssemble(arg);
2359
2366
  }
2360
2367
  async function createAppFromCanvas(canvas, opts, bundler) {
2361
- const maxCanvasPixelRatio = opts?.maxCanvasPixelRatio;
2362
- if (maxCanvasPixelRatio !== void 0 && (!Number.isFinite(maxCanvasPixelRatio) || maxCanvasPixelRatio <= 0)) {
2363
- return err(
2364
- makeAppError2(
2365
- "app-invalid-canvas-pixel-ratio",
2366
- APP_EXPECTED["app-invalid-canvas-pixel-ratio"],
2367
- APP_ERROR_HINTS["app-invalid-canvas-pixel-ratio"],
2368
- { value: maxCanvasPixelRatio }
2369
- )
2370
- );
2371
- }
2372
2368
  if (!canvas.isConnected) {
2373
2369
  return err(
2374
2370
  makeAppError2(
@@ -2379,22 +2375,18 @@ async function createAppFromCanvas(canvas, opts, bundler) {
2379
2375
  )
2380
2376
  );
2381
2377
  }
2382
- const executionOptions = opts?.execution;
2383
- const normalizedExecutionBootstrap = executionOptions === void 0 ? void 0 : normalizeExecutionBootstrapUrl(executionOptions.bootstrap);
2384
- if (normalizedExecutionBootstrap?.ok === false) {
2385
- return err(normalizedExecutionBootstrap.error);
2386
- }
2387
- syncCanvasDrawingBuffer(canvas, maxCanvasPixelRatio);
2388
- if (opts?.execution !== void 0 && normalizedExecutionBootstrap?.ok === true) {
2378
+ syncCanvasDrawingBuffer(canvas);
2379
+ let executionBootstrapUrl;
2380
+ if (opts?.execution !== void 0) {
2381
+ const normalizedBootstrap = normalizeExecutionBootstrapUrl(opts.execution.bootstrap);
2382
+ if (!normalizedBootstrap.ok) return err(normalizedBootstrap.error);
2383
+ executionBootstrapUrl = normalizedBootstrap.value;
2389
2384
  const realmBoundOption = [
2390
2385
  ["features", opts.features],
2391
2386
  ["plugins", opts.plugins],
2392
2387
  ["rhi", opts.rhi],
2393
- ["rawDeviceForContextConfigure", opts.rawDeviceForContextConfigure],
2388
+ ["rhiInstrumentation", opts.rhiInstrumentation],
2394
2389
  ["drawSource", opts.drawSource],
2395
- ["membershipTiming", opts.membershipTiming],
2396
- ["assetCatalog", opts.assetCatalog],
2397
- ["assetDecoders", opts.assetDecoders],
2398
2390
  ["bundler.importTransport", bundler?.importTransport]
2399
2391
  ].find(([, value]) => value !== void 0)?.[0];
2400
2392
  if (realmBoundOption !== void 0) {
@@ -2405,7 +2397,7 @@ async function createAppFromCanvas(canvas, opts, bundler) {
2405
2397
  APP_ERROR_HINTS["app-execution-bootstrap-failed"],
2406
2398
  {
2407
2399
  phase: "prepare",
2408
- moduleUrl: normalizedExecutionBootstrap.value,
2400
+ moduleUrl: executionBootstrapUrl,
2409
2401
  cause: new TypeError(
2410
2402
  `${realmBoundOption} must be constructed by the execution bootstrap module`
2411
2403
  )
@@ -2416,11 +2408,10 @@ async function createAppFromCanvas(canvas, opts, bundler) {
2416
2408
  }
2417
2409
  let executionContext;
2418
2410
  let preparedExecutionBootstrap;
2419
- if (opts?.execution !== void 0 && normalizedExecutionBootstrap?.ok === true) {
2420
- const executionOptions2 = opts.execution;
2411
+ if (opts?.execution !== void 0) {
2421
2412
  const capabilities = await probeExecutionCapabilities(canvas);
2422
2413
  const selected = selectExecutionTier({
2423
- requestedTier: executionOptions2.tier ?? "auto",
2414
+ requestedTier: opts.execution.tier ?? "auto",
2424
2415
  capabilities,
2425
2416
  sharedEvidencePassed: true
2426
2417
  });
@@ -2430,15 +2421,18 @@ async function createAppFromCanvas(canvas, opts, bundler) {
2430
2421
  return createWorkerExecutionApp({
2431
2422
  canvas,
2432
2423
  appOptions: opts,
2433
- syncCanvas: () => measureCanvasDrawingBuffer(canvas, maxCanvasPixelRatio),
2424
+ syncCanvas: () => measureCanvasDrawingBuffer(canvas),
2434
2425
  ...bundler !== void 0 ? { bundler } : {},
2435
2426
  capabilities,
2436
2427
  selection: selected.value
2437
2428
  });
2438
2429
  }
2430
+ if (executionBootstrapUrl === void 0) {
2431
+ throw new Error("execution bootstrap URL was not normalized");
2432
+ }
2439
2433
  const prepared = await prepareBootstrapEntry(
2440
- normalizedExecutionBootstrap.value,
2441
- executionOptions2.bootstrapData
2434
+ executionBootstrapUrl,
2435
+ opts.execution.bootstrapData
2442
2436
  );
2443
2437
  if (!prepared.ok) return err(prepared.error);
2444
2438
  preparedExecutionBootstrap = prepared.value;
@@ -2447,24 +2441,19 @@ async function createAppFromCanvas(canvas, opts, bundler) {
2447
2441
  if (opts?.rhi !== void 0) {
2448
2442
  Object.assign(rendererOpts, { rhi: opts.rhi });
2449
2443
  }
2450
- if (opts?.rhiInstrumentation !== void 0) {
2451
- Object.assign(rendererOpts, { rhiInstrumentation: opts.rhiInstrumentation });
2452
- }
2453
- if (opts?.rawDeviceForContextConfigure !== void 0) {
2454
- Object.assign(rendererOpts, {
2455
- rawDeviceForContextConfigure: opts.rawDeviceForContextConfigure
2456
- });
2457
- }
2458
2444
  if (opts?.profiler !== void 0) {
2459
2445
  Object.assign(rendererOpts, { profiler: opts.profiler });
2460
2446
  }
2461
- if (opts?.membershipTiming !== void 0) {
2462
- Object.assign(rendererOpts, { membershipTiming: opts.membershipTiming });
2463
- }
2464
2447
  const rendererFeatures = preparedExecutionBootstrap?.features ?? opts?.features;
2465
2448
  if (rendererFeatures !== void 0) {
2466
2449
  Object.assign(rendererOpts, { features: rendererFeatures });
2467
2450
  }
2451
+ if (opts?.standardProfile !== void 0) {
2452
+ Object.assign(rendererOpts, { standardProfile: opts.standardProfile });
2453
+ }
2454
+ if (opts?.rhiInstrumentation !== void 0) {
2455
+ Object.assign(rendererOpts, { rhiInstrumentation: opts.rhiInstrumentation });
2456
+ }
2468
2457
  let rhiAttachment;
2469
2458
  let rhiCapture;
2470
2459
  let rhiDebugGlobal;
@@ -2482,61 +2471,54 @@ async function createAppFromCanvas(canvas, opts, bundler) {
2482
2471
  const rhiDebugFlag = resolveRhiDebugFlag(importMetaEnv, processEnv);
2483
2472
  const nav = typeof globalThis !== "undefined" ? globalThis.navigator : void 0;
2484
2473
  const hasWebGPU = nav !== void 0 && "gpu" in nav && nav.gpu !== void 0;
2485
- if (opts?.rhi === void 0 && (browserBuildRhiDebugFlag === void 0 || browserBuildRhiDebugFlag === "1") && rhiDebugFlag === "1") {
2486
- const backend = hasWebGPU ? await import('@forgeax/engine-rhi-webgpu') : await import('@forgeax/engine-rhi-wgpu');
2487
- if (!hasWebGPU && "ensureReady" in backend) await backend.ensureReady();
2488
- const pack = loadRhiPack(backend);
2489
- const debugModule = await import(
2490
- /* @vite-ignore */
2491
- '@forgeax/engine-rhi-debug'
2492
- );
2474
+ if ((browserBuildRhiDebugFlag === void 0 || browserBuildRhiDebugFlag === "1") && rhiDebugFlag === "1") {
2475
+ const realBackend = hasWebGPU ? await import('@forgeax/engine-rhi-webgpu') : await import('@forgeax/engine-rhi-wgpu');
2476
+ if (!hasWebGPU && "ensureReady" in realBackend) {
2477
+ await realBackend.ensureReady();
2478
+ }
2479
+ const pack = loadRhiPack(realBackend);
2493
2480
  if (pack.createShaderModule === void 0) {
2494
- throw new Error("RHI debug capture requires the backend shader-module entry");
2481
+ throw new Error("RHI-debug requires a backend createShaderModule capability");
2495
2482
  }
2496
- const createShaderModule = pack.createShaderModule;
2497
- const attached = debugModule.attachRecorder({
2483
+ const attached = attachRecorder({
2498
2484
  rhi: pack.rhi,
2499
- createShaderModule
2485
+ createShaderModule: pack.createShaderModule
2500
2486
  });
2501
2487
  if (!attached.ok) throw new Error(attached.error.hint);
2502
- const attachment = attached.value;
2503
- rhiAttachment = attachment;
2504
- const capture = createRhiCapture(attachment);
2505
- rhiCapture = capture;
2488
+ rhiAttachment = attached.value;
2489
+ rhiCapture = createRhiCapture(attached.value);
2490
+ const capture = rhiCapture;
2491
+ rhiDebugGlobal = { captureFrame: (options) => capture.captureFrame(options) };
2506
2492
  Object.assign(rendererOpts, {
2507
- rhi: attachment.backend.rhi,
2508
- rhiInstrumentation: createRhiInstrumentation(attachment)
2493
+ rhi: attached.value.backend.rhi,
2494
+ rhiInstrumentation: createRhiInstrumentation(attached.value)
2509
2495
  });
2510
- rhiDebugGlobal = {
2511
- captureFrame: (options) => capture.captureFrame(options)
2512
- };
2513
2496
  globalThis.__forgeax = rhiDebugGlobal;
2514
2497
  }
2515
- const assetAssemblyResult = assembleAppAssetRuntime(opts?.assetCatalog, opts?.assetDecoders);
2516
- if (!assetAssemblyResult.ok) {
2517
- cleanupRhiDebugHost();
2518
- return err(assetAssemblyResult.error);
2519
- }
2520
- const assetAssembly = assetAssemblyResult.value;
2521
2498
  let renderer;
2499
+ let rendererDebugDrawHost;
2500
+ let rendererFeatureHost;
2501
+ let assets;
2522
2502
  try {
2523
- renderer = await createRenderer(canvas, rendererOpts, {
2524
- ...bundler,
2525
- assetResolver: getAssetRegistryResolver(assetAssembly.registry)
2526
- });
2503
+ const constructed = await constructRuntimeRendererHost(canvas, rendererOpts, bundler);
2504
+ if (!constructed.ok) throw constructed.error;
2505
+ renderer = constructed.value.renderer;
2506
+ rendererDebugDrawHost = constructed.value.debugDrawHost;
2507
+ rendererFeatureHost = createRenderFeatureHost(constructed.value.featureHost);
2508
+ assets = constructed.value.assets;
2527
2509
  } catch (e) {
2528
2510
  cleanupRhiDebugHost();
2529
- assetAssembly.dispose();
2530
- assetAssembly.registry.dispose();
2531
- if (e instanceof EngineEnvironmentError) {
2532
- return err(e);
2533
- }
2534
- throw e;
2511
+ if (e instanceof EngineEnvironmentError) return err(e);
2512
+ const detail = e instanceof Error ? e : new Error(String(e));
2513
+ return err(
2514
+ new EngineEnvironmentError("renderer construction failed", {
2515
+ webgpuError: detail
2516
+ })
2517
+ );
2535
2518
  }
2536
2519
  const shouldStartRemote = resolveRemoteServeFlag(
2537
2520
  typeof import.meta !== "undefined" ? import.meta.env?.DEV : void 0,
2538
- globalThis.process?.env,
2539
- typeof window !== "undefined"
2521
+ globalThis.process?.env
2540
2522
  );
2541
2523
  let debugDraw;
2542
2524
  const world = new World(opts?.time !== void 0 ? { time: opts.time } : {});
@@ -2560,8 +2542,10 @@ async function createAppFromCanvas(canvas, opts, bundler) {
2560
2542
  world,
2561
2543
  mainEngineProfile({
2562
2544
  renderer,
2563
- assetAssembly,
2564
- animationPayloads: createAnimationPayloadLookup(renderer.assets),
2545
+ rendererDebugDrawHost,
2546
+ rendererFeatureHost,
2547
+ assets,
2548
+ animationPayloads: createAnimationPayloadLookup(assets),
2565
2549
  ...inputBackend === void 0 ? {} : { input: inputBackend },
2566
2550
  ...inputHandle === void 0 ? {} : { inputDispose: inputHandle.cleanup },
2567
2551
  ...opts?.inputMap === void 0 ? {} : { inputMap: opts.inputMap },
@@ -2571,14 +2555,12 @@ async function createAppFromCanvas(canvas, opts, bundler) {
2571
2555
  extensions: [
2572
2556
  rhiDebugHostPlugin(cleanupRhiDebugHost),
2573
2557
  ...userPlugins,
2574
- canvasAspectPlugin(canvas, maxCanvasPixelRatio)
2558
+ canvasAspectPlugin(canvas)
2575
2559
  ]
2576
2560
  })
2577
2561
  );
2578
2562
  } catch (cause) {
2579
2563
  cleanupRhiDebugHost();
2580
- assetAssembly.dispose();
2581
- assetAssembly.registry.dispose();
2582
2564
  return err(
2583
2565
  makeAppError2(
2584
2566
  "app-plugin-activation-failed",
@@ -2626,18 +2608,11 @@ async function createAppFromCanvas(canvas, opts, bundler) {
2626
2608
  })
2627
2609
  }
2628
2610
  );
2629
- const remoteHandle = shouldStartRemote ? await startRemoteServer(
2630
- world,
2631
- renderer,
2632
- assetAssembly.registry,
2633
- rhiCapture,
2634
- opts?.profiler,
2635
- executionControl
2636
- ) : void 0;
2611
+ const remoteHandle = shouldStartRemote ? await startRemoteServer(world, renderer, assets, rhiCapture, opts?.profiler, executionControl) : void 0;
2637
2612
  if (remoteHandle !== void 0) await pluginContext.plugin(remoteServerPlugin(remoteHandle));
2638
2613
  const buildArgs = {
2639
2614
  renderer,
2640
- assets: assetAssembly.registry,
2615
+ assets,
2641
2616
  world,
2642
2617
  pluginContext,
2643
2618
  executionControl,
@@ -2663,10 +2638,7 @@ async function createAppFromCanvas(canvas, opts, bundler) {
2663
2638
  Object.assign(buildArgs, { profiler: opts.profiler });
2664
2639
  }
2665
2640
  if (rhiCapture !== void 0) {
2666
- Object.assign(buildArgs, {
2667
- rhiCapture,
2668
- ...rhiAttachment === void 0 ? {} : { rhiAttachment }
2669
- });
2641
+ Object.assign(buildArgs, { rhiCapture });
2670
2642
  }
2671
2643
  if (debugDraw !== void 0) {
2672
2644
  Object.assign(buildArgs, { debugDraw });
@@ -2684,17 +2656,17 @@ async function createAppFromCanvas(canvas, opts, bundler) {
2684
2656
  const bridgePort = import.meta.env?.VITE_FORGEAX_ENGINE_BRIDGE_PORT ?? "5733";
2685
2657
  await pluginContext.plugin({
2686
2658
  name: "browser-remote-bridge",
2687
- inject: ["world", "renderer", "assetRegistry"],
2659
+ inject: ["world", "renderer", "assets"],
2688
2660
  async apply(ctx) {
2689
- if (ctx.renderer === void 0 || ctx.assetRegistry === void 0) {
2690
- throw new Error("browser remote bridge requires renderer and assetRegistry services");
2661
+ if (ctx.renderer === void 0 || ctx.assets === void 0) {
2662
+ throw new Error("browser remote bridge requires renderer and assets services");
2691
2663
  }
2692
2664
  try {
2693
2665
  const bridge = await Promise.resolve().then(() => (init_browser_remote_bridge(), browser_remote_bridge_exports));
2694
2666
  const teardown = await bridge.installBrowserRemoteBridge({
2695
2667
  world: ctx.world,
2696
2668
  renderer: ctx.renderer,
2697
- assets: ctx.assetRegistry,
2669
+ assets: ctx.assets,
2698
2670
  runtimeModule: engineRuntimeModule,
2699
2671
  ...rhiCapture !== void 0 ? { rhiCapture } : {},
2700
2672
  ...opts?.profiler !== void 0 ? { profiler: opts.profiler } : {},
@@ -2710,43 +2682,62 @@ async function createAppFromCanvas(canvas, opts, bundler) {
2710
2682
  }
2711
2683
  return built;
2712
2684
  }
2713
- function syncCanvasDrawingBuffer(canvas, maxCanvasPixelRatio) {
2714
- const size = resolveCanvasDrawingBufferSize(canvas, maxCanvasPixelRatio);
2715
- if (size === void 0) return { width: canvas.width, height: canvas.height };
2716
- if (canvas.width !== size.width) canvas.width = size.width;
2717
- if (canvas.height !== size.height) canvas.height = size.height;
2718
- return size;
2719
- }
2720
- function measureCanvasDrawingBuffer(canvas, maxCanvasPixelRatio) {
2721
- return resolveCanvasDrawingBufferSize(canvas, maxCanvasPixelRatio) ?? {
2722
- width: canvas.width,
2723
- height: canvas.height
2724
- };
2725
- }
2726
- function resolveCanvasDrawingBufferSize(canvas, maxCanvasPixelRatio) {
2685
+ function syncCanvasDrawingBuffer(canvas) {
2727
2686
  if (!Number.isFinite(canvas.clientWidth) || !Number.isFinite(canvas.clientHeight) || canvas.clientWidth <= 0 || canvas.clientHeight <= 0)
2728
- return void 0;
2687
+ return;
2729
2688
  const hasExplicitCssSize = (canvas.style?.width ?? "") !== "" || (canvas.style?.height ?? "") !== "";
2730
2689
  if (!hasExplicitCssSize && canvas.clientWidth === canvas.width && canvas.clientHeight === canvas.height) {
2731
- return { width: canvas.width, height: canvas.height };
2690
+ return;
2732
2691
  }
2733
- const devicePixelRatio = Math.max(1, globalThis.devicePixelRatio || 1);
2734
- const dpr = maxCanvasPixelRatio === void 0 || !Number.isFinite(maxCanvasPixelRatio) || maxCanvasPixelRatio <= 0 ? devicePixelRatio : Math.min(devicePixelRatio, maxCanvasPixelRatio);
2692
+ const dpr = Math.max(1, globalThis.devicePixelRatio || 1);
2735
2693
  const previous = syncedCanvasSizes.get(canvas);
2736
2694
  const drawingBufferIsTheLayoutMeasurement = previous !== void 0 && canvas.width === previous.drawingWidth && canvas.height === previous.drawingHeight && canvas.clientWidth === previous.drawingWidth && canvas.clientHeight === previous.drawingHeight;
2737
2695
  const cssWidth = drawingBufferIsTheLayoutMeasurement ? previous.cssWidth : canvas.clientWidth;
2738
2696
  const cssHeight = drawingBufferIsTheLayoutMeasurement ? previous.cssHeight : canvas.clientHeight;
2739
2697
  const width = Math.max(1, Math.round(cssWidth * dpr));
2740
2698
  const height = Math.max(1, Math.round(cssHeight * dpr));
2699
+ if (canvas.width !== width) canvas.width = width;
2700
+ if (canvas.height !== height) canvas.height = height;
2741
2701
  syncedCanvasSizes.set(canvas, {
2742
2702
  cssWidth,
2743
2703
  cssHeight,
2744
2704
  drawingWidth: width,
2745
2705
  drawingHeight: height
2746
2706
  });
2747
- return { width, height };
2707
+ }
2708
+ function measureCanvasDrawingBuffer(canvas, maxCanvasPixelRatio) {
2709
+ if (!Number.isFinite(canvas.clientWidth) || !Number.isFinite(canvas.clientHeight) || canvas.clientWidth <= 0 || canvas.clientHeight <= 0) {
2710
+ return { width: canvas.width, height: canvas.height };
2711
+ }
2712
+ const hasExplicitCssSize = (canvas.style?.width ?? "") !== "" || (canvas.style?.height ?? "") !== "";
2713
+ if (!hasExplicitCssSize && canvas.clientWidth === canvas.width && canvas.clientHeight === canvas.height) {
2714
+ return { width: canvas.width, height: canvas.height };
2715
+ }
2716
+ const devicePixelRatio = Math.max(1, globalThis.devicePixelRatio || 1);
2717
+ const dpr = maxCanvasPixelRatio === void 0 || !Number.isFinite(maxCanvasPixelRatio) || maxCanvasPixelRatio <= 0 ? devicePixelRatio : Math.min(devicePixelRatio, maxCanvasPixelRatio);
2718
+ const previous = syncedCanvasSizes.get(canvas);
2719
+ const drawingBufferIsTheLayoutMeasurement = previous !== void 0 && canvas.width === previous.drawingWidth && canvas.height === previous.drawingHeight && canvas.clientWidth === previous.drawingWidth && canvas.clientHeight === previous.drawingHeight;
2720
+ const cssWidth = drawingBufferIsTheLayoutMeasurement ? previous.cssWidth : canvas.clientWidth;
2721
+ const cssHeight = drawingBufferIsTheLayoutMeasurement ? previous.cssHeight : canvas.clientHeight;
2722
+ return {
2723
+ width: Math.max(1, Math.round(cssWidth * dpr)),
2724
+ height: Math.max(1, Math.round(cssHeight * dpr))
2725
+ };
2748
2726
  }
2749
2727
  var syncedCanvasSizes = /* @__PURE__ */ new WeakMap();
2728
+ function syncCameraAspect(world, canvasW, canvasH) {
2729
+ if (canvasW <= 0 || canvasH <= 0) return;
2730
+ const aspect = canvasW / canvasH;
2731
+ const query = world.query({ with: [Camera] }).unwrap();
2732
+ for (const row of query) {
2733
+ const entity = row.entity;
2734
+ const r = world.get(entity, Camera);
2735
+ if (!r.ok) continue;
2736
+ if (r.value.autoAspect !== true) continue;
2737
+ if (r.value.projection !== CAMERA_PROJECTION_PERSPECTIVE) continue;
2738
+ world.set(entity, Camera, { aspect });
2739
+ }
2740
+ }
2750
2741
  async function startRemoteServer(world, renderer, assets, rhiCapture, profiler, execution) {
2751
2742
  try {
2752
2743
  const remoteServerMod = await import(
@@ -2772,22 +2763,17 @@ async function startRemoteServer(world, renderer, assets, rhiCapture, profiler,
2772
2763
  return void 0;
2773
2764
  }
2774
2765
  async function createAppFromAssemble(args) {
2775
- const assetAssemblyResult = assembleAppAssetRuntime(args.assetCatalog, args.assetDecoders);
2776
- if (!assetAssemblyResult.ok) return err(assetAssemblyResult.error);
2777
- const assetAssembly = assetAssemblyResult.value;
2778
2766
  let pluginContext;
2779
2767
  try {
2780
2768
  pluginContext = await createWorldContext(
2781
2769
  args.world,
2782
2770
  assembledEngineProfile({
2783
2771
  renderer: args.renderer,
2784
- assetAssembly,
2772
+ ...args.assets === void 0 ? {} : { assets: args.assets },
2785
2773
  extensions: args.plugins ?? []
2786
2774
  })
2787
2775
  );
2788
2776
  } catch (cause) {
2789
- assetAssembly.dispose();
2790
- assetAssembly.registry.dispose();
2791
2777
  return err(
2792
2778
  makeAppError2(
2793
2779
  "app-plugin-activation-failed",
@@ -2829,7 +2815,7 @@ async function createAppFromAssemble(args) {
2829
2815
  const remoteHandle = shouldStartRemote ? await startRemoteServer(
2830
2816
  args.world,
2831
2817
  args.renderer,
2832
- assetAssembly.registry,
2818
+ args.assets,
2833
2819
  void 0,
2834
2820
  args.profiler,
2835
2821
  executionControl
@@ -2837,7 +2823,6 @@ async function createAppFromAssemble(args) {
2837
2823
  if (remoteHandle !== void 0) await pluginContext.plugin(remoteServerPlugin(remoteHandle));
2838
2824
  const buildArgs = {
2839
2825
  renderer: args.renderer,
2840
- assets: assetAssembly.registry,
2841
2826
  world: args.world,
2842
2827
  pluginContext,
2843
2828
  executionControl,
@@ -2870,14 +2855,12 @@ async function createAppFromAssemble(args) {
2870
2855
  async function buildApp(args) {
2871
2856
  const {
2872
2857
  renderer,
2873
- assets,
2874
2858
  world,
2875
2859
  pluginContext,
2876
2860
  inputBackend,
2877
2861
  audioBackend,
2878
2862
  silenceUnhandledErrors,
2879
2863
  rhiCapture,
2880
- rhiAttachment,
2881
2864
  debugDraw,
2882
2865
  remoteHandle,
2883
2866
  profiler
@@ -2888,6 +2871,7 @@ async function buildApp(args) {
2888
2871
  const fanout = new ErrorFanoutRegistry(
2889
2872
  silenceUnhandledErrors !== void 0 ? { silenceUnhandledErrors } : {}
2890
2873
  );
2874
+ let lastError;
2891
2875
  const execution = args.executionControl ?? createLocalExecutionControl(
2892
2876
  {
2893
2877
  ...createExecutionReport("main-serial", unavailableExecutionCapabilities("not required")),
@@ -2913,6 +2897,7 @@ async function buildApp(args) {
2913
2897
  }
2914
2898
  );
2915
2899
  function dispatch(e) {
2900
+ lastError = e;
2916
2901
  fanout.fire(e);
2917
2902
  }
2918
2903
  if (args.wireOnLockErrorDispatch) {
@@ -2930,7 +2915,7 @@ async function buildApp(args) {
2930
2915
  Object.assign(loopOpts, { profiler });
2931
2916
  }
2932
2917
  const loop = createFrameLoop(loopOpts);
2933
- if (rhiCapture !== void 0 && rhiAttachment !== void 0) {
2918
+ if (rhiCapture !== void 0) {
2934
2919
  bindRhiCaptureFrameDriver(rhiCapture, {
2935
2920
  getState: () => loop.getState(),
2936
2921
  pause: () => {
@@ -2946,17 +2931,15 @@ async function buildApp(args) {
2946
2931
  stepFrame: (deltaSeconds) => loop.stepFrame(deltaSeconds)
2947
2932
  });
2948
2933
  }
2949
- let lastError;
2950
2934
  let rendererUnsubscribe;
2951
2935
  let resumeAfterSurfaceRestore = false;
2952
2936
  function subscribeRendererErrors() {
2953
2937
  if (rendererUnsubscribe !== void 0) {
2954
2938
  return;
2955
2939
  }
2956
- rendererUnsubscribe = renderer.onError((e) => {
2957
- if (e?.code === "device-lost") {
2958
- lastError = e;
2959
- }
2940
+ rendererUnsubscribe = renderer.subscribe((event) => {
2941
+ if (event.kind !== "error") return;
2942
+ const e = event.error;
2960
2943
  dispatch(e);
2961
2944
  });
2962
2945
  }
@@ -2968,7 +2951,7 @@ async function buildApp(args) {
2968
2951
  }
2969
2952
  const stub = {
2970
2953
  renderer,
2971
- assets,
2954
+ ...args.assets === void 0 ? {} : { assets: args.assets },
2972
2955
  world,
2973
2956
  execution,
2974
2957
  async releaseSurfacePreserveWorld() {
@@ -3063,7 +3046,7 @@ async function buildApp(args) {
3063
3046
  loop.setDrawSource(drawSource);
3064
3047
  },
3065
3048
  /**
3066
- * Most recent device-loss error retained for host self-inspection.
3049
+ * Most recent dispatched error retained for host self-inspection.
3067
3050
  */
3068
3051
  get lastError() {
3069
3052
  return lastError;
@@ -3072,10 +3055,6 @@ async function buildApp(args) {
3072
3055
  ...debugDraw !== void 0 ? { debugDraw } : {},
3073
3056
  ...remoteHandle !== void 0 ? { remote: remoteHandle } : {}
3074
3057
  };
3075
- const readyResult = await renderer.ready;
3076
- if (!readyResult.ok) {
3077
- return err(readyResult.error);
3078
- }
3079
3058
  return ok(stub);
3080
3059
  }
3081
3060
  function ensureFallbackCamera(world, aspect = 1) {
@@ -3682,19 +3661,6 @@ async function createToolPreviewHost(options) {
3682
3661
  const app = appResult.value;
3683
3662
  const appErrors = [];
3684
3663
  const unsubscribeAppErrors = app.onError((error) => appErrors.push(error));
3685
- const rendererReady = await app.renderer.ready;
3686
- if (!rendererReady.ok) {
3687
- unsubscribeAppErrors();
3688
- await app.dispose();
3689
- await runtime.attachment.dispose();
3690
- removeOwnedCanvas(canvas, ownsCanvas);
3691
- return err({
3692
- code: "tool-preview-bootstrap-failed",
3693
- expected: "the real WebGPU Renderer to finish pipeline initialization before preview frames",
3694
- hint: rendererReady.error.hint,
3695
- detail: { phase: "renderer-ready", cause: rendererReady.error }
3696
- });
3697
- }
3698
3664
  let resourceFacts;
3699
3665
  try {
3700
3666
  const prepared = await options.prepare?.(app);
@@ -3719,7 +3685,7 @@ async function createToolPreviewHost(options) {
3719
3685
  detail: { phase: bootstrapFailure.phase, cause }
3720
3686
  });
3721
3687
  }
3722
- const attached = app.renderer.attachWorld(app.world);
3688
+ const attached = app.renderer.attach(app.world);
3723
3689
  if (!attached.ok) {
3724
3690
  unsubscribeAppErrors();
3725
3691
  await app.dispose();
@@ -3980,6 +3946,6 @@ function fitToolPreviewCameraToAabb(aabb, options) {
3980
3946
  };
3981
3947
  }
3982
3948
 
3983
- export { APP_ERROR_HINTS, APP_EXPECTED, APP_PHASE_CATALOG, AppError, EXECUTION_CAPABILITY_NAMES, EXECUTION_REPORT_SCHEMA_VERSION, EXECUTION_REQUESTED_TIERS, EXECUTION_TIERS, LOAD_GAME_ERROR_HINTS, LOAD_GAME_EXPECTED, LoadGameError, assembleAssetRuntime, createApp, createExecutionReport, createToolPreviewEvidence, createToolPreviewHost, createToolPreviewRecipe, ensureFallbackCamera, executionBootstrapHostPlugin, fitToolPreviewCameraToAabb, gameHostPlugin, inputPlugin, isAppError, isExecutionReport, isLoadGameError, joinResourcePreviewEvidence, joinToolPreviewEvidence, loadBootstrapEntry, loadGame, measureCanvasDrawingBuffer, missingExecutionCapabilities, prepareBootstrapEntry, probeExecutionCapabilities, replayToolPreviewCapture, selectExecutionTier, syncCanvasDrawingBuffer, unavailableExecutionCapabilities, validateExecutionBootstrapData, validateToolPreviewTrace };
3949
+ export { APP_ERROR_HINTS, APP_EXPECTED, APP_PHASE_CATALOG, AppError, EXECUTION_CAPABILITY_NAMES, EXECUTION_REPORT_SCHEMA_VERSION, EXECUTION_REQUESTED_TIERS, EXECUTION_TIERS, LOAD_GAME_ERROR_HINTS, LOAD_GAME_EXPECTED, LoadGameError, createApp, createExecutionReport, createToolPreviewEvidence, createToolPreviewHost, createToolPreviewRecipe, ensureFallbackCamera, executionBootstrapHostPlugin, fitToolPreviewCameraToAabb, gameHostPlugin, inputPlugin, isAppError, isExecutionReport, isLoadGameError, joinResourcePreviewEvidence, joinToolPreviewEvidence, loadBootstrapEntry, loadGame, measureCanvasDrawingBuffer, missingExecutionCapabilities, ownedRendererPlugin, prepareBootstrapEntry, probeExecutionCapabilities, renderFeatureHostPlugin, renderFeaturePlugin, rendererPlugin, replayToolPreviewCapture, selectExecutionTier, syncCanvasDrawingBuffer, unavailableExecutionCapabilities, validateExecutionBootstrapData, validateToolPreviewTrace };
3984
3950
  //# sourceMappingURL=index.mjs.map
3985
3951
  //# sourceMappingURL=index.mjs.map