@forgeax/engine-devkit 0.1.6 → 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 (36) hide show
  1. package/README.md +143 -1
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/__tests__/engine-binding.unit.test.d.ts +2 -0
  4. package/dist/__tests__/engine-binding.unit.test.d.ts.map +1 -0
  5. package/dist/__tests__/software-browser-session.unit.test.d.ts +2 -0
  6. package/dist/__tests__/software-browser-session.unit.test.d.ts.map +1 -0
  7. package/dist/__tests__/software-capture-pixels.unit.test.d.ts +2 -0
  8. package/dist/__tests__/software-capture-pixels.unit.test.d.ts.map +1 -0
  9. package/dist/__tests__/software-capture.unit.test.d.ts +2 -0
  10. package/dist/__tests__/software-capture.unit.test.d.ts.map +1 -0
  11. package/dist/cli-output.d.ts.map +1 -1
  12. package/dist/cli.mjs +1477 -246
  13. package/dist/cli.mjs.map +1 -1
  14. package/dist/commands.d.ts +2 -0
  15. package/dist/commands.d.ts.map +1 -1
  16. package/dist/engine-binding.d.ts +58 -0
  17. package/dist/engine-binding.d.ts.map +1 -0
  18. package/dist/host.d.ts.map +1 -1
  19. package/dist/index.d.ts +7 -2
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.mjs +1414 -295
  22. package/dist/index.mjs.map +1 -1
  23. package/dist/rhi-debug/cli-context.d.ts.map +1 -1
  24. package/dist/rhi-debug/operations.d.ts +3 -3
  25. package/dist/rhi-debug/operations.d.ts.map +1 -1
  26. package/dist/sdk-bootstrap.d.ts.map +1 -1
  27. package/dist/sdk-cli.mjs +4 -2
  28. package/dist/sdk-cli.mjs.map +1 -1
  29. package/dist/software-capture.d.ts +123 -0
  30. package/dist/software-capture.d.ts.map +1 -0
  31. package/dist/tools/browser-host.d.ts.map +1 -1
  32. package/dist/tools/commands.d.ts +5 -0
  33. package/dist/tools/commands.d.ts.map +1 -1
  34. package/dist/types.d.ts +21 -1
  35. package/dist/types.d.ts.map +1 -1
  36. package/package.json +32 -30
package/dist/index.mjs CHANGED
@@ -1,7 +1,8 @@
1
1
  import { deflateRawSync } from 'zlib';
2
- import { createHash } from 'crypto';
3
- import { readFile, writeFile, mkdir, copyFile, readdir, mkdtemp, cp, rename, rm, stat, symlink, realpath, unlink, lstat, readlink, rmdir, access } from 'fs/promises';
2
+ import { createHash, randomUUID } from 'crypto';
3
+ import { readFile, writeFile, access, readdir, stat, realpath, rm, mkdir, copyFile, mkdtemp, cp, rename, symlink, unlink, lstat, readlink, rmdir } from 'fs/promises';
4
4
  import { resolve, relative, sep, isAbsolute, extname, dirname, basename, join } from 'path';
5
+ import { GameProjectSchema } from '@forgeax/engine-project';
5
6
  import { existsSync } from 'fs';
6
7
  import { createRequire } from 'module';
7
8
  import { fileURLToPath } from 'url';
@@ -18,17 +19,19 @@ import { createStandaloneRuntimeAssetBinding, err, ok } from '@forgeax/engine-ty
18
19
  import { createParticleCodeNativeCookerFromRoots } from '@forgeax/engine-vfx-compiler';
19
20
  import { pluginPack, reloadAssetHost } from '@forgeax/engine-vite-plugin-pack';
20
21
  import { forgeaxShader } from '@forgeax/engine-vite-plugin-shader';
21
- import { GameProjectSchema } from '@forgeax/engine-project';
22
22
  import { runCliGltf } from '@forgeax/engine-gltf/cli-gltf';
23
23
  import { scanEntries } from '@forgeax/engine-pack/cli-asset';
24
24
  import { AssetGuid } from '@forgeax/engine-pack/guid';
25
- import { execFile } from 'child_process';
25
+ import { execFile, spawn } from 'child_process';
26
26
  import { tmpdir } from 'os';
27
27
  import { promisify } from 'util';
28
- import { createRhiDebugError, decodeTape, openReplay, buildFrameModel } from '@forgeax/engine-rhi-debug';
29
- import { rhi, createShaderModule } from '@forgeax/engine-rhi-null';
28
+ import { replayDeviceRequest, createRhiDebugError, decodeTape, openReplay, buildFrameModel } from '@forgeax/engine-rhi-debug';
29
+ import { rhi, createShaderModule } from '@forgeax/engine-rhi-webgpu';
30
30
  import { build, preview, createServer as createServer$2 } from 'vite';
31
31
  import { createServer as createServer$1 } from 'net';
32
+ import { createToolPreviewRecipe, createToolPreviewHost, FORGEAX_FRAME_SUBMITTED_DATASET } from '@forgeax/engine-app';
33
+ import { parseImage } from '@forgeax/engine-image/parse-image';
34
+ import { chromium } from 'playwright';
32
35
  import { startVitest } from 'vitest/node';
33
36
  import materialPreviewPlugin from '@forgeax/engine-preview/material';
34
37
  import meshPreviewPlugin from '@forgeax/engine-preview/mesh';
@@ -39,8 +42,6 @@ export { createAuthenticatedLoopbackTransport, createCapabilityToken, createMigr
39
42
  import { Context, isToolPlugin } from '@forgeax/engine-plugin';
40
43
  import { installCatalogLoader, projectPluginEntries, bootstrapCatalogLoader } from '@forgeax/engine-plugin/loader';
41
44
  import { createServer } from 'http';
42
- import { createToolPreviewRecipe, createToolPreviewHost } from '@forgeax/engine-app';
43
- import { chromium } from 'playwright';
44
45
 
45
46
  var __defProp = Object.defineProperty;
46
47
  var __getOwnPropNames = Object.getOwnPropertyNames;
@@ -298,10 +299,541 @@ async function verifyDist(rootInput) {
298
299
  };
299
300
  }
300
301
  }
