@laplace.live/persona-sdk 1.19.0 → 1.21.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/dist/index.d.ts CHANGED
@@ -17,10 +17,12 @@ export * from './values/limits.ts';
17
17
  export * from './values/lipsync.ts';
18
18
  export * from './values/locale.ts';
19
19
  export * from './values/model-info.ts';
20
+ export * from './values/model-movement.ts';
20
21
  export * from './values/mouse.ts';
21
22
  export * from './values/scene-transition.ts';
22
23
  export * from './values/stage-info.ts';
23
24
  export * from './values/terms.ts';
25
+ export * from './values/time.ts';
24
26
  export * from './values/volumetric-lighting.ts';
25
27
  export * from './values/vrm-bindings.ts';
26
28
  export * from './wire/envelope.ts';
package/dist/index.js CHANGED
@@ -19,10 +19,12 @@ export * from "./values/limits.js";
19
19
  export * from "./values/lipsync.js";
20
20
  export * from "./values/locale.js";
21
21
  export * from "./values/model-info.js";
22
+ export * from "./values/model-movement.js";
22
23
  export * from "./values/mouse.js";
23
24
  export * from "./values/scene-transition.js";
24
25
  export * from "./values/stage-info.js";
25
26
  export * from "./values/terms.js";
27
+ export * from "./values/time.js";
26
28
  export * from "./values/volumetric-lighting.js";
27
29
  export * from "./values/vrm-bindings.js";
28
30
  export * from "./wire/envelope.js";
