@laplace.live/persona-sdk 0.1.1 → 0.3.0
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 +31 -2
- package/dist/client.d.ts +33 -2
- package/dist/client.js +76 -9
- package/dist/events.d.ts +14 -1
- package/dist/events.js +3 -0
- package/dist/methods.d.ts +50 -0
- package/dist/protocol.d.ts +4 -0
- package/dist/protocol.js +4 -0
- package/dist/schemas.d.ts +13 -4
- package/dist/schemas.js +8 -4
- package/dist/types.d.ts +183 -58
- package/dist/types.js +6 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -43,11 +43,40 @@ import WebSocket from "ws";
|
|
|
43
43
|
const persona = new PersonaClient({
|
|
44
44
|
token,
|
|
45
45
|
auth: "header",
|
|
46
|
-
createWebSocket: (url, headers) =>
|
|
47
|
-
new WebSocket(url, { headers }) as unknown as globalThis.WebSocket,
|
|
46
|
+
createWebSocket: (url, headers) => new WebSocket(url, { headers }),
|
|
48
47
|
});
|
|
49
48
|
```
|
|
50
49
|
|
|
50
|
+
## Identifying your app
|
|
51
|
+
|
|
52
|
+
Pass `clientInfo` so your app shows up by name under **Connected Clients** in Persona's
|
|
53
|
+
settings (optional — unidentified clients work the same). The SDK re-declares it on every
|
|
54
|
+
reconnect; it is self-declared and display-only, never authorization:
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
const persona = new PersonaClient({
|
|
58
|
+
token,
|
|
59
|
+
clientInfo: { name: "My Overlay", version: "1.2.0", developer: "You" },
|
|
60
|
+
});
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Two server close codes are terminal and stop the reconnect loop: `CLOSE_KEY_REVOKED` (4001,
|
|
64
|
+
the key was revoked) and `CLOSE_FORCE_DISCONNECTED` (4002, the user disconnected the session
|
|
65
|
+
in Persona). The client reports them via `onWarning` and goes `closed`.
|
|
66
|
+
|
|
51
67
|
Treat the token like a password: it grants control of the app, including loading registered
|
|
52
68
|
models and web overlays. The server listens on loopback unless LAN access is enabled in
|
|
53
69
|
Persona's settings.
|
|
70
|
+
|
|
71
|
+
## Registry and catalog metadata
|
|
72
|
+
|
|
73
|
+
`model.list` / `asset.list` return refs with provenance and attribution: assets carry
|
|
74
|
+
`origin` (`'bundled'` shipped with the app, `'user'` registered by the user; models carry
|
|
75
|
+
the same as `kind`), and both may carry optional `author` / `url` credits sourced from the
|
|
76
|
+
app's content manifest.
|
|
77
|
+
|
|
78
|
+
`CatalogItem` is the transport-agnostic content-metadata shape behind pickers: a stable
|
|
79
|
+
`id` (scene refs key off it), an `InventoryKind`, display `name`, and optional
|
|
80
|
+
`thumbnailUrl`, `version`, `author`, `url`, and `download` (payload location + integrity
|
|
81
|
+
for items not yet on disk — Persona will use this to ship bundled content as
|
|
82
|
+
metadata-only rows downloaded from its CDN on demand).
|
package/dist/client.d.ts
CHANGED
|
@@ -1,7 +1,25 @@
|
|
|
1
1
|
import type { EventData, EventName } from './events.ts';
|
|
2
|
-
import type { MethodName, MethodRequest, MethodResponse } from './methods.ts';
|
|
2
|
+
import type { MethodName, MethodRequest, MethodResponse, SessionIdentifyRequest } from './methods.ts';
|
|
3
3
|
import type { InjectTarget } from './types.ts';
|
|
4
4
|
export type PersonaClientState = 'closed' | 'connecting' | 'open' | 'reconnecting';
|
|
5
|
+
/**
|
|
6
|
+
* The subset of the WebSocket API the client uses. The global `WebSocket`
|
|
7
|
+
* (browsers, Node ≥22, Bun) and the `ws` package's client both satisfy it
|
|
8
|
+
* structurally, so `createWebSocket` implementations need no casts.
|
|
9
|
+
*/
|
|
10
|
+
export interface WebSocketLike {
|
|
11
|
+
readonly readyState: number;
|
|
12
|
+
send(data: string): void;
|
|
13
|
+
close(code?: number, reason?: string): void;
|
|
14
|
+
addEventListener(type: 'message', listener: (event: {
|
|
15
|
+
data?: unknown;
|
|
16
|
+
}) => void): void;
|
|
17
|
+
addEventListener(type: 'close', listener: (event?: {
|
|
18
|
+
code?: number;
|
|
19
|
+
reason?: string;
|
|
20
|
+
}) => void): void;
|
|
21
|
+
addEventListener(type: 'error', listener: () => void): void;
|
|
22
|
+
}
|
|
5
23
|
export interface PersonaClientOptions {
|
|
6
24
|
/** An API key created in Persona's settings. */
|
|
7
25
|
token: string;
|
|
@@ -18,8 +36,13 @@ export interface PersonaClientOptions {
|
|
|
18
36
|
reconnectDelayMs?: number;
|
|
19
37
|
reconnectDelayMaxMs?: number;
|
|
20
38
|
requestTimeoutMs?: number;
|
|
39
|
+
/**
|
|
40
|
+
* Identifies your app in Persona's settings (Connected Clients). Declarative and
|
|
41
|
+
* display-only, never authorization; sent automatically on every connection.
|
|
42
|
+
*/
|
|
43
|
+
clientInfo?: SessionIdentifyRequest;
|
|
21
44
|
/** Custom socket factory — for `ws` with headers, or tests. `headers` is set only for `auth: 'header'`. */
|
|
22
|
-
createWebSocket?: (url: string, headers: Record<string, string> | undefined) =>
|
|
45
|
+
createWebSocket?: (url: string, headers: Record<string, string> | undefined) => WebSocketLike;
|
|
23
46
|
onStateChange?: (state: PersonaClientState) => void;
|
|
24
47
|
/** Non-fatal notices (protocol version mismatch). Default `console.warn`. */
|
|
25
48
|
onWarning?: (message: string) => void;
|
|
@@ -84,6 +107,14 @@ export declare class PersonaClient {
|
|
|
84
107
|
private buildUrl;
|
|
85
108
|
private open;
|
|
86
109
|
private onHello;
|
|
110
|
+
/**
|
|
111
|
+
* Subscribe, falling back to one request per event when the *server* refuses the batch:
|
|
112
|
+
* servers before v0.15 reject the whole array over a single unknown event name.
|
|
113
|
+
* A dropped socket rejects too, and is not a refusal — the next open re-subscribes.
|
|
114
|
+
*/
|
|
115
|
+
private subscribe;
|
|
116
|
+
private subscribeIndividually;
|
|
117
|
+
private warnUnsupported;
|
|
87
118
|
private onServerMessage;
|
|
88
119
|
private failPending;
|
|
89
120
|
private scheduleReconnect;
|
package/dist/client.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { parseServerMessage } from "./envelope.js";
|
|
2
2
|
import { PersonaApiError } from "./errors.js";
|
|
3
|
-
import { DEFAULT_API_PORT, INJECT_HEARTBEAT_MS, injectTargetKey, PROTOCOL_VERSION } from "./protocol.js";
|
|
3
|
+
import { CLOSE_FORCE_DISCONNECTED, CLOSE_KEY_REVOKED, DEFAULT_API_PORT, INJECT_HEARTBEAT_MS, injectTargetKey, PROTOCOL_VERSION, } from "./protocol.js";
|
|
4
4
|
const DEFAULTS = {
|
|
5
5
|
url: `ws://127.0.0.1:${String(DEFAULT_API_PORT)}`,
|
|
6
6
|
reconnectDelayMs: 500,
|
|
@@ -89,9 +89,8 @@ export class PersonaClient {
|
|
|
89
89
|
this.listeners.set(event, set);
|
|
90
90
|
}
|
|
91
91
|
set.add(cb);
|
|
92
|
-
if (isNew && this.state === 'open')
|
|
93
|
-
|
|
94
|
-
}
|
|
92
|
+
if (isNew && this.state === 'open')
|
|
93
|
+
this.subscribe([event]);
|
|
95
94
|
return () => {
|
|
96
95
|
const s = this.listeners.get(event);
|
|
97
96
|
if (!s)
|
|
@@ -117,7 +116,10 @@ export class PersonaClient {
|
|
|
117
116
|
...(opts?.onError === undefined ? {} : { onError: opts.onError }),
|
|
118
117
|
};
|
|
119
118
|
this.leases.set(key, lease);
|
|
120
|
-
|
|
119
|
+
// Only while open — onHello resumes held leases, so a lease taken on a closed
|
|
120
|
+
// client must not start a referenced interval (it would pin a Node process).
|
|
121
|
+
if (this.state === 'open')
|
|
122
|
+
this.startHeartbeat();
|
|
121
123
|
this.queueFlush();
|
|
122
124
|
return {
|
|
123
125
|
set: (v) => {
|
|
@@ -196,7 +198,7 @@ export class PersonaClient {
|
|
|
196
198
|
}
|
|
197
199
|
this.onServerMessage(msg);
|
|
198
200
|
});
|
|
199
|
-
ws.addEventListener('close',
|
|
201
|
+
ws.addEventListener('close', ev => {
|
|
200
202
|
if (this.ws !== ws)
|
|
201
203
|
return; // an intentional close already moved on
|
|
202
204
|
this.ws = null;
|
|
@@ -207,6 +209,19 @@ export class PersonaClient {
|
|
|
207
209
|
this.setState('closed');
|
|
208
210
|
return;
|
|
209
211
|
}
|
|
212
|
+
const code = ev?.code;
|
|
213
|
+
if (code === CLOSE_KEY_REVOKED || code === CLOSE_FORCE_DISCONNECTED) {
|
|
214
|
+
// Terminal by protocol: redialing would either fail auth forever or undo the user's action.
|
|
215
|
+
if (this.reconnectTimer !== null)
|
|
216
|
+
clearTimeout(this.reconnectTimer);
|
|
217
|
+
this.reconnectTimer = null;
|
|
218
|
+
this.stopHeartbeat();
|
|
219
|
+
this.warn(code === CLOSE_KEY_REVOKED
|
|
220
|
+
? 'the server revoked this API key — create a new one in Persona and reconnect'
|
|
221
|
+
: 'the server disconnected this session — not reconnecting');
|
|
222
|
+
this.setState('closed');
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
210
225
|
if (!this.opts.reconnect || this.state === 'connecting') {
|
|
211
226
|
// Initial connect failed: report to the caller instead of retrying forever.
|
|
212
227
|
this.setState('closed');
|
|
@@ -225,12 +240,64 @@ export class PersonaClient {
|
|
|
225
240
|
this.warn(`protocol version mismatch: server speaks v${String(protocol)}, SDK speaks v${String(PROTOCOL_VERSION)}`);
|
|
226
241
|
}
|
|
227
242
|
this.reconnectAttempt = 0;
|
|
228
|
-
this.
|
|
243
|
+
const info = this.opts.clientInfo;
|
|
244
|
+
// Identity is session-scoped, so every (re)connect re-declares it. Swallow errors:
|
|
245
|
+
// pre-feature servers answer `unknown-method`, and a mid-flight drop is not news.
|
|
246
|
+
if (info)
|
|
247
|
+
void this.call('session.identify', info).catch(() => undefined);
|
|
229
248
|
const events = [...this.listeners.keys()];
|
|
249
|
+
// Before `open`, so a listener registered by an onStateChange handler does not
|
|
250
|
+
// race a second subscribe for the same event.
|
|
230
251
|
if (events.length > 0)
|
|
231
|
-
|
|
232
|
-
|
|
252
|
+
this.subscribe(events);
|
|
253
|
+
this.setState('open');
|
|
254
|
+
// Held leases resume here; restart the heartbeat that close()/a terminal close stopped.
|
|
255
|
+
if (this.leases.size > 0) {
|
|
256
|
+
this.startHeartbeat();
|
|
233
257
|
this.queueFlush();
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Subscribe, falling back to one request per event when the *server* refuses the batch:
|
|
262
|
+
* servers before v0.15 reject the whole array over a single unknown event name.
|
|
263
|
+
* A dropped socket rejects too, and is not a refusal — the next open re-subscribes.
|
|
264
|
+
*/
|
|
265
|
+
subscribe(events) {
|
|
266
|
+
void this.call('events.subscribe', { events })
|
|
267
|
+
.then(r => {
|
|
268
|
+
const missing = events.filter(e => !r.subscribed.includes(e));
|
|
269
|
+
if (missing.length > 0)
|
|
270
|
+
this.warnUnsupported(missing);
|
|
271
|
+
})
|
|
272
|
+
.catch((err) => {
|
|
273
|
+
if (!(err instanceof PersonaApiError))
|
|
274
|
+
return;
|
|
275
|
+
if (events.length === 1)
|
|
276
|
+
this.warnUnsupported(events);
|
|
277
|
+
else
|
|
278
|
+
this.subscribeIndividually(events);
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
subscribeIndividually(events) {
|
|
282
|
+
const refused = [];
|
|
283
|
+
void Promise.all(events.map(e => this.call('events.subscribe', { events: [e] })
|
|
284
|
+
.then(r => {
|
|
285
|
+
// A v0.15+ server answers per-event by omission, not rejection.
|
|
286
|
+
if (!r.subscribed.includes(e))
|
|
287
|
+
refused.push(e);
|
|
288
|
+
})
|
|
289
|
+
.catch((err) => {
|
|
290
|
+
if (err instanceof PersonaApiError)
|
|
291
|
+
refused.push(e);
|
|
292
|
+
}))).then(() => {
|
|
293
|
+
if (refused.length > 0)
|
|
294
|
+
this.warnUnsupported(refused);
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
warnUnsupported(events) {
|
|
298
|
+
const app = this.serverInfo?.app;
|
|
299
|
+
const server = app ? `${app.name} ${app.version}` : 'the connected Persona app';
|
|
300
|
+
this.warn(`${server} does not support these events: ${events.join(', ')} — update the desktop app to receive them`);
|
|
234
301
|
}
|
|
235
302
|
onServerMessage(msg) {
|
|
236
303
|
if (msg.kind === 'event') {
|
package/dist/events.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { HotkeyState, ModelFormat, PoseStatus, SceneState, Settings, TrackingStatus } from './types.ts';
|
|
1
|
+
import type { ExpressionPersistence, HotkeyState, ModelFormat, PoseStatus, SceneState, Settings, TrackingStatus } from './types.ts';
|
|
2
2
|
/** Every push event a session can subscribe to, with its payload. */
|
|
3
3
|
export interface EventMap {
|
|
4
4
|
/** Any scene mutation: create/delete/rename/activate/shortcut and persisted scene edits. */
|
|
@@ -35,6 +35,19 @@ export interface EventMap {
|
|
|
35
35
|
'scene.loading': {
|
|
36
36
|
loading: boolean;
|
|
37
37
|
};
|
|
38
|
+
/** The desktop's editing selection moved (any source: stage click, panel, or a plugin); null = cleared. */
|
|
39
|
+
'selection.changed': {
|
|
40
|
+
instanceId: string | null;
|
|
41
|
+
};
|
|
42
|
+
/** A model or asset was registered: re-run `model.list`/`asset.list` for that kind. */
|
|
43
|
+
'registry.changed': {
|
|
44
|
+
kind: 'model' | 'asset';
|
|
45
|
+
};
|
|
46
|
+
/** The per-model "remember expressions" flag changed. */
|
|
47
|
+
'expression.persistence': {
|
|
48
|
+
modelId: string;
|
|
49
|
+
persistence: ExpressionPersistence;
|
|
50
|
+
};
|
|
38
51
|
}
|
|
39
52
|
export type EventName = keyof EventMap;
|
|
40
53
|
export type EventData<E extends EventName = EventName> = EventMap[E];
|
package/dist/events.js
CHANGED
|
@@ -10,6 +10,9 @@ export const EVENT_NAMES = Object.keys({
|
|
|
10
10
|
'motion.started': true,
|
|
11
11
|
'motion.ended': true,
|
|
12
12
|
'scene.loading': true,
|
|
13
|
+
'selection.changed': true,
|
|
14
|
+
'registry.changed': true,
|
|
15
|
+
'expression.persistence': true,
|
|
13
16
|
});
|
|
14
17
|
export function isEventName(v) {
|
|
15
18
|
return typeof v === 'string' && EVENT_NAMES.includes(v);
|
package/dist/methods.d.ts
CHANGED
|
@@ -59,6 +59,11 @@ export interface InstanceAddRequest {
|
|
|
59
59
|
export interface InstanceAddResponse {
|
|
60
60
|
instanceId: string;
|
|
61
61
|
}
|
|
62
|
+
export interface InstanceSetModelRequest {
|
|
63
|
+
instanceId: string;
|
|
64
|
+
modelId: string;
|
|
65
|
+
}
|
|
66
|
+
export type InstanceSetModelResponse = EmptyResponse;
|
|
62
67
|
export interface InstanceRemoveRequest {
|
|
63
68
|
instanceId: string;
|
|
64
69
|
}
|
|
@@ -77,6 +82,15 @@ export interface InstanceSetPrimaryRequest {
|
|
|
77
82
|
instanceId: string;
|
|
78
83
|
}
|
|
79
84
|
export type InstanceSetPrimaryResponse = EmptyResponse;
|
|
85
|
+
export interface SelectionSetRequest {
|
|
86
|
+
/** Scene item (model or object) to select on the desktop; null clears the selection. */
|
|
87
|
+
instanceId: string | null;
|
|
88
|
+
}
|
|
89
|
+
export type SelectionSetResponse = EmptyResponse;
|
|
90
|
+
export type SelectionGetRequest = EmptyRequest;
|
|
91
|
+
export interface SelectionGetResponse {
|
|
92
|
+
instanceId: string | null;
|
|
93
|
+
}
|
|
80
94
|
export interface InstanceSetPlacementRequest {
|
|
81
95
|
instanceId?: string;
|
|
82
96
|
live2d?: Partial<ScreenPlacement>;
|
|
@@ -296,6 +310,19 @@ export type AppStatsRequest = EmptyRequest;
|
|
|
296
310
|
export interface AppStatsResponse {
|
|
297
311
|
fps: number | null;
|
|
298
312
|
}
|
|
313
|
+
/**
|
|
314
|
+
* Self-declared identity of the connecting app, shown in Persona's settings.
|
|
315
|
+
* Declarative and unverified — display/attribution only, never authorization.
|
|
316
|
+
*/
|
|
317
|
+
export interface SessionIdentifyRequest {
|
|
318
|
+
/** Display name of the connecting application. 1–64 chars. */
|
|
319
|
+
name: string;
|
|
320
|
+
/** Version of the connecting application. Up to 32 chars. */
|
|
321
|
+
version?: string;
|
|
322
|
+
/** Developer/vendor shown alongside the name. Up to 64 chars. */
|
|
323
|
+
developer?: string;
|
|
324
|
+
}
|
|
325
|
+
export type SessionIdentifyResponse = EmptyResponse;
|
|
299
326
|
export interface EventsSubscribeRequest {
|
|
300
327
|
events: EventName[];
|
|
301
328
|
}
|
|
@@ -372,6 +399,11 @@ export interface MethodMap {
|
|
|
372
399
|
request: InstanceAddRequest;
|
|
373
400
|
response: InstanceAddResponse;
|
|
374
401
|
};
|
|
402
|
+
/** Swap a model layer's model in place; placement, primary status, and identity carry over. */
|
|
403
|
+
'instance.setModel': {
|
|
404
|
+
request: InstanceSetModelRequest;
|
|
405
|
+
response: InstanceSetModelResponse;
|
|
406
|
+
};
|
|
375
407
|
/** Removes any scene item — model or object. */
|
|
376
408
|
'instance.remove': {
|
|
377
409
|
request: InstanceRemoveRequest;
|
|
@@ -389,6 +421,16 @@ export interface MethodMap {
|
|
|
389
421
|
request: InstanceSetPrimaryRequest;
|
|
390
422
|
response: InstanceSetPrimaryResponse;
|
|
391
423
|
};
|
|
424
|
+
/** Drives the desktop's editing selection (outline + Model tab); ephemeral, never persisted. */
|
|
425
|
+
'selection.set': {
|
|
426
|
+
request: SelectionSetRequest;
|
|
427
|
+
response: SelectionSetResponse;
|
|
428
|
+
};
|
|
429
|
+
/** Current editing selection; changes stream as `selection.changed`. */
|
|
430
|
+
'selection.get': {
|
|
431
|
+
request: SelectionGetRequest;
|
|
432
|
+
response: SelectionGetResponse;
|
|
433
|
+
};
|
|
392
434
|
'instance.setPlacement': {
|
|
393
435
|
request: InstanceSetPlacementRequest;
|
|
394
436
|
response: InstanceSetPlacementResponse;
|
|
@@ -553,6 +595,14 @@ export interface MethodMap {
|
|
|
553
595
|
request: AppStatsRequest;
|
|
554
596
|
response: AppStatsResponse;
|
|
555
597
|
};
|
|
598
|
+
/**
|
|
599
|
+
* Declare who this client is (display only, never authorization). Session-scoped:
|
|
600
|
+
* re-send on every connection — the SDK's `clientInfo` option does it automatically.
|
|
601
|
+
*/
|
|
602
|
+
'session.identify': {
|
|
603
|
+
request: SessionIdentifyRequest;
|
|
604
|
+
response: SessionIdentifyResponse;
|
|
605
|
+
};
|
|
556
606
|
'events.subscribe': {
|
|
557
607
|
request: EventsSubscribeRequest;
|
|
558
608
|
response: EventsSubscribeResponse;
|
package/dist/protocol.d.ts
CHANGED
|
@@ -3,6 +3,10 @@ import type { InjectTarget } from './types.ts';
|
|
|
3
3
|
export declare const PROTOCOL_VERSION = 1;
|
|
4
4
|
/** Default port the Persona API server listens on (user-configurable in the app). */
|
|
5
5
|
export declare const DEFAULT_API_PORT = 25034;
|
|
6
|
+
/** Server close code: the session's API key was revoked. Terminal — the client must not redial. */
|
|
7
|
+
export declare const CLOSE_KEY_REVOKED = 4001;
|
|
8
|
+
/** Server close code: the user disconnected this session from Persona's settings. Terminal — the client must not redial. */
|
|
9
|
+
export declare const CLOSE_FORCE_DISCONNECTED = 4002;
|
|
6
10
|
/** An injected parameter reverts this long after its last write — the lease, not a setting. */
|
|
7
11
|
export declare const INJECT_LEASE_TTL_MS = 1000;
|
|
8
12
|
/** How often {@link PersonaClient.driveParameter} re-sends held leases to keep them alive. */
|
package/dist/protocol.js
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
export const PROTOCOL_VERSION = 1;
|
|
3
3
|
/** Default port the Persona API server listens on (user-configurable in the app). */
|
|
4
4
|
export const DEFAULT_API_PORT = 25034;
|
|
5
|
+
/** Server close code: the session's API key was revoked. Terminal — the client must not redial. */
|
|
6
|
+
export const CLOSE_KEY_REVOKED = 4001;
|
|
7
|
+
/** Server close code: the user disconnected this session from Persona's settings. Terminal — the client must not redial. */
|
|
8
|
+
export const CLOSE_FORCE_DISCONNECTED = 4002;
|
|
5
9
|
/** An injected parameter reverts this long after its last write — the lease, not a setting. */
|
|
6
10
|
export const INJECT_LEASE_TTL_MS = 1000;
|
|
7
11
|
/** How often {@link PersonaClient.driveParameter} re-sends held leases to keep them alive. */
|
package/dist/schemas.d.ts
CHANGED
|
@@ -34,9 +34,9 @@ export declare const SettingsPatchSchema: z.ZodObject<{
|
|
|
34
34
|
fpsLimit: z.ZodOptional<z.ZodNumber>;
|
|
35
35
|
selectionOutline: z.ZodOptional<z.ZodBoolean>;
|
|
36
36
|
effectsQuality: z.ZodOptional<z.ZodEnum<{
|
|
37
|
-
high: "high";
|
|
38
|
-
medium: "medium";
|
|
39
37
|
low: "low";
|
|
38
|
+
medium: "medium";
|
|
39
|
+
high: "high";
|
|
40
40
|
}>>;
|
|
41
41
|
}, z.core.$strip>>;
|
|
42
42
|
}, z.core.$strip>;
|
|
@@ -64,6 +64,10 @@ export declare const requestSchemas: {
|
|
|
64
64
|
'instance.add': z.ZodObject<{
|
|
65
65
|
modelId: z.ZodString;
|
|
66
66
|
}, z.core.$strip>;
|
|
67
|
+
'instance.setModel': z.ZodObject<{
|
|
68
|
+
instanceId: z.ZodString;
|
|
69
|
+
modelId: z.ZodString;
|
|
70
|
+
}, z.core.$strip>;
|
|
67
71
|
'object.add': z.ZodObject<{
|
|
68
72
|
content: z.ZodCustom<ObjectContent, ObjectContent>;
|
|
69
73
|
name: z.ZodOptional<z.ZodString>;
|
|
@@ -116,9 +120,9 @@ export declare const requestSchemas: {
|
|
|
116
120
|
fpsLimit: z.ZodOptional<z.ZodNumber>;
|
|
117
121
|
selectionOutline: z.ZodOptional<z.ZodBoolean>;
|
|
118
122
|
effectsQuality: z.ZodOptional<z.ZodEnum<{
|
|
119
|
-
high: "high";
|
|
120
|
-
medium: "medium";
|
|
121
123
|
low: "low";
|
|
124
|
+
medium: "medium";
|
|
125
|
+
high: "high";
|
|
122
126
|
}>>;
|
|
123
127
|
}, z.core.$strip>>;
|
|
124
128
|
}, z.core.$strip>;
|
|
@@ -144,6 +148,11 @@ export declare const requestSchemas: {
|
|
|
144
148
|
'pose.setPort': z.ZodObject<{
|
|
145
149
|
port: z.ZodNumber;
|
|
146
150
|
}, z.core.$strip>;
|
|
151
|
+
'session.identify': z.ZodObject<{
|
|
152
|
+
name: z.ZodString;
|
|
153
|
+
version: z.ZodOptional<z.ZodString>;
|
|
154
|
+
developer: z.ZodOptional<z.ZodString>;
|
|
155
|
+
}, z.core.$strip>;
|
|
147
156
|
'events.subscribe': z.ZodObject<{
|
|
148
157
|
events: z.ZodArray<z.ZodCustom<keyof import("./events.ts").EventMap, keyof import("./events.ts").EventMap>>;
|
|
149
158
|
}, z.core.$strip>;
|
package/dist/schemas.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import * as z from 'zod';
|
|
2
|
-
import { isEventName } from "./events.js";
|
|
3
2
|
// Runtime validation for the request side of the wire. Schemas exist for the
|
|
4
3
|
// methods whose params the app's main process consumes directly; methods without
|
|
5
4
|
// one are stage-owned — the renderer validates and heals them (same rules as the
|
|
@@ -31,8 +30,7 @@ export const SettingsPatchSchema = z.object({
|
|
|
31
30
|
})
|
|
32
31
|
.optional(),
|
|
33
32
|
});
|
|
34
|
-
|
|
35
|
-
/** Tolerates unknown names (version skew); the server ignores what it cannot map. */
|
|
33
|
+
/** Tolerates unknown names for version skew; the server filters them and reports what took. */
|
|
36
34
|
const lenientEventNameSchema = z.custom(v => typeof v === 'string', 'event names must be strings');
|
|
37
35
|
export const requestSchemas = {
|
|
38
36
|
'scene.get': z.object({ sceneId: nonEmpty.optional() }),
|
|
@@ -42,6 +40,7 @@ export const requestSchemas = {
|
|
|
42
40
|
'scene.delete': z.object({ sceneId: nonEmpty }),
|
|
43
41
|
'scene.setShortcut': z.object({ sceneId: nonEmpty, accelerator: nonEmpty.nullable() }),
|
|
44
42
|
'instance.add': z.object({ modelId: nonEmpty }),
|
|
43
|
+
'instance.setModel': z.object({ instanceId: nonEmpty, modelId: nonEmpty }),
|
|
45
44
|
'object.add': z.object({
|
|
46
45
|
content: z.custom(isRecord, 'content must be an object'),
|
|
47
46
|
name: nonEmpty.optional(),
|
|
@@ -66,7 +65,12 @@ export const requestSchemas = {
|
|
|
66
65
|
'pose.setEnabled': z.object({ enabled: z.boolean() }),
|
|
67
66
|
'pose.setSource': z.object({ source: z.enum(['vmc']) }),
|
|
68
67
|
'pose.setPort': z.object({ port: z.number().int().min(1).max(65535) }),
|
|
69
|
-
'
|
|
68
|
+
'session.identify': z.object({
|
|
69
|
+
name: z.string().trim().min(1).max(64),
|
|
70
|
+
version: z.string().max(32).optional(),
|
|
71
|
+
developer: z.string().max(64).optional(),
|
|
72
|
+
}),
|
|
73
|
+
'events.subscribe': z.object({ events: z.array(lenientEventNameSchema).min(1) }),
|
|
70
74
|
'events.unsubscribe': z.object({ events: z.array(lenientEventNameSchema).optional() }),
|
|
71
75
|
'param.inject': z.object({ entries: z.array(InjectEntrySchema).min(1) }),
|
|
72
76
|
'param.release': z.object({ targets: z.array(InjectTargetSchema).optional() }),
|
package/dist/types.d.ts
CHANGED
|
@@ -1,19 +1,59 @@
|
|
|
1
1
|
export type ModelFormat = 'live2d' | 'vrm';
|
|
2
2
|
export type ModelKind = 'bundled' | 'user';
|
|
3
|
+
/** Where an item came from: shipped with the app, or added by the user. */
|
|
4
|
+
export type ContentOrigin = 'bundled' | 'user';
|
|
3
5
|
/** A model as the registry lists it. Paths deliberately never cross the wire. */
|
|
4
6
|
export interface ModelRef {
|
|
7
|
+
/** Stable slug, unique per installed model. */
|
|
5
8
|
id: string;
|
|
6
9
|
name: string;
|
|
7
10
|
kind: ModelKind;
|
|
8
11
|
format: ModelFormat;
|
|
12
|
+
/** Creator credit, for attribution in pickers. */
|
|
13
|
+
author?: string;
|
|
14
|
+
/** Creator or content homepage (https). */
|
|
15
|
+
url?: string;
|
|
9
16
|
}
|
|
17
|
+
/** How a registered file is labelled. Wider than an object's content kinds: `.hdr` is only ever an environment map. */
|
|
10
18
|
export type AssetKind = 'image' | 'video' | 'prop' | 'ibl' | 'lut';
|
|
11
19
|
/** A registered object-source file. `exists` is false once the file is gone from disk. */
|
|
12
20
|
export interface AssetRef {
|
|
13
21
|
id: string;
|
|
14
22
|
name: string;
|
|
23
|
+
/** What the extension makes it — an object created from it starts on this kind. */
|
|
15
24
|
kind: AssetKind;
|
|
25
|
+
origin: ContentOrigin;
|
|
16
26
|
exists: boolean;
|
|
27
|
+
/** Creator credit, for attribution in pickers. */
|
|
28
|
+
author?: string;
|
|
29
|
+
/** Creator or content homepage (https). */
|
|
30
|
+
url?: string;
|
|
31
|
+
}
|
|
32
|
+
/** Every content kind the Inventory can list. `pngtuber` is schema-ready before any producer exists. */
|
|
33
|
+
export type InventoryKind = 'live2d' | 'vrm' | 'pngtuber' | 'image' | 'video' | 'prop';
|
|
34
|
+
/**
|
|
35
|
+
* Content metadata decoupled from any on-disk file — local registry entries and
|
|
36
|
+
* remote catalog rows both map into it. `id` is stable forever: scene refs key
|
|
37
|
+
* off it, and it must survive a ship-in-app → download-from-CDN migration.
|
|
38
|
+
*/
|
|
39
|
+
export interface CatalogItem {
|
|
40
|
+
id: string;
|
|
41
|
+
kind: InventoryKind;
|
|
42
|
+
name: string;
|
|
43
|
+
/** `persona://` for local items; CDN https for metadata-only rows. */
|
|
44
|
+
thumbnailUrl?: string;
|
|
45
|
+
/** Payload location + integrity when not on disk; absent = already local. */
|
|
46
|
+
download?: {
|
|
47
|
+
url: string;
|
|
48
|
+
size: number;
|
|
49
|
+
sha256: string;
|
|
50
|
+
};
|
|
51
|
+
/** Content revision, for CDN-side updates of an installed item. */
|
|
52
|
+
version?: string;
|
|
53
|
+
/** Creator credit, for attribution in the Inventory row/detail. */
|
|
54
|
+
author?: string;
|
|
55
|
+
/** Creator or content homepage (https). */
|
|
56
|
+
url?: string;
|
|
17
57
|
}
|
|
18
58
|
/** Screen-space placement: pixels from the stage centre, rotation in radians. */
|
|
19
59
|
export interface ScreenPlacement {
|
|
@@ -22,7 +62,7 @@ export interface ScreenPlacement {
|
|
|
22
62
|
scale: number;
|
|
23
63
|
rotation: number;
|
|
24
64
|
}
|
|
25
|
-
/** World-space
|
|
65
|
+
/** World-space placement. Rotations in radians, applied YXZ; `rotY` composes over the model's base yaw. */
|
|
26
66
|
export interface VrmPlacement {
|
|
27
67
|
x: number;
|
|
28
68
|
y: number;
|
|
@@ -32,20 +72,33 @@ export interface VrmPlacement {
|
|
|
32
72
|
rotZ: number;
|
|
33
73
|
scale: number;
|
|
34
74
|
}
|
|
75
|
+
/** {@link ScreenPlacement} plus the per-object fade. */
|
|
35
76
|
export interface Place2D extends ScreenPlacement {
|
|
36
77
|
opacity: number;
|
|
37
78
|
}
|
|
79
|
+
/** {@link VrmPlacement} plus the per-object fade. */
|
|
38
80
|
export interface Place3D extends VrmPlacement {
|
|
39
81
|
opacity: number;
|
|
40
82
|
}
|
|
41
|
-
/**
|
|
83
|
+
/**
|
|
84
|
+
* Per-instance MToon fine-tuning, layered over each material's authored values —
|
|
85
|
+
* offsets add, multipliers scale, so materials keep their relative differences.
|
|
86
|
+
* All-neutral values (1, 0, 0, 0, 1, 1, 1) render the model exactly as authored.
|
|
87
|
+
*/
|
|
42
88
|
export interface MToonTuning {
|
|
89
|
+
/** 0..1 strength of the authored shading: 1 keeps it, 0 lifts every shade color to its lit color. */
|
|
43
90
|
shade: number;
|
|
91
|
+
/** -1..1 added to authored shading shift; positive pushes the terminator so less of the model is shaded. */
|
|
44
92
|
shadingShift: number;
|
|
93
|
+
/** -1..1 added to authored toony; positive hardens the lit/shade edge, negative softens it. */
|
|
45
94
|
shadingToony: number;
|
|
95
|
+
/** -1..1 added to authored GI equalization: how evenly ambient/IBL light wraps the model. */
|
|
46
96
|
giEqualization: number;
|
|
97
|
+
/** Multiplier on the parametric rim color. */
|
|
47
98
|
rim: number;
|
|
99
|
+
/** Multiplier on outline width. */
|
|
48
100
|
outlineWidth: number;
|
|
101
|
+
/** Multiplier on emissive intensity; >1 pairs with bloom. */
|
|
49
102
|
emissive: number;
|
|
50
103
|
}
|
|
51
104
|
export interface SceneModelItem {
|
|
@@ -57,11 +110,14 @@ export interface SceneModelItem {
|
|
|
57
110
|
vrm: VrmPlacement;
|
|
58
111
|
idleAnimation: boolean;
|
|
59
112
|
idleClip: string;
|
|
113
|
+
/** MToon material fine-tuning, VRM only. */
|
|
60
114
|
mtoon: MToonTuning;
|
|
61
115
|
}
|
|
62
116
|
export type ObjectSpace = '2d' | '3d';
|
|
117
|
+
/** Where a webpage overlay renders relative to the stage. */
|
|
63
118
|
export type WebLayer = 'behind' | 'front';
|
|
64
119
|
export type CaptureKind = 'display' | 'window';
|
|
120
|
+
/** What an object renders. Mirrors VTube Studio's items and Warudo's screen/prop assets. */
|
|
65
121
|
export type ObjectContent = {
|
|
66
122
|
kind: 'image';
|
|
67
123
|
assetId: string;
|
|
@@ -89,36 +145,49 @@ export type ObjectContent = {
|
|
|
89
145
|
sourceId: string;
|
|
90
146
|
label: string;
|
|
91
147
|
};
|
|
92
|
-
/** Where on a parent model an object rides. */
|
|
148
|
+
/** Where on a parent model an object rides. `root` is the model's own transform. */
|
|
93
149
|
export type AttachAnchor = {
|
|
94
150
|
kind: 'root';
|
|
95
|
-
}
|
|
151
|
+
}
|
|
152
|
+
/** VRM humanoid bone name (`hips`, `head`, `leftHand`, …). */
|
|
153
|
+
| {
|
|
96
154
|
kind: 'bone';
|
|
97
155
|
bone: string;
|
|
98
|
-
}
|
|
156
|
+
}
|
|
157
|
+
/** Live2D drawable + the triangle and barycentric weights of the pin: it deforms with the mesh. */
|
|
158
|
+
| {
|
|
99
159
|
kind: 'artMesh';
|
|
100
160
|
id: string;
|
|
101
161
|
verts: [number, number, number];
|
|
102
162
|
weights: [number, number, number];
|
|
103
163
|
};
|
|
164
|
+
/** Live2D only: smoothed ParamAngleZ × multiplier turns the pinned item (X/Y ride the mesh). */
|
|
165
|
+
export interface AttachHeadAngle {
|
|
166
|
+
multiplier: number;
|
|
167
|
+
/** VTS-style 0..50; ~frames of lag at 60 fps (0 = instant). */
|
|
168
|
+
smoothing: number;
|
|
169
|
+
}
|
|
170
|
+
/** 3D only: spring lag on the follow; absent is rigid. */
|
|
171
|
+
export interface AttachElasticity {
|
|
172
|
+
stiffness: number;
|
|
173
|
+
damping: number;
|
|
174
|
+
maxSpeed: number;
|
|
175
|
+
}
|
|
104
176
|
export interface Attach {
|
|
177
|
+
/** Always a model instance in the same space; anything else is cleared on load. */
|
|
105
178
|
parentInstanceId: string;
|
|
106
179
|
anchor: AttachAnchor;
|
|
107
180
|
followRotation: boolean;
|
|
108
|
-
headAngle:
|
|
109
|
-
|
|
110
|
-
smoothing: number;
|
|
111
|
-
} | null;
|
|
112
|
-
elasticity: {
|
|
113
|
-
stiffness: number;
|
|
114
|
-
damping: number;
|
|
115
|
-
maxSpeed: number;
|
|
116
|
-
} | null;
|
|
181
|
+
headAngle: AttachHeadAngle | null;
|
|
182
|
+
elasticity: AttachElasticity | null;
|
|
117
183
|
}
|
|
184
|
+
/** User override for one light baked into a prop asset. */
|
|
118
185
|
export interface ObjectLightOverride {
|
|
119
186
|
enabled: boolean;
|
|
187
|
+
/** Scene-slider units, or null to keep the asset's own (normalized) intensity. */
|
|
120
188
|
intensity: number | null;
|
|
121
189
|
}
|
|
190
|
+
/** A non-avatar thing on stage: image, video, screen capture, webpage, or 3D prop. */
|
|
122
191
|
export interface SceneObjectItem {
|
|
123
192
|
kind: 'object';
|
|
124
193
|
instanceId: string;
|
|
@@ -128,9 +197,16 @@ export interface SceneObjectItem {
|
|
|
128
197
|
content: ObjectContent;
|
|
129
198
|
place2d: Place2D;
|
|
130
199
|
place3d: Place3D;
|
|
200
|
+
/** Riding a model in the same space, with the placement read as an offset; null stands alone. */
|
|
131
201
|
attach: Attach | null;
|
|
202
|
+
/**
|
|
203
|
+
* Overrides for lights baked into prop content, keyed by traversal index —
|
|
204
|
+
* the only stable handle a glb gives. A re-exported asset that reorders its
|
|
205
|
+
* lights shifts them; they degrade, never break.
|
|
206
|
+
*/
|
|
132
207
|
lightOverrides: Record<string, ObjectLightOverride>;
|
|
133
208
|
}
|
|
209
|
+
/** Anything the stage renders. Array order in {@link Scene.items} is z-order within each space. */
|
|
134
210
|
export type SceneItem = SceneModelItem | SceneObjectItem;
|
|
135
211
|
export type BackgroundMode = 'transparent' | 'color' | 'image';
|
|
136
212
|
/** `imagePath` is accepted but survives only when the app has allowlisted it via its own picker. */
|
|
@@ -142,6 +218,7 @@ export interface SceneBackground {
|
|
|
142
218
|
export interface SceneBehavior {
|
|
143
219
|
lookAtCursor: boolean;
|
|
144
220
|
}
|
|
221
|
+
/** VRM stage framing: the camera moves, the model does not. Angles in radians, distance in world units. */
|
|
145
222
|
export interface OrbitTransform {
|
|
146
223
|
azimuth: number;
|
|
147
224
|
elevation: number;
|
|
@@ -149,11 +226,18 @@ export interface OrbitTransform {
|
|
|
149
226
|
targetX: number;
|
|
150
227
|
targetY: number;
|
|
151
228
|
}
|
|
229
|
+
/** Scene-level VRM camera. `orbit: null` = never framed — the first VRM load frames it from model height. */
|
|
152
230
|
export interface SceneCamera {
|
|
153
231
|
orbit: OrbitTransform | null;
|
|
154
232
|
fov: number;
|
|
155
233
|
}
|
|
156
234
|
export type SceneLightType = 'directional' | 'point' | 'ambient';
|
|
235
|
+
/**
|
|
236
|
+
* One scene light. Angles are degrees. Directional lights aim with
|
|
237
|
+
* azimuth/elevation and sit at x/y/z (which moves their handle and shadow
|
|
238
|
+
* coverage, not their parallel shading), point lights use x/y/z + range,
|
|
239
|
+
* ambient uses none — all fields stay so a type switch keeps them.
|
|
240
|
+
*/
|
|
157
241
|
export interface SceneLight {
|
|
158
242
|
id: string;
|
|
159
243
|
type: SceneLightType;
|
|
@@ -165,64 +249,95 @@ export interface SceneLight {
|
|
|
165
249
|
y: number;
|
|
166
250
|
z: number;
|
|
167
251
|
range: number;
|
|
252
|
+
/** Ambient light is directionless, so it never casts whatever this says. */
|
|
168
253
|
castShadow: boolean;
|
|
254
|
+
/** Penumbra width in shadow-map texels — a stylistic dial; the filter widens the edge uniformly. */
|
|
169
255
|
shadowRadius: number;
|
|
170
256
|
}
|
|
257
|
+
/** Distance haze. Only geometry fogs — empty space keeps the window's transparency. */
|
|
171
258
|
export interface SceneFog {
|
|
172
259
|
enabled: boolean;
|
|
173
260
|
color: string;
|
|
261
|
+
/** Exponential-squared falloff; the useful band is well under 1 at avatar scale. */
|
|
174
262
|
density: number;
|
|
175
263
|
}
|
|
264
|
+
/** Display transform applied after the scene renders. `none` keeps colors exactly as authored. */
|
|
176
265
|
export type SceneToneMapping = 'none' | 'neutral' | 'aces' | 'agx';
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
266
|
+
/** Glow around bright pixels. `threshold` is the luminance floor; `radius` widens the halo. */
|
|
267
|
+
export interface SceneBloom {
|
|
268
|
+
enabled: boolean;
|
|
269
|
+
intensity: number;
|
|
270
|
+
threshold: number;
|
|
271
|
+
radius: number;
|
|
272
|
+
}
|
|
273
|
+
/** Darkened frame corners. `offset` pushes the falloff outward. */
|
|
274
|
+
export interface SceneVignette {
|
|
275
|
+
enabled: boolean;
|
|
276
|
+
darkness: number;
|
|
277
|
+
offset: number;
|
|
278
|
+
}
|
|
279
|
+
/** Parametric grading. Hue in degrees; the rest are -1..1 around neutral 0. */
|
|
280
|
+
export interface SceneColorGrade {
|
|
281
|
+
enabled: boolean;
|
|
282
|
+
hue: number;
|
|
283
|
+
saturation: number;
|
|
284
|
+
brightness: number;
|
|
285
|
+
contrast: number;
|
|
286
|
+
}
|
|
287
|
+
/** RGB fringing toward frame edges. `strength` 0..1. */
|
|
288
|
+
export interface SceneChromaticAberration {
|
|
289
|
+
enabled: boolean;
|
|
290
|
+
strength: number;
|
|
291
|
+
}
|
|
292
|
+
/** Animated film grain. `strength` is blend opacity 0..1. */
|
|
293
|
+
export interface SceneFilmGrain {
|
|
294
|
+
enabled: boolean;
|
|
295
|
+
strength: number;
|
|
296
|
+
}
|
|
297
|
+
/** Color-grading lookup table (.cube/.3dl asset). Active whenever an asset is set. */
|
|
298
|
+
export interface SceneLut {
|
|
299
|
+
assetId: string | null;
|
|
300
|
+
intensity: number;
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Bokeh blur away from the focus plane. Focus tracks the camera's orbit target,
|
|
304
|
+
* so whatever the user framed stays sharp; `focusRange` widens the sharp band.
|
|
305
|
+
*/
|
|
306
|
+
export interface SceneDepthOfField {
|
|
307
|
+
enabled: boolean;
|
|
308
|
+
bokehScale: number;
|
|
309
|
+
focusRange: number;
|
|
310
|
+
}
|
|
311
|
+
/** Overlay/stylization gimmicks, each independently switchable. */
|
|
312
|
+
export interface SceneStylize {
|
|
313
|
+
pixelate: {
|
|
203
314
|
enabled: boolean;
|
|
204
|
-
|
|
205
|
-
};
|
|
206
|
-
lut: {
|
|
207
|
-
assetId: string | null;
|
|
208
|
-
intensity: number;
|
|
315
|
+
granularity: number;
|
|
209
316
|
};
|
|
210
|
-
|
|
317
|
+
glitch: {
|
|
211
318
|
enabled: boolean;
|
|
212
|
-
bokehScale: number;
|
|
213
|
-
focusRange: number;
|
|
214
|
-
};
|
|
215
|
-
stylize: {
|
|
216
|
-
pixelate: {
|
|
217
|
-
enabled: boolean;
|
|
218
|
-
granularity: number;
|
|
219
|
-
};
|
|
220
|
-
glitch: {
|
|
221
|
-
enabled: boolean;
|
|
222
|
-
};
|
|
223
319
|
};
|
|
224
320
|
}
|
|
321
|
+
/** Post-processing over the rendered 3D frame. Everything off skips the effect chain entirely. */
|
|
322
|
+
export interface SceneEffects {
|
|
323
|
+
toneMapping: SceneToneMapping;
|
|
324
|
+
/** Scene brightness multiplied in before the tone curve; 1 is neutral. Works in every mode, including `none`. */
|
|
325
|
+
exposure: number;
|
|
326
|
+
bloom: SceneBloom;
|
|
327
|
+
vignette: SceneVignette;
|
|
328
|
+
color: SceneColorGrade;
|
|
329
|
+
chromaticAberration: SceneChromaticAberration;
|
|
330
|
+
grain: SceneFilmGrain;
|
|
331
|
+
lut: SceneLut;
|
|
332
|
+
dof: SceneDepthOfField;
|
|
333
|
+
stylize: SceneStylize;
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Image-based lighting for the 3D stage: an environment map, whether to show it
|
|
337
|
+
* behind the scene, the scene-global fog, and post-processing.
|
|
338
|
+
*/
|
|
225
339
|
export interface SceneEnvironment {
|
|
340
|
+
/** Equirectangular `.hdr` or image driving the environment lighting, or null. */
|
|
226
341
|
iblAssetId: string | null;
|
|
227
342
|
iblIntensity: number;
|
|
228
343
|
showSkybox: boolean;
|
|
@@ -232,13 +347,17 @@ export interface SceneEnvironment {
|
|
|
232
347
|
export interface Scene {
|
|
233
348
|
id: string;
|
|
234
349
|
name: string;
|
|
350
|
+
/** Models and objects in one list; array order = z-order (bottom to top) within each space. */
|
|
235
351
|
items: SceneItem[];
|
|
352
|
+
/** Tracking/pose/hotkey/expression target. null only when `items` holds no model. */
|
|
236
353
|
primaryInstanceId: string | null;
|
|
237
354
|
background: SceneBackground;
|
|
238
355
|
behavior: SceneBehavior;
|
|
239
356
|
vrmCamera: SceneCamera;
|
|
357
|
+
/** Array order is display order only; live lights are keyed by id. */
|
|
240
358
|
lights: SceneLight[];
|
|
241
359
|
environment: SceneEnvironment;
|
|
360
|
+
/** Electron accelerator that applies this scene, or null. */
|
|
242
361
|
shortcut: string | null;
|
|
243
362
|
}
|
|
244
363
|
export interface SceneState {
|
|
@@ -322,9 +441,15 @@ export interface ExpressionPersistence {
|
|
|
322
441
|
}
|
|
323
442
|
export type TrackingSourceId = 'vts-ios' | 'vts-ios-native' | 'ifacialmocap';
|
|
324
443
|
export type PoseSourceId = 'vmc';
|
|
325
|
-
export type EffectsQuality = 'high' | 'medium' | 'low';
|
|
326
444
|
export type TrackingStatus = 'off' | 'waiting' | 'tracking' | 'no-face';
|
|
327
445
|
export type PoseStatus = 'off' | 'waiting' | 'tracking';
|
|
446
|
+
export declare const EFFECTS_QUALITY_LEVELS: readonly ["low", "medium", "high"];
|
|
447
|
+
export type EffectsQuality = (typeof EFFECTS_QUALITY_LEVELS)[number];
|
|
448
|
+
/**
|
|
449
|
+
* Accepted values for `performance.fpsLimit`; 0 = unlimited. Anything else is snapped
|
|
450
|
+
* to the nearest preset by the app, so a picker offering other values would lie.
|
|
451
|
+
*/
|
|
452
|
+
export declare const FPS_LIMIT_PRESETS: readonly [0, 15, 30, 60, 90];
|
|
328
453
|
/** The curated settings surface the API exposes — never the raw store shape. */
|
|
329
454
|
export interface Settings {
|
|
330
455
|
window: {
|
package/dist/types.js
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
// The API's data model: the entity shapes that requests, responses, and events
|
|
2
2
|
// carry. Structural mirrors of the app's scene/settings models, minus anything
|
|
3
3
|
// filesystem-shaped — model refs are sanitized to ids, never directories.
|
|
4
|
+
export const EFFECTS_QUALITY_LEVELS = ['low', 'medium', 'high'];
|
|
5
|
+
/**
|
|
6
|
+
* Accepted values for `performance.fpsLimit`; 0 = unlimited. Anything else is snapped
|
|
7
|
+
* to the nearest preset by the app, so a picker offering other values would lie.
|
|
8
|
+
*/
|
|
9
|
+
export const FPS_LIMIT_PRESETS = [0, 15, 30, 60, 90];
|
|
4
10
|
// ---- Injection -----------------------------------------------------------------
|
|
5
11
|
/** VTS's input vocabulary — the valid `id`s for `input` inject targets (and the names a model's `.vtube.json` references). */
|
|
6
12
|
export const INPUT_NAMES = [
|