@vgai/sdk 0.5.1 → 0.5.3

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.
@@ -2,25 +2,25 @@
2
2
  * `play.seed.set` / `play.timeScale.set`
3
3
  * (B4, §8 B4 "deterministic seed and time-scale control").
4
4
  *
5
- * TIME-SCALE is REAL since Wave 5 (docs/SYNTHETIC-PLAYER-SPEC.md §3.5): the
6
- * `set-time-scale` relay case (`command-listener.ts`) reaches the live
7
- * session's `GameLoop.timeScale` (`packages/engine/src/core/game-loop.ts`,
8
- * a live get/set property clamped to `[0, 8]`) through `play-mode.ts`'s
9
- * `getPlayRuntimeAccess()`, and `collectState` reports the applied value
10
- * back through `play.status`. `TIME_SCALE_CONTROL_UNSUPPORTED` now fires
11
- * only against a stale editor page (the structured `UNKNOWN_COMMAND_TYPE`
12
- * marker, translated to `undefined` by the transport) — or a session where
13
- * play isn't running, which surfaces as `COMMAND_FAILED`.
5
+ * TIME-SCALE is REAL since Wave 5: the `set-time-scale` relay case
6
+ * (`command-listener.ts`) reaches the live session's `GameLoop.timeScale`
7
+ * (`packages/engine/src/core/game-loop.ts`, a live get/set property clamped
8
+ * to `[0, 8]`) through `play-mode.ts`'s `getPlayRuntimeAccess()`, and
9
+ * `collectState` reports the applied value back through `play.status`.
10
+ * `TIME_SCALE_CONTROL_UNSUPPORTED` now fires only against a stale editor
11
+ * page (the structured `UNKNOWN_COMMAND_TYPE` marker, translated to
12
+ * `undefined` by the transport) — or a session where play isn't running,
13
+ * which surfaces as `COMMAND_FAILED`.
14
14
  *
