@vgai/sdk 0.5.2 → 0.5.4
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
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `project.discover` and `project.manifest.*` (B2,
|
|
3
|
-
* docs/AI-NATIVE-AUTHORING-IMPLEMENTATION-SPEC.md §8 B2 "project
|
|
2
|
+
* `project.discover` and `project.manifest.*` (B2, "project
|
|
4
3
|
* discovery/status" + "manifest read/validate/update").
|
|
5
4
|
*
|
|
6
5
|
* REUSE, not reimplementation: `project.manifest.validate`/`.update` both
|
|
@@ -33,7 +32,7 @@ import {
|
|
|
33
32
|
writeFileAtomic,
|
|
34
33
|
} from './shared.js';
|
|
35
34
|
|
|
36
|
-
const MANIFEST_FILENAME = 'vgai.
|
|
35
|
+
const MANIFEST_FILENAME = 'vgai.project.json';
|
|
37
36
|
|
|
38
37
|
// ---------------------------------------------------------------------------
|
|
39
38
|
// project.discover — richer, real project-native discovery (B1's
|
|
@@ -49,7 +48,7 @@ const ProjectDiscoverResult = z
|
|
|
49
48
|
projectRoot: z.string().describe('Absolute path this discovery ran against.'),
|
|
50
49
|
hasManifest: z
|
|
51
50
|
.boolean()
|
|
52
|
-
.describe('True when vgai.
|
|
51
|
+
.describe('True when vgai.project.json exists directly under projectRoot.'),
|
|
53
52
|
manifestName: z
|
|
54
53
|
.string()
|
|
55
54
|
.optional()
|
|
@@ -58,9 +57,6 @@ const ProjectDiscoverResult = z
|
|
|
58
57
|
.array(z.string())
|
|
59
58
|
.optional()
|
|
60
59
|
.describe('Adapter-root ids declared in the manifest, when present/valid.'),
|
|
61
|
-
sceneFiles: z
|
|
62
|
-
.array(z.string())
|
|
63
|
-
.describe('Every *.vscn.json found under the project (project-relative).'),
|
|
64
60
|
inputMapFiles: z
|
|
65
61
|
.array(z.string())
|
|
66
62
|
.describe('Every *.inputmap.json found under the project (project-relative).'),
|
|
@@ -114,7 +110,6 @@ export const projectDiscover = defineTool({
|
|
|
114
110
|
hasManifest,
|
|
115
111
|
...(manifestName !== undefined ? { manifestName } : {}),
|
|
116
112
|
...(rootIds !== undefined ? { rootIds } : {}),
|
|
117
|
-
sceneFiles: listProjectFiles(projectRoot, (p) => p.endsWith('.vscn.json')),
|
|
118
113
|
inputMapFiles: listProjectFiles(projectRoot, (p) => p.endsWith('.inputmap.json')),
|
|
119
114
|
storyFiles: listProjectFiles(
|
|
120
115
|
projectRoot,
|
|
@@ -133,11 +128,11 @@ export const projectDiscover = defineTool({
|
|
|
133
128
|
|
|
134
129
|
const ProjectManifestReadInput = z
|
|
135
130
|
.object({})
|
|
136
|
-
.describe('No input — reads vgai.
|
|
131
|
+
.describe('No input — reads vgai.project.json under ctx.projectRoot.');
|
|
137
132
|
|
|
138
133
|
const ProjectManifestReadResult = z
|
|
139
134
|
.object({
|
|
140
|
-
path: z.string().describe('Absolute path to vgai.
|
|
135
|
+
path: z.string().describe('Absolute path to vgai.project.json.'),
|
|
141
136
|
contentHash: z
|
|
142
137
|
.string()
|
|
143
138
|
.describe(
|
|
@@ -153,7 +148,7 @@ const ProjectManifestReadResult = z
|
|
|
153
148
|
|
|
154
149
|
export const projectManifestRead = defineTool({
|
|
155
150
|
name: 'project.manifest.read',
|
|
156
|
-
summary: 'Read the raw vgai.
|
|
151
|
+
summary: 'Read the raw vgai.project.json manifest for the project.',
|
|
157
152
|
description:
|
|
158
153
|
'File-native read. Returns the raw parsed JSON plus a content hash for optimistic-concurrency updates.',
|
|
159
154
|
input: ProjectManifestReadInput,
|
|
@@ -163,7 +158,7 @@ export const projectManifestRead = defineTool({
|
|
|
163
158
|
host: 'node',
|
|
164
159
|
mutates: false,
|
|
165
160
|
supportsDryRun: false,
|
|
166
|
-
permission: { risk: 'read', summary: 'Reads vgai.
|
|
161
|
+
permission: { risk: 'read', summary: 'Reads vgai.project.json; no writes.' },
|
|
167
162
|
async impl(_input, ctx) {
|
|
168
163
|
const projectRoot = requireProjectRoot(ctx);
|
|
169
164
|
const path = resolveProjectPath(projectRoot, MANIFEST_FILENAME);
|
|
@@ -181,7 +176,7 @@ const ProjectManifestValidateInput = z
|
|
|
181
176
|
manifest: z
|
|
182
177
|
.unknown()
|
|
183
178
|
.optional()
|
|
184
|
-
.describe('Validate this raw value instead of re-reading vgai.
|
|
179
|
+
.describe('Validate this raw value instead of re-reading vgai.project.json from disk.'),
|
|
185
180
|
})
|
|
186
181
|
.describe('Validate the on-disk manifest, or a given candidate document.');
|
|
187
182
|
|
|
@@ -209,7 +204,7 @@ export const projectManifestValidate = defineTool({
|
|
|
209
204
|
supportsDryRun: false,
|
|
210
205
|
permission: {
|
|
211
206
|
risk: 'read',
|
|
212
|
-
summary: 'Reads vgai.
|
|
207
|
+
summary: 'Reads vgai.project.json (or validates a given document); no writes.',
|
|
213
208
|
},
|
|
214
209
|
async impl(input, ctx) {
|
|
215
210
|
let raw: unknown;
|
|
@@ -264,7 +259,7 @@ const ProjectManifestUpdateInput = z
|
|
|
264
259
|
dryRun: DryRunField,
|
|
265
260
|
baseHash: BaseHashField,
|
|
266
261
|
})
|
|
267
|
-
.describe('Patch a subset of top-level vgai.
|
|
262
|
+
.describe('Patch a subset of top-level vgai.project.json fields.');
|
|
268
263
|
|
|
269
264
|
const ProjectManifestUpdateResult = mutationResultSchema(
|
|
270
265
|
z
|
|
@@ -274,7 +269,7 @@ const ProjectManifestUpdateResult = mutationResultSchema(
|
|
|
274
269
|
|
|
275
270
|
export const projectManifestUpdate = defineTool({
|
|
276
271
|
name: 'project.manifest.update',
|
|
277
|
-
summary: 'Patch name/version/resolution on vgai.
|
|
272
|
+
summary: 'Patch name/version/resolution on vgai.project.json, dry-run capable, conflict-checked.',
|
|
278
273
|
description:
|
|
279
274
|
'Merges `patch` onto the raw on-disk manifest, re-validates the FULL merged document through ' +
|
|
280
275
|
'GameManifestSchema (the real schema, cross-field rules included) before ever writing, and ' +
|
|
@@ -296,7 +291,7 @@ export const projectManifestUpdate = defineTool({
|
|
|
296
291
|
host: 'node',
|
|
297
292
|
mutates: true,
|
|
298
293
|
supportsDryRun: true,
|
|
299
|
-
permission: { risk: 'write', summary: 'Writes vgai.
|
|
294
|
+
permission: { risk: 'write', summary: 'Writes vgai.project.json.' },
|
|
300
295
|
async impl(input, ctx) {
|
|
301
296
|
const projectRoot = requireProjectRoot(ctx);
|
|
302
297
|
const path = resolveProjectPath(projectRoot, MANIFEST_FILENAME);
|
package/src/project/shared.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Shared plumbing for B2's `project.*` operations
|
|
3
|
-
* (docs/AI-NATIVE-AUTHORING-IMPLEMENTATION-SPEC.md §8 B2).
|
|
2
|
+
* Shared plumbing for B2's `project.*` operations.
|
|
4
3
|
*
|
|
5
4
|
* Every `project.*` op is file-native (host: 'node', requires: { project: true })
|
|
6
5
|
* and runs with NO editor process — it resolves `ctx.projectRoot`, reads/writes
|
|
7
6
|
* plain files on disk, and reuses the engine's existing Zod schemas +
|
|
8
|
-
* `applyDiff` for anything
|
|
7
|
+
* schemas. (WO-8: this also said it reused the engine's `applyDiff` for anything
|
|
8
|
+
* scene-shaped — `engine/src/scene/scene-apply.ts` and the twelve
|
|
9
|
+
* `project.scene.*` ops that used it are deleted with the `.vscn.json` format.)
|
|
9
10
|
*
|
|
10
11
|
* Two cross-cutting decisions (pinned by the B2 task brief) live here so every
|
|
11
12
|
* operation module applies them identically:
|
|
@@ -38,7 +39,6 @@ import {
|
|
|
38
39
|
writeFileSync,
|
|
39
40
|
} from 'node:fs';
|
|
40
41
|
import { dirname, isAbsolute, join, relative, resolve, win32 } from 'node:path';
|
|
41
|
-
import type { SceneEntity } from '@vgai/engine/scene/scene-types';
|
|
42
42
|
import { z } from 'zod';
|
|
43
43
|
import { ToolError } from '../errors.js';
|
|
44
44
|
import type { ToolErrorDefinition } from '../registry.js';
|
|
@@ -274,41 +274,6 @@ export function mutationResultSchema<T extends z.ZodType>(afterSchema: T) {
|
|
|
274
274
|
});
|
|
275
275
|
}
|
|
276
276
|
|
|
277
|
-
// ---------------------------------------------------------------------------
|
|
278
|
-
// Scene entity tree walk (read-side helper shared by entity/component ops)
|
|
279
|
-
// ---------------------------------------------------------------------------
|
|
280
|
-
|
|
281
|
-
export const ENTITY_NOT_FOUND_ERROR: ToolErrorDefinition = {
|
|
282
|
-
code: 'ENTITY_NOT_FOUND',
|
|
283
|
-
summary: 'No entity with the given id exists in the scene.',
|
|
284
|
-
data: z.object({ id: z.string() }),
|
|
285
|
-
};
|
|
286
|
-
|
|
287
|
-
/** Depth-first search for an entity by its stable `id` — the SAME identity `applyDiff` addresses by (never an array index). */
|
|
288
|
-
export function findEntityById(entities: SceneEntity[], id: string): SceneEntity | undefined {
|
|
289
|
-
for (const e of entities) {
|
|
290
|
-
if (e.id === id) return e;
|
|
291
|
-
if (e.children) {
|
|
292
|
-
const found = findEntityById(e.children, id);
|
|
293
|
-
if (found) return found;
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
return undefined;
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
/** Require an entity by id, throwing the declared `ENTITY_NOT_FOUND` shape otherwise. */
|
|
300
|
-
export function requireEntityById(entities: SceneEntity[], id: string): SceneEntity {
|
|
301
|
-
const found = findEntityById(entities, id);
|
|
302
|
-
if (!found) {
|
|
303
|
-
throw new ToolError(
|
|
304
|
-
'ENTITY_NOT_FOUND',
|
|
305
|
-
`No entity with id "${id}" exists in this scene.`,
|
|
306
|
-
{ id },
|
|
307
|
-
);
|
|
308
|
-
}
|
|
309
|
-
return found;
|
|
310
|
-
}
|
|
311
|
-
|
|
312
277
|
// ---------------------------------------------------------------------------
|
|
313
278
|
// Project-wide file discovery (shared by project.discover / story / theatre /
|
|
314
279
|
// xstate discovery ops — all "find files matching X under the project" ops).
|
package/src/registry.ts
CHANGED
|
@@ -137,8 +137,14 @@ export type ToolOutcome<TResult = unknown> =
|
|
|
137
137
|
* forwarding unvalidated data).
|
|
138
138
|
* 3. Anything else — an `ToolError` with an undeclared code, a plain
|
|
139
139
|
* `Error`, or a non-Error throw — normalized into INTERNAL_ERROR. The
|
|
140
|
-
* raw exception/message is
|
|
141
|
-
*
|
|
140
|
+
* raw exception/message is never used as the identifying `code`, but it
|
|
141
|
+
* IS carried in `message` as well as `data.message`: every projection
|
|
142
|
+
* (the CLI's `vgai run`, the oclif commands, `vgai screenshot`'s module
|
|
143
|
+
* lane) shows `error.message` and only some of them dump `data`, so a
|
|
144
|
+
* `message` that said nothing but "threw an unstructured exception"
|
|
145
|
+
* hid the one sentence the caller needed ("No editor connected — open
|
|
146
|
+
* the editor in a browser tab, then retry.") behind whichever surface
|
|
147
|
+
* happened to print the whole outcome.
|
|
142
148
|
*/
|
|
143
149
|
function normalizeThrown(def: ToolDefinition, err: unknown): StructuredOperationError {
|
|
144
150
|
if (isOperationError(err)) {
|
|
@@ -173,7 +179,7 @@ function normalizeThrown(def: ToolDefinition, err: unknown): StructuredOperation
|
|
|
173
179
|
const message = err instanceof Error ? err.message : String(err);
|
|
174
180
|
return {
|
|
175
181
|
code: CORE_ERROR_CODES.INTERNAL_ERROR,
|
|
176
|
-
message: `"${def.name}"
|
|
182
|
+
message: `"${def.name}" failed: ${message}`,
|
|
177
183
|
data: { message },
|
|
178
184
|
};
|
|
179
185
|
}
|
package/src/render/index.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The render pipeline core (I0/I2-I8, A1) — relocated INTO `@vgai/sdk` so the
|
|
3
|
-
* dependency direction is CLI -> SDK -> engine, never SDK -> CLI (
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* Formerly `packages/vgai-cli/src/render-cinematic.ts` +
|
|
3
|
+
* dependency direction is CLI -> SDK -> engine, never SDK -> CLI (the SDK is
|
|
4
|
+
* the authoritative operation core; CLI/HTTP/MCP are projections on top of
|
|
5
|
+
* it). Formerly `packages/vgai-cli/src/render-cinematic.ts` +
|
|
7
6
|
* `packages/vgai-cli/src/capabilities/ffmpeg.ts`, imported by the SDK's own
|
|
8
7
|
* `cinematic.*` operations (`../cinematic/`) via a deep-src `@vgai/cli`
|
|
9
8
|
* tsconfig path alias — a package-layering inversion once B6 makes the CLI
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `vgai render-cinematic` — the I0 crux deliverable
|
|
3
|
-
* (`docs/AI-NATIVE-AUTHORING-IMPLEMENTATION-SPEC.md` §18 "I0 — recording
|
|
2
|
+
* `vgai render-cinematic` — the I0 crux deliverable ("I0 — recording
|
|
4
3
|
* spike", §15 Workstream I). Produces a REAL, deterministic, playable video
|
|
5
4
|
* file from an authored Theatre-driven cinematic: launches headless
|
|
6
5
|
* Chromium (SwiftShader) against a render-mode page (`?vgai-render=1`,
|
|
@@ -81,8 +80,7 @@ export function defaultSimulateCinematicViteConfig(
|
|
|
81
80
|
export type RenderCinematicFormat = 'mp4' | 'webm' | 'png';
|
|
82
81
|
export type RenderCinematicBackground = 'opaque' | 'transparent';
|
|
83
82
|
/**
|
|
84
|
-
* I6 audio mode (
|
|
85
|
-
* the audio-capture half of §13 G4):
|
|
83
|
+
* I6 audio mode (+ the audio-capture half of §13 G4):
|
|
86
84
|
* - `'auto'` (default) — mux audio iff the render page declares a Tone
|
|
87
85
|
* score (`window.__vgaiRenderAudio.hasAudio === true`,
|
|
88
86
|
* `@engine/runtime/render-audio-control`); a page with no score renders
|
|
@@ -890,19 +888,21 @@ async function readExcludedFromDeterminism(page: Page): Promise<string[]> {
|
|
|
890
888
|
* the ACTUAL fixed gameplay substep `dt` the harness is using, instead of
|
|
891
889
|
* this pipeline assuming every fixture matches its own `1/60` default. Only
|
|
892
890
|
* called for a `resolved.simulate: true` request (see the call site in
|
|
893
|
-
* {@link renderCinematic}).
|
|
894
|
-
*
|
|
895
|
-
*
|
|
896
|
-
*
|
|
897
|
-
*
|
|
898
|
-
*
|
|
891
|
+
* {@link renderCinematic}).
|
|
892
|
+
*
|
|
893
|
+
* `simulateFixedDt()` is a REQUIRED member of `VgaiRenderHarness` and every
|
|
894
|
+
* render page is built from this repo, so there is no "older page" case: the
|
|
895
|
+
* former `typeof === 'function'` probe (and the `?? 1/60` fallback it fed in
|
|
896
|
+
* {@link buildSimulateManifest}) could only ever have converted a genuinely
|
|
897
|
+
* broken harness into a plausible-looking guess. Both are deleted — a
|
|
898
|
+
* missing method now throws where it happens.
|
|
899
899
|
*/
|
|
900
|
-
async function readSimulateFixedDt(page: Page): Promise<number
|
|
901
|
-
return page.evaluate(() =>
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
900
|
+
async function readSimulateFixedDt(page: Page): Promise<number> {
|
|
901
|
+
return page.evaluate(() =>
|
|
902
|
+
(
|
|
903
|
+
globalThis as unknown as { __vgaiRender: { simulateFixedDt(): number } }
|
|
904
|
+
).__vgaiRender.simulateFixedDt(),
|
|
905
|
+
);
|
|
906
906
|
}
|
|
907
907
|
|
|
908
908
|
/**
|
|
@@ -1558,25 +1558,26 @@ async function preparePipelineOutputs(
|
|
|
1558
1558
|
* (`RenderControlHarnessOptions.simulateFixedDt`) — silently wrong for any
|
|
1559
1559
|
* fixture that set a different substep dt. `actualFixedDt` is
|
|
1560
1560
|
* {@link readSimulateFixedDt}'s result — the REAL value read off the live
|
|
1561
|
-
* harness right before the page closed
|
|
1562
|
-
*
|
|
1563
|
-
* `
|
|
1564
|
-
* honestly-commented "best known default", not a value ever claimed to be
|
|
1565
|
-
* measured.
|
|
1561
|
+
* harness right before the page closed, always measured, never defaulted.
|
|
1562
|
+
* It is `undefined` only for a non-`--simulate` request, where this function
|
|
1563
|
+
* returns `undefined` before reading it.
|
|
1566
1564
|
*/
|
|
1567
1565
|
function buildSimulateManifest(
|
|
1568
1566
|
resolved: ResolvedRenderCinematicRequest,
|
|
1569
1567
|
actualFixedDt: number | undefined,
|
|
1570
1568
|
): RenderManifestSimulate | undefined {
|
|
1571
1569
|
if (!resolved.simulate) return undefined;
|
|
1570
|
+
if (actualFixedDt === undefined) {
|
|
1571
|
+
throw new Error(
|
|
1572
|
+
'renderCinematic: --simulate requested but the render harness reported no ' +
|
|
1573
|
+
'simulateFixedDt() — the page is not a real @engine/runtime/render-control harness.',
|
|
1574
|
+
);
|
|
1575
|
+
}
|
|
1572
1576
|
return {
|
|
1573
1577
|
seed: resolved.seed,
|
|
1574
1578
|
substeps: resolved.simulateSubsteps,
|
|
1575
1579
|
warmupSubsteps: resolved.simulateWarmupSubsteps,
|
|
1576
|
-
|
|
1577
|
-
// (predates I7), so this is the engine-wide documented default
|
|
1578
|
-
// (`render-control.ts`'s own `1/60`), NOT a measured value.
|
|
1579
|
-
fixedTimestep: actualFixedDt ?? 1 / 60,
|
|
1580
|
+
fixedTimestep: actualFixedDt,
|
|
1580
1581
|
};
|
|
1581
1582
|
}
|
|
1582
1583
|
|
|
@@ -1815,9 +1816,7 @@ export async function renderCinematic(
|
|
|
1815
1816
|
const excludedFromDeterminism = await readExcludedFromDeterminism(page);
|
|
1816
1817
|
// I7 fold-in (I5 nit) — the ACTUAL harness dt, read before the page/
|
|
1817
1818
|
// browser closes, same constraint as the two calls above. `undefined`
|
|
1818
|
-
// for a non-`--simulate` request
|
|
1819
|
-
// that predates this harness method (back-compat fallback in
|
|
1820
|
-
// `buildSimulateManifest`).
|
|
1819
|
+
// only for a non-`--simulate` request, where it is never called.
|
|
1821
1820
|
const actualSimulateFixedDt = resolved.simulate ? await readSimulateFixedDt(page) : undefined;
|
|
1822
1821
|
|
|
1823
1822
|
await page.context().close();
|
package/src/types.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Shared vocabulary for the operation registry (B1
|
|
3
|
-
* docs/AI-NATIVE-AUTHORING-IMPLEMENTATION-SPEC.md §8 B1 / §5.7 / §3.4).
|
|
2
|
+
* Shared vocabulary for the operation registry (B1).
|
|
4
3
|
*/
|
|
5
4
|
|
|
6
5
|
/** The four namespaces every operation name must live under (§5.7). */
|
|
@@ -53,7 +52,7 @@ export interface ProjectOutputFile {
|
|
|
53
52
|
path: string;
|
|
54
53
|
content: string | Uint8Array;
|
|
55
54
|
mediaType?: string;
|
|
56
|
-
role?: 'asset' | '
|
|
55
|
+
role?: 'asset' | 'provenance' | 'other';
|
|
57
56
|
}
|
|
58
57
|
|
|
59
58
|
/** JSON-safe summary returned after an atomic generated-output transaction. */
|
|
@@ -123,5 +122,10 @@ export interface ToolContext {
|
|
|
123
122
|
signal?: AbortSignal;
|
|
124
123
|
/** Atomic writer for generated project assets (Node project operations). */
|
|
125
124
|
projectOutputs?: ProjectOutputWriter;
|
|
125
|
+
/** WHICH mounted instance a game-driving tool should address, when the
|
|
126
|
+
* editor has several live (multiplayer authoring). A tool that drives the
|
|
127
|
+
* game binds `game.instance(ctx.instance)` from it; omitted means the sole
|
|
128
|
+
* live instance. */
|
|
129
|
+
instance?: string;
|
|
126
130
|
[key: string]: unknown;
|
|
127
131
|
}
|