301
- return { ok: true, value: manifest };
302
+ return { ok: true, value: manifest };
303
+ }
304
+ var init_dist = __esm({
305
+ "src/dist.ts"() {
306
+ }
307
+ });
308
+ function projectError(code, expected, hint, detail) {
309
+ return { ok: false, error: { code, expected, hint, detail } };
310
+ }
311
+ async function readJson(path) {
312
+ return JSON.parse(await readFile(path, "utf8"));
313
+ }
314
+ function firstUnsupportedStandaloneRealm(entries2, inheritedRealm = "engine") {
315
+ for (const entry of entries2) {
316
+ const realm = entry.realm ?? inheritedRealm ?? "engine";
317
+ if (realm !== "engine") return { id: entry.id, realm };
318
+ if (entry.group === true) {
319
+ const unsupported = firstUnsupportedStandaloneRealm(
320
+ entry.config,
321
+ realm
322
+ );
323
+ if (unsupported !== void 0) return unsupported;
324
+ }
325
+ }
326
+ return void 0;
327
+ }
328
+ function pluginModuleNames(entries2) {
329
+ return entries2.flatMap(
330
+ (entry) => entry.group === true ? pluginModuleNames(entry.config) : [entry.name]
331
+ );
332
+ }
333
+ async function readProjectFacts(rootInput = process.cwd()) {
334
+ const root = resolve(rootInput);
335
+ let forgeValue;
336
+ let packageValue;
337
+ try {
338
+ [forgeValue, packageValue] = await Promise.all([
339
+ readJson(resolve(root, "forge.json")),
340
+ readJson(resolve(root, "package.json"))
341
+ ]);
342
+ } catch (cause) {
343
+ return projectError(
344
+ "project-manifest-unreadable",
345
+ "readable forge.json and package.json files",
346
+ "Run the command from a ForgeaX game root or pass its directory.",
347
+ { root, reason: cause instanceof Error ? cause.message : String(cause) }
348
+ );
349
+ }
350
+ if (forgeValue === null || typeof forgeValue !== "object") {
351
+ return projectError(
352
+ "project-manifest-invalid",
353
+ "forge.json to contain an object",
354
+ "Repair forge.json before running DevKit.",
355
+ { root }
356
+ );
357
+ }
358
+ if (packageValue === null || typeof packageValue !== "object") {
359
+ return projectError(
360
+ "package-manifest-invalid",
361
+ "package.json to contain an object",
362
+ "Repair package.json before running DevKit.",
363
+ { root }
364
+ );
365
+ }
366
+ const parsedForge = GameProjectSchema.safeParse(forgeValue);
367
+ if (!parsedForge.success) {
368
+ return projectError(
369
+ "project-manifest-invalid",
370
+ "forge.json to satisfy @forgeax/engine-project GameProjectSchema",
371
+ "Repair the fields reported by the authoritative project schema.",
372
+ { root, issues: parsedForge.error.issues }
373
+ );
374
+ }
375
+ const forge = parsedForge.data;
376
+ if (forge.id.length === 0 || forge.name.length === 0 || forge.entry === void 0 || forge.entry.length === 0) {
377
+ return projectError(
378
+ "project-manifest-invalid",
379
+ "forge.json to declare id, name, and entry",
380
+ "Restore the project entry; plugin Entries remain optional additions.",
381
+ { root }
382
+ );
383
+ }
384
+ const plugins = forge.plugins ?? [];
385
+ const unsupportedRealm = firstUnsupportedStandaloneRealm(plugins);
386
+ if (unsupportedRealm !== void 0) {
387
+ return projectError(
388
+ "project-plugin-realm-unsupported",
389
+ "the standalone Devkit host to contain only engine-realm plugin Entries",
390
+ "Move Host or build plugins to a host that owns that physical realm.",
391
+ { root, ...unsupportedRealm }
392
+ );
393
+ }
394
+ const entryPath = isAbsolute(forge.entry) ? forge.entry : resolve(root, forge.entry);
395
+ try {
396
+ await readFile(entryPath);
397
+ } catch {
398
+ return projectError(
399
+ "project-entry-missing",
400
+ "forge.json#entry to resolve to a readable module",
401
+ "Restore the game entry or update forge.json#entry.",
402
+ { root, entry: forge.entry }
403
+ );
404
+ }
405
+ const packageJson = packageValue;
406
+ const forgeax = packageJson.forgeax;
407
+ const configuredRoots = forgeax !== null && typeof forgeax === "object" ? forgeax.assets?.roots : void 0;
408
+ const assetRoots = Array.isArray(configuredRoots) && configuredRoots.every((value) => typeof value === "string") ? configuredRoots : ["assets"];
409
+ const configuredImporters = forgeax !== null && typeof forgeax === "object" ? forgeax.assets?.importers : void 0;
410
+ const assetImporters = Array.isArray(configuredImporters) && configuredImporters.every((value) => typeof value === "string") ? configuredImporters : [];
411
+ const configuredPublicDir = forgeax !== null && typeof forgeax === "object" ? forgeax.assets?.publicDir : void 0;
412
+ const assetPublicDir = typeof configuredPublicDir === "string" ? configuredPublicDir : void 0;
413
+ const physics = forge.physics === "2d" || forge.physics === "3d" ? forge.physics : void 0;
414
+ const defaultScene = typeof forge.defaultScene === "string" && forge.defaultScene.length > 0 ? forge.defaultScene : void 0;
415
+ const normalizedEntry = forge.entry.startsWith("./") ? forge.entry : `./${forge.entry}`;
416
+ const bootstrapEntry = pluginModuleNames(plugins).map((name) => name.startsWith("./") ? name : `./${name}`).includes(normalizedEntry) ? void 0 : forge.entry;
417
+ return {
418
+ ok: true,
419
+ value: {
420
+ root,
421
+ id: forge.id,
422
+ name: forge.name,
423
+ entry: forge.entry,
424
+ ...bootstrapEntry === void 0 ? {} : { bootstrapEntry },
425
+ plugins,
426
+ ...physics === void 0 ? {} : { physics },
427
+ ...defaultScene === void 0 ? {} : { defaultScene },
428
+ assetRoots,
429
+ ...assetImporters.length === 0 ? {} : { assetImporters },
430
+ ...assetPublicDir === void 0 ? {} : { assetPublicDir },
431
+ packageJson
432
+ }
433
+ };
434
+ }
435
+ function commandError(cause, fallbackCode) {
436
+ if (cause !== null && typeof cause === "object" && "code" in cause && "expected" in cause && "hint" in cause && "detail" in cause) {
437
+ return cause;
438
+ }
439
+ return {
440
+ code: fallbackCode,
441
+ expected: "the ForgeaX command to complete",
442
+ hint: "Inspect the underlying diagnostic and repair the owning input.",
443
+ detail: { reason: cause instanceof Error ? cause.message : String(cause) }
444
+ };
445
+ }
446
+ var init_project = __esm({
447
+ "src/project.ts"() {
448
+ }
449
+ });
450
+ function bindingError(code, expected, hint, detail = {}) {
451
+ return { ok: false, error: { code, expected, hint, detail } };
452
+ }
453
+ function isMissing(cause) {
454
+ return cause !== null && typeof cause === "object" && "code" in cause && cause.code === "ENOENT";
455
+ }
456
+ async function pathExists(path) {
457
+ try {
458
+ await access(path);
459
+ return true;
460
+ } catch {
461
+ return false;
462
+ }
463
+ }
464
+ function parseBinding(value, path) {
465
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
466
+ return bindingError(
467
+ "engine-binding-invalid",
468
+ "engine-binding.json to contain one object",
469
+ "Run forgeax engine unlink, then select a binding again.",
470
+ { path }
471
+ );
472
+ }
473
+ const candidate = value;
474
+ if (candidate.schemaVersion !== ENGINE_BINDING_SCHEMA_VERSION) {
475
+ return bindingError(
476
+ "engine-binding-version-unsupported",
477
+ `engine-binding.json schemaVersion ${ENGINE_BINDING_SCHEMA_VERSION}`,
478
+ "Upgrade the SDK or remove the stale binding with forgeax engine unlink.",
479
+ { path, schemaVersion: candidate.schemaVersion ?? null }
480
+ );
481
+ }
482
+ if (typeof candidate.path !== "string" || candidate.path.length === 0) {
483
+ return bindingError(
484
+ "engine-binding-path-missing",
485
+ "engine-binding.json to contain one non-empty local Engine path",
486
+ "Run forgeax engine use-local <engine-directory>.",
487
+ { path }
488
+ );
489
+ }
490
+ return {
491
+ ok: true,
492
+ value: {
493
+ schemaVersion: ENGINE_BINDING_SCHEMA_VERSION,
494
+ path: resolve(candidate.path)
495
+ }
496
+ };
497
+ }
498
+ async function readEngineBinding(rootInput = process.cwd()) {
499
+ const root = resolve(rootInput);
500
+ const path = bindingFile(root);
501
+ try {
502
+ const value = JSON.parse(await readFile(path, "utf8"));
503
+ return parseBinding(value, path);
504
+ } catch (cause) {
505
+ if (isMissing(cause)) return { ok: true, value: null };
506
+ return bindingError(
507
+ "engine-binding-unreadable",
508
+ "engine-binding.json to be readable JSON",
509
+ "Repair or remove .forgeax/engine-binding.json, then select a binding again.",
510
+ { path, reason: cause instanceof Error ? cause.message : String(cause) }
511
+ );
512
+ }
513
+ }
514
+ async function writeEngineBinding(root, binding) {
515
+ const path = bindingFile(root);
516
+ const partial = `${path}.partial-${process.pid}`;
517
+ await mkdir(dirname(path), { recursive: true });
518
+ await writeFile(partial, `${JSON.stringify(binding, null, 2)}
519
+ `, "utf8");
520
+ await rename(partial, path);
521
+ }
522
+ function conditionalExport(value) {
523
+ if (typeof value === "string") return value;
524
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return void 0;
525
+ const record = value;
526
+ for (const key of ["import", "browser", "node", "default", "require", "types"]) {
527
+ const candidate = record[key];
528
+ const selected = conditionalExport(candidate);
529
+ if (selected !== void 0) return selected;
530
+ }
531
+ return void 0;
532
+ }
533
+ function packageEntry(manifest) {
534
+ const exportsValue = manifest.exports;
535
+ if (exportsValue !== void 0) {
536
+ if (typeof exportsValue === "object" && exportsValue !== null && !Array.isArray(exportsValue)) {
537
+ const root = exportsValue["."];
538
+ const selected2 = conditionalExport(root ?? exportsValue);
539
+ if (selected2 !== void 0) return selected2;
540
+ }
541
+ const selected = conditionalExport(exportsValue);
542
+ if (selected !== void 0) return selected;
543
+ }
544
+ return typeof manifest.module === "string" ? manifest.module : typeof manifest.main === "string" ? manifest.main : void 0;
545
+ }
546
+ async function readManifest(path) {
547
+ const value = JSON.parse(await readFile(path, "utf8"));
548
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
549
+ throw new Error(`engine-package-manifest-invalid: ${path}`);
550
+ }
551
+ return value;
552
+ }
553
+ async function inspectEngineWorkspace(workspaceInput) {
554
+ const root = resolve(workspaceInput);
555
+ const packageRoot = resolve(root, "packages");
556
+ try {
557
+ await access(resolve(root, "pnpm-workspace.yaml"));
558
+ const entries2 = await readdir(packageRoot, { withFileTypes: true });
559
+ const packages = [];
560
+ for (const entry of entries2) {
561
+ if (!entry.isDirectory()) continue;
562
+ const packagePath = resolve(packageRoot, entry.name);
563
+ let manifest;
564
+ try {
565
+ manifest = await readManifest(resolve(packagePath, "package.json"));
566
+ } catch {
567
+ continue;
568
+ }
569
+ if (typeof manifest.name !== "string" || !manifest.name.startsWith("@forgeax/engine"))
570
+ continue;
571
+ if (typeof manifest.version !== "string") {
572
+ return bindingError(
573
+ "engine-package-version-missing",
574
+ "every local Engine package to declare a version",
575
+ "Restore the package manifest version before selecting a local Engine.",
576
+ { package: manifest.name, root: packagePath }
577
+ );
578
+ }
579
+ const target = packageEntry(manifest);
580
+ const entryPath = target === void 0 ? void 0 : resolve(packagePath, target);
581
+ let entryDigest = null;
582
+ let builtAt2 = null;
583
+ if (entryPath !== void 0 && await pathExists(entryPath)) {
584
+ const [bytes, metadata] = await Promise.all([readFile(entryPath), stat(entryPath)]);
585
+ entryDigest = `sha256:${createHash("sha256").update(bytes).digest("hex")}`;
586
+ builtAt2 = metadata.mtime.toISOString();
587
+ }
588
+ packages.push({
589
+ name: manifest.name,
590
+ root: packagePath,
591
+ version: manifest.version,
592
+ entry: entryPath ?? null,
593
+ built: entryDigest !== null,
594
+ entryDigest,
595
+ builtAt: builtAt2
596
+ });
597
+ }
598
+ packages.sort((a, b) => a.name.localeCompare(b.name));
599
+ if (packages.length === 0) {
600
+ return bindingError(
601
+ "engine-workspace-empty",
602
+ "the local Engine workspace to contain @forgeax/engine packages",
603
+ "Pass the Engine repository or its source/engine SDK directory.",
604
+ { root, packageRoot }
605
+ );
606
+ }
607
+ const versions = [...new Set(packages.map((item) => item.version))].sort();
608
+ const missingBuilds = packages.filter((item) => !item.built).map((item) => item.name);
609
+ const builtAt = packages.flatMap((item) => item.builtAt === null ? [] : [item.builtAt]).sort().at(-1) ?? null;
610
+ const digest = `sha256:${createHash("sha256").update(
611
+ packages.map((item) => `${item.name}\0${item.version}\0${item.entryDigest ?? "unbuilt"}`).join("\n")
612
+ ).digest("hex")}`;
613
+ return {
614
+ ok: true,
615
+ value: {
616
+ root,
617
+ packageCount: packages.length,
618
+ builtPackages: packages.length - missingBuilds.length,
619
+ missingBuilds,
620
+ versions,
621
+ packages,
622
+ digest,
623
+ builtAt
624
+ }
625
+ };
626
+ } catch (cause) {
627
+ return bindingError(
628
+ isMissing(cause) ? "engine-workspace-missing" : "engine-workspace-unreadable",
629
+ "a readable Engine workspace with pnpm-workspace.yaml and packages/",
630
+ "Pass the Engine repository root, not its packages/ directory.",
631
+ { root, packageRoot, reason: cause instanceof Error ? cause.message : String(cause) }
632
+ );
633
+ }
634
+ }
635
+ function projectDependencyMode(packageJson) {
636
+ let workspace = false;
637
+ let registry = false;
638
+ for (const section of [
639
+ "dependencies",
640
+ "devDependencies",
641
+ "optionalDependencies",
642
+ "peerDependencies"
643
+ ]) {
644
+ const value = packageJson[section];
645
+ if (value === null || typeof value !== "object" || Array.isArray(value)) continue;
646
+ for (const version of Object.values(value)) {
647
+ if (typeof version !== "string") continue;
648
+ if (version.startsWith("workspace:") || version.startsWith("file:")) workspace = true;
649
+ else registry = true;
650
+ }
651
+ }
652
+ return workspace ? registry ? "mixed" : "workspace" : registry ? "registry" : "none";
653
+ }
654
+ async function sdkResolution(root) {
655
+ const packageJson = resolve(root, "node_modules", "@forgeax", "engine", "package.json");
656
+ try {
657
+ const manifest = await readManifest(packageJson);
658
+ const target = packageEntry(manifest);
659
+ const entry = target === void 0 ? null : resolve(dirname(packageJson), target);
660
+ return {
661
+ root: dirname(packageJson),
662
+ version: typeof manifest.version === "string" ? manifest.version : null,
663
+ entry,
664
+ built: entry !== null && await pathExists(entry),
665
+ source: "sdk"
666
+ };
667
+ } catch {
668
+ return { root: null, version: null, entry: null, built: false, source: "unresolved" };
669
+ }
670
+ }
671
+ async function engineStatusCommand(options = {}) {
672
+ const facts = await readProjectFacts(options.root);
673
+ if (!facts.ok) return facts;
674
+ const bindingResult = await readEngineBinding(facts.value.root);
675
+ if (!bindingResult.ok) return bindingResult;
676
+ const binding = bindingResult.value;
677
+ const mode = binding === null ? "sdk" : "local";
678
+ const projectDependencies = projectDependencyMode(facts.value.packageJson);
679
+ if (binding !== null) {
680
+ const localPath = binding.path;
681
+ const localBinding = {
682
+ schemaVersion: ENGINE_BINDING_SCHEMA_VERSION,
683
+ path: localPath
684
+ };
685
+ const workspaceResult = await inspectEngineWorkspace(localPath);
686
+ if (!workspaceResult.ok) {
687
+ const resolved3 = {
688
+ root: localPath,
689
+ version: null,
690
+ entry: null,
691
+ built: false,
692
+ source: "local"
693
+ };
694
+ return {
695
+ ok: true,
696
+ value: {
697
+ schemaVersion: ENGINE_BINDING_SCHEMA_VERSION,
698
+ root: facts.value.root,
699
+ binding: localBinding,
700
+ mode,
701
+ resolved: resolved3,
702
+ workspace: null,
703
+ projectDependencies,
704
+ npmCompatible: projectDependencies === "registry" || projectDependencies === "none",
705
+ healthy: false,
706
+ next: ["forgeax engine unlink", "forgeax engine use-local <engine-directory>"],
707
+ diagnostic: workspaceResult.error
708
+ }
709
+ };
710
+ }
711
+ const workspace = workspaceResult.value;
712
+ const version = workspace.versions.length === 1 ? workspace.versions[0] ?? null : null;
713
+ const umbrella = workspace.packages.find((item) => item.name === "@forgeax/engine");
714
+ const resolved2 = {
715
+ root: localPath,
716
+ version,
717
+ entry: umbrella?.entry ?? null,
718
+ built: umbrella?.built ?? false,
719
+ source: "local"
720
+ };
721
+ const healthy = workspace.versions.length === 1 && workspace.missingBuilds.length === 0;
722
+ return {
723
+ ok: true,
724
+ value: {
725
+ schemaVersion: ENGINE_BINDING_SCHEMA_VERSION,
726
+ root: facts.value.root,
727
+ binding: localBinding,
728
+ mode,
729
+ resolved: resolved2,
730
+ workspace,
731
+ projectDependencies,
732
+ npmCompatible: projectDependencies === "registry" || projectDependencies === "none",
733
+ healthy,
734
+ next: healthy ? ["forgeax build", "forgeax capture --backend auto --require-ui"] : ["pnpm build:engine", "forgeax engine doctor"]
735
+ }
736
+ };
737
+ }
738
+ const resolved = await sdkResolution(facts.value.root);
739
+ return {
740
+ ok: true,
741
+ value: {
742
+ schemaVersion: ENGINE_BINDING_SCHEMA_VERSION,
743
+ root: facts.value.root,
744
+ binding,
745
+ mode,
746
+ resolved,
747
+ workspace: null,
748
+ projectDependencies,
749
+ npmCompatible: projectDependencies === "registry" || projectDependencies === "none",
750
+ healthy: resolved.source === "sdk" && resolved.built,
751
+ next: resolved.source === "sdk" && resolved.built ? ["forgeax build", "forgeax capture --backend auto --require-ui"] : ["pnpm install", "forgeax doctor"]
752
+ }
753
+ };
754
+ }
755
+ async function engineUseLocalCommand(options) {
756
+ const root = resolve(options.root ?? process.cwd());
757
+ if (options.path === void 0 || options.path.trim().length === 0) {
758
+ return bindingError(
759
+ "engine-binding-path-missing",
760
+ "forgeax engine use-local <engine-directory>",
761
+ "Pass the local Engine repository or SDK source directory."
762
+ );
763
+ }
764
+ let localPath;
765
+ try {
766
+ localPath = await realpath(resolve(root, options.path));
767
+ } catch (cause) {
768
+ return bindingError(
769
+ "engine-workspace-missing",
770
+ "the local Engine directory to exist",
771
+ "Pass an existing Engine repository or SDK source directory.",
772
+ {
773
+ path: resolve(root, options.path),
774
+ reason: cause instanceof Error ? cause.message : String(cause)
775
+ }
776
+ );
777
+ }
778
+ const workspace = await inspectEngineWorkspace(localPath);
779
+ if (!workspace.ok) return workspace;
780
+ const binding = {
781
+ schemaVersion: ENGINE_BINDING_SCHEMA_VERSION,
782
+ path: localPath
783
+ };
784
+ if (options.dryRun !== true) await writeEngineBinding(root, binding);
785
+ const status = await engineStatusCommand({ root });
786
+ if (!status.ok) return status;
787
+ return {
788
+ ok: true,
789
+ value: {
790
+ ...status.value,
791
+ next: workspace.value.missingBuilds.length === 0 ? ["forgeax build", "forgeax capture --backend auto --require-ui"] : ["pnpm build:engine", "forgeax engine doctor"]
792
+ }
793
+ };
794
+ }
795
+ async function engineUnlinkCommand(options = {}) {
796
+ const root = resolve(options.root ?? process.cwd());
797
+ if (options.dryRun !== true) await rm(bindingFile(root), { force: true });
798
+ return engineStatusCommand({ root });
799
+ }
800
+ async function engineDoctorCommand(options = {}) {
801
+ const status = await engineStatusCommand(options);
802
+ if (!status.ok) return status;
803
+ if (status.value.projectDependencies === "workspace" || status.value.projectDependencies === "mixed") {
804
+ return {
805
+ ok: false,
806
+ error: {
807
+ code: "engine-project-workspace-dependency",
808
+ expected: "the game project to use registry or SDK-resolved dependencies for npm consumers",
809
+ hint: "This project is a pnpm workspace; use pnpm here, or create a clean SDK game project for npm install.",
810
+ detail: { root: status.value.root, projectDependencies: status.value.projectDependencies }
811
+ }
812
+ };
813
+ }
814
+ if (status.value.diagnostic !== void 0) return { ok: false, error: status.value.diagnostic };
815
+ if (!status.value.healthy) {
816
+ return {
817
+ ok: false,
818
+ error: {
819
+ code: status.value.mode === "local" ? "engine-local-build-missing" : "engine-sdk-unresolved",
820
+ expected: "the selected Engine binding to resolve to built packages",
821
+ hint: status.value.mode === "local" ? "Run pnpm build:engine in the selected Engine checkout, then rerun forgeax engine doctor." : "Run pnpm install in the game project or select a valid local Engine checkout.",
822
+ detail: { status: status.value }
823
+ }
824
+ };
825
+ }
826
+ return status;
302
827
  }
