@laplace.live/persona-sdk 1.7.0 → 1.8.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/dist/index.d.ts CHANGED
@@ -25,5 +25,6 @@ export * from './wire/errors.ts';
25
25
  export * from './wire/events.ts';
26
26
  export * from './wire/methods.ts';
27
27
  export * from './wire/protocol.ts';
28
- export * from './wire/schemas.ts';
28
+ export * from './wire/schemas/requests.ts';
29
+ export * from './wire/schemas/settings.ts';
29
30
  export * from './wire/types.ts';
package/dist/index.js CHANGED
@@ -27,5 +27,6 @@ export * from "./wire/errors.js";
27
27
  export * from "./wire/events.js";
28
28
  export * from "./wire/methods.js";
29
29
  export * from "./wire/protocol.js";
30
- export * from "./wire/schemas.js";
30
+ export * from "./wire/schemas/requests.js";
31
+ export * from "./wire/schemas/settings.js";
31
32
  export * from "./wire/types.js";
@@ -35,6 +35,8 @@ export declare function controllerInputName(base: BaseControllerInputName, slot:
35
35
  export declare function baseControllerInputName(full: ControllerInputName): BaseControllerInputName;
36
36
  /** Whether an input names a supported controller parameter. */
37
37
  export declare function isControllerInputName(v: string): v is ControllerInputName;
38
+ /** JSON Schema pattern for the numbered (slot 2+) spellings of these base inputs; slot 1 keeps the bare names. */
39
+ export declare function numberedControllerInputPattern(bases: readonly BaseControllerInputName[]): string;
38
40
  /** Natural parameter range, shared by every controller profile. */
39
41
  export declare function controllerInputRange(name: ControllerInputName): readonly [number, number];
40
42
  /** VTS shortcut names differ from parameter names, especially shoulders, clicks and D-pad. */
@@ -58,6 +58,10 @@ export function isControllerInputName(v) {
58
58
  const parts = controllerParts(v);
59
59
  return parts !== null && CONTROLLER_INPUT_SET.has(parts.base);
60
60
  }
61
+ /** JSON Schema pattern for the numbered (slot 2+) spellings of these base inputs; slot 1 keeps the bare names. */
62
+ export function numberedControllerInputPattern(bases) {
63
+ return `^Controller(?:[2-9]|[1-9][0-9]+)(?:${bases.map(base => base.slice('Controller'.length)).join('|')})$`;
64
+ }
61
65
  /** Natural parameter range, shared by every controller profile. */
62
66
  export function controllerInputRange(name) {
63
67
  return BASE_CONTROLLER_INPUT_RANGES[baseControllerInputName(name)];
@@ -12,6 +12,8 @@ export interface EditHistory<T> {
12
12
  undo(): T | null;
13
13
  /** Step forward; mirror of {@link EditHistory.undo}. */
14
14
  redo(): T | null;
15
+ canUndo(): boolean;
16
+ canRedo(): boolean;
15
17
  /**
16
18
  * Rewrite the run of entries around the cursor that share its `runKey`, outward until the key
17
19
  * changes on either side — a fold, never a step, so undo and redo across the run all carry it.
@@ -36,6 +36,8 @@ export function createEditHistory(limit = EDIT_HISTORY_LIMIT) {
36
36
  cursor++;
37
37
  return structuredClone(stack[cursor].value);
38
38
  },
39
+ canUndo: () => cursor > 0,
40
+ canRedo: () => cursor < stack.length - 1,
39
41
  amend(runKey, rewrite) {
40
42
  const cur = stack[cursor];
41
43
  if (!cur)
@@ -9,12 +9,9 @@ export declare function fileBasename(path: string): string;
9
9
  export declare function motionLabel(file: string): string;
10
10
  /** English label for one automation action kind; kinds newer than this SDK read `Action`. */
11
11
  export declare function automationActionLabel(kind: string): string;
12
- /** True for an action that ends a batch and waits before continuing. */
12
+ /** True for an action that prepares the scene before playback. */
13
13
  export declare function isAutomationBoundary(kind: string | undefined): kind is AutomationBoundaryKind;
14
- /**
15
- * Join one automation's action labels: a batch boundary reads as an arrow, while actions
16
- * inside a batch read as ` + `. `kinds` must match `labels` in length and position.
17
- */
14
+ /** Join action labels without implying timing that the client summary does not carry. */
18
15
  export declare function joinAutomationActionLabels(labels: readonly string[], kinds: readonly string[]): string;
19
16
  /** Display label for an app automation: its title, else its joined action-kind labels. */
20
17
  export declare function automationLabel(automation: Pick<AutomationInfo, 'title' | 'actionKinds'>): string;
@@ -17,6 +17,7 @@ export function motionLabel(file) {
17
17
  const AUTOMATION_ACTION_KIND_LABELS = {
18
18
  'effect-toggle': 'Toggle Effect',
19
19
  'effect-params': 'Effect Settings',
20
+ 'effect-clip': 'Effect Clip',
20
21
  'camera-pose': 'Camera Position',
21
22
  'reset-camera': 'Reset Camera',
22
23
  'layer-visibility': 'Layer Visibility',
@@ -24,29 +25,27 @@ const AUTOMATION_ACTION_KIND_LABELS = {
24
25
  'switch-scene': 'Switch Scene',
25
26
  'toggle-expression': 'Toggle Expression',
26
27
  'play-motion': 'Play Motion',
28
+ 'play-audio': 'Play Audio',
29
+ 'audio-control': 'Control Audio',
30
+ 'play-camera-motion': 'Play Camera Motion',
31
+ 'stop-camera-motion': 'Stop Camera Motion',
27
32
  'remove-all-expressions': 'Clear All Expressions',
28
33
  'load-model': 'Swap Model',
29
34
  'model-position': 'Model Position',
30
- delay: 'Wait',
31
35
  };
32
36
  /** English label for one automation action kind; kinds newer than this SDK read `Action`. */
33
37
  export function automationActionLabel(kind) {
34
38
  return AUTOMATION_ACTION_KIND_LABELS[kind] ?? 'Action';
35
39
  }
36
- /** True for an action that ends a batch and waits before continuing. */
40
+ /** True for an action that prepares the scene before playback. */
37
41
  export function isAutomationBoundary(kind) {
38
42
  return isOneOf(kind, AUTOMATION_BOUNDARY_KINDS);
39
43
  }
40
- /**
41
- * Join one automation's action labels: a batch boundary reads as an arrow, while actions
42
- * inside a batch read as ` + `. `kinds` must match `labels` in length and position.
43
- */
44
+ /** Join action labels without implying timing that the client summary does not carry. */
44
45
  export function joinAutomationActionLabels(labels, kinds) {
45
46
  if (labels.length !== kinds.length)
46
47
  throw new RangeError('automation action labels and kinds must have the same length');
47
- return labels.reduce((out, label, i) => out +
48
- (i === 0 ? '' : isAutomationBoundary(kinds[i]) || isAutomationBoundary(kinds[i - 1]) ? ' → ' : ' + ') +
49
- label, '');
48
+ return labels.join(' · ');
50
49
  }
51
50
  /** Display label for an app automation: its title, else its joined action-kind labels. */
52
51
  export function automationLabel(automation) {
@@ -39,9 +39,17 @@ export declare function sceneLightShadowRadiusMax(type: SceneLightType): number;
39
39
  export declare const SCENE_FOG_DENSITY_MAX = 0.5;
40
40
  /** ×4 is +2 stops — enough to rescue an AgX-dimmed avatar without turning the slider to mush. */
41
41
  export declare const SCENE_EXPOSURE_MAX = 4;
42
- /** Vertical field of view in degrees; the band either side of a portrait lens, before the framing distorts. */
42
+ /** Manual editor field-of-view range in degrees. Authored cameras use CAMERA_FOV_MIN/MAX. */
43
43
  export declare const SCENE_FOV_MIN = 10;
44
44
  export declare const SCENE_FOV_MAX = 90;
45
+ /** Nonsingular authored field of view in degrees, including every valid whole-degree VMD key. */
46
+ export declare const CAMERA_FOV_MIN = 1;
47
+ export declare const CAMERA_FOV_MAX = 179;
48
+ /** Manual navigation stops short of ±90°; authored camera elevations remain unrestricted. */
49
+ export declare const ELEVATION_LIMIT: number;
50
+ /** Manual navigation distance bounds; authored cameras retain signed, unrestricted distances. */
51
+ export declare const DISTANCE_MIN = 0.35;
52
+ export declare const DISTANCE_MAX = 20;
45
53
  /** ×2 doubles Cubism's own breath amplitude — past that the head sway reads as a nod, not a breath. */
46
54
  export declare const BREATH_DEPTH_MAX = 2;
47
55
  export declare const MTOON_NORMAL_SCALE_MAX = 2;
@@ -62,9 +62,17 @@ export function sceneLightShadowRadiusMax(type) {
62
62
  export const SCENE_FOG_DENSITY_MAX = 0.5;
63
63
  /** ×4 is +2 stops — enough to rescue an AgX-dimmed avatar without turning the slider to mush. */
64
64
  export const SCENE_EXPOSURE_MAX = 4;
65
- /** Vertical field of view in degrees; the band either side of a portrait lens, before the framing distorts. */
65
+ /** Manual editor field-of-view range in degrees. Authored cameras use CAMERA_FOV_MIN/MAX. */
66
66
  export const SCENE_FOV_MIN = 10;
67
67
  export const SCENE_FOV_MAX = 90;
68
+ /** Nonsingular authored field of view in degrees, including every valid whole-degree VMD key. */
69
+ export const CAMERA_FOV_MIN = 1;
70
+ export const CAMERA_FOV_MAX = 179;
71
+ /** Manual navigation stops short of ±90°; authored camera elevations remain unrestricted. */
72
+ export const ELEVATION_LIMIT = (80 * Math.PI) / 180;
73
+ /** Manual navigation distance bounds; authored cameras retain signed, unrestricted distances. */
74
+ export const DISTANCE_MIN = 0.35;
75
+ export const DISTANCE_MAX = 20;
68
76
  /** ×2 doubles Cubism's own breath amplitude — past that the head sway reads as a nod, not a breath. */
69
77
  export const BREATH_DEPTH_MAX = 2;
70
78
  export const MTOON_NORMAL_SCALE_MAX = 2;
@@ -8,6 +8,10 @@ export type LipSyncMode = (typeof LIP_SYNC_MODES)[number];
8
8
  export declare const LIP_SYNC_GAIN_MAX = 30;
9
9
  export declare const LIP_SYNC_NOISE_GATE_MIN = -60;
10
10
  export declare const LIP_SYNC_SMOOTHING_MAX = 0.3;
11
+ /** Persisted calibration dimensions, shared by the analyzer, healer, and settings schema. */
12
+ export declare const LIP_SYNC_MFCC_COEFFICIENTS = 12;
13
+ export declare const LIP_SYNC_MIN_SAMPLES = 12;
14
+ export declare const LIP_SYNC_MAX_SAMPLES = 40;
11
15
  /** Local microphone calibration; each sample is a 12-coefficient MFCC vector. */
12
16
  export interface LipSyncProfile {
13
17
  deviceId: string;
@@ -7,6 +7,10 @@ export const LIP_SYNC_MODES = ['off', 'always', 'when-untracked'];
7
7
  export const LIP_SYNC_GAIN_MAX = 30;
8
8
  export const LIP_SYNC_NOISE_GATE_MIN = -60;
9
9
  export const LIP_SYNC_SMOOTHING_MAX = 0.3;
10
+ /** Persisted calibration dimensions, shared by the analyzer, healer, and settings schema. */
11
+ export const LIP_SYNC_MFCC_COEFFICIENTS = 12;
12
+ export const LIP_SYNC_MIN_SAMPLES = 12;
13
+ export const LIP_SYNC_MAX_SAMPLES = 40;
10
14
  /** Calibration steps that carry no phoneme; `record` is the sixth action and takes one. */
11
15
  export const LIP_SYNC_CALIBRATION_ACTIONS = ['start', 'cancel', 'preview', 'save', 'reset'];
12
16
  export const DEFAULT_LIP_SYNC_CONFIG = {
@@ -31,10 +35,10 @@ export function healLipSyncProfile(raw) {
31
35
  const samples = { A: [], I: [], U: [], E: [], O: [], S: [] };
32
36
  for (const phoneme of LIP_SYNC_PHONEMES) {
33
37
  const vectors = raw.samples[phoneme];
34
- if (!Array.isArray(vectors) || vectors.length < 12 || vectors.length > 40)
38
+ if (!Array.isArray(vectors) || vectors.length < LIP_SYNC_MIN_SAMPLES || vectors.length > LIP_SYNC_MAX_SAMPLES)
35
39
  return null;
36
40
  for (const vector of vectors) {
37
- if (!Array.isArray(vector) || vector.length !== 12 || !vector.every(isFiniteNumber))
41
+ if (!Array.isArray(vector) || vector.length !== LIP_SYNC_MFCC_COEFFICIENTS || !vector.every(isFiniteNumber))
38
42
  return null;
39
43
  samples[phoneme].push([...vector]);
40
44
  }
@@ -1,12 +1,12 @@
1
1
  import * as z from 'zod';
2
- import type { MethodName } from './methods.ts';
3
- import type { HotkeyConfig, ObjectContent } from './types.ts';
2
+ import type { MethodName } from '../methods.ts';
3
+ import type { HotkeyConfig, ObjectContent } from '../types.ts';
4
4
  /** A complete transition replacement; scene.patch remains stage-owned for its other slices. */
5
5
  export declare const SceneTransitionSchema: z.ZodObject<{
6
6
  type: z.ZodEnum<{
7
- circle: "circle";
8
7
  image: "image";
9
8
  video: "video";
9
+ circle: "circle";
10
10
  cut: "cut";
11
11
  fade: "fade";
12
12
  wipe: "wipe";
@@ -39,38 +39,6 @@ export declare const InjectEntrySchema: z.ZodObject<{
39
39
  value: z.ZodNumber;
40
40
  weight: z.ZodOptional<z.ZodNumber>;
41
41
  }, z.core.$strip>;
42
- export declare const SettingsPatchSchema: z.ZodObject<{
43
- lipSync: z.ZodOptional<z.ZodObject<{
44
- enabled: z.ZodOptional<z.ZodBoolean>;
45
- deviceId: z.ZodOptional<z.ZodString>;
46
- gain: z.ZodOptional<z.ZodNumber>;
47
- noiseGate: z.ZodOptional<z.ZodNumber>;
48
- smoothing: z.ZodOptional<z.ZodNumber>;
49
- }, z.core.$strip>>;
50
- controller: z.ZodOptional<z.ZodObject<{
51
- enabled: z.ZodOptional<z.ZodBoolean>;
52
- }, z.core.$strip>>;
53
- window: z.ZodOptional<z.ZodObject<{
54
- alwaysOnTop: z.ZodOptional<z.ZodBoolean>;
55
- }, z.core.$strip>>;
56
- ui: z.ZodOptional<z.ZodObject<{
57
- trayVisible: z.ZodOptional<z.ZodBoolean>;
58
- }, z.core.$strip>>;
59
- performance: z.ZodOptional<z.ZodObject<{
60
- showFps: z.ZodOptional<z.ZodBoolean>;
61
- fpsLimit: z.ZodOptional<z.ZodNumber>;
62
- effectsQuality: z.ZodOptional<z.ZodEnum<{
63
- low: "low";
64
- medium: "medium";
65
- high: "high";
66
- }>>;
67
- renderScale: z.ZodOptional<z.ZodNumber>;
68
- live2dEngine: z.ZodOptional<z.ZodEnum<{
69
- pixi: "pixi";
70
- three: "three";
71
- }>>;
72
- }, z.core.$strip>>;
73
- }, z.core.$strip>;
74
42
  export declare const requestSchemas: {
75
43
  'lipSync.state': z.ZodObject<{}, z.core.$strip>;
76
44
  'lipSync.configure': z.ZodObject<{
@@ -233,6 +201,7 @@ export declare const requestSchemas: {
233
201
  want: z.ZodOptional<z.ZodEnum<{
234
202
  image: "image";
235
203
  video: "video";
204
+ audio: "audio";
236
205
  prop: "prop";
237
206
  ibl: "ibl";
238
207
  lut: "lut";
@@ -262,9 +231,9 @@ export declare const requestSchemas: {
262
231
  showFps: z.ZodOptional<z.ZodBoolean>;
263
232
  fpsLimit: z.ZodOptional<z.ZodNumber>;
264
233
  effectsQuality: z.ZodOptional<z.ZodEnum<{
234
+ high: "high";
265
235
  low: "low";
266
236
  medium: "medium";
267
- high: "high";
268
237
  }>>;
269
238
  renderScale: z.ZodOptional<z.ZodNumber>;
270
239
  live2dEngine: z.ZodOptional<z.ZodEnum<{
@@ -320,10 +289,10 @@ export declare const requestSchemas: {
320
289
  instanceId: z.ZodOptional<z.ZodString>;
321
290
  }, z.core.$strip>;
322
291
  'events.subscribe': z.ZodObject<{
323
- events: z.ZodArray<z.ZodCustom<keyof import("./events.ts").EventMap, keyof import("./events.ts").EventMap>>;
292
+ events: z.ZodArray<z.ZodCustom<keyof import("../events.ts").EventMap, keyof import("../events.ts").EventMap>>;
324
293
  }, z.core.$strip>;
325
294
  'events.unsubscribe': z.ZodObject<{
326
- events: z.ZodOptional<z.ZodArray<z.ZodCustom<keyof import("./events.ts").EventMap, keyof import("./events.ts").EventMap>>>;
295
+ events: z.ZodOptional<z.ZodArray<z.ZodCustom<keyof import("../events.ts").EventMap, keyof import("../events.ts").EventMap>>>;
327
296
  }, z.core.$strip>;
328
297
  'param.inject': z.ZodObject<{
329
298
  entries: z.ZodArray<z.ZodObject<{
@@ -1,10 +1,11 @@
1
1
  import * as z from 'zod';
2
- import { CONTROLLER_DEAD_ZONE_MAX } from "../values/controller.js";
3
- import { isRecord } from "../values/guards.js";
4
- import { SCENE_COLOR_RE, SPEECH_URL_MAX_LENGTH, STORAGE_KEY_MAX_LENGTH, STORAGE_VALUE_MAX_LENGTH, } from "../values/limits.js";
5
- import { LIP_SYNC_CALIBRATION_ACTIONS, LIP_SYNC_GAIN_MAX, LIP_SYNC_MODES, LIP_SYNC_NOISE_GATE_MIN, LIP_SYNC_PHONEMES, LIP_SYNC_SMOOTHING_MAX, } from "../values/lipsync.js";
6
- import { SCENE_TRANSITION_DURATION_MAX_MS, SCENE_TRANSITION_DURATION_MIN_MS, SCENE_TRANSITION_FADE_DEFAULT_MS, SCENE_TRANSITION_SWITCH_POINT_MAX, SCENE_TRANSITION_SWITCH_POINT_MIN, SCENE_TRANSITION_TYPES, } from "../values/scene-transition.js";
7
- import { ASSET_KINDS, EFFECTS_QUALITY_LEVELS, INJECT_TARGET_TYPES, LIVE2D_ENGINES, MEDIAPIPE_DELEGATES, POSE_SOURCE_IDS, TRACKING_SOURCE_IDS, TRACKING_SOURCE_KINDS, } from "./types.js";
2
+ import { CONTROLLER_DEAD_ZONE_MAX } from "../../values/controller.js";
3
+ import { isRecord } from "../../values/guards.js";
4
+ import { SCENE_COLOR_RE, SPEECH_URL_MAX_LENGTH, STORAGE_KEY_MAX_LENGTH, STORAGE_VALUE_MAX_LENGTH, } from "../../values/limits.js";
5
+ import { LIP_SYNC_CALIBRATION_ACTIONS, LIP_SYNC_MODES, LIP_SYNC_PHONEMES, } from "../../values/lipsync.js";
6
+ import { SCENE_TRANSITION_DURATION_MAX_MS, SCENE_TRANSITION_DURATION_MIN_MS, SCENE_TRANSITION_FADE_DEFAULT_MS, SCENE_TRANSITION_SWITCH_POINT_MAX, SCENE_TRANSITION_SWITCH_POINT_MIN, SCENE_TRANSITION_TYPES, } from "../../values/scene-transition.js";
7
+ import { ASSET_KINDS, INJECT_TARGET_TYPES, POSE_SOURCE_IDS, TRACKING_SOURCE_IDS, TRACKING_SOURCE_KINDS, } from "../types.js";
8
+ import { LipSyncConfigSchema, MediaPipeConfigSchema, PortSchema, SettingsPatchSchema } from "./settings.js";
8
9
  // Runtime validation for the request side of the wire. Schemas exist for the
9
10
  // methods whose params the app's main process consumes directly; methods without
10
11
  // one are stage-owned — the renderer validates and heals them (same rules as the
@@ -22,26 +23,12 @@ export const SceneTransitionSchema = z.object({
22
23
  fadeInMs: z.number().min(0).max(SCENE_TRANSITION_DURATION_MAX_MS).default(SCENE_TRANSITION_FADE_DEFAULT_MS),
23
24
  fadeOutMs: z.number().min(0).max(SCENE_TRANSITION_DURATION_MAX_MS).default(SCENE_TRANSITION_FADE_DEFAULT_MS),
24
25
  });
25
- const port = z.number().int().min(1).max(65535);
26
26
  const lipSyncCalibration = z.discriminatedUnion('action', [
27
27
  z.object({ action: z.enum(LIP_SYNC_CALIBRATION_ACTIONS) }),
28
28
  z.object({ action: z.literal('record'), phoneme: z.enum(LIP_SYNC_PHONEMES) }),
29
29
  ]);
30
- const lipSyncPatch = z.object({
31
- enabled: z.boolean().optional(),
32
- deviceId: z.string().optional(),
33
- gain: z.number().min(0).max(LIP_SYNC_GAIN_MAX).optional(),
34
- noiseGate: z.number().min(LIP_SYNC_NOISE_GATE_MIN).max(0).optional(),
35
- smoothing: z.number().min(0).max(LIP_SYNC_SMOOTHING_MAX).optional(),
36
- });
37
- const mediapipePatch = z.object({
38
- deviceId: z.string().optional(),
39
- mirror: z.boolean().optional(),
40
- face: z.boolean().optional(),
41
- hands: z.boolean().optional(),
42
- body: z.boolean().optional(),
43
- delegate: z.enum(MEDIAPIPE_DELEGATES).optional(),
44
- });
30
+ const lipSyncPatch = LipSyncConfigSchema.partial();
31
+ const mediapipePatch = MediaPipeConfigSchema.partial();
45
32
  export const InjectTargetSchema = z.object({
46
33
  type: z.enum(INJECT_TARGET_TYPES),
47
34
  id: nonEmpty,
@@ -51,21 +38,6 @@ export const InjectEntrySchema = InjectTargetSchema.extend({
51
38
  value: z.number(),
52
39
  weight: z.number().min(0).max(1).optional(),
53
40
  });
54
- export const SettingsPatchSchema = z.object({
55
- lipSync: lipSyncPatch.optional(),
56
- controller: z.object({ enabled: z.boolean().optional() }).optional(),
57
- window: z.object({ alwaysOnTop: z.boolean().optional() }).optional(),
58
- ui: z.object({ trayVisible: z.boolean().optional() }).optional(),
59
- performance: z
60
- .object({
61
- showFps: z.boolean().optional(),
62
- fpsLimit: z.number().optional(),
63
- effectsQuality: z.enum(EFFECTS_QUALITY_LEVELS).optional(),
64
- renderScale: z.number().optional(),
65
- live2dEngine: z.enum(LIVE2D_ENGINES).optional(),
66
- })
67
- .optional(),
68
- });
69
41
  /** Tolerates unknown names for version skew; the server filters them and reports what took. */
70
42
  const lenientEventNameSchema = z.custom(v => typeof v === 'string', 'event names must be strings');
71
43
  const storageKey = z.string().min(1).max(STORAGE_KEY_MAX_LENGTH);
@@ -131,7 +103,7 @@ export const requestSchemas = {
131
103
  kind: z.enum(TRACKING_SOURCE_KINDS),
132
104
  name: z.string().optional(),
133
105
  phoneIp: nonEmpty.nullable().optional(),
134
- port: port.optional(),
106
+ port: PortSchema.optional(),
135
107
  mediapipe: mediapipePatch.optional(),
136
108
  }),
137
109
  'tracking.updateSource': z.object({
@@ -139,7 +111,7 @@ export const requestSchemas = {
139
111
  name: z.string().optional(),
140
112
  enabled: z.boolean().optional(),
141
113
  phoneIp: nonEmpty.nullable().optional(),
142
- port: port.optional(),
114
+ port: PortSchema.optional(),
143
115
  mediapipe: mediapipePatch.optional(),
144
116
  }),
145
117
  'tracking.removeSource': z.object({ id: nonEmpty }),
@@ -152,7 +124,7 @@ export const requestSchemas = {
152
124
  'tracking.setSource': z.object({ source: z.enum(TRACKING_SOURCE_IDS) }),
153
125
  'pose.setEnabled': z.object({ enabled: z.boolean() }),
154
126
  'pose.setSource': z.object({ source: z.enum(POSE_SOURCE_IDS) }),
155
- 'pose.setPort': z.object({ port }),
127
+ 'pose.setPort': z.object({ port: PortSchema }),
156
128
  'session.identify': z.object({
157
129
  name: z.string().trim().min(1).max(64),
158
130
  version: z.string().max(32).optional(),
@@ -0,0 +1,284 @@
1
+ import * as z from 'zod';
2
+ /** Shared field definitions; parsing validates values without adding defaults or healing them. */
3
+ export declare const PortSchema: z.ZodNumber;
4
+ export declare const Live2DEngineSchema: z.ZodEnum<{
5
+ pixi: "pixi";
6
+ three: "three";
7
+ }>;
8
+ export declare const EffectsQualitySchema: z.ZodEnum<{
9
+ high: "high";
10
+ low: "low";
11
+ medium: "medium";
12
+ }>;
13
+ export declare const LanguageSettingSchema: z.ZodEnum<{
14
+ en: "en";
15
+ ja: "ja";
16
+ "zh-CN": "zh-CN";
17
+ "zh-TW": "zh-TW";
18
+ system: "system";
19
+ }>;
20
+ export declare const ThemeSettingSchema: z.ZodEnum<{
21
+ system: "system";
22
+ light: "light";
23
+ dark: "dark";
24
+ }>;
25
+ /** Microphone configuration shared by desktop persistence and the plugin API. */
26
+ export declare const LipSyncConfigSchema: z.ZodObject<{
27
+ enabled: z.ZodBoolean;
28
+ deviceId: z.ZodString;
29
+ gain: z.ZodNumber;
30
+ noiseGate: z.ZodNumber;
31
+ smoothing: z.ZodNumber;
32
+ }, z.core.$strip>;
33
+ /** Saved local microphone calibration; raw samples never cross the settings API. */
34
+ export declare const LipSyncProfileSchema: z.ZodObject<{
35
+ deviceId: z.ZodString;
36
+ deviceLabel: z.ZodString;
37
+ updatedAt: z.ZodNumber;
38
+ samples: z.ZodRecord<z.ZodEnum<{
39
+ A: "A";
40
+ I: "I";
41
+ U: "U";
42
+ E: "E";
43
+ O: "O";
44
+ S: "S";
45
+ }>, z.ZodArray<z.ZodArray<z.ZodNumber>>>;
46
+ }, z.core.$strip>;
47
+ /** Webcam camera selection and enabled inference tasks. */
48
+ export declare const MediaPipeConfigSchema: z.ZodObject<{
49
+ deviceId: z.ZodString;
50
+ mirror: z.ZodBoolean;
51
+ face: z.ZodBoolean;
52
+ hands: z.ZodBoolean;
53
+ body: z.ZodBoolean;
54
+ delegate: z.ZodEnum<{
55
+ CPU: "CPU";
56
+ GPU: "GPU";
57
+ }>;
58
+ }, z.core.$strip>;
59
+ /** One configured tracking source; several may run concurrently. */
60
+ export declare const TrackingSourceConfigSchema: z.ZodObject<{
61
+ id: z.ZodString;
62
+ kind: z.ZodEnum<{
63
+ "persona-ios": "persona-ios";
64
+ ifacialmocap: "ifacialmocap";
65
+ "vts-ios": "vts-ios";
66
+ vmc: "vmc";
67
+ mocopi: "mocopi";
68
+ mediapipe: "mediapipe";
69
+ }>;
70
+ name: z.ZodString;
71
+ enabled: z.ZodBoolean;
72
+ phoneIp: z.ZodNullable<z.ZodString>;
73
+ port: z.ZodNullable<z.ZodNumber>;
74
+ mediapipe: z.ZodOptional<z.ZodObject<{
75
+ deviceId: z.ZodString;
76
+ mirror: z.ZodBoolean;
77
+ face: z.ZodBoolean;
78
+ hands: z.ZodBoolean;
79
+ body: z.ZodBoolean;
80
+ delegate: z.ZodEnum<{
81
+ CPU: "CPU";
82
+ GPU: "GPU";
83
+ }>;
84
+ }, z.core.$strip>>;
85
+ }, z.core.$strip>;
86
+ /** Public controller profile fields; desktop persistence adds local hardware metadata. */
87
+ export declare const ControllerProfileInfoSchema: z.ZodObject<{
88
+ slot: z.ZodNumber;
89
+ deviceId: z.ZodString;
90
+ name: z.ZodOptional<z.ZodString>;
91
+ deadZone: z.ZodOptional<z.ZodNumber>;
92
+ }, z.core.$strip>;
93
+ /** Controller settings exposed to remote editors. */
94
+ export declare const ControllerConfigSchema: z.ZodObject<{
95
+ enabled: z.ZodBoolean;
96
+ profiles: z.ZodArray<z.ZodObject<{
97
+ slot: z.ZodNumber;
98
+ deviceId: z.ZodString;
99
+ name: z.ZodOptional<z.ZodString>;
100
+ deadZone: z.ZodOptional<z.ZodNumber>;
101
+ }, z.core.$strip>>;
102
+ }, z.core.$strip>;
103
+ /** Shared performance fields; desktop healing snaps numeric presets after API patches. */
104
+ export declare const PerformanceSettingsSchema: z.ZodObject<{
105
+ showFps: z.ZodBoolean;
106
+ fpsLimit: z.ZodNumber;
107
+ effectsQuality: z.ZodEnum<{
108
+ high: "high";
109
+ low: "low";
110
+ medium: "medium";
111
+ }>;
112
+ renderScale: z.ZodNumber;
113
+ live2dEngine: z.ZodEnum<{
114
+ pixi: "pixi";
115
+ three: "three";
116
+ }>;
117
+ }, z.core.$strip>;
118
+ /** Stage content dimensions in device-independent pixels. */
119
+ export declare const StageSizeSchema: z.ZodObject<{
120
+ width: z.ZodNumber;
121
+ height: z.ZodNumber;
122
+ }, z.core.$strip>;
123
+ /** Face master switch and sources, with the legacy source mirror for older clients. */
124
+ export declare const TrackingSettingsSchema: z.ZodObject<{
125
+ enabled: z.ZodBoolean;
126
+ source: z.ZodEnum<{
127
+ "persona-ios": "persona-ios";
128
+ ifacialmocap: "ifacialmocap";
129
+ "vts-ios": "vts-ios";
130
+ }>;
131
+ sources: z.ZodArray<z.ZodObject<{
132
+ id: z.ZodString;
133
+ kind: z.ZodEnum<{
134
+ "persona-ios": "persona-ios";
135
+ ifacialmocap: "ifacialmocap";
136
+ "vts-ios": "vts-ios";
137
+ vmc: "vmc";
138
+ mocopi: "mocopi";
139
+ mediapipe: "mediapipe";
140
+ }>;
141
+ name: z.ZodString;
142
+ enabled: z.ZodBoolean;
143
+ phoneIp: z.ZodNullable<z.ZodString>;
144
+ port: z.ZodNullable<z.ZodNumber>;
145
+ mediapipe: z.ZodOptional<z.ZodObject<{
146
+ deviceId: z.ZodString;
147
+ mirror: z.ZodBoolean;
148
+ face: z.ZodBoolean;
149
+ hands: z.ZodBoolean;
150
+ body: z.ZodBoolean;
151
+ delegate: z.ZodEnum<{
152
+ CPU: "CPU";
153
+ GPU: "GPU";
154
+ }>;
155
+ }, z.core.$strip>>;
156
+ }, z.core.$strip>>;
157
+ }, z.core.$strip>;
158
+ /** Network body master switch, with source and port mirrors for older clients. */
159
+ export declare const PoseSettingsSchema: z.ZodObject<{
160
+ enabled: z.ZodBoolean;
161
+ source: z.ZodEnum<{
162
+ vmc: "vmc";
163
+ mocopi: "mocopi";
164
+ }>;
165
+ port: z.ZodNumber;
166
+ }, z.core.$strip>;
167
+ /** The curated API response; absent capability fields stay absent on older hosts. */
168
+ export declare const SettingsSchema: z.ZodObject<{
169
+ lipSync: z.ZodOptional<z.ZodObject<{
170
+ enabled: z.ZodBoolean;
171
+ deviceId: z.ZodString;
172
+ gain: z.ZodNumber;
173
+ noiseGate: z.ZodNumber;
174
+ smoothing: z.ZodNumber;
175
+ }, z.core.$strip>>;
176
+ controller: z.ZodOptional<z.ZodObject<{
177
+ enabled: z.ZodBoolean;
178
+ profiles: z.ZodArray<z.ZodObject<{
179
+ slot: z.ZodNumber;
180
+ deviceId: z.ZodString;
181
+ name: z.ZodOptional<z.ZodString>;
182
+ deadZone: z.ZodOptional<z.ZodNumber>;
183
+ }, z.core.$strip>>;
184
+ }, z.core.$strip>>;
185
+ window: z.ZodObject<{
186
+ alwaysOnTop: z.ZodBoolean;
187
+ stageSize: z.ZodOptional<z.ZodObject<{
188
+ width: z.ZodNumber;
189
+ height: z.ZodNumber;
190
+ }, z.core.$strip>>;
191
+ }, z.core.$strip>;
192
+ ui: z.ZodObject<{
193
+ trayVisible: z.ZodBoolean;
194
+ }, z.core.$strip>;
195
+ performance: z.ZodObject<{
196
+ showFps: z.ZodBoolean;
197
+ fpsLimit: z.ZodNumber;
198
+ effectsQuality: z.ZodEnum<{
199
+ high: "high";
200
+ low: "low";
201
+ medium: "medium";
202
+ }>;
203
+ renderScale: z.ZodOptional<z.ZodNumber>;
204
+ live2dEngine: z.ZodOptional<z.ZodEnum<{
205
+ pixi: "pixi";
206
+ three: "three";
207
+ }>>;
208
+ }, z.core.$strip>;
209
+ tracking: z.ZodObject<{
210
+ enabled: z.ZodBoolean;
211
+ source: z.ZodEnum<{
212
+ "persona-ios": "persona-ios";
213
+ ifacialmocap: "ifacialmocap";
214
+ "vts-ios": "vts-ios";
215
+ }>;
216
+ sources: z.ZodArray<z.ZodObject<{
217
+ id: z.ZodString;
218
+ kind: z.ZodEnum<{
219
+ "persona-ios": "persona-ios";
220
+ ifacialmocap: "ifacialmocap";
221
+ "vts-ios": "vts-ios";
222
+ vmc: "vmc";
223
+ mocopi: "mocopi";
224
+ mediapipe: "mediapipe";
225
+ }>;
226
+ name: z.ZodString;
227
+ enabled: z.ZodBoolean;
228
+ phoneIp: z.ZodNullable<z.ZodString>;
229
+ port: z.ZodNullable<z.ZodNumber>;
230
+ mediapipe: z.ZodOptional<z.ZodObject<{
231
+ deviceId: z.ZodString;
232
+ mirror: z.ZodBoolean;
233
+ face: z.ZodBoolean;
234
+ hands: z.ZodBoolean;
235
+ body: z.ZodBoolean;
236
+ delegate: z.ZodEnum<{
237
+ CPU: "CPU";
238
+ GPU: "GPU";
239
+ }>;
240
+ }, z.core.$strip>>;
241
+ }, z.core.$strip>>;
242
+ }, z.core.$strip>;
243
+ pose: z.ZodObject<{
244
+ enabled: z.ZodBoolean;
245
+ source: z.ZodEnum<{
246
+ vmc: "vmc";
247
+ mocopi: "mocopi";
248
+ }>;
249
+ port: z.ZodNumber;
250
+ }, z.core.$strip>;
251
+ }, z.core.$strip>;
252
+ /** Allowed settings mutations; unknown and read-only keys are stripped at every level. */
253
+ export declare const SettingsPatchSchema: z.ZodObject<{
254
+ lipSync: z.ZodOptional<z.ZodObject<{
255
+ enabled: z.ZodOptional<z.ZodBoolean>;
256
+ deviceId: z.ZodOptional<z.ZodString>;
257
+ gain: z.ZodOptional<z.ZodNumber>;
258
+ noiseGate: z.ZodOptional<z.ZodNumber>;
259
+ smoothing: z.ZodOptional<z.ZodNumber>;
260
+ }, z.core.$strip>>;
261
+ controller: z.ZodOptional<z.ZodObject<{
262
+ enabled: z.ZodOptional<z.ZodBoolean>;
263
+ }, z.core.$strip>>;
264
+ window: z.ZodOptional<z.ZodObject<{
265
+ alwaysOnTop: z.ZodOptional<z.ZodBoolean>;
266
+ }, z.core.$strip>>;
267
+ ui: z.ZodOptional<z.ZodObject<{
268
+ trayVisible: z.ZodOptional<z.ZodBoolean>;
269
+ }, z.core.$strip>>;
270
+ performance: z.ZodOptional<z.ZodObject<{
271
+ showFps: z.ZodOptional<z.ZodBoolean>;
272
+ fpsLimit: z.ZodOptional<z.ZodNumber>;
273
+ effectsQuality: z.ZodOptional<z.ZodEnum<{
274
+ high: "high";
275
+ low: "low";
276
+ medium: "medium";
277
+ }>>;
278
+ renderScale: z.ZodOptional<z.ZodNumber>;
279
+ live2dEngine: z.ZodOptional<z.ZodEnum<{
280
+ pixi: "pixi";
281
+ three: "three";
282
+ }>>;
283
+ }, z.core.$strip>>;
284
+ }, z.core.$strip>;
@@ -0,0 +1,125 @@
1
+ import * as z from 'zod';
2
+ import { CONTROLLER_DEAD_ZONE_MAX } from "../../values/controller.js";
3
+ import { LIP_SYNC_GAIN_MAX, LIP_SYNC_MAX_SAMPLES, LIP_SYNC_MFCC_COEFFICIENTS, LIP_SYNC_MIN_SAMPLES, LIP_SYNC_NOISE_GATE_MIN, LIP_SYNC_PHONEMES, LIP_SYNC_SMOOTHING_MAX, } from "../../values/lipsync.js";
4
+ import { LOCALES } from "../../values/locale.js";
5
+ import { EFFECTS_QUALITY_LEVELS, LIVE2D_ENGINES, MEDIAPIPE_DELEGATES, POSE_SOURCE_IDS, TRACKING_SOURCE_IDS, TRACKING_SOURCE_KINDS, } from "../types.js";
6
+ /** Shared field definitions; parsing validates values without adding defaults or healing them. */
7
+ export const PortSchema = z.number().int().min(1).max(65535).describe('Network port, from 1 through 65535.');
8
+ export const Live2DEngineSchema = z.enum(LIVE2D_ENGINES).describe('Engine used to render Live2D models.');
9
+ export const EffectsQualitySchema = z.enum(EFFECTS_QUALITY_LEVELS).describe('Scene and layer effects quality tier.');
10
+ export const LanguageSettingSchema = z
11
+ .enum(['system', ...LOCALES])
12
+ .describe('Interface language, or system to follow the operating system.');
13
+ export const ThemeSettingSchema = z
14
+ .enum(['system', 'light', 'dark'])
15
+ .describe('Interface appearance, or system to follow the operating system.');
16
+ /** Microphone configuration shared by desktop persistence and the plugin API. */
17
+ export const LipSyncConfigSchema = z.object({
18
+ enabled: z.boolean().describe('Enable microphone lip sync.'),
19
+ deviceId: z.string().describe('Microphone device identifier; an empty string selects the system default.'),
20
+ gain: z.number().min(0).max(LIP_SYNC_GAIN_MAX).describe('Microphone input gain in dB.'),
21
+ noiseGate: z.number().min(LIP_SYNC_NOISE_GATE_MIN).max(0).describe('Silence threshold in dBFS.'),
22
+ smoothing: z.number().min(0).max(LIP_SYNC_SMOOTHING_MAX).describe('Volume and vowel smoothing in seconds.'),
23
+ });
24
+ /** Saved local microphone calibration; raw samples never cross the settings API. */
25
+ export const LipSyncProfileSchema = z.object({
26
+ deviceId: z.string().min(1).describe('Identifier of the calibrated microphone.'),
27
+ deviceLabel: z.string().describe('Microphone label captured when calibrating.'),
28
+ updatedAt: z.number().min(0).describe('Calibration timestamp in milliseconds since the Unix epoch.'),
29
+ samples: z
30
+ .record(z.enum(LIP_SYNC_PHONEMES), z
31
+ .array(z.array(z.number().describe('Finite MFCC coefficient.')).length(LIP_SYNC_MFCC_COEFFICIENTS))
32
+ .min(LIP_SYNC_MIN_SAMPLES)
33
+ .max(LIP_SYNC_MAX_SAMPLES))
34
+ .describe('Twelve to forty MFCC samples per phoneme, each containing twelve finite coefficients.'),
35
+ });
36
+ /** Webcam camera selection and enabled inference tasks. */
37
+ export const MediaPipeConfigSchema = z.object({
38
+ deviceId: z.string().describe('Camera device identifier; an empty string selects the system default.'),
39
+ mirror: z.boolean().describe('Mirror the webcam tracking input.'),
40
+ face: z.boolean().describe('Enable webcam face tracking.'),
41
+ hands: z.boolean().describe('Enable webcam hand tracking.'),
42
+ body: z.boolean().describe('Enable webcam body tracking.'),
43
+ delegate: z.enum(MEDIAPIPE_DELEGATES).describe('Processor used for webcam inference.'),
44
+ });
45
+ /** One configured tracking source; several may run concurrently. */
46
+ export const TrackingSourceConfigSchema = z.object({
47
+ id: z.string().min(1).describe('Stable source identifier used by scene model bindings.'),
48
+ kind: z.enum(TRACKING_SOURCE_KINDS).describe('Tracking protocol or webcam source kind.'),
49
+ name: z.string().describe('User label; an empty string displays the source kind label.'),
50
+ enabled: z.boolean().describe('Enable this tracking source.'),
51
+ phoneIp: z.string().nullable().describe('Pinned face sender IP, or null to accept any unclaimed sender.'),
52
+ port: PortSchema.nullable().describe('UDP receive port, or null for protocols with a fixed port.'),
53
+ mediapipe: MediaPipeConfigSchema.optional().describe('Camera configuration, present only for a webcam source.'),
54
+ });
55
+ /** Public controller profile fields; desktop persistence adds local hardware metadata. */
56
+ export const ControllerProfileInfoSchema = z.object({
57
+ slot: z.number().int().positive().describe('Positive safe-integer profile identifier, independent of browser index.'),
58
+ deviceId: z.string().describe('Controller device identifier.'),
59
+ name: z.string().optional().describe('User-assigned controller label.'),
60
+ deadZone: z
61
+ .number()
62
+ .min(0)
63
+ .max(CONTROLLER_DEAD_ZONE_MAX)
64
+ .optional()
65
+ .describe('Radial stick dead zone; absent until tuned.'),
66
+ });
67
+ /** Controller settings exposed to remote editors. */
68
+ export const ControllerConfigSchema = z.object({
69
+ enabled: z.boolean().describe('Enable controller input.'),
70
+ profiles: z.array(ControllerProfileInfoSchema).describe('Saved controller profiles and per-device tuning.'),
71
+ });
72
+ /** Shared performance fields; desktop healing snaps numeric presets after API patches. */
73
+ export const PerformanceSettingsSchema = z.object({
74
+ showFps: z.boolean().describe('Show the frame-rate overlay.'),
75
+ fpsLimit: z.number().describe('Frame-rate cap; zero is unlimited. The desktop snaps changes to supported presets.'),
76
+ effectsQuality: EffectsQualitySchema,
77
+ renderScale: z.number().describe('Render resolution multiplier. The desktop snaps changes to supported presets.'),
78
+ live2dEngine: Live2DEngineSchema,
79
+ });
80
+ /** Stage content dimensions in device-independent pixels. */
81
+ export const StageSizeSchema = z.object({
82
+ width: z.number().describe('Stage content width in device-independent pixels.'),
83
+ height: z.number().describe('Stage content height in device-independent pixels.'),
84
+ });
85
+ /** Face master switch and sources, with the legacy source mirror for older clients. */
86
+ export const TrackingSettingsSchema = z.object({
87
+ enabled: z.boolean().describe('Enable network face tracking; webcam tasks use their own switches.'),
88
+ source: z.enum(TRACKING_SOURCE_IDS).describe('Legacy mirror of the first network face source kind.'),
89
+ sources: z.array(TrackingSourceConfigSchema).describe('Configured face, body, and webcam sources.'),
90
+ });
91
+ /** Network body master switch, with source and port mirrors for older clients. */
92
+ export const PoseSettingsSchema = z.object({
93
+ enabled: z.boolean().describe('Enable network body tracking; webcam tasks use their own switches.'),
94
+ source: z.enum(POSE_SOURCE_IDS).describe('Legacy mirror of the first network body source kind.'),
95
+ port: PortSchema.describe('Legacy mirror of the first network body source receive port.'),
96
+ });
97
+ /** The curated API response; absent capability fields stay absent on older hosts. */
98
+ export const SettingsSchema = z.object({
99
+ lipSync: LipSyncConfigSchema.optional().describe('Microphone settings; absent on hosts without microphone lip sync.'),
100
+ controller: ControllerConfigSchema.optional().describe('Controller settings; absent on older hosts.'),
101
+ window: z
102
+ .object({
103
+ alwaysOnTop: z.boolean().describe('Keep the control-panel window above other windows.'),
104
+ stageSize: StageSizeSchema.optional().describe('Read-only stage content size; absent on older hosts.'),
105
+ })
106
+ .describe('Public window configuration and read-only stage dimensions.'),
107
+ ui: z
108
+ .object({ trayVisible: z.boolean().describe('Show the tray or menu bar icon.') })
109
+ .describe('Public interface preferences.'),
110
+ performance: PerformanceSettingsSchema.partial({ renderScale: true, live2dEngine: true }).describe('Renderer settings; renderScale and live2dEngine are absent on older hosts.'),
111
+ tracking: TrackingSettingsSchema.describe('Network face master switch and all configured tracking sources.'),
112
+ pose: PoseSettingsSchema.describe('Network body master switch and legacy source mirrors.'),
113
+ });
114
+ /** Allowed settings mutations; unknown and read-only keys are stripped at every level. */
115
+ export const SettingsPatchSchema = z.object({
116
+ lipSync: LipSyncConfigSchema.partial().optional().describe('Microphone fields to update.'),
117
+ controller: ControllerConfigSchema.pick({ enabled: true }).partial().optional().describe('Controller master switch.'),
118
+ window: SettingsSchema.shape.window
119
+ .pick({ alwaysOnTop: true })
120
+ .partial()
121
+ .optional()
122
+ .describe('Control-panel window fields to update.'),
123
+ ui: SettingsSchema.shape.ui.partial().optional().describe('Public interface fields to update.'),
124
+ performance: PerformanceSettingsSchema.partial().optional().describe('Renderer fields to update.'),
125
+ });
@@ -1,10 +1,11 @@
1
- import type { ControllerConfig } from '../values/controller.ts';
1
+ import type * as z from 'zod';
2
2
  import type { EFFECT_BLEND_MODES, EFFECT_SCOPES } from '../values/effect-schema.ts';
3
3
  import type { SCENE_TRANSITION_TYPES } from '../values/scene-transition.ts';
4
+ import type { SettingsPatchSchema, SettingsSchema } from './schemas/settings.ts';
4
5
  import { type ArkitInputName } from '../values/arkit.ts';
5
6
  import { type BaseControllerInputName, type ControllerInputName, type ControllerMovementConfig } from '../values/controller.ts';
6
7
  import { type HandInputName } from '../values/hands.ts';
7
- import { type LipSyncConfig, type LipSyncMode, type VoiceInputName } from '../values/lipsync.ts';
8
+ import { type LipSyncMode, type VoiceInputName } from '../values/lipsync.ts';
8
9
  export type ModelFormat = 'live2d' | 'vrm';
9
10
  /** Where an item came from: shipped with the app, or added by the user. */
10
11
  export type ContentOrigin = 'bundled' | 'user';
@@ -13,7 +14,7 @@ export type ContentOrigin = 'bundled' | 'user';
13
14
  * ever an environment map, and a `.vmd` splits by content — `cameraMotion` for one that
14
15
  * frames a shot, `animation` for one that drives a rig.
15
16
  */
16
- export declare const ASSET_KINDS: readonly ["image", "video", "prop", "ibl", "lut", "animation", "cameraMotion"];
17
+ export declare const ASSET_KINDS: readonly ["image", "video", "audio", "prop", "ibl", "lut", "animation", "cameraMotion"];
17
18
  export type AssetKind = (typeof ASSET_KINDS)[number];
18
19
  /** Narrow a kind string to the set the asset registry owns — models and effects have their own. */
19
20
  export declare function isAssetKind(v: unknown): v is AssetKind;
@@ -302,8 +303,9 @@ export interface SceneBackground {
302
303
  export interface OrbitTransform {
303
304
  azimuth: number;
304
305
  elevation: number;
306
+ /** Signed target-to-eye offset along camera-local +Z; negative values cross the target without reversing the view. */
305
307
  distance: number;
306
- /** Pivot the camera orbits and looks at. Panning moves it along the camera's own axes, so it leaves the XY plane. */
308
+ /** Framing pivot. Panning moves it along the camera's own axes, so it leaves the XY plane. */
307
309
  targetX: number;
308
310
  targetY: number;
309
311
  targetZ: number;
@@ -311,13 +313,23 @@ export interface OrbitTransform {
311
313
  /** Scene-level VRM camera. `orbit: null` = never framed — the first VRM load frames it from model height. */
312
314
  export interface SceneCamera {
313
315
  orbit: OrbitTransform | null;
316
+ /** Vertical field of view in degrees, 1–179. */
314
317
  fov: number;
318
+ /** Rotation about camera-local +Z in radians; absent on older hosts, default 0. */
319
+ roll?: number;
320
+ /** Absent on older hosts; defaults to perspective. */
321
+ projection?: 'perspective' | 'orthographic';
315
322
  /**
316
323
  * A `cameraMotion` asset driving the camera, or null for the user's own orbit. While one
317
- * is set it overrides `orbit` and `fov` every frame; neither field is written back.
324
+ * is set it overrides the framing every frame without writing it back.
318
325
  */
319
326
  clipAssetId: string | null;
320
327
  }
328
+ /**
329
+ * The slice of {@link SceneCamera} a saved pose snapshots and applies — a framing, never
330
+ * the clip: applying a pose leaves whatever camera motion the scene has running.
331
+ */
332
+ export type CameraPose = Pick<SceneCamera, 'orbit' | 'fov' | 'roll' | 'projection'>;
321
333
  export type SceneLightType = 'directional' | 'point' | 'ambient';
322
334
  /**
323
335
  * Per-light shadow tier. `off` is the old `castShadow: false`; the rest raise
@@ -969,10 +981,10 @@ export interface HotkeyState extends HotkeyConfig {
969
981
  registered: string[];
970
982
  }
971
983
  /** Action kinds an app automation can carry today; servers may send kinds newer than this list. */
972
- export declare const AUTOMATION_ACTION_KINDS: readonly ["effect-toggle", "effect-params", "camera-pose", "reset-camera", "layer-visibility", "stream-mode", "switch-scene", "toggle-expression", "play-motion", "remove-all-expressions", "load-model", "model-position", "delay"];
984
+ export declare const AUTOMATION_ACTION_KINDS: readonly ["effect-toggle", "effect-params", "effect-clip", "camera-pose", "reset-camera", "play-camera-motion", "stop-camera-motion", "layer-visibility", "stream-mode", "switch-scene", "toggle-expression", "play-motion", "play-audio", "audio-control", "remove-all-expressions", "load-model", "model-position"];
973
985
  export type AutomationActionKind = (typeof AUTOMATION_ACTION_KINDS)[number];
974
- /** Kinds that end an action batch: the actions after one wait until it finishes. */
975
- export declare const AUTOMATION_BOUNDARY_KINDS: readonly ["switch-scene", "load-model", "delay"];
986
+ /** Setup kinds that finish loading before the sequence clock starts. */
987
+ export declare const AUTOMATION_BOUNDARY_KINDS: readonly ["switch-scene", "load-model"];
976
988
  export type AutomationBoundaryKind = (typeof AUTOMATION_BOUNDARY_KINDS)[number];
977
989
  /**
978
990
  * One app automation as clients see it. Action kinds and scene targets drive derived
@@ -1099,58 +1111,9 @@ export interface StageSize {
1099
1111
  height: number;
1100
1112
  }
1101
1113
  /** The curated settings surface the API exposes — never the raw store shape. */
1102
- export interface Settings {
1103
- /** Absent on hosts without microphone lipsync. */
1104
- lipSync?: LipSyncConfig;
1105
- /** Absent on hosts without remote controller management. */
1106
- controller?: ControllerConfig;
1107
- /** `alwaysOnTop` floats the control-panel window, never the stage. */
1108
- window: {
1109
- alwaysOnTop: boolean;
1110
- /** Read-only; absent on older desktops. */
1111
- stageSize?: StageSize;
1112
- };
1113
- ui: {
1114
- trayVisible: boolean;
1115
- };
1116
- performance: {
1117
- showFps: boolean;
1118
- fpsLimit: number;
1119
- effectsQuality: EffectsQuality;
1120
- /** Absent on older hosts. */
1121
- renderScale?: number;
1122
- live2dEngine?: Live2DEngine;
1123
- };
1124
- /** `enabled` gates network face sources; `source` mirrors the first one's kind for old clients. */
1125
- tracking: {
1126
- enabled: boolean;
1127
- source: TrackingSourceId;
1128
- sources: TrackingSourceConfig[];
1129
- };
1130
- /** `enabled` gates network body sources; `source`/`port` mirror the first one's values for old clients. */
1131
- pose: {
1132
- enabled: boolean;
1133
- source: PoseSourceId;
1134
- port: number;
1135
- };
1136
- }
1137
- export interface SettingsPatch {
1138
- lipSync?: Partial<LipSyncConfig>;
1139
- controller?: Partial<Pick<ControllerConfig, 'enabled'>>;
1140
- window?: {
1141
- alwaysOnTop?: boolean;
1142
- };
1143
- ui?: {
1144
- trayVisible?: boolean;
1145
- };
1146
- performance?: {
1147
- showFps?: boolean;
1148
- fpsLimit?: number;
1149
- effectsQuality?: EffectsQuality;
1150
- renderScale?: number;
1151
- live2dEngine?: Live2DEngine;
1152
- };
1153
- }
1114
+ export type Settings = z.infer<typeof SettingsSchema>;
1115
+ /** Allowed settings mutations; other fields have dedicated API methods or are desktop-private. */
1116
+ export type SettingsPatch = z.infer<typeof SettingsPatchSchema>;
1154
1117
  /**
1155
1118
  * From VTS's own registry (`FaceTrackingParamInfo.paramNameDictionary`), which is both the
1156
1119
  * range its editor seeds a new parameter with and what every `.vtube.json` was authored
@@ -5,13 +5,13 @@ import { ARKIT_INPUT_NAMES } from "../values/arkit.js";
5
5
  import { BASE_CONTROLLER_INPUT_NAMES, BASE_CONTROLLER_INPUT_RANGES, controllerInputRange, isControllerInputName, } from "../values/controller.js";
6
6
  import { isOneOf, keysOf } from "../values/guards.js";
7
7
  import { HAND_INPUT_NAMES, HAND_INPUT_RANGES } from "../values/hands.js";
8
- import { VOICE_INPUT_NAMES, VOICE_INPUT_RANGES, } from "../values/lipsync.js";
8
+ import { VOICE_INPUT_NAMES, VOICE_INPUT_RANGES } from "../values/lipsync.js";
9
9
  /**
10
10
  * How a registered file is labelled. Wider than an object's content kinds: `.hdr` is only
11
11
  * ever an environment map, and a `.vmd` splits by content — `cameraMotion` for one that
12
12
  * frames a shot, `animation` for one that drives a rig.
13
13
  */
14
- export const ASSET_KINDS = ['image', 'video', 'prop', 'ibl', 'lut', 'animation', 'cameraMotion'];
14
+ export const ASSET_KINDS = ['image', 'video', 'audio', 'prop', 'ibl', 'lut', 'animation', 'cameraMotion'];
15
15
  /** Narrow a kind string to the set the asset registry owns — models and effects have their own. */
16
16
  export function isAssetKind(v) {
17
17
  return isOneOf(v, ASSET_KINDS);
@@ -47,23 +47,26 @@ export function isAppCapability(v) {
47
47
  export const AUTOMATION_ACTION_KINDS = [
48
48
  'effect-toggle',
49
49
  'effect-params',
50
+ 'effect-clip',
50
51
  'camera-pose',
51
52
  'reset-camera',
53
+ 'play-camera-motion',
54
+ 'stop-camera-motion',
52
55
  'layer-visibility',
53
56
  'stream-mode',
54
57
  'switch-scene',
55
58
  'toggle-expression',
56
59
  'play-motion',
60
+ 'play-audio',
61
+ 'audio-control',
57
62
  'remove-all-expressions',
58
63
  'load-model',
59
64
  'model-position',
60
- 'delay',
61
65
  ];
62
- /** Kinds that end an action batch: the actions after one wait until it finishes. */
66
+ /** Setup kinds that finish loading before the sequence clock starts. */
63
67
  export const AUTOMATION_BOUNDARY_KINDS = [
64
68
  'switch-scene',
65
69
  'load-model',
66
- 'delay',
67
70
  ];
68
71
  // ---- Settings ------------------------------------------------------------------
69
72
  export const LIVE2D_ENGINES = ['pixi', 'three'];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@laplace.live/persona-sdk",
3
- "version": "1.7.0",
3
+ "version": "1.8.0",
4
4
  "description": "TypeScript SDK and wire schema for the LAPLACE Persona plugin API",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -34,7 +34,7 @@
34
34
  "node": ">=24"
35
35
  },
36
36
  "dependencies": {
37
- "zod": "^4.6.1"
37
+ "zod": "^4.6.2"
38
38
  },
39
39
  "scripts": {
40
40
  "build": "rimraf dist && tsc -p tsconfig.build.json",