@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.
- package/README.md +143 -1
- package/dist/.tsbuildinfo +1 -1
- package/dist/__tests__/engine-binding.unit.test.d.ts +2 -0
- package/dist/__tests__/engine-binding.unit.test.d.ts.map +1 -0
- package/dist/__tests__/software-browser-session.unit.test.d.ts +2 -0
- package/dist/__tests__/software-browser-session.unit.test.d.ts.map +1 -0
- package/dist/__tests__/software-capture-pixels.unit.test.d.ts +2 -0
- package/dist/__tests__/software-capture-pixels.unit.test.d.ts.map +1 -0
- package/dist/__tests__/software-capture.unit.test.d.ts +2 -0
- package/dist/__tests__/software-capture.unit.test.d.ts.map +1 -0
- package/dist/cli-output.d.ts.map +1 -1
- package/dist/cli.mjs +1477 -246
- package/dist/cli.mjs.map +1 -1
- package/dist/commands.d.ts +2 -0
- package/dist/commands.d.ts.map +1 -1
- package/dist/engine-binding.d.ts +58 -0
- package/dist/engine-binding.d.ts.map +1 -0
- package/dist/host.d.ts.map +1 -1
- package/dist/index.d.ts +7 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +1414 -295
- package/dist/index.mjs.map +1 -1
- package/dist/rhi-debug/cli-context.d.ts.map +1 -1
- package/dist/rhi-debug/operations.d.ts +3 -3
- package/dist/rhi-debug/operations.d.ts.map +1 -1
- package/dist/sdk-bootstrap.d.ts.map +1 -1
- package/dist/sdk-cli.mjs +4 -2
- package/dist/sdk-cli.mjs.map +1 -1
- package/dist/software-capture.d.ts +123 -0
- package/dist/software-capture.d.ts.map +1 -0
- package/dist/tools/browser-host.d.ts.map +1 -1
- package/dist/tools/commands.d.ts +5 -0
- package/dist/tools/commands.d.ts.map +1 -1
- package/dist/types.d.ts +21 -1
- package/dist/types.d.ts.map +1 -1
- 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,
|
|
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-
|
|
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
|
-
|
|
304
|
-
|
|
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 (
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
if (!
|
|
383
|
-
const
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
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
|
-
|
|
400
|
-
|
|
401
|
-
return engineWorkspacePackagesPromise;
|
|
925
|
+
}
|
|
926
|
+
return packages;
|
|
402
927
|
}
|
|
403
|
-
function
|
|
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 =
|
|
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 ?
|
|
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 ?
|
|
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
|
|
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 =
|
|
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
|
|
446
|
-
const separator = source.indexOf("/", "@forgeax/engine
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
1404
|
-
|
|
1405
|
-
|
|
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
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
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
|
|
1415
|
-
|
|
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.
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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: {
|
|
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((
|
|
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
|
-
|
|
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
|
-
(
|
|
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((
|
|
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
|
-
|
|
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((
|
|
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((
|
|
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
|
-
|
|
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((
|
|
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((
|
|
5751
|
-
server.close((error) => error === void 0 ?
|
|
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
|