@forgeax/engine-app 0.1.4 → 0.1.7

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 (148) 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/browser-frame-signal.d.ts +17 -0
  10. package/dist/browser-frame-signal.d.ts.map +1 -0
  11. package/dist/create-app.d.ts +31 -10
  12. package/dist/create-app.d.ts.map +1 -1
  13. package/dist/engine-worker-runtime.mjs +72 -129
  14. package/dist/engine-worker-runtime.mjs.map +1 -1
  15. package/dist/errors.d.ts +3 -12
  16. package/dist/errors.d.ts.map +1 -1
  17. package/dist/execution/attached-world-swap.d.ts +1 -12
  18. package/dist/execution/attached-world-swap.d.ts.map +1 -1
  19. package/dist/execution/bootstrap-url.d.ts +5 -0
  20. package/dist/execution/bootstrap-url.d.ts.map +1 -0
  21. package/dist/execution/control.d.ts +0 -2
  22. package/dist/execution/control.d.ts.map +1 -1
  23. package/dist/execution/engine-worker.d.ts +1 -0
  24. package/dist/execution/engine-worker.d.ts.map +1 -1
  25. package/dist/execution/host-controller.d.ts.map +1 -1
  26. package/dist/execution/index.d.ts +1 -1
  27. package/dist/execution/index.d.ts.map +1 -1
  28. package/dist/execution/protocol.d.ts +4 -1
  29. package/dist/execution/protocol.d.ts.map +1 -1
  30. package/dist/execution/types.d.ts +17 -1
  31. package/dist/execution/types.d.ts.map +1 -1
  32. package/dist/game-context.d.ts +1 -1
  33. package/dist/game-context.d.ts.map +1 -1
  34. package/dist/index.d.ts +5 -2
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.mjs +357 -359
  37. package/dist/index.mjs.map +1 -1
  38. package/dist/internal/assembled-engine-profile.d.ts.map +1 -1
  39. package/dist/internal/assets-world-plugin.d.ts +3 -17
  40. package/dist/internal/assets-world-plugin.d.ts.map +1 -1
  41. package/dist/internal/browser-remote-bridge.d.ts +1 -2
  42. package/dist/internal/browser-remote-bridge.d.ts.map +1 -1
  43. package/dist/internal/browser-rhi-debug-runtime.d.ts +1 -1
  44. package/dist/internal/browser-rhi-debug-runtime.d.ts.map +1 -1
  45. package/dist/internal/debug-draw.d.ts +9 -0
  46. package/dist/internal/debug-draw.d.ts.map +1 -0
  47. package/dist/internal/engine-profile-common.d.ts +2 -2
  48. package/dist/internal/engine-profile-common.d.ts.map +1 -1
  49. package/dist/internal/frame-loop.d.ts +7 -4
  50. package/dist/internal/frame-loop.d.ts.map +1 -1
  51. package/dist/internal/main-engine-profile.d.ts +4 -0
  52. package/dist/internal/main-engine-profile.d.ts.map +1 -1
  53. package/dist/internal/remote-serve-flag.d.ts +3 -7
  54. package/dist/internal/remote-serve-flag.d.ts.map +1 -1
  55. package/dist/internal/rhi-capture.d.ts +1 -1
  56. package/dist/internal/rhi-capture.d.ts.map +1 -1
  57. package/dist/internal/worker-engine-profile.d.ts +2 -0
  58. package/dist/internal/worker-engine-profile.d.ts.map +1 -1
  59. package/dist/renderer-plugin.d.ts +25 -0
  60. package/dist/renderer-plugin.d.ts.map +1 -0
  61. package/dist/tool-preview/bootstrap.d.ts.map +1 -1
  62. package/dist/types.d.ts +21 -61
  63. package/dist/types.d.ts.map +1 -1
  64. package/package.json +25 -26
  65. package/src/__tests__/callback-inference.test-d.ts +7 -3
  66. package/src/__tests__/consumer-migration.unit.test.ts +13 -0
  67. package/src/__tests__/create-app-error-retention.integration.test.ts +125 -0
  68. package/src/__tests__/create-app-features.test.ts +21 -10
  69. package/src/__tests__/create-app-listener-sync.test.ts +23 -19
  70. package/src/__tests__/create-app-physics-plugin-failure.integration.test.ts +142 -0
  71. package/src/__tests__/create-app-stop.test.ts +4 -12
  72. package/src/__tests__/create-app.test-d.ts +12 -11
  73. package/src/__tests__/create-app.test.ts +1 -60
  74. package/src/__tests__/create-renderer-error.integration.test.ts +3 -2
  75. package/src/__tests__/create-renderer-lifecycle.integration.test.ts +10 -5
  76. package/src/__tests__/draw-source-injected-world-update-visibility.test.ts +7 -13
  77. package/src/__tests__/draw-source-single-world-regression.test.ts +23 -24
  78. package/src/__tests__/engine-worker-bootstrap-channel.unit.test.ts +8 -0
  79. package/src/__tests__/engine-worker-world-swap.unit.test.ts +15 -22
  80. package/src/__tests__/errors-pointer-lock-failed.test.ts +4 -7
  81. package/src/__tests__/execution-bootstrap-isolation.unit.test.ts +5 -6
  82. package/src/__tests__/execution-frame-order.unit.test.ts +2 -2
  83. package/src/__tests__/execution-frame-protocol.unit.test.ts +3 -4
  84. package/src/__tests__/execution-public-api.test-d.ts +4 -0
  85. package/src/__tests__/frame-loop-time-elapsed.test.ts +1 -1
  86. package/src/__tests__/frame-loop-time.unit.test.ts +1 -1
  87. package/src/__tests__/frame-loop-world-array.test.ts +82 -73
  88. package/src/__tests__/plugin-profiles.unit.test.ts +3 -3
  89. package/src/__tests__/profiler-default-off.test.ts +1 -1
  90. package/src/__tests__/profiler-frame-token.test.ts +17 -30
  91. package/src/__tests__/profiler-phase-integration.test.ts +15 -27
  92. package/src/__tests__/surface-handoff.test.ts +1 -4
  93. package/src/__tests__/visibility-remote.integration.test.ts +2 -5
  94. package/src/__tests__/worker-execution-stop.test.ts +64 -7
  95. package/src/animation-asset-lookup.ts +2 -2
  96. package/src/browser-frame-signal.ts +44 -0
  97. package/src/create-app.ts +208 -295
  98. package/src/errors.ts +27 -39
  99. package/src/execution/attached-world-swap.ts +4 -38
  100. package/src/execution/bootstrap-url.ts +25 -0
  101. package/src/execution/control.ts +3 -7
  102. package/src/execution/engine-worker-runtime.ts +23 -40
  103. package/src/execution/engine-worker.ts +2 -0
  104. package/src/execution/host-controller.ts +8 -2
  105. package/src/execution/index.ts +1 -0
  106. package/src/execution/protocol.ts +4 -1
  107. package/src/execution/types.ts +18 -1
  108. package/src/game-context.ts +1 -1
  109. package/src/index.ts +18 -4
  110. package/src/internal/assembled-engine-profile.ts +3 -11
  111. package/src/internal/assets-world-plugin.ts +7 -53
  112. package/src/internal/browser-remote-bridge.ts +2 -3
  113. package/src/internal/browser-rhi-debug-runtime.ts +1 -1
  114. package/src/internal/debug-draw.ts +38 -0
  115. package/src/internal/engine-profile-common.ts +2 -3
  116. package/src/internal/frame-loop.ts +77 -63
  117. package/src/internal/main-engine-profile.ts +21 -17
  118. package/src/internal/remote-serve-flag.ts +5 -14
  119. package/src/internal/rhi-capture.ts +1 -1
  120. package/src/internal/worker-engine-profile.ts +10 -10
  121. package/src/renderer-plugin.ts +96 -0
  122. package/src/tool-preview/bootstrap.ts +1 -14
  123. package/src/types.ts +22 -77
  124. package/src/vite-env.d.ts +0 -2
  125. package/dist/__tests__/app-rebuild.test.d.ts +0 -2
  126. package/dist/__tests__/app-rebuild.test.d.ts.map +0 -1
  127. package/dist/__tests__/asset-registry-types.test-d.d.ts +0 -2
  128. package/dist/__tests__/asset-registry-types.test-d.d.ts.map +0 -1
  129. package/dist/__tests__/assets-runtime-assembly.integration.test.d.ts +0 -2
  130. package/dist/__tests__/assets-runtime-assembly.integration.test.d.ts.map +0 -1
  131. package/dist/__tests__/draw-source-headless-full-chain.test.d.ts +0 -2
  132. package/dist/__tests__/draw-source-headless-full-chain.test.d.ts.map +0 -1
  133. package/dist/__tests__/ecs-import.unit.test.d.ts +0 -2
  134. package/dist/__tests__/ecs-import.unit.test.d.ts.map +0 -1
  135. package/dist/__tests__/execution-world-health-owner.test-d.d.ts +0 -2
  136. package/dist/__tests__/execution-world-health-owner.test-d.d.ts.map +0 -1
  137. package/dist/__tests__/remote-serve-flag.test.d.ts +0 -2
  138. package/dist/__tests__/remote-serve-flag.test.d.ts.map +0 -1
  139. package/dist/assets-runtime-assembly.d.ts +0 -19
  140. package/dist/assets-runtime-assembly.d.ts.map +0 -1
  141. package/src/__tests__/app-rebuild.test.ts +0 -31
  142. package/src/__tests__/asset-registry-types.test-d.ts +0 -16
  143. package/src/__tests__/assets-runtime-assembly.integration.test.ts +0 -68
  144. package/src/__tests__/draw-source-headless-full-chain.test.ts +0 -226
  145. package/src/__tests__/ecs-import.unit.test.ts +0 -40
  146. package/src/__tests__/execution-world-health-owner.test-d.ts +0 -9
  147. package/src/__tests__/remote-serve-flag.test.ts +0 -24
  148. 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,34 @@ 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
