@laplace.live/persona-sdk 1.0.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -20,7 +20,9 @@ await persona.connect();
20
20
  // Typed request/response
21
21
  const { scenes, activeSceneId } = await persona.call("scene.list");
22
22
  await persona.call("expression.toggle", { name: "Smile" });
23
- await persona.call("scene.patch", { behavior: { lookAtCursor: true } });
23
+ await persona.call("scene.patch", {
24
+ background: { mode: "color", color: "#112233", imageAssetId: null },
25
+ });
24
26
 
25
27
  // Events (subscriptions survive reconnects)
26
28
  persona.on("motion.started", (m) => console.log("playing", m.group));
package/dist/index.d.ts CHANGED
@@ -8,9 +8,11 @@ export * from './values/edit-history.ts';
8
8
  export * from './values/effect-schema.ts';
9
9
  export * from './values/gltf-extensions.ts';
10
10
  export * from './values/guards.ts';
11
+ export * from './values/hands.ts';
11
12
  export * from './values/hotkeys.ts';
12
13
  export * from './values/labels.ts';
13
14
  export * from './values/limits.ts';
15
+ export * from './values/lipsync.ts';
14
16
  export * from './values/locale.ts';
15
17
  export * from './values/model-info.ts';
16
18
  export * from './values/stage-info.ts';
package/dist/index.js CHANGED
@@ -10,9 +10,11 @@ export * from "./values/edit-history.js";
10
10
  export * from "./values/effect-schema.js";
11
11
  export * from "./values/gltf-extensions.js";
12
12
  export * from "./values/guards.js";
13
+ export * from "./values/hands.js";
13
14
  export * from "./values/hotkeys.js";
14
15
  export * from "./values/labels.js";
15
16
  export * from "./values/limits.js";
17
+ export * from "./values/lipsync.js";
16
18
  export * from "./values/locale.js";
17
19
  export * from "./values/model-info.js";
18
20
  export * from "./values/stage-info.js";
@@ -1,4 +1,38 @@
1
- import type { SceneEffects, ToggleEffectKey } from '../wire/types.ts';
1
+ import type { EffectKey, EffectKeyForScope, EffectScope, EffectValues, LayerEffectKey, LayerEffects, SceneEffectKey } from '../wire/types.ts';
2
+ /** Supported owners of each toggle effect; scene and layer membership are independent. */
3
+ export declare const EFFECT_SCOPES: {
4
+ readonly color: readonly ["scene", "layer"];
5
+ readonly levels: readonly ["scene", "layer"];
6
+ readonly colorWheels: readonly ["scene", "layer"];
7
+ readonly colorShift: readonly ["scene", "layer"];
8
+ readonly selectColors: readonly ["scene", "layer"];
9
+ readonly blur: readonly ["scene", "layer"];
10
+ readonly bloom: readonly ["scene", "layer"];
11
+ readonly diffusion: readonly ["scene", "layer"];
12
+ readonly dof: readonly ["scene"];
13
+ readonly chromaticAberration: readonly ["scene"];
14
+ readonly grain: readonly ["scene"];
15
+ readonly vignette: readonly ["scene"];
16
+ readonly rim: readonly ["scene", "layer"];
17
+ readonly outline: readonly ["scene", "layer"];
18
+ readonly dropShadow: readonly ["scene", "layer"];
19
+ readonly pixelate: readonly ["scene"];
20
+ readonly glitch: readonly ["scene"];
21
+ readonly droplets: readonly ["scene"];
22
+ readonly rain: readonly ["scene"];
23
+ readonly snow: readonly ["scene"];
24
+ };
25
+ export declare const EFFECT_KEYS: readonly EffectKey[];
26
+ /** Older hosts advertise effect availability through the values present in their payload. */
27
+ export declare function availableEffectKeys<S extends EffectScope>(scope: S, effects: Partial<Record<EffectKey, unknown>>): EffectKeyForScope<S>[];
28
+ /** Preserve the option shape advertised by the host when resetting or applying settings. */
29
+ export declare function supportedEffectParams<T extends object>(current: T, patch: Partial<T>): Partial<T>;
30
+ /** Named modes distinguish the newer rim/outline size and bloom intensity units from older hosts. */
31
+ export declare function hasCurrentEffectFormat(effect: EffectKey, value: object): boolean;
32
+ export declare const SCENE_EFFECT_KEYS: readonly EffectKeyForScope<"scene">[];
33
+ /** Scene toggle keys; retained for existing SDK consumers. */
34
+ export declare const TOGGLE_EFFECT_KEYS: readonly EffectKeyForScope<"scene">[];
35
+ export declare const LAYER_EFFECT_KEYS: readonly EffectKeyForScope<"layer">[];
2
36
  /** Slider + healing metadata for one numeric effect parameter. */