15
- * SEED is REAL since D15/T-D15.6 (`docs/D15-DETERMINISM-DESIGN.md` §2.d) —
16
- * no longer an honest gap. `play.seed.set` reaches the live session's
17
- * `ctx.random.reseed(seed)` (`packages/engine/src/core/seeded-random.ts`,
18
- * built on T-D15.1's core) through the `set-seed` relay case
19
- * (`command-listener.ts`) and `play-mode.ts`'s `getPlayRuntimeAccess()`.
20
- * Semantics are FUTURE-DRAWS-ONLY, matching `SeededRandom.reseed`'s own
21
- * contract exactly: reseeding mid-run re-derives every live stream so every
22
- * draw AFTER this call is reproducible from the new seed, but it can never
23
- * make an already-diverged session's PAST draws reproducible — for a fully
15
+ * SEED is REAL since D15/T-D15.6 — no longer an honest gap. `play.seed.set`
16
+ * reaches the live session's `ctx.random.reseed(seed)`
17
+ * (`packages/engine/src/core/seeded-random.ts`, built on T-D15.1's core)
18
+ * through the `set-seed` relay case (`command-listener.ts`) and
19
+ * `play-mode.ts`'s `getPlayRuntimeAccess()`. Semantics are
20
+ * FUTURE-DRAWS-ONLY, matching `SeededRandom.reseed`'s own contract exactly:
21
+ * reseeding mid-run re-derives every live stream so every draw AFTER this
22
+ * call is reproducible from the new seed, but it can never make an
23
+ * already-diverged session's PAST draws reproducible — for a fully
24
24
  * reproducible run, pass the seed at session start instead (manifest
25
25
  * `determinism.defaultSeed` / `?vgai-seed=` / `play.start`'s `seed` field /
26
26
  * `vgai play --seed` / a probe fixture seed option). Throws the declared
@@ -65,7 +65,7 @@ const DETERMINISM_NOT_DECLARED_ERROR = {
65
65
  summary:
66
66
  "The running project's manifest does not declare determinism.seededRandom (D15) — " +
67
67
  'play.seed.set has no documented determinism contract to reseed against. Declare the ' +
68
- 'block (docs/D15-DETERMINISM-DESIGN.md §2.a) to opt in.',
68
+ 'block to opt in.',
69
69
  data: z.object({}),
70
70
  } as const;
71
71
 
@@ -1,7 +1,6 @@
1
1
  /**
2
- * `play.debugCommand.list` / `play.debugCommand.invoke`
3
- * (Wave 5, docs/SYNTHETIC-PLAYER-SPEC.md §3.5 — the editor/CLI/MCP door onto
4
- * `ctx.debug.registerCommand` registrations).
2
+ * `play.debugCommand.list` / `play.debugCommand.invoke` (Wave 5 — the
3
+ * editor/CLI/MCP door onto `ctx.debug.registerCommand` registrations).
5
4
  *
6
5
  * Debug commands are FIXTURES, never proofs (D16): they set up state so a
7
6
  * proof can then drive honest input and assert observable state — hence
package/src/play/index.ts CHANGED
@@ -1,8 +1,7 @@
1
1
  /**
2
- * B4 — `play.*` operations
3
- * (docs/AI-NATIVE-AUTHORING-IMPLEMENTATION-SPEC.md §8 B4). Registered
4
- * separately from B1's `registerBuiltinTools` (`../operations.ts`),
5
- * B2's `registerProjectOperations` (`../project/index.ts`), and B3's
2
+ * `play.*` operations. Registered separately from B1's
3
+ * `registerBuiltinTools` (`../operations.ts`), B2's
4
+ * `registerProjectOperations` (`../project/index.ts`), and B3's
6
5
  * `registerEditorOperations` (`../editor/index.ts`) — the default
7
6
  * `operations` singleton (`../index.ts`) calls all four.
8
7
  */
@@ -16,7 +15,7 @@ export * from './run-ticks-operations.js';
16
15
  export * from './state-operations.js';
17
16
  export * from './status-operations.js';
18
17
  export type {
19
- ActiveWorldInfo,
18
+ ActiveRootInfo,
20
19
  InputInjectionKind,
21
20
  InputInjectionRequest,
22
21
  PlayCommandResult,
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * `play.input.inject` (B4, §8 B4 "input injection through normal input
3
- * paths"; wired for real in Wave 5, docs/SYNTHETIC-PLAYER-SPEC.md §3.2/§3.5).
3
+ * paths"; wired for real in Wave 5).
4
4
  *
5
5
  * MUST exercise the NORMAL input action path, never a direct game-state
6
6
  * mutation (§8 B4 AC). Two tiers, both engine-native:
@@ -11,8 +11,8 @@
11
11
  * actuation surfaces `{delivered: false, reason}` and this op throws the
12
12
  * declared `INPUT_GATED` — never a false ok. Unknown action names throw
13
13
  * `INPUT_ACTION_NOT_FOUND` carrying the declared-action list. `atTick`
14
- * (D15/T-D15.5, docs/D15-DETERMINISM-DESIGN.md §2.c) defers the actuation
15
- * to `InputManager.scheduleActionAtTick` instead: applied at the START of
14
+ * (D15/T-D15.5) defers the actuation to
15
+ * `InputManager.scheduleActionAtTick` instead: applied at the START of
16
16
  * that tick's input phase, composing with `play.runTicks`/
17
17
  * `window.__vgai.runTicks` (schedule at tick 500, run 1000 ticks, the
18
18
  * action fires exactly at 500 — a digital `true` produces a genuine
@@ -23,8 +23,8 @@
23
23
  * report yet — the actuation hasn't happened). CAVEAT: a world that is
24
24
  * paused/frozen when its target tick would have been serviced (or is
25
25
  * skipped by a multi-tick gap) silently drops the schedule rather than
26
- * applying it late — the only trace is an `'input.schedule.dropped'`
27
- * debug event (`{tick, action}`), readable via `play.gameplayState
26
+ * applying it late — the only trace is an `'input.schedule.dropped'` debug
27
+ * event (`{tick, action}`), readable via `play.gameplayState
28
28
  * .inspect`/`play.debugCommand`'s event surface, not through this op.
29
29
  * - The four legacy named-test-source kinds (`'axis' | 'vector2' |
30
30
  * 'pointerDelta' | 'pointerPosition'`) — `InputManager.injectAxis`/
@@ -43,7 +43,7 @@
43
43
  * fix): targets a SPECIFIC world's `InputManager` in a multi-world project.
44
44
  * Omitted, it resolves to the SAME default world `window.__vgai.input.*`
45
45
  * (door a) resolves to — one shared resolution function
46
- * (`debug-registry.ts`'s `resolveInputWorldId`), so this op and the debug
46
+ * (`debug-registry.ts`'s `resolveInputRootId`), so this op and the debug
47
47
  * bridge can never disagree about which world an unqualified injection
48
48
  * targets. An explicit, unregistered `worldId` throws the declared
49
49
  * `INPUT_WORLD_NOT_FOUND` (`data.registered` lists every world with a wired
@@ -121,7 +121,7 @@ const COMMAND_FAILED_ERROR = {
121
121
 
122
122
  const Vector2Schema = z.object({ x: z.number(), y: z.number() });
123
123
 
124
- const WorldIdField = z
124
+ const RootIdField = z
125
125
  .string()
126
126
  .optional()
127
127
  .describe(
@@ -157,13 +157,13 @@ const PlayInputInjectInput = z
157
157
  'with play.runTicks/window.__vgai.runTicks. Throws TICK_ALREADY_PASSED for a tick ' +
158
158
  'that already elapsed in the running session.',
159
159
  ),
160
- worldId: WorldIdField,
160
+ worldId: RootIdField,
161
161
  }),
162
162
  z.object({
163
163
  kind: z.literal('axis'),
164
164
  sourceId: z.string().describe('Named test-input source id an action binds to (test_axis).'),
165
165
  value: z.number().describe('Raw scalar value (same units InputManager.injectAxis takes).'),
166
- worldId: WorldIdField,
166
+ worldId: RootIdField,
167
167
  }),
168
168
  z.object({
169
169
  kind: z.literal('vector2'),
@@ -171,7 +171,7 @@ const PlayInputInjectInput = z
171
171
  .string()
172
172
  .describe('Named test-input source id an action binds to (test_vector2).'),
173
173
  value: Vector2Schema,
174
- worldId: WorldIdField,
174
+ worldId: RootIdField,
175
175
  }),
176
176
  z.object({
177
177
  kind: z.literal('pointerDelta'),
@@ -179,7 +179,7 @@ const PlayInputInjectInput = z
179
179
  .string()
180
180
  .describe('Named test-input source id an action binds to (test_pointer_delta).'),
181
181
  value: Vector2Schema,
182
- worldId: WorldIdField,
182
+ worldId: RootIdField,
183
183
  }),
184
184
  z.object({
185
185
  kind: z.literal('pointerPosition'),
@@ -187,7 +187,7 @@ const PlayInputInjectInput = z
187
187
  .string()
188
188
  .describe('Named test-input source id an action binds to (test_pointer_position).'),
189
189
  value: Vector2Schema,
190
- worldId: WorldIdField,
190
+ worldId: RootIdField,
191
191
  }),
192
192
  ])
193
193
  .describe(
@@ -12,7 +12,7 @@
12
12
  * readiness or a named timeout"): `case 'play'` in `handleCommand`
13
13
  * `await`s `enterPlayMode()` — which itself awaits the ENTIRE async game
14
14
  * boot (asset loads, procedural worldgen, `createGameRuntime`/
15
- * `mountManifestWorlds`) — before acking back over
15
+ * `mountManifestRoots`) — before acking back over
16
16
  * `POST /__editor/command-result`. That ack is what unblocks the relay's
17
17
  * HTTP response. So `play.start`'s `impl` genuinely does not resolve before
18
18
  * the runtime is ready: it `await`s the transport call, and the transport
@@ -101,8 +101,8 @@ const PlayStartInput = z.object({
101
101
  .describe(
102
102
  'D15/T-D15.6 — the explicit-config leg of the boot-time deterministic-seed precedence ' +
103
103
  '(beats manifest.determinism.defaultSeed/?vgai-seed= on the editor page URL); baked in ' +
104
- 'before any world mounts (`enterPlayMode`/`mountManifestWorlds`, not a post-hoc reseed). ' +
105
- 'Mirrors `vgai play --seed <n>` — see docs/D15-DETERMINISM-DESIGN.md §2.d.',
104
+ 'before any world mounts (`enterPlayMode`/`mountManifestRoots`, not a post-hoc reseed). ' +
105
+ 'Mirrors `vgai play --seed <n>`.',
106
106
  ),
107
107
  });
108
108
 
@@ -1,8 +1,8 @@
1
1
  /**
2
- * `play.runTicks` (D15/T-D15.4, `docs/D15-DETERMINISM-DESIGN.md` §2.b) the
3
- * editor door (b) of the same run-ticks primitive `window.__vgai.runTicks`
4
- * (door a, `runtime/debug-bridge.ts`) exposes: synchronously fast-forward a
5
- * running play session `n` fixed gameplay ticks, decoupled from wall clock.
2
+ * `play.runTicks` (D15/T-D15.4) the editor door (b) of the same run-ticks
3
+ * primitive `window.__vgai.runTicks` (door a, `runtime/debug-bridge.ts`)
4
+ * exposes: synchronously fast-forward a running play session `n` fixed
5
+ * gameplay ticks, decoupled from wall clock.
6
6
  *
7
7
  * Wire: relay case `run-ticks` (`command-listener.ts`) reaching the live
8
8
  * session's `GameInternal.runTicks` through `play-mode.ts`'s
@@ -1,7 +1,7 @@
1
1
  /**
2
- * `play.gameplayState.inspect` / `play.gameplayState.list`
3
- * (B4, §8 B4 "gameplay state/test-hook inspection when provided by the game";
4
- * implemented for real in Wave 5, docs/SYNTHETIC-PLAYER-SPEC.md §3.5).
2
+ * `play.gameplayState.inspect` / `play.gameplayState.list` (B4, §8 B4
3
+ * "gameplay state/test-hook inspection when provided by the game";
4
+ * implemented for real in Wave 5).
5
5
  *
6
6
  * The registration mechanism this file's old HONEST-GAP jsdoc anticipated
7
7
  * now exists: games register named state providers via
@@ -97,7 +97,7 @@ export const playGameplayStateInspect = defineTool({
97
97
  summary: 'Inspect game-registered state providers from a running play session.',
98
98
  description:
99
99
  'Reads named state providers a game registered via ctx.debug.registerStateProvider ' +
100
- '(docs/SYNTHETIC-PLAYER-SPEC.md §3.1) from the live editor play session, through the ' +
100
+ 'from the live editor play session, through the ' +
101
101
  'inspect-gameplay-state relay command. `keys` are provider names; omitting them returns ' +
102
102
  'one coherent snapshot of every provider. Returns { state: null } when the running game ' +
103
103
  'exposes no debug adapter; throws GAMEPLAY_STATE_UNSUPPORTED against a stale editor page.',
@@ -1,5 +1,5 @@
1
1
  /**
2
- * `play.status` / `play.activeWorld.inspect` (B4, §8 B4 "status and
2
+ * `play.status` / `play.activeRoot.inspect` (B4, §8 B4 "status and
3
3
  * active-world inspection").
4
4
  *
5
5
  * status.playState is genuinely wired: `GET /__editor/state`'s `playState`
@@ -11,11 +11,11 @@
11
11
  * .timeScale`; each is `null`/`false` only when no session/Game is running
12
12
  * or a prior `play.seed.set`/`play.timeScale.set` never succeeded.
13
13
  *
14
- * `activeWorld.inspect` is a full HONEST GAP: `collectState()` carries no
14
+ * `activeRoot.inspect` is a full HONEST GAP: `collectState()` carries no
15
15
  * world id/kind field at all today (only a scalar `entityCount` and the
16
16
  * other UI-state fields already listed in `../editor/transport.ts`'s
17
17
  * jsdoc) — the live wire protocol has nothing to report. A test transport
18
- * can supply a real `ActiveWorldInfo`, proving the result schema round-trips
18
+ * can supply a real `ActiveRootInfo`, proving the result schema round-trips
19
19
  * end to end (see `../../test/play-operations.test.ts`).
20
20
  */
21
21
 
@@ -108,27 +108,27 @@ export const playStatus = defineTool({
108
108
  });
109
109
 
110
110
  // ---------------------------------------------------------------------------
111
- // play.activeWorld.inspect
111
+ // play.activeRoot.inspect
112
112
  // ---------------------------------------------------------------------------
113
113
 
114
- const PlayActiveWorldInspectInput = z
114
+ const PlayActiveRootInspectInput = z
115
115
  .object({})
116
116
  .describe('No input — inspects the active world of the running play session.');
117
117
 
118
- const PlayActiveWorldInspectResult = z.object({
118
+ const PlayActiveRootInspectResult = z.object({
119
119
  worldId: z.string(),
120
120
  worldKind: z.string(),
121
121
  });
122
122
 
123
- export const playActiveWorldInspect = defineTool({
124
- name: 'play.activeWorld.inspect',
123
+ export const playActiveRootInspect = defineTool({
124
+ name: 'play.activeRoot.inspect',
125
125
  summary: 'Inspect the active world of a running play session (id/kind).',
126
126
  description:
127
127
  'Real wire protocol has no world data today (see module jsdoc) — always throws the declared ' +
128
128
  'ACTIVE_WORLD_UNAVAILABLE against the real transport; a test transport can supply real data, ' +
129
129
  'proving the result schema round-trips end to end.',
130
- input: PlayActiveWorldInspectInput,
131
- result: PlayActiveWorldInspectResult,
130
+ input: PlayActiveRootInspectInput,
131
+ result: PlayActiveRootInspectResult,
132
132
  errors: [PLAY_RUNTIME_NOT_AVAILABLE_ERROR, ACTIVE_WORLD_UNAVAILABLE_ERROR],
133
133
  requires: { editor: true, play: true },
134
134
  host: 'runtime-page',
@@ -139,9 +139,9 @@ export const playActiveWorldInspect = defineTool({
139
139
  const transport = getPlayTransport(ctx);
140
140
  const session = await resolvePlaySession(ctx, transport);
141
141
  const world = await withPlayTimeout(
142
- transport.getActiveWorld(session, PLAY_READ_TIMEOUT_MS),
142
+ transport.getActiveRoot(session, PLAY_READ_TIMEOUT_MS),
143
143
  PLAY_READ_TIMEOUT_MS,
144
- 'play.activeWorld.inspect',
144
+ 'play.activeRoot.inspect',
145
145
  ).catch(() => undefined);
146
146
  if (!world) {
147
147
  throw new ToolError(
@@ -156,5 +156,5 @@ export const playActiveWorldInspect = defineTool({
156
156
 
157
157
  export function registerStatusOperations(registry: ToolRegistry): void {
158
158
  registry.register(playStatus);
159
- registry.register(playActiveWorldInspect);
159
+ registry.register(playActiveRootInspect);
160
160
  }
@@ -1,6 +1,5 @@
1
1
  /**
2
- * Transport seam for B4's `play.*` operations
3
- * (docs/AI-NATIVE-AUTHORING-IMPLEMENTATION-SPEC.md §8 B4).
2
+ * Transport seam for B4's `play.*` operations.
4
3
  *
5
4
  * `play.*` targets or launches a playable runtime (§5.7). TODAY, the only
6
5
  * playable runtime this repo can launch/target is the one already mounted
@@ -17,8 +16,8 @@
17
16
  * (these ops target/launch a playable runtime, which is the host
18
17
  * classification's whole point), while the REAL transport underneath rides
19
18
  * the existing editor-browser relay — see each op file's jsdoc for exactly
20
- * which relay call it reuses, and PLAY-GAPS.md-style notes below for what
21
- * has no relay wiring today.
19
+ * which relay call it reuses, and the notes below for what has no relay
20
+ * wiring today.
22
21
  *
23
22
  * SESSION RESOLUTION: `resolvePlaySession` mirrors
24
23
  * `../editor/transport.ts`'s `resolveEditorSession` byte-for-byte in
@@ -32,17 +31,16 @@
32
31
  * module jsdoc documents for its session-registry reader — a few lines
33
32
  * duplicated beats a much larger, unrelated interface coupling.
34
33
  *
35
- * REAL TRANSPORT SURFACE — what `HttpPlayTransport` actually talks to.
36
- * Wave 5 (docs/SYNTHETIC-PLAYER-SPEC.md §3.5) closed the version-skew lie:
37
- * `command-listener.ts`'s `handleCommand` now has a `default:` case answering
38
- * `{ok:false, data:{code:'UNKNOWN_COMMAND_TYPE'}}` for any command type the
39
- * connected editor page predates, and the relay's result leg carries `data`
40
- * end to end (`reportCommandResult` → `/__editor/command-result`
41
- * `commandResponseFor` `relayCommand` below). Methods here translate that
42
- * structured marker into `undefined` — the undefined-means-unsupported
43
- * convention `editor/transport.ts`'s `openStory`/`getCamera` established —
44
- * so each op throws its own declared `*_UNSUPPORTED` code against a stale
45
- * editor, never trusting a fabricated ack.
34
+ * REAL TRANSPORT SURFACE — what `HttpPlayTransport` actually talks to. Wave 5
35
+ * closed the version-skew lie: `command-listener.ts`'s `handleCommand` now has
36
+ * a `default:` case answering `{ok:false, data:{code:'UNKNOWN_COMMAND_TYPE'}}`
37
+ * for any command type the connected editor page predates, and the relay's
38
+ * result leg carries `data` end to end (`reportCommandResult` →
39
+ * `/__editor/command-result` `commandResponseFor` → `relayCommand` below).
40
+ * Methods here translate that structured marker into `undefined` — the
41
+ * undefined-means-unsupported convention `editor/transport.ts`'s
42
+ * `openStory`/`getCamera` established — so each op throws its own declared
43
+ * `*_UNSUPPORTED` code against a stale editor, never trusting a fabricated ack.
46
44
  *
47
45
  * - start/pause/resume/stop/frameStep: `POST /__editor/command` with
48
46
  * `{type:'play'|'stop'|'pause'|'resume'|'step'}` — ALL FIVE are real,
@@ -172,8 +170,6 @@ export interface PlaySessionInfo {
172
170
  port: number;
173
171
  project: string | null;
174
172
  pid: number | null;
175
- /** Registry discriminator. `kind: 'e2e'` is standalone and cannot host editor-relayed play control. */
176
- kind?: string;
177
173
  /** Exact explicitly targeted editor origin/base URL, including protocol and host. */
178
174
  url?: string;
179
175
  }
@@ -218,14 +214,13 @@ export interface PlayStatus {
218
214
  * `determinism.seededRandom` (D15) — i.e. `play.seed.set`/the
219
215
  * `gameplay-rng-ban` scan/the dev-mode RNG trap all apply to it. `false`
220
216
  * for an undeclared project even though `ctx.random` still exists (it
221
- * just isn't a documented contract — see `docs/D15-DETERMINISM-DESIGN.md`
222
- * §2.a). */
217
+ * just isn't a documented contract). */
223
218
  deterministic: boolean;
224
219
  /** Currently-applied simulation time-scale, when known — null when never successfully set (see `control-operations.ts`'s honest gap). */
225
220
  timeScale: number | null;
226
221
  }
227
222
 
228
- export interface ActiveWorldInfo {
223
+ export interface ActiveRootInfo {
229
224
  worldId: string;
230
225
  worldKind: string;
231
226
  }
@@ -298,7 +293,7 @@ export interface PlayTransport {
298
293
  frameStep(session: PlaySessionInfo, timeoutMs: number): Promise<PlayCommandResult>;
299
294
  getStatus(session: PlaySessionInfo, timeoutMs: number): Promise<PlayStatus | undefined>;
300
295
  /** Honest gap against the real transport — see `status-operations.ts`. */
301
- getActiveWorld(session: PlaySessionInfo, timeoutMs: number): Promise<ActiveWorldInfo | undefined>;
296
+ getActiveRoot(session: PlaySessionInfo, timeoutMs: number): Promise<ActiveRootInfo | undefined>;
302
297
  getLogFollowMetadata(
303
298
  session: PlaySessionInfo,
304
299
  timeoutMs: number,
@@ -312,9 +307,9 @@ export interface PlayTransport {
312
307
  timeoutMs: number,
313
308
  ): Promise<PlayCommandResult | undefined>;
314
309
  /**
315
- * D15/T-D15.6 (`docs/D15-DETERMINISM-DESIGN.md` §2.d): real since this
316
- * track — relay case `set-seed` (`command-listener.ts`) reaching the live
317
- * session's `ctx.random.reseed(seed)` through `play-mode.ts`'s
310
+ * D15/T-D15.6: real since this track — relay case `set-seed`
311
+ * (`command-listener.ts`) reaching the live session's
312
+ * `ctx.random.reseed(seed)` through `play-mode.ts`'s
318
313
  * `getPlayRuntimeAccess()`. FUTURE DRAWS ONLY (reseeding mid-run can never
319
314
  * make an already-diverged session reproducible — for that, seed at
320
315
  * session start instead). `undefined` only against a stale editor page
@@ -368,13 +363,12 @@ export interface PlayTransport {
368
363
  timeoutMs: number,
369
364
  ): Promise<PlayCommandResult | undefined>;
370
365
  /**
371
- * D15/T-D15.4 (`docs/D15-DETERMINISM-DESIGN.md` §2.b): real since this
372
- * track — relay case `run-ticks` (`command-listener.ts`) reaching the live
373
- * session's `GameInternal.runTicks` through `play-mode.ts`'s
374
- * `getPlayRuntimeAccess()`. Success is a plain `{ok: true}` (no data leg);
375
- * `undefined` only against a stale editor page (`UNKNOWN_COMMAND_TYPE`
376
- * marker). Structured failures (`RUN_TICKS_PAUSED`, `RUN_TICKS_UNAVAILABLE`)
377
- * ride `data.code`.
366
+ * D15/T-D15.4: real since this track — relay case `run-ticks`
367
+ * (`command-listener.ts`) reaching the live session's
368
+ * `GameInternal.runTicks` through `play-mode.ts`'s `getPlayRuntimeAccess()`.
369
+ * Success is a plain `{ok: true}` (no data leg); `undefined` only against a
370
+ * stale editor page (`UNKNOWN_COMMAND_TYPE` marker). Structured failures
371
+ * (`RUN_TICKS_PAUSED`, `RUN_TICKS_UNAVAILABLE`) ride `data.code`.
378
372
  */
379
373
  runTicks(
380
374
  session: PlaySessionInfo,
@@ -426,7 +420,6 @@ function readRegisteredSessions(): RegistrySessionEntry[] {
426
420
  return Array.isArray(raw)
427
421
  ? raw
428
422
  .filter(isRegistrySessionEntry)
429
- .filter((s) => s.kind !== 'e2e')
430
423
  .filter((s) => pidAlive(s.pid))
431
424
  : [];
432
425
  } catch {
@@ -492,7 +485,7 @@ async function postJson(url: string, body: unknown, timeoutMs: number): Promise<
492
485
  }
493
486
 
494
487
  function baseUrl(session: PlaySessionInfo): string {
495
- return session.url ?? `http://localhost:${session.port}`;
488
+ return session.url ?? `http://127.0.0.1:${session.port}`;
496
489
  }
497
490
 
498
491
  async function relayCommand(
@@ -558,7 +551,7 @@ export class HttpPlayTransport implements PlayTransport {
558
551
  const probes = await Promise.all(
559
552
  registered.map(async (s) => {
560
553
  const body = await fetchJson(
561
- `http://localhost:${s.port}/__editor/project`,
554
+ `http://127.0.0.1:${s.port}/__editor/project`,
562
555
  perProbeTimeout,
563
556
  );
564
557
  if (body === undefined) return undefined;
@@ -567,7 +560,6 @@ export class HttpPlayTransport implements PlayTransport {
567
560
  port: s.port,
568
561
  project,
569
562
  pid: s.pid,
570
- ...(s.kind !== undefined ? { kind: s.kind } : {}),
571
563
  };
572
564
  return info;
573
565
  }),
@@ -627,10 +619,10 @@ export class HttpPlayTransport implements PlayTransport {
627
619
  };
628
620
  }
629
621
 
630
- async getActiveWorld(
622
+ async getActiveRoot(
631
623
  _session: PlaySessionInfo,
632
624
  _timeoutMs: number,
633
- ): Promise<ActiveWorldInfo | undefined> {
625
+ ): Promise<ActiveRootInfo | undefined> {
634
626
  // `collectState()` (command-listener.ts) carries no world field today —
635
627
  // honest gap, never a fabricated world id (see status-operations.ts).
636
628
  return undefined;
@@ -819,7 +811,6 @@ export async function resolvePlaySession(
819
811
  } catch {
820
812
  return notRunning();
821
813
  }
822
- sessions = sessions.filter((session) => session.kind !== 'e2e');
823
814
  if (sessions.length === 0) return notRunning();
824
815
 
825
816
  if (editorUrl !== undefined) {
@@ -1,36 +1,29 @@
1
1
  /**
2
- * B2 — `project.*` operations
3
- * (docs/AI-NATIVE-AUTHORING-IMPLEMENTATION-SPEC.md §8 B2). Registered
4
- * SEPARATELY from B1's `registerBuiltinTools` (see `../operations.ts`)
5
- * so B1's own registry tests which assert `registerBuiltinTools`
6
- * registers "exactly the three B1 sample operations" — are unaffected; the
7
- * default `operations` singleton (`../index.ts`) calls both.
2
+ * `project.*` operations. Registered SEPARATELY from B1's
3
+ * `registerBuiltinTools` (see `../operations.ts`) so B1's own registry
4
+ * tests which assert `registerBuiltinTools` registers "exactly the three
5
+ * B1 sample operations"are unaffected; the default `operations`
6
+ * singleton (`../index.ts`) calls both.
8
7
  */
9
8
 
10
9
  export * from './asset-operations.js';
11
- export * from './component-operations.js';
12
10
  export * from './discovery-operations.js';
13
- export * from './entity-operations.js';
14
11
  export * from './input-map-operations.js';
15
12
  export * from './inspection.js';
16
13
  export * from './inspection-operation.js';
17
14
  export * from './manifest-operations.js';
18
15
  export * from './provenance.js';
19
- export * from './scene-operations.js';
20
16
  export {
21
17
  BaseHashField,
22
18
  CONFLICT_ERROR,
23
19
  checkNotStale,
24
20
  DryRunField,
25
- ENTITY_NOT_FOUND_ERROR,
26
21
  FILE_NOT_FOUND_ERROR,
27
- findEntityById,
28
22
  listProjectFiles,
29
23
  mutationResultSchema,
30
24
  NO_PROJECT_ROOT_ERROR,
31
25
  PATH_OUTSIDE_PROJECT_ERROR,
32
26
  readFileWithHash,
33
- requireEntityById,
34
27
  requireProjectRoot,
35
28
  resolveProjectPath,
36
29
  sha256Hex,
@@ -39,20 +32,23 @@ export {
39
32
 
40
33
  import type { ToolRegistry } from '../registry.js';
41
34
  import { registerAssetOperations } from './asset-operations.js';
42
- import { registerComponentOperations } from './component-operations.js';
43
35
  import { registerDiscoveryOperations } from './discovery-operations.js';
44
- import { registerEntityOperations } from './entity-operations.js';
45
36
  import { registerInputMapOperations } from './input-map-operations.js';
46
37
  import { registerInspectionOperations } from './inspection-operation.js';
47
38
  import { registerManifestOperations } from './manifest-operations.js';
48
- import { registerSceneOperations } from './scene-operations.js';
49
39
 
50
- /** Register every B2 `project.*` operation onto `registry`. */
40
+ /**
41
+ * Register every B2 `project.*` operation onto `registry`.
42
+ *
43
+ * WO-8 removed the twelve `.vscn` document tools:
44
+ * `project.scene.{read,validate,diff,apply}`,
45
+ * `project.scene.entity.{add,update,move,remove}` and
46
+ * `project.scene.component.{list,add,update,remove}`. They read and wrote the
47
+ * deleted scene format; a three root is authored as TSX source now, so the source
48
+ * tools (`project.file.*` / the editor's own adapter seam) are what act on it.
49
+ */
51
50
  export function registerProjectOperations(registry: ToolRegistry): void {
52
51
  registerManifestOperations(registry);
53
- registerSceneOperations(registry);
54
- registerEntityOperations(registry);
55
- registerComponentOperations(registry);
56
52
  registerAssetOperations(registry);
57
53
  registerInputMapOperations(registry);
58
54
  registerDiscoveryOperations(registry);
@@ -1,7 +1,7 @@
1
1
  /** Node filesystem binding for the shared, read-only project inspector. */
2
2
 
3
3
  import { readdir, readFile, stat, writeFile } from 'node:fs/promises';
4
- import { basename, join, resolve } from 'node:path';
4
+ import { basename, resolve } from 'node:path';
5
5
  import {
6
6
  createIngestManifest,
7
7
  inspectProject,
@@ -9,6 +9,7 @@ import {
9
9
  type ProjectInspectionReader,
10
10
  type SuggestedAdapterSurface,
11
11
  } from './inspection.js';
12
+ import { resolveManifestPath } from '../../../engine/src/manifest/locate.js';
12
13
  import { resolveProjectPath } from './shared.js';
13
14
 
14
15
  export type { SuggestedAdapterSurface } from './inspection.js';
@@ -56,17 +57,17 @@ export async function adaptProjectFolderPath(
56
57
  surface?: SuggestedAdapterSurface | undefined;
57
58
  entry?: string | undefined;
58
59
  },
59
- ): Promise<{ path: string; writes: ['vgai.game.json']; manifest: Record<string, unknown> }> {
60
+ ): Promise<{ path: string; writes: ['vgai.project.json']; manifest: Record<string, unknown> }> {
60
61
  const preview = await previewAdaptProjectFolderPath(path, options);
61
62
  await writeFile(
62
- join(preview.path, 'vgai.game.json'),
63
+ resolveManifestPath(preview.path),
63
64
  `${JSON.stringify(preview.manifest, null, 2)}\n`,
64
65
  {
65
66
  encoding: 'utf-8',
66
67
  flag: 'wx',
67
68
  },
68
69
  );
69
- return { path: preview.path, writes: ['vgai.game.json'], manifest: preview.manifest };
70
+ return { path: preview.path, writes: ['vgai.project.json'], manifest: preview.manifest };
70
71
  }
71
72
 
72
73
  /** Build the exact proposed sidecar without writing it. */
@@ -87,7 +88,7 @@ export async function previewAdaptProjectFolderPath(
87
88
  const rootStat = await stat(root);
88
89
  if (!rootStat.isDirectory()) throw new Error(`Project path is not a directory: ${root}`);
89
90
  const report = await inspectProject(nodeProjectInspectionReader(root));
90
- if (report.hasManifest) throw new Error('This folder already has a vgai.game.json manifest.');
91
+ if (report.hasManifest) throw new Error('This folder already has a vgai.project.json manifest.');
91
92
  let name = basename(root);
92
93
  try {
93
94
  const packageJson = JSON.parse(
@@ -9,7 +9,7 @@ import { NO_PROJECT_ROOT_ERROR, requireProjectRoot } from './shared.js';
9
9
  const ProjectInspectionResult = z.object({
10
10
  workspaceState: z.enum(['vgai-project', 'adaptable-project', 'empty-folder', 'unknown-project']),
11
11
  detectedTechnologies: z.array(z.string()),
12
- suggestedSurface: z.enum(['threejs', 'pixijs', 'react']).nullable(),
12
+ suggestedSurface: z.enum(['three', 'canvas', 'dom']).nullable(),
13
13
  entryCandidates: z.array(z.string()),
14
14
  adaptationBlockers: z.array(z.string()),
15
15
  hasManifest: z.boolean(),
@@ -9,7 +9,7 @@ export type ProjectWorkspaceState =
9
9
  | 'empty-folder'
10
10
  | 'unknown-project';
11
11
 
12
- export type SuggestedAdapterSurface = 'threejs' | 'pixijs' | 'react';
12
+ export type SuggestedAdapterSurface = 'three' | 'canvas' | 'dom';
13
13
 
14
14
  export interface ProjectInspection {
15
15
  workspaceState: ProjectWorkspaceState;
@@ -135,7 +135,7 @@ function candidateEntries(files: string[]): string[] {
135
135
  /** Inspect a folder without mutating it or assuming that it is already VGAI-shaped. */
136
136
  // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: one ordered evidence-to-recommendation decision table.
137
137
  export async function inspectProject(reader: ProjectInspectionReader): Promise<ProjectInspection> {
138
- if (await reader.exists('vgai.game.json')) {
138
+ if (await reader.exists('vgai.project.json')) {
139
139
  return {
140
140
  workspaceState: 'vgai-project',
141
141
  detectedTechnologies: ['VGAI'],
@@ -181,18 +181,18 @@ export async function inspectProject(reader: ProjectInspectionReader): Promise<P
181
181
  const suggestedSurface: SuggestedAdapterSurface | null = technologies.some(
182
182
  (name) => name === 'three.js' || name === 'React Three Fiber',
183
183
  )
184
- ? 'threejs'
184
+ ? 'three'
185
185
  : technologies.includes('PixiJS')
186
- ? 'pixijs'
186
+ ? 'canvas'
187
187
  : technologies.includes('React')
188
- ? 'react'
188
+ ? 'dom'
189
189
  : null;
190
190
  const entries = candidateEntries(files);
191
191
  const adaptationBlockers: string[] = [];
192
192
  if (suggestedSurface && entries.length === 0) {
193
193
  adaptationBlockers.push('No JavaScript or TypeScript entry candidate was found.');
194
194
  }
195
- if (suggestedSurface === 'react' && entries[0]) {
195
+ if (suggestedSurface === 'dom' && entries[0]) {
196
196
  let entrySource = '';
197
197
  try {
198
198
  entrySource = await reader.read(entries[0]);