@vgai/cli 0.5.10 → 0.5.12

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 (3) hide show
  1. package/README.md +1 -1
  2. package/dist/index.js +309 -367
  3. package/package.json +7 -7
package/README.md CHANGED
@@ -74,7 +74,7 @@ vgai upgrade [--report] report engine drift vs. the project's engine pin
74
74
  present reports "not a scaffolded project" instead of
75
75
  dumping the whole template — see `vgai validate` instead)
76
76
  vgai validate [folder] in-process: validate vgai.project.json, check every declared
77
- entry/scene/entryHtml/bundleUrl file exists, print the engine
77
+ entry/contractShim file exists, print the engine
78
78
  pin (default: cwd) — no monorepo Vite/editor stack required
79
79
  Exit codes: 0 valid, 1 invalid manifest or missing file, 2 usage
80
80
  npm run vgai -- add deploy-vercel add project-owned Vercel scripts/config
package/dist/index.js CHANGED
@@ -413,6 +413,51 @@ var init_client = __esm({
413
413
  const data = await this.command({ type: "inspect" });
414
414
  return data.subject;
415
415
  }
416
+ /**
417
+ * The HIERARCHY PANEL's actual rendered row tree, as data.
418
+ *
419
+ * The same rows a human is looking at: the adapter's tree after the component
420
+ * marks fold implementation subtrees, after the internals reveal, after the
421
+ * document promotion, the child cap, the collapse state, the search filter
422
+ * and the selection scope. Works in play mode and edit mode alike — the
423
+ * answer reports which (`playState`, `activeViewportTab`), because a
424
+ * play-mode tree and an edit-mode tree come from different adapters.
425
+ *
426
+ * Deliberately NOT `status().entities`, which walks the raw adapter tree and
427
+ * therefore answers a different question: a panel defect is invisible in it.
428
+ *
429
+ * Each row carries `childCount` (what its caret opens), `internalChildCount`
430
+ * (what is folded behind "Reveal Internals") and `expandable` (whether the
431
+ * panel draws a caret at all) — so "this subtree exists but the UI offers no
432
+ * way to open it" is a readable fact rather than something only a human
433
+ * squinting at the panel can notice.
434
+ *
435
+ * Rejects, naming the panel, when no hierarchy panel is mounted: an empty
436
+ * tree would be a fabricated answer about a surface nobody is being shown.
437
+ */
438
+ async hierarchy() {
439
+ const data = await this.command({ type: "hierarchy" });
440
+ return data.hierarchy;
441
+ }
442
+ /** Write one editable path through the active Inspector's own IO. */
443
+ async setInspectionField(path, value) {
444
+ const data = await this.command({
445
+ type: "set-inspection-field",
446
+ path,
447
+ value
448
+ });
449
+ return data.subject;
450
+ }
451
+ /**
452
+ * Undo / redo one project transaction — the same queue the keyboard shortcut
453
+ * drives. `moved` is false when there was nothing left in that direction.
454
+ */
455
+ async undo() {
456
+ return this.command({ type: "undo" });
457
+ }
458
+ async redo() {
459
+ return this.command({ type: "redo" });
460
+ }
416
461
  /** Read the editor's actual current durable projection. */
417
462
  async currentView() {
418
463
  const data = await this.command({ type: "current-view" });
@@ -425,7 +470,30 @@ var init_client = __esm({
425
470
  ...size === void 0 ? {} : { size }
426
471
  });
427
472
  }
473
+ /**
474
+ * Read or drive the ACTIVE center document's own DOM — the scoped
475
+ * editor-chrome door, and the read/gesture half of the same subject
476
+ * {@link captureActiveDocument} photographs. NOT play-mode gated, and NOT
477
+ * page automation: a target outside the active document's container is
478
+ * refused by name. Design and scope contract:
479
+ * `packages/editor/src/editor-document-probe.ts`.
480
+ */
481
+ async documentProbe(step) {
482
+ return this.command({ type: "document-probe", step });
483
+ }
428
484
  // --- Display (set semantics) ---
485
+ /**
486
+ * The Game document's "Persist to game source" consent, over the relay —
487
+ * the same session-scoped switch the checkbox flips, refused for the same
488
+ * reasons (it answers with the server's own words when the base cannot be
489
+ * written). `recorder` names who accounts for the diff a write produces.
490
+ */
491
+ async setSourcePersistConsent(enabled) {
492
+ return this.command({
493
+ type: "set-source-persist-consent",
494
+ enabled
495
+ });
496
+ }
429
497
  async setGrid(enabled) {
430
498
  await this.command({ type: "set-grid", enabled });
431
499
  }
@@ -18296,6 +18364,9 @@ var init_inspection_operations = __esm({
18296
18364
  id: external_exports.string(),
18297
18365
  title: external_exports.string(),
18298
18366
  kindLabel: external_exports.string().optional(),
18367
+ note: external_exports.string().optional().describe(
18368
+ "The identity row\u2019s own line \u2014 for an ingest mount, where the object was CONSTRUCTED (`Source \xB7 game.js:271`), or the reason it has no such line."
18369
+ ),
18299
18370
  hint: external_exports.string().optional().describe("The quiet line a subject with nothing to edit carries."),
18300
18371
  presentation: external_exports.object({
18301
18372
  preferred: external_exports.string().describe("The surface\u2019s presentation affinity: card|column."),
@@ -18311,6 +18382,7 @@ var init_inspection_operations = __esm({
18311
18382
  disabled: external_exports.boolean().optional()
18312
18383
  })
18313
18384
  ),
18385
+ related: external_exports.array(external_exports.object({ id: external_exports.string(), title: external_exports.string() })).describe("Agent-visible counterparts of the inspector\u2019s related-document buttons."),
18314
18386
  sections: external_exports.array(InspectedSectionSchema)
18315
18387
  });
18316
18388
  NothingInspectedSchema = external_exports.object({ none: external_exports.literal(true).describe("There is no inspector showing.") }).describe("Nothing is being inspected.");
@@ -39603,36 +39675,12 @@ function rejectRemovedRootAdapterSpelling(value, ctx) {
39603
39675
  function rootMedium(root) {
39604
39676
  return typeof root.adapter === "string" ? root.adapter : root.adapter.surface;
39605
39677
  }
39606
- var GAME_MANIFEST_VERSION, IngestStrategySchema, TierSchema, REMOVED_SURFACE_SPELLING, REMOVED_BARE_ADAPTER_SPELLING, RootAdapterSchema, AdapterRootSchema, LearnKindSchema, LearnDifficultySchema, LearnMetadataSchema, GameManifestSchema;
39678
+ var GAME_MANIFEST_VERSION, REMOVED_SURFACE_SPELLING, REMOVED_BARE_ADAPTER_SPELLING, RootAdapterSchema, AdapterRootSchema, LearnKindSchema, LearnDifficultySchema, LearnMetadataSchema, GameManifestSchema;
39607
39679
  var init_schema = __esm({
39608
39680
  "../engine/src/manifest/schema.ts"() {
39609
39681
  "use strict";
39610
39682
  init_zod();
39611
39683
  GAME_MANIFEST_VERSION = 2;
39612
- IngestStrategySchema = external_exports.enum([
39613
- "shared",
39614
- // game imports host-served ESM three — full in-realm capture
39615
- "deduped",
39616
- // game source built by OUR pipeline, bundler dedupe — full capture
39617
- "iframe-reachable",
39618
- // prebuilt bundle in iframe, three reachable — cross-realm capture
39619
- "opaque-embed"
39620
- // prebuilt bundle, nothing reachable — host lifecycle only
39621
- ]);
39622
- TierSchema = external_exports.enum([
39623
- "first-party",
39624
- // vgai-native root: full editor, full loop control
39625
- "shared",
39626
- // = IngestStrategy 'shared' rung
39627
- "deduped",
39628
- // = IngestStrategy 'deduped' rung
39629
- "iframe-reachable",
39630
- // = IngestStrategy 'iframe-reachable' rung
39631
- "opaque-embed",
39632
- // = IngestStrategy 'opaque-embed' rung
39633
- "unsupported"
39634
- // this root does not function in this delivery context
39635
- ]);
39636
39684
  REMOVED_SURFACE_SPELLING = {
39637
39685
  threejs: "three",
39638
39686
  pixijs: "canvas",
@@ -39654,11 +39702,11 @@ var init_schema = __esm({
39654
39702
  external_exports.object({
39655
39703
  surface: external_exports.enum(["three", "canvas", "dom"]).describe("Native surface captured from the unmodified game"),
39656
39704
  ingest: external_exports.object({
39657
- strategy: IngestStrategySchema.describe(
39658
- "Capture mechanics rung this ingested root uses (\xA74) \u2014 also the root`s natural local capability tier"
39705
+ contractShim: external_exports.string().optional().describe(
39706
+ "Project-relative path to a HOST-ADDED ES module that declares this game's `window.vgaiGame` contract (adapter/ingest/game-contract.ts) WITHOUT editing a vendored byte \u2014 the pristine-copy door. The host imports it immediately BEFORE the game's own entry module, in the editor's realm. TIMING CONTRACT, which a shim author must obey: because it runs before the game`s entry, it MUST assign `window.vgaiGame` SYNCHRONOUSLY at top level. Anything that needs the game`s own modules must be LAZY: a dynamic `import()` INSIDE each verb/provider closure, resolved at call time. Importing a game module eagerly from the shim would evaluate it ahead of the game`s own entry and reorder its module side effects. Every such `import()` MUST take a STRING LITERAL, never a variable/table lookup. A variable specifier is not statically analyzable, so the dev server leaves it alone and it resolves at runtime against the shim`s own `/@fs/` url \u2014 while the game`s entry has had ITS relative imports rewritten to whatever url the server considers canonical for those files. Module identity is per-url, so the shim silently binds a SECOND, freshly-evaluated copy of the whole game and every provider reports a game that never started. Measured on three-descent: 285 segments through the game`s graph, 0 through the shim`s, and no error anywhere."
39659
39707
  ),
39660
- entryHtml: external_exports.string().optional().describe(
39661
- "Entry HTML path, project-relative. WIRED for opaque-embed (D-W2): the file`s bytes are read verbatim through the project-static route and hosted in a sandboxed iframe (embed-only, no scene introspection), for both canvas and three roots. Legal but NOT wired to a mount for iframe-reachable (a named error at resolve names this \u2014 use `bundleUrl` instead, the wired reachable route)."
39708
+ dataWriter: external_exports.string().optional().describe(
39709
+ "Project-relative path to a HOST-ADDED ES module that writes this game's own DATA file back \u2014 the sibling of a source write for a game whose authorable truth is not source. Descent places its robots, powerups and hostages in binary records inside `descent.hog`, reaching no source literal, so an authored move of one is an edit to that file and nothing else can write it. The module declares two exports and the host knows nothing else about it: `dataFile` (a project-relative string \u2014 the ONE file this writer edits) and `planDataEdit(bytes, {record, property, baseline, next})`, which returns `{changed: true, bytes}` or `{changed: false, reason}` and may be async. It is loaded in the editor realm beside the game, so it may `import()` the game's own modules to resolve what only a running game knows (Descent's writer asks the game`s own `find_point_seg` which segment a moved object landed in, and refuses \"outside the mine\" rather than writing data the game would read back wrong). It NEVER fabricates: a record it cannot address, or a property it does not model, is a named refusal. An object is anchored to a record by carrying `userData.vgaiRecordIndex` \u2014 the game declares that identity itself (a recorded patch or a shim); the host never infers one."
39662
39710
  ),
39663
39711
  assets: external_exports.record(external_exports.string(), external_exports.string()).optional().describe(
39664
39712
  "Path-substring -> served-URL rewrites for this ingested game (IngestGame.assets today)"
@@ -39667,63 +39715,11 @@ var init_schema = __esm({
39667
39715
  "DOM API stub ids this ingested game requires to run headlessly/in-realm"
39668
39716
  ),
39669
39717
  captureTimeoutMs: external_exports.number().optional().describe(
39670
- "How long to wait for capture before REPORTING the degrade loudly \u2014 not how long to keep listening. On the self-hosted-three route the devtools subscription is permanent, so a renderer appearing later still upgrades a reported-embed-only mount in place; the host clamps this value so a long one cannot block the editor on a question the upgrade path answers anyway."
39671
- ),
39672
- // The six iframe-reachable-multi mount fields
39673
- // (`ingest-iframe-2d.ts`'s `IframeReachableMultiOpts`), legal
39674
- // ONLY when `strategy` is 'iframe-reachable' (enforced by the
39675
- // `.superRefine` below).
39676
- bundleUrl: external_exports.string().optional().describe(
39677
- "Absolute URL of the externalized built game bundle \u2014 the WIRED iframe-reachable mount route (iframe-reachable only), for BOTH canvas (Track P, ingest-iframe-2d.ts's mountIngestGame2DIframeReachableMulti) and three (D-W1, ingest-iframe-reachable-adapter.ts's mountIngestGameIframeReachableBundle). PRECONDITION: the bundle must externalize its root`s own runtime as a bare, un-rewritten import (`pixi.js` for canvas, `three` for three \u2014 `vgai bundle` does this for you) so it resolves through the iframe importmap to the HOST instance the capture trap is installed on; a bundle with an inlined runtime is unreachable and degrades to the embed-only floor. Exactly one of bundleUrl/entryHtml is required when strategy is iframe-reachable."
39678
- ),
39679
- baseHref: external_exports.string().optional().describe(
39680
- "Iframe <base href> so the game's relative asset URLs resolve (iframe-reachable only)."
39681
- ),
39682
- assetBaseUrl: external_exports.string().optional().describe(
39683
- "Host-side basePath forced onto the trapped runtime's own asset resolution \u2014 pixi's `Assets.init` for a canvas root, three's `DefaultLoadingManager.setURLModifier` for a three root (D-W1) \u2014 absolute, or root-relative (resolved against the editor page's own origin at mount time, ingest-mode.ts, so a static manifest never has to know the dev-server port) (iframe-reachable only \u2014 a document.write iframe keeps the parent window.location)."
39684
- ),
39685
- extraDeps: external_exports.array(external_exports.string()).optional().describe(
39686
- "Bare specifiers of this root's externalized deps beyond its own runtime (pixi.js/three \u2014 e.g. '@pixi/sound', 'gsap'). Resolved by the editor's host-namespace registry into module namespaces when known (D-P1 \u2014 the manifest carries data, not code); an unregistered specifier falls back to the open project's own node_modules (D-Z3), served as a verbatim iframe importmap URL. Throws a named error if neither resolves it (iframe-reachable only)."
39687
- ),
39688
- pixiModuleUrl: external_exports.string().optional().describe(
39689
- 'PIXI-ONLY: standalone matching-major pixi ESM URL to trap instead of the host pixi, for version-skewed games (e.g. a v6 game on a v8 host); this field\'s PRESENCE is how the skew is expressed (D-P2 \u2014 no separate strategy/tier for version skew) (iframe-reachable only). Declaring this on a `kind: "three"` root is schema-legal (this field is kind-agnostic here) but throws a NAMED not-supported error at resolve \u2014 three has no version-skew mount this wave (D-W7: a hypothetical `threeModuleUrl` is explicitly parked, no consumer exists).'
39690
- ),
39691
- bodyHtml: external_exports.string().optional().describe(
39692
- 'HTML injected into the iframe body before the game script boots (e.g. a `<div id="game-root">` the game expects to find) (iframe-reachable only).'
39718
+ "How long the host waits for this game's first captured frame before the mount FAILS by name. A game that needs a long boot (streamed world assets, a menu the player must clear) raises it; the default is 10s."
39693
39719
  )
39694
- }).describe(
39720
+ }).strict().describe(
39695
39721
  "Ingest adapter configuration for an unmodified game (a repo-vendored game or your own external folder)"
39696
- ).superRefine((ingest, ctx) => {
39697
- const iframeOnlyFields = [
39698
- ["bundleUrl", ingest.bundleUrl !== void 0],
39699
- ["baseHref", ingest.baseHref !== void 0],
39700
- ["assetBaseUrl", ingest.assetBaseUrl !== void 0],
39701
- ["extraDeps", ingest.extraDeps !== void 0],
39702
- ["pixiModuleUrl", ingest.pixiModuleUrl !== void 0],
39703
- ["bodyHtml", ingest.bodyHtml !== void 0]
39704
- ];
39705
- if (ingest.strategy !== "iframe-reachable") {
39706
- for (const [key, present] of iframeOnlyFields) {
39707
- if (present) {
39708
- ctx.addIssue({
39709
- code: external_exports.ZodIssueCode.custom,
39710
- message: `\`${key}\` is only legal when strategy is 'iframe-reachable' (got "${ingest.strategy}") \u2014 bundleUrl/baseHref/assetBaseUrl/extraDeps/pixiModuleUrl/bodyHtml are the iframe-reachable-multi mount's options (canvas: ingest-iframe-2d.ts IframeReachableMultiOpts; three, D-W1: ingest-iframe-reachable-adapter.ts IframeReachableBundleOpts) and have no meaning under any other strategy.`,
39711
- path: [key]
39712
- });
39713
- }
39714
- }
39715
- return;
39716
- }
39717
- const hasBundleUrl = ingest.bundleUrl !== void 0;
39718
- const hasEntryHtml = ingest.entryHtml !== void 0;
39719
- if (hasBundleUrl === hasEntryHtml) {
39720
- ctx.addIssue({
39721
- code: external_exports.ZodIssueCode.custom,
39722
- message: `strategy 'iframe-reachable' requires EXACTLY ONE of \`bundleUrl\` (externalized multi-file bundle, the iframe-reachable-multi mount) or \`entryHtml\` (single-file/legacy iframe entry) \u2014 ${hasBundleUrl ? "both are present" : "neither is present"}.`,
39723
- path: hasBundleUrl ? ["bundleUrl"] : ["entryHtml"]
39724
- });
39725
- }
39726
- })
39722
+ )
39727
39723
  }).strict()
39728
39724
  ])
39729
39725
  ).describe(
@@ -39748,17 +39744,7 @@ var init_schema = __esm({
39748
39744
  "Marks this root as a DEV LAYER: the game's own dev GUI, not shipped game content. Four readers act on it. `mountManifestRoots` mounts it only when dev layers are enabled (`devLayersEnabled`, runtime/dev-layers.ts), so a production build carries no dev GUI; the host stacks every dev root ABOVE every non-dev root (a dev layer sits topmost, and as a DOM layer it steals no input from the game while closed); the play compositor EXCLUDES dev layers from a capture unless the capture asks for them; and the editor mounts a dev root as edit-time CHROME rather than authorable content (no hierarchy entry, no OID stamping, no JSX write-back, not selectable)."
39749
39745
  ),
39750
39746
  loop: external_exports.enum(["gated", "self-driven"]).default("gated").describe(
39751
- 'gated: host-driven loop (default). self-driven: this root drives its own loop (the "composited, unsynchronized" tier) \u2014 an axis independent of capability tier'
39752
- ),
39753
- capabilities: external_exports.object({
39754
- local: TierSchema.optional().describe(
39755
- "Declared capability tier when served via the local CLI (Vite pipeline); omitted -> derived (\xA74)"
39756
- ),
39757
- hosted: TierSchema.optional().describe(
39758
- "Declared capability tier when served hosted (no bundler: esbuild-wasm + externals only); omitted -> derived (\xA74)"
39759
- )
39760
- }).optional().describe(
39761
- "Per-delivery-context capability tier declarations (\xA74). Declaring above the adapter's ceiling for that context is an error; declaring at or below it (including `unsupported`) is legal."
39747
+ "gated: host-driven loop (default). self-driven: this root drives its own loop (composited, unsynchronized)"
39762
39748
  )
39763
39749
  }).strict();
39764
39750
  LearnKindSchema = external_exports.enum(["starter", "feature", "sample-game", "lesson-companion"]).describe(
@@ -39857,6 +39843,13 @@ var init_schema = __esm({
39857
39843
  width: external_exports.number().describe("Canvas width in pixels"),
39858
39844
  height: external_exports.number().describe("Canvas height in pixels")
39859
39845
  }).optional().describe("Canvas resolution"),
39846
+ rendering: external_exports.object({
39847
+ antialias: external_exports.boolean().describe(
39848
+ "Whether every three root is built with a multisampled drawing buffer. This is the ONE render property a world cannot declare for itself (world3d-react/renderer-config.ts): a WebGL context fixes its sample count at CREATION from this boolean, long before a world mounts, so it belongs to the PROJECT. The runtime reader is mount-manifest.ts, which threads it into createHostRenderer for each three root. WebGL exposes no sample COUNT \u2014 the implementation picks one (4x on every desktop browser measured), so a source engine that authored 8x/16x gets multisampling but not its exact count."
39849
+ )
39850
+ }).strict().optional().describe(
39851
+ "Construction-time renderer properties, which no world can declare after the fact. Everything a world CAN declare (tone mapping, output colour space, clear colour, shadow filter) lives on the world's own renderer config instead."
39852
+ ),
39860
39853
  debug: external_exports.object({
39861
39854
  allowInProduction: external_exports.boolean().describe(
39862
39855
  "Allow the ?vgai-debug=1 introspection bridge and debug-command invocation in production builds. Default false: the bridge only installs in dev builds. The runtime reader is the debug-bridge installer (D18)."
@@ -39913,6 +39906,11 @@ function checkAdapterEntryRules(root) {
39913
39906
  `Game manifest: root "${root.id}" uses the '${adapter}' adapter but declares no \`entry\` \u2014 a built-in adapter root is authored as an entry module.`
39914
39907
  );
39915
39908
  }
39909
+ if (typeof adapter === "object" && "ingest" in adapter && !root.entry) {
39910
+ throw new Error(
39911
+ `Game manifest: root "${root.id}" is an { ingest } root but declares no \`entry\` \u2014 the game's own entry module is what the host imports.`
39912
+ );
39913
+ }
39916
39914
  }
39917
39915
  function resolveAdapter(root) {
39918
39916
  const { adapter } = root;
@@ -39933,51 +39931,13 @@ function resolveAdapter(root) {
39933
39931
  type: "ingest",
39934
39932
  identity,
39935
39933
  surface: adapter.surface,
39936
- strategy: ingest.strategy,
39937
- entryHtml: ingest.entryHtml,
39934
+ contractShim: ingest.contractShim,
39935
+ dataWriter: ingest.dataWriter,
39938
39936
  assets: ingest.assets,
39939
39937
  domStubs: ingest.domStubs,
39940
- captureTimeoutMs: ingest.captureTimeoutMs,
39941
- bundleUrl: ingest.bundleUrl,
39942
- baseHref: ingest.baseHref,
39943
- assetBaseUrl: ingest.assetBaseUrl,
39944
- extraDeps: ingest.extraDeps,
39945
- pixiModuleUrl: ingest.pixiModuleUrl,
39946
- bodyHtml: ingest.bodyHtml
39938
+ captureTimeoutMs: ingest.captureTimeoutMs
39947
39939
  };
39948
39940
  }
39949
- function ceilingFor(adapter, context) {
39950
- if (typeof adapter === "string") return CAPABILITY_CEILINGS.builtin[context];
39951
- if (isModuleAdapter(adapter)) return CAPABILITY_CEILINGS.module[context];
39952
- return CAPABILITY_CEILINGS.ingest[adapter.ingest.strategy][context];
39953
- }
39954
- function resolveCapabilities(root) {
39955
- const contexts = ["local", "hosted"];
39956
- const result = {};
39957
- for (const context of contexts) {
39958
- const declared = root.capabilities?.[context];
39959
- if (declared !== void 0) {
39960
- const ceiling = ceilingFor(root.adapter, context);
39961
- if (TIER_RANK[declared] > TIER_RANK[ceiling]) {
39962
- throw new Error(
39963
- `Game manifest: root "${root.id}": declared capabilities.${context} tier "${declared}" exceeds the "${ceiling}" ceiling for this adapter in the ${context} context (\xA74). Under-claiming (including \`unsupported\`) is legal; over-claiming is not.`
39964
- );
39965
- }
39966
- result[context] = declared;
39967
- continue;
39968
- }
39969
- if (typeof root.adapter === "string") {
39970
- result[context] = "first-party";
39971
- } else if (isModuleAdapter(root.adapter)) {
39972
- throw new Error(
39973
- `Game manifest: root "${root.id}": { module } adapter requires an explicit capabilities.${context} declaration \u2014 a custom adapter's reach is not derivable (\xA74).`
39974
- );
39975
- } else {
39976
- result[context] = CAPABILITY_CEILINGS.ingest[root.adapter.ingest.strategy][context];
39977
- }
39978
- }
39979
- return result;
39980
- }
39981
39941
  function checkEngineVersionPin(version2) {
39982
39942
  if (!SEMVER_RE.test(version2)) {
39983
39943
  throw new Error(
@@ -39997,8 +39957,7 @@ function resolveRoot2(root) {
39997
39957
  zOrder: root.zOrder,
39998
39958
  pausable: root.pausable,
39999
39959
  dev: root.dev,
40000
- loop: root.loop,
40001
- capabilities: resolveCapabilities(root)
39960
+ loop: root.loop
40002
39961
  };
40003
39962
  }
40004
39963
  function loadGameManifest(raw) {
@@ -40031,6 +39990,7 @@ function loadGameManifest(raw) {
40031
39990
  roots,
40032
39991
  server: server2,
40033
39992
  resolution: manifest.resolution,
39993
+ rendering: manifest.rendering,
40034
39994
  authoring: manifest.authoring,
40035
39995
  debug: manifest.debug,
40036
39996
  determinism: manifest.determinism,
@@ -40038,29 +39998,11 @@ function loadGameManifest(raw) {
40038
39998
  learn: manifest.learn
40039
39999
  };
40040
40000
  }
40041
- var CAPABILITY_CEILINGS, TIER_RANK, DEFAULT_SERVER_MODULE, SEMVER_RE;
40001
+ var DEFAULT_SERVER_MODULE, SEMVER_RE;
40042
40002
  var init_load = __esm({
40043
40003
  "../engine/src/manifest/load.ts"() {
40044
40004
  "use strict";
40045
40005
  init_schema();
40046
- CAPABILITY_CEILINGS = {
40047
- builtin: { local: "first-party", hosted: "first-party" },
40048
- ingest: {
40049
- shared: { local: "shared", hosted: "shared" },
40050
- deduped: { local: "deduped", hosted: "unsupported" },
40051
- "iframe-reachable": { local: "iframe-reachable", hosted: "iframe-reachable" },
40052
- "opaque-embed": { local: "opaque-embed", hosted: "opaque-embed" }
40053
- },
40054
- module: { local: "first-party", hosted: "first-party" }
40055
- };
40056
- TIER_RANK = {
40057
- "first-party": 5,
40058
- shared: 4,
40059
- deduped: 4,
40060
- "iframe-reachable": 3,
40061
- "opaque-embed": 2,
40062
- unsupported: 1
40063
- };
40064
40006
  DEFAULT_SERVER_MODULE = "server/colyseus-setup.ts";
40065
40007
  SEMVER_RE = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z-.]+)?(?:\+[0-9A-Za-z-.]+)?$/;
40066
40008
  }
@@ -42651,7 +42593,7 @@ function createIngestManifest(inspection, options) {
42651
42593
  roots: [
42652
42594
  {
42653
42595
  id: "game",
42654
- adapter: { surface, ingest: { strategy: "deduped" } },
42596
+ adapter: { surface, ingest: {} },
42655
42597
  entry
42656
42598
  }
42657
42599
  ]
@@ -304437,12 +304379,54 @@ var require_lib = __commonJS({
304437
304379
  }
304438
304380
  });
304439
304381
 
304382
+ // ../vgai-live/dist/editor-document.js
304383
+ var LiveEditorDocument;
304384
+ var init_editor_document = __esm({
304385
+ "../vgai-live/dist/editor-document.js"() {
304386
+ "use strict";
304387
+ LiveEditorDocument = class {
304388
+ #client;
304389
+ constructor(client) {
304390
+ this.#client = client;
304391
+ }
304392
+ /** Read matching elements inside the active document: tag, text, attributes,
304393
+ * value/checked/disabled and rect. `matched` is the total before `limit`. */
304394
+ async query(selector, options) {
304395
+ return this.#probe({
304396
+ action: "query",
304397
+ selector,
304398
+ ...options?.limit === void 0 ? {} : { limit: options.limit }
304399
+ });
304400
+ }
304401
+ /** A REAL pointer gesture (pointerdown/mousedown/focus/pointerup/mouseup/click)
304402
+ * — not `element.click()`, which a `pointerdown` listener never sees. */
304403
+ async click(selector, options) {
304404
+ return this.#probe({
304405
+ action: "click",
304406
+ selector,
304407
+ ...options?.index === void 0 ? {} : { index: options.index }
304408
+ });
304409
+ }
304410
+ /** A real keydown/keyup on the target, or on whatever inside the document has focus. */
304411
+ async key(key, options) {
304412
+ return this.#probe({ action: "key", key, ...options ?? {} });
304413
+ }
304414
+ /** A real `ClipboardEvent` carrying `text/plain` — the gesture nothing else
304415
+ * in the product can produce. */
304416
+ async paste(text, options) {
304417
+ return this.#probe({ action: "paste", text, ...options ?? {} });
304418
+ }
304419
+ #probe(step) {
304420
+ return this.#client.documentProbe(step);
304421
+ }
304422
+ };
304423
+ }
304424
+ });
304425
+
304440
304426
  // ../vgai-live/dist/editor.js
304441
304427
  function inferAssetKind(path) {
304442
304428
  if (path.endsWith(".prefab.json"))
304443
304429
  return "prefab";
304444
- if (path.endsWith(".mat.json"))
304445
- return "material";
304446
304430
  const dot = path.lastIndexOf(".");
304447
304431
  const ext = dot >= 0 ? path.slice(dot).toLowerCase() : "";
304448
304432
  return EXTENSION_KIND[ext] ?? "json";
@@ -304451,6 +304435,7 @@ var EXTENSION_KIND, LiveEditor;
304451
304435
  var init_editor2 = __esm({
304452
304436
  "../vgai-live/dist/editor.js"() {
304453
304437
  "use strict";
304438
+ init_editor_document();
304454
304439
  EXTENSION_KIND = {
304455
304440
  ".glb": "model",
304456
304441
  ".gltf": "model",
@@ -304460,10 +304445,15 @@ var init_editor2 = __esm({
304460
304445
  ".webp": "image",
304461
304446
  ".gif": "image",
304462
304447
  ".svg": "image",
304448
+ ".hdr": "image",
304449
+ ".exr": "image",
304463
304450
  ".mp3": "audio",
304464
304451
  ".ogg": "audio",
304465
304452
  ".wav": "audio",
304466
- ".flac": "audio"
304453
+ ".flac": "audio",
304454
+ ".glsl": "source",
304455
+ ".vert": "source",
304456
+ ".frag": "source"
304467
304457
  };
304468
304458
  LiveEditor = class {
304469
304459
  /** `#`-private, not `private`: `vgai eval --list` enumerates this object's
@@ -304471,8 +304461,18 @@ var init_editor2 = __esm({
304471
304461
  * raw `EditorClient` advertised beside them. See `./game-client/`'s
304472
304462
  * `client.ts` (GameClient's field block) for the full reasoning. */
304473
304463
  #client;
304464
+ /**
304465
+ * The ACTIVE center document's own DOM: read it, click it, key it, paste
304466
+ * into it. The one door onto editor chrome that is not play-mode gated, and
304467
+ * deliberately scoped to that document alone —
304468
+ * `packages/editor/src/editor-document-probe.ts` carries the design and the
304469
+ * refusal contract. Screenshotting the same subject is
304470
+ * {@link LiveEditor.captureActiveDocument}, not a fifth verb here.
304471
+ */
304472
+ document;
304474
304473
  constructor(client) {
304475
304474
  this.#client = client;
304475
+ this.document = new LiveEditorDocument(client);
304476
304476
  }
304477
304477
  /**
304478
304478
  * The active authoring adapter's persistence destination — where a save would
@@ -304624,6 +304624,23 @@ var init_editor2 = __esm({
304624
304624
  async shading(mode) {
304625
304625
  await this.#client.setShadingMode(mode);
304626
304626
  }
304627
+ /**
304628
+ * CONSENT to edits being written into the game's own source files, for this
304629
+ * session — the Game document's "Persist to game source" checkbox, reachable
304630
+ * from a script.
304631
+ *
304632
+ * Off by default every session, on purpose: it is a statement about what you
304633
+ * are doing right now, never a property of the game. With it off, an edit
304634
+ * lives on the running object and says so; with it on, an edit that can be
304635
+ * honestly anchored to the line that CREATED the object is written there,
304636
+ * and one that cannot still says so. Answers with the server's own phrase for
304637
+ * who records the resulting diff — your version control, or a vendored
304638
+ * game's own lock — and refuses, with the reason, where the checkbox is
304639
+ * disabled.
304640
+ */
304641
+ async persistToGameSource(on) {
304642
+ return this.#client.setSourcePersistConsent(on);
304643
+ }
304627
304644
  /**
304628
304645
  * READ the inspector, as data — the serialized inspection subject
304629
304646
  * (`editor.inspect()`; design: `docs/ARCHITECTURE-CORE.md` §Editor chrome,
@@ -304654,6 +304671,51 @@ var init_editor2 = __esm({
304654
304671
  async inspect() {
304655
304672
  return this.#client.inspect();
304656
304673
  }
304674
+ /**
304675
+ * READ the hierarchy panel, as data — the rows a human is looking at right
304676
+ * now, nested exactly as the panel nests them.
304677
+ *
304678
+ * The companion to {@link inspect}: that one answers "what IS the selected
304679
+ * thing", this one answers "what does the tree LOOK LIKE". It is the panel's
304680
+ * own output, not a fresh walk of the scene — the adapter's tree after the
304681
+ * component marks fold implementation subtrees (bones, particle renderers,
304682
+ * instanced pools), after the internals reveal, the document promotion, the
304683
+ * child cap, the collapse state, the search filter and the selection scope.
304684
+ *
304685
+ * Works in play mode and edit mode; the answer says which (`playState`,
304686
+ * `activeViewportTab`), because the two are different adapters and a tree
304687
+ * that looks wrong is very often the wrong adapter's tree.
304688
+ *
304689
+ * Prefer this over `status().entities`, which is deliberately a different
304690
+ * question — the RAW adapter tree, unprojected. A panel that renders the
304691
+ * wrong rows looks perfectly healthy in that facet.
304692
+ *
304693
+ * Each row carries `childCount` (what its caret opens), `internalChildCount`
304694
+ * (what is folded behind "Reveal Internals") and `expandable` (whether the
304695
+ * panel draws a caret at all), so "this subtree exists but nothing in the UI
304696
+ * opens it" is a fact you can read rather than one you have to notice.
304697
+ *
304698
+ * Rejects, naming the panel, when no hierarchy panel is mounted — an empty
304699
+ * tree would be a fabricated answer about a surface nobody is being shown.
304700
+ */
304701
+ async hierarchy() {
304702
+ return this.#client.hierarchy();
304703
+ }
304704
+ /**
304705
+ * Write one editable field from `inspect()` by its stable path, through the
304706
+ * same Inspector IO and persistence boundary the human control uses.
304707
+ */
304708
+ async setField(path, value) {
304709
+ return this.#client.setInspectionField(path, value);
304710
+ }
304711
+ /** Undo / redo one project transaction, through the session's own history
304712
+ * queue — the same one the keyboard shortcut drives. */
304713
+ async undo() {
304714
+ return this.#client.undo();
304715
+ }
304716
+ async redo() {
304717
+ return this.#client.redo();
304718
+ }
304657
304719
  /** Mirrors `vgai status` — the full live editor state as JSON. */
304658
304720
  async status() {
304659
304721
  return this.#client.getState();
@@ -306322,6 +306384,7 @@ __export(dist_exports, {
306322
306384
  HIDDEN_RECOVERY_STALL_POLLS: () => HIDDEN_RECOVERY_STALL_POLLS,
306323
306385
  HiddenRecoveryDriver: () => HiddenRecoveryDriver,
306324
306386
  LiveEditor: () => LiveEditor,
306387
+ LiveEditorDocument: () => LiveEditorDocument,
306325
306388
  LiveTools: () => LiveTools,
306326
306389
  PageTransport: () => PageTransport,
306327
306390
  RelayTransport: () => RelayTransport,
@@ -306395,6 +306458,7 @@ var init_dist = __esm({
306395
306458
  init_singleton();
306396
306459
  init_tools();
306397
306460
  init_editor2();
306461
+ init_editor_document();
306398
306462
  init_game();
306399
306463
  init_game_client();
306400
306464
  init_session();
@@ -307538,7 +307602,6 @@ function rewritePackageJson(targetDir, slug, engineDir, monoRoot) {
307538
307602
  function resolvePackageRoots() {
307539
307603
  return {
307540
307604
  engineRelPath: "node_modules/@vgai/engine",
307541
- p2pRelPath: "node_modules/@vgai/p2p-colyseus/",
307542
307605
  editorRelPath: "node_modules/@vgai/editor",
307543
307606
  liveRelPath: "node_modules/@vgai/live",
307544
307607
  sdkRelPath: "node_modules/@vgai/sdk",
@@ -307599,6 +307662,32 @@ function rewriteGameManifest(targetDir, name, slug, template, monoRoot) {
307599
307662
  writeJson(manifestPath, manifest);
307600
307663
  return manifest;
307601
307664
  }
307665
+ function repinEngineAfterInstall(targetDir) {
307666
+ const installed = readInstalledVersion(targetDir, "@vgai/engine");
307667
+ if (!installed) return null;
307668
+ const manifestPath = resolveManifestPath(targetDir);
307669
+ let manifest;
307670
+ try {
307671
+ manifest = JSON.parse(readFileSync12(manifestPath, "utf-8"));
307672
+ } catch {
307673
+ return null;
307674
+ }
307675
+ const engine = manifest["engine"] ?? {};
307676
+ const from = typeof engine["version"] === "string" ? engine["version"] : "";
307677
+ if (from === installed) return null;
307678
+ manifest["engine"] = { ...engine, version: installed };
307679
+ writeJson(manifestPath, manifest);
307680
+ const baselinePath = join14(targetDir, SCAFFOLD_BASELINE_RELATIVE_PATH);
307681
+ try {
307682
+ const baseline = JSON.parse(readFileSync12(baselinePath, "utf-8"));
307683
+ baseline.engineVersion = installed;
307684
+ baseline.files["vgai.project.json"] = hashFile(manifestPath);
307685
+ writeFileSync4(baselinePath, `${JSON.stringify(baseline, null, 2)}
307686
+ `, "utf-8");
307687
+ } catch {
307688
+ }
307689
+ return { from, to: installed };
307690
+ }
307602
307691
  var REACT_ONLY_PAGE_SOURCE = `export interface ReactGamePageProps {
307603
307692
  readonly lastInput: string;
307604
307693
  readonly title?: string;
@@ -307745,26 +307834,13 @@ function rewriteTsconfig(targetDir, engineRelPath, editorRelPath) {
307745
307834
  }
307746
307835
  writeJson(tsconfigPath, tsconfig);
307747
307836
  }
307748
- function rewriteViteConfig(targetDir, engineRelPath, p2pRelPath) {
307837
+ function rewriteViteConfig(targetDir) {
307749
307838
  const vitePath = join14(targetDir, "vite.config.ts");
307750
307839
  if (!existsSync11(vitePath)) return;
307751
- writeFileSync4(
307752
- vitePath,
307753
- rewriteViteConfigContent(readFileSync12(vitePath, "utf-8"), engineRelPath, p2pRelPath),
307754
- "utf-8"
307755
- );
307840
+ writeFileSync4(vitePath, rewriteViteConfigContent(readFileSync12(vitePath, "utf-8")), "utf-8");
307756
307841
  }
307757
- function rewriteViteConfigContent(content, engineRelPath, p2pRelPath) {
307758
- const enginePluginRoot = engineRelPath.startsWith(".") ? engineRelPath : `./${engineRelPath}`;
307759
- return ensureReactDedupe(
307760
- content.replace(
307761
- /'@engine':\s*path\.resolve\(__dirname,\s*['"][^'"]+['"]\)/,
307762
- `'@engine': path.resolve(__dirname, '${engineRelPath}/src')`
307763
- ).replace(/\.\.\/\.\.\/(?:packages\/)?p2p-colyseus\//g, p2pRelPath).replace(
307764
- /(['"])\.\.\/\.\.\/(?:packages\/)?engine\/src\/data\/vite-plugin-data\1/g,
307765
- `$1${enginePluginRoot}/src/data/vite-plugin-data$1`
307766
- )
307767
- );
307842
+ function rewriteViteConfigContent(content) {
307843
+ return ensureReactDedupe(content);
307768
307844
  }
307769
307845
  function ensureReactDedupe(content) {
307770
307846
  const REQUIRED = ["react", "react-dom"];
@@ -307784,32 +307860,6 @@ function ensureReactDedupe(content) {
307784
307860
  "resolve: {\n // react/react-dom dedupe (GH #123): see packages/editor/template/vite.config.ts's\n // matching comment for the dual-React-instance root cause this avoids.\n dedupe: ['react', 'react-dom'],"
307785
307861
  );
307786
307862
  }
307787
- function rewriteDataRefModule(targetDir, engineRelPath) {
307788
- const dataRefPath = join14(targetDir, "src", "data", "data-ref.ts");
307789
- if (!existsSync11(dataRefPath)) return;
307790
- writeFileSync4(
307791
- dataRefPath,
307792
- rewriteDataRefModuleContent(readFileSync12(dataRefPath, "utf-8"), engineRelPath),
307793
- "utf-8"
307794
- );
307795
- }
307796
- function rewriteDataRefModuleContent(content, engineRelPath) {
307797
- return content.replace(
307798
- // Match BOTH in-repo spellings, exactly like the sibling p2p-colyseus and
307799
- // vite-plugin-data rewrites above (`(?:packages\/)?`): the STARTER template
307800
- // (packages/editor/template/) spells this `../../../../engine/src/data/…`
307801
- // (already inside `packages/`), but an `examples/<id>/` project spells the
307802
- // identical on-disk target `../../../../packages/engine/src/data/…` (WITH
307803
- // the `packages/` segment). Without the optional `packages/` here a bare
307804
- // literal match no-oped on every data-trio EXAMPLE scaffold, leaving its
307805
- // data-ref.ts pointed at the in-checkout depth — which fails `tsc`/`vite
307806
- // build` the moment it's scaffolded into any external directory (E4
307807
- // AC-TPL-002; no prior CI externally-scaffolds a data-trio example, so it
307808
- // stayed latent). The p2p/vite-plugin rewrites already carried this guard.
307809
- /(['"])(?:\.\.\/)+(?:packages\/)?engine\/src\/data\/data-ref\1/,
307810
- `$1../../${engineRelPath}/src/data/data-ref$1`
307811
- );
307812
- }
307813
307863
  function rewriteIndexHtml(targetDir, name) {
307814
307864
  const htmlPath = join14(targetDir, "index.html");
307815
307865
  if (!existsSync11(htmlPath)) return;
@@ -307846,13 +307896,12 @@ function scaffoldProject(opts) {
307846
307896
  writeExampleReadme(targetDir, name, opts.exampleId);
307847
307897
  }
307848
307898
  if (opts.presentation) applyPresentation(targetDir, opts.presentation);
307849
- const { engineRelPath, p2pRelPath, editorRelPath } = resolvePackageRoots();
307899
+ const { engineRelPath, editorRelPath } = resolvePackageRoots();
307850
307900
  rewritePackageJson(targetDir, slug, engineDir, monoRoot);
307851
307901
  const manifest = rewriteGameManifest(targetDir, name, slug, template, monoRoot);
307852
307902
  rewriteTemplateVariantFiles(targetDir, template);
307853
307903
  rewriteTsconfig(targetDir, engineRelPath, editorRelPath);
307854
- rewriteViteConfig(targetDir, engineRelPath, p2pRelPath);
307855
- rewriteDataRefModule(targetDir, engineRelPath);
307904
+ rewriteViteConfig(targetDir);
307856
307905
  rewriteIndexHtml(targetDir, name);
307857
307906
  rewriteRoadmap(targetDir, name);
307858
307907
  if (hasCapabilityDistribution) initializeProjectCatalog(targetDir, catalogDir);
@@ -308045,15 +308094,6 @@ function checkAdapterWall(manifest) {
308045
308094
  }
308046
308095
  }
308047
308096
  }
308048
- function checkHostedTierWall(manifest) {
308049
- for (const world of manifest.roots) {
308050
- if (world.capabilities.hosted !== "first-party") {
308051
- throw new DeployError(
308052
- `vgai deploy: world "${world.id}" declares capabilities.hosted = "${world.capabilities.hosted}", below the required "first-party" tier \u2014 a static host IS a hosted delivery context (\xA71.B), and only first-party roots can ship there.`
308053
- );
308054
- }
308055
- }
308056
- }
308057
308097
  function runProjectBuild(projectDir, extraViteArgs) {
308058
308098
  const cmd = extraViteArgs ? `npm run build -- ${extraViteArgs}` : "npm run build";
308059
308099
  try {
@@ -308141,7 +308181,6 @@ async function deployProject(opts) {
308141
308181
  const { projectDir, reportOnly = false, wrangler } = opts;
308142
308182
  const manifest = loadManifestOrThrow(projectDir);
308143
308183
  checkAdapterWall(manifest);
308144
- checkHostedTierWall(manifest);
308145
308184
  const projectName = opts.projectName ?? defaultProjectName(manifest.name);
308146
308185
  const targetUrlShape = `${projectName}.pages.dev`;
308147
308186
  if (!reportOnly) {
@@ -309225,7 +309264,6 @@ async function packageItchZip(opts) {
309225
309264
  const { projectDir, reportOnly = false } = opts;
309226
309265
  const manifest = loadManifestOrThrow(projectDir);
309227
309266
  checkAdapterWall(manifest);
309228
- checkHostedTierWall(manifest);
309229
309267
  runProjectBuild(projectDir, "--base ./");
309230
309268
  const manifestPath = resolveManifestPath(projectDir);
309231
309269
  const staging = stageBuildOutput(projectDir, manifestPath);
@@ -309676,7 +309714,7 @@ function projectHasThreeRoot(projectDir) {
309676
309714
  }
309677
309715
  function reapplyScaffoldRewrites(scratchDir, projectDir, monoRoot, name, currentEngineVersion, targetPackageVersions) {
309678
309716
  const engineDir = resolveScaffoldPackageDir(monoRoot, join18("packages", "engine"), "@vgai/engine");
309679
- const { engineRelPath, p2pRelPath, editorRelPath, liveRelPath, sdkRelPath, editorSdkRelPath } = resolvePackageRoots();
309717
+ const { engineRelPath, editorRelPath, liveRelPath, sdkRelPath, editorSdkRelPath } = resolvePackageRoots();
309680
309718
  const targetEditorVersion = targetPackageVersions["@vgai/editor"] ?? readInstalledVersion(monoRoot, "@vgai/editor");
309681
309719
  const satelliteRelPaths = {
309682
309720
  "@vgai/live": liveRelPath,
@@ -309761,21 +309799,9 @@ function reapplyScaffoldRewrites(scratchDir, projectDir, monoRoot, name, current
309761
309799
  }
309762
309800
  const vitePath = join18(scratchDir, "vite.config.ts");
309763
309801
  if (existsSync14(vitePath)) {
309764
- const content = rewriteViteConfigContent(
309765
- readFileSync16(vitePath, "utf-8"),
309766
- engineRelPath,
309767
- p2pRelPath
309768
- );
309802
+ const content = rewriteViteConfigContent(readFileSync16(vitePath, "utf-8"));
309769
309803
  writeFileSync7(vitePath, content, "utf-8");
309770
309804
  }
309771
- const dataRefPath = join18(scratchDir, "src", "data", "data-ref.ts");
309772
- if (existsSync14(dataRefPath)) {
309773
- writeFileSync7(
309774
- dataRefPath,
309775
- rewriteDataRefModuleContent(readFileSync16(dataRefPath, "utf-8"), engineRelPath),
309776
- "utf-8"
309777
- );
309778
- }
309779
309805
  const htmlPath = join18(scratchDir, "index.html");
309780
309806
  if (existsSync14(htmlPath)) {
309781
309807
  const html = readFileSync16(htmlPath, "utf-8").replace(
@@ -310334,6 +310360,7 @@ function formatTabCensus(census) {
310334
310360
  census.heapUsedMB === null ? "heap n/a" : `heap ${census.heapUsedMB}MB${census.heapLimitMB === null ? "" : `/${census.heapLimitMB}MB`}`,
310335
310361
  `canvas ${census.canvasMB}MB in ${census.canvases}`
310336
310362
  ];
310363
+ if (census.mountEpochs !== void 0) parts.splice(1, 0, `mount epochs ${census.mountEpochs}`);
310337
310364
  if (census.textures !== void 0) parts.push(`tex ${census.textures}`);
310338
310365
  if (census.geometries !== void 0) parts.push(`geo ${census.geometries}`);
310339
310366
  if (census.programs !== void 0) parts.push(`prog ${census.programs}`);
@@ -321165,7 +321192,14 @@ function shipRoutesLine(projectRoot) {
321165
321192
  }
321166
321193
  return text.includes(STARTER_SENTINEL) ? "ship routes: not yet implemented (fine while the game is forming \u2014 see AGENTS.md)" : "ship routes: implemented";
321167
321194
  }
321168
- var GAMEPLAY_PATHS = ["src/components", "src/lib", "src/world.tsx", "src/hooks"];
321195
+ var GAMEPLAY_PATHS = [
321196
+ "src/components",
321197
+ "src/scenes",
321198
+ "src/prefabs",
321199
+ "src/lib",
321200
+ "src/world.tsx",
321201
+ "src/hooks"
321202
+ ];
321169
321203
  var STALE_AUTOPLAY_COMMIT_THRESHOLD = 5;
321170
321204
  function git2(cwd2, args2) {
321171
321205
  const result = spawnSync8("git", args2, { cwd: cwd2, encoding: "utf-8" });
@@ -321199,13 +321233,7 @@ function livePlaneLine(shape) {
321199
321233
  }
321200
321234
 
321201
321235
  // src/integrations.ts
321202
- import {
321203
- existsSync as existsSync22,
321204
- mkdirSync as mkdirSync11,
321205
- readFileSync as readFileSync25,
321206
- renameSync as renameSync3,
321207
- writeFileSync as writeFileSync9
321208
- } from "node:fs";
321236
+ import { existsSync as existsSync22, mkdirSync as mkdirSync11, readFileSync as readFileSync25, renameSync as renameSync3, writeFileSync as writeFileSync9 } from "node:fs";
321209
321237
  import { homedir as homedir6 } from "node:os";
321210
321238
  import { dirname as dirname13, join as join29 } from "node:path";
321211
321239
 
@@ -321272,9 +321300,7 @@ function parseHookStdin(raw) {
321272
321300
  return {};
321273
321301
  }
321274
321302
  function buildCallerSessionContext(opts) {
321275
- const lines = [
321276
- "vgai caller-session bridge (advisory, not a gate):"
321277
- ];
321303
+ const lines = ["vgai caller-session bridge (advisory, not a gate):"];
321278
321304
  if (opts.projectRoot) {
321279
321305
  lines.push(` project root: ${opts.projectRoot}`);
321280
321306
  } else {
@@ -321291,19 +321317,13 @@ function buildCallerSessionContext(opts) {
321291
321317
  }
321292
321318
  if (opts.projectRoot) {
321293
321319
  const canon = canonicalPath(opts.projectRoot);
321294
- const match = editors.find(
321295
- (s) => s.project !== null && canonicalPath(s.project) === canon
321296
- );
321320
+ const match = editors.find((s) => s.project !== null && canonicalPath(s.project) === canon);
321297
321321
  if (match) {
321298
- lines.push(
321299
- ` prefer this session for control commands: http://127.0.0.1:${match.port}`
321300
- );
321322
+ lines.push(` prefer this session for control commands: http://127.0.0.1:${match.port}`);
321301
321323
  }
321302
321324
  }
321303
321325
  }
321304
- lines.push(
321305
- " drive the game with `vgai eval`, never synthetic keys / wall-clock sleeps."
321306
- );
321326
+ lines.push(" drive the game with `vgai eval`, never synthetic keys / wall-clock sleeps.");
321307
321327
  return lines.join("\n");
321308
321328
  }
321309
321329
  function gatherLiveSessionNotes() {
@@ -321428,9 +321448,7 @@ function readIntegrationStatus(paths) {
321428
321448
  if (codex) {
321429
321449
  const hooks = codex["hooks"];
321430
321450
  if (typeof hooks === "object" && hooks !== null) {
321431
- codexSessionStartInstalled = hasVgaiCommandInHookList(
321432
- hooks["SessionStart"]
321433
- );
321451
+ codexSessionStartInstalled = hasVgaiCommandInHookList(hooks["SessionStart"]);
321434
321452
  legacyCodexPreToolUseBashGate = hasLegacyCodexPreToolUseBashGate(codex);
321435
321453
  }
321436
321454
  } else if (existsSync22(paths.codexHooksJson)) {
@@ -321440,14 +321458,10 @@ function readIntegrationStatus(paths) {
321440
321458
  if (claude) {
321441
321459
  const hooks = claude["hooks"];
321442
321460
  if (typeof hooks === "object" && hooks !== null) {
321443
- claudeSessionStartInstalled = hasVgaiCommandInHookList(
321444
- hooks["SessionStart"]
321445
- );
321461
+ claudeSessionStartInstalled = hasVgaiCommandInHookList(hooks["SessionStart"]);
321446
321462
  }
321447
321463
  } else if (existsSync22(paths.claudeSettingsJson)) {
321448
- notes.push(
321449
- `claude settings file exists but is not valid JSON: ${paths.claudeSettingsJson}`
321450
- );
321464
+ notes.push(`claude settings file exists but is not valid JSON: ${paths.claudeSettingsJson}`);
321451
321465
  }
321452
321466
  if (legacyCodexPreToolUseBashGate) {
321453
321467
  notes.push(
@@ -324149,9 +324163,6 @@ function detectUnmanagedReactRoots(source, file2) {
324149
324163
  }
324150
324164
 
324151
324165
  // src/validate.ts
324152
- function isServedRouteOrAbsoluteUrl(value) {
324153
- return /^[a-z][a-z0-9+.-]*:\/\//i.test(value) || value.startsWith("//") || value.startsWith("/project-game-static/");
324154
- }
324155
324166
  function checkFile(worldId, field, declaredPath, resolvedPath) {
324156
324167
  return { worldId, field, declaredPath, resolvedPath, exists: existsSync25(resolvedPath) };
324157
324168
  }
@@ -324162,16 +324173,9 @@ function collectFileChecks(folder, manifest) {
324162
324173
  checks.push(checkFile(world.id, "entry", world.entry, join35(folder, world.entry)));
324163
324174
  }
324164
324175
  if (world.adapter.type === "ingest") {
324165
- const { entryHtml, bundleUrl } = world.adapter;
324166
- if (entryHtml !== void 0) {
324167
- checks.push(
324168
- checkFile(world.id, "adapter.ingest.entryHtml", entryHtml, join35(folder, entryHtml))
324169
- );
324170
- }
324171
- if (bundleUrl !== void 0 && !isServedRouteOrAbsoluteUrl(bundleUrl)) {
324172
- checks.push(
324173
- checkFile(world.id, "adapter.ingest.bundleUrl", bundleUrl, join35(folder, bundleUrl))
324174
- );
324176
+ const shim = world.adapter.contractShim;
324177
+ if (shim !== void 0) {
324178
+ checks.push(checkFile(world.id, "adapter.ingest.contractShim", shim, join35(folder, shim)));
324175
324179
  }
324176
324180
  }
324177
324181
  }
@@ -324685,8 +324689,8 @@ function catalogDistributionDir() {
324685
324689
  );
324686
324690
  }
324687
324691
  function cliVersion() {
324688
- if ("0.5.10") {
324689
- return "0.5.10";
324692
+ if ("0.5.12") {
324693
+ return "0.5.12";
324690
324694
  }
324691
324695
  try {
324692
324696
  const pkg = JSON.parse(readFileSync32(join39(__dirname4, "..", "package.json"), "utf8"));
@@ -324705,7 +324709,7 @@ function bakedTargetVersions() {
324705
324709
  if (false)
324706
324710
  return void 0;
324707
324711
  try {
324708
- return JSON.parse('{"@vgai/engine":"0.5.10","@vgai/editor":"0.5.10","@vgai/p2p-colyseus":"0.5.10","@vgai/live":"0.5.10","@vgai/sdk":"0.5.10","@vgai/editor-sdk":"0.5.10","@vgai/cli":"0.5.10"}');
324712
+ return JSON.parse('{"@vgai/engine":"0.5.12","@vgai/editor":"0.5.12","@vgai/p2p-colyseus":"0.5.12","@vgai/live":"0.5.12","@vgai/sdk":"0.5.12","@vgai/editor-sdk":"0.5.12","@vgai/cli":"0.5.12"}');
324709
324713
  } catch {
324710
324714
  return void 0;
324711
324715
  }
@@ -324808,7 +324812,6 @@ Verify it:
324808
324812
  Ship it:
324809
324813
  npm run deploy The project's provider-native deployment script
324810
324814
  deploy [folder] Legacy Cloudflare/itch compatibility for existing projects
324811
- bundle <folder> Sidecar ESM bundle for an external pixi game
324812
324815
  render-cinematic Deterministic frame-addressed video export
324813
324816
 
324814
324817
  Bring in an existing web game:
@@ -324974,7 +324977,7 @@ Project:
324974
324977
  --to (that package, if named) > this CLI build's baked-in version
324975
324978
  > (in-repo only) this checkout's installed version.
324976
324979
  validate [folder] In-process: validate vgai.project.json, check every declared
324977
- entry/scene/entryHtml/bundleUrl file exists, print the engine pin
324980
+ entry/contractShim file exists, print the engine pin
324978
324981
  (default: cwd) \u2014 no monorepo/editor/Vite needed beyond this checkout
324979
324982
  Exit codes: 0 valid, 1 invalid manifest/files/React roots/design states, 2 usage
324980
324983
  Deployment is project-owned: add deploy-vercel or deploy-cloudflare, then
@@ -324983,15 +324986,6 @@ Project:
324983
324986
  --json Print a single machine-readable JSON report instead of text
324984
324987
  Exit codes: 0 pass, 1 conformance failure, 2 usage/environment error
324985
324988
  Requires a monorepo checkout (same precondition as \`edit\`)
324986
- bundle <folder> Build an external pixi game's iframe-reachable world as a sidecar
324987
- ESM bundle (pixi.js + declared extraDeps externalized), never
324988
- touching game source. Prints the vgai.project.json fields to add.
324989
- --write Patch vgai.project.json's bundleUrl in place instead of printing it
324990
- --entry <path> Build entry, folder-relative (default: probed from package.json)
324991
- --world <id> Which world to build (default: the manifest's one iframe-reachable
324992
- canvas world \u2014 an error if there are zero or several)
324993
- Exit codes: 0 built, 1 build failure, 2 usage error
324994
- Requires a monorepo checkout (same precondition as \`edit\`)
324995
324989
  doctor <folder> Headless mount of every declared world: static validation
324996
324990
  (manifest + declared files + engine pin), then a REAL headless
324997
324991
  editor session. Exactly one verdict per world \u2014
@@ -326029,11 +326023,7 @@ function renderValidateReport(report) {
326029
326023
  `${manifestPath}: OK \u2014 "${manifest.name}" v${manifest.version} (engine ${manifest.engine.version})`
326030
326024
  );
326031
326025
  for (const world of manifest.roots) {
326032
- const { adapter } = world;
326033
- const adapterLabel = adapter.type === "ingest" ? `${adapter.identity} (${adapter.strategy})` : adapter.identity;
326034
- lines.push(
326035
- ` root "${world.id}" surface=${world.surface} adapter=${adapterLabel} tiers={local: ${world.capabilities.local}, hosted: ${world.capabilities.hosted}}`
326036
- );
326026
+ lines.push(` root "${world.id}" surface=${world.surface} adapter=${world.adapter.identity}`);
326037
326027
  }
326038
326028
  lines.push("");
326039
326029
  const missing = fileChecks.filter((f) => !f.exists);
@@ -326435,6 +326425,24 @@ function describeEditorProvenance(packagedEntry, engineRoot) {
326435
326425
  }
326436
326426
  return `Editor: packaged @vgai/editor@${version2} (${editorPkgDir})`;
326437
326427
  }
326428
+ function assertEditorProjectInstalled(absProject, engineRoot = ENGINE_ROOT) {
326429
+ let realEngineRoot;
326430
+ try {
326431
+ realEngineRoot = realpathSync8(engineRoot);
326432
+ } catch {
326433
+ realEngineRoot = engineRoot;
326434
+ }
326435
+ const engineRootIsCheckout = existsSync28(
326436
+ join39(realEngineRoot, "packages", "editor", "package.json")
326437
+ );
326438
+ const projectIsInCheckout = absProject === realEngineRoot || absProject.startsWith(realEngineRoot + sep6);
326439
+ if (engineRootIsCheckout && projectIsInCheckout) return;
326440
+ if (existsSync28(join39(absProject, "node_modules"))) return;
326441
+ throw new Error(
326442
+ `Project dependencies are not installed in ${absProject}.
326443
+ Run \`npm install\` in that project, then run \`vgai edit\` again. vgai will not borrow React or editor packages from another checkout.`
326444
+ );
326445
+ }
326438
326446
  async function launchEditor(projectPath, opts = {}) {
326439
326447
  if (opts.noOpen === void 0 && process.env["VGAI_NO_OPEN"]) {
326440
326448
  opts = { ...opts, noOpen: true };
@@ -326444,6 +326452,7 @@ async function launchEditor(projectPath, opts = {}) {
326444
326452
  }
326445
326453
  const absProject = canonicalPath(resolve20(projectPath));
326446
326454
  discoverProject(absProject);
326455
+ assertEditorProjectInstalled(absProject);
326447
326456
  const portRequest = resolveEditorPortRequest(
326448
326457
  absProject,
326449
326458
  opts.portOverride,
@@ -326782,50 +326791,6 @@ No vgai.project.json in ${absFolder}`
326782
326791
  });
326783
326792
  process.exit(code);
326784
326793
  }
326785
- async function runBundle(folderArg, opts) {
326786
- const absFolder = resolve20(folderArg);
326787
- if (!existsSync28(absFolder) || !statSync12(absFolder).isDirectory()) {
326788
- console.error(
326789
- `Usage: vgai bundle <folder> [--write] [--entry <path>] [--world <id>]
326790
- Folder not found: ${absFolder}`
326791
- );
326792
- process.exit(2);
326793
- }
326794
- if (!hasManifest(absFolder)) {
326795
- console.error(
326796
- `Usage: vgai bundle <folder> [--write] [--entry <path>] [--world <id>]
326797
- No vgai.project.json in ${absFolder}`
326798
- );
326799
- process.exit(2);
326800
- }
326801
- const runnerScript = join39(ENGINE_ROOT, "packages/editor/scripts/run-bundle.ts");
326802
- if (!existsSync28(runnerScript)) {
326803
- throw new Error(`Bundle runner not found at ${runnerScript}. Is the engine installed?`);
326804
- }
326805
- const childArgs = [
326806
- "tsx",
326807
- runnerScript,
326808
- absFolder,
326809
- ...opts.write ? ["--write"] : [],
326810
- ...opts.entry !== void 0 ? ["--entry", opts.entry] : [],
326811
- ...opts.world !== void 0 ? ["--world", opts.world] : []
326812
- ];
326813
- const code = await new Promise((resolvePromise) => {
326814
- const child = spawn4("npx", childArgs, {
326815
- cwd: ENGINE_ROOT,
326816
- stdio: "inherit",
326817
- shell: true
326818
- });
326819
- child.on("error", (err2) => {
326820
- console.error(`Failed to start bundle runner: ${err2.message}`);
326821
- resolvePromise(1);
326822
- });
326823
- child.on("close", (exitCode) => {
326824
- resolvePromise(exitCode ?? 1);
326825
- });
326826
- });
326827
- process.exit(code);
326828
- }
326829
326794
  var DOCTOR_USAGE = "Usage: vgai doctor <folder> [--json] [--port <n>] [--timeout <ms>]";
326830
326795
  async function runDoctor(folderArg, opts) {
326831
326796
  const absFolder = resolve20(folderArg);
@@ -327701,7 +327666,6 @@ var KNOWN_COMMANDS = /* @__PURE__ */ new Set([
327701
327666
  "screenshot",
327702
327667
  "eval",
327703
327668
  "conformance",
327704
- "bundle",
327705
327669
  "doctor",
327706
327670
  "render-cinematic",
327707
327671
  "perf",
@@ -328199,6 +328163,10 @@ Unrecognized option or invalid project name: ${name}`);
328199
328163
  throw new Error(`npm install exited with code ${exitCode ?? "unknown"}`);
328200
328164
  cacheDependencies(targetDir);
328201
328165
  }
328166
+ const repin = repinEngineAfterInstall(targetDir);
328167
+ if (repin) {
328168
+ console.log(`Engine pin updated ${repin.from} -> ${repin.to} (the project's installed @vgai/engine).`);
328169
+ }
328202
328170
  } catch {
328203
328171
  console.error("npm install failed. The editor was not started; fix the install and retry.");
328204
328172
  process.exitCode = 1;
@@ -328311,7 +328279,7 @@ ${subcommand === "list" ? `Unknown option: ${unknown2[0]}` : `Unknown subcommand
328311
328279
  console.log(`Usage: vgai validate [folder]
328312
328280
 
328313
328281
  In-process: validate vgai.project.json, check every declared
328314
- entry/scene/entryHtml/bundleUrl file exists, print the engine pin (default: cwd)
328282
+ entry/contractShim file exists, print the engine pin (default: cwd)
328315
328283
  \u2014 no monorepo/editor/Vite needed beyond this checkout.
328316
328284
 
328317
328285
  Exit codes: 0 valid, 1 invalid manifest/files/React roots/design states, 2 usage`);
@@ -328898,33 +328866,6 @@ Choose exactly one target: project, --port/--url, --all, or --everywhere.`
328898
328866
  await runConformance(positional[0], jsonMode);
328899
328867
  break;
328900
328868
  }
328901
- case "bundle": {
328902
- if (hasHelpFlag(args)) {
328903
- console.log("Usage: vgai bundle <folder> [--write] [--entry <path>] [--world <id>]");
328904
- break;
328905
- }
328906
- const write = args.includes("--write");
328907
- const entryIdx = args.indexOf("--entry");
328908
- const entry = entryIdx >= 0 ? args[entryIdx + 1] : void 0;
328909
- const worldIdx = args.indexOf("--world");
328910
- const worldId = worldIdx >= 0 ? args[worldIdx + 1] : void 0;
328911
- const positional = [];
328912
- for (let i = 1; i < args.length; i++) {
328913
- const a = args[i];
328914
- if (a === "--write") continue;
328915
- if (a === "--entry" || a === "--world") {
328916
- i++;
328917
- continue;
328918
- }
328919
- positional.push(a);
328920
- }
328921
- if (!positional[0]) {
328922
- console.error("Usage: vgai bundle <folder> [--write] [--entry <path>] [--world <id>]");
328923
- process.exit(2);
328924
- }
328925
- await runBundle(positional[0], { write, entry, world: worldId });
328926
- break;
328927
- }
328928
328869
  case "doctor": {
328929
328870
  if (hasHelpFlag(args)) {
328930
328871
  console.log(DOCTOR_USAGE);
@@ -329422,6 +329363,7 @@ if (process.argv[1] && import.meta.url === pathToFileURL2(realpathOrSelf(process
329422
329363
  export {
329423
329364
  BRIDGE_SCREENSHOT_STALE,
329424
329365
  HIDDEN_FRAME_NOTICE,
329366
+ assertEditorProjectInstalled,
329425
329367
  authoringWarningsWarning,
329426
329368
  autoAddCapabilityForTool,
329427
329369
  autoLaunchEditorAfterCreate,
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@vgai/cli",
3
3
  "author": "Volter AI, Inc.",
4
4
  "license": "Apache-2.0",
5
- "version": "0.5.10",
5
+ "version": "0.5.12",
6
6
  "description": "Create, open, control, validate, and playtest VGAI game projects.",
7
7
  "keywords": [
8
8
  "game-engine",
@@ -39,12 +39,12 @@
39
39
  },
40
40
  "dependencies": {
41
41
  "@oclif/core": "^4.11.14",
42
- "@vgai/editor": "0.5.10",
43
- "@vgai/editor-sdk": "0.5.10",
44
- "@vgai/engine": "0.5.10",
45
- "@vgai/live": "0.5.10",
46
- "@vgai/p2p-colyseus": "0.5.10",
47
- "@vgai/sdk": "0.5.10",
42
+ "@vgai/editor": "0.5.12",
43
+ "@vgai/editor-sdk": "0.5.12",
44
+ "@vgai/engine": "0.5.12",
45
+ "@vgai/live": "0.5.12",
46
+ "@vgai/p2p-colyseus": "0.5.12",
47
+ "@vgai/sdk": "0.5.12",
48
48
  "ink": "^7.1.0",
49
49
  "playwright": "^1.58.2",
50
50
  "react": "^19.2.4",