@forgeax/engine-app 0.1.7 → 0.1.20

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.
package/dist/index.mjs CHANGED
@@ -3,7 +3,7 @@ export { Context } from '@forgeax/engine-plugin';
3
3
  export { createFullscreenRenderFeature } from '@forgeax/engine-render/authoring';
4
4
  import { Camera, perspective, renderComponentsPlugin, CAMERA_PROJECTION_PERSPECTIVE } from '@forgeax/engine-render';
5
5
  import { RhiError } from '@forgeax/engine-rhi';
6
- import { attachRecorder, decodeTape, openReplay, decodeToRgba8, buildTapeIndex, createRhiDebugError } from '@forgeax/engine-rhi-debug';
6
+ import { attachRecorder, decodeTape, openReplay, decodeToRgba8, buildTapeIndex, replayDeviceRequest, createRhiDebugError } from '@forgeax/engine-rhi-debug';
7
7
  import * as engineRuntimeModule from '@forgeax/engine-runtime';
8
8
  import { EngineEnvironmentError } from '@forgeax/engine-runtime';
9
9
  import { loadRhiPack, constructRuntimeRendererHost } from '@forgeax/engine-runtime/internal/renderer-host';
@@ -353,8 +353,8 @@ var APP_EXPECTED = Object.fromEntries(
353
353
  var APP_ERROR_HINTS = Object.fromEntries(
354
354
  Object.entries(appErrorPolicy).map(([code, policy]) => [code, policy.hint])
355
355
  );
356
- function isAppError(err13) {
357
- return err13 instanceof AppErrorClass;
356
+ function isAppError(err14) {
357
+ return err14 instanceof AppErrorClass;
358
358
  }
359
359
  function executionBootstrapHostPlugin(host) {
360
360
  return {
@@ -832,16 +832,16 @@ var ErrorFanoutRegistry = class {
832
832
  * console.error(err) so the unhandled error is visible in devtools
833
833
  * (charter P3 explicit failure: silent drop is a footgun).
834
834
  */
835
- fire(err13) {
835
+ fire(err14) {
836
836
  if (this.listeners.size === 0) {
837
837
  if (!this.silenceUnhandledErrors) {
838
- console.error(err13);
838
+ console.error(err14);
839
839
  }
840
840
  return;
841
841
  }
842
842
  const snapshot = Array.from(this.listeners);
843
843
  for (const cb of snapshot) {
844
- cb(err13);
844
+ cb(err14);
845
845
  }
846
846
  }
847
847
  /**
@@ -2023,13 +2023,13 @@ function attachInputAuto(canvas, options = {}) {
2023
2023
  ...options.lockProvider ? { lockProvider: options.lockProvider } : {},
2024
2024
  onLockError: (detail) => {
2025
2025
  if (onLockErrorDispatch) {
2026
- const err13 = new AppError({
2026
+ const err14 = new AppError({
2027
2027
  code: "app-pointer-lock-failed",
2028
2028
  expected: APP_EXPECTED["app-pointer-lock-failed"],
2029
2029
  hint: APP_ERROR_HINTS["app-pointer-lock-failed"],
2030
2030
  detail
2031
2031
  });
2032
- onLockErrorDispatch(err13);
2032
+ onLockErrorDispatch(err14);
2033
2033
  }
2034
2034
  }
2035
2035
  };
@@ -2088,21 +2088,27 @@ function inputPlugin() {
2088
2088
  }
2089
2089
  };
2090
2090
  }
2091
-
2092
- // ../render/src/record/render-context.ts
2093
- function selectSwapChainFormat(storageBufferCapable) {
2094
- if (!storageBufferCapable) return { storage: "rgba8unorm", view: "rgba8unorm-srgb" };
2091
+ function selectSwapChainFormat(storageBufferCapable, surfaceViewFormats = true) {
2092
+ if (!storageBufferCapable) {
2093
+ return {
2094
+ storage: "rgba8unorm",
2095
+ view: surfaceViewFormats ? "rgba8unorm-srgb" : "rgba8unorm"
2096
+ };
2097
+ }
2095
2098
  const nav = globalThis.navigator;
2096
2099
  const gpu = nav?.gpu;
2097
2100
  const getPreferred = gpu?.getPreferredCanvasFormat;
2098
2101
  if (gpu !== void 0 && typeof getPreferred === "function") {
2099
2102
  const storage = getPreferred.call(gpu);
2100
2103
  const view = storage === "rgba8unorm" ? "rgba8unorm-srgb" : storage === "bgra8unorm" ? "bgra8unorm-srgb" : storage;
2101
- return { storage, view };
2104
+ return {
2105
+ storage,
2106
+ view: surfaceViewFormats ? view : storage
2107
+ };
2102
2108
  }
2103
2109
  return {
2104
2110
  storage: "rgba8unorm",
2105
- view: "rgba8unorm-srgb",
2111
+ view: surfaceViewFormats ? "rgba8unorm-srgb" : "rgba8unorm",
2106
2112
  fallbackReason: "preferred-canvas-format-missing"
2107
2113
  };
2108
2114
  }
@@ -2760,6 +2766,7 @@ function syncCameraAspect(world, canvasW, canvasH) {
2760
2766
  if (!r.ok) continue;
2761
2767
  if (r.value.autoAspect !== true) continue;
2762
2768
  if (r.value.projection !== CAMERA_PROJECTION_PERSPECTIVE) continue;
2769
+ if (r.value.aspect === Math.fround(aspect)) continue;
2763
2770
  world.set(entity, Camera, { aspect });
2764
2771
  }
2765
2772
  }
@@ -3142,8 +3149,8 @@ var LOAD_GAME_EXPECTED = Object.fromEntries(
3142
3149
  var LOAD_GAME_ERROR_HINTS = Object.fromEntries(
3143
3150
  Object.entries(loadGameErrorPolicy).map(([code, policy]) => [code, policy.hint])
3144
3151
  );
3145
- function isLoadGameError(err13) {
3146
- return err13 instanceof LoadGameErrorClass;
3152
+ function isLoadGameError(err14) {
3153
+ return err14 instanceof LoadGameErrorClass;
3147
3154
  }
3148
3155
  function isPlugin(value) {
3149
3156
  return typeof value === "function" || typeof value === "object" && value !== null && "apply" in value && typeof value.apply === "function";
@@ -3204,10 +3211,12 @@ async function createBrowserRhiDebugRuntime() {
3204
3211
  attachment,
3205
3212
  createShaderModule: backend.createShaderModule,
3206
3213
  rhiInstrumentation: createRhiInstrumentation(attachment),
3207
- createReplayDevice: async () => {
3214
+ createReplayDevice: async (tape) => {
3208
3215
  const adapter = await backend.rhi.requestAdapter();
3209
3216
  if (!adapter.ok) return adapter;
3210
- return adapter.value.requestDevice();
3217
+ return adapter.value.requestDevice(
3218
+ replayDeviceRequest(tape, adapter.value.features, adapter.value.limits)
3219
+ );
3211
3220
  },
3212
3221
  attachRenderer(_renderer) {
3213
3222
  return () => {
@@ -3345,6 +3354,11 @@ function resourceSubjectDigest(resource) {
3345
3354
  ];
3346
3355
  return candidates.find((value) => typeof value === "string" && value.length > 0);
3347
3356
  }
3357
+ function resourceSubjectDrawn(resource, drawCalls) {
3358
+ if (resource.kind === "texture") return drawCalls > 0;
3359
+ if (resource.kind === "material" || resource.kind === "mesh") return drawCalls > 2;
3360
+ return resource.observation !== void 0;
3361
+ }
3348
3362
  async function replayToolPreviewCapture(capture) {
3349
3363
  const analyzeStartedAtMs = performance.now();
3350
3364
  const tapeBytes = dataUriBytes(capture.tape.jsonUri);
@@ -3357,13 +3371,23 @@ async function replayToolPreviewCapture(capture) {
3357
3371
  detail: { phase: "tape-parse", cause: parsed.error }
3358
3372
  });
3359
3373
  }
3360
- const subjectDigest = resourceSubjectDigest(capture.resource);
3361
- if (capture.resource !== void 0 && subjectDigest === void 0) {
3374
+ const drawCalls = countDrawCalls(parsed.value);
3375
+ const resource = capture.resource !== void 0 && capture.resource.observation === void 0 && capture.resource.ownerFacts !== void 0 && resourceSubjectDrawn(capture.resource, drawCalls) ? { ...capture.resource, observation: capture.resource.ownerFacts } : capture.resource;
3376
+ const subjectDigest = resourceSubjectDigest(resource);
3377
+ if (resource !== void 0 && subjectDigest === void 0) {
3362
3378
  return err({
3363
3379
  code: "tool-preview-bootstrap-failed",
3364
3380
  expected: "the resource owner to publish a subject identity before evidence publication",
3365
3381
  hint: "repair AssetRegistry owner facts or post-frame observation; recipe and trace are not subject identity",
3366
- detail: { phase: "resource-identity" }
3382
+ detail: {
3383
+ phase: "resource-identity",
3384
+ cause: {
3385
+ kind: resource.kind,
3386
+ drawCalls,
3387
+ ownerFactsPublished: resource.ownerFacts !== void 0,
3388
+ observationPublished: resource.observation !== void 0
3389
+ }
3390
+ }
3367
3391
  });
3368
3392
  }
3369
3393
  let runtime;
@@ -3377,7 +3401,7 @@ async function replayToolPreviewCapture(capture) {
3377
3401
  detail: { phase: "replay-runtime", cause }
3378
3402
  });
3379
3403
  }
3380
- const replayDevice = await runtime.createReplayDevice();
3404
+ const replayDevice = await runtime.createReplayDevice(parsed.value);
3381
3405
  if (!replayDevice.ok) {
3382
3406
  return err({
3383
3407
  code: "tool-preview-capability-unavailable",
@@ -3399,7 +3423,6 @@ async function replayToolPreviewCapture(capture) {
3399
3423
  });
3400
3424
  }
3401
3425
  const replay = replayResult.value;
3402
- const drawCalls = countDrawCalls(parsed.value);
3403
3426
  if (drawCalls === 0) {
3404
3427
  await replay.dispose();
3405
3428
  return err({
@@ -3619,7 +3642,7 @@ async function replayToolPreviewCapture(capture) {
3619
3642
  }
3620
3643
  }
3621
3644
  },
3622
- ...capture.resource === void 0 ? {} : { resource: capture.resource }
3645
+ ...resource === void 0 ? {} : { resource }
3623
3646
  });
3624
3647
  }
3625
3648
  async function createToolPreviewHost(options) {
@@ -3772,7 +3795,7 @@ async function createToolPreviewHost(options) {
3772
3795
  });
3773
3796
  const executeStartedAtMs = performance.now();
3774
3797
  const actionTrace = [];
3775
- const encodedCapture = runtime.attachment.captureFrame();
3798
+ let encodedCapture;
3776
3799
  for (let frame = 0; frame < recipe.frames; frame += 1) {
3777
3800
  for (const action of recipe.actions.filter((candidate) => candidate.frame === frame)) {
3778
3801
  let accepted = false;
@@ -3796,6 +3819,17 @@ async function createToolPreviewHost(options) {
3796
3819
  }
3797
3820
  actionTrace.push(action);
3798
3821
  }
3822
+ if (frame === recipe.frames - 1) {
3823
+ encodedCapture = runtime.attachment.captureFrame();
3824
+ const snapshot = await runtime.attachment.frameBoundary();
3825
+ if (!snapshot.ok)
3826
+ return err({
3827
+ code: "tool-preview-bootstrap-failed",
3828
+ expected: "RHI-debug snapshots live resources before the captured frame",
3829
+ hint: snapshot.error.hint,
3830
+ detail: { phase: "rhi-snapshot", cause: snapshot.error }
3831
+ });
3832
+ }
3799
3833
  const stepped = app.stepFrame(recipe.deltaSeconds);
3800
3834
  if (!stepped.ok)
3801
3835
  return err({
@@ -3805,9 +3839,14 @@ async function createToolPreviewHost(options) {
3805
3839
  });
3806
3840
  await new Promise((resolve) => setTimeout(resolve, 0));
3807
3841
  }
3808
- await runtime.attachment.frameBoundary();
3809
- await new Promise((resolve) => setTimeout(resolve, 0));
3810
- await runtime.attachment.frameBoundary();
3842
+ const finalized = await runtime.attachment.frameBoundary();
3843
+ if (!finalized.ok)
3844
+ return err({
3845
+ code: "tool-preview-bootstrap-failed",
3846
+ expected: "RHI-debug finalizes the captured frame",
3847
+ hint: finalized.error.hint,
3848
+ detail: { phase: "rhi-finalize", cause: finalized.error }
3849
+ });
3811
3850
  if (options.collectResourceFacts !== void 0) {
3812
3851
  try {
3813
3852
  const collected = await options.collectResourceFacts(app, resourceFacts);
@@ -3836,6 +3875,14 @@ async function createToolPreviewHost(options) {
3836
3875
  detail: { phase: "profiler-finish", cause: profile.error }
3837
3876
  });
3838
3877
  }
3878
+ if (encodedCapture === void 0) {
3879
+ return err({
3880
+ code: "tool-preview-bootstrap-failed",
3881
+ expected: "a positive-frame recipe to arm one captured frame",
3882
+ hint: "validate the recipe before running the preview host",
3883
+ detail: { phase: "rhi-capture" }
3884
+ });
3885
+ }
3839
3886
  const tape = await encodedCapture;
3840
3887
  if (!tape.ok)
3841
3888
  return err({