@forgeax/engine-devkit 0.1.4 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/README.md +19 -4
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/__tests__/cli-output.unit.test.d.ts +2 -0
  4. package/dist/__tests__/cli-output.unit.test.d.ts.map +1 -0
  5. package/dist/__tests__/gltf-source-key-diagnostics.integration.test.d.ts +2 -0
  6. package/dist/__tests__/gltf-source-key-diagnostics.integration.test.d.ts.map +1 -0
  7. package/dist/__tests__/preview-port.integration.test.d.ts +2 -0
  8. package/dist/__tests__/preview-port.integration.test.d.ts.map +1 -0
  9. package/dist/__tests__/rhi-debug-build-graph.integration.test.d.ts +2 -0
  10. package/dist/__tests__/rhi-debug-build-graph.integration.test.d.ts.map +1 -0
  11. package/dist/__tests__/sdk-update.unit.test.d.ts +2 -0
  12. package/dist/__tests__/sdk-update.unit.test.d.ts.map +1 -0
  13. package/dist/assets.d.ts.map +1 -1
  14. package/dist/bootstrap-commands.d.ts.map +1 -1
  15. package/dist/cli-output.d.ts +4 -0
  16. package/dist/cli-output.d.ts.map +1 -0
  17. package/dist/cli.mjs +380 -567
  18. package/dist/cli.mjs.map +1 -1
  19. package/dist/commands.d.ts.map +1 -1
  20. package/dist/host/__tests__/bootstrap-root-owner.test-d.d.ts +2 -0
  21. package/dist/host/__tests__/bootstrap-root-owner.test-d.d.ts.map +1 -0
  22. package/dist/host/project-bootstrap.d.ts +4 -2
  23. package/dist/host/project-bootstrap.d.ts.map +1 -1
  24. package/dist/host/resource-bootstrap.d.ts +5 -3
  25. package/dist/host/resource-bootstrap.d.ts.map +1 -1
  26. package/dist/host.d.ts +2 -1
  27. package/dist/host.d.ts.map +1 -1
  28. package/dist/index.d.ts +4 -0
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.mjs +303 -570
  31. package/dist/index.mjs.map +1 -1
  32. package/dist/project.d.ts +0 -1
  33. package/dist/project.d.ts.map +1 -1
  34. package/dist/rhi-debug/cli-context.d.ts.map +1 -1
  35. package/dist/sdk-bootstrap.d.ts +9 -0
  36. package/dist/sdk-bootstrap.d.ts.map +1 -1
  37. package/dist/sdk-cli.mjs +190 -24
  38. package/dist/sdk-cli.mjs.map +1 -1
  39. package/dist/sdk-install.d.ts.map +1 -1
  40. package/dist/sdk-update.d.ts +23 -0
  41. package/dist/sdk-update.d.ts.map +1 -0
  42. package/dist/tools/benchmark/__tests__/benchmark-vocabulary-owner.test-d.d.ts +2 -0
  43. package/dist/tools/benchmark/__tests__/benchmark-vocabulary-owner.test-d.d.ts.map +1 -0
  44. package/dist/tools/benchmark/harness.d.ts.map +1 -1
  45. package/dist/tools/browser-host.d.ts.map +1 -1
  46. package/dist/tools/native-preview.d.ts.map +1 -1
  47. package/dist/types.d.ts +7 -0
  48. package/dist/types.d.ts.map +1 -1
  49. package/package.json +29 -30
  50. package/dist/__tests__/tool-migration.test.d.ts +0 -2
  51. package/dist/__tests__/tool-migration.test.d.ts.map +0 -1
package/dist/index.mjs CHANGED
@@ -1,8 +1,8 @@
1
1
  import { deflateRawSync } from 'zlib';
2
2
  import { createHash } from 'crypto';
3
- import { readFile, writeFile, copyFile, readdir, mkdir, mkdtemp, cp, rename, rm, stat, symlink, realpath, unlink, lstat, readlink, rmdir, access } from 'fs/promises';
3
+ import { readFile, writeFile, mkdir, copyFile, readdir, mkdtemp, cp, rename, rm, stat, symlink, realpath, unlink, lstat, readlink, rmdir, access } from 'fs/promises';
4
4
  import { resolve, relative, sep, isAbsolute, extname, dirname, basename, join } from 'path';
5
- import { existsSync, readFileSync } from 'fs';
5
+ import { existsSync } from 'fs';
6
6
  import { createRequire } from 'module';
7
7
  import { fileURLToPath } from 'url';
8
8
  import { audioImporter } from '@forgeax/engine-audio-webaudio/audio-importer';
@@ -12,9 +12,9 @@ import { gltfImporter } from '@forgeax/engine-gltf';
12
12
  import { imageImporter } from '@forgeax/engine-image/image-importer';
13
13
  import { BUILTIN_MESH_ASSETS } from '@forgeax/engine-pack/builtin';
14
14
  import { scanInventory } from '@forgeax/engine-pack/scanner';
15
- import { validatePreviewArtifactManifest, createMaterialPreviewContribution, createMeshPreviewContribution, createVfxPreviewContribution, createTexturePreviewContribution, describeResourcePreviewFailure, validateCanonicalKitReceipt } from '@forgeax/engine-preview';
15
+ import { validatePreviewArtifactManifest, createMaterialPreviewContribution, createMeshPreviewContribution, createVfxPreviewContribution, createTexturePreviewContribution, validateCanonicalKitReceipt, describeResourcePreviewFailure } from '@forgeax/engine-preview';
16
16
  import { createMaterialPackCooker } from '@forgeax/engine-shader-compiler';
17
- import { err, ok, createStandaloneRuntimeAssetBinding } from '@forgeax/engine-types';
17
+ import { createStandaloneRuntimeAssetBinding, err, ok } from '@forgeax/engine-types';
18
18
  import { createParticleCodeNativeCookerFromRoots } from '@forgeax/engine-vfx-compiler';
19
19
  import { pluginPack, reloadAssetHost } from '@forgeax/engine-vite-plugin-pack';
20
20
  import { forgeaxShader } from '@forgeax/engine-vite-plugin-shader';
