@vgai/editor-sdk 0.5.21 → 0.5.23
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/README.md +1 -2
- package/package.json +4 -3
- package/src/client.ts +42 -12
- package/src/extension.ts +1 -1
- package/src/index.ts +4 -0
- package/src/share.ts +160 -0
- package/src/types.ts +117 -43
package/README.md
CHANGED
|
@@ -48,8 +48,7 @@ One export, `EditorClient`, plus its types (`EditorState`, `ProjectInfo`,
|
|
|
48
48
|
the independent `bounds` category). Shading targets the active Scene/Game
|
|
49
49
|
viewport and remains render-only, session-local state.
|
|
50
50
|
- Transform tools: `setTransformMode`, `setTransformSpace`, `setSnap`
|
|
51
|
-
-
|
|
52
|
-
`listRecentProjects`
|
|
51
|
+
- Project: `createProject`, `openProject`, `getProject`, `listRecentProjects`
|
|
53
52
|
- State/logs: `getState`, `waitForState(predicate, timeoutMs)`,
|
|
54
53
|
`getLogEntries`
|
|
55
54
|
- Project tools: `listProjectTools`, `runProjectTool`
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@vgai/editor-sdk",
|
|
3
3
|
"author": "Volter AI, Inc.",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
|
-
"version": "0.5.
|
|
5
|
+
"version": "0.5.23",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
@@ -19,11 +19,12 @@
|
|
|
19
19
|
".": "./src/index.ts",
|
|
20
20
|
"./contributions": "./src/contributions.ts",
|
|
21
21
|
"./document-probe": "./src/document-probe.ts",
|
|
22
|
-
"./extension": "./src/extension.ts"
|
|
22
|
+
"./extension": "./src/extension.ts",
|
|
23
|
+
"./share": "./src/share.ts"
|
|
23
24
|
},
|
|
24
25
|
"dependencies": {
|
|
25
26
|
"@types/three": "^0.180.0",
|
|
26
|
-
"@vgai/sdk": "0.5.
|
|
27
|
+
"@vgai/sdk": "0.5.23"
|
|
27
28
|
},
|
|
28
29
|
"peerDependencies": {
|
|
29
30
|
"@vgai/engine": "*",
|
package/src/client.ts
CHANGED
|
@@ -21,6 +21,7 @@ import type {
|
|
|
21
21
|
InspectedHierarchy,
|
|
22
22
|
InspectedInspection,
|
|
23
23
|
LabeledShotSetCapture,
|
|
24
|
+
PlayStarted,
|
|
24
25
|
PresentedEditorView,
|
|
25
26
|
ProjectInfo,
|
|
26
27
|
ProjectTemplate,
|
|
@@ -294,11 +295,21 @@ export class EditorClient {
|
|
|
294
295
|
* `logs/play-*.jsonl` filename and its session-journal line. Findability
|
|
295
296
|
* only: no registry, no uniqueness, no lookup verb — grep and `ls` are the
|
|
296
297
|
* query engine. Omitted, the filename keeps its exact unnamed shape. */
|
|
297
|
-
|
|
298
|
-
|
|
298
|
+
/* `opts.record` (`vgai play --record <name>`) — NAMES this run's recording
|
|
299
|
+
* file. It does not ENABLE recording: every relayed play records, with no
|
|
300
|
+
* flag (see `editor/src/play-recording.ts`). Omitted, the clip lands in the
|
|
301
|
+
* rotating `.vgai/recordings/play-latest.webm`; named, in
|
|
302
|
+
* `.vgai/recordings/<name>.webm`, which nothing ever rotates. */
|
|
303
|
+
async play(opts?: {
|
|
304
|
+
seed?: number;
|
|
305
|
+
name?: string | null;
|
|
306
|
+
record?: string | null;
|
|
307
|
+
}): Promise<PlayStarted> {
|
|
308
|
+
return this.command<PlayStarted>({
|
|
299
309
|
type: 'play',
|
|
300
310
|
...(opts?.seed !== undefined ? { seed: opts.seed } : {}),
|
|
301
311
|
...(opts?.name ? { name: opts.name } : {}),
|
|
312
|
+
...(opts?.record ? { record: opts.record } : {}),
|
|
302
313
|
});
|
|
303
314
|
}
|
|
304
315
|
|
|
@@ -307,8 +318,10 @@ export class EditorClient {
|
|
|
307
318
|
await this.command({ type: 'play' });
|
|
308
319
|
}
|
|
309
320
|
|
|
310
|
-
|
|
311
|
-
|
|
321
|
+
/** Stops play, and finalizes this run's recording before the surface it was
|
|
322
|
+
* photographing is torn down. The capture is absent when nothing recorded. */
|
|
323
|
+
async stop(): Promise<{ recording?: GameplayRecordingCapture }> {
|
|
324
|
+
return this.command<{ recording?: GameplayRecordingCapture }>({ type: 'stop' });
|
|
312
325
|
}
|
|
313
326
|
|
|
314
327
|
async pause(): Promise<void> {
|
|
@@ -806,12 +819,6 @@ export class EditorClient {
|
|
|
806
819
|
await this.command({ type: 'set-snap', enabled });
|
|
807
820
|
}
|
|
808
821
|
|
|
809
|
-
// --- Scene ---
|
|
810
|
-
//
|
|
811
|
-
// `openScene(path)` lived here, relaying `{type:'open-scene', path}`. That
|
|
812
|
-
// verb now rejects — the `.vscn.json` format it opened is deleted, and a three
|
|
813
|
-
// root is opened by activating its root.
|
|
814
|
-
|
|
815
822
|
// --- Project management ---
|
|
816
823
|
|
|
817
824
|
async createProject(
|
|
@@ -962,11 +969,13 @@ export class EditorClient {
|
|
|
962
969
|
`${this.baseUrl}/__editor/console${opts?.all === true ? '?all=1' : ''}`,
|
|
963
970
|
{ signal: AbortSignal.timeout(CONSOLE_DRAIN_TIMEOUT_MS) },
|
|
964
971
|
);
|
|
965
|
-
|
|
972
|
+
// Status first: a 404's empty body would otherwise die inside readJson
|
|
973
|
+
// with a parse error that hides the one fact the caller classifies on
|
|
974
|
+
// (does this server SERVE the console route at all?).
|
|
966
975
|
if (!res.ok) {
|
|
967
976
|
throw new Error(`Failed to read unresolved console: ${res.status}`);
|
|
968
977
|
}
|
|
969
|
-
return
|
|
978
|
+
return await this.readJson(res, true);
|
|
970
979
|
}
|
|
971
980
|
|
|
972
981
|
/** Acknowledge one named console condition. The response is observed and
|
|
@@ -993,6 +1002,27 @@ export class EditorClient {
|
|
|
993
1002
|
* original request. That makes a subsequent `process.exit()` safe: the
|
|
994
1003
|
* observer has already received every condition and occurrence count. */
|
|
995
1004
|
private async readJson(res: Response, consoleComplete = false): Promise<unknown> {
|
|
1005
|
+
// Every one of this client's twelve routes funnels through here, so this is
|
|
1006
|
+
// where "did the editor server answer?" is asked — the same question, and
|
|
1007
|
+
// the same JSON-content-type rule, that
|
|
1008
|
+
// `packages/editor/src/editor-server-response.ts` owns on the browser side.
|
|
1009
|
+
// It is asked again rather than imported because THIS package is published
|
|
1010
|
+
// and depends on neither `@vgai/engine` nor the editor bundle (see
|
|
1011
|
+
// `DEFAULT_URL` above for that policy).
|
|
1012
|
+
//
|
|
1013
|
+
// Not theoretical here: `baseUrl` is whatever `--url`/`VGAI_EDITOR_URL`
|
|
1014
|
+
// says, so the CLI is routinely pointed at a SHARE TUNNEL or a static host
|
|
1015
|
+
// — both of which answer `200 text/html` for a route nothing serves, and
|
|
1016
|
+
// `res.json()` then died as `Unexpected token '<'`, naming neither the URL
|
|
1017
|
+
// nor the cause.
|
|
1018
|
+
const contentType = res.headers.get('content-type') ?? '';
|
|
1019
|
+
if (!contentType.includes('application/json')) {
|
|
1020
|
+
throw new Error(
|
|
1021
|
+
`The editor at ${this.baseUrl} answered with its page fallback ` +
|
|
1022
|
+
`(${contentType || 'no content-type'}) rather than JSON, so no editor server handled ` +
|
|
1023
|
+
'the request. Check that this URL is a running `vgai edit` session.',
|
|
1024
|
+
);
|
|
1025
|
+
}
|
|
996
1026
|
const body: unknown = await res.json();
|
|
997
1027
|
this.observe(body, { unresolvedConsoleComplete: consoleComplete });
|
|
998
1028
|
if (
|
package/src/extension.ts
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* Types: `ToolInspectorContributionProps` and friends, same module.
|
|
15
15
|
* (c) **System adapters** — runtime capabilities (networking, debug, …)
|
|
16
16
|
* registered from game code via `ctx.registerSystemAdapter?.(kind, impl)`
|
|
17
|
-
* (`SystemAdapters` in `@vgai/engine`'s
|
|
17
|
+
* (`SystemAdapters` in `@vgai/engine`'s
|
|
18
18
|
* `adapter/system-adapter`). Deliberately NOT re-exported here: the
|
|
19
19
|
* engine already publishes that seam and every consumer of it also
|
|
20
20
|
* imports the engine — an alias would be a dead surface.
|
package/src/index.ts
CHANGED
|
@@ -73,6 +73,7 @@ export type {
|
|
|
73
73
|
InspectionPresentationKind,
|
|
74
74
|
InspectionSurface,
|
|
75
75
|
LabeledShotSetCapture,
|
|
76
|
+
PlayStarted,
|
|
76
77
|
PresentedEditorView,
|
|
77
78
|
ProjectInfo,
|
|
78
79
|
ProjectTemplate,
|
|
@@ -83,7 +84,9 @@ export type {
|
|
|
83
84
|
RagdollGenerationResult,
|
|
84
85
|
RecentProject,
|
|
85
86
|
ShadingMode,
|
|
87
|
+
ShotSetPoseMorph,
|
|
86
88
|
ShotSetPoseRotation,
|
|
89
|
+
ShotSetPoseStep,
|
|
87
90
|
ShotSetShot,
|
|
88
91
|
StoryCaptureOptions,
|
|
89
92
|
StoryVariantCapture,
|
|
@@ -98,5 +101,6 @@ export type {
|
|
|
98
101
|
export {
|
|
99
102
|
EDITOR_VIEW_BUILT_IN_UTILITY_IDS,
|
|
100
103
|
EDITOR_VIEW_TOOL_UTILITY_PREFIX,
|
|
104
|
+
EDITOR_VIEW_WORKSPACE_DOCUMENT_IDS,
|
|
101
105
|
isEditorViewUtility,
|
|
102
106
|
} from './types.js';
|
package/src/share.ts
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE share-session wire — `/__editor/share-control`'s payloads, declared once.
|
|
3
|
+
*
|
|
4
|
+
* This route has three clients living in three different compilation units:
|
|
5
|
+
* the editor SERVER that serves it (`packages/editor/server/`), the browser
|
|
6
|
+
* PANEL that polls it (`packages/editor/src/`), and the `vgai share` CLI
|
|
7
|
+
* (`packages/vgai-cli/src/`). Each of them used to declare its own copy of the
|
|
8
|
+
* payload, and the copies had already drifted — silently, in both directions,
|
|
9
|
+
* because an under-declared read of a JSON superset is a type error nowhere:
|
|
10
|
+
*
|
|
11
|
+
* - the CLI's invitation had no `delivery`, so `vgai share` could not report
|
|
12
|
+
* that the invitation email had FAILED to send — the one state the field
|
|
13
|
+
* exists for;
|
|
14
|
+
* - the CLI's audit rows typed `type` and `capability` as bare `string`,
|
|
15
|
+
* which accepts any spelling of an event the server never emits;
|
|
16
|
+
* - the browser panel's invitation had no `inviterAccountId`;
|
|
17
|
+
* - the browser panel had no `audit` at all, and re-declared the tunnel
|
|
18
|
+
* health shape inline instead of naming it.
|
|
19
|
+
*
|
|
20
|
+
* This package is the right home for all three: `@vgai/editor` and `@vgai/cli`
|
|
21
|
+
* both already depend on it, it depends on neither, and the CLI's esbuild
|
|
22
|
+
* bundle inlines it. (The CLI's standing "no editor-package dependency" rule is
|
|
23
|
+
* about `@vgai/editor` — the server+UI package whose express/vite/chokidar
|
|
24
|
+
* graph the standalone bundle must not drag in. This is a types-only module.)
|
|
25
|
+
*
|
|
26
|
+
* Two things deliberately stay OFF this wire, and stay off it structurally
|
|
27
|
+
* rather than by memory:
|
|
28
|
+
*
|
|
29
|
+
* - the invitation TOKEN HASH, which is a local extension on the server
|
|
30
|
+
* (`share-host.ts`'s `StoredShareInvitation extends ShareInvitationRecord`);
|
|
31
|
+
* - a participant's ORGANIZATION list, which the gateway holds on its own
|
|
32
|
+
* `ShareAccountProjection` to resolve team invitations. {@link ShareAccount}
|
|
33
|
+
* is what a participant projects to, so `status()` narrows to it.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* A share role, least authority first.
|
|
38
|
+
*
|
|
39
|
+
* `maintainer` is deliberately absent: it is local-only and has no share role,
|
|
40
|
+
* which is why the editor's `CollaborationRole` is this union PLUS `maintainer`
|
|
41
|
+
* rather than the other way around.
|
|
42
|
+
*/
|
|
43
|
+
export type ShareRole = 'viewer' | 'commenter' | 'tester' | 'editor' | 'terminal';
|
|
44
|
+
|
|
45
|
+
export type ShareTunnelProvider = 'cloudflared' | 'ngrok';
|
|
46
|
+
|
|
47
|
+
export type ShareTunnelState = 'stopped' | 'starting' | 'healthy' | 'failed';
|
|
48
|
+
|
|
49
|
+
export interface ShareTunnelHealth {
|
|
50
|
+
checkedAt: string;
|
|
51
|
+
latencyMs: number;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* The capability vocabulary a grant is checked against.
|
|
56
|
+
*
|
|
57
|
+
* Wider than the roles on purpose: `public` and `never-share` are DECLARATIONS
|
|
58
|
+
* a route makes about itself, and `maintain` is the local-only authority no
|
|
59
|
+
* share role reaches — so this union is not derivable from {@link ShareRole}.
|
|
60
|
+
*/
|
|
61
|
+
export type ShareCapability =
|
|
62
|
+
| 'public'
|
|
63
|
+
| 'view'
|
|
64
|
+
| 'comment'
|
|
65
|
+
| 'test'
|
|
66
|
+
| 'edit'
|
|
67
|
+
| 'terminal'
|
|
68
|
+
| 'maintain'
|
|
69
|
+
| 'never-share';
|
|
70
|
+
|
|
71
|
+
/** A verified VGAI account, as a share participant projects onto the wire. */
|
|
72
|
+
export interface ShareAccount {
|
|
73
|
+
id: string;
|
|
74
|
+
email: string;
|
|
75
|
+
name?: string;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* How the invitation was DELIVERED, when we delivered it.
|
|
80
|
+
*
|
|
81
|
+
* Present-and-`failed` is the state this field exists for: an invitation
|
|
82
|
+
* created whose email never left is otherwise indistinguishable, on every
|
|
83
|
+
* surface, from one that arrived.
|
|
84
|
+
*/
|
|
85
|
+
export interface ShareInvitationDelivery {
|
|
86
|
+
channel: 'clerk-email';
|
|
87
|
+
status: 'sent' | 'failed';
|
|
88
|
+
providerInvitationId?: string;
|
|
89
|
+
sentAt?: string;
|
|
90
|
+
problem?: string;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export interface ShareInvitationRecord {
|
|
94
|
+
id: string;
|
|
95
|
+
inviterAccountId: string;
|
|
96
|
+
recipientAccountId?: string;
|
|
97
|
+
recipientEmail?: string;
|
|
98
|
+
recipientOrganizationId?: string;
|
|
99
|
+
recipientOrganizationName?: string;
|
|
100
|
+
recipientOrganizationDomain?: string;
|
|
101
|
+
/** The highest role this invitation may ever be raised to. */
|
|
102
|
+
roleCeiling: ShareRole;
|
|
103
|
+
usePolicy: 'person' | 'team';
|
|
104
|
+
createdAt: string;
|
|
105
|
+
expiresAt: string;
|
|
106
|
+
revokedAt?: string;
|
|
107
|
+
delivery?: ShareInvitationDelivery;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export interface ShareParticipant {
|
|
111
|
+
participantId: string;
|
|
112
|
+
account: ShareAccount;
|
|
113
|
+
invitationId: string;
|
|
114
|
+
role: ShareRole;
|
|
115
|
+
roleCeiling: ShareRole;
|
|
116
|
+
credentials: number;
|
|
117
|
+
joined: boolean;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export type ShareAuditEventType =
|
|
121
|
+
| 'auth-failed'
|
|
122
|
+
| 'auth-started'
|
|
123
|
+
| 'invite-redeemed'
|
|
124
|
+
| 'capability-denied'
|
|
125
|
+
| 'role-changed'
|
|
126
|
+
| 'participant-kicked'
|
|
127
|
+
| 'invitation-revoked'
|
|
128
|
+
| 'terminal-invoked';
|
|
129
|
+
|
|
130
|
+
export interface ShareAuditEvent {
|
|
131
|
+
at: string;
|
|
132
|
+
type: ShareAuditEventType;
|
|
133
|
+
invitationId?: string;
|
|
134
|
+
participantId?: string;
|
|
135
|
+
accountId?: string;
|
|
136
|
+
capability?: ShareCapability;
|
|
137
|
+
/** The role a `role-changed` event settled on. */
|
|
138
|
+
role?: ShareRole;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** The `/__editor/share-control/status` payload. */
|
|
142
|
+
export interface ShareStatus {
|
|
143
|
+
active: boolean;
|
|
144
|
+
sessionId: string;
|
|
145
|
+
projectRoot: string;
|
|
146
|
+
publicUrl?: string;
|
|
147
|
+
tunnelProvider?: ShareTunnelProvider;
|
|
148
|
+
tunnel: ShareTunnelState;
|
|
149
|
+
health?: ShareTunnelHealth;
|
|
150
|
+
invitations: readonly ShareInvitationRecord[];
|
|
151
|
+
participants: readonly ShareParticipant[];
|
|
152
|
+
audit: readonly ShareAuditEvent[];
|
|
153
|
+
problem?: string;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** The `/__editor/share-control/invitations` reply. */
|
|
157
|
+
export interface CreatedShareInvitation {
|
|
158
|
+
invitation: ShareInvitationRecord;
|
|
159
|
+
url: string;
|
|
160
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The two viewport tabs — the editor's two MODES, named for what the user is
|
|
3
3
|
* doing rather than for what happens to be mounted (ARCHITECTURE-CORE
|
|
4
|
-
* §Vocabulary, decided 2026-08-02).
|
|
5
|
-
* `'scene'`
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* §Vocabulary, decided 2026-08-02). Deliberately not `'scene' | 'game'`:
|
|
5
|
+
* `'scene'` names a document format (a three root is TSX, and
|
|
6
|
+
* asset/story/tool documents live on that tab too), and `'game'`
|
|
7
|
+
* names the mounted artifact rather than the mode. Not to be confused with the
|
|
8
8
|
* PLAY TRANSPORT (the five stable controls) — this is which tab is showing.
|
|
9
9
|
*/
|
|
10
10
|
export type ViewportTab = 'edit' | 'play';
|
|
@@ -104,6 +104,9 @@ export interface GameCapture {
|
|
|
104
104
|
export interface GameplayRecordingOptions {
|
|
105
105
|
/** Requested real-time capture cadence. Defaults to 30; range 1–60. */
|
|
106
106
|
fps?: number;
|
|
107
|
+
/** Names the file under `.vgai/recordings/`. Absent takes the rotating
|
|
108
|
+
* `play-latest.webm`, which the next play overwrites. */
|
|
109
|
+
name?: string | null;
|
|
107
110
|
}
|
|
108
111
|
|
|
109
112
|
/** Facts fixed when a gameplay recording starts. */
|
|
@@ -115,15 +118,34 @@ export interface GameplayRecordingStarted {
|
|
|
115
118
|
fps: number;
|
|
116
119
|
audio: boolean;
|
|
117
120
|
layers: { canvases: number; domOverlays: number };
|
|
121
|
+
/** Where the bytes are landing — known at START, so a caller can say where
|
|
122
|
+
* the evidence for a run still in progress will be. */
|
|
123
|
+
path: string;
|
|
124
|
+
/** True for the rotating unnamed clip; false for a `--record <name>` keepsake. */
|
|
125
|
+
rotates: boolean;
|
|
126
|
+
/** This run's `logs/play-*.jsonl`, whose event timestamps map to clip offsets. */
|
|
127
|
+
logFile: string | null;
|
|
118
128
|
}
|
|
119
129
|
|
|
120
130
|
/** Final browser recording. Media chunks stream directly to the project while
|
|
121
131
|
* recording, so the command result stays small even for a long playthrough. */
|
|
122
132
|
export interface GameplayRecordingCapture extends GameplayRecordingStarted {
|
|
123
|
-
path: string;
|
|
124
133
|
durationMs: number;
|
|
125
134
|
droppedFrames: number;
|
|
126
135
|
frameErrors: number;
|
|
136
|
+
/** The cadence ACHIEVED (frames over wall duration), which diverges from the
|
|
137
|
+
* requested `fps` on a throttled hidden tab. Read this before reasoning
|
|
138
|
+
* about a clip's timing. */
|
|
139
|
+
effectiveFps: number;
|
|
140
|
+
/** True when the tab was hidden for any part of the recording. */
|
|
141
|
+
hidden: boolean;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** What `play` reports back once the run is up and recording. */
|
|
145
|
+
export interface PlayStarted {
|
|
146
|
+
/** Absent only when the recorder could not start; play is up either way and
|
|
147
|
+
* the editor console carries the reason. */
|
|
148
|
+
recording?: GameplayRecordingStarted;
|
|
127
149
|
}
|
|
128
150
|
|
|
129
151
|
export type AssetPreviewView = 'front' | 'right' | 'top' | 'perspective';
|
|
@@ -241,32 +263,60 @@ export interface AssetCompareCapture {
|
|
|
241
263
|
}
|
|
242
264
|
|
|
243
265
|
/**
|
|
266
|
+
* THE shot-set contract. This block is the ONE declaration of it.
|
|
267
|
+
*
|
|
244
268
|
* A project-defined labeled shot set (`vgai screenshot <target> --shots <set>`).
|
|
245
269
|
* The DEFINITION is project data: a registered project tool named
|
|
246
270
|
* `project.<set>.previewShots` returns it (installed capabilities register
|
|
247
|
-
* theirs —
|
|
248
|
-
* and the editor's generic capture engine renders
|
|
249
|
-
* and skeleton-anchored zoom crops, optionally under
|
|
250
|
-
* a disposable snapshot
|
|
271
|
+
* theirs — the bird, humanoid and walking-castle capabilities each contribute
|
|
272
|
+
* their canonical verify set), and the editor's generic capture engine renders
|
|
273
|
+
* it — turntable yaw angles and skeleton-anchored zoom crops, optionally under
|
|
274
|
+
* a named pose applied to a disposable snapshot. Every shot is labeled so a
|
|
275
|
+
* flat directory of PNGs is self-describing without a manifest file.
|
|
276
|
+
*
|
|
277
|
+
* The contract lives HERE, in the SDK, because it crosses the editor relay:
|
|
278
|
+
* the capability tool that authors a set, the CLI that ships it across, and
|
|
279
|
+
* `packages/editor/src/asset-preview.ts`'s capture engine that renders it are
|
|
280
|
+
* three different programs. Each of those used to declare its own copy — five
|
|
281
|
+
* declarations in total — and the copies had already drifted on what a pose
|
|
282
|
+
* step's `radians` is measured FROM. Every side now type-checks against this
|
|
283
|
+
* one: the capture engine annotates its parser's return with
|
|
284
|
+
* `AssetPreviewShotSetDefinition`, and each capability tool annotates its
|
|
285
|
+
* exported set with it, so a Zod schema that stops matching this shape is a
|
|
286
|
+
* compile error rather than a runtime surprise at the relay.
|
|
251
287
|
*/
|
|
252
288
|
export interface ShotSetPoseRotation {
|
|
253
289
|
bone: string;
|
|
254
290
|
axis: 'x' | 'y' | 'z';
|
|
291
|
+
/**
|
|
292
|
+
* A DELTA about the bone's own local axis, in radians, relative to the
|
|
293
|
+
* loaded GLB's baked rest pose — NOT an absolute local rotation.
|
|
294
|
+
*
|
|
295
|
+
* That is what the capture engine does with it: `bone.rotateX/Y/Z(radians)`,
|
|
296
|
+
* which composes onto the bone's existing local quaternion. The consequence
|
|
297
|
+
* to watch for is that "rest pose" means whatever the GLB baked, not
|
|
298
|
+
* identity — a rig baked mid-gait needs `pose(t) - pose(0)` here, while a
|
|
299
|
+
* rig baked at identity can pass its absolute angle unchanged. Getting that
|
|
300
|
+
* backwards silently double-applies the baked pose.
|
|
301
|
+
*/
|
|
255
302
|
radians: number;
|
|
256
303
|
}
|
|
257
304
|
|
|
258
305
|
/** The other half of a pose: a named morph target driven to an influence.
|
|
259
|
-
*
|
|
260
|
-
*
|
|
261
|
-
*
|
|
306
|
+
* Morphs are how a rig expresses what a joint cannot — an eyelid sliding over
|
|
307
|
+
* an eyeball, a brow band tilting — so a set that poses a FACE needs both
|
|
308
|
+
* halves or it can only ever show the jaw. A rig whose meshes carry no such
|
|
309
|
+
* morph is posed by the rotations alone: the same degrade-don't-throw rule
|
|
310
|
+
* the rotations follow for a missing joint. */
|
|
262
311
|
export interface ShotSetPoseMorph {
|
|
263
312
|
morph: string;
|
|
264
313
|
influence: number;
|
|
265
314
|
}
|
|
266
315
|
|
|
267
|
-
/** One step of a named pose. A
|
|
268
|
-
*
|
|
269
|
-
*
|
|
316
|
+
/** One step of a named pose. A flat union rather than two parallel lists: a
|
|
317
|
+
* single expression is normally one rotation AND one morph (a jaw ROTATION
|
|
318
|
+
* plus a brow MORPH), so keeping them in one ordered list means a definition
|
|
319
|
+
* never has to zip them. */
|
|
270
320
|
export type ShotSetPoseStep = ShotSetPoseRotation | ShotSetPoseMorph;
|
|
271
321
|
|
|
272
322
|
export type ShotSetShot =
|
|
@@ -276,12 +326,19 @@ export type ShotSetShot =
|
|
|
276
326
|
view: 'bone-zoom';
|
|
277
327
|
bones: string[];
|
|
278
328
|
spanFraction: number;
|
|
279
|
-
/**
|
|
280
|
-
*
|
|
281
|
-
*
|
|
282
|
-
*
|
|
283
|
-
*
|
|
284
|
-
*
|
|
329
|
+
/**
|
|
330
|
+
* The angle the crop is taken FROM, in the same convention and units as
|
|
331
|
+
* a turntable shot's `yaw` (radians; 0 is the front camera, -PI/2 the
|
|
332
|
+
* subject's left, +PI/2 its right). Omitted means 0 — the front-camera
|
|
333
|
+
* framing every bone-zoom shot had before this field existed, so an
|
|
334
|
+
* existing shot set renders unchanged.
|
|
335
|
+
*
|
|
336
|
+
* It exists because the front camera is not a general answer for a long
|
|
337
|
+
* subject: on an 8 m quadruped a crop anchored on the tail root
|
|
338
|
+
* photographs the hind legs standing between the camera and the tail. A
|
|
339
|
+
* junction whose axis runs down the body's length is inspectable only
|
|
340
|
+
* from the side.
|
|
341
|
+
*/
|
|
285
342
|
yaw?: number | undefined;
|
|
286
343
|
pose?: string | undefined;
|
|
287
344
|
};
|
|
@@ -289,8 +346,8 @@ export type ShotSetShot =
|
|
|
289
346
|
export interface AssetPreviewShotSetDefinition {
|
|
290
347
|
/** The set's name (the CLI's `--shots <name>`), echoed in error messages. */
|
|
291
348
|
name: string;
|
|
292
|
-
/** Joints that must exist on the loaded GLB's OWN skeleton
|
|
293
|
-
* plus a loud failure naming the missing joints
|
|
349
|
+
/** Joints that must exist on the loaded GLB's OWN skeleton — zoom anchors
|
|
350
|
+
* plus a loud failure naming the missing joints (never a silent
|
|
294
351
|
* bounding-box fallback for a set that promised skeleton anchoring). */
|
|
295
352
|
requiredBones?: string[];
|
|
296
353
|
/** Optional clause appended to rig-requirement errors,
|
|
@@ -447,12 +504,12 @@ export interface EditorState {
|
|
|
447
504
|
*
|
|
448
505
|
* `census` is the tab's RESOURCE PROFILE, sampled by the page every five
|
|
449
506
|
* seconds and carried on the heartbeat: what a browser-level renderer death
|
|
450
|
-
* would otherwise leave unexplained.
|
|
451
|
-
*
|
|
452
|
-
*
|
|
453
|
-
*
|
|
454
|
-
*
|
|
455
|
-
* stale the profile is — a hidden tab is not sampled.
|
|
507
|
+
* would otherwise leave unexplained. It is `@vgai/sdk/tab-census`'s
|
|
508
|
+
* {@link RecordedTabCensus} — the census MINUS the `mountEpochs` guarantee,
|
|
509
|
+
* because this is a response and the server that answered it may be older
|
|
510
|
+
* than that field; every other absence (`heapUsedMB` off Chromium, renderer
|
|
511
|
+
* counts with no mounted adapter) is documented on the type itself.
|
|
512
|
+
* `censusAgeMs` says how stale the profile is — a hidden tab is not sampled.
|
|
456
513
|
*/
|
|
457
514
|
tabs?: Array<{
|
|
458
515
|
tabId8: string;
|
|
@@ -470,16 +527,7 @@ export interface EditorState {
|
|
|
470
527
|
unresponsive: boolean;
|
|
471
528
|
commandListener?: 'ready' | 'not attached' | (string & {});
|
|
472
529
|
pageErrors?: string[];
|
|
473
|
-
census?:
|
|
474
|
-
heapUsedMB: number | null;
|
|
475
|
-
heapLimitMB: number | null;
|
|
476
|
-
mountEpochs?: number;
|
|
477
|
-
canvases: number;
|
|
478
|
-
canvasMB: number;
|
|
479
|
-
textures?: number;
|
|
480
|
-
geometries?: number;
|
|
481
|
-
programs?: number;
|
|
482
|
-
} | null;
|
|
530
|
+
census?: RecordedTabCensus | null;
|
|
483
531
|
censusAgeMs?: number | null;
|
|
484
532
|
}>;
|
|
485
533
|
/**
|
|
@@ -654,8 +702,17 @@ export type AnimationCaptureAction = 'start' | 'stop' | 'review' | 'commit' | 'd
|
|
|
654
702
|
export type TransformMode = 'translate' | 'rotate' | 'scale';
|
|
655
703
|
export type TransformSpace = 'world' | 'local';
|
|
656
704
|
|
|
657
|
-
/**
|
|
658
|
-
*
|
|
705
|
+
/**
|
|
706
|
+
* Stable editor-owned workspace documents that may appear in a shareable
|
|
707
|
+
* view. One runtime list owns both URL parsing and the public id type.
|
|
708
|
+
*
|
|
709
|
+
* This SDK cannot import the editor — the dependency runs the other way — so
|
|
710
|
+
* nothing links this list to the documents the editor actually registers, and
|
|
711
|
+
* an id added there is silently unaddressable here (a view carrying it
|
|
712
|
+
* round-trips to nothing). The link is asserted from the side that CAN see
|
|
713
|
+
* both: `packages/editor/test/editor-view-address-space.test.ts`. Adding a
|
|
714
|
+
* document id means adding it here too.
|
|
715
|
+
*/
|
|
659
716
|
export const EDITOR_VIEW_WORKSPACE_DOCUMENT_IDS = [
|
|
660
717
|
'workspace:scene',
|
|
661
718
|
'workspace:canvas-scene',
|
|
@@ -664,6 +721,7 @@ export const EDITOR_VIEW_WORKSPACE_DOCUMENT_IDS = [
|
|
|
664
721
|
'workspace:2d-components',
|
|
665
722
|
'workspace:ui-components',
|
|
666
723
|
'workspace:build-profiles',
|
|
724
|
+
'workspace:asset-budget',
|
|
667
725
|
'account',
|
|
668
726
|
'project-tools',
|
|
669
727
|
] as const;
|
|
@@ -677,13 +735,28 @@ export type EditorViewWorkspaceDocumentId = (typeof EDITOR_VIEW_WORKSPACE_DOCUME
|
|
|
677
735
|
* reason: a separate hand-written union and parser allowlist drift silently
|
|
678
736
|
* (`behavior` was in the union and missing from the allowlist, so a view
|
|
679
737
|
* carrying it round-tripped to nothing).
|
|
738
|
+
*
|
|
739
|
+
* This must equal the editor's live `BUILT_IN_WORKSPACE_UTILITIES`
|
|
740
|
+
* (`packages/editor/src/workspace-core-utilities.ts`). It had drifted to five
|
|
741
|
+
* of thirteen — every id from `generations` onward was unaddressable in a
|
|
742
|
+
* shared view. As above, the SDK cannot import the editor to derive this, so
|
|
743
|
+
* `packages/editor/test/editor-view-address-space.test.ts` asserts the
|
|
744
|
+
* equality from the side that sees both.
|
|
680
745
|
*/
|
|
681
746
|
export const EDITOR_VIEW_BUILT_IN_UTILITY_IDS = [
|
|
682
|
-
'profiler',
|
|
683
|
-
'console',
|
|
684
747
|
'animation',
|
|
685
748
|
'behavior',
|
|
749
|
+
'generations',
|
|
750
|
+
'console',
|
|
751
|
+
'profiler',
|
|
752
|
+
'state-watch',
|
|
686
753
|
'light-explorer',
|
|
754
|
+
'network',
|
|
755
|
+
'audio',
|
|
756
|
+
'build-output',
|
|
757
|
+
'story-actions',
|
|
758
|
+
'story-interactions',
|
|
759
|
+
'story-accessibility',
|
|
687
760
|
] as const;
|
|
688
761
|
|
|
689
762
|
export type EditorViewBuiltInUtilityId = (typeof EDITOR_VIEW_BUILT_IN_UTILITY_IDS)[number];
|
|
@@ -801,6 +874,7 @@ export type ProjectToolOutcome =
|
|
|
801
874
|
};
|
|
802
875
|
|
|
803
876
|
import type { GenerationJob } from '@vgai/sdk/generations';
|
|
877
|
+
import type { RecordedTabCensus } from '@vgai/sdk/tab-census';
|
|
804
878
|
|
|
805
879
|
export type {
|
|
806
880
|
ProjectToolCatalog,
|