3
37
  export interface EffectParamSpec {
4
38
  default: number;
@@ -14,28 +48,54 @@ export interface EffectParamSpec {
14
48
  export interface EffectColorSpec {
15
49
  default: string;
16
50
  }
17
- export declare const EFFECT_SPECS: Record<ToggleEffectKey, Readonly<Record<string, EffectParamSpec>>>;
51
+ /** Boolean effect parameters other than the common enabled switch. */
52
+ export interface EffectBooleanSpec {
53
+ default: boolean;
54
+ }
55
+ /** Named choices; values are protocol strings and labels belong to the UI. */
56
+ export interface EffectEnumSpec<T extends string = string> {
57
+ default: T;
58
+ values: readonly T[];
59
+ }
60
+ export declare const EFFECT_SPECS: Record<EffectKey, Readonly<Record<string, EffectParamSpec>>>;
18
61
  /**
19
62
  * Hex-color params, keyed like {@link EFFECT_SPECS} — generic walkers (healing,
20
63
  * defaults, hotkey snapshots, the panel) visit both tables. Only effects with a
21
64
  * string param appear.
22
65
  */
23
- export declare const EFFECT_COLOR_SPECS: Partial<Record<ToggleEffectKey, Readonly<Record<string, EffectColorSpec>>>>;
24
- export declare const TOGGLE_EFFECT_KEYS: readonly ToggleEffectKey[];
66
+ export declare const EFFECT_COLOR_SPECS: Partial<Record<EffectKey, Readonly<Record<string, EffectColorSpec>>>>;
67
+ /** Boolean defaults, validated alongside numeric and color parameters. */
68
+ export declare const EFFECT_BOOLEAN_SPECS: Partial<Record<EffectKey, Readonly<Record<string, EffectBooleanSpec>>>>;
69
+ /** Enum defaults and allowed values. */
70
+ export declare const EFFECT_ENUM_SPECS: Partial<Record<EffectKey, Readonly<Record<string, EffectEnumSpec>>>>;
71
+ /** Select the layer effects from any value set containing them. */
72
+ export declare function pickLayerEffects<T extends LayerEffects>(effects: T): LayerEffects;
73
+ /** The canonical per-item effect list, preserving disabled rows and including every enabled effect. */
74
+ export declare function layerEffectKeys(listed: readonly unknown[], effects: LayerEffects): LayerEffectKey[];
75
+ /** An independent, disabled set for a fresh source layer. */
76
+ export declare function defaultLayerEffects(): LayerEffects;
77
+ /** Whether a source needs its own effect pass. */
78
+ export declare function layerEffectsActive(effects: LayerEffects): boolean;
25
79
  /**
26
80
  * Registry effects that render as scene geometry inside the scene pass rather
27
81
  * than composing into the post chain. A new scene-space effect must join this
28
82
  * list, or its toggle needlessly forces the post path on and rebuilds the graph.
29
83
  */
30
- export declare const SCENE_SPACE_EFFECT_KEYS: readonly ToggleEffectKey[];
84
+ export declare const SCENE_SPACE_EFFECT_KEYS: readonly SceneEffectKey[];
31
85
  /** Registry effects the post chain composes — {@link TOGGLE_EFFECT_KEYS} minus the scene-space ones. */
32
- export declare const POST_EFFECT_KEYS: readonly ToggleEffectKey[];
33
- /** The canonical `effectLayers`: registry order, junk dropped, every switched-on effect included. */
34
- export declare function effectLayerKeys(listed: readonly unknown[], effects: Pick<SceneEffects, ToggleEffectKey>): ToggleEffectKey[];
86
+ export declare const POST_EFFECT_KEYS: readonly SceneEffectKey[];
87
+ /** The canonical scene `effectLayers`. */
88
+ export declare function effectLayerKeys(listed: readonly unknown[], effects: Pick<EffectValues, SceneEffectKey>): SceneEffectKey[];
35
89
  /**
36
90
  * Composition order, bottom to top.
37
91
  * Display-only: the desktop chain's `rebuild()` is the order that runs — a stage moved there moves here too.
38
92
  */
39
- export declare const EFFECT_STACK_ORDER: readonly ToggleEffectKey[];
40
- /** Every registry effect at its defaults the derived half of a fresh {@link SceneEffects}. */
41
- export declare function defaultToggleEffects(): Pick<SceneEffects, ToggleEffectKey>;
93
+ export declare const ALL_EFFECT_STACK_ORDER: readonly EffectKey[];
94
+ /** Scene composition order, retained under its existing SDK name. */
95
+ export declare const EFFECT_STACK_ORDER: readonly SceneEffectKey[];
96
+ /** Layer composition order before the result joins the scene. */
97
+ export declare const LAYER_EFFECT_STACK_ORDER: readonly LayerEffectKey[];
98
+ /** Heal only the requested effect keys, using the same parameter kinds as defaults and editors. */
99
+ export declare function healEffectValues<K extends EffectKey>(raw: unknown, keys: readonly K[]): Pick<EffectValues, K>;
100
+ /** Scene toggle defaults, independent of effects offered only on layers. */
101
+ export declare function defaultToggleEffects(): Pick<EffectValues, SceneEffectKey>;
@@ -1,18 +1,50 @@
1
- // The scene-effect registry: one spec per toggle-plus-numeric-params effect.
2
- // Defaults, healing clamps, active checks, structural keys, and the panel's
3
- // sliders are all derived from this table, so adding an effect is:
4
- // 1. Type its values in `SceneEffects` (types.ts) + its interface.
5
- // 2. Spec it here (hex-color params go in EFFECT_COLOR_SPECS); label it in
6
- // the panel's effect-labels.ts (parity-checked).
7
- // 3. Implement its stage under apps/desktop/src/renderer/vrm/webgpu/effects/.
8
- // Scene-space effects (geometry, not a post stage — e.g. snow) also join
9
- // SCENE_SPACE_EFFECT_KEYS below.
10
- // 4. Drop one `<EffectSection>` line into a panel section.
11
- // Effects that don't fit the pattern (tone mapping, exposure, LUT, shockwave)
12
- // stay bespoke — extend the pattern before special-casing a third shape.
13
- //
14
- // Plain data only — no React, no lingui (labels live in the panel's
15
- // effect-labels.ts) — so main-process healing and API plugins import it freely.
1
+ // Scope and parameter metadata are plain data; labels and rendering stay with their consumers.
2
+ import { finiteOr, isRecord } from "./guards.js";
3
+ import { clamp, hexColorOr } from "./limits.js";
4
+ /** Supported owners of each toggle effect; scene and layer membership are independent. */
5
+ export const EFFECT_SCOPES = {
6
+ color: ['scene', 'layer'],
7
+ levels: ['scene', 'layer'],
8
+ colorWheels: ['scene', 'layer'],
9
+ colorShift: ['scene', 'layer'],
10
+ selectColors: ['scene', 'layer'],
11
+ blur: ['scene', 'layer'],
12
+ bloom: ['scene', 'layer'],
13
+ diffusion: ['scene', 'layer'],
14
+ dof: ['scene'],
15
+ chromaticAberration: ['scene'],
16
+ grain: ['scene'],
17
+ vignette: ['scene'],
18
+ rim: ['scene', 'layer'],
19
+ outline: ['scene', 'layer'],
20
+ dropShadow: ['scene', 'layer'],
21
+ pixelate: ['scene'],
22
+ glitch: ['scene'],
23
+ droplets: ['scene'],
24
+ rain: ['scene'],
25
+ snow: ['scene'],
26
+ };
27
+ export const EFFECT_KEYS = Object.keys(EFFECT_SCOPES);
28
+ function keysForScope(scope) {
29
+ return EFFECT_KEYS.filter((key) => EFFECT_SCOPES[key].includes(scope));
30
+ }
31
+ /** Older hosts advertise effect availability through the values present in their payload. */
32
+ export function availableEffectKeys(scope, effects) {
33
+ return keysForScope(scope).filter(key => effects[key] !== undefined);
34
+ }
35
+ /** Preserve the option shape advertised by the host when resetting or applying settings. */
36
+ export function supportedEffectParams(current, patch) {
37
+ return Object.fromEntries(Object.entries(patch).filter(([key]) => Object.hasOwn(current, key)));
38
+ }
39
+ /** Named modes distinguish the newer rim/outline size and bloom intensity units from older hosts. */
40
+ export function hasCurrentEffectFormat(effect, value) {
41
+ const field = effect === 'outline' ? 'quality' : effect === 'rim' || effect === 'bloom' ? 'mode' : null;
42
+ return field === null || Object.hasOwn(value, field);
43
+ }
44
+ export const SCENE_EFFECT_KEYS = keysForScope('scene');
45
+ /** Scene toggle keys; retained for existing SDK consumers. */
46
+ export const TOGGLE_EFFECT_KEYS = SCENE_EFFECT_KEYS;
47
+ export const LAYER_EFFECT_KEYS = keysForScope('layer');
16
48
  // Declared string-indexed so generic call sites (slider loop, healing walk) iterate
17
49
  // without erasure casts; `satisfies` still checks exact per-effect param parity here.
18
50
  export const EFFECT_SPECS = {
@@ -25,18 +57,84 @@ export const EFFECT_SPECS = {
25
57
  temperature: { default: 0, min: -100, max: 100, step: 1 },
26
58
  tint: { default: 0, min: -100, max: 100, step: 1 },
27
59
  },
60
+ levels: {
61
+ inputBlack: { default: 0, min: 0, max: 1, step: 0.01 },
62
+ inputWhite: { default: 1, min: 0, max: 1, step: 0.01 },
63
+ inputGamma: { default: 1, min: 0.1, max: 4, step: 0.01 },
64
+ outputBlack: { default: 0, min: 0, max: 1, step: 0.01 },
65
+ outputWhite: { default: 1, min: 0, max: 1, step: 0.01 },
66
+ inputBlackR: { default: 0, min: 0, max: 1, step: 0.01 },
67
+ inputWhiteR: { default: 1, min: 0, max: 1, step: 0.01 },
68
+ inputGammaR: { default: 1, min: 0.1, max: 4, step: 0.01 },
69
+ outputBlackR: { default: 0, min: 0, max: 1, step: 0.01 },
70
+ outputWhiteR: { default: 1, min: 0, max: 1, step: 0.01 },
71
+ inputBlackG: { default: 0, min: 0, max: 1, step: 0.01 },
72
+ inputWhiteG: { default: 1, min: 0, max: 1, step: 0.01 },
73
+ inputGammaG: { default: 1, min: 0.1, max: 4, step: 0.01 },
74
+ outputBlackG: { default: 0, min: 0, max: 1, step: 0.01 },
75
+ outputWhiteG: { default: 1, min: 0, max: 1, step: 0.01 },
76
+ inputBlackB: { default: 0, min: 0, max: 1, step: 0.01 },
77
+ inputWhiteB: { default: 1, min: 0, max: 1, step: 0.01 },
78
+ inputGammaB: { default: 1, min: 0.1, max: 4, step: 0.01 },
79
+ outputBlackB: { default: 0, min: 0, max: 1, step: 0.01 },
80
+ outputWhiteB: { default: 1, min: 0, max: 1, step: 0.01 },
81
+ },
82
+ colorWheels: {
83
+ lift: { default: 0, min: -1, max: 1, step: 0.01 },
84
+ gamma: { default: 0, min: -1, max: 1, step: 0.01 },
85
+ gain: { default: 0, min: -1, max: 1, step: 0.01 },
86
+ shadows: { default: 0, min: -1, max: 1, step: 0.01 },
87
+ midtones: { default: 0, min: -1, max: 1, step: 0.01 },
88
+ highlights: { default: 0, min: -1, max: 1, step: 0.01 },
89
+ shadowLimit: { default: 0.3, min: 0, max: 1, step: 0.01 },
90
+ highlightLimit: { default: 0.7, min: 0, max: 1, step: 0.01 },
91
+ },
92
+ colorShift: {
93
+ hueRed: { default: 0, min: -180, max: 180, step: 1, unit: '°' },
94
+ saturationRed: { default: 0, min: -1, max: 1, step: 0.01 },
95
+ luminanceRed: { default: 0, min: -1, max: 1, step: 0.01 },
96
+ luminanceSaturationRed: { default: 0, min: -1, max: 1, step: 0.01 },
97
+ hueYellow: { default: 0, min: -180, max: 180, step: 1, unit: '°' },
98
+ saturationYellow: { default: 0, min: -1, max: 1, step: 0.01 },
99
+ luminanceYellow: { default: 0, min: -1, max: 1, step: 0.01 },
100
+ luminanceSaturationYellow: { default: 0, min: -1, max: 1, step: 0.01 },
101
+ hueGreen: { default: 0, min: -180, max: 180, step: 1, unit: '°' },
102
+ saturationGreen: { default: 0, min: -1, max: 1, step: 0.01 },
103
+ luminanceGreen: { default: 0, min: -1, max: 1, step: 0.01 },
104
+ luminanceSaturationGreen: { default: 0, min: -1, max: 1, step: 0.01 },
105
+ hueCyan: { default: 0, min: -180, max: 180, step: 1, unit: '°' },
106
+ saturationCyan: { default: 0, min: -1, max: 1, step: 0.01 },
107
+ luminanceCyan: { default: 0, min: -1, max: 1, step: 0.01 },
108
+ luminanceSaturationCyan: { default: 0, min: -1, max: 1, step: 0.01 },
109
+ hueBlue: { default: 0, min: -180, max: 180, step: 1, unit: '°' },
110
+ saturationBlue: { default: 0, min: -1, max: 1, step: 0.01 },
111
+ luminanceBlue: { default: 0, min: -1, max: 1, step: 0.01 },
112
+ luminanceSaturationBlue: { default: 0, min: -1, max: 1, step: 0.01 },
113
+ hueMagenta: { default: 0, min: -180, max: 180, step: 1, unit: '°' },
114
+ saturationMagenta: { default: 0, min: -1, max: 1, step: 0.01 },
115
+ luminanceMagenta: { default: 0, min: -1, max: 1, step: 0.01 },
116
+ luminanceSaturationMagenta: { default: 0, min: -1, max: 1, step: 0.01 },
117
+ },
118
+ selectColors: {
119
+ hueRange: { default: 0.08, min: 0, max: 0.5, step: 0.01 },
120
+ saturationRange: { default: 1, min: 0, max: 1, step: 0.01 },
121
+ brightnessRange: { default: 1, min: 0, max: 1, step: 0.01 },
122
+ saturation: { default: -1, min: -1, max: 1, step: 0.01 },
123
+ blend: { default: 1, min: 0, max: 1, step: 0.01 },
124
+ },
125
+ blur: {
126
+ radius: { default: 8, min: 0, max: 64, step: 0.5, unit: 'px' },
127
+ },
28
128
  bloom: {
29
- // Past ×3 the halo overwhelms the source pixels and everything reads as haze.
30
- intensity: { default: 1, min: 0, max: 3, step: 0.01 },
31
- // Threshold 0.8 catches highlights without hazing the whole avatar; radius is the lib default.
32
- threshold: { default: 0.8, min: 0, max: 1, step: 0.01 },
33
- radius: { default: 0.85, min: 0, max: 1, step: 0.01 },
34
- // VTS bloom's anamorphic-flare half. Streak 0 keeps the passes out of the
35
- // graph; the angle is VTS's horizontal/vertical toggle made continuous.
36
- streak: { default: 0, min: 0, max: 3, step: 0.01 },
37
- streakThreshold: { default: 0.8, min: 0, max: 1, step: 0.01 },
38
- streakAngle: { default: 0, min: -90, max: 90, step: 1, unit: '°' },
39
- darken: { default: 0, min: 0, max: 1, step: 0.01 },
129
+ intensity: { default: 0, min: 0, max: 10, step: 0.01 },
130
+ threshold: { default: 0.5, min: 0, max: 2, step: 0.01 },
131
+ thresholdSmooth: { default: 0, min: 0, max: 1, step: 0.01 },
132
+ radius: { default: 1, min: 0, max: 6, step: 0.01 },
133
+ saturation: { default: 0, min: -1, max: 1, step: 0.01 },
134
+ opacity: { default: 1, min: 0, max: 1, step: 0.01 },
135
+ starRays: { default: 3, min: 1, max: 6, step: 1 },
136
+ starAngle: { default: 0, min: 0, max: 360, step: 1, unit: '°' },
137
+ colorTolerance: { default: 0.5, min: 0.01, max: 1, step: 0.01 },
40
138
  },
41
139
  // Shoost's diffusion (its Kino bloom variant, sliders Radius/Intensity/
42
140
  // Contrast→threshold): screen-composited, so its Opacity slider folds into
@@ -51,8 +149,7 @@ export const EFFECT_SPECS = {
51
149
  /** World metres of acceptably-sharp depth around the focus plane. */
52
150
  focusRange: { default: 2, min: 0.1, max: 10, step: 0.01 },
53
151
  },
54
- // VTube Studio's chromatic_aberration surface: strength + blur edges, the
55
- // latter continuous like rim.bothSides (VTS fades the same shader uniform).
152
+ // VTube Studio's chromatic_aberration surface: strength + continuous blur edges.
56
153
  chromaticAberration: {
57
154
  strength: { default: 0.2, min: 0, max: 1, step: 0.01 },
58
155
  blurEdges: { default: 1, min: 0, max: 1, step: 0.01 },
@@ -64,28 +161,16 @@ export const EFFECT_SPECS = {
64
161
  darkness: { default: 0.5, min: 0, max: 1, step: 0.01 },
65
162
  offset: { default: 0.5, min: 0, max: 1, step: 0.01 },
66
163
  },
67
- // Shoost's rim-light ranges where the control carried over (size, angle,
68
- // opacity→intensity); the rest follows VTube Studio's backlight. New params
69
- // default neutral (omni 0, limit 1, darken 0) so healed scenes keep their look.
70
164
  rim: {
71
- intensity: { default: 1, min: 0, max: 2, step: 0.01 },
72
- omni: { default: 0, min: 0, max: 1, step: 0.01 },
73
- size: { default: 16, min: 1, max: 64, step: 1, unit: 'px' },
74
- softness: { default: 0.6, min: 0, max: 1, step: 0.01 },
75
- angle: { default: 45, min: 0, max: 360, step: 1, unit: '°' },
76
- bothSides: { default: 0.5, min: 0, max: 1, step: 0.01 },
77
- brightnessLimit: { default: 1, min: 0, max: 1, step: 0.01 },
78
- darken: { default: 0, min: 0, max: 1, step: 0.01 },
79
- },
80
- // VTube Studio's backlight outline block: stripe count/speed/curve ranges are
81
- // VTS's; size is ours (px, like rim) instead of VTS's normalized 0..1.
165
+ size: { default: 0.5, min: 0, max: 1, step: 0.01 },
166
+ brightness: { default: 0, min: 0, max: 5, step: 0.01 },
167
+ contrast: { default: 0, min: 0, max: 1, step: 0.01 },
168
+ angle: { default: 0, min: -180, max: 180, step: 1, unit: '°' },
169
+ opacity: { default: 1, min: 0, max: 1, step: 0.01 },
170
+ },
82
171
  outline: {
83
- size: { default: 8, min: 1, max: 64, step: 1, unit: 'px' },
172
+ size: { default: 0, min: 0, max: 1, step: 0.01 },
84
173
  opacity: { default: 1, min: 0, max: 1, step: 0.01 },
85
- stripes: { default: 0.5, min: 0, max: 1, step: 0.01 },
86
- stripeMix: { default: 1, min: 0, max: 1, step: 0.01 },
87
- stripeSpeed: { default: 0.2, min: -1, max: 1, step: 0.01 },
88
- stripeCurve: { default: 0, min: 0, max: 1, step: 0.01 },
89
174
  },
90
175
  // VTube Studio's backlight shadow: an offset silhouette copy. VTS hides it at
91
176
  // zero offset; ours defaults to a visible down-right cast so enabling shows it.
@@ -93,6 +178,7 @@ export const EFFECT_SPECS = {
93
178
  opacity: { default: 0.6, min: 0, max: 1, step: 0.01 },
94
179
  offsetX: { default: 3, min: -20, max: 20, step: 0.5, digits: 1, unit: '%' },
95
180
  offsetY: { default: 3, min: -20, max: 20, step: 0.5, digits: 1, unit: '%' },
181
+ size: { default: 0, min: 0, max: 64, step: 0.5, unit: 'px' },
96
182
  },
97
183
  pixelate: {
98
184
  // Pixels per block; past ~64 the frame is a handful of tiles.
@@ -154,24 +240,127 @@ export const EFFECT_SPECS = {
154
240
  * string param appear.
155
241
  */
156
242
  export const EFFECT_COLOR_SPECS = {
243
+ colorWheels: {
244
+ liftColor: { default: '#ffffff' },
245
+ gammaColor: { default: '#ffffff' },
246
+ gainColor: { default: '#ffffff' },
247
+ shadowsColor: { default: '#ffffff' },
248
+ midtonesColor: { default: '#ffffff' },
249
+ highlightsColor: { default: '#ffffff' },
250
+ },
251
+ selectColors: {
252
+ color1: { default: '#ff0000' },
253
+ color2: { default: '#ffff00' },
254
+ color3: { default: '#00ff00' },
255
+ color4: { default: '#00ffff' },
256
+ color5: { default: '#0000ff' },
257
+ color6: { default: '#ff00ff' },
258
+ color7: { default: '#ffffff' },
259
+ color8: { default: '#808080' },
260
+ color9: { default: '#000000' },
261
+ color10: { default: '#ff8800' },
262
+ },
157
263
  bloom: {
158
264
  tint: { default: '#ffffff' },
159
- // Beautify's anamorphic-flare tint default the classic lens-streak blue.
160
- streakTint: { default: '#8080ff' },
265
+ color1: { default: '#ffffff' },
266
+ color2: { default: '#ffffff' },
267
+ color3: { default: '#ffffff' },
268
+ color4: { default: '#ffffff' },
269
+ color5: { default: '#ffffff' },
161
270
  },
162
271
  rim: {
163
272
  color: { default: '#ffffff' },
164
273
  },
165
274
  outline: {
166
- color: { default: '#ffffff' },
167
- stripeColor: { default: '#000000' },
275
+ color: { default: '#000000' },
168
276
  },
169
277
  dropShadow: {
170
278
  color: { default: '#000000' },
171
279
  },
172
280
  };
173
- // Object.keys widens to string[]; the annotation above pins the keys to exactly ToggleEffectKey.
174
- export const TOGGLE_EFFECT_KEYS = Object.keys(EFFECT_SPECS);
281
+ /** Boolean defaults, validated alongside numeric and color parameters. */
282
+ export const EFFECT_BOOLEAN_SPECS = {
283
+ blur: { highQuality: { default: false } },
284
+ bloom: {
285
+ selectColors: { default: false },
286
+ invertColors: { default: false },
287
+ // Like Select Colors' first slot: with every pick off the mask is zero, so switching
288
+ // selection on would make the glow vanish rather than narrow.
289
+ color1Enabled: { default: true },
290
+ color2Enabled: { default: false },
291
+ color3Enabled: { default: false },
292
+ color4Enabled: { default: false },
293
+ color5Enabled: { default: false },
294
+ },
295
+ selectColors: {
296
+ invert: { default: false },
297
+ color1Enabled: { default: true },
298
+ color2Enabled: { default: false },
299
+ color3Enabled: { default: false },
300
+ color4Enabled: { default: false },
301
+ color5Enabled: { default: false },
302
+ color6Enabled: { default: false },
303
+ color7Enabled: { default: false },
304
+ color8Enabled: { default: false },
305
+ color9Enabled: { default: false },
306
+ color10Enabled: { default: false },
307
+ },
308
+ };
309
+ /** Enum defaults and allowed values. */
310
+ export const EFFECT_ENUM_SPECS = {
311
+ blur: { mode: { default: 'gaussian', values: ['gaussian', 'bokeh'] } },
312
+ bloom: { mode: { default: 'normal', values: ['normal', 'streak', 'star'] } },
313
+ rim: {
314
+ mode: { default: 'single', values: ['single', 'double', 'sharpenSingle', 'sharpenDouble'] },
315
+ blendMode: {
316
+ default: 'overlay',
317
+ values: [
318
+ 'normal',
319
+ 'darken',
320
+ 'multiply',
321
+ 'colorBurn',
322
+ 'linearBurn',
323
+ 'add',
324
+ 'lighten',
325
+ 'screen',
326
+ 'colorDodge',
327
+ 'overlay',
328
+ 'softLight',
329
+ 'hardLight',
330
+ 'vividLight',
331
+ 'linearLight',
332
+ 'pinLight',
333
+ 'hardMix',
334
+ 'difference',
335
+ 'exclusion',
336
+ 'subtract',
337
+ 'divide',
338
+ 'hue',
339
+ 'saturation',
340
+ 'color',
341
+ 'luminosity',
342
+ ],
343
+ },
344
+ },
345
+ outline: { quality: { default: 'medium', values: ['low', 'medium', 'high'] } },
346
+ colorWheels: { mode: { default: 'liftGammaGain', values: ['liftGammaGain', 'shadowsMidtonesHighlights'] } },
347
+ };
348
+ /** Select the layer effects from any value set containing them. */
349
+ export function pickLayerEffects(effects) {
350
+ return Object.fromEntries(LAYER_EFFECT_KEYS.map(key => [key, effects[key]]));
351
+ }
352
+ /** The canonical per-item effect list, preserving disabled rows and including every enabled effect. */
353
+ export function layerEffectKeys(listed, effects) {
354
+ return canonicalKeys(LAYER_EFFECT_KEYS, listed, effects);
355
+ }
356
+ /** An independent, disabled set for a fresh source layer. */
357
+ export function defaultLayerEffects() {
358
+ return defaultsForKeys(LAYER_EFFECT_KEYS);
359
+ }
360
+ /** Whether a source needs its own effect pass. */
361
+ export function layerEffectsActive(effects) {
362
+ return LAYER_EFFECT_KEYS.some(key => effects[key].enabled);
363
+ }
175
364
  /**
176
365
  * Registry effects that render as scene geometry inside the scene pass rather
177
366
  * than composing into the post chain. A new scene-space effect must join this
@@ -179,16 +368,20 @@ export const TOGGLE_EFFECT_KEYS = Object.keys(EFFECT_SPECS);
179
368
  */
180
369
  export const SCENE_SPACE_EFFECT_KEYS = ['rain', 'snow'];
181
370
  /** Registry effects the post chain composes — {@link TOGGLE_EFFECT_KEYS} minus the scene-space ones. */
182
- export const POST_EFFECT_KEYS = TOGGLE_EFFECT_KEYS.filter(key => !SCENE_SPACE_EFFECT_KEYS.includes(key));
183
- /** The canonical `effectLayers`: registry order, junk dropped, every switched-on effect included. */
371
+ export const POST_EFFECT_KEYS = SCENE_EFFECT_KEYS.filter(key => !SCENE_SPACE_EFFECT_KEYS.includes(key));
372
+ /** Registry order, junk dropped, every switched-on effect included — the rule both layer lists follow. */
373
+ function canonicalKeys(keys, listed, effects) {
374
+ return keys.filter(key => effects[key] !== undefined && (listed.includes(key) || effects[key].enabled));
375
+ }
376
+ /** The canonical scene `effectLayers`. */
184
377
  export function effectLayerKeys(listed, effects) {
185
- return TOGGLE_EFFECT_KEYS.filter(key => listed.includes(key) || effects[key].enabled);
378
+ return canonicalKeys(SCENE_EFFECT_KEYS, listed, effects);
186
379
  }
187
380
  /**
188
381
  * Composition order, bottom to top.
189
382
  * Display-only: the desktop chain's `rebuild()` is the order that runs — a stage moved there moves here too.
190
383
  */
191
- export const EFFECT_STACK_ORDER = [
384
+ export const ALL_EFFECT_STACK_ORDER = [
192
385
  'rain',
193
386
  'snow',
194
387
  'dof',
@@ -199,24 +392,44 @@ export const EFFECT_STACK_ORDER = [
199
392
  'bloom',
200
393
  'diffusion',
201
394
  'color',
395
+ 'levels',
396
+ 'colorWheels',
397
+ 'colorShift',
398
+ 'selectColors',
399
+ 'blur',
202
400
  'pixelate',
203
401
  'glitch',
204
402
  'droplets',
205
403
  'grain',
206
404
  'vignette',
207
405
  ];
208
- /** Every registry effect at its defaults the derived half of a fresh {@link SceneEffects}. */
209
- export function defaultToggleEffects() {
406
+ /** Scene composition order, retained under its existing SDK name. */
407
+ export const EFFECT_STACK_ORDER = ALL_EFFECT_STACK_ORDER.filter((key) => SCENE_EFFECT_KEYS.includes(key));
408
+ /** Layer composition order before the result joins the scene. */
409
+ export const LAYER_EFFECT_STACK_ORDER = ALL_EFFECT_STACK_ORDER.filter((key) => LAYER_EFFECT_KEYS.includes(key));
410
+ /** Heal only the requested effect keys, using the same parameter kinds as defaults and editors. */
411
+ export function healEffectValues(raw, keys) {
412
+ const src = isRecord(raw) ? raw : {};
210
413
  const out = {};
211
- for (const key of TOGGLE_EFFECT_KEYS) {
212
- const fx = { enabled: false };
414
+ for (const key of keys) {
415
+ const value = isRecord(src[key]) ? src[key] : {};
416
+ const fx = { enabled: value.enabled === true };
213
417
  for (const [param, spec] of Object.entries(EFFECT_SPECS[key]))
214
- fx[param] = spec.default;
418
+ fx[param] = clamp(finiteOr(value[param], spec.default), spec.min, spec.max);
215
419
  for (const [param, spec] of Object.entries(EFFECT_COLOR_SPECS[key] ?? {}))
216
- fx[param] = spec.default;
420
+ fx[param] = hexColorOr(value[param], spec.default);
421
+ for (const [param, spec] of Object.entries(EFFECT_BOOLEAN_SPECS[key] ?? {}))
422
+ fx[param] = typeof value[param] === 'boolean' ? value[param] : spec.default;
423
+ for (const [param, spec] of Object.entries(EFFECT_ENUM_SPECS[key] ?? {}))
424
+ fx[param] = typeof value[param] === 'string' && spec.values.includes(value[param]) ? value[param] : spec.default;
217
425
  out[key] = fx;
218
426
  }
219
- // Runtime-built, invisible to TS; EFFECT_SPECS's satisfies check is what pins
220
- // each entry's params to exactly SceneEffects[key].
221
427
  return out;
222
428
  }
429
+ function defaultsForKeys(keys) {
430
+ return healEffectValues(null, keys);
431
+ }
432
+ /** Scene toggle defaults, independent of effects offered only on layers. */
433
+ export function defaultToggleEffects() {
434
+ return defaultsForKeys(SCENE_EFFECT_KEYS);
435
+ }
@@ -0,0 +1,53 @@
1
+ /** VTube Studio hand inputs, extended with nizima LIVE's wrist Y rotation. */
2
+ export declare const HAND_INPUT_NAMES: readonly ["HandLeftFound", "HandLeftPositionX", "HandLeftPositionY", "HandLeftPositionZ", "HandLeftAngleX", "HandLeftAngleY", "HandLeftAngleZ", "HandLeftOpen", "HandLeftFinger_1_Thumb", "HandLeftFinger_2_Index", "HandLeftFinger_3_Middle", "HandLeftFinger_4_Ring", "HandLeftFinger_5_Pinky", "HandRightFound", "HandRightPositionX", "HandRightPositionY", "HandRightPositionZ", "HandRightAngleX", "HandRightAngleY", "HandRightAngleZ", "HandRightOpen", "HandRightFinger_1_Thumb", "HandRightFinger_2_Index", "HandRightFinger_3_Middle", "HandRightFinger_4_Ring", "HandRightFinger_5_Pinky", "BothHandsFound", "HandDistance"];
3
+ export type HandInputName = (typeof HAND_INPUT_NAMES)[number];
4
+ export type HandInputs = Partial<Record<HandInputName, number>>;
5
+ /** Whether an input uses the canonical hand vocabulary. */
6
+ export declare function isHandInputName(input: string): input is HandInputName;
7
+ /** Names nizima uses for VTS's numbered fingers and both-hands detection. */
8
+ export declare const NIZIMA_HAND_ALIASES: {
9
+ readonly HandLeftFingerThumb: "HandLeftFinger_1_Thumb";
10
+ readonly HandLeftFingerIndex: "HandLeftFinger_2_Index";
11
+ readonly HandLeftFingerMiddle: "HandLeftFinger_3_Middle";
12
+ readonly HandLeftFingerRing: "HandLeftFinger_4_Ring";
13
+ readonly HandLeftFingerPinky: "HandLeftFinger_5_Pinky";
14
+ readonly HandRightFingerThumb: "HandRightFinger_1_Thumb";
15
+ readonly HandRightFingerIndex: "HandRightFinger_2_Index";
16
+ readonly HandRightFingerMiddle: "HandRightFinger_3_Middle";
17
+ readonly HandRightFingerRing: "HandRightFinger_4_Ring";
18
+ readonly HandRightFingerPinky: "HandRightFinger_5_Pinky";
19
+ readonly HandBothFound: "BothHandsFound";
20
+ };
21
+ /** Canonicalize a hand name; aliases share canonical units on the plugin API. */
22
+ export declare function canonicalHandInputName(input: string): HandInputName | null;
23
+ /** Angles are degrees; positions and distance use VTS's ten-unit scale. */
24
+ export declare const HAND_INPUT_RANGES: {
25
+ readonly HandLeftFound: readonly [0, 1];
26
+ readonly HandLeftPositionX: readonly [-10, 10];
27
+ readonly HandLeftPositionY: readonly [-10, 10];
28
+ readonly HandLeftPositionZ: readonly [-10, 10];
29
+ readonly HandLeftAngleX: readonly [-180, 180];
30
+ readonly HandLeftAngleY: readonly [-90, 90];
31
+ readonly HandLeftAngleZ: readonly [-180, 180];
32
+ readonly HandLeftOpen: readonly [0, 1];
33
+ readonly HandLeftFinger_1_Thumb: readonly [0, 1];
34
+ readonly HandLeftFinger_2_Index: readonly [0, 1];
35
+ readonly HandLeftFinger_3_Middle: readonly [0, 1];
36
+ readonly HandLeftFinger_4_Ring: readonly [0, 1];
37
+ readonly HandLeftFinger_5_Pinky: readonly [0, 1];
38
+ readonly HandRightFound: readonly [0, 1];
39
+ readonly HandRightPositionX: readonly [-10, 10];
40
+ readonly HandRightPositionY: readonly [-10, 10];
41
+ readonly HandRightPositionZ: readonly [-10, 10];
42
+ readonly HandRightAngleX: readonly [-180, 180];
43
+ readonly HandRightAngleY: readonly [-90, 90];
44
+ readonly HandRightAngleZ: readonly [-180, 180];
45
+ readonly HandRightOpen: readonly [0, 1];
46
+ readonly HandRightFinger_1_Thumb: readonly [0, 1];
47
+ readonly HandRightFinger_2_Index: readonly [0, 1];
48
+ readonly HandRightFinger_3_Middle: readonly [0, 1];
49
+ readonly HandRightFinger_4_Ring: readonly [0, 1];
50
+ readonly HandRightFinger_5_Pinky: readonly [0, 1];
51
+ readonly BothHandsFound: readonly [0, 1];
52
+ readonly HandDistance: readonly [0, 10];
53
+ };