- });
219
+
220
+ // src/browser-frame-signal.ts
221
+ var FORGEAX_FRAME_SUBMITTED_DATASET = "forgeaxFrameSubmitted";
222
+ var FORGEAX_FRAME_SUBMITTED_EVENT = "forgeax:frame-submitted";
223
+ function resetBrowserFrameSubmitted(canvas) {
224
+ const documentElement = canvas.ownerDocument?.documentElement;
225
+ if (documentElement !== void 0) {
226
+ delete documentElement.dataset[FORGEAX_FRAME_SUBMITTED_DATASET];
227
+ }
228
+ }
229
+ function publishBrowserFrameSubmitted(canvas, event) {
230
+ const documentElement = canvas.ownerDocument?.documentElement;
231
+ if (documentElement !== void 0) {
232
+ documentElement.dataset[FORGEAX_FRAME_SUBMITTED_DATASET] = String(event.frameId);
233
+ }
234
+ if (typeof CustomEvent === "function" && typeof canvas.dispatchEvent === "function") {
235
+ canvas.dispatchEvent(
236
+ new CustomEvent(FORGEAX_FRAME_SUBMITTED_EVENT, {
237
+ detail: Object.freeze({ ...event })
238
+ })
239
+ );
250
240
  }
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
241
  }
262
242
 
263
243
  // src/animation-asset-lookup.ts
264
244
  function createAnimationPayloadLookup(source) {
265
245
  return (guid) => source?.lookup(guid);
266
246
  }
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
247
 
280
248
  // src/errors.ts
281
249
  function summarizeCause(cause) {
@@ -338,10 +306,6 @@ var appErrorPolicy = {
338
306
  expected: "canvas.isConnected === true at createApp(canvas) entry",
339
307
  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
308
  },
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
309
  "app-frame-step-invalid": {
346
310
  expected: "stepFrame(deltaSeconds) runs only while the App is paused and deltaSeconds is finite and non-negative",
347
311
  hint: "pause the App before deterministic stepping and pass an explicit finite delta; resume after the bounded step sequence completes"
@@ -737,20 +701,17 @@ function createLocalExecutionControl(initial, providers = {}) {
737
701
  world: providers.world?.() ?? current.world,
738
702
  audio: executionAudioReport(providers.audio?.())
739
703
  }),
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
- }
704
+ rebuild: async () => err(
705
+ new AppError({
706
+ code: "app-execution-rebuild-failed",
707
+ expected: APP_EXPECTED["app-execution-rebuild-failed"],
708
+ hint: APP_ERROR_HINTS["app-execution-rebuild-failed"],
709
+ detail: {
710
+ worldIdentity: current.world.identity,
711
+ cause: new Error("This local assembly has no bootstrap module.")
712
+ }
713
+ })
714
+ )
754
715
  };
755
716
  }
756
717
  var ENGINE_WORKER_CAPABILITIES = [
@@ -825,6 +786,25 @@ function selectExecutionTier(input) {
825
786
  sharedEvidencePassed: input.sharedEvidencePassed
826
787
  });
827
788
  }
789
+ function normalizeExecutionBootstrapUrl(bootstrap) {
790
+ const moduleUrl = typeof bootstrap === "string" ? bootstrap : bootstrap.href;
791
+ try {
792
+ return ok(new URL(bootstrap, globalThis.location?.href).href);
793
+ } catch (cause) {
794
+ return err(
795
+ new AppError({
796
+ code: "app-execution-bootstrap-failed",
797
+ expected: APP_EXPECTED["app-execution-bootstrap-failed"],
798
+ hint: APP_ERROR_HINTS["app-execution-bootstrap-failed"],
799
+ detail: {
800
+ phase: "prepare",
801
+ moduleUrl,
802
+ cause: cause instanceof TypeError ? cause : new TypeError(String(cause))
803
+ }
804
+ })
805
+ );
806
+ }
807
+ }
828
808
 
829
809
  // src/internal/error-fanout.ts
