@forgeax/engine-app 0.1.4 → 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 +331 -362
  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 +2 -3
  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
 
@@ -216,66 +216,11 @@ var init_browser_remote_bridge = __esm({
216
216
  init_ecs_import();
217
217
  }
218
218
  });
219
- function assembleAssetRuntime(registry, contributions) {
220
- const leases = [];
221
- const overrides = new Set(contributions.map((contribution) => contribution.kind.kind));
222
- const defaults = defaultAssetDecoderContributions ?? [];
223
- const assembledContributions = [
224
- ...defaults.filter((contribution) => !overrides.has(contribution.kind.kind)),
225
- ...contributions
226
- ];
227
- try {
228
- const installedKinds = /* @__PURE__ */ new Set();
229
- for (const contribution of assembledContributions) {
230
- if (installedKinds.has(contribution.kind.kind)) {
231
- throw new TypeError(`duplicate decoder kind "${contribution.kind.kind}"`);
232
- }
233
- installedKinds.add(contribution.kind.kind);
234
- leases.push(
235
- registry.installDecoder(
236
- contribution.kind,
237
- contribution.decoder
238
- )
239
- );
240
- }
241
- } catch (cause) {
242
- for (const lease of leases) lease.dispose();
243
- const failed = assembledContributions[leases.length];
244
- return err({
245
- code: "asset-assembly-failed",
246
- expected: "each owner decoder contribution to install exactly once in the realm registry",
247
- hint: "remove the conflicting owner contribution and assemble the current realm again",
248
- detail: { kind: failed?.kind.kind ?? "unknown", cause }
249
- });
250
- }
251
- let disposed = false;
252
- return ok({
253
- registry,
254
- leases: Object.freeze([...leases]),
255
- dispose() {
256
- if (disposed) return;
257
- disposed = true;
258
- for (const lease of leases) lease.dispose();
259
- }
260
- });
261
- }
262
219
 
263
220
  // src/animation-asset-lookup.ts
264
221
  function createAnimationPayloadLookup(source) {
265
222
  return (guid) => source?.lookup(guid);
266
223
  }
267
- function syncCameraAspect(world, canvasW, canvasH) {
268
- if (canvasW <= 0 || canvasH <= 0) return;
269
- const aspect = canvasW / canvasH;
270
- const query = world.query({ with: [Camera] }).unwrap();
271
- for (const row of query) {
272
- const camera = world.get(row.entity, Camera);
273
- if (!camera.ok) continue;
274
- if (camera.value.autoAspect !== true) continue;
275
- if (camera.value.projection !== CAMERA_PROJECTION_PERSPECTIVE) continue;
276
- world.set(row.entity, Camera, { aspect });
277
- }
278
- }
279
224
 
280
225
  // src/errors.ts
281
226
  function summarizeCause(cause) {
@@ -338,10 +283,6 @@ var appErrorPolicy = {
338
283
  expected: "canvas.isConnected === true at createApp(canvas) entry",
339
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"
340
285
  },
341
- "app-invalid-canvas-pixel-ratio": {
342
- expected: "maxCanvasPixelRatio is finite and greater than 0",
343
- hint: "omit maxCanvasPixelRatio to keep the device pixel ratio, or pass a finite value > 0 from the Host policy"
344
- },
345
286
  "app-frame-step-invalid": {
346
287
  expected: "stepFrame(deltaSeconds) runs only while the App is paused and deltaSeconds is finite and non-negative",
347
288
  hint: "pause the App before deterministic stepping and pass an explicit finite delta; resume after the bounded step sequence completes"
@@ -737,20 +678,17 @@ function createLocalExecutionControl(initial, providers = {}) {
737
678
  world: providers.world?.() ?? current.world,
738
679
  audio: executionAudioReport(providers.audio?.())
739
680
  }),
740
- rebuild: async () => {
741
- if (providers.rebuild !== void 0) return providers.rebuild();
742
- return err(
743
- new AppError({
744
- code: "app-execution-rebuild-failed",
745
- expected: APP_EXPECTED["app-execution-rebuild-failed"],
746
- hint: APP_ERROR_HINTS["app-execution-rebuild-failed"],
747
- detail: {
748
- worldIdentity: current.world.identity,
749
- cause: new Error("This local assembly has no bootstrap module.")
750
- }
751
- })
752
- );
753
- }
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
+ )
754
692
  };
755
693
  }
756
694
  var ENGINE_WORKER_CAPABILITIES = [
@@ -825,6 +763,25 @@ function selectExecutionTier(input) {
825
763
  sharedEvidencePassed: input.sharedEvidencePassed
826
764
  });
827
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
+ }
828
785
 
829
786
  // src/internal/error-fanout.ts