@@ -26,7 +26,9 @@ import { execFile } from 'child_process';
26
26
  import { tmpdir } from 'os';
27
27
  import { promisify } from 'util';
28
28
  import { createRhiDebugError, decodeTape, openReplay, buildFrameModel } from '@forgeax/engine-rhi-debug';
29
- import { build, createServer, preview } from 'vite';
29
+ import { rhi, createShaderModule } from '@forgeax/engine-rhi-null';
30
+ import { build, preview, createServer as createServer$2 } from 'vite';
31
+ import { createServer as createServer$1 } from 'net';
30
32
  import { startVitest } from 'vitest/node';
31
33
  import materialPreviewPlugin from '@forgeax/engine-preview/material';
32
34
  import meshPreviewPlugin from '@forgeax/engine-preview/mesh';
@@ -36,9 +38,8 @@ import { validateArtifactManifest, defineTool, createToolRuntime, validateRealmB
36
38
  export { createAuthenticatedLoopbackTransport, createCapabilityToken, createMigrationRecipe, createRealmCapabilityMatrix, createServiceCapability, probeMigrationTarget } from '@forgeax/engine-tool-runtime';
37
39
  import { Context, isToolPlugin } from '@forgeax/engine-plugin';
38
40
  import { installCatalogLoader, projectPluginEntries, bootstrapCatalogLoader } from '@forgeax/engine-plugin/loader';
39
- import { createServer as createServer$1 } from 'http';
41
+ import { createServer } from 'http';
40
42
  import { createToolPreviewRecipe, createToolPreviewHost } from '@forgeax/engine-app';
41
- import { createServer as createServer$2 } from 'net';
42
43
  import { chromium } from 'playwright';
43
44
 
44
45
  var __defProp = Object.defineProperty;
@@ -303,16 +304,17 @@ var init_dist = __esm({
303
304
  "src/dist.ts"() {
304
305
  }
305
306
  });
307
+
308
+ // src/host.ts
309
+ var host_exports = {};
310
+ __export(host_exports, {
311
+ createViteConfig: () => createViteConfig,
312
+ devKitDdcRoots: () => devKitDdcRoots,
313
+ ignoreDevKitCatalogPath: () => ignoreDevKitCatalogPath
314
+ });
306
315
  function ignoreDevKitCatalogPath(path) {
307
316
  const normalized = path.replace(/\\/g, "/");
308
- if (normalized.split("/").includes("shaders")) return true;
309
- if (!normalized.endsWith(".meta.json")) return false;
310
- try {
311
- const meta = JSON.parse(readFileSync(path, "utf8"));
312
- return meta.importer === "game-default-target-profile";
313
- } catch {
314
- return false;
315
- }
317
+ return normalized.split("/").includes("shaders");
316
318
  }
317
319
  function devKitDdcRoots(projectRoot) {
318
320
  return {
@@ -578,17 +580,20 @@ await app.pluginContext.plugin({
578
580
  return `import { forgeaxBundlerAdapter } from 'virtual:forgeax/bundler';
579
581
  import { createApp, createToolPreviewHost, createToolPreviewRecipe, fitToolPreviewCameraToAabb, gameHostPlugin, replayToolPreviewCapture } from '@forgeax/engine/app';
580
582
  import { createCatalogSource } from '@forgeax/engine/assets-runtime';
583
+ import {
584
+ createRuntimeAssetImportTransport,
585
+ runtimeBinding,
586
+ } from 'virtual:forgeax/pack-runtime';
581
587
  import { installCatalogLoader, projectPluginEntries } from '@forgeax/engine/plugin/loader';
582
588
  import { audioPlugin } from '@forgeax/engine/audio';
583
589
  import { webAudioPlugin } from '@forgeax/engine/audio-webaudio';
584
590
  import { physicsPlugin } from '@forgeax/engine/physics';
585
591
  import { skinningPlugin } from '@forgeax/engine/skinning';
586
592
  import { createPrimitiveMesh } from '@forgeax/engine/geometry';
587
- import { createDevImportTransport } from '@forgeax/engine/runtime';
588
593
  import { mat4 } from '@forgeax/engine/math';
589
594
  import { CAMERA_PROJECTION_ORTHOGRAPHIC, Camera, DirectionalLight, Materials, MeshFilter, MeshRenderer, Skylight, SkyboxBackground, TONEMAP_NONE, TONEMAP_REINHARD_EXTENDED } from '@forgeax/engine/render';
590
- import { projectSceneAsset, Transform, worldInstantiateScene } from '@forgeax/engine/scene';
591
- import { createStandaloneRuntimeAssetBinding } from '@forgeax/engine/types';
595
+ import { Transform } from '@forgeax/engine/scene';
596
+ import { type SceneAsset } from '@forgeax/engine/types';
592
597
  import { ParticleEffectPlayer, vfxGpuEffectContribution } from '@forgeax/engine/vfx';
593
598
  import { createVfxRuntimeHost } from '@forgeax/engine/vfx-render';
594
599
 
@@ -689,17 +694,39 @@ const resizeCanvas = () => {
689
694
  const resizeObserver = new ResizeObserver(resizeCanvas);
690
695
  resizeObserver.observe(canvas);
691
696
  resizeCanvas();
692
- const runtimeScopeBinding = createStandaloneRuntimeAssetBinding(${JSON.stringify(facts.id)});
697
+ const runtimeScopeBinding = runtimeBinding;
698
+ if (runtimeScopeBinding === undefined) {
699
+ throw new Error('forgeax: Vite Pack runtime binding is required in the generated host');
700
+ }
693
701
  const assetCatalog = createCatalogSource({
694
702
  url: import.meta.env.DEV
695
703
  ? runtimeScopeBinding.catalogUrl
696
704
  : new URL('pack-index.json', document.baseURI).href,
697
- expectedScope: runtimeScopeBinding,
705
+ ...(import.meta.env.DEV ? { expectedScope: runtimeScopeBinding } : {}),
698
706
  });
699
707
  const bundler = {
700
708
  ...forgeaxBundlerAdapter(),
701
- ...(import.meta.env.DEV ? { importTransport: createDevImportTransport(runtimeScopeBinding) } : {}),
709
+ ...(import.meta.env.DEV
710
+ ? { importTransport: createRuntimeAssetImportTransport(runtimeScopeBinding) }
711
+ : {}),
702
712
  };
713
+ const assetPreparation = new WeakMap();
714
+
715
+ function prepareAssetRegistry(assets) {
716
+ const existing = assetPreparation.get(assets);
717
+ if (existing !== undefined) return existing;
718
+ const pending = (async () => {
719
+ assets.configureRuntimeBinding(runtimeScopeBinding);
720
+ assets.setCatalogSource(assetCatalog);
721
+ if (vfxRuntimeHost !== undefined) {
722
+ assets.installDecoder(vfxGpuEffectContribution.kind, vfxGpuEffectContribution.decoder);
723
+ }
724
+ const catalog = await assets.enumerateCatalog();
725
+ if (!catalog.ok) throw catalog.error;
726
+ })();
727
+ assetPreparation.set(assets, pending);
728
+ return pending;
729
+ }
703
730
 
704
731
  function previewStable(value) {
705
732
  if (value instanceof ArrayBuffer) return 'ArrayBuffer:' + JSON.stringify(Array.from(new Uint8Array(value)));
@@ -760,13 +787,14 @@ async function previewOwnerFacts(assets, resource, payload) {
760
787
  }
761
788
 
762
789
  async function prepareProject(app) {
790
+ await prepareAssetRegistry(app.assets);
763
791
  previewWorld = app.world;
764
792
  const assets = app.assets;
765
793
  let canonicalEnvironment;
766
794
  if (resource !== undefined) {
767
795
  if (resource.kind !== 'texture') {
768
796
  if (canonicalEnvironmentGuid === null) throw new Error('canonical preview environment is unavailable');
769
- const environment = await assets.load(canonicalEnvironmentGuid, 'equirect');
797
+ const environment = await assets.loadByGuid(assets.parseGuid(canonicalEnvironmentGuid));
770
798
  if (!environment.ok) throw environment.error;
771
799
  if (environment.value.kind !== 'equirect') {
772
800
  throw new Error(\`canonical preview environment expected equirect, received \${environment.value.kind}\`);
@@ -781,7 +809,7 @@ if (resource !== undefined) {
781
809
  const attached = await vfxRuntimeHost.attachWorld({ world: app.world, assets });
782
810
  if (!attached.ok) throw attached.error;
783
811
  }
784
- const loaded = await assets.load(resource.guid, resource.kind === 'vfx' ? 'particle-effect' : resource.kind);
812
+ const loaded = await assets.loadByGuid(assets.parseGuid(resource.guid));
785
813
  if (!loaded.ok) throw loaded.error;
786
814
  const payload = loaded.value;
787
815
  if (resource.kind === 'vfx' && payload.kind !== 'particle-effect') {
@@ -794,7 +822,7 @@ if (resource !== undefined) {
794
822
  const meshMaterialHandles = resource.kind === 'mesh' && payload.kind === 'mesh'
795
823
  ? await Promise.all(payload.materialSlots.map(async (slot) => {
796
824
  if (slot.defaultMaterial === undefined) return 0;
797
- const material = await assets.load(slot.defaultMaterial, 'material');
825
+ const material = await assets.loadByGuid(assets.parseGuid(slot.defaultMaterial));
798
826
  if (!material.ok) throw material.error;
799
827
  if (material.value.kind !== 'material') {
800
828
  throw new Error('mesh material slot resolved to a non-material asset');
@@ -924,15 +952,13 @@ let defaultScene;
924
952
  let defaultSceneRoot;
925
953
  const defaultSceneGuid = ${JSON.stringify(facts.defaultScene)};
926
954
  if (defaultSceneGuid !== undefined) {
927
- const loaded = await assets.load(defaultSceneGuid, 'scene');
955
+ const loaded = await assets.loadByGuid<SceneAsset>(assets.parseGuid(defaultSceneGuid));
928
956
  if (!loaded.ok) throw loaded.error;
929
- const projected = await projectSceneAsset(app.world, loaded.value, (guid, kind) => assets.load(guid, kind));
930
- if (!projected.ok) throw projected.error;
931
- defaultScene = projected.value;
932
- const handle = app.world.allocSharedRef('SceneAsset', projected.value);
933
- const instantiated = worldInstantiateScene(app.world, handle);
957
+ defaultScene = loaded.value;
958
+ const handle = app.world.allocSharedRef('SceneAsset', loaded.value);
959
+ const instantiated = assets.instantiate<SceneAsset>(handle, app.world);
934
960
  if (!instantiated.ok) throw instantiated.error;
935
- defaultSceneRoot = instantiated.value.root;
961
+ defaultSceneRoot = instantiated.value;
936
962
  }
937
963
  const uiRoot = document.querySelector('#game-ui');
938
964
  await app.pluginContext.plugin(gameHostPlugin({
@@ -971,8 +997,6 @@ if (query.has('forgeax-tool-replay')) {
971
997
  ...(resource === undefined ? {} : { resource }),
972
998
  canvas,
973
999
  app: {
974
- ...(assetCatalog === undefined ? {} : { assetCatalog }),
975
- ...(vfxRuntimeHost === undefined ? {} : { assetDecoders: [vfxGpuEffectContribution] }),
976
1000
  plugins: [${plugins.join(", ")}],
977
1001
  ...(vfxRuntimeHost === undefined ? {} : { features: [vfxRuntimeHost.feature] }),
978
1002
  },
@@ -1085,8 +1109,6 @@ if (query.has('forgeax-tool-replay')) {
1085
1109
  const result = await createApp(
1086
1110
  canvas,
1087
1111
  {
1088
- ...(assetCatalog === undefined ? {} : { assetCatalog }),
1089
- ...(vfxRuntimeHost === undefined ? {} : { assetDecoders: [vfxGpuEffectContribution] }),
1090
1112
  plugins: [${plugins.join(", ")}],
1091
1113
  ...(pointerLockAllowed === undefined ? {} : { pointerLockAllowed }),
1092
1114
  },
@@ -1234,9 +1256,9 @@ async function createViteConfig(facts, command, base = "/", options = {}) {
1234
1256
  plugins,
1235
1257
  resolve: {
1236
1258
  alias: consumerAliases,
1237
- dedupe: ["@forgeax/engine-app", "@forgeax/engine-ecs", "@forgeax/engine-runtime"]
1259
+ dedupe: ["@forgeax/engine"]
1238
1260
  },
1239
- server: { fs: { allow: [facts.root, ...roots] } },
1261
+ server: { ...options.server, fs: { allow: [facts.root, ...roots] } },
1240
1262
  build: {
1241
1263
  target: "esnext",
1242
1264
  outDir: options.outDir ?? resolve(facts.root, "dist"),
@@ -1260,7 +1282,7 @@ async function readJson(path) {
1260
1282
  function firstUnsupportedStandaloneRealm(entries2, inheritedRealm = "engine") {
1261
1283
  for (const entry of entries2) {
1262
1284
  const realm = entry.realm ?? inheritedRealm ?? "engine";
1263
- if (realm === "host") return { id: entry.id, realm };
1285
+ if (realm !== "engine") return { id: entry.id, realm };
1264
1286
  if (entry.group === true) {
1265
1287
  const unsupported = firstUnsupportedStandaloneRealm(
1266
1288
  entry.config,
@@ -1378,13 +1400,10 @@ async function readProjectFacts(rootInput = process.cwd()) {
1378
1400
  }
1379
1401
  };
1380
1402
  }
1381
- function isCommandError(value) {
1382
- if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
1383
- const candidate = value;
1384
- return typeof candidate.code === "string" && typeof candidate.expected === "string" && typeof candidate.hint === "string" && candidate.detail !== null && typeof candidate.detail === "object" && !Array.isArray(candidate.detail);
1385
- }
1386
1403
  function commandError(cause, fallbackCode) {
1387
- if (isCommandError(cause)) return cause;
1404
+ if (cause !== null && typeof cause === "object" && "code" in cause && "expected" in cause && "hint" in cause && "detail" in cause) {
1405
+ return cause;
1406
+ }
1388
1407
  return {
1389
1408
  code: fallbackCode,
1390
1409
  expected: "the ForgeaX command to complete",
@@ -1396,16 +1415,17 @@ var init_project = __esm({
1396
1415
  "src/project.ts"() {
1397
1416
  }
1398
1417
  });
1399
- function failure(error) {
1400
- return { ok: false, error };
1418
+
1419
+ // src/types.ts
1420
+ function resolveProjectPort(port) {
1421
+ return { port: port ?? 5173, strictPort: port !== 0 };
1401
1422
  }
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 {
1423
+ var init_types = __esm({
1424
+ "src/types.ts"() {
1407
1425
  }
1408
- return failure({ ...fallback, detail: { ...detail, diagnostic: stderr.join("\n") } });
1426
+ });
1427
+ function failure(error) {
1428
+ return { ok: false, error };
1409
1429
  }
1410
1430
  async function sourcesAt(path) {
1411
1431
  const info = await stat(path);
@@ -1490,15 +1510,16 @@ async function addGltf(sourcePath, dryRun) {
1490
1510
  stderrWrite: (line) => stderr.push(line)
1491
1511
  });
1492
1512
  if (exitCode !== 0) {
1493
- return producerFailure(
1494
- stderr,
1495
- {
1513
+ try {
1514
+ return failure(JSON.parse(stderr.at(-1) ?? ""));
1515
+ } catch {
1516
+ return failure({
1496
1517
  code: "asset-add-failed",
1497
1518
  expected: "the glTF producer to create or reuse a valid sidecar",
1498
- hint: "Inspect the glTF source and its external references."
1499
- },
1500
- { source: sourcePath }
1501
- );
1519
+ hint: "Inspect the glTF source and its external references.",
1520
+ detail: { source: sourcePath, diagnostic: stderr.join("\n") }
1521
+ });
1522
+ }
1502
1523
  }
1503
1524
  return { ok: true, value: { source: sourcePath, metaPath: `${sourcePath}.meta.json` } };
1504
1525
  }
@@ -1541,15 +1562,16 @@ async function entries(options) {
1541
1562
  { stdoutWrite: (line) => stdout.push(line), stderrWrite: (line) => stderr.push(line) }
1542
1563
  );
1543
1564
  if (!result.ok) {
1544
- return producerFailure(
1545
- stderr,
1546
- {
1565
+ try {
1566
+ return failure(JSON.parse(stderr.at(-1) ?? ""));
1567
+ } catch {
1568
+ return failure({
1547
1569
  code: "asset-authority-invalid",
1548
1570
  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
- );
1571
+ hint: "Repair the first invalid asset authority reported by the scanner.",
1572
+ detail: { diagnostic: stderr.join("\n") }
1573
+ });
1574
+ }
1553
1575
  }
1554
1576
  return { ok: true, value: { facts: facts.value, entries: result.value } };
1555
1577
  }
@@ -1634,7 +1656,6 @@ var init_package = __esm({
1634
1656
  "@forgeax/engine-audio-webaudio": "workspace:*",
1635
1657
  "@forgeax/engine-fbx": "workspace:*",
1636
1658
  "@forgeax/engine-font": "workspace:*",
1637
- "@forgeax/engine-geometry": "workspace:*",
1638
1659
  "@forgeax/engine-gltf": "workspace:*",
1639
1660
  "@forgeax/engine-image": "workspace:*",
1640
1661
  "@forgeax/engine-input": "workspace:*",
@@ -1644,6 +1665,7 @@ var init_package = __esm({
1644
1665
  "@forgeax/engine-preview": "workspace:*",
1645
1666
  "@forgeax/engine-project": "workspace:*",
1646
1667
  "@forgeax/engine-rhi-debug": "workspace:*",
1668
+ "@forgeax/engine-rhi-null": "workspace:*",
1647
1669
  "@forgeax/engine-render": "workspace:*",
1648
1670
  "@forgeax/engine-render-graph": "workspace:*",
1649
1671
  "@forgeax/engine-runtime": "workspace:*",
@@ -1661,7 +1683,6 @@ var init_package = __esm({
1661
1683
  vitest: "4.1.5"
1662
1684
  },
1663
1685
  devDependencies: {
1664
- "@forgeax/engine-rhi-null": "workspace:*",
1665
1686
  "@types/node": "^20.14.0"
1666
1687
  },
1667
1688
  forgeax: {
@@ -1875,6 +1896,20 @@ var init_sdk = __esm({
1875
1896
  "src/sdk.ts"() {
1876
1897
  }
1877
1898
  });
1899
+ function agentOnboarding(sdk, projectRoot) {
1900
+ const root = projectRoot ?? sdk.root;
1901
+ return {
1902
+ read: [
1903
+ resolve(root, "AGENTS.md"),
1904
+ resolve(root, "skills", "forgeax-engine-sdk", "SKILL.md"),
1905
+ resolve(root, "skills", "forgeax-engine-sdk", "references", "feature-catalog.md")
1906
+ ],
1907
+ next: projectRoot === void 0 ? {
1908
+ cwd: root,
1909
+ argv: ["node", "./bin/forgeax.mjs", "new", "../my-game"]
1910
+ } : { cwd: root, argv: ["pnpm", "exec", "forgeax", "list", "--json"] }
1911
+ };
1912
+ }
1878
1913
  function sdkInitPath(sdk) {
1879
1914
  return resolve(sdk.root, ...SDK_INIT_PATH);
1880
1915
  }
@@ -1889,7 +1924,7 @@ function sdkProjectInstallArgs(store) {
1889
1924
  "--side-effects-cache=true",
1890
1925
  "--child-concurrency=1"
1891
1926
  ];
1892
- return store === void 0 ? common : [...common, "--offline", "--trust-lockfile", "--store-dir", store];
1927
+ return store === void 0 ? common : [...common, "--offline", "--config.trust-lockfile=true", "--store-dir", store];
1893
1928
  }
1894
1929
  function sdkBootstrapInstallArgs(store) {
1895
1930
  const common = [
@@ -1898,7 +1933,7 @@ function sdkBootstrapInstallArgs(store) {
1898
1933
  "--child-concurrency=1",
1899
1934
  "--side-effects-cache=true"
1900
1935
  ];
1901
- return store === void 0 ? common : [...common, "--offline", "--trust-lockfile", "--store-dir", store];
1936
+ return store === void 0 ? common : [...common, "--offline", "--config.trust-lockfile=true", "--store-dir", store];
1902
1937
  }
1903
1938
  function supportedPnpm(version) {
1904
1939
  const [major = 0, minor = 0] = version.split(".").map(Number);
@@ -1988,7 +2023,8 @@ async function sdkInitCommand(sdk, options = {}) {
1988
2023
  const report = {
1989
2024
  ...state,
1990
2025
  root: sdk.root,
1991
- store: sdk.store === void 0 ? "registry" : "offline"
2026
+ store: sdk.store === void 0 ? "registry" : "offline",
2027
+ onboarding: agentOnboarding(sdk)
1992
2028
  };
1993
2029
  if (options.dryRun === true || options.install === false) return { ok: true, value: report };
1994
2030
  let staging;
@@ -2023,6 +2059,107 @@ var init_sdk_bootstrap = __esm({
2023
2059
  SDK_INIT_SCHEMA_VERSION = "1.0.0";
2024
2060
  }
2025
2061
  });
2062
+
2063
+ // src/sdk-update.ts
2064
+ function parseVersion(version) {
2065
+ const match = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/.exec(
2066
+ version
2067
+ );
2068
+ if (match === null) return void 0;
2069
+ const major = match[1];
2070
+ const minor = match[2];
2071
+ const patch = match[3];
2072
+ if (major === void 0 || minor === void 0 || patch === void 0) return void 0;
2073
+ const prerelease = match[4]?.split(".");
2074
+ if (prerelease?.some(
2075
+ (identifier) => identifier.length === 0 || /^\d+$/.test(identifier) && /^0\d+/.test(identifier)
2076
+ )) {
2077
+ return void 0;
2078
+ }
2079
+ return {
2080
+ core: [major, minor, patch],
2081
+ ...prerelease === void 0 ? {} : { prerelease }
2082
+ };
2083
+ }
2084
+ function compareNumericIdentifier(current, latest) {
2085
+ if (current.length !== latest.length) return latest.length > current.length ? 1 : -1;
2086
+ if (current === latest) return 0;
2087
+ return latest > current ? 1 : -1;
2088
+ }
2089
+ function comparePrerelease(current, latest) {
2090
+ const length = Math.max(current.length, latest.length);
2091
+ for (let index = 0; index < length; index += 1) {
2092
+ const currentIdentifier = current[index];
2093
+ const latestIdentifier = latest[index];
2094
+ if (currentIdentifier === void 0) return 1;
2095
+ if (latestIdentifier === void 0) return -1;
2096
+ if (currentIdentifier === latestIdentifier) continue;
2097
+ const currentNumeric = /^\d+$/.test(currentIdentifier);
2098
+ const latestNumeric = /^\d+$/.test(latestIdentifier);
2099
+ if (currentNumeric && latestNumeric) {
2100
+ return compareNumericIdentifier(currentIdentifier, latestIdentifier);
2101
+ }
2102
+ if (currentNumeric !== latestNumeric) return latestNumeric ? -1 : 1;
2103
+ return latestIdentifier > currentIdentifier ? 1 : -1;
2104
+ }
2105
+ return 0;
2106
+ }
2107
+ function newerSdkVersion(currentVersion, latestVersion) {
2108
+ const current = parseVersion(currentVersion);
2109
+ const latest = parseVersion(latestVersion);
2110
+ if (current === void 0 || latest === void 0) return false;
2111
+ for (let index = 0; index < current.core.length; index += 1) {
2112
+ const precedence = compareNumericIdentifier(
2113
+ current.core[index] ?? "0",
2114
+ latest.core[index] ?? "0"
2115
+ );
2116
+ if (precedence !== 0) return precedence > 0;
2117
+ }
2118
+ if (current.prerelease !== void 0 && latest.prerelease === void 0) return true;
2119
+ if (current.prerelease === void 0 || latest.prerelease === void 0) return false;
2120
+ return comparePrerelease(current.prerelease, latest.prerelease) > 0;
2121
+ }
2122
+ function offline() {
2123
+ return ["1", "true"].includes((process.env.npm_config_offline ?? "").toLowerCase());
2124
+ }
2125
+ async function checkSdkUpdate(currentVersion) {
2126
+ if (process.env.FORGEAX_DISABLE_UPDATE_CHECK === "1") {
2127
+ return { status: "skipped", currentVersion, reason: "disabled" };
2128
+ }
2129
+ if (offline()) return { status: "skipped", currentVersion, reason: "offline" };
2130
+ try {
2131
+ const registry = process.env.npm_config_registry ?? "https://registry.npmjs.org/";
2132
+ const response = await fetch(
2133
+ new URL("@forgeax%2Fengine-sdk", `${registry.replace(/\/$/, "")}/`),
2134
+ {
2135
+ headers: { accept: "application/vnd.npm.install-v1+json" },
2136
+ signal: AbortSignal.timeout(2e3)
2137
+ }
2138
+ );
2139
+ if (!response.ok) throw new Error(`registry-http-${response.status}`);
2140
+ const metadata = await response.json();
2141
+ const latestVersion = metadata["dist-tags"]?.latest;
2142
+ if (typeof latestVersion !== "string") throw new Error("registry-latest-version-missing");
2143
+ return newerSdkVersion(currentVersion, latestVersion) ? {
2144
+ status: "available",
2145
+ currentVersion,
2146
+ latestVersion,
2147
+ migrationRisk: UPDATE_MIGRATION_RISK
2148
+ } : { status: "current", currentVersion, latestVersion };
2149
+ } catch (cause) {
2150
+ return {
2151
+ status: "unavailable",
2152
+ currentVersion,
2153
+ reason: cause instanceof Error ? cause.message : String(cause)
2154
+ };
2155
+ }
2156
+ }
2157
+ var UPDATE_MIGRATION_RISK;
2158
+ var init_sdk_update = __esm({
2159
+ "src/sdk-update.ts"() {
2160
+ 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.";
2161
+ }
2162
+ });
2026
2163
  function slash(path) {
2027
2164
  return path.split(sep).join("/");
2028
2165
  }
@@ -2650,9 +2787,16 @@ async function newCommand(options = {}) {
2650
2787
  env: { ...process.env, CI: "true" },
2651
2788
  maxBuffer: 16 * 1024 * 1024
2652
2789
  });
2790
+ const sdkUpdate = await checkSdkUpdate(sdk.manifest.sdkVersion);
2653
2791
  return {
2654
2792
  ok: true,
2655
- value: { root, template: templateId, sdkVersion: sdk.manifest.sdkVersion }
2793
+ value: {
2794
+ root,
2795
+ template: templateId,
2796
+ sdkVersion: sdk.manifest.sdkVersion,
2797
+ onboarding: agentOnboarding(sdk, root),
2798
+ sdkUpdate
2799
+ }
2656
2800
  };
2657
2801
  } catch (cause) {
2658
2802
  if (staging !== void 0) await rm(staging, { recursive: true, force: true });
@@ -2685,6 +2829,7 @@ var init_bootstrap_commands = __esm({
2685
2829
  init_project();
2686
2830
  init_sdk();
2687
2831
  init_sdk_bootstrap();
2832
+ init_sdk_update();
2688
2833
  init_skill_install();
2689
2834
  execFileAsync2 = promisify(execFile);
2690
2835
  }
@@ -2853,491 +2998,12 @@ var init_plugin_authoring = __esm({
2853
2998
  execFileAsync3 = promisify(execFile);
2854
2999
  }
2855
3000
  });
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;
3323
- }
3324
- getCurrentTexture() {
3325
- return ok({});
3326
- }
3327
- };
3328
- rhi = {
3329
- requestAdapter,
3330
- acquireCanvasContext,
3331
- createShaderModule
3332
- };
3333
- }
3334
- });
3335
3001
  function createCliRhiDebugOperationContext() {
3336
3002
  return {
3337
3003
  captureFrame: async () => err(
3338
3004
  createRhiDebugError("capture-unavailable", {
3339
3005
  stage: "capture",
3340
- cause: "the standalone CLI has no live App capture provider"
3006
+ cause: "the standalone CLI has no live App capture provider; start a recorder-enabled live host and invoke its rhiCapture root"
3341
3007
  })
3342
3008
  ),
3343
3009
  async readArtifact(artifact) {
@@ -3400,7 +3066,6 @@ function createCliRhiDebugOperationContext() {
3400
3066
  }
3401
3067
  var init_cli_context = __esm({
3402
3068
  "src/rhi-debug/cli-context.ts"() {
3403
- init_dist3();
3404
3069
  }
3405
3070
  });
3406
3071
  function createRhiDebugOperationContext(host) {
@@ -3738,7 +3403,15 @@ async function sdkInstallCommand(options) {
3738
3403
  await rename(resolve(staging, name), resolve(root, name));
3739
3404
  }
3740
3405
  }
3741
- return { ok: true, value: { root, sdkVersion: version, source: "@forgeax/engine-sdk" } };
3406
+ return {
3407
+ ok: true,
3408
+ value: {
3409
+ root,
3410
+ sdkVersion: version,
3411
+ source: "@forgeax/engine-sdk",
3412
+ next: { cwd: root, argv: ["node", "./bin/forgeax.mjs", "init"] }
3413
+ }
3414
+ };
3742
3415
  } catch (cause) {
3743
3416
  return { ok: false, error: commandError(cause, "sdk-install-failed") };
3744
3417
  } finally {
@@ -3821,6 +3494,21 @@ __export(commands_exports, {
3821
3494
  function runRhiDebugCommand(name, input, context) {
3822
3495
  return runRhiDebugOperation(name, input, context);
3823
3496
  }
3497
+ async function materializeViteDevPort(port) {
3498
+ if (port !== 0) return port;
3499
+ const reservation = createServer$1();
3500
+ await new Promise((resolveListen, rejectListen) => {
3501
+ reservation.once("error", rejectListen);
3502
+ reservation.listen(0, "127.0.0.1", resolveListen);
3503
+ });
3504
+ const address = reservation.address();
3505
+ const assigned = typeof address === "object" && address !== null ? address.port : 0;
3506
+ await new Promise((resolveClose, rejectClose) => {
3507
+ reservation.close((error) => error === void 0 ? resolveClose() : rejectClose(error));
3508
+ });
3509
+ if (assigned === 0) throw new Error("OS did not assign an ephemeral preview port");
3510
+ return assigned;
3511
+ }
3824
3512
  async function buildCommand(options = {}) {
3825
3513
  const facts = await readProjectFacts(options.root);
3826
3514
  if (!facts.ok) return facts;
@@ -3923,13 +3611,38 @@ async function devCommand(options = {}) {
3923
3611
  const facts = await readProjectFacts(options.root);
3924
3612
  if (!facts.ok) return facts;
3925
3613
  const previous = process.cwd();
3614
+ let server;
3926
3615
  try {
3616
+ const { createServer: createServer5 } = await import('vite');
3617
+ const { createViteConfig: createViteConfig2 } = await Promise.resolve().then(() => (init_host(), host_exports));
3618
+ const port = resolveProjectPort(options.port);
3619
+ const vitePort = { ...port, port: await materializeViteDevPort(port.port) };
3927
3620
  process.chdir(facts.value.root);
3928
- const server = await createServer(await createViteConfig(facts.value, "serve"));
3929
- await server.listen();
3621
+ server = await createServer5(
3622
+ await createViteConfig2(facts.value, "serve", "/", {
3623
+ server: vitePort
3624
+ })
3625
+ );
3626
+ await server.listen(vitePort.port);
3930
3627
  if (options.json !== true) server.printUrls();
3931
- return { ok: true, value: { root: facts.value.root, urls: server.resolvedUrls } };
3628
+ return {
3629
+ ok: true,
3630
+ value: {
3631
+ root: facts.value.root,
3632
+ urls: server.resolvedUrls,
3633
+ mode: "dev",
3634
+ serves: "source",
3635
+ capabilities: {
3636
+ "rhi.capture": {
3637
+ available: false,
3638
+ realm: "host",
3639
+ reason: "standalone-dev-server-has-no-live-app-cli-attachment"
3640
+ }
3641
+ }
3642
+ }
3643
+ };
3932
3644
  } catch (cause) {
3645
+ await server?.close();
3933
3646
  process.chdir(previous);
3934
3647
  return { ok: false, error: commandError(cause, "dev-server-failed") };
3935
3648
  }
@@ -3939,15 +3652,31 @@ async function previewCommand(options = {}) {
3939
3652
  const verified = await verifyDist(resolve(root, "dist"));
3940
3653
  if (!verified.ok) return verified;
3941
3654
  try {
3655
+ const port = resolveProjectPort(options.port);
3942
3656
  const server = await preview({
3943
3657
  root,
3944
3658
  configFile: false,
3945
3659
  base: verified.value.base,
3946
- preview: { open: false, port: 0, strictPort: false },
3660
+ preview: { open: false, ...port },
3947
3661
  build: { outDir: resolve(root, "dist") }
3948
3662
  });
3949
3663
  if (options.json !== true) server.printUrls();
3950
- return { ok: true, value: { root, urls: server.resolvedUrls } };
3664
+ return {
3665
+ ok: true,
3666
+ value: {
3667
+ root,
3668
+ urls: server.resolvedUrls,
3669
+ mode: "preview",
3670
+ serves: "dist",
3671
+ capabilities: {
3672
+ "rhi.capture": {
3673
+ available: false,
3674
+ realm: "host",
3675
+ reason: "static-dist-host-does-not-install-dev-capture"
3676
+ }
3677
+ }
3678
+ }
3679
+ };
3951
3680
  } catch (cause) {
3952
3681
  return { ok: false, error: commandError(cause, "preview-server-failed") };
3953
3682
  }
@@ -3997,6 +3726,7 @@ var init_commands = __esm({
3997
3726
  init_dist();
3998
3727
  init_host();
3999
3728
  init_project();
3729
+ init_types();
4000
3730
  init_assets();
4001
3731
  init_bootstrap_commands();
4002
3732
  init_plugin_authoring();
@@ -4391,7 +4121,7 @@ function hasStaticToolDeclarations(entry) {
4391
4121
  return Array.isArray(Reflect.get(entry.config, "tools"));
4392
4122
  }
4393
4123
  async function createViteModuleLoader(root) {
4394
- const server = await createServer({
4124
+ const server = await createServer$2({
4395
4125
  root,
4396
4126
  appType: "custom",
4397
4127
  configFile: false,
@@ -4773,9 +4503,11 @@ function createAdmissionReport(recipe, samples, thresholds = DEFAULT_ADMISSION_T
4773
4503
  async function runBenchmarkAdmission(options) {
4774
4504
  const sampleCount = options.thresholds?.sampleCountPerPhase ?? 30;
4775
4505
  const samples = [];
4776
- for (const phase of ["cold", "warm"]) {
4506
+ const phases = ["cold", "warm"];
4507
+ const modes = ["private", "service"];
4508
+ for (const phase of phases) {
4777
4509
  for (let index = 0; index < sampleCount; index += 1) {
4778
- for (const mode of ["private", "service"]) {
4510
+ for (const mode of modes) {
4779
4511
  const measurement = await options.measure(mode, phase, index);
4780
4512
  samples.push({
4781
4513
  mode,
@@ -4882,7 +4614,7 @@ function reply(response, status, payload) {
4882
4614
  async function createCarrierProviderService(options) {
4883
4615
  const host = options.host ?? "127.0.0.1";
4884
4616
  const bearerToken = options.machine.offer.bearerToken;
4885
- const server = createServer$1(async (request, response) => {
4617
+ const server = createServer(async (request, response) => {
4886
4618
  if (request.method !== "POST" || !request.url?.startsWith("/carrier/")) {
4887
4619
  reply(
4888
4620
  response,
@@ -5318,7 +5050,7 @@ function projectUri(projectRoot, path) {
5318
5050
  return relative(projectRoot, path).split(sep).join("/");
5319
5051
  }
5320
5052
  async function allocateLoopbackPort() {
5321
- const probe = createServer$2();
5053
+ const probe = createServer$1();
5322
5054
  try {
5323
5055
  await new Promise((resolve16, reject) => {
5324
5056
  const onError = (error) => {
@@ -5497,7 +5229,7 @@ async function runBrowserPreviewHost(projectRoot, recipe, snapshot, runId, signa
5497
5229
  let server;
5498
5230
  try {
5499
5231
  const port = await allocateLoopbackPort();
5500
- server = await createServer({
5232
+ server = await createServer$2({
5501
5233
  ...config,
5502
5234
  cacheDir,
5503
5235
  logLevel: "silent",
@@ -5619,9 +5351,7 @@ async function runBrowserPreviewHost(projectRoot, recipe, snapshot, runId, signa
5619
5351
  );
5620
5352
  });
5621
5353
  await Promise.all(responseDiagnostics);
5622
- if (!captured.ok) {
5623
- return browserFailure("capture-run", captured.error, pageErrors);
5624
- }
5354
+ if (!captured.ok) return browserFailure("capture-run", captured.error, pageErrors);
5625
5355
  if (pageErrors.length > 0)
5626
5356
  return browserFailure("capture-page-runtime", pageErrors[0], pageErrors);
5627
5357
  const capturePng = await page.screenshot({ type: "png" });
@@ -5962,7 +5692,7 @@ function isAuthorized(request, bearerToken) {
5962
5692
  async function createAuthenticatedLoopbackService(options) {
5963
5693
  if (options.bearerToken.length < 8) throw new TypeError("service bearer token is too short");
5964
5694
  const host = options.host ?? "127.0.0.1";
5965
- const server = createServer$1(async (request, response) => {
5695
+ const server = createServer(async (request, response) => {
5966
5696
  if (request.method !== "POST" || request.url !== "/run") {
5967
5697
  reply2(response, 404, { error: "service route not found" });
5968
5698
  return;
@@ -6025,6 +5755,9 @@ async function createAuthenticatedLoopbackService(options) {
6025
5755
  };
6026
5756
  }
6027
5757
 
6028
- export { DEFAULT_ADMISSION_THRESHOLDS, RHI_DEBUG_OPERATION_MANIFEST, analyzePreviewArtifacts, assetAddCommand, assetInspectCommand, assetListCommand, assetVerifyCommand, bootstrapRealm, buildCommand, createAdmissionReport, createAuthenticatedLoopbackService, createAuthorContribution, createBuildContribution, createCarrierProvider, createCarrierProviderService, createCarrierRendezvous, createCliRhiDebugOperationContext, createDefaultContributions, createDevkitToolRuntime, createDomainPreviewContributions, createInitPlan, createMigrationRoster, createOfflineAnalysisContribution, createPreviewContribution, createPreviewContributions, createPreviewToolRuntime, createResourceProbe, createRhiDebugOperationContext, createServiceCache, createServiceExecutor, createToolClient, describeTool, devCommand, discoverRhiDebugOperations, doctorCommand, initCommand, listTools, materializeToolCatalog, newCommand, packageCommand, pluginInstallCommand, pluginUninstallCommand, previewCommand, readProjectFacts, rebuildToolCatalog, recoverRhiDebugError, renderRhiDebugHelp, resolveMigration, resolveRealmCapability, runBenchmarkAdmission, runCarrierPreviewRoute, runGenericTool, runLibraryTool, runNamedTool, runPreviewHost, runPrivateTool, runRhiDebugCommand, runRhiDebugOperation, sdkInstallCommand, shaderCheckCommand, skillInstallCommand, skillVerifyCommand, summarizeBenchmarkSamples, testCommand, verifyDist, writeDistManifest };
5758
+ // src/index.ts
5759
+ init_types();
5760
+
5761
+ export { DEFAULT_ADMISSION_THRESHOLDS, RHI_DEBUG_OPERATION_MANIFEST, analyzePreviewArtifacts, assetAddCommand, assetInspectCommand, assetListCommand, assetVerifyCommand, bootstrapRealm, buildCommand, createAdmissionReport, createAuthenticatedLoopbackService, createAuthorContribution, createBuildContribution, createCarrierProvider, createCarrierProviderService, createCarrierRendezvous, createCliRhiDebugOperationContext, createDefaultContributions, createDevkitToolRuntime, createDomainPreviewContributions, createInitPlan, createMigrationRoster, createOfflineAnalysisContribution, createPreviewContribution, createPreviewContributions, createPreviewToolRuntime, createResourceProbe, createRhiDebugOperationContext, createServiceCache, createServiceExecutor, createToolClient, describeTool, devCommand, discoverRhiDebugOperations, doctorCommand, initCommand, listTools, materializeToolCatalog, newCommand, packageCommand, pluginInstallCommand, pluginUninstallCommand, previewCommand, readProjectFacts, rebuildToolCatalog, recoverRhiDebugError, renderRhiDebugHelp, resolveMigration, resolveProjectPort, resolveRealmCapability, runBenchmarkAdmission, runCarrierPreviewRoute, runGenericTool, runLibraryTool, runNamedTool, runPreviewHost, runPrivateTool, runRhiDebugCommand, runRhiDebugOperation, sdkInstallCommand, shaderCheckCommand, skillInstallCommand, skillVerifyCommand, summarizeBenchmarkSamples, testCommand, verifyDist, writeDistManifest };
6029
5762
  //# sourceMappingURL=index.mjs.map
6030
5763
  //# sourceMappingURL=index.mjs.map