@laplace.live/persona-sdk 1.0.0 → 1.2.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.
@@ -2,6 +2,7 @@ import * as z from 'zod';
2
2
  import { CONTROLLER_DEAD_ZONE_MAX } from "../values/controller.js";
3
3
  import { isRecord } from "../values/guards.js";
4
4
  import { 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";
5
6
  import { ASSET_KINDS, POSE_SOURCE_IDS, TRACKING_SOURCE_IDS, TRACKING_SOURCE_KINDS } from "./types.js";
6
7
  // Runtime validation for the request side of the wire. Schemas exist for the
7
8
  // methods whose params the app's main process consumes directly; methods without
@@ -10,6 +11,25 @@ import { ASSET_KINDS, POSE_SOURCE_IDS, TRACKING_SOURCE_IDS, TRACKING_SOURCE_KIND
10
11
  // the app heals them into shape server-side.
11
12
  const nonEmpty = z.string().min(1);
12
13
  const port = z.number().int().min(1).max(65535);
14
+ const lipSyncCalibration = z.discriminatedUnion('action', [
15
+ z.object({ action: z.enum(LIP_SYNC_CALIBRATION_ACTIONS) }),
16
+ z.object({ action: z.literal('record'), phoneme: z.enum(LIP_SYNC_PHONEMES) }),
17
+ ]);
18
+ const lipSyncPatch = z.object({
19
+ enabled: z.boolean().optional(),
20
+ deviceId: z.string().optional(),
21
+ gain: z.number().min(0).max(LIP_SYNC_GAIN_MAX).optional(),
22
+ noiseGate: z.number().min(LIP_SYNC_NOISE_GATE_MIN).max(0).optional(),
23
+ smoothing: z.number().min(0).max(LIP_SYNC_SMOOTHING_MAX).optional(),
24
+ });
25
+ const mediapipePatch = z.object({
26
+ deviceId: z.string().optional(),
27
+ mirror: z.boolean().optional(),
28
+ face: z.boolean().optional(),
29
+ hands: z.boolean().optional(),
30
+ body: z.boolean().optional(),
31
+ delegate: z.enum(['CPU', 'GPU']).optional(),
32
+ });
13
33
  export const InjectTargetSchema = z.object({
14
34
  type: z.enum(['input', 'live2d-param', 'vrm-expression']),
15
35
  id: nonEmpty,
@@ -20,6 +40,7 @@ export const InjectEntrySchema = InjectTargetSchema.extend({
20
40
  weight: z.number().min(0).max(1).optional(),
21
41
  });
22
42
  export const SettingsPatchSchema = z.object({
43
+ lipSync: lipSyncPatch.optional(),
23
44
  controller: z.object({ enabled: z.boolean().optional() }).optional(),
24
45
  window: z.object({ alwaysOnTop: z.boolean().optional() }).optional(),
25
46
  ui: z.object({ trayVisible: z.boolean().optional() }).optional(),
@@ -56,6 +77,11 @@ const speechUrl = z
56
77
  }
57
78
  }, 'url must be http(s) or data:audio/*');
58
79
  export const requestSchemas = {
80
+ 'lipSync.state': z.object({}),
81
+ 'lipSync.configure': lipSyncPatch,
82
+ 'lipSync.restart': z.object({}),
83
+ 'lipSync.calibrate': lipSyncCalibration,
84
+ 'instance.setLipSync': z.object({ instanceId: nonEmpty.optional(), mode: z.enum(LIP_SYNC_MODES) }),
59
85
  'registry.thumbnail': z.object({ kind: z.enum(['model', 'asset']), id: nonEmpty }),
60
86
  'controller.rename': z.object({ slot: z.number().int().positive(), name: z.string() }),
61
87
  'controller.setDeadZone': z.object({
@@ -96,6 +122,7 @@ export const requestSchemas = {
96
122
  name: z.string().optional(),
97
123
  phoneIp: nonEmpty.nullable().optional(),
98
124
  port: port.optional(),
125
+ mediapipe: mediapipePatch.optional(),
99
126
  }),
100
127
  'tracking.updateSource': z.object({
101
128
  id: nonEmpty,
@@ -103,6 +130,7 @@ export const requestSchemas = {
103
130
  enabled: z.boolean().optional(),
104
131
  phoneIp: nonEmpty.nullable().optional(),
105
132
  port: port.optional(),
133
+ mediapipe: mediapipePatch.optional(),
106
134
  }),
107
135
  'tracking.removeSource': z.object({ id: nonEmpty }),
108
136
  'model.register': z.object({ path: nonEmpty }),
@@ -1,6 +1,9 @@
1
1
  import type { ControllerConfig } from '../values/controller.ts';
2
+ import type { EFFECT_SCOPES } from '../values/effect-schema.ts';
2
3
  import { type ArkitInputName } from '../values/arkit.ts';
3
4
  import { type BaseControllerInputName, type ControllerInputName, type ControllerMovementConfig } from '../values/controller.ts';
5
+ import { type HandInputName } from '../values/hands.ts';
6
+ import { type LipSyncConfig, type LipSyncMode, type VoiceInputName } from '../values/lipsync.ts';
4
7
  export type ModelFormat = 'live2d' | 'vrm';
5
8
  /** Where an item came from: shipped with the app, or added by the user. */
6
9
  export type ContentOrigin = 'bundled' | 'user';
@@ -139,6 +142,10 @@ export interface SceneModelItem {
139
142
  instanceId: string;
140
143
  ref: ModelRef;
141
144
  visible: boolean;
145
+ /** Independent effects on this source, before scene effects; WebGPU only. */
146
+ effects: LayerEffects;
147
+ /** Added layer effects, including disabled rows whose tuning is retained. */
148
+ effectLayers: LayerEffectKey[];
142
149
  /**
143
150
  * Face source tracking this instance ({@link TrackingSourceConfig.id}); null = its face is untracked.
144
151
  * A dangling or disabled id behaves as null. One source may track several instances (mirroring).
@@ -146,6 +153,12 @@ export interface SceneModelItem {
146
153
  faceSourceId: string | null;
147
154
  /** Body source tracking this instance; null = its pose is untracked. New instances bind to each channel's default source. */
148
155
  poseSourceId: string | null;
156
+ /** Hand source tracking this instance; null leaves hands untracked. */
157
+ handSourceId: string | null;
158
+ /** Track wrists and arms, or only fingers while the body source owns the arms. */
159
+ handTrackingMode: HandTrackingMode;
160
+ /** Whether microphone lipsync drives this model, independently of the face source assignment. */
161
+ lipSyncMode: LipSyncMode;
149
162
  /** Opt-in controller movement, independent of parameter/bone bindings; disabled by default. */
150
163
  controllerMovement: ControllerMovementConfig;
151
164
  live2d: ScreenPlacement;
@@ -257,6 +270,10 @@ export interface SceneObjectItem {
257
270
  instanceId: string;
258
271
  name: string;
259
272
  visible: boolean;
273
+ /** Independent effects on this source, before scene effects; WebGPU only. */
274
+ effects: LayerEffects;
275
+ /** Added layer effects, including disabled rows whose tuning is retained. */
276
+ effectLayers: LayerEffectKey[];
260
277
  space: ObjectSpace;
261
278
  content: ObjectContent;
262
279
  place2d: Place2D;
@@ -279,9 +296,6 @@ export interface SceneBackground {
279
296
  color: string;
280
297
  imageAssetId: string | null;
281
298
  }
282
- export interface SceneBehavior {
283
- lookAtCursor: boolean;
284
- }
285
299
  /** VRM stage framing: the camera moves, the model does not. Angles in radians, distance in world units. */
286
300
  export interface OrbitTransform {
287
301
  azimuth: number;
@@ -341,28 +355,35 @@ export interface SceneFog {
341
355
  }
342
356
  /** Display transform applied after the scene renders. `none` keeps colors exactly as authored. */
343
357
  export type SceneToneMapping = 'none' | 'neutral' | 'aces' | 'agx';
344
- /**
345
- * Glow around bright pixels, with anamorphic streak flares on the side
346
- * (VTube Studio's Beautify-based bloom). `threshold` is the luminance floor;
347
- * `radius` widens the halo.
348
- */
358
+ /** Highlight glow in normal, streak, or star mode; color selection applies only to layer effects. */
349
359
  export interface SceneBloom {
350
360
  enabled: boolean;
361
+ mode: 'normal' | 'streak' | 'star';
362
+ /** UI offset: the glow's brightness multiplier is 1 + intensity. */
351
363
  intensity: number;
352
364
  threshold: number;
365
+ thresholdSmooth: number;
353
366
  radius: number;
354
- /** Halo tint (hex); white is untinted. */
367
+ saturation: number;
368
+ /** Glow color (hex); white is untinted. */
355
369
  tint: string;
356
- /** Anamorphic streak strength; 0 keeps the streak passes out of the graph. */
357
- streak: number;
358
- /** Luminance floor for the streak's own bright pass. */
359
- streakThreshold: number;
360
- /** Streak axis in degrees: 0 horizontal, ±90 vertical (VTS's vertical toggle, continuous). */
361
- streakAngle: number;
362
- /** Streak tint (hex); classic anamorphic flares are light blue. */
363
- streakTint: string;
364
- /** Darkens the avatar under the glow so highlights pop (VTS's model darken), 0..1. */
365
- darken: number;
370
+ opacity: number;
371
+ starRays: number;
372
+ starAngle: number;
373
+ selectColors: boolean;
374
+ invertColors: boolean;
375
+ /** Shared saturation/brightness tolerance; hue tolerance is fixed at 0.03. */
376
+ colorTolerance: number;
377
+ color1: string;
378
+ color2: string;
379
+ color3: string;
380
+ color4: string;
381
+ color5: string;
382
+ color1Enabled: boolean;
383
+ color2Enabled: boolean;
384
+ color3Enabled: boolean;
385
+ color4Enabled: boolean;
386
+ color5Enabled: boolean;
366
387
  }
367
388
  /**
368
389
  * Cinematic soft-focus veil (Shoost's diffusion, a Kino bloom variant): the
@@ -396,6 +417,116 @@ export interface SceneColorGrade {
396
417
  /** White balance along green↔magenta, −100..100 — the axis `temperature` leaves alone. */
397
418
  tint: number;
398
419
  }
420
+ /** Master and per-channel input/output levels; RGB channels run before master levels. */
421
+ export interface EffectLevels {
422
+ enabled: boolean;
423
+ inputBlack: number;
424
+ inputWhite: number;
425
+ inputGamma: number;
426
+ outputBlack: number;
427
+ outputWhite: number;
428
+ inputBlackR: number;
429
+ inputWhiteR: number;
430
+ inputGammaR: number;
431
+ outputBlackR: number;
432
+ outputWhiteR: number;
433
+ inputBlackG: number;
434
+ inputWhiteG: number;
435
+ inputGammaG: number;
436
+ outputBlackG: number;
437
+ outputWhiteG: number;
438
+ inputBlackB: number;
439
+ inputWhiteB: number;
440
+ inputGammaB: number;
441
+ outputBlackB: number;
442
+ outputWhiteB: number;
443
+ }
444
+ /** Color wheels in lift/gamma/gain or shadows/midtones/highlights mode. */
445
+ export interface EffectColorWheels {
446
+ enabled: boolean;
447
+ mode: 'liftGammaGain' | 'shadowsMidtonesHighlights';
448
+ lift: number;
449
+ liftColor: string;
450
+ gamma: number;
451
+ gammaColor: string;
452
+ gain: number;
453
+ gainColor: string;
454
+ shadows: number;
455
+ shadowsColor: string;
456
+ midtones: number;
457
+ midtonesColor: string;
458
+ highlights: number;
459
+ highlightsColor: string;
460
+ shadowLimit: number;
461
+ highlightLimit: number;
462
+ }
463
+ /** Six hue bands; hue offsets are degrees, other offsets are -1..1. */
464
+ export interface EffectColorShift {
465
+ enabled: boolean;
466
+ hueRed: number;
467
+ saturationRed: number;
468
+ luminanceRed: number;
469
+ luminanceSaturationRed: number;
470
+ hueYellow: number;
471
+ saturationYellow: number;
472
+ luminanceYellow: number;
473
+ luminanceSaturationYellow: number;
474
+ hueGreen: number;
475
+ saturationGreen: number;
476
+ luminanceGreen: number;
477
+ luminanceSaturationGreen: number;
478
+ hueCyan: number;
479
+ saturationCyan: number;
480
+ luminanceCyan: number;
481
+ luminanceSaturationCyan: number;
482
+ hueBlue: number;
483
+ saturationBlue: number;
484
+ luminanceBlue: number;
485
+ luminanceSaturationBlue: number;
486
+ hueMagenta: number;
487
+ saturationMagenta: number;
488
+ luminanceMagenta: number;
489
+ luminanceSaturationMagenta: number;
490
+ }
491
+ /** Preserve selected colors while adjusting saturation outside their HSV ranges. */
492
+ export interface EffectSelectColors {
493
+ enabled: boolean;
494
+ invert: boolean;
495
+ /** Hue distance in turns around the color wheel. */
496
+ hueRange: number;
497
+ saturationRange: number;
498
+ brightnessRange: number;
499
+ /** Saturation offset outside the selection; -1 makes it monochrome. */
500
+ saturation: number;
501
+ blend: number;
502
+ color1: string;
503
+ color1Enabled: boolean;
504
+ color2: string;
505
+ color2Enabled: boolean;
506
+ color3: string;
507
+ color3Enabled: boolean;
508
+ color4: string;
509
+ color4Enabled: boolean;
510
+ color5: string;
511
+ color5Enabled: boolean;
512
+ color6: string;
513
+ color6Enabled: boolean;
514
+ color7: string;
515
+ color7Enabled: boolean;
516
+ color8: string;
517
+ color8Enabled: boolean;
518
+ color9: string;
519
+ color9Enabled: boolean;
520
+ color10: string;
521
+ color10Enabled: boolean;
522
+ }
523
+ /** Gaussian or disk-shaped bokeh blur in output pixels. */
524
+ export interface EffectBlur {
525
+ enabled: boolean;
526
+ radius: number;
527
+ mode: 'gaussian' | 'bokeh';
528
+ highQuality: boolean;
529
+ }
399
530
  /** Lens fringing that grows toward frame edges (Unity PPv2's curve, the one VTube Studio wraps). */
400
531
  export interface SceneChromaticAberration {
401
532
  enabled: boolean;
@@ -476,55 +607,33 @@ export interface SceneDroplets {
476
607
  /** Specular glint on drops where the frame is transparent, so rain reads over the desktop (0 to 1). */
477
608
  glints: number;
478
609
  }
479
- /**
480
- * Screen-space rim light along the avatar silhouette (Shoost's layer rim light,
481
- * VTube Studio's backlight): coverage edges facing `angle` catch the glow.
482
- * Silhouette-driven — an opaque skybox leaves no edges to light.
483
- */
610
+ /** Blend modes shared with Shoost's rim light. */
611
+ export type EffectBlendMode = 'normal' | 'darken' | 'multiply' | 'colorBurn' | 'linearBurn' | 'add' | 'lighten' | 'screen' | 'colorDodge' | 'overlay' | 'softLight' | 'hardLight' | 'vividLight' | 'linearLight' | 'pinLight' | 'hardMix' | 'difference' | 'exclusion' | 'subtract' | 'divide' | 'hue' | 'saturation' | 'color' | 'luminosity';
612
+ /** Directional silhouette lighting, optionally sharpened or applied to both sides. */
484
613
  export interface SceneRim {
485
614
  enabled: boolean;
615
+ mode: 'single' | 'double' | 'sharpenSingle' | 'sharpenDouble';
616
+ blendMode: EffectBlendMode;
486
617
  /** Rim tint (hex). */
487
618
  color: string;
488
- /** Glow strength; past 1 overdrives into bloom territory. */
489
- intensity: number;
490
- /** Rim width in output pixels. */
619
+ /** Normalized rim width, 0..1. */
491
620
  size: number;
492
- /** Edge falloff: 0 a crisp line, 1 a wide soft fade. */
493
- softness: number;
494
- /** Light direction in degrees, compass-style: 0 lights from above, 90 from the right. */
621
+ brightness: number;
622
+ contrast: number;
623
+ /** Light direction in degrees, -180..180. */
495
624
  angle: number;
496
- /** How much the edge opposite the light glows too (0 to 1). */
497
- bothSides: number;
498
- /** Uniform glow on every edge regardless of `angle` (VTS's main backlight strength), 0..1. */
499
- omni: number;
500
- /** Ceiling the glow lightens pixels toward: 1 screens to white, lower protects highlights. */
501
- brightnessLimit: number;
502
- /** Darkens the avatar under the glow for contrast (VTS's model darken), 0..1. */
503
- darken: number;
625
+ opacity: number;
504
626
  }
505
- /**
506
- * Contour band hugging the avatar silhouette, with an optional animated stripe
507
- * pattern scrolling through it (VTube Studio's backlight outline). Silhouette-
508
- * driven like the rim light — it needs coverage edges to trace.
509
- */
627
+ /** Solid silhouette border with selectable edge sampling quality. */
510
628
  export interface SceneOutline {
511
629
  enabled: boolean;
630
+ quality: 'low' | 'medium' | 'high';
512
631
  /** Outline color (hex). */
513
632
  color: string;
514
- /** Stripe color (hex), painted over `color` where the stripe pattern lands. */
515
- stripeColor: string;
516
- /** Band thickness in output pixels. */
633
+ /** Normalized border thickness, 0..1. */
517
634
  size: number;
518
635
  /** Outline opacity 0..1. */
519
636
  opacity: number;
520
- /** Stripe density: 0 broad bands, 1 fine candy stripes. */
521
- stripes: number;
522
- /** Stripe visibility: 0 a solid outline, 1 full-strength stripes. */
523
- stripeMix: number;
524
- /** Stripe scroll speed; negative reverses the direction. */
525
- stripeSpeed: number;
526
- /** Bends the stripes into waves: 0 straight, 1 strongly curled. */
527
- stripeCurve: number;
528
637
  }
529
638
  /**
530
639
  * Hard-edged copy of the avatar silhouette cast behind it (VTube Studio's
@@ -540,6 +649,8 @@ export interface SceneDropShadow {
540
649
  offsetX: number;
541
650
  /** Vertical offset, percent of frame height; positive casts down. */
542
651
  offsetY: number;
652
+ /** Gaussian shadow softness in output pixels; 0 keeps a hard edge. */
653
+ size: number;
543
654
  }
544
655
  /** Compute-driven 3D rainfall: motion-blur streaks fall through the scene and splash on models, props, and the floor. */
545
656
  export interface SceneRain {
@@ -577,23 +688,19 @@ export interface SceneSnow {
577
688
  /** Flake opacity. */
578
689
  opacity: number;
579
690
  }
580
- /**
581
- * Post-processing over the rendered 3D frame. Everything off skips the effect
582
- * chain entirely. Deliberately flat: every toggle-plus-numbers effect sits at
583
- * the top level so tooling (defaults, healing, editors) can walk the effect
584
- * registry generically. Grouping is a panel concern, not a data one.
585
- */
586
- export interface SceneEffects {
587
- toneMapping: SceneToneMapping;
588
- /** Scene brightness multiplied in before the tone curve; 1 is neutral. Works in every mode, including `none`. */
589
- exposure: number;
691
+ /** Toggle effects independent of the scene or layer that owns their values. */
692
+ export interface EffectValues {
590
693
  bloom: SceneBloom;
591
694
  diffusion: SceneDiffusion;
592
695
  vignette: SceneVignette;
593
696
  color: SceneColorGrade;
697
+ levels: EffectLevels;
698
+ colorWheels: EffectColorWheels;
699
+ colorShift: EffectColorShift;
700
+ selectColors: EffectSelectColors;
701
+ blur: EffectBlur;
594
702
  chromaticAberration: SceneChromaticAberration;
595
703
  grain: SceneFilmGrain;
596
- lut: SceneLut;
597
704
  dof: SceneDepthOfField;
598
705
  rim: SceneRim;
599
706
  outline: SceneOutline;
@@ -604,12 +711,29 @@ export interface SceneEffects {
604
711
  rain: SceneRain;
605
712
  snow: SceneSnow;
606
713
  }
607
- /** Keys of {@link SceneEffects} that follow the `{ enabled } + params` pattern. */
608
- export type ToggleEffectKey = {
609
- [K in keyof SceneEffects]: SceneEffects[K] extends {
610
- enabled: boolean;
611
- } ? K : never;
612
- }[keyof SceneEffects];
714
+ export type EffectKey = keyof EffectValues;
715
+ export type EffectScope = 'scene' | 'layer';
716
+ /** Effect availability comes from the catalog, independently for each scope. */
717
+ export type EffectKeyForScope<S extends EffectScope> = {
718
+ [K in EffectKey]: S extends (typeof EFFECT_SCOPES)[K][number] ? K : never;
719
+ }[EffectKey];
720
+ export type SceneEffectKey = EffectKeyForScope<'scene'>;
721
+ /** Scene toggle keys; retained for existing SDK consumers. */
722
+ export type ToggleEffectKey = SceneEffectKey;
723
+ /** Scene-wide toggles plus the display transform and color lookup table. */
724
+ export interface SceneEffects extends Pick<EffectValues, SceneEffectKey> {
725
+ toneMapping: SceneToneMapping;
726
+ /** Scene brightness multiplied in before the tone curve; 1 is neutral. Works in every mode, including `none`. */
727
+ exposure: number;
728
+ lut: SceneLut;
729
+ }
730
+ /** Source-local effects, evaluated before the source joins the scene composition. */
731
+ export type LayerEffectKey = EffectKeyForScope<'layer'>;
732
+ export type LayerEffects = Pick<EffectValues, LayerEffectKey>;
733
+ /** Omitted effects and parameters retain their current values. */
734
+ export type LayerEffectsPatch = {
735
+ [K in LayerEffectKey]?: Partial<LayerEffects[K]>;
736
+ };
613
737
  /**
614
738
  * A 3D set's own suggested look — the fog and post settings it was authored against,
615
739
  * read from its root `LAPLACE_environment` extension and held on the scene for every client.
@@ -680,7 +804,6 @@ export interface Scene {
680
804
  /** Hotkey target and the default instance for model-scoped methods. null only when `items` holds no model. */
681
805
  primaryInstanceId: string | null;
682
806
  background: SceneBackground;
683
- behavior: SceneBehavior;
684
807
  vrmCamera: SceneCamera;
685
808
  /** Array order is display order only; live lights are keyed by id. */
686
809
  lights: SceneLight[];
@@ -701,7 +824,6 @@ export interface SceneState {
701
824
  */
702
825
  export interface ScenePatch {
703
826
  background?: SceneBackground;
704
- behavior?: SceneBehavior;
705
827
  vrmCamera?: SceneCamera;
706
828
  lights?: SceneLight[];
707
829
  environment?: SceneEnvironment;
@@ -710,7 +832,7 @@ export interface ScenePatch {
710
832
  * App-level features a client gates on (never version-sniff): `hello` and
711
833
  * `app.info` report them — the per-app mirror of {@link InstanceRuntime.capabilities}.
712
834
  */
713
- export declare const APP_CAPABILITIES: readonly ["storage", "speech", "shortcuts", "controllers", "model-editing", "asset-inspection"];
835
+ export declare const APP_CAPABILITIES: readonly ["storage", "speech", "shortcuts", "controllers", "model-editing", "asset-inspection", "layer-effects"];
714
836
  export type AppCapability = (typeof APP_CAPABILITIES)[number];
715
837
  export declare function isAppCapability(v: unknown): v is AppCapability;
716
838
  /** What a loaded model instance can do; absent capabilities answer `unsupported-for-format`. */
@@ -817,9 +939,30 @@ export type PoseSourceId = (typeof POSE_SOURCE_IDS)[number];
817
939
  export type TrackingStatus = 'off' | 'waiting' | 'tracking' | 'no-face';
818
940
  export type PoseStatus = 'off' | 'waiting' | 'tracking';
819
941
  /** Every protocol a tracking source instance can speak; face and body kinds. */
820
- export declare const TRACKING_SOURCE_KINDS: readonly ["persona-ios", "ifacialmocap", "vts-ios", "vmc", "mocopi"];
942
+ export declare const TRACKING_SOURCE_KINDS: readonly ["persona-ios", "ifacialmocap", "vts-ios", "vmc", "mocopi", "mediapipe"];
821
943
  export type TrackingSourceKind = (typeof TRACKING_SOURCE_KINDS)[number];
822
- /** Whether a source kind feeds the face channel. */
944
+ export declare const TRACKING_CHANNELS: readonly ["face", "pose", "hands"];
945
+ export type TrackingChannel = (typeof TRACKING_CHANNELS)[number];
946
+ /** The scene-item binding each channel reads and writes. */
947
+ export declare const TRACKING_CHANNEL_FIELDS: {
948
+ readonly face: "faceSourceId";
949
+ readonly pose: "poseSourceId";
950
+ readonly hands: "handSourceId";
951
+ };
952
+ export declare const HAND_TRACKING_MODES: readonly ["arms", "fingers"];
953
+ export type HandTrackingMode = (typeof HAND_TRACKING_MODES)[number];
954
+ export declare function isHandTrackingMode(v: unknown): v is HandTrackingMode;
955
+ /** The shared webcam source's enabled inference tasks and camera configuration. */
956
+ export interface MediaPipeConfig {
957
+ deviceId: string;
958
+ mirror: boolean;
959
+ face: boolean;
960
+ hands: boolean;
961
+ body: boolean;
962
+ delegate: 'CPU' | 'GPU';
963
+ }
964
+ export declare const DEFAULT_MEDIAPIPE_CONFIG: Readonly<MediaPipeConfig>;
965
+ /** Whether a source kind uses a network face receiver. */
823
966
  export declare function isFaceSourceKind(kind: TrackingSourceKind): kind is TrackingSourceId;
824
967
  /**
825
968
  * One configured tracking source instance. Several can run at once — one per person —
@@ -841,11 +984,27 @@ export interface TrackingSourceConfig {
841
984
  * Send Port must match). Null on persona-ios and vts-ios, whose ports the protocol fixes.
842
985
  */
843
986
  port: number | null;
987
+ /** Present only on the shared webcam source. */
988
+ mediapipe?: MediaPipeConfig;
844
989
  }
845
- /** Whether a source feeds the body channel — the narrowing counterpart of `isFaceSourceKind`. */
990
+ /** Whether a source uses a network body receiver. */
846
991
  export declare function isPoseSource(source: TrackingSourceConfig): source is TrackingSourceConfig & {
847
992
  kind: PoseSourceId;
848
993
  };
994
+ /** The channels a kind can supply; the webcam's are further gated by its task switches. */
995
+ export declare function sourceKindChannels(kind: TrackingSourceKind): readonly TrackingChannel[];
996
+ /** Whether this source can supply a channel; {@link sourceChannelEnabled} folds in the switches. */
997
+ export declare function sourceSupportsChannel(source: TrackingSourceConfig, channel: TrackingChannel): boolean;
998
+ /** The face and body master switches; they gate network sources only. */
999
+ export interface ChannelMasters {
1000
+ face: boolean;
1001
+ pose: boolean;
1002
+ }
1003
+ /**
1004
+ * Whether a source feeds a channel right now: its own switch, its task, and — for a network
1005
+ * face or body source — the channel master. Webcam tasks and hands answer to the source switch alone.
1006
+ */
1007
+ export declare function sourceChannelEnabled(source: TrackingSourceConfig, channel: TrackingChannel, masters: ChannelMasters): boolean;
849
1008
  /**
850
1009
  * Whether another source already holds `port` and would contend for it. iFacialMocap instances
851
1010
  * share one socket per port (frames demux by phone); every other pairing EADDRINUSEs a listener.
@@ -865,6 +1024,8 @@ export interface StageSize {
865
1024
  }
866
1025
  /** The curated settings surface the API exposes — never the raw store shape. */
867
1026
  export interface Settings {
1027
+ /** Absent on hosts without microphone lipsync. */
1028
+ lipSync?: LipSyncConfig;
868
1029
  /** Absent on hosts without remote controller management. */
869
1030
  controller?: ControllerConfig;
870
1031
  /** `alwaysOnTop` floats the control-panel window, never the stage. */
@@ -885,13 +1046,13 @@ export interface Settings {
885
1046
  renderScale?: number;
886
1047
  live2dEngine?: 'pixi' | 'three';
887
1048
  };
888
- /** `source` mirrors the first face source's kind — the pre-multi-source field, kept for old clients. */
1049
+ /** `enabled` gates network face sources; `source` mirrors the first one's kind for old clients. */
889
1050
  tracking: {
890
1051
  enabled: boolean;
891
1052
  source: TrackingSourceId;
892
1053
  sources: TrackingSourceConfig[];
893
1054
  };
894
- /** `source`/`port` mirror the first body source the pre-multi-source fields, kept for old clients. */
1055
+ /** `enabled` gates network body sources; `source`/`port` mirror the first one's values for old clients. */
895
1056
  pose: {
896
1057
  enabled: boolean;
897
1058
  source: PoseSourceId;
@@ -899,6 +1060,7 @@ export interface Settings {
899
1060
  };
900
1061
  }
901
1062
  export interface SettingsPatch {
1063
+ lipSync?: Partial<LipSyncConfig>;
902
1064
  controller?: Partial<Pick<ControllerConfig, 'enabled'>>;
903
1065
  window?: {
904
1066
  alwaysOnTop?: boolean;
@@ -919,11 +1081,11 @@ export interface SettingsPatch {
919
1081
  declare const VTS_INPUT_NAMES: readonly ["FaceAngleX", "FaceAngleY", "FaceAngleZ", "FacePositionX", "FacePositionY", "FacePositionZ", "EyeOpenLeft", "EyeOpenRight", "EyeLeftX", "EyeLeftY", "EyeRightX", "EyeRightY", "Brows", "BrowLeftY", "BrowRightY", "MouthSmile", "MouthOpen", "MouthX", "CheekPuff", "JawOpen", "TongueOut"];
920
1082
  type VtsInputName = (typeof VTS_INPUT_NAMES)[number];
921
1083
  /**
922
- * Default input vocabulary: VTS face inputs, raw ARKit channels and controller profile 1.
1084
+ * Default input vocabulary: face and hand inputs, raw ARKit channels and controller profile 1.
923
1085
  * Additional controller profile ids are accepted by isInputName without appearing in this list.
924
1086
  */
925
- export declare const INPUT_NAMES: readonly (VtsInputName | ArkitInputName | BaseControllerInputName)[];
926
- export type InputName = VtsInputName | ArkitInputName | ControllerInputName;
1087
+ export declare const INPUT_NAMES: readonly (VtsInputName | HandInputName | VoiceInputName | ArkitInputName | BaseControllerInputName)[];
1088
+ export type InputName = VtsInputName | HandInputName | VoiceInputName | ArkitInputName | ControllerInputName;
927
1089
  /** Whether an untrusted string names a tracking input — the guard every wire boundary needs. */
928
1090
  export declare function isInputName(v: string): v is InputName;
929
1091
  /**
@@ -951,7 +1113,7 @@ export declare function arkitTwinOf(input: InputName): BindingInput;
951
1113
  * Default inputs' natural spans. Use getInputRange for a dynamically numbered controller input.
952
1114
  * Head angles are degrees; the rest are unitless.
953
1115
  */
954
- export declare const INPUT_RANGES: Record<VtsInputName | ArkitInputName | BaseControllerInputName, readonly [number, number]>;
1116
+ export declare const INPUT_RANGES: Record<VtsInputName | HandInputName | VoiceInputName | ArkitInputName | BaseControllerInputName, readonly [number, number]>;
955
1117
  /** Natural span of any valid input, including dynamically numbered controller profiles. */
956
1118
  export declare function getInputRange(name: InputName): readonly [number, number];
957
1119
  /**