@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/index.mjs
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { deflateRawSync } from 'zlib';
|
|
2
|
-
import { createHash } from 'crypto';
|
|
3
|
-
import { readFile, writeFile,
|
|
2
|
+
import { createHash, randomUUID } from 'crypto';
|
|
3
|
+
import { readFile, writeFile, access, readdir, stat, realpath, rm, mkdir, copyFile, mkdtemp, cp, rename, symlink, unlink, lstat, readlink, rmdir } from 'fs/promises';
|
|
4
4
|
import { resolve, relative, sep, isAbsolute, extname, dirname, basename, join } from 'path';
|
|
5
|
-
import {
|
|
5
|
+
import { GameProjectSchema } from '@forgeax/engine-project';
|
|
6
|
+
import { existsSync } from 'fs';
|
|
6
7
|
import { createRequire } from 'module';
|
|
7
8
|
import { fileURLToPath } from 'url';
|
|
8
9
|
import { audioImporter } from '@forgeax/engine-audio-webaudio/audio-importer';
|
|
@@ -12,21 +13,25 @@ import { gltfImporter } from '@forgeax/engine-gltf';
|
|
|
12
13
|
import { imageImporter } from '@forgeax/engine-image/image-importer';
|
|
13
14
|
import { BUILTIN_MESH_ASSETS } from '@forgeax/engine-pack/builtin';
|
|
14
15
|
import { scanInventory } from '@forgeax/engine-pack/scanner';
|
|
15
|
-
import { validatePreviewArtifactManifest, createMaterialPreviewContribution, createMeshPreviewContribution, createVfxPreviewContribution, createTexturePreviewContribution,
|
|
16
|
+
import { validatePreviewArtifactManifest, createMaterialPreviewContribution, createMeshPreviewContribution, createVfxPreviewContribution, createTexturePreviewContribution, validateCanonicalKitReceipt, describeResourcePreviewFailure } from '@forgeax/engine-preview';
|
|
16
17
|
import { createMaterialPackCooker } from '@forgeax/engine-shader-compiler';
|
|
17
|
-
import { err, ok
|
|
18
|
+
import { createStandaloneRuntimeAssetBinding, err, ok } from '@forgeax/engine-types';
|
|
18
19
|
import { createParticleCodeNativeCookerFromRoots } from '@forgeax/engine-vfx-compiler';
|
|
19
20
|
import { pluginPack, reloadAssetHost } from '@forgeax/engine-vite-plugin-pack';
|
|
20
21
|
import { forgeaxShader } from '@forgeax/engine-vite-plugin-shader';
|
|
21
|
-
import { GameProjectSchema } from '@forgeax/engine-project';
|
|
22
22
|
import { runCliGltf } from '@forgeax/engine-gltf/cli-gltf';
|
|
23
23
|
import { scanEntries } from '@forgeax/engine-pack/cli-asset';
|
|
24
24
|
import { AssetGuid } from '@forgeax/engine-pack/guid';
|
|
25
|
-
import { execFile } from 'child_process';
|
|
25
|
+
import { execFile, spawn } from 'child_process';
|
|
26
26
|
import { tmpdir } from 'os';
|
|
27
27
|
import { promisify } from 'util';
|
|
28
|
-
import { createRhiDebugError, decodeTape, openReplay, buildFrameModel } from '@forgeax/engine-rhi-debug';
|
|
29
|
-
import {
|
|
28
|
+
import { replayDeviceRequest, createRhiDebugError, decodeTape, openReplay, buildFrameModel } from '@forgeax/engine-rhi-debug';
|
|
29
|
+
import { rhi, createShaderModule } from '@forgeax/engine-rhi-webgpu';
|
|
30
|
+
import { build, preview, createServer as createServer$2 } from 'vite';
|
|
31
|
+
import { createServer as createServer$1 } from 'net';
|
|
32
|
+
import { createToolPreviewRecipe, createToolPreviewHost, FORGEAX_FRAME_SUBMITTED_DATASET } from '@forgeax/engine-app';
|
|
33
|
+
import { parseImage } from '@forgeax/engine-image/parse-image';
|
|
34
|
+
import { chromium } from 'playwright';
|
|
30
35
|
import { startVitest } from 'vitest/node';
|
|
31
36
|
import materialPreviewPlugin from '@forgeax/engine-preview/material';
|
|
32
37
|
import meshPreviewPlugin from '@forgeax/engine-preview/mesh';
|
|
@@ -36,10 +41,7 @@ import { validateArtifactManifest, defineTool, createToolRuntime, validateRealmB
|
|
|
36
41
|
export { createAuthenticatedLoopbackTransport, createCapabilityToken, createMigrationRecipe, createRealmCapabilityMatrix, createServiceCapability, probeMigrationTarget } from '@forgeax/engine-tool-runtime';
|
|
37
42
|
import { Context, isToolPlugin } from '@forgeax/engine-plugin';
|
|
38
43
|
import { installCatalogLoader, projectPluginEntries, bootstrapCatalogLoader } from '@forgeax/engine-plugin/loader';
|
|
39
|
-
import { createServer
|
|
40
|
-
import { createToolPreviewRecipe, createToolPreviewHost } from '@forgeax/engine-app';
|
|
41
|
-
import { createServer as createServer$2 } from 'net';
|
|
42
|
-
import { chromium } from 'playwright';
|
|
44
|
+
import { createServer } from 'http';
|
|
43
45
|
|
|
44
46
|
var __defProp = Object.defineProperty;
|
|
45
47
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
@@ -303,17 +305,549 @@ var init_dist = __esm({
|
|
|
303
305
|
"src/dist.ts"() {
|
|
304
306
|
}
|
|
305
307
|
});
|
|
306
|
-
function
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
308
|
+
function projectError(code, expected, hint, detail) {
|
|
309
|
+
return { ok: false, error: { code, expected, hint, detail } };
|
|
310
|
+
}
|
|
311
|
+
async function readJson(path) {
|
|
312
|
+
return JSON.parse(await readFile(path, "utf8"));
|
|
313
|
+
}
|
|
314
|
+
function firstUnsupportedStandaloneRealm(entries2, inheritedRealm = "engine") {
|
|
315
|
+
for (const entry of entries2) {
|
|
316
|
+
const realm = entry.realm ?? inheritedRealm ?? "engine";
|
|
317
|
+
if (realm !== "engine") return { id: entry.id, realm };
|
|
318
|
+
if (entry.group === true) {
|
|
319
|
+
const unsupported = firstUnsupportedStandaloneRealm(
|
|
320
|
+
entry.config,
|
|
321
|
+
realm
|
|
322
|
+
);
|
|
323
|
+
if (unsupported !== void 0) return unsupported;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
return void 0;
|
|
327
|
+
}
|
|
328
|
+
function pluginModuleNames(entries2) {
|
|
329
|
+
return entries2.flatMap(
|
|
330
|
+
(entry) => entry.group === true ? pluginModuleNames(entry.config) : [entry.name]
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
async function readProjectFacts(rootInput = process.cwd()) {
|
|
334
|
+
const root = resolve(rootInput);
|
|
335
|
+
let forgeValue;
|
|
336
|
+
let packageValue;
|
|
310
337
|
try {
|
|
311
|
-
|
|
312
|
-
|
|
338
|
+
[forgeValue, packageValue] = await Promise.all([
|
|
339
|
+
readJson(resolve(root, "forge.json")),
|
|
340
|
+
readJson(resolve(root, "package.json"))
|
|
341
|
+
]);
|
|
342
|
+
} catch (cause) {
|
|
343
|
+
return projectError(
|
|
344
|
+
"project-manifest-unreadable",
|
|
345
|
+
"readable forge.json and package.json files",
|
|
346
|
+
"Run the command from a ForgeaX game root or pass its directory.",
|
|
347
|
+
{ root, reason: cause instanceof Error ? cause.message : String(cause) }
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
if (forgeValue === null || typeof forgeValue !== "object") {
|
|
351
|
+
return projectError(
|
|
352
|
+
"project-manifest-invalid",
|
|
353
|
+
"forge.json to contain an object",
|
|
354
|
+
"Repair forge.json before running DevKit.",
|
|
355
|
+
{ root }
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
if (packageValue === null || typeof packageValue !== "object") {
|
|
359
|
+
return projectError(
|
|
360
|
+
"package-manifest-invalid",
|
|
361
|
+
"package.json to contain an object",
|
|
362
|
+
"Repair package.json before running DevKit.",
|
|
363
|
+
{ root }
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
const parsedForge = GameProjectSchema.safeParse(forgeValue);
|
|
367
|
+
if (!parsedForge.success) {
|
|
368
|
+
return projectError(
|
|
369
|
+
"project-manifest-invalid",
|
|
370
|
+
"forge.json to satisfy @forgeax/engine-project GameProjectSchema",
|
|
371
|
+
"Repair the fields reported by the authoritative project schema.",
|
|
372
|
+
{ root, issues: parsedForge.error.issues }
|
|
373
|
+
);
|
|
374
|
+
}
|
|
375
|
+
const forge = parsedForge.data;
|
|
376
|
+
if (forge.id.length === 0 || forge.name.length === 0 || forge.entry === void 0 || forge.entry.length === 0) {
|
|
377
|
+
return projectError(
|
|
378
|
+
"project-manifest-invalid",
|
|
379
|
+
"forge.json to declare id, name, and entry",
|
|
380
|
+
"Restore the project entry; plugin Entries remain optional additions.",
|
|
381
|
+
{ root }
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
const plugins = forge.plugins ?? [];
|
|
385
|
+
const unsupportedRealm = firstUnsupportedStandaloneRealm(plugins);
|
|
386
|
+
if (unsupportedRealm !== void 0) {
|
|
387
|
+
return projectError(
|
|
388
|
+
"project-plugin-realm-unsupported",
|
|
389
|
+
"the standalone Devkit host to contain only engine-realm plugin Entries",
|
|
390
|
+
"Move Host or build plugins to a host that owns that physical realm.",
|
|
391
|
+
{ root, ...unsupportedRealm }
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
const entryPath = isAbsolute(forge.entry) ? forge.entry : resolve(root, forge.entry);
|
|
395
|
+
try {
|
|
396
|
+
await readFile(entryPath);
|
|
397
|
+
} catch {
|
|
398
|
+
return projectError(
|
|
399
|
+
"project-entry-missing",
|
|
400
|
+
"forge.json#entry to resolve to a readable module",
|
|
401
|
+
"Restore the game entry or update forge.json#entry.",
|
|
402
|
+
{ root, entry: forge.entry }
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
const packageJson = packageValue;
|
|
406
|
+
const forgeax = packageJson.forgeax;
|
|
407
|
+
const configuredRoots = forgeax !== null && typeof forgeax === "object" ? forgeax.assets?.roots : void 0;
|
|
408
|
+
const assetRoots = Array.isArray(configuredRoots) && configuredRoots.every((value) => typeof value === "string") ? configuredRoots : ["assets"];
|
|
409
|
+
const configuredImporters = forgeax !== null && typeof forgeax === "object" ? forgeax.assets?.importers : void 0;
|
|
410
|
+
const assetImporters = Array.isArray(configuredImporters) && configuredImporters.every((value) => typeof value === "string") ? configuredImporters : [];
|
|
411
|
+
const configuredPublicDir = forgeax !== null && typeof forgeax === "object" ? forgeax.assets?.publicDir : void 0;
|
|
412
|
+
const assetPublicDir = typeof configuredPublicDir === "string" ? configuredPublicDir : void 0;
|
|
413
|
+
const physics = forge.physics === "2d" || forge.physics === "3d" ? forge.physics : void 0;
|
|
414
|
+
const defaultScene = typeof forge.defaultScene === "string" && forge.defaultScene.length > 0 ? forge.defaultScene : void 0;
|
|
415
|
+
const normalizedEntry = forge.entry.startsWith("./") ? forge.entry : `./${forge.entry}`;
|
|
416
|
+
const bootstrapEntry = pluginModuleNames(plugins).map((name) => name.startsWith("./") ? name : `./${name}`).includes(normalizedEntry) ? void 0 : forge.entry;
|
|
417
|
+
return {
|
|
418
|
+
ok: true,
|
|
419
|
+
value: {
|
|
420
|
+
root,
|
|
421
|
+
id: forge.id,
|
|
422
|
+
name: forge.name,
|
|
423
|
+
entry: forge.entry,
|
|
424
|
+
...bootstrapEntry === void 0 ? {} : { bootstrapEntry },
|
|
425
|
+
plugins,
|
|
426
|
+
...physics === void 0 ? {} : { physics },
|
|
427
|
+
...defaultScene === void 0 ? {} : { defaultScene },
|
|
428
|
+
assetRoots,
|
|
429
|
+
...assetImporters.length === 0 ? {} : { assetImporters },
|
|
430
|
+
...assetPublicDir === void 0 ? {} : { assetPublicDir },
|
|
431
|
+
packageJson
|
|
432
|
+
}
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
function commandError(cause, fallbackCode) {
|
|
436
|
+
if (cause !== null && typeof cause === "object" && "code" in cause && "expected" in cause && "hint" in cause && "detail" in cause) {
|
|
437
|
+
return cause;
|
|
438
|
+
}
|
|
439
|
+
return {
|
|
440
|
+
code: fallbackCode,
|
|
441
|
+
expected: "the ForgeaX command to complete",
|
|
442
|
+
hint: "Inspect the underlying diagnostic and repair the owning input.",
|
|
443
|
+
detail: { reason: cause instanceof Error ? cause.message : String(cause) }
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
var init_project = __esm({
|
|
447
|
+
"src/project.ts"() {
|
|
448
|
+
}
|
|
449
|
+
});
|
|
450
|
+
function bindingError(code, expected, hint, detail = {}) {
|
|
451
|
+
return { ok: false, error: { code, expected, hint, detail } };
|
|
452
|
+
}
|
|
453
|
+
function isMissing(cause) {
|
|
454
|
+
return cause !== null && typeof cause === "object" && "code" in cause && cause.code === "ENOENT";
|
|
455
|
+
}
|
|
456
|
+
async function pathExists(path) {
|
|
457
|
+
try {
|
|
458
|
+
await access(path);
|
|
459
|
+
return true;
|
|
313
460
|
} catch {
|
|
314
461
|
return false;
|
|
315
462
|
}
|
|
316
463
|
}
|
|
464
|
+
function parseBinding(value, path) {
|
|
465
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
466
|
+
return bindingError(
|
|
467
|
+
"engine-binding-invalid",
|
|
468
|
+
"engine-binding.json to contain one object",
|
|
469
|
+
"Run forgeax engine unlink, then select a binding again.",
|
|
470
|
+
{ path }
|
|
471
|
+
);
|
|
472
|
+
}
|
|
473
|
+
const candidate = value;
|
|
474
|
+
if (candidate.schemaVersion !== ENGINE_BINDING_SCHEMA_VERSION) {
|
|
475
|
+
return bindingError(
|
|
476
|
+
"engine-binding-version-unsupported",
|
|
477
|
+
`engine-binding.json schemaVersion ${ENGINE_BINDING_SCHEMA_VERSION}`,
|
|
478
|
+
"Upgrade the SDK or remove the stale binding with forgeax engine unlink.",
|
|
479
|
+
{ path, schemaVersion: candidate.schemaVersion ?? null }
|
|
480
|
+
);
|
|
481
|
+
}
|
|
482
|
+
if (typeof candidate.path !== "string" || candidate.path.length === 0) {
|
|
483
|
+
return bindingError(
|
|
484
|
+
"engine-binding-path-missing",
|
|
485
|
+
"engine-binding.json to contain one non-empty local Engine path",
|
|
486
|
+
"Run forgeax engine use-local <engine-directory>.",
|
|
487
|
+
{ path }
|
|
488
|
+
);
|
|
489
|
+
}
|
|
490
|
+
return {
|
|
491
|
+
ok: true,
|
|
492
|
+
value: {
|
|
493
|
+
schemaVersion: ENGINE_BINDING_SCHEMA_VERSION,
|
|
494
|
+
path: resolve(candidate.path)
|
|
495
|
+
}
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
async function readEngineBinding(rootInput = process.cwd()) {
|
|
499
|
+
const root = resolve(rootInput);
|
|
500
|
+
const path = bindingFile(root);
|
|
501
|
+
try {
|
|
502
|
+
const value = JSON.parse(await readFile(path, "utf8"));
|
|
503
|
+
return parseBinding(value, path);
|
|
504
|
+
} catch (cause) {
|
|
505
|
+
if (isMissing(cause)) return { ok: true, value: null };
|
|
506
|
+
return bindingError(
|
|
507
|
+
"engine-binding-unreadable",
|
|
508
|
+
"engine-binding.json to be readable JSON",
|
|
509
|
+
"Repair or remove .forgeax/engine-binding.json, then select a binding again.",
|
|
510
|
+
{ path, reason: cause instanceof Error ? cause.message : String(cause) }
|
|
511
|
+
);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
async function writeEngineBinding(root, binding) {
|
|
515
|
+
const path = bindingFile(root);
|
|
516
|
+
const partial = `${path}.partial-${process.pid}`;
|
|
517
|
+
await mkdir(dirname(path), { recursive: true });
|
|
518
|
+
await writeFile(partial, `${JSON.stringify(binding, null, 2)}
|
|
519
|
+
`, "utf8");
|
|
520
|
+
await rename(partial, path);
|
|
521
|
+
}
|
|
522
|
+
function conditionalExport(value) {
|
|
523
|
+
if (typeof value === "string") return value;
|
|
524
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
525
|
+
const record = value;
|
|
526
|
+
for (const key of ["import", "browser", "node", "default", "require", "types"]) {
|
|
527
|
+
const candidate = record[key];
|
|
528
|
+
const selected = conditionalExport(candidate);
|
|
529
|
+
if (selected !== void 0) return selected;
|
|
530
|
+
}
|
|
531
|
+
return void 0;
|
|
532
|
+
}
|
|
533
|
+
function packageEntry(manifest) {
|
|
534
|
+
const exportsValue = manifest.exports;
|
|
535
|
+
if (exportsValue !== void 0) {
|
|
536
|
+
if (typeof exportsValue === "object" && exportsValue !== null && !Array.isArray(exportsValue)) {
|
|
537
|
+
const root = exportsValue["."];
|
|
538
|
+
const selected2 = conditionalExport(root ?? exportsValue);
|
|
539
|
+
if (selected2 !== void 0) return selected2;
|
|
540
|
+
}
|
|
541
|
+
const selected = conditionalExport(exportsValue);
|
|
542
|
+
if (selected !== void 0) return selected;
|
|
543
|
+
}
|
|
544
|
+
return typeof manifest.module === "string" ? manifest.module : typeof manifest.main === "string" ? manifest.main : void 0;
|
|
545
|
+
}
|
|
546
|
+
async function readManifest(path) {
|
|
547
|
+
const value = JSON.parse(await readFile(path, "utf8"));
|
|
548
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
549
|
+
throw new Error(`engine-package-manifest-invalid: ${path}`);
|
|
550
|
+
}
|
|
551
|
+
return value;
|
|
552
|
+
}
|
|
553
|
+
async function inspectEngineWorkspace(workspaceInput) {
|
|
554
|
+
const root = resolve(workspaceInput);
|
|
555
|
+
const packageRoot = resolve(root, "packages");
|
|
556
|
+
try {
|
|
557
|
+
await access(resolve(root, "pnpm-workspace.yaml"));
|
|
558
|
+
const entries2 = await readdir(packageRoot, { withFileTypes: true });
|
|
559
|
+
const packages = [];
|
|
560
|
+
for (const entry of entries2) {
|
|
561
|
+
if (!entry.isDirectory()) continue;
|
|
562
|
+
const packagePath = resolve(packageRoot, entry.name);
|
|
563
|
+
let manifest;
|
|
564
|
+
try {
|
|
565
|
+
manifest = await readManifest(resolve(packagePath, "package.json"));
|
|
566
|
+
} catch {
|
|
567
|
+
continue;
|
|
568
|
+
}
|
|
569
|
+
if (typeof manifest.name !== "string" || !manifest.name.startsWith("@forgeax/engine"))
|
|
570
|
+
continue;
|
|
571
|
+
if (typeof manifest.version !== "string") {
|
|
572
|
+
return bindingError(
|
|
573
|
+
"engine-package-version-missing",
|
|
574
|
+
"every local Engine package to declare a version",
|
|
575
|
+
"Restore the package manifest version before selecting a local Engine.",
|
|
576
|
+
{ package: manifest.name, root: packagePath }
|
|
577
|
+
);
|
|
578
|
+
}
|
|
579
|
+
const target = packageEntry(manifest);
|
|
580
|
+
const entryPath = target === void 0 ? void 0 : resolve(packagePath, target);
|
|
581
|
+
let entryDigest = null;
|
|
582
|
+
let builtAt2 = null;
|
|
583
|
+
if (entryPath !== void 0 && await pathExists(entryPath)) {
|
|
584
|
+
const [bytes, metadata] = await Promise.all([readFile(entryPath), stat(entryPath)]);
|
|
585
|
+
entryDigest = `sha256:${createHash("sha256").update(bytes).digest("hex")}`;
|
|
586
|
+
builtAt2 = metadata.mtime.toISOString();
|
|
587
|
+
}
|
|
588
|
+
packages.push({
|
|
589
|
+
name: manifest.name,
|
|
590
|
+
root: packagePath,
|
|
591
|
+
version: manifest.version,
|
|
592
|
+
entry: entryPath ?? null,
|
|
593
|
+
built: entryDigest !== null,
|
|
594
|
+
entryDigest,
|
|
595
|
+
builtAt: builtAt2
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
packages.sort((a, b) => a.name.localeCompare(b.name));
|
|
599
|
+
if (packages.length === 0) {
|
|
600
|
+
return bindingError(
|
|
601
|
+
"engine-workspace-empty",
|
|
602
|
+
"the local Engine workspace to contain @forgeax/engine packages",
|
|
603
|
+
"Pass the Engine repository or its source/engine SDK directory.",
|
|
604
|
+
{ root, packageRoot }
|
|
605
|
+
);
|
|
606
|
+
}
|
|
607
|
+
const versions = [...new Set(packages.map((item) => item.version))].sort();
|
|
608
|
+
const missingBuilds = packages.filter((item) => !item.built).map((item) => item.name);
|
|
609
|
+
const builtAt = packages.flatMap((item) => item.builtAt === null ? [] : [item.builtAt]).sort().at(-1) ?? null;
|
|
610
|
+
const digest = `sha256:${createHash("sha256").update(
|
|
611
|
+
packages.map((item) => `${item.name}\0${item.version}\0${item.entryDigest ?? "unbuilt"}`).join("\n")
|
|
612
|
+
).digest("hex")}`;
|
|
613
|
+
return {
|
|
614
|
+
ok: true,
|
|
615
|
+
value: {
|
|
616
|
+
root,
|
|
617
|
+
packageCount: packages.length,
|
|
618
|
+
builtPackages: packages.length - missingBuilds.length,
|
|
619
|
+
missingBuilds,
|
|
620
|
+
versions,
|
|
621
|
+
packages,
|
|
622
|
+
digest,
|
|
623
|
+
builtAt
|
|
624
|
+
}
|
|
625
|
+
};
|
|
626
|
+
} catch (cause) {
|
|
627
|
+
return bindingError(
|
|
628
|
+
isMissing(cause) ? "engine-workspace-missing" : "engine-workspace-unreadable",
|
|
629
|
+
"a readable Engine workspace with pnpm-workspace.yaml and packages/",
|
|
630
|
+
"Pass the Engine repository root, not its packages/ directory.",
|
|
631
|
+
{ root, packageRoot, reason: cause instanceof Error ? cause.message : String(cause) }
|
|
632
|
+
);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
function projectDependencyMode(packageJson) {
|
|
636
|
+
let workspace = false;
|
|
637
|
+
let registry = false;
|
|
638
|
+
for (const section of [
|
|
639
|
+
"dependencies",
|
|
640
|
+
"devDependencies",
|
|
641
|
+
"optionalDependencies",
|
|
642
|
+
"peerDependencies"
|
|
643
|
+
]) {
|
|
644
|
+
const value = packageJson[section];
|
|
645
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) continue;
|
|
646
|
+
for (const version of Object.values(value)) {
|
|
647
|
+
if (typeof version !== "string") continue;
|
|
648
|
+
if (version.startsWith("workspace:") || version.startsWith("file:")) workspace = true;
|
|
649
|
+
else registry = true;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
return workspace ? registry ? "mixed" : "workspace" : registry ? "registry" : "none";
|
|
653
|
+
}
|
|
654
|
+
async function sdkResolution(root) {
|
|
655
|
+
const packageJson = resolve(root, "node_modules", "@forgeax", "engine", "package.json");
|
|
656
|
+
try {
|
|
657
|
+
const manifest = await readManifest(packageJson);
|
|
658
|
+
const target = packageEntry(manifest);
|
|
659
|
+
const entry = target === void 0 ? null : resolve(dirname(packageJson), target);
|
|
660
|
+
return {
|
|
661
|
+
root: dirname(packageJson),
|
|
662
|
+
version: typeof manifest.version === "string" ? manifest.version : null,
|
|
663
|
+
entry,
|
|
664
|
+
built: entry !== null && await pathExists(entry),
|
|
665
|
+
source: "sdk"
|
|
666
|
+
};
|
|
667
|
+
} catch {
|
|
668
|
+
return { root: null, version: null, entry: null, built: false, source: "unresolved" };
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
async function engineStatusCommand(options = {}) {
|
|
672
|
+
const facts = await readProjectFacts(options.root);
|
|
673
|
+
if (!facts.ok) return facts;
|
|
674
|
+
const bindingResult = await readEngineBinding(facts.value.root);
|
|
675
|
+
if (!bindingResult.ok) return bindingResult;
|
|
676
|
+
const binding = bindingResult.value;
|
|
677
|
+
const mode = binding === null ? "sdk" : "local";
|
|
678
|
+
const projectDependencies = projectDependencyMode(facts.value.packageJson);
|
|
679
|
+
if (binding !== null) {
|
|
680
|
+
const localPath = binding.path;
|
|
681
|
+
const localBinding = {
|
|
682
|
+
schemaVersion: ENGINE_BINDING_SCHEMA_VERSION,
|
|
683
|
+
path: localPath
|
|
684
|
+
};
|
|
685
|
+
const workspaceResult = await inspectEngineWorkspace(localPath);
|
|
686
|
+
if (!workspaceResult.ok) {
|
|
687
|
+
const resolved3 = {
|
|
688
|
+
root: localPath,
|
|
689
|
+
version: null,
|
|
690
|
+
entry: null,
|
|
691
|
+
built: false,
|
|
692
|
+
source: "local"
|
|
693
|
+
};
|
|
694
|
+
return {
|
|
695
|
+
ok: true,
|
|
696
|
+
value: {
|
|
697
|
+
schemaVersion: ENGINE_BINDING_SCHEMA_VERSION,
|
|
698
|
+
root: facts.value.root,
|
|
699
|
+
binding: localBinding,
|
|
700
|
+
mode,
|
|
701
|
+
resolved: resolved3,
|
|
702
|
+
workspace: null,
|
|
703
|
+
projectDependencies,
|
|
704
|
+
npmCompatible: projectDependencies === "registry" || projectDependencies === "none",
|
|
705
|
+
healthy: false,
|
|
706
|
+
next: ["forgeax engine unlink", "forgeax engine use-local <engine-directory>"],
|
|
707
|
+
diagnostic: workspaceResult.error
|
|
708
|
+
}
|
|
709
|
+
};
|
|
710
|
+
}
|
|
711
|
+
const workspace = workspaceResult.value;
|
|
712
|
+
const version = workspace.versions.length === 1 ? workspace.versions[0] ?? null : null;
|
|
713
|
+
const umbrella = workspace.packages.find((item) => item.name === "@forgeax/engine");
|
|
714
|
+
const resolved2 = {
|
|
715
|
+
root: localPath,
|
|
716
|
+
version,
|
|
717
|
+
entry: umbrella?.entry ?? null,
|
|
718
|
+
built: umbrella?.built ?? false,
|
|
719
|
+
source: "local"
|
|
720
|
+
};
|
|
721
|
+
const healthy = workspace.versions.length === 1 && workspace.missingBuilds.length === 0;
|
|
722
|
+
return {
|
|
723
|
+
ok: true,
|
|
724
|
+
value: {
|
|
725
|
+
schemaVersion: ENGINE_BINDING_SCHEMA_VERSION,
|
|
726
|
+
root: facts.value.root,
|
|
727
|
+
binding: localBinding,
|
|
728
|
+
mode,
|
|
729
|
+
resolved: resolved2,
|
|
730
|
+
workspace,
|
|
731
|
+
projectDependencies,
|
|
732
|
+
npmCompatible: projectDependencies === "registry" || projectDependencies === "none",
|
|
733
|
+
healthy,
|
|
734
|
+
next: healthy ? ["forgeax build", "forgeax capture --backend auto --require-ui"] : ["pnpm build:engine", "forgeax engine doctor"]
|
|
735
|
+
}
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
const resolved = await sdkResolution(facts.value.root);
|
|
739
|
+
return {
|
|
740
|
+
ok: true,
|
|
741
|
+
value: {
|
|
742
|
+
schemaVersion: ENGINE_BINDING_SCHEMA_VERSION,
|
|
743
|
+
root: facts.value.root,
|
|
744
|
+
binding,
|
|
745
|
+
mode,
|
|
746
|
+
resolved,
|
|
747
|
+
workspace: null,
|
|
748
|
+
projectDependencies,
|
|
749
|
+
npmCompatible: projectDependencies === "registry" || projectDependencies === "none",
|
|
750
|
+
healthy: resolved.source === "sdk" && resolved.built,
|
|
751
|
+
next: resolved.source === "sdk" && resolved.built ? ["forgeax build", "forgeax capture --backend auto --require-ui"] : ["pnpm install", "forgeax doctor"]
|
|
752
|
+
}
|
|
753
|
+
};
|
|
754
|
+
}
|
|
755
|
+
async function engineUseLocalCommand(options) {
|
|
756
|
+
const root = resolve(options.root ?? process.cwd());
|
|
757
|
+
if (options.path === void 0 || options.path.trim().length === 0) {
|
|
758
|
+
return bindingError(
|
|
759
|
+
"engine-binding-path-missing",
|
|
760
|
+
"forgeax engine use-local <engine-directory>",
|
|
761
|
+
"Pass the local Engine repository or SDK source directory."
|
|
762
|
+
);
|
|
763
|
+
}
|
|
764
|
+
let localPath;
|
|
765
|
+
try {
|
|
766
|
+
localPath = await realpath(resolve(root, options.path));
|
|
767
|
+
} catch (cause) {
|
|
768
|
+
return bindingError(
|
|
769
|
+
"engine-workspace-missing",
|
|
770
|
+
"the local Engine directory to exist",
|
|
771
|
+
"Pass an existing Engine repository or SDK source directory.",
|
|
772
|
+
{
|
|
773
|
+
path: resolve(root, options.path),
|
|
774
|
+
reason: cause instanceof Error ? cause.message : String(cause)
|
|
775
|
+
}
|
|
776
|
+
);
|
|
777
|
+
}
|
|
778
|
+
const workspace = await inspectEngineWorkspace(localPath);
|
|
779
|
+
if (!workspace.ok) return workspace;
|
|
780
|
+
const binding = {
|
|
781
|
+
schemaVersion: ENGINE_BINDING_SCHEMA_VERSION,
|
|
782
|
+
path: localPath
|
|
783
|
+
};
|
|
784
|
+
if (options.dryRun !== true) await writeEngineBinding(root, binding);
|
|
785
|
+
const status = await engineStatusCommand({ root });
|
|
786
|
+
if (!status.ok) return status;
|
|
787
|
+
return {
|
|
788
|
+
ok: true,
|
|
789
|
+
value: {
|
|
790
|
+
...status.value,
|
|
791
|
+
next: workspace.value.missingBuilds.length === 0 ? ["forgeax build", "forgeax capture --backend auto --require-ui"] : ["pnpm build:engine", "forgeax engine doctor"]
|
|
792
|
+
}
|
|
793
|
+
};
|
|
794
|
+
}
|
|
795
|
+
async function engineUnlinkCommand(options = {}) {
|
|
796
|
+
const root = resolve(options.root ?? process.cwd());
|
|
797
|
+
if (options.dryRun !== true) await rm(bindingFile(root), { force: true });
|
|
798
|
+
return engineStatusCommand({ root });
|
|
799
|
+
}
|
|
800
|
+
async function engineDoctorCommand(options = {}) {
|
|
801
|
+
const status = await engineStatusCommand(options);
|
|
802
|
+
if (!status.ok) return status;
|
|
803
|
+
if (status.value.projectDependencies === "workspace" || status.value.projectDependencies === "mixed") {
|
|
804
|
+
return {
|
|
805
|
+
ok: false,
|
|
806
|
+
error: {
|
|
807
|
+
code: "engine-project-workspace-dependency",
|
|
808
|
+
expected: "the game project to use registry or SDK-resolved dependencies for npm consumers",
|
|
809
|
+
hint: "This project is a pnpm workspace; use pnpm here, or create a clean SDK game project for npm install.",
|
|
810
|
+
detail: { root: status.value.root, projectDependencies: status.value.projectDependencies }
|
|
811
|
+
}
|
|
812
|
+
};
|
|
813
|
+
}
|
|
814
|
+
if (status.value.diagnostic !== void 0) return { ok: false, error: status.value.diagnostic };
|
|
815
|
+
if (!status.value.healthy) {
|
|
816
|
+
return {
|
|
817
|
+
ok: false,
|
|
818
|
+
error: {
|
|
819
|
+
code: status.value.mode === "local" ? "engine-local-build-missing" : "engine-sdk-unresolved",
|
|
820
|
+
expected: "the selected Engine binding to resolve to built packages",
|
|
821
|
+
hint: status.value.mode === "local" ? "Run pnpm build:engine in the selected Engine checkout, then rerun forgeax engine doctor." : "Run pnpm install in the game project or select a valid local Engine checkout.",
|
|
822
|
+
detail: { status: status.value }
|
|
823
|
+
}
|
|
824
|
+
};
|
|
825
|
+
}
|
|
826
|
+
return status;
|
|
827
|
+
}
|
|
828
|
+
function engineBindingFilePath(rootInput = process.cwd()) {
|
|
829
|
+
return bindingFile(resolve(rootInput));
|
|
830
|
+
}
|
|
831
|
+
var ENGINE_BINDING_SCHEMA_VERSION, bindingFile;
|
|
832
|
+
var init_engine_binding = __esm({
|
|
833
|
+
"src/engine-binding.ts"() {
|
|
834
|
+
init_project();
|
|
835
|
+
ENGINE_BINDING_SCHEMA_VERSION = "1.0.0";
|
|
836
|
+
bindingFile = (root) => resolve(root, ".forgeax", "engine-binding.json");
|
|
837
|
+
}
|
|
838
|
+
});
|
|
839
|
+
|
|
840
|
+
// src/host.ts
|
|
841
|
+
var host_exports = {};
|
|
842
|
+
__export(host_exports, {
|
|
843
|
+
createViteConfig: () => createViteConfig,
|
|
844
|
+
devKitDdcRoots: () => devKitDdcRoots,
|
|
845
|
+
ignoreDevKitCatalogPath: () => ignoreDevKitCatalogPath
|
|
846
|
+
});
|
|
847
|
+
function ignoreDevKitCatalogPath(path) {
|
|
848
|
+
const normalized = path.replace(/\\/g, "/");
|
|
849
|
+
return normalized.split("/").includes("shaders");
|
|
850
|
+
}
|
|
317
851
|
function devKitDdcRoots(projectRoot) {
|
|
318
852
|
return {
|
|
319
853
|
buildCacheRoot: resolve(projectRoot, ".forgeax", "ddc", "build-cache"),
|
|
@@ -371,40 +905,33 @@ function findEngineWorkspaceRoot() {
|
|
|
371
905
|
cursor = parent;
|
|
372
906
|
}
|
|
373
907
|
}
|
|
374
|
-
async function engineWorkspacePackages() {
|
|
375
|
-
if (
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
if (!
|
|
381
|
-
const
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
);
|
|
389
|
-
if (manifest === null || typeof manifest !== "object") continue;
|
|
390
|
-
const name = manifest.name;
|
|
391
|
-
if (typeof name === "string" && name.startsWith("@forgeax/engine-")) {
|
|
392
|
-
packages.set(name, { root, manifest });
|
|
393
|
-
}
|
|
394
|
-
} catch {
|
|
908
|
+
async function engineWorkspacePackages(workspaceRoot = findEngineWorkspaceRoot()) {
|
|
909
|
+
if (workspaceRoot === void 0) return /* @__PURE__ */ new Map();
|
|
910
|
+
const packageRoot = resolve(workspaceRoot, "packages");
|
|
911
|
+
if (!existsSync(packageRoot)) return /* @__PURE__ */ new Map();
|
|
912
|
+
const packages = /* @__PURE__ */ new Map();
|
|
913
|
+
for (const entry of await readdir(packageRoot, { withFileTypes: true })) {
|
|
914
|
+
if (!entry.isDirectory()) continue;
|
|
915
|
+
const root = resolve(packageRoot, entry.name);
|
|
916
|
+
try {
|
|
917
|
+
const manifest = JSON.parse(await readFile(resolve(root, "package.json"), "utf8"));
|
|
918
|
+
if (manifest === null || typeof manifest !== "object") continue;
|
|
919
|
+
const name = manifest.name;
|
|
920
|
+
if (typeof name === "string" && name.startsWith("@forgeax/engine")) {
|
|
921
|
+
packages.set(name, { root, manifest });
|
|
395
922
|
}
|
|
923
|
+
} catch {
|
|
396
924
|
}
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
return engineWorkspacePackagesPromise;
|
|
925
|
+
}
|
|
926
|
+
return packages;
|
|
400
927
|
}
|
|
401
|
-
function
|
|
928
|
+
function conditionalExport2(value) {
|
|
402
929
|
if (typeof value === "string") return value;
|
|
403
930
|
if (value === null || Array.isArray(value)) return void 0;
|
|
404
931
|
for (const condition of ["browser", "import", "node", "default"]) {
|
|
405
932
|
const candidate = value[condition];
|
|
406
933
|
if (candidate === void 0) continue;
|
|
407
|
-
const selected =
|
|
934
|
+
const selected = conditionalExport2(candidate);
|
|
408
935
|
if (selected !== void 0) return selected;
|
|
409
936
|
}
|
|
410
937
|
return void 0;
|
|
@@ -417,14 +944,14 @@ function packageExportTarget(manifest, subpath) {
|
|
|
417
944
|
return typeof main === "string" ? main : void 0;
|
|
418
945
|
}
|
|
419
946
|
if (typeof exportsValue === "string" || exportsValue === null) {
|
|
420
|
-
return subpath.length === 0 ?
|
|
947
|
+
return subpath.length === 0 ? conditionalExport2(exportsValue) : void 0;
|
|
421
948
|
}
|
|
422
949
|
const keys = Object.keys(exportsValue);
|
|
423
950
|
const subpathMap = keys.some((key) => key === "." || key.startsWith("./"));
|
|
424
|
-
if (!subpathMap) return subpath.length === 0 ?
|
|
951
|
+
if (!subpathMap) return subpath.length === 0 ? conditionalExport2(exportsValue) : void 0;
|
|
425
952
|
const requested = subpath.length === 0 ? "." : `./${subpath}`;
|
|
426
953
|
const exact = exportsValue[requested];
|
|
427
|
-
if (exact !== void 0) return
|
|
954
|
+
if (exact !== void 0) return conditionalExport2(exact);
|
|
428
955
|
for (const key of keys) {
|
|
429
956
|
const marker = key.indexOf("*");
|
|
430
957
|
if (marker < 0) continue;
|
|
@@ -434,14 +961,14 @@ function packageExportTarget(manifest, subpath) {
|
|
|
434
961
|
const replacement = requested.slice(prefix.length, requested.length - suffix.length);
|
|
435
962
|
const exportTarget = exportsValue[key];
|
|
436
963
|
if (exportTarget === void 0) continue;
|
|
437
|
-
const selected =
|
|
964
|
+
const selected = conditionalExport2(exportTarget);
|
|
438
965
|
return selected?.replaceAll("*", replacement);
|
|
439
966
|
}
|
|
440
967
|
return void 0;
|
|
441
968
|
}
|
|
442
969
|
function engineWorkspaceImport(source, packages) {
|
|
443
|
-
if (!source.startsWith("@forgeax/engine
|
|
444
|
-
const separator = source.indexOf("/", "@forgeax/engine
|
|
970
|
+
if (!source.startsWith("@forgeax/engine")) return void 0;
|
|
971
|
+
const separator = source.indexOf("/", "@forgeax/engine".length);
|
|
445
972
|
const packageName = separator < 0 ? source : source.slice(0, separator);
|
|
446
973
|
const subpath = separator < 0 ? "" : source.slice(separator + 1);
|
|
447
974
|
const packageInfo = packages.get(packageName);
|
|
@@ -455,14 +982,25 @@ function engineWorkspaceImport(source, packages) {
|
|
|
455
982
|
}
|
|
456
983
|
return absolute;
|
|
457
984
|
}
|
|
458
|
-
async function createEngineWorkspaceResolver() {
|
|
459
|
-
const
|
|
985
|
+
async function createEngineWorkspaceResolver(projectRoot) {
|
|
986
|
+
const binding = await readEngineBinding(projectRoot);
|
|
987
|
+
if (!binding.ok) {
|
|
988
|
+
throw new Error(`${binding.error.code}: ${binding.error.hint}`);
|
|
989
|
+
}
|
|
990
|
+
const localRoot = binding.value?.path;
|
|
991
|
+
if (localRoot !== void 0) {
|
|
992
|
+
const inspected = await inspectEngineWorkspace(localRoot);
|
|
993
|
+
if (!inspected.ok) throw new Error(`${inspected.error.code}: ${inspected.error.hint}`);
|
|
994
|
+
}
|
|
995
|
+
const packages = await engineWorkspacePackages(localRoot);
|
|
460
996
|
if (packages.size === 0) return void 0;
|
|
461
997
|
return {
|
|
462
998
|
name: "forgeax:devkit-engine-workspace-resolver",
|
|
999
|
+
enforce: "pre",
|
|
463
1000
|
async resolveId(source, importer) {
|
|
464
1001
|
const bareSource = source.split("?", 1)[0] ?? source;
|
|
465
|
-
if (!bareSource.startsWith("@forgeax/engine
|
|
1002
|
+
if (!bareSource.startsWith("@forgeax/engine")) return null;
|
|
1003
|
+
if (localRoot !== void 0) return engineWorkspaceImport(bareSource, packages);
|
|
466
1004
|
try {
|
|
467
1005
|
const resolved = await this.resolve(source, importer, { skipSelf: true });
|
|
468
1006
|
if (resolved !== null) return resolved;
|
|
@@ -578,17 +1116,20 @@ await app.pluginContext.plugin({
|
|
|
578
1116
|
return `import { forgeaxBundlerAdapter } from 'virtual:forgeax/bundler';
|
|
579
1117
|
import { createApp, createToolPreviewHost, createToolPreviewRecipe, fitToolPreviewCameraToAabb, gameHostPlugin, replayToolPreviewCapture } from '@forgeax/engine/app';
|
|
580
1118
|
import { createCatalogSource } from '@forgeax/engine/assets-runtime';
|
|
1119
|
+
import {
|
|
1120
|
+
createRuntimeAssetImportTransport,
|
|
1121
|
+
runtimeBinding,
|
|
1122
|
+
} from 'virtual:forgeax/pack-runtime';
|
|
581
1123
|
import { installCatalogLoader, projectPluginEntries } from '@forgeax/engine/plugin/loader';
|
|
582
1124
|
import { audioPlugin } from '@forgeax/engine/audio';
|
|
583
1125
|
import { webAudioPlugin } from '@forgeax/engine/audio-webaudio';
|
|
584
1126
|
import { physicsPlugin } from '@forgeax/engine/physics';
|
|
585
1127
|
import { skinningPlugin } from '@forgeax/engine/skinning';
|
|
586
1128
|
import { createPrimitiveMesh } from '@forgeax/engine/geometry';
|
|
587
|
-
import { createDevImportTransport } from '@forgeax/engine/runtime';
|
|
588
1129
|
import { mat4 } from '@forgeax/engine/math';
|
|
589
1130
|
import { CAMERA_PROJECTION_ORTHOGRAPHIC, Camera, DirectionalLight, Materials, MeshFilter, MeshRenderer, Skylight, SkyboxBackground, TONEMAP_NONE, TONEMAP_REINHARD_EXTENDED } from '@forgeax/engine/render';
|
|
590
|
-
import {
|
|
591
|
-
import {
|
|
1131
|
+
import { Transform } from '@forgeax/engine/scene';
|
|
1132
|
+
import { type SceneAsset } from '@forgeax/engine/types';
|
|
592
1133
|
import { ParticleEffectPlayer, vfxGpuEffectContribution } from '@forgeax/engine/vfx';
|
|
593
1134
|
import { createVfxRuntimeHost } from '@forgeax/engine/vfx-render';
|
|
594
1135
|
|
|
@@ -689,17 +1230,43 @@ const resizeCanvas = () => {
|
|
|
689
1230
|
const resizeObserver = new ResizeObserver(resizeCanvas);
|
|
690
1231
|
resizeObserver.observe(canvas);
|
|
691
1232
|
resizeCanvas();
|
|
692
|
-
const runtimeScopeBinding =
|
|
1233
|
+
const runtimeScopeBinding = runtimeBinding;
|
|
1234
|
+
if (runtimeScopeBinding === undefined) {
|
|
1235
|
+
throw new Error('forgeax: Vite Pack runtime binding is required in the generated host');
|
|
1236
|
+
}
|
|
693
1237
|
const assetCatalog = createCatalogSource({
|
|
694
1238
|
url: import.meta.env.DEV
|
|
695
1239
|
? runtimeScopeBinding.catalogUrl
|
|
696
1240
|
: new URL('pack-index.json', document.baseURI).href,
|
|
697
|
-
expectedScope: runtimeScopeBinding,
|
|
1241
|
+
...(import.meta.env.DEV ? { expectedScope: runtimeScopeBinding } : {}),
|
|
698
1242
|
});
|
|
699
1243
|
const bundler = {
|
|
700
1244
|
...forgeaxBundlerAdapter(),
|
|
701
|
-
...(import.meta.env.DEV
|
|
1245
|
+
...(import.meta.env.DEV
|
|
1246
|
+
? { importTransport: createRuntimeAssetImportTransport(runtimeScopeBinding) }
|
|
1247
|
+
: {}),
|
|
702
1248
|
};
|
|
1249
|
+
const assetPreparation = new WeakMap();
|
|
1250
|
+
|
|
1251
|
+
function prepareAssetRegistry(assets) {
|
|
1252
|
+
const existing = assetPreparation.get(assets);
|
|
1253
|
+
if (existing !== undefined) return existing;
|
|
1254
|
+
const pending = (async () => {
|
|
1255
|
+
if (import.meta.env.DEV) {
|
|
1256
|
+
assets.configureRuntimeBinding(runtimeScopeBinding);
|
|
1257
|
+
} else {
|
|
1258
|
+
assets.configurePackIndex(new URL('pack-index.json', document.baseURI).href);
|
|
1259
|
+
}
|
|
1260
|
+
assets.setCatalogSource(assetCatalog);
|
|
1261
|
+
if (vfxRuntimeHost !== undefined) {
|
|
1262
|
+
assets.installDecoder(vfxGpuEffectContribution.kind, vfxGpuEffectContribution.decoder);
|
|
1263
|
+
}
|
|
1264
|
+
const catalog = await assets.enumerateCatalog();
|
|
1265
|
+
if (!catalog.ok) throw catalog.error;
|
|
1266
|
+
})();
|
|
1267
|
+
assetPreparation.set(assets, pending);
|
|
1268
|
+
return pending;
|
|
1269
|
+
}
|
|
703
1270
|
|
|
704
1271
|
function previewStable(value) {
|
|
705
1272
|
if (value instanceof ArrayBuffer) return 'ArrayBuffer:' + JSON.stringify(Array.from(new Uint8Array(value)));
|
|
@@ -760,13 +1327,14 @@ async function previewOwnerFacts(assets, resource, payload) {
|
|
|
760
1327
|
}
|
|
761
1328
|
|
|
762
1329
|
async function prepareProject(app) {
|
|
1330
|
+
await prepareAssetRegistry(app.assets);
|
|
763
1331
|
previewWorld = app.world;
|
|
764
1332
|
const assets = app.assets;
|
|
765
1333
|
let canonicalEnvironment;
|
|
766
1334
|
if (resource !== undefined) {
|
|
767
1335
|
if (resource.kind !== 'texture') {
|
|
768
1336
|
if (canonicalEnvironmentGuid === null) throw new Error('canonical preview environment is unavailable');
|
|
769
|
-
const environment = await assets.
|
|
1337
|
+
const environment = await assets.loadByGuid(assets.parseGuid(canonicalEnvironmentGuid));
|
|
770
1338
|
if (!environment.ok) throw environment.error;
|
|
771
1339
|
if (environment.value.kind !== 'equirect') {
|
|
772
1340
|
throw new Error(\`canonical preview environment expected equirect, received \${environment.value.kind}\`);
|
|
@@ -781,7 +1349,7 @@ if (resource !== undefined) {
|
|
|
781
1349
|
const attached = await vfxRuntimeHost.attachWorld({ world: app.world, assets });
|
|
782
1350
|
if (!attached.ok) throw attached.error;
|
|
783
1351
|
}
|
|
784
|
-
const loaded = await assets.
|
|
1352
|
+
const loaded = await assets.loadByGuid(assets.parseGuid(resource.guid));
|
|
785
1353
|
if (!loaded.ok) throw loaded.error;
|
|
786
1354
|
const payload = loaded.value;
|
|
787
1355
|
if (resource.kind === 'vfx' && payload.kind !== 'particle-effect') {
|
|
@@ -794,7 +1362,7 @@ if (resource !== undefined) {
|
|
|
794
1362
|
const meshMaterialHandles = resource.kind === 'mesh' && payload.kind === 'mesh'
|
|
795
1363
|
? await Promise.all(payload.materialSlots.map(async (slot) => {
|
|
796
1364
|
if (slot.defaultMaterial === undefined) return 0;
|
|
797
|
-
const material = await assets.
|
|
1365
|
+
const material = await assets.loadByGuid(assets.parseGuid(slot.defaultMaterial));
|
|
798
1366
|
if (!material.ok) throw material.error;
|
|
799
1367
|
if (material.value.kind !== 'material') {
|
|
800
1368
|
throw new Error('mesh material slot resolved to a non-material asset');
|
|
@@ -924,15 +1492,13 @@ let defaultScene;
|
|
|
924
1492
|
let defaultSceneRoot;
|
|
925
1493
|
const defaultSceneGuid = ${JSON.stringify(facts.defaultScene)};
|
|
926
1494
|
if (defaultSceneGuid !== undefined) {
|
|
927
|
-
const loaded = await assets.
|
|
1495
|
+
const loaded = await assets.loadByGuid<SceneAsset>(assets.parseGuid(defaultSceneGuid));
|
|
928
1496
|
if (!loaded.ok) throw loaded.error;
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
const handle = app.world.allocSharedRef('SceneAsset', projected.value);
|
|
933
|
-
const instantiated = worldInstantiateScene(app.world, handle);
|
|
1497
|
+
defaultScene = loaded.value;
|
|
1498
|
+
const handle = app.world.allocSharedRef('SceneAsset', loaded.value);
|
|
1499
|
+
const instantiated = assets.instantiate<SceneAsset>(handle, app.world);
|
|
934
1500
|
if (!instantiated.ok) throw instantiated.error;
|
|
935
|
-
defaultSceneRoot = instantiated.value
|
|
1501
|
+
defaultSceneRoot = instantiated.value;
|
|
936
1502
|
}
|
|
937
1503
|
const uiRoot = document.querySelector('#game-ui');
|
|
938
1504
|
await app.pluginContext.plugin(gameHostPlugin({
|
|
@@ -971,8 +1537,6 @@ if (query.has('forgeax-tool-replay')) {
|
|
|
971
1537
|
...(resource === undefined ? {} : { resource }),
|
|
972
1538
|
canvas,
|
|
973
1539
|
app: {
|
|
974
|
-
...(assetCatalog === undefined ? {} : { assetCatalog }),
|
|
975
|
-
...(vfxRuntimeHost === undefined ? {} : { assetDecoders: [vfxGpuEffectContribution] }),
|
|
976
1540
|
plugins: [${plugins.join(", ")}],
|
|
977
1541
|
...(vfxRuntimeHost === undefined ? {} : { features: [vfxRuntimeHost.feature] }),
|
|
978
1542
|
},
|
|
@@ -1085,8 +1649,6 @@ if (query.has('forgeax-tool-replay')) {
|
|
|
1085
1649
|
const result = await createApp(
|
|
1086
1650
|
canvas,
|
|
1087
1651
|
{
|
|
1088
|
-
...(assetCatalog === undefined ? {} : { assetCatalog }),
|
|
1089
|
-
...(vfxRuntimeHost === undefined ? {} : { assetDecoders: [vfxGpuEffectContribution] }),
|
|
1090
1652
|
plugins: [${plugins.join(", ")}],
|
|
1091
1653
|
...(pointerLockAllowed === undefined ? {} : { pointerLockAllowed }),
|
|
1092
1654
|
},
|
|
@@ -1147,32 +1709,62 @@ function htmlSource(title) {
|
|
|
1147
1709
|
<div id="app-shell"><canvas id="app"></canvas><div id="game-ui"></div></div><div id="forgeax-fatal" role="alert"></div>
|
|
1148
1710
|
<script>
|
|
1149
1711
|
(() => {
|
|
1712
|
+
const appendStructuredFailure = (value, prefix, depth, seen, lines) => {
|
|
1713
|
+
if (value === null || typeof value !== 'object' || depth > 3) return;
|
|
1714
|
+
if (seen.has(value)) {
|
|
1715
|
+
lines.push((prefix || 'cause') + ': [circular]');
|
|
1716
|
+
return;
|
|
1717
|
+
}
|
|
1718
|
+
seen.add(value);
|
|
1719
|
+
const record = value;
|
|
1720
|
+
const name = typeof record.name === 'string' && record.name.length > 0
|
|
1721
|
+
? record.name
|
|
1722
|
+
: undefined;
|
|
1723
|
+
const code = typeof record.code === 'string' && record.code.length > 0
|
|
1724
|
+
? record.code
|
|
1725
|
+
: undefined;
|
|
1726
|
+
const message = typeof record.message === 'string' && record.message.length > 0
|
|
1727
|
+
? record.message
|
|
1728
|
+
: undefined;
|
|
1729
|
+
if (name !== undefined || code !== undefined || message !== undefined) {
|
|
1730
|
+
const identity = [name || 'Error', code].filter(Boolean).join(' ');
|
|
1731
|
+
lines.push((prefix ? prefix + ': ' : '') + identity + (message ? ': ' + message : ''));
|
|
1732
|
+
}
|
|
1733
|
+
for (const key of ['expected', 'hint', 'reason']) {
|
|
1734
|
+
if (typeof record[key] === 'string' && record[key].length > 0) {
|
|
1735
|
+
lines.push((prefix ? prefix + '.' : '') + key + ': ' + record[key]);
|
|
1736
|
+
}
|
|
1737
|
+
}
|
|
1738
|
+
for (const key of ['cause', 'detail', 'webgpuError', 'wgpuError', 'error']) {
|
|
1739
|
+
const nested = record[key];
|
|
1740
|
+
const nestedPrefix = (prefix ? prefix + '.' : '') + key;
|
|
1741
|
+
if (nested !== null && typeof nested === 'object') {
|
|
1742
|
+
appendStructuredFailure(nested, nestedPrefix, depth + 1, seen, lines);
|
|
1743
|
+
} else if (typeof nested === 'string' && nested.length > 0) {
|
|
1744
|
+
lines.push(nestedPrefix + ': ' + nested);
|
|
1745
|
+
}
|
|
1746
|
+
}
|
|
1747
|
+
};
|
|
1150
1748
|
const formatStartupFailure = (reason) => {
|
|
1151
|
-
if (reason instanceof Error) return reason.message;
|
|
1152
1749
|
if (reason !== null && typeof reason === 'object') {
|
|
1153
|
-
const record = reason;
|
|
1154
1750
|
const lines = [];
|
|
1155
|
-
|
|
1156
|
-
if (typeof record[key] === 'string' && record[key].length > 0) {
|
|
1157
|
-
lines.push(key + ': ' + record[key]);
|
|
1158
|
-
}
|
|
1159
|
-
}
|
|
1160
|
-
const detail = record.detail;
|
|
1161
|
-
if (detail !== null && typeof detail === 'object') {
|
|
1162
|
-
for (const key of ['reason', 'guid']) {
|
|
1163
|
-
if (typeof detail[key] === 'string' && detail[key].length > 0) {
|
|
1164
|
-
lines.push('detail.' + key + ': ' + detail[key]);
|
|
1165
|
-
}
|
|
1166
|
-
}
|
|
1167
|
-
}
|
|
1751
|
+
appendStructuredFailure(reason, '', 0, new Set(), lines);
|
|
1168
1752
|
if (lines.length > 0) return lines.join('\\n');
|
|
1753
|
+
try {
|
|
1754
|
+
return JSON.stringify(reason) || 'Unknown structured startup failure';
|
|
1755
|
+
} catch {
|
|
1756
|
+
return 'Unserializable structured startup failure';
|
|
1757
|
+
}
|
|
1169
1758
|
}
|
|
1170
1759
|
return String(reason ?? 'Unknown startup failure');
|
|
1171
1760
|
};
|
|
1172
1761
|
const show = (reason) => {
|
|
1173
1762
|
const notice = document.querySelector('#forgeax-fatal');
|
|
1174
1763
|
if (!(notice instanceof HTMLElement)) return;
|
|
1175
|
-
|
|
1764
|
+
let message = formatStartupFailure(reason);
|
|
1765
|
+
if (/webgpu|adapter-unavailable|no usable (rendering )?backend/i.test(message)) {
|
|
1766
|
+
message += '\\n\\nRenderer diagnosis: ForgeaX supports browser WebGPU and a wgpu/WebGL2 fallback. This failure alone does not prove that WebGPU is unsupported; use the structured code, hint, and nested backend causes above.';
|
|
1767
|
+
}
|
|
1176
1768
|
notice.textContent = 'ForgeaX game failed to start.\\n' + message;
|
|
1177
1769
|
notice.style.display = 'grid';
|
|
1178
1770
|
};
|
|
@@ -1204,7 +1796,7 @@ async function createViteConfig(facts, command, base = "/", options = {}) {
|
|
|
1204
1796
|
];
|
|
1205
1797
|
const importers = [...projectImporters(facts)];
|
|
1206
1798
|
const runtimeBinding = createStandaloneRuntimeAssetBinding(facts.id);
|
|
1207
|
-
const engineWorkspaceResolver = await createEngineWorkspaceResolver();
|
|
1799
|
+
const engineWorkspaceResolver = await createEngineWorkspaceResolver(facts.root);
|
|
1208
1800
|
const consumerAliases = await consumerEngineAliases(facts.root);
|
|
1209
1801
|
const plugins = [
|
|
1210
1802
|
...engineWorkspaceResolver === void 0 ? [] : [engineWorkspaceResolver],
|
|
@@ -1215,198 +1807,55 @@ async function createViteConfig(facts, command, base = "/", options = {}) {
|
|
|
1215
1807
|
ddc: devKitDdcRoots(facts.root),
|
|
1216
1808
|
refresh: command === "serve" ? reloadAssetHost() : void 0,
|
|
1217
1809
|
importers: [
|
|
1218
|
-
audioImporter,
|
|
1219
|
-
imageImporter,
|
|
1220
|
-
fbxImporter,
|
|
1221
|
-
gltfImporter,
|
|
1222
|
-
fontImporter,
|
|
1223
|
-
...importers
|
|
1224
|
-
],
|
|
1225
|
-
cookers: [createMaterialPackCooker(roots), createParticleCodeNativeCookerFromRoots(roots)],
|
|
1226
|
-
ignorePath
|
|
1227
|
-
})
|
|
1228
|
-
];
|
|
1229
|
-
return {
|
|
1230
|
-
root: generated,
|
|
1231
|
-
base,
|
|
1232
|
-
configFile: false,
|
|
1233
|
-
publicDir: facts.assetPublicDir === void 0 ? false : resolve(facts.root, facts.assetPublicDir),
|
|
1234
|
-
plugins,
|
|
1235
|
-
resolve: {
|
|
1236
|
-
alias: consumerAliases,
|
|
1237
|
-
dedupe: ["@forgeax/engine
|
|
1238
|
-
},
|
|
1239
|
-
server: { fs: { allow: [facts.root, ...roots] } },
|
|
1240
|
-
build: {
|
|
1241
|
-
target: "esnext",
|
|
1242
|
-
outDir: options.outDir ?? resolve(facts.root, "dist"),
|
|
1243
|
-
emptyOutDir: true,
|
|
1244
|
-
rollupOptions: { input: resolve(generated, "index.html") }
|
|
1245
|
-
}
|
|
1246
|
-
};
|
|
1247
|
-
}
|
|
1248
|
-
var hostRequire, engineWorkspacePackagesPromise;
|
|
1249
|
-
var init_host = __esm({
|
|
1250
|
-
"src/host.ts"() {
|
|
1251
|
-
hostRequire = createRequire(import.meta.url);
|
|
1252
|
-
}
|
|
1253
|
-
});
|
|
1254
|
-
function projectError(code, expected, hint, detail) {
|
|
1255
|
-
return { ok: false, error: { code, expected, hint, detail } };
|
|
1256
|
-
}
|
|
1257
|
-
async function readJson(path) {
|
|
1258
|
-
return JSON.parse(await readFile(path, "utf8"));
|
|
1259
|
-
}
|
|
1260
|
-
function firstUnsupportedStandaloneRealm(entries2, inheritedRealm = "engine") {
|
|
1261
|
-
for (const entry of entries2) {
|
|
1262
|
-
const realm = entry.realm ?? inheritedRealm ?? "engine";
|
|
1263
|
-
if (realm === "host") return { id: entry.id, realm };
|
|
1264
|
-
if (entry.group === true) {
|
|
1265
|
-
const unsupported = firstUnsupportedStandaloneRealm(
|
|
1266
|
-
entry.config,
|
|
1267
|
-
realm
|
|
1268
|
-
);
|
|
1269
|
-
if (unsupported !== void 0) return unsupported;
|
|
1270
|
-
}
|
|
1271
|
-
}
|
|
1272
|
-
return void 0;
|
|
1273
|
-
}
|
|
1274
|
-
function pluginModuleNames(entries2) {
|
|
1275
|
-
return entries2.flatMap(
|
|
1276
|
-
(entry) => entry.group === true ? pluginModuleNames(entry.config) : [entry.name]
|
|
1277
|
-
);
|
|
1278
|
-
}
|
|
1279
|
-
async function readProjectFacts(rootInput = process.cwd()) {
|
|
1280
|
-
const root = resolve(rootInput);
|
|
1281
|
-
let forgeValue;
|
|
1282
|
-
let packageValue;
|
|
1283
|
-
try {
|
|
1284
|
-
[forgeValue, packageValue] = await Promise.all([
|
|
1285
|
-
readJson(resolve(root, "forge.json")),
|
|
1286
|
-
readJson(resolve(root, "package.json"))
|
|
1287
|
-
]);
|
|
1288
|
-
} catch (cause) {
|
|
1289
|
-
return projectError(
|
|
1290
|
-
"project-manifest-unreadable",
|
|
1291
|
-
"readable forge.json and package.json files",
|
|
1292
|
-
"Run the command from a ForgeaX game root or pass its directory.",
|
|
1293
|
-
{ root, reason: cause instanceof Error ? cause.message : String(cause) }
|
|
1294
|
-
);
|
|
1295
|
-
}
|
|
1296
|
-
if (forgeValue === null || typeof forgeValue !== "object") {
|
|
1297
|
-
return projectError(
|
|
1298
|
-
"project-manifest-invalid",
|
|
1299
|
-
"forge.json to contain an object",
|
|
1300
|
-
"Repair forge.json before running DevKit.",
|
|
1301
|
-
{ root }
|
|
1302
|
-
);
|
|
1303
|
-
}
|
|
1304
|
-
if (packageValue === null || typeof packageValue !== "object") {
|
|
1305
|
-
return projectError(
|
|
1306
|
-
"package-manifest-invalid",
|
|
1307
|
-
"package.json to contain an object",
|
|
1308
|
-
"Repair package.json before running DevKit.",
|
|
1309
|
-
{ root }
|
|
1310
|
-
);
|
|
1311
|
-
}
|
|
1312
|
-
const parsedForge = GameProjectSchema.safeParse(forgeValue);
|
|
1313
|
-
if (!parsedForge.success) {
|
|
1314
|
-
return projectError(
|
|
1315
|
-
"project-manifest-invalid",
|
|
1316
|
-
"forge.json to satisfy @forgeax/engine-project GameProjectSchema",
|
|
1317
|
-
"Repair the fields reported by the authoritative project schema.",
|
|
1318
|
-
{ root, issues: parsedForge.error.issues }
|
|
1319
|
-
);
|
|
1320
|
-
}
|
|
1321
|
-
const forge = parsedForge.data;
|
|
1322
|
-
if (forge.id.length === 0 || forge.name.length === 0 || forge.entry === void 0 || forge.entry.length === 0) {
|
|
1323
|
-
return projectError(
|
|
1324
|
-
"project-manifest-invalid",
|
|
1325
|
-
"forge.json to declare id, name, and entry",
|
|
1326
|
-
"Restore the project entry; plugin Entries remain optional additions.",
|
|
1327
|
-
{ root }
|
|
1328
|
-
);
|
|
1329
|
-
}
|
|
1330
|
-
const plugins = forge.plugins ?? [];
|
|
1331
|
-
const unsupportedRealm = firstUnsupportedStandaloneRealm(plugins);
|
|
1332
|
-
if (unsupportedRealm !== void 0) {
|
|
1333
|
-
return projectError(
|
|
1334
|
-
"project-plugin-realm-unsupported",
|
|
1335
|
-
"the standalone Devkit host to contain only engine-realm plugin Entries",
|
|
1336
|
-
"Move Host or build plugins to a host that owns that physical realm.",
|
|
1337
|
-
{ root, ...unsupportedRealm }
|
|
1338
|
-
);
|
|
1339
|
-
}
|
|
1340
|
-
const entryPath = isAbsolute(forge.entry) ? forge.entry : resolve(root, forge.entry);
|
|
1341
|
-
try {
|
|
1342
|
-
await readFile(entryPath);
|
|
1343
|
-
} catch {
|
|
1344
|
-
return projectError(
|
|
1345
|
-
"project-entry-missing",
|
|
1346
|
-
"forge.json#entry to resolve to a readable module",
|
|
1347
|
-
"Restore the game entry or update forge.json#entry.",
|
|
1348
|
-
{ root, entry: forge.entry }
|
|
1349
|
-
);
|
|
1350
|
-
}
|
|
1351
|
-
const packageJson = packageValue;
|
|
1352
|
-
const forgeax = packageJson.forgeax;
|
|
1353
|
-
const configuredRoots = forgeax !== null && typeof forgeax === "object" ? forgeax.assets?.roots : void 0;
|
|
1354
|
-
const assetRoots = Array.isArray(configuredRoots) && configuredRoots.every((value) => typeof value === "string") ? configuredRoots : ["assets"];
|
|
1355
|
-
const configuredImporters = forgeax !== null && typeof forgeax === "object" ? forgeax.assets?.importers : void 0;
|
|
1356
|
-
const assetImporters = Array.isArray(configuredImporters) && configuredImporters.every((value) => typeof value === "string") ? configuredImporters : [];
|
|
1357
|
-
const configuredPublicDir = forgeax !== null && typeof forgeax === "object" ? forgeax.assets?.publicDir : void 0;
|
|
1358
|
-
const assetPublicDir = typeof configuredPublicDir === "string" ? configuredPublicDir : void 0;
|
|
1359
|
-
const physics = forge.physics === "2d" || forge.physics === "3d" ? forge.physics : void 0;
|
|
1360
|
-
const defaultScene = typeof forge.defaultScene === "string" && forge.defaultScene.length > 0 ? forge.defaultScene : void 0;
|
|
1361
|
-
const normalizedEntry = forge.entry.startsWith("./") ? forge.entry : `./${forge.entry}`;
|
|
1362
|
-
const bootstrapEntry = pluginModuleNames(plugins).map((name) => name.startsWith("./") ? name : `./${name}`).includes(normalizedEntry) ? void 0 : forge.entry;
|
|
1363
|
-
return {
|
|
1364
|
-
ok: true,
|
|
1365
|
-
value: {
|
|
1366
|
-
root,
|
|
1367
|
-
id: forge.id,
|
|
1368
|
-
name: forge.name,
|
|
1369
|
-
entry: forge.entry,
|
|
1370
|
-
...bootstrapEntry === void 0 ? {} : { bootstrapEntry },
|
|
1371
|
-
plugins,
|
|
1372
|
-
...physics === void 0 ? {} : { physics },
|
|
1373
|
-
...defaultScene === void 0 ? {} : { defaultScene },
|
|
1374
|
-
assetRoots,
|
|
1375
|
-
...assetImporters.length === 0 ? {} : { assetImporters },
|
|
1376
|
-
...assetPublicDir === void 0 ? {} : { assetPublicDir },
|
|
1377
|
-
packageJson
|
|
1810
|
+
audioImporter,
|
|
1811
|
+
imageImporter,
|
|
1812
|
+
fbxImporter,
|
|
1813
|
+
gltfImporter,
|
|
1814
|
+
fontImporter,
|
|
1815
|
+
...importers
|
|
1816
|
+
],
|
|
1817
|
+
cookers: [createMaterialPackCooker(roots), createParticleCodeNativeCookerFromRoots(roots)],
|
|
1818
|
+
ignorePath
|
|
1819
|
+
})
|
|
1820
|
+
];
|
|
1821
|
+
return {
|
|
1822
|
+
root: generated,
|
|
1823
|
+
base,
|
|
1824
|
+
configFile: false,
|
|
1825
|
+
publicDir: facts.assetPublicDir === void 0 ? false : resolve(facts.root, facts.assetPublicDir),
|
|
1826
|
+
plugins,
|
|
1827
|
+
resolve: {
|
|
1828
|
+
alias: consumerAliases,
|
|
1829
|
+
dedupe: ["@forgeax/engine"]
|
|
1830
|
+
},
|
|
1831
|
+
server: { ...options.server, fs: { allow: [facts.root, ...roots] } },
|
|
1832
|
+
build: {
|
|
1833
|
+
target: "esnext",
|
|
1834
|
+
outDir: options.outDir ?? resolve(facts.root, "dist"),
|
|
1835
|
+
emptyOutDir: true,
|
|
1836
|
+
rollupOptions: { input: resolve(generated, "index.html") }
|
|
1378
1837
|
}
|
|
1379
1838
|
};
|
|
1380
1839
|
}
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
detail: { reason: cause instanceof Error ? cause.message : String(cause) }
|
|
1393
|
-
};
|
|
1840
|
+
var hostRequire;
|
|
1841
|
+
var init_host = __esm({
|
|
1842
|
+
"src/host.ts"() {
|
|
1843
|
+
init_engine_binding();
|
|
1844
|
+
hostRequire = createRequire(import.meta.url);
|
|
1845
|
+
}
|
|
1846
|
+
});
|
|
1847
|
+
|
|
1848
|
+
// src/types.ts
|
|
1849
|
+
function resolveProjectPort(port) {
|
|
1850
|
+
return { port: port ?? 5173, strictPort: port !== 0 };
|
|
1394
1851
|
}
|
|
1395
|
-
var
|
|
1396
|
-
"src/
|
|
1852
|
+
var init_types = __esm({
|
|
1853
|
+
"src/types.ts"() {
|
|
1397
1854
|
}
|
|
1398
1855
|
});
|
|
1399
1856
|
function failure(error) {
|
|
1400
1857
|
return { ok: false, error };
|
|
1401
1858
|
}
|
|
1402
|
-
function producerFailure(stderr, fallback, detail) {
|
|
1403
|
-
try {
|
|
1404
|
-
const parsed = JSON.parse(stderr.at(-1) ?? "");
|
|
1405
|
-
if (isCommandError(parsed)) return failure(parsed);
|
|
1406
|
-
} catch {
|
|
1407
|
-
}
|
|
1408
|
-
return failure({ ...fallback, detail: { ...detail, diagnostic: stderr.join("\n") } });
|
|
1409
|
-
}
|
|
1410
1859
|
async function sourcesAt(path) {
|
|
1411
1860
|
const info = await stat(path);
|
|
1412
1861
|
if (info.isFile()) return [path];
|
|
@@ -1490,15 +1939,16 @@ async function addGltf(sourcePath, dryRun) {
|
|
|
1490
1939
|
stderrWrite: (line) => stderr.push(line)
|
|
1491
1940
|
});
|
|
1492
1941
|
if (exitCode !== 0) {
|
|
1493
|
-
|
|
1494
|
-
stderr
|
|
1495
|
-
|
|
1942
|
+
try {
|
|
1943
|
+
return failure(JSON.parse(stderr.at(-1) ?? ""));
|
|
1944
|
+
} catch {
|
|
1945
|
+
return failure({
|
|
1496
1946
|
code: "asset-add-failed",
|
|
1497
1947
|
expected: "the glTF producer to create or reuse a valid sidecar",
|
|
1498
|
-
hint: "Inspect the glTF source and its external references."
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1948
|
+
hint: "Inspect the glTF source and its external references.",
|
|
1949
|
+
detail: { source: sourcePath, diagnostic: stderr.join("\n") }
|
|
1950
|
+
});
|
|
1951
|
+
}
|
|
1502
1952
|
}
|
|
1503
1953
|
return { ok: true, value: { source: sourcePath, metaPath: `${sourcePath}.meta.json` } };
|
|
1504
1954
|
}
|
|
@@ -1541,15 +1991,16 @@ async function entries(options) {
|
|
|
1541
1991
|
{ stdoutWrite: (line) => stdout.push(line), stderrWrite: (line) => stderr.push(line) }
|
|
1542
1992
|
);
|
|
1543
1993
|
if (!result.ok) {
|
|
1544
|
-
|
|
1545
|
-
stderr
|
|
1546
|
-
|
|
1994
|
+
try {
|
|
1995
|
+
return failure(JSON.parse(stderr.at(-1) ?? ""));
|
|
1996
|
+
} catch {
|
|
1997
|
+
return failure({
|
|
1547
1998
|
code: "asset-authority-invalid",
|
|
1548
1999
|
expected: "all asset roots and sidecars to pass the pack scanner",
|
|
1549
|
-
hint: "Repair the first invalid asset authority reported by the scanner."
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
2000
|
+
hint: "Repair the first invalid asset authority reported by the scanner.",
|
|
2001
|
+
detail: { diagnostic: stderr.join("\n") }
|
|
2002
|
+
});
|
|
2003
|
+
}
|
|
1553
2004
|
}
|
|
1554
2005
|
return { ok: true, value: { facts: facts.value, entries: result.value } };
|
|
1555
2006
|
}
|
|
@@ -1634,7 +2085,6 @@ var init_package = __esm({
|
|
|
1634
2085
|
"@forgeax/engine-audio-webaudio": "workspace:*",
|
|
1635
2086
|
"@forgeax/engine-fbx": "workspace:*",
|
|
1636
2087
|
"@forgeax/engine-font": "workspace:*",
|
|
1637
|
-
"@forgeax/engine-geometry": "workspace:*",
|
|
1638
2088
|
"@forgeax/engine-gltf": "workspace:*",
|
|
1639
2089
|
"@forgeax/engine-image": "workspace:*",
|
|
1640
2090
|
"@forgeax/engine-input": "workspace:*",
|
|
@@ -1644,6 +2094,8 @@ var init_package = __esm({
|
|
|
1644
2094
|
"@forgeax/engine-preview": "workspace:*",
|
|
1645
2095
|
"@forgeax/engine-project": "workspace:*",
|
|
1646
2096
|
"@forgeax/engine-rhi-debug": "workspace:*",
|
|
2097
|
+
"@forgeax/engine-rhi-null": "workspace:*",
|
|
2098
|
+
"@forgeax/engine-rhi-webgpu": "workspace:*",
|
|
1647
2099
|
"@forgeax/engine-render": "workspace:*",
|
|
1648
2100
|
"@forgeax/engine-render-graph": "workspace:*",
|
|
1649
2101
|
"@forgeax/engine-runtime": "workspace:*",
|
|
@@ -1658,10 +2110,10 @@ var init_package = __esm({
|
|
|
1658
2110
|
jiti: "1.21.7",
|
|
1659
2111
|
playwright: "1.60.0",
|
|
1660
2112
|
vite: "8.0.10",
|
|
1661
|
-
vitest: "4.
|
|
2113
|
+
vitest: "4.0.18",
|
|
2114
|
+
webgpu: "^0.4.0"
|
|
1662
2115
|
},
|
|
1663
2116
|
devDependencies: {
|
|
1664
|
-
"@forgeax/engine-rhi-null": "workspace:*",
|
|
1665
2117
|
"@types/node": "^20.14.0"
|
|
1666
2118
|
},
|
|
1667
2119
|
forgeax: {
|
|
@@ -1833,7 +2285,7 @@ var init_init = __esm({
|
|
|
1833
2285
|
"@webgpu/types": "0.1.71",
|
|
1834
2286
|
tsx: "4.23.1",
|
|
1835
2287
|
typescript: "6.0.3",
|
|
1836
|
-
vitest: "4.
|
|
2288
|
+
vitest: "4.0.18"
|
|
1837
2289
|
};
|
|
1838
2290
|
}
|
|
1839
2291
|
});
|
|
@@ -1875,6 +2327,20 @@ var init_sdk = __esm({
|
|
|
1875
2327
|
"src/sdk.ts"() {
|
|
1876
2328
|
}
|
|
1877
2329
|
});
|
|
2330
|
+
function agentOnboarding(sdk, projectRoot) {
|
|
2331
|
+
const root = projectRoot ?? sdk.root;
|
|
2332
|
+
return {
|
|
2333
|
+
read: [
|
|
2334
|
+
resolve(root, "AGENTS.md"),
|
|
2335
|
+
resolve(root, "skills", "forgeax-engine-sdk", "SKILL.md"),
|
|
2336
|
+
resolve(root, "skills", "forgeax-engine-sdk", "references", "feature-catalog.md")
|
|
2337
|
+
],
|
|
2338
|
+
next: projectRoot === void 0 ? {
|
|
2339
|
+
cwd: root,
|
|
2340
|
+
argv: ["node", "./bin/forgeax.mjs", "new", "../my-game"]
|
|
2341
|
+
} : { cwd: root, argv: ["pnpm", "exec", "forgeax", "list", "--json"] }
|
|
2342
|
+
};
|
|
2343
|
+
}
|
|
1878
2344
|
function sdkInitPath(sdk) {
|
|
1879
2345
|
return resolve(sdk.root, ...SDK_INIT_PATH);
|
|
1880
2346
|
}
|
|
@@ -1886,19 +2352,21 @@ function sdkProjectInstallArgs(store) {
|
|
|
1886
2352
|
"install",
|
|
1887
2353
|
"--frozen-lockfile",
|
|
1888
2354
|
"--ignore-scripts",
|
|
2355
|
+
"--config.pm-on-fail=ignore",
|
|
1889
2356
|
"--side-effects-cache=true",
|
|
1890
2357
|
"--child-concurrency=1"
|
|
1891
2358
|
];
|
|
1892
|
-
return store === void 0 ? common : [...common, "--offline", "--trust-lockfile", "--store-dir", store];
|
|
2359
|
+
return store === void 0 ? common : [...common, "--offline", "--config.trust-lockfile=true", "--store-dir", store];
|
|
1893
2360
|
}
|
|
1894
2361
|
function sdkBootstrapInstallArgs(store) {
|
|
1895
2362
|
const common = [
|
|
1896
2363
|
"install",
|
|
1897
2364
|
"--frozen-lockfile",
|
|
2365
|
+
"--config.pm-on-fail=ignore",
|
|
1898
2366
|
"--child-concurrency=1",
|
|
1899
2367
|
"--side-effects-cache=true"
|
|
1900
2368
|
];
|
|
1901
|
-
return store === void 0 ? common : [...common, "--offline", "--trust-lockfile", "--store-dir", store];
|
|
2369
|
+
return store === void 0 ? common : [...common, "--offline", "--config.trust-lockfile=true", "--store-dir", store];
|
|
1902
2370
|
}
|
|
1903
2371
|
function supportedPnpm(version) {
|
|
1904
2372
|
const [major = 0, minor = 0] = version.split(".").map(Number);
|
|
@@ -1988,7 +2456,8 @@ async function sdkInitCommand(sdk, options = {}) {
|
|
|
1988
2456
|
const report = {
|
|
1989
2457
|
...state,
|
|
1990
2458
|
root: sdk.root,
|
|
1991
|
-
store: sdk.store === void 0 ? "registry" : "offline"
|
|
2459
|
+
store: sdk.store === void 0 ? "registry" : "offline",
|
|
2460
|
+
onboarding: agentOnboarding(sdk)
|
|
1992
2461
|
};
|
|
1993
2462
|
if (options.dryRun === true || options.install === false) return { ok: true, value: report };
|
|
1994
2463
|
let staging;
|
|
@@ -2023,6 +2492,107 @@ var init_sdk_bootstrap = __esm({
|
|
|
2023
2492
|
SDK_INIT_SCHEMA_VERSION = "1.0.0";
|
|
2024
2493
|
}
|
|
2025
2494
|
});
|
|
2495
|
+
|
|
2496
|
+
// src/sdk-update.ts
|
|
2497
|
+
function parseVersion(version) {
|
|
2498
|
+
const match = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/.exec(
|
|
2499
|
+
version
|
|
2500
|
+
);
|
|
2501
|
+
if (match === null) return void 0;
|
|
2502
|
+
const major = match[1];
|
|
2503
|
+
const minor = match[2];
|
|
2504
|
+
const patch = match[3];
|
|
2505
|
+
if (major === void 0 || minor === void 0 || patch === void 0) return void 0;
|
|
2506
|
+
const prerelease = match[4]?.split(".");
|
|
2507
|
+
if (prerelease?.some(
|
|
2508
|
+
(identifier) => identifier.length === 0 || /^\d+$/.test(identifier) && /^0\d+/.test(identifier)
|
|
2509
|
+
)) {
|
|
2510
|
+
return void 0;
|
|
2511
|
+
}
|
|
2512
|
+
return {
|
|
2513
|
+
core: [major, minor, patch],
|
|
2514
|
+
...prerelease === void 0 ? {} : { prerelease }
|
|
2515
|
+
};
|
|
2516
|
+
}
|
|
2517
|
+
function compareNumericIdentifier(current, latest) {
|
|
2518
|
+
if (current.length !== latest.length) return latest.length > current.length ? 1 : -1;
|
|
2519
|
+
if (current === latest) return 0;
|
|
2520
|
+
return latest > current ? 1 : -1;
|
|
2521
|
+
}
|
|
2522
|
+
function comparePrerelease(current, latest) {
|
|
2523
|
+
const length = Math.max(current.length, latest.length);
|
|
2524
|
+
for (let index = 0; index < length; index += 1) {
|
|
2525
|
+
const currentIdentifier = current[index];
|
|
2526
|
+
const latestIdentifier = latest[index];
|
|
2527
|
+
if (currentIdentifier === void 0) return 1;
|
|
2528
|
+
if (latestIdentifier === void 0) return -1;
|
|
2529
|
+
if (currentIdentifier === latestIdentifier) continue;
|
|
2530
|
+
const currentNumeric = /^\d+$/.test(currentIdentifier);
|
|
2531
|
+
const latestNumeric = /^\d+$/.test(latestIdentifier);
|
|
2532
|
+
if (currentNumeric && latestNumeric) {
|
|
2533
|
+
return compareNumericIdentifier(currentIdentifier, latestIdentifier);
|
|
2534
|
+
}
|
|
2535
|
+
if (currentNumeric !== latestNumeric) return latestNumeric ? -1 : 1;
|
|
2536
|
+
return latestIdentifier > currentIdentifier ? 1 : -1;
|
|
2537
|
+
}
|
|
2538
|
+
return 0;
|
|
2539
|
+
}
|
|
2540
|
+
function newerSdkVersion(currentVersion, latestVersion) {
|
|
2541
|
+
const current = parseVersion(currentVersion);
|
|
2542
|
+
const latest = parseVersion(latestVersion);
|
|
2543
|
+
if (current === void 0 || latest === void 0) return false;
|
|
2544
|
+
for (let index = 0; index < current.core.length; index += 1) {
|
|
2545
|
+
const precedence = compareNumericIdentifier(
|
|
2546
|
+
current.core[index] ?? "0",
|
|
2547
|
+
latest.core[index] ?? "0"
|
|
2548
|
+
);
|
|
2549
|
+
if (precedence !== 0) return precedence > 0;
|
|
2550
|
+
}
|
|
2551
|
+
if (current.prerelease !== void 0 && latest.prerelease === void 0) return true;
|
|
2552
|
+
if (current.prerelease === void 0 || latest.prerelease === void 0) return false;
|
|
2553
|
+
return comparePrerelease(current.prerelease, latest.prerelease) > 0;
|
|
2554
|
+
}
|
|
2555
|
+
function offline() {
|
|
2556
|
+
return ["1", "true"].includes((process.env.npm_config_offline ?? "").toLowerCase());
|
|
2557
|
+
}
|
|
2558
|
+
async function checkSdkUpdate(currentVersion) {
|
|
2559
|
+
if (process.env.FORGEAX_DISABLE_UPDATE_CHECK === "1") {
|
|
2560
|
+
return { status: "skipped", currentVersion, reason: "disabled" };
|
|
2561
|
+
}
|
|
2562
|
+
if (offline()) return { status: "skipped", currentVersion, reason: "offline" };
|
|
2563
|
+
try {
|
|
2564
|
+
const registry = process.env.npm_config_registry ?? "https://registry.npmjs.org/";
|
|
2565
|
+
const response = await fetch(
|
|
2566
|
+
new URL("@forgeax%2Fengine-sdk", `${registry.replace(/\/$/, "")}/`),
|
|
2567
|
+
{
|
|
2568
|
+
headers: { accept: "application/vnd.npm.install-v1+json" },
|
|
2569
|
+
signal: AbortSignal.timeout(2e3)
|
|
2570
|
+
}
|
|
2571
|
+
);
|
|
2572
|
+
if (!response.ok) throw new Error(`registry-http-${response.status}`);
|
|
2573
|
+
const metadata = await response.json();
|
|
2574
|
+
const latestVersion = metadata["dist-tags"]?.latest;
|
|
2575
|
+
if (typeof latestVersion !== "string") throw new Error("registry-latest-version-missing");
|
|
2576
|
+
return newerSdkVersion(currentVersion, latestVersion) ? {
|
|
2577
|
+
status: "available",
|
|
2578
|
+
currentVersion,
|
|
2579
|
+
latestVersion,
|
|
2580
|
+
migrationRisk: UPDATE_MIGRATION_RISK
|
|
2581
|
+
} : { status: "current", currentVersion, latestVersion };
|
|
2582
|
+
} catch (cause) {
|
|
2583
|
+
return {
|
|
2584
|
+
status: "unavailable",
|
|
2585
|
+
currentVersion,
|
|
2586
|
+
reason: cause instanceof Error ? cause.message : String(cause)
|
|
2587
|
+
};
|
|
2588
|
+
}
|
|
2589
|
+
}
|
|
2590
|
+
var UPDATE_MIGRATION_RISK;
|
|
2591
|
+
var init_sdk_update = __esm({
|
|
2592
|
+
"src/sdk-update.ts"() {
|
|
2593
|
+
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.";
|
|
2594
|
+
}
|
|
2595
|
+
});
|
|
2026
2596
|
function slash(path) {
|
|
2027
2597
|
return path.split(sep).join("/");
|
|
2028
2598
|
}
|
|
@@ -2650,9 +3220,16 @@ async function newCommand(options = {}) {
|
|
|
2650
3220
|
env: { ...process.env, CI: "true" },
|
|
2651
3221
|
maxBuffer: 16 * 1024 * 1024
|
|
2652
3222
|
});
|
|
3223
|
+
const sdkUpdate = await checkSdkUpdate(sdk.manifest.sdkVersion);
|
|
2653
3224
|
return {
|
|
2654
3225
|
ok: true,
|
|
2655
|
-
value: {
|
|
3226
|
+
value: {
|
|
3227
|
+
root,
|
|
3228
|
+
template: templateId,
|
|
3229
|
+
sdkVersion: sdk.manifest.sdkVersion,
|
|
3230
|
+
onboarding: agentOnboarding(sdk, root),
|
|
3231
|
+
sdkUpdate
|
|
3232
|
+
}
|
|
2656
3233
|
};
|
|
2657
3234
|
} catch (cause) {
|
|
2658
3235
|
if (staging !== void 0) await rm(staging, { recursive: true, force: true });
|
|
@@ -2685,6 +3262,7 @@ var init_bootstrap_commands = __esm({
|
|
|
2685
3262
|
init_project();
|
|
2686
3263
|
init_sdk();
|
|
2687
3264
|
init_sdk_bootstrap();
|
|
3265
|
+
init_sdk_update();
|
|
2688
3266
|
init_skill_install();
|
|
2689
3267
|
execFileAsync2 = promisify(execFile);
|
|
2690
3268
|
}
|
|
@@ -2696,7 +3274,7 @@ __export(plugin_authoring_exports, {
|
|
|
2696
3274
|
pluginInstallCommand: () => pluginInstallCommand,
|
|
2697
3275
|
pluginUninstallCommand: () => pluginUninstallCommand
|
|
2698
3276
|
});
|
|
2699
|
-
async function
|
|
3277
|
+
async function readManifest2(root) {
|
|
2700
3278
|
const path = resolve(root, "forge.json");
|
|
2701
3279
|
try {
|
|
2702
3280
|
const raw = await readFile(path, "utf8");
|
|
@@ -2749,7 +3327,7 @@ async function mutateDependency(root, action, dependency) {
|
|
|
2749
3327
|
}
|
|
2750
3328
|
async function pluginInstallCommand(options) {
|
|
2751
3329
|
const root = resolve(options.root ?? process.cwd());
|
|
2752
|
-
const manifest = await
|
|
3330
|
+
const manifest = await readManifest2(root);
|
|
2753
3331
|
if (!manifest.ok) return manifest;
|
|
2754
3332
|
const parsed = GameProjectSchema.safeParse(manifest.value.value);
|
|
2755
3333
|
if (!parsed.success) {
|
|
@@ -2798,546 +3376,67 @@ async function pluginInstallCommand(options) {
|
|
|
2798
3376
|
detail: { reason: cause instanceof Error ? cause.message : String(cause) }
|
|
2799
3377
|
}
|
|
2800
3378
|
};
|
|
2801
|
-
}
|
|
2802
|
-
}
|
|
2803
|
-
async function pluginUninstallCommand(options) {
|
|
2804
|
-
const root = resolve(options.root ?? process.cwd());
|
|
2805
|
-
const manifest = await
|
|
2806
|
-
if (!manifest.ok) return manifest;
|
|
2807
|
-
const parsed = GameProjectSchema.safeParse(manifest.value.value);
|
|
2808
|
-
if (!parsed.success) {
|
|
2809
|
-
return {
|
|
2810
|
-
ok: false,
|
|
2811
|
-
error: {
|
|
2812
|
-
code: "plugin-manifest-invalid",
|
|
2813
|
-
expected: "forge.json to satisfy GameProjectSchema",
|
|
2814
|
-
hint: "Repair the manifest before uninstalling a plugin.",
|
|
2815
|
-
detail: { issues: parsed.error.issues }
|
|
2816
|
-
}
|
|
2817
|
-
};
|
|
2818
|
-
}
|
|
2819
|
-
const entries2 = parsed.data.plugins ?? [];
|
|
2820
|
-
if (!allEntries(entries2).some((entry) => entry.id === options.id)) {
|
|
2821
|
-
return {
|
|
2822
|
-
ok: false,
|
|
2823
|
-
error: {
|
|
2824
|
-
code: "plugin-entry-missing",
|
|
2825
|
-
expected: "the plugin Entry id to exist",
|
|
2826
|
-
hint: "Inspect forge.json#plugins and pass an installed Entry id.",
|
|
2827
|
-
detail: { id: options.id }
|
|
2828
|
-
}
|
|
2829
|
-
};
|
|
2830
|
-
}
|
|
2831
|
-
const next = { ...parsed.data, plugins: withoutEntry(entries2, options.id) };
|
|
2832
|
-
if (options.dryRun === true) return { ok: true, value: { root, manifest: next } };
|
|
2833
|
-
try {
|
|
2834
|
-
await writeManifest(root, next);
|
|
2835
|
-
await mutateDependency(root, "remove", options.dependency);
|
|
2836
|
-
return { ok: true, value: { root, id: options.id } };
|
|
2837
|
-
} catch (cause) {
|
|
2838
|
-
await writeFile(resolve(root, "forge.json"), manifest.value.raw);
|
|
2839
|
-
return {
|
|
2840
|
-
ok: false,
|
|
2841
|
-
error: {
|
|
2842
|
-
code: "plugin-uninstall-failed",
|
|
2843
|
-
expected: "Entry and dependency removal to commit together",
|
|
2844
|
-
hint: "Inspect the package-manager failure; the original forge.json was restored.",
|
|
2845
|
-
detail: { reason: cause instanceof Error ? cause.message : String(cause) }
|
|
2846
|
-
}
|
|
2847
|
-
};
|
|
2848
|
-
}
|
|
2849
|
-
}
|
|
2850
|
-
var execFileAsync3;
|
|
2851
|
-
var init_plugin_authoring = __esm({
|
|
2852
|
-
"src/plugin-authoring.ts"() {
|
|
2853
|
-
execFileAsync3 = promisify(execFile);
|
|
2854
|
-
}
|
|
2855
|
-
});
|
|
2856
|
-
var RhiError;
|
|
2857
|
-
var init_dist2 = __esm({
|
|
2858
|
-
"../rhi/dist/index.mjs"() {
|
|
2859
|
-
RhiError = class extends Error {
|
|
2860
|
-
code;
|
|
2861
|
-
expected;
|
|
2862
|
-
hint;
|
|
2863
|
-
detail;
|
|
2864
|
-
constructor(args) {
|
|
2865
|
-
super(`[RhiError ${args.code}] expected: ${args.expected}; hint: ${args.hint}`);
|
|
2866
|
-
this.name = "RhiError";
|
|
2867
|
-
this.code = args.code;
|
|
2868
|
-
this.expected = args.expected;
|
|
2869
|
-
this.hint = args.hint;
|
|
2870
|
-
this.detail = args.detail;
|
|
2871
|
-
}
|
|
2872
|
-
};
|
|
2873
|
-
}
|
|
2874
|
-
});
|
|
2875
|
-
function readPassLabel(desc) {
|
|
2876
|
-
if (desc && typeof desc.label === "string" && desc.label.length > 0) {
|
|
2877
|
-
return desc.label;
|
|
2878
|
-
}
|
|
2879
|
-
return "<unnamed>";
|
|
2880
|
-
}
|
|
2881
|
-
function readRecord(handle) {
|
|
2882
|
-
if (handle === null || typeof handle !== "object") return void 0;
|
|
2883
|
-
const tagged = handle;
|
|
2884
|
-
return tagged[BOOKKEEPING_KEY];
|
|
2885
|
-
}
|
|
2886
|
-
function isHandleDestroyed(handle) {
|
|
2887
|
-
if (readRecord(handle)?.destroyed === true) {
|
|
2888
|
-
return err(
|
|
2889
|
-
new RhiError({
|
|
2890
|
-
code: "destroy-after-destroy",
|
|
2891
|
-
expected: "GPU buffer/texture handle has not been destroyed yet",
|
|
2892
|
-
hint: "object already destroyed; track lifecycle in caller or check isDestroyed before re-destroy"
|
|
2893
|
-
})
|
|
2894
|
-
);
|
|
2895
|
-
}
|
|
2896
|
-
return ok(void 0);
|
|
2897
|
-
}
|
|
2898
|
-
function acquireCanvasContext(_canvas) {
|
|
2899
|
-
return ok(new RhiNullCanvasContext());
|
|
2900
|
-
}
|
|
2901
|
-
function createShaderModule(_device, _desc) {
|
|
2902
|
-
return Promise.resolve(ok({}));
|
|
2903
|
-
}
|
|
2904
|
-
function requestAdapter(_opts, _compatibleSurface) {
|
|
2905
|
-
return Promise.resolve(ok(new RhiNullAdapter()));
|
|
2906
|
-
}
|
|
2907
|
-
var RhiNullRenderPassEncoder, RhiNullComputePassEncoder, DeviceCounter, RhiNullCommandEncoder, BOOKKEEPING_KEY, Bookkeeper, nextDeviceId, RhiNullDevice, EMPTY_FEATURES, EMPTY_LIMITS, NEVER, RhiNullQueue, RhiNullAdapter, RhiNullCanvasContext, rhi;
|
|
2908
|
-
var init_dist3 = __esm({
|
|
2909
|
-
"../rhi-null/dist/index.mjs"() {
|
|
2910
|
-
init_dist2();
|
|
2911
|
-
RhiNullRenderPassEncoder = class {
|
|
2912
|
-
/** Number of draw* calls issued on this pass (AC-06 readback). */
|
|
2913
|
-
drawCount = 0;
|
|
2914
|
-
bindGroupCount = 0;
|
|
2915
|
-
/** Most recent setVertexBuffer / setBindGroup ownership validation; ok unless
|
|
2916
|
-
* a foreign handle was passed (AC-09 readback). */
|
|
2917
|
-
lastValidation = ok(void 0);
|
|
2918
|
-
bookkeeper;
|
|
2919
|
-
counter;
|
|
2920
|
-
passName;
|
|
2921
|
-
constructor(bookkeeper, counter, passName) {
|
|
2922
|
-
this.bookkeeper = bookkeeper;
|
|
2923
|
-
this.counter = counter;
|
|
2924
|
-
this.passName = passName;
|
|
2925
|
-
}
|
|
2926
|
-
setPipeline(_pipeline) {
|
|
2927
|
-
}
|
|
2928
|
-
setVertexBuffer(_slot, buffer, _offset, _size) {
|
|
2929
|
-
this.lastValidation = this.bookkeeper.validateOwnership(buffer);
|
|
2930
|
-
}
|
|
2931
|
-
setIndexBuffer(_buffer, _format, _offset, _size) {
|
|
2932
|
-
}
|
|
2933
|
-
setBindGroup(_index, bindGroup, _dynamicOffsetsData, _dynamicOffsetsDataStart, _dynamicOffsetsDataLength) {
|
|
2934
|
-
this.bindGroupCount++;
|
|
2935
|
-
this.counter?.recordBindGroup();
|
|
2936
|
-
this.lastValidation = this.bookkeeper.validateOwnership(bindGroup);
|
|
2937
|
-
}
|
|
2938
|
-
draw(_vertexCount, _instanceCount, _firstVertex, _firstInstance) {
|
|
2939
|
-
this.drawCount++;
|
|
2940
|
-
this.counter?.recordDraw();
|
|
2941
|
-
}
|
|
2942
|
-
drawIndexed(_indexCount, _instanceCount, _firstIndex, _baseVertex, _firstInstance) {
|
|
2943
|
-
this.drawCount++;
|
|
2944
|
-
this.counter?.recordDraw();
|
|
2945
|
-
}
|
|
2946
|
-
end() {
|
|
2947
|
-
this.counter?.recordPassName(this.passName);
|
|
2948
|
-
}
|
|
2949
|
-
setViewport(_x, _y, _w, _h, _minDepth, _maxDepth) {
|
|
2950
|
-
}
|
|
2951
|
-
setScissorRect(_x, _y, _w, _h) {
|
|
2952
|
-
}
|
|
2953
|
-
setBlendConstant(_color) {
|
|
2954
|
-
}
|
|
2955
|
-
setStencilReference(_reference) {
|
|
2956
|
-
}
|
|
2957
|
-
drawIndirect(_indirectBuffer, _indirectOffset) {
|
|
2958
|
-
this.drawCount++;
|
|
2959
|
-
this.counter?.recordDraw();
|
|
2960
|
-
}
|
|
2961
|
-
drawIndexedIndirect(_indirectBuffer, _indirectOffset) {
|
|
2962
|
-
this.drawCount++;
|
|
2963
|
-
this.counter?.recordDraw();
|
|
2964
|
-
}
|
|
2965
|
-
pushDebugGroup(_groupLabel) {
|
|
2966
|
-
}
|
|
2967
|
-
popDebugGroup() {
|
|
2968
|
-
}
|
|
2969
|
-
insertDebugMarker(_markerLabel) {
|
|
2970
|
-
}
|
|
2971
|
-
executeBundles(_bundles) {
|
|
2972
|
-
return ok(void 0);
|
|
2973
|
-
}
|
|
2974
|
-
beginOcclusionQuery(_queryIndex) {
|
|
2975
|
-
return ok(void 0);
|
|
2976
|
-
}
|
|
2977
|
-
endOcclusionQuery() {
|
|
2978
|
-
return ok(void 0);
|
|
2979
|
-
}
|
|
2980
|
-
};
|
|
2981
|
-
RhiNullComputePassEncoder = class {
|
|
2982
|
-
/** Number of dispatchWorkgroups calls issued on this pass (readback). */
|
|
2983
|
-
dispatchCount = 0;
|
|
2984
|
-
/** Most recent setBindGroup ownership validation (AC-09 readback). */
|
|
2985
|
-
lastValidation = ok(void 0);
|
|
2986
|
-
bookkeeper;
|
|
2987
|
-
counter;
|
|
2988
|
-
passName;
|
|
2989
|
-
constructor(bookkeeper, counter, passName) {
|
|
2990
|
-
this.bookkeeper = bookkeeper;
|
|
2991
|
-
this.counter = counter;
|
|
2992
|
-
this.passName = passName;
|
|
2993
|
-
}
|
|
2994
|
-
setPipeline(_pipeline) {
|
|
2995
|
-
}
|
|
2996
|
-
setBindGroup(_index, bindGroup, _dynamicOffsets) {
|
|
2997
|
-
this.lastValidation = this.bookkeeper.validateOwnership(bindGroup);
|
|
2998
|
-
}
|
|
2999
|
-
dispatchWorkgroups(_x, _y, _z) {
|
|
3000
|
-
this.dispatchCount++;
|
|
3001
|
-
this.counter?.recordDispatch();
|
|
3002
|
-
}
|
|
3003
|
-
dispatchWorkgroupsIndirect(indirectBuffer, _indirectOffset) {
|
|
3004
|
-
this.lastValidation = this.bookkeeper.validateOwnership(indirectBuffer);
|
|
3005
|
-
this.dispatchCount++;
|
|
3006
|
-
this.counter?.recordDispatch();
|
|
3007
|
-
}
|
|
3008
|
-
end() {
|
|
3009
|
-
this.counter?.recordPassName(this.passName);
|
|
3010
|
-
}
|
|
3011
|
-
};
|
|
3012
|
-
DeviceCounter = class {
|
|
3013
|
-
constructor(device) {
|
|
3014
|
-
this.device = device;
|
|
3015
|
-
}
|
|
3016
|
-
device;
|
|
3017
|
-
recordDraw() {
|
|
3018
|
-
this.device.totalDrawCount++;
|
|
3019
|
-
}
|
|
3020
|
-
recordDispatch() {
|
|
3021
|
-
this.device.totalDispatchCount++;
|
|
3022
|
-
}
|
|
3023
|
-
recordBindGroup() {
|
|
3024
|
-
this.device.totalBindGroupCount++;
|
|
3025
|
-
}
|
|
3026
|
-
recordPassName(name) {
|
|
3027
|
-
this.device.framePassNames.push(name);
|
|
3028
|
-
}
|
|
3029
|
-
};
|
|
3030
|
-
RhiNullCommandEncoder = class {
|
|
3031
|
-
bookkeeper;
|
|
3032
|
-
counter;
|
|
3033
|
-
constructor(bookkeeper, device) {
|
|
3034
|
-
this.bookkeeper = bookkeeper;
|
|
3035
|
-
this.counter = new DeviceCounter(device);
|
|
3036
|
-
}
|
|
3037
|
-
beginRenderPass(desc) {
|
|
3038
|
-
const label = readPassLabel(desc);
|
|
3039
|
-
return new RhiNullRenderPassEncoder(this.bookkeeper, this.counter, label);
|
|
3040
|
-
}
|
|
3041
|
-
beginComputePass(desc) {
|
|
3042
|
-
const label = readPassLabel(desc);
|
|
3043
|
-
return new RhiNullComputePassEncoder(this.bookkeeper, this.counter, label);
|
|
3044
|
-
}
|
|
3045
|
-
copyBufferToBuffer(_source, _sourceOffsetOrDestination, _destinationOrSize, _destinationOffset, _size) {
|
|
3046
|
-
}
|
|
3047
|
-
copyBufferToTexture(_source, _destination, _copySize) {
|
|
3048
|
-
}
|
|
3049
|
-
copyTextureToBuffer(_source, _destination, _copySize) {
|
|
3050
|
-
}
|
|
3051
|
-
copyTextureToTexture(_source, _destination, _copySize) {
|
|
3052
|
-
}
|
|
3053
|
-
clearBuffer(_buffer, _offset, _size) {
|
|
3054
|
-
}
|
|
3055
|
-
resolveQuerySet(_querySet, _firstQuery, _queryCount, _destination, _destinationOffset) {
|
|
3056
|
-
return ok(void 0);
|
|
3057
|
-
}
|
|
3058
|
-
writeTimestamp(_querySet, _queryIndex) {
|
|
3059
|
-
}
|
|
3060
|
-
pushDebugGroup(_groupLabel) {
|
|
3061
|
-
}
|
|
3062
|
-
popDebugGroup() {
|
|
3063
|
-
}
|
|
3064
|
-
insertDebugMarker(_markerLabel) {
|
|
3065
|
-
}
|
|
3066
|
-
finish() {
|
|
3067
|
-
return ok(this.bookkeeper.register("CommandBuffer"));
|
|
3068
|
-
}
|
|
3069
|
-
};
|
|
3070
|
-
BOOKKEEPING_KEY = /* @__PURE__ */ Symbol("forgeax-rhi-null-bookkeeping");
|
|
3071
|
-
Bookkeeper = class {
|
|
3072
|
-
deviceId;
|
|
3073
|
-
nextHandleId = 0;
|
|
3074
|
-
records = /* @__PURE__ */ new Map();
|
|
3075
|
-
constructor(deviceId) {
|
|
3076
|
-
this.deviceId = deviceId;
|
|
3077
|
-
}
|
|
3078
|
-
/**
|
|
3079
|
-
* Register a freshly-minted handle of the given kind, returning a plain
|
|
3080
|
-
* object that carries its ledger row. The caller casts the return value to
|
|
3081
|
-
* the concrete brand (`as unknown as Buffer` etc.).
|
|
3082
|
-
*/
|
|
3083
|
-
register(kind) {
|
|
3084
|
-
const id = this.nextHandleId++;
|
|
3085
|
-
const record = {
|
|
3086
|
-
id,
|
|
3087
|
-
kind,
|
|
3088
|
-
destroyed: false,
|
|
3089
|
-
sourceDeviceId: this.deviceId
|
|
3090
|
-
};
|
|
3091
|
-
this.records.set(id, record);
|
|
3092
|
-
return { [BOOKKEEPING_KEY]: record };
|
|
3093
|
-
}
|
|
3094
|
-
/**
|
|
3095
|
-
* Mark a handle destroyed. Fail-fasts on a second destroy
|
|
3096
|
-
* ('destroy-after-destroy') or on a handle issued by a different device
|
|
3097
|
-
* ('rhi-not-available'); otherwise flips the destroyed flag and returns ok.
|
|
3098
|
-
*/
|
|
3099
|
-
destroy(handle) {
|
|
3100
|
-
const ownership = this.validateOwnership(handle);
|
|
3101
|
-
if (!ownership.ok) return ownership;
|
|
3102
|
-
const destroyedCheck = isHandleDestroyed(handle);
|
|
3103
|
-
if (!destroyedCheck.ok) return destroyedCheck;
|
|
3104
|
-
ownership.value.destroyed = true;
|
|
3105
|
-
return ok(void 0);
|
|
3106
|
-
}
|
|
3107
|
-
/**
|
|
3108
|
-
* Report whether a handle has already been destroyed (true once destroy()
|
|
3109
|
-
* has flipped its flag). Used by command-stream methods that must not
|
|
3110
|
-
* consume a stale handle.
|
|
3111
|
-
*/
|
|
3112
|
-
isDestroyed(handle) {
|
|
3113
|
-
return readRecord(handle)?.destroyed === true;
|
|
3114
|
-
}
|
|
3115
|
-
/**
|
|
3116
|
-
* Validate that a handle was issued by THIS device. Returns the ledger row on
|
|
3117
|
-
* success so callers can mutate it (e.g. flip destroyed); returns
|
|
3118
|
-
* 'rhi-not-available' for a foreign handle (AC-09 — no silent pass).
|
|
3119
|
-
*/
|
|
3120
|
-
validateOwnership(handle) {
|
|
3121
|
-
const record = readRecord(handle);
|
|
3122
|
-
if (record === void 0 || record.sourceDeviceId !== this.deviceId) {
|
|
3123
|
-
return err(
|
|
3124
|
-
new RhiError({
|
|
3125
|
-
code: "rhi-not-available",
|
|
3126
|
-
expected: "handle was issued by this RhiNull device",
|
|
3127
|
-
hint: "do not pass a handle created on a different RhiNull device into this device; create resources on the device they are used with"
|
|
3128
|
-
})
|
|
3129
|
-
);
|
|
3130
|
-
}
|
|
3131
|
-
return ok(record);
|
|
3132
|
-
}
|
|
3133
|
-
/**
|
|
3134
|
-
* Return all ledger rows as a readonly array. M3 unit tests (w17) read this
|
|
3135
|
-
* to assert create/destroy pairing, BGL/PSO shape counts, and resource
|
|
3136
|
-
* lifecycle coverage (AC-05/06/07). Returns a snapshot of the current Map
|
|
3137
|
-
* so callers can iterate without a stale reference after further mutations.
|
|
3138
|
-
*/
|
|
3139
|
-
allRecords() {
|
|
3140
|
-
return [...this.records.values()];
|
|
3141
|
-
}
|
|
3142
|
-
/** Report the total number of records in the ledger. */
|
|
3143
|
-
recordCount() {
|
|
3144
|
-
return this.records.size;
|
|
3145
|
-
}
|
|
3146
|
-
};
|
|
3147
|
-
nextDeviceId = 0;
|
|
3148
|
-
RhiNullDevice = class {
|
|
3149
|
-
internalBookkeeper;
|
|
3150
|
-
nullQueue;
|
|
3151
|
-
encoderFactory;
|
|
3152
|
-
/** Per-frame total draw count across all pass encoders executed this frame
|
|
3153
|
-
* (aggregated by the command encoder on finish, then reset). M3 unit tests
|
|
3154
|
-
* (w17) read this to assert draw count >= 1 (AC-06). */
|
|
3155
|
-
totalDrawCount = 0;
|
|
3156
|
-
/** Per-frame total direct and indirect compute dispatch count. */
|
|
3157
|
-
totalDispatchCount = 0;
|
|
3158
|
-
/** Per-frame total bind group set count (AC-06 / AC-05 readback). */
|
|
3159
|
-
totalBindGroupCount = 0;
|
|
3160
|
-
/** Per-frame pass names executed this frame, in schedule order (AC-04). */
|
|
3161
|
-
framePassNames = [];
|
|
3162
|
-
/** The per-device handle ledger — exposed so M3 tests can assert create/destroy
|
|
3163
|
-
* pairing and BGL/PSO shape counts (AC-05/06/07). */
|
|
3164
|
-
get bookkeeper() {
|
|
3165
|
-
return this.internalBookkeeper;
|
|
3166
|
-
}
|
|
3167
|
-
constructor(queue, encoderFactory) {
|
|
3168
|
-
this.internalBookkeeper = new Bookkeeper(nextDeviceId++);
|
|
3169
|
-
this.nullQueue = queue;
|
|
3170
|
-
this.encoderFactory = encoderFactory;
|
|
3171
|
-
}
|
|
3172
|
-
get caps() {
|
|
3173
|
-
return {
|
|
3174
|
-
backendKind: "null",
|
|
3175
|
-
compute: true,
|
|
3176
|
-
timestampQuery: false,
|
|
3177
|
-
timestampPeriodNanoseconds: null,
|
|
3178
|
-
indirectDrawing: true,
|
|
3179
|
-
textureCompressionBc: false,
|
|
3180
|
-
textureCompressionEtc2: false,
|
|
3181
|
-
textureCompressionAstc: false,
|
|
3182
|
-
// 3 wgpu-native-only reserved flags stay false on non-native backends
|
|
3183
|
-
// (D-5); the headless backend is not a native runtime.
|
|
3184
|
-
multiDrawIndirect: false,
|
|
3185
|
-
pushConstants: false,
|
|
3186
|
-
textureBindingArray: false,
|
|
3187
|
-
samplerAliasing: true,
|
|
3188
|
-
firstInstanceIndirect: true,
|
|
3189
|
-
storageBuffer: true,
|
|
3190
|
-
storageTexture: true,
|
|
3191
|
-
rgba16floatRenderable: true,
|
|
3192
|
-
rg11b10ufloatRenderable: true,
|
|
3193
|
-
float32Filterable: true,
|
|
3194
|
-
maxColorAttachments: 8
|
|
3195
|
-
};
|
|
3196
|
-
}
|
|
3197
|
-
get features() {
|
|
3198
|
-
return EMPTY_FEATURES;
|
|
3199
|
-
}
|
|
3200
|
-
get limits() {
|
|
3201
|
-
return EMPTY_LIMITS;
|
|
3202
|
-
}
|
|
3203
|
-
get queue() {
|
|
3204
|
-
return this.nullQueue;
|
|
3205
|
-
}
|
|
3206
|
-
// forgeax-async-whitelist: dom-native — spec `GPUDevice.lost` Promise
|
|
3207
|
-
// passthrough. The headless backend never loses a device (no GPU), so the
|
|
3208
|
-
// Promise stays unsettled for the lifetime of the device, mirroring a live
|
|
3209
|
-
// device that never transitions to the lost state.
|
|
3210
|
-
get lost() {
|
|
3211
|
-
return NEVER;
|
|
3212
|
-
}
|
|
3213
|
-
createBuffer(_desc) {
|
|
3214
|
-
return ok(this.internalBookkeeper.register("Buffer"));
|
|
3215
|
-
}
|
|
3216
|
-
createTexture(_desc) {
|
|
3217
|
-
return ok(this.internalBookkeeper.register("Texture"));
|
|
3218
|
-
}
|
|
3219
|
-
destroyBuffer(buf) {
|
|
3220
|
-
return this.internalBookkeeper.destroy(buf);
|
|
3221
|
-
}
|
|
3222
|
-
destroyQuerySet(querySet) {
|
|
3223
|
-
return this.internalBookkeeper.destroy(querySet);
|
|
3224
|
-
}
|
|
3225
|
-
destroyTexture(tex) {
|
|
3226
|
-
return this.internalBookkeeper.destroy(tex);
|
|
3227
|
-
}
|
|
3228
|
-
createTextureView(_texture, _desc) {
|
|
3229
|
-
return ok(this.internalBookkeeper.register("TextureView"));
|
|
3230
|
-
}
|
|
3231
|
-
createSampler(_desc) {
|
|
3232
|
-
return ok(this.internalBookkeeper.register("Sampler"));
|
|
3233
|
-
}
|
|
3234
|
-
createBindGroupLayout(_desc) {
|
|
3235
|
-
return ok(this.internalBookkeeper.register("BindGroupLayout"));
|
|
3236
|
-
}
|
|
3237
|
-
createBindGroup(_desc) {
|
|
3238
|
-
return ok(this.internalBookkeeper.register("BindGroup"));
|
|
3239
|
-
}
|
|
3240
|
-
createPipelineLayout(_desc) {
|
|
3241
|
-
return ok(this.internalBookkeeper.register("PipelineLayout"));
|
|
3242
|
-
}
|
|
3243
|
-
createRenderPipeline(_desc) {
|
|
3244
|
-
return ok(this.makePipeline("RenderPipeline"));
|
|
3245
|
-
}
|
|
3246
|
-
createComputePipeline(_desc) {
|
|
3247
|
-
return ok(this.makePipeline("ComputePipeline"));
|
|
3248
|
-
}
|
|
3249
|
-
createQuerySet(desc) {
|
|
3250
|
-
if (desc.type === "timestamp") {
|
|
3251
|
-
return err(
|
|
3252
|
-
new RhiError({
|
|
3253
|
-
code: "feature-not-enabled",
|
|
3254
|
-
expected: "caps.timestampQuery === true (timestamp-query feature)",
|
|
3255
|
-
hint: "RhiNull is structural-only and cannot produce GPU timestamp ticks"
|
|
3256
|
-
})
|
|
3257
|
-
);
|
|
3258
|
-
}
|
|
3259
|
-
return ok(this.internalBookkeeper.register("QuerySet"));
|
|
3260
|
-
}
|
|
3261
|
-
createCommandEncoder(_desc) {
|
|
3262
|
-
return ok(this.encoderFactory(this.internalBookkeeper, this));
|
|
3263
|
-
}
|
|
3264
|
-
/**
|
|
3265
|
-
* Mint a pipeline handle whose object also carries the no-op
|
|
3266
|
-
* `getBindGroupLayout(index)` ops method (D-2). The prod auto-layout path
|
|
3267
|
-
* (debug-draw.ts) and the existing mock unit tests both call
|
|
3268
|
-
* `pipeline.getBindGroupLayout(n)`; returning a legal BindGroupLayout brand
|
|
3269
|
-
* (recorded in the ledger) keeps those consumers from crashing on a missing
|
|
3270
|
-
* method.
|
|
3271
|
-
*/
|
|
3272
|
-
makePipeline(kind) {
|
|
3273
|
-
const handle = this.internalBookkeeper.register(kind);
|
|
3274
|
-
const getBindGroupLayout = (_index) => this.internalBookkeeper.register("BindGroupLayout");
|
|
3275
|
-
return Object.assign(handle, { getBindGroupLayout });
|
|
3276
|
-
}
|
|
3277
|
-
};
|
|
3278
|
-
EMPTY_FEATURES = /* @__PURE__ */ new Set();
|
|
3279
|
-
EMPTY_LIMITS = {};
|
|
3280
|
-
NEVER = new Promise(() => {
|
|
3281
|
-
});
|
|
3282
|
-
RhiNullQueue = class {
|
|
3283
|
-
writeBuffer(_buffer, _bufferOffset, _data, _dataOffset, _size) {
|
|
3284
|
-
return ok(void 0);
|
|
3285
|
-
}
|
|
3286
|
-
writeTexture(_destination, _data, _dataLayout, _size) {
|
|
3287
|
-
return ok(void 0);
|
|
3288
|
-
}
|
|
3289
|
-
copyExternalImageToTexture(_source, _destination, _copySize) {
|
|
3290
|
-
return ok(void 0);
|
|
3291
|
-
}
|
|
3292
|
-
submit(_commandBuffers) {
|
|
3293
|
-
return ok(void 0);
|
|
3294
|
-
}
|
|
3295
|
-
// forgeax-async-whitelist: dom-native — spec `GPUQueue.onSubmittedWorkDone`
|
|
3296
|
-
// never rejects. The headless backend has no pending GPU work, so it resolves
|
|
3297
|
-
// immediately (AC-12: read-back idioms must not hang).
|
|
3298
|
-
onSubmittedWorkDone() {
|
|
3299
|
-
return Promise.resolve(void 0);
|
|
3300
|
-
}
|
|
3301
|
-
};
|
|
3302
|
-
RhiNullAdapter = class {
|
|
3303
|
-
features = /* @__PURE__ */ new Set();
|
|
3304
|
-
limits = {};
|
|
3305
|
-
// forgeax-async-whitelist is not needed: this returns Promise<Result<...>>
|
|
3306
|
-
// per the spec contract; never rejects.
|
|
3307
|
-
requestDevice(_opts) {
|
|
3308
|
-
const device = new RhiNullDevice(
|
|
3309
|
-
new RhiNullQueue(),
|
|
3310
|
-
(bookkeeper, dev) => new RhiNullCommandEncoder(bookkeeper, dev)
|
|
3311
|
-
);
|
|
3312
|
-
return Promise.resolve(ok(device));
|
|
3313
|
-
}
|
|
3314
|
-
};
|
|
3315
|
-
RhiNullCanvasContext = class {
|
|
3316
|
-
configure(_desc) {
|
|
3317
|
-
return ok(void 0);
|
|
3318
|
-
}
|
|
3319
|
-
unconfigure() {
|
|
3320
|
-
}
|
|
3321
|
-
getConfiguration() {
|
|
3322
|
-
return void 0;
|
|
3379
|
+
}
|
|
3380
|
+
}
|
|
3381
|
+
async function pluginUninstallCommand(options) {
|
|
3382
|
+
const root = resolve(options.root ?? process.cwd());
|
|
3383
|
+
const manifest = await readManifest2(root);
|
|
3384
|
+
if (!manifest.ok) return manifest;
|
|
3385
|
+
const parsed = GameProjectSchema.safeParse(manifest.value.value);
|
|
3386
|
+
if (!parsed.success) {
|
|
3387
|
+
return {
|
|
3388
|
+
ok: false,
|
|
3389
|
+
error: {
|
|
3390
|
+
code: "plugin-manifest-invalid",
|
|
3391
|
+
expected: "forge.json to satisfy GameProjectSchema",
|
|
3392
|
+
hint: "Repair the manifest before uninstalling a plugin.",
|
|
3393
|
+
detail: { issues: parsed.error.issues }
|
|
3323
3394
|
}
|
|
3324
|
-
|
|
3325
|
-
|
|
3395
|
+
};
|
|
3396
|
+
}
|
|
3397
|
+
const entries2 = parsed.data.plugins ?? [];
|
|
3398
|
+
if (!allEntries(entries2).some((entry) => entry.id === options.id)) {
|
|
3399
|
+
return {
|
|
3400
|
+
ok: false,
|
|
3401
|
+
error: {
|
|
3402
|
+
code: "plugin-entry-missing",
|
|
3403
|
+
expected: "the plugin Entry id to exist",
|
|
3404
|
+
hint: "Inspect forge.json#plugins and pass an installed Entry id.",
|
|
3405
|
+
detail: { id: options.id }
|
|
3326
3406
|
}
|
|
3327
3407
|
};
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3408
|
+
}
|
|
3409
|
+
const next = { ...parsed.data, plugins: withoutEntry(entries2, options.id) };
|
|
3410
|
+
if (options.dryRun === true) return { ok: true, value: { root, manifest: next } };
|
|
3411
|
+
try {
|
|
3412
|
+
await writeManifest(root, next);
|
|
3413
|
+
await mutateDependency(root, "remove", options.dependency);
|
|
3414
|
+
return { ok: true, value: { root, id: options.id } };
|
|
3415
|
+
} catch (cause) {
|
|
3416
|
+
await writeFile(resolve(root, "forge.json"), manifest.value.raw);
|
|
3417
|
+
return {
|
|
3418
|
+
ok: false,
|
|
3419
|
+
error: {
|
|
3420
|
+
code: "plugin-uninstall-failed",
|
|
3421
|
+
expected: "Entry and dependency removal to commit together",
|
|
3422
|
+
hint: "Inspect the package-manager failure; the original forge.json was restored.",
|
|
3423
|
+
detail: { reason: cause instanceof Error ? cause.message : String(cause) }
|
|
3424
|
+
}
|
|
3332
3425
|
};
|
|
3333
3426
|
}
|
|
3427
|
+
}
|
|
3428
|
+
var execFileAsync3;
|
|
3429
|
+
var init_plugin_authoring = __esm({
|
|
3430
|
+
"src/plugin-authoring.ts"() {
|
|
3431
|
+
execFileAsync3 = promisify(execFile);
|
|
3432
|
+
}
|
|
3334
3433
|
});
|
|
3335
3434
|
function createCliRhiDebugOperationContext() {
|
|
3336
3435
|
return {
|
|
3337
3436
|
captureFrame: async () => err(
|
|
3338
3437
|
createRhiDebugError("capture-unavailable", {
|
|
3339
3438
|
stage: "capture",
|
|
3340
|
-
cause: "the standalone CLI has no live App capture provider"
|
|
3439
|
+
cause: "the standalone CLI has no live App capture provider; start a recorder-enabled live host and invoke its rhiCapture root"
|
|
3341
3440
|
})
|
|
3342
3441
|
),
|
|
3343
3442
|
async readArtifact(artifact) {
|
|
@@ -3369,26 +3468,62 @@ function createCliRhiDebugOperationContext() {
|
|
|
3369
3468
|
};
|
|
3370
3469
|
}
|
|
3371
3470
|
},
|
|
3372
|
-
async createReplayBackend() {
|
|
3471
|
+
async createReplayBackend(tape) {
|
|
3472
|
+
let createDawn;
|
|
3473
|
+
let gpuGlobals;
|
|
3474
|
+
try {
|
|
3475
|
+
const dawn = await import('webgpu');
|
|
3476
|
+
createDawn = dawn.create;
|
|
3477
|
+
gpuGlobals = dawn.globals;
|
|
3478
|
+
} catch (cause) {
|
|
3479
|
+
return {
|
|
3480
|
+
ok: false,
|
|
3481
|
+
error: {
|
|
3482
|
+
code: "replay-backend-unavailable",
|
|
3483
|
+
expected: "the Dawn WebGPU provider to load",
|
|
3484
|
+
hint: "Install the DevKit runtime closure and retry rhi.inspect.",
|
|
3485
|
+
detail: {
|
|
3486
|
+
stage: "provider",
|
|
3487
|
+
cause: cause instanceof Error ? cause.message : String(cause)
|
|
3488
|
+
}
|
|
3489
|
+
}
|
|
3490
|
+
};
|
|
3491
|
+
}
|
|
3492
|
+
Object.assign(globalThis, gpuGlobals);
|
|
3493
|
+
const gpu = createDawn([]);
|
|
3494
|
+
if (!("navigator" in globalThis) || globalThis.navigator === void 0) {
|
|
3495
|
+
Object.defineProperty(globalThis, "navigator", {
|
|
3496
|
+
value: {},
|
|
3497
|
+
configurable: true,
|
|
3498
|
+
writable: true
|
|
3499
|
+
});
|
|
3500
|
+
}
|
|
3501
|
+
Object.defineProperty(globalThis.navigator, "gpu", {
|
|
3502
|
+
value: gpu,
|
|
3503
|
+
configurable: true,
|
|
3504
|
+
writable: true
|
|
3505
|
+
});
|
|
3373
3506
|
const adapter = await rhi.requestAdapter();
|
|
3374
3507
|
if (!adapter.ok) {
|
|
3375
3508
|
return {
|
|
3376
3509
|
ok: false,
|
|
3377
3510
|
error: {
|
|
3378
3511
|
code: "replay-backend-unavailable",
|
|
3379
|
-
expected: "a fresh
|
|
3512
|
+
expected: "a fresh Dawn WebGPU adapter",
|
|
3380
3513
|
hint: adapter.error.hint,
|
|
3381
3514
|
detail: { stage: "adapter", cause: adapter.error.hint }
|
|
3382
3515
|
}
|
|
3383
3516
|
};
|
|
3384
3517
|
}
|
|
3385
|
-
const device = await adapter.value.requestDevice(
|
|
3518
|
+
const device = await adapter.value.requestDevice(
|
|
3519
|
+
replayDeviceRequest(tape, adapter.value.features, adapter.value.limits)
|
|
3520
|
+
);
|
|
3386
3521
|
if (!device.ok) {
|
|
3387
3522
|
return {
|
|
3388
3523
|
ok: false,
|
|
3389
3524
|
error: {
|
|
3390
3525
|
code: "replay-backend-unavailable",
|
|
3391
|
-
expected: "a fresh
|
|
3526
|
+
expected: "a fresh Dawn WebGPU device satisfying the recorded tape",
|
|
3392
3527
|
hint: device.error.hint,
|
|
3393
3528
|
detail: { stage: "device", cause: device.error.hint }
|
|
3394
3529
|
}
|
|
@@ -3400,7 +3535,6 @@ function createCliRhiDebugOperationContext() {
|
|
|
3400
3535
|
}
|
|
3401
3536
|
var init_cli_context = __esm({
|
|
3402
3537
|
"src/rhi-debug/cli-context.ts"() {
|
|
3403
|
-
init_dist3();
|
|
3404
3538
|
}
|
|
3405
3539
|
});
|
|
3406
3540
|
function createRhiDebugOperationContext(host) {
|
|
@@ -3427,6 +3561,9 @@ function renderRhiDebugHelp() {
|
|
|
3427
3561
|
return [
|
|
3428
3562
|
"forgeax run <operation>",
|
|
3429
3563
|
...discoverRhiDebugOperations().map((operation) => ` ${operation.name}: ${operation.summary}`),
|
|
3564
|
+
"Usage:",
|
|
3565
|
+
" forgeax run rhi.summary --artifact PATH --digest SHA256 --json",
|
|
3566
|
+
" forgeax run rhi.inspect --artifact PATH --digest SHA256 --work-index N --fields pipeline,bindings,pixels --json",
|
|
3430
3567
|
"ArtifactRef schema:",
|
|
3431
3568
|
JSON.stringify(RHI_DEBUG_OPERATION_MANIFEST.artifactRefSchema)
|
|
3432
3569
|
].join("\n");
|
|
@@ -3554,7 +3691,7 @@ async function runRhiDebugOperation(name, input, context) {
|
|
|
3554
3691
|
"Provide a fresh device and shader factory before running rhi.inspect."
|
|
3555
3692
|
);
|
|
3556
3693
|
}
|
|
3557
|
-
const backend = await context.createReplayBackend();
|
|
3694
|
+
const backend = await context.createReplayBackend(decoded.value);
|
|
3558
3695
|
if (!backend.ok) return backend;
|
|
3559
3696
|
const opened = await openReplay(decoded.value, backend.value);
|
|
3560
3697
|
if (!opened.ok) return { ok: false, error: coreError(opened.error) };
|
|
@@ -3646,7 +3783,10 @@ var init_operations = __esm({
|
|
|
3646
3783
|
properties: {
|
|
3647
3784
|
artifact: artifactRefSchema,
|
|
3648
3785
|
workIndex: { type: "integer", minimum: 0 },
|
|
3649
|
-
fields: {
|
|
3786
|
+
fields: {
|
|
3787
|
+
type: "array",
|
|
3788
|
+
items: { type: "string", enum: ["bindings", "pipeline", "pixels"] }
|
|
3789
|
+
}
|
|
3650
3790
|
},
|
|
3651
3791
|
required: ["artifact", "workIndex"],
|
|
3652
3792
|
additionalProperties: false
|
|
@@ -3738,7 +3878,15 @@ async function sdkInstallCommand(options) {
|
|
|
3738
3878
|
await rename(resolve(staging, name), resolve(root, name));
|
|
3739
3879
|
}
|
|
3740
3880
|
}
|
|
3741
|
-
return {
|
|
3881
|
+
return {
|
|
3882
|
+
ok: true,
|
|
3883
|
+
value: {
|
|
3884
|
+
root,
|
|
3885
|
+
sdkVersion: version,
|
|
3886
|
+
source: "@forgeax/engine-sdk",
|
|
3887
|
+
next: { cwd: root, argv: ["node", "./bin/forgeax.mjs", "init"] }
|
|
3888
|
+
}
|
|
3889
|
+
};
|
|
3742
3890
|
} catch (cause) {
|
|
3743
3891
|
return { ok: false, error: commandError(cause, "sdk-install-failed") };
|
|
3744
3892
|
} finally {
|
|
@@ -3787,6 +3935,639 @@ var init_shader_check = __esm({
|
|
|
3787
3935
|
init_project();
|
|
3788
3936
|
}
|
|
3789
3937
|
});
|
|
3938
|
+
function fail(code, expected, hint, detail = {}) {
|
|
3939
|
+
throw new SoftwareCaptureError(code, expected, hint, detail);
|
|
3940
|
+
}
|
|
3941
|
+
async function pathExists2(path) {
|
|
3942
|
+
try {
|
|
3943
|
+
await access(path);
|
|
3944
|
+
return true;
|
|
3945
|
+
} catch {
|
|
3946
|
+
return false;
|
|
3947
|
+
}
|
|
3948
|
+
}
|
|
3949
|
+
async function reservePort() {
|
|
3950
|
+
const reservation = createServer$1();
|
|
3951
|
+
await new Promise((resolveListen, rejectListen) => {
|
|
3952
|
+
reservation.once("error", rejectListen);
|
|
3953
|
+
reservation.listen(0, "127.0.0.1", resolveListen);
|
|
3954
|
+
});
|
|
3955
|
+
const address = reservation.address();
|
|
3956
|
+
const port = typeof address === "object" && address !== null ? address.port : 0;
|
|
3957
|
+
await new Promise((resolveClose, rejectClose) => {
|
|
3958
|
+
reservation.close((error) => error === void 0 ? resolveClose() : rejectClose(error));
|
|
3959
|
+
});
|
|
3960
|
+
if (port === 0) throw new Error("OS did not assign an ephemeral capture port");
|
|
3961
|
+
return port;
|
|
3962
|
+
}
|
|
3963
|
+
async function stopProcess(child) {
|
|
3964
|
+
if (child.exitCode !== null || child.signalCode !== null) return;
|
|
3965
|
+
await new Promise((resolveStop) => {
|
|
3966
|
+
const timeout = setTimeout(() => {
|
|
3967
|
+
child.kill("SIGKILL");
|
|
3968
|
+
resolveStop();
|
|
3969
|
+
}, 2e3);
|
|
3970
|
+
child.once("exit", () => {
|
|
3971
|
+
clearTimeout(timeout);
|
|
3972
|
+
resolveStop();
|
|
3973
|
+
});
|
|
3974
|
+
child.kill("SIGTERM");
|
|
3975
|
+
});
|
|
3976
|
+
}
|
|
3977
|
+
async function startVirtualDisplay(width, height) {
|
|
3978
|
+
if (process.platform !== "linux" || process.env.DISPLAY !== void 0) {
|
|
3979
|
+
return {
|
|
3980
|
+
...process.env.DISPLAY === void 0 ? {} : { value: process.env.DISPLAY },
|
|
3981
|
+
async close() {
|
|
3982
|
+
}
|
|
3983
|
+
};
|
|
3984
|
+
}
|
|
3985
|
+
for (let offset = 0; offset < 100; offset += 1) {
|
|
3986
|
+
const number = 90 + (process.pid + offset) % 100;
|
|
3987
|
+
const socket = `/tmp/.X11-unix/X${number}`;
|
|
3988
|
+
const lock = `/tmp/.X${number}-lock`;
|
|
3989
|
+
if (await pathExists2(socket) || await pathExists2(lock)) continue;
|
|
3990
|
+
const display = `:${number}`;
|
|
3991
|
+
const child = spawn(
|
|
3992
|
+
"Xvfb",
|
|
3993
|
+
[display, "-screen", "0", `${width}x${height}x24`, "-nolisten", "tcp"],
|
|
3994
|
+
{ stdio: ["ignore", "ignore", "pipe"] }
|
|
3995
|
+
);
|
|
3996
|
+
let diagnostic = "";
|
|
3997
|
+
child.stderr?.setEncoding("utf8");
|
|
3998
|
+
child.stderr?.on("data", (chunk) => {
|
|
3999
|
+
diagnostic += chunk;
|
|
4000
|
+
});
|
|
4001
|
+
try {
|
|
4002
|
+
await new Promise((resolveReady, rejectReady) => {
|
|
4003
|
+
const timeout = setTimeout(
|
|
4004
|
+
() => rejectReady(new Error(`Xvfb did not create ${socket}: ${diagnostic.trim()}`)),
|
|
4005
|
+
5e3
|
|
4006
|
+
);
|
|
4007
|
+
const poll = setInterval(() => {
|
|
4008
|
+
void pathExists2(socket).then((exists) => {
|
|
4009
|
+
if (!exists) return;
|
|
4010
|
+
clearTimeout(timeout);
|
|
4011
|
+
clearInterval(poll);
|
|
4012
|
+
resolveReady();
|
|
4013
|
+
});
|
|
4014
|
+
}, 50);
|
|
4015
|
+
child.once("error", (error) => {
|
|
4016
|
+
clearTimeout(timeout);
|
|
4017
|
+
clearInterval(poll);
|
|
4018
|
+
rejectReady(error);
|
|
4019
|
+
});
|
|
4020
|
+
child.once("exit", (code, signal) => {
|
|
4021
|
+
clearTimeout(timeout);
|
|
4022
|
+
clearInterval(poll);
|
|
4023
|
+
rejectReady(
|
|
4024
|
+
new Error(`Xvfb exited before ready (${code ?? signal}): ${diagnostic.trim()}`)
|
|
4025
|
+
);
|
|
4026
|
+
});
|
|
4027
|
+
});
|
|
4028
|
+
return { value: display, close: () => stopProcess(child) };
|
|
4029
|
+
} catch (cause) {
|
|
4030
|
+
await stopProcess(child);
|
|
4031
|
+
throw cause;
|
|
4032
|
+
}
|
|
4033
|
+
}
|
|
4034
|
+
throw new Error("no free X11 display number was available for browser capture");
|
|
4035
|
+
}
|
|
4036
|
+
async function lavapipeIcd() {
|
|
4037
|
+
for (const candidate of [
|
|
4038
|
+
"/usr/share/vulkan/icd.d/lvp_icd.x86_64.json",
|
|
4039
|
+
"/usr/share/vulkan/icd.d/lvp_icd.aarch64.json"
|
|
4040
|
+
]) {
|
|
4041
|
+
if (await pathExists2(candidate)) return candidate;
|
|
4042
|
+
}
|
|
4043
|
+
return void 0;
|
|
4044
|
+
}
|
|
4045
|
+
function evidencePath(output) {
|
|
4046
|
+
return output.toLowerCase().endsWith(".png") ? `${output.slice(0, -4)}.json` : `${output}.json`;
|
|
4047
|
+
}
|
|
4048
|
+
function newRunId() {
|
|
4049
|
+
return `${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}-${randomUUID().slice(0, 8)}`;
|
|
4050
|
+
}
|
|
4051
|
+
function checkpointSlug(checkpoint) {
|
|
4052
|
+
const value = (checkpoint ?? "capture").trim().toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
|
|
4053
|
+
return value.length === 0 ? "capture" : value;
|
|
4054
|
+
}
|
|
4055
|
+
function captureDigest(bytes) {
|
|
4056
|
+
return `sha256:${createHash("sha256").update(bytes).digest("hex")}`;
|
|
4057
|
+
}
|
|
4058
|
+
function observedBackend(runtime) {
|
|
4059
|
+
if (runtime.adapter === null) return "unknown";
|
|
4060
|
+
return isSoftwareAdapter(runtime) ? "software" : "hardware";
|
|
4061
|
+
}
|
|
4062
|
+
async function resolveBrowserExecutable(requested) {
|
|
4063
|
+
const candidates = [
|
|
4064
|
+
requested,
|
|
4065
|
+
process.env.FORGEAX_BROWSER_EXECUTABLE,
|
|
4066
|
+
"/opt/google/chrome-beta/chrome",
|
|
4067
|
+
"/usr/bin/google-chrome",
|
|
4068
|
+
"/usr/bin/google-chrome-stable",
|
|
4069
|
+
"/usr/bin/chromium",
|
|
4070
|
+
"/usr/bin/chromium-browser"
|
|
4071
|
+
].filter((candidate) => candidate !== void 0 && candidate.length > 0);
|
|
4072
|
+
for (const candidate of candidates) {
|
|
4073
|
+
if (await pathExists2(candidate)) return candidate;
|
|
4074
|
+
}
|
|
4075
|
+
return void 0;
|
|
4076
|
+
}
|
|
4077
|
+
function browserLaunchArgs(backend) {
|
|
4078
|
+
const common = [
|
|
4079
|
+
"--enable-unsafe-webgpu",
|
|
4080
|
+
"--ignore-gpu-blocklist",
|
|
4081
|
+
"--disable-gpu-driver-bug-workarounds",
|
|
4082
|
+
"--force-color-profile=srgb",
|
|
4083
|
+
"--force-device-scale-factor=1"
|
|
4084
|
+
];
|
|
4085
|
+
if (backend !== "software") return common;
|
|
4086
|
+
return [
|
|
4087
|
+
...common,
|
|
4088
|
+
"--enable-features=Vulkan",
|
|
4089
|
+
"--use-vulkan=swiftshader",
|
|
4090
|
+
"--use-angle=swiftshader",
|
|
4091
|
+
"--disable-vulkan-surface"
|
|
4092
|
+
];
|
|
4093
|
+
}
|
|
4094
|
+
function summarizeCapturePixels(rgba, width, height) {
|
|
4095
|
+
const pixelCount = width * height;
|
|
4096
|
+
const stride = Math.max(1, Math.floor(pixelCount / 4096));
|
|
4097
|
+
const histogram = new Uint32Array(16);
|
|
4098
|
+
let lumaMin = 255;
|
|
4099
|
+
let lumaMax = 0;
|
|
4100
|
+
let sampledPixels = 0;
|
|
4101
|
+
for (let pixel = 0; pixel < pixelCount; pixel += stride) {
|
|
4102
|
+
const offset = pixel * 4;
|
|
4103
|
+
const red = rgba[offset];
|
|
4104
|
+
const green = rgba[offset + 1];
|
|
4105
|
+
const blue = rgba[offset + 2];
|
|
4106
|
+
if (red === void 0 || green === void 0 || blue === void 0) break;
|
|
4107
|
+
const luma = Math.round((54 * red + 183 * green + 19 * blue) / 256);
|
|
4108
|
+
lumaMin = Math.min(lumaMin, luma);
|
|
4109
|
+
lumaMax = Math.max(lumaMax, luma);
|
|
4110
|
+
const bucket = Math.min(15, Math.floor(luma / 16));
|
|
4111
|
+
histogram[bucket] = (histogram[bucket] ?? 0) + 1;
|
|
4112
|
+
sampledPixels += 1;
|
|
4113
|
+
}
|
|
4114
|
+
const dominantPixels = histogram.reduce((largest, count) => Math.max(largest, count), 0);
|
|
4115
|
+
const varyingPixels = sampledPixels - dominantPixels;
|
|
4116
|
+
const lumaRange = lumaMax - lumaMin;
|
|
4117
|
+
const requiredVariation = Math.min(sampledPixels, Math.max(8, Math.ceil(sampledPixels * 2e-3)));
|
|
4118
|
+
return {
|
|
4119
|
+
width,
|
|
4120
|
+
height,
|
|
4121
|
+
sampledPixels,
|
|
4122
|
+
lumaMin,
|
|
4123
|
+
lumaMax,
|
|
4124
|
+
lumaRange,
|
|
4125
|
+
varyingPixels,
|
|
4126
|
+
rendered: lumaRange >= 8 && varyingPixels >= requiredVariation
|
|
4127
|
+
};
|
|
4128
|
+
}
|
|
4129
|
+
function inspectCapturePng(png) {
|
|
4130
|
+
const decoded = parseImage(png, "image/png", { mipmap: false });
|
|
4131
|
+
if (!decoded.ok) throw decoded.error;
|
|
4132
|
+
return summarizeCapturePixels(decoded.value.bytes, decoded.value.width, decoded.value.height);
|
|
4133
|
+
}
|
|
4134
|
+
async function screenshotWithWitness(page) {
|
|
4135
|
+
const canvasPng = await page.locator("canvas").first().screenshot({
|
|
4136
|
+
type: "png",
|
|
4137
|
+
style: "* { visibility: hidden !important; } canvas { visibility: visible !important; }"
|
|
4138
|
+
});
|
|
4139
|
+
const png = await page.screenshot({ type: "png", caret: "hide" });
|
|
4140
|
+
return { png, pixels: inspectCapturePng(canvasPng) };
|
|
4141
|
+
}
|
|
4142
|
+
async function waitForCompositor(page) {
|
|
4143
|
+
await page.evaluate(async () => {
|
|
4144
|
+
await document.fonts.ready;
|
|
4145
|
+
await new Promise((resolveFrame) => requestAnimationFrame(() => resolveFrame()));
|
|
4146
|
+
await new Promise((resolveFrame) => requestAnimationFrame(() => resolveFrame()));
|
|
4147
|
+
});
|
|
4148
|
+
}
|
|
4149
|
+
async function runtimeWitness(page) {
|
|
4150
|
+
return page.evaluate(async (datasetKey) => {
|
|
4151
|
+
const canvas = document.querySelector("canvas");
|
|
4152
|
+
const uiRoot = document.querySelector("#game-ui");
|
|
4153
|
+
const shadowHosts = [...uiRoot?.querySelectorAll("*") ?? []].filter(
|
|
4154
|
+
(element) => element.shadowRoot?.childElementCount !== 0
|
|
4155
|
+
);
|
|
4156
|
+
let adapter = null;
|
|
4157
|
+
let adapterError = null;
|
|
4158
|
+
try {
|
|
4159
|
+
const gpuAdapter = await navigator.gpu?.requestAdapter();
|
|
4160
|
+
if (gpuAdapter === null || gpuAdapter === void 0) {
|
|
4161
|
+
adapterError = "navigator.gpu.requestAdapter() returned null";
|
|
4162
|
+
} else {
|
|
4163
|
+
const info = gpuAdapter.info;
|
|
4164
|
+
adapter = {
|
|
4165
|
+
vendor: info.vendor,
|
|
4166
|
+
architecture: info.architecture,
|
|
4167
|
+
device: info.device,
|
|
4168
|
+
description: info.description
|
|
4169
|
+
};
|
|
4170
|
+
}
|
|
4171
|
+
} catch (cause) {
|
|
4172
|
+
adapterError = String(cause);
|
|
4173
|
+
}
|
|
4174
|
+
const frameId = Number(document.documentElement.dataset[datasetKey]);
|
|
4175
|
+
return {
|
|
4176
|
+
title: document.title,
|
|
4177
|
+
canvas: canvas instanceof HTMLCanvasElement ? { width: canvas.width, height: canvas.height } : null,
|
|
4178
|
+
domUi: {
|
|
4179
|
+
rootChildren: uiRoot?.childElementCount ?? 0,
|
|
4180
|
+
openShadowRoots: shadowHosts.length,
|
|
4181
|
+
textWitness: shadowHosts.map((host) => host.shadowRoot?.textContent?.replace(/\s+/g, " ").trim() ?? "").filter((text) => text.length > 0).join(" | ").slice(0, 500)
|
|
4182
|
+
},
|
|
4183
|
+
adapter,
|
|
4184
|
+
adapterError,
|
|
4185
|
+
engineFrameId: Number.isSafeInteger(frameId) && frameId > 0 ? frameId : null,
|
|
4186
|
+
captureReady: document.documentElement.dataset.forgeaxCaptureReady ?? null,
|
|
4187
|
+
userAgent: navigator.userAgent
|
|
4188
|
+
};
|
|
4189
|
+
}, FORGEAX_FRAME_SUBMITTED_DATASET);
|
|
4190
|
+
}
|
|
4191
|
+
function isSoftwareAdapter(runtime) {
|
|
4192
|
+
const witness = Object.values(runtime.adapter ?? {}).map((value) => String(value).toLowerCase()).join(" ");
|
|
4193
|
+
return ["swiftshader", "llvmpipe", "lavapipe", "software"].some(
|
|
4194
|
+
(token) => witness.includes(token)
|
|
4195
|
+
);
|
|
4196
|
+
}
|
|
4197
|
+
async function writeRunReport(report) {
|
|
4198
|
+
await mkdir(dirname(report.report), { recursive: true });
|
|
4199
|
+
await writeFile(report.report, `${JSON.stringify(report, null, 2)}
|
|
4200
|
+
`, "utf8");
|
|
4201
|
+
}
|
|
4202
|
+
async function openBrowserCaptureSession(root, options) {
|
|
4203
|
+
const backend = options.backend ?? (options.software === true ? "software" : "auto");
|
|
4204
|
+
if (options.software === true && options.backend !== void 0 && options.backend !== "software") {
|
|
4205
|
+
fail(
|
|
4206
|
+
"browser-capture-option-conflict",
|
|
4207
|
+
"software and backend options to describe the same capture lane",
|
|
4208
|
+
"Use either software: true or backend: software; use backend: auto for a portable capture.",
|
|
4209
|
+
{ software: options.software, backend: options.backend }
|
|
4210
|
+
);
|
|
4211
|
+
}
|
|
4212
|
+
const facts = await readProjectFacts(root);
|
|
4213
|
+
if (!facts.ok) {
|
|
4214
|
+
throw new SoftwareCaptureError(
|
|
4215
|
+
facts.error.code,
|
|
4216
|
+
facts.error.expected,
|
|
4217
|
+
facts.error.hint,
|
|
4218
|
+
facts.error.detail
|
|
4219
|
+
);
|
|
4220
|
+
}
|
|
4221
|
+
const width = options.width ?? 1280;
|
|
4222
|
+
const height = options.height ?? 720;
|
|
4223
|
+
const browserPath = await resolveBrowserExecutable(options.browser);
|
|
4224
|
+
if (options.browser !== void 0 && browserPath === void 0) {
|
|
4225
|
+
fail(
|
|
4226
|
+
"browser-capture-browser-missing",
|
|
4227
|
+
`a runnable browser at ${options.browser}`,
|
|
4228
|
+
"Install Chrome/Chromium or pass --browser with an executable path.",
|
|
4229
|
+
{ browser: options.browser }
|
|
4230
|
+
);
|
|
4231
|
+
}
|
|
4232
|
+
const id = options.runId ?? newRunId();
|
|
4233
|
+
const outputDirectory = resolve(
|
|
4234
|
+
facts.value.root,
|
|
4235
|
+
options.outputDir ?? `artifacts/playthrough/${id}`
|
|
4236
|
+
);
|
|
4237
|
+
const reportPath = resolve(
|
|
4238
|
+
facts.value.root,
|
|
4239
|
+
options.report ?? resolve(outputDirectory, "run.json")
|
|
4240
|
+
);
|
|
4241
|
+
const port = options.port === void 0 || options.port === 0 ? await reservePort() : options.port;
|
|
4242
|
+
let server;
|
|
4243
|
+
let browser;
|
|
4244
|
+
let page;
|
|
4245
|
+
let display;
|
|
4246
|
+
try {
|
|
4247
|
+
const { createServer: createServer5 } = await import('vite');
|
|
4248
|
+
server = await createServer5(
|
|
4249
|
+
await createViteConfig(facts.value, "serve", "/", {
|
|
4250
|
+
server: { port, strictPort: true }
|
|
4251
|
+
})
|
|
4252
|
+
);
|
|
4253
|
+
await server.listen(port);
|
|
4254
|
+
display = await startVirtualDisplay(width, height);
|
|
4255
|
+
const captureDisplay = display;
|
|
4256
|
+
const icd = await lavapipeIcd();
|
|
4257
|
+
const consoleErrors = [];
|
|
4258
|
+
const pageErrors = [];
|
|
4259
|
+
const hasDisplay = display.value !== void 0;
|
|
4260
|
+
const headless = options.headless ?? (!hasDisplay && process.platform !== "linux");
|
|
4261
|
+
const baseUrl = new URL(server.resolvedUrls?.local[0] ?? `http://127.0.0.1:${port}/`);
|
|
4262
|
+
const openPage = async (launchBackend) => {
|
|
4263
|
+
const browserEnvironment = {
|
|
4264
|
+
...Object.fromEntries(
|
|
4265
|
+
Object.entries(process.env).filter(
|
|
4266
|
+
(entry) => entry[1] !== void 0
|
|
4267
|
+
)
|
|
4268
|
+
),
|
|
4269
|
+
...launchBackend === "software" ? { LIBGL_ALWAYS_SOFTWARE: "1" } : {},
|
|
4270
|
+
...captureDisplay.value === void 0 ? {} : { DISPLAY: captureDisplay.value }
|
|
4271
|
+
};
|
|
4272
|
+
const launchOptions = {
|
|
4273
|
+
headless,
|
|
4274
|
+
env: browserEnvironment,
|
|
4275
|
+
args: browserLaunchArgs(launchBackend),
|
|
4276
|
+
...browserPath === void 0 ? {} : { executablePath: browserPath }
|
|
4277
|
+
};
|
|
4278
|
+
browser = await chromium.launch(launchOptions);
|
|
4279
|
+
page = await browser.newPage({
|
|
4280
|
+
viewport: { width, height },
|
|
4281
|
+
screen: { width, height },
|
|
4282
|
+
deviceScaleFactor: 1,
|
|
4283
|
+
colorScheme: "light",
|
|
4284
|
+
locale: "en-US",
|
|
4285
|
+
timezoneId: "UTC",
|
|
4286
|
+
serviceWorkers: "block"
|
|
4287
|
+
});
|
|
4288
|
+
page.on("console", (message) => {
|
|
4289
|
+
if (message.type() === "error") consoleErrors.push(message.text());
|
|
4290
|
+
});
|
|
4291
|
+
page.on("pageerror", (error) => pageErrors.push(String(error)));
|
|
4292
|
+
const captureUrl2 = new URL(baseUrl.href);
|
|
4293
|
+
if (options.deterministic === true) captureUrl2.searchParams.set("forgeaxCapture", "1");
|
|
4294
|
+
await page.goto(captureUrl2.href, { waitUntil: "domcontentloaded", timeout: 12e4 });
|
|
4295
|
+
await page.waitForFunction(
|
|
4296
|
+
(requireUi) => {
|
|
4297
|
+
const canvas = document.querySelector("canvas");
|
|
4298
|
+
if (!(canvas instanceof HTMLCanvasElement) || canvas.width <= 0 || canvas.height <= 0)
|
|
4299
|
+
return false;
|
|
4300
|
+
if (!requireUi) return true;
|
|
4301
|
+
const uiRoot = document.querySelector("#game-ui");
|
|
4302
|
+
return uiRoot !== null && uiRoot.childElementCount > 0;
|
|
4303
|
+
},
|
|
4304
|
+
options.requireUi === true,
|
|
4305
|
+
{ timeout: 12e4 }
|
|
4306
|
+
);
|
|
4307
|
+
return runtimeWitness(page);
|
|
4308
|
+
};
|
|
4309
|
+
const initialLaunchBackend = backend === "software" ? "software" : "hardware";
|
|
4310
|
+
let runtime = await openPage(initialLaunchBackend);
|
|
4311
|
+
let observed = observedBackend(runtime);
|
|
4312
|
+
if (backend === "auto" && runtime.adapter === null) {
|
|
4313
|
+
await browser?.close();
|
|
4314
|
+
browser = void 0;
|
|
4315
|
+
page = void 0;
|
|
4316
|
+
consoleErrors.length = 0;
|
|
4317
|
+
pageErrors.length = 0;
|
|
4318
|
+
runtime = await openPage("software");
|
|
4319
|
+
observed = observedBackend(runtime);
|
|
4320
|
+
}
|
|
4321
|
+
const captureUrl = new URL(baseUrl.href);
|
|
4322
|
+
if (options.deterministic === true) captureUrl.searchParams.set("forgeaxCapture", "1");
|
|
4323
|
+
if (page === void 0 || browser === void 0) {
|
|
4324
|
+
fail(
|
|
4325
|
+
"browser-capture-browser-unavailable",
|
|
4326
|
+
"the browser capture session to open",
|
|
4327
|
+
"Inspect the browser launch diagnostic and retry with --browser or --headless."
|
|
4328
|
+
);
|
|
4329
|
+
}
|
|
4330
|
+
const captures = [];
|
|
4331
|
+
let closed = false;
|
|
4332
|
+
const report = {
|
|
4333
|
+
schemaVersion: "2.0.0",
|
|
4334
|
+
runId: id,
|
|
4335
|
+
ok: false,
|
|
4336
|
+
mode: "browser-compositor",
|
|
4337
|
+
root: facts.value.root,
|
|
4338
|
+
url: captureUrl.href,
|
|
4339
|
+
report: reportPath,
|
|
4340
|
+
backendRequested: backend,
|
|
4341
|
+
backend: observed,
|
|
4342
|
+
softwareRequested: backend === "software",
|
|
4343
|
+
deterministicRequested: options.deterministic === true,
|
|
4344
|
+
viewport: {
|
|
4345
|
+
width,
|
|
4346
|
+
height,
|
|
4347
|
+
deviceScaleFactor: 1,
|
|
4348
|
+
colorProfile: "srgb",
|
|
4349
|
+
colorScheme: "light",
|
|
4350
|
+
locale: "en-US",
|
|
4351
|
+
timezone: "UTC"
|
|
4352
|
+
},
|
|
4353
|
+
browser: { version: browser.version(), executable: browserPath ?? "playwright-managed" },
|
|
4354
|
+
display: display.value ?? null,
|
|
4355
|
+
lavapipeIcd: icd ?? null,
|
|
4356
|
+
captures,
|
|
4357
|
+
consoleErrors,
|
|
4358
|
+
pageErrors,
|
|
4359
|
+
boundary: "Browser-compositor capture is visual iteration evidence, not physical-GPU performance, HDR-display output, or release acceptance."
|
|
4360
|
+
};
|
|
4361
|
+
await writeRunReport(report);
|
|
4362
|
+
const updateReport = async () => {
|
|
4363
|
+
Object.assign(report, {
|
|
4364
|
+
ok: captures.length > 0 && captures.every((capture) => capture.ok) && consoleErrors.length === 0 && pageErrors.length === 0
|
|
4365
|
+
});
|
|
4366
|
+
await writeRunReport(report);
|
|
4367
|
+
};
|
|
4368
|
+
const close = async () => {
|
|
4369
|
+
if (closed) return;
|
|
4370
|
+
closed = true;
|
|
4371
|
+
await Promise.allSettled([browser?.close(), display?.close(), server?.close()]);
|
|
4372
|
+
Object.assign(report, { closedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
4373
|
+
await updateReport();
|
|
4374
|
+
};
|
|
4375
|
+
return {
|
|
4376
|
+
page,
|
|
4377
|
+
url: captureUrl.href,
|
|
4378
|
+
reportPath,
|
|
4379
|
+
async capture(checkpoint, captureOptions = {}) {
|
|
4380
|
+
if (closed) {
|
|
4381
|
+
fail(
|
|
4382
|
+
"browser-capture-session-closed",
|
|
4383
|
+
"capture to run inside an open browser session",
|
|
4384
|
+
"Open one session, complete all checkpoints, then close it.",
|
|
4385
|
+
{ report: reportPath }
|
|
4386
|
+
);
|
|
4387
|
+
}
|
|
4388
|
+
const activePage = page;
|
|
4389
|
+
if (activePage === void 0) {
|
|
4390
|
+
fail(
|
|
4391
|
+
"browser-capture-browser-unavailable",
|
|
4392
|
+
"an open Playwright page",
|
|
4393
|
+
"Inspect the browser launch diagnostic and retry the capture."
|
|
4394
|
+
);
|
|
4395
|
+
}
|
|
4396
|
+
const expectedReady = checkpoint ?? (options.deterministic === true ? "true" : void 0);
|
|
4397
|
+
await activePage.waitForFunction(
|
|
4398
|
+
({ datasetKey }) => Number(document.documentElement.dataset[datasetKey]) > 0,
|
|
4399
|
+
{ datasetKey: FORGEAX_FRAME_SUBMITTED_DATASET },
|
|
4400
|
+
{ timeout: 12e4 }
|
|
4401
|
+
);
|
|
4402
|
+
if (expectedReady !== void 0) {
|
|
4403
|
+
await activePage.waitForFunction(
|
|
4404
|
+
(expected) => document.documentElement.dataset.forgeaxCaptureReady === expected,
|
|
4405
|
+
expectedReady,
|
|
4406
|
+
{ timeout: 12e4 }
|
|
4407
|
+
);
|
|
4408
|
+
}
|
|
4409
|
+
await waitForCompositor(activePage);
|
|
4410
|
+
let captured = await screenshotWithWitness(activePage);
|
|
4411
|
+
const deadline = Date.now() + 12e4;
|
|
4412
|
+
while (!captured.pixels.rendered && Date.now() < deadline) {
|
|
4413
|
+
await activePage.waitForTimeout(500);
|
|
4414
|
+
await waitForCompositor(activePage);
|
|
4415
|
+
captured = await screenshotWithWitness(activePage);
|
|
4416
|
+
}
|
|
4417
|
+
const waitMs = captureOptions.waitMs ?? 0;
|
|
4418
|
+
if (waitMs > 0) {
|
|
4419
|
+
await activePage.waitForTimeout(waitMs);
|
|
4420
|
+
await waitForCompositor(activePage);
|
|
4421
|
+
captured = await screenshotWithWitness(activePage);
|
|
4422
|
+
while (!captured.pixels.rendered && Date.now() < deadline) {
|
|
4423
|
+
await activePage.waitForTimeout(500);
|
|
4424
|
+
await waitForCompositor(activePage);
|
|
4425
|
+
captured = await screenshotWithWitness(activePage);
|
|
4426
|
+
}
|
|
4427
|
+
}
|
|
4428
|
+
const runtime2 = await runtimeWitness(activePage);
|
|
4429
|
+
const uiPresent = runtime2.domUi.rootChildren > 0;
|
|
4430
|
+
const requireUi = captureOptions.requireUi ?? options.requireUi ?? false;
|
|
4431
|
+
const captureBackend = observedBackend(runtime2);
|
|
4432
|
+
if (captureBackend !== "unknown") Object.assign(report, { backend: captureBackend });
|
|
4433
|
+
const backendMatches = backend === "auto" || captureBackend === backend;
|
|
4434
|
+
const ok2 = runtime2.canvas !== null && captured.pixels.rendered && backendMatches && runtime2.engineFrameId !== null && consoleErrors.length === 0 && pageErrors.length === 0 && (expectedReady === void 0 || runtime2.captureReady === expectedReady) && (!requireUi || uiPresent);
|
|
4435
|
+
const index = captures.length + 1;
|
|
4436
|
+
const output = resolve(
|
|
4437
|
+
facts.value.root,
|
|
4438
|
+
captureOptions.output ?? resolve(
|
|
4439
|
+
outputDirectory,
|
|
4440
|
+
`${String(index).padStart(3, "0")}-${checkpointSlug(checkpoint)}.png`
|
|
4441
|
+
)
|
|
4442
|
+
);
|
|
4443
|
+
await mkdir(dirname(output), { recursive: true });
|
|
4444
|
+
await writeFile(output, captured.png);
|
|
4445
|
+
const record = {
|
|
4446
|
+
index,
|
|
4447
|
+
checkpoint: checkpoint ?? null,
|
|
4448
|
+
ok: ok2,
|
|
4449
|
+
output,
|
|
4450
|
+
digest: captureDigest(captured.png),
|
|
4451
|
+
pixels: captured.pixels,
|
|
4452
|
+
runtime: runtime2
|
|
4453
|
+
};
|
|
4454
|
+
captures.push(record);
|
|
4455
|
+
await updateReport();
|
|
4456
|
+
if (!ok2) {
|
|
4457
|
+
fail(
|
|
4458
|
+
`${backend === "software" ? "software" : "browser"}-capture-runtime-failed`,
|
|
4459
|
+
"non-flat canvas pixels, the requested browser backend/checkpoint, and no browser errors",
|
|
4460
|
+
"Inspect the run report and repair the first browser runtime failure.",
|
|
4461
|
+
{ checkpoint: checkpoint ?? null, output, report: reportPath }
|
|
4462
|
+
);
|
|
4463
|
+
}
|
|
4464
|
+
return record;
|
|
4465
|
+
},
|
|
4466
|
+
report: () => report,
|
|
4467
|
+
close
|
|
4468
|
+
};
|
|
4469
|
+
} catch (cause) {
|
|
4470
|
+
await Promise.allSettled([browser?.close(), display?.close(), server?.close()]);
|
|
4471
|
+
throw cause;
|
|
4472
|
+
}
|
|
4473
|
+
}
|
|
4474
|
+
function createBrowserCapture(root) {
|
|
4475
|
+
const sessions = /* @__PURE__ */ new Set();
|
|
4476
|
+
return {
|
|
4477
|
+
async open(options) {
|
|
4478
|
+
const session = await openBrowserCaptureSession(root, options);
|
|
4479
|
+
sessions.add(session);
|
|
4480
|
+
return session;
|
|
4481
|
+
},
|
|
4482
|
+
async close() {
|
|
4483
|
+
await Promise.allSettled([...sessions].map((session) => session.close()));
|
|
4484
|
+
sessions.clear();
|
|
4485
|
+
}
|
|
4486
|
+
};
|
|
4487
|
+
}
|
|
4488
|
+
function createSoftwareBrowser(root) {
|
|
4489
|
+
const browser = createBrowserCapture(root);
|
|
4490
|
+
return {
|
|
4491
|
+
async open(options) {
|
|
4492
|
+
return browser.open({ ...options, backend: "software", software: true });
|
|
4493
|
+
},
|
|
4494
|
+
close: () => browser.close()
|
|
4495
|
+
};
|
|
4496
|
+
}
|
|
4497
|
+
function captureCommandError(cause, legacySoftware = false) {
|
|
4498
|
+
if (cause instanceof SoftwareCaptureError) {
|
|
4499
|
+
const code = legacySoftware && cause.code.startsWith("browser-capture-") ? cause.code.replace(/^browser-capture-/, "software-capture-") : cause.code;
|
|
4500
|
+
return {
|
|
4501
|
+
code,
|
|
4502
|
+
expected: cause.expected,
|
|
4503
|
+
hint: cause.hint,
|
|
4504
|
+
detail: cause.detail
|
|
4505
|
+
};
|
|
4506
|
+
}
|
|
4507
|
+
return commandError(cause, legacySoftware ? "software-capture-failed" : "browser-capture-failed");
|
|
4508
|
+
}
|
|
4509
|
+
async function browserCaptureCommand(options) {
|
|
4510
|
+
const root = options.root ?? process.cwd();
|
|
4511
|
+
const output = resolve(root, options.output ?? "artifacts/capture/game-ui.png");
|
|
4512
|
+
const browser = createBrowserCapture(root);
|
|
4513
|
+
try {
|
|
4514
|
+
const backend = options.backend ?? (options.software === true ? "software" : "auto");
|
|
4515
|
+
const session = await browser.open({
|
|
4516
|
+
backend,
|
|
4517
|
+
...backend === "software" ? { software: true } : {},
|
|
4518
|
+
...options.browser === void 0 ? {} : { browser: options.browser },
|
|
4519
|
+
...options.width === void 0 ? {} : { width: options.width },
|
|
4520
|
+
...options.height === void 0 ? {} : { height: options.height },
|
|
4521
|
+
...options.port === void 0 ? {} : { port: options.port },
|
|
4522
|
+
...options.headless === void 0 ? {} : { headless: options.headless },
|
|
4523
|
+
requireUi: options.requireUi === true,
|
|
4524
|
+
deterministic: options.deterministic === true,
|
|
4525
|
+
outputDir: dirname(output),
|
|
4526
|
+
report: evidencePath(output)
|
|
4527
|
+
});
|
|
4528
|
+
await session.capture(options.deterministic === true ? "true" : void 0, {
|
|
4529
|
+
output,
|
|
4530
|
+
waitMs: options.waitMs ?? 4e3,
|
|
4531
|
+
requireUi: options.requireUi === true
|
|
4532
|
+
});
|
|
4533
|
+
await session.close();
|
|
4534
|
+
return { ok: true, value: session.report() };
|
|
4535
|
+
} catch (cause) {
|
|
4536
|
+
return { ok: false, error: captureCommandError(cause) };
|
|
4537
|
+
} finally {
|
|
4538
|
+
await browser.close();
|
|
4539
|
+
}
|
|
4540
|
+
}
|
|
4541
|
+
async function softwareCaptureCommand(options) {
|
|
4542
|
+
const result = await browserCaptureCommand({ ...options, backend: "software", software: true });
|
|
4543
|
+
if (result.ok) return result;
|
|
4544
|
+
return { ok: false, error: captureCommandErrorFromResult(result.error) };
|
|
4545
|
+
}
|
|
4546
|
+
function captureCommandErrorFromResult(error, legacySoftware) {
|
|
4547
|
+
if (!error.code.startsWith("browser-capture-")) return error;
|
|
4548
|
+
return { ...error, code: error.code.replace(/^browser-capture-/, "software-capture-") };
|
|
4549
|
+
}
|
|
4550
|
+
var SoftwareCaptureError;
|
|
4551
|
+
var init_software_capture = __esm({
|
|
4552
|
+
"src/software-capture.ts"() {
|
|
4553
|
+
init_host();
|
|
4554
|
+
init_project();
|
|
4555
|
+
SoftwareCaptureError = class extends Error {
|
|
4556
|
+
constructor(code, expected, hint, detail) {
|
|
4557
|
+
super(`${code}: ${hint}`);
|
|
4558
|
+
this.code = code;
|
|
4559
|
+
this.expected = expected;
|
|
4560
|
+
this.hint = hint;
|
|
4561
|
+
this.detail = detail;
|
|
4562
|
+
this.name = "SoftwareCaptureError";
|
|
4563
|
+
}
|
|
4564
|
+
code;
|
|
4565
|
+
expected;
|
|
4566
|
+
hint;
|
|
4567
|
+
detail;
|
|
4568
|
+
};
|
|
4569
|
+
}
|
|
4570
|
+
});
|
|
3790
4571
|
|
|
3791
4572
|
// src/commands.ts
|
|
3792
4573
|
var commands_exports = {};
|
|
@@ -3796,12 +4577,17 @@ __export(commands_exports, {
|
|
|
3796
4577
|
assetInspectCommand: () => assetInspectCommand,
|
|
3797
4578
|
assetListCommand: () => assetListCommand,
|
|
3798
4579
|
assetVerifyCommand: () => assetVerifyCommand,
|
|
4580
|
+
browserCaptureCommand: () => browserCaptureCommand,
|
|
3799
4581
|
buildCommand: () => buildCommand,
|
|
3800
4582
|
createCliRhiDebugOperationContext: () => createCliRhiDebugOperationContext,
|
|
3801
4583
|
createRhiDebugOperationContext: () => createRhiDebugOperationContext,
|
|
3802
4584
|
devCommand: () => devCommand,
|
|
3803
4585
|
discoverRhiDebugOperations: () => discoverRhiDebugOperations,
|
|
3804
4586
|
doctorCommand: () => doctorCommand,
|
|
4587
|
+
engineDoctorCommand: () => engineDoctorCommand,
|
|
4588
|
+
engineStatusCommand: () => engineStatusCommand,
|
|
4589
|
+
engineUnlinkCommand: () => engineUnlinkCommand,
|
|
4590
|
+
engineUseLocalCommand: () => engineUseLocalCommand,
|
|
3805
4591
|
initCommand: () => initCommand,
|
|
3806
4592
|
newCommand: () => newCommand,
|
|
3807
4593
|
packageCommand: () => packageCommand,
|
|
@@ -3816,11 +4602,27 @@ __export(commands_exports, {
|
|
|
3816
4602
|
shaderCheckCommand: () => shaderCheckCommand,
|
|
3817
4603
|
skillInstallCommand: () => skillInstallCommand,
|
|
3818
4604
|
skillVerifyCommand: () => skillVerifyCommand,
|
|
4605
|
+
softwareCaptureCommand: () => softwareCaptureCommand,
|
|
3819
4606
|
testCommand: () => testCommand
|
|
3820
4607
|
});
|
|
3821
4608
|
function runRhiDebugCommand(name, input, context) {
|
|
3822
4609
|
return runRhiDebugOperation(name, input, context);
|
|
3823
4610
|
}
|
|
4611
|
+
async function materializeViteDevPort(port) {
|
|
4612
|
+
if (port !== 0) return port;
|
|
4613
|
+
const reservation = createServer$1();
|
|
4614
|
+
await new Promise((resolveListen, rejectListen) => {
|
|
4615
|
+
reservation.once("error", rejectListen);
|
|
4616
|
+
reservation.listen(0, "127.0.0.1", resolveListen);
|
|
4617
|
+
});
|
|
4618
|
+
const address = reservation.address();
|
|
4619
|
+
const assigned = typeof address === "object" && address !== null ? address.port : 0;
|
|
4620
|
+
await new Promise((resolveClose, rejectClose) => {
|
|
4621
|
+
reservation.close((error) => error === void 0 ? resolveClose() : rejectClose(error));
|
|
4622
|
+
});
|
|
4623
|
+
if (assigned === 0) throw new Error("OS did not assign an ephemeral preview port");
|
|
4624
|
+
return assigned;
|
|
4625
|
+
}
|
|
3824
4626
|
async function buildCommand(options = {}) {
|
|
3825
4627
|
const facts = await readProjectFacts(options.root);
|
|
3826
4628
|
if (!facts.ok) return facts;
|
|
@@ -3923,13 +4725,38 @@ async function devCommand(options = {}) {
|
|
|
3923
4725
|
const facts = await readProjectFacts(options.root);
|
|
3924
4726
|
if (!facts.ok) return facts;
|
|
3925
4727
|
const previous = process.cwd();
|
|
4728
|
+
let server;
|
|
3926
4729
|
try {
|
|
4730
|
+
const { createServer: createServer5 } = await import('vite');
|
|
4731
|
+
const { createViteConfig: createViteConfig2 } = await Promise.resolve().then(() => (init_host(), host_exports));
|
|
4732
|
+
const port = resolveProjectPort(options.port);
|
|
4733
|
+
const vitePort = { ...port, port: await materializeViteDevPort(port.port) };
|
|
3927
4734
|
process.chdir(facts.value.root);
|
|
3928
|
-
|
|
3929
|
-
|
|
4735
|
+
server = await createServer5(
|
|
4736
|
+
await createViteConfig2(facts.value, "serve", "/", {
|
|
4737
|
+
server: vitePort
|
|
4738
|
+
})
|
|
4739
|
+
);
|
|
4740
|
+
await server.listen(vitePort.port);
|
|
3930
4741
|
if (options.json !== true) server.printUrls();
|
|
3931
|
-
return {
|
|
4742
|
+
return {
|
|
4743
|
+
ok: true,
|
|
4744
|
+
value: {
|
|
4745
|
+
root: facts.value.root,
|
|
4746
|
+
urls: server.resolvedUrls,
|
|
4747
|
+
mode: "dev",
|
|
4748
|
+
serves: "source",
|
|
4749
|
+
capabilities: {
|
|
4750
|
+
"rhi.capture": {
|
|
4751
|
+
available: false,
|
|
4752
|
+
realm: "host",
|
|
4753
|
+
reason: "standalone-dev-server-has-no-live-app-cli-attachment"
|
|
4754
|
+
}
|
|
4755
|
+
}
|
|
4756
|
+
}
|
|
4757
|
+
};
|
|
3932
4758
|
} catch (cause) {
|
|
4759
|
+
await server?.close();
|
|
3933
4760
|
process.chdir(previous);
|
|
3934
4761
|
return { ok: false, error: commandError(cause, "dev-server-failed") };
|
|
3935
4762
|
}
|
|
@@ -3939,15 +4766,31 @@ async function previewCommand(options = {}) {
|
|
|
3939
4766
|
const verified = await verifyDist(resolve(root, "dist"));
|
|
3940
4767
|
if (!verified.ok) return verified;
|
|
3941
4768
|
try {
|
|
4769
|
+
const port = resolveProjectPort(options.port);
|
|
3942
4770
|
const server = await preview({
|
|
3943
4771
|
root,
|
|
3944
4772
|
configFile: false,
|
|
3945
4773
|
base: verified.value.base,
|
|
3946
|
-
preview: { open: false, port
|
|
4774
|
+
preview: { open: false, ...port },
|
|
3947
4775
|
build: { outDir: resolve(root, "dist") }
|
|
3948
4776
|
});
|
|
3949
4777
|
if (options.json !== true) server.printUrls();
|
|
3950
|
-
return {
|
|
4778
|
+
return {
|
|
4779
|
+
ok: true,
|
|
4780
|
+
value: {
|
|
4781
|
+
root,
|
|
4782
|
+
urls: server.resolvedUrls,
|
|
4783
|
+
mode: "preview",
|
|
4784
|
+
serves: "dist",
|
|
4785
|
+
capabilities: {
|
|
4786
|
+
"rhi.capture": {
|
|
4787
|
+
available: false,
|
|
4788
|
+
realm: "host",
|
|
4789
|
+
reason: "static-dist-host-does-not-install-dev-capture"
|
|
4790
|
+
}
|
|
4791
|
+
}
|
|
4792
|
+
}
|
|
4793
|
+
};
|
|
3951
4794
|
} catch (cause) {
|
|
3952
4795
|
return { ok: false, error: commandError(cause, "preview-server-failed") };
|
|
3953
4796
|
}
|
|
@@ -3997,14 +4840,17 @@ var init_commands = __esm({
|
|
|
3997
4840
|
init_dist();
|
|
3998
4841
|
init_host();
|
|
3999
4842
|
init_project();
|
|
4843
|
+
init_types();
|
|
4000
4844
|
init_assets();
|
|
4001
4845
|
init_bootstrap_commands();
|
|
4846
|
+
init_engine_binding();
|
|
4002
4847
|
init_plugin_authoring();
|
|
4003
4848
|
init_cli_context();
|
|
4004
4849
|
init_operations();
|
|
4005
4850
|
init_sdk_install();
|
|
4006
4851
|
init_shader_check();
|
|
4007
4852
|
init_skill_install();
|
|
4853
|
+
init_software_capture();
|
|
4008
4854
|
init_operations();
|
|
4009
4855
|
}
|
|
4010
4856
|
});
|
|
@@ -4391,7 +5237,7 @@ function hasStaticToolDeclarations(entry) {
|
|
|
4391
5237
|
return Array.isArray(Reflect.get(entry.config, "tools"));
|
|
4392
5238
|
}
|
|
4393
5239
|
async function createViteModuleLoader(root) {
|
|
4394
|
-
const server = await createServer({
|
|
5240
|
+
const server = await createServer$2({
|
|
4395
5241
|
root,
|
|
4396
5242
|
appType: "custom",
|
|
4397
5243
|
configFile: false,
|
|
@@ -4651,9 +5497,11 @@ var init_runtime = __esm({
|
|
|
4651
5497
|
// src/index.ts
|
|
4652
5498
|
init_commands();
|
|
4653
5499
|
init_dist();
|
|
5500
|
+
init_engine_binding();
|
|
4654
5501
|
init_init();
|
|
4655
5502
|
init_project();
|
|
4656
5503
|
init_operations();
|
|
5504
|
+
init_software_capture();
|
|
4657
5505
|
|
|
4658
5506
|
// src/tools/benchmark/statistics.ts
|
|
4659
5507
|
function calculateSampleStatistics(samples) {
|
|
@@ -4773,9 +5621,11 @@ function createAdmissionReport(recipe, samples, thresholds = DEFAULT_ADMISSION_T
|
|
|
4773
5621
|
async function runBenchmarkAdmission(options) {
|
|
4774
5622
|
const sampleCount = options.thresholds?.sampleCountPerPhase ?? 30;
|
|
4775
5623
|
const samples = [];
|
|
4776
|
-
|
|
5624
|
+
const phases = ["cold", "warm"];
|
|
5625
|
+
const modes = ["private", "service"];
|
|
5626
|
+
for (const phase of phases) {
|
|
4777
5627
|
for (let index = 0; index < sampleCount; index += 1) {
|
|
4778
|
-
for (const mode of
|
|
5628
|
+
for (const mode of modes) {
|
|
4779
5629
|
const measurement = await options.measure(mode, phase, index);
|
|
4780
5630
|
samples.push({
|
|
4781
5631
|
mode,
|
|
@@ -4882,7 +5732,7 @@ function reply(response, status, payload) {
|
|
|
4882
5732
|
async function createCarrierProviderService(options) {
|
|
4883
5733
|
const host = options.host ?? "127.0.0.1";
|
|
4884
5734
|
const bearerToken = options.machine.offer.bearerToken;
|
|
4885
|
-
const server = createServer
|
|
5735
|
+
const server = createServer(async (request, response) => {
|
|
4886
5736
|
if (request.method !== "POST" || !request.url?.startsWith("/carrier/")) {
|
|
4887
5737
|
reply(
|
|
4888
5738
|
response,
|
|
@@ -5001,14 +5851,14 @@ async function createCarrierProviderService(options) {
|
|
|
5001
5851
|
);
|
|
5002
5852
|
}
|
|
5003
5853
|
});
|
|
5004
|
-
await new Promise((
|
|
5854
|
+
await new Promise((resolve18, reject) => {
|
|
5005
5855
|
const onError = (error) => {
|
|
5006
5856
|
server.off("listening", onListening);
|
|
5007
5857
|
reject(error);
|
|
5008
5858
|
};
|
|
5009
5859
|
const onListening = () => {
|
|
5010
5860
|
server.off("error", onError);
|
|
5011
|
-
|
|
5861
|
+
resolve18();
|
|
5012
5862
|
};
|
|
5013
5863
|
server.once("error", onError);
|
|
5014
5864
|
server.once("listening", onListening);
|
|
@@ -5026,7 +5876,7 @@ async function createCarrierProviderService(options) {
|
|
|
5026
5876
|
if (closed) return;
|
|
5027
5877
|
closed = true;
|
|
5028
5878
|
await new Promise(
|
|
5029
|
-
(
|
|
5879
|
+
(resolve18, reject) => server.close((error) => error === void 0 ? resolve18() : reject(error))
|
|
5030
5880
|
);
|
|
5031
5881
|
}
|
|
5032
5882
|
};
|
|
@@ -5318,16 +6168,16 @@ function projectUri(projectRoot, path) {
|
|
|
5318
6168
|
return relative(projectRoot, path).split(sep).join("/");
|
|
5319
6169
|
}
|
|
5320
6170
|
async function allocateLoopbackPort() {
|
|
5321
|
-
const probe = createServer$
|
|
6171
|
+
const probe = createServer$1();
|
|
5322
6172
|
try {
|
|
5323
|
-
await new Promise((
|
|
6173
|
+
await new Promise((resolve18, reject) => {
|
|
5324
6174
|
const onError = (error) => {
|
|
5325
6175
|
probe.off("listening", onListening);
|
|
5326
6176
|
reject(error);
|
|
5327
6177
|
};
|
|
5328
6178
|
const onListening = () => {
|
|
5329
6179
|
probe.off("error", onError);
|
|
5330
|
-
|
|
6180
|
+
resolve18();
|
|
5331
6181
|
};
|
|
5332
6182
|
probe.once("error", onError);
|
|
5333
6183
|
probe.once("listening", onListening);
|
|
@@ -5340,7 +6190,7 @@ async function allocateLoopbackPort() {
|
|
|
5340
6190
|
return address.port;
|
|
5341
6191
|
} finally {
|
|
5342
6192
|
if (probe.listening) {
|
|
5343
|
-
await new Promise((
|
|
6193
|
+
await new Promise((resolve18) => probe.close(() => resolve18()));
|
|
5344
6194
|
}
|
|
5345
6195
|
}
|
|
5346
6196
|
}
|
|
@@ -5497,7 +6347,7 @@ async function runBrowserPreviewHost(projectRoot, recipe, snapshot, runId, signa
|
|
|
5497
6347
|
let server;
|
|
5498
6348
|
try {
|
|
5499
6349
|
const port = await allocateLoopbackPort();
|
|
5500
|
-
server = await createServer({
|
|
6350
|
+
server = await createServer$2({
|
|
5501
6351
|
...config,
|
|
5502
6352
|
cacheDir,
|
|
5503
6353
|
logLevel: "silent",
|
|
@@ -5533,6 +6383,7 @@ async function runBrowserPreviewHost(projectRoot, recipe, snapshot, runId, signa
|
|
|
5533
6383
|
"--enable-unsafe-webgpu",
|
|
5534
6384
|
"--enable-features=Vulkan,UseSkiaRenderer,SharedArrayBuffer",
|
|
5535
6385
|
"--use-vulkan=swiftshader",
|
|
6386
|
+
"--use-angle=swiftshader",
|
|
5536
6387
|
"--disable-vulkan-surface",
|
|
5537
6388
|
"--ignore-gpu-blocklist",
|
|
5538
6389
|
"--disable-gpu-driver-bug-workarounds",
|
|
@@ -5619,9 +6470,7 @@ async function runBrowserPreviewHost(projectRoot, recipe, snapshot, runId, signa
|
|
|
5619
6470
|
);
|
|
5620
6471
|
});
|
|
5621
6472
|
await Promise.all(responseDiagnostics);
|
|
5622
|
-
if (!captured.ok)
|
|
5623
|
-
return browserFailure("capture-run", captured.error, pageErrors);
|
|
5624
|
-
}
|
|
6473
|
+
if (!captured.ok) return browserFailure("capture-run", captured.error, pageErrors);
|
|
5625
6474
|
if (pageErrors.length > 0)
|
|
5626
6475
|
return browserFailure("capture-page-runtime", pageErrors[0], pageErrors);
|
|
5627
6476
|
const capturePng = await page.screenshot({ type: "png" });
|
|
@@ -5962,7 +6811,7 @@ function isAuthorized(request, bearerToken) {
|
|
|
5962
6811
|
async function createAuthenticatedLoopbackService(options) {
|
|
5963
6812
|
if (options.bearerToken.length < 8) throw new TypeError("service bearer token is too short");
|
|
5964
6813
|
const host = options.host ?? "127.0.0.1";
|
|
5965
|
-
const server = createServer
|
|
6814
|
+
const server = createServer(async (request, response) => {
|
|
5966
6815
|
if (request.method !== "POST" || request.url !== "/run") {
|
|
5967
6816
|
reply2(response, 404, { error: "service route not found" });
|
|
5968
6817
|
return;
|
|
@@ -5987,14 +6836,14 @@ async function createAuthenticatedLoopbackService(options) {
|
|
|
5987
6836
|
reply2(response, 400, { error: message });
|
|
5988
6837
|
}
|
|
5989
6838
|
});
|
|
5990
|
-
await new Promise((
|
|
6839
|
+
await new Promise((resolve18, reject) => {
|
|
5991
6840
|
const onError = (error) => {
|
|
5992
6841
|
server.off("listening", onListening);
|
|
5993
6842
|
reject(error);
|
|
5994
6843
|
};
|
|
5995
6844
|
const onListening = () => {
|
|
5996
6845
|
server.off("error", onError);
|
|
5997
|
-
|
|
6846
|
+
resolve18();
|
|
5998
6847
|
};
|
|
5999
6848
|
server.once("error", onError);
|
|
6000
6849
|
server.once("listening", onListening);
|
|
@@ -6002,7 +6851,7 @@ async function createAuthenticatedLoopbackService(options) {
|
|
|
6002
6851
|
});
|
|
6003
6852
|
const address = server.address();
|
|
6004
6853
|
if (address === null || typeof address === "string") {
|
|
6005
|
-
await new Promise((
|
|
6854
|
+
await new Promise((resolve18) => server.close(() => resolve18()));
|
|
6006
6855
|
throw new Error("loopback service did not expose a TCP address");
|
|
6007
6856
|
}
|
|
6008
6857
|
const endpoint = `http://${host}:${address.port}/run`;
|
|
@@ -6017,14 +6866,17 @@ async function createAuthenticatedLoopbackService(options) {
|
|
|
6017
6866
|
async close() {
|
|
6018
6867
|
if (closed) return;
|
|
6019
6868
|
closed = true;
|
|
6020
|
-
await new Promise((
|
|
6021
|
-
server.close((error) => error === void 0 ?
|
|
6869
|
+
await new Promise((resolve18, reject) => {
|
|
6870
|
+
server.close((error) => error === void 0 ? resolve18() : reject(error));
|
|
6022
6871
|
});
|
|
6023
6872
|
transport.close();
|
|
6024
6873
|
}
|
|
6025
6874
|
};
|
|
6026
6875
|
}
|
|
6027
6876
|
|
|
6028
|
-
|
|
6877
|
+
// src/index.ts
|
|
6878
|
+
init_types();
|
|
6879
|
+
|
|
6880
|
+
export { DEFAULT_ADMISSION_THRESHOLDS, ENGINE_BINDING_SCHEMA_VERSION, RHI_DEBUG_OPERATION_MANIFEST, analyzePreviewArtifacts, assetAddCommand, assetInspectCommand, assetListCommand, assetVerifyCommand, bootstrapRealm, browserCaptureCommand, buildCommand, createAdmissionReport, createAuthenticatedLoopbackService, createAuthorContribution, createBrowserCapture, createBuildContribution, createCarrierProvider, createCarrierProviderService, createCarrierRendezvous, createCliRhiDebugOperationContext, createDefaultContributions, createDevkitToolRuntime, createDomainPreviewContributions, createInitPlan, createMigrationRoster, createOfflineAnalysisContribution, createPreviewContribution, createPreviewContributions, createPreviewToolRuntime, createResourceProbe, createRhiDebugOperationContext, createServiceCache, createServiceExecutor, createSoftwareBrowser, createToolClient, describeTool, devCommand, discoverRhiDebugOperations, doctorCommand, engineBindingFilePath, engineDoctorCommand, engineStatusCommand, engineUnlinkCommand, engineUseLocalCommand, initCommand, inspectEngineWorkspace, listTools, materializeToolCatalog, newCommand, packageCommand, pluginInstallCommand, pluginUninstallCommand, previewCommand, readEngineBinding, readProjectFacts, rebuildToolCatalog, recoverRhiDebugError, renderRhiDebugHelp, resolveMigration, resolveProjectPort, resolveRealmCapability, runBenchmarkAdmission, runCarrierPreviewRoute, runGenericTool, runLibraryTool, runNamedTool, runPreviewHost, runPrivateTool, runRhiDebugCommand, runRhiDebugOperation, sdkInstallCommand, shaderCheckCommand, skillInstallCommand, skillVerifyCommand, softwareCaptureCommand, summarizeBenchmarkSamples, testCommand, verifyDist, writeDistManifest };
|
|
6029
6881
|
//# sourceMappingURL=index.mjs.map
|
|
6030
6882
|
//# sourceMappingURL=index.mjs.map
|