830
810
  var ErrorFanoutRegistry = class {
@@ -974,6 +954,7 @@ async function startEngineWorker(options) {
974
954
  bootstrapUrl: options.bootstrapUrl,
975
955
  ...options.bootstrapData === void 0 ? {} : { bootstrapData: options.bootstrapData },
976
956
  ...options.bootstrapPort === void 0 ? {} : { bootstrapPort: options.bootstrapPort },
957
+ ...options.assetCatalog === void 0 ? {} : { assetCatalog: options.assetCatalog },
977
958
  ...options.shaderManifestUrl !== void 0 ? { shaderManifestUrl: options.shaderManifestUrl } : {},
978
959
  ...options.time !== void 0 ? { time: options.time } : {},
979
960
  tier: options.tier
@@ -1044,7 +1025,7 @@ var FrameCreditLedger = class {
1044
1025
  nextFrameId = 1;
1045
1026
  inFlight = null;
1046
1027
  completed = 0;
1047
- issue(deltaSeconds, sampleInput, canvasSize) {
1028
+ issue(deltaSeconds, sampleInput, canvasSize = { width: 0, height: 0 }) {
1048
1029
  if (this.inFlight !== null) return void 0;
1049
1030
  const frameId = this.nextFrameId;
1050
1031
  this.nextFrameId += 1;
@@ -1106,15 +1087,18 @@ function runtimeError(message) {
1106
1087
  async function createWorkerExecutionApp(options) {
1107
1088
  const executionOptions = options.appOptions.execution;
1108
1089
  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;
1090
+ const normalizedBootstrap = normalizeExecutionBootstrapUrl(executionOptions.bootstrap);
1091
+ if (!normalizedBootstrap.ok) return normalizedBootstrap;
1092
+ const bootstrapUrl = normalizedBootstrap.value;
1111
1093
  const startupTimeoutMs = executionOptions.startupTimeoutMs ?? 1e4;
1112
1094
  const frameTimeoutMs = executionOptions.frameTimeoutMs ?? 2e3;
1095
+ const syncCanvas = options.syncCanvas;
1113
1096
  const started = await startEngineWorker({
1114
1097
  canvas: options.canvas,
1115
1098
  bootstrapUrl,
1116
1099
  ...executionOptions.bootstrapData === void 0 ? {} : { bootstrapData: executionOptions.bootstrapData },
1117
1100
  ...executionOptions.bootstrapPort === void 0 ? {} : { bootstrapPort: executionOptions.bootstrapPort },
1101
+ ...executionOptions.assetCatalog === void 0 ? {} : { assetCatalog: executionOptions.assetCatalog },
1118
1102
  ...options.bundler?.shaderManifestUrl !== void 0 ? { shaderManifestUrl: options.bundler.shaderManifestUrl } : {},
1119
1103
  ...options.appOptions.time !== void 0 ? { time: options.appOptions.time } : {},
1120
1104
  timeoutMs: startupTimeoutMs,
@@ -1450,61 +1434,99 @@ async function createWorkerExecutionApp(options) {
1450
1434
  return ok(app);
1451
1435
  }
1452
1436
 
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() {
1437
+ // src/renderer-plugin.ts
1438
+ function createRenderFeatureHost(host) {
1457
1439
  return {
1458
- name: "renderer-assets",
1459
- inject: ["renderer"],
1460
- provide: "assets",
1440
+ async installFeature(feature) {
1441
+ const installed = await host.installRenderFeature(feature);
1442
+ if (!installed.ok) return installed;
1443
+ let released = false;
1444
+ return {
1445
+ ok: true,
1446
+ value: {
1447
+ release: async () => {
1448
+ if (released) return { ok: true, value: void 0 };
1449
+ released = true;
1450
+ return host.uninstallRenderFeature(feature);
1451
+ }
1452
+ }
1453
+ };
1454
+ }
1455
+ };
1456
+ }
1457
+ function rendererPlugin(renderer) {
1458
+ return {
1459
+ name: "renderer",
1460
+ provide: "renderer",
1461
1461
  apply(ctx) {
1462
- if (ctx.renderer === void 0) throw new Error("assets require the renderer service");
1463
- ctx.provide("assets", ctx.renderer.assets);
1462
+ ctx.provide("renderer", renderer);
1464
1463
  }
1465
1464
  };
1466
1465
  }
1467
- function assetRegistryPlugin(assembly) {
1466
+ function ownedRendererPlugin(renderer) {
1468
1467
  return {
1469
- name: "asset-registry",
1470
- provide: "assetRegistry",
1468
+ name: "renderer",
1469
+ provide: "renderer",
1471
1470
  apply(ctx) {
1472
- ctx.provide("assetRegistry", assembly.registry);
1471
+ ctx.provide("renderer", renderer);
1472
+ ctx.effect(() => () => renderer.dispose(), "render/renderer");
1473
+ }
1474
+ };
1475
+ }
1476
+ function renderFeatureHostPlugin(host) {
1477
+ return {
1478
+ name: "render-feature-host",
1479
+ provide: "renderFeatureHost",
1480
+ apply(ctx) {
1481
+ ctx.provide("renderFeatureHost", host);
1482
+ }
1483
+ };
1484
+ }
1485
+ function renderFeaturePlugin(feature) {
1486
+ return {
1487
+ name: `render-feature:${feature.identity}`,
1488
+ inject: ["renderFeatureHost"],
1489
+ async apply(ctx) {
1490
+ if (ctx.renderFeatureHost === void 0) {
1491
+ throw new Error("render-feature host capability is unavailable in this App realm");
1492
+ }
1493
+ const installed = await ctx.renderFeatureHost.installFeature(feature);
1494
+ if (!installed.ok) throw installed.error;
1473
1495
  ctx.effect(
1474
- () => () => {
1475
- assembly.dispose();
1476
- assembly.registry.dispose();
1496
+ () => async () => {
1497
+ const removed = await installed.value.release();
1498
+ if (!removed.ok) throw removed.error;
1477
1499
  },
1478
- "assets/registry"
1500
+ `render-feature/${feature.identity}`
1479
1501
  );
1480
1502
  }
1481
1503
  };
1482
1504
  }
1483
- function assetsWorldPlugin() {
1505
+
1506
+ // src/internal/assets-world-plugin.ts
1507
+ var ASSET_REGISTRY_RESOURCE_KEY = "AssetRegistry";
1508
+ function rendererAssetsPlugin(assets) {
1484
1509
  return {
1485
- name: "assets-world",
1486
- inject: ["world", "assets"],
1510
+ name: "renderer-assets",
1511
+ inject: ["renderer"],
1512
+ provide: "assets",
1487
1513
  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");
1514
+ if (assets !== void 0) ctx.provide("assets", assets);
1494
1515
  }
1495
1516
  };
1496
1517
  }
1497
- function assetRegistryWorldPlugin() {
1518
+ function assetsWorldPlugin() {
1498
1519
  return {
1499
- name: "asset-registry-world",
1500
- inject: ["world", "assetRegistry"],
1520
+ name: "assets-world",
1521
+ inject: ["world", "assets"],
1501
1522
  apply(ctx) {
1523
+ if (ctx.assets === void 0) return;
1502
1524
  ctx.effect(() => {
1503
- ctx.world.insertResource(ASSET_REGISTRY_RESOURCE_KEY, ctx.assetRegistry);
1525
+ ctx.world.insertResource(ASSET_REGISTRY_RESOURCE_KEY, ctx.assets);
1504
1526
  return () => {
1505
1527
  ctx.world.removeResource(ASSET_REGISTRY_RESOURCE_KEY);
1506
1528
  };
1507
- }, "assets/world-registry");
1529
+ }, "assets/world-resource");
1508
1530
  }
1509
1531
  };
1510
1532
  }
@@ -1513,8 +1535,7 @@ function assetRegistryWorldPlugin() {
1513
1535
  function assembledEngineProfile(options) {
1514
1536
  return [
1515
1537
  rendererPlugin(options.renderer),
1516
- rendererAssetsPlugin(),
1517
- ...options.assetAssembly === void 0 ? [] : [assetRegistryPlugin(options.assetAssembly), assetRegistryWorldPlugin()],
1538
+ rendererAssetsPlugin(options.assets),
1518
1539
  assetsWorldPlugin(),
1519
1540
  ...options.extensions ?? []
1520
1541
  ];
@@ -1677,7 +1698,6 @@ function resolveCaf(opts) {
1677
1698
  }
1678
1699
  function createFrameLoop(opts) {
1679
1700
  const { world, renderer } = opts;
1680
- const primaryDrawWorlds = [world];
1681
1701
  const phaseCatalogRegistration = opts.profiler?.registerPhaseCatalog("app", APP_PHASE_CATALOG);
1682
1702
  let releasePhaseCatalog = phaseCatalogRegistration?.ok === true ? phaseCatalogRegistration.value : void 0;
1683
1703
  let drawSource = opts.drawSource;
@@ -1689,53 +1709,50 @@ function createFrameLoop(opts) {
1689
1709
  let pendingFrameId = 0;
1690
1710
  let profilerFrameId = 0;
1691
1711
  let profilerCaptureId;
1692
- let primaryAttached = false;
1693
- let activeInjectedWorlds;
1694
- let nextInjectedWorlds;
1712
+ const leases = /* @__PURE__ */ new Map();
1695
1713
  function attachPrimary(fireError) {
1696
- if (primaryAttached) return;
1714
+ if (leases.has(world)) return;
1697
1715
  try {
1698
- const result = renderer.attachWorld(world);
1699
- primaryAttached = result.ok;
1700
- if (!result.ok) fireError(result.error);
1716
+ const result = renderer.attach(world);
1717
+ if (result.ok) leases.set(world, result.value);
1718
+ else fireError(result.error);
1701
1719
  } catch (cause) {
1702
1720
  fireError(makeWorldUpdateError(cause));
1703
1721
  }
1704
1722
  }
1705
1723
  function syncInjectedAttachments(worlds, fireError) {
1706
1724
  if (worlds === void 0) {
1707
- if (activeInjectedWorlds !== void 0) {
1708
- for (const attached of activeInjectedWorlds) renderer.detachWorld(attached);
1709
- activeInjectedWorlds.clear();
1725
+ for (const [attached, lease] of leases) {
1726
+ if (attached !== world) {
1727
+ lease.dispose();
1728
+ leases.delete(attached);
1729
+ }
1710
1730
  }
1711
1731
  return void 0;
1712
1732
  }
1713
- const next = nextInjectedWorlds ?? /* @__PURE__ */ new Set();
1714
- nextInjectedWorlds = next;
1715
- next.clear();
1716
- const active = activeInjectedWorlds;
1733
+ const next = /* @__PURE__ */ new Set();
1717
1734
  for (const candidate of worlds) {
1718
1735
  if (candidate === world || next.has(candidate)) continue;
1719
- if (active?.has(candidate) === true) {
1736
+ if (leases.has(candidate)) {
1720
1737
  next.add(candidate);
1721
1738
  continue;
1722
1739
  }
1723
1740
  try {
1724
- const result = renderer.attachWorld(candidate);
1725
- if (result.ok) next.add(candidate);
1726
- else fireError?.(result.error);
1741
+ const result = renderer.attach(candidate);
1742
+ if (result.ok) {
1743
+ leases.set(candidate, result.value);
1744
+ next.add(candidate);
1745
+ } else fireError?.(result.error);
1727
1746
  } catch (cause) {
1728
1747
  fireError?.(makeWorldUpdateError(cause));
1729
1748
  }
1730
1749
  }
1731
- if (active !== void 0) {
1732
- for (const attached of active) {
1733
- if (!next.has(attached)) renderer.detachWorld(attached);
1750
+ for (const [attached, lease] of leases) {
1751
+ if (attached !== world && !next.has(attached)) {
1752
+ lease.dispose();
1753
+ leases.delete(attached);
1734
1754
  }
1735
- active.clear();
1736
1755
  }
1737
- activeInjectedWorlds = next;
1738
- nextInjectedWorlds = active;
1739
1756
  return next;
1740
1757
  }
1741
1758
  function releaseInjectedAttachments() {
@@ -1743,9 +1760,11 @@ function createFrameLoop(opts) {
1743
1760
  }
1744
1761
  function releaseAttachments() {
1745
1762
  releaseInjectedAttachments();
1746
- if (!primaryAttached) return;
1747
- renderer.detachWorld(world);
1748
- primaryAttached = false;
1763
+ const lease = leases.get(world);
1764
+ if (lease !== void 0) {
1765
+ lease.dispose();
1766
+ leases.delete(world);
1767
+ }
1749
1768
  }
1750
1769
  function runProfiledPhase(session, phase, action) {
1751
1770
  const opened = beginPhase(session, phase);
@@ -1796,7 +1815,7 @@ function createFrameLoop(opts) {
1796
1815
  });
1797
1816
  const attachedInjectedWorlds = syncInjectedAttachments(injected?.worlds, reportError);
1798
1817
  const updatedWorlds = injected === void 0 ? void 0 : /* @__PURE__ */ new Set();
1799
- if (updatedWorlds !== void 0 && primaryUpdated && primaryAttached) {
1818
+ if (updatedWorlds !== void 0 && primaryUpdated && leases.has(world)) {
1800
1819
  updatedWorlds.add(world);
1801
1820
  }
1802
1821
  runProfiledPhase(session, "world-update-injected", () => {
@@ -1813,27 +1832,34 @@ function createFrameLoop(opts) {
1813
1832
  });
1814
1833
  runProfiledPhase(session, "renderer-draw", () => {
1815
1834
  try {
1816
- const profileOptions = profileFrame === void 0 ? {} : { profileFrame };
1817
1835
  let drawResult;
1818
1836
  if (injected === void 0) {
1819
- if (!primaryUpdated || !primaryAttached) return;
1820
- drawResult = renderer.draw(primaryDrawWorlds, {
1821
- cameraOwner: 0,
1822
- resourceOwner: 0,
1823
- ...profileOptions
1837
+ const primaryLease = leases.get(world);
1838
+ if (!primaryUpdated || primaryLease === void 0) return;
1839
+ drawResult = renderer.draw({
1840
+ leases: [primaryLease],
1841
+ camera: { lease: primaryLease },
1842
+ environment: { lease: primaryLease },
1843
+ ...profileFrame === void 0 ? {} : { profileFrame }
1824
1844
  });
1825
1845
  } else {
1826
1846
  if (updatedWorlds === void 0) return;
1827
- const readyWorlds = injected.worlds.filter((candidate) => updatedWorlds.has(candidate));
1847
+ const readyEntries = injected.worlds.filter((candidate) => updatedWorlds.has(candidate)).map((candidate) => ({ candidate, lease: leases.get(candidate) })).filter(
1848
+ (entry) => entry.lease !== void 0
1849
+ );
1850
+ const readyLeases = readyEntries.map((entry) => entry.lease);
1828
1851
  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
1852
+ const environmentWorld = injected.worlds[injected.resourceOwner];
1853
+ if (cameraWorld === void 0 || environmentWorld === void 0) return;
1854
+ const cameraLease = leases.get(cameraWorld);
1855
+ const environmentLease = leases.get(environmentWorld);
1856
+ if (readyLeases.length === 0 || cameraLease === void 0 || environmentLease === void 0)
1857
+ return;
1858
+ drawResult = renderer.draw({
1859
+ leases: readyLeases,
1860
+ camera: { lease: cameraLease },
1861
+ environment: { lease: environmentLease },
1862
+ ...profileFrame === void 0 ? {} : { profileFrame }
1837
1863
  });
1838
1864
  }
1839
1865
  if (drawResult !== void 0) {
@@ -1848,14 +1874,14 @@ function createFrameLoop(opts) {
1848
1874
  });
1849
1875
  });
1850
1876
  endFrame(session);
1877
+ try {
1878
+ opts.debugRhi?.onFrameEnd();
1879
+ } catch {
1880
+ }
1851
1881
  return frameError === void 0 ? ok(void 0) : err(frameError);
1852
1882
  }
1853
1883
  function tick() {
1854
1884
  if (state !== "running") return;
1855
- if (renderer.health?.().reason === "device-lost") {
1856
- pendingFrameId = raf(tick);
1857
- return;
1858
- }
1859
1885
  const timestamp = now();
1860
1886
  const deltaSeconds = (timestamp - lastTimestamp) / 1e3;
1861
1887
  lastTimestamp = timestamp;
@@ -2063,17 +2089,53 @@ function inputPlugin() {
2063
2089
  };
2064
2090
  }
2065
2091
 
2092
+ // ../render/src/record/render-context.ts
2093
+ function selectSwapChainFormat(storageBufferCapable) {
2094
+ if (!storageBufferCapable) return { storage: "rgba8unorm", view: "rgba8unorm-srgb" };
2095
+ const nav = globalThis.navigator;
2096
+ const gpu = nav?.gpu;
2097
+ const getPreferred = gpu?.getPreferredCanvasFormat;
2098
+ if (gpu !== void 0 && typeof getPreferred === "function") {
2099
+ const storage = getPreferred.call(gpu);
2100
+ const view = storage === "rgba8unorm" ? "rgba8unorm-srgb" : storage === "bgra8unorm" ? "bgra8unorm-srgb" : storage;
2101
+ return { storage, view };
2102
+ }
2103
+ return {
2104
+ storage: "rgba8unorm",
2105
+ view: "rgba8unorm-srgb",
2106
+ fallbackReason: "preferred-canvas-format-missing"
2107
+ };
2108
+ }
2109
+
2110
+ // src/internal/debug-draw.ts
2111
+ async function createDebugDrawOnReady(context, format) {
2112
+ const ready = await context.initialization;
2113
+ if (!ready.ok) throw ready.error;
2114
+ const resolvedFormat = selectSwapChainFormat(context.device.caps.storageBuffer).view;
2115
+ const result = await createDebugDraw({
2116
+ device: context.device,
2117
+ queue: context.device.queue,
2118
+ createShaderModule: context._internal_createShaderModule,
2119
+ format: resolvedFormat
2120
+ });
2121
+ if (!result.ok) throw result.error;
2122
+ context._internal_setRenderOverlay(result.value);
2123
+ return result.value;
2124
+ }
2125
+ function releaseDebugDraw(context, debugDraw) {
2126
+ context._internal_setRenderOverlay(void 0);
2127
+ debugDraw.destroy();
2128
+ }
2129
+
2066
2130
  // src/internal/main-engine-profile.ts
2067
- function debugDrawPlugin(onReady) {
2131
+ function debugDrawPlugin(context, onReady) {
2068
2132
  return {
2069
2133
  name: "debug-draw",
2070
- inject: ["renderer"],
2071
2134
  async apply(ctx) {
2072
- if (ctx.renderer === void 0) throw new Error("debug draw requires the renderer service");
2073
2135
  try {
2074
- const debugDraw = await createDebugDrawOnReady(ctx.renderer);
2136
+ const debugDraw = await createDebugDrawOnReady(context);
2075
2137
  onReady(debugDraw);
2076
- ctx.effect(() => () => releaseDebugDraw(debugDraw), "render/debug-draw");
2138
+ ctx.effect(() => () => releaseDebugDraw(context, debugDraw), "render/debug-draw");
2077
2139
  } catch {
2078
2140
  }
2079
2141
  }
@@ -2083,8 +2145,8 @@ function mainEngineProfile(options) {
2083
2145
  return [
2084
2146
  ownedRendererPlugin(options.renderer),
2085
2147
  renderComponentsPlugin(),
2086
- rendererAssetsPlugin(),
2087
- ...options.assetAssembly === void 0 ? [] : [assetRegistryPlugin(options.assetAssembly), assetRegistryWorldPlugin()],
2148
+ ...options.rendererFeatureHost === void 0 ? [] : [renderFeatureHostPlugin(options.rendererFeatureHost)],
2149
+ rendererAssetsPlugin(options.assets),
2088
2150
  assetsWorldPlugin(),
2089
2151
  ...options.input === void 0 ? [] : [
2090
2152
  options.inputDispose === void 0 ? inputBackendPlugin(options.input) : ownedInputBackendPlugin(options.input, options.inputDispose)
@@ -2093,7 +2155,7 @@ function mainEngineProfile(options) {
2093
2155
  animationPayloadsPlugin(options.animationPayloads),
2094
2156
  animationRuntimePlugin(),
2095
2157
  statePlugin(),
2096
- ...options.onDebugDrawReady === void 0 ? [] : [debugDrawPlugin(options.onDebugDrawReady)],
2158
+ ...options.onDebugDrawReady === void 0 || options.rendererDebugDrawHost === void 0 ? [] : [debugDrawPlugin(options.rendererDebugDrawHost, options.onDebugDrawReady)],
2097
2159
  ...options.input === void 0 ? [] : [inputPlugin()],
2098
2160
  ...options.inputMap === void 0 ? [] : [inputMapPlugin(options.inputMap)],
2099
2161
  ...options.extensions ?? []
@@ -2101,8 +2163,7 @@ function mainEngineProfile(options) {
2101
2163
  }
2102
2164
 
2103
2165
  // src/internal/remote-serve-flag.ts
2104
- function resolveRemoteServeFlag(isDev, processEnv, isBrowser = false) {
2105
- if (isBrowser) return false;
2166
+ function resolveRemoteServeFlag(isDev, processEnv) {
2106
2167
  if (isDev === true) return true;
2107
2168
  if (processEnv?.FORGEAX_ENGINE_REMOTE_SERVE === "1") return true;
2108
2169
  return false;
@@ -2289,38 +2350,10 @@ function resolveRhiDebugFlag(importMetaEnv, processEnv) {
2289
2350
  }
2290
2351
 
2291
2352
  // 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
2353
  function makeAppError2(code, expected, hint, detail) {
2306
2354
  return new AppError({ code, expected, hint, detail });
2307
2355
  }
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) {
2356
+ function canvasAspectPlugin(canvas) {
2324
2357
  return {
2325
2358
  name: "canvas-aspect",
2326
2359
  inject: ["world"],
@@ -2330,7 +2363,7 @@ function canvasAspectPlugin(canvas, maxCanvasPixelRatio) {
2330
2363
  name: "app-sync-camera-aspect",
2331
2364
  queries: [],
2332
2365
  fn: () => {
2333
- syncCanvasDrawingBuffer(canvas, maxCanvasPixelRatio);
2366
+ syncCanvasDrawingBuffer(canvas);
2334
2367
  syncCameraAspect(ctx.world, canvas.width, canvas.height);
2335
2368
  }
2336
2369
  };
@@ -2355,17 +2388,6 @@ function createApp(arg, opts, bundler) {
2355
2388
  return createAppFromAssemble(arg);
2356
2389
  }
2357
2390
  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
2391
  if (!canvas.isConnected) {
2370
2392
  return err(
2371
2393
  makeAppError2(
@@ -2376,22 +2398,18 @@ async function createAppFromCanvas(canvas, opts, bundler) {
2376
2398
  )
2377
2399
  );
2378
2400
  }
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) {
2401
+ syncCanvasDrawingBuffer(canvas);
2402
+ let executionBootstrapUrl;
2403
+ if (opts?.execution !== void 0) {
2404
+ const normalizedBootstrap = normalizeExecutionBootstrapUrl(opts.execution.bootstrap);
2405
+ if (!normalizedBootstrap.ok) return err(normalizedBootstrap.error);
2406
+ executionBootstrapUrl = normalizedBootstrap.value;
2386
2407
  const realmBoundOption = [
2387
2408
  ["features", opts.features],
2388
2409
  ["plugins", opts.plugins],
2389
2410
  ["rhi", opts.rhi],
2390
- ["rawDeviceForContextConfigure", opts.rawDeviceForContextConfigure],
2411
+ ["rhiInstrumentation", opts.rhiInstrumentation],
2391
2412
  ["drawSource", opts.drawSource],
2392
- ["membershipTiming", opts.membershipTiming],
2393
- ["assetCatalog", opts.assetCatalog],
2394
- ["assetDecoders", opts.assetDecoders],
2395
2413
  ["bundler.importTransport", bundler?.importTransport]
2396
2414
  ].find(([, value]) => value !== void 0)?.[0];
2397
2415
  if (realmBoundOption !== void 0) {
@@ -2402,7 +2420,7 @@ async function createAppFromCanvas(canvas, opts, bundler) {
2402
2420
  APP_ERROR_HINTS["app-execution-bootstrap-failed"],
2403
2421
  {
2404
2422
  phase: "prepare",
2405
- moduleUrl: normalizedExecutionBootstrap.value,
2423
+ moduleUrl: executionBootstrapUrl,
2406
2424
  cause: new TypeError(
2407
2425
  `${realmBoundOption} must be constructed by the execution bootstrap module`
2408
2426
  )
@@ -2413,11 +2431,10 @@ async function createAppFromCanvas(canvas, opts, bundler) {
2413
2431
  }
2414
2432
  let executionContext;
2415
2433
  let preparedExecutionBootstrap;
2416
- if (opts?.execution !== void 0 && normalizedExecutionBootstrap?.ok === true) {
2417
- const executionOptions2 = opts.execution;
2434
+ if (opts?.execution !== void 0) {
2418
2435
  const capabilities = await probeExecutionCapabilities(canvas);
2419
2436
  const selected = selectExecutionTier({
2420
- requestedTier: executionOptions2.tier ?? "auto",
2437
+ requestedTier: opts.execution.tier ?? "auto",
2421
2438
  capabilities,
2422
2439
  sharedEvidencePassed: true
2423
2440
  });
@@ -2427,15 +2444,18 @@ async function createAppFromCanvas(canvas, opts, bundler) {
2427
2444
  return createWorkerExecutionApp({
2428
2445
  canvas,
2429
2446
  appOptions: opts,
2430
- syncCanvas: () => measureCanvasDrawingBuffer(canvas, maxCanvasPixelRatio),
2447
+ syncCanvas: () => measureCanvasDrawingBuffer(canvas),
2431
2448
  ...bundler !== void 0 ? { bundler } : {},
2432
2449
  capabilities,
2433
2450
  selection: selected.value
2434
2451
  });
2435
2452
  }
2453
+ if (executionBootstrapUrl === void 0) {
2454
+ throw new Error("execution bootstrap URL was not normalized");
2455
+ }
2436
2456
  const prepared = await prepareBootstrapEntry(
2437
- normalizedExecutionBootstrap.value,
2438
- executionOptions2.bootstrapData
2457
+ executionBootstrapUrl,
2458
+ opts.execution.bootstrapData
2439
2459
  );
2440
2460
  if (!prepared.ok) return err(prepared.error);
2441
2461
  preparedExecutionBootstrap = prepared.value;
@@ -2444,24 +2464,19 @@ async function createAppFromCanvas(canvas, opts, bundler) {
2444
2464
  if (opts?.rhi !== void 0) {
2445
2465
  Object.assign(rendererOpts, { rhi: opts.rhi });
2446
2466
  }
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
2467
  if (opts?.profiler !== void 0) {
2456
2468
  Object.assign(rendererOpts, { profiler: opts.profiler });
2457
2469
  }
2458
- if (opts?.membershipTiming !== void 0) {
2459
- Object.assign(rendererOpts, { membershipTiming: opts.membershipTiming });
2460
- }
2461
2470
  const rendererFeatures = preparedExecutionBootstrap?.features ?? opts?.features;
2462
2471
  if (rendererFeatures !== void 0) {
2463
2472
  Object.assign(rendererOpts, { features: rendererFeatures });
2464
2473
  }
2474
+ if (opts?.standardProfile !== void 0) {
2475
+ Object.assign(rendererOpts, { standardProfile: opts.standardProfile });
2476
+ }
2477
+ if (opts?.rhiInstrumentation !== void 0) {
2478
+ Object.assign(rendererOpts, { rhiInstrumentation: opts.rhiInstrumentation });
2479
+ }
2465
2480
  let rhiAttachment;
2466
2481
  let rhiCapture;
2467
2482
  let rhiDebugGlobal;
@@ -2479,61 +2494,54 @@ async function createAppFromCanvas(canvas, opts, bundler) {
2479
2494
  const rhiDebugFlag = resolveRhiDebugFlag(importMetaEnv, processEnv);
2480
2495
  const nav = typeof globalThis !== "undefined" ? globalThis.navigator : void 0;
2481
2496
  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
- );
2497
+ if ((browserBuildRhiDebugFlag === void 0 || browserBuildRhiDebugFlag === "1") && rhiDebugFlag === "1") {
2498
+ const realBackend = hasWebGPU ? await import('@forgeax/engine-rhi-webgpu') : await import('@forgeax/engine-rhi-wgpu');
2499
+ if (!hasWebGPU && "ensureReady" in realBackend) {
2500
+ await realBackend.ensureReady();
2501
+ }
2502
+ const pack = loadRhiPack(realBackend);
2490
2503
  if (pack.createShaderModule === void 0) {
2491
- throw new Error("RHI debug capture requires the backend shader-module entry");
2504
+ throw new Error("RHI-debug requires a backend createShaderModule capability");
2492
2505
  }
2493
- const createShaderModule = pack.createShaderModule;
2494
- const attached = debugModule.attachRecorder({
2506
+ const attached = attachRecorder({
2495
2507
  rhi: pack.rhi,
2496
- createShaderModule
2508
+ createShaderModule: pack.createShaderModule
2497
2509
  });
2498
2510
  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;
2511
+ rhiAttachment = attached.value;
2512
+ rhiCapture = createRhiCapture(attached.value);
2513
+ const capture = rhiCapture;
2514
+ rhiDebugGlobal = { captureFrame: (options) => capture.captureFrame(options) };
2503
2515
  Object.assign(rendererOpts, {
2504
- rhi: attachment.backend.rhi,
2505
- rhiInstrumentation: createRhiInstrumentation(attachment)
2516
+ rhi: attached.value.backend.rhi,
2517
+ rhiInstrumentation: createRhiInstrumentation(attached.value)
2506
2518
  });
2507
- rhiDebugGlobal = {
2508
- captureFrame: (options) => capture.captureFrame(options)
2509
- };
2510
2519
  globalThis.__forgeax = rhiDebugGlobal;
2511
2520
  }
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
2521
  let renderer;
2522
+ let rendererDebugDrawHost;
2523
+ let rendererFeatureHost;
2524
+ let assets;
2519
2525
  try {
2520
- renderer = await createRenderer(canvas, rendererOpts, {
2521
- ...bundler,
2522
- assetResolver: getAssetRegistryResolver(assetAssembly.registry)
2523
- });
2526
+ const constructed = await constructRuntimeRendererHost(canvas, rendererOpts, bundler);
2527
+ if (!constructed.ok) throw constructed.error;
2528
+ renderer = constructed.value.renderer;
2529
+ rendererDebugDrawHost = constructed.value.debugDrawHost;
2530
+ rendererFeatureHost = createRenderFeatureHost(constructed.value.featureHost);
2531
+ assets = constructed.value.assets;
2524
2532
  } catch (e) {
2525
2533
  cleanupRhiDebugHost();
2526
- assetAssembly.dispose();
2527
- assetAssembly.registry.dispose();
2528
- if (e instanceof EngineEnvironmentError) {
2529
- return err(e);
2530
- }
2531
- throw e;
2534
+ if (e instanceof EngineEnvironmentError) return err(e);
2535
+ const detail = e instanceof Error ? e : new Error(String(e));
2536
+ return err(
2537
+ new EngineEnvironmentError("renderer construction failed", {
2538
+ webgpuError: detail
2539
+ })
2540
+ );
2532
2541
  }
2533
2542
  const shouldStartRemote = resolveRemoteServeFlag(
2534
2543
  typeof import.meta !== "undefined" ? import.meta.env?.DEV : void 0,
2535
- globalThis.process?.env,
2536
- typeof window !== "undefined"
2544
+ globalThis.process?.env
2537
2545
  );
2538
2546
  let debugDraw;
2539
2547
  const world = new World(opts?.time !== void 0 ? { time: opts.time } : {});
@@ -2557,8 +2565,10 @@ async function createAppFromCanvas(canvas, opts, bundler) {
2557
2565
  world,
2558
2566
  mainEngineProfile({
2559
2567
  renderer,
2560
- assetAssembly,
2561
- animationPayloads: createAnimationPayloadLookup(renderer.assets),
2568
+ rendererDebugDrawHost,
2569
+ rendererFeatureHost,
2570
+ assets,
2571
+ animationPayloads: createAnimationPayloadLookup(assets),
2562
2572
  ...inputBackend === void 0 ? {} : { input: inputBackend },
2563
2573
  ...inputHandle === void 0 ? {} : { inputDispose: inputHandle.cleanup },
2564
2574
  ...opts?.inputMap === void 0 ? {} : { inputMap: opts.inputMap },
@@ -2568,14 +2578,12 @@ async function createAppFromCanvas(canvas, opts, bundler) {
2568
2578
  extensions: [
2569
2579
  rhiDebugHostPlugin(cleanupRhiDebugHost),
2570
2580
  ...userPlugins,
2571
- canvasAspectPlugin(canvas, maxCanvasPixelRatio)
2581
+ canvasAspectPlugin(canvas)
2572
2582
  ]
2573
2583
  })
2574
2584
  );
2575
2585
  } catch (cause) {
2576
2586
  cleanupRhiDebugHost();
2577
- assetAssembly.dispose();
2578
- assetAssembly.registry.dispose();
2579
2587
  return err(
2580
2588
  makeAppError2(
2581
2589
  "app-plugin-activation-failed",
@@ -2623,21 +2631,15 @@ async function createAppFromCanvas(canvas, opts, bundler) {
2623
2631
  })
2624
2632
  }
2625
2633
  );
2626
- const remoteHandle = shouldStartRemote ? await startRemoteServer(
2627
- world,
2628
- renderer,
2629
- assetAssembly.registry,
2630
- rhiCapture,
2631
- opts?.profiler,
2632
- executionControl
2633
- ) : void 0;
2634
+ const remoteHandle = shouldStartRemote ? await startRemoteServer(world, renderer, assets, rhiCapture, opts?.profiler, executionControl) : void 0;
2634
2635
  if (remoteHandle !== void 0) await pluginContext.plugin(remoteServerPlugin(remoteHandle));
2635
2636
  const buildArgs = {
2636
2637
  renderer,
2637
- assets: assetAssembly.registry,
2638
+ assets,
2638
2639
  world,
2639
2640
  pluginContext,
2640
2641
  executionControl,
2642
+ onFrameSubmitted: (event) => publishBrowserFrameSubmitted(canvas, event),
2641
2643
  ...inputBackend !== void 0 ? { inputBackend } : {},
2642
2644
  ...inputHandle === void 0 ? {} : {
2643
2645
  wireOnLockErrorDispatch: (dispatch) => {
@@ -2660,10 +2662,7 @@ async function createAppFromCanvas(canvas, opts, bundler) {
2660
2662
  Object.assign(buildArgs, { profiler: opts.profiler });
2661
2663
  }
2662
2664
  if (rhiCapture !== void 0) {
2663
- Object.assign(buildArgs, {
2664
- rhiCapture,
2665
- ...rhiAttachment === void 0 ? {} : { rhiAttachment }
2666
- });
2665
+ Object.assign(buildArgs, { rhiCapture });
2667
2666
  }
2668
2667
  if (debugDraw !== void 0) {
2669
2668
  Object.assign(buildArgs, { debugDraw });
@@ -2671,6 +2670,7 @@ async function createAppFromCanvas(canvas, opts, bundler) {
2671
2670
  if (remoteHandle !== void 0) {
2672
2671
  Object.assign(buildArgs, { remoteHandle });
2673
2672
  }
2673
+ resetBrowserFrameSubmitted(canvas);
2674
2674
  const built = await buildApp(buildArgs);
2675
2675
  if (!built.ok) {
2676
2676
  await pluginContext.fiber.dispose();
@@ -2681,17 +2681,17 @@ async function createAppFromCanvas(canvas, opts, bundler) {
2681
2681
  const bridgePort = import.meta.env?.VITE_FORGEAX_ENGINE_BRIDGE_PORT ?? "5733";
2682
2682
  await pluginContext.plugin({
2683
2683
  name: "browser-remote-bridge",
2684
- inject: ["world", "renderer", "assetRegistry"],
2684
+ inject: ["world", "renderer", "assets"],
2685
2685
  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");
2686
+ if (ctx.renderer === void 0 || ctx.assets === void 0) {
2687
+ throw new Error("browser remote bridge requires renderer and assets services");
2688
2688
  }
2689
2689
  try {
2690
2690
  const bridge = await Promise.resolve().then(() => (init_browser_remote_bridge(), browser_remote_bridge_exports));
2691
2691
  const teardown = await bridge.installBrowserRemoteBridge({
2692
2692
  world: ctx.world,
2693
2693
  renderer: ctx.renderer,
2694
- assets: ctx.assetRegistry,
2694
+ assets: ctx.assets,
2695
2695
  runtimeModule: engineRuntimeModule,
2696
2696
  ...rhiCapture !== void 0 ? { rhiCapture } : {},
2697
2697
  ...opts?.profiler !== void 0 ? { profiler: opts.profiler } : {},
@@ -2707,43 +2707,62 @@ async function createAppFromCanvas(canvas, opts, bundler) {
2707
2707
  }
2708
2708
  return built;
2709
2709
  }
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) {
2710
+ function syncCanvasDrawingBuffer(canvas) {
2724
2711
  if (!Number.isFinite(canvas.clientWidth) || !Number.isFinite(canvas.clientHeight) || canvas.clientWidth <= 0 || canvas.clientHeight <= 0)
2725
- return void 0;
2712
+ return;
2726
2713
  const hasExplicitCssSize = (canvas.style?.width ?? "") !== "" || (canvas.style?.height ?? "") !== "";
2727
2714
  if (!hasExplicitCssSize && canvas.clientWidth === canvas.width && canvas.clientHeight === canvas.height) {
2728
- return { width: canvas.width, height: canvas.height };
2715
+ return;
2729
2716
  }
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);
2717
+ const dpr = Math.max(1, globalThis.devicePixelRatio || 1);
2732
2718
  const previous = syncedCanvasSizes.get(canvas);
2733
2719
  const drawingBufferIsTheLayoutMeasurement = previous !== void 0 && canvas.width === previous.drawingWidth && canvas.height === previous.drawingHeight && canvas.clientWidth === previous.drawingWidth && canvas.clientHeight === previous.drawingHeight;
2734
2720
  const cssWidth = drawingBufferIsTheLayoutMeasurement ? previous.cssWidth : canvas.clientWidth;
2735
2721
  const cssHeight = drawingBufferIsTheLayoutMeasurement ? previous.cssHeight : canvas.clientHeight;
2736
2722
  const width = Math.max(1, Math.round(cssWidth * dpr));
2737
2723
  const height = Math.max(1, Math.round(cssHeight * dpr));
2724
+ if (canvas.width !== width) canvas.width = width;
2725
+ if (canvas.height !== height) canvas.height = height;
2738
2726
  syncedCanvasSizes.set(canvas, {
2739
2727
  cssWidth,
2740
2728
  cssHeight,
2741
2729
  drawingWidth: width,
2742
2730
  drawingHeight: height
2743
2731
  });
2744
- return { width, height };
2732
+ }
2733
+ function measureCanvasDrawingBuffer(canvas, maxCanvasPixelRatio) {
2734
+ if (!Number.isFinite(canvas.clientWidth) || !Number.isFinite(canvas.clientHeight) || canvas.clientWidth <= 0 || canvas.clientHeight <= 0) {
2735
+ return { width: canvas.width, height: canvas.height };
2736
+ }
2737
+ const hasExplicitCssSize = (canvas.style?.width ?? "") !== "" || (canvas.style?.height ?? "") !== "";
2738
+ if (!hasExplicitCssSize && canvas.clientWidth === canvas.width && canvas.clientHeight === canvas.height) {
2739
+ return { width: canvas.width, height: canvas.height };
2740
+ }
2741
+ const devicePixelRatio = Math.max(1, globalThis.devicePixelRatio || 1);
2742
+ const dpr = maxCanvasPixelRatio === void 0 || !Number.isFinite(maxCanvasPixelRatio) || maxCanvasPixelRatio <= 0 ? devicePixelRatio : Math.min(devicePixelRatio, maxCanvasPixelRatio);
2743
+ const previous = syncedCanvasSizes.get(canvas);
2744
+ const drawingBufferIsTheLayoutMeasurement = previous !== void 0 && canvas.width === previous.drawingWidth && canvas.height === previous.drawingHeight && canvas.clientWidth === previous.drawingWidth && canvas.clientHeight === previous.drawingHeight;
2745
+ const cssWidth = drawingBufferIsTheLayoutMeasurement ? previous.cssWidth : canvas.clientWidth;
2746
+ const cssHeight = drawingBufferIsTheLayoutMeasurement ? previous.cssHeight : canvas.clientHeight;
2747
+ return {
2748
+ width: Math.max(1, Math.round(cssWidth * dpr)),
2749
+ height: Math.max(1, Math.round(cssHeight * dpr))
2750
+ };
2745
2751
  }
2746
2752
  var syncedCanvasSizes = /* @__PURE__ */ new WeakMap();
2753
+ function syncCameraAspect(world, canvasW, canvasH) {
2754
+ if (canvasW <= 0 || canvasH <= 0) return;
2755
+ const aspect = canvasW / canvasH;
2756
+ const query = world.query({ with: [Camera] }).unwrap();
2757
+ for (const row of query) {
2758
+ const entity = row.entity;
2759
+ const r = world.get(entity, Camera);
2760
+ if (!r.ok) continue;
2761
+ if (r.value.autoAspect !== true) continue;
2762
+ if (r.value.projection !== CAMERA_PROJECTION_PERSPECTIVE) continue;
2763
+ world.set(entity, Camera, { aspect });
2764
+ }
2765
+ }
2747
2766
  async function startRemoteServer(world, renderer, assets, rhiCapture, profiler, execution) {
2748
2767
  try {
2749
2768
  const remoteServerMod = await import(
@@ -2769,22 +2788,17 @@ async function startRemoteServer(world, renderer, assets, rhiCapture, profiler,
2769
2788
  return void 0;
2770
2789
  }
2771
2790
  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
2791
  let pluginContext;
2776
2792
  try {
2777
2793
  pluginContext = await createWorldContext(
2778
2794
  args.world,
2779
2795
  assembledEngineProfile({
2780
2796
  renderer: args.renderer,
2781
- assetAssembly,
2797
+ ...args.assets === void 0 ? {} : { assets: args.assets },
2782
2798
  extensions: args.plugins ?? []
2783
2799
  })
2784
2800
  );
2785
2801
  } catch (cause) {
2786
- assetAssembly.dispose();
2787
- assetAssembly.registry.dispose();
2788
2802
  return err(
2789
2803
  makeAppError2(
2790
2804
  "app-plugin-activation-failed",
@@ -2826,7 +2840,7 @@ async function createAppFromAssemble(args) {
2826
2840
  const remoteHandle = shouldStartRemote ? await startRemoteServer(
2827
2841
  args.world,
2828
2842
  args.renderer,
2829
- assetAssembly.registry,
2843
+ args.assets,
2830
2844
  void 0,
2831
2845
  args.profiler,
2832
2846
  executionControl
@@ -2834,7 +2848,6 @@ async function createAppFromAssemble(args) {
2834
2848
  if (remoteHandle !== void 0) await pluginContext.plugin(remoteServerPlugin(remoteHandle));
2835
2849
  const buildArgs = {
2836
2850
  renderer: args.renderer,
2837
- assets: assetAssembly.registry,
2838
2851
  world: args.world,
2839
2852
  pluginContext,
2840
2853
  executionControl,
@@ -2867,14 +2880,12 @@ async function createAppFromAssemble(args) {
2867
2880
  async function buildApp(args) {
2868
2881
  const {
2869
2882
  renderer,
2870
- assets,
2871
2883
  world,
2872
2884
  pluginContext,
2873
2885
  inputBackend,
2874
2886
  audioBackend,
2875
2887
  silenceUnhandledErrors,
2876
2888
  rhiCapture,
2877
- rhiAttachment,
2878
2889
  debugDraw,
2879
2890
  remoteHandle,
2880
2891
  profiler
@@ -2885,6 +2896,7 @@ async function buildApp(args) {
2885
2896
  const fanout = new ErrorFanoutRegistry(
2886
2897
  silenceUnhandledErrors !== void 0 ? { silenceUnhandledErrors } : {}
2887
2898
  );
2899
+ let lastError;
2888
2900
  const execution = args.executionControl ?? createLocalExecutionControl(
2889
2901
  {
2890
2902
  ...createExecutionReport("main-serial", unavailableExecutionCapabilities("not required")),
@@ -2910,6 +2922,7 @@ async function buildApp(args) {
2910
2922
  }
2911
2923
  );
2912
2924
  function dispatch(e) {
2925
+ lastError = e;
2913
2926
  fanout.fire(e);
2914
2927
  }
2915
2928
  if (args.wireOnLockErrorDispatch) {
@@ -2927,7 +2940,7 @@ async function buildApp(args) {
2927
2940
  Object.assign(loopOpts, { profiler });
2928
2941
  }
2929
2942
  const loop = createFrameLoop(loopOpts);
2930
- if (rhiCapture !== void 0 && rhiAttachment !== void 0) {
2943
+ if (rhiCapture !== void 0) {
2931
2944
  bindRhiCaptureFrameDriver(rhiCapture, {
2932
2945
  getState: () => loop.getState(),
2933
2946
  pause: () => {
@@ -2943,17 +2956,19 @@ async function buildApp(args) {
2943
2956
  stepFrame: (deltaSeconds) => loop.stepFrame(deltaSeconds)
2944
2957
  });
2945
2958
  }
2946
- let lastError;
2947
2959
  let rendererUnsubscribe;
2948
2960
  let resumeAfterSurfaceRestore = false;
2949
2961
  function subscribeRendererErrors() {
2950
2962
  if (rendererUnsubscribe !== void 0) {
2951
2963
  return;
2952
2964
  }
2953
- rendererUnsubscribe = renderer.onError((e) => {
2954
- if (e?.code === "device-lost") {
2955
- lastError = e;
2965
+ rendererUnsubscribe = renderer.subscribe((event) => {
2966
+ if (event.kind === "frame-submitted") {
2967
+ args.onFrameSubmitted?.(event);
2968
+ return;
2956
2969
  }
2970
+ if (event.kind !== "error") return;
2971
+ const e = event.error;
2957
2972
  dispatch(e);
2958
2973
  });
2959
2974
  }
@@ -2965,7 +2980,7 @@ async function buildApp(args) {
2965
2980
  }
2966
2981
  const stub = {
2967
2982
  renderer,
2968
- assets,
2983
+ ...args.assets === void 0 ? {} : { assets: args.assets },
2969
2984
  world,
2970
2985
  execution,
2971
2986
  async releaseSurfacePreserveWorld() {
@@ -3060,7 +3075,7 @@ async function buildApp(args) {
3060
3075
  loop.setDrawSource(drawSource);
3061
3076
  },
3062
3077
  /**
3063
- * Most recent device-loss error retained for host self-inspection.
3078
+ * Most recent dispatched error retained for host self-inspection.
3064
3079
  */
3065
3080
  get lastError() {
3066
3081
  return lastError;
@@ -3069,10 +3084,6 @@ async function buildApp(args) {
3069
3084
  ...debugDraw !== void 0 ? { debugDraw } : {},
3070
3085
  ...remoteHandle !== void 0 ? { remote: remoteHandle } : {}
3071
3086
  };
3072
- const readyResult = await renderer.ready;
3073
- if (!readyResult.ok) {
3074
- return err(readyResult.error);
3075
- }
3076
3087
  return ok(stub);
3077
3088
  }
3078
3089
  function ensureFallbackCamera(world, aspect = 1) {
@@ -3679,19 +3690,6 @@ async function createToolPreviewHost(options) {
3679
3690
  const app = appResult.value;
3680
3691
  const appErrors = [];
3681
3692
  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
3693
  let resourceFacts;
3696
3694
  try {
3697
3695
  const prepared = await options.prepare?.(app);
@@ -3716,7 +3714,7 @@ async function createToolPreviewHost(options) {
3716
3714
  detail: { phase: bootstrapFailure.phase, cause }
3717
3715
  });
3718
3716
  }
3719
- const attached = app.renderer.attachWorld(app.world);
3717
+ const attached = app.renderer.attach(app.world);
3720
3718
  if (!attached.ok) {
3721
3719
  unsubscribeAppErrors();
3722
3720
  await app.dispose();
@@ -3977,6 +3975,6 @@ function fitToolPreviewCameraToAabb(aabb, options) {
3977
3975
  };
3978
3976
  }
3979
3977
 
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 };
3978
+ export { APP_ERROR_HINTS, APP_EXPECTED, APP_PHASE_CATALOG, AppError, EXECUTION_CAPABILITY_NAMES, EXECUTION_REPORT_SCHEMA_VERSION, EXECUTION_REQUESTED_TIERS, EXECUTION_TIERS, FORGEAX_FRAME_SUBMITTED_DATASET, FORGEAX_FRAME_SUBMITTED_EVENT, 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, publishBrowserFrameSubmitted, renderFeatureHostPlugin, renderFeaturePlugin, rendererPlugin, replayToolPreviewCapture, resetBrowserFrameSubmitted, selectExecutionTier, syncCanvasDrawingBuffer, unavailableExecutionCapabilities, validateExecutionBootstrapData, validateToolPreviewTrace };
3981
3979
  //# sourceMappingURL=index.mjs.map
3982
3980
  //# sourceMappingURL=index.mjs.map