@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.
- package/package.json +3 -3
- package/src/account.ts +20 -0
- package/src/cinematic/gsap-operations.ts +3 -3
- package/src/cinematic/index.ts +3 -4
- package/src/cinematic/theatre-operations.ts +1 -1
- package/src/editor/index.ts +11 -4
- package/src/editor/inspection-operations.ts +189 -0
- package/src/editor/open-operations.ts +4 -44
- package/src/editor/transport.ts +94 -10
- package/src/operations.ts +10 -5
- package/src/perf/perf-run.ts +4 -4
- package/src/play/control-operations.ts +19 -19
- package/src/play/debug-command-operations.ts +2 -3
- package/src/play/index.ts +4 -5
- package/src/play/input-operations.ts +12 -12
- package/src/play/lifecycle-operations.ts +3 -3
- package/src/play/run-ticks-operations.ts +4 -4
- package/src/play/state-operations.ts +4 -4
- package/src/play/status-operations.ts +13 -13
- package/src/play/transport.ts +29 -38
- package/src/project/index.ts +15 -19
- package/src/project/inspection-node.ts +6 -5
- package/src/project/inspection-operation.ts +1 -1
- package/src/project/inspection.ts +6 -6
- package/src/project/manifest-operations.ts +12 -17
- package/src/project/shared.ts +4 -39
- package/src/registry.ts +9 -3
- package/src/render/index.ts +3 -4
- package/src/render/render-cinematic.ts +27 -28
- package/src/types.ts +7 -3
- package/src/project/component-operations.ts +0 -337
- package/src/project/entity-operations.ts +0 -366
- package/src/project/scene-operations.ts +0 -426
|
@@ -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
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* `
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
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
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
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
|
|
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
|
-
*
|
|
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
|
-
*
|
|
3
|
-
* (
|
|
4
|
-
*
|
|
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
|
-
|
|
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
|
|
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
|
|
15
|
-
*
|
|
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
|
-
*
|
|
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 `
|
|
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
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
* `
|
|
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`/`
|
|
105
|
-
'Mirrors `vgai play --seed <n
|
|
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
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
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
|
-
*
|
|
4
|
-
* implemented for real in Wave 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
|
-
'
|
|
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.
|
|
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
|
-
* `
|
|
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 `
|
|
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.
|
|
111
|
+
// play.activeRoot.inspect
|
|
112
112
|
// ---------------------------------------------------------------------------
|
|
113
113
|
|
|
114
|
-
const
|
|
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
|
|
118
|
+
const PlayActiveRootInspectResult = z.object({
|
|
119
119
|
worldId: z.string(),
|
|
120
120
|
worldKind: z.string(),
|
|
121
121
|
});
|
|
122
122
|
|
|
123
|
-
export const
|
|
124
|
-
name: 'play.
|
|
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:
|
|
131
|
-
result:
|
|
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.
|
|
142
|
+
transport.getActiveRoot(session, PLAY_READ_TIMEOUT_MS),
|
|
143
143
|
PLAY_READ_TIMEOUT_MS,
|
|
144
|
-
'play.
|
|
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(
|
|
159
|
+
registry.register(playActiveRootInspect);
|
|
160
160
|
}
|
package/src/play/transport.ts
CHANGED
|
@@ -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
|
|
21
|
-
*
|
|
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
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
316
|
-
*
|
|
317
|
-
*
|
|
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
|
|
372
|
-
*
|
|
373
|
-
*
|
|
374
|
-
*
|
|
375
|
-
*
|
|
376
|
-
*
|
|
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://
|
|
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://
|
|
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
|
|
622
|
+
async getActiveRoot(
|
|
631
623
|
_session: PlaySessionInfo,
|
|
632
624
|
_timeoutMs: number,
|
|
633
|
-
): Promise<
|
|
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) {
|
package/src/project/index.ts
CHANGED
|
@@ -1,36 +1,29 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* (
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
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
|
-
/**
|
|
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,
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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(['
|
|
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 = '
|
|
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.
|
|
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
|
-
? '
|
|
184
|
+
? 'three'
|
|
185
185
|
: technologies.includes('PixiJS')
|
|
186
|
-
? '
|
|
186
|
+
? 'canvas'
|
|
187
187
|
: technologies.includes('React')
|
|
188
|
-
? '
|
|
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 === '
|
|
195
|
+
if (suggestedSurface === 'dom' && entries[0]) {
|
|
196
196
|
let entrySource = '';
|
|
197
197
|
try {
|
|
198
198
|
entrySource = await reader.read(entries[0]);
|