830
787
  var ErrorFanoutRegistry = class {
@@ -974,6 +931,7 @@ async function startEngineWorker(options) {
974
931
  bootstrapUrl: options.bootstrapUrl,
975
932
  ...options.bootstrapData === void 0 ? {} : { bootstrapData: options.bootstrapData },
976
933
  ...options.bootstrapPort === void 0 ? {} : { bootstrapPort: options.bootstrapPort },
934
+ ...options.assetCatalog === void 0 ? {} : { assetCatalog: options.assetCatalog },
977
935
  ...options.shaderManifestUrl !== void 0 ? { shaderManifestUrl: options.shaderManifestUrl } : {},
978
936
  ...options.time !== void 0 ? { time: options.time } : {},
979
937
  tier: options.tier
@@ -1044,7 +1002,7 @@ var FrameCreditLedger = class {
1044
1002
  nextFrameId = 1;
1045
1003
  inFlight = null;
1046
1004
  completed = 0;
1047
- issue(deltaSeconds, sampleInput, canvasSize) {
1005
+ issue(deltaSeconds, sampleInput, canvasSize = { width: 0, height: 0 }) {
1048
1006
  if (this.inFlight !== null) return void 0;
1049
1007
  const frameId = this.nextFrameId;
1050
1008
  this.nextFrameId += 1;
@@ -1106,15 +1064,18 @@ function runtimeError(message) {
1106
1064
  async function createWorkerExecutionApp(options) {
1107
1065
  const executionOptions = options.appOptions.execution;
1108
1066
  if (executionOptions === void 0) throw new Error("execution options are required");
1109
- const syncCanvas = options.syncCanvas;
1110
- 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;
1111
1070
  const startupTimeoutMs = executionOptions.startupTimeoutMs ?? 1e4;
1112
1071
  const frameTimeoutMs = executionOptions.frameTimeoutMs ?? 2e3;
1072
+ const syncCanvas = options.syncCanvas;
1113
1073
  const started = await startEngineWorker({
1114
1074
  canvas: options.canvas,
1115
1075
  bootstrapUrl,
1116
1076
  ...executionOptions.bootstrapData === void 0 ? {} : { bootstrapData: executionOptions.bootstrapData },
1117
1077
  ...executionOptions.bootstrapPort === void 0 ? {} : { bootstrapPort: executionOptions.bootstrapPort },
1078
+ ...executionOptions.assetCatalog === void 0 ? {} : { assetCatalog: executionOptions.assetCatalog },
1118
1079
  ...options.bundler?.shaderManifestUrl !== void 0 ? { shaderManifestUrl: options.bundler.shaderManifestUrl } : {},
1119
1080
  ...options.appOptions.time !== void 0 ? { time: options.appOptions.time } : {},
1120
1081
  timeoutMs: startupTimeoutMs,
@@ -1450,61 +1411,99 @@ async function createWorkerExecutionApp(options) {
1450
1411
  return ok(app);
1451
1412
  }
1452
1413
 
1453
- // src/internal/assets-world-plugin.ts
1454
- var RENDER_ASSET_PORT_RESOURCE_KEY = "RenderAssetPort";
1455
- var ASSET_REGISTRY_RESOURCE_KEY = "AssetRegistry";
1456
- function rendererAssetsPlugin() {
1414
+ // src/renderer-plugin.ts
1415
+ function createRenderFeatureHost(host) {
1457
1416
  return {
1458
- name: "renderer-assets",
1459
- inject: ["renderer"],
1460
- 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",
1461
1438
  apply(ctx) {
1462
- if (ctx.renderer === void 0) throw new Error("assets require the renderer service");
1463
- ctx.provide("assets", ctx.renderer.assets);
1439
+ ctx.provide("renderer", renderer);
1464
1440
  }
1465
1441
  };
1466
1442
  }
1467
- function assetRegistryPlugin(assembly) {
1443
+ function ownedRendererPlugin(renderer) {
1468
1444
  return {
1469
- name: "asset-registry",
1470
- provide: "assetRegistry",
1445
+ name: "renderer",
1446
+ provide: "renderer",
1471
1447
  apply(ctx) {
1472
- 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;
1473
1472
  ctx.effect(
1474
- () => () => {
1475
- assembly.dispose();
1476
- assembly.registry.dispose();
1473
+ () => async () => {
1474
+ const removed = await installed.value.release();
1475
+ if (!removed.ok) throw removed.error;
1477
1476
  },
1478
- "assets/registry"
1477
+ `render-feature/${feature.identity}`
1479
1478
  );
1480
1479
  }
1481
1480
  };
1482
1481
  }
1483
- function assetsWorldPlugin() {
1482
+
1483
+ // src/internal/assets-world-plugin.ts
1484
+ var ASSET_REGISTRY_RESOURCE_KEY = "AssetRegistry";
1485
+ function rendererAssetsPlugin(assets) {
1484
1486
  return {
1485
- name: "assets-world",
1486
- inject: ["world", "assets"],
1487
+ name: "renderer-assets",
1488
+ inject: ["renderer"],
1489
+ provide: "assets",
1487
1490
  apply(ctx) {
1488
- ctx.effect(() => {
1489
- ctx.world.insertResource(RENDER_ASSET_PORT_RESOURCE_KEY, ctx.assets);
1490
- return () => {
1491
- ctx.world.removeResource(RENDER_ASSET_PORT_RESOURCE_KEY);
1492
- };
1493
- }, "assets/world-render-projection");
1491
+ if (assets !== void 0) ctx.provide("assets", assets);
1494
1492
  }
1495
1493
  };
1496
1494
  }
1497
- function assetRegistryWorldPlugin() {
1495
+ function assetsWorldPlugin() {
1498
1496
  return {
1499
- name: "asset-registry-world",
1500
- inject: ["world", "assetRegistry"],
1497
+ name: "assets-world",
1498
+ inject: ["world", "assets"],
1501
1499
  apply(ctx) {
1500
+ if (ctx.assets === void 0) return;
1502
1501
  ctx.effect(() => {
1503
- ctx.world.insertResource(ASSET_REGISTRY_RESOURCE_KEY, ctx.assetRegistry);
1502
+ ctx.world.insertResource(ASSET_REGISTRY_RESOURCE_KEY, ctx.assets);
1504
1503
  return () => {
1505
1504
  ctx.world.removeResource(ASSET_REGISTRY_RESOURCE_KEY);
1506
1505
  };
1507
- }, "assets/world-registry");
1506
+ }, "assets/world-resource");
1508
1507
  }
1509
1508
  };
1510
1509
  }
@@ -1513,8 +1512,7 @@ function assetRegistryWorldPlugin() {
1513
1512
  function assembledEngineProfile(options) {
1514
1513
  return [
1515
1514
  rendererPlugin(options.renderer),
1516
- rendererAssetsPlugin(),
1517
- ...options.assetAssembly === void 0 ? [] : [assetRegistryPlugin(options.assetAssembly), assetRegistryWorldPlugin()],
1515
+ rendererAssetsPlugin(options.assets),
1518
1516
  assetsWorldPlugin(),
1519
1517
  ...options.extensions ?? []
1520
1518
  ];
@@ -1677,7 +1675,6 @@ function resolveCaf(opts) {
1677
1675
  }
1678
1676
  function createFrameLoop(opts) {
1679
1677
  const { world, renderer } = opts;
1680
- const primaryDrawWorlds = [world];
1681
1678
  const phaseCatalogRegistration = opts.profiler?.registerPhaseCatalog("app", APP_PHASE_CATALOG);
1682
1679
  let releasePhaseCatalog = phaseCatalogRegistration?.ok === true ? phaseCatalogRegistration.value : void 0;
1683
1680
  let drawSource = opts.drawSource;
@@ -1689,53 +1686,50 @@ function createFrameLoop(opts) {
1689
1686
  let pendingFrameId = 0;
1690
1687
  let profilerFrameId = 0;
1691
1688
  let profilerCaptureId;
1692
- let primaryAttached = false;
1693
- let activeInjectedWorlds;
1694
- let nextInjectedWorlds;
1689
+ const leases = /* @__PURE__ */ new Map();
1695
1690
  function attachPrimary(fireError) {
1696
- if (primaryAttached) return;
1691
+ if (leases.has(world)) return;
1697
1692
  try {
1698
- const result = renderer.attachWorld(world);
1699
- primaryAttached = result.ok;
1700
- 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);
1701
1696
  } catch (cause) {
1702
1697
  fireError(makeWorldUpdateError(cause));
1703
1698
  }
1704
1699
  }
1705
1700
  function syncInjectedAttachments(worlds, fireError) {
1706
1701
  if (worlds === void 0) {
1707
- if (activeInjectedWorlds !== void 0) {
1708
- for (const attached of activeInjectedWorlds) renderer.detachWorld(attached);
1709
- activeInjectedWorlds.clear();
1702
+ for (const [attached, lease] of leases) {
1703
+ if (attached !== world) {
1704
+ lease.dispose();
1705
+ leases.delete(attached);
1706
+ }
1710
1707
  }
1711
1708
  return void 0;
1712
1709
  }
1713
- const next = nextInjectedWorlds ?? /* @__PURE__ */ new Set();
1714
- nextInjectedWorlds = next;
1715
- next.clear();
1716
- const active = activeInjectedWorlds;
1710
+ const next = /* @__PURE__ */ new Set();
1717
1711
  for (const candidate of worlds) {
1718
1712
  if (candidate === world || next.has(candidate)) continue;
1719
- if (active?.has(candidate) === true) {
1713
+ if (leases.has(candidate)) {
1720
1714
  next.add(candidate);
1721
1715
  continue;
1722
1716
  }
1723
1717
  try {
1724
- const result = renderer.attachWorld(candidate);
1725
- if (result.ok) next.add(candidate);
1726
- 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);
1727
1723
  } catch (cause) {
1728
1724
  fireError?.(makeWorldUpdateError(cause));
1729
1725
  }
1730
1726
  }
1731
- if (active !== void 0) {
1732
- for (const attached of active) {
1733
- 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);
1734
1731
  }
1735
- active.clear();
1736
1732
  }
1737
- activeInjectedWorlds = next;
1738
- nextInjectedWorlds = active;
1739
1733
  return next;
1740
1734
  }
1741
1735
  function releaseInjectedAttachments() {
@@ -1743,9 +1737,11 @@ function createFrameLoop(opts) {
1743
1737
  }
1744
1738
  function releaseAttachments() {
1745
1739
  releaseInjectedAttachments();
1746
- if (!primaryAttached) return;
1747
- renderer.detachWorld(world);
1748
- primaryAttached = false;
1740
+ const lease = leases.get(world);
1741
+ if (lease !== void 0) {
1742
+ lease.dispose();
1743
+ leases.delete(world);
1744
+ }
1749
1745
  }
1750
1746
  function runProfiledPhase(session, phase, action) {
1751
1747
  const opened = beginPhase(session, phase);
@@ -1796,7 +1792,7 @@ function createFrameLoop(opts) {
1796
1792
  });
1797
1793
  const attachedInjectedWorlds = syncInjectedAttachments(injected?.worlds, reportError);
1798
1794
  const updatedWorlds = injected === void 0 ? void 0 : /* @__PURE__ */ new Set();
1799
- if (updatedWorlds !== void 0 && primaryUpdated && primaryAttached) {
1795
+ if (updatedWorlds !== void 0 && primaryUpdated && leases.has(world)) {
1800
1796
  updatedWorlds.add(world);
1801
1797
  }
1802
1798
  runProfiledPhase(session, "world-update-injected", () => {
@@ -1813,27 +1809,34 @@ function createFrameLoop(opts) {
1813
1809
  });
1814
1810
  runProfiledPhase(session, "renderer-draw", () => {
1815
1811
  try {
1816
- const profileOptions = profileFrame === void 0 ? {} : { profileFrame };
1817
1812
  let drawResult;
1818
1813
  if (injected === void 0) {
1819
- if (!primaryUpdated || !primaryAttached) return;
1820
- drawResult = renderer.draw(primaryDrawWorlds, {
1821
- cameraOwner: 0,
1822
- resourceOwner: 0,
1823
- ...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 }
1824
1821
  });
1825
1822
  } else {
1826
1823
  if (updatedWorlds === void 0) return;
1827
- 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);
1828
1828
  const cameraWorld = injected.worlds[injected.cameraOwner];
1829
- const resourceWorld = injected.worlds[injected.resourceOwner];
1830
- const cameraOwner = cameraWorld === void 0 ? -1 : readyWorlds.indexOf(cameraWorld);
1831
- const resourceOwner = resourceWorld === void 0 ? -1 : readyWorlds.indexOf(resourceWorld);
1832
- if (readyWorlds.length === 0 || cameraOwner < 0 || resourceOwner < 0) return;
1833
- drawResult = renderer.draw(readyWorlds, {
1834
- cameraOwner,
1835
- resourceOwner,
1836
- ...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 }
1837
1840
  });
1838
1841
  }
1839
1842
  if (drawResult !== void 0) {
@@ -1848,14 +1851,14 @@ function createFrameLoop(opts) {
1848
1851
  });
1849
1852
  });
1850
1853
  endFrame(session);
1854
+ try {
1855
+ opts.debugRhi?.onFrameEnd();
1856
+ } catch {
1857
+ }
1851
1858
  return frameError === void 0 ? ok(void 0) : err(frameError);
1852
1859
  }
1853
1860
  function tick() {
1854
1861
  if (state !== "running") return;
1855
- if (renderer.health?.().reason === "device-lost") {
1856
- pendingFrameId = raf(tick);
1857
- return;
1858
- }
1859
1862
  const timestamp = now();
1860
1863
  const deltaSeconds = (timestamp - lastTimestamp) / 1e3;
1861
1864
  lastTimestamp = timestamp;
@@ -2063,17 +2066,53 @@ function inputPlugin() {
2063
2066
  };
2064
2067
  }
2065
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
+
2066
2107
  // src/internal/main-engine-profile.ts
2067
- function debugDrawPlugin(onReady) {
2108
+ function debugDrawPlugin(context, onReady) {
2068
2109
  return {
2069
2110
  name: "debug-draw",
2070
- inject: ["renderer"],
2071
2111
  async apply(ctx) {
2072
- if (ctx.renderer === void 0) throw new Error("debug draw requires the renderer service");
2073
2112
  try {
2074
- const debugDraw = await createDebugDrawOnReady(ctx.renderer);
2113
+ const debugDraw = await createDebugDrawOnReady(context);
2075
2114
  onReady(debugDraw);
2076
- ctx.effect(() => () => releaseDebugDraw(debugDraw), "render/debug-draw");
2115
+ ctx.effect(() => () => releaseDebugDraw(context, debugDraw), "render/debug-draw");
2077
2116
  } catch {
2078
2117
  }
2079
2118
  }
@@ -2083,8 +2122,8 @@ function mainEngineProfile(options) {
2083
2122
  return [
2084
2123
  ownedRendererPlugin(options.renderer),
2085
2124
  renderComponentsPlugin(),
2086
- rendererAssetsPlugin(),
2087
- ...options.assetAssembly === void 0 ? [] : [assetRegistryPlugin(options.assetAssembly), assetRegistryWorldPlugin()],
2125
+ ...options.rendererFeatureHost === void 0 ? [] : [renderFeatureHostPlugin(options.rendererFeatureHost)],
2126
+ rendererAssetsPlugin(options.assets),
2088
2127
  assetsWorldPlugin(),
2089
2128
  ...options.input === void 0 ? [] : [
2090
2129
  options.inputDispose === void 0 ? inputBackendPlugin(options.input) : ownedInputBackendPlugin(options.input, options.inputDispose)
@@ -2093,7 +2132,7 @@ function mainEngineProfile(options) {
2093
2132
  animationPayloadsPlugin(options.animationPayloads),
2094
2133
  animationRuntimePlugin(),
2095
2134
  statePlugin(),
2096
- ...options.onDebugDrawReady === void 0 ? [] : [debugDrawPlugin(options.onDebugDrawReady)],
2135
+ ...options.onDebugDrawReady === void 0 || options.rendererDebugDrawHost === void 0 ? [] : [debugDrawPlugin(options.rendererDebugDrawHost, options.onDebugDrawReady)],
2097
2136
  ...options.input === void 0 ? [] : [inputPlugin()],
2098
2137
  ...options.inputMap === void 0 ? [] : [inputMapPlugin(options.inputMap)],
2099
2138
  ...options.extensions ?? []
@@ -2101,8 +2140,7 @@ function mainEngineProfile(options) {
2101
2140
  }
2102
2141
 
2103
2142
  // src/internal/remote-serve-flag.ts
2104
- function resolveRemoteServeFlag(isDev, processEnv, isBrowser = false) {
2105
- if (isBrowser) return false;
2143
+ function resolveRemoteServeFlag(isDev, processEnv) {
2106
2144
  if (isDev === true) return true;
2107
2145
  if (processEnv?.FORGEAX_ENGINE_REMOTE_SERVE === "1") return true;
2108
2146
  return false;
@@ -2289,38 +2327,10 @@ function resolveRhiDebugFlag(importMetaEnv, processEnv) {
2289
2327
  }
2290
2328
 
2291
2329
  // src/create-app.ts
2292
- var DEFAULT_ASSET_CATALOG_PATH = "pack-index.json";
2293
- function defaultAssetCatalogUrl() {
2294
- const base = typeof document !== "undefined" ? document.baseURI : globalThis.location?.href;
2295
- return base === void 0 ? DEFAULT_ASSET_CATALOG_PATH : new URL(DEFAULT_ASSET_CATALOG_PATH, base).href;
2296
- }
2297
- function assembleAppAssetRuntime(catalog, contributions) {
2298
- const registry = createAssetRegistry({
2299
- catalog: catalog ?? createCatalogSource({ url: defaultAssetCatalogUrl() })
2300
- });
2301
- const assembled = assembleAssetRuntime(registry, contributions ?? []);
2302
- if (!assembled.ok) registry.dispose();
2303
- return assembled;
2304
- }
2305
2330
  function makeAppError2(code, expected, hint, detail) {
2306
2331
  return new AppError({ code, expected, hint, detail });
2307
2332
  }
2308
- function normalizeExecutionBootstrapUrl(bootstrap) {
2309
- const moduleUrl = typeof bootstrap === "string" ? bootstrap : bootstrap.href;
2310
- try {
2311
- return ok(new URL(bootstrap, globalThis.location?.href).href);
2312
- } catch (cause) {
2313
- return err(
2314
- makeAppError2(
2315
- "app-execution-bootstrap-failed",
2316
- APP_EXPECTED["app-execution-bootstrap-failed"],
2317
- APP_ERROR_HINTS["app-execution-bootstrap-failed"],
2318
- { phase: "prepare", moduleUrl, cause }
2319
- )
2320
- );
2321
- }
2322
- }
2323
- function canvasAspectPlugin(canvas, maxCanvasPixelRatio) {
2333
+ function canvasAspectPlugin(canvas) {
2324
2334
  return {
2325
2335
  name: "canvas-aspect",
2326
2336
  inject: ["world"],
@@ -2330,7 +2340,7 @@ function canvasAspectPlugin(canvas, maxCanvasPixelRatio) {
2330
2340
  name: "app-sync-camera-aspect",
2331
2341
  queries: [],
2332
2342
  fn: () => {
2333
- syncCanvasDrawingBuffer(canvas, maxCanvasPixelRatio);
2343
+ syncCanvasDrawingBuffer(canvas);
2334
2344
  syncCameraAspect(ctx.world, canvas.width, canvas.height);
2335
2345
  }
2336
2346
  };
@@ -2355,17 +2365,6 @@ function createApp(arg, opts, bundler) {
2355
2365
  return createAppFromAssemble(arg);
2356
2366
  }
2357
2367
  async function createAppFromCanvas(canvas, opts, bundler) {
2358
- const maxCanvasPixelRatio = opts?.maxCanvasPixelRatio;
2359
- if (maxCanvasPixelRatio !== void 0 && (!Number.isFinite(maxCanvasPixelRatio) || maxCanvasPixelRatio <= 0)) {
2360
- return err(
2361
- makeAppError2(
2362
- "app-invalid-canvas-pixel-ratio",
2363
- APP_EXPECTED["app-invalid-canvas-pixel-ratio"],
2364
- APP_ERROR_HINTS["app-invalid-canvas-pixel-ratio"],
2365
- { value: maxCanvasPixelRatio }
2366
- )
2367
- );
2368
- }
2369
2368
  if (!canvas.isConnected) {
2370
2369
  return err(
2371
2370
  makeAppError2(
@@ -2376,22 +2375,18 @@ async function createAppFromCanvas(canvas, opts, bundler) {
2376
2375
  )
2377
2376
  );
2378
2377
  }
2379
- const executionOptions = opts?.execution;
2380
- const normalizedExecutionBootstrap = executionOptions === void 0 ? void 0 : normalizeExecutionBootstrapUrl(executionOptions.bootstrap);
2381
- if (normalizedExecutionBootstrap?.ok === false) {
2382
- return err(normalizedExecutionBootstrap.error);
2383
- }
2384
- syncCanvasDrawingBuffer(canvas, maxCanvasPixelRatio);
2385
- 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;
2386
2384
  const realmBoundOption = [
2387
2385
  ["features", opts.features],
2388
2386
  ["plugins", opts.plugins],
2389
2387
  ["rhi", opts.rhi],
2390
- ["rawDeviceForContextConfigure", opts.rawDeviceForContextConfigure],
2388
+ ["rhiInstrumentation", opts.rhiInstrumentation],
2391
2389
  ["drawSource", opts.drawSource],
2392
- ["membershipTiming", opts.membershipTiming],
2393
- ["assetCatalog", opts.assetCatalog],
2394
- ["assetDecoders", opts.assetDecoders],
2395
2390
  ["bundler.importTransport", bundler?.importTransport]
2396
2391
  ].find(([, value]) => value !== void 0)?.[0];
2397
2392
  if (realmBoundOption !== void 0) {
@@ -2402,7 +2397,7 @@ async function createAppFromCanvas(canvas, opts, bundler) {
2402
2397
  APP_ERROR_HINTS["app-execution-bootstrap-failed"],
2403
2398
  {
2404
2399
  phase: "prepare",
2405
- moduleUrl: normalizedExecutionBootstrap.value,
2400
+ moduleUrl: executionBootstrapUrl,
2406
2401
  cause: new TypeError(
2407
2402
  `${realmBoundOption} must be constructed by the execution bootstrap module`
2408
2403
  )
@@ -2413,11 +2408,10 @@ async function createAppFromCanvas(canvas, opts, bundler) {
2413
2408
  }
2414
2409
  let executionContext;
2415
2410
  let preparedExecutionBootstrap;
2416
- if (opts?.execution !== void 0 && normalizedExecutionBootstrap?.ok === true) {
2417
- const executionOptions2 = opts.execution;
2411
+ if (opts?.execution !== void 0) {
2418
2412
  const capabilities = await probeExecutionCapabilities(canvas);
2419
2413
  const selected = selectExecutionTier({
2420
- requestedTier: executionOptions2.tier ?? "auto",
2414
+ requestedTier: opts.execution.tier ?? "auto",
2421
2415
  capabilities,
2422
2416
  sharedEvidencePassed: true
2423
2417
  });
@@ -2427,15 +2421,18 @@ async function createAppFromCanvas(canvas, opts, bundler) {
2427
2421
  return createWorkerExecutionApp({
2428
2422
  canvas,
2429
2423
  appOptions: opts,
2430
- syncCanvas: () => measureCanvasDrawingBuffer(canvas, maxCanvasPixelRatio),
2424
+ syncCanvas: () => measureCanvasDrawingBuffer(canvas),
2431
2425
  ...bundler !== void 0 ? { bundler } : {},
2432
2426
  capabilities,
2433
2427
  selection: selected.value
2434
2428
  });
2435
2429
  }
2430
+ if (executionBootstrapUrl === void 0) {
2431
+ throw new Error("execution bootstrap URL was not normalized");
2432
+ }
2436
2433
  const prepared = await prepareBootstrapEntry(
2437
- normalizedExecutionBootstrap.value,
2438
- executionOptions2.bootstrapData
2434
+ executionBootstrapUrl,
2435
+ opts.execution.bootstrapData
2439
2436
  );
2440
2437
  if (!prepared.ok) return err(prepared.error);
2441
2438
  preparedExecutionBootstrap = prepared.value;
@@ -2444,24 +2441,19 @@ async function createAppFromCanvas(canvas, opts, bundler) {
2444
2441
  if (opts?.rhi !== void 0) {
2445
2442
  Object.assign(rendererOpts, { rhi: opts.rhi });
2446
2443
  }
2447
- if (opts?.rhiInstrumentation !== void 0) {
2448
- Object.assign(rendererOpts, { rhiInstrumentation: opts.rhiInstrumentation });
2449
- }
2450
- if (opts?.rawDeviceForContextConfigure !== void 0) {
2451
- Object.assign(rendererOpts, {
2452
- rawDeviceForContextConfigure: opts.rawDeviceForContextConfigure
2453
- });
2454
- }
2455
2444
  if (opts?.profiler !== void 0) {
2456
2445
  Object.assign(rendererOpts, { profiler: opts.profiler });
2457
2446
  }
2458
- if (opts?.membershipTiming !== void 0) {
2459
- Object.assign(rendererOpts, { membershipTiming: opts.membershipTiming });
2460
- }
2461
2447
  const rendererFeatures = preparedExecutionBootstrap?.features ?? opts?.features;
2462
2448
  if (rendererFeatures !== void 0) {
2463
2449
  Object.assign(rendererOpts, { features: rendererFeatures });
2464
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
+ }
2465
2457
  let rhiAttachment;
2466
2458
  let rhiCapture;
2467
2459
  let rhiDebugGlobal;
@@ -2479,61 +2471,54 @@ async function createAppFromCanvas(canvas, opts, bundler) {
2479
2471
  const rhiDebugFlag = resolveRhiDebugFlag(importMetaEnv, processEnv);
2480
2472
  const nav = typeof globalThis !== "undefined" ? globalThis.navigator : void 0;
2481
2473
  const hasWebGPU = nav !== void 0 && "gpu" in nav && nav.gpu !== void 0;
2482
- if (opts?.rhi === void 0 && (browserBuildRhiDebugFlag === void 0 || browserBuildRhiDebugFlag === "1") && rhiDebugFlag === "1") {
2483
- const backend = hasWebGPU ? await import('@forgeax/engine-rhi-webgpu') : await import('@forgeax/engine-rhi-wgpu');
2484
- if (!hasWebGPU && "ensureReady" in backend) await backend.ensureReady();
2485
- const pack = loadRhiPack(backend);
2486
- const debugModule = await import(
2487
- /* @vite-ignore */
2488
- '@forgeax/engine-rhi-debug'
2489
- );
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);
2490
2480
  if (pack.createShaderModule === void 0) {
2491
- throw new Error("RHI debug capture requires the backend shader-module entry");
2481
+ throw new Error("RHI-debug requires a backend createShaderModule capability");
2492
2482
  }
2493
- const createShaderModule = pack.createShaderModule;
2494
- const attached = debugModule.attachRecorder({
2483
+ const attached = attachRecorder({
2495
2484
  rhi: pack.rhi,
2496
- createShaderModule
2485
+ createShaderModule: pack.createShaderModule
2497
2486
  });
2498
2487
  if (!attached.ok) throw new Error(attached.error.hint);
2499
- const attachment = attached.value;
2500
- rhiAttachment = attachment;
2501
- const capture = createRhiCapture(attachment);
2502
- rhiCapture = capture;
2488
+ rhiAttachment = attached.value;
2489
+ rhiCapture = createRhiCapture(attached.value);
2490
+ const capture = rhiCapture;
2491
+ rhiDebugGlobal = { captureFrame: (options) => capture.captureFrame(options) };
2503
2492
  Object.assign(rendererOpts, {
2504
- rhi: attachment.backend.rhi,
2505
- rhiInstrumentation: createRhiInstrumentation(attachment)
2493
+ rhi: attached.value.backend.rhi,
2494
+ rhiInstrumentation: createRhiInstrumentation(attached.value)
2506
2495
  });
2507
- rhiDebugGlobal = {
2508
- captureFrame: (options) => capture.captureFrame(options)
2509
- };
2510
2496
  globalThis.__forgeax = rhiDebugGlobal;
2511
2497
  }
2512
- const assetAssemblyResult = assembleAppAssetRuntime(opts?.assetCatalog, opts?.assetDecoders);
2513
- if (!assetAssemblyResult.ok) {
2514
- cleanupRhiDebugHost();
2515
- return err(assetAssemblyResult.error);
2516
- }
2517
- const assetAssembly = assetAssemblyResult.value;
2518
2498
  let renderer;
2499
+ let rendererDebugDrawHost;
2500
+ let rendererFeatureHost;
2501
+ let assets;
2519
2502
  try {
2520
- renderer = await createRenderer(canvas, rendererOpts, {
2521
- ...bundler,
2522
- assetResolver: getAssetRegistryResolver(assetAssembly.registry)
2523
- });
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;
2524
2509
  } catch (e) {
2525
2510
  cleanupRhiDebugHost();
2526
- assetAssembly.dispose();
2527
- assetAssembly.registry.dispose();
2528
- if (e instanceof EngineEnvironmentError) {
2529
- return err(e);
2530
- }
2531
- 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
+ );
2532
2518
  }
2533
2519
  const shouldStartRemote = resolveRemoteServeFlag(
2534
2520
  typeof import.meta !== "undefined" ? import.meta.env?.DEV : void 0,
2535
- globalThis.process?.env,
2536
- typeof window !== "undefined"
2521
+ globalThis.process?.env
2537
2522
  );
2538
2523
  let debugDraw;
2539
2524
  const world = new World(opts?.time !== void 0 ? { time: opts.time } : {});
@@ -2557,8 +2542,10 @@ async function createAppFromCanvas(canvas, opts, bundler) {
2557
2542
  world,
2558
2543
  mainEngineProfile({
2559
2544
  renderer,
2560
- assetAssembly,
2561
- animationPayloads: createAnimationPayloadLookup(renderer.assets),
2545
+ rendererDebugDrawHost,
2546
+ rendererFeatureHost,
2547
+ assets,
2548
+ animationPayloads: createAnimationPayloadLookup(assets),
2562
2549
  ...inputBackend === void 0 ? {} : { input: inputBackend },
2563
2550
  ...inputHandle === void 0 ? {} : { inputDispose: inputHandle.cleanup },
2564
2551
  ...opts?.inputMap === void 0 ? {} : { inputMap: opts.inputMap },
@@ -2568,14 +2555,12 @@ async function createAppFromCanvas(canvas, opts, bundler) {
2568
2555
  extensions: [
2569
2556
  rhiDebugHostPlugin(cleanupRhiDebugHost),
2570
2557
  ...userPlugins,
2571
- canvasAspectPlugin(canvas, maxCanvasPixelRatio)
2558
+ canvasAspectPlugin(canvas)
2572
2559
  ]
2573
2560
  })
2574
2561
  );
2575
2562
  } catch (cause) {
2576
2563
  cleanupRhiDebugHost();
2577
- assetAssembly.dispose();
2578
- assetAssembly.registry.dispose();
2579
2564
  return err(
2580
2565
  makeAppError2(
2581
2566
  "app-plugin-activation-failed",
@@ -2623,18 +2608,11 @@ async function createAppFromCanvas(canvas, opts, bundler) {
2623
2608
  })
2624
2609
  }
2625
2610
  );
2626
- const remoteHandle = shouldStartRemote ? await startRemoteServer(
2627
- world,
2628
- renderer,
2629
- assetAssembly.registry,
2630
- rhiCapture,
2631
- opts?.profiler,
2632
- executionControl
2633
- ) : void 0;
2611
+ const remoteHandle = shouldStartRemote ? await startRemoteServer(world, renderer, assets, rhiCapture, opts?.profiler, executionControl) : void 0;
2634
2612
  if (remoteHandle !== void 0) await pluginContext.plugin(remoteServerPlugin(remoteHandle));
2635
2613
  const buildArgs = {
2636
2614
  renderer,
2637
- assets: assetAssembly.registry,
2615
+ assets,
2638
2616
  world,
2639
2617
  pluginContext,
2640
2618
  executionControl,
@@ -2660,10 +2638,7 @@ async function createAppFromCanvas(canvas, opts, bundler) {
2660
2638
  Object.assign(buildArgs, { profiler: opts.profiler });
2661
2639
  }
2662
2640
  if (rhiCapture !== void 0) {
2663
- Object.assign(buildArgs, {
2664
- rhiCapture,
2665
- ...rhiAttachment === void 0 ? {} : { rhiAttachment }
2666
- });
2641
+ Object.assign(buildArgs, { rhiCapture });
2667
2642
  }
2668
2643
  if (debugDraw !== void 0) {
2669
2644
  Object.assign(buildArgs, { debugDraw });
@@ -2681,17 +2656,17 @@ async function createAppFromCanvas(canvas, opts, bundler) {
2681
2656
  const bridgePort = import.meta.env?.VITE_FORGEAX_ENGINE_BRIDGE_PORT ?? "5733";
2682
2657
  await pluginContext.plugin({
2683
2658
  name: "browser-remote-bridge",
2684
- inject: ["world", "renderer", "assetRegistry"],
2659
+ inject: ["world", "renderer", "assets"],
2685
2660
  async apply(ctx) {
2686
- if (ctx.renderer === void 0 || ctx.assetRegistry === void 0) {
2687
- 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");
2688
2663
  }
2689
2664
  try {
2690
2665
  const bridge = await Promise.resolve().then(() => (init_browser_remote_bridge(), browser_remote_bridge_exports));
2691
2666
  const teardown = await bridge.installBrowserRemoteBridge({
2692
2667
  world: ctx.world,
2693
2668
  renderer: ctx.renderer,
2694
- assets: ctx.assetRegistry,
2669
+ assets: ctx.assets,
2695
2670
  runtimeModule: engineRuntimeModule,
2696
2671
  ...rhiCapture !== void 0 ? { rhiCapture } : {},
2697
2672
  ...opts?.profiler !== void 0 ? { profiler: opts.profiler } : {},
@@ -2707,43 +2682,62 @@ async function createAppFromCanvas(canvas, opts, bundler) {
2707
2682
  }
2708
2683
  return built;
2709
2684
  }
2710
- function syncCanvasDrawingBuffer(canvas, maxCanvasPixelRatio) {
2711
- const size = resolveCanvasDrawingBufferSize(canvas, maxCanvasPixelRatio);
2712
- if (size === void 0) return { width: canvas.width, height: canvas.height };
2713
- if (canvas.width !== size.width) canvas.width = size.width;
2714
- if (canvas.height !== size.height) canvas.height = size.height;
2715
- return size;
2716
- }
2717
- function measureCanvasDrawingBuffer(canvas, maxCanvasPixelRatio) {
2718
- return resolveCanvasDrawingBufferSize(canvas, maxCanvasPixelRatio) ?? {
2719
- width: canvas.width,
2720
- height: canvas.height
2721
- };
2722
- }
2723
- function resolveCanvasDrawingBufferSize(canvas, maxCanvasPixelRatio) {
2685
+ function syncCanvasDrawingBuffer(canvas) {
2724
2686
  if (!Number.isFinite(canvas.clientWidth) || !Number.isFinite(canvas.clientHeight) || canvas.clientWidth <= 0 || canvas.clientHeight <= 0)
2725
- return void 0;
2687
+ return;
2726
2688
  const hasExplicitCssSize = (canvas.style?.width ?? "") !== "" || (canvas.style?.height ?? "") !== "";
2727
2689
  if (!hasExplicitCssSize && canvas.clientWidth === canvas.width && canvas.clientHeight === canvas.height) {
2728
- return { width: canvas.width, height: canvas.height };
2690
+ return;
2729
2691
  }
2730
- const devicePixelRatio = Math.max(1, globalThis.devicePixelRatio || 1);
2731
- const dpr = maxCanvasPixelRatio === void 0 || !Number.isFinite(maxCanvasPixelRatio) || maxCanvasPixelRatio <= 0 ? devicePixelRatio : Math.min(devicePixelRatio, maxCanvasPixelRatio);
2692
+ const dpr = Math.max(1, globalThis.devicePixelRatio || 1);
2732
2693
  const previous = syncedCanvasSizes.get(canvas);
2733
2694
  const drawingBufferIsTheLayoutMeasurement = previous !== void 0 && canvas.width === previous.drawingWidth && canvas.height === previous.drawingHeight && canvas.clientWidth === previous.drawingWidth && canvas.clientHeight === previous.drawingHeight;
2734
2695
  const cssWidth = drawingBufferIsTheLayoutMeasurement ? previous.cssWidth : canvas.clientWidth;
2735
2696
  const cssHeight = drawingBufferIsTheLayoutMeasurement ? previous.cssHeight : canvas.clientHeight;
2736
2697
  const width = Math.max(1, Math.round(cssWidth * dpr));
2737
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;
2738
2701
  syncedCanvasSizes.set(canvas, {
2739
2702
  cssWidth,
2740
2703
  cssHeight,
2741
2704
  drawingWidth: width,
2742
2705
  drawingHeight: height
2743
2706
  });
2744
- 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
+ };
2745
2726
  }
2746
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
+ }
2747
2741
  async function startRemoteServer(world, renderer, assets, rhiCapture, profiler, execution) {
2748
2742
  try {
2749
2743
  const remoteServerMod = await import(
@@ -2769,22 +2763,17 @@ async function startRemoteServer(world, renderer, assets, rhiCapture, profiler,
2769
2763
  return void 0;
2770
2764
  }
2771
2765
  async function createAppFromAssemble(args) {
2772
- const assetAssemblyResult = assembleAppAssetRuntime(args.assetCatalog, args.assetDecoders);
2773
- if (!assetAssemblyResult.ok) return err(assetAssemblyResult.error);
2774
- const assetAssembly = assetAssemblyResult.value;
2775
2766
  let pluginContext;
2776
2767
  try {
2777
2768
  pluginContext = await createWorldContext(
2778
2769
  args.world,
2779
2770
  assembledEngineProfile({
2780
2771
  renderer: args.renderer,
2781
- assetAssembly,
2772
+ ...args.assets === void 0 ? {} : { assets: args.assets },
2782
2773
  extensions: args.plugins ?? []
2783
2774
  })
2784
2775
  );
2785
2776
  } catch (cause) {
2786
- assetAssembly.dispose();
2787
- assetAssembly.registry.dispose();
2788
2777
  return err(
2789
2778
  makeAppError2(
2790
2779
  "app-plugin-activation-failed",
@@ -2826,7 +2815,7 @@ async function createAppFromAssemble(args) {
2826
2815
  const remoteHandle = shouldStartRemote ? await startRemoteServer(
2827
2816
  args.world,
2828
2817
  args.renderer,
2829
- assetAssembly.registry,
2818
+ args.assets,
2830
2819
  void 0,
2831
2820
  args.profiler,
2832
2821
  executionControl
@@ -2834,7 +2823,6 @@ async function createAppFromAssemble(args) {
2834
2823
  if (remoteHandle !== void 0) await pluginContext.plugin(remoteServerPlugin(remoteHandle));
2835
2824
  const buildArgs = {
2836
2825
  renderer: args.renderer,
2837
- assets: assetAssembly.registry,
2838
2826
  world: args.world,
2839
2827
  pluginContext,
2840
2828
  executionControl,
@@ -2867,14 +2855,12 @@ async function createAppFromAssemble(args) {
2867
2855
  async function buildApp(args) {
2868
2856
  const {
2869
2857
  renderer,
2870
- assets,
2871
2858
  world,
2872
2859
  pluginContext,
2873
2860
  inputBackend,
2874
2861
  audioBackend,
2875
2862
  silenceUnhandledErrors,
2876
2863
  rhiCapture,
2877
- rhiAttachment,
2878
2864
  debugDraw,
2879
2865
  remoteHandle,
2880
2866
  profiler
@@ -2885,6 +2871,7 @@ async function buildApp(args) {
2885
2871
  const fanout = new ErrorFanoutRegistry(
2886
2872
  silenceUnhandledErrors !== void 0 ? { silenceUnhandledErrors } : {}
2887
2873
  );
2874
+ let lastError;
2888
2875
  const execution = args.executionControl ?? createLocalExecutionControl(
2889
2876
  {
2890
2877
  ...createExecutionReport("main-serial", unavailableExecutionCapabilities("not required")),
@@ -2910,6 +2897,7 @@ async function buildApp(args) {
2910
2897
  }
2911
2898
  );
2912
2899
  function dispatch(e) {
2900
+ lastError = e;
2913
2901
  fanout.fire(e);
2914
2902
  }
2915
2903
  if (args.wireOnLockErrorDispatch) {
@@ -2927,7 +2915,7 @@ async function buildApp(args) {
2927
2915
  Object.assign(loopOpts, { profiler });
2928
2916
  }
2929
2917
  const loop = createFrameLoop(loopOpts);
2930
- if (rhiCapture !== void 0 && rhiAttachment !== void 0) {
2918
+ if (rhiCapture !== void 0) {
2931
2919
  bindRhiCaptureFrameDriver(rhiCapture, {
2932
2920
  getState: () => loop.getState(),
2933
2921
  pause: () => {
@@ -2943,17 +2931,15 @@ async function buildApp(args) {
2943
2931
  stepFrame: (deltaSeconds) => loop.stepFrame(deltaSeconds)
2944
2932
  });
2945
2933
  }
2946
- let lastError;
2947
2934
  let rendererUnsubscribe;
2948
2935
  let resumeAfterSurfaceRestore = false;
2949
2936
  function subscribeRendererErrors() {
2950
2937
  if (rendererUnsubscribe !== void 0) {
2951
2938
  return;
2952
2939
  }
2953
- rendererUnsubscribe = renderer.onError((e) => {
2954
- if (e?.code === "device-lost") {
2955
- lastError = e;
2956
- }
2940
+ rendererUnsubscribe = renderer.subscribe((event) => {
2941
+ if (event.kind !== "error") return;
2942
+ const e = event.error;
2957
2943
  dispatch(e);
2958
2944
  });
2959
2945
  }
@@ -2965,7 +2951,7 @@ async function buildApp(args) {
2965
2951
  }
2966
2952
  const stub = {
2967
2953
  renderer,
2968
- assets,
2954
+ ...args.assets === void 0 ? {} : { assets: args.assets },
2969
2955
  world,
2970
2956
  execution,
2971
2957
  async releaseSurfacePreserveWorld() {
@@ -3060,7 +3046,7 @@ async function buildApp(args) {
3060
3046
  loop.setDrawSource(drawSource);
3061
3047
  },
3062
3048
  /**
3063
- * Most recent device-loss error retained for host self-inspection.
3049
+ * Most recent dispatched error retained for host self-inspection.
3064
3050
  */
3065
3051
  get lastError() {
3066
3052
  return lastError;
@@ -3069,10 +3055,6 @@ async function buildApp(args) {
3069
3055
  ...debugDraw !== void 0 ? { debugDraw } : {},
3070
3056
  ...remoteHandle !== void 0 ? { remote: remoteHandle } : {}
3071
3057
  };
3072
- const readyResult = await renderer.ready;
3073
- if (!readyResult.ok) {
3074
- return err(readyResult.error);
3075
- }
3076
3058
  return ok(stub);
3077
3059
  }
3078
3060
  function ensureFallbackCamera(world, aspect = 1) {
@@ -3679,19 +3661,6 @@ async function createToolPreviewHost(options) {
3679
3661
  const app = appResult.value;
3680
3662
  const appErrors = [];
3681
3663
  const unsubscribeAppErrors = app.onError((error) => appErrors.push(error));
3682
- const rendererReady = await app.renderer.ready;
3683
- if (!rendererReady.ok) {
3684
- unsubscribeAppErrors();
3685
- await app.dispose();
3686
- await runtime.attachment.dispose();
3687
- removeOwnedCanvas(canvas, ownsCanvas);
3688
- return err({
3689
- code: "tool-preview-bootstrap-failed",
3690
- expected: "the real WebGPU Renderer to finish pipeline initialization before preview frames",
3691
- hint: rendererReady.error.hint,
3692
- detail: { phase: "renderer-ready", cause: rendererReady.error }
3693
- });
3694
- }
3695
3664
  let resourceFacts;
3696
3665
  try {
3697
3666
  const prepared = await options.prepare?.(app);
@@ -3716,7 +3685,7 @@ async function createToolPreviewHost(options) {
3716
3685
  detail: { phase: bootstrapFailure.phase, cause }
3717
3686
  });
3718
3687
  }
3719
- const attached = app.renderer.attachWorld(app.world);
3688
+ const attached = app.renderer.attach(app.world);
3720
3689
  if (!attached.ok) {
3721
3690
  unsubscribeAppErrors();
3722
3691
  await app.dispose();
@@ -3977,6 +3946,6 @@ function fitToolPreviewCameraToAabb(aabb, options) {
3977
3946
  };
3978
3947
  }
3979
3948
 
3980
- 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 };
3981
3950
  //# sourceMappingURL=index.mjs.map
3982
3951
  //# sourceMappingURL=index.mjs.map