@laplace.live/persona-sdk 1.24.0 → 1.25.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 +10 -10
- package/dist/client/client.js +5 -6
- package/dist/client/legacy-names.d.ts +2 -0
- package/dist/client/{legacy-kinds.js → legacy-names.js} +12 -3
- package/dist/client/node.d.ts +13 -0
- package/dist/client/node.js +29 -0
- package/dist/client/scene-hotkeys.d.ts +16 -0
- package/dist/client/scene-hotkeys.js +17 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/values/bindings.d.ts +40 -15
- package/dist/values/controller.d.ts +9 -6
- package/dist/values/effect-schema.d.ts +26 -0
- package/dist/values/effect-schema.js +76 -91
- package/dist/values/hotkey-targets.d.ts +10 -3
- package/dist/values/hotkey-targets.js +20 -0
- package/dist/values/hotkeys.d.ts +9 -1
- package/dist/values/hotkeys.js +11 -0
- package/dist/values/labels.js +1 -0
- package/dist/values/limits.d.ts +96 -8
- package/dist/values/limits.js +109 -1
- package/dist/values/lipsync.d.ts +7 -8
- package/dist/values/locale.d.ts +4 -1
- package/dist/values/locale.js +4 -0
- package/dist/values/model-movement.d.ts +19 -4
- package/dist/values/model-movement.js +17 -4
- package/dist/values/volumetric-lighting.d.ts +1 -1
- package/dist/values/volumetric-lighting.js +1 -1
- package/dist/wire/envelope.js +6 -3
- package/dist/wire/methods.d.ts +16 -17
- package/dist/wire/protocol.d.ts +2 -0
- package/dist/wire/protocol.js +4 -0
- package/dist/wire/schemas/fields.d.ts +8 -0
- package/dist/wire/schemas/fields.js +1 -0
- package/dist/wire/schemas/requests.d.ts +2 -2
- package/dist/wire/schemas/requests.js +70 -20
- package/dist/wire/schemas/settings.d.ts +3 -3
- package/dist/wire/schemas/settings.js +3 -3
- package/dist/wire/types.d.ts +68 -115
- package/dist/wire/types.js +68 -9
- package/package.json +2 -2
- package/dist/client/legacy-kinds.d.ts +0 -2
package/dist/values/hotkeys.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Hotkey } from '../wire/types.ts';
|
|
1
|
+
import type { Hotkey, HotkeyState } from '../wire/types.ts';
|
|
2
2
|
/** True when the combo carries a command modifier and so may be registered globally. */
|
|
3
3
|
export declare function hasCommandModifier(accelerator: string | null): boolean;
|
|
4
4
|
/**
|
|
@@ -9,6 +9,12 @@ export declare function hasCommandModifier(accelerator: string | null): boolean;
|
|
|
9
9
|
export declare function canRunInBackground(accelerator: string | null): boolean;
|
|
10
10
|
/** True when a model hotkey is meant to fire while Persona sits in the background — what main registers on, and what the globe shows. */
|
|
11
11
|
export declare function hotkeyRunsInBackground(h: Hotkey): boolean;
|
|
12
|
+
/**
|
|
13
|
+
* True when main should hold an OS registration for `h` but doesn't — another app owns its
|
|
14
|
+
* command-modifier combo. A non-command combo (a controller one included) never registers, so its
|
|
15
|
+
* absence is not contention; nor is anything under the model's master switch turned off.
|
|
16
|
+
*/
|
|
17
|
+
export declare function hotkeyContended(state: Pick<HotkeyState, 'keyboardEnabled' | 'registered'>, h: Hotkey): boolean;
|
|
12
18
|
/**
|
|
13
19
|
* Why a `hold` hotkey toggles instead of holding, or null when it can hold. A combo registered with
|
|
14
20
|
* the OS reaches Persona as a press alone — Electron reports no key-up for it — and one registration
|
|
@@ -16,6 +22,8 @@ export declare function hotkeyRunsInBackground(h: Hotkey): boolean;
|
|
|
16
22
|
* `'shared'` when only another one on its combo does, which `registered` (main's list) alone can tell.
|
|
17
23
|
*/
|
|
18
24
|
export declare function holdBlockedBy(h: Pick<Hotkey, 'hold' | 'global' | 'accelerator'>, registered: boolean): 'own' | 'shared' | null;
|
|
25
|
+
/** A hotkey's playback options, which every writer and healer treats as one group. */
|
|
26
|
+
export type HotkeyOptions = Pick<Hotkey, 'hold' | 'fadeSeconds' | 'deactivateAfterSeconds' | 'stopsOnLastFrame'>;
|
|
19
27
|
/** VTS's editor limits for a hotkey's fade and auto-off times, in seconds; healing clamps API input to them. */
|
|
20
28
|
export declare const HOTKEY_FADE_SECONDS_MAX = 2;
|
|
21
29
|
export declare const HOTKEY_DEACTIVATE_SECONDS_MIN = 0.1;
|
package/dist/values/hotkeys.js
CHANGED
|
@@ -25,6 +25,17 @@ export function canRunInBackground(accelerator) {
|
|
|
25
25
|
export function hotkeyRunsInBackground(h) {
|
|
26
26
|
return h.global && h.active && h.action !== null && canRunInBackground(h.accelerator);
|
|
27
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* True when main should hold an OS registration for `h` but doesn't — another app owns its
|
|
30
|
+
* command-modifier combo. A non-command combo (a controller one included) never registers, so its
|
|
31
|
+
* absence is not contention; nor is anything under the model's master switch turned off.
|
|
32
|
+
*/
|
|
33
|
+
export function hotkeyContended(state, h) {
|
|
34
|
+
return (state.keyboardEnabled &&
|
|
35
|
+
hotkeyRunsInBackground(h) &&
|
|
36
|
+
hasCommandModifier(h.accelerator) &&
|
|
37
|
+
!state.registered.includes(h.id));
|
|
38
|
+
}
|
|
28
39
|
/**
|
|
29
40
|
* Why a `hold` hotkey toggles instead of holding, or null when it can hold. A combo registered with
|
|
30
41
|
* the OS reaches Persona as a press alone — Electron reports no key-up for it — and one registration
|
package/dist/values/labels.js
CHANGED
|
@@ -26,6 +26,7 @@ export function motionSlots(files) {
|
|
|
26
26
|
return files.flatMap((file, index) => (file === '' ? [] : [{ file, index }]));
|
|
27
27
|
}
|
|
28
28
|
// English fallbacks; localizing clients keep their own map and use these for unknown kinds.
|
|
29
|
+
// The panel's `KIND_SPECS` labels must read the same — a desktop test holds them together.
|
|
29
30
|
const AUTOMATION_ACTION_KIND_LABELS = {
|
|
30
31
|
'effect-toggle': 'Toggle Effect',
|
|
31
32
|
'effect-params': 'Apply Effect Settings',
|
package/dist/values/limits.d.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
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';
|
|
1
|
+
import type { AssetRef, Attach, AttachAnchor, AttachDepth, CameraAnchor, CameraFollowDamping, CameraTrackTarget, ChromaKey, EnvironmentLook, KeyableContent, ModelFormat, MToonTuning, ObjectContent, ObjectSpace, SceneCameraAim, SceneCameraAimSettings, SceneCameraFocus, SceneCameraFocusSettings, SceneCameraFollow, SceneCameraFollowSettings, SceneCameraHandheld, SceneEnvironment, SceneLight, SceneLightCameraFollowOptions, SceneLightType, ScreenPlacement, TextFont, 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;
|
|
5
5
|
/** Wrap an angle into (-π, π]. */
|
|
6
6
|
export declare function wrapAngle(a: number): number;
|
|
7
|
+
/** Wrap degrees into (-180°, 180°], {@link wrapAngle}'s range; whole turns keep in-range values exact. */
|
|
8
|
+
export declare function wrapDegrees(v: number): number;
|
|
7
9
|
export declare const DEFAULT_LIVE2D_PLACEMENT: ScreenPlacement;
|
|
8
10
|
export declare const DEFAULT_VRM_PLACEMENT: VrmPlacement;
|
|
9
11
|
/** Scene colors heal to 6-digit hex; both cases are spelled out because JSON Schema patterns carry no flags. */
|
|
@@ -27,6 +29,11 @@ export declare const SCENE_LIGHT_SPOT_ANGLE_MIN = 1;
|
|
|
27
29
|
export declare const SCENE_LIGHT_SPOT_ANGLE_MAX = 89;
|
|
28
30
|
export declare const DEFAULT_SPOT_ANGLE_DEG = 30;
|
|
29
31
|
export declare const DEFAULT_SPOT_PENUMBRA = 0.3;
|
|
32
|
+
/** Light azimuth and roll bounds in degrees, symmetric: a half turn either way. */
|
|
33
|
+
export declare const SCENE_LIGHT_AZIMUTH_MAX = 180;
|
|
34
|
+
export declare const SCENE_LIGHT_ROLL_MAX = 180;
|
|
35
|
+
/** Light elevation bound in degrees, symmetric: straight up or straight down. */
|
|
36
|
+
export declare const SCENE_LIGHT_ELEVATION_MAX = 90;
|
|
30
37
|
/** Positional lights need falloff headroom; the slider and scene healing share this ceiling. */
|
|
31
38
|
export declare function sceneLightIntensityMax(type: SceneLightType): number;
|
|
32
39
|
/** Whether a light of `type` aims with azimuth/elevation. */
|
|
@@ -41,6 +48,7 @@ export declare const LIVE2D_SCALE_MIN = 0.1;
|
|
|
41
48
|
export declare const LIVE2D_SCALE_MAX = 24;
|
|
42
49
|
export declare const VRM_SCALE_MIN = 0.05;
|
|
43
50
|
export declare const VRM_SCALE_MAX = 10;
|
|
51
|
+
export declare const SCENE_LIGHT_RANGE_MIN = 0.1;
|
|
44
52
|
export declare const SCENE_LIGHT_RANGE_MAX = 20;
|
|
45
53
|
/** Past this the 5-tap kernel spreads thin enough that its dither reads as noise. */
|
|
46
54
|
export declare const SCENE_LIGHT_SHADOW_RADIUS_MAX = 16;
|
|
@@ -48,6 +56,8 @@ export declare const SCENE_LIGHT_SHADOW_RADIUS_MAX = 16;
|
|
|
48
56
|
export declare const SCENE_LIGHT_POINT_SHADOW_RADIUS_MAX = 32;
|
|
49
57
|
export declare const SCENE_LIGHT_SHADOW_SOURCE_SIZE_MAX = 10;
|
|
50
58
|
export declare const DEFAULT_LIGHT_SHADOW_SOURCE_SIZE = 0.1;
|
|
59
|
+
/** A light override's key: the baked light's traversal index, in decimal digits. */
|
|
60
|
+
export declare const LIGHT_OVERRIDE_KEY_RE: RegExp;
|
|
51
61
|
/** Shadow softness (PCF disk radius, in shadow-map texels) ceiling for a light of `type`. */
|
|
52
62
|
export declare function sceneLightShadowRadiusMax(type: SceneLightType): number;
|
|
53
63
|
/**
|
|
@@ -68,6 +78,8 @@ export declare const CAMERA_DAMPING_MAX = 10;
|
|
|
68
78
|
/** Ceiling for {@link SceneCameraAim}'s lookahead time, in seconds, and its smoothing. */
|
|
69
79
|
export declare const CAMERA_AIM_LOOKAHEAD_MAX = 1;
|
|
70
80
|
export declare const CAMERA_AIM_SMOOTHING_MAX = 30;
|
|
81
|
+
/** {@link SceneCameraAim}'s bias bound on each axis, symmetric: a share of the slack between its zones. */
|
|
82
|
+
export declare const CAMERA_AIM_BIAS_MAX = 0.5;
|
|
71
83
|
/** Ceiling for {@link SceneCameraHandheld}'s speed. */
|
|
72
84
|
export declare const CAMERA_HANDHELD_SPEED_MAX = 2;
|
|
73
85
|
/** Warudo's Handheld Movement defaults, switched off. */
|
|
@@ -105,6 +117,30 @@ export declare function cameraTrackKind(item: {
|
|
|
105
117
|
kind: 'object';
|
|
106
118
|
space: ObjectSpace;
|
|
107
119
|
}): 'model' | 'object' | null;
|
|
120
|
+
/**
|
|
121
|
+
* The layer `target` tracks in `scene` — the primary model for a primary pick, else the first model — when it
|
|
122
|
+
* is one a camera can track.
|
|
123
|
+
*/
|
|
124
|
+
export declare function cameraTrackItem<T extends {
|
|
125
|
+
kind: 'model';
|
|
126
|
+
instanceId: string;
|
|
127
|
+
ref: {
|
|
128
|
+
kind: string;
|
|
129
|
+
};
|
|
130
|
+
} | {
|
|
131
|
+
kind: 'object';
|
|
132
|
+
instanceId: string;
|
|
133
|
+
space: ObjectSpace;
|
|
134
|
+
}>(scene: {
|
|
135
|
+
items: readonly T[];
|
|
136
|
+
primaryInstanceId: string | null;
|
|
137
|
+
}, target: CameraTrackTarget): T | undefined;
|
|
138
|
+
/** A layer the camera's follow, aim and focus pickers offer; `model` tells a model from an object. */
|
|
139
|
+
export interface CameraTrackCandidate {
|
|
140
|
+
instanceId: string;
|
|
141
|
+
name: string;
|
|
142
|
+
model: boolean;
|
|
143
|
+
}
|
|
108
144
|
/** 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
145
|
export declare function cameraTrackCandidates(items: readonly ({
|
|
110
146
|
kind: 'model';
|
|
@@ -118,11 +154,7 @@ export declare function cameraTrackCandidates(items: readonly ({
|
|
|
118
154
|
instanceId: string;
|
|
119
155
|
name: string;
|
|
120
156
|
space: ObjectSpace;
|
|
121
|
-
})[]):
|
|
122
|
-
instanceId: string;
|
|
123
|
-
name: string;
|
|
124
|
-
model: boolean;
|
|
125
|
-
}[];
|
|
157
|
+
})[]): CameraTrackCandidate[];
|
|
126
158
|
/** Manual navigation stops short of ±90°; authored camera elevations remain unrestricted. */
|
|
127
159
|
export declare const ELEVATION_LIMIT: number;
|
|
128
160
|
/** Manual navigation distance bounds; authored cameras retain signed, unrestricted distances. */
|
|
@@ -153,6 +185,23 @@ export declare const MTOON_OUTLINE_WIDTH_MAX = 2;
|
|
|
153
185
|
/** HDR headroom: past ×1 the point is pushing emissive parts over the bloom threshold. */
|
|
154
186
|
export declare const MTOON_EMISSIVE_MAX = 4;
|
|
155
187
|
export declare const MTOON_UV_ANIMATION_MAX = 4;
|
|
188
|
+
/** Each {@link MToonTuning} field's `[min, max]`: offsets −1..1, `shade` 0..1, multipliers 0 to their ceiling. */
|
|
189
|
+
export declare const MTOON_RANGES: {
|
|
190
|
+
readonly shade: readonly [0, 1];
|
|
191
|
+
readonly shadingShift: readonly [-1, 1];
|
|
192
|
+
readonly shadingToony: readonly [-1, 1];
|
|
193
|
+
readonly giEqualization: readonly [-1, 1];
|
|
194
|
+
readonly normalScale: readonly [0, 2];
|
|
195
|
+
readonly rim: readonly [0, 2];
|
|
196
|
+
readonly rimLift: readonly [-1, 1];
|
|
197
|
+
readonly rimFresnelPower: readonly [0, 4];
|
|
198
|
+
readonly rimLightingMix: readonly [-1, 1];
|
|
199
|
+
readonly matcap: readonly [0, 4];
|
|
200
|
+
readonly outlineWidth: readonly [0, 2];
|
|
201
|
+
readonly outlineLightingMix: readonly [-1, 1];
|
|
202
|
+
readonly emissive: readonly [0, 4];
|
|
203
|
+
readonly uvAnimation: readonly [0, 4];
|
|
204
|
+
};
|
|
156
205
|
export declare function defaultMToonTuning(): MToonTuning;
|
|
157
206
|
export declare const DEFAULT_LIGHT_AZIMUTH_DEG = 45;
|
|
158
207
|
export declare const DEFAULT_LIGHT_ELEVATION_DEG: number;
|
|
@@ -262,8 +311,26 @@ export declare const TEXT_BEVEL_RESOLUTION_MAX = 8;
|
|
|
262
311
|
export declare const TEXT_EMISSION_MAX = 4;
|
|
263
312
|
/** A PostScript name: printable ASCII, at most 63 chars, minus the quote and backslash that would escape `local("…")`. */
|
|
264
313
|
export declare const TEXT_POSTSCRIPT_NAME_RE: RegExp;
|
|
314
|
+
/** A text font's CSS `font-weight` bounds. */
|
|
315
|
+
export declare const TEXT_FONT_WEIGHT_MIN = 1;
|
|
316
|
+
export declare const TEXT_FONT_WEIGHT_MAX = 1000;
|
|
317
|
+
/** A text font's CSS `font-stretch` bounds, in percent. */
|
|
318
|
+
export declare const TEXT_FONT_STRETCH_MIN = 50;
|
|
319
|
+
export declare const TEXT_FONT_STRETCH_MAX = 200;
|
|
320
|
+
/** CSS's normal face: what a text font, or a font file, that leaves its weight, slope or width unsaid reads as. */
|
|
321
|
+
export declare const DEFAULT_TEXT_FONT_FACE: Pick<TextFont, 'weight' | 'italic' | 'stretch'>;
|
|
265
322
|
/** A prop is a mesh, so it only exists in the three.js scene; every other kind renders in both. */
|
|
266
323
|
export declare function objectSupportsSpace(kind: ObjectContent['kind'], space: ObjectSpace): boolean;
|
|
324
|
+
/** The {@link ObjectContent} kinds that stream from a registered asset; the rest carry their source inline. */
|
|
325
|
+
declare const ASSET_CONTENT_KINDS: readonly ["image", "video", "prop"];
|
|
326
|
+
/** The variants of {@link ObjectContent} that stream from a registered asset. */
|
|
327
|
+
export type AssetContent = Extract<ObjectContent, {
|
|
328
|
+
kind: (typeof ASSET_CONTENT_KINDS)[number];
|
|
329
|
+
}>;
|
|
330
|
+
/** Whether `kind` — a content kind, or an Inventory row's — is one whose asset streams into a layer. */
|
|
331
|
+
export declare function isAssetContentKind(kind: string): kind is AssetContent['kind'];
|
|
332
|
+
/** Whether the content streams from a registered asset: image, video and prop; the rest carry their source inline. */
|
|
333
|
+
export declare function isAssetContent(content: ObjectContent): content is AssetContent;
|
|
267
334
|
/** The asset an object streams from, or null for kinds that carry their source inline (web, capture, text). */
|
|
268
335
|
export declare function contentAssetId(content: ObjectContent): string | null;
|
|
269
336
|
/**
|
|
@@ -281,6 +348,12 @@ export declare function pinRiders(items: readonly {
|
|
|
281
348
|
parentInstanceId: string;
|
|
282
349
|
} | null;
|
|
283
350
|
}[], instanceId: string): Set<string>;
|
|
351
|
+
/** A pin picker row; `ridesThis` flags a model that already rides the item. */
|
|
352
|
+
interface PinnableParent {
|
|
353
|
+
instanceId: string;
|
|
354
|
+
name: string;
|
|
355
|
+
ridesThis: boolean;
|
|
356
|
+
}
|
|
284
357
|
/**
|
|
285
358
|
* Rows for the pin picker: every other Live2D model, each flagged when it already rides
|
|
286
359
|
* `instanceId` — picking one would put the two in a circle, so both apps offer it disabled
|
|
@@ -297,10 +370,20 @@ export declare function pinnableParents(models: readonly {
|
|
|
297
370
|
attach?: {
|
|
298
371
|
parentInstanceId: string;
|
|
299
372
|
} | null;
|
|
300
|
-
}[], instanceId: string):
|
|
373
|
+
}[], instanceId: string): PinnableParent[];
|
|
374
|
+
/**
|
|
375
|
+
* {@link pinnableParents} for an object: every model of the one format its space can ride. Nothing
|
|
376
|
+
* rides an object, so no row is ever flagged.
|
|
377
|
+
*/
|
|
378
|
+
export declare function objectPinParents(models: readonly {
|
|
379
|
+
instanceId: string;
|
|
380
|
+
ref: {
|
|
381
|
+
kind: string;
|
|
382
|
+
name: string;
|
|
383
|
+
};
|
|
384
|
+
}[], space: ObjectSpace): {
|
|
301
385
|
instanceId: string;
|
|
302
386
|
name: string;
|
|
303
|
-
ridesThis: boolean;
|
|
304
387
|
}[];
|
|
305
388
|
/**
|
|
306
389
|
* Whether pinning puts an item inside its Live2D host's ArtMesh stack, where `attach.depth` places
|
|
@@ -323,6 +406,8 @@ export declare function pinEntersStack(item: {
|
|
|
323
406
|
export declare function depthStops(meshes: readonly string[]): AttachDepth[];
|
|
324
407
|
/** Which stop a depth sits at; an ArtMesh the host no longer has reads as the frontmost. */
|
|
325
408
|
export declare function depthStopIndex(meshes: readonly string[], depth: AttachDepth): number;
|
|
409
|
+
/** A stored anchor — a model's root, a VRM bone or a point on a Live2D ArtMesh — as a usable one, or null. */
|
|
410
|
+
export declare function healAttachAnchor(raw: unknown): AttachAnchor | null;
|
|
326
411
|
export declare const DEFAULT_HEAD_ANGLE: NonNullable<Attach['headAngle']>;
|
|
327
412
|
/**
|
|
328
413
|
* A fresh pin onto `parentInstanceId`: root anchor, in front of the model. A screen-space item (a
|
|
@@ -343,6 +428,8 @@ export declare const STORAGE_KEY_MAX_LENGTH = 128;
|
|
|
343
428
|
export declare const STORAGE_VALUE_MAX_LENGTH: number;
|
|
344
429
|
/** Keys one API key may hold; a `storage.set` that would exceed it answers `invalid-state`. */
|
|
345
430
|
export declare const STORAGE_KEYS_MAX = 256;
|
|
431
|
+
/** `session.identify` refuses a longer name, so a client composing one trims it to this. */
|
|
432
|
+
export declare const SESSION_NAME_MAX_LENGTH = 64;
|
|
346
433
|
/** `speech.play` URL ceiling — sized for a ~40 s WAV as a base64 `data:audio/*` payload. */
|
|
347
434
|
export declare const SPEECH_URL_MAX_LENGTH = 8000000;
|
|
348
435
|
/**
|
|
@@ -364,3 +451,4 @@ export declare const TWO_COLUMN_WIDTH = 672;
|
|
|
364
451
|
export declare const SAVED_COLORS_MAX = 12;
|
|
365
452
|
/** 3D-stage resolution multipliers offered by both performance pickers. */
|
|
366
453
|
export declare const RENDER_SCALE_PRESETS: readonly [1, 0.85, 0.75, 0.66, 0.5];
|
|
454
|
+
export {};
|
package/dist/values/limits.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { effectLayerKeys } from "./effect-schema.js";
|
|
2
|
+
import { isFiniteNumber, isOneOf, isRecord } from "./guards.js";
|
|
2
3
|
export function clamp(v, min, max) {
|
|
3
4
|
return Math.min(max, Math.max(min, v));
|
|
4
5
|
}
|
|
@@ -16,6 +17,10 @@ export function wrapAngle(a) {
|
|
|
16
17
|
r += TAU;
|
|
17
18
|
return r;
|
|
18
19
|
}
|
|
20
|
+
/** Wrap degrees into (-180°, 180°], {@link wrapAngle}'s range; whole turns keep in-range values exact. */
|
|
21
|
+
export function wrapDegrees(v) {
|
|
22
|
+
return v - 360 * Math.ceil((v - 180) / 360);
|
|
23
|
+
}
|
|
19
24
|
export const DEFAULT_LIVE2D_PLACEMENT = { x: 0, y: 0, scale: 1, rotation: 0 };
|
|
20
25
|
export const DEFAULT_VRM_PLACEMENT = { x: 0, y: 0, z: 0, rotX: 0, rotY: 0, rotZ: 0, scale: 1 };
|
|
21
26
|
/** Scene colors heal to 6-digit hex; both cases are spelled out because JSON Schema patterns carry no flags. */
|
|
@@ -43,6 +48,11 @@ export const SCENE_LIGHT_SPOT_ANGLE_MIN = 1;
|
|
|
43
48
|
export const SCENE_LIGHT_SPOT_ANGLE_MAX = 89;
|
|
44
49
|
export const DEFAULT_SPOT_ANGLE_DEG = 30;
|
|
45
50
|
export const DEFAULT_SPOT_PENUMBRA = 0.3;
|
|
51
|
+
/** Light azimuth and roll bounds in degrees, symmetric: a half turn either way. */
|
|
52
|
+
export const SCENE_LIGHT_AZIMUTH_MAX = 180;
|
|
53
|
+
export const SCENE_LIGHT_ROLL_MAX = 180;
|
|
54
|
+
/** Light elevation bound in degrees, symmetric: straight up or straight down. */
|
|
55
|
+
export const SCENE_LIGHT_ELEVATION_MAX = 90;
|
|
46
56
|
/** Positional lights need falloff headroom; the slider and scene healing share this ceiling. */
|
|
47
57
|
export function sceneLightIntensityMax(type) {
|
|
48
58
|
return type === 'directional' || type === 'ambient' ? SCENE_LIGHT_INTENSITY_MAX : SCENE_LIGHT_POINT_INTENSITY_MAX;
|
|
@@ -68,6 +78,7 @@ export const LIVE2D_SCALE_MIN = 0.1;
|
|
|
68
78
|
export const LIVE2D_SCALE_MAX = 24;
|
|
69
79
|
export const VRM_SCALE_MIN = 0.05;
|
|
70
80
|
export const VRM_SCALE_MAX = 10;
|
|
81
|
+
export const SCENE_LIGHT_RANGE_MIN = 0.1;
|
|
71
82
|
// Range only *cuts off* a point light — three's 1/d² decay has extinguished it
|
|
72
83
|
// by ~18 m even at max intensity, so a slider past 20 changes nothing visible.
|
|
73
84
|
export const SCENE_LIGHT_RANGE_MAX = 20;
|
|
@@ -77,6 +88,8 @@ export const SCENE_LIGHT_SHADOW_RADIUS_MAX = 16;
|
|
|
77
88
|
export const SCENE_LIGHT_POINT_SHADOW_RADIUS_MAX = 32;
|
|
78
89
|
export const SCENE_LIGHT_SHADOW_SOURCE_SIZE_MAX = 10;
|
|
79
90
|
export const DEFAULT_LIGHT_SHADOW_SOURCE_SIZE = 0.1;
|
|
91
|
+
/** A light override's key: the baked light's traversal index, in decimal digits. */
|
|
92
|
+
export const LIGHT_OVERRIDE_KEY_RE = /^\d+$/;
|
|
80
93
|
/** Shadow softness (PCF disk radius, in shadow-map texels) ceiling for a light of `type`. */
|
|
81
94
|
export function sceneLightShadowRadiusMax(type) {
|
|
82
95
|
return type === 'point' ? SCENE_LIGHT_POINT_SHADOW_RADIUS_MAX : SCENE_LIGHT_SHADOW_RADIUS_MAX;
|
|
@@ -99,6 +112,8 @@ export const CAMERA_DAMPING_MAX = 10;
|
|
|
99
112
|
/** Ceiling for {@link SceneCameraAim}'s lookahead time, in seconds, and its smoothing. */
|
|
100
113
|
export const CAMERA_AIM_LOOKAHEAD_MAX = 1;
|
|
101
114
|
export const CAMERA_AIM_SMOOTHING_MAX = 30;
|
|
115
|
+
/** {@link SceneCameraAim}'s bias bound on each axis, symmetric: a share of the slack between its zones. */
|
|
116
|
+
export const CAMERA_AIM_BIAS_MAX = 0.5;
|
|
102
117
|
/** Ceiling for {@link SceneCameraHandheld}'s speed. */
|
|
103
118
|
export const CAMERA_HANDHELD_SPEED_MAX = 2;
|
|
104
119
|
/** Warudo's Handheld Movement defaults, switched off. */
|
|
@@ -158,6 +173,18 @@ export function cameraTrackKind(item) {
|
|
|
158
173
|
return item.ref.kind === 'vrm' ? 'model' : null;
|
|
159
174
|
return item.space === '3d' ? 'object' : null;
|
|
160
175
|
}
|
|
176
|
+
/**
|
|
177
|
+
* The layer `target` tracks in `scene` — the primary model for a primary pick, else the first model — when it
|
|
178
|
+
* is one a camera can track.
|
|
179
|
+
*/
|
|
180
|
+
export function cameraTrackItem(scene, target) {
|
|
181
|
+
// Runs per frame for each camera track, so it searches the list rather than filtering a copy.
|
|
182
|
+
const item = target.target === 'primary'
|
|
183
|
+
? (scene.items.find(i => i.kind === 'model' && i.instanceId === scene.primaryInstanceId) ??
|
|
184
|
+
scene.items.find(i => i.kind === 'model'))
|
|
185
|
+
: scene.items.find(i => i.instanceId === target.instanceId);
|
|
186
|
+
return item && cameraTrackKind(item) ? item : undefined;
|
|
187
|
+
}
|
|
161
188
|
/** Rows for the camera's follow, aim and focus pickers, in layer order. One rule, or the console offers a layer the desktop drops. */
|
|
162
189
|
export function cameraTrackCandidates(items) {
|
|
163
190
|
return items.flatMap(item => {
|
|
@@ -196,6 +223,23 @@ export const MTOON_OUTLINE_WIDTH_MAX = 2;
|
|
|
196
223
|
/** HDR headroom: past ×1 the point is pushing emissive parts over the bloom threshold. */
|
|
197
224
|
export const MTOON_EMISSIVE_MAX = 4;
|
|
198
225
|
export const MTOON_UV_ANIMATION_MAX = 4;
|
|
226
|
+
/** Each {@link MToonTuning} field's `[min, max]`: offsets −1..1, `shade` 0..1, multipliers 0 to their ceiling. */
|
|
227
|
+
export const MTOON_RANGES = {
|
|
228
|
+
shade: [0, 1],
|
|
229
|
+
shadingShift: [-1, 1],
|
|
230
|
+
shadingToony: [-1, 1],
|
|
231
|
+
giEqualization: [-1, 1],
|
|
232
|
+
normalScale: [0, MTOON_NORMAL_SCALE_MAX],
|
|
233
|
+
rim: [0, MTOON_RIM_MAX],
|
|
234
|
+
rimLift: [-1, 1],
|
|
235
|
+
rimFresnelPower: [0, MTOON_RIM_FRESNEL_POWER_MAX],
|
|
236
|
+
rimLightingMix: [-1, 1],
|
|
237
|
+
matcap: [0, MTOON_MATCAP_MAX],
|
|
238
|
+
outlineWidth: [0, MTOON_OUTLINE_WIDTH_MAX],
|
|
239
|
+
outlineLightingMix: [-1, 1],
|
|
240
|
+
emissive: [0, MTOON_EMISSIVE_MAX],
|
|
241
|
+
uvAnimation: [0, MTOON_UV_ANIMATION_MAX],
|
|
242
|
+
};
|
|
199
243
|
export function defaultMToonTuning() {
|
|
200
244
|
return {
|
|
201
245
|
shade: 1,
|
|
@@ -402,13 +446,35 @@ export const TEXT_BEVEL_RESOLUTION_MAX = 8;
|
|
|
402
446
|
export const TEXT_EMISSION_MAX = 4;
|
|
403
447
|
/** A PostScript name: printable ASCII, at most 63 chars, minus the quote and backslash that would escape `local("…")`. */
|
|
404
448
|
export const TEXT_POSTSCRIPT_NAME_RE = /^[\x21\x23-\x5b\x5d-\x7e]{1,63}$/;
|
|
449
|
+
/** A text font's CSS `font-weight` bounds. */
|
|
450
|
+
export const TEXT_FONT_WEIGHT_MIN = 1;
|
|
451
|
+
export const TEXT_FONT_WEIGHT_MAX = 1000;
|
|
452
|
+
/** A text font's CSS `font-stretch` bounds, in percent. */
|
|
453
|
+
export const TEXT_FONT_STRETCH_MIN = 50;
|
|
454
|
+
export const TEXT_FONT_STRETCH_MAX = 200;
|
|
455
|
+
/** CSS's normal face: what a text font, or a font file, that leaves its weight, slope or width unsaid reads as. */
|
|
456
|
+
export const DEFAULT_TEXT_FONT_FACE = {
|
|
457
|
+
weight: 400,
|
|
458
|
+
italic: false,
|
|
459
|
+
stretch: 100,
|
|
460
|
+
};
|
|
405
461
|
/** A prop is a mesh, so it only exists in the three.js scene; every other kind renders in both. */
|
|
406
462
|
export function objectSupportsSpace(kind, space) {
|
|
407
463
|
return kind === 'prop' ? space === '3d' : true;
|
|
408
464
|
}
|
|
465
|
+
/** The {@link ObjectContent} kinds that stream from a registered asset; the rest carry their source inline. */
|
|
466
|
+
const ASSET_CONTENT_KINDS = ['image', 'video', 'prop'];
|
|
467
|
+
/** Whether `kind` — a content kind, or an Inventory row's — is one whose asset streams into a layer. */
|
|
468
|
+
export function isAssetContentKind(kind) {
|
|
469
|
+
return isOneOf(kind, ASSET_CONTENT_KINDS);
|
|
470
|
+
}
|
|
471
|
+
/** Whether the content streams from a registered asset: image, video and prop; the rest carry their source inline. */
|
|
472
|
+
export function isAssetContent(content) {
|
|
473
|
+
return isAssetContentKind(content.kind);
|
|
474
|
+
}
|
|
409
475
|
/** The asset an object streams from, or null for kinds that carry their source inline (web, capture, text). */
|
|
410
476
|
export function contentAssetId(content) {
|
|
411
|
-
return content
|
|
477
|
+
return isAssetContent(content) ? content.assetId : null;
|
|
412
478
|
}
|
|
413
479
|
/**
|
|
414
480
|
* The one model format a space's objects can ride: the screen layer always composites
|
|
@@ -453,6 +519,14 @@ export function pinnableParents(models, items, instanceId) {
|
|
|
453
519
|
}
|
|
454
520
|
return out;
|
|
455
521
|
}
|
|
522
|
+
/**
|
|
523
|
+
* {@link pinnableParents} for an object: every model of the one format its space can ride. Nothing
|
|
524
|
+
* rides an object, so no row is ever flagged.
|
|
525
|
+
*/
|
|
526
|
+
export function objectPinParents(models, space) {
|
|
527
|
+
const format = attachableParentFormat(space);
|
|
528
|
+
return models.filter(m => m.ref.kind === format).map(m => ({ instanceId: m.instanceId, name: m.ref.name }));
|
|
529
|
+
}
|
|
456
530
|
/**
|
|
457
531
|
* Whether pinning puts an item inside its Live2D host's ArtMesh stack, where `attach.depth` places
|
|
458
532
|
* it: a Live2D item, or a 2D object as one quad. A 3D object only ever paints in front.
|
|
@@ -477,6 +551,38 @@ export function depthStopIndex(meshes, depth) {
|
|
|
477
551
|
const at = meshes.indexOf(depth.id);
|
|
478
552
|
return at < 0 ? meshes.length + 1 : at + 1;
|
|
479
553
|
}
|
|
554
|
+
/** Three non-negative finite numbers — whole ones when `int` — or null. */
|
|
555
|
+
function healTriple(raw, int) {
|
|
556
|
+
if (!Array.isArray(raw) || raw.length !== 3)
|
|
557
|
+
return null;
|
|
558
|
+
const usable = (v) => isFiniteNumber(v) && v >= 0 && (!int || Number.isInteger(v));
|
|
559
|
+
const [a, b, c] = raw;
|
|
560
|
+
return usable(a) && usable(b) && usable(c) ? [a, b, c] : null;
|
|
561
|
+
}
|
|
562
|
+
/** A stored anchor — a model's root, a VRM bone or a point on a Live2D ArtMesh — as a usable one, or null. */
|
|
563
|
+
export function healAttachAnchor(raw) {
|
|
564
|
+
if (!isRecord(raw))
|
|
565
|
+
return null;
|
|
566
|
+
if (raw.kind === 'root')
|
|
567
|
+
return { kind: 'root' };
|
|
568
|
+
if (raw.kind === 'bone')
|
|
569
|
+
return typeof raw.bone === 'string' && raw.bone !== '' ? { kind: 'bone', bone: raw.bone } : null;
|
|
570
|
+
if (raw.kind !== 'artMesh' || typeof raw.id !== 'string' || raw.id === '')
|
|
571
|
+
return null;
|
|
572
|
+
const verts = healTriple(raw.verts, true);
|
|
573
|
+
const weights = healTriple(raw.weights, false);
|
|
574
|
+
if (!verts || !weights)
|
|
575
|
+
return null;
|
|
576
|
+
const sum = weights[0] + weights[1] + weights[2];
|
|
577
|
+
// Finite weights can still sum to Infinity; dividing by it would zero the point.
|
|
578
|
+
if (!Number.isFinite(sum) || sum <= 0)
|
|
579
|
+
return null;
|
|
580
|
+
// Normalized so the point stays inside its triangle whatever was stored. A normalized triple is kept
|
|
581
|
+
// bit for bit: dividing it again can flip last bits, and hosts key anchor caches on them.
|
|
582
|
+
if (Math.abs(sum - 1) < 1e-9)
|
|
583
|
+
return { kind: 'artMesh', id: raw.id, verts, weights };
|
|
584
|
+
return { kind: 'artMesh', id: raw.id, verts, weights: [weights[0] / sum, weights[1] / sum, weights[2] / sum] };
|
|
585
|
+
}
|
|
480
586
|
export const DEFAULT_HEAD_ANGLE = {
|
|
481
587
|
multiplier: 1,
|
|
482
588
|
parallaxX: 0,
|
|
@@ -514,6 +620,8 @@ export const STORAGE_KEY_MAX_LENGTH = 128;
|
|
|
514
620
|
export const STORAGE_VALUE_MAX_LENGTH = 64 * 1024;
|
|
515
621
|
/** Keys one API key may hold; a `storage.set` that would exceed it answers `invalid-state`. */
|
|
516
622
|
export const STORAGE_KEYS_MAX = 256;
|
|
623
|
+
/** `session.identify` refuses a longer name, so a client composing one trims it to this. */
|
|
624
|
+
export const SESSION_NAME_MAX_LENGTH = 64;
|
|
517
625
|
/** `speech.play` URL ceiling — sized for a ~40 s WAV as a base64 `data:audio/*` payload. */
|
|
518
626
|
export const SPEECH_URL_MAX_LENGTH = 8_000_000;
|
|
519
627
|
/**
|
package/dist/values/lipsync.d.ts
CHANGED
|
@@ -54,19 +54,18 @@ export interface LipSyncSample {
|
|
|
54
54
|
/** Smoothed vowel weights sum to at most one; the remainder represents non-vowel audio. */
|
|
55
55
|
vowels: Record<LipSyncVowel, number>;
|
|
56
56
|
}
|
|
57
|
+
/** A capture or output device as a device picker lists it. */
|
|
58
|
+
export interface MediaDeviceOption {
|
|
59
|
+
deviceId: string;
|
|
60
|
+
label: string;
|
|
61
|
+
}
|
|
57
62
|
export interface LipSyncState {
|
|
58
63
|
config: LipSyncConfig;
|
|
59
64
|
status: 'off' | 'starting' | 'listening' | 'error';
|
|
60
65
|
error: 'permission' | 'device' | 'analysis' | null;
|
|
61
|
-
devices:
|
|
62
|
-
deviceId: string;
|
|
63
|
-
label: string;
|
|
64
|
-
}[];
|
|
66
|
+
devices: MediaDeviceOption[];
|
|
65
67
|
sample: LipSyncSample | null;
|
|
66
|
-
microphone:
|
|
67
|
-
deviceId: string;
|
|
68
|
-
label: string;
|
|
69
|
-
} | null;
|
|
68
|
+
microphone: MediaDeviceOption | null;
|
|
70
69
|
profile: Omit<LipSyncProfile, 'samples'> | null;
|
|
71
70
|
calibration: LipSyncCalibrationState | null;
|
|
72
71
|
}
|
package/dist/values/locale.d.ts
CHANGED
|
@@ -2,7 +2,8 @@ export type Locale = keyof typeof LOCALE_LABELS;
|
|
|
2
2
|
/** `ui.language` setting: an explicit locale, or follow the OS language. */
|
|
3
3
|
export type LanguageSetting = 'system' | Locale;
|
|
4
4
|
/** `ui.theme` setting: an explicit appearance, or follow the OS. */
|
|
5
|
-
export
|
|
5
|
+
export declare const THEME_SETTINGS: readonly ["system", "light", "dark"];
|
|
6
|
+
export type ThemeSetting = (typeof THEME_SETTINGS)[number];
|
|
6
7
|
/** Native-language display names for the language picker (deliberately untranslated). */
|
|
7
8
|
export declare const LOCALE_LABELS: {
|
|
8
9
|
en: string;
|
|
@@ -12,5 +13,7 @@ export declare const LOCALE_LABELS: {
|
|
|
12
13
|
};
|
|
13
14
|
/** Locales shipped in the compiled catalogs (`pnpm i18n`). */
|
|
14
15
|
export declare const LOCALES: readonly Locale[];
|
|
16
|
+
/** Every `ui.language` value, `system` first. */
|
|
17
|
+
export declare const LANGUAGE_SETTINGS: readonly LanguageSetting[];
|
|
15
18
|
/** Best supported locale for a BCP 47-ish tag: exact match first, then fuzzy per language. */
|
|
16
19
|
export declare function matchLocaleTag(tag: string): Locale;
|
package/dist/values/locale.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { isOneOf, keysOf } from "./guards.js";
|
|
2
|
+
/** `ui.theme` setting: an explicit appearance, or follow the OS. */
|
|
3
|
+
export const THEME_SETTINGS = ['system', 'light', 'dark'];
|
|
2
4
|
/** Native-language display names for the language picker (deliberately untranslated). */
|
|
3
5
|
export const LOCALE_LABELS = {
|
|
4
6
|
en: 'English',
|
|
@@ -8,6 +10,8 @@ export const LOCALE_LABELS = {
|
|
|
8
10
|
};
|
|
9
11
|
/** Locales shipped in the compiled catalogs (`pnpm i18n`). */
|
|
10
12
|
export const LOCALES = keysOf(LOCALE_LABELS);
|
|
13
|
+
/** Every `ui.language` value, `system` first. */
|
|
14
|
+
export const LANGUAGE_SETTINGS = ['system', ...LOCALES];
|
|
11
15
|
/** Best supported locale for a BCP 47-ish tag: exact match first, then fuzzy per language. */
|
|
12
16
|
export function matchLocaleTag(tag) {
|
|
13
17
|
if (isOneOf(tag, LOCALES))
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
import type { AttachAnchor } from '../wire/types.ts';
|
|
2
|
+
/**
|
|
3
|
+
* The point `z` grows and shrinks the model around: its centre (`root`), or a point on one of its
|
|
4
|
+
* ArtMeshes, taken where that mesh sits in the model's rest pose so head motion never moves it.
|
|
5
|
+
*/
|
|
6
|
+
export type ModelMovementPivot = Extract<AttachAnchor, {
|
|
7
|
+
kind: 'root' | 'artMesh';
|
|
8
|
+
}>;
|
|
1
9
|
/**
|
|
2
10
|
* VTube Studio's Movement Config: how far tracked face position slides (`x`, `y`) and scales (`z`)
|
|
3
11
|
* a whole Live2D model. It moves the model itself and never writes a parameter, so it stacks with
|
|
@@ -9,21 +17,28 @@ export interface ModelMovementConfig {
|
|
|
9
17
|
x: number;
|
|
10
18
|
y: number;
|
|
11
19
|
z: number;
|
|
20
|
+
/** Persona-only: VTS has no such setting, so it lives in the sidecar even beside a `.vtube.json`. */
|
|
21
|
+
pivot: ModelMovementPivot;
|
|
12
22
|
/** Smoothing levels 0–100, read once when the model loads; file-only, as in VTS. */
|
|
13
23
|
smoothingX: number;
|
|
14
24
|
smoothingY: number;
|
|
15
25
|
smoothingZ: number;
|
|
16
26
|
}
|
|
17
|
-
/**
|
|
18
|
-
export
|
|
27
|
+
/** The per-axis smoothing levels, read once when the model loads and never edited. */
|
|
28
|
+
export type ModelMovementSmoothing = Pick<ModelMovementConfig, 'smoothingX' | 'smoothingY' | 'smoothingZ'>;
|
|
29
|
+
/**
|
|
30
|
+
* The editor's limit: an edited amount is a whole number within ±50. VTS's own sliders stop at ±20
|
|
31
|
+
* (`MAX_MOVEMENT`), and releasing one there writes all three axes back clamped to that.
|
|
32
|
+
*/
|
|
33
|
+
export declare const MODEL_MOVEMENT_MAX = 50;
|
|
19
34
|
/** VTS's `ModelPositionMovement` field initializers — a model nobody configured moves by default. */
|
|
20
35
|
export declare const DEFAULT_MODEL_MOVEMENT: Readonly<ModelMovementConfig>;
|
|
21
36
|
/**
|
|
22
37
|
* A stored or relayed configuration, read per field over the defaults. Amounts are taken as
|
|
23
|
-
* written:
|
|
38
|
+
* written: the editors clamp what they write, never what a file holds.
|
|
24
39
|
*/
|
|
25
40
|
export declare function healModelMovement(raw: unknown): ModelMovementConfig;
|
|
26
41
|
/** 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'>>;
|
|
42
|
+
export type ModelMovementEdit = Partial<Pick<ModelMovementConfig, 'enabled' | 'x' | 'y' | 'z' | 'pivot'>>;
|
|
28
43
|
/** An edit healed to the editor's limits; an absent or unusable field is dropped, not defaulted. */
|
|
29
44
|
export declare function healModelMovementEdit(raw: unknown): ModelMovementEdit;
|
|
@@ -1,20 +1,29 @@
|
|
|
1
1
|
import { finiteOr, isFiniteNumber, isRecord } from "./guards.js";
|
|
2
|
-
import { clamp } from "./limits.js";
|
|
3
|
-
/**
|
|
4
|
-
|
|
2
|
+
import { clamp, healAttachAnchor } from "./limits.js";
|
|
3
|
+
/**
|
|
4
|
+
* The editor's limit: an edited amount is a whole number within ±50. VTS's own sliders stop at ±20
|
|
5
|
+
* (`MAX_MOVEMENT`), and releasing one there writes all three axes back clamped to that.
|
|
6
|
+
*/
|
|
7
|
+
export const MODEL_MOVEMENT_MAX = 50;
|
|
5
8
|
/** VTS's `ModelPositionMovement` field initializers — a model nobody configured moves by default. */
|
|
6
9
|
export const DEFAULT_MODEL_MOVEMENT = {
|
|
7
10
|
enabled: true,
|
|
8
11
|
x: 6,
|
|
9
12
|
y: 8,
|
|
10
13
|
z: 11,
|
|
14
|
+
pivot: { kind: 'root' },
|
|
11
15
|
smoothingX: 10,
|
|
12
16
|
smoothingY: 10,
|
|
13
17
|
smoothingZ: 10,
|
|
14
18
|
};
|
|
19
|
+
/** A stored or relayed pivot as a usable one: any anchor but a bone, which no Live2D model has. */
|
|
20
|
+
function healPivot(raw) {
|
|
21
|
+
const anchor = healAttachAnchor(raw);
|
|
22
|
+
return anchor?.kind === 'bone' ? null : anchor;
|
|
23
|
+
}
|
|
15
24
|
/**
|
|
16
25
|
* A stored or relayed configuration, read per field over the defaults. Amounts are taken as
|
|
17
|
-
* written:
|
|
26
|
+
* written: the editors clamp what they write, never what a file holds.
|
|
18
27
|
*/
|
|
19
28
|
export function healModelMovement(raw) {
|
|
20
29
|
const r = isRecord(raw) ? raw : {};
|
|
@@ -24,6 +33,7 @@ export function healModelMovement(raw) {
|
|
|
24
33
|
x: finiteOr(r.x, d.x),
|
|
25
34
|
y: finiteOr(r.y, d.y),
|
|
26
35
|
z: finiteOr(r.z, d.z),
|
|
36
|
+
pivot: healPivot(r.pivot) ?? d.pivot,
|
|
27
37
|
smoothingX: finiteOr(r.smoothingX, d.smoothingX),
|
|
28
38
|
smoothingY: finiteOr(r.smoothingY, d.smoothingY),
|
|
29
39
|
smoothingZ: finiteOr(r.smoothingZ, d.smoothingZ),
|
|
@@ -40,5 +50,8 @@ export function healModelMovementEdit(raw) {
|
|
|
40
50
|
if (isFiniteNumber(v))
|
|
41
51
|
edit[axis] = clamp(Math.round(v), -MODEL_MOVEMENT_MAX, MODEL_MOVEMENT_MAX);
|
|
42
52
|
}
|
|
53
|
+
const pivot = healPivot(r.pivot);
|
|
54
|
+
if (pivot)
|
|
55
|
+
edit.pivot = pivot;
|
|
43
56
|
return edit;
|
|
44
57
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { SceneEnvironment, SceneVolumetricLighting } from '../wire/types.ts';
|
|
2
2
|
/** Slider and healing metadata for the haze's numeric settings. */
|
|
3
3
|
export declare const SCENE_VOLUMETRIC_LIGHTING_SPECS: {
|
|
4
4
|
readonly density: {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { EFFECTS_QUALITY_LEVELS } from "
|
|
1
|
+
import { EFFECTS_QUALITY_LEVELS } from "./effect-schema.js";
|
|
2
2
|
import { finiteOr, isOneOf, isRecord } from "./guards.js";
|
|
3
3
|
import { clamp } from "./limits.js";
|
|
4
4
|
/** Slider and healing metadata for the haze's numeric settings. */
|
package/dist/wire/envelope.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { isRecord } from "../values/guards.js";
|
|
2
2
|
import { isApiErrorCode } from "./errors.js";
|
|
3
3
|
import { isEventName } from "./events.js";
|
|
4
|
-
import { isAppCapability } from "./types.js";
|
|
4
|
+
import { canonicalAppCapability, isAppCapability } from "./types.js";
|
|
5
5
|
/**
|
|
6
6
|
* Parse one inbound client frame. Returns the request, or an error code telling
|
|
7
7
|
* the server what to answer: `parse-error` for junk bytes, `invalid-request`
|
|
@@ -47,8 +47,11 @@ export function parseServerMessage(raw) {
|
|
|
47
47
|
const { name, version, platform } = v.app;
|
|
48
48
|
if (typeof name !== 'string' || typeof version !== 'string' || typeof platform !== 'string')
|
|
49
49
|
return null;
|
|
50
|
-
// Tolerant both ways: absent on older servers, unknown names from newer ones dropped.
|
|
51
|
-
|
|
50
|
+
// Tolerant both ways: absent on older servers, unknown names from newer ones dropped. Old names fold
|
|
51
|
+
// into their replacements, which a newer server also lists.
|
|
52
|
+
const names = Array.isArray(v.app.capabilities) ? v.app.capabilities : [];
|
|
53
|
+
const folded = names.map(n => (typeof n === 'string' ? canonicalAppCapability(n) : n));
|
|
54
|
+
const capabilities = [...new Set(folded.filter(isAppCapability))];
|
|
52
55
|
return { kind: 'hello', protocol: v.protocol, app: { name, version, platform, capabilities } };
|
|
53
56
|
}
|
|
54
57
|
case 'response':
|