@laplace.live/persona-sdk 0.18.0 → 1.0.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.
Files changed (41) hide show
  1. package/README.md +1 -1
  2. package/dist/client/client.d.ts +1 -1
  3. package/dist/index.d.ts +7 -1
  4. package/dist/index.js +7 -1
  5. package/dist/values/bindings.d.ts +43 -0
  6. package/dist/values/bindings.js +1 -0
  7. package/dist/values/controller.d.ts +109 -0
  8. package/dist/values/controller.js +167 -0
  9. package/dist/values/curve.d.ts +7 -8
  10. package/dist/values/curve.js +18 -8
  11. package/dist/values/edit-history.d.ts +21 -0
  12. package/dist/values/edit-history.js +62 -0
  13. package/dist/values/effect-schema.d.ts +2 -2
  14. package/dist/values/effect-schema.js +1 -2
  15. package/dist/values/gltf-extensions.d.ts +20 -0
  16. package/dist/values/gltf-extensions.js +68 -0
  17. package/dist/values/guards.d.ts +2 -0
  18. package/dist/values/guards.js +5 -0
  19. package/dist/values/hotkeys.d.ts +6 -0
  20. package/dist/values/hotkeys.js +11 -0
  21. package/dist/values/labels.d.ts +2 -0
  22. package/dist/values/labels.js +5 -1
  23. package/dist/values/limits.d.ts +25 -0
  24. package/dist/values/limits.js +33 -0
  25. package/dist/values/locale.d.ts +2 -0
  26. package/dist/values/model-info.d.ts +66 -0
  27. package/dist/values/model-info.js +1 -0
  28. package/dist/values/stage-info.d.ts +27 -0
  29. package/dist/values/stage-info.js +1 -0
  30. package/dist/values/vrm-bindings.d.ts +14 -0
  31. package/dist/values/vrm-bindings.js +20 -0
  32. package/dist/wire/methods.d.ts +151 -5
  33. package/dist/wire/schemas.d.ts +46 -5
  34. package/dist/wire/schemas.js +12 -0
  35. package/dist/wire/types.d.ts +69 -58
  36. package/dist/wire/types.js +39 -11
  37. package/package.json +2 -16
  38. package/dist/effects.d.ts +0 -79
  39. package/dist/effects.js +0 -6
  40. package/dist/values/custom-effect.d.ts +0 -44
  41. package/dist/values/custom-effect.js +0 -136