@@ -18,8 +18,8 @@ export interface Keyframe {
18
18
  anchor: Point;
19
19
  /**
20
20
  * Bezier handles. `x` is a **fraction of the segment's x-span** (0 = this segment's left
21
- * anchor, 1 = its right), `y` is absolute — nizima's asymmetry, and worth keeping: an
22
- * anchor dragged sideways carries its handles, while the shape stays pinned to real output.
21
+ * anchor, 1 = its right), `y` is absolute — nizima's format. Because `y` stays put when its
22
+ * anchor moves, an editor drag has to carry the handles itself (`dragAnchor`), or the point kinks.
23
23
  */
24
24
  next?: Point;
25
25
  previous?: Point;
@@ -61,8 +61,9 @@ export interface CurveHandle extends Point {
61
61
  span: readonly [number, number];
62
62
  }
63
63
  /**
64
- * The keyframe's draggable handles, in curve coordinates — its outgoing one when it governs a
65
- * Bezier segment, its incoming one when the keyframe before it does.
64
+ * The keyframe's handles, in curve coordinates — its outgoing one when it governs a Bezier
65
+ * segment, its incoming one when the keyframe before it does. A sharp point's are retracted onto
66
+ * it and still reported; `extendedHandles` is what an editor draws.
66
67
  *
67
68
  * `handleX` rather than a second clamp-and-lerp: a handle has to read back where the evaluator
68
69
  * looks for it. `y` is absolute and deliberately unclamped — `sanitizeCurve` keeps a control point
@@ -70,6 +71,8 @@ export interface CurveHandle extends Point {
70
71
  * legitimate overshoot at the wrong height.
71
72
  */
72
73
  export declare function handlesFor(keyframes: Keyframe[], index: number): CurveHandle[];
74
+ /** The keyframe's handles an editor draws and lets you grab: a retracted one has nothing to grab. */
75
+ export declare function extendedHandles(keyframes: Keyframe[], index: number): CurveHandle[];
73
76
  /**
74
77
  * How far an anchor may travel in x, as `[lo, hi]`.
75
78
  *
@@ -81,6 +84,31 @@ export declare function handlesFor(keyframes: Keyframe[], index: number): CurveH
81
84
  * One statement of the rule, so an editor's drag and its typed field cannot drift apart.
82
85
  */
83
86
  export declare function anchorBounds(keyframes: Keyframe[], index: number): [number, number];
87
+ /**
88
+ * The anchor at `index` moved to `to`, held within `anchorBounds` and the plot, carrying its own
89
+ * handles as a pen tool does, so the curve leaves and arrives at it in the same directions: a
90
+ * smooth point stays smooth, a corner keeps its angle. The neighbours' handles keep their
91
+ * fractions, so the segments either side stretch rather than re-aim.
92
+ */
93
+ export declare function dragAnchor(keyframes: Keyframe[], index: number, to: Point): Keyframe[];
94
+ /**
95
+ * The `side` handle of the anchor at `index` moved to `to`. On a smooth point the other handle
96
+ * turns to stay in line, keeping its length — a pen tool's default — unless `independent`, which
97
+ * moves this one alone and leaves a corner. `to` is held within the segment the handle shapes,
98
+ * where `handleX` reads it, and on the plot, where it can be found again; a stored overshoot still
99
+ * reads back. The turned handle is shortened to fit its own.
100
+ */
101
+ export declare function dragHandle(keyframes: Keyframe[], index: number, side: HandleSide, to: Point, independent: boolean): Keyframe[];
102
+ /**
103
+ * A pen tool's convert-point click on the interior anchor at `index`: a smooth point turns sharp,
104
+ * anything else turns smooth. Beside a step there is nothing to be in line with, so a point there is
105
+ * smooth while its one handle is out. Sharp retracts the handles onto the anchor, so no curve bends
106
+ * at it. Smooth aims them through the neighbours — flat at a peak or valley, so the curve cannot
107
+ * overshoot the neighbour it turns at — a third of the way along each segment, and a linear side
108
+ * becomes Bezier with its far handle on the old line, so the neighbour's end keeps its direction.
109
+ * Step sides are left as they are. Returns **the input array itself** when it declines.
110
+ */
111
+ export declare function toggleSmooth(keyframes: Keyframe[], index: number): Keyframe[];
84
112
  /**
85
113
  * Every Bezier segment carries both handles, seeded collinear unless it already has both.
86
114
  *
@@ -146,13 +174,18 @@ export declare const INTERPOLATIONS: readonly ["linear", "step", "invertStep", "
146
174
  */
147
175
  export declare function sanitizeCurve(v: unknown): Curve | null;
148
176
  /**
149
- * The straight 0..1 line — what a binding without a curve already does, as an editable start.
177
+ * The straight 0..1 line — what a binding without a curve already does — as the Linear preset.
150
178
  *
151
179
  * No handles: a `linear` keyframe's are never read, and one that outlives the segment it was cut
152
180
  * for is what bends a curve on a mode switch. `withBezierHandles` cuts fresh ones against whatever
153
181
  * the anchors are by then, so Bezier leaves the line where it is and only a drag shapes it.
154
182
  */
155
183
  export declare function identityCurve(): Curve;
184
+ /**
185
+ * The same straight line as one Bezier segment, handles on the line at the thirds — what a new
186
+ * curve starts as, so a point clicked into it splits smooth and a drag bends it rather than kinking.
187
+ */
188
+ export declare function bezierIdentityCurve(): Curve;
156
189
  /** A ready-made response shape that replaces the whole curve; `linear` is the identity. */
157
190
  export type CurvePresetId = (typeof CURVE_PRESET_IDS)[number];
158
191
  export declare const CURVE_PRESET_IDS: readonly ["linear", "step", "invertStep", "bezier", "easeIn", "easeOut", "sCurve", "threshold", "steps"];
@@ -92,8 +92,9 @@ export function evaluateCurve(curve, x) {
92
92
  return left === undefined || right === undefined ? first.anchor.y : segmentAt(left, right, x);
93
93
  }
94
94
  /**
95
- * The keyframe's draggable handles, in curve coordinates — its outgoing one when it governs a
96
- * Bezier segment, its incoming one when the keyframe before it does.
95
+ * The keyframe's handles, in curve coordinates — its outgoing one when it governs a Bezier
96
+ * segment, its incoming one when the keyframe before it does. A sharp point's are retracted onto
97
+ * it and still reported; `extendedHandles` is what an editor draws.
97
98
  *
98
99
  * `handleX` rather than a second clamp-and-lerp: a handle has to read back where the evaluator
99
100
  * looks for it. `y` is absolute and deliberately unclamped — `sanitizeCurve` keeps a control point
@@ -117,6 +118,15 @@ export function handlesFor(keyframes, index) {
117
118
  }
118
119
  return out;
119
120
  }
121
+ /** Below this length a handle is retracted onto its anchor, as a sharp point's are. */
122
+ const RETRACTED = 1e-9;
123
+ /** The keyframe's handles an editor draws and lets you grab: a retracted one has nothing to grab. */
124
+ export function extendedHandles(keyframes, index) {
125
+ const anchor = keyframes[index]?.anchor;
126
+ if (anchor === undefined)
127
+ return [];
128
+ return handlesFor(keyframes, index).filter(h => Math.hypot(h.x - anchor.x, h.y - anchor.y) > RETRACTED);
129
+ }
120
130
  /**
121
131
  * How far an anchor may travel in x, as `[lo, hi]`.
122
132
  *
@@ -133,7 +143,143 @@ export function anchorBounds(keyframes, index) {
133
143
  return [0, 1];
134
144
  if (index === 0 || index === keyframes.length - 1)
135
145
  return [held.anchor.x, held.anchor.x];
136
- return [(keyframes[index - 1]?.anchor.x ?? 0) + EDGE_EPSILON, (keyframes[index + 1]?.anchor.x ?? 1) - EDGE_EPSILON];
146
+ const lo = keyframes[index - 1]?.anchor.x ?? 0;
147
+ const hi = keyframes[index + 1]?.anchor.x ?? 1;
148
+ // Clicks in one column stack anchors on the curve; neighbours too close for the margin meet halfway.
149
+ const edge = Math.min(EDGE_EPSILON, (hi - lo) / 2);
150
+ return [lo + edge, hi - edge];
151
+ }
152
+ /**
153
+ * The handle at `anchor + s·offset` for the largest `s` in 0..1 inside the box, stored against
154
+ * `xs`, its segment's span: a handle out of room is shortened along its own direction, so a
155
+ * smooth point stays smooth.
156
+ */
157
+ function fitHandle(anchor, offset, xs, ys) {
158
+ let s = 1;
159
+ if (offset.x > 0)
160
+ s = Math.min(s, (xs[1] - anchor.x) / offset.x);
161
+ if (offset.x < 0)
162
+ s = Math.min(s, (xs[0] - anchor.x) / offset.x);
163
+ if (offset.y > 0)
164
+ s = Math.min(s, (ys[1] - anchor.y) / offset.y);
165
+ if (offset.y < 0)
166
+ s = Math.min(s, (ys[0] - anchor.y) / offset.y);
167
+ s = Math.max(0, s);
168
+ return { x: handleFraction(xs[0], xs[1], anchor.x + offset.x * s), y: anchor.y + offset.y * s };
169
+ }
170
+ /** Where an authored handle may sit in y: on the plot, or no further out than a stored overshoot. */
171
+ function heightsFor(y) {
172
+ return [Math.min(0, y), Math.max(1, y)];
173
+ }
174
+ /** Two handles in line through their anchor, on opposite sides of it: a smooth point. */
175
+ function inLine(anchor, a, b) {
176
+ const u = { x: a.x - anchor.x, y: a.y - anchor.y };
177
+ const v = { x: b.x - anchor.x, y: b.y - anchor.y };
178
+ const lu = Math.hypot(u.x, u.y);
179
+ const lv = Math.hypot(v.x, v.y);
180
+ if (lu <= RETRACTED || lv <= RETRACTED)
181
+ return false;
182
+ return u.x * v.x + u.y * v.y < 0 && Math.abs(u.x * v.y - u.y * v.x) <= 1e-3 * lu * lv;
183
+ }
184
+ /**
185
+ * The anchor at `index` moved to `to`, held within `anchorBounds` and the plot, carrying its own
186
+ * handles as a pen tool does, so the curve leaves and arrives at it in the same directions: a
187
+ * smooth point stays smooth, a corner keeps its angle. The neighbours' handles keep their
188
+ * fractions, so the segments either side stretch rather than re-aim.
189
+ */
190
+ export function dragAnchor(keyframes, index, to) {
191
+ const held = keyframes[index];
192
+ if (held === undefined)
193
+ return keyframes;
194
+ const [lo, hi] = anchorBounds(keyframes, index);
195
+ const at = { x: clamp(to.x, lo, hi), y: clamp(to.y, 0, 1) };
196
+ const moved = { ...held, anchor: at };
197
+ for (const h of handlesFor(keyframes, index)) {
198
+ const span = h.side === 'next' ? [at.x, h.span[1]] : [h.span[0], at.x];
199
+ moved[h.side] = fitHandle(at, { x: h.x - held.anchor.x, y: h.y - held.anchor.y }, span, heightsFor(h.y));
200
+ }
201
+ return keyframes.map((k, i) => (i === index ? moved : k));
202
+ }
203
+ /**
204
+ * The `side` handle of the anchor at `index` moved to `to`. On a smooth point the other handle
205
+ * turns to stay in line, keeping its length — a pen tool's default — unless `independent`, which
206
+ * moves this one alone and leaves a corner. `to` is held within the segment the handle shapes,
207
+ * where `handleX` reads it, and on the plot, where it can be found again; a stored overshoot still
208
+ * reads back. The turned handle is shortened to fit its own.
209
+ */
210
+ export function dragHandle(keyframes, index, side, to, independent) {
211
+ const held = keyframes[index];
212
+ const handles = handlesFor(keyframes, index);
213
+ const dragged = handles.find(h => h.side === side);
214
+ if (held === undefined || dragged === undefined)
215
+ return keyframes;
216
+ const at = { x: clamp(to.x, dragged.span[0], dragged.span[1]), y: clamp(to.y, 0, 1) };
217
+ const moved = { ...held };
218
+ moved[side] = { x: handleFraction(dragged.span[0], dragged.span[1], at.x), y: at.y };
219
+ const other = handles.find(h => h.side !== side);
220
+ const a = held.anchor;
221
+ const reach = Math.hypot(at.x - a.x, at.y - a.y);
222
+ if (!independent && other !== undefined && reach > 0 && inLine(a, dragged, other)) {
223
+ const keep = Math.hypot(other.x - a.x, other.y - a.y) / reach;
224
+ const away = { x: (a.x - at.x) * keep, y: (a.y - at.y) * keep };
225
+ moved[other.side] = fitHandle(a, away, other.span, heightsFor(other.y));
226
+ }
227
+ return keyframes.map((k, i) => (i === index ? moved : k));
228
+ }
229
+ /**
230
+ * A pen tool's convert-point click on the interior anchor at `index`: a smooth point turns sharp,
231
+ * anything else turns smooth. Beside a step there is nothing to be in line with, so a point there is
232
+ * smooth while its one handle is out. Sharp retracts the handles onto the anchor, so no curve bends
233
+ * at it. Smooth aims them through the neighbours — flat at a peak or valley, so the curve cannot
234
+ * overshoot the neighbour it turns at — a third of the way along each segment, and a linear side
235
+ * becomes Bezier with its far handle on the old line, so the neighbour's end keeps its direction.
236
+ * Step sides are left as they are. Returns **the input array itself** when it declines.
237
+ */
238
+ export function toggleSmooth(keyframes, index) {
239
+ const before = keyframes[index - 1];
240
+ const held = keyframes[index];
241
+ const after = keyframes[index + 1];
242
+ if (before === undefined || held === undefined || after === undefined)
243
+ return keyframes;
244
+ // A step side has no curve to aim; a linear one turns Bezier below.
245
+ const smoothIn = before.interpolation === 'linear' || before.interpolation === 'bezier';
246
+ const smoothOut = held.interpolation === 'linear' || held.interpolation === 'bezier';
247
+ if (!smoothIn && !smoothOut)
248
+ return keyframes;
249
+ const a = held.anchor;
250
+ const handles = handlesFor(keyframes, index);
251
+ const incoming = handles.find(h => h.side === 'previous');
252
+ const outgoing = handles.find(h => h.side === 'next');
253
+ const out = [...keyframes];
254
+ const isSmooth = smoothIn && smoothOut
255
+ ? incoming !== undefined && outgoing !== undefined && inLine(a, incoming, outgoing)
256
+ : extendedHandles(keyframes, index).length > 0;
257
+ if (isSmooth) {
258
+ const sharp = { ...held };
259
+ if (incoming !== undefined)
260
+ sharp.previous = { x: 1, y: a.y };
261
+ if (outgoing !== undefined)
262
+ sharp.next = { x: 0, y: a.y };
263
+ out[index] = sharp;
264
+ return out;
265
+ }
266
+ const l = before.anchor;
267
+ const r = after.anchor;
268
+ const slope = (a.y - l.y) * (r.y - a.y) > 0 ? (r.y - l.y) / (r.x - l.x) : 0;
269
+ const smooth = { ...held };
270
+ if (before.interpolation === 'linear') {
271
+ out[index - 1] = { ...before, interpolation: 'bezier', next: { x: 1 / 3, y: lerp(l.y, a.y, 1 / 3) } };
272
+ }
273
+ if (smoothIn)
274
+ smooth.previous = fitHandle(a, { x: (l.x - a.x) / 3, y: (slope * (l.x - a.x)) / 3 }, [l.x, a.x], [0, 1]);
275
+ if (held.interpolation === 'linear') {
276
+ smooth.interpolation = 'bezier';
277
+ out[index + 1] = { ...after, previous: { x: 2 / 3, y: lerp(a.y, r.y, 2 / 3) } };
278
+ }
279
+ if (smoothOut)
280
+ smooth.next = fitHandle(a, { x: (r.x - a.x) / 3, y: (slope * (r.x - a.x)) / 3 }, [a.x, r.x], [0, 1]);
281
+ out[index] = smooth;
282
+ return out;
137
283
  }
138
284
  /**
139
285
  * Every Bezier segment carries both handles, seeded collinear unless it already has both.
@@ -308,7 +454,7 @@ export function sanitizeCurve(v) {
308
454
  return { keyframes: withBezierHandles(keyframes) };
309
455
  }
310
456
  /**
311
- * The straight 0..1 line — what a binding without a curve already does, as an editable start.
457
+ * The straight 0..1 line — what a binding without a curve already does — as the Linear preset.
312
458
  *
313
459
  * No handles: a `linear` keyframe's are never read, and one that outlives the segment it was cut
314
460
  * for is what bends a curve on a mode switch. `withBezierHandles` cuts fresh ones against whatever
@@ -322,6 +468,18 @@ export function identityCurve() {
322
468
  ],
323
469
  };
324
470
  }
471
+ /**
472
+ * The same straight line as one Bezier segment, handles on the line at the thirds — what a new
473
+ * curve starts as, so a point clicked into it splits smooth and a drag bends it rather than kinking.
474
+ */
475
+ export function bezierIdentityCurve() {
476
+ return {
477
+ keyframes: withBezierHandles([
478
+ { anchor: { x: 0, y: 0 }, interpolation: 'bezier' },
479
+ { anchor: { x: 1, y: 1 }, interpolation: 'linear' },
480
+ ]),
481
+ };
482
+ }
325
483
  export const CURVE_PRESET_IDS = [
326
484
  'linear',
327
485
  'step',
@@ -11,6 +11,7 @@ export declare const EFFECT_SCOPES: {
11
11
  readonly blur: readonly ["scene", "layer"];
12
12
  readonly bloom: readonly ["scene", "layer"];
13
13
  readonly diffusion: readonly ["scene", "layer"];
14
+ readonly sketch: readonly ["scene", "layer"];
14
15
  readonly dof: readonly ["scene"];
15
16
  readonly chromaticAberration: readonly ["scene"];
16
17
  readonly grain: readonly ["scene"];
@@ -13,6 +13,7 @@ export const EFFECT_SCOPES = {
13
13
  blur: ['scene', 'layer'],
14
14
  bloom: ['scene', 'layer'],
15
15
  diffusion: ['scene', 'layer'],
16
+ sketch: ['scene', 'layer'],
16
17
  dof: ['scene'],
17
18
  chromaticAberration: ['scene'],
18
19
  grain: ['scene'],
@@ -165,6 +166,15 @@ export const EFFECT_SPECS = {
165
166
  radius: { default: 0.85, min: 0, max: 1, step: 0.01 },
166
167
  threshold: { default: 0.35, min: 0, max: 1, step: 0.01 },
167
168
  },
169
+ sketch: {
170
+ lines: { default: 1, min: 0, max: 2, step: 0.01 },
171
+ lineWidth: { default: 1, min: 0.5, max: 4, step: 0.1, digits: 1, unit: 'px' },
172
+ // 1 is the source's density; above it the grain packs toward solid graphite.
173
+ shading: { default: 1, min: 0, max: 2, step: 0.01 },
174
+ softness: { default: 1, min: 0, max: 1, step: 0.01 },
175
+ saturation: { default: 0, min: 0, max: 1, step: 0.01 },
176
+ opacity: { default: 1, min: 0, max: 1, step: 0.01 },
177
+ },
168
178
  dof: {
169
179
  bokehScale: { default: 2, min: 0, max: 8, step: 0.01 },
170
180
  /** World metres of acceptably-sharp depth around the focus plane. */
@@ -303,6 +313,10 @@ export const EFFECT_COLOR_SPECS = {
303
313
  outline: {
304
314
  color: { default: '#000000' },
305
315
  },
316
+ // The mean of the source demo's kraft paper photo.
317
+ sketch: {
318
+ paperColor: { default: '#b69b83' },
319
+ },
306
320
  dropShadow: {
307
321
  color: { default: '#000000' },
308
322
  },
@@ -315,6 +329,7 @@ export const EFFECT_BOOLEAN_SPECS = {
315
329
  transparentBackground: { default: false },
316
330
  },
317
331
  blur: { highQuality: { default: false } },
332
+ sketch: { paperBackground: { default: false } },
318
333
  bloom: {
319
334
  selectColors: { default: false },
320
335
  invertColors: { default: false },
@@ -435,6 +450,7 @@ export const ALL_EFFECT_STACK_ORDER = [
435
450
  'chromaticAberration',
436
451
  'bloom',
437
452
  'diffusion',
453
+ 'sketch',
438
454
  'color',
439
455
  'levels',
440
456
  'colorWheels',
@@ -212,6 +212,19 @@ export declare function pinnableParents(models: readonly {
212
212
  name: string;
213
213
  ridesThis: boolean;
214
214
  }[];
215
+ /**
216
+ * Whether pinning puts an item inside its Live2D host's ArtMesh stack, where `attach.depth` places
217
+ * it: a Live2D item, or a 2D object as one quad. A 3D object only ever paints in front.
218
+ */
219
+ export declare function pinEntersStack(item: {
220
+ kind: 'model';
221
+ ref: {
222
+ kind: ModelFormat;
223
+ };
224
+ } | {
225
+ kind: 'object';
226
+ space: ObjectSpace;
227
+ }): boolean;
215
228
  /**
216
229
  * The depth control's stops, back to front: behind the model, above each of its ArtMeshes in
217
230
  * paint order, then in front of it all. One continuous run, so depth is a slider over the host's
@@ -254,5 +267,7 @@ export declare const OUTLINER_DEFAULT_WIDTH = 288;
254
267
  * split must fit inside it, or no layout satisfies both floors and the group clips instead.
255
268
  */
256
269
  export declare const TWO_COLUMN_WIDTH = 672;
270
+ /** Colors a color picker keeps saved — two rows of its swatch grid; saving more waits for a removal. */
271
+ export declare const SAVED_COLORS_MAX = 12;
257
272
  /** 3D-stage resolution multipliers offered by both performance pickers. */
258
273
  export declare const RENDER_SCALE_PRESETS: readonly [1, 0.85, 0.75, 0.66, 0.5];
@@ -343,6 +343,13 @@ export function pinnableParents(models, items, instanceId) {
343
343
  }
344
344
  return out;
345
345
  }
346
+ /**
347
+ * Whether pinning puts an item inside its Live2D host's ArtMesh stack, where `attach.depth` places
348
+ * it: a Live2D item, or a 2D object as one quad. A 3D object only ever paints in front.
349
+ */
350
+ export function pinEntersStack(item) {
351
+ return item.kind === 'model' ? item.ref.kind === 'live2d' : item.space === '2d';
352
+ }
346
353
  /**
347
354
  * The depth control's stops, back to front: behind the model, above each of its ArtMeshes in
348
355
  * paint order, then in front of it all. One continuous run, so depth is a slider over the host's
@@ -411,5 +418,7 @@ export const OUTLINER_DEFAULT_WIDTH = 288;
411
418
  * split must fit inside it, or no layout satisfies both floors and the group clips instead.
412
419
  */
413
420
  export const TWO_COLUMN_WIDTH = 672;
421
+ /** Colors a color picker keeps saved — two rows of its swatch grid; saving more waits for a removal. */
422
+ export const SAVED_COLORS_MAX = 12;
414
423
  /** 3D-stage resolution multipliers offered by both performance pickers. */
415
424
  export const RENDER_SCALE_PRESETS = [1, 0.85, 0.75, 0.66, 0.5];
@@ -0,0 +1,29 @@
1
+ /**
2
+ * VTube Studio's Movement Config: how far tracked face position slides (`x`, `y`) and scales (`z`)
3
+ * a whole Live2D model. It moves the model itself and never writes a parameter, so it stacks with
4
+ * a rig that binds `FacePositionZ` to a parameter of its own. Amounts are signed — a negative one
5
+ * inverts its axis, 0 switches it off.
6
+ */
7
+ export interface ModelMovementConfig {
8
+ enabled: boolean;
9
+ x: number;
10
+ y: number;
11
+ z: number;
12
+ /** Smoothing levels 0–100, read once when the model loads; file-only, as in VTS. */
13
+ smoothingX: number;
14
+ smoothingY: number;
15
+ smoothingZ: number;
16
+ }
17
+ /** VTS's slider limit (`MAX_MOVEMENT`): an edited amount is a whole number within ±20. */
18
+ export declare const MODEL_MOVEMENT_MAX = 20;
19
+ /** VTS's `ModelPositionMovement` field initializers — a model nobody configured moves by default. */
20
+ export declare const DEFAULT_MODEL_MOVEMENT: Readonly<ModelMovementConfig>;
21
+ /**
22
+ * A stored or relayed configuration, read per field over the defaults. Amounts are taken as
23
+ * written: VTS clamps its sliders to ±20, never what a file holds.
24
+ */
25
+ export declare function healModelMovement(raw: unknown): ModelMovementConfig;
26
+ /** What the Movement Config editor changes; a file's smoothing levels are left as they are. */
27
+ export type ModelMovementEdit = Partial<Pick<ModelMovementConfig, 'enabled' | 'x' | 'y' | 'z'>>;
28
+ /** An edit healed to the editor's limits; an absent or unusable field is dropped, not defaulted. */
29
+ export declare function healModelMovementEdit(raw: unknown): ModelMovementEdit;
@@ -0,0 +1,44 @@
1
+ import { finiteOr, isFiniteNumber, isRecord } from "./guards.js";
2
+ import { clamp } from "./limits.js";
3
+ /** VTS's slider limit (`MAX_MOVEMENT`): an edited amount is a whole number within ±20. */
4
+ export const MODEL_MOVEMENT_MAX = 20;
5
+ /** VTS's `ModelPositionMovement` field initializers — a model nobody configured moves by default. */
6
+ export const DEFAULT_MODEL_MOVEMENT = {
7
+ enabled: true,
8
+ x: 6,
9
+ y: 8,
10
+ z: 11,
11
+ smoothingX: 10,
12
+ smoothingY: 10,
13
+ smoothingZ: 10,
14
+ };
15
+ /**
16
+ * A stored or relayed configuration, read per field over the defaults. Amounts are taken as
17
+ * written: VTS clamps its sliders to ±20, never what a file holds.
18
+ */
19
+ export function healModelMovement(raw) {
20
+ const r = isRecord(raw) ? raw : {};
21
+ const d = DEFAULT_MODEL_MOVEMENT;
22
+ return {
23
+ enabled: typeof r.enabled === 'boolean' ? r.enabled : d.enabled,
24
+ x: finiteOr(r.x, d.x),
25
+ y: finiteOr(r.y, d.y),
26
+ z: finiteOr(r.z, d.z),
27
+ smoothingX: finiteOr(r.smoothingX, d.smoothingX),
28
+ smoothingY: finiteOr(r.smoothingY, d.smoothingY),
29
+ smoothingZ: finiteOr(r.smoothingZ, d.smoothingZ),
30
+ };
31
+ }
32
+ /** An edit healed to the editor's limits; an absent or unusable field is dropped, not defaulted. */
33
+ export function healModelMovementEdit(raw) {
34
+ const r = isRecord(raw) ? raw : {};
35
+ const edit = {};
36
+ if (typeof r.enabled === 'boolean')
37
+ edit.enabled = r.enabled;
38
+ for (const axis of ['x', 'y', 'z']) {
39
+ const v = r[axis];
40
+ if (isFiniteNumber(v))
41
+ edit[axis] = clamp(Math.round(v), -MODEL_MOVEMENT_MAX, MODEL_MOVEMENT_MAX);
42
+ }
43
+ return edit;
44
+ }
@@ -0,0 +1,9 @@
1
+ /** Desktop local wall-clock time, normalized over a repeating 24-hour or 12-hour cycle. */
2
+ export declare const TIME_INPUT_RANGES: {
3
+ readonly Time24h: readonly [0, 1];
4
+ readonly Time12h: readonly [0, 1];
5
+ };
6
+ export type TimeInputName = keyof typeof TIME_INPUT_RANGES;
7
+ export type TimeInputs = Readonly<Record<TimeInputName, number>>;
8
+ export declare const TIME_INPUT_NAMES: readonly TimeInputName[];
9
+ export declare function isTimeInputName(v: string): v is TimeInputName;
@@ -0,0 +1,11 @@
1
+ import { keysOf } from "./guards.js";
2
+ /** Desktop local wall-clock time, normalized over a repeating 24-hour or 12-hour cycle. */
3
+ export const TIME_INPUT_RANGES = {
4
+ Time24h: [0, 1],
5
+ Time12h: [0, 1],
6
+ };
7
+ export const TIME_INPUT_NAMES = keysOf(TIME_INPUT_RANGES);
8
+ const TIME_INPUT_SET = new Set(TIME_INPUT_NAMES);
9
+ export function isTimeInputName(v) {
10
+ return TIME_INPUT_SET.has(v);
11
+ }
@@ -1,3 +1,4 @@
1
+ import type { ModelMovementConfig } from '../values/model-movement.ts';
1
2
  import type { AutomationInfo, ExpressionPersistence, HotkeyState, ModelFormat, PoseStatus, SceneState, Settings, TrackingStatus } from './types.ts';
2
3
  /** Every push event a session can subscribe to, with its payload. */
3
4
  export interface EventMap {
@@ -70,6 +71,11 @@ export interface EventMap {
70
71
  modelId: string;
71
72
  persistence: ExpressionPersistence;
72
73
  };
74
+ /** A model's Movement Config changed, from any editor; every instance of the model follows it. */
75
+ 'model.movement': {
76
+ modelId: string;
77
+ movement: ModelMovementConfig;
78
+ };
73
79
  }
74
80
  export type EventName = keyof EventMap;
75
81
  export type EventData<E extends EventName = EventName> = EventMap[E];
@@ -17,6 +17,7 @@ export const EVENT_NAMES = Object.keys({
17
17
  'selection.changed': true,
18
18
  'registry.changed': true,
19
19
  'expression.persistence': true,
20
+ 'model.movement': true,
20
21
  });
21
22
  export function isEventName(v) {
22
23
  return isOneOf(v, EVENT_NAMES);
@@ -2,6 +2,7 @@ import type { Binding, BindingEditorData, BindingLiveValues } from '../values/bi
2
2
  import type { ControllerMovementConfig, ControllerState } from '../values/controller.ts';
3
3
  import type { SpawnTransition } from '../values/item-transition.ts';
4
4
  import type { LipSyncCalibrationCommand, LipSyncConfig, LipSyncMode, LipSyncState } from '../values/lipsync.ts';
5
+ import type { ModelMovementConfig, ModelMovementEdit } from '../values/model-movement.ts';
5
6
  import type { SceneInspection } from '../values/stage-info.ts';
6
7
  import type { EventName } from './events.ts';
7
8
  import type { AnchorOption, AppCapability, AssetKind, AssetRef, Attach, AttachHeadAngle, AutomationInfo, BindingInput, Expression, ExpressionPersistence, HandTrackingMode, HotkeyConfig, HotkeyState, InjectEntry, InjectTarget, InstanceRuntime, JsonValue, LayerEffectKey, LayerEffects, LayerEffectsPatch, MediaPipeConfig, ModelInfo, ModelRef, MotionGroup, MToonTuning, ObjectContent, ObjectLightOverride, ObjectSpace, Place2D, Place3D, PlayingMotion, PoseSourceId, PoseStatus, Scene, SceneItem, SceneLight, SceneLightCameraFollowOptions, ScenePatch, SceneState, ScreenPlacement, Settings, SettingsPatch, TrackingLostBehavior, TrackingSourceConfig, TrackingSourceId, TrackingSourceKind, TrackingStatus, VrmPlacement } from './types.ts';
@@ -633,6 +634,23 @@ export interface BindingOverrideRequest extends BindingTarget {
633
634
  value: number | null;
634
635
  }
635
636
  export type BindingOverrideResponse = EmptyResponse;
637
+ /** `modelId` defaults to the primary model's; movement is Live2D-only. */
638
+ export interface ModelGetMovementRequest {
639
+ modelId?: string;
640
+ }
641
+ export interface ModelGetMovementResponse {
642
+ movement: ModelMovementConfig;
643
+ }
644
+ export interface ModelSetMovementRequest {
645
+ modelId?: string;
646
+ movement: ModelMovementEdit;
647
+ }
648
+ export interface ModelSetMovementResponse {
649
+ /** The configuration now moving the model, whether or not it reached the file. */
650
+ movement: ModelMovementConfig;
651
+ /** False when nothing reached the file: a refused write (read-only, unreadable), or no usable field. */
652
+ saved: boolean;
653
+ }
636
654
  export interface InstanceSetBreathRequest {
637
655
  instanceId: string;
638
656
  breath?: boolean;
@@ -974,6 +992,14 @@ export interface MethodMap {
974
992
  request: ModelRegisterRequest;
975
993
  response: ModelRegisterResponse;
976
994
  };
995
+ 'model.getMovement': {
996
+ request: ModelGetMovementRequest;
997
+ response: ModelGetMovementResponse;
998
+ };
999
+ 'model.setMovement': {
1000
+ request: ModelSetMovementRequest;
1001
+ response: ModelSetMovementResponse;
1002
+ };
977
1003
  'asset.register': {
978
1004
  request: AssetRegisterRequest;
979
1005
  response: AssetRegisterResponse;
@@ -60,6 +60,8 @@ export const METHOD_NAMES = Object.keys({
60
60
  'automation.run': true,
61
61
  'model.list': true,
62
62
  'model.register': true,
63
+ 'model.getMovement': true,
64
+ 'model.setMovement': true,
63
65
  'asset.list': true,
64
66
  'asset.register': true,
65
67
  'registry.thumbnail': true,
@@ -216,6 +216,13 @@ export declare const requestSchemas: {
216
216
  'model.register': z.ZodObject<{
217
217
  path: z.ZodString;
218
218
  }, z.core.$strip>;
219
+ 'model.getMovement': z.ZodObject<{
220
+ modelId: z.ZodOptional<z.ZodString>;
221
+ }, z.core.$strip>;
222
+ 'model.setMovement': z.ZodObject<{
223
+ modelId: z.ZodOptional<z.ZodString>;
224
+ movement: z.ZodCustom<Partial<Pick<import("../../index.ts").ModelMovementConfig, "enabled" | "x" | "y" | "z">>, Partial<Pick<import("../../index.ts").ModelMovementConfig, "enabled" | "x" | "y" | "z">>>;
225
+ }, z.core.$strip>;
219
226
  'asset.register': z.ZodObject<{
220
227
  path: z.ZodString;
221
228
  want: z.ZodOptional<z.ZodEnum<{
@@ -128,6 +128,12 @@ export const requestSchemas = {
128
128
  }),
129
129
  'tracking.removeSource': z.object({ id: nonEmpty }),
130
130
  'model.register': z.object({ path: nonEmpty }),
131
+ 'model.getMovement': z.object({ modelId: nonEmpty.optional() }),
132
+ // Healed by the host like the desktop editor's own edits, so only the shape is checked here.
133
+ 'model.setMovement': z.object({
134
+ modelId: nonEmpty.optional(),
135
+ movement: z.custom(isRecord, 'movement must be an object'),
136
+ }),
131
137
  // Straight off ASSET_KINDS: a kind added there has to reach the wire gate too, or
132
138
  // `want` answers invalid-params for a kind the registry already knows how to take.
133
139
  'asset.register': z.object({ path: nonEmpty, want: z.enum(ASSET_KINDS).optional() }),
@@ -180,9 +180,14 @@ export declare const SettingsSchema: z.ZodObject<{
180
180
  width: z.ZodNumber;
181
181
  height: z.ZodNumber;
182
182
  }, z.core.$strip>>;
183
+ stageReferenceSize: z.ZodOptional<z.ZodObject<{
184
+ width: z.ZodNumber;
185
+ height: z.ZodNumber;
186
+ }, z.core.$strip>>;
183
187
  }, z.core.$strip>;
184
188
  ui: z.ZodObject<{
185
189
  trayVisible: z.ZodBoolean;
190
+ streamMode: z.ZodOptional<z.ZodBoolean>;
186
191
  }, z.core.$strip>;
187
192
  performance: z.ZodObject<{
188
193
  showFps: z.ZodBoolean;
@@ -99,11 +99,15 @@ export const SettingsSchema = z.object({
99
99
  window: z
100
100
  .object({
101
101
  alwaysOnTop: z.boolean().describe('Keep the control-panel window above other windows.'),
102
- stageSize: StageSizeSchema.optional().describe('Read-only stage content size; absent on older hosts.'),
102
+ stageSize: StageSizeSchema.optional().describe('Read-only stage window content size; absent on older hosts.'),
103
+ stageReferenceSize: StageSizeSchema.optional().describe('Read-only initial stage size calibrating placement units and model fit; not the output aspect.'),
103
104
  })
104
105
  .describe('Public window configuration and read-only stage dimensions.'),
105
106
  ui: z
106
- .object({ trayVisible: z.boolean().describe('Show the tray or menu bar icon.') })
107
+ .object({
108
+ trayVisible: z.boolean().describe('Show the tray or menu bar icon.'),
109
+ streamMode: z.boolean().optional().describe('Read-only Stream Mode state; absent on older hosts.'),
110
+ })
107
111
  .describe('Public interface preferences.'),
108
112
  performance: PerformanceSettingsSchema.partial({ renderScale: true }).describe('Renderer settings; renderScale is absent on older hosts.'),
109
113
  tracking: TrackingSettingsSchema.describe('Network face master switch and all configured tracking sources.'),
@@ -118,6 +122,10 @@ export const SettingsPatchSchema = z.object({
118
122
  .partial()
119
123
  .optional()
120
124
  .describe('Control-panel window fields to update.'),
121
- ui: SettingsSchema.shape.ui.partial().optional().describe('Public interface fields to update.'),
125
+ ui: SettingsSchema.shape.ui
126
+ .pick({ trayVisible: true })
127
+ .partial()
128
+ .optional()
129
+ .describe('Public interface fields to update.'),
122
130
  performance: PerformanceSettingsSchema.partial().optional().describe('Renderer fields to update.'),
123
131
  });
@@ -8,6 +8,7 @@ import { type BaseControllerInputName, type ControllerInputName, type Controller
8
8
  import { type HandInputName } from '../values/hands.ts';
9
9
  import { type LipSyncMode, type VoiceInputName } from '../values/lipsync.ts';
10
10
  import { type MouseInputName } from '../values/mouse.ts';
11
+ import { type TimeInputName } from '../values/time.ts';
11
12
  export type ModelFormat = 'live2d' | 'vrm';
12
13
  /** Where an item came from: shipped with the app, or added by the user. */
13
14
  export type ContentOrigin = 'bundled' | 'user';
@@ -80,7 +81,7 @@ export interface CatalogItem extends Omit<ContentRef, 'origin'> {
80
81
  /** Creator or content homepage (https). */
81
82
  url?: string;
82
83
  }
83
- /** Screen-space placement: pixels from the stage centre, rotation in radians. */
84
+ /** Screen-space placement: calibrated stage units from the viewport centre, rotation in radians. */
84
85
  export interface ScreenPlacement {
85
86
  x: number;
86
87
  y: number;
@@ -207,8 +208,6 @@ export interface SceneModelItem {
207
208
  }
208
209
  export declare const OBJECT_SPACES: readonly ["2d", "3d"];
209
210
  export type ObjectSpace = (typeof OBJECT_SPACES)[number];
210
- /** Where a webpage overlay renders relative to the stage. */
211
- export type WebLayer = 'behind' | 'front';
212
211
  export type CaptureKind = 'display' | 'window';
213
212
  /** What an object renders. Mirrors VTube Studio's items and Warudo's screen/prop assets. */
214
213
  export type ObjectContent = {
@@ -236,8 +235,6 @@ export type ObjectContent = {
236
235
  transparent: boolean;
237
236
  /** Stylesheet injected into every page the object loads; empty for none. */
238
237
  css: string;
239
- /** 2D only: composited behind or in front of the whole stage, not interleaved with other layers. */
240
- layer: WebLayer;
241
238
  /** Close the page while the object is hidden and reload it on show, as an OBS browser source can. */
242
239
  shutdownWhenHidden: boolean;
243
240
  } | {
@@ -661,6 +658,28 @@ export interface EffectBlur {
661
658
  mode: 'gaussian' | 'bokeh';
662
659
  highQuality: boolean;
663
660
  }
661
+ /**
662
+ * Pencil sketch on paper (Jaume Sanchez's NPR sketch shader): graphite grain shades the darker
663
+ * tones and pencil lines trace color and coverage edges, over a procedural paper grain.
664
+ */
665
+ export interface EffectSketch {
666
+ enabled: boolean;
667
+ /** Paper tint (hex) under the drawing. */
668
+ paperColor: string;
669
+ /** Scene only: paper also fills transparent pixels. Layers always keep their coverage. */
670
+ paperBackground: boolean;
671
+ /** Pencil-line darkness, 0–2. */
672
+ lines: number;
673
+ /** Pencil-line width in CSS pixels, 0.5–4, so it holds across display densities. */
674
+ lineWidth: number;
675
+ /** Graphite grain in the darker tones, 0–2: 0 leaves only the lines, 1 is the source's density. */
676
+ shading: number;
677
+ /** How strongly a wide blur of the surrounding tones lightens the shading, 0–1. */
678
+ softness: number;
679
+ /** 0 draws in graphite, 1 keeps the image's colors at the same lightness. */
680
+ saturation: number;
681
+ opacity: number;
682
+ }
664
683
  /** Lens fringing that grows toward frame edges (Unity PPv2's curve, the one VTube Studio wraps). */
665
684
  export interface SceneChromaticAberration {
666
685
  enabled: boolean;
@@ -877,6 +896,7 @@ export interface EffectValues {
877
896
  gradient: EffectGradient;
878
897
  flare: SceneFlare;
879
898
  blur: EffectBlur;
899
+ sketch: EffectSketch;
880
900
  chromaticAberration: SceneChromaticAberration;
881
901
  grain: SceneFilmGrain;
882
902
  dof: SceneDepthOfField;
@@ -1041,7 +1061,7 @@ export interface ScenePatch {
1041
1061
  * App-level features a client gates on (never version-sniff): `hello` and
1042
1062
  * `app.info` report them — the per-app mirror of {@link InstanceRuntime.capabilities}.
1043
1063
  */
1044
- export declare const APP_CAPABILITIES: readonly ["storage", "speech", "automations", "controllers", "model-editing", "asset-inspection", "layer-effects", "scene-transitions", "area-lights", "spot-lights", "camera-follow-lights", "shadow-filters", "environment-map-model", "spawn", "tracking-lost", "motion-stop", "stage-capture"];
1064
+ export declare const APP_CAPABILITIES: readonly ["storage", "speech", "automations", "controllers", "model-editing", "asset-inspection", "layer-effects", "scene-transitions", "area-lights", "spot-lights", "camera-follow-lights", "shadow-filters", "environment-map-model", "spawn", "tracking-lost", "motion-stop", "stage-capture", "model-movement", "object-pin-depth"];
1045
1065
  export type AppCapability = (typeof APP_CAPABILITIES)[number];
1046
1066
  export declare function isAppCapability(v: unknown): v is AppCapability;
1047
1067
  /** What a loaded model instance can do; absent capabilities answer `unsupported-for-format`. */
@@ -1302,12 +1322,12 @@ declare const VTS_INPUT_RANGES: {
1302
1322
  };
1303
1323
  type VtsInputName = keyof typeof VTS_INPUT_RANGES;
1304
1324
  /**
1305
- * Default input vocabulary: face, hand and cursor inputs, raw ARKit channels and controller
1325
+ * Default input vocabulary: face, hand, cursor and time inputs, raw ARKit channels and controller
1306
1326
  * profile 1. Additional controller profile ids are accepted by isInputName without appearing
1307
1327
  * in this list.
1308
1328
  */
1309
- export declare const INPUT_NAMES: readonly (VtsInputName | HandInputName | VoiceInputName | MouseInputName | ArkitInputName | BaseControllerInputName)[];
1310
- export type InputName = VtsInputName | HandInputName | VoiceInputName | MouseInputName | ArkitInputName | ControllerInputName;
1329
+ export declare const INPUT_NAMES: readonly (VtsInputName | HandInputName | VoiceInputName | MouseInputName | TimeInputName | ArkitInputName | BaseControllerInputName)[];
1330
+ export type InputName = VtsInputName | HandInputName | VoiceInputName | MouseInputName | TimeInputName | ArkitInputName | ControllerInputName;
1311
1331
  /** Whether an untrusted string names a tracking input — the guard every wire boundary needs. */
1312
1332
  export declare function isInputName(v: string): v is InputName;
1313
1333
  /**
@@ -1335,7 +1355,7 @@ export declare function arkitTwinOf(input: InputName): BindingInput;
1335
1355
  * Default inputs' natural spans. Use getInputRange for a dynamically numbered controller input.
1336
1356
  * Head angles are degrees; the rest are unitless.
1337
1357
  */
1338
- export declare const INPUT_RANGES: Record<VtsInputName | HandInputName | VoiceInputName | MouseInputName | ArkitInputName | BaseControllerInputName, readonly [number, number]>;
1358
+ export declare const INPUT_RANGES: Record<VtsInputName | HandInputName | VoiceInputName | MouseInputName | TimeInputName | ArkitInputName | BaseControllerInputName, readonly [number, number]>;
1339
1359
  /** Natural span of any valid input, including dynamically numbered controller profiles. */
1340
1360
  export declare function getInputRange(name: InputName): readonly [number, number];
1341
1361
  /**
@@ -7,6 +7,7 @@ import { isOneOf, keysOf } from "../values/guards.js";
7
7
  import { HAND_INPUT_NAMES, HAND_INPUT_RANGES } from "../values/hands.js";
8
8
  import { VOICE_INPUT_NAMES, VOICE_INPUT_RANGES } from "../values/lipsync.js";
9
9
  import { MOUSE_INPUT_NAMES, MOUSE_INPUT_RANGES } from "../values/mouse.js";
10
+ import { TIME_INPUT_NAMES, TIME_INPUT_RANGES } from "../values/time.js";
10
11
  /**
11
12
  * How a registered file is labelled. Wider than an object's content kinds: `.hdr` is only
12
13
  * ever an environment map, and a `.vmd` splits by content — `cameraMotion` for one that
@@ -53,6 +54,13 @@ export const APP_CAPABILITIES = [
53
54
  'motion-stop',
54
55
  /** `stage.capture`. */
55
56
  'stage-capture',
57
+ /** `model.getMovement` / `model.setMovement` and the `model.movement` event. */
58
+ 'model-movement',
59
+ /**
60
+ * 2D images, videos and webpages all paint in the stage's layer stack (a webpage has no `layer`), and
61
+ * one pinned to a Live2D model takes `attach.depth` in that model's stack, as a Live2D item does.
62
+ */
63
+ 'object-pin-depth',
56
64
  ];
57
65
  export function isAppCapability(v) {
58
66
  return isOneOf(v, APP_CAPABILITIES);
@@ -193,7 +201,7 @@ const VTS_INPUT_RANGES = {
193
201
  /** VTS's input vocabulary, plus `JawOpen` — the derived half of {@link INPUT_NAMES}. */
194
202
  const VTS_INPUT_NAMES = keysOf(VTS_INPUT_RANGES);
195
203
  /**
196
- * Default input vocabulary: face, hand and cursor inputs, raw ARKit channels and controller
204
+ * Default input vocabulary: face, hand, cursor and time inputs, raw ARKit channels and controller
197
205
  * profile 1. Additional controller profile ids are accepted by isInputName without appearing
198
206
  * in this list.
199
207
  */
@@ -202,6 +210,7 @@ export const INPUT_NAMES = [
202
210
  ...VOICE_INPUT_NAMES,
203
211
  ...HAND_INPUT_NAMES,
204
212
  ...MOUSE_INPUT_NAMES,
213
+ ...TIME_INPUT_NAMES,
205
214
  ...ARKIT_INPUT_NAMES,
206
215
  ...BASE_CONTROLLER_INPUT_NAMES,
207
216
  ];
@@ -242,6 +251,7 @@ export const INPUT_RANGES = {
242
251
  ...VOICE_INPUT_RANGES,
243
252
  ...HAND_INPUT_RANGES,
244
253
  ...MOUSE_INPUT_RANGES,
254
+ ...TIME_INPUT_RANGES,
245
255
  ...BASE_CONTROLLER_INPUT_RANGES,
246
256
  // `fromEntries` widens the key type back to `string`; the annotation above is what keeps
247
257
  // this exhaustive, and it fails to typecheck if a name ever lacks a range.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@laplace.live/persona-sdk",
3
- "version": "1.19.0",
3
+ "version": "1.21.0",
4
4
  "description": "TypeScript SDK and wire schema for the LAPLACE Persona plugin API",
5
5
  "license": "MIT",
6
6
  "type": "module",