@laplace.live/persona-sdk 0.18.1 → 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 +5 -8
  10. package/dist/values/curve.js +16 -7
  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 +19 -0
  24. package/dist/values/limits.js +27 -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 +59 -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
@@ -22,6 +22,9 @@ export declare const InjectEntrySchema: z.ZodObject<{
22
22
  weight: z.ZodOptional<z.ZodNumber>;
23
23
  }, z.core.$strip>;
24
24
  export declare const SettingsPatchSchema: z.ZodObject<{
25
+ controller: z.ZodOptional<z.ZodObject<{
26
+ enabled: z.ZodOptional<z.ZodBoolean>;
27
+ }, z.core.$strip>>;
25
28
  window: z.ZodOptional<z.ZodObject<{
26
29
  alwaysOnTop: z.ZodOptional<z.ZodBoolean>;
27
30
  }, z.core.$strip>>;
@@ -33,13 +36,39 @@ export declare const SettingsPatchSchema: z.ZodObject<{
33
36
  fpsLimit: z.ZodOptional<z.ZodNumber>;
34
37
  selectionOutline: z.ZodOptional<z.ZodBoolean>;
35
38
  effectsQuality: z.ZodOptional<z.ZodEnum<{
39
+ high: "high";
36
40
  low: "low";
37
41
  medium: "medium";
38
- high: "high";
42
+ }>>;
43
+ renderScale: z.ZodOptional<z.ZodNumber>;
44
+ live2dEngine: z.ZodOptional<z.ZodEnum<{
45
+ pixi: "pixi";
46
+ three: "three";
39
47
  }>>;
40
48
  }, z.core.$strip>>;
41
49
  }, z.core.$strip>;
