@forgeax/engine-devkit 0.1.4 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +162 -5
- package/dist/.tsbuildinfo +1 -1
- package/dist/__tests__/cli-output.unit.test.d.ts +2 -0
- package/dist/__tests__/cli-output.unit.test.d.ts.map +1 -0
- 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__/gltf-source-key-diagnostics.integration.test.d.ts +2 -0
- package/dist/__tests__/gltf-source-key-diagnostics.integration.test.d.ts.map +1 -0
- package/dist/__tests__/preview-port.integration.test.d.ts +2 -0
- package/dist/__tests__/preview-port.integration.test.d.ts.map +1 -0
- package/dist/__tests__/rhi-debug-build-graph.integration.test.d.ts +2 -0
- package/dist/__tests__/rhi-debug-build-graph.integration.test.d.ts.map +1 -0
- package/dist/__tests__/sdk-update.unit.test.d.ts +2 -0
- package/dist/__tests__/sdk-update.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/assets.d.ts.map +1 -1
- package/dist/bootstrap-commands.d.ts.map +1 -1
- package/dist/cli-output.d.ts +4 -0
- package/dist/cli-output.d.ts.map +1 -0
- package/dist/cli.mjs +1863 -819
- 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/__tests__/bootstrap-root-owner.test-d.d.ts +2 -0
- package/dist/host/__tests__/bootstrap-root-owner.test-d.d.ts.map +1 -0
- package/dist/host/project-bootstrap.d.ts +4 -2
- package/dist/host/project-bootstrap.d.ts.map +1 -1
- package/dist/host/resource-bootstrap.d.ts +5 -3
- package/dist/host/resource-bootstrap.d.ts.map +1 -1
- package/dist/host.d.ts +2 -1
- package/dist/host.d.ts.map +1 -1
- package/dist/index.d.ts +11 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +1714 -862
- package/dist/index.mjs.map +1 -1
- package/dist/project.d.ts +0 -1
- package/dist/project.d.ts.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 +9 -0
- package/dist/sdk-bootstrap.d.ts.map +1 -1
- package/dist/sdk-cli.mjs +193 -25
- package/dist/sdk-cli.mjs.map +1 -1
- package/dist/sdk-install.d.ts.map +1 -1
- package/dist/sdk-update.d.ts +23 -0
- package/dist/sdk-update.d.ts.map +1 -0
- package/dist/software-capture.d.ts +123 -0
- package/dist/software-capture.d.ts.map +1 -0
- package/dist/tools/benchmark/__tests__/benchmark-vocabulary-owner.test-d.d.ts +2 -0
- package/dist/tools/benchmark/__tests__/benchmark-vocabulary-owner.test-d.d.ts.map +1 -0
- package/dist/tools/benchmark/harness.d.ts.map +1 -1
- 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/tools/native-preview.d.ts.map +1 -1
- package/dist/types.d.ts +28 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +32 -31
- package/dist/__tests__/tool-migration.test.d.ts +0 -2
- package/dist/__tests__/tool-migration.test.d.ts.map +0 -1
package/dist/cli.mjs
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { deflateRawSync } from 'zlib';
|
|
3
|
-
import { createHash } from 'crypto';
|
|
4
|
-
import { readFile, mkdtemp, rm, readdir, mkdir, cp, rename, writeFile, copyFile, stat, symlink, unlink,
|
|
3
|
+
import { createHash, randomUUID } from 'crypto';
|
|
4
|
+
import { readFile, mkdtemp, rm, readdir, mkdir, cp, rename, writeFile, realpath, copyFile, stat, symlink, unlink, access, readlink, lstat, rmdir } from 'fs/promises';
|
|
5
5
|
import { resolve, extname, dirname, basename, relative, isAbsolute, sep } from 'path';
|
|
6
|
+
import { GameProjectSchema } from '@forgeax/engine-project';
|
|
6
7
|
import { readFileSync, existsSync } from 'fs';
|
|
7
8
|
import { createRequire } from 'module';
|
|
8
9
|
import { pathToFileURL, fileURLToPath } from 'url';
|
|
@@ -19,15 +20,19 @@ import { createStandaloneRuntimeAssetBinding, ok, err } from '@forgeax/engine-ty
|
|
|
19
20
|
import { createParticleCodeNativeCookerFromRoots } from '@forgeax/engine-vfx-compiler';
|
|
20
21
|
import { pluginPack, reloadAssetHost } from '@forgeax/engine-vite-plugin-pack';
|
|
21
22
|
import { forgeaxShader } from '@forgeax/engine-vite-plugin-shader';
|
|
22
|
-
import { GameProjectSchema } from '@forgeax/engine-project';
|
|
23
23
|
import { runCliGltf } from '@forgeax/engine-gltf/cli-gltf';
|
|
24
24
|
import { scanEntries } from '@forgeax/engine-pack/cli-asset';
|
|
25
25
|
import { AssetGuid } from '@forgeax/engine-pack/guid';
|
|
26
|
-
import { execFile } from 'child_process';
|
|
26
|
+
import { spawn, execFile } from 'child_process';
|
|
27
27
|
import { tmpdir } from 'os';
|
|
28
28
|
import { promisify } from 'util';
|
|
29
|
-
import { decodeTape, openReplay, buildFrameModel, createRhiDebugError } from '@forgeax/engine-rhi-debug';
|
|
30
|
-
import {
|
|
29
|
+
import { decodeTape, openReplay, buildFrameModel, replayDeviceRequest, createRhiDebugError } from '@forgeax/engine-rhi-debug';
|
|
30
|
+
import { rhi, createShaderModule } from '@forgeax/engine-rhi-webgpu';
|
|
31
|
+
import { build, preview, createServer as createServer$1 } from 'vite';
|
|
32
|
+
import { createServer } from 'net';
|
|
33
|
+
import { FORGEAX_FRAME_SUBMITTED_DATASET } from '@forgeax/engine-app';
|
|
34
|
+
import { parseImage } from '@forgeax/engine-image/parse-image';
|
|
35
|
+
import { chromium } from 'playwright';
|
|
31
36
|
import { startVitest } from 'vitest/node';
|
|
32
37
|
import materialPreviewPlugin from '@forgeax/engine-preview/material';
|
|
33
38
|
import meshPreviewPlugin from '@forgeax/engine-preview/mesh';
|
|
@@ -299,17 +304,546 @@ var init_dist = __esm({
|
|
|
299
304
|
"src/dist.ts"() {
|
|
300
305
|
}
|
|
301
306
|
});
|
|
302
|
-
function
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
307
|
+
function projectError(code, expected, hint, detail) {
|
|
308
|
+
return { ok: false, error: { code, expected, hint, detail } };
|
|
309
|
+
}
|
|
310
|
+
async function readJson(path) {
|
|
311
|
+
return JSON.parse(await readFile(path, "utf8"));
|
|
312
|
+
}
|
|
313
|
+
function firstUnsupportedStandaloneRealm(entries2, inheritedRealm = "engine") {
|
|
314
|
+
for (const entry of entries2) {
|
|
315
|
+
const realm = entry.realm ?? inheritedRealm ?? "engine";
|
|
316
|
+
if (realm !== "engine") return { id: entry.id, realm };
|
|
317
|
+
if (entry.group === true) {
|
|
318
|
+
const unsupported = firstUnsupportedStandaloneRealm(
|
|
319
|
+
entry.config,
|
|
320
|
+
realm
|
|
321
|
+
);
|
|
322
|
+
if (unsupported !== void 0) return unsupported;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
return void 0;
|
|
326
|
+
}
|
|
327
|
+
function pluginModuleNames(entries2) {
|
|
328
|
+
return entries2.flatMap(
|
|
329
|
+
(entry) => entry.group === true ? pluginModuleNames(entry.config) : [entry.name]
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
async function readProjectFacts(rootInput = process.cwd()) {
|
|
333
|
+
const root = resolve(rootInput);
|
|
334
|
+
let forgeValue;
|
|
335
|
+
let packageValue;
|
|
336
|
+
try {
|
|
337
|
+
[forgeValue, packageValue] = await Promise.all([
|
|
338
|
+
readJson(resolve(root, "forge.json")),
|
|
339
|
+
readJson(resolve(root, "package.json"))
|
|
340
|
+
]);
|
|
341
|
+
} catch (cause) {
|
|
342
|
+
return projectError(
|
|
343
|
+
"project-manifest-unreadable",
|
|
344
|
+
"readable forge.json and package.json files",
|
|
345
|
+
"Run the command from a ForgeaX game root or pass its directory.",
|
|
346
|
+
{ root, reason: cause instanceof Error ? cause.message : String(cause) }
|
|
347
|
+
);
|
|
348
|
+
}
|
|
349
|
+
if (forgeValue === null || typeof forgeValue !== "object") {
|
|
350
|
+
return projectError(
|
|
351
|
+
"project-manifest-invalid",
|
|
352
|
+
"forge.json to contain an object",
|
|
353
|
+
"Repair forge.json before running DevKit.",
|
|
354
|
+
{ root }
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
if (packageValue === null || typeof packageValue !== "object") {
|
|
358
|
+
return projectError(
|
|
359
|
+
"package-manifest-invalid",
|
|
360
|
+
"package.json to contain an object",
|
|
361
|
+
"Repair package.json before running DevKit.",
|
|
362
|
+
{ root }
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
const parsedForge = GameProjectSchema.safeParse(forgeValue);
|
|
366
|
+
if (!parsedForge.success) {
|
|
367
|
+
return projectError(
|
|
368
|
+
"project-manifest-invalid",
|
|
369
|
+
"forge.json to satisfy @forgeax/engine-project GameProjectSchema",
|
|
370
|
+
"Repair the fields reported by the authoritative project schema.",
|
|
371
|
+
{ root, issues: parsedForge.error.issues }
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
const forge = parsedForge.data;
|
|
375
|
+
if (forge.id.length === 0 || forge.name.length === 0 || forge.entry === void 0 || forge.entry.length === 0) {
|
|
376
|
+
return projectError(
|
|
377
|
+
"project-manifest-invalid",
|
|
378
|
+
"forge.json to declare id, name, and entry",
|
|
379
|
+
"Restore the project entry; plugin Entries remain optional additions.",
|
|
380
|
+
{ root }
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
const plugins = forge.plugins ?? [];
|
|
384
|
+
const unsupportedRealm = firstUnsupportedStandaloneRealm(plugins);
|
|
385
|
+
if (unsupportedRealm !== void 0) {
|
|
386
|
+
return projectError(
|
|
387
|
+
"project-plugin-realm-unsupported",
|
|
388
|
+
"the standalone Devkit host to contain only engine-realm plugin Entries",
|
|
389
|
+
"Move Host or build plugins to a host that owns that physical realm.",
|
|
390
|
+
{ root, ...unsupportedRealm }
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
const entryPath = isAbsolute(forge.entry) ? forge.entry : resolve(root, forge.entry);
|
|
306
394
|
try {
|
|
307
|
-
|
|
308
|
-
|
|
395
|
+
await readFile(entryPath);
|
|
396
|
+
} catch {
|
|
397
|
+
return projectError(
|
|
398
|
+
"project-entry-missing",
|
|
399
|
+
"forge.json#entry to resolve to a readable module",
|
|
400
|
+
"Restore the game entry or update forge.json#entry.",
|
|
401
|
+
{ root, entry: forge.entry }
|
|
402
|
+
);
|
|
403
|
+
}
|
|
404
|
+
const packageJson = packageValue;
|
|
405
|
+
const forgeax = packageJson.forgeax;
|
|
406
|
+
const configuredRoots = forgeax !== null && typeof forgeax === "object" ? forgeax.assets?.roots : void 0;
|
|
407
|
+
const assetRoots = Array.isArray(configuredRoots) && configuredRoots.every((value) => typeof value === "string") ? configuredRoots : ["assets"];
|
|
408
|
+
const configuredImporters = forgeax !== null && typeof forgeax === "object" ? forgeax.assets?.importers : void 0;
|
|
409
|
+
const assetImporters = Array.isArray(configuredImporters) && configuredImporters.every((value) => typeof value === "string") ? configuredImporters : [];
|
|
410
|
+
const configuredPublicDir = forgeax !== null && typeof forgeax === "object" ? forgeax.assets?.publicDir : void 0;
|
|
411
|
+
const assetPublicDir = typeof configuredPublicDir === "string" ? configuredPublicDir : void 0;
|
|
412
|
+
const physics = forge.physics === "2d" || forge.physics === "3d" ? forge.physics : void 0;
|
|
413
|
+
const defaultScene = typeof forge.defaultScene === "string" && forge.defaultScene.length > 0 ? forge.defaultScene : void 0;
|
|
414
|
+
const normalizedEntry = forge.entry.startsWith("./") ? forge.entry : `./${forge.entry}`;
|
|
415
|
+
const bootstrapEntry = pluginModuleNames(plugins).map((name) => name.startsWith("./") ? name : `./${name}`).includes(normalizedEntry) ? void 0 : forge.entry;
|
|
416
|
+
return {
|
|
417
|
+
ok: true,
|
|
418
|
+
value: {
|
|
419
|
+
root,
|
|
420
|
+
id: forge.id,
|
|
421
|
+
name: forge.name,
|
|
422
|
+
entry: forge.entry,
|
|
423
|
+
...bootstrapEntry === void 0 ? {} : { bootstrapEntry },
|
|
424
|
+
plugins,
|
|
425
|
+
...physics === void 0 ? {} : { physics },
|
|
426
|
+
...defaultScene === void 0 ? {} : { defaultScene },
|
|
427
|
+
assetRoots,
|
|
428
|
+
...assetImporters.length === 0 ? {} : { assetImporters },
|
|
429
|
+
...assetPublicDir === void 0 ? {} : { assetPublicDir },
|
|
430
|
+
packageJson
|
|
431
|
+
}
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
function commandError(cause, fallbackCode) {
|
|
435
|
+
if (cause !== null && typeof cause === "object" && "code" in cause && "expected" in cause && "hint" in cause && "detail" in cause) {
|
|
436
|
+
return cause;
|
|
437
|
+
}
|
|
438
|
+
return {
|
|
439
|
+
code: fallbackCode,
|
|
440
|
+
expected: "the ForgeaX command to complete",
|
|
441
|
+
hint: "Inspect the underlying diagnostic and repair the owning input.",
|
|
442
|
+
detail: { reason: cause instanceof Error ? cause.message : String(cause) }
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
var init_project = __esm({
|
|
446
|
+
"src/project.ts"() {
|
|
447
|
+
}
|
|
448
|
+
});
|
|
449
|
+
function bindingError(code, expected, hint, detail = {}) {
|
|
450
|
+
return { ok: false, error: { code, expected, hint, detail } };
|
|
451
|
+
}
|
|
452
|
+
function isMissing(cause) {
|
|
453
|
+
return cause !== null && typeof cause === "object" && "code" in cause && cause.code === "ENOENT";
|
|
454
|
+
}
|
|
455
|
+
async function pathExists(path) {
|
|
456
|
+
try {
|
|
457
|
+
await access(path);
|
|
458
|
+
return true;
|
|
309
459
|
} catch {
|
|
310
460
|
return false;
|
|
311
461
|
}
|
|
312
462
|
}
|
|
463
|
+
function parseBinding(value, path) {
|
|
464
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
465
|
+
return bindingError(
|
|
466
|
+
"engine-binding-invalid",
|
|
467
|
+
"engine-binding.json to contain one object",
|
|
468
|
+
"Run forgeax engine unlink, then select a binding again.",
|
|
469
|
+
{ path }
|
|
470
|
+
);
|
|
471
|
+
}
|
|
472
|
+
const candidate = value;
|
|
473
|
+
if (candidate.schemaVersion !== ENGINE_BINDING_SCHEMA_VERSION) {
|
|
474
|
+
return bindingError(
|
|
475
|
+
"engine-binding-version-unsupported",
|
|
476
|
+
`engine-binding.json schemaVersion ${ENGINE_BINDING_SCHEMA_VERSION}`,
|
|
477
|
+
"Upgrade the SDK or remove the stale binding with forgeax engine unlink.",
|
|
478
|
+
{ path, schemaVersion: candidate.schemaVersion ?? null }
|
|
479
|
+
);
|
|
480
|
+
}
|
|
481
|
+
if (typeof candidate.path !== "string" || candidate.path.length === 0) {
|
|
482
|
+
return bindingError(
|
|
483
|
+
"engine-binding-path-missing",
|
|
484
|
+
"engine-binding.json to contain one non-empty local Engine path",
|
|
485
|
+
"Run forgeax engine use-local <engine-directory>.",
|
|
486
|
+
{ path }
|
|
487
|
+
);
|
|
488
|
+
}
|
|
489
|
+
return {
|
|
490
|
+
ok: true,
|
|
491
|
+
value: {
|
|
492
|
+
schemaVersion: ENGINE_BINDING_SCHEMA_VERSION,
|
|
493
|
+
path: resolve(candidate.path)
|
|
494
|
+
}
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
async function readEngineBinding(rootInput = process.cwd()) {
|
|
498
|
+
const root = resolve(rootInput);
|
|
499
|
+
const path = bindingFile(root);
|
|
500
|
+
try {
|
|
501
|
+
const value = JSON.parse(await readFile(path, "utf8"));
|
|
502
|
+
return parseBinding(value, path);
|
|
503
|
+
} catch (cause) {
|
|
504
|
+
if (isMissing(cause)) return { ok: true, value: null };
|
|
505
|
+
return bindingError(
|
|
506
|
+
"engine-binding-unreadable",
|
|
507
|
+
"engine-binding.json to be readable JSON",
|
|
508
|
+
"Repair or remove .forgeax/engine-binding.json, then select a binding again.",
|
|
509
|
+
{ path, reason: cause instanceof Error ? cause.message : String(cause) }
|
|
510
|
+
);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
async function writeEngineBinding(root, binding) {
|
|
514
|
+
const path = bindingFile(root);
|
|
515
|
+
const partial = `${path}.partial-${process.pid}`;
|
|
516
|
+
await mkdir(dirname(path), { recursive: true });
|
|
517
|
+
await writeFile(partial, `${JSON.stringify(binding, null, 2)}
|
|
518
|
+
`, "utf8");
|
|
519
|
+
await rename(partial, path);
|
|
520
|
+
}
|
|
521
|
+
function conditionalExport(value) {
|
|
522
|
+
if (typeof value === "string") return value;
|
|
523
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
524
|
+
const record = value;
|
|
525
|
+
for (const key of ["import", "browser", "node", "default", "require", "types"]) {
|
|
526
|
+
const candidate = record[key];
|
|
527
|
+
const selected = conditionalExport(candidate);
|
|
528
|
+
if (selected !== void 0) return selected;
|
|
529
|
+
}
|
|
530
|
+
return void 0;
|
|
531
|
+
}
|
|
532
|
+
function packageEntry(manifest) {
|
|
533
|
+
const exportsValue = manifest.exports;
|
|
534
|
+
if (exportsValue !== void 0) {
|
|
535
|
+
if (typeof exportsValue === "object" && exportsValue !== null && !Array.isArray(exportsValue)) {
|
|
536
|
+
const root = exportsValue["."];
|
|
537
|
+
const selected2 = conditionalExport(root ?? exportsValue);
|
|
538
|
+
if (selected2 !== void 0) return selected2;
|
|
539
|
+
}
|
|
540
|
+
const selected = conditionalExport(exportsValue);
|
|
541
|
+
if (selected !== void 0) return selected;
|
|
542
|
+
}
|
|
543
|
+
return typeof manifest.module === "string" ? manifest.module : typeof manifest.main === "string" ? manifest.main : void 0;
|
|
544
|
+
}
|
|
545
|
+
async function readManifest(path) {
|
|
546
|
+
const value = JSON.parse(await readFile(path, "utf8"));
|
|
547
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
548
|
+
throw new Error(`engine-package-manifest-invalid: ${path}`);
|
|
549
|
+
}
|
|
550
|
+
return value;
|
|
551
|
+
}
|
|
552
|
+
async function inspectEngineWorkspace(workspaceInput) {
|
|
553
|
+
const root = resolve(workspaceInput);
|
|
554
|
+
const packageRoot = resolve(root, "packages");
|
|
555
|
+
try {
|
|
556
|
+
await access(resolve(root, "pnpm-workspace.yaml"));
|
|
557
|
+
const entries2 = await readdir(packageRoot, { withFileTypes: true });
|
|
558
|
+
const packages = [];
|
|
559
|
+
for (const entry of entries2) {
|
|
560
|
+
if (!entry.isDirectory()) continue;
|
|
561
|
+
const packagePath = resolve(packageRoot, entry.name);
|
|
562
|
+
let manifest;
|
|
563
|
+
try {
|
|
564
|
+
manifest = await readManifest(resolve(packagePath, "package.json"));
|
|
565
|
+
} catch {
|
|
566
|
+
continue;
|
|
567
|
+
}
|
|
568
|
+
if (typeof manifest.name !== "string" || !manifest.name.startsWith("@forgeax/engine"))
|
|
569
|
+
continue;
|
|
570
|
+
if (typeof manifest.version !== "string") {
|
|
571
|
+
return bindingError(
|
|
572
|
+
"engine-package-version-missing",
|
|
573
|
+
"every local Engine package to declare a version",
|
|
574
|
+
"Restore the package manifest version before selecting a local Engine.",
|
|
575
|
+
{ package: manifest.name, root: packagePath }
|
|
576
|
+
);
|
|
577
|
+
}
|
|
578
|
+
const target = packageEntry(manifest);
|
|
579
|
+
const entryPath = target === void 0 ? void 0 : resolve(packagePath, target);
|
|
580
|
+
let entryDigest = null;
|
|
581
|
+
let builtAt2 = null;
|
|
582
|
+
if (entryPath !== void 0 && await pathExists(entryPath)) {
|
|
583
|
+
const [bytes, metadata] = await Promise.all([readFile(entryPath), stat(entryPath)]);
|
|
584
|
+
entryDigest = `sha256:${createHash("sha256").update(bytes).digest("hex")}`;
|
|
585
|
+
builtAt2 = metadata.mtime.toISOString();
|
|
586
|
+
}
|
|
587
|
+
packages.push({
|
|
588
|
+
name: manifest.name,
|
|
589
|
+
root: packagePath,
|
|
590
|
+
version: manifest.version,
|
|
591
|
+
entry: entryPath ?? null,
|
|
592
|
+
built: entryDigest !== null,
|
|
593
|
+
entryDigest,
|
|
594
|
+
builtAt: builtAt2
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
packages.sort((a, b) => a.name.localeCompare(b.name));
|
|
598
|
+
if (packages.length === 0) {
|
|
599
|
+
return bindingError(
|
|
600
|
+
"engine-workspace-empty",
|
|
601
|
+
"the local Engine workspace to contain @forgeax/engine packages",
|
|
602
|
+
"Pass the Engine repository or its source/engine SDK directory.",
|
|
603
|
+
{ root, packageRoot }
|
|
604
|
+
);
|
|
605
|
+
}
|
|
606
|
+
const versions = [...new Set(packages.map((item) => item.version))].sort();
|
|
607
|
+
const missingBuilds = packages.filter((item) => !item.built).map((item) => item.name);
|
|
608
|
+
const builtAt = packages.flatMap((item) => item.builtAt === null ? [] : [item.builtAt]).sort().at(-1) ?? null;
|
|
609
|
+
const digest = `sha256:${createHash("sha256").update(
|
|
610
|
+
packages.map((item) => `${item.name}\0${item.version}\0${item.entryDigest ?? "unbuilt"}`).join("\n")
|
|
611
|
+
).digest("hex")}`;
|
|
612
|
+
return {
|
|
613
|
+
ok: true,
|
|
614
|
+
value: {
|
|
615
|
+
root,
|
|
616
|
+
packageCount: packages.length,
|
|
617
|
+
builtPackages: packages.length - missingBuilds.length,
|
|
618
|
+
missingBuilds,
|
|
619
|
+
versions,
|
|
620
|
+
packages,
|
|
621
|
+
digest,
|
|
622
|
+
builtAt
|
|
623
|
+
}
|
|
624
|
+
};
|
|
625
|
+
} catch (cause) {
|
|
626
|
+
return bindingError(
|
|
627
|
+
isMissing(cause) ? "engine-workspace-missing" : "engine-workspace-unreadable",
|
|
628
|
+
"a readable Engine workspace with pnpm-workspace.yaml and packages/",
|
|
629
|
+
"Pass the Engine repository root, not its packages/ directory.",
|
|
630
|
+
{ root, packageRoot, reason: cause instanceof Error ? cause.message : String(cause) }
|
|
631
|
+
);
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
function projectDependencyMode(packageJson) {
|
|
635
|
+
let workspace = false;
|
|
636
|
+
let registry = false;
|
|
637
|
+
for (const section of [
|
|
638
|
+
"dependencies",
|
|
639
|
+
"devDependencies",
|
|
640
|
+
"optionalDependencies",
|
|
641
|
+
"peerDependencies"
|
|
642
|
+
]) {
|
|
643
|
+
const value = packageJson[section];
|
|
644
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) continue;
|
|
645
|
+
for (const version of Object.values(value)) {
|
|
646
|
+
if (typeof version !== "string") continue;
|
|
647
|
+
if (version.startsWith("workspace:") || version.startsWith("file:")) workspace = true;
|
|
648
|
+
else registry = true;
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
return workspace ? registry ? "mixed" : "workspace" : registry ? "registry" : "none";
|
|
652
|
+
}
|
|
653
|
+
async function sdkResolution(root) {
|
|
654
|
+
const packageJson = resolve(root, "node_modules", "@forgeax", "engine", "package.json");
|
|
655
|
+
try {
|
|
656
|
+
const manifest = await readManifest(packageJson);
|
|
657
|
+
const target = packageEntry(manifest);
|
|
658
|
+
const entry = target === void 0 ? null : resolve(dirname(packageJson), target);
|
|
659
|
+
return {
|
|
660
|
+
root: dirname(packageJson),
|
|
661
|
+
version: typeof manifest.version === "string" ? manifest.version : null,
|
|
662
|
+
entry,
|
|
663
|
+
built: entry !== null && await pathExists(entry),
|
|
664
|
+
source: "sdk"
|
|
665
|
+
};
|
|
666
|
+
} catch {
|
|
667
|
+
return { root: null, version: null, entry: null, built: false, source: "unresolved" };
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
async function engineStatusCommand(options = {}) {
|
|
671
|
+
const facts = await readProjectFacts(options.root);
|
|
672
|
+
if (!facts.ok) return facts;
|
|
673
|
+
const bindingResult = await readEngineBinding(facts.value.root);
|
|
674
|
+
if (!bindingResult.ok) return bindingResult;
|
|
675
|
+
const binding = bindingResult.value;
|
|
676
|
+
const mode = binding === null ? "sdk" : "local";
|
|
677
|
+
const projectDependencies = projectDependencyMode(facts.value.packageJson);
|
|
678
|
+
if (binding !== null) {
|
|
679
|
+
const localPath = binding.path;
|
|
680
|
+
const localBinding = {
|
|
681
|
+
schemaVersion: ENGINE_BINDING_SCHEMA_VERSION,
|
|
682
|
+
path: localPath
|
|
683
|
+
};
|
|
684
|
+
const workspaceResult = await inspectEngineWorkspace(localPath);
|
|
685
|
+
if (!workspaceResult.ok) {
|
|
686
|
+
const resolved3 = {
|
|
687
|
+
root: localPath,
|
|
688
|
+
version: null,
|
|
689
|
+
entry: null,
|
|
690
|
+
built: false,
|
|
691
|
+
source: "local"
|
|
692
|
+
};
|
|
693
|
+
return {
|
|
694
|
+
ok: true,
|
|
695
|
+
value: {
|
|
696
|
+
schemaVersion: ENGINE_BINDING_SCHEMA_VERSION,
|
|
697
|
+
root: facts.value.root,
|
|
698
|
+
binding: localBinding,
|
|
699
|
+
mode,
|
|
700
|
+
resolved: resolved3,
|
|
701
|
+
workspace: null,
|
|
702
|
+
projectDependencies,
|
|
703
|
+
npmCompatible: projectDependencies === "registry" || projectDependencies === "none",
|
|
704
|
+
healthy: false,
|
|
705
|
+
next: ["forgeax engine unlink", "forgeax engine use-local <engine-directory>"],
|
|
706
|
+
diagnostic: workspaceResult.error
|
|
707
|
+
}
|
|
708
|
+
};
|
|
709
|
+
}
|
|
710
|
+
const workspace = workspaceResult.value;
|
|
711
|
+
const version = workspace.versions.length === 1 ? workspace.versions[0] ?? null : null;
|
|
712
|
+
const umbrella = workspace.packages.find((item) => item.name === "@forgeax/engine");
|
|
713
|
+
const resolved2 = {
|
|
714
|
+
root: localPath,
|
|
715
|
+
version,
|
|
716
|
+
entry: umbrella?.entry ?? null,
|
|
717
|
+
built: umbrella?.built ?? false,
|
|
718
|
+
source: "local"
|
|
719
|
+
};
|
|
720
|
+
const healthy = workspace.versions.length === 1 && workspace.missingBuilds.length === 0;
|
|
721
|
+
return {
|
|
722
|
+
ok: true,
|
|
723
|
+
value: {
|
|
724
|
+
schemaVersion: ENGINE_BINDING_SCHEMA_VERSION,
|
|
725
|
+
root: facts.value.root,
|
|
726
|
+
binding: localBinding,
|
|
727
|
+
mode,
|
|
728
|
+
resolved: resolved2,
|
|
729
|
+
workspace,
|
|
730
|
+
projectDependencies,
|
|
731
|
+
npmCompatible: projectDependencies === "registry" || projectDependencies === "none",
|
|
732
|
+
healthy,
|
|
733
|
+
next: healthy ? ["forgeax build", "forgeax capture --backend auto --require-ui"] : ["pnpm build:engine", "forgeax engine doctor"]
|
|
734
|
+
}
|
|
735
|
+
};
|
|
736
|
+
}
|
|
737
|
+
const resolved = await sdkResolution(facts.value.root);
|
|
738
|
+
return {
|
|
739
|
+
ok: true,
|
|
740
|
+
value: {
|
|
741
|
+
schemaVersion: ENGINE_BINDING_SCHEMA_VERSION,
|
|
742
|
+
root: facts.value.root,
|
|
743
|
+
binding,
|
|
744
|
+
mode,
|
|
745
|
+
resolved,
|
|
746
|
+
workspace: null,
|
|
747
|
+
projectDependencies,
|
|
748
|
+
npmCompatible: projectDependencies === "registry" || projectDependencies === "none",
|
|
749
|
+
healthy: resolved.source === "sdk" && resolved.built,
|
|
750
|
+
next: resolved.source === "sdk" && resolved.built ? ["forgeax build", "forgeax capture --backend auto --require-ui"] : ["pnpm install", "forgeax doctor"]
|
|
751
|
+
}
|
|
752
|
+
};
|
|
753
|
+
}
|
|
754
|
+
async function engineUseLocalCommand(options) {
|
|
755
|
+
const root = resolve(options.root ?? process.cwd());
|
|
756
|
+
if (options.path === void 0 || options.path.trim().length === 0) {
|
|
757
|
+
return bindingError(
|
|
758
|
+
"engine-binding-path-missing",
|
|
759
|
+
"forgeax engine use-local <engine-directory>",
|
|
760
|
+
"Pass the local Engine repository or SDK source directory."
|
|
761
|
+
);
|
|
762
|
+
}
|
|
763
|
+
let localPath;
|
|
764
|
+
try {
|
|
765
|
+
localPath = await realpath(resolve(root, options.path));
|
|
766
|
+
} catch (cause) {
|
|
767
|
+
return bindingError(
|
|
768
|
+
"engine-workspace-missing",
|
|
769
|
+
"the local Engine directory to exist",
|
|
770
|
+
"Pass an existing Engine repository or SDK source directory.",
|
|
771
|
+
{
|
|
772
|
+
path: resolve(root, options.path),
|
|
773
|
+
reason: cause instanceof Error ? cause.message : String(cause)
|
|
774
|
+
}
|
|
775
|
+
);
|
|
776
|
+
}
|
|
777
|
+
const workspace = await inspectEngineWorkspace(localPath);
|
|
778
|
+
if (!workspace.ok) return workspace;
|
|
779
|
+
const binding = {
|
|
780
|
+
schemaVersion: ENGINE_BINDING_SCHEMA_VERSION,
|
|
781
|
+
path: localPath
|
|
782
|
+
};
|
|
783
|
+
if (options.dryRun !== true) await writeEngineBinding(root, binding);
|
|
784
|
+
const status = await engineStatusCommand({ root });
|
|
785
|
+
if (!status.ok) return status;
|
|
786
|
+
return {
|
|
787
|
+
ok: true,
|
|
788
|
+
value: {
|
|
789
|
+
...status.value,
|
|
790
|
+
next: workspace.value.missingBuilds.length === 0 ? ["forgeax build", "forgeax capture --backend auto --require-ui"] : ["pnpm build:engine", "forgeax engine doctor"]
|
|
791
|
+
}
|
|
792
|
+
};
|
|
793
|
+
}
|
|
794
|
+
async function engineUnlinkCommand(options = {}) {
|
|
795
|
+
const root = resolve(options.root ?? process.cwd());
|
|
796
|
+
if (options.dryRun !== true) await rm(bindingFile(root), { force: true });
|
|
797
|
+
return engineStatusCommand({ root });
|
|
798
|
+
}
|
|
799
|
+
async function engineDoctorCommand(options = {}) {
|
|
800
|
+
const status = await engineStatusCommand(options);
|
|
801
|
+
if (!status.ok) return status;
|
|
802
|
+
if (status.value.projectDependencies === "workspace" || status.value.projectDependencies === "mixed") {
|
|
803
|
+
return {
|
|
804
|
+
ok: false,
|
|
805
|
+
error: {
|
|
806
|
+
code: "engine-project-workspace-dependency",
|
|
807
|
+
expected: "the game project to use registry or SDK-resolved dependencies for npm consumers",
|
|
808
|
+
hint: "This project is a pnpm workspace; use pnpm here, or create a clean SDK game project for npm install.",
|
|
809
|
+
detail: { root: status.value.root, projectDependencies: status.value.projectDependencies }
|
|
810
|
+
}
|
|
811
|
+
};
|
|
812
|
+
}
|
|
813
|
+
if (status.value.diagnostic !== void 0) return { ok: false, error: status.value.diagnostic };
|
|
814
|
+
if (!status.value.healthy) {
|
|
815
|
+
return {
|
|
816
|
+
ok: false,
|
|
817
|
+
error: {
|
|
818
|
+
code: status.value.mode === "local" ? "engine-local-build-missing" : "engine-sdk-unresolved",
|
|
819
|
+
expected: "the selected Engine binding to resolve to built packages",
|
|
820
|
+
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.",
|
|
821
|
+
detail: { status: status.value }
|
|
822
|
+
}
|
|
823
|
+
};
|
|
824
|
+
}
|
|
825
|
+
return status;
|
|
826
|
+
}
|
|
827
|
+
var ENGINE_BINDING_SCHEMA_VERSION, bindingFile;
|
|
828
|
+
var init_engine_binding = __esm({
|
|
829
|
+
"src/engine-binding.ts"() {
|
|
830
|
+
init_project();
|
|
831
|
+
ENGINE_BINDING_SCHEMA_VERSION = "1.0.0";
|
|
832
|
+
bindingFile = (root) => resolve(root, ".forgeax", "engine-binding.json");
|
|
833
|
+
}
|
|
834
|
+
});
|
|
835
|
+
|
|
836
|
+
// src/host.ts
|
|
837
|
+
var host_exports = {};
|
|
838
|
+
__export(host_exports, {
|
|
839
|
+
createViteConfig: () => createViteConfig,
|
|
840
|
+
devKitDdcRoots: () => devKitDdcRoots,
|
|
841
|
+
ignoreDevKitCatalogPath: () => ignoreDevKitCatalogPath
|
|
842
|
+
});
|
|
843
|
+
function ignoreDevKitCatalogPath(path) {
|
|
844
|
+
const normalized = path.replace(/\\/g, "/");
|
|
845
|
+
return normalized.split("/").includes("shaders");
|
|
846
|
+
}
|
|
313
847
|
function devKitDdcRoots(projectRoot2) {
|
|
314
848
|
return {
|
|
315
849
|
buildCacheRoot: resolve(projectRoot2, ".forgeax", "ddc", "build-cache"),
|
|
@@ -367,40 +901,33 @@ function findEngineWorkspaceRoot() {
|
|
|
367
901
|
cursor = parent;
|
|
368
902
|
}
|
|
369
903
|
}
|
|
370
|
-
async function engineWorkspacePackages() {
|
|
371
|
-
if (
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
if (!
|
|
377
|
-
const
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
);
|
|
385
|
-
if (manifest === null || typeof manifest !== "object") continue;
|
|
386
|
-
const name = manifest.name;
|
|
387
|
-
if (typeof name === "string" && name.startsWith("@forgeax/engine-")) {
|
|
388
|
-
packages.set(name, { root, manifest });
|
|
389
|
-
}
|
|
390
|
-
} catch {
|
|
904
|
+
async function engineWorkspacePackages(workspaceRoot = findEngineWorkspaceRoot()) {
|
|
905
|
+
if (workspaceRoot === void 0) return /* @__PURE__ */ new Map();
|
|
906
|
+
const packageRoot = resolve(workspaceRoot, "packages");
|
|
907
|
+
if (!existsSync(packageRoot)) return /* @__PURE__ */ new Map();
|
|
908
|
+
const packages = /* @__PURE__ */ new Map();
|
|
909
|
+
for (const entry of await readdir(packageRoot, { withFileTypes: true })) {
|
|
910
|
+
if (!entry.isDirectory()) continue;
|
|
911
|
+
const root = resolve(packageRoot, entry.name);
|
|
912
|
+
try {
|
|
913
|
+
const manifest = JSON.parse(await readFile(resolve(root, "package.json"), "utf8"));
|
|
914
|
+
if (manifest === null || typeof manifest !== "object") continue;
|
|
915
|
+
const name = manifest.name;
|
|
916
|
+
if (typeof name === "string" && name.startsWith("@forgeax/engine")) {
|
|
917
|
+
packages.set(name, { root, manifest });
|
|
391
918
|
}
|
|
919
|
+
} catch {
|
|
392
920
|
}
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
return engineWorkspacePackagesPromise;
|
|
921
|
+
}
|
|
922
|
+
return packages;
|
|
396
923
|
}
|
|
397
|
-
function
|
|
924
|
+
function conditionalExport2(value) {
|
|
398
925
|
if (typeof value === "string") return value;
|
|
399
926
|
if (value === null || Array.isArray(value)) return void 0;
|
|
400
927
|
for (const condition of ["browser", "import", "node", "default"]) {
|
|
401
928
|
const candidate = value[condition];
|
|
402
929
|
if (candidate === void 0) continue;
|
|
403
|
-
const selected =
|
|
930
|
+
const selected = conditionalExport2(candidate);
|
|
404
931
|
if (selected !== void 0) return selected;
|
|
405
932
|
}
|
|
406
933
|
return void 0;
|
|
@@ -413,14 +940,14 @@ function packageExportTarget(manifest, subpath) {
|
|
|
413
940
|
return typeof main === "string" ? main : void 0;
|
|
414
941
|
}
|
|
415
942
|
if (typeof exportsValue === "string" || exportsValue === null) {
|
|
416
|
-
return subpath.length === 0 ?
|
|
943
|
+
return subpath.length === 0 ? conditionalExport2(exportsValue) : void 0;
|
|
417
944
|
}
|
|
418
945
|
const keys = Object.keys(exportsValue);
|
|
419
946
|
const subpathMap = keys.some((key) => key === "." || key.startsWith("./"));
|
|
420
|
-
if (!subpathMap) return subpath.length === 0 ?
|
|
947
|
+
if (!subpathMap) return subpath.length === 0 ? conditionalExport2(exportsValue) : void 0;
|
|
421
948
|
const requested = subpath.length === 0 ? "." : `./${subpath}`;
|
|
422
949
|
const exact = exportsValue[requested];
|
|
423
|
-
if (exact !== void 0) return
|
|
950
|
+
if (exact !== void 0) return conditionalExport2(exact);
|
|
424
951
|
for (const key of keys) {
|
|
425
952
|
const marker = key.indexOf("*");
|
|
426
953
|
if (marker < 0) continue;
|
|
@@ -430,14 +957,14 @@ function packageExportTarget(manifest, subpath) {
|
|
|
430
957
|
const replacement = requested.slice(prefix.length, requested.length - suffix.length);
|
|
431
958
|
const exportTarget = exportsValue[key];
|
|
432
959
|
if (exportTarget === void 0) continue;
|
|
433
|
-
const selected =
|
|
960
|
+
const selected = conditionalExport2(exportTarget);
|
|
434
961
|
return selected?.replaceAll("*", replacement);
|
|
435
962
|
}
|
|
436
963
|
return void 0;
|
|
437
964
|
}
|
|
438
965
|
function engineWorkspaceImport(source, packages) {
|
|
439
|
-
if (!source.startsWith("@forgeax/engine
|
|
440
|
-
const separator = source.indexOf("/", "@forgeax/engine
|
|
966
|
+
if (!source.startsWith("@forgeax/engine")) return void 0;
|
|
967
|
+
const separator = source.indexOf("/", "@forgeax/engine".length);
|
|
441
968
|
const packageName = separator < 0 ? source : source.slice(0, separator);
|
|
442
969
|
const subpath = separator < 0 ? "" : source.slice(separator + 1);
|
|
443
970
|
const packageInfo = packages.get(packageName);
|
|
@@ -451,14 +978,25 @@ function engineWorkspaceImport(source, packages) {
|
|
|
451
978
|
}
|
|
452
979
|
return absolute;
|
|
453
980
|
}
|
|
454
|
-
async function createEngineWorkspaceResolver() {
|
|
455
|
-
const
|
|
981
|
+
async function createEngineWorkspaceResolver(projectRoot2) {
|
|
982
|
+
const binding = await readEngineBinding(projectRoot2);
|
|
983
|
+
if (!binding.ok) {
|
|
984
|
+
throw new Error(`${binding.error.code}: ${binding.error.hint}`);
|
|
985
|
+
}
|
|
986
|
+
const localRoot = binding.value?.path;
|
|
987
|
+
if (localRoot !== void 0) {
|
|
988
|
+
const inspected = await inspectEngineWorkspace(localRoot);
|
|
989
|
+
if (!inspected.ok) throw new Error(`${inspected.error.code}: ${inspected.error.hint}`);
|
|
990
|
+
}
|
|
991
|
+
const packages = await engineWorkspacePackages(localRoot);
|
|
456
992
|
if (packages.size === 0) return void 0;
|
|
457
993
|
return {
|
|
458
994
|
name: "forgeax:devkit-engine-workspace-resolver",
|
|
995
|
+
enforce: "pre",
|
|
459
996
|
async resolveId(source, importer) {
|
|
460
997
|
const bareSource = source.split("?", 1)[0] ?? source;
|
|
461
|
-
if (!bareSource.startsWith("@forgeax/engine
|
|
998
|
+
if (!bareSource.startsWith("@forgeax/engine")) return null;
|
|
999
|
+
if (localRoot !== void 0) return engineWorkspaceImport(bareSource, packages);
|
|
462
1000
|
try {
|
|
463
1001
|
const resolved = await this.resolve(source, importer, { skipSelf: true });
|
|
464
1002
|
if (resolved !== null) return resolved;
|
|
@@ -574,17 +1112,20 @@ await app.pluginContext.plugin({
|
|
|
574
1112
|
return `import { forgeaxBundlerAdapter } from 'virtual:forgeax/bundler';
|
|
575
1113
|
import { createApp, createToolPreviewHost, createToolPreviewRecipe, fitToolPreviewCameraToAabb, gameHostPlugin, replayToolPreviewCapture } from '@forgeax/engine/app';
|
|
576
1114
|
import { createCatalogSource } from '@forgeax/engine/assets-runtime';
|
|
1115
|
+
import {
|
|
1116
|
+
createRuntimeAssetImportTransport,
|
|
1117
|
+
runtimeBinding,
|
|
1118
|
+
} from 'virtual:forgeax/pack-runtime';
|
|
577
1119
|
import { installCatalogLoader, projectPluginEntries } from '@forgeax/engine/plugin/loader';
|
|
578
1120
|
import { audioPlugin } from '@forgeax/engine/audio';
|
|
579
1121
|
import { webAudioPlugin } from '@forgeax/engine/audio-webaudio';
|
|
580
1122
|
import { physicsPlugin } from '@forgeax/engine/physics';
|
|
581
1123
|
import { skinningPlugin } from '@forgeax/engine/skinning';
|
|
582
1124
|
import { createPrimitiveMesh } from '@forgeax/engine/geometry';
|
|
583
|
-
import { createDevImportTransport } from '@forgeax/engine/runtime';
|
|
584
1125
|
import { mat4 } from '@forgeax/engine/math';
|
|
585
1126
|
import { CAMERA_PROJECTION_ORTHOGRAPHIC, Camera, DirectionalLight, Materials, MeshFilter, MeshRenderer, Skylight, SkyboxBackground, TONEMAP_NONE, TONEMAP_REINHARD_EXTENDED } from '@forgeax/engine/render';
|
|
586
|
-
import {
|
|
587
|
-
import {
|
|
1127
|
+
import { Transform } from '@forgeax/engine/scene';
|
|
1128
|
+
import { type SceneAsset } from '@forgeax/engine/types';
|
|
588
1129
|
import { ParticleEffectPlayer, vfxGpuEffectContribution } from '@forgeax/engine/vfx';
|
|
589
1130
|
import { createVfxRuntimeHost } from '@forgeax/engine/vfx-render';
|
|
590
1131
|
|
|
@@ -685,17 +1226,43 @@ const resizeCanvas = () => {
|
|
|
685
1226
|
const resizeObserver = new ResizeObserver(resizeCanvas);
|
|
686
1227
|
resizeObserver.observe(canvas);
|
|
687
1228
|
resizeCanvas();
|
|
688
|
-
const runtimeScopeBinding =
|
|
1229
|
+
const runtimeScopeBinding = runtimeBinding;
|
|
1230
|
+
if (runtimeScopeBinding === undefined) {
|
|
1231
|
+
throw new Error('forgeax: Vite Pack runtime binding is required in the generated host');
|
|
1232
|
+
}
|
|
689
1233
|
const assetCatalog = createCatalogSource({
|
|
690
1234
|
url: import.meta.env.DEV
|
|
691
1235
|
? runtimeScopeBinding.catalogUrl
|
|
692
1236
|
: new URL('pack-index.json', document.baseURI).href,
|
|
693
|
-
expectedScope: runtimeScopeBinding,
|
|
1237
|
+
...(import.meta.env.DEV ? { expectedScope: runtimeScopeBinding } : {}),
|
|
694
1238
|
});
|
|
695
1239
|
const bundler = {
|
|
696
1240
|
...forgeaxBundlerAdapter(),
|
|
697
|
-
...(import.meta.env.DEV
|
|
1241
|
+
...(import.meta.env.DEV
|
|
1242
|
+
? { importTransport: createRuntimeAssetImportTransport(runtimeScopeBinding) }
|
|
1243
|
+
: {}),
|
|
698
1244
|
};
|
|
1245
|
+
const assetPreparation = new WeakMap();
|
|
1246
|
+
|
|
1247
|
+
function prepareAssetRegistry(assets) {
|
|
1248
|
+
const existing = assetPreparation.get(assets);
|
|
1249
|
+
if (existing !== undefined) return existing;
|
|
1250
|
+
const pending = (async () => {
|
|
1251
|
+
if (import.meta.env.DEV) {
|
|
1252
|
+
assets.configureRuntimeBinding(runtimeScopeBinding);
|
|
1253
|
+
} else {
|
|
1254
|
+
assets.configurePackIndex(new URL('pack-index.json', document.baseURI).href);
|
|
1255
|
+
}
|
|
1256
|
+
assets.setCatalogSource(assetCatalog);
|
|
1257
|
+
if (vfxRuntimeHost !== undefined) {
|
|
1258
|
+
assets.installDecoder(vfxGpuEffectContribution.kind, vfxGpuEffectContribution.decoder);
|
|
1259
|
+
}
|
|
1260
|
+
const catalog = await assets.enumerateCatalog();
|
|
1261
|
+
if (!catalog.ok) throw catalog.error;
|
|
1262
|
+
})();
|
|
1263
|
+
assetPreparation.set(assets, pending);
|
|
1264
|
+
return pending;
|
|
1265
|
+
}
|
|
699
1266
|
|
|
700
1267
|
function previewStable(value) {
|
|
701
1268
|
if (value instanceof ArrayBuffer) return 'ArrayBuffer:' + JSON.stringify(Array.from(new Uint8Array(value)));
|
|
@@ -756,13 +1323,14 @@ async function previewOwnerFacts(assets, resource, payload) {
|
|
|
756
1323
|
}
|
|
757
1324
|
|
|
758
1325
|
async function prepareProject(app) {
|
|
1326
|
+
await prepareAssetRegistry(app.assets);
|
|
759
1327
|
previewWorld = app.world;
|
|
760
1328
|
const assets = app.assets;
|
|
761
1329
|
let canonicalEnvironment;
|
|
762
1330
|
if (resource !== undefined) {
|
|
763
1331
|
if (resource.kind !== 'texture') {
|
|
764
1332
|
if (canonicalEnvironmentGuid === null) throw new Error('canonical preview environment is unavailable');
|
|
765
|
-
const environment = await assets.
|
|
1333
|
+
const environment = await assets.loadByGuid(assets.parseGuid(canonicalEnvironmentGuid));
|
|
766
1334
|
if (!environment.ok) throw environment.error;
|
|
767
1335
|
if (environment.value.kind !== 'equirect') {
|
|
768
1336
|
throw new Error(\`canonical preview environment expected equirect, received \${environment.value.kind}\`);
|
|
@@ -777,7 +1345,7 @@ if (resource !== undefined) {
|
|
|
777
1345
|
const attached = await vfxRuntimeHost.attachWorld({ world: app.world, assets });
|
|
778
1346
|
if (!attached.ok) throw attached.error;
|
|
779
1347
|
}
|
|
780
|
-
const loaded = await assets.
|
|
1348
|
+
const loaded = await assets.loadByGuid(assets.parseGuid(resource.guid));
|
|
781
1349
|
if (!loaded.ok) throw loaded.error;
|
|
782
1350
|
const payload = loaded.value;
|
|
783
1351
|
if (resource.kind === 'vfx' && payload.kind !== 'particle-effect') {
|
|
@@ -790,7 +1358,7 @@ if (resource !== undefined) {
|
|
|
790
1358
|
const meshMaterialHandles = resource.kind === 'mesh' && payload.kind === 'mesh'
|
|
791
1359
|
? await Promise.all(payload.materialSlots.map(async (slot) => {
|
|
792
1360
|
if (slot.defaultMaterial === undefined) return 0;
|
|
793
|
-
const material = await assets.
|
|
1361
|
+
const material = await assets.loadByGuid(assets.parseGuid(slot.defaultMaterial));
|
|
794
1362
|
if (!material.ok) throw material.error;
|
|
795
1363
|
if (material.value.kind !== 'material') {
|
|
796
1364
|
throw new Error('mesh material slot resolved to a non-material asset');
|
|
@@ -920,15 +1488,13 @@ let defaultScene;
|
|
|
920
1488
|
let defaultSceneRoot;
|
|
921
1489
|
const defaultSceneGuid = ${JSON.stringify(facts.defaultScene)};
|
|
922
1490
|
if (defaultSceneGuid !== undefined) {
|
|
923
|
-
const loaded = await assets.
|
|
1491
|
+
const loaded = await assets.loadByGuid<SceneAsset>(assets.parseGuid(defaultSceneGuid));
|
|
924
1492
|
if (!loaded.ok) throw loaded.error;
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
const handle = app.world.allocSharedRef('SceneAsset', projected.value);
|
|
929
|
-
const instantiated = worldInstantiateScene(app.world, handle);
|
|
1493
|
+
defaultScene = loaded.value;
|
|
1494
|
+
const handle = app.world.allocSharedRef('SceneAsset', loaded.value);
|
|
1495
|
+
const instantiated = assets.instantiate<SceneAsset>(handle, app.world);
|
|
930
1496
|
if (!instantiated.ok) throw instantiated.error;
|
|
931
|
-
defaultSceneRoot = instantiated.value
|
|
1497
|
+
defaultSceneRoot = instantiated.value;
|
|
932
1498
|
}
|
|
933
1499
|
const uiRoot = document.querySelector('#game-ui');
|
|
934
1500
|
await app.pluginContext.plugin(gameHostPlugin({
|
|
@@ -967,8 +1533,6 @@ if (query.has('forgeax-tool-replay')) {
|
|
|
967
1533
|
...(resource === undefined ? {} : { resource }),
|
|
968
1534
|
canvas,
|
|
969
1535
|
app: {
|
|
970
|
-
...(assetCatalog === undefined ? {} : { assetCatalog }),
|
|
971
|
-
...(vfxRuntimeHost === undefined ? {} : { assetDecoders: [vfxGpuEffectContribution] }),
|
|
972
1536
|
plugins: [${plugins.join(", ")}],
|
|
973
1537
|
...(vfxRuntimeHost === undefined ? {} : { features: [vfxRuntimeHost.feature] }),
|
|
974
1538
|
},
|
|
@@ -1081,8 +1645,6 @@ if (query.has('forgeax-tool-replay')) {
|
|
|
1081
1645
|
const result = await createApp(
|
|
1082
1646
|
canvas,
|
|
1083
1647
|
{
|
|
1084
|
-
...(assetCatalog === undefined ? {} : { assetCatalog }),
|
|
1085
|
-
...(vfxRuntimeHost === undefined ? {} : { assetDecoders: [vfxGpuEffectContribution] }),
|
|
1086
1648
|
plugins: [${plugins.join(", ")}],
|
|
1087
1649
|
...(pointerLockAllowed === undefined ? {} : { pointerLockAllowed }),
|
|
1088
1650
|
},
|
|
@@ -1143,32 +1705,62 @@ function htmlSource(title) {
|
|
|
1143
1705
|
<div id="app-shell"><canvas id="app"></canvas><div id="game-ui"></div></div><div id="forgeax-fatal" role="alert"></div>
|
|
1144
1706
|
<script>
|
|
1145
1707
|
(() => {
|
|
1708
|
+
const appendStructuredFailure = (value, prefix, depth, seen, lines) => {
|
|
1709
|
+
if (value === null || typeof value !== 'object' || depth > 3) return;
|
|
1710
|
+
if (seen.has(value)) {
|
|
1711
|
+
lines.push((prefix || 'cause') + ': [circular]');
|
|
1712
|
+
return;
|
|
1713
|
+
}
|
|
1714
|
+
seen.add(value);
|
|
1715
|
+
const record = value;
|
|
1716
|
+
const name = typeof record.name === 'string' && record.name.length > 0
|
|
1717
|
+
? record.name
|
|
1718
|
+
: undefined;
|
|
1719
|
+
const code = typeof record.code === 'string' && record.code.length > 0
|
|
1720
|
+
? record.code
|
|
1721
|
+
: undefined;
|
|
1722
|
+
const message = typeof record.message === 'string' && record.message.length > 0
|
|
1723
|
+
? record.message
|
|
1724
|
+
: undefined;
|
|
1725
|
+
if (name !== undefined || code !== undefined || message !== undefined) {
|
|
1726
|
+
const identity = [name || 'Error', code].filter(Boolean).join(' ');
|
|
1727
|
+
lines.push((prefix ? prefix + ': ' : '') + identity + (message ? ': ' + message : ''));
|
|
1728
|
+
}
|
|
1729
|
+
for (const key of ['expected', 'hint', 'reason']) {
|
|
1730
|
+
if (typeof record[key] === 'string' && record[key].length > 0) {
|
|
1731
|
+
lines.push((prefix ? prefix + '.' : '') + key + ': ' + record[key]);
|
|
1732
|
+
}
|
|
1733
|
+
}
|
|
1734
|
+
for (const key of ['cause', 'detail', 'webgpuError', 'wgpuError', 'error']) {
|
|
1735
|
+
const nested = record[key];
|
|
1736
|
+
const nestedPrefix = (prefix ? prefix + '.' : '') + key;
|
|
1737
|
+
if (nested !== null && typeof nested === 'object') {
|
|
1738
|
+
appendStructuredFailure(nested, nestedPrefix, depth + 1, seen, lines);
|
|
1739
|
+
} else if (typeof nested === 'string' && nested.length > 0) {
|
|
1740
|
+
lines.push(nestedPrefix + ': ' + nested);
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1743
|
+
};
|
|
1146
1744
|
const formatStartupFailure = (reason) => {
|
|
1147
|
-
if (reason instanceof Error) return reason.message;
|
|
1148
1745
|
if (reason !== null && typeof reason === 'object') {
|
|
1149
|
-
const record = reason;
|
|
1150
1746
|
const lines = [];
|
|
1151
|
-
|
|
1152
|
-
if (typeof record[key] === 'string' && record[key].length > 0) {
|
|
1153
|
-
lines.push(key + ': ' + record[key]);
|
|
1154
|
-
}
|
|
1155
|
-
}
|
|
1156
|
-
const detail = record.detail;
|
|
1157
|
-
if (detail !== null && typeof detail === 'object') {
|
|
1158
|
-
for (const key of ['reason', 'guid']) {
|
|
1159
|
-
if (typeof detail[key] === 'string' && detail[key].length > 0) {
|
|
1160
|
-
lines.push('detail.' + key + ': ' + detail[key]);
|
|
1161
|
-
}
|
|
1162
|
-
}
|
|
1163
|
-
}
|
|
1747
|
+
appendStructuredFailure(reason, '', 0, new Set(), lines);
|
|
1164
1748
|
if (lines.length > 0) return lines.join('\\n');
|
|
1749
|
+
try {
|
|
1750
|
+
return JSON.stringify(reason) || 'Unknown structured startup failure';
|
|
1751
|
+
} catch {
|
|
1752
|
+
return 'Unserializable structured startup failure';
|
|
1753
|
+
}
|
|
1165
1754
|
}
|
|
1166
1755
|
return String(reason ?? 'Unknown startup failure');
|
|
1167
1756
|
};
|
|
1168
1757
|
const show = (reason) => {
|
|
1169
1758
|
const notice = document.querySelector('#forgeax-fatal');
|
|
1170
1759
|
if (!(notice instanceof HTMLElement)) return;
|
|
1171
|
-
|
|
1760
|
+
let message = formatStartupFailure(reason);
|
|
1761
|
+
if (/webgpu|adapter-unavailable|no usable (rendering )?backend/i.test(message)) {
|
|
1762
|
+
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.';
|
|
1763
|
+
}
|
|
1172
1764
|
notice.textContent = 'ForgeaX game failed to start.\\n' + message;
|
|
1173
1765
|
notice.style.display = 'grid';
|
|
1174
1766
|
};
|
|
@@ -1194,215 +1786,88 @@ async function createViteConfig(facts, command2, base = "/", options = {}) {
|
|
|
1194
1786
|
writeFile(resolve(generated, "main.ts"), hostSource(facts, bootstrapRoot, canonicalKit?.guid))
|
|
1195
1787
|
]);
|
|
1196
1788
|
const roots = [
|
|
1197
|
-
...projectRoots,
|
|
1198
|
-
...builtinPack === void 0 ? [] : [builtinPack],
|
|
1199
|
-
...canonicalKit === void 0 ? [] : [canonicalKit.root]
|
|
1200
|
-
];
|
|
1201
|
-
const importers = [...projectImporters(facts)];
|
|
1202
|
-
const runtimeBinding = createStandaloneRuntimeAssetBinding(facts.id);
|
|
1203
|
-
const engineWorkspaceResolver = await createEngineWorkspaceResolver();
|
|
1204
|
-
const consumerAliases = await consumerEngineAliases(facts.root);
|
|
1205
|
-
const plugins = [
|
|
1206
|
-
...engineWorkspaceResolver === void 0 ? [] : [engineWorkspaceResolver],
|
|
1207
|
-
forgeaxShader(),
|
|
1208
|
-
pluginPack({
|
|
1209
|
-
roots,
|
|
1210
|
-
runtimeBinding,
|
|
1211
|
-
ddc: devKitDdcRoots(facts.root),
|
|
1212
|
-
refresh: command2 === "serve" ? reloadAssetHost() : void 0,
|
|
1213
|
-
importers: [
|
|
1214
|
-
audioImporter,
|
|
1215
|
-
imageImporter,
|
|
1216
|
-
fbxImporter,
|
|
1217
|
-
gltfImporter,
|
|
1218
|
-
fontImporter,
|
|
1219
|
-
...importers
|
|
1220
|
-
],
|
|
1221
|
-
cookers: [createMaterialPackCooker(roots), createParticleCodeNativeCookerFromRoots(roots)],
|
|
1222
|
-
ignorePath
|
|
1223
|
-
})
|
|
1224
|
-
];
|
|
1225
|
-
return {
|
|
1226
|
-
root: generated,
|
|
1227
|
-
base,
|
|
1228
|
-
configFile: false,
|
|
1229
|
-
publicDir: facts.assetPublicDir === void 0 ? false : resolve(facts.root, facts.assetPublicDir),
|
|
1230
|
-
plugins,
|
|
1231
|
-
resolve: {
|
|
1232
|
-
alias: consumerAliases,
|
|
1233
|
-
dedupe: ["@forgeax/engine-app", "@forgeax/engine-ecs", "@forgeax/engine-runtime"]
|
|
1234
|
-
},
|
|
1235
|
-
server: { fs: { allow: [facts.root, ...roots] } },
|
|
1236
|
-
build: {
|
|
1237
|
-
target: "esnext",
|
|
1238
|
-
outDir: options.outDir ?? resolve(facts.root, "dist"),
|
|
1239
|
-
emptyOutDir: true,
|
|
1240
|
-
rollupOptions: { input: resolve(generated, "index.html") }
|
|
1241
|
-
}
|
|
1242
|
-
};
|
|
1243
|
-
}
|
|
1244
|
-
var hostRequire, engineWorkspacePackagesPromise;
|
|
1245
|
-
var init_host = __esm({
|
|
1246
|
-
"src/host.ts"() {
|
|
1247
|
-
hostRequire = createRequire(import.meta.url);
|
|
1248
|
-
}
|
|
1249
|
-
});
|
|
1250
|
-
function projectError(code, expected, hint, detail) {
|
|
1251
|
-
return { ok: false, error: { code, expected, hint, detail } };
|
|
1252
|
-
}
|
|
1253
|
-
async function readJson(path) {
|
|
1254
|
-
return JSON.parse(await readFile(path, "utf8"));
|
|
1255
|
-
}
|
|
1256
|
-
function firstUnsupportedStandaloneRealm(entries2, inheritedRealm = "engine") {
|
|
1257
|
-
for (const entry of entries2) {
|
|
1258
|
-
const realm = entry.realm ?? inheritedRealm ?? "engine";
|
|
1259
|
-
if (realm === "host") return { id: entry.id, realm };
|
|
1260
|
-
if (entry.group === true) {
|
|
1261
|
-
const unsupported = firstUnsupportedStandaloneRealm(
|
|
1262
|
-
entry.config,
|
|
1263
|
-
realm
|
|
1264
|
-
);
|
|
1265
|
-
if (unsupported !== void 0) return unsupported;
|
|
1266
|
-
}
|
|
1267
|
-
}
|
|
1268
|
-
return void 0;
|
|
1269
|
-
}
|
|
1270
|
-
function pluginModuleNames(entries2) {
|
|
1271
|
-
return entries2.flatMap(
|
|
1272
|
-
(entry) => entry.group === true ? pluginModuleNames(entry.config) : [entry.name]
|
|
1273
|
-
);
|
|
1274
|
-
}
|
|
1275
|
-
async function readProjectFacts(rootInput = process.cwd()) {
|
|
1276
|
-
const root = resolve(rootInput);
|
|
1277
|
-
let forgeValue;
|
|
1278
|
-
let packageValue;
|
|
1279
|
-
try {
|
|
1280
|
-
[forgeValue, packageValue] = await Promise.all([
|
|
1281
|
-
readJson(resolve(root, "forge.json")),
|
|
1282
|
-
readJson(resolve(root, "package.json"))
|
|
1283
|
-
]);
|
|
1284
|
-
} catch (cause) {
|
|
1285
|
-
return projectError(
|
|
1286
|
-
"project-manifest-unreadable",
|
|
1287
|
-
"readable forge.json and package.json files",
|
|
1288
|
-
"Run the command from a ForgeaX game root or pass its directory.",
|
|
1289
|
-
{ root, reason: cause instanceof Error ? cause.message : String(cause) }
|
|
1290
|
-
);
|
|
1291
|
-
}
|
|
1292
|
-
if (forgeValue === null || typeof forgeValue !== "object") {
|
|
1293
|
-
return projectError(
|
|
1294
|
-
"project-manifest-invalid",
|
|
1295
|
-
"forge.json to contain an object",
|
|
1296
|
-
"Repair forge.json before running DevKit.",
|
|
1297
|
-
{ root }
|
|
1298
|
-
);
|
|
1299
|
-
}
|
|
1300
|
-
if (packageValue === null || typeof packageValue !== "object") {
|
|
1301
|
-
return projectError(
|
|
1302
|
-
"package-manifest-invalid",
|
|
1303
|
-
"package.json to contain an object",
|
|
1304
|
-
"Repair package.json before running DevKit.",
|
|
1305
|
-
{ root }
|
|
1306
|
-
);
|
|
1307
|
-
}
|
|
1308
|
-
const parsedForge = GameProjectSchema.safeParse(forgeValue);
|
|
1309
|
-
if (!parsedForge.success) {
|
|
1310
|
-
return projectError(
|
|
1311
|
-
"project-manifest-invalid",
|
|
1312
|
-
"forge.json to satisfy @forgeax/engine-project GameProjectSchema",
|
|
1313
|
-
"Repair the fields reported by the authoritative project schema.",
|
|
1314
|
-
{ root, issues: parsedForge.error.issues }
|
|
1315
|
-
);
|
|
1316
|
-
}
|
|
1317
|
-
const forge = parsedForge.data;
|
|
1318
|
-
if (forge.id.length === 0 || forge.name.length === 0 || forge.entry === void 0 || forge.entry.length === 0) {
|
|
1319
|
-
return projectError(
|
|
1320
|
-
"project-manifest-invalid",
|
|
1321
|
-
"forge.json to declare id, name, and entry",
|
|
1322
|
-
"Restore the project entry; plugin Entries remain optional additions.",
|
|
1323
|
-
{ root }
|
|
1324
|
-
);
|
|
1325
|
-
}
|
|
1326
|
-
const plugins = forge.plugins ?? [];
|
|
1327
|
-
const unsupportedRealm = firstUnsupportedStandaloneRealm(plugins);
|
|
1328
|
-
if (unsupportedRealm !== void 0) {
|
|
1329
|
-
return projectError(
|
|
1330
|
-
"project-plugin-realm-unsupported",
|
|
1331
|
-
"the standalone Devkit host to contain only engine-realm plugin Entries",
|
|
1332
|
-
"Move Host or build plugins to a host that owns that physical realm.",
|
|
1333
|
-
{ root, ...unsupportedRealm }
|
|
1334
|
-
);
|
|
1335
|
-
}
|
|
1336
|
-
const entryPath = isAbsolute(forge.entry) ? forge.entry : resolve(root, forge.entry);
|
|
1337
|
-
try {
|
|
1338
|
-
await readFile(entryPath);
|
|
1339
|
-
} catch {
|
|
1340
|
-
return projectError(
|
|
1341
|
-
"project-entry-missing",
|
|
1342
|
-
"forge.json#entry to resolve to a readable module",
|
|
1343
|
-
"Restore the game entry or update forge.json#entry.",
|
|
1344
|
-
{ root, entry: forge.entry }
|
|
1345
|
-
);
|
|
1346
|
-
}
|
|
1347
|
-
const packageJson = packageValue;
|
|
1348
|
-
const forgeax = packageJson.forgeax;
|
|
1349
|
-
const configuredRoots = forgeax !== null && typeof forgeax === "object" ? forgeax.assets?.roots : void 0;
|
|
1350
|
-
const assetRoots = Array.isArray(configuredRoots) && configuredRoots.every((value) => typeof value === "string") ? configuredRoots : ["assets"];
|
|
1351
|
-
const configuredImporters = forgeax !== null && typeof forgeax === "object" ? forgeax.assets?.importers : void 0;
|
|
1352
|
-
const assetImporters = Array.isArray(configuredImporters) && configuredImporters.every((value) => typeof value === "string") ? configuredImporters : [];
|
|
1353
|
-
const configuredPublicDir = forgeax !== null && typeof forgeax === "object" ? forgeax.assets?.publicDir : void 0;
|
|
1354
|
-
const assetPublicDir = typeof configuredPublicDir === "string" ? configuredPublicDir : void 0;
|
|
1355
|
-
const physics = forge.physics === "2d" || forge.physics === "3d" ? forge.physics : void 0;
|
|
1356
|
-
const defaultScene = typeof forge.defaultScene === "string" && forge.defaultScene.length > 0 ? forge.defaultScene : void 0;
|
|
1357
|
-
const normalizedEntry = forge.entry.startsWith("./") ? forge.entry : `./${forge.entry}`;
|
|
1358
|
-
const bootstrapEntry = pluginModuleNames(plugins).map((name) => name.startsWith("./") ? name : `./${name}`).includes(normalizedEntry) ? void 0 : forge.entry;
|
|
1789
|
+
...projectRoots,
|
|
1790
|
+
...builtinPack === void 0 ? [] : [builtinPack],
|
|
1791
|
+
...canonicalKit === void 0 ? [] : [canonicalKit.root]
|
|
1792
|
+
];
|
|
1793
|
+
const importers = [...projectImporters(facts)];
|
|
1794
|
+
const runtimeBinding = createStandaloneRuntimeAssetBinding(facts.id);
|
|
1795
|
+
const engineWorkspaceResolver = await createEngineWorkspaceResolver(facts.root);
|
|
1796
|
+
const consumerAliases = await consumerEngineAliases(facts.root);
|
|
1797
|
+
const plugins = [
|
|
1798
|
+
...engineWorkspaceResolver === void 0 ? [] : [engineWorkspaceResolver],
|
|
1799
|
+
forgeaxShader(),
|
|
1800
|
+
pluginPack({
|
|
1801
|
+
roots,
|
|
1802
|
+
runtimeBinding,
|
|
1803
|
+
ddc: devKitDdcRoots(facts.root),
|
|
1804
|
+
refresh: command2 === "serve" ? reloadAssetHost() : void 0,
|
|
1805
|
+
importers: [
|
|
1806
|
+
audioImporter,
|
|
1807
|
+
imageImporter,
|
|
1808
|
+
fbxImporter,
|
|
1809
|
+
gltfImporter,
|
|
1810
|
+
fontImporter,
|
|
1811
|
+
...importers
|
|
1812
|
+
],
|
|
1813
|
+
cookers: [createMaterialPackCooker(roots), createParticleCodeNativeCookerFromRoots(roots)],
|
|
1814
|
+
ignorePath
|
|
1815
|
+
})
|
|
1816
|
+
];
|
|
1359
1817
|
return {
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1818
|
+
root: generated,
|
|
1819
|
+
base,
|
|
1820
|
+
configFile: false,
|
|
1821
|
+
publicDir: facts.assetPublicDir === void 0 ? false : resolve(facts.root, facts.assetPublicDir),
|
|
1822
|
+
plugins,
|
|
1823
|
+
resolve: {
|
|
1824
|
+
alias: consumerAliases,
|
|
1825
|
+
dedupe: ["@forgeax/engine"]
|
|
1826
|
+
},
|
|
1827
|
+
server: { ...options.server, fs: { allow: [facts.root, ...roots] } },
|
|
1828
|
+
build: {
|
|
1829
|
+
target: "esnext",
|
|
1830
|
+
outDir: options.outDir ?? resolve(facts.root, "dist"),
|
|
1831
|
+
emptyOutDir: true,
|
|
1832
|
+
rollupOptions: { input: resolve(generated, "index.html") }
|
|
1374
1833
|
}
|
|
1375
1834
|
};
|
|
1376
1835
|
}
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1836
|
+
var hostRequire;
|
|
1837
|
+
var init_host = __esm({
|
|
1838
|
+
"src/host.ts"() {
|
|
1839
|
+
init_engine_binding();
|
|
1840
|
+
hostRequire = createRequire(import.meta.url);
|
|
1841
|
+
}
|
|
1842
|
+
});
|
|
1843
|
+
|
|
1844
|
+
// src/types.ts
|
|
1845
|
+
function parseProjectPortOption(value, provided) {
|
|
1846
|
+
if (!provided) return { ok: true, value: void 0 };
|
|
1847
|
+
if (value !== void 0 && /^(0|[1-9]\d*)$/.test(value)) {
|
|
1848
|
+
const port = Number(value);
|
|
1849
|
+
if (Number.isSafeInteger(port) && port <= 65535) return { ok: true, value: port };
|
|
1850
|
+
}
|
|
1384
1851
|
return {
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1852
|
+
ok: false,
|
|
1853
|
+
error: {
|
|
1854
|
+
code: "cli-parse-error",
|
|
1855
|
+
expected: "--port to be 0 or an integer from 1 to 65535",
|
|
1856
|
+
hint: "Omit --port for strict 5173, pass a positive port for a strict binding, or pass 0 for an OS-assigned port.",
|
|
1857
|
+
detail: { option: "--port", received: value ?? null }
|
|
1858
|
+
}
|
|
1389
1859
|
};
|
|
1390
1860
|
}
|
|
1391
|
-
|
|
1392
|
-
|
|
1861
|
+
function resolveProjectPort(port) {
|
|
1862
|
+
return { port: port ?? 5173, strictPort: port !== 0 };
|
|
1863
|
+
}
|
|
1864
|
+
var init_types = __esm({
|
|
1865
|
+
"src/types.ts"() {
|
|
1393
1866
|
}
|
|
1394
1867
|
});
|
|
1395
1868
|
function failure(error) {
|
|
1396
1869
|
return { ok: false, error };
|
|
1397
1870
|
}
|
|
1398
|
-
function producerFailure(stderr, fallback, detail) {
|
|
1399
|
-
try {
|
|
1400
|
-
const parsed = JSON.parse(stderr.at(-1) ?? "");
|
|
1401
|
-
if (isCommandError(parsed)) return failure(parsed);
|
|
1402
|
-
} catch {
|
|
1403
|
-
}
|
|
1404
|
-
return failure({ ...fallback, detail: { ...detail, diagnostic: stderr.join("\n") } });
|
|
1405
|
-
}
|
|
1406
1871
|
async function sourcesAt(path) {
|
|
1407
1872
|
const info = await stat(path);
|
|
1408
1873
|
if (info.isFile()) return [path];
|
|
@@ -1486,15 +1951,16 @@ async function addGltf(sourcePath, dryRun2) {
|
|
|
1486
1951
|
stderrWrite: (line) => stderr.push(line)
|
|
1487
1952
|
});
|
|
1488
1953
|
if (exitCode !== 0) {
|
|
1489
|
-
|
|
1490
|
-
stderr
|
|
1491
|
-
|
|
1954
|
+
try {
|
|
1955
|
+
return failure(JSON.parse(stderr.at(-1) ?? ""));
|
|
1956
|
+
} catch {
|
|
1957
|
+
return failure({
|
|
1492
1958
|
code: "asset-add-failed",
|
|
1493
1959
|
expected: "the glTF producer to create or reuse a valid sidecar",
|
|
1494
|
-
hint: "Inspect the glTF source and its external references."
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1960
|
+
hint: "Inspect the glTF source and its external references.",
|
|
1961
|
+
detail: { source: sourcePath, diagnostic: stderr.join("\n") }
|
|
1962
|
+
});
|
|
1963
|
+
}
|
|
1498
1964
|
}
|
|
1499
1965
|
return { ok: true, value: { source: sourcePath, metaPath: `${sourcePath}.meta.json` } };
|
|
1500
1966
|
}
|
|
@@ -1537,15 +2003,16 @@ async function entries(options) {
|
|
|
1537
2003
|
{ stdoutWrite: (line) => stdout.push(line), stderrWrite: (line) => stderr.push(line) }
|
|
1538
2004
|
);
|
|
1539
2005
|
if (!result.ok) {
|
|
1540
|
-
|
|
1541
|
-
stderr
|
|
1542
|
-
|
|
2006
|
+
try {
|
|
2007
|
+
return failure(JSON.parse(stderr.at(-1) ?? ""));
|
|
2008
|
+
} catch {
|
|
2009
|
+
return failure({
|
|
1543
2010
|
code: "asset-authority-invalid",
|
|
1544
2011
|
expected: "all asset roots and sidecars to pass the pack scanner",
|
|
1545
|
-
hint: "Repair the first invalid asset authority reported by the scanner."
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
2012
|
+
hint: "Repair the first invalid asset authority reported by the scanner.",
|
|
2013
|
+
detail: { diagnostic: stderr.join("\n") }
|
|
2014
|
+
});
|
|
2015
|
+
}
|
|
1549
2016
|
}
|
|
1550
2017
|
return { ok: true, value: { facts: facts.value, entries: result.value } };
|
|
1551
2018
|
}
|
|
@@ -1630,7 +2097,6 @@ var init_package = __esm({
|
|
|
1630
2097
|
"@forgeax/engine-audio-webaudio": "workspace:*",
|
|
1631
2098
|
"@forgeax/engine-fbx": "workspace:*",
|
|
1632
2099
|
"@forgeax/engine-font": "workspace:*",
|
|
1633
|
-
"@forgeax/engine-geometry": "workspace:*",
|
|
1634
2100
|
"@forgeax/engine-gltf": "workspace:*",
|
|
1635
2101
|
"@forgeax/engine-image": "workspace:*",
|
|
1636
2102
|
"@forgeax/engine-input": "workspace:*",
|
|
@@ -1640,6 +2106,8 @@ var init_package = __esm({
|
|
|
1640
2106
|
"@forgeax/engine-preview": "workspace:*",
|
|
1641
2107
|
"@forgeax/engine-project": "workspace:*",
|
|
1642
2108
|
"@forgeax/engine-rhi-debug": "workspace:*",
|
|
2109
|
+
"@forgeax/engine-rhi-null": "workspace:*",
|
|
2110
|
+
"@forgeax/engine-rhi-webgpu": "workspace:*",
|
|
1643
2111
|
"@forgeax/engine-render": "workspace:*",
|
|
1644
2112
|
"@forgeax/engine-render-graph": "workspace:*",
|
|
1645
2113
|
"@forgeax/engine-runtime": "workspace:*",
|
|
@@ -1654,10 +2122,10 @@ var init_package = __esm({
|
|
|
1654
2122
|
jiti: "1.21.7",
|
|
1655
2123
|
playwright: "1.60.0",
|
|
1656
2124
|
vite: "8.0.10",
|
|
1657
|
-
vitest: "4.
|
|
2125
|
+
vitest: "4.0.18",
|
|
2126
|
+
webgpu: "^0.4.0"
|
|
1658
2127
|
},
|
|
1659
2128
|
devDependencies: {
|
|
1660
|
-
"@forgeax/engine-rhi-null": "workspace:*",
|
|
1661
2129
|
"@types/node": "^20.14.0"
|
|
1662
2130
|
},
|
|
1663
2131
|
forgeax: {
|
|
@@ -1829,7 +2297,7 @@ var init_init = __esm({
|
|
|
1829
2297
|
"@webgpu/types": "0.1.71",
|
|
1830
2298
|
tsx: "4.23.1",
|
|
1831
2299
|
typescript: "6.0.3",
|
|
1832
|
-
vitest: "4.
|
|
2300
|
+
vitest: "4.0.18"
|
|
1833
2301
|
};
|
|
1834
2302
|
}
|
|
1835
2303
|
});
|
|
@@ -1871,6 +2339,20 @@ var init_sdk = __esm({
|
|
|
1871
2339
|
"src/sdk.ts"() {
|
|
1872
2340
|
}
|
|
1873
2341
|
});
|
|
2342
|
+
function agentOnboarding(sdk, projectRoot2) {
|
|
2343
|
+
const root = projectRoot2 ?? sdk.root;
|
|
2344
|
+
return {
|
|
2345
|
+
read: [
|
|
2346
|
+
resolve(root, "AGENTS.md"),
|
|
2347
|
+
resolve(root, "skills", "forgeax-engine-sdk", "SKILL.md"),
|
|
2348
|
+
resolve(root, "skills", "forgeax-engine-sdk", "references", "feature-catalog.md")
|
|
2349
|
+
],
|
|
2350
|
+
next: projectRoot2 === void 0 ? {
|
|
2351
|
+
cwd: root,
|
|
2352
|
+
argv: ["node", "./bin/forgeax.mjs", "new", "../my-game"]
|
|
2353
|
+
} : { cwd: root, argv: ["pnpm", "exec", "forgeax", "list", "--json"] }
|
|
2354
|
+
};
|
|
2355
|
+
}
|
|
1874
2356
|
function sdkInitPath(sdk) {
|
|
1875
2357
|
return resolve(sdk.root, ...SDK_INIT_PATH);
|
|
1876
2358
|
}
|
|
@@ -1882,19 +2364,21 @@ function sdkProjectInstallArgs(store) {
|
|
|
1882
2364
|
"install",
|
|
1883
2365
|
"--frozen-lockfile",
|
|
1884
2366
|
"--ignore-scripts",
|
|
2367
|
+
"--config.pm-on-fail=ignore",
|
|
1885
2368
|
"--side-effects-cache=true",
|
|
1886
2369
|
"--child-concurrency=1"
|
|
1887
2370
|
];
|
|
1888
|
-
return store === void 0 ? common : [...common, "--offline", "--trust-lockfile", "--store-dir", store];
|
|
2371
|
+
return store === void 0 ? common : [...common, "--offline", "--config.trust-lockfile=true", "--store-dir", store];
|
|
1889
2372
|
}
|
|
1890
2373
|
function sdkBootstrapInstallArgs(store) {
|
|
1891
2374
|
const common = [
|
|
1892
2375
|
"install",
|
|
1893
2376
|
"--frozen-lockfile",
|
|
2377
|
+
"--config.pm-on-fail=ignore",
|
|
1894
2378
|
"--child-concurrency=1",
|
|
1895
2379
|
"--side-effects-cache=true"
|
|
1896
2380
|
];
|
|
1897
|
-
return store === void 0 ? common : [...common, "--offline", "--trust-lockfile", "--store-dir", store];
|
|
2381
|
+
return store === void 0 ? common : [...common, "--offline", "--config.trust-lockfile=true", "--store-dir", store];
|
|
1898
2382
|
}
|
|
1899
2383
|
function supportedPnpm(version) {
|
|
1900
2384
|
const [major = 0, minor = 0] = version.split(".").map(Number);
|
|
@@ -1984,7 +2468,8 @@ async function sdkInitCommand(sdk, options = {}) {
|
|
|
1984
2468
|
const report = {
|
|
1985
2469
|
...state,
|
|
1986
2470
|
root: sdk.root,
|
|
1987
|
-
store: sdk.store === void 0 ? "registry" : "offline"
|
|
2471
|
+
store: sdk.store === void 0 ? "registry" : "offline",
|
|
2472
|
+
onboarding: agentOnboarding(sdk)
|
|
1988
2473
|
};
|
|
1989
2474
|
if (options.dryRun === true || options.install === false) return { ok: true, value: report };
|
|
1990
2475
|
let staging;
|
|
@@ -2019,6 +2504,107 @@ var init_sdk_bootstrap = __esm({
|
|
|
2019
2504
|
SDK_INIT_SCHEMA_VERSION = "1.0.0";
|
|
2020
2505
|
}
|
|
2021
2506
|
});
|
|
2507
|
+
|
|
2508
|
+
// src/sdk-update.ts
|
|
2509
|
+
function parseVersion(version) {
|
|
2510
|
+
const match = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/.exec(
|
|
2511
|
+
version
|
|
2512
|
+
);
|
|
2513
|
+
if (match === null) return void 0;
|
|
2514
|
+
const major = match[1];
|
|
2515
|
+
const minor = match[2];
|
|
2516
|
+
const patch = match[3];
|
|
2517
|
+
if (major === void 0 || minor === void 0 || patch === void 0) return void 0;
|
|
2518
|
+
const prerelease = match[4]?.split(".");
|
|
2519
|
+
if (prerelease?.some(
|
|
2520
|
+
(identifier) => identifier.length === 0 || /^\d+$/.test(identifier) && /^0\d+/.test(identifier)
|
|
2521
|
+
)) {
|
|
2522
|
+
return void 0;
|
|
2523
|
+
}
|
|
2524
|
+
return {
|
|
2525
|
+
core: [major, minor, patch],
|
|
2526
|
+
...prerelease === void 0 ? {} : { prerelease }
|
|
2527
|
+
};
|
|
2528
|
+
}
|
|
2529
|
+
function compareNumericIdentifier(current, latest) {
|
|
2530
|
+
if (current.length !== latest.length) return latest.length > current.length ? 1 : -1;
|
|
2531
|
+
if (current === latest) return 0;
|
|
2532
|
+
return latest > current ? 1 : -1;
|
|
2533
|
+
}
|
|
2534
|
+
function comparePrerelease(current, latest) {
|
|
2535
|
+
const length = Math.max(current.length, latest.length);
|
|
2536
|
+
for (let index = 0; index < length; index += 1) {
|
|
2537
|
+
const currentIdentifier = current[index];
|
|
2538
|
+
const latestIdentifier = latest[index];
|
|
2539
|
+
if (currentIdentifier === void 0) return 1;
|
|
2540
|
+
if (latestIdentifier === void 0) return -1;
|
|
2541
|
+
if (currentIdentifier === latestIdentifier) continue;
|
|
2542
|
+
const currentNumeric = /^\d+$/.test(currentIdentifier);
|
|
2543
|
+
const latestNumeric = /^\d+$/.test(latestIdentifier);
|
|
2544
|
+
if (currentNumeric && latestNumeric) {
|
|
2545
|
+
return compareNumericIdentifier(currentIdentifier, latestIdentifier);
|
|
2546
|
+
}
|
|
2547
|
+
if (currentNumeric !== latestNumeric) return latestNumeric ? -1 : 1;
|
|
2548
|
+
return latestIdentifier > currentIdentifier ? 1 : -1;
|
|
2549
|
+
}
|
|
2550
|
+
return 0;
|
|
2551
|
+
}
|
|
2552
|
+
function newerSdkVersion(currentVersion, latestVersion) {
|
|
2553
|
+
const current = parseVersion(currentVersion);
|
|
2554
|
+
const latest = parseVersion(latestVersion);
|
|
2555
|
+
if (current === void 0 || latest === void 0) return false;
|
|
2556
|
+
for (let index = 0; index < current.core.length; index += 1) {
|
|
2557
|
+
const precedence = compareNumericIdentifier(
|
|
2558
|
+
current.core[index] ?? "0",
|
|
2559
|
+
latest.core[index] ?? "0"
|
|
2560
|
+
);
|
|
2561
|
+
if (precedence !== 0) return precedence > 0;
|
|
2562
|
+
}
|
|
2563
|
+
if (current.prerelease !== void 0 && latest.prerelease === void 0) return true;
|
|
2564
|
+
if (current.prerelease === void 0 || latest.prerelease === void 0) return false;
|
|
2565
|
+
return comparePrerelease(current.prerelease, latest.prerelease) > 0;
|
|
2566
|
+
}
|
|
2567
|
+
function offline() {
|
|
2568
|
+
return ["1", "true"].includes((process.env.npm_config_offline ?? "").toLowerCase());
|
|
2569
|
+
}
|
|
2570
|
+
async function checkSdkUpdate(currentVersion) {
|
|
2571
|
+
if (process.env.FORGEAX_DISABLE_UPDATE_CHECK === "1") {
|
|
2572
|
+
return { status: "skipped", currentVersion, reason: "disabled" };
|
|
2573
|
+
}
|
|
2574
|
+
if (offline()) return { status: "skipped", currentVersion, reason: "offline" };
|
|
2575
|
+
try {
|
|
2576
|
+
const registry = process.env.npm_config_registry ?? "https://registry.npmjs.org/";
|
|
2577
|
+
const response = await fetch(
|
|
2578
|
+
new URL("@forgeax%2Fengine-sdk", `${registry.replace(/\/$/, "")}/`),
|
|
2579
|
+
{
|
|
2580
|
+
headers: { accept: "application/vnd.npm.install-v1+json" },
|
|
2581
|
+
signal: AbortSignal.timeout(2e3)
|
|
2582
|
+
}
|
|
2583
|
+
);
|
|
2584
|
+
if (!response.ok) throw new Error(`registry-http-${response.status}`);
|
|
2585
|
+
const metadata = await response.json();
|
|
2586
|
+
const latestVersion = metadata["dist-tags"]?.latest;
|
|
2587
|
+
if (typeof latestVersion !== "string") throw new Error("registry-latest-version-missing");
|
|
2588
|
+
return newerSdkVersion(currentVersion, latestVersion) ? {
|
|
2589
|
+
status: "available",
|
|
2590
|
+
currentVersion,
|
|
2591
|
+
latestVersion,
|
|
2592
|
+
migrationRisk: UPDATE_MIGRATION_RISK
|
|
2593
|
+
} : { status: "current", currentVersion, latestVersion };
|
|
2594
|
+
} catch (cause) {
|
|
2595
|
+
return {
|
|
2596
|
+
status: "unavailable",
|
|
2597
|
+
currentVersion,
|
|
2598
|
+
reason: cause instanceof Error ? cause.message : String(cause)
|
|
2599
|
+
};
|
|
2600
|
+
}
|
|
2601
|
+
}
|
|
2602
|
+
var UPDATE_MIGRATION_RISK;
|
|
2603
|
+
var init_sdk_update = __esm({
|
|
2604
|
+
"src/sdk-update.ts"() {
|
|
2605
|
+
UPDATE_MIGRATION_RISK = "Install a newer SDK in a separate directory. Existing games remain pinned and are not migrated automatically; review release notes and migrate and test each game before changing its Engine version.";
|
|
2606
|
+
}
|
|
2607
|
+
});
|
|
2022
2608
|
function slash(path) {
|
|
2023
2609
|
return path.split(sep).join("/");
|
|
2024
2610
|
}
|
|
@@ -2646,9 +3232,16 @@ async function newCommand(options = {}) {
|
|
|
2646
3232
|
env: { ...process.env, CI: "true" },
|
|
2647
3233
|
maxBuffer: 16 * 1024 * 1024
|
|
2648
3234
|
});
|
|
3235
|
+
const sdkUpdate = await checkSdkUpdate(sdk.manifest.sdkVersion);
|
|
2649
3236
|
return {
|
|
2650
3237
|
ok: true,
|
|
2651
|
-
value: {
|
|
3238
|
+
value: {
|
|
3239
|
+
root,
|
|
3240
|
+
template: templateId,
|
|
3241
|
+
sdkVersion: sdk.manifest.sdkVersion,
|
|
3242
|
+
onboarding: agentOnboarding(sdk, root),
|
|
3243
|
+
sdkUpdate
|
|
3244
|
+
}
|
|
2652
3245
|
};
|
|
2653
3246
|
} catch (cause) {
|
|
2654
3247
|
if (staging !== void 0) await rm(staging, { recursive: true, force: true });
|
|
@@ -2681,6 +3274,7 @@ var init_bootstrap_commands = __esm({
|
|
|
2681
3274
|
init_project();
|
|
2682
3275
|
init_sdk();
|
|
2683
3276
|
init_sdk_bootstrap();
|
|
3277
|
+
init_sdk_update();
|
|
2684
3278
|
init_skill_install();
|
|
2685
3279
|
execFileAsync2 = promisify(execFile);
|
|
2686
3280
|
}
|
|
@@ -2692,7 +3286,7 @@ __export(plugin_authoring_exports, {
|
|
|
2692
3286
|
pluginInstallCommand: () => pluginInstallCommand,
|
|
2693
3287
|
pluginUninstallCommand: () => pluginUninstallCommand
|
|
2694
3288
|
});
|
|
2695
|
-
async function
|
|
3289
|
+
async function readManifest2(root) {
|
|
2696
3290
|
const path = resolve(root, "forge.json");
|
|
2697
3291
|
try {
|
|
2698
3292
|
const raw = await readFile(path, "utf8");
|
|
@@ -2745,7 +3339,7 @@ async function mutateDependency(root, action, dependency) {
|
|
|
2745
3339
|
}
|
|
2746
3340
|
async function pluginInstallCommand(options) {
|
|
2747
3341
|
const root = resolve(options.root ?? process.cwd());
|
|
2748
|
-
const manifest = await
|
|
3342
|
+
const manifest = await readManifest2(root);
|
|
2749
3343
|
if (!manifest.ok) return manifest;
|
|
2750
3344
|
const parsed = GameProjectSchema.safeParse(manifest.value.value);
|
|
2751
3345
|
if (!parsed.success) {
|
|
@@ -2798,7 +3392,7 @@ async function pluginInstallCommand(options) {
|
|
|
2798
3392
|
}
|
|
2799
3393
|
async function pluginUninstallCommand(options) {
|
|
2800
3394
|
const root = resolve(options.root ?? process.cwd());
|
|
2801
|
-
const manifest = await
|
|
3395
|
+
const manifest = await readManifest2(root);
|
|
2802
3396
|
if (!manifest.ok) return manifest;
|
|
2803
3397
|
const parsed = GameProjectSchema.safeParse(manifest.value.value);
|
|
2804
3398
|
if (!parsed.success) {
|
|
@@ -2849,491 +3443,12 @@ var init_plugin_authoring = __esm({
|
|
|
2849
3443
|
execFileAsync3 = promisify(execFile);
|
|
2850
3444
|
}
|
|
2851
3445
|
});
|
|
2852
|
-
var RhiError;
|
|
2853
|
-
var init_dist2 = __esm({
|
|
2854
|
-
"../rhi/dist/index.mjs"() {
|
|
2855
|
-
RhiError = class extends Error {
|
|
2856
|
-
code;
|
|
2857
|
-
expected;
|
|
2858
|
-
hint;
|
|
2859
|
-
detail;
|
|
2860
|
-
constructor(args2) {
|
|
2861
|
-
super(`[RhiError ${args2.code}] expected: ${args2.expected}; hint: ${args2.hint}`);
|
|
2862
|
-
this.name = "RhiError";
|
|
2863
|
-
this.code = args2.code;
|
|
2864
|
-
this.expected = args2.expected;
|
|
2865
|
-
this.hint = args2.hint;
|
|
2866
|
-
this.detail = args2.detail;
|
|
2867
|
-
}
|
|
2868
|
-
};
|
|
2869
|
-
}
|
|
2870
|
-
});
|
|
2871
|
-
function readPassLabel(desc) {
|
|
2872
|
-
if (desc && typeof desc.label === "string" && desc.label.length > 0) {
|
|
2873
|
-
return desc.label;
|
|
2874
|
-
}
|
|
2875
|
-
return "<unnamed>";
|
|
2876
|
-
}
|
|
2877
|
-
function readRecord(handle) {
|
|
2878
|
-
if (handle === null || typeof handle !== "object") return void 0;
|
|
2879
|
-
const tagged = handle;
|
|
2880
|
-
return tagged[BOOKKEEPING_KEY];
|
|
2881
|
-
}
|
|
2882
|
-
function isHandleDestroyed(handle) {
|
|
2883
|
-
if (readRecord(handle)?.destroyed === true) {
|
|
2884
|
-
return err(
|
|
2885
|
-
new RhiError({
|
|
2886
|
-
code: "destroy-after-destroy",
|
|
2887
|
-
expected: "GPU buffer/texture handle has not been destroyed yet",
|
|
2888
|
-
hint: "object already destroyed; track lifecycle in caller or check isDestroyed before re-destroy"
|
|
2889
|
-
})
|
|
2890
|
-
);
|
|
2891
|
-
}
|
|
2892
|
-
return ok(void 0);
|
|
2893
|
-
}
|
|
2894
|
-
function acquireCanvasContext(_canvas) {
|
|
2895
|
-
return ok(new RhiNullCanvasContext());
|
|
2896
|
-
}
|
|
2897
|
-
function createShaderModule(_device, _desc) {
|
|
2898
|
-
return Promise.resolve(ok({}));
|
|
2899
|
-
}
|
|
2900
|
-
function requestAdapter(_opts, _compatibleSurface) {
|
|
2901
|
-
return Promise.resolve(ok(new RhiNullAdapter()));
|
|
2902
|
-
}
|
|
2903
|
-
var RhiNullRenderPassEncoder, RhiNullComputePassEncoder, DeviceCounter, RhiNullCommandEncoder, BOOKKEEPING_KEY, Bookkeeper, nextDeviceId, RhiNullDevice, EMPTY_FEATURES, EMPTY_LIMITS, NEVER, RhiNullQueue, RhiNullAdapter, RhiNullCanvasContext, rhi;
|
|
2904
|
-
var init_dist3 = __esm({
|
|
2905
|
-
"../rhi-null/dist/index.mjs"() {
|
|
2906
|
-
init_dist2();
|
|
2907
|
-
RhiNullRenderPassEncoder = class {
|
|
2908
|
-
/** Number of draw* calls issued on this pass (AC-06 readback). */
|
|
2909
|
-
drawCount = 0;
|
|
2910
|
-
bindGroupCount = 0;
|
|
2911
|
-
/** Most recent setVertexBuffer / setBindGroup ownership validation; ok unless
|
|
2912
|
-
* a foreign handle was passed (AC-09 readback). */
|
|
2913
|
-
lastValidation = ok(void 0);
|
|
2914
|
-
bookkeeper;
|
|
2915
|
-
counter;
|
|
2916
|
-
passName;
|
|
2917
|
-
constructor(bookkeeper, counter, passName) {
|
|
2918
|
-
this.bookkeeper = bookkeeper;
|
|
2919
|
-
this.counter = counter;
|
|
2920
|
-
this.passName = passName;
|
|
2921
|
-
}
|
|
2922
|
-
setPipeline(_pipeline) {
|
|
2923
|
-
}
|
|
2924
|
-
setVertexBuffer(_slot, buffer, _offset, _size) {
|
|
2925
|
-
this.lastValidation = this.bookkeeper.validateOwnership(buffer);
|
|
2926
|
-
}
|
|
2927
|
-
setIndexBuffer(_buffer, _format, _offset, _size) {
|
|
2928
|
-
}
|
|
2929
|
-
setBindGroup(_index, bindGroup, _dynamicOffsetsData, _dynamicOffsetsDataStart, _dynamicOffsetsDataLength) {
|
|
2930
|
-
this.bindGroupCount++;
|
|
2931
|
-
this.counter?.recordBindGroup();
|
|
2932
|
-
this.lastValidation = this.bookkeeper.validateOwnership(bindGroup);
|
|
2933
|
-
}
|
|
2934
|
-
draw(_vertexCount, _instanceCount, _firstVertex, _firstInstance) {
|
|
2935
|
-
this.drawCount++;
|
|
2936
|
-
this.counter?.recordDraw();
|
|
2937
|
-
}
|
|
2938
|
-
drawIndexed(_indexCount, _instanceCount, _firstIndex, _baseVertex, _firstInstance) {
|
|
2939
|
-
this.drawCount++;
|
|
2940
|
-
this.counter?.recordDraw();
|
|
2941
|
-
}
|
|
2942
|
-
end() {
|
|
2943
|
-
this.counter?.recordPassName(this.passName);
|
|
2944
|
-
}
|
|
2945
|
-
setViewport(_x, _y, _w, _h, _minDepth, _maxDepth) {
|
|
2946
|
-
}
|
|
2947
|
-
setScissorRect(_x, _y, _w, _h) {
|
|
2948
|
-
}
|
|
2949
|
-
setBlendConstant(_color) {
|
|
2950
|
-
}
|
|
2951
|
-
setStencilReference(_reference) {
|
|
2952
|
-
}
|
|
2953
|
-
drawIndirect(_indirectBuffer, _indirectOffset) {
|
|
2954
|
-
this.drawCount++;
|
|
2955
|
-
this.counter?.recordDraw();
|
|
2956
|
-
}
|
|
2957
|
-
drawIndexedIndirect(_indirectBuffer, _indirectOffset) {
|
|
2958
|
-
this.drawCount++;
|
|
2959
|
-
this.counter?.recordDraw();
|
|
2960
|
-
}
|
|
2961
|
-
pushDebugGroup(_groupLabel) {
|
|
2962
|
-
}
|
|
2963
|
-
popDebugGroup() {
|
|
2964
|
-
}
|
|
2965
|
-
insertDebugMarker(_markerLabel) {
|
|
2966
|
-
}
|
|
2967
|
-
executeBundles(_bundles) {
|
|
2968
|
-
return ok(void 0);
|
|
2969
|
-
}
|
|
2970
|
-
beginOcclusionQuery(_queryIndex) {
|
|
2971
|
-
return ok(void 0);
|
|
2972
|
-
}
|
|
2973
|
-
endOcclusionQuery() {
|
|
2974
|
-
return ok(void 0);
|
|
2975
|
-
}
|
|
2976
|
-
};
|
|
2977
|
-
RhiNullComputePassEncoder = class {
|
|
2978
|
-
/** Number of dispatchWorkgroups calls issued on this pass (readback). */
|
|
2979
|
-
dispatchCount = 0;
|
|
2980
|
-
/** Most recent setBindGroup ownership validation (AC-09 readback). */
|
|
2981
|
-
lastValidation = ok(void 0);
|
|
2982
|
-
bookkeeper;
|
|
2983
|
-
counter;
|
|
2984
|
-
passName;
|
|
2985
|
-
constructor(bookkeeper, counter, passName) {
|
|
2986
|
-
this.bookkeeper = bookkeeper;
|
|
2987
|
-
this.counter = counter;
|
|
2988
|
-
this.passName = passName;
|
|
2989
|
-
}
|
|
2990
|
-
setPipeline(_pipeline) {
|
|
2991
|
-
}
|
|
2992
|
-
setBindGroup(_index, bindGroup, _dynamicOffsets) {
|
|
2993
|
-
this.lastValidation = this.bookkeeper.validateOwnership(bindGroup);
|
|
2994
|
-
}
|
|
2995
|
-
dispatchWorkgroups(_x, _y, _z) {
|
|
2996
|
-
this.dispatchCount++;
|
|
2997
|
-
this.counter?.recordDispatch();
|
|
2998
|
-
}
|
|
2999
|
-
dispatchWorkgroupsIndirect(indirectBuffer, _indirectOffset) {
|
|
3000
|
-
this.lastValidation = this.bookkeeper.validateOwnership(indirectBuffer);
|
|
3001
|
-
this.dispatchCount++;
|
|
3002
|
-
this.counter?.recordDispatch();
|
|
3003
|
-
}
|
|
3004
|
-
end() {
|
|
3005
|
-
this.counter?.recordPassName(this.passName);
|
|
3006
|
-
}
|
|
3007
|
-
};
|
|
3008
|
-
DeviceCounter = class {
|
|
3009
|
-
constructor(device) {
|
|
3010
|
-
this.device = device;
|
|
3011
|
-
}
|
|
3012
|
-
device;
|
|
3013
|
-
recordDraw() {
|
|
3014
|
-
this.device.totalDrawCount++;
|
|
3015
|
-
}
|
|
3016
|
-
recordDispatch() {
|
|
3017
|
-
this.device.totalDispatchCount++;
|
|
3018
|
-
}
|
|
3019
|
-
recordBindGroup() {
|
|
3020
|
-
this.device.totalBindGroupCount++;
|
|
3021
|
-
}
|
|
3022
|
-
recordPassName(name) {
|
|
3023
|
-
this.device.framePassNames.push(name);
|
|
3024
|
-
}
|
|
3025
|
-
};
|
|
3026
|
-
RhiNullCommandEncoder = class {
|
|
3027
|
-
bookkeeper;
|
|
3028
|
-
counter;
|
|
3029
|
-
constructor(bookkeeper, device) {
|
|
3030
|
-
this.bookkeeper = bookkeeper;
|
|
3031
|
-
this.counter = new DeviceCounter(device);
|
|
3032
|
-
}
|
|
3033
|
-
beginRenderPass(desc) {
|
|
3034
|
-
const label = readPassLabel(desc);
|
|
3035
|
-
return new RhiNullRenderPassEncoder(this.bookkeeper, this.counter, label);
|
|
3036
|
-
}
|
|
3037
|
-
beginComputePass(desc) {
|
|
3038
|
-
const label = readPassLabel(desc);
|
|
3039
|
-
return new RhiNullComputePassEncoder(this.bookkeeper, this.counter, label);
|
|
3040
|
-
}
|
|
3041
|
-
copyBufferToBuffer(_source, _sourceOffsetOrDestination, _destinationOrSize, _destinationOffset, _size) {
|
|
3042
|
-
}
|
|
3043
|
-
copyBufferToTexture(_source, _destination, _copySize) {
|
|
3044
|
-
}
|
|
3045
|
-
copyTextureToBuffer(_source, _destination, _copySize) {
|
|
3046
|
-
}
|
|
3047
|
-
copyTextureToTexture(_source, _destination, _copySize) {
|
|
3048
|
-
}
|
|
3049
|
-
clearBuffer(_buffer, _offset, _size) {
|
|
3050
|
-
}
|
|
3051
|
-
resolveQuerySet(_querySet, _firstQuery, _queryCount, _destination, _destinationOffset) {
|
|
3052
|
-
return ok(void 0);
|
|
3053
|
-
}
|
|
3054
|
-
writeTimestamp(_querySet, _queryIndex) {
|
|
3055
|
-
}
|
|
3056
|
-
pushDebugGroup(_groupLabel) {
|
|
3057
|
-
}
|
|
3058
|
-
popDebugGroup() {
|
|
3059
|
-
}
|
|
3060
|
-
insertDebugMarker(_markerLabel) {
|
|
3061
|
-
}
|
|
3062
|
-
finish() {
|
|
3063
|
-
return ok(this.bookkeeper.register("CommandBuffer"));
|
|
3064
|
-
}
|
|
3065
|
-
};
|
|
3066
|
-
BOOKKEEPING_KEY = /* @__PURE__ */ Symbol("forgeax-rhi-null-bookkeeping");
|
|
3067
|
-
Bookkeeper = class {
|
|
3068
|
-
deviceId;
|
|
3069
|
-
nextHandleId = 0;
|
|
3070
|
-
records = /* @__PURE__ */ new Map();
|
|
3071
|
-
constructor(deviceId) {
|
|
3072
|
-
this.deviceId = deviceId;
|
|
3073
|
-
}
|
|
3074
|
-
/**
|
|
3075
|
-
* Register a freshly-minted handle of the given kind, returning a plain
|
|
3076
|
-
* object that carries its ledger row. The caller casts the return value to
|
|
3077
|
-
* the concrete brand (`as unknown as Buffer` etc.).
|
|
3078
|
-
*/
|
|
3079
|
-
register(kind) {
|
|
3080
|
-
const id = this.nextHandleId++;
|
|
3081
|
-
const record = {
|
|
3082
|
-
id,
|
|
3083
|
-
kind,
|
|
3084
|
-
destroyed: false,
|
|
3085
|
-
sourceDeviceId: this.deviceId
|
|
3086
|
-
};
|
|
3087
|
-
this.records.set(id, record);
|
|
3088
|
-
return { [BOOKKEEPING_KEY]: record };
|
|
3089
|
-
}
|
|
3090
|
-
/**
|
|
3091
|
-
* Mark a handle destroyed. Fail-fasts on a second destroy
|
|
3092
|
-
* ('destroy-after-destroy') or on a handle issued by a different device
|
|
3093
|
-
* ('rhi-not-available'); otherwise flips the destroyed flag and returns ok.
|
|
3094
|
-
*/
|
|
3095
|
-
destroy(handle) {
|
|
3096
|
-
const ownership = this.validateOwnership(handle);
|
|
3097
|
-
if (!ownership.ok) return ownership;
|
|
3098
|
-
const destroyedCheck = isHandleDestroyed(handle);
|
|
3099
|
-
if (!destroyedCheck.ok) return destroyedCheck;
|
|
3100
|
-
ownership.value.destroyed = true;
|
|
3101
|
-
return ok(void 0);
|
|
3102
|
-
}
|
|
3103
|
-
/**
|
|
3104
|
-
* Report whether a handle has already been destroyed (true once destroy()
|
|
3105
|
-
* has flipped its flag). Used by command-stream methods that must not
|
|
3106
|
-
* consume a stale handle.
|
|
3107
|
-
*/
|
|
3108
|
-
isDestroyed(handle) {
|
|
3109
|
-
return readRecord(handle)?.destroyed === true;
|
|
3110
|
-
}
|
|
3111
|
-
/**
|
|
3112
|
-
* Validate that a handle was issued by THIS device. Returns the ledger row on
|
|
3113
|
-
* success so callers can mutate it (e.g. flip destroyed); returns
|
|
3114
|
-
* 'rhi-not-available' for a foreign handle (AC-09 — no silent pass).
|
|
3115
|
-
*/
|
|
3116
|
-
validateOwnership(handle) {
|
|
3117
|
-
const record = readRecord(handle);
|
|
3118
|
-
if (record === void 0 || record.sourceDeviceId !== this.deviceId) {
|
|
3119
|
-
return err(
|
|
3120
|
-
new RhiError({
|
|
3121
|
-
code: "rhi-not-available",
|
|
3122
|
-
expected: "handle was issued by this RhiNull device",
|
|
3123
|
-
hint: "do not pass a handle created on a different RhiNull device into this device; create resources on the device they are used with"
|
|
3124
|
-
})
|
|
3125
|
-
);
|
|
3126
|
-
}
|
|
3127
|
-
return ok(record);
|
|
3128
|
-
}
|
|
3129
|
-
/**
|
|
3130
|
-
* Return all ledger rows as a readonly array. M3 unit tests (w17) read this
|
|
3131
|
-
* to assert create/destroy pairing, BGL/PSO shape counts, and resource
|
|
3132
|
-
* lifecycle coverage (AC-05/06/07). Returns a snapshot of the current Map
|
|
3133
|
-
* so callers can iterate without a stale reference after further mutations.
|
|
3134
|
-
*/
|
|
3135
|
-
allRecords() {
|
|
3136
|
-
return [...this.records.values()];
|
|
3137
|
-
}
|
|
3138
|
-
/** Report the total number of records in the ledger. */
|
|
3139
|
-
recordCount() {
|
|
3140
|
-
return this.records.size;
|
|
3141
|
-
}
|
|
3142
|
-
};
|
|
3143
|
-
nextDeviceId = 0;
|
|
3144
|
-
RhiNullDevice = class {
|
|
3145
|
-
internalBookkeeper;
|
|
3146
|
-
nullQueue;
|
|
3147
|
-
encoderFactory;
|
|
3148
|
-
/** Per-frame total draw count across all pass encoders executed this frame
|
|
3149
|
-
* (aggregated by the command encoder on finish, then reset). M3 unit tests
|
|
3150
|
-
* (w17) read this to assert draw count >= 1 (AC-06). */
|
|
3151
|
-
totalDrawCount = 0;
|
|
3152
|
-
/** Per-frame total direct and indirect compute dispatch count. */
|
|
3153
|
-
totalDispatchCount = 0;
|
|
3154
|
-
/** Per-frame total bind group set count (AC-06 / AC-05 readback). */
|
|
3155
|
-
totalBindGroupCount = 0;
|
|
3156
|
-
/** Per-frame pass names executed this frame, in schedule order (AC-04). */
|
|
3157
|
-
framePassNames = [];
|
|
3158
|
-
/** The per-device handle ledger — exposed so M3 tests can assert create/destroy
|
|
3159
|
-
* pairing and BGL/PSO shape counts (AC-05/06/07). */
|
|
3160
|
-
get bookkeeper() {
|
|
3161
|
-
return this.internalBookkeeper;
|
|
3162
|
-
}
|
|
3163
|
-
constructor(queue, encoderFactory) {
|
|
3164
|
-
this.internalBookkeeper = new Bookkeeper(nextDeviceId++);
|
|
3165
|
-
this.nullQueue = queue;
|
|
3166
|
-
this.encoderFactory = encoderFactory;
|
|
3167
|
-
}
|
|
3168
|
-
get caps() {
|
|
3169
|
-
return {
|
|
3170
|
-
backendKind: "null",
|
|
3171
|
-
compute: true,
|
|
3172
|
-
timestampQuery: false,
|
|
3173
|
-
timestampPeriodNanoseconds: null,
|
|
3174
|
-
indirectDrawing: true,
|
|
3175
|
-
textureCompressionBc: false,
|
|
3176
|
-
textureCompressionEtc2: false,
|
|
3177
|
-
textureCompressionAstc: false,
|
|
3178
|
-
// 3 wgpu-native-only reserved flags stay false on non-native backends
|
|
3179
|
-
// (D-5); the headless backend is not a native runtime.
|
|
3180
|
-
multiDrawIndirect: false,
|
|
3181
|
-
pushConstants: false,
|
|
3182
|
-
textureBindingArray: false,
|
|
3183
|
-
samplerAliasing: true,
|
|
3184
|
-
firstInstanceIndirect: true,
|
|
3185
|
-
storageBuffer: true,
|
|
3186
|
-
storageTexture: true,
|
|
3187
|
-
rgba16floatRenderable: true,
|
|
3188
|
-
rg11b10ufloatRenderable: true,
|
|
3189
|
-
float32Filterable: true,
|
|
3190
|
-
maxColorAttachments: 8
|
|
3191
|
-
};
|
|
3192
|
-
}
|
|
3193
|
-
get features() {
|
|
3194
|
-
return EMPTY_FEATURES;
|
|
3195
|
-
}
|
|
3196
|
-
get limits() {
|
|
3197
|
-
return EMPTY_LIMITS;
|
|
3198
|
-
}
|
|
3199
|
-
get queue() {
|
|
3200
|
-
return this.nullQueue;
|
|
3201
|
-
}
|
|
3202
|
-
// forgeax-async-whitelist: dom-native — spec `GPUDevice.lost` Promise
|
|
3203
|
-
// passthrough. The headless backend never loses a device (no GPU), so the
|
|
3204
|
-
// Promise stays unsettled for the lifetime of the device, mirroring a live
|
|
3205
|
-
// device that never transitions to the lost state.
|
|
3206
|
-
get lost() {
|
|
3207
|
-
return NEVER;
|
|
3208
|
-
}
|
|
3209
|
-
createBuffer(_desc) {
|
|
3210
|
-
return ok(this.internalBookkeeper.register("Buffer"));
|
|
3211
|
-
}
|
|
3212
|
-
createTexture(_desc) {
|
|
3213
|
-
return ok(this.internalBookkeeper.register("Texture"));
|
|
3214
|
-
}
|
|
3215
|
-
destroyBuffer(buf) {
|
|
3216
|
-
return this.internalBookkeeper.destroy(buf);
|
|
3217
|
-
}
|
|
3218
|
-
destroyQuerySet(querySet) {
|
|
3219
|
-
return this.internalBookkeeper.destroy(querySet);
|
|
3220
|
-
}
|
|
3221
|
-
destroyTexture(tex) {
|
|
3222
|
-
return this.internalBookkeeper.destroy(tex);
|
|
3223
|
-
}
|
|
3224
|
-
createTextureView(_texture, _desc) {
|
|
3225
|
-
return ok(this.internalBookkeeper.register("TextureView"));
|
|
3226
|
-
}
|
|
3227
|
-
createSampler(_desc) {
|
|
3228
|
-
return ok(this.internalBookkeeper.register("Sampler"));
|
|
3229
|
-
}
|
|
3230
|
-
createBindGroupLayout(_desc) {
|
|
3231
|
-
return ok(this.internalBookkeeper.register("BindGroupLayout"));
|
|
3232
|
-
}
|
|
3233
|
-
createBindGroup(_desc) {
|
|
3234
|
-
return ok(this.internalBookkeeper.register("BindGroup"));
|
|
3235
|
-
}
|
|
3236
|
-
createPipelineLayout(_desc) {
|
|
3237
|
-
return ok(this.internalBookkeeper.register("PipelineLayout"));
|
|
3238
|
-
}
|
|
3239
|
-
createRenderPipeline(_desc) {
|
|
3240
|
-
return ok(this.makePipeline("RenderPipeline"));
|
|
3241
|
-
}
|
|
3242
|
-
createComputePipeline(_desc) {
|
|
3243
|
-
return ok(this.makePipeline("ComputePipeline"));
|
|
3244
|
-
}
|
|
3245
|
-
createQuerySet(desc) {
|
|
3246
|
-
if (desc.type === "timestamp") {
|
|
3247
|
-
return err(
|
|
3248
|
-
new RhiError({
|
|
3249
|
-
code: "feature-not-enabled",
|
|
3250
|
-
expected: "caps.timestampQuery === true (timestamp-query feature)",
|
|
3251
|
-
hint: "RhiNull is structural-only and cannot produce GPU timestamp ticks"
|
|
3252
|
-
})
|
|
3253
|
-
);
|
|
3254
|
-
}
|
|
3255
|
-
return ok(this.internalBookkeeper.register("QuerySet"));
|
|
3256
|
-
}
|
|
3257
|
-
createCommandEncoder(_desc) {
|
|
3258
|
-
return ok(this.encoderFactory(this.internalBookkeeper, this));
|
|
3259
|
-
}
|
|
3260
|
-
/**
|
|
3261
|
-
* Mint a pipeline handle whose object also carries the no-op
|
|
3262
|
-
* `getBindGroupLayout(index)` ops method (D-2). The prod auto-layout path
|
|
3263
|
-
* (debug-draw.ts) and the existing mock unit tests both call
|
|
3264
|
-
* `pipeline.getBindGroupLayout(n)`; returning a legal BindGroupLayout brand
|
|
3265
|
-
* (recorded in the ledger) keeps those consumers from crashing on a missing
|
|
3266
|
-
* method.
|
|
3267
|
-
*/
|
|
3268
|
-
makePipeline(kind) {
|
|
3269
|
-
const handle = this.internalBookkeeper.register(kind);
|
|
3270
|
-
const getBindGroupLayout = (_index) => this.internalBookkeeper.register("BindGroupLayout");
|
|
3271
|
-
return Object.assign(handle, { getBindGroupLayout });
|
|
3272
|
-
}
|
|
3273
|
-
};
|
|
3274
|
-
EMPTY_FEATURES = /* @__PURE__ */ new Set();
|
|
3275
|
-
EMPTY_LIMITS = {};
|
|
3276
|
-
NEVER = new Promise(() => {
|
|
3277
|
-
});
|
|
3278
|
-
RhiNullQueue = class {
|
|
3279
|
-
writeBuffer(_buffer, _bufferOffset, _data, _dataOffset, _size) {
|
|
3280
|
-
return ok(void 0);
|
|
3281
|
-
}
|
|
3282
|
-
writeTexture(_destination, _data, _dataLayout, _size) {
|
|
3283
|
-
return ok(void 0);
|
|
3284
|
-
}
|
|
3285
|
-
copyExternalImageToTexture(_source, _destination, _copySize) {
|
|
3286
|
-
return ok(void 0);
|
|
3287
|
-
}
|
|
3288
|
-
submit(_commandBuffers) {
|
|
3289
|
-
return ok(void 0);
|
|
3290
|
-
}
|
|
3291
|
-
// forgeax-async-whitelist: dom-native — spec `GPUQueue.onSubmittedWorkDone`
|
|
3292
|
-
// never rejects. The headless backend has no pending GPU work, so it resolves
|
|
3293
|
-
// immediately (AC-12: read-back idioms must not hang).
|
|
3294
|
-
onSubmittedWorkDone() {
|
|
3295
|
-
return Promise.resolve(void 0);
|
|
3296
|
-
}
|
|
3297
|
-
};
|
|
3298
|
-
RhiNullAdapter = class {
|
|
3299
|
-
features = /* @__PURE__ */ new Set();
|
|
3300
|
-
limits = {};
|
|
3301
|
-
// forgeax-async-whitelist is not needed: this returns Promise<Result<...>>
|
|
3302
|
-
// per the spec contract; never rejects.
|
|
3303
|
-
requestDevice(_opts) {
|
|
3304
|
-
const device = new RhiNullDevice(
|
|
3305
|
-
new RhiNullQueue(),
|
|
3306
|
-
(bookkeeper, dev) => new RhiNullCommandEncoder(bookkeeper, dev)
|
|
3307
|
-
);
|
|
3308
|
-
return Promise.resolve(ok(device));
|
|
3309
|
-
}
|
|
3310
|
-
};
|
|
3311
|
-
RhiNullCanvasContext = class {
|
|
3312
|
-
configure(_desc) {
|
|
3313
|
-
return ok(void 0);
|
|
3314
|
-
}
|
|
3315
|
-
unconfigure() {
|
|
3316
|
-
}
|
|
3317
|
-
getConfiguration() {
|
|
3318
|
-
return void 0;
|
|
3319
|
-
}
|
|
3320
|
-
getCurrentTexture() {
|
|
3321
|
-
return ok({});
|
|
3322
|
-
}
|
|
3323
|
-
};
|
|
3324
|
-
rhi = {
|
|
3325
|
-
requestAdapter,
|
|
3326
|
-
acquireCanvasContext,
|
|
3327
|
-
createShaderModule
|
|
3328
|
-
};
|
|
3329
|
-
}
|
|
3330
|
-
});
|
|
3331
3446
|
function createCliRhiDebugOperationContext() {
|
|
3332
3447
|
return {
|
|
3333
3448
|
captureFrame: async () => err(
|
|
3334
3449
|
createRhiDebugError("capture-unavailable", {
|
|
3335
3450
|
stage: "capture",
|
|
3336
|
-
cause: "the standalone CLI has no live App capture provider"
|
|
3451
|
+
cause: "the standalone CLI has no live App capture provider; start a recorder-enabled live host and invoke its rhiCapture root"
|
|
3337
3452
|
})
|
|
3338
3453
|
),
|
|
3339
3454
|
async readArtifact(artifact) {
|
|
@@ -3358,33 +3473,69 @@ function createCliRhiDebugOperationContext() {
|
|
|
3358
3473
|
expected: "the ArtifactRef path to be readable",
|
|
3359
3474
|
hint: "Check the tape path and recapture if the artifact was removed.",
|
|
3360
3475
|
detail: {
|
|
3361
|
-
path: artifact.path,
|
|
3362
|
-
reason: cause instanceof Error ? cause.message : String(cause)
|
|
3476
|
+
path: artifact.path,
|
|
3477
|
+
reason: cause instanceof Error ? cause.message : String(cause)
|
|
3478
|
+
}
|
|
3479
|
+
}
|
|
3480
|
+
};
|
|
3481
|
+
}
|
|
3482
|
+
},
|
|
3483
|
+
async createReplayBackend(tape) {
|
|
3484
|
+
let createDawn;
|
|
3485
|
+
let gpuGlobals;
|
|
3486
|
+
try {
|
|
3487
|
+
const dawn = await import('webgpu');
|
|
3488
|
+
createDawn = dawn.create;
|
|
3489
|
+
gpuGlobals = dawn.globals;
|
|
3490
|
+
} catch (cause) {
|
|
3491
|
+
return {
|
|
3492
|
+
ok: false,
|
|
3493
|
+
error: {
|
|
3494
|
+
code: "replay-backend-unavailable",
|
|
3495
|
+
expected: "the Dawn WebGPU provider to load",
|
|
3496
|
+
hint: "Install the DevKit runtime closure and retry rhi.inspect.",
|
|
3497
|
+
detail: {
|
|
3498
|
+
stage: "provider",
|
|
3499
|
+
cause: cause instanceof Error ? cause.message : String(cause)
|
|
3363
3500
|
}
|
|
3364
3501
|
}
|
|
3365
3502
|
};
|
|
3366
3503
|
}
|
|
3367
|
-
|
|
3368
|
-
|
|
3504
|
+
Object.assign(globalThis, gpuGlobals);
|
|
3505
|
+
const gpu = createDawn([]);
|
|
3506
|
+
if (!("navigator" in globalThis) || globalThis.navigator === void 0) {
|
|
3507
|
+
Object.defineProperty(globalThis, "navigator", {
|
|
3508
|
+
value: {},
|
|
3509
|
+
configurable: true,
|
|
3510
|
+
writable: true
|
|
3511
|
+
});
|
|
3512
|
+
}
|
|
3513
|
+
Object.defineProperty(globalThis.navigator, "gpu", {
|
|
3514
|
+
value: gpu,
|
|
3515
|
+
configurable: true,
|
|
3516
|
+
writable: true
|
|
3517
|
+
});
|
|
3369
3518
|
const adapter = await rhi.requestAdapter();
|
|
3370
3519
|
if (!adapter.ok) {
|
|
3371
3520
|
return {
|
|
3372
3521
|
ok: false,
|
|
3373
3522
|
error: {
|
|
3374
3523
|
code: "replay-backend-unavailable",
|
|
3375
|
-
expected: "a fresh
|
|
3524
|
+
expected: "a fresh Dawn WebGPU adapter",
|
|
3376
3525
|
hint: adapter.error.hint,
|
|
3377
3526
|
detail: { stage: "adapter", cause: adapter.error.hint }
|
|
3378
3527
|
}
|
|
3379
3528
|
};
|
|
3380
3529
|
}
|
|
3381
|
-
const device = await adapter.value.requestDevice(
|
|
3530
|
+
const device = await adapter.value.requestDevice(
|
|
3531
|
+
replayDeviceRequest(tape, adapter.value.features, adapter.value.limits)
|
|
3532
|
+
);
|
|
3382
3533
|
if (!device.ok) {
|
|
3383
3534
|
return {
|
|
3384
3535
|
ok: false,
|
|
3385
3536
|
error: {
|
|
3386
3537
|
code: "replay-backend-unavailable",
|
|
3387
|
-
expected: "a fresh
|
|
3538
|
+
expected: "a fresh Dawn WebGPU device satisfying the recorded tape",
|
|
3388
3539
|
hint: device.error.hint,
|
|
3389
3540
|
detail: { stage: "device", cause: device.error.hint }
|
|
3390
3541
|
}
|
|
@@ -3396,7 +3547,6 @@ function createCliRhiDebugOperationContext() {
|
|
|
3396
3547
|
}
|
|
3397
3548
|
var init_cli_context = __esm({
|
|
3398
3549
|
"src/rhi-debug/cli-context.ts"() {
|
|
3399
|
-
init_dist3();
|
|
3400
3550
|
}
|
|
3401
3551
|
});
|
|
3402
3552
|
function createRhiDebugOperationContext(host) {
|
|
@@ -3423,6 +3573,9 @@ function renderRhiDebugHelp() {
|
|
|
3423
3573
|
return [
|
|
3424
3574
|
"forgeax run <operation>",
|
|
3425
3575
|
...discoverRhiDebugOperations().map((operation) => ` ${operation.name}: ${operation.summary}`),
|
|
3576
|
+
"Usage:",
|
|
3577
|
+
" forgeax run rhi.summary --artifact PATH --digest SHA256 --json",
|
|
3578
|
+
" forgeax run rhi.inspect --artifact PATH --digest SHA256 --work-index N --fields pipeline,bindings,pixels --json",
|
|
3426
3579
|
"ArtifactRef schema:",
|
|
3427
3580
|
JSON.stringify(RHI_DEBUG_OPERATION_MANIFEST.artifactRefSchema)
|
|
3428
3581
|
].join("\n");
|
|
@@ -3550,7 +3703,7 @@ async function runRhiDebugOperation(name, input, context) {
|
|
|
3550
3703
|
"Provide a fresh device and shader factory before running rhi.inspect."
|
|
3551
3704
|
);
|
|
3552
3705
|
}
|
|
3553
|
-
const backend = await context.createReplayBackend();
|
|
3706
|
+
const backend = await context.createReplayBackend(decoded.value);
|
|
3554
3707
|
if (!backend.ok) return backend;
|
|
3555
3708
|
const opened = await openReplay(decoded.value, backend.value);
|
|
3556
3709
|
if (!opened.ok) return { ok: false, error: coreError(opened.error) };
|
|
@@ -3642,7 +3795,10 @@ var init_operations = __esm({
|
|
|
3642
3795
|
properties: {
|
|
3643
3796
|
artifact: artifactRefSchema,
|
|
3644
3797
|
workIndex: { type: "integer", minimum: 0 },
|
|
3645
|
-
fields: {
|
|
3798
|
+
fields: {
|
|
3799
|
+
type: "array",
|
|
3800
|
+
items: { type: "string", enum: ["bindings", "pipeline", "pixels"] }
|
|
3801
|
+
}
|
|
3646
3802
|
},
|
|
3647
3803
|
required: ["artifact", "workIndex"],
|
|
3648
3804
|
additionalProperties: false
|
|
@@ -3734,7 +3890,15 @@ async function sdkInstallCommand(options) {
|
|
|
3734
3890
|
await rename(resolve(staging, name), resolve(root, name));
|
|
3735
3891
|
}
|
|
3736
3892
|
}
|
|
3737
|
-
return {
|
|
3893
|
+
return {
|
|
3894
|
+
ok: true,
|
|
3895
|
+
value: {
|
|
3896
|
+
root,
|
|
3897
|
+
sdkVersion: version,
|
|
3898
|
+
source: "@forgeax/engine-sdk",
|
|
3899
|
+
next: { cwd: root, argv: ["node", "./bin/forgeax.mjs", "init"] }
|
|
3900
|
+
}
|
|
3901
|
+
};
|
|
3738
3902
|
} catch (cause) {
|
|
3739
3903
|
return { ok: false, error: commandError(cause, "sdk-install-failed") };
|
|
3740
3904
|
} finally {
|
|
@@ -3783,6 +3947,630 @@ var init_shader_check = __esm({
|
|
|
3783
3947
|
init_project();
|
|
3784
3948
|
}
|
|
3785
3949
|
});
|
|
3950
|
+
function fail(code, expected, hint, detail = {}) {
|
|
3951
|
+
throw new SoftwareCaptureError(code, expected, hint, detail);
|
|
3952
|
+
}
|
|
3953
|
+
async function pathExists2(path) {
|
|
3954
|
+
try {
|
|
3955
|
+
await access(path);
|
|
3956
|
+
return true;
|
|
3957
|
+
} catch {
|
|
3958
|
+
return false;
|
|
3959
|
+
}
|
|
3960
|
+
}
|
|
3961
|
+
async function reservePort() {
|
|
3962
|
+
const reservation = createServer();
|
|
3963
|
+
await new Promise((resolveListen, rejectListen) => {
|
|
3964
|
+
reservation.once("error", rejectListen);
|
|
3965
|
+
reservation.listen(0, "127.0.0.1", resolveListen);
|
|
3966
|
+
});
|
|
3967
|
+
const address = reservation.address();
|
|
3968
|
+
const port = typeof address === "object" && address !== null ? address.port : 0;
|
|
3969
|
+
await new Promise((resolveClose, rejectClose) => {
|
|
3970
|
+
reservation.close((error) => error === void 0 ? resolveClose() : rejectClose(error));
|
|
3971
|
+
});
|
|
3972
|
+
if (port === 0) throw new Error("OS did not assign an ephemeral capture port");
|
|
3973
|
+
return port;
|
|
3974
|
+
}
|
|
3975
|
+
async function stopProcess(child) {
|
|
3976
|
+
if (child.exitCode !== null || child.signalCode !== null) return;
|
|
3977
|
+
await new Promise((resolveStop) => {
|
|
3978
|
+
const timeout = setTimeout(() => {
|
|
3979
|
+
child.kill("SIGKILL");
|
|
3980
|
+
resolveStop();
|
|
3981
|
+
}, 2e3);
|
|
3982
|
+
child.once("exit", () => {
|
|
3983
|
+
clearTimeout(timeout);
|
|
3984
|
+
resolveStop();
|
|
3985
|
+
});
|
|
3986
|
+
child.kill("SIGTERM");
|
|
3987
|
+
});
|
|
3988
|
+
}
|
|
3989
|
+
async function startVirtualDisplay(width, height) {
|
|
3990
|
+
if (process.platform !== "linux" || process.env.DISPLAY !== void 0) {
|
|
3991
|
+
return {
|
|
3992
|
+
...process.env.DISPLAY === void 0 ? {} : { value: process.env.DISPLAY },
|
|
3993
|
+
async close() {
|
|
3994
|
+
}
|
|
3995
|
+
};
|
|
3996
|
+
}
|
|
3997
|
+
for (let offset = 0; offset < 100; offset += 1) {
|
|
3998
|
+
const number = 90 + (process.pid + offset) % 100;
|
|
3999
|
+
const socket = `/tmp/.X11-unix/X${number}`;
|
|
4000
|
+
const lock = `/tmp/.X${number}-lock`;
|
|
4001
|
+
if (await pathExists2(socket) || await pathExists2(lock)) continue;
|
|
4002
|
+
const display = `:${number}`;
|
|
4003
|
+
const child = spawn(
|
|
4004
|
+
"Xvfb",
|
|
4005
|
+
[display, "-screen", "0", `${width}x${height}x24`, "-nolisten", "tcp"],
|
|
4006
|
+
{ stdio: ["ignore", "ignore", "pipe"] }
|
|
4007
|
+
);
|
|
4008
|
+
let diagnostic = "";
|
|
4009
|
+
child.stderr?.setEncoding("utf8");
|
|
4010
|
+
child.stderr?.on("data", (chunk) => {
|
|
4011
|
+
diagnostic += chunk;
|
|
4012
|
+
});
|
|
4013
|
+
try {
|
|
4014
|
+
await new Promise((resolveReady, rejectReady) => {
|
|
4015
|
+
const timeout = setTimeout(
|
|
4016
|
+
() => rejectReady(new Error(`Xvfb did not create ${socket}: ${diagnostic.trim()}`)),
|
|
4017
|
+
5e3
|
|
4018
|
+
);
|
|
4019
|
+
const poll = setInterval(() => {
|
|
4020
|
+
void pathExists2(socket).then((exists) => {
|
|
4021
|
+
if (!exists) return;
|
|
4022
|
+
clearTimeout(timeout);
|
|
4023
|
+
clearInterval(poll);
|
|
4024
|
+
resolveReady();
|
|
4025
|
+
});
|
|
4026
|
+
}, 50);
|
|
4027
|
+
child.once("error", (error) => {
|
|
4028
|
+
clearTimeout(timeout);
|
|
4029
|
+
clearInterval(poll);
|
|
4030
|
+
rejectReady(error);
|
|
4031
|
+
});
|
|
4032
|
+
child.once("exit", (code, signal) => {
|
|
4033
|
+
clearTimeout(timeout);
|
|
4034
|
+
clearInterval(poll);
|
|
4035
|
+
rejectReady(
|
|
4036
|
+
new Error(`Xvfb exited before ready (${code ?? signal}): ${diagnostic.trim()}`)
|
|
4037
|
+
);
|
|
4038
|
+
});
|
|
4039
|
+
});
|
|
4040
|
+
return { value: display, close: () => stopProcess(child) };
|
|
4041
|
+
} catch (cause) {
|
|
4042
|
+
await stopProcess(child);
|
|
4043
|
+
throw cause;
|
|
4044
|
+
}
|
|
4045
|
+
}
|
|
4046
|
+
throw new Error("no free X11 display number was available for browser capture");
|
|
4047
|
+
}
|
|
4048
|
+
async function lavapipeIcd() {
|
|
4049
|
+
for (const candidate of [
|
|
4050
|
+
"/usr/share/vulkan/icd.d/lvp_icd.x86_64.json",
|
|
4051
|
+
"/usr/share/vulkan/icd.d/lvp_icd.aarch64.json"
|
|
4052
|
+
]) {
|
|
4053
|
+
if (await pathExists2(candidate)) return candidate;
|
|
4054
|
+
}
|
|
4055
|
+
return void 0;
|
|
4056
|
+
}
|
|
4057
|
+
function evidencePath(output) {
|
|
4058
|
+
return output.toLowerCase().endsWith(".png") ? `${output.slice(0, -4)}.json` : `${output}.json`;
|
|
4059
|
+
}
|
|
4060
|
+
function newRunId() {
|
|
4061
|
+
return `${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}-${randomUUID().slice(0, 8)}`;
|
|
4062
|
+
}
|
|
4063
|
+
function checkpointSlug(checkpoint) {
|
|
4064
|
+
const value = (checkpoint ?? "capture").trim().toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
|
|
4065
|
+
return value.length === 0 ? "capture" : value;
|
|
4066
|
+
}
|
|
4067
|
+
function captureDigest(bytes) {
|
|
4068
|
+
return `sha256:${createHash("sha256").update(bytes).digest("hex")}`;
|
|
4069
|
+
}
|
|
4070
|
+
function observedBackend(runtime) {
|
|
4071
|
+
if (runtime.adapter === null) return "unknown";
|
|
4072
|
+
return isSoftwareAdapter(runtime) ? "software" : "hardware";
|
|
4073
|
+
}
|
|
4074
|
+
async function resolveBrowserExecutable(requested) {
|
|
4075
|
+
const candidates = [
|
|
4076
|
+
requested,
|
|
4077
|
+
process.env.FORGEAX_BROWSER_EXECUTABLE,
|
|
4078
|
+
"/opt/google/chrome-beta/chrome",
|
|
4079
|
+
"/usr/bin/google-chrome",
|
|
4080
|
+
"/usr/bin/google-chrome-stable",
|
|
4081
|
+
"/usr/bin/chromium",
|
|
4082
|
+
"/usr/bin/chromium-browser"
|
|
4083
|
+
].filter((candidate) => candidate !== void 0 && candidate.length > 0);
|
|
4084
|
+
for (const candidate of candidates) {
|
|
4085
|
+
if (await pathExists2(candidate)) return candidate;
|
|
4086
|
+
}
|
|
4087
|
+
return void 0;
|
|
4088
|
+
}
|
|
4089
|
+
function browserLaunchArgs(backend) {
|
|
4090
|
+
const common = [
|
|
4091
|
+
"--enable-unsafe-webgpu",
|
|
4092
|
+
"--ignore-gpu-blocklist",
|
|
4093
|
+
"--disable-gpu-driver-bug-workarounds",
|
|
4094
|
+
"--force-color-profile=srgb",
|
|
4095
|
+
"--force-device-scale-factor=1"
|
|
4096
|
+
];
|
|
4097
|
+
if (backend !== "software") return common;
|
|
4098
|
+
return [
|
|
4099
|
+
...common,
|
|
4100
|
+
"--enable-features=Vulkan",
|
|
4101
|
+
"--use-vulkan=swiftshader",
|
|
4102
|
+
"--use-angle=swiftshader",
|
|
4103
|
+
"--disable-vulkan-surface"
|
|
4104
|
+
];
|
|
4105
|
+
}
|
|
4106
|
+
function summarizeCapturePixels(rgba, width, height) {
|
|
4107
|
+
const pixelCount = width * height;
|
|
4108
|
+
const stride = Math.max(1, Math.floor(pixelCount / 4096));
|
|
4109
|
+
const histogram = new Uint32Array(16);
|
|
4110
|
+
let lumaMin = 255;
|
|
4111
|
+
let lumaMax = 0;
|
|
4112
|
+
let sampledPixels = 0;
|
|
4113
|
+
for (let pixel = 0; pixel < pixelCount; pixel += stride) {
|
|
4114
|
+
const offset = pixel * 4;
|
|
4115
|
+
const red = rgba[offset];
|
|
4116
|
+
const green = rgba[offset + 1];
|
|
4117
|
+
const blue = rgba[offset + 2];
|
|
4118
|
+
if (red === void 0 || green === void 0 || blue === void 0) break;
|
|
4119
|
+
const luma = Math.round((54 * red + 183 * green + 19 * blue) / 256);
|
|
4120
|
+
lumaMin = Math.min(lumaMin, luma);
|
|
4121
|
+
lumaMax = Math.max(lumaMax, luma);
|
|
4122
|
+
const bucket = Math.min(15, Math.floor(luma / 16));
|
|
4123
|
+
histogram[bucket] = (histogram[bucket] ?? 0) + 1;
|
|
4124
|
+
sampledPixels += 1;
|
|
4125
|
+
}
|
|
4126
|
+
const dominantPixels = histogram.reduce((largest, count) => Math.max(largest, count), 0);
|
|
4127
|
+
const varyingPixels = sampledPixels - dominantPixels;
|
|
4128
|
+
const lumaRange = lumaMax - lumaMin;
|
|
4129
|
+
const requiredVariation = Math.min(sampledPixels, Math.max(8, Math.ceil(sampledPixels * 2e-3)));
|
|
4130
|
+
return {
|
|
4131
|
+
width,
|
|
4132
|
+
height,
|
|
4133
|
+
sampledPixels,
|
|
4134
|
+
lumaMin,
|
|
4135
|
+
lumaMax,
|
|
4136
|
+
lumaRange,
|
|
4137
|
+
varyingPixels,
|
|
4138
|
+
rendered: lumaRange >= 8 && varyingPixels >= requiredVariation
|
|
4139
|
+
};
|
|
4140
|
+
}
|
|
4141
|
+
function inspectCapturePng(png) {
|
|
4142
|
+
const decoded = parseImage(png, "image/png", { mipmap: false });
|
|
4143
|
+
if (!decoded.ok) throw decoded.error;
|
|
4144
|
+
return summarizeCapturePixels(decoded.value.bytes, decoded.value.width, decoded.value.height);
|
|
4145
|
+
}
|
|
4146
|
+
async function screenshotWithWitness(page) {
|
|
4147
|
+
const canvasPng = await page.locator("canvas").first().screenshot({
|
|
4148
|
+
type: "png",
|
|
4149
|
+
style: "* { visibility: hidden !important; } canvas { visibility: visible !important; }"
|
|
4150
|
+
});
|
|
4151
|
+
const png = await page.screenshot({ type: "png", caret: "hide" });
|
|
4152
|
+
return { png, pixels: inspectCapturePng(canvasPng) };
|
|
4153
|
+
}
|
|
4154
|
+
async function waitForCompositor(page) {
|
|
4155
|
+
await page.evaluate(async () => {
|
|
4156
|
+
await document.fonts.ready;
|
|
4157
|
+
await new Promise((resolveFrame) => requestAnimationFrame(() => resolveFrame()));
|
|
4158
|
+
await new Promise((resolveFrame) => requestAnimationFrame(() => resolveFrame()));
|
|
4159
|
+
});
|
|
4160
|
+
}
|
|
4161
|
+
async function runtimeWitness(page) {
|
|
4162
|
+
return page.evaluate(async (datasetKey) => {
|
|
4163
|
+
const canvas = document.querySelector("canvas");
|
|
4164
|
+
const uiRoot = document.querySelector("#game-ui");
|
|
4165
|
+
const shadowHosts = [...uiRoot?.querySelectorAll("*") ?? []].filter(
|
|
4166
|
+
(element) => element.shadowRoot?.childElementCount !== 0
|
|
4167
|
+
);
|
|
4168
|
+
let adapter = null;
|
|
4169
|
+
let adapterError = null;
|
|
4170
|
+
try {
|
|
4171
|
+
const gpuAdapter = await navigator.gpu?.requestAdapter();
|
|
4172
|
+
if (gpuAdapter === null || gpuAdapter === void 0) {
|
|
4173
|
+
adapterError = "navigator.gpu.requestAdapter() returned null";
|
|
4174
|
+
} else {
|
|
4175
|
+
const info = gpuAdapter.info;
|
|
4176
|
+
adapter = {
|
|
4177
|
+
vendor: info.vendor,
|
|
4178
|
+
architecture: info.architecture,
|
|
4179
|
+
device: info.device,
|
|
4180
|
+
description: info.description
|
|
4181
|
+
};
|
|
4182
|
+
}
|
|
4183
|
+
} catch (cause) {
|
|
4184
|
+
adapterError = String(cause);
|
|
4185
|
+
}
|
|
4186
|
+
const frameId = Number(document.documentElement.dataset[datasetKey]);
|
|
4187
|
+
return {
|
|
4188
|
+
title: document.title,
|
|
4189
|
+
canvas: canvas instanceof HTMLCanvasElement ? { width: canvas.width, height: canvas.height } : null,
|
|
4190
|
+
domUi: {
|
|
4191
|
+
rootChildren: uiRoot?.childElementCount ?? 0,
|
|
4192
|
+
openShadowRoots: shadowHosts.length,
|
|
4193
|
+
textWitness: shadowHosts.map((host) => host.shadowRoot?.textContent?.replace(/\s+/g, " ").trim() ?? "").filter((text) => text.length > 0).join(" | ").slice(0, 500)
|
|
4194
|
+
},
|
|
4195
|
+
adapter,
|
|
4196
|
+
adapterError,
|
|
4197
|
+
engineFrameId: Number.isSafeInteger(frameId) && frameId > 0 ? frameId : null,
|
|
4198
|
+
captureReady: document.documentElement.dataset.forgeaxCaptureReady ?? null,
|
|
4199
|
+
userAgent: navigator.userAgent
|
|
4200
|
+
};
|
|
4201
|
+
}, FORGEAX_FRAME_SUBMITTED_DATASET);
|
|
4202
|
+
}
|
|
4203
|
+
function isSoftwareAdapter(runtime) {
|
|
4204
|
+
const witness = Object.values(runtime.adapter ?? {}).map((value) => String(value).toLowerCase()).join(" ");
|
|
4205
|
+
return ["swiftshader", "llvmpipe", "lavapipe", "software"].some(
|
|
4206
|
+
(token) => witness.includes(token)
|
|
4207
|
+
);
|
|
4208
|
+
}
|
|
4209
|
+
async function writeRunReport(report) {
|
|
4210
|
+
await mkdir(dirname(report.report), { recursive: true });
|
|
4211
|
+
await writeFile(report.report, `${JSON.stringify(report, null, 2)}
|
|
4212
|
+
`, "utf8");
|
|
4213
|
+
}
|
|
4214
|
+
async function openBrowserCaptureSession(root, options) {
|
|
4215
|
+
const backend = options.backend ?? (options.software === true ? "software" : "auto");
|
|
4216
|
+
if (options.software === true && options.backend !== void 0 && options.backend !== "software") {
|
|
4217
|
+
fail(
|
|
4218
|
+
"browser-capture-option-conflict",
|
|
4219
|
+
"software and backend options to describe the same capture lane",
|
|
4220
|
+
"Use either software: true or backend: software; use backend: auto for a portable capture.",
|
|
4221
|
+
{ software: options.software, backend: options.backend }
|
|
4222
|
+
);
|
|
4223
|
+
}
|
|
4224
|
+
const facts = await readProjectFacts(root);
|
|
4225
|
+
if (!facts.ok) {
|
|
4226
|
+
throw new SoftwareCaptureError(
|
|
4227
|
+
facts.error.code,
|
|
4228
|
+
facts.error.expected,
|
|
4229
|
+
facts.error.hint,
|
|
4230
|
+
facts.error.detail
|
|
4231
|
+
);
|
|
4232
|
+
}
|
|
4233
|
+
const width = options.width ?? 1280;
|
|
4234
|
+
const height = options.height ?? 720;
|
|
4235
|
+
const browserPath = await resolveBrowserExecutable(options.browser);
|
|
4236
|
+
if (options.browser !== void 0 && browserPath === void 0) {
|
|
4237
|
+
fail(
|
|
4238
|
+
"browser-capture-browser-missing",
|
|
4239
|
+
`a runnable browser at ${options.browser}`,
|
|
4240
|
+
"Install Chrome/Chromium or pass --browser with an executable path.",
|
|
4241
|
+
{ browser: options.browser }
|
|
4242
|
+
);
|
|
4243
|
+
}
|
|
4244
|
+
const id = options.runId ?? newRunId();
|
|
4245
|
+
const outputDirectory = resolve(
|
|
4246
|
+
facts.value.root,
|
|
4247
|
+
options.outputDir ?? `artifacts/playthrough/${id}`
|
|
4248
|
+
);
|
|
4249
|
+
const reportPath = resolve(
|
|
4250
|
+
facts.value.root,
|
|
4251
|
+
options.report ?? resolve(outputDirectory, "run.json")
|
|
4252
|
+
);
|
|
4253
|
+
const port = options.port === void 0 || options.port === 0 ? await reservePort() : options.port;
|
|
4254
|
+
let server;
|
|
4255
|
+
let browser;
|
|
4256
|
+
let page;
|
|
4257
|
+
let display;
|
|
4258
|
+
try {
|
|
4259
|
+
const { createServer: createServer2 } = await import('vite');
|
|
4260
|
+
server = await createServer2(
|
|
4261
|
+
await createViteConfig(facts.value, "serve", "/", {
|
|
4262
|
+
server: { port, strictPort: true }
|
|
4263
|
+
})
|
|
4264
|
+
);
|
|
4265
|
+
await server.listen(port);
|
|
4266
|
+
display = await startVirtualDisplay(width, height);
|
|
4267
|
+
const captureDisplay = display;
|
|
4268
|
+
const icd = await lavapipeIcd();
|
|
4269
|
+
const consoleErrors = [];
|
|
4270
|
+
const pageErrors = [];
|
|
4271
|
+
const hasDisplay = display.value !== void 0;
|
|
4272
|
+
const headless = options.headless ?? (!hasDisplay && process.platform !== "linux");
|
|
4273
|
+
const baseUrl = new URL(server.resolvedUrls?.local[0] ?? `http://127.0.0.1:${port}/`);
|
|
4274
|
+
const openPage = async (launchBackend) => {
|
|
4275
|
+
const browserEnvironment = {
|
|
4276
|
+
...Object.fromEntries(
|
|
4277
|
+
Object.entries(process.env).filter(
|
|
4278
|
+
(entry) => entry[1] !== void 0
|
|
4279
|
+
)
|
|
4280
|
+
),
|
|
4281
|
+
...launchBackend === "software" ? { LIBGL_ALWAYS_SOFTWARE: "1" } : {},
|
|
4282
|
+
...captureDisplay.value === void 0 ? {} : { DISPLAY: captureDisplay.value }
|
|
4283
|
+
};
|
|
4284
|
+
const launchOptions = {
|
|
4285
|
+
headless,
|
|
4286
|
+
env: browserEnvironment,
|
|
4287
|
+
args: browserLaunchArgs(launchBackend),
|
|
4288
|
+
...browserPath === void 0 ? {} : { executablePath: browserPath }
|
|
4289
|
+
};
|
|
4290
|
+
browser = await chromium.launch(launchOptions);
|
|
4291
|
+
page = await browser.newPage({
|
|
4292
|
+
viewport: { width, height },
|
|
4293
|
+
screen: { width, height },
|
|
4294
|
+
deviceScaleFactor: 1,
|
|
4295
|
+
colorScheme: "light",
|
|
4296
|
+
locale: "en-US",
|
|
4297
|
+
timezoneId: "UTC",
|
|
4298
|
+
serviceWorkers: "block"
|
|
4299
|
+
});
|
|
4300
|
+
page.on("console", (message) => {
|
|
4301
|
+
if (message.type() === "error") consoleErrors.push(message.text());
|
|
4302
|
+
});
|
|
4303
|
+
page.on("pageerror", (error) => pageErrors.push(String(error)));
|
|
4304
|
+
const captureUrl2 = new URL(baseUrl.href);
|
|
4305
|
+
if (options.deterministic === true) captureUrl2.searchParams.set("forgeaxCapture", "1");
|
|
4306
|
+
await page.goto(captureUrl2.href, { waitUntil: "domcontentloaded", timeout: 12e4 });
|
|
4307
|
+
await page.waitForFunction(
|
|
4308
|
+
(requireUi) => {
|
|
4309
|
+
const canvas = document.querySelector("canvas");
|
|
4310
|
+
if (!(canvas instanceof HTMLCanvasElement) || canvas.width <= 0 || canvas.height <= 0)
|
|
4311
|
+
return false;
|
|
4312
|
+
if (!requireUi) return true;
|
|
4313
|
+
const uiRoot = document.querySelector("#game-ui");
|
|
4314
|
+
return uiRoot !== null && uiRoot.childElementCount > 0;
|
|
4315
|
+
},
|
|
4316
|
+
options.requireUi === true,
|
|
4317
|
+
{ timeout: 12e4 }
|
|
4318
|
+
);
|
|
4319
|
+
return runtimeWitness(page);
|
|
4320
|
+
};
|
|
4321
|
+
const initialLaunchBackend = backend === "software" ? "software" : "hardware";
|
|
4322
|
+
let runtime = await openPage(initialLaunchBackend);
|
|
4323
|
+
let observed = observedBackend(runtime);
|
|
4324
|
+
if (backend === "auto" && runtime.adapter === null) {
|
|
4325
|
+
await browser?.close();
|
|
4326
|
+
browser = void 0;
|
|
4327
|
+
page = void 0;
|
|
4328
|
+
consoleErrors.length = 0;
|
|
4329
|
+
pageErrors.length = 0;
|
|
4330
|
+
runtime = await openPage("software");
|
|
4331
|
+
observed = observedBackend(runtime);
|
|
4332
|
+
}
|
|
4333
|
+
const captureUrl = new URL(baseUrl.href);
|
|
4334
|
+
if (options.deterministic === true) captureUrl.searchParams.set("forgeaxCapture", "1");
|
|
4335
|
+
if (page === void 0 || browser === void 0) {
|
|
4336
|
+
fail(
|
|
4337
|
+
"browser-capture-browser-unavailable",
|
|
4338
|
+
"the browser capture session to open",
|
|
4339
|
+
"Inspect the browser launch diagnostic and retry with --browser or --headless."
|
|
4340
|
+
);
|
|
4341
|
+
}
|
|
4342
|
+
const captures = [];
|
|
4343
|
+
let closed = false;
|
|
4344
|
+
const report = {
|
|
4345
|
+
schemaVersion: "2.0.0",
|
|
4346
|
+
runId: id,
|
|
4347
|
+
ok: false,
|
|
4348
|
+
mode: "browser-compositor",
|
|
4349
|
+
root: facts.value.root,
|
|
4350
|
+
url: captureUrl.href,
|
|
4351
|
+
report: reportPath,
|
|
4352
|
+
backendRequested: backend,
|
|
4353
|
+
backend: observed,
|
|
4354
|
+
softwareRequested: backend === "software",
|
|
4355
|
+
deterministicRequested: options.deterministic === true,
|
|
4356
|
+
viewport: {
|
|
4357
|
+
width,
|
|
4358
|
+
height,
|
|
4359
|
+
deviceScaleFactor: 1,
|
|
4360
|
+
colorProfile: "srgb",
|
|
4361
|
+
colorScheme: "light",
|
|
4362
|
+
locale: "en-US",
|
|
4363
|
+
timezone: "UTC"
|
|
4364
|
+
},
|
|
4365
|
+
browser: { version: browser.version(), executable: browserPath ?? "playwright-managed" },
|
|
4366
|
+
display: display.value ?? null,
|
|
4367
|
+
lavapipeIcd: icd ?? null,
|
|
4368
|
+
captures,
|
|
4369
|
+
consoleErrors,
|
|
4370
|
+
pageErrors,
|
|
4371
|
+
boundary: "Browser-compositor capture is visual iteration evidence, not physical-GPU performance, HDR-display output, or release acceptance."
|
|
4372
|
+
};
|
|
4373
|
+
await writeRunReport(report);
|
|
4374
|
+
const updateReport = async () => {
|
|
4375
|
+
Object.assign(report, {
|
|
4376
|
+
ok: captures.length > 0 && captures.every((capture) => capture.ok) && consoleErrors.length === 0 && pageErrors.length === 0
|
|
4377
|
+
});
|
|
4378
|
+
await writeRunReport(report);
|
|
4379
|
+
};
|
|
4380
|
+
const close = async () => {
|
|
4381
|
+
if (closed) return;
|
|
4382
|
+
closed = true;
|
|
4383
|
+
await Promise.allSettled([browser?.close(), display?.close(), server?.close()]);
|
|
4384
|
+
Object.assign(report, { closedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
4385
|
+
await updateReport();
|
|
4386
|
+
};
|
|
4387
|
+
return {
|
|
4388
|
+
page,
|
|
4389
|
+
url: captureUrl.href,
|
|
4390
|
+
reportPath,
|
|
4391
|
+
async capture(checkpoint, captureOptions = {}) {
|
|
4392
|
+
if (closed) {
|
|
4393
|
+
fail(
|
|
4394
|
+
"browser-capture-session-closed",
|
|
4395
|
+
"capture to run inside an open browser session",
|
|
4396
|
+
"Open one session, complete all checkpoints, then close it.",
|
|
4397
|
+
{ report: reportPath }
|
|
4398
|
+
);
|
|
4399
|
+
}
|
|
4400
|
+
const activePage = page;
|
|
4401
|
+
if (activePage === void 0) {
|
|
4402
|
+
fail(
|
|
4403
|
+
"browser-capture-browser-unavailable",
|
|
4404
|
+
"an open Playwright page",
|
|
4405
|
+
"Inspect the browser launch diagnostic and retry the capture."
|
|
4406
|
+
);
|
|
4407
|
+
}
|
|
4408
|
+
const expectedReady = checkpoint ?? (options.deterministic === true ? "true" : void 0);
|
|
4409
|
+
await activePage.waitForFunction(
|
|
4410
|
+
({ datasetKey }) => Number(document.documentElement.dataset[datasetKey]) > 0,
|
|
4411
|
+
{ datasetKey: FORGEAX_FRAME_SUBMITTED_DATASET },
|
|
4412
|
+
{ timeout: 12e4 }
|
|
4413
|
+
);
|
|
4414
|
+
if (expectedReady !== void 0) {
|
|
4415
|
+
await activePage.waitForFunction(
|
|
4416
|
+
(expected) => document.documentElement.dataset.forgeaxCaptureReady === expected,
|
|
4417
|
+
expectedReady,
|
|
4418
|
+
{ timeout: 12e4 }
|
|
4419
|
+
);
|
|
4420
|
+
}
|
|
4421
|
+
await waitForCompositor(activePage);
|
|
4422
|
+
let captured = await screenshotWithWitness(activePage);
|
|
4423
|
+
const deadline = Date.now() + 12e4;
|
|
4424
|
+
while (!captured.pixels.rendered && Date.now() < deadline) {
|
|
4425
|
+
await activePage.waitForTimeout(500);
|
|
4426
|
+
await waitForCompositor(activePage);
|
|
4427
|
+
captured = await screenshotWithWitness(activePage);
|
|
4428
|
+
}
|
|
4429
|
+
const waitMs = captureOptions.waitMs ?? 0;
|
|
4430
|
+
if (waitMs > 0) {
|
|
4431
|
+
await activePage.waitForTimeout(waitMs);
|
|
4432
|
+
await waitForCompositor(activePage);
|
|
4433
|
+
captured = await screenshotWithWitness(activePage);
|
|
4434
|
+
while (!captured.pixels.rendered && Date.now() < deadline) {
|
|
4435
|
+
await activePage.waitForTimeout(500);
|
|
4436
|
+
await waitForCompositor(activePage);
|
|
4437
|
+
captured = await screenshotWithWitness(activePage);
|
|
4438
|
+
}
|
|
4439
|
+
}
|
|
4440
|
+
const runtime2 = await runtimeWitness(activePage);
|
|
4441
|
+
const uiPresent = runtime2.domUi.rootChildren > 0;
|
|
4442
|
+
const requireUi = captureOptions.requireUi ?? options.requireUi ?? false;
|
|
4443
|
+
const captureBackend = observedBackend(runtime2);
|
|
4444
|
+
if (captureBackend !== "unknown") Object.assign(report, { backend: captureBackend });
|
|
4445
|
+
const backendMatches = backend === "auto" || captureBackend === backend;
|
|
4446
|
+
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);
|
|
4447
|
+
const index = captures.length + 1;
|
|
4448
|
+
const output = resolve(
|
|
4449
|
+
facts.value.root,
|
|
4450
|
+
captureOptions.output ?? resolve(
|
|
4451
|
+
outputDirectory,
|
|
4452
|
+
`${String(index).padStart(3, "0")}-${checkpointSlug(checkpoint)}.png`
|
|
4453
|
+
)
|
|
4454
|
+
);
|
|
4455
|
+
await mkdir(dirname(output), { recursive: true });
|
|
4456
|
+
await writeFile(output, captured.png);
|
|
4457
|
+
const record = {
|
|
4458
|
+
index,
|
|
4459
|
+
checkpoint: checkpoint ?? null,
|
|
4460
|
+
ok: ok2,
|
|
4461
|
+
output,
|
|
4462
|
+
digest: captureDigest(captured.png),
|
|
4463
|
+
pixels: captured.pixels,
|
|
4464
|
+
runtime: runtime2
|
|
4465
|
+
};
|
|
4466
|
+
captures.push(record);
|
|
4467
|
+
await updateReport();
|
|
4468
|
+
if (!ok2) {
|
|
4469
|
+
fail(
|
|
4470
|
+
`${backend === "software" ? "software" : "browser"}-capture-runtime-failed`,
|
|
4471
|
+
"non-flat canvas pixels, the requested browser backend/checkpoint, and no browser errors",
|
|
4472
|
+
"Inspect the run report and repair the first browser runtime failure.",
|
|
4473
|
+
{ checkpoint: checkpoint ?? null, output, report: reportPath }
|
|
4474
|
+
);
|
|
4475
|
+
}
|
|
4476
|
+
return record;
|
|
4477
|
+
},
|
|
4478
|
+
report: () => report,
|
|
4479
|
+
close
|
|
4480
|
+
};
|
|
4481
|
+
} catch (cause) {
|
|
4482
|
+
await Promise.allSettled([browser?.close(), display?.close(), server?.close()]);
|
|
4483
|
+
throw cause;
|
|
4484
|
+
}
|
|
4485
|
+
}
|
|
4486
|
+
function createBrowserCapture(root) {
|
|
4487
|
+
const sessions = /* @__PURE__ */ new Set();
|
|
4488
|
+
return {
|
|
4489
|
+
async open(options) {
|
|
4490
|
+
const session = await openBrowserCaptureSession(root, options);
|
|
4491
|
+
sessions.add(session);
|
|
4492
|
+
return session;
|
|
4493
|
+
},
|
|
4494
|
+
async close() {
|
|
4495
|
+
await Promise.allSettled([...sessions].map((session) => session.close()));
|
|
4496
|
+
sessions.clear();
|
|
4497
|
+
}
|
|
4498
|
+
};
|
|
4499
|
+
}
|
|
4500
|
+
function captureCommandError(cause, legacySoftware = false) {
|
|
4501
|
+
if (cause instanceof SoftwareCaptureError) {
|
|
4502
|
+
const code = legacySoftware && cause.code.startsWith("browser-capture-") ? cause.code.replace(/^browser-capture-/, "software-capture-") : cause.code;
|
|
4503
|
+
return {
|
|
4504
|
+
code,
|
|
4505
|
+
expected: cause.expected,
|
|
4506
|
+
hint: cause.hint,
|
|
4507
|
+
detail: cause.detail
|
|
4508
|
+
};
|
|
4509
|
+
}
|
|
4510
|
+
return commandError(cause, legacySoftware ? "software-capture-failed" : "browser-capture-failed");
|
|
4511
|
+
}
|
|
4512
|
+
async function browserCaptureCommand(options) {
|
|
4513
|
+
const root = options.root ?? process.cwd();
|
|
4514
|
+
const output = resolve(root, options.output ?? "artifacts/capture/game-ui.png");
|
|
4515
|
+
const browser = createBrowserCapture(root);
|
|
4516
|
+
try {
|
|
4517
|
+
const backend = options.backend ?? (options.software === true ? "software" : "auto");
|
|
4518
|
+
const session = await browser.open({
|
|
4519
|
+
backend,
|
|
4520
|
+
...backend === "software" ? { software: true } : {},
|
|
4521
|
+
...options.browser === void 0 ? {} : { browser: options.browser },
|
|
4522
|
+
...options.width === void 0 ? {} : { width: options.width },
|
|
4523
|
+
...options.height === void 0 ? {} : { height: options.height },
|
|
4524
|
+
...options.port === void 0 ? {} : { port: options.port },
|
|
4525
|
+
...options.headless === void 0 ? {} : { headless: options.headless },
|
|
4526
|
+
requireUi: options.requireUi === true,
|
|
4527
|
+
deterministic: options.deterministic === true,
|
|
4528
|
+
outputDir: dirname(output),
|
|
4529
|
+
report: evidencePath(output)
|
|
4530
|
+
});
|
|
4531
|
+
await session.capture(options.deterministic === true ? "true" : void 0, {
|
|
4532
|
+
output,
|
|
4533
|
+
waitMs: options.waitMs ?? 4e3,
|
|
4534
|
+
requireUi: options.requireUi === true
|
|
4535
|
+
});
|
|
4536
|
+
await session.close();
|
|
4537
|
+
return { ok: true, value: session.report() };
|
|
4538
|
+
} catch (cause) {
|
|
4539
|
+
return { ok: false, error: captureCommandError(cause) };
|
|
4540
|
+
} finally {
|
|
4541
|
+
await browser.close();
|
|
4542
|
+
}
|
|
4543
|
+
}
|
|
4544
|
+
async function softwareCaptureCommand(options) {
|
|
4545
|
+
const result = await browserCaptureCommand({ ...options, backend: "software", software: true });
|
|
4546
|
+
if (result.ok) return result;
|
|
4547
|
+
return { ok: false, error: captureCommandErrorFromResult(result.error) };
|
|
4548
|
+
}
|
|
4549
|
+
function captureCommandErrorFromResult(error, legacySoftware) {
|
|
4550
|
+
if (!error.code.startsWith("browser-capture-")) return error;
|
|
4551
|
+
return { ...error, code: error.code.replace(/^browser-capture-/, "software-capture-") };
|
|
4552
|
+
}
|
|
4553
|
+
var SoftwareCaptureError;
|
|
4554
|
+
var init_software_capture = __esm({
|
|
4555
|
+
"src/software-capture.ts"() {
|
|
4556
|
+
init_host();
|
|
4557
|
+
init_project();
|
|
4558
|
+
SoftwareCaptureError = class extends Error {
|
|
4559
|
+
constructor(code, expected, hint, detail) {
|
|
4560
|
+
super(`${code}: ${hint}`);
|
|
4561
|
+
this.code = code;
|
|
4562
|
+
this.expected = expected;
|
|
4563
|
+
this.hint = hint;
|
|
4564
|
+
this.detail = detail;
|
|
4565
|
+
this.name = "SoftwareCaptureError";
|
|
4566
|
+
}
|
|
4567
|
+
code;
|
|
4568
|
+
expected;
|
|
4569
|
+
hint;
|
|
4570
|
+
detail;
|
|
4571
|
+
};
|
|
4572
|
+
}
|
|
4573
|
+
});
|
|
3786
4574
|
|
|
3787
4575
|
// src/commands.ts
|
|
3788
4576
|
var commands_exports = {};
|
|
@@ -3792,12 +4580,17 @@ __export(commands_exports, {
|
|
|
3792
4580
|
assetInspectCommand: () => assetInspectCommand,
|
|
3793
4581
|
assetListCommand: () => assetListCommand,
|
|
3794
4582
|
assetVerifyCommand: () => assetVerifyCommand,
|
|
4583
|
+
browserCaptureCommand: () => browserCaptureCommand,
|
|
3795
4584
|
buildCommand: () => buildCommand,
|
|
3796
4585
|
createCliRhiDebugOperationContext: () => createCliRhiDebugOperationContext,
|
|
3797
4586
|
createRhiDebugOperationContext: () => createRhiDebugOperationContext,
|
|
3798
4587
|
devCommand: () => devCommand,
|
|
3799
4588
|
discoverRhiDebugOperations: () => discoverRhiDebugOperations,
|
|
3800
4589
|
doctorCommand: () => doctorCommand,
|
|
4590
|
+
engineDoctorCommand: () => engineDoctorCommand,
|
|
4591
|
+
engineStatusCommand: () => engineStatusCommand,
|
|
4592
|
+
engineUnlinkCommand: () => engineUnlinkCommand,
|
|
4593
|
+
engineUseLocalCommand: () => engineUseLocalCommand,
|
|
3801
4594
|
initCommand: () => initCommand,
|
|
3802
4595
|
newCommand: () => newCommand,
|
|
3803
4596
|
packageCommand: () => packageCommand,
|
|
@@ -3812,11 +4605,27 @@ __export(commands_exports, {
|
|
|
3812
4605
|
shaderCheckCommand: () => shaderCheckCommand,
|
|
3813
4606
|
skillInstallCommand: () => skillInstallCommand,
|
|
3814
4607
|
skillVerifyCommand: () => skillVerifyCommand,
|
|
4608
|
+
softwareCaptureCommand: () => softwareCaptureCommand,
|
|
3815
4609
|
testCommand: () => testCommand
|
|
3816
4610
|
});
|
|
3817
4611
|
function runRhiDebugCommand(name, input, context) {
|
|
3818
4612
|
return runRhiDebugOperation(name, input, context);
|
|
3819
4613
|
}
|
|
4614
|
+
async function materializeViteDevPort(port) {
|
|
4615
|
+
if (port !== 0) return port;
|
|
4616
|
+
const reservation = createServer();
|
|
4617
|
+
await new Promise((resolveListen, rejectListen) => {
|
|
4618
|
+
reservation.once("error", rejectListen);
|
|
4619
|
+
reservation.listen(0, "127.0.0.1", resolveListen);
|
|
4620
|
+
});
|
|
4621
|
+
const address = reservation.address();
|
|
4622
|
+
const assigned = typeof address === "object" && address !== null ? address.port : 0;
|
|
4623
|
+
await new Promise((resolveClose, rejectClose) => {
|
|
4624
|
+
reservation.close((error) => error === void 0 ? resolveClose() : rejectClose(error));
|
|
4625
|
+
});
|
|
4626
|
+
if (assigned === 0) throw new Error("OS did not assign an ephemeral preview port");
|
|
4627
|
+
return assigned;
|
|
4628
|
+
}
|
|
3820
4629
|
async function buildCommand(options = {}) {
|
|
3821
4630
|
const facts = await readProjectFacts(options.root);
|
|
3822
4631
|
if (!facts.ok) return facts;
|
|
@@ -3919,13 +4728,38 @@ async function devCommand(options = {}) {
|
|
|
3919
4728
|
const facts = await readProjectFacts(options.root);
|
|
3920
4729
|
if (!facts.ok) return facts;
|
|
3921
4730
|
const previous = process.cwd();
|
|
4731
|
+
let server;
|
|
3922
4732
|
try {
|
|
4733
|
+
const { createServer: createServer2 } = await import('vite');
|
|
4734
|
+
const { createViteConfig: createViteConfig2 } = await Promise.resolve().then(() => (init_host(), host_exports));
|
|
4735
|
+
const port = resolveProjectPort(options.port);
|
|
4736
|
+
const vitePort = { ...port, port: await materializeViteDevPort(port.port) };
|
|
3923
4737
|
process.chdir(facts.value.root);
|
|
3924
|
-
|
|
3925
|
-
|
|
4738
|
+
server = await createServer2(
|
|
4739
|
+
await createViteConfig2(facts.value, "serve", "/", {
|
|
4740
|
+
server: vitePort
|
|
4741
|
+
})
|
|
4742
|
+
);
|
|
4743
|
+
await server.listen(vitePort.port);
|
|
3926
4744
|
if (options.json !== true) server.printUrls();
|
|
3927
|
-
return {
|
|
4745
|
+
return {
|
|
4746
|
+
ok: true,
|
|
4747
|
+
value: {
|
|
4748
|
+
root: facts.value.root,
|
|
4749
|
+
urls: server.resolvedUrls,
|
|
4750
|
+
mode: "dev",
|
|
4751
|
+
serves: "source",
|
|
4752
|
+
capabilities: {
|
|
4753
|
+
"rhi.capture": {
|
|
4754
|
+
available: false,
|
|
4755
|
+
realm: "host",
|
|
4756
|
+
reason: "standalone-dev-server-has-no-live-app-cli-attachment"
|
|
4757
|
+
}
|
|
4758
|
+
}
|
|
4759
|
+
}
|
|
4760
|
+
};
|
|
3928
4761
|
} catch (cause) {
|
|
4762
|
+
await server?.close();
|
|
3929
4763
|
process.chdir(previous);
|
|
3930
4764
|
return { ok: false, error: commandError(cause, "dev-server-failed") };
|
|
3931
4765
|
}
|
|
@@ -3935,15 +4769,31 @@ async function previewCommand(options = {}) {
|
|
|
3935
4769
|
const verified = await verifyDist(resolve(root, "dist"));
|
|
3936
4770
|
if (!verified.ok) return verified;
|
|
3937
4771
|
try {
|
|
4772
|
+
const port = resolveProjectPort(options.port);
|
|
3938
4773
|
const server = await preview({
|
|
3939
4774
|
root,
|
|
3940
4775
|
configFile: false,
|
|
3941
4776
|
base: verified.value.base,
|
|
3942
|
-
preview: { open: false, port
|
|
4777
|
+
preview: { open: false, ...port },
|
|
3943
4778
|
build: { outDir: resolve(root, "dist") }
|
|
3944
4779
|
});
|
|
3945
4780
|
if (options.json !== true) server.printUrls();
|
|
3946
|
-
return {
|
|
4781
|
+
return {
|
|
4782
|
+
ok: true,
|
|
4783
|
+
value: {
|
|
4784
|
+
root,
|
|
4785
|
+
urls: server.resolvedUrls,
|
|
4786
|
+
mode: "preview",
|
|
4787
|
+
serves: "dist",
|
|
4788
|
+
capabilities: {
|
|
4789
|
+
"rhi.capture": {
|
|
4790
|
+
available: false,
|
|
4791
|
+
realm: "host",
|
|
4792
|
+
reason: "static-dist-host-does-not-install-dev-capture"
|
|
4793
|
+
}
|
|
4794
|
+
}
|
|
4795
|
+
}
|
|
4796
|
+
};
|
|
3947
4797
|
} catch (cause) {
|
|
3948
4798
|
return { ok: false, error: commandError(cause, "preview-server-failed") };
|
|
3949
4799
|
}
|
|
@@ -3993,14 +4843,17 @@ var init_commands = __esm({
|
|
|
3993
4843
|
init_dist();
|
|
3994
4844
|
init_host();
|
|
3995
4845
|
init_project();
|
|
4846
|
+
init_types();
|
|
3996
4847
|
init_assets();
|
|
3997
4848
|
init_bootstrap_commands();
|
|
4849
|
+
init_engine_binding();
|
|
3998
4850
|
init_plugin_authoring();
|
|
3999
4851
|
init_cli_context();
|
|
4000
4852
|
init_operations();
|
|
4001
4853
|
init_sdk_install();
|
|
4002
4854
|
init_shader_check();
|
|
4003
4855
|
init_skill_install();
|
|
4856
|
+
init_software_capture();
|
|
4004
4857
|
init_operations();
|
|
4005
4858
|
}
|
|
4006
4859
|
});
|
|
@@ -4365,7 +5218,7 @@ function hasStaticToolDeclarations(entry) {
|
|
|
4365
5218
|
return Array.isArray(Reflect.get(entry.config, "tools"));
|
|
4366
5219
|
}
|
|
4367
5220
|
async function createViteModuleLoader(root) {
|
|
4368
|
-
const server = await createServer({
|
|
5221
|
+
const server = await createServer$1({
|
|
4369
5222
|
root,
|
|
4370
5223
|
appType: "custom",
|
|
4371
5224
|
configFile: false,
|
|
@@ -4622,9 +5475,42 @@ var init_runtime = __esm({
|
|
|
4622
5475
|
}
|
|
4623
5476
|
});
|
|
4624
5477
|
|
|
5478
|
+
// src/cli-output.ts
|
|
5479
|
+
function agentOnboardingLines(value) {
|
|
5480
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return [];
|
|
5481
|
+
const onboarding = value.onboarding;
|
|
5482
|
+
if (onboarding === null || typeof onboarding !== "object" || Array.isArray(onboarding)) return [];
|
|
5483
|
+
const candidate = onboarding;
|
|
5484
|
+
const lines = Array.isArray(candidate.read) ? candidate.read.filter((path) => typeof path === "string").map((path) => `[forgeax] read: ${path}`) : [];
|
|
5485
|
+
if (typeof candidate.next?.cwd === "string" && Array.isArray(candidate.next.argv) && candidate.next.argv.every((part) => typeof part === "string")) {
|
|
5486
|
+
lines.push(`[forgeax] next cwd: ${candidate.next.cwd}`);
|
|
5487
|
+
lines.push(`[forgeax] next: ${candidate.next.argv.join(" ")}`);
|
|
5488
|
+
}
|
|
5489
|
+
return lines;
|
|
5490
|
+
}
|
|
5491
|
+
function sdkUpdateLines(value) {
|
|
5492
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return [];
|
|
5493
|
+
const update = value.sdkUpdate;
|
|
5494
|
+
if (update === null || typeof update !== "object" || Array.isArray(update)) return [];
|
|
5495
|
+
const candidate = update;
|
|
5496
|
+
if (candidate.status !== "available" || typeof candidate.currentVersion !== "string" || typeof candidate.latestVersion !== "string" || typeof candidate.migrationRisk !== "string") {
|
|
5497
|
+
return [];
|
|
5498
|
+
}
|
|
5499
|
+
return [
|
|
5500
|
+
`[forgeax] update available: @forgeax/engine-sdk ${candidate.currentVersion} -> ${candidate.latestVersion}`,
|
|
5501
|
+
`[forgeax] update note: ${candidate.migrationRisk}`
|
|
5502
|
+
];
|
|
5503
|
+
}
|
|
5504
|
+
function renderForgeaxUsage() {
|
|
5505
|
+
return "Usage: forgeax new [directory] [--template empty|game-3d]\n forgeax <init|doctor|test|dev|build|package|serve|preview> [directory]\n forgeax capture [directory] [--backend auto|software|hardware] [--require-ui] [--deterministic] [--headless] [--output PATH]\n forgeax engine <status|doctor|unlink> [--root directory]\n forgeax engine use-local <engine-directory> [--root directory]\n forgeax package [directory] [--output release/game-web.zip]\n forgeax run <rhi.capture|rhi.summary|rhi.inspect> [options]\n forgeax run <operation-id> --input <request.json>\n forgeax exec <program.mjs> [--json]\n forgeax asset <add|verify|inspect|list> [subject]\n forgeax shader check [path]\n forgeax plugin <install|uninstall> <module-or-id> [options]\n forgeax skill <install|verify> [--root directory]\n forgeax sdk install <directory> [--version VERSION]\n";
|
|
5506
|
+
}
|
|
5507
|
+
|
|
4625
5508
|
// src/cli.ts
|
|
4626
5509
|
init_commands();
|
|
4627
5510
|
|
|
5511
|
+
// src/tools/commands.ts
|
|
5512
|
+
init_software_capture();
|
|
5513
|
+
|
|
4628
5514
|
// src/tools/client.ts
|
|
4629
5515
|
init_catalog();
|
|
4630
5516
|
function decorateResourcePreviewTerminal(terminal) {
|
|
@@ -4795,6 +5681,7 @@ async function execCommand(options) {
|
|
|
4795
5681
|
};
|
|
4796
5682
|
}
|
|
4797
5683
|
const root = options.root ?? process.cwd();
|
|
5684
|
+
const browser = createBrowserCapture(root);
|
|
4798
5685
|
try {
|
|
4799
5686
|
const module = await import(pathToFileURL(resolve(root, options.program)).href);
|
|
4800
5687
|
const program = module.default ?? module.run;
|
|
@@ -4802,7 +5689,8 @@ async function execCommand(options) {
|
|
|
4802
5689
|
throw new TypeError("operation program must export a default function or named run function");
|
|
4803
5690
|
}
|
|
4804
5691
|
const client = await createToolClient({ projectRoot: root });
|
|
4805
|
-
const
|
|
5692
|
+
const context = { ...client, browser };
|
|
5693
|
+
const value = await program(context);
|
|
4806
5694
|
if (!isSerializableValue(value)) {
|
|
4807
5695
|
throw new TypeError("tool program returned a non-serializable live value");
|
|
4808
5696
|
}
|
|
@@ -4817,17 +5705,22 @@ async function execCommand(options) {
|
|
|
4817
5705
|
detail: { reason: cause instanceof Error ? cause.message : String(cause) }
|
|
4818
5706
|
}
|
|
4819
5707
|
};
|
|
5708
|
+
} finally {
|
|
5709
|
+
await browser.close();
|
|
4820
5710
|
}
|
|
4821
5711
|
}
|
|
4822
5712
|
|
|
4823
5713
|
// src/cli.ts
|
|
4824
|
-
|
|
5714
|
+
init_types();
|
|
5715
|
+
var rawArgs = process.argv.slice(2);
|
|
5716
|
+
var args = [...rawArgs];
|
|
4825
5717
|
var primary = args.shift();
|
|
4826
|
-
var nested = primary === "asset" || primary === "shader" || primary === "plugin" || primary === "skill" || primary === "sdk" ? args.shift() : void 0;
|
|
4827
|
-
var command = primary === "asset" || primary === "shader" || primary === "plugin" || primary === "skill" || primary === "sdk" ? `${primary}.${nested ?? ""}` : primary;
|
|
5718
|
+
var nested = primary === "asset" || primary === "shader" || primary === "plugin" || primary === "skill" || primary === "sdk" || primary === "engine" ? args.shift() : void 0;
|
|
5719
|
+
var command = primary === "asset" || primary === "shader" || primary === "plugin" || primary === "skill" || primary === "sdk" || primary === "engine" ? `${primary}.${nested ?? ""}` : primary;
|
|
4828
5720
|
var json = args.includes("--json");
|
|
4829
5721
|
var dryRun = args.includes("--dry-run");
|
|
4830
5722
|
var noInstall = args.includes("--no-install");
|
|
5723
|
+
var help = rawArgs.includes("--help") || rawArgs.includes("-h");
|
|
4831
5724
|
function option(name) {
|
|
4832
5725
|
const index = args.indexOf(name);
|
|
4833
5726
|
return index < 0 ? void 0 : args[index + 1];
|
|
@@ -4836,6 +5729,8 @@ var optionValueIndexes = new Set(
|
|
|
4836
5729
|
[
|
|
4837
5730
|
"--artifact",
|
|
4838
5731
|
"--base",
|
|
5732
|
+
"--backend",
|
|
5733
|
+
"--browser",
|
|
4839
5734
|
"--digest",
|
|
4840
5735
|
"--id",
|
|
4841
5736
|
"--realm",
|
|
@@ -4844,8 +5739,12 @@ var optionValueIndexes = new Set(
|
|
|
4844
5739
|
"--input",
|
|
4845
5740
|
"--output",
|
|
4846
5741
|
"--root",
|
|
5742
|
+
"--port",
|
|
4847
5743
|
"--template",
|
|
5744
|
+
"--height",
|
|
4848
5745
|
"--version",
|
|
5746
|
+
"--wait-ms",
|
|
5747
|
+
"--width",
|
|
4849
5748
|
"--work-index"
|
|
4850
5749
|
].flatMap((name) => {
|
|
4851
5750
|
const index = args.indexOf(name);
|
|
@@ -4865,6 +5764,11 @@ var commands = [
|
|
|
4865
5764
|
"dev",
|
|
4866
5765
|
"build",
|
|
4867
5766
|
"package",
|
|
5767
|
+
"capture",
|
|
5768
|
+
"engine.status",
|
|
5769
|
+
"engine.use-local",
|
|
5770
|
+
"engine.unlink",
|
|
5771
|
+
"engine.doctor",
|
|
4868
5772
|
"serve",
|
|
4869
5773
|
"preview",
|
|
4870
5774
|
"run",
|
|
@@ -4882,6 +5786,18 @@ var commands = [
|
|
|
4882
5786
|
"describe",
|
|
4883
5787
|
"exec"
|
|
4884
5788
|
];
|
|
5789
|
+
function projectServerOptions() {
|
|
5790
|
+
const parsedPort = parseProjectPortOption(option("--port"), args.includes("--port"));
|
|
5791
|
+
if (!parsedPort.ok) return parsedPort;
|
|
5792
|
+
return {
|
|
5793
|
+
ok: true,
|
|
5794
|
+
value: {
|
|
5795
|
+
root: projectRoot,
|
|
5796
|
+
json,
|
|
5797
|
+
...parsedPort.value === void 0 ? {} : { port: parsedPort.value }
|
|
5798
|
+
}
|
|
5799
|
+
};
|
|
5800
|
+
}
|
|
4885
5801
|
async function run(value) {
|
|
4886
5802
|
switch (value) {
|
|
4887
5803
|
case "new": {
|
|
@@ -4898,8 +5814,10 @@ async function run(value) {
|
|
|
4898
5814
|
return doctorCommand({ root: projectRoot});
|
|
4899
5815
|
case "test":
|
|
4900
5816
|
return testCommand({ root: projectRoot});
|
|
4901
|
-
case "dev":
|
|
4902
|
-
|
|
5817
|
+
case "dev": {
|
|
5818
|
+
const options = projectServerOptions();
|
|
5819
|
+
return options.ok ? devCommand(options.value) : options;
|
|
5820
|
+
}
|
|
4903
5821
|
case "build": {
|
|
4904
5822
|
const base = option("--base");
|
|
4905
5823
|
const outDir = option("--out-dir");
|
|
@@ -4917,10 +5835,100 @@ async function run(value) {
|
|
|
4917
5835
|
...output === void 0 ? {} : { output }
|
|
4918
5836
|
});
|
|
4919
5837
|
}
|
|
4920
|
-
case "
|
|
4921
|
-
|
|
4922
|
-
|
|
4923
|
-
|
|
5838
|
+
case "capture": {
|
|
5839
|
+
const parsedPort = parseProjectPortOption(option("--port"), args.includes("--port"));
|
|
5840
|
+
if (!parsedPort.ok) return parsedPort;
|
|
5841
|
+
const software = args.includes("--software");
|
|
5842
|
+
const requestedBackend = option("--backend");
|
|
5843
|
+
if (requestedBackend !== void 0 && requestedBackend !== "auto" && requestedBackend !== "software" && requestedBackend !== "hardware") {
|
|
5844
|
+
return {
|
|
5845
|
+
ok: false,
|
|
5846
|
+
error: {
|
|
5847
|
+
code: "cli-parse-error",
|
|
5848
|
+
expected: "--backend to be auto, software, or hardware",
|
|
5849
|
+
hint: "Use auto for a portable capture, or assert one browser rendering lane explicitly.",
|
|
5850
|
+
detail: { option: "--backend", received: requestedBackend }
|
|
5851
|
+
}
|
|
5852
|
+
};
|
|
5853
|
+
}
|
|
5854
|
+
if (software && requestedBackend !== void 0 && requestedBackend !== "software") {
|
|
5855
|
+
return {
|
|
5856
|
+
ok: false,
|
|
5857
|
+
error: {
|
|
5858
|
+
code: "cli-parse-error",
|
|
5859
|
+
expected: "--software and --backend to select the same capture lane",
|
|
5860
|
+
hint: "Use --backend software (or only the legacy --software flag).",
|
|
5861
|
+
detail: { software: true, backend: requestedBackend }
|
|
5862
|
+
}
|
|
5863
|
+
};
|
|
5864
|
+
}
|
|
5865
|
+
const parseInteger = (name, fallback, minimum, maximum) => {
|
|
5866
|
+
const raw = option(name);
|
|
5867
|
+
if (raw === void 0) return { ok: true, value: fallback };
|
|
5868
|
+
const parsed = Number(raw);
|
|
5869
|
+
return Number.isSafeInteger(parsed) && parsed >= minimum && parsed <= maximum ? { ok: true, value: parsed } : {
|
|
5870
|
+
ok: false,
|
|
5871
|
+
error: {
|
|
5872
|
+
code: "cli-parse-error",
|
|
5873
|
+
expected: `${name} to be an integer from ${minimum} to ${maximum}`,
|
|
5874
|
+
hint: "Pass a bounded deterministic capture value.",
|
|
5875
|
+
detail: { option: name, received: raw }
|
|
5876
|
+
}
|
|
5877
|
+
};
|
|
5878
|
+
};
|
|
5879
|
+
const width = parseInteger("--width", 1280, 64, 8192);
|
|
5880
|
+
if (!width.ok) return width;
|
|
5881
|
+
const height = parseInteger("--height", 720, 64, 8192);
|
|
5882
|
+
if (!height.ok) return height;
|
|
5883
|
+
const waitMs = parseInteger("--wait-ms", 4e3, 0, 12e4);
|
|
5884
|
+
if (!waitMs.ok) return waitMs;
|
|
5885
|
+
const output = option("--output");
|
|
5886
|
+
const browser = option("--browser");
|
|
5887
|
+
return browserCaptureCommand({
|
|
5888
|
+
root: projectRoot,
|
|
5889
|
+
json,
|
|
5890
|
+
backend: requestedBackend ?? (software ? "software" : "auto"),
|
|
5891
|
+
...software ? { software: true } : {},
|
|
5892
|
+
width: width.value,
|
|
5893
|
+
height: height.value,
|
|
5894
|
+
waitMs: waitMs.value,
|
|
5895
|
+
requireUi: args.includes("--require-ui"),
|
|
5896
|
+
deterministic: args.includes("--deterministic"),
|
|
5897
|
+
...args.includes("--headless") ? { headless: true } : {},
|
|
5898
|
+
...parsedPort.value === void 0 ? {} : { port: parsedPort.value },
|
|
5899
|
+
...output === void 0 ? {} : { output },
|
|
5900
|
+
...browser === void 0 ? {} : { browser }
|
|
5901
|
+
});
|
|
5902
|
+
}
|
|
5903
|
+
case "engine.status":
|
|
5904
|
+
return engineStatusCommand({ root: scopedRoot});
|
|
5905
|
+
case "engine.use-local": {
|
|
5906
|
+
const path = positionals[0];
|
|
5907
|
+
if (path === void 0) {
|
|
5908
|
+
return {
|
|
5909
|
+
ok: false,
|
|
5910
|
+
error: {
|
|
5911
|
+
code: "cli-parse-error",
|
|
5912
|
+
expected: "forgeax engine use-local <engine-directory>",
|
|
5913
|
+
hint: "Pass the local Engine source checkout or SDK source directory.",
|
|
5914
|
+
detail: {}
|
|
5915
|
+
}
|
|
5916
|
+
};
|
|
5917
|
+
}
|
|
5918
|
+
return engineUseLocalCommand({ root: scopedRoot, path, dryRun, json });
|
|
5919
|
+
}
|
|
5920
|
+
case "engine.unlink":
|
|
5921
|
+
return engineUnlinkCommand({ root: scopedRoot, dryRun});
|
|
5922
|
+
case "engine.doctor":
|
|
5923
|
+
return engineDoctorCommand({ root: scopedRoot});
|
|
5924
|
+
case "preview": {
|
|
5925
|
+
const options = projectServerOptions();
|
|
5926
|
+
return options.ok ? previewCommand(options.value) : options;
|
|
5927
|
+
}
|
|
5928
|
+
case "serve": {
|
|
5929
|
+
const options = projectServerOptions();
|
|
5930
|
+
return options.ok ? devCommand(options.value) : options;
|
|
5931
|
+
}
|
|
4924
5932
|
case "plugin.install": {
|
|
4925
5933
|
const module = positionals[0];
|
|
4926
5934
|
const id = option("--id");
|
|
@@ -5093,14 +6101,36 @@ async function run(value) {
|
|
|
5093
6101
|
}
|
|
5094
6102
|
};
|
|
5095
6103
|
}
|
|
5096
|
-
|
|
6104
|
+
const fieldsOption = option("--fields");
|
|
6105
|
+
const fields = fieldsOption?.split(",").map((field) => field.trim()).filter((field) => field.length > 0);
|
|
6106
|
+
const allowedFields = /* @__PURE__ */ new Set(["bindings", "pipeline", "pixels"]);
|
|
6107
|
+
if (fields?.some((field) => !allowedFields.has(field))) {
|
|
6108
|
+
return {
|
|
6109
|
+
ok: false,
|
|
6110
|
+
error: {
|
|
6111
|
+
code: "cli-parse-error",
|
|
6112
|
+
expected: "--fields to contain pipeline, bindings, and/or pixels",
|
|
6113
|
+
hint: "Use a comma-separated subset such as --fields pipeline,bindings.",
|
|
6114
|
+
detail: { fields: fieldsOption }
|
|
6115
|
+
}
|
|
6116
|
+
};
|
|
6117
|
+
}
|
|
6118
|
+
return runRhiDebugCommand(
|
|
6119
|
+
operation,
|
|
6120
|
+
{
|
|
6121
|
+
artifact,
|
|
6122
|
+
workIndex,
|
|
6123
|
+
...fields === void 0 ? {} : { fields }
|
|
6124
|
+
},
|
|
6125
|
+
context
|
|
6126
|
+
);
|
|
5097
6127
|
}
|
|
5098
6128
|
}
|
|
5099
6129
|
}
|
|
5100
|
-
if (
|
|
5101
|
-
process.
|
|
5102
|
-
|
|
5103
|
-
);
|
|
6130
|
+
if (help && primary !== "run") {
|
|
6131
|
+
process.stdout.write(renderForgeaxUsage());
|
|
6132
|
+
} else if (command === void 0 || !commands.includes(command)) {
|
|
6133
|
+
process.stderr.write(renderForgeaxUsage());
|
|
5104
6134
|
process.exitCode = 2;
|
|
5105
6135
|
} else {
|
|
5106
6136
|
const stdoutWrite = process.stdout.write;
|
|
@@ -5120,9 +6150,23 @@ if (command === void 0 || !commands.includes(command)) {
|
|
|
5120
6150
|
const envelope = result.ok ? { schemaVersion: "1.0.0", command, ok: true, value: result.value } : { schemaVersion: "1.0.0", command, ok: false, error: result.error };
|
|
5121
6151
|
if (json) stdoutWrite.call(process.stdout, `${JSON.stringify(envelope)}
|
|
5122
6152
|
`);
|
|
5123
|
-
else if (result.ok)
|
|
6153
|
+
else if (result.ok) {
|
|
6154
|
+
process.stdout.write(`[forgeax] ${command} ready
|
|
6155
|
+
`);
|
|
6156
|
+
for (const line of agentOnboardingLines(result.value)) process.stdout.write(`${line}
|
|
6157
|
+
`);
|
|
6158
|
+
for (const line of sdkUpdateLines(result.value)) process.stdout.write(`${line}
|
|
5124
6159
|
`);
|
|
5125
|
-
|
|
6160
|
+
if (command === "sdk.install") {
|
|
6161
|
+
const value = result.value;
|
|
6162
|
+
if (typeof value.next?.cwd === "string" && Array.isArray(value.next.argv) && value.next.argv.every((part) => typeof part === "string")) {
|
|
6163
|
+
process.stdout.write(`[forgeax] next cwd: ${value.next.cwd}
|
|
6164
|
+
`);
|
|
6165
|
+
process.stdout.write(`[forgeax] next: ${value.next.argv.join(" ")}
|
|
6166
|
+
`);
|
|
6167
|
+
}
|
|
6168
|
+
}
|
|
6169
|
+
} else process.stderr.write(`[forgeax] ${result.error.code}: ${result.error.hint}
|
|
5126
6170
|
`);
|
|
5127
6171
|
if (!result.ok) process.exitCode = result.error.code === "cli-parse-error" ? 2 : 1;
|
|
5128
6172
|
}
|