@laplace.live/persona-sdk 1.21.0 → 1.23.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 { parseServerMessage } from "../wire/envelope.js";
2
2
  import { PersonaApiError } from "../wire/errors.js";
3
3
  import { CLOSE_FORCE_DISCONNECTED, CLOSE_KEY_REVOKED, INJECT_HEARTBEAT_MS, injectTargetKey, PROTOCOL_VERSION, SCENE_ACTIVATION_TIMEOUT_MS, } from "../wire/protocol.js";
4
4
  import { personaWsUrl } from "./address.js";
5
+ import { renameLegacyKinds } from "./legacy-kinds.js";
5
6
  const DEFAULTS = {
6
7
  url: personaWsUrl(),
7
8
  reconnectDelayMs: 500,
@@ -109,7 +110,8 @@ export class PersonaClient {
109
110
  reject(new Error(`request timed out: ${method}`));
110
111
  }, this.opts.requestTimeoutMs ??
111
112
  (method === 'scene.activate' ? SCENE_ACTIVATION_TIMEOUT_MS : DEFAULTS.requestTimeoutMs));
112
- this.pending.set(id, { resolve: resolve, reject, timer });
113
+ // Only the runtime id ties a response back to `M`, and its result is unvalidated wire JSON.
114
+ this.pending.set(id, { method, resolve: resolve, reject, timer });
113
115
  ws.send(JSON.stringify({ kind: 'request', id, method, ...(params === undefined ? {} : { params }) }));
114
116
  });
115
117
  }
@@ -355,8 +357,10 @@ export class PersonaClient {
355
357
  const set = this.listeners.get(msg.event);
356
358
  if (!set)
357
359
  return;
360
+ const data = renameLegacyKinds(msg.event, msg.data);
361
+ // Only `msg.event` ties these callbacks to their payload type, and the payload is unvalidated wire JSON.
358
362
  for (const cb of set)
359
- cb(msg.data);
363
+ cb(data);
360
364
  return;
361
365
  }
362
366
  const { id } = msg;
@@ -368,7 +372,7 @@ export class PersonaClient {
368
372
  this.pending.delete(id);
369
373
  clearTimeout(p.timer);
370
374
  if (msg.kind === 'response')
371
- p.resolve(msg.result);
375
+ p.resolve(renameLegacyKinds(p.method, msg.result));
372
376
  else
373
377
  p.reject(new PersonaApiError(msg.code, msg.message));
374
378
  }
@@ -0,0 +1,2 @@
1
+ /** A method result or event payload with the action kinds an older host still sends renamed to current ones. */
2
+ export declare function renameLegacyKinds(name: string, data: unknown): unknown;
@@ -0,0 +1,40 @@
1
+ import { isRecord } from "../values/guards.js";
2
+ import { canonicalActionKind } from "../wire/types.js";
3
+ function renameHotkeyActions(state) {
4
+ if (!isRecord(state) || !Array.isArray(state.hotkeys))
5
+ return state;
6
+ return {
7
+ ...state,
8
+ hotkeys: state.hotkeys.map((hotkey) => isRecord(hotkey) && typeof hotkey.action === 'string'
9
+ ? { ...hotkey, action: canonicalActionKind(hotkey.action) }
10
+ : hotkey),
11
+ };
12
+ }
13
+ function renameActionKinds(list) {
14
+ if (!isRecord(list) || !Array.isArray(list.automations))
15
+ return list;
16
+ return {
17
+ ...list,
18
+ automations: list.automations.map((automation) => isRecord(automation) && Array.isArray(automation.actionKinds)
19
+ ? {
20
+ ...automation,
21
+ actionKinds: automation.actionKinds.map((kind) => typeof kind === 'string' ? canonicalActionKind(kind) : kind),
22
+ }
23
+ : automation),
24
+ };
25
+ }
26
+ /** A method result or event payload with the action kinds an older host still sends renamed to current ones. */
27
+ export function renameLegacyKinds(name, data) {
28
+ switch (name) {
29
+ case 'hotkey.list':
30
+ case 'hotkey.set':
31
+ return renameHotkeyActions(data);
32
+ case 'hotkey.state':
33
+ return isRecord(data) ? { ...data, config: renameHotkeyActions(data.config) } : data;
34
+ case 'automation.list':
35
+ case 'automation.state':
36
+ return renameActionKinds(data);
37
+ default:
38
+ return data;
39
+ }
40
+ }
package/dist/index.d.ts CHANGED
@@ -10,6 +10,7 @@ export * from './values/effect-schema.ts';
10
10
  export * from './values/gltf-extensions.ts';