42
50
  export declare const requestSchemas: {
51
+ 'registry.thumbnail': z.ZodObject<{
52
+ kind: z.ZodEnum<{
53
+ model: "model";
54
+ asset: "asset";
55
+ }>;
56
+ id: z.ZodString;
57
+ }, z.core.$strip>;
58
+ 'controller.rename': z.ZodObject<{
59
+ slot: z.ZodNumber;
60
+ name: z.ZodString;
61
+ }, z.core.$strip>;
62
+ 'controller.setDeadZone': z.ZodObject<{
63
+ slot: z.ZodNumber;
64
+ deadZone: z.ZodNumber;
65
+ }, z.core.$strip>;
66
+ 'controller.remove': z.ZodObject<{
67
+ slot: z.ZodNumber;
68
+ }, z.core.$strip>;
69
+ 'controller.assign': z.ZodObject<{
70
+ slot: z.ZodNullable<z.ZodNumber>;
71
+ }, z.core.$strip>;
43
72
  'scene.get': z.ZodObject<{
44
73
  sceneId: z.ZodOptional<z.ZodString>;
45
74
  }, z.core.$strip>;
@@ -97,9 +126,11 @@ export declare const requestSchemas: {
97
126
  }, z.core.$strip>;
98
127
  'tracking.addSource': z.ZodObject<{
99
128
  kind: z.ZodEnum<{
100
- "vts-ios": "vts-ios";
129
+ "persona-ios": "persona-ios";
101
130
  ifacialmocap: "ifacialmocap";
131
+ "vts-ios": "vts-ios";
102
132
  vmc: "vmc";
133
+ mocopi: "mocopi";
103
134
  }>;
104
135
  name: z.ZodOptional<z.ZodString>;
105
136
  phoneIp: z.ZodOptional<z.ZodNullable<z.ZodString>>;
@@ -121,17 +152,20 @@ export declare const requestSchemas: {
121
152
  'asset.register': z.ZodObject<{
122
153
  path: z.ZodString;
123
154
  want: z.ZodOptional<z.ZodEnum<{
155
+ lut: "lut";
124
156
  image: "image";
125
157
  video: "video";
126
158
  prop: "prop";
127
159
  ibl: "ibl";
128
- lut: "lut";
129
160
  animation: "animation";
130
161
  cameraMotion: "cameraMotion";
131
162
  }>>;
132
163
  }, z.core.$strip>;
133
164
  'settings.patch': z.ZodObject<{
134
165
  settings: z.ZodObject<{
166
+ controller: z.ZodOptional<z.ZodObject<{
167
+ enabled: z.ZodOptional<z.ZodBoolean>;
168
+ }, z.core.$strip>>;
135
169
  window: z.ZodOptional<z.ZodObject<{
136
170
  alwaysOnTop: z.ZodOptional<z.ZodBoolean>;
137
171
  }, z.core.$strip>>;
@@ -143,9 +177,14 @@ export declare const requestSchemas: {
143
177
  fpsLimit: z.ZodOptional<z.ZodNumber>;
144
178
  selectionOutline: z.ZodOptional<z.ZodBoolean>;
145
179
  effectsQuality: z.ZodOptional<z.ZodEnum<{
180
+ high: "high";
146
181
  low: "low";
147
182
  medium: "medium";
148
- high: "high";
183
+ }>>;
184
+ renderScale: z.ZodOptional<z.ZodNumber>;
185
+ live2dEngine: z.ZodOptional<z.ZodEnum<{
186
+ pixi: "pixi";
187
+ three: "three";
149
188
  }>>;
150
189
  }, z.core.$strip>>;
151
190
  }, z.core.$strip>;
@@ -155,8 +194,9 @@ export declare const requestSchemas: {
155
194
  }, z.core.$strip>;
156
195
  'tracking.setSource': z.ZodObject<{
157
196
  source: z.ZodEnum<{
158
- "vts-ios": "vts-ios";
197
+ "persona-ios": "persona-ios";
159
198
  ifacialmocap: "ifacialmocap";
199
+ "vts-ios": "vts-ios";
160
200
  }>;
161
201
  }, z.core.$strip>;
162
202
  'pose.setEnabled': z.ZodObject<{
@@ -165,6 +205,7 @@ export declare const requestSchemas: {
165
205
  'pose.setSource': z.ZodObject<{
166
206
  source: z.ZodEnum<{
167
207
  vmc: "vmc";
208
+ mocopi: "mocopi";
168
209
  }>;
169
210
  }, z.core.$strip>;
170
211
  'pose.setPort': z.ZodObject<{
@@ -1,4 +1,5 @@
1
1
  import * as z from 'zod';
2
+ import { CONTROLLER_DEAD_ZONE_MAX } from "../values/controller.js";
2
3
  import { isRecord } from "../values/guards.js";
3
4
  import { SPEECH_URL_MAX_LENGTH, STORAGE_KEY_MAX_LENGTH, STORAGE_VALUE_MAX_LENGTH } from "../values/limits.js";
4
5
  import { ASSET_KINDS, POSE_SOURCE_IDS, TRACKING_SOURCE_IDS, TRACKING_SOURCE_KINDS } from "./types.js";
@@ -19,6 +20,7 @@ export const InjectEntrySchema = InjectTargetSchema.extend({
19
20
  weight: z.number().min(0).max(1).optional(),
20
21
  });
21
22
  export const SettingsPatchSchema = z.object({
23
+ controller: z.object({ enabled: z.boolean().optional() }).optional(),
22
24
  window: z.object({ alwaysOnTop: z.boolean().optional() }).optional(),
23
25
  ui: z.object({ trayVisible: z.boolean().optional() }).optional(),
24
26
  performance: z
@@ -27,6 +29,8 @@ export const SettingsPatchSchema = z.object({
27
29
  fpsLimit: z.number().optional(),
28
30
  selectionOutline: z.boolean().optional(),
29
31
  effectsQuality: z.enum(['high', 'medium', 'low']).optional(),
32
+ renderScale: z.number().optional(),
33
+ live2dEngine: z.enum(['pixi', 'three']).optional(),
30
34
  })
31
35
  .optional(),
32
36
  });
@@ -52,6 +56,14 @@ const speechUrl = z
52
56
  }
53
57
  }, 'url must be http(s) or data:audio/*');
54
58
  export const requestSchemas = {
59
+ 'registry.thumbnail': z.object({ kind: z.enum(['model', 'asset']), id: nonEmpty }),
60
+ 'controller.rename': z.object({ slot: z.number().int().positive(), name: z.string() }),
61
+ 'controller.setDeadZone': z.object({
62
+ slot: z.number().int().positive(),
63
+ deadZone: z.number().min(0).max(CONTROLLER_DEAD_ZONE_MAX),
64
+ }),
65
+ 'controller.remove': z.object({ slot: z.number().int().positive() }),
66
+ 'controller.assign': z.object({ slot: z.number().int().positive().nullable() }),
55
67
  'scene.get': z.object({ sceneId: nonEmpty.optional() }),
56
68
  'scene.activate': z.object({ sceneId: nonEmpty }),
57
69
  'scene.duplicate': z.object({ sceneId: nonEmpty }),
@@ -1,4 +1,6 @@
1
+ import type { ControllerConfig } from '../values/controller.ts';
1
2
  import { type ArkitInputName } from '../values/arkit.ts';
3
+ import { type BaseControllerInputName, type ControllerInputName, type ControllerMovementConfig } from '../values/controller.ts';
2
4
  export type ModelFormat = 'live2d' | 'vrm';
3
5
  /** Where an item came from: shipped with the app, or added by the user. */
4
6
  export type ContentOrigin = 'bundled' | 'user';
@@ -44,6 +46,8 @@ export interface AssetRef extends ContentRef {
44
46
  /** What the extension makes it — an object created from it starts on this kind. */
45
47
  kind: AssetKind;
46
48
  exists: boolean;
49
+ /** Source basename, with no directory. Absent on older hosts. */
50
+ file?: string;
47
51
  }
48
52
  /**
49
53
  * Content metadata decoupled from any on-disk file — local registry entries and
@@ -140,8 +144,10 @@ export interface SceneModelItem {
140
144
  * A dangling or disabled id behaves as null. One source may track several instances (mirroring).
141
145
  */
142
146
  faceSourceId: string | null;
143
- /** Body source (vmc) tracking this instance; null = its pose is untracked. New instances bind to each channel's default source. */
147
+ /** Body source tracking this instance; null = its pose is untracked. New instances bind to each channel's default source. */
144
148
  poseSourceId: string | null;
149
+ /** Opt-in controller movement, independent of parameter/bone bindings; disabled by default. */
150
+ controllerMovement: ControllerMovementConfig;
145
151
  live2d: ScreenPlacement;
146
152
  vrm: VrmPlacement;
147
153
  idleAnimation: boolean;
@@ -188,6 +194,8 @@ export type ObjectContent = {
188
194
  css: string;
189
195
  /** 2D only: composited behind or in front of the whole stage, not interleaved with other layers. */
190
196
  layer: WebLayer;
197
+ /** Close the page while the object is hidden and reload it on show, as an OBS browser source can. */
198
+ shutdownWhenHidden: boolean;
191
199
  } | {
192
200
  kind: 'capture';
193
201
  source: CaptureKind;
@@ -602,24 +610,6 @@ export type ToggleEffectKey = {
602
610
  enabled: boolean;
603
611
  } ? K : never;
604
612
  }[keyof SceneEffects];
605
- /**
606
- * One user-authored effect's placement in a scene. Deliberately a sibling of
607
- * {@link SceneEffects} rather than a key inside it: `ToggleEffectKey` is derived
608
- * from that interface, and healing, defaults, hotkey snapshots and the panel all
609
- * iterate it as a closed compile-time union. Runtime keys in there would erase
610
- * that guarantee for the built-in effects too.
611
- */
612
- export interface SceneCustomEffect {
613
- /** Folder name under the config home's `effects/` dir — stable id and module location. */
614
- slug: string;
615
- enabled: boolean;
616
- /**
617
- * Author-declared values, keyed by the manifest's param names. Untyped by
618
- * construction: the specs arrive from disk at runtime, so these are validated
619
- * against the installed manifest rather than checked at compile time.
620
- */
621
- params: Record<string, number | boolean | string>;
622
- }
623
613
  /**
624
614
  * A 3D set's own suggested look — the fog and post settings it was authored against,
625
615
  * read from its root `LAPLACE_environment` extension and held on the scene for every client.
@@ -681,14 +671,6 @@ export interface SceneEnvironment {
681
671
  * switched off. Healing lists every switched-on effect, so a patch that switches one on adds its layer.
682
672
  */
683
673
  effectLayers: ToggleEffectKey[];
684
- /**
685
- * User-authored effects, in the order they compose. They run as a group at one
686
- * fixed point in the post chain — after grading and the screen-space warps,
687
- * before grain and vignette — so the built-in composition order stays fixed.
688
- * An entry whose effect is not installed here is kept, not dropped: it would
689
- * cost the user their tuning on a machine that simply lacks the folder.
690
- */
691
- customEffects: SceneCustomEffect[];
692
674
  }
693
675
  export interface Scene {
694
676
  id: string;
@@ -728,11 +710,13 @@ export interface ScenePatch {
728
710
  * App-level features a client gates on (never version-sniff): `hello` and
729
711
  * `app.info` report them — the per-app mirror of {@link InstanceRuntime.capabilities}.
730
712
  */
731
- export declare const APP_CAPABILITIES: readonly ["storage", "speech", "shortcuts"];
713
+ export declare const APP_CAPABILITIES: readonly ["storage", "speech", "shortcuts", "controllers", "model-editing", "asset-inspection"];
732
714
  export type AppCapability = (typeof APP_CAPABILITIES)[number];
733
715
  export declare function isAppCapability(v: unknown): v is AppCapability;
734
716
  /** What a loaded model instance can do; absent capabilities answer `unsupported-for-format`. */
735
717
  export type InstanceCapability = 'motions' | 'expressions' | 'placement-2d' | 'placement-3d' | 'mtoon' | 'idle-clips' | 'pose' | 'live2d-params'
718
+ /** `instance.setBreath`; the idle breathing loop is Cubism's, so VRM lacks it. */
719
+ | 'breath'
736
720
  /** `expression.setWeight`; Cubism expressions carry no user-settable weight, so Live2D lacks it. */
737
721
  | 'expression-weights'
738
722
  /** `speech.play` lip-sync; engine-dependent, so gate on the runtime, not the format. */
@@ -752,6 +736,8 @@ export interface InstanceRuntime {
752
736
  capabilities: InstanceCapability[];
753
737
  }
754
738
  export interface MotionGroup {
739
+ /** Whether this group loops; absent on older hosts. */
740
+ looping?: boolean;
755
741
  group: string;
756
742
  files: string[];
757
743
  }
@@ -776,20 +762,7 @@ export interface AnchorOption {
776
762
  anchor: AttachAnchor;
777
763
  label: string;
778
764
  }
779
- /**
780
- * Panel-style model info block. Format-specific fields mirror the app's `ModelInfo`.
781
- * `format` stays the union's discriminant here — this is a runtime diagnostic, not
782
- * content metadata, so it does not follow {@link ContentRef}'s `kind`.
783
- */
784
- export interface ModelInfo {
785
- format: ModelFormat;
786
- name: string;
787
- origin: ContentOrigin;
788
- file: string;
789
- loadMs: number;
790
- /** Format-specific details (canvas/params/textures for Live2D, spec/bones for VRM). */
791
- [key: string]: unknown;
792
- }
765
+ export type { ModelInfo } from '../values/model-info.ts';
793
766
  export interface Hotkey {
794
767
  id: string;
795
768
  name: string;
@@ -836,17 +809,17 @@ export interface ExpressionPersistence {
836
809
  export type JsonValue = string | number | boolean | null | JsonValue[] | {
837
810
  [key: string]: JsonValue;
838
811
  };
839
- export declare const TRACKING_SOURCE_IDS: readonly ["vts-ios", "ifacialmocap"];
812
+ export declare const TRACKING_SOURCE_IDS: readonly ["persona-ios", "ifacialmocap", "vts-ios"];
840
813
  export type TrackingSourceId = (typeof TRACKING_SOURCE_IDS)[number];
841
814
  /** Body-pose protocols. Every one so far is UDP with a configurable port. */
842
- export declare const POSE_SOURCE_IDS: readonly ["vmc"];
815
+ export declare const POSE_SOURCE_IDS: readonly ["vmc", "mocopi"];
843
816
  export type PoseSourceId = (typeof POSE_SOURCE_IDS)[number];
844
817
  export type TrackingStatus = 'off' | 'waiting' | 'tracking' | 'no-face';
845
818
  export type PoseStatus = 'off' | 'waiting' | 'tracking';
846
- /** Every protocol a tracking source instance can speak; face kinds plus the body (vmc) kind. */
847
- export declare const TRACKING_SOURCE_KINDS: readonly ["vts-ios", "ifacialmocap", "vmc"];
819
+ /** 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"];
848
821
  export type TrackingSourceKind = (typeof TRACKING_SOURCE_KINDS)[number];
849
- /** Whether a source kind feeds the face channel (vmc is the body channel). */
822
+ /** Whether a source kind feeds the face channel. */
850
823
  export declare function isFaceSourceKind(kind: TrackingSourceKind): kind is TrackingSourceId;
851
824
  /**
852
825
  * One configured tracking source instance. Several can run at once — one per person —
@@ -860,12 +833,24 @@ export interface TrackingSourceConfig {
860
833
  enabled: boolean;
861
834
  /**
862
835
  * Face kinds: accept only this device/sender IP; null accepts any sender not claimed
863
- * by a pinned instance of the same kind. Ignored by vmc (its senders pick the port).
836
+ * by a pinned instance of the same kind. Ignored by body sources (their senders pick the port).
864
837
  */
865
838
  phoneIp: string | null;
866
- /** vmc only: the UDP port this instance listens on. Null on face kinds — they bind per protocol. */
839
+ /**
840
+ * The UDP port this instance listens on: vmc, mocopi, and ifacialmocap (its receive port — the phone's
841
+ * Send Port must match). Null on persona-ios and vts-ios, whose ports the protocol fixes.
842
+ */
867
843
  port: number | null;
868
844
  }
845
+ /** Whether a source feeds the body channel — the narrowing counterpart of `isFaceSourceKind`. */
846
+ export declare function isPoseSource(source: TrackingSourceConfig): source is TrackingSourceConfig & {
847
+ kind: PoseSourceId;
848
+ };
849
+ /**
850
+ * Whether another source already holds `port` and would contend for it. iFacialMocap instances
851
+ * share one socket per port (frames demux by phone); every other pairing EADDRINUSEs a listener.
852
+ */
853
+ export declare function trackingPortTaken(sources: readonly TrackingSourceConfig[], kind: TrackingSourceKind, port: number, excludeId?: string): boolean;
869
854
  export declare const EFFECTS_QUALITY_LEVELS: readonly ["low", "medium", "high"];
870
855
  export type EffectsQuality = (typeof EFFECTS_QUALITY_LEVELS)[number];
871
856
  /**
@@ -873,11 +858,20 @@ export type EffectsQuality = (typeof EFFECTS_QUALITY_LEVELS)[number];
873
858
  * to the nearest preset by the app, so a picker offering other values would lie.
874
859
  */
875
860
  export declare const FPS_LIMIT_PRESETS: readonly [0, 15, 30, 60, 90];
861
+ /** A stage window content size in device-independent px — what the desktop's window bounds report. */
862
+ export interface StageSize {
863
+ width: number;
864
+ height: number;
865
+ }
876
866
  /** The curated settings surface the API exposes — never the raw store shape. */
877
867
  export interface Settings {
868
+ /** Absent on hosts without remote controller management. */
869
+ controller?: ControllerConfig;
878
870
  /** `alwaysOnTop` floats the control-panel window, never the stage. */
879
871
  window: {
880
872
  alwaysOnTop: boolean;
873
+ /** Read-only; absent on older desktops. */
874
+ stageSize?: StageSize;
881
875
  };
882
876
  ui: {
883
877
  trayVisible: boolean;
@@ -887,6 +881,9 @@ export interface Settings {
887
881
  fpsLimit: number;
888
882
  selectionOutline: boolean;
889
883
  effectsQuality: EffectsQuality;
884
+ /** Absent on older hosts. */
885
+ renderScale?: number;
886
+ live2dEngine?: 'pixi' | 'three';
890
887
  };
891
888
  /** `source` mirrors the first face source's kind — the pre-multi-source field, kept for old clients. */
892
889
  tracking: {
@@ -894,7 +891,7 @@ export interface Settings {
894
891
  source: TrackingSourceId;
895
892
  sources: TrackingSourceConfig[];
896
893
  };
897
- /** `source`/`port` mirror the first vmc source — the pre-multi-source fields, kept for old clients. */
894
+ /** `source`/`port` mirror the first body source — the pre-multi-source fields, kept for old clients. */
898
895
  pose: {
899
896
  enabled: boolean;
900
897
  source: PoseSourceId;
@@ -902,6 +899,7 @@ export interface Settings {
902
899
  };
903
900
  }
904
901
  export interface SettingsPatch {
902
+ controller?: Partial<Pick<ControllerConfig, 'enabled'>>;
905
903
  window?: {
906
904
  alwaysOnTop?: boolean;
907
905
  };
@@ -913,17 +911,19 @@ export interface SettingsPatch {
913
911
  fpsLimit?: number;
914
912
  selectionOutline?: boolean;
915
913
  effectsQuality?: EffectsQuality;
914
+ renderScale?: number;
915
+ live2dEngine?: 'pixi' | 'three';
916
916
  };
917
917
  }
918
918
  /** VTS's input vocabulary, plus `JawOpen` — the derived half of {@link INPUT_NAMES}. */
919
919
  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
920
  type VtsInputName = (typeof VTS_INPUT_NAMES)[number];
921
921
  /**
922
- * The valid `id`s for `input` inject targets, and the names a model's `.vtube.json`
923
- * references: VTS's derived vocabulary plus every raw ARKit channel.
922
+ * Default input vocabulary: VTS face inputs, raw ARKit channels and controller profile 1.
923
+ * Additional controller profile ids are accepted by isInputName without appearing in this list.
924
924
  */
925
- export declare const INPUT_NAMES: readonly InputName[];
926
- export type InputName = VtsInputName | ArkitInputName;
925
+ export declare const INPUT_NAMES: readonly (VtsInputName | ArkitInputName | BaseControllerInputName)[];
926
+ export type InputName = VtsInputName | ArkitInputName | ControllerInputName;
927
927
  /** Whether an untrusted string names a tracking input — the guard every wire boundary needs. */
928
928
  export declare function isInputName(v: string): v is InputName;
929
929
  /**
@@ -948,10 +948,12 @@ export declare const BINDING_INPUT_NAMES: readonly BindingInput[];
948
948
  /** The id an input is read as: its ARKit twin when it has one, else itself. */
949
949
  export declare function arkitTwinOf(input: InputName): BindingInput;
950
950
  /**
951
- * Each input's natural span — the units a sender should write, and the input range a new
952
- * binding starts from. Head angles are **degrees**; the rest are unitless.
951
+ * Default inputs' natural spans. Use getInputRange for a dynamically numbered controller input.
952
+ * Head angles are degrees; the rest are unitless.
953
953
  */
954
- export declare const INPUT_RANGES: Record<InputName, readonly [number, number]>;
954
+ export declare const INPUT_RANGES: Record<VtsInputName | ArkitInputName | BaseControllerInputName, readonly [number, number]>;
955
+ /** Natural span of any valid input, including dynamically numbered controller profiles. */
956
+ export declare function getInputRange(name: InputName): readonly [number, number];
955
957
  /**
956
958
  * What an injected value drives:
957
959
  * - `input` — a VTS-vocabulary tracking input (`MouthOpen`, `FaceAngleX`, …), mapped
@@ -972,4 +974,3 @@ export interface InjectEntry extends InjectTarget {
972
974
  /** 0..1 blend over whatever else drives the parameter; default 1. */
973
975
  weight?: number;
974
976
  }
975
- export {};
@@ -2,6 +2,7 @@
2
2
  // carry. Structural mirrors of the app's scene/settings models, minus anything
3
3
  // filesystem-shaped — model refs are sanitized to ids, never directories.
4
4
  import { ARKIT_INPUT_NAMES } from "../values/arkit.js";
5
+ import { BASE_CONTROLLER_INPUT_NAMES, BASE_CONTROLLER_INPUT_RANGES, controllerInputRange, isControllerInputName, } from "../values/controller.js";
5
6
  /**
6
7
  * How a registered file is labelled. Wider than an object's content kinds: `.hdr` is only
7
8
  * ever an environment map, and a `.vmd` splits by content — `cameraMotion` for one that
@@ -23,7 +24,14 @@ export const SHADOW_QUALITY_LEVELS = ['off', 'low', 'medium', 'high', 'ultra'];
23
24
  * App-level features a client gates on (never version-sniff): `hello` and
24
25
  * `app.info` report them — the per-app mirror of {@link InstanceRuntime.capabilities}.
25
26
  */
26
- export const APP_CAPABILITIES = ['storage', 'speech', 'shortcuts'];
27
+ export const APP_CAPABILITIES = [
28
+ 'storage',
29
+ 'speech',
30
+ 'shortcuts',
31
+ 'controllers',
32
+ 'model-editing',
33
+ 'asset-inspection',
34
+ ];
27
35
  export function isAppCapability(v) {
28
36
  return typeof v === 'string' && APP_CAPABILITIES.includes(v);
29
37
  }
@@ -38,15 +46,26 @@ export const SHORTCUT_ACTION_KINDS = [
38
46
  'stream-mode',
39
47
  ];
40
48
  // ---- Settings ------------------------------------------------------------------
41
- export const TRACKING_SOURCE_IDS = ['vts-ios', 'ifacialmocap'];
49
+ export const TRACKING_SOURCE_IDS = ['persona-ios', 'ifacialmocap', 'vts-ios'];
42
50
  /** Body-pose protocols. Every one so far is UDP with a configurable port. */
43
- export const POSE_SOURCE_IDS = ['vmc'];
44
- /** Every protocol a tracking source instance can speak; face kinds plus the body (vmc) kind. */
51
+ export const POSE_SOURCE_IDS = ['vmc', 'mocopi'];
52
+ /** Every protocol a tracking source instance can speak; face and body kinds. */
45
53
  export const TRACKING_SOURCE_KINDS = [...TRACKING_SOURCE_IDS, ...POSE_SOURCE_IDS];
46
- /** Whether a source kind feeds the face channel (vmc is the body channel). */
54
+ /** Whether a source kind feeds the face channel. */
47
55
  export function isFaceSourceKind(kind) {
48
56
  return TRACKING_SOURCE_IDS.includes(kind);
49
57
  }
58
+ /** Whether a source feeds the body channel — the narrowing counterpart of `isFaceSourceKind`. */
59
+ export function isPoseSource(source) {
60
+ return !isFaceSourceKind(source.kind);
61
+ }
62
+ /**
63
+ * Whether another source already holds `port` and would contend for it. iFacialMocap instances
64
+ * share one socket per port (frames demux by phone); every other pairing EADDRINUSEs a listener.
65
+ */
66
+ export function trackingPortTaken(sources, kind, port, excludeId) {
67
+ return sources.some(s => s.id !== excludeId && s.port === port && !(s.kind === 'ifacialmocap' && kind === 'ifacialmocap'));
68
+ }
50
69
  export const EFFECTS_QUALITY_LEVELS = ['low', 'medium', 'high'];
51
70
  /**
52
71
  * Accepted values for `performance.fpsLimit`; 0 = unlimited. Anything else is snapped
@@ -79,14 +98,18 @@ const VTS_INPUT_NAMES = [
79
98
  'TongueOut',
80
99
  ];
81
100
  /**
82
- * The valid `id`s for `input` inject targets, and the names a model's `.vtube.json`
83
- * references: VTS's derived vocabulary plus every raw ARKit channel.
101
+ * Default input vocabulary: VTS face inputs, raw ARKit channels and controller profile 1.
102
+ * Additional controller profile ids are accepted by isInputName without appearing in this list.
84
103
  */
85
- export const INPUT_NAMES = [...VTS_INPUT_NAMES, ...ARKIT_INPUT_NAMES];
104
+ export const INPUT_NAMES = [
105
+ ...VTS_INPUT_NAMES,
106
+ ...ARKIT_INPUT_NAMES,
107
+ ...BASE_CONTROLLER_INPUT_NAMES,
108
+ ];
86
109
  const INPUT_NAME_SET = new Set(INPUT_NAMES);
87
110
  /** Whether an untrusted string names a tracking input — the guard every wire boundary needs. */
88
111
  export function isInputName(v) {
89
- return INPUT_NAME_SET.has(v);
112
+ return INPUT_NAME_SET.has(v) || isControllerInputName(v);
90
113
  }
91
114
  /**
92
115
  * VTS-vocabulary inputs that are the same float as one raw ARKit channel (the desktop derives
@@ -145,12 +168,17 @@ const VTS_INPUT_RANGES = {
145
168
  /** ARKit blendshapes are unit-scale by definition, so every raw channel shares one span. */
146
169
  const ARKIT_RANGE = [0, 1];
147
170
  /**
148
- * Each input's natural span — the units a sender should write, and the input range a new
149
- * binding starts from. Head angles are **degrees**; the rest are unitless.
171
+ * Default inputs' natural spans. Use getInputRange for a dynamically numbered controller input.
172
+ * Head angles are degrees; the rest are unitless.
150
173
  */
151
174
  export const INPUT_RANGES = {
152
175
  ...VTS_INPUT_RANGES,
176
+ ...BASE_CONTROLLER_INPUT_RANGES,
153
177
  // `fromEntries` widens the key type back to `string`; the annotation above is what keeps
154
178
  // this exhaustive, and it fails to typecheck if a name ever lacks a range.
155
179
  ...Object.fromEntries(ARKIT_INPUT_NAMES.map(n => [n, ARKIT_RANGE])),
156
180
  };
181
+ /** Natural span of any valid input, including dynamically numbered controller profiles. */
182
+ export function getInputRange(name) {
183
+ return isControllerInputName(name) ? controllerInputRange(name) : INPUT_RANGES[name];
184
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@laplace.live/persona-sdk",
3
- "version": "0.18.1",
3
+ "version": "1.0.0",
4
4
  "description": "TypeScript SDK and wire schema for the LAPLACE Persona plugin API",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -16,10 +16,6 @@
16
16
  ".": {
17
17
  "types": "./dist/index.d.ts",
18
18
  "import": "./dist/index.js"
19
- },
20
- "./effects": {
21
- "types": "./dist/effects.d.ts",
22
- "import": "./dist/effects.js"
23
19
  }
24
20
  },
25
21
  "files": [
@@ -30,22 +26,12 @@
30
26
  "provenance": true
31
27
  },
32
28
  "devDependencies": {
33
- "@types/three": "^0.185.4",
34
29
  "rimraf": "^6.1.3",
35
- "three": "0.185.1",
36
30
  "typescript": "~6.0.3",
37
31
  "vitest": "^4.1.11"
38
32
  },
39
- "peerDependencies": {
40
- "@types/three": "^0.185.0"
41
- },
42
- "peerDependenciesMeta": {
43
- "@types/three": {
44
- "optional": true
45
- }
46
- },
47
33
  "engines": {
48
- "node": ">=22"
34
+ "node": ">=24"
49
35
  },
50
36
  "dependencies": {
51
37
  "zod": "^4.5.4"
package/dist/effects.d.ts DELETED
@@ -1,79 +0,0 @@
1
- import type * as TSL from 'three/tsl';
2
- import type { Color, Node, Vector2, Vector3, Vector4 } from 'three/webgpu';
3
- export * from './values/custom-effect.ts';
4
- /** A vec4-typed TSL node with its swizzles — what `sample` returns and `build` composes over. */
5
- export type CustomEffectVec4 = ReturnType<typeof TSL.nodeObject<Node<'vec4'>>>;
6
- /** A float-typed TSL node with its operators — what `viewZ` returns. */
7
- export type CustomEffectFloat = ReturnType<typeof TSL.nodeObject<Node<'float'>>>;
8
- /** A vec3-typed TSL node with its swizzles — what the camera probes return. */
9
- export type CustomEffectVec3 = ReturnType<typeof TSL.nodeObject<Node<'vec3'>>>;
10
- /** What an author's module receives. Not a security boundary — just the useful surface. */
11
- export interface CustomEffectApi {
12
- /** The whole `three/tsl` namespace: `vec4`, `uv`, `uniform`, `mix`, `Fn`, … */
13
- tsl: typeof TSL;
14
- three: {
15
- Color: typeof Color;
16
- Vector2: typeof Vector2;
17
- Vector3: typeof Vector3;
18
- Vector4: typeof Vector4;
19
- };
20
- /**
21
- * Stock three display nodes an effect cannot build for itself. `afterImage`
22
- * is the seam for anything temporal: correct frame feedback needs two render
23
- * targets ping-ponged plus a renderer reference, and `build()` hands authors
24
- * neither. Upstream types return the bare class, hiding the TSL swizzles the
25
- * runtime proxy carries; this signature types the echo as what authors use it
26
- * as — a sampleable vec4 image.
27
- */
28
- nodes: {
29
- afterImage: (node: Node, damp?: Node | number) => CustomEffectVec4;
30
- };
31
- }
32
- /** Build-time context for one custom effect. */
33
- export interface CustomEffectBuildContext {
34
- /** Sample the incoming frame at any UV — the seam that makes warps and blurs possible. */
35
- sample: (at: unknown) => CustomEffectVec4;
36
- /** Screen UV of the pixel being shaded. */
37
- uv: typeof TSL.uv;
38
- /**
39
- * View-space Z of the 3D scene at this pixel — three's convention: 0 at the
40
- * camera, more negative with distance, the far plane where nothing was drawn.
41
- * The Live2D layer and 2D objects contribute no depth. Costs one depth read,
42
- * and only if called.
43
- */
44
- viewZ: () => CustomEffectFloat;
45
- /**
46
- * Normalized world-space direction of the scene camera's view ray through
47
- * this pixel, fov and aspect folded in. Inside the post pass TSL's own
48
- * `cameraPosition`/`cameraWorldMatrix` describe the fullscreen quad's
49
- * camera — this is the real one. Anchor a field here instead of `uv()` and
50
- * it holds still in the world while the camera orbits.
51
- */
52
- worldRay: () => CustomEffectVec3;
53
- /** The scene camera's world position in metres, as a per-frame uniform. */
54
- cameraPosition: () => CustomEffectVec3;
55
- /** Hand a build-time disposable (a blur render target) to the chain's transient list. */
56
- track: (t: {
57
- dispose?: () => void;
58
- }) => void;
59
- }
60
- /** What an author's module returns. Everything but `build` is optional. */
61
- export interface CustomEffectModule {
62
- /** Uniform nodes keyed by manifest param name; param edits write straight into `.value`. */
63
- uniforms?: Record<string, {
64
- value: unknown;
65
- }>;
66
- /** Compose the effect over `input` — the frame so far, already sampleable — and return the result node. */
67
- build: (input: Node, ctx: CustomEffectBuildContext) => Node;
68
- /** Per-frame CPU state. `dt` and `elapsed` are seconds. */
69
- update?: (dt: number, elapsed: number) => void;
70
- dispose?: () => void;
71
- }
72
- /** The module's default export. Runs once per load — mint uniforms here so they survive rebuilds. */
73
- export type CustomEffectFactory = (api: CustomEffectApi) => CustomEffectModule;
74
- /** Why an effect is not rendering, for the panel's error row. */
75
- export interface CustomEffectFault {
76
- slug: string;
77
- phase: 'load' | 'build' | 'update';
78
- message: string;
79
- }
package/dist/effects.js DELETED
@@ -1,6 +0,0 @@
1
- // The custom-effect authoring contract: what an effect module receives, what it
2
- // returns, and (re-exported) the manifest shape naming its params. Everything
3
- // touching three is type-only — the desktop hands the runtime to the module's
4
- // factory, because a module loaded from `persona://` can import nothing.
5
- // Typechecking against this entry needs `@types/three` (optional peer).
6
- export * from "./values/custom-effect.js";