@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
@@ -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;
@@ -175,19 +181,31 @@ export type ObjectContent = {
175
181
  assetId: string;
176
182
  } | {
177
183
  kind: 'web';
184
+ /** http(s) only; anything else is dropped on load. Rendered in an isolated in-memory browser session. */
178
185
  url: string;
186
+ /** Browser viewport, CSS px. A change reloads the page. */
179
187
  width: number;
180
188
  height: number;
189
+ /** Paint-rate cap; a static page paints only when it changes regardless. */
181
190
  fps: number;
191
+ /** Transparent page background (else white), as an OBS browser source. */
182
192
  transparent: boolean;
193
+ /** Stylesheet injected into every page the object loads; empty for none. */
183
194
  css: string;
195
+ /** 2D only: composited behind or in front of the whole stage, not interleaved with other layers. */
184
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;
185
199
  } | {
186
200
  kind: 'capture';
187
201
  source: CaptureKind;
188
202
  sourceId: string;
189
203
  label: string;
190
204
  };
205
+ /** The webpage variant of {@link ObjectContent}. */
206
+ export type WebContent = Extract<ObjectContent, {
207
+ kind: 'web';
208
+ }>;
191
209
  /** Where on a parent model an object rides. `root` is the model's own transform. */
192
210
  export type AttachAnchor = {
193
211
  kind: 'root';
@@ -592,24 +610,6 @@ export type ToggleEffectKey = {
592
610
  enabled: boolean;
593
611
  } ? K : never;
594
612
  }[keyof SceneEffects];
595
- /**
596
- * One user-authored effect's placement in a scene. Deliberately a sibling of
597
- * {@link SceneEffects} rather than a key inside it: `ToggleEffectKey` is derived
598
- * from that interface, and healing, defaults, hotkey snapshots and the panel all
599
- * iterate it as a closed compile-time union. Runtime keys in there would erase
600
- * that guarantee for the built-in effects too.
601
- */
602
- export interface SceneCustomEffect {
603
- /** Folder name under the config home's `effects/` dir — stable id and module location. */
604
- slug: string;
605
- enabled: boolean;
606
- /**
607
- * Author-declared values, keyed by the manifest's param names. Untyped by
608
- * construction: the specs arrive from disk at runtime, so these are validated
609
- * against the installed manifest rather than checked at compile time.
610
- */
611
- params: Record<string, number | boolean | string>;
612
- }
613
613
  /**
614
614
  * A 3D set's own suggested look — the fog and post settings it was authored against,
615
615
  * read from its root `LAPLACE_environment` extension and held on the scene for every client.
@@ -671,14 +671,6 @@ export interface SceneEnvironment {
671
671
  * switched off. Healing lists every switched-on effect, so a patch that switches one on adds its layer.
672
672
  */
673
673
  effectLayers: ToggleEffectKey[];
674
- /**
675
- * User-authored effects, in the order they compose. They run as a group at one
676
- * fixed point in the post chain — after grading and the screen-space warps,
677
- * before grain and vignette — so the built-in composition order stays fixed.
678
- * An entry whose effect is not installed here is kept, not dropped: it would
679
- * cost the user their tuning on a machine that simply lacks the folder.
680
- */
681
- customEffects: SceneCustomEffect[];
682
674
  }