11
11
  export * from './values/guards.ts';
12
12
  export * from './values/hands.ts';
13
+ export * from './values/hotkey-targets.ts';
13
14
  export * from './values/hotkeys.ts';
14
15
  export * from './values/item-transition.ts';
15
16
  export * from './values/labels.ts';
package/dist/index.js CHANGED
@@ -12,6 +12,7 @@ export * from "./values/effect-schema.js";
12
12
  export * from "./values/gltf-extensions.js";
13
13
  export * from "./values/guards.js";
14
14
  export * from "./values/hands.js";
15
+ export * from "./values/hotkey-targets.js";
15
16
  export * from "./values/hotkeys.js";
16
17
  export * from "./values/item-transition.js";
17
18
  export * from "./values/labels.js";
@@ -147,11 +147,12 @@ export const EFFECT_SPECS = {
147
147
  blur: {
148
148
  radius: { default: 8, min: 0, max: 64, step: 0.5, unit: 'px' },
149
149
  },
150
+ // Intensity and Threshold take Warudo's Camera Bloom units; with Radius, the defaults are its look at 1080p.
150
151
  bloom: {
151
- intensity: { default: 0, min: 0, max: 10, step: 0.01 },
152
- threshold: { default: 0.5, min: 0, max: 2, step: 0.01 },
152
+ intensity: { default: 0.1, min: 0, max: 10, step: 0.01 },
153
+ threshold: { default: 0.75, min: 0, max: 5, step: 0.01 },
153
154
  thresholdSmooth: { default: 0, min: 0, max: 1, step: 0.01 },
154
- radius: { default: 1, min: 0, max: 6, step: 0.01 },
155
+ radius: { default: 3, min: 0, max: 6, step: 0.01 },
155
156
  saturation: { default: 0, min: -1, max: 1, step: 0.01 },
156
157
  opacity: { default: 1, min: 0, max: 1, step: 0.01 },
157
158
  starRays: { default: 3, min: 1, max: 6, step: 1 },
@@ -308,7 +309,7 @@ export const EFFECT_COLOR_SPECS = {
308
309
  color5: { default: '#ffffff' },
309
310
  },
310
311
  rim: {
311
- color: { default: '#ffffff' },
312
+ color: { default: '#ffeb74' },
312
313
  },
313
314
  outline: {
314
315
  color: { default: '#000000' },
@@ -394,13 +395,7 @@ export const EFFECT_ENUM_SPECS = {
394
395
  },
395
396
  blur: { mode: { default: 'gaussian', values: ['gaussian', 'bokeh'] } },
396
397
  bloom: { mode: { default: 'normal', values: ['normal', 'streak', 'star'] } },
397
- rim: {
398
- mode: { default: 'single', values: ['single', 'double', 'sharpenSingle', 'sharpenDouble'] },
399
- blendMode: {
400
- default: 'overlay',
401
- values: EFFECT_BLEND_MODES,
402
- },
403
- },
398
+ rim: { mode: { default: 'single', values: ['single', 'double', 'sharpenSingle', 'sharpenDouble'] } },
404
399
  outline: { quality: { default: 'medium', values: ['low', 'medium', 'high'] } },
405
400
  colorWheels: { mode: { default: 'liftGammaGain', values: ['liftGammaGain', 'shadowsMidtonesHighlights'] } },
406
401
  };
@@ -0,0 +1,56 @@
1
+ import type { Hotkey, MotionGroup } from '../wire/types.ts';
2
+ /** A hotkey action Persona can run. */
3
+ type HotkeyAction = NonNullable<Hotkey['action']>;
4
+ /** What a model offers hotkeys: its expressions (a null `file` on formats with none) and its motion groups. */
5
+ export interface HotkeyListing {
6
+ expressions: readonly {
7
+ name: string;
8
+ file: string | null;
9
+ }[];
10
+ motions: readonly MotionGroup[];
11
+ }
12
+ /**
13
+ * The id an expression is stored and bound by: its exp3 basename on Live2D (VTS parity),
14
+ * its name on VRM, which declares no per-expression file. Every persisted or hotkey-bound
15
+ * reference to an expression goes through this, so the two can never disagree.
16
+ */
17
+ export declare function expressionKey(def: {
18
+ name: string;
19
+ file: string | null;
20
+ }): string;
21
+ /** Saved basenames → definition Names, in saved order; unmatched entries dropped. */
22
+ export declare function matchSavedToDefinitions(saved: readonly string[], defs: readonly {
23
+ Name: string;
24
+ File: string;
25
+ }[]): string[];
26
+ /** Expression Name for a stored expression key, or null when the model no longer declares it. */
27
+ export declare function resolveExpressionName(listing: HotkeyListing, file: string): string | null;
28
+ /** Motion group and index for a stored motion3 basename, or null when it's gone. */
29
+ export declare function resolveMotion(listing: {
30
+ motions: readonly MotionGroup[];
31
+ }, file: string): {
32
+ group: string;
33
+ index: number;
34
+ } | null;
35
+ /** `file` is a basename throughout — the form hotkeys are stored in. */
36
+ export interface HotkeyTarget {
37
+ file: string;
38
+ label: string;
39
+ }
40
+ /**
41
+ * The files an action's hotkey can point at, with display labels. Deduped by basename,
42
+ * first occurrence wins — the same rule the resolvers above apply, so the picker always
43
+ * lists exactly what a hotkey would run.
44
+ * @param groupLabel Display name for a motion group; VRM groups are clip refs, not names.
45
+ */
46
+ export declare function hotkeyTargets(listing: HotkeyListing, action: HotkeyAction, groupLabel?: (group: string) => string): HotkeyTarget[];
47
+ /**
48
+ * Whether a model-load hotkey's stored `File` names this model: its `.vtube.json` name (what VTS
49
+ * stores) or its entry file, case-insensitively — VTS's own file-name resolution. A `ModelRef`
50
+ * from an older host lists neither, and matches nothing.
51
+ */
52
+ export declare function modelMatchesFile(file: string, model: {
53
+ vtubeFile?: string | null;
54
+ entryFile?: string;
55
+ }): boolean;
56
+ export {};
@@ -0,0 +1,88 @@
1
+ import { fileBasename, motionLabel, motionSlots } from "./labels.js";
2
+ /**
3
+ * The id an expression is stored and bound by: its exp3 basename on Live2D (VTS parity),
4
+ * its name on VRM, which declares no per-expression file. Every persisted or hotkey-bound
5
+ * reference to an expression goes through this, so the two can never disagree.
6
+ */
7
+ export function expressionKey(def) {
8
+ return def.file === null ? def.name : fileBasename(def.file);
9
+ }
10
+ /** Saved basenames → definition Names, in saved order; unmatched entries dropped. */
11
+ export function matchSavedToDefinitions(saved, defs) {
12
+ const byBase = new Map();
13
+ for (const d of defs) {
14
+ const base = fileBasename(d.File);
15
+ if (!byBase.has(base))
16
+ byBase.set(base, d.Name);
17
+ }
18
+ return saved.flatMap(s => {
19
+ const name = byBase.get(fileBasename(s));
20
+ return name === undefined ? [] : [name];
21
+ });
22
+ }
23
+ // A hotkey stores the same id expression persistence does, so a VRM entry binds by name.
24
+ // A keyless entry (a model3 declaring an empty File) is dropped — nothing could bind it.
25
+ function expressionDefs(listing) {
26
+ return listing.expressions.flatMap(d => {
27
+ const key = expressionKey(d);
28
+ return key === '' ? [] : [{ Name: d.name, File: key }];
29
+ });
30
+ }
31
+ /** Expression Name for a stored expression key, or null when the model no longer declares it. */
32
+ export function resolveExpressionName(listing, file) {
33
+ return matchSavedToDefinitions([file], expressionDefs(listing))[0] ?? null;
34
+ }
35
+ /** Motion group and index for a stored motion3 basename, or null when it's gone. */
36
+ export function resolveMotion(listing, file) {
37
+ const base = fileBasename(file);
38
+ for (const { group, files } of listing.motions) {
39
+ const slot = motionSlots(files).find(s => fileBasename(s.file) === base);
40
+ if (slot)
41
+ return { group, index: slot.index };
42
+ }
43
+ return null;
44
+ }
45
+ /**
46
+ * The files an action's hotkey can point at, with display labels. Deduped by basename,
47
+ * first occurrence wins — the same rule the resolvers above apply, so the picker always
48
+ * lists exactly what a hotkey would run.
49
+ * @param groupLabel Display name for a motion group; VRM groups are clip refs, not names.
50
+ */
51
+ export function hotkeyTargets(listing, action, groupLabel = g => g) {
52
+ const seen = new Set();
53
+ if (action === 'expression-toggle') {
54
+ return expressionDefs(listing).flatMap(d => {
55
+ const file = fileBasename(d.File);
56
+ if (seen.has(file))
57
+ return [];
58
+ seen.add(file);
59
+ return [{ file, label: d.Name }];
60
+ });
61
+ }
62
+ if (action === 'motion-play') {
63
+ return listing.motions.flatMap(g => motionSlots(g.files).flatMap(({ file: f }) => {
64
+ const file = fileBasename(f);
65
+ if (seen.has(file))
66
+ return [];
67
+ seen.add(file);
68
+ // A VRM group is a single clip named for its own file, so the ` · file` half
69
+ // would just repeat the group.
70
+ const label = g.group === f ? groupLabel(g.group) : `${groupLabel(g.group)} · ${motionLabel(f)}`;
71
+ return [{ file, label }];
72
+ }));
73
+ }
74
+ return [];
75
+ }
76
+ /**
77
+ * Whether a model-load hotkey's stored `File` names this model: its `.vtube.json` name (what VTS
78
+ * stores) or its entry file, case-insensitively — VTS's own file-name resolution. A `ModelRef`
79
+ * from an older host lists neither, and matches nothing.
80
+ */
81
+ export function modelMatchesFile(file, model) {
82
+ const base = fileBasename(file).toLowerCase();
83
+ if (base === '')
84
+ return false;
85
+ if (model.vtubeFile?.toLowerCase() === base)
86
+ return true;
87
+ return model.entryFile !== undefined && fileBasename(model.entryFile).toLowerCase() === base;
88
+ }
@@ -1,12 +1,14 @@
1
- export declare const ITEM_TRANSITION_STYLES: readonly ["glitch", "dither", "pop", "cut"];
1
+ export declare const ITEM_TRANSITION_STYLES: readonly ["glitch", "fade", "pop", "cut"];
2
2
  export type ItemTransitionStyle = (typeof ITEM_TRANSITION_STYLES)[number];
3
+ /** What `fade` was named while it was a dithered dissolve; hosts still accept it, as `fade`. */
4
+ export declare const LEGACY_DITHER_STYLE = "dither";
3
5
  /** One ramp length for a model switch, an object fade, or a body's entrance and exit. */
4
6
  export declare const ITEM_TRANSITION_DEFAULT_MS = 300;
5
7
  export declare const ITEM_TRANSITION_MAX_MS = 5000;
6
8
  export interface ItemTransition {
7
9
  /**
8
- * `glitch` tears and burns through a dithered dissolve, `dither` is the plain screen-door,
9
- * `pop` scales in and out, `cut` switches.
10
+ * `glitch` tears and burns through a dithered dissolve, `fade` fades each item by alpha as one
11
+ * layer, `pop` scales in and out, `cut` switches.
10
12
  */
11
13
  style: ItemTransitionStyle;
12
14
  /** Length of the ramp either way, 0–`ITEM_TRANSITION_MAX_MS`; 0 behaves as `cut`. */
@@ -16,8 +18,8 @@ export declare function defaultItemTransition(): ItemTransition;
16
18
  /** A per-call departure from the scene's item transition, for `stage.spawn`. */
17
19
  export interface SpawnTransition {
18
20
  style?: ItemTransitionStyle;
19
- /** Entrance ramp, 0–`ITEM_TRANSITION_MAX_MS`; defaults to the scene's duration. */
21
+ /** Entrance ramp, 0–`ITEM_TRANSITION_MAX_MS`; defaults to the scene's duration, and a `cut` ignores it. */
20
22
  inMs?: number;
21
- /** Exit ramp — expiry, eviction and clears — 0–`ITEM_TRANSITION_MAX_MS`; defaults to the scene's duration. */
23
+ /** Exit ramp — expiry, eviction and clears — 0–`ITEM_TRANSITION_MAX_MS`; as `inMs`. */
22
24
  outMs?: number;
23
25
  }
@@ -1,6 +1,8 @@
1
1
  // How stage items appear and disappear — models, objects and spawned bodies alike. Per scene,
2
2
  // on `SceneEnvironment.itemTransition`; `stage.spawn` may override it per call.
3
- export const ITEM_TRANSITION_STYLES = ['glitch', 'dither', 'pop', 'cut'];
3
+ export const ITEM_TRANSITION_STYLES = ['glitch', 'fade', 'pop', 'cut'];
4
+ /** What `fade` was named while it was a dithered dissolve; hosts still accept it, as `fade`. */
5
+ export const LEGACY_DITHER_STYLE = 'dither';
4
6
  /** One ramp length for a model switch, an object fade, or a body's entrance and exit. */
5
7
  export const ITEM_TRANSITION_DEFAULT_MS = 300;
6
8
  export const ITEM_TRANSITION_MAX_MS = 5000;
@@ -1,4 +1,4 @@
1
- import type { AutomationBoundaryKind, AutomationInfo } from '../wire/types.ts';
1
+ import type { AutomationInfo, AutomationSetupKind } from '../wire/types.ts';
2
2
  /** Last path segment, tolerating both `/` and `\` separators. */
3
3
  export declare function fileBasename(path: string): string;
4
4
  /**
@@ -20,7 +20,9 @@ export declare function motionSlots(files: readonly string[]): {
20
20
  /** English label for one automation action kind; kinds newer than this SDK read `Action`. */
21
21
  export declare function automationActionLabel(kind: string): string;
22
22
  /** True for an action that prepares the scene before playback. */
23
- export declare function isAutomationBoundary(kind: string | undefined): kind is AutomationBoundaryKind;
23
+ export declare function isAutomationSetup(kind: string | undefined): kind is AutomationSetupKind;
24
+ /** @deprecated Use `isAutomationSetup`. */
25
+ export declare const isAutomationBoundary: typeof isAutomationSetup;
24
26
  /** Join action labels without implying timing that the client summary does not carry. */
25
27
  export declare function joinAutomationActionLabels(labels: readonly string[], kinds: readonly string[]): string;
26
28
  /** Display label for an app automation: its title, else its joined action-kind labels. */
@@ -1,4 +1,4 @@
1
- import { AUTOMATION_BOUNDARY_KINDS } from "../wire/types.js";
1
+ import { AUTOMATION_SETUP_KINDS } from "../wire/types.js";
2
2
  import { isOneOf } from "./guards.js";
3
3
  /** Last path segment, tolerating both `/` and `\` separators. */
4
4
  export function fileBasename(path) {
@@ -28,21 +28,27 @@ export function motionSlots(files) {
28
28
  // English fallbacks; localizing clients keep their own map and use these for unknown kinds.
29
29
  const AUTOMATION_ACTION_KIND_LABELS = {
30
30
  'effect-toggle': 'Toggle Effect',
31
- 'effect-params': 'Effect Settings',
32
- 'effect-clip': 'Effect Clip',
31
+ 'effect-params': 'Apply Effect Settings',
32
+ 'effect-clip': 'Temporary Effect',
33
33
  'camera-pose': 'Camera Position',
34
- 'reset-camera': 'Reset Camera',
34
+ 'camera-reset': 'Reset Camera',
35
35
  'layer-visibility': 'Layer Visibility',
36
+ 'text-set': 'Set Text',
37
+ 'lyrics-play': 'Lyrics',
36
38
  'stream-mode': 'Stream Mode',
37
- 'switch-scene': 'Switch Scene',
38
- 'toggle-expression': 'Toggle Expression',
39
- 'play-motion': 'Play Motion',
40
- 'play-audio': 'Play Audio',
39
+ 'scene-switch': 'Switch Scene',
40
+ 'expression-toggle': 'Toggle Expression',
41
+ 'motion-play': 'Play Motion',
42
+ 'audio-play': 'Play Audio',
41
43
  'audio-control': 'Control Audio',
42
- 'play-camera-motion': 'Play Camera Motion',
43
- 'stop-camera-motion': 'Stop Camera Motion',
44
- 'remove-all-expressions': 'Clear All Expressions',
45
- 'load-model': 'Swap Model',
44
+ 'camera-motion-play': 'Play Camera Motion',
45
+ 'camera-motion-stop': 'Stop Camera Motion',
46
+ 'camera-follow': 'Camera Follow',
47
+ 'camera-aim': 'Camera Aim',
48
+ 'camera-focus': 'Camera Focus',
49
+ 'camera-handheld': 'Handheld Movement',
50
+ 'expression-clear': 'Clear All Expressions',
51
+ 'model-load': 'Swap Model',
46
52
  'model-position': 'Model Position',
47
53
  };
48
54
  /** English label for one automation action kind; kinds newer than this SDK read `Action`. */
@@ -50,9 +56,11 @@ export function automationActionLabel(kind) {
50
56
  return AUTOMATION_ACTION_KIND_LABELS[kind] ?? 'Action';
51
57
  }
52
58
  /** True for an action that prepares the scene before playback. */
53
- export function isAutomationBoundary(kind) {
54
- return isOneOf(kind, AUTOMATION_BOUNDARY_KINDS);
59
+ export function isAutomationSetup(kind) {
60
+ return isOneOf(kind, AUTOMATION_SETUP_KINDS);
55
61
  }
62
+ /** @deprecated Use `isAutomationSetup`. */
63
+ export const isAutomationBoundary = isAutomationSetup;
56
64
  /** Join action labels without implying timing that the client summary does not carry. */
57
65
  export function joinAutomationActionLabels(labels, kinds) {
58
66
  if (labels.length !== kinds.length)
@@ -1,4 +1,4 @@
1
- import type { AssetRef, Attach, AttachDepth, EnvironmentLook, ModelFormat, MToonTuning, ObjectContent, ObjectSpace, SceneEnvironment, SceneLight, SceneLightCameraFollowOptions, SceneLightType, ScreenPlacement, VrmPlacement } from '../wire/types.ts';
1
+ import type { AssetRef, Attach, AttachDepth, CameraAnchor, CameraFollowDamping, CameraTrackTarget, ChromaKey, EnvironmentLook, KeyableContent, ModelFormat, MToonTuning, ObjectContent, ObjectSpace, SceneCameraAim, SceneCameraAimSettings, SceneCameraFocus, SceneCameraFocusSettings, SceneCameraFollow, SceneCameraFollowSettings, SceneCameraHandheld, SceneEnvironment, SceneLight, SceneLightCameraFollowOptions, SceneLightType, ScreenPlacement, VrmPlacement } from '../wire/types.ts';
2
2
  export declare function clamp(v: number, min: number, max: number): number;
3
3
  /** Clamp to the unit interval. */
4
4
  export declare function clamp01(v: number): number;
@@ -63,6 +63,66 @@ export declare const SCENE_FOV_MAX = 90;
63
63
  /** Nonsingular authored field of view in degrees, including every valid whole-degree VMD key. */
64
64
  export declare const CAMERA_FOV_MIN = 1;
65
65
  export declare const CAMERA_FOV_MAX = 179;
66
+ /** Ceiling for each follow and aim damping, in seconds. */
67
+ export declare const CAMERA_DAMPING_MAX = 10;
68
+ /** Ceiling for {@link SceneCameraAim}'s lookahead time, in seconds, and its smoothing. */
69
+ export declare const CAMERA_AIM_LOOKAHEAD_MAX = 1;
70
+ export declare const CAMERA_AIM_SMOOTHING_MAX = 30;
71
+ /** Ceiling for {@link SceneCameraHandheld}'s speed. */
72
+ export declare const CAMERA_HANDHELD_SPEED_MAX = 2;
73
+ /** Warudo's Handheld Movement defaults, switched off. */
74
+ export declare const DEFAULT_CAMERA_HANDHELD: SceneCameraHandheld;
75
+ /**
76
+ * A fresh follow's damping: Warudo's second of position lag, and half a second on each turn so a
77
+ * tracked head's jitter never shakes the view.
78
+ */
79
+ export declare const DEFAULT_CAMERA_FOLLOW_DAMPING: CameraFollowDamping;
80
+ /** Where a fresh follow, aim or focus tracks a model; an object is always tracked by its root. */
81
+ export declare const DEFAULT_CAMERA_ANCHOR: CameraAnchor;
82
+ /** A fresh follow's settings: position only. */
83
+ export declare function defaultCameraFollowSettings(): SceneCameraFollowSettings;
84
+ /** A fresh follow of `target`, position only. */
85
+ export declare function defaultCameraFollow(target: CameraTrackTarget): SceneCameraFollow;
86
+ /** A fresh aim's settings, Warudo's Composer defaults: centred, no dead zone, 0.8 soft zone. */
87
+ export declare function defaultCameraAimSettings(): SceneCameraAimSettings;
88
+ /** A fresh aim at `target`. */
89
+ export declare function defaultCameraAim(target: CameraTrackTarget): SceneCameraAim;
90
+ /** A fresh focus's settings, refocusing about as fast as Warudo's default focusing speed. */
91
+ export declare function defaultCameraFocusSettings(): SceneCameraFocusSettings;
92
+ /** A fresh focus on `target`. */
93
+ export declare function defaultCameraFocus(target: CameraTrackTarget): SceneCameraFocus;
94
+ /** A follow's, aim's or focus's settings alone, without the layer it names. */
95
+ export declare function cameraTrackSettingsOf<T extends CameraTrackTarget>(track: T): Omit<T, 'instanceId' | 'target'>;
96
+ /** The instance `target` tracks in a scene whose primary model is `primaryInstanceId`. */
97
+ export declare function cameraTrackInstanceId(target: CameraTrackTarget, primaryInstanceId: string | null): string | null;
98
+ /** How a camera can track `item`: by any anchor on a VRM model, by the root of a 3D object, or not at all. */
99
+ export declare function cameraTrackKind(item: {
100
+ kind: 'model';
101
+ ref: {
102
+ kind: string;
103
+ };
104
+ } | {
105
+ kind: 'object';
106
+ space: ObjectSpace;
107
+ }): 'model' | 'object' | null;
108
+ /** Rows for the camera's follow, aim and focus pickers, in layer order. One rule, or the console offers a layer the desktop drops. */
109
+ export declare function cameraTrackCandidates(items: readonly ({
110
+ kind: 'model';
111
+ instanceId: string;
112
+ ref: {
113
+ kind: string;
114
+ name: string;
115
+ };
116
+ } | {
117
+ kind: 'object';
118
+ instanceId: string;
119
+ name: string;
120
+ space: ObjectSpace;
121
+ })[]): {
122
+ instanceId: string;
123
+ name: string;
124
+ model: boolean;
125
+ }[];
66
126
  /** Manual navigation stops short of ±90°; authored camera elevations remain unrestricted. */
67
127
  export declare const ELEVATION_LIMIT: number;
68
128
  /** Manual navigation distance bounds; authored cameras retain signed, unrestricted distances. */
@@ -172,9 +232,39 @@ export declare const WEB_SIZE_MAX = 7680;
172
232
  /** Web object paint-rate bounds, frames per second. */
173
233
  export declare const WEB_FPS_MIN = 1;
174
234
  export declare const WEB_FPS_MAX = 60;
235
+ /** Chroma key tolerance bounds, OBS's 1–1000 ÷ 1000; the floor keeps the edge ramps from dividing by zero. */
236
+ export declare const CHROMA_KEY_TOLERANCE_MIN = 0.001;
237
+ export declare const CHROMA_KEY_TOLERANCE_MAX = 1;
238
+ /** OBS's Chroma Key defaults: green, similarity 400, smoothness 80, spill reduction 100. */
239
+ export declare const DEFAULT_CHROMA_KEY: ChromaKey;
240
+ /** Whether a chroma key can reach the content's pixels: image, video and webpage quads. */
241
+ export declare function isKeyableContent(content: ObjectContent): content is KeyableContent;
242
+ /** Text object bounds: characters, em size in px, and the box and paint extents in px. */
243
+ export declare const TEXT_LENGTH_MAX = 10000;
244
+ export declare const TEXT_SIZE_MIN = 4;
245
+ export declare const TEXT_SIZE_MAX = 1000;
246
+ export declare const TEXT_SHEAR_MAX = 1;
247
+ /** Letter and word spacing, em. */
248
+ export declare const TEXT_SPACING_MIN = -0.5;
249
+ export declare const TEXT_SPACING_MAX = 2;
250
+ export declare const TEXT_LINE_HEIGHT_MIN = 0.5;
251
+ export declare const TEXT_LINE_HEIGHT_MAX = 4;
252
+ export declare const TEXT_BOX_MAX = 7680;
253
+ export declare const TEXT_STROKE_MAX = 100;
254
+ export declare const TEXT_SHADOW_OFFSET_MAX = 200;
255
+ export declare const TEXT_SHADOW_BLUR_MAX = 100;
256
+ export declare const TEXT_PLATE_PADDING_MAX = 500;
257
+ export declare const TEXT_PLATE_RADIUS_MAX = 500;
258
+ /** 3D text: extrusion and bevel in em, bevel segments, and emission strength. */
259
+ export declare const TEXT_EXTRUDE_MAX = 2;
260
+ export declare const TEXT_BEVEL_MAX = 0.2;
261
+ export declare const TEXT_BEVEL_RESOLUTION_MAX = 8;
262
+ export declare const TEXT_EMISSION_MAX = 4;
263
+ /** A PostScript name: printable ASCII, at most 63 chars, minus the quote and backslash that would escape `local("…")`. */
264
+ export declare const TEXT_POSTSCRIPT_NAME_RE: RegExp;
175
265
  /** A prop is a mesh, so it only exists in the three.js scene; every other kind renders in both. */
176
266
  export declare function objectSupportsSpace(kind: ObjectContent['kind'], space: ObjectSpace): boolean;
177
- /** The asset an object streams from, or null for kinds that carry their source inline (web, capture). */
267
+ /** The asset an object streams from, or null for kinds that carry their source inline (web, capture, text). */
178
268
  export declare function contentAssetId(content: ObjectContent): string | null;
179
269
  /**
180
270
  * The one model format a space's objects can ride: the screen layer always composites
@@ -233,9 +323,12 @@ export declare function pinEntersStack(item: {
233
323
  export declare function depthStops(meshes: readonly string[]): AttachDepth[];
234
324
  /** Which stop a depth sits at; an ArtMesh the host no longer has reads as the frontmost. */
235
325
  export declare function depthStopIndex(meshes: readonly string[], depth: AttachDepth): number;
236
- /** A fresh pin onto `parentInstanceId`: root anchor, in front of the model, no response tuning. */
237
- export declare function defaultAttach(parentInstanceId: string): Attach;
238
326
  export declare const DEFAULT_HEAD_ANGLE: NonNullable<Attach['headAngle']>;
327
+ /**
328
+ * A fresh pin onto `parentInstanceId`: root anchor, in front of the model. A screen-space item (a
329
+ * Live2D model, a 2D object) follows the host's head at the default response; others take none.
330
+ */
331
+ export declare function defaultAttach(parentInstanceId: string, screenSpace?: boolean): Attach;
239
332
  export declare const ATTACH_MULTIPLIER_MIN = -2;
240
333
  export declare const ATTACH_MULTIPLIER_MAX = 2;
241
334
  /** Parallax slider ceiling, symmetric: stage px at a full head turn. */