@@ -0,0 +1,68 @@
1
+ // Which glTF extensions each loader honours — `loadVrm` for avatars, `loadGltf` for props and
2
+ // sets (`renderer/vrm/loader.ts`) — so the info rows can split a file's declared list. The test
3
+ // pins the glTF lists to three's own table and the VRM list to three-vrm.
4
+ /** The app's wardrobe extension, read on avatars — `renderer/vrm/gltf-outfit.ts`. */
5
+ export const LAPLACE_OUTFIT = 'LAPLACE_outfit';
6
+ /** The app's room extension, read on props and sets — `renderer/vrm/gltf-extension.ts`. */
7
+ export const LAPLACE_ENVIRONMENT = 'LAPLACE_environment';
8
+ /** VRM's own — every name `VRMLoaderPlugin`'s default sub-plugins read; `VRM` is 0.x. */
9
+ const VRM_EXTENSIONS = [
10
+ 'VRM',
11
+ 'VRMC_vrm',
12
+ 'VRMC_springBone',
13
+ 'VRMC_springBone_extended_collider',
14
+ 'VRMC_node_constraint',
15
+ 'VRMC_materials_mtoon',
16
+ 'VRMC_materials_hdr_emissiveMultiplier',
17
+ ];
18
+ /** `GLTFLoader` extensions that throw at parse without a decoder — and the app sets none. */
19
+ export const DECODER_EXTENSIONS = [
20
+ 'KHR_draco_mesh_compression',
21
+ 'KHR_texture_basisu',
22
+ 'EXT_meshopt_compression',
23
+ 'KHR_meshopt_compression',
24
+ ];
25
+ /** The rest of `GLTFLoader`'s own `EXTENSIONS` table, which either loader renders as authored. */
26
+ const RENDERABLE = [
27
+ 'KHR_binary_glTF',
28
+ 'KHR_lights_punctual',
29
+ 'KHR_materials_clearcoat',
30
+ 'KHR_materials_dispersion',
31
+ 'KHR_materials_ior',
32
+ 'KHR_materials_sheen',
33
+ 'KHR_materials_specular',
34
+ 'KHR_materials_transmission',
35
+ 'KHR_materials_iridescence',
36
+ 'KHR_materials_anisotropy',
37
+ 'KHR_materials_unlit',
38
+ 'KHR_materials_volume',
39
+ 'KHR_texture_transform',
40
+ 'KHR_mesh_quantization',
41
+ 'KHR_materials_emissive_strength',
42
+ 'EXT_materials_bump',
43
+ 'EXT_texture_webp',
44
+ 'EXT_texture_avif',
45
+ 'EXT_mesh_gpu_instancing',
46
+ ];
47
+ /** Per loader; the app's own extensions ride with the loader that reads them. */
48
+ export const SUPPORTED_EXTENSIONS = {
49
+ vrm: new Set([...VRM_EXTENSIONS, LAPLACE_OUTFIT, ...RENDERABLE]),
50
+ gltf: new Set([LAPLACE_ENVIRONMENT, ...RENDERABLE]),
51
+ };
52
+ /** The extensions a glTF root declares (`extensionsUsed`) or carries itself, deduped in file order. */
53
+ export function declaredExtensions(json) {
54
+ const used = Array.isArray(json.extensionsUsed)
55
+ ? json.extensionsUsed.filter((e) => typeof e === 'string')
56
+ : [];
57
+ // Plus the root's own keys: three honours an undeclared root extension, and a VRM is detected by one.
58
+ const root = typeof json.extensions === 'object' && json.extensions !== null ? Object.keys(json.extensions) : [];
59
+ return [...new Set([...used, ...root])];
60
+ }
61
+ /** A file's declared extensions split by what its loader honours, each side in file order. */
62
+ export function partitionExtensions(declared, path) {
63
+ const supported = [];
64
+ const unsupported = [];
65
+ for (const name of declared)
66
+ (SUPPORTED_EXTENSIONS[path].has(name) ? supported : unsupported).push(name);
67
+ return { supported, unsupported };
68
+ }
@@ -6,3 +6,5 @@ export declare function isFiniteNumber(v: unknown): v is number;
6
6
  export declare function finiteOr(v: unknown, fallback: number): number;
7
7
  /** The string when it has non-whitespace content (returned untrimmed), else null. */
8
8
  export declare function nonEmptyString(v: unknown): string | null;