303
- var init_dist = __esm({
304
- "src/dist.ts"() {
828
+ function engineBindingFilePath(rootInput = process.cwd()) {
829
+ return bindingFile(resolve(rootInput));
830
+ }
831
+ var ENGINE_BINDING_SCHEMA_VERSION, bindingFile;
832
+ var init_engine_binding = __esm({
833
+ "src/engine-binding.ts"() {
834
+ init_project();
835
+ ENGINE_BINDING_SCHEMA_VERSION = "1.0.0";
836
+ bindingFile = (root) => resolve(root, ".forgeax", "engine-binding.json");
305
837
  }
306
838
  });
307
839
 
@@ -373,40 +905,33 @@ function findEngineWorkspaceRoot() {
373
905
  cursor = parent;
374
906
  }
375
907
  }
376
- async function engineWorkspacePackages() {
377
- if (engineWorkspacePackagesPromise !== void 0) return engineWorkspacePackagesPromise;
378
- engineWorkspacePackagesPromise = (async () => {
379
- const workspaceRoot = findEngineWorkspaceRoot();
380
- if (workspaceRoot === void 0) return /* @__PURE__ */ new Map();
381
- const packageRoot = resolve(workspaceRoot, "packages");
382
- if (!existsSync(packageRoot)) return /* @__PURE__ */ new Map();
383
- const packages = /* @__PURE__ */ new Map();
384
- for (const entry of await readdir(packageRoot, { withFileTypes: true })) {
385
- if (!entry.isDirectory()) continue;
386
- const root = resolve(packageRoot, entry.name);
387
- try {
388
- const manifest = JSON.parse(
389
- await readFile(resolve(root, "package.json"), "utf8")
390
- );
391
- if (manifest === null || typeof manifest !== "object") continue;
392
- const name = manifest.name;
393
- if (typeof name === "string" && name.startsWith("@forgeax/engine-")) {
394
- packages.set(name, { root, manifest });
395
- }
396
- } catch {
908
+ async function engineWorkspacePackages(workspaceRoot = findEngineWorkspaceRoot()) {
909
+ if (workspaceRoot === void 0) return /* @__PURE__ */ new Map();
910
+ const packageRoot = resolve(workspaceRoot, "packages");
911
+ if (!existsSync(packageRoot)) return /* @__PURE__ */ new Map();
912
+ const packages = /* @__PURE__ */ new Map();
913
+ for (const entry of await readdir(packageRoot, { withFileTypes: true })) {
914
+ if (!entry.isDirectory()) continue;
915
+ const root = resolve(packageRoot, entry.name);
916
+ try {
917
+ const manifest = JSON.parse(await readFile(resolve(root, "package.json"), "utf8"));
918
+ if (manifest === null || typeof manifest !== "object") continue;
919
+ const name = manifest.name;
920
+ if (typeof name === "string" && name.startsWith("@forgeax/engine")) {
921
+ packages.set(name, { root, manifest });
397
922
  }
923
+ } catch {
398
924
  }
399
- return packages;
400
- })();
401
- return engineWorkspacePackagesPromise;
925
+ }
926
+ return packages;
402
927
  }
403
- function conditionalExport(value) {
928
+ function conditionalExport2(value) {
404
929
  if (typeof value === "string") return value;
405
930
  if (value === null || Array.isArray(value)) return void 0;
406
931
  for (const condition of ["browser", "import", "node", "default"]) {
407
932
  const candidate = value[condition];
408
933
  if (candidate === void 0) continue;
409
- const selected = conditionalExport(candidate);
934
+ const selected = conditionalExport2(candidate);
410
935
  if (selected !== void 0) return selected;
411
936
  }
412
937
  return void 0;
@@ -419,14 +944,14 @@ function packageExportTarget(manifest, subpath) {
419
944
  return typeof main === "string" ? main : void 0;
420
945
  }
421
946
  if (typeof exportsValue === "string" || exportsValue === null) {
422
- return subpath.length === 0 ? conditionalExport(exportsValue) : void 0;
947
+ return subpath.length === 0 ? conditionalExport2(exportsValue) : void 0;
423
948
  }
424
949
  const keys = Object.keys(exportsValue);
425
950
  const subpathMap = keys.some((key) => key === "." || key.startsWith("./"));
426
- if (!subpathMap) return subpath.length === 0 ? conditionalExport(exportsValue) : void 0;
951
+ if (!subpathMap) return subpath.length === 0 ? conditionalExport2(exportsValue) : void 0;
427
952
  const requested = subpath.length === 0 ? "." : `./${subpath}`;
428
953
  const exact = exportsValue[requested];
429
- if (exact !== void 0) return conditionalExport(exact);
954
+ if (exact !== void 0) return conditionalExport2(exact);
430
955
  for (const key of keys) {
431
956
  const marker = key.indexOf("*");
432
957
  if (marker < 0) continue;
@@ -436,14 +961,14 @@ function packageExportTarget(manifest, subpath) {
436
961
  const replacement = requested.slice(prefix.length, requested.length - suffix.length);
437
962
  const exportTarget = exportsValue[key];
438
963
  if (exportTarget === void 0) continue;
439
- const selected = conditionalExport(exportTarget);
964
+ const selected = conditionalExport2(exportTarget);
440
965
  return selected?.replaceAll("*", replacement);
441
966
  }
442
967
  return void 0;
443
968
  }
444
969
  function engineWorkspaceImport(source, packages) {
445
- if (!source.startsWith("@forgeax/engine-")) return void 0;
446
- const separator = source.indexOf("/", "@forgeax/engine-".length);
970
+ if (!source.startsWith("@forgeax/engine")) return void 0;
971
+ const separator = source.indexOf("/", "@forgeax/engine".length);
447
972
  const packageName = separator < 0 ? source : source.slice(0, separator);
448
973
  const subpath = separator < 0 ? "" : source.slice(separator + 1);
449
974
  const packageInfo = packages.get(packageName);
@@ -457,14 +982,25 @@ function engineWorkspaceImport(source, packages) {
457
982
  }
458
983
  return absolute;
459
984
  }
460
- async function createEngineWorkspaceResolver() {
461
- const packages = await engineWorkspacePackages();
985
+ async function createEngineWorkspaceResolver(projectRoot) {
986
+ const binding = await readEngineBinding(projectRoot);
987
+ if (!binding.ok) {
988
+ throw new Error(`${binding.error.code}: ${binding.error.hint}`);
989
+ }
990
+ const localRoot = binding.value?.path;
991
+ if (localRoot !== void 0) {
992
+ const inspected = await inspectEngineWorkspace(localRoot);
993
+ if (!inspected.ok) throw new Error(`${inspected.error.code}: ${inspected.error.hint}`);
994
+ }
995
+ const packages = await engineWorkspacePackages(localRoot);
462
996
  if (packages.size === 0) return void 0;
463
997
  return {
464
998
  name: "forgeax:devkit-engine-workspace-resolver",
999
+ enforce: "pre",
465
1000
  async resolveId(source, importer) {
466
1001
  const bareSource = source.split("?", 1)[0] ?? source;
467
- if (!bareSource.startsWith("@forgeax/engine-")) return null;
1002
+ if (!bareSource.startsWith("@forgeax/engine")) return null;
1003
+ if (localRoot !== void 0) return engineWorkspaceImport(bareSource, packages);
468
1004
  try {
469
1005
  const resolved = await this.resolve(source, importer, { skipSelf: true });
470
1006
  if (resolved !== null) return resolved;
@@ -716,7 +1252,11 @@ function prepareAssetRegistry(assets) {
716
1252
  const existing = assetPreparation.get(assets);
717
1253
  if (existing !== undefined) return existing;
718
1254
  const pending = (async () => {
719
- assets.configureRuntimeBinding(runtimeScopeBinding);
1255
+ if (import.meta.env.DEV) {
1256
+ assets.configureRuntimeBinding(runtimeScopeBinding);
1257
+ } else {
1258
+ assets.configurePackIndex(new URL('pack-index.json', document.baseURI).href);
1259
+ }
720
1260
  assets.setCatalogSource(assetCatalog);
721
1261
  if (vfxRuntimeHost !== undefined) {
722
1262
  assets.installDecoder(vfxGpuEffectContribution.kind, vfxGpuEffectContribution.decoder);
@@ -1169,250 +1709,139 @@ function htmlSource(title) {
1169
1709
  <div id="app-shell"><canvas id="app"></canvas><div id="game-ui"></div></div><div id="forgeax-fatal" role="alert"></div>
1170
1710
  <script>
1171
1711
  (() => {
1712
+ const appendStructuredFailure = (value, prefix, depth, seen, lines) => {
1713
+ if (value === null || typeof value !== 'object' || depth > 3) return;
1714
+ if (seen.has(value)) {
1715
+ lines.push((prefix || 'cause') + ': [circular]');
1716
+ return;
1717
+ }
1718
+ seen.add(value);
1719
+ const record = value;
1720
+ const name = typeof record.name === 'string' && record.name.length > 0
1721
+ ? record.name
1722
+ : undefined;
1723
+ const code = typeof record.code === 'string' && record.code.length > 0
1724
+ ? record.code
1725
+ : undefined;
1726
+ const message = typeof record.message === 'string' && record.message.length > 0
1727
+ ? record.message
1728
+ : undefined;
1729
+ if (name !== undefined || code !== undefined || message !== undefined) {
1730
+ const identity = [name || 'Error', code].filter(Boolean).join(' ');
1731
+ lines.push((prefix ? prefix + ': ' : '') + identity + (message ? ': ' + message : ''));
1732
+ }
1733
+ for (const key of ['expected', 'hint', 'reason']) {
1734
+ if (typeof record[key] === 'string' && record[key].length > 0) {
1735
+ lines.push((prefix ? prefix + '.' : '') + key + ': ' + record[key]);
1736
+ }
1737
+ }
1738
+ for (const key of ['cause', 'detail', 'webgpuError', 'wgpuError', 'error']) {
1739
+ const nested = record[key];
1740
+ const nestedPrefix = (prefix ? prefix + '.' : '') + key;
1741
+ if (nested !== null && typeof nested === 'object') {
1742
+ appendStructuredFailure(nested, nestedPrefix, depth + 1, seen, lines);
1743
+ } else if (typeof nested === 'string' && nested.length > 0) {
1744
+ lines.push(nestedPrefix + ': ' + nested);
1745
+ }
1746
+ }
1747
+ };
1172
1748
  const formatStartupFailure = (reason) => {
1173
- if (reason instanceof Error) return reason.message;
1174
1749
  if (reason !== null && typeof reason === 'object') {
1175
- const record = reason;
1176
1750
  const lines = [];
1177
- for (const key of ['code', 'expected', 'hint']) {
1178
- if (typeof record[key] === 'string' && record[key].length > 0) {
1179
- lines.push(key + ': ' + record[key]);
1180
- }
1181
- }
1182
- const detail = record.detail;
1183
- if (detail !== null && typeof detail === 'object') {
1184
- for (const key of ['reason', 'guid']) {
1185
- if (typeof detail[key] === 'string' && detail[key].length > 0) {
1186
- lines.push('detail.' + key + ': ' + detail[key]);
1187
- }
1188
- }
1189
- }
1751
+ appendStructuredFailure(reason, '', 0, new Set(), lines);
1190
1752
  if (lines.length > 0) return lines.join('\\n');
1753
+ try {
1754
+ return JSON.stringify(reason) || 'Unknown structured startup failure';
1755
+ } catch {
1756
+ return 'Unserializable structured startup failure';
1757
+ }
1191
1758
  }
1192
1759
  return String(reason ?? 'Unknown startup failure');
1193
1760
  };
1194
1761
  const show = (reason) => {
1195
1762
  const notice = document.querySelector('#forgeax-fatal');
1196
1763
  if (!(notice instanceof HTMLElement)) return;
1197
- const message = formatStartupFailure(reason);
1764
+ let message = formatStartupFailure(reason);
1765
+ if (/webgpu|adapter-unavailable|no usable (rendering )?backend/i.test(message)) {
1766
+ message += '\\n\\nRenderer diagnosis: ForgeaX supports browser WebGPU and a wgpu/WebGL2 fallback. This failure alone does not prove that WebGPU is unsupported; use the structured code, hint, and nested backend causes above.';
1767
+ }
1198
1768
  notice.textContent = 'ForgeaX game failed to start.\\n' + message;
1199
1769
  notice.style.display = 'grid';
1200
1770
  };
1201
1771
  window.addEventListener('error', (event) => show(event.error ?? event.message));
1202
1772
  window.addEventListener('unhandledrejection', (event) => show(event.reason));
1203
- })();
1204
- </script>
1205
- <script type="module" src="./main.ts"></script>
1206
- </body>
1207
- </html>
1208
- `;
1209
- }
1210
- async function createViteConfig(facts, command, base = "/", options = {}) {
1211
- const bootstrapRoot = options.bootstrapRoot ?? "project-bootstrap";
1212
- const canonicalKit = await resolveCanonicalKit();
1213
- const generated = resolve(facts.root, ".forgeax", "generated");
1214
- await mkdir(generated, { recursive: true });
1215
- const projectRoots = facts.assetRoots.map((root) => resolve(facts.root, root));
1216
- const ignorePath = bootstrapRoot === "resource-bootstrap" ? isResourcePreviewIgnoredPath : isProjectSourceIgnoredPath;
1217
- const builtinPack = await prepareBuiltinPack(projectRoots, generated, ignorePath);
1218
- await Promise.all([
1219
- writeFile(resolve(generated, "index.html"), htmlSource(facts.name)),
1220
- writeFile(resolve(generated, "main.ts"), hostSource(facts, bootstrapRoot, canonicalKit?.guid))
1221
- ]);
1222
- const roots = [
1223
- ...projectRoots,
1224
- ...builtinPack === void 0 ? [] : [builtinPack],
1225
- ...canonicalKit === void 0 ? [] : [canonicalKit.root]
1226
- ];
1227
- const importers = [...projectImporters(facts)];
1228
- const runtimeBinding = createStandaloneRuntimeAssetBinding(facts.id);
1229
- const engineWorkspaceResolver = await createEngineWorkspaceResolver();
1230
- const consumerAliases = await consumerEngineAliases(facts.root);
1231
- const plugins = [
1232
- ...engineWorkspaceResolver === void 0 ? [] : [engineWorkspaceResolver],
1233
- forgeaxShader(),
1234
- pluginPack({
1235
- roots,
1236
- runtimeBinding,
1237
- ddc: devKitDdcRoots(facts.root),
1238
- refresh: command === "serve" ? reloadAssetHost() : void 0,
1239
- importers: [
1240
- audioImporter,
1241
- imageImporter,
1242
- fbxImporter,
1243
- gltfImporter,
1244
- fontImporter,
1245
- ...importers
1246
- ],
1247
- cookers: [createMaterialPackCooker(roots), createParticleCodeNativeCookerFromRoots(roots)],
1248
- ignorePath
1249
- })
1250
- ];
1251
- return {
1252
- root: generated,
1253
- base,
1254
- configFile: false,
1255
- publicDir: facts.assetPublicDir === void 0 ? false : resolve(facts.root, facts.assetPublicDir),
1256
- plugins,
1257
- resolve: {
1258
- alias: consumerAliases,
1259
- dedupe: ["@forgeax/engine"]
1260
- },
1261
- server: { ...options.server, fs: { allow: [facts.root, ...roots] } },
1262
- build: {
1263
- target: "esnext",
1264
- outDir: options.outDir ?? resolve(facts.root, "dist"),
1265
- emptyOutDir: true,
1266
- rollupOptions: { input: resolve(generated, "index.html") }
1267
- }
1268
- };
1269
- }
1270
- var hostRequire, engineWorkspacePackagesPromise;
1271
- var init_host = __esm({
1272
- "src/host.ts"() {
1273
- hostRequire = createRequire(import.meta.url);
1274
- }
1275
- });
1276
- function projectError(code, expected, hint, detail) {
1277
- return { ok: false, error: { code, expected, hint, detail } };
1278
- }
1279
- async function readJson(path) {
1280
- return JSON.parse(await readFile(path, "utf8"));
1281
- }
1282
- function firstUnsupportedStandaloneRealm(entries2, inheritedRealm = "engine") {
1283
- for (const entry of entries2) {
1284
- const realm = entry.realm ?? inheritedRealm ?? "engine";
1285
- if (realm !== "engine") return { id: entry.id, realm };
1286
- if (entry.group === true) {
1287
- const unsupported = firstUnsupportedStandaloneRealm(
1288
- entry.config,
1289
- realm
1290
- );
1291
- if (unsupported !== void 0) return unsupported;
1292
- }
1293
- }
1294
- return void 0;
1295
- }
1296
- function pluginModuleNames(entries2) {
1297
- return entries2.flatMap(
1298
- (entry) => entry.group === true ? pluginModuleNames(entry.config) : [entry.name]
1299
- );
1300
- }
1301
- async function readProjectFacts(rootInput = process.cwd()) {
1302
- const root = resolve(rootInput);
1303
- let forgeValue;
1304
- let packageValue;
1305
- try {
1306
- [forgeValue, packageValue] = await Promise.all([
1307
- readJson(resolve(root, "forge.json")),
1308
- readJson(resolve(root, "package.json"))
1309
- ]);
1310
- } catch (cause) {
1311
- return projectError(
1312
- "project-manifest-unreadable",
1313
- "readable forge.json and package.json files",
1314
- "Run the command from a ForgeaX game root or pass its directory.",
1315
- { root, reason: cause instanceof Error ? cause.message : String(cause) }
1316
- );
1317
- }
1318
- if (forgeValue === null || typeof forgeValue !== "object") {
1319
- return projectError(
1320
- "project-manifest-invalid",
1321
- "forge.json to contain an object",
1322
- "Repair forge.json before running DevKit.",
1323
- { root }
1324
- );
1325
- }
1326
- if (packageValue === null || typeof packageValue !== "object") {
1327
- return projectError(
1328
- "package-manifest-invalid",
1329
- "package.json to contain an object",
1330
- "Repair package.json before running DevKit.",
1331
- { root }
1332
- );
1333
- }
1334
- const parsedForge = GameProjectSchema.safeParse(forgeValue);
1335
- if (!parsedForge.success) {
1336
- return projectError(
1337
- "project-manifest-invalid",
1338
- "forge.json to satisfy @forgeax/engine-project GameProjectSchema",
1339
- "Repair the fields reported by the authoritative project schema.",
1340
- { root, issues: parsedForge.error.issues }
1341
- );
1342
- }
1343
- const forge = parsedForge.data;
1344
- if (forge.id.length === 0 || forge.name.length === 0 || forge.entry === void 0 || forge.entry.length === 0) {
1345
- return projectError(
1346
- "project-manifest-invalid",
1347
- "forge.json to declare id, name, and entry",
1348
- "Restore the project entry; plugin Entries remain optional additions.",
1349
- { root }
1350
- );
1351
- }
1352
- const plugins = forge.plugins ?? [];
1353
- const unsupportedRealm = firstUnsupportedStandaloneRealm(plugins);
1354
- if (unsupportedRealm !== void 0) {
1355
- return projectError(
1356
- "project-plugin-realm-unsupported",
1357
- "the standalone Devkit host to contain only engine-realm plugin Entries",
1358
- "Move Host or build plugins to a host that owns that physical realm.",
1359
- { root, ...unsupportedRealm }
1360
- );
1361
- }
1362
- const entryPath = isAbsolute(forge.entry) ? forge.entry : resolve(root, forge.entry);
1363
- try {
1364
- await readFile(entryPath);
1365
- } catch {
1366
- return projectError(
1367
- "project-entry-missing",
1368
- "forge.json#entry to resolve to a readable module",
1369
- "Restore the game entry or update forge.json#entry.",
1370
- { root, entry: forge.entry }
1371
- );
1372
- }
1373
- const packageJson = packageValue;
1374
- const forgeax = packageJson.forgeax;
1375
- const configuredRoots = forgeax !== null && typeof forgeax === "object" ? forgeax.assets?.roots : void 0;
1376
- const assetRoots = Array.isArray(configuredRoots) && configuredRoots.every((value) => typeof value === "string") ? configuredRoots : ["assets"];
1377
- const configuredImporters = forgeax !== null && typeof forgeax === "object" ? forgeax.assets?.importers : void 0;
1378
- const assetImporters = Array.isArray(configuredImporters) && configuredImporters.every((value) => typeof value === "string") ? configuredImporters : [];
1379
- const configuredPublicDir = forgeax !== null && typeof forgeax === "object" ? forgeax.assets?.publicDir : void 0;
1380
- const assetPublicDir = typeof configuredPublicDir === "string" ? configuredPublicDir : void 0;
1381
- const physics = forge.physics === "2d" || forge.physics === "3d" ? forge.physics : void 0;
1382
- const defaultScene = typeof forge.defaultScene === "string" && forge.defaultScene.length > 0 ? forge.defaultScene : void 0;
1383
- const normalizedEntry = forge.entry.startsWith("./") ? forge.entry : `./${forge.entry}`;
1384
- const bootstrapEntry = pluginModuleNames(plugins).map((name) => name.startsWith("./") ? name : `./${name}`).includes(normalizedEntry) ? void 0 : forge.entry;
1385
- return {
1386
- ok: true,
1387
- value: {
1388
- root,
1389
- id: forge.id,
1390
- name: forge.name,
1391
- entry: forge.entry,
1392
- ...bootstrapEntry === void 0 ? {} : { bootstrapEntry },
1393
- plugins,
1394
- ...physics === void 0 ? {} : { physics },
1395
- ...defaultScene === void 0 ? {} : { defaultScene },
1396
- assetRoots,
1397
- ...assetImporters.length === 0 ? {} : { assetImporters },
1398
- ...assetPublicDir === void 0 ? {} : { assetPublicDir },
1399
- packageJson
1400
- }
1401
- };
1773
+ })();
1774
+ </script>
1775
+ <script type="module" src="./main.ts"></script>
1776
+ </body>
1777
+ </html>
1778
+ `;
1402
1779
  }
1403
- function commandError(cause, fallbackCode) {
1404
- if (cause !== null && typeof cause === "object" && "code" in cause && "expected" in cause && "hint" in cause && "detail" in cause) {
1405
- return cause;
1406
- }
1780
+ async function createViteConfig(facts, command, base = "/", options = {}) {
1781
+ const bootstrapRoot = options.bootstrapRoot ?? "project-bootstrap";
1782
+ const canonicalKit = await resolveCanonicalKit();
1783
+ const generated = resolve(facts.root, ".forgeax", "generated");
1784
+ await mkdir(generated, { recursive: true });
1785
+ const projectRoots = facts.assetRoots.map((root) => resolve(facts.root, root));
1786
+ const ignorePath = bootstrapRoot === "resource-bootstrap" ? isResourcePreviewIgnoredPath : isProjectSourceIgnoredPath;
1787
+ const builtinPack = await prepareBuiltinPack(projectRoots, generated, ignorePath);
1788
+ await Promise.all([
1789
+ writeFile(resolve(generated, "index.html"), htmlSource(facts.name)),
1790
+ writeFile(resolve(generated, "main.ts"), hostSource(facts, bootstrapRoot, canonicalKit?.guid))
1791
+ ]);
1792
+ const roots = [
1793
+ ...projectRoots,
1794
+ ...builtinPack === void 0 ? [] : [builtinPack],
1795
+ ...canonicalKit === void 0 ? [] : [canonicalKit.root]
1796
+ ];
1797
+ const importers = [...projectImporters(facts)];
1798
+ const runtimeBinding = createStandaloneRuntimeAssetBinding(facts.id);
1799
+ const engineWorkspaceResolver = await createEngineWorkspaceResolver(facts.root);
1800
+ const consumerAliases = await consumerEngineAliases(facts.root);
1801
+ const plugins = [
1802
+ ...engineWorkspaceResolver === void 0 ? [] : [engineWorkspaceResolver],
1803
+ forgeaxShader(),
1804
+ pluginPack({
1805
+ roots,
1806
+ runtimeBinding,
1807
+ ddc: devKitDdcRoots(facts.root),
1808
+ refresh: command === "serve" ? reloadAssetHost() : void 0,
1809
+ importers: [
1810
+ audioImporter,
1811
+ imageImporter,
1812
+ fbxImporter,
1813
+ gltfImporter,
1814
+ fontImporter,
1815
+ ...importers
1816
+ ],
1817
+ cookers: [createMaterialPackCooker(roots), createParticleCodeNativeCookerFromRoots(roots)],
1818
+ ignorePath
1819
+ })
1820
+ ];
1407
1821
  return {
1408
- code: fallbackCode,
1409
- expected: "the ForgeaX command to complete",
1410
- hint: "Inspect the underlying diagnostic and repair the owning input.",
1411
- detail: { reason: cause instanceof Error ? cause.message : String(cause) }
1822
+ root: generated,
1823
+ base,
1824
+ configFile: false,
1825
+ publicDir: facts.assetPublicDir === void 0 ? false : resolve(facts.root, facts.assetPublicDir),
1826
+ plugins,
1827
+ resolve: {
1828
+ alias: consumerAliases,
1829
+ dedupe: ["@forgeax/engine"]
1830
+ },
1831
+ server: { ...options.server, fs: { allow: [facts.root, ...roots] } },
1832
+ build: {
1833
+ target: "esnext",
1834
+ outDir: options.outDir ?? resolve(facts.root, "dist"),
1835
+ emptyOutDir: true,
1836
+ rollupOptions: { input: resolve(generated, "index.html") }
1837
+ }
1412
1838
  };
1413
1839
  }
1414
- var init_project = __esm({
1415
- "src/project.ts"() {
1840
+ var hostRequire;
1841
+ var init_host = __esm({
1842
+ "src/host.ts"() {
1843
+ init_engine_binding();
1844
+ hostRequire = createRequire(import.meta.url);
1416
1845
  }
1417
1846
  });
1418
1847
 
@@ -1666,6 +2095,7 @@ var init_package = __esm({
1666
2095
  "@forgeax/engine-project": "workspace:*",
1667
2096
  "@forgeax/engine-rhi-debug": "workspace:*",
1668
2097
  "@forgeax/engine-rhi-null": "workspace:*",
2098
+ "@forgeax/engine-rhi-webgpu": "workspace:*",
1669
2099
  "@forgeax/engine-render": "workspace:*",
1670
2100
  "@forgeax/engine-render-graph": "workspace:*",
1671
2101
  "@forgeax/engine-runtime": "workspace:*",
@@ -1680,7 +2110,8 @@ var init_package = __esm({
1680
2110
  jiti: "1.21.7",
1681
2111
  playwright: "1.60.0",
1682
2112
  vite: "8.0.10",
1683
- vitest: "4.1.5"
2113
+ vitest: "4.0.18",
2114
+ webgpu: "^0.4.0"
1684
2115
  },
1685
2116
  devDependencies: {
1686
2117
  "@types/node": "^20.14.0"
@@ -1854,7 +2285,7 @@ var init_init = __esm({
1854
2285
  "@webgpu/types": "0.1.71",
1855
2286
  tsx: "4.23.1",
1856
2287
  typescript: "6.0.3",
1857
- vitest: "4.1.5"
2288
+ vitest: "4.0.18"
1858
2289
  };
1859
2290
  }
1860
2291
  });
@@ -1921,6 +2352,7 @@ function sdkProjectInstallArgs(store) {
1921
2352
  "install",
1922
2353
  "--frozen-lockfile",
1923
2354
  "--ignore-scripts",
2355
+ "--config.pm-on-fail=ignore",
1924
2356
  "--side-effects-cache=true",
1925
2357
  "--child-concurrency=1"
1926
2358
  ];
@@ -1930,6 +2362,7 @@ function sdkBootstrapInstallArgs(store) {
1930
2362
  const common = [
1931
2363
  "install",
1932
2364
  "--frozen-lockfile",
2365
+ "--config.pm-on-fail=ignore",
1933
2366
  "--child-concurrency=1",
1934
2367
  "--side-effects-cache=true"
1935
2368
  ];
@@ -2841,7 +3274,7 @@ __export(plugin_authoring_exports, {
2841
3274
  pluginInstallCommand: () => pluginInstallCommand,
2842
3275
  pluginUninstallCommand: () => pluginUninstallCommand
2843
3276
  });
2844
- async function readManifest(root) {
3277
+ async function readManifest2(root) {
2845
3278
  const path = resolve(root, "forge.json");
2846
3279
  try {
2847
3280
  const raw = await readFile(path, "utf8");
@@ -2894,7 +3327,7 @@ async function mutateDependency(root, action, dependency) {
2894
3327
  }
2895
3328
  async function pluginInstallCommand(options) {
2896
3329
  const root = resolve(options.root ?? process.cwd());
2897
- const manifest = await readManifest(root);
3330
+ const manifest = await readManifest2(root);
2898
3331
  if (!manifest.ok) return manifest;
2899
3332
  const parsed = GameProjectSchema.safeParse(manifest.value.value);
2900
3333
  if (!parsed.success) {
@@ -2947,7 +3380,7 @@ async function pluginInstallCommand(options) {
2947
3380
  }
2948
3381
  async function pluginUninstallCommand(options) {
2949
3382
  const root = resolve(options.root ?? process.cwd());
2950
- const manifest = await readManifest(root);
3383
+ const manifest = await readManifest2(root);
2951
3384
  if (!manifest.ok) return manifest;
2952
3385
  const parsed = GameProjectSchema.safeParse(manifest.value.value);
2953
3386
  if (!parsed.success) {
@@ -3035,26 +3468,62 @@ function createCliRhiDebugOperationContext() {
3035
3468
  };
3036
3469
  }
3037
3470
  },
3038
- async createReplayBackend() {
3471
+ async createReplayBackend(tape) {
3472
+ let createDawn;
3473
+ let gpuGlobals;
3474
+ try {
3475
+ const dawn = await import('webgpu');
3476
+ createDawn = dawn.create;
3477
+ gpuGlobals = dawn.globals;
3478
+ } catch (cause) {
3479
+ return {
3480
+ ok: false,
3481
+ error: {
3482
+ code: "replay-backend-unavailable",
3483
+ expected: "the Dawn WebGPU provider to load",
3484
+ hint: "Install the DevKit runtime closure and retry rhi.inspect.",
3485
+ detail: {
3486
+ stage: "provider",
3487
+ cause: cause instanceof Error ? cause.message : String(cause)
3488
+ }
3489
+ }
3490
+ };
3491
+ }
3492
+ Object.assign(globalThis, gpuGlobals);
3493
+ const gpu = createDawn([]);
3494
+ if (!("navigator" in globalThis) || globalThis.navigator === void 0) {
3495
+ Object.defineProperty(globalThis, "navigator", {
3496
+ value: {},
3497
+ configurable: true,
3498
+ writable: true
3499
+ });
3500
+ }
3501
+ Object.defineProperty(globalThis.navigator, "gpu", {
3502
+ value: gpu,
3503
+ configurable: true,
3504
+ writable: true
3505
+ });
3039
3506
  const adapter = await rhi.requestAdapter();
3040
3507
  if (!adapter.ok) {
3041
3508
  return {
3042
3509
  ok: false,
3043
3510
  error: {
3044
3511
  code: "replay-backend-unavailable",
3045
- expected: "a fresh rhi-null adapter",
3512
+ expected: "a fresh Dawn WebGPU adapter",
3046
3513
  hint: adapter.error.hint,
3047
3514
  detail: { stage: "adapter", cause: adapter.error.hint }
3048
3515
  }
3049
3516
  };
3050
3517
  }
3051
- const device = await adapter.value.requestDevice();
3518
+ const device = await adapter.value.requestDevice(
3519
+ replayDeviceRequest(tape, adapter.value.features, adapter.value.limits)
3520
+ );
3052
3521
  if (!device.ok) {
3053
3522
  return {
3054
3523
  ok: false,
3055
3524
  error: {
3056
3525
  code: "replay-backend-unavailable",
3057
- expected: "a fresh rhi-null device",
3526
+ expected: "a fresh Dawn WebGPU device satisfying the recorded tape",
3058
3527
  hint: device.error.hint,
3059
3528
  detail: { stage: "device", cause: device.error.hint }
3060
3529
  }
@@ -3092,6 +3561,9 @@ function renderRhiDebugHelp() {
3092
3561
  return [
3093
3562
  "forgeax run <operation>",
3094
3563
  ...discoverRhiDebugOperations().map((operation) => ` ${operation.name}: ${operation.summary}`),
3564
+ "Usage:",
3565
+ " forgeax run rhi.summary --artifact PATH --digest SHA256 --json",
3566
+ " forgeax run rhi.inspect --artifact PATH --digest SHA256 --work-index N --fields pipeline,bindings,pixels --json",
3095
3567
  "ArtifactRef schema:",
3096
3568
  JSON.stringify(RHI_DEBUG_OPERATION_MANIFEST.artifactRefSchema)
3097
3569
  ].join("\n");
@@ -3219,7 +3691,7 @@ async function runRhiDebugOperation(name, input, context) {
3219
3691
  "Provide a fresh device and shader factory before running rhi.inspect."
3220
3692
  );
3221
3693
  }
3222
- const backend = await context.createReplayBackend();
3694
+ const backend = await context.createReplayBackend(decoded.value);
3223
3695
  if (!backend.ok) return backend;
3224
3696
  const opened = await openReplay(decoded.value, backend.value);
3225
3697
  if (!opened.ok) return { ok: false, error: coreError(opened.error) };
@@ -3311,7 +3783,10 @@ var init_operations = __esm({
3311
3783
  properties: {
3312
3784
  artifact: artifactRefSchema,
3313
3785
  workIndex: { type: "integer", minimum: 0 },
3314
- fields: { type: "array", items: { type: "string" } }
3786
+ fields: {
3787
+ type: "array",
3788
+ items: { type: "string", enum: ["bindings", "pipeline", "pixels"] }
3789
+ }
3315
3790
  },
3316
3791
  required: ["artifact", "workIndex"],
3317
3792
  additionalProperties: false
@@ -3460,6 +3935,639 @@ var init_shader_check = __esm({
3460
3935
  init_project();
3461
3936
  }
3462
3937
  });
3938
+ function fail(code, expected, hint, detail = {}) {
3939
+ throw new SoftwareCaptureError(code, expected, hint, detail);
3940
+ }
3941
+ async function pathExists2(path) {
3942
+ try {
3943
+ await access(path);
3944
+ return true;
3945
+ } catch {
3946
+ return false;
3947
+ }
3948
+ }
3949
+ async function reservePort() {
3950
+ const reservation = createServer$1();
3951
+ await new Promise((resolveListen, rejectListen) => {
3952
+ reservation.once("error", rejectListen);
3953
+ reservation.listen(0, "127.0.0.1", resolveListen);
3954
+ });
3955
+ const address = reservation.address();
3956
+ const port = typeof address === "object" && address !== null ? address.port : 0;
3957
+ await new Promise((resolveClose, rejectClose) => {
3958
+ reservation.close((error) => error === void 0 ? resolveClose() : rejectClose(error));
3959
+ });
3960
+ if (port === 0) throw new Error("OS did not assign an ephemeral capture port");
3961
+ return port;
3962
+ }
3963
+ async function stopProcess(child) {
3964
+ if (child.exitCode !== null || child.signalCode !== null) return;
3965
+ await new Promise((resolveStop) => {
3966
+ const timeout = setTimeout(() => {
3967
+ child.kill("SIGKILL");
3968
+ resolveStop();
3969
+ }, 2e3);
3970
+ child.once("exit", () => {
3971
+ clearTimeout(timeout);
3972
+ resolveStop();
3973
+ });
3974
+ child.kill("SIGTERM");
3975
+ });
3976
+ }
3977
+ async function startVirtualDisplay(width, height) {
3978
+ if (process.platform !== "linux" || process.env.DISPLAY !== void 0) {
3979
+ return {
3980
+ ...process.env.DISPLAY === void 0 ? {} : { value: process.env.DISPLAY },
3981
+ async close() {
3982
+ }
3983
+ };
3984
+ }
3985
+ for (let offset = 0; offset < 100; offset += 1) {
3986
+ const number = 90 + (process.pid + offset) % 100;
3987
+ const socket = `/tmp/.X11-unix/X${number}`;
3988
+ const lock = `/tmp/.X${number}-lock`;
3989
+ if (await pathExists2(socket) || await pathExists2(lock)) continue;
3990
+ const display = `:${number}`;
3991
+ const child = spawn(
3992
+ "Xvfb",
3993
+ [display, "-screen", "0", `${width}x${height}x24`, "-nolisten", "tcp"],
3994
+ { stdio: ["ignore", "ignore", "pipe"] }
3995
+ );
3996
+ let diagnostic = "";
3997
+ child.stderr?.setEncoding("utf8");
3998
+ child.stderr?.on("data", (chunk) => {
3999
+ diagnostic += chunk;
4000
+ });
4001
+ try {
4002
+ await new Promise((resolveReady, rejectReady) => {
4003
+ const timeout = setTimeout(
4004
+ () => rejectReady(new Error(`Xvfb did not create ${socket}: ${diagnostic.trim()}`)),
4005
+ 5e3
4006
+ );
4007
+ const poll = setInterval(() => {
4008
+ void pathExists2(socket).then((exists) => {
4009
+ if (!exists) return;
4010
+ clearTimeout(timeout);
4011
+ clearInterval(poll);
4012
+ resolveReady();
4013
+ });
4014
+ }, 50);
4015
+ child.once("error", (error) => {
4016
+ clearTimeout(timeout);
4017
+ clearInterval(poll);
4018
+ rejectReady(error);
4019
+ });
4020
+ child.once("exit", (code, signal) => {
4021
+ clearTimeout(timeout);
4022
+ clearInterval(poll);
4023
+ rejectReady(
4024
+ new Error(`Xvfb exited before ready (${code ?? signal}): ${diagnostic.trim()}`)
4025
+ );
4026
+ });
4027
+ });
4028
+ return { value: display, close: () => stopProcess(child) };
4029
+ } catch (cause) {
4030
+ await stopProcess(child);
4031
+ throw cause;
4032
+ }
4033
+ }
4034
+ throw new Error("no free X11 display number was available for browser capture");
4035
+ }
4036
+ async function lavapipeIcd() {
4037
+ for (const candidate of [
4038
+ "/usr/share/vulkan/icd.d/lvp_icd.x86_64.json",
4039
+ "/usr/share/vulkan/icd.d/lvp_icd.aarch64.json"
4040
+ ]) {
4041
+ if (await pathExists2(candidate)) return candidate;
4042
+ }
4043
+ return void 0;
4044
+ }
4045
+ function evidencePath(output) {
4046
+ return output.toLowerCase().endsWith(".png") ? `${output.slice(0, -4)}.json` : `${output}.json`;
4047
+ }
4048
+ function newRunId() {
4049
+ return `${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}-${randomUUID().slice(0, 8)}`;
4050
+ }
4051
+ function checkpointSlug(checkpoint) {
4052
+ const value = (checkpoint ?? "capture").trim().toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
4053
+ return value.length === 0 ? "capture" : value;
4054
+ }
4055
+ function captureDigest(bytes) {
4056
+ return `sha256:${createHash("sha256").update(bytes).digest("hex")}`;
4057
+ }
4058
+ function observedBackend(runtime) {
4059
+ if (runtime.adapter === null) return "unknown";
4060
+ return isSoftwareAdapter(runtime) ? "software" : "hardware";
4061
+ }
4062
+ async function resolveBrowserExecutable(requested) {
4063
+ const candidates = [
4064
+ requested,
4065
+ process.env.FORGEAX_BROWSER_EXECUTABLE,
4066
+ "/opt/google/chrome-beta/chrome",
4067
+ "/usr/bin/google-chrome",
4068
+ "/usr/bin/google-chrome-stable",
4069
+ "/usr/bin/chromium",
4070
+ "/usr/bin/chromium-browser"
4071
+ ].filter((candidate) => candidate !== void 0 && candidate.length > 0);
4072
+ for (const candidate of candidates) {
4073
+ if (await pathExists2(candidate)) return candidate;
4074
+ }
4075
+ return void 0;
4076
+ }
4077
+ function browserLaunchArgs(backend) {
4078
+ const common = [
4079
+ "--enable-unsafe-webgpu",
4080
+ "--ignore-gpu-blocklist",
4081
+ "--disable-gpu-driver-bug-workarounds",
4082
+ "--force-color-profile=srgb",
4083
+ "--force-device-scale-factor=1"
4084
+ ];
4085
+ if (backend !== "software") return common;
4086
+ return [
4087
+ ...common,
4088
+ "--enable-features=Vulkan",
4089
+ "--use-vulkan=swiftshader",
4090
+ "--use-angle=swiftshader",
4091
+ "--disable-vulkan-surface"
4092
+ ];
4093
+ }
4094
+ function summarizeCapturePixels(rgba, width, height) {
4095
+ const pixelCount = width * height;
4096
+ const stride = Math.max(1, Math.floor(pixelCount / 4096));
4097
+ const histogram = new Uint32Array(16);
4098
+ let lumaMin = 255;
4099
+ let lumaMax = 0;
4100
+ let sampledPixels = 0;
4101
+ for (let pixel = 0; pixel < pixelCount; pixel += stride) {
4102
+ const offset = pixel * 4;
4103
+ const red = rgba[offset];
4104
+ const green = rgba[offset + 1];
4105
+ const blue = rgba[offset + 2];
4106
+ if (red === void 0 || green === void 0 || blue === void 0) break;
4107
+ const luma = Math.round((54 * red + 183 * green + 19 * blue) / 256);
4108
+ lumaMin = Math.min(lumaMin, luma);
4109
+ lumaMax = Math.max(lumaMax, luma);
4110
+ const bucket = Math.min(15, Math.floor(luma / 16));
4111
+ histogram[bucket] = (histogram[bucket] ?? 0) + 1;
4112
+ sampledPixels += 1;
4113
+ }
4114
+ const dominantPixels = histogram.reduce((largest, count) => Math.max(largest, count), 0);
4115
+ const varyingPixels = sampledPixels - dominantPixels;
4116
+ const lumaRange = lumaMax - lumaMin;
4117
+ const requiredVariation = Math.min(sampledPixels, Math.max(8, Math.ceil(sampledPixels * 2e-3)));
4118
+ return {
4119
+ width,
4120
+ height,
4121
+ sampledPixels,
4122
+ lumaMin,
4123
+ lumaMax,
4124
+ lumaRange,
4125
+ varyingPixels,
4126
+ rendered: lumaRange >= 8 && varyingPixels >= requiredVariation
4127
+ };
4128
+ }
4129
+ function inspectCapturePng(png) {
4130
+ const decoded = parseImage(png, "image/png", { mipmap: false });
4131
+ if (!decoded.ok) throw decoded.error;
4132
+ return summarizeCapturePixels(decoded.value.bytes, decoded.value.width, decoded.value.height);
4133
+ }
4134
+ async function screenshotWithWitness(page) {
4135
+ const canvasPng = await page.locator("canvas").first().screenshot({
4136
+ type: "png",
4137
+ style: "* { visibility: hidden !important; } canvas { visibility: visible !important; }"
4138
+ });
4139
+ const png = await page.screenshot({ type: "png", caret: "hide" });
4140
+ return { png, pixels: inspectCapturePng(canvasPng) };
4141
+ }
4142
+ async function waitForCompositor(page) {
4143
+ await page.evaluate(async () => {
4144
+ await document.fonts.ready;
4145
+ await new Promise((resolveFrame) => requestAnimationFrame(() => resolveFrame()));
4146
+ await new Promise((resolveFrame) => requestAnimationFrame(() => resolveFrame()));
4147
+ });
4148
+ }
4149
+ async function runtimeWitness(page) {
4150
+ return page.evaluate(async (datasetKey) => {
4151
+ const canvas = document.querySelector("canvas");
4152
+ const uiRoot = document.querySelector("#game-ui");
4153
+ const shadowHosts = [...uiRoot?.querySelectorAll("*") ?? []].filter(
4154
+ (element) => element.shadowRoot?.childElementCount !== 0
4155
+ );
4156
+ let adapter = null;
4157
+ let adapterError = null;
4158
+ try {
4159
+ const gpuAdapter = await navigator.gpu?.requestAdapter();
4160
+ if (gpuAdapter === null || gpuAdapter === void 0) {
4161
+ adapterError = "navigator.gpu.requestAdapter() returned null";
4162
+ } else {
4163
+ const info = gpuAdapter.info;
4164
+ adapter = {
4165
+ vendor: info.vendor,
4166
+ architecture: info.architecture,
4167
+ device: info.device,
4168
+ description: info.description
4169
+ };
4170
+ }
4171
+ } catch (cause) {
4172
+ adapterError = String(cause);
4173
+ }
4174
+ const frameId = Number(document.documentElement.dataset[datasetKey]);
4175
+ return {
4176
+ title: document.title,
4177
+ canvas: canvas instanceof HTMLCanvasElement ? { width: canvas.width, height: canvas.height } : null,
4178
+ domUi: {
4179
+ rootChildren: uiRoot?.childElementCount ?? 0,
4180
+ openShadowRoots: shadowHosts.length,
4181
+ textWitness: shadowHosts.map((host) => host.shadowRoot?.textContent?.replace(/\s+/g, " ").trim() ?? "").filter((text) => text.length > 0).join(" | ").slice(0, 500)
4182
+ },
4183
+ adapter,
4184
+ adapterError,
4185
+ engineFrameId: Number.isSafeInteger(frameId) && frameId > 0 ? frameId : null,
4186
+ captureReady: document.documentElement.dataset.forgeaxCaptureReady ?? null,
4187
+ userAgent: navigator.userAgent
4188
+ };
4189
+ }, FORGEAX_FRAME_SUBMITTED_DATASET);
4190
+ }
4191
+ function isSoftwareAdapter(runtime) {
4192
+ const witness = Object.values(runtime.adapter ?? {}).map((value) => String(value).toLowerCase()).join(" ");
4193
+ return ["swiftshader", "llvmpipe", "lavapipe", "software"].some(
4194
+ (token) => witness.includes(token)
4195
+ );
4196
+ }
4197
+ async function writeRunReport(report) {
4198
+ await mkdir(dirname(report.report), { recursive: true });
4199
+ await writeFile(report.report, `${JSON.stringify(report, null, 2)}
4200
+ `, "utf8");
4201
+ }
4202
+ async function openBrowserCaptureSession(root, options) {
4203
+ const backend = options.backend ?? (options.software === true ? "software" : "auto");
4204
+ if (options.software === true && options.backend !== void 0 && options.backend !== "software") {
4205
+ fail(
4206
+ "browser-capture-option-conflict",
4207
+ "software and backend options to describe the same capture lane",
4208
+ "Use either software: true or backend: software; use backend: auto for a portable capture.",
4209
+ { software: options.software, backend: options.backend }
4210
+ );
4211
+ }
4212
+ const facts = await readProjectFacts(root);
4213
+ if (!facts.ok) {
4214
+ throw new SoftwareCaptureError(
4215
+ facts.error.code,
4216
+ facts.error.expected,
4217
+ facts.error.hint,
4218
+ facts.error.detail
4219
+ );
4220
+ }
4221
+ const width = options.width ?? 1280;
4222
+ const height = options.height ?? 720;
4223
+ const browserPath = await resolveBrowserExecutable(options.browser);
4224
+ if (options.browser !== void 0 && browserPath === void 0) {
4225
+ fail(
4226
+ "browser-capture-browser-missing",
4227
+ `a runnable browser at ${options.browser}`,
4228
+ "Install Chrome/Chromium or pass --browser with an executable path.",
4229
+ { browser: options.browser }
4230
+ );
4231
+ }
4232
+ const id = options.runId ?? newRunId();
4233
+ const outputDirectory = resolve(
4234
+ facts.value.root,
4235
+ options.outputDir ?? `artifacts/playthrough/${id}`
4236
+ );
4237
+ const reportPath = resolve(
4238
+ facts.value.root,
4239
+ options.report ?? resolve(outputDirectory, "run.json")
4240
+ );
4241
+ const port = options.port === void 0 || options.port === 0 ? await reservePort() : options.port;
4242
+ let server;
4243
+ let browser;
4244
+ let page;
4245
+ let display;
4246
+ try {
4247
+ const { createServer: createServer5 } = await import('vite');
4248
+ server = await createServer5(
4249
+ await createViteConfig(facts.value, "serve", "/", {
4250
+ server: { port, strictPort: true }
4251
+ })
4252
+ );
4253
+ await server.listen(port);
4254
+ display = await startVirtualDisplay(width, height);
4255
+ const captureDisplay = display;
4256
+ const icd = await lavapipeIcd();
4257
+ const consoleErrors = [];
4258
+ const pageErrors = [];
4259
+ const hasDisplay = display.value !== void 0;
4260
+ const headless = options.headless ?? (!hasDisplay && process.platform !== "linux");
4261
+ const baseUrl = new URL(server.resolvedUrls?.local[0] ?? `http://127.0.0.1:${port}/`);
4262
+ const openPage = async (launchBackend) => {
4263
+ const browserEnvironment = {
4264
+ ...Object.fromEntries(
4265
+ Object.entries(process.env).filter(
4266
+ (entry) => entry[1] !== void 0
4267
+ )
4268
+ ),
4269
+ ...launchBackend === "software" ? { LIBGL_ALWAYS_SOFTWARE: "1" } : {},
4270
+ ...captureDisplay.value === void 0 ? {} : { DISPLAY: captureDisplay.value }
4271
+ };
4272
+ const launchOptions = {
4273
+ headless,
4274
+ env: browserEnvironment,
4275
+ args: browserLaunchArgs(launchBackend),
4276
+ ...browserPath === void 0 ? {} : { executablePath: browserPath }
4277
+ };
4278
+ browser = await chromium.launch(launchOptions);
4279
+ page = await browser.newPage({
4280
+ viewport: { width, height },
4281
+ screen: { width, height },
4282
+ deviceScaleFactor: 1,
4283
+ colorScheme: "light",
4284
+ locale: "en-US",
4285
+ timezoneId: "UTC",
4286
+ serviceWorkers: "block"
4287
+ });
4288
+ page.on("console", (message) => {
4289
+ if (message.type() === "error") consoleErrors.push(message.text());
4290
+ });
4291
+ page.on("pageerror", (error) => pageErrors.push(String(error)));
4292
+ const captureUrl2 = new URL(baseUrl.href);
4293
+ if (options.deterministic === true) captureUrl2.searchParams.set("forgeaxCapture", "1");
4294
+ await page.goto(captureUrl2.href, { waitUntil: "domcontentloaded", timeout: 12e4 });
4295
+ await page.waitForFunction(
4296
+ (requireUi) => {
4297
+ const canvas = document.querySelector("canvas");
4298
+ if (!(canvas instanceof HTMLCanvasElement) || canvas.width <= 0 || canvas.height <= 0)
4299
+ return false;
4300
+ if (!requireUi) return true;
4301
+ const uiRoot = document.querySelector("#game-ui");
4302
+ return uiRoot !== null && uiRoot.childElementCount > 0;
4303
+ },
4304
+ options.requireUi === true,
4305
+ { timeout: 12e4 }
4306
+ );
4307
+ return runtimeWitness(page);
4308
+ };
4309
+ const initialLaunchBackend = backend === "software" ? "software" : "hardware";
4310
+ let runtime = await openPage(initialLaunchBackend);
4311
+ let observed = observedBackend(runtime);
4312
+ if (backend === "auto" && runtime.adapter === null) {
4313
+ await browser?.close();
4314
+ browser = void 0;
4315
+ page = void 0;
4316
+ consoleErrors.length = 0;
4317
+ pageErrors.length = 0;
4318
+ runtime = await openPage("software");
4319
+ observed = observedBackend(runtime);
4320
+ }
4321
+ const captureUrl = new URL(baseUrl.href);
4322
+ if (options.deterministic === true) captureUrl.searchParams.set("forgeaxCapture", "1");
4323
+ if (page === void 0 || browser === void 0) {
4324
+ fail(
4325
+ "browser-capture-browser-unavailable",
4326
+ "the browser capture session to open",
4327
+ "Inspect the browser launch diagnostic and retry with --browser or --headless."
4328
+ );
4329
+ }
4330
+ const captures = [];
4331
+ let closed = false;
4332
+ const report = {
4333
+ schemaVersion: "2.0.0",
4334
+ runId: id,
4335
+ ok: false,
4336
+ mode: "browser-compositor",
4337
+ root: facts.value.root,
4338
+ url: captureUrl.href,
4339
+ report: reportPath,
4340
+ backendRequested: backend,
4341
+ backend: observed,
4342
+ softwareRequested: backend === "software",
4343
+ deterministicRequested: options.deterministic === true,
4344
+ viewport: {
4345
+ width,
4346
+ height,
4347
+ deviceScaleFactor: 1,
4348
+ colorProfile: "srgb",
4349
+ colorScheme: "light",
4350
+ locale: "en-US",
4351
+ timezone: "UTC"
4352
+ },
4353
+ browser: { version: browser.version(), executable: browserPath ?? "playwright-managed" },
4354
+ display: display.value ?? null,
4355
+ lavapipeIcd: icd ?? null,
4356
+ captures,
4357
+ consoleErrors,
4358
+ pageErrors,
4359
+ boundary: "Browser-compositor capture is visual iteration evidence, not physical-GPU performance, HDR-display output, or release acceptance."
4360
+ };
4361
+ await writeRunReport(report);
4362
+ const updateReport = async () => {
4363
+ Object.assign(report, {
4364
+ ok: captures.length > 0 && captures.every((capture) => capture.ok) && consoleErrors.length === 0 && pageErrors.length === 0
4365
+ });
4366
+ await writeRunReport(report);
4367
+ };
4368
+ const close = async () => {
4369
+ if (closed) return;
4370
+ closed = true;
4371
+ await Promise.allSettled([browser?.close(), display?.close(), server?.close()]);
4372
+ Object.assign(report, { closedAt: (/* @__PURE__ */ new Date()).toISOString() });
4373
+ await updateReport();
4374
+ };
4375
+ return {
4376
+ page,
4377
+ url: captureUrl.href,
4378
+ reportPath,
4379
+ async capture(checkpoint, captureOptions = {}) {
4380
+ if (closed) {
4381
+ fail(
4382
+ "browser-capture-session-closed",
4383
+ "capture to run inside an open browser session",
4384
+ "Open one session, complete all checkpoints, then close it.",
4385
+ { report: reportPath }
4386
+ );
4387
+ }
4388
+ const activePage = page;
4389
+ if (activePage === void 0) {
4390
+ fail(
4391
+ "browser-capture-browser-unavailable",
4392
+ "an open Playwright page",
4393
+ "Inspect the browser launch diagnostic and retry the capture."
4394
+ );
4395
+ }
4396
+ const expectedReady = checkpoint ?? (options.deterministic === true ? "true" : void 0);
4397
+ await activePage.waitForFunction(
4398
+ ({ datasetKey }) => Number(document.documentElement.dataset[datasetKey]) > 0,
4399
+ { datasetKey: FORGEAX_FRAME_SUBMITTED_DATASET },
4400
+ { timeout: 12e4 }
4401
+ );
4402
+ if (expectedReady !== void 0) {
4403
+ await activePage.waitForFunction(
4404
+ (expected) => document.documentElement.dataset.forgeaxCaptureReady === expected,
4405
+ expectedReady,
4406
+ { timeout: 12e4 }
4407
+ );
4408
+ }
4409
+ await waitForCompositor(activePage);
4410
+ let captured = await screenshotWithWitness(activePage);
4411
+ const deadline = Date.now() + 12e4;
4412
+ while (!captured.pixels.rendered && Date.now() < deadline) {
4413
+ await activePage.waitForTimeout(500);
4414
+ await waitForCompositor(activePage);
4415
+ captured = await screenshotWithWitness(activePage);
4416
+ }
4417
+ const waitMs = captureOptions.waitMs ?? 0;
4418
+ if (waitMs > 0) {
4419
+ await activePage.waitForTimeout(waitMs);
4420
+ await waitForCompositor(activePage);
4421
+ captured = await screenshotWithWitness(activePage);
4422
+ while (!captured.pixels.rendered && Date.now() < deadline) {
4423
+ await activePage.waitForTimeout(500);
4424
+ await waitForCompositor(activePage);
4425
+ captured = await screenshotWithWitness(activePage);
4426
+ }
4427
+ }
4428
+ const runtime2 = await runtimeWitness(activePage);
4429
+ const uiPresent = runtime2.domUi.rootChildren > 0;
4430
+ const requireUi = captureOptions.requireUi ?? options.requireUi ?? false;
4431
+ const captureBackend = observedBackend(runtime2);
4432
+ if (captureBackend !== "unknown") Object.assign(report, { backend: captureBackend });
4433
+ const backendMatches = backend === "auto" || captureBackend === backend;
4434
+ const ok2 = runtime2.canvas !== null && captured.pixels.rendered && backendMatches && runtime2.engineFrameId !== null && consoleErrors.length === 0 && pageErrors.length === 0 && (expectedReady === void 0 || runtime2.captureReady === expectedReady) && (!requireUi || uiPresent);
4435
+ const index = captures.length + 1;
4436
+ const output = resolve(
4437
+ facts.value.root,
4438
+ captureOptions.output ?? resolve(
4439
+ outputDirectory,
4440
+ `${String(index).padStart(3, "0")}-${checkpointSlug(checkpoint)}.png`
4441
+ )
4442
+ );
4443
+ await mkdir(dirname(output), { recursive: true });
4444
+ await writeFile(output, captured.png);
4445
+ const record = {
4446
+ index,
4447
+ checkpoint: checkpoint ?? null,
4448
+ ok: ok2,
4449
+ output,
4450
+ digest: captureDigest(captured.png),
4451
+ pixels: captured.pixels,
4452
+ runtime: runtime2
4453
+ };
4454
+ captures.push(record);
4455
+ await updateReport();
4456
+ if (!ok2) {
4457
+ fail(
4458
+ `${backend === "software" ? "software" : "browser"}-capture-runtime-failed`,
4459
+ "non-flat canvas pixels, the requested browser backend/checkpoint, and no browser errors",
4460
+ "Inspect the run report and repair the first browser runtime failure.",
4461
+ { checkpoint: checkpoint ?? null, output, report: reportPath }
4462
+ );
4463
+ }
4464
+ return record;
4465
+ },
4466
+ report: () => report,
4467
+ close
4468
+ };
4469
+ } catch (cause) {
4470
+ await Promise.allSettled([browser?.close(), display?.close(), server?.close()]);
4471
+ throw cause;
4472
+ }
4473
+ }
4474
+ function createBrowserCapture(root) {
4475
+ const sessions = /* @__PURE__ */ new Set();
4476
+ return {
4477
+ async open(options) {
4478
+ const session = await openBrowserCaptureSession(root, options);
4479
+ sessions.add(session);
4480
+ return session;
4481
+ },
4482
+ async close() {
4483
+ await Promise.allSettled([...sessions].map((session) => session.close()));
4484
+ sessions.clear();
4485
+ }
4486
+ };
4487
+ }
4488
+ function createSoftwareBrowser(root) {
4489
+ const browser = createBrowserCapture(root);
4490
+ return {
4491
+ async open(options) {
4492
+ return browser.open({ ...options, backend: "software", software: true });
4493
+ },
4494
+ close: () => browser.close()
4495
+ };
4496
+ }
4497
+ function captureCommandError(cause, legacySoftware = false) {
4498
+ if (cause instanceof SoftwareCaptureError) {
4499
+ const code = legacySoftware && cause.code.startsWith("browser-capture-") ? cause.code.replace(/^browser-capture-/, "software-capture-") : cause.code;
4500
+ return {
4501
+ code,
4502
+ expected: cause.expected,
4503
+ hint: cause.hint,
4504
+ detail: cause.detail
4505
+ };
4506
+ }
4507
+ return commandError(cause, legacySoftware ? "software-capture-failed" : "browser-capture-failed");
4508
+ }
4509
+ async function browserCaptureCommand(options) {
4510
+ const root = options.root ?? process.cwd();
4511
+ const output = resolve(root, options.output ?? "artifacts/capture/game-ui.png");
4512
+ const browser = createBrowserCapture(root);
4513
+ try {
4514
+ const backend = options.backend ?? (options.software === true ? "software" : "auto");
4515
+ const session = await browser.open({
4516
+ backend,
4517
+ ...backend === "software" ? { software: true } : {},
4518
+ ...options.browser === void 0 ? {} : { browser: options.browser },
4519
+ ...options.width === void 0 ? {} : { width: options.width },
4520
+ ...options.height === void 0 ? {} : { height: options.height },
4521
+ ...options.port === void 0 ? {} : { port: options.port },
4522
+ ...options.headless === void 0 ? {} : { headless: options.headless },
4523
+ requireUi: options.requireUi === true,
4524
+ deterministic: options.deterministic === true,
4525
+ outputDir: dirname(output),
4526
+ report: evidencePath(output)
4527
+ });
4528
+ await session.capture(options.deterministic === true ? "true" : void 0, {
4529
+ output,
4530
+ waitMs: options.waitMs ?? 4e3,
4531
+ requireUi: options.requireUi === true
4532
+ });
4533
+ await session.close();
4534
+ return { ok: true, value: session.report() };
4535
+ } catch (cause) {
4536
+ return { ok: false, error: captureCommandError(cause) };
4537
+ } finally {
4538
+ await browser.close();
4539
+ }
4540
+ }
4541
+ async function softwareCaptureCommand(options) {
4542
+ const result = await browserCaptureCommand({ ...options, backend: "software", software: true });
4543
+ if (result.ok) return result;
4544
+ return { ok: false, error: captureCommandErrorFromResult(result.error) };
4545
+ }
4546
+ function captureCommandErrorFromResult(error, legacySoftware) {
4547
+ if (!error.code.startsWith("browser-capture-")) return error;
4548
+ return { ...error, code: error.code.replace(/^browser-capture-/, "software-capture-") };
4549
+ }
4550
+ var SoftwareCaptureError;
4551
+ var init_software_capture = __esm({
4552
+ "src/software-capture.ts"() {
4553
+ init_host();
4554
+ init_project();
4555
+ SoftwareCaptureError = class extends Error {
4556
+ constructor(code, expected, hint, detail) {
4557
+ super(`${code}: ${hint}`);
4558
+ this.code = code;
4559
+ this.expected = expected;
4560
+ this.hint = hint;
4561
+ this.detail = detail;
4562
+ this.name = "SoftwareCaptureError";
4563
+ }
4564
+ code;
4565
+ expected;
4566
+ hint;
4567
+ detail;
4568
+ };
4569
+ }
4570
+ });
3463
4571
 
3464
4572
  // src/commands.ts
3465
4573
  var commands_exports = {};
@@ -3469,12 +4577,17 @@ __export(commands_exports, {
3469
4577
  assetInspectCommand: () => assetInspectCommand,
3470
4578
  assetListCommand: () => assetListCommand,
3471
4579
  assetVerifyCommand: () => assetVerifyCommand,
4580
+ browserCaptureCommand: () => browserCaptureCommand,
3472
4581
  buildCommand: () => buildCommand,
3473
4582
  createCliRhiDebugOperationContext: () => createCliRhiDebugOperationContext,
3474
4583
  createRhiDebugOperationContext: () => createRhiDebugOperationContext,
3475
4584
  devCommand: () => devCommand,
3476
4585
  discoverRhiDebugOperations: () => discoverRhiDebugOperations,
3477
4586
  doctorCommand: () => doctorCommand,
4587
+ engineDoctorCommand: () => engineDoctorCommand,
4588
+ engineStatusCommand: () => engineStatusCommand,
4589
+ engineUnlinkCommand: () => engineUnlinkCommand,
4590
+ engineUseLocalCommand: () => engineUseLocalCommand,
3478
4591
  initCommand: () => initCommand,
3479
4592
  newCommand: () => newCommand,
3480
4593
  packageCommand: () => packageCommand,
@@ -3489,6 +4602,7 @@ __export(commands_exports, {
3489
4602
  shaderCheckCommand: () => shaderCheckCommand,
3490
4603
  skillInstallCommand: () => skillInstallCommand,
3491
4604
  skillVerifyCommand: () => skillVerifyCommand,
4605
+ softwareCaptureCommand: () => softwareCaptureCommand,
3492
4606
  testCommand: () => testCommand
3493
4607
  });
3494
4608
  function runRhiDebugCommand(name, input, context) {
@@ -3729,12 +4843,14 @@ var init_commands = __esm({
3729
4843
  init_types();
3730
4844
  init_assets();
3731
4845
  init_bootstrap_commands();
4846
+ init_engine_binding();
3732
4847
  init_plugin_authoring();
3733
4848
  init_cli_context();
3734
4849
  init_operations();
3735
4850
  init_sdk_install();
3736
4851
  init_shader_check();
3737
4852
  init_skill_install();
4853
+ init_software_capture();
3738
4854
  init_operations();
3739
4855
  }
3740
4856
  });
@@ -4381,9 +5497,11 @@ var init_runtime = __esm({
4381
5497
  // src/index.ts
4382
5498
  init_commands();
4383
5499
  init_dist();
5500
+ init_engine_binding();
4384
5501
  init_init();
4385
5502
  init_project();
4386
5503
  init_operations();
5504
+ init_software_capture();
4387
5505
 
4388
5506
  // src/tools/benchmark/statistics.ts
4389
5507
  function calculateSampleStatistics(samples) {
@@ -4733,14 +5851,14 @@ async function createCarrierProviderService(options) {
4733
5851
  );
4734
5852
  }
4735
5853
  });
4736
- await new Promise((resolve16, reject) => {
5854
+ await new Promise((resolve18, reject) => {
4737
5855
  const onError = (error) => {
4738
5856
  server.off("listening", onListening);
4739
5857
  reject(error);
4740
5858
  };
4741
5859
  const onListening = () => {
4742
5860
  server.off("error", onError);
4743
- resolve16();
5861
+ resolve18();
4744
5862
  };
4745
5863
  server.once("error", onError);
4746
5864
  server.once("listening", onListening);
@@ -4758,7 +5876,7 @@ async function createCarrierProviderService(options) {
4758
5876
  if (closed) return;
4759
5877
  closed = true;
4760
5878
  await new Promise(
4761
- (resolve16, reject) => server.close((error) => error === void 0 ? resolve16() : reject(error))
5879
+ (resolve18, reject) => server.close((error) => error === void 0 ? resolve18() : reject(error))
4762
5880
  );
4763
5881
  }
4764
5882
  };
@@ -5052,14 +6170,14 @@ function projectUri(projectRoot, path) {
5052
6170
  async function allocateLoopbackPort() {
5053
6171
  const probe = createServer$1();
5054
6172
  try {
5055
- await new Promise((resolve16, reject) => {
6173
+ await new Promise((resolve18, reject) => {
5056
6174
  const onError = (error) => {
5057
6175
  probe.off("listening", onListening);
5058
6176
  reject(error);
5059
6177
  };
5060
6178
  const onListening = () => {
5061
6179
  probe.off("error", onError);
5062
- resolve16();
6180
+ resolve18();
5063
6181
  };
5064
6182
  probe.once("error", onError);
5065
6183
  probe.once("listening", onListening);
@@ -5072,7 +6190,7 @@ async function allocateLoopbackPort() {
5072
6190
  return address.port;
5073
6191
  } finally {
5074
6192
  if (probe.listening) {
5075
- await new Promise((resolve16) => probe.close(() => resolve16()));
6193
+ await new Promise((resolve18) => probe.close(() => resolve18()));
5076
6194
  }
5077
6195
  }
5078
6196
  }
@@ -5265,6 +6383,7 @@ async function runBrowserPreviewHost(projectRoot, recipe, snapshot, runId, signa
5265
6383
  "--enable-unsafe-webgpu",
5266
6384
  "--enable-features=Vulkan,UseSkiaRenderer,SharedArrayBuffer",
5267
6385
  "--use-vulkan=swiftshader",
6386
+ "--use-angle=swiftshader",
5268
6387
  "--disable-vulkan-surface",
5269
6388
  "--ignore-gpu-blocklist",
5270
6389
  "--disable-gpu-driver-bug-workarounds",
@@ -5717,14 +6836,14 @@ async function createAuthenticatedLoopbackService(options) {
5717
6836
  reply2(response, 400, { error: message });
5718
6837
  }
5719
6838
  });
5720
- await new Promise((resolve16, reject) => {
6839
+ await new Promise((resolve18, reject) => {
5721
6840
  const onError = (error) => {
5722
6841
  server.off("listening", onListening);
5723
6842
  reject(error);
5724
6843
  };
5725
6844
  const onListening = () => {
5726
6845
  server.off("error", onError);
5727
- resolve16();
6846
+ resolve18();
5728
6847
  };
5729
6848
  server.once("error", onError);
5730
6849
  server.once("listening", onListening);
@@ -5732,7 +6851,7 @@ async function createAuthenticatedLoopbackService(options) {
5732
6851
  });
5733
6852
  const address = server.address();
5734
6853
  if (address === null || typeof address === "string") {
5735
- await new Promise((resolve16) => server.close(() => resolve16()));
6854
+ await new Promise((resolve18) => server.close(() => resolve18()));
5736
6855
  throw new Error("loopback service did not expose a TCP address");
5737
6856
  }
5738
6857
  const endpoint = `http://${host}:${address.port}/run`;
@@ -5747,8 +6866,8 @@ async function createAuthenticatedLoopbackService(options) {
5747
6866
  async close() {
5748
6867
  if (closed) return;
5749
6868
  closed = true;
5750
- await new Promise((resolve16, reject) => {
5751
- server.close((error) => error === void 0 ? resolve16() : reject(error));
6869
+ await new Promise((resolve18, reject) => {
6870
+ server.close((error) => error === void 0 ? resolve18() : reject(error));
5752
6871
  });
5753
6872
  transport.close();
5754
6873
  }
@@ -5758,6 +6877,6 @@ async function createAuthenticatedLoopbackService(options) {
5758
6877
  // src/index.ts
5759
6878
  init_types();
5760
6879
 
5761
- export { DEFAULT_ADMISSION_THRESHOLDS, RHI_DEBUG_OPERATION_MANIFEST, analyzePreviewArtifacts, assetAddCommand, assetInspectCommand, assetListCommand, assetVerifyCommand, bootstrapRealm, buildCommand, createAdmissionReport, createAuthenticatedLoopbackService, createAuthorContribution, createBuildContribution, createCarrierProvider, createCarrierProviderService, createCarrierRendezvous, createCliRhiDebugOperationContext, createDefaultContributions, createDevkitToolRuntime, createDomainPreviewContributions, createInitPlan, createMigrationRoster, createOfflineAnalysisContribution, createPreviewContribution, createPreviewContributions, createPreviewToolRuntime, createResourceProbe, createRhiDebugOperationContext, createServiceCache, createServiceExecutor, createToolClient, describeTool, devCommand, discoverRhiDebugOperations, doctorCommand, initCommand, listTools, materializeToolCatalog, newCommand, packageCommand, pluginInstallCommand, pluginUninstallCommand, previewCommand, readProjectFacts, rebuildToolCatalog, recoverRhiDebugError, renderRhiDebugHelp, resolveMigration, resolveProjectPort, resolveRealmCapability, runBenchmarkAdmission, runCarrierPreviewRoute, runGenericTool, runLibraryTool, runNamedTool, runPreviewHost, runPrivateTool, runRhiDebugCommand, runRhiDebugOperation, sdkInstallCommand, shaderCheckCommand, skillInstallCommand, skillVerifyCommand, summarizeBenchmarkSamples, testCommand, verifyDist, writeDistManifest };
6880
+ export { DEFAULT_ADMISSION_THRESHOLDS, ENGINE_BINDING_SCHEMA_VERSION, RHI_DEBUG_OPERATION_MANIFEST, analyzePreviewArtifacts, assetAddCommand, assetInspectCommand, assetListCommand, assetVerifyCommand, bootstrapRealm, browserCaptureCommand, buildCommand, createAdmissionReport, createAuthenticatedLoopbackService, createAuthorContribution, createBrowserCapture, createBuildContribution, createCarrierProvider, createCarrierProviderService, createCarrierRendezvous, createCliRhiDebugOperationContext, createDefaultContributions, createDevkitToolRuntime, createDomainPreviewContributions, createInitPlan, createMigrationRoster, createOfflineAnalysisContribution, createPreviewContribution, createPreviewContributions, createPreviewToolRuntime, createResourceProbe, createRhiDebugOperationContext, createServiceCache, createServiceExecutor, createSoftwareBrowser, createToolClient, describeTool, devCommand, discoverRhiDebugOperations, doctorCommand, engineBindingFilePath, engineDoctorCommand, engineStatusCommand, engineUnlinkCommand, engineUseLocalCommand, initCommand, inspectEngineWorkspace, listTools, materializeToolCatalog, newCommand, packageCommand, pluginInstallCommand, pluginUninstallCommand, previewCommand, readEngineBinding, readProjectFacts, rebuildToolCatalog, recoverRhiDebugError, renderRhiDebugHelp, resolveMigration, resolveProjectPort, resolveRealmCapability, runBenchmarkAdmission, runCarrierPreviewRoute, runGenericTool, runLibraryTool, runNamedTool, runPreviewHost, runPrivateTool, runRhiDebugCommand, runRhiDebugOperation, sdkInstallCommand, shaderCheckCommand, skillInstallCommand, skillVerifyCommand, softwareCaptureCommand, summarizeBenchmarkSamples, testCommand, verifyDist, writeDistManifest };
5762
6881
  //# sourceMappingURL=index.mjs.map
5763
6882
  //# sourceMappingURL=index.mjs.map