683
675
  export interface Scene {
684
676
  id: string;
@@ -718,11 +710,13 @@ export interface ScenePatch {
718
710
  * App-level features a client gates on (never version-sniff): `hello` and
719
711
  * `app.info` report them — the per-app mirror of {@link InstanceRuntime.capabilities}.
720
712
  */
721
- export declare const APP_CAPABILITIES: readonly ["storage", "speech", "shortcuts"];
713
+ export declare const APP_CAPABILITIES: readonly ["storage", "speech", "shortcuts", "controllers", "model-editing", "asset-inspection"];
722
714
  export type AppCapability = (typeof APP_CAPABILITIES)[number];
723
715
  export declare function isAppCapability(v: unknown): v is AppCapability;
724
716
  /** What a loaded model instance can do; absent capabilities answer `unsupported-for-format`. */
725
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'
726
720
  /** `expression.setWeight`; Cubism expressions carry no user-settable weight, so Live2D lacks it. */
727
721
  | 'expression-weights'
728
722
  /** `speech.play` lip-sync; engine-dependent, so gate on the runtime, not the format. */
@@ -742,6 +736,8 @@ export interface InstanceRuntime {
742
736
  capabilities: InstanceCapability[];
743
737
  }
744
738
  export interface MotionGroup {
739
+ /** Whether this group loops; absent on older hosts. */
740
+ looping?: boolean;
745
741
  group: string;
746
742
  files: string[];
747
743
  }
@@ -766,20 +762,7 @@ export interface AnchorOption {
766
762
  anchor: AttachAnchor;
767
763
  label: string;
768
764
  }
769
- /**
770
- * Panel-style model info block. Format-specific fields mirror the app's `ModelInfo`.
771
- * `format` stays the union's discriminant here — this is a runtime diagnostic, not
772
- * content metadata, so it does not follow {@link ContentRef}'s `kind`.
773
- */
774
- export interface ModelInfo {
775
- format: ModelFormat;
776
- name: string;
777
- origin: ContentOrigin;
778
- file: string;
779
- loadMs: number;
780
- /** Format-specific details (canvas/params/textures for Live2D, spec/bones for VRM). */
781
- [key: string]: unknown;
782
- }
765
+ export type { ModelInfo } from '../values/model-info.ts';
783
766
  export interface Hotkey {
784
767
  id: string;
785
768
  name: string;
@@ -826,17 +809,17 @@ export interface ExpressionPersistence {
826
809
  export type JsonValue = string | number | boolean | null | JsonValue[] | {
827
810
  [key: string]: JsonValue;
828
811
  };
829
- export declare const TRACKING_SOURCE_IDS: readonly ["vts-ios", "ifacialmocap"];
812
+ export declare const TRACKING_SOURCE_IDS: readonly ["persona-ios", "ifacialmocap", "vts-ios"];
830
813
  export type TrackingSourceId = (typeof TRACKING_SOURCE_IDS)[number];
831
814
  /** Body-pose protocols. Every one so far is UDP with a configurable port. */
832
- export declare const POSE_SOURCE_IDS: readonly ["vmc"];
815
+ export declare const POSE_SOURCE_IDS: readonly ["vmc", "mocopi"];
833
816
  export type PoseSourceId = (typeof POSE_SOURCE_IDS)[number];
834
817
  export type TrackingStatus = 'off' | 'waiting' | 'tracking' | 'no-face';
835
818
  export type PoseStatus = 'off' | 'waiting' | 'tracking';
836
- /** Every protocol a tracking source instance can speak; face kinds plus the body (vmc) kind. */
837
- 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"];
838
821
  export type TrackingSourceKind = (typeof TRACKING_SOURCE_KINDS)[number];
839
- /** Whether a source kind feeds the face channel (vmc is the body channel). */
822
+ /** Whether a source kind feeds the face channel. */
840
823
  export declare function isFaceSourceKind(kind: TrackingSourceKind): kind is TrackingSourceId;
841
824
  /**
842
825
  * One configured tracking source instance. Several can run at once — one per person —
@@ -850,12 +833,24 @@ export interface TrackingSourceConfig {
850
833
  enabled: boolean;
851
834
  /**
852
835
  * Face kinds: accept only this device/sender IP; null accepts any sender not claimed
853
- * 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).
854
837
  */
855
838
  phoneIp: string | null;
856
- /** 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
+ */
857
843
  port: number | null;
858
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;
859
854
  export declare const EFFECTS_QUALITY_LEVELS: readonly ["low", "medium", "high"];
860
855
  export type EffectsQuality = (typeof EFFECTS_QUALITY_LEVELS)[number];
861
856
  /**
@@ -863,11 +858,20 @@ export type EffectsQuality = (typeof EFFECTS_QUALITY_LEVELS)[number];
863
858
  * to the nearest preset by the app, so a picker offering other values would lie.
864
859
  */
865
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
+ }
866
866
  /** The curated settings surface the API exposes — never the raw store shape. */
867
867
  export interface Settings {
868
+ /** Absent on hosts without remote controller management. */
869
+ controller?: ControllerConfig;
868
870
  /** `alwaysOnTop` floats the control-panel window, never the stage. */
869
871
  window: {
870
872
  alwaysOnTop: boolean;
873
+ /** Read-only; absent on older desktops. */
874
+ stageSize?: StageSize;
871
875
  };
872
876
  ui: {
873
877
  trayVisible: boolean;
@@ -877,6 +881,9 @@ export interface Settings {
877
881
  fpsLimit: number;
878
882
  selectionOutline: boolean;
879
883
  effectsQuality: EffectsQuality;
884
+ /** Absent on older hosts. */
885
+ renderScale?: number;
886
+ live2dEngine?: 'pixi' | 'three';
880
887
  };
881
888
  /** `source` mirrors the first face source's kind — the pre-multi-source field, kept for old clients. */
882
889
  tracking: {
@@ -884,7 +891,7 @@ export interface Settings {
884
891
  source: TrackingSourceId;
885
892
  sources: TrackingSourceConfig[];
886
893
  };
887
- /** `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. */
888
895
  pose: {
889
896
  enabled: boolean;
890
897
  source: PoseSourceId;
@@ -892,6 +899,7 @@ export interface Settings {
892
899
  };
893
900
  }
894
901
  export interface SettingsPatch {
902
+ controller?: Partial<Pick<ControllerConfig, 'enabled'>>;
895
903
  window?: {
896
904
  alwaysOnTop?: boolean;
897
905
  };
@@ -903,17 +911,19 @@ export interface SettingsPatch {
903
911
  fpsLimit?: number;
904
912
  selectionOutline?: boolean;
905
913
  effectsQuality?: EffectsQuality;
914
+ renderScale?: number;
915
+ live2dEngine?: 'pixi' | 'three';
906
916
  };
907
917
  }
908
918
  /** VTS's input vocabulary, plus `JawOpen` — the derived half of {@link INPUT_NAMES}. */
909
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"];
910
920
  type VtsInputName = (typeof VTS_INPUT_NAMES)[number];
911
921
  /**
912
- * The valid `id`s for `input` inject targets, and the names a model's `.vtube.json`
913
- * 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.
914
924
  */
915
- export declare const INPUT_NAMES: readonly InputName[];
916
- export type InputName = VtsInputName | ArkitInputName;
925
+ export declare const INPUT_NAMES: readonly (VtsInputName | ArkitInputName | BaseControllerInputName)[];
926
+ export type InputName = VtsInputName | ArkitInputName | ControllerInputName;
917
927
  /** Whether an untrusted string names a tracking input — the guard every wire boundary needs. */
918
928
  export declare function isInputName(v: string): v is InputName;
919
929
  /**
@@ -938,10 +948,12 @@ export declare const BINDING_INPUT_NAMES: readonly BindingInput[];
938
948
  /** The id an input is read as: its ARKit twin when it has one, else itself. */
939
949
  export declare function arkitTwinOf(input: InputName): BindingInput;
940
950
  /**
941
- * Each input's natural span — the units a sender should write, and the input range a new
942
- * 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.
943
953
  */
944
- 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];
945
957
  /**
946
958
  * What an injected value drives:
947
959
  * - `input` — a VTS-vocabulary tracking input (`MouthOpen`, `FaceAngleX`, …), mapped
@@ -962,4 +974,3 @@ export interface InjectEntry extends InjectTarget {
962
974
  /** 0..1 blend over whatever else drives the parameter; default 1. */
963
975
  weight?: number;
964
976
  }
965
- 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.0",
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
- }