9
+ /** Same keys, same values, one level deep — for snapshots rebuilt every tick, where identity says nothing. */
10
+ export declare function shallowEqualRecord(a: Partial<Record<string, unknown>>, b: Partial<Record<string, unknown>>): boolean;
@@ -15,3 +15,8 @@ export function finiteOr(v, fallback) {
15
15
  export function nonEmptyString(v) {
16
16
  return typeof v === 'string' && v.trim() !== '' ? v : null;
17
17
  }
18
+ /** Same keys, same values, one level deep — for snapshots rebuilt every tick, where identity says nothing. */
19
+ export function shallowEqualRecord(a, b) {
20
+ const keys = Object.keys(a);
21
+ return keys.length === Object.keys(b).length && keys.every(k => a[k] === b[k]);
22
+ }
@@ -1,2 +1,8 @@
1
1
  /** True when the combo carries a command modifier and so may be registered globally. */
2
2
  export declare function hasCommandModifier(accelerator: string | null): boolean;
3
+ /**
4
+ * True when `global` can apply: a command-modifier combo registers with the OS, and a controller
5
+ * combo is observed directly, so neither needs Persona focused. Classifies stored combos, which
6
+ * every writer canonicalizes; it does not validate one.
7
+ */
8
+ export declare function canRunInBackground(accelerator: string | null): boolean;
@@ -1,5 +1,6 @@
1
1
  // Hotkey rules every consumer must agree on: main decides OS registration by them, and a
2
2
  // client reading `Hotkey.global` off the wire needs the same rule to know the flag can apply.
3
+ import { isControllerTriggerName } from "./controller.js";
3
4
  // Shift is deliberately absent: Shift+letter is how text is typed, and a global
4
5
  // registration consumes the combo system-wide (VTS can allow it because it observes).
5
6
  const COMMAND_MODIFIERS = ['Control', 'Alt', 'Super'];
@@ -9,3 +10,13 @@ export function hasCommandModifier(accelerator) {
9
10
  return false;
10
11
  return accelerator.split('+').some(t => COMMAND_MODIFIERS.includes(t));
11
12
  }
13
+ /**
14
+ * True when `global` can apply: a command-modifier combo registers with the OS, and a controller
15
+ * combo is observed directly, so neither needs Persona focused. Classifies stored combos, which
16
+ * every writer canonicalizes; it does not validate one.
17
+ */
18
+ export function canRunInBackground(accelerator) {
19
+ if (accelerator === null)
20
+ return false;
21
+ return hasCommandModifier(accelerator) || accelerator.split('+').every(isControllerTriggerName);
22
+ }
@@ -1,4 +1,6 @@
1
1
  import type { ShortcutInfo } from '../wire/types.ts';
2
+ /** Last path segment, tolerating both `/` and `\` separators. */
3
+ export declare function fileBasename(path: string): string;
2
4
  /**
3
5
  * Display label for a model-relative motion/expression/animation file: the
4
6
  * basename with its format extension stripped. Cubism entries carry no `Name`,
@@ -1,10 +1,14 @@
1
+ /** Last path segment, tolerating both `/` and `\` separators. */
2
+ export function fileBasename(path) {
3
+ return path.split(/[/\\]/).at(-1) ?? path;
4
+ }
1
5
  /**
2
6
  * Display label for a model-relative motion/expression/animation file: the
3
7
  * basename with its format extension stripped. Cubism entries carry no `Name`,
4
8
  * so the basename is the only identifier there is.
5
9
  */
6
10
  export function motionLabel(file) {
7
- const base = file.split(/[\\/]/).pop() ?? file;
11
+ const base = fileBasename(file);
8
12
  return base.replace(/\.motion3\.json$|\.exp3\.json$|\.vrma$|\.json$/i, '') || base;
9
13
  }
10
14
  // English fallbacks; localizing clients keep their own map and use these for unknown kinds.
@@ -2,6 +2,8 @@ import type { AssetRef, Attach, EnvironmentLook, ModelFormat, MToonTuning, Objec
2
2
  export declare function clamp(v: number, min: number, max: number): number;
3
3
  /** Clamp to the unit interval. */
4
4
  export declare function clamp01(v: number): number;
5
+ /** Wrap an angle into (-π, π]. */
6
+ export declare function wrapAngle(a: number): number;
5
7
  export declare const DEFAULT_LIVE2D_PLACEMENT: ScreenPlacement;
6
8
  export declare const DEFAULT_VRM_PLACEMENT: VrmPlacement;
7
9
  /** Scene colors heal to 6-digit hex. */
@@ -109,6 +111,12 @@ export declare const PLACE_2D_SCALE_MIN = 0.01;
109
111
  export declare const PLACE_2D_SCALE_MAX = 50;
110
112
  export declare const PLACE_3D_SCALE_MIN = 0.01;
111
113
  export declare const PLACE_3D_SCALE_MAX = 100;
114
+ /** Web object viewport bounds, CSS px per side. */
115
+ export declare const WEB_SIZE_MIN = 16;
116
+ export declare const WEB_SIZE_MAX = 7680;
117
+ /** Web object paint-rate bounds, frames per second. */
118
+ export declare const WEB_FPS_MIN = 1;
119
+ export declare const WEB_FPS_MAX = 60;
112
120
  /** A prop is a mesh, so it only exists in the three.js scene; every other kind renders in both. */
113
121
  export declare function objectSupportsSpace(kind: ObjectContent['kind'], space: ObjectSpace): boolean;
114
122
  /** The asset an object streams from, or null for kinds that carry their source inline (web, capture). */
@@ -136,3 +144,20 @@ export declare const STORAGE_VALUE_MAX_LENGTH: number;
136
144
  export declare const STORAGE_KEYS_MAX = 256;
137
145
  /** `speech.play` URL ceiling — sized for a ~40 s WAV as a base64 `data:audio/*` payload. */
138
146
  export declare const SPEECH_URL_MAX_LENGTH = 8000000;
147
+ /**
148
+ * Narrowest the panel's field column still reads at: the three-up axis fields divide the width,
149
+ * they never wrap. The window's own floor, and the detail column's beside a dragged outliner.
150
+ */
151
+ export declare const CONTROL_MIN_WIDTH = 360;
152
+ /** Narrowest the outliner column drags to: the scene picker and a layer row's affordances still fit. */
153
+ export declare const OUTLINER_MIN_WIDTH = 260;
154
+ /** Width the outliner column opens at before anyone drags it, and the one a double-click restores. */
155
+ export declare const OUTLINER_DEFAULT_WIDTH = 288;
156
+ /**
157
+ * Where the panel splits in two: below it the detail follows the outliner in one scroller, at or
158
+ * above it each column scrolls on its own. `OUTLINER_MIN_WIDTH + CONTROL_MIN_WIDTH` and the 1 px
159
+ * split must fit inside it, or no layout satisfies both floors and the group clips instead.
160
+ */
161
+ export declare const TWO_COLUMN_WIDTH = 672;
162
+ /** 3D-stage resolution multipliers offered by both performance pickers. */
163
+ export declare const RENDER_SCALE_PRESETS: readonly [1, 0.85, 0.75, 0.66, 0.5];
@@ -6,6 +6,16 @@ export function clamp(v, min, max) {
6
6
  export function clamp01(v) {
7
7
  return v < 0 ? 0 : v > 1 ? 1 : v;
8
8
  }
9
+ /** Wrap an angle into (-π, π]. */
10
+ export function wrapAngle(a) {
11
+ const TAU = Math.PI * 2;
12
+ let r = a % TAU;
13
+ if (r > Math.PI)
14
+ r -= TAU;
15
+ if (r <= -Math.PI)
16
+ r += TAU;
17
+ return r;
18
+ }
9
19
  export const DEFAULT_LIVE2D_PLACEMENT = { x: 0, y: 0, scale: 1, rotation: 0 };
10
20
  export const DEFAULT_VRM_PLACEMENT = { x: 0, y: 0, z: 0, rotX: 0, rotY: 0, rotZ: 0, scale: 1 };
11
21
  /** Scene colors heal to 6-digit hex. */
@@ -193,6 +203,12 @@ export const PLACE_2D_SCALE_MIN = 0.01;
193
203
  export const PLACE_2D_SCALE_MAX = 50;
194
204
  export const PLACE_3D_SCALE_MIN = 0.01;
195
205
  export const PLACE_3D_SCALE_MAX = 100;
206
+ /** Web object viewport bounds, CSS px per side. */
207
+ export const WEB_SIZE_MIN = 16;
208
+ export const WEB_SIZE_MAX = 7680;
209
+ /** Web object paint-rate bounds, frames per second. */
210
+ export const WEB_FPS_MIN = 1;
211
+ export const WEB_FPS_MAX = 60;
196
212
  /** A prop is a mesh, so it only exists in the three.js scene; every other kind renders in both. */
197
213
  export function objectSupportsSpace(kind, space) {
198
214
  return kind === 'prop' ? space === '3d' : true;
@@ -233,3 +249,20 @@ export const STORAGE_VALUE_MAX_LENGTH = 64 * 1024;
233
249
  export const STORAGE_KEYS_MAX = 256;
234
250
  /** `speech.play` URL ceiling — sized for a ~40 s WAV as a base64 `data:audio/*` payload. */
235
251
  export const SPEECH_URL_MAX_LENGTH = 8_000_000;
252
+ /**
253
+ * Narrowest the panel's field column still reads at: the three-up axis fields divide the width,
254
+ * they never wrap. The window's own floor, and the detail column's beside a dragged outliner.
255
+ */
256
+ export const CONTROL_MIN_WIDTH = 360;
257
+ /** Narrowest the outliner column drags to: the scene picker and a layer row's affordances still fit. */
258
+ export const OUTLINER_MIN_WIDTH = 260;
259
+ /** Width the outliner column opens at before anyone drags it, and the one a double-click restores. */
260
+ export const OUTLINER_DEFAULT_WIDTH = 288;
261
+ /**
262
+ * Where the panel splits in two: below it the detail follows the outliner in one scroller, at or
263
+ * above it each column scrolls on its own. `OUTLINER_MIN_WIDTH + CONTROL_MIN_WIDTH` and the 1 px
264
+ * split must fit inside it, or no layout satisfies both floors and the group clips instead.
265
+ */
266
+ export const TWO_COLUMN_WIDTH = 672;
267
+ /** 3D-stage resolution multipliers offered by both performance pickers. */
268
+ export const RENDER_SCALE_PRESETS = [1, 0.85, 0.75, 0.66, 0.5];
@@ -3,6 +3,8 @@ export declare const LOCALES: readonly ["en", "ja", "zh-CN", "zh-TW"];
3
3
  export type Locale = (typeof LOCALES)[number];
4
4
  /** `ui.language` setting: an explicit locale, or follow the OS language. */
5
5
  export type LanguageSetting = 'system' | Locale;
6
+ /** `ui.theme` setting: an explicit appearance, or follow the OS. */
7
+ export type ThemeSetting = 'system' | 'light' | 'dark';
6
8
  /** Native-language display names for the language picker (deliberately untranslated). */
7
9
  export declare const LOCALE_LABELS: Record<Locale, string>;
8
10
  /** Best supported locale for a BCP 47-ish tag: exact match first, then fuzzy per language. */
@@ -0,0 +1,66 @@
1
+ import type { ContentOrigin } from '../wire/types.ts';
2
+ /** Which VRM specification the file declares. */
3
+ export type VrmSpec = '0.x' | '1.0';
4
+ /** Everything the panel reports about a Live2D model on stage. */
5
+ export interface Live2DModelInfo {
6
+ format: 'live2d';
7
+ name: string;
8
+ origin: ContentOrigin;
9
+ file: string;
10
+ /** Config sidecars found next to the entry file (`*.vtube.json`, `*.persona.json`). */
11
+ configFiles: string[];
12
+ canvas: {
13
+ width: number;
14
+ height: number;
15
+ };
16
+ /** `csmMocVersion` of the model's own moc3, or null when the header couldn't be read. */
17
+ mocVersion: number | null;
18
+ /** Highest `csmMocVersion` the vendored Core can load (`csmGetLatestMocVersion()`). */
19
+ coreMocVersion: number;
20
+ /** Packed uint32 from `csmGetVersion()`; 0 when the Core global is missing. */
21
+ coreVersion: number;
22
+ params: number;
23
+ parts: number;
24
+ /** Pixel size of each loaded texture atlas. */
25
+ textureSizes: {
26
+ width: number;
27
+ height: number;
28
+ }[];
29
+ motionGroups: number;
30
+ motionTotal: number;
31
+ expressions: number;
32
+ /** Param ids the auto eye-blink drives (the engine falls back to standard params when undeclared). */
33
+ eyeBlinkParams: string[];
34
+ /** Param ids of the model's `LipSync` group; empty means lip sync cannot move the mouth. */
35
+ lipSyncParams: string[];
36
+ /** How many distinct raw ARKit channels the rig's bindings read; 52 is full perfect sync. */
37
+ arkitInputs: number;
38
+ /** Optional subsystems that loaded, e.g. `['physics', 'pose']`. */
39
+ extras: string[];
40
+ /** Wall-clock ms the stage spent loading the model. */
41
+ loadMs: number;
42
+ }
43
+ /** What the panel reports about a VRM on stage. */
44
+ export interface VrmModelInfo {
45
+ format: 'vrm';
46
+ name: string;
47
+ origin: ContentOrigin;
48
+ file: string;
49
+ spec: VrmSpec;
50
+ specVersion: string;
51
+ /** Every glTF extension the file declares, in file order; the rows split them by stage support. */
52
+ extensions: string[];
53
+ humanBones: number;
54
+ springGroups: number;
55
+ expressions: number;
56
+ /** How many of the 52 ARKit shapes the expressions cover; 52 enables perfect-sync tracking. */
57
+ arkitExpressions: number;
58
+ authors: string[];
59
+ licenseUrl: string | null;
60
+ /** VRM 1.0 `creditNotation === 'required'`; always false for 0.x. */
61
+ creditRequired: boolean;
62
+ /** Wall-clock ms the stage spent loading the model. */
63
+ loadMs: number;
64
+ }
65
+ /** Everything the panel reports about the model currently on stage. */
66
+ export type ModelInfo = Live2DModelInfo | VrmModelInfo;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,27 @@
1
+ /** Decoded image/video pixels, or a web object's configured viewport pixels, before placement. */
2
+ export interface ObjectMediaSize {
3
+ width: number;
4
+ height: number;
5
+ }
6
+ /** One light baked into a prop asset, as the panel lists it. `index` is the override key. */
7
+ export interface BakedLightInfo {
8
+ index: number;
9
+ /** Node name from the asset; may be empty. */
10
+ name: string;
11
+ type: 'point' | 'spot' | 'directional';
12
+ /** The asset's own intensity after normalization, in scene-slider units. */
13
+ defaultIntensity: number;
14
+ }
15
+ /** Loaded asset metadata; absent dimensions/extensions mean the asset has not reported them yet. */
16
+ export interface SceneAssetInfo {
17
+ assetId: string | null;
18
+ lights: BakedLightInfo[];
19
+ extensions: string[] | null;
20
+ mediaSize: ObjectMediaSize | null;
21
+ }
22
+ /** Metadata for the active scene, tagged so a response from a previous scene can be discarded. */
23
+ export interface SceneInspection {
24
+ sceneId: string;
25
+ environment: Omit<SceneAssetInfo, 'mediaSize'>;
26
+ objects: Record<string, SceneAssetInfo>;
27
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,14 @@
1
+ /** Where a VRM binding target lands; expressions are the model's own, the rest are pose offsets. */
2
+ export type VrmBindingGroup = 'head' | 'body' | 'gaze' | 'expressions';
3
+ export interface VrmPoseTarget {
4
+ id: string;
5
+ group: Exclude<VrmBindingGroup, 'expressions'>;
6
+ /** Symmetric range in degrees. */
7
+ range: number;
8
+ }
9
+ /** The generated VRM binding targets, in degrees; each is an offset layered over the current pose. */
10
+ export declare const VRM_POSE_TARGETS: readonly VrmPoseTarget[];
11
+ /** The binding target id for a VRM expression, keeping the model author's spelling. */
12
+ export declare function vrmExpressionTarget(name: string): string;
13
+ /** The expression behind a VRM binding target id; null for a pose target. */
14
+ export declare function vrmExpressionName(id: string): string | null;
@@ -0,0 +1,20 @@
1
+ /** The generated VRM binding targets, in degrees; each is an offset layered over the current pose. */
2
+ export const VRM_POSE_TARGETS = [
3
+ { id: 'HeadYaw', group: 'head', range: 30 },
4
+ { id: 'HeadPitch', group: 'head', range: 30 },
5
+ { id: 'HeadRoll', group: 'head', range: 30 },
6
+ { id: 'BodyYaw', group: 'body', range: 15 },
7
+ { id: 'BodyPitch', group: 'body', range: 15 },
8
+ { id: 'BodyRoll', group: 'body', range: 15 },
9
+ { id: 'GazeYaw', group: 'gaze', range: 90 },
10
+ { id: 'GazePitch', group: 'gaze', range: 90 },
11
+ ];
12
+ const VRM_EXPRESSION_PREFIX = 'Expression:';
13
+ /** The binding target id for a VRM expression, keeping the model author's spelling. */
14
+ export function vrmExpressionTarget(name) {
15
+ return `${VRM_EXPRESSION_PREFIX}${name}`;
16
+ }
17
+ /** The expression behind a VRM binding target id; null for a pose target. */
18
+ export function vrmExpressionName(id) {
19
+ return id.startsWith(VRM_EXPRESSION_PREFIX) ? id.slice(VRM_EXPRESSION_PREFIX.length) : null;
20
+ }
@@ -1,5 +1,8 @@
1
+ import type { Binding, BindingEditorData } from '../values/bindings.ts';
2
+ import type { ControllerMovementConfig, ControllerState } from '../values/controller.ts';
3
+ import type { SceneInspection } from '../values/stage-info.ts';
1
4
  import type { EventName } from './events.ts';
2
- import type { AnchorOption, AppCapability, AssetKind, AssetRef, Attach, AttachHeadAngle, Expression, ExpressionPersistence, HotkeyConfig, HotkeyState, InjectEntry, InjectTarget, InstanceRuntime, JsonValue, ModelInfo, ModelRef, MotionGroup, MToonTuning, ObjectContent, ObjectLightOverride, ObjectSpace, Place2D, Place3D, PlayingMotion, PoseSourceId, PoseStatus, Scene, SceneItem, ScenePatch, SceneState, ScreenPlacement, Settings, SettingsPatch, ShortcutInfo, TrackingSourceConfig, TrackingSourceId, TrackingSourceKind, TrackingStatus, VrmPlacement } from './types.ts';
5
+ import type { AnchorOption, AppCapability, AssetKind, AssetRef, Attach, AttachHeadAngle, BindingInput, Expression, ExpressionPersistence, HotkeyConfig, HotkeyState, InjectEntry, InjectTarget, InstanceRuntime, JsonValue, ModelInfo, ModelRef, MotionGroup, MToonTuning, ObjectContent, ObjectLightOverride, ObjectSpace, Place2D, Place3D, PlayingMotion, PoseSourceId, PoseStatus, Scene, SceneItem, ScenePatch, SceneState, ScreenPlacement, Settings, SettingsPatch, ShortcutInfo, TrackingSourceConfig, TrackingSourceId, TrackingSourceKind, TrackingStatus, VrmPlacement } from './types.ts';
3
6
  /** Marker for methods that take no parameters; the client lets you omit the argument. */
4
7
  export type EmptyRequest = Record<never, never>;
5
8
  /** Marker for methods whose success response carries no data. */
@@ -80,6 +83,13 @@ export interface InstanceSetTrackingSourcesRequest {
80
83
  poseSourceId?: string | null;
81
84
  }
82
85
  export type InstanceSetTrackingSourcesResponse = EmptyResponse;
86
+ export interface InstanceSetControllerMovementRequest {
87
+ instanceId: string;
88
+ movement: Partial<ControllerMovementConfig>;
89
+ }
90
+ export interface InstanceSetControllerMovementResponse {
91
+ movement: ControllerMovementConfig;
92
+ }
83
93
  export interface InstanceReorderRequest {
84
94
  /** Full z-order within the scene; ids not listed keep their relative order at the end. */
85
95
  orderedInstanceIds: string[];
@@ -315,7 +325,7 @@ export type PoseSetEnabledResponse = EmptyResponse;
315
325
  export interface PoseSetSourceRequest {
316
326
  source: PoseSourceId;
317
327
  }
318
- /** Switching protocol resets the port to that source's default; the response reports it. */
328
+ /** Switching protocol resets the port to that source's next free default; the response reports it. */
319
329
  export interface PoseSetSourceResponse {
320
330
  port: number;
321
331
  }
@@ -328,7 +338,10 @@ export interface TrackingAddSourceRequest {
328
338
  name?: string;
329
339
  /** Face kinds: pin to a device IP. */
330
340
  phoneIp?: string | null;
331
- /** vmc: the UDP port to listen on; defaults to the next free VMC port. */
341
+ /**
342
+ * vmc / mocopi: the UDP port to listen on, default the next free port from 39539 / 12351.
343
+ * ifacialmocap: the receive port, default 49983 (set the phone's Send Port to match).
344
+ */
332
345
  port?: number;
333
346
  }
334
347
  export interface TrackingAddSourceResponse {
@@ -440,11 +453,140 @@ export interface ParamReleaseRequest {
440
453
  targets?: InjectTarget[];
441
454
  }
442
455
  export type ParamReleaseResponse = EmptyResponse;
456
+ export type ControllerStateRequest = EmptyRequest;
457
+ export type ControllerStateResponse = ControllerState;
458
+ export interface ControllerRenameRequest {
459
+ slot: number;
460
+ name: string;
461
+ }
462
+ export type ControllerRenameResponse = EmptyResponse;
463
+ export interface ControllerSetDeadZoneRequest {
464
+ slot: number;
465
+ /** Radial stick dead zone, 0..0.5. */
466
+ deadZone: number;
467
+ }
468
+ export type ControllerSetDeadZoneResponse = EmptyResponse;
469
+ export interface ControllerRemoveRequest {
470
+ slot: number;
471
+ }
472
+ export type ControllerRemoveResponse = EmptyResponse;
473
+ export interface ControllerAssignRequest {
474
+ slot: number | null;
475
+ }
476
+ export type ControllerAssignResponse = EmptyResponse;
477
+ export interface BindingTarget {
478
+ instanceId: string;
479
+ modelId: string;
480
+ }
481
+ export type BindingGetRequest = BindingTarget;
482
+ export type BindingReleaseRequest = BindingTarget;
483
+ export type BindingReleaseResponse = EmptyResponse;
484
+ export interface BindingGetResponse {
485
+ data: BindingEditorData | null;
486
+ }
487
+ export interface BindingPreviewRequest extends BindingTarget {
488
+ connections: Binding[];
489
+ }
490
+ export type BindingPreviewResponse = EmptyResponse;
491
+ export type BindingSetRequest = BindingPreviewRequest;
492
+ export interface BindingSetResponse {
493
+ saved: boolean;
494
+ }
495
+ export type BindingInputsRequest = BindingTarget;
496
+ export interface BindingInputsResponse {
497
+ inputs: Partial<Record<BindingInput, number>>;
498
+ }
499
+ export interface BindingOverrideRequest extends BindingTarget {
500
+ input: BindingInput;
501
+ value: number | null;
502
+ }
503
+ export type BindingOverrideResponse = EmptyResponse;
504
+ export interface InstanceSetBreathRequest {
505
+ instanceId: string;
506
+ breath?: boolean;
507
+ breathDepth?: number;
508
+ }
509
+ export type InstanceSetBreathResponse = EmptyResponse;
510
+ /** A small image preview for a registered id; never a path or arbitrary file request. */
511
+ export interface RegistryThumbnailRequest {
512
+ kind: 'model' | 'asset';
513
+ id: string;
514
+ }
515
+ export interface RegistryThumbnailResponse {
516
+ dataUrl: string | null;
517
+ }
518
+ export type SceneInspectRequest = EmptyRequest;
519
+ export type SceneInspectResponse = SceneInspection;
520
+ export interface MotionPlayAssetRequest {
521
+ instanceId: string;
522
+ assetId: string;
523
+ }
524
+ export type MotionPlayAssetResponse = EmptyResponse;
443
525
  /**
444
526
  * The full request surface: method → request/response. Model-scoped methods take
445
527
  * an optional `instanceId` defaulting to the scene's primary model instance.
446
528
  */
447
529
  export interface MethodMap {
530
+ 'registry.thumbnail': {
531
+ request: RegistryThumbnailRequest;
532
+ response: RegistryThumbnailResponse;
533
+ };
534
+ 'scene.inspect': {
535
+ request: SceneInspectRequest;
536
+ response: SceneInspectResponse;
537
+ };
538
+ 'motion.playAsset': {
539
+ request: MotionPlayAssetRequest;
540
+ response: MotionPlayAssetResponse;
541
+ };
542
+ 'binding.release': {
543
+ request: BindingReleaseRequest;
544
+ response: BindingReleaseResponse;
545
+ };
546
+ 'binding.get': {
547
+ request: BindingGetRequest;
548
+ response: BindingGetResponse;
549
+ };
550
+ 'binding.preview': {
551
+ request: BindingPreviewRequest;
552
+ response: BindingPreviewResponse;
553
+ };
554
+ 'binding.set': {
555
+ request: BindingSetRequest;
556
+ response: BindingSetResponse;
557
+ };
558
+ 'binding.inputs': {
559
+ request: BindingInputsRequest;
560
+ response: BindingInputsResponse;
561
+ };
562
+ 'binding.override': {
563
+ request: BindingOverrideRequest;
564
+ response: BindingOverrideResponse;
565
+ };
566
+ 'instance.setBreath': {
567
+ request: InstanceSetBreathRequest;
568
+ response: InstanceSetBreathResponse;
569
+ };
570
+ 'controller.state': {
571
+ request: ControllerStateRequest;
572
+ response: ControllerStateResponse;
573
+ };
574
+ 'controller.rename': {
575
+ request: ControllerRenameRequest;
576
+ response: ControllerRenameResponse;
577
+ };
578
+ 'controller.setDeadZone': {
579
+ request: ControllerSetDeadZoneRequest;
580
+ response: ControllerSetDeadZoneResponse;
581
+ };
582
+ 'controller.remove': {
583
+ request: ControllerRemoveRequest;
584
+ response: ControllerRemoveResponse;
585
+ };
586
+ 'controller.assign': {
587
+ request: ControllerAssignRequest;
588
+ response: ControllerAssignResponse;
589
+ };
448
590
  'scene.list': {
449
591
  request: SceneListRequest;
450
592
  response: SceneListResponse;
@@ -513,6 +655,10 @@ export interface MethodMap {
513
655
  request: InstanceSetTrackingSourcesRequest;
514
656
  response: InstanceSetTrackingSourcesResponse;
515
657
  };
658
+ 'instance.setControllerMovement': {
659
+ request: InstanceSetControllerMovementRequest;
660
+ response: InstanceSetControllerMovementResponse;
661
+ };
516
662
  'instance.reorder': {
517
663
  request: InstanceReorderRequest;
518
664
  response: InstanceReorderResponse;
@@ -696,12 +842,12 @@ export interface MethodMap {
696
842
  request: PoseSetEnabledRequest;
697
843
  response: PoseSetEnabledResponse;
698
844
  };
699
- /** Legacy: operates on the first vmc source. Prefer the tracking.* source CRUD. */
845
+ /** Legacy: operates on the first body source. Prefer the tracking.* source CRUD. */
700
846
  'pose.setSource': {
701
847
  request: PoseSetSourceRequest;
702
848
  response: PoseSetSourceResponse;
703
849
  };
704
- /** Legacy: sets the first vmc source's port. */
850
+ /** Legacy: sets the first body source's port. */
705
851
  'pose.setPort': {
706
852
  request: PoseSetPortRequest;
707
853
  response: PoseSetPortResponse;