@starcried/starlite-vrm 0.1.0-alpha.2 → 0.1.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/config.cjs CHANGED
@@ -19,11 +19,35 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
19
19
  // src/config.ts
20
20
  var config_exports = {};
21
21
  __export(config_exports, {
22
+ BUILTIN_ANIMATIONS: () => BUILTIN_ANIMATIONS,
23
+ isBuiltinAnimation: () => isBuiltinAnimation,
22
24
  validateCharacterProfile: () => validateCharacterProfile,
25
+ validateEnvironmentLighting: () => validateEnvironmentLighting,
26
+ validateModelAdjustments: () => validateModelAdjustments,
23
27
  validateVrmWidgetConfig: () => validateVrmWidgetConfig
24
28
  });
25
29
  module.exports = __toCommonJS(config_exports);
26
30
 
31
+ // ../core/dist/locale.js
32
+ function validateLocale(value) {
33
+ if (value !== void 0 && value !== "zh-CN" && value !== "en") {
34
+ throw new Error("locale must be zh-CN or en");
35
+ }
36
+ }
37
+
38
+ // ../core/dist/environment-lighting.js
39
+ function validateEnvironmentLighting(input) {
40
+ if (!input || typeof input !== "object" || Array.isArray(input))
41
+ throw new Error("environmentLighting must be an object");
42
+ const value = input;
43
+ if (Object.keys(value).some((key) => key !== "color" && key !== "strength" && key !== "brightness") || typeof value.color !== "string" || !/^#[0-9a-f]{6}$/iu.test(value.color))
44
+ throw new Error("environmentLighting.color must be #RRGGBB");
45
+ if (value.strength !== void 0 && (typeof value.strength !== "number" || !Number.isFinite(value.strength) || value.strength < 0 || value.strength > 2))
46
+ throw new Error("environmentLighting.strength must be between 0 and 2");
47
+ if (value.brightness !== void 0 && (typeof value.brightness !== "number" || !Number.isFinite(value.brightness) || value.brightness < 0.8 || value.brightness > 1.1))
48
+ throw new Error("environmentLighting.brightness must be between 0.8 and 1.1");
49
+ }
50
+
27
51
  // ../core/dist/dialogue.js
28
52
  function resolveDialogueSentences(input, animations, field) {
29
53
  const resolve = (value, path) => {
@@ -61,12 +85,53 @@ function resolveDialogueSentences(input, animations, field) {
61
85
  return { ...lines ? { lines } : {}, linesByAnimation };
62
86
  }
63
87
 
88
+ // ../core/dist/performance.js
89
+ function validatePerformanceConfig(value) {
90
+ if (value === void 0)
91
+ return;
92
+ if (!value || typeof value !== "object" || Array.isArray(value) || Object.keys(value).some((key) => key !== "show") || "show" in value && value.show !== void 0 && typeof value.show !== "boolean") {
93
+ throw new Error("performance must be an object with an optional boolean show");
94
+ }
95
+ }
96
+
97
+ // src/builtin-motion-catalog.ts
98
+ var BUILTIN_ANIMATIONS = [
99
+ { name: "StarLiteIdle", durationMs: 6e3 },
100
+ { name: "StarLiteCuriousPeek", durationMs: 2100 },
101
+ { name: "StarLiteHappyWave", durationMs: 4600 },
102
+ { name: "StarLiteSleepyStartle", durationMs: 5500 }
103
+ ];
104
+ function isBuiltinAnimation(name) {
105
+ return BUILTIN_ANIMATIONS.some((animation) => animation.name === name);
106
+ }
107
+
64
108
  // src/config.ts
65
109
  function finite(value, min, max) {
66
110
  return typeof value === "number" && Number.isFinite(value) && value >= min && value <= max;
67
111
  }
112
+ function validateModelAdjustments(input) {
113
+ const object = (value) => Boolean(value && typeof value === "object" && !Array.isArray(value));
114
+ if (!object(input) || Object.keys(input).some((key) => key !== "idle" && key !== "gaze")) throw new Error("invalid model adjustments");
115
+ const groups = {
116
+ idle: { armLiftDegrees: [-30, 45], palmTwistDegrees: [-45, 45], wristLiftDegrees: [-45, 45], handCurl: [0, 1], elbowBendDegrees: [-20, 45] },
117
+ gaze: { headStrength: [0, 2], eyeStrength: [0, 2], smoothingMs: [20, 1e3] }
118
+ };
119
+ for (const [group, limits] of Object.entries(groups)) {
120
+ const values = input[group];
121
+ if (values === void 0) continue;
122
+ if (!object(values)) throw new Error(`invalid model adjustments.${group}`);
123
+ for (const [key, value] of Object.entries(values)) {
124
+ const range = Object.hasOwn(limits, key) ? limits[key] : void 0;
125
+ if (!range || value !== void 0 && !finite(value, range[0], range[1])) throw new Error(`invalid model adjustments.${group}.${key}`);
126
+ }
127
+ }
128
+ }
68
129
  function validateVrmWidgetConfig(config) {
69
130
  if (!config || config.backend !== void 0 && config.backend !== "vrm") throw new Error("invalid VRM backend");
131
+ validateLocale(config.locale);
132
+ if (config.adjustments !== void 0) validateModelAdjustments(config.adjustments);
133
+ validatePerformanceConfig(config.performance);
134
+ if (config.environmentLighting !== void 0) validateEnvironmentLighting(config.environmentLighting);
70
135
  if (config.interactions?.passThrough !== void 0 && typeof config.interactions.passThrough !== "boolean") throw new Error("interactions.passThrough must be boolean");
71
136
  if (config.interactions?.passThrough && config.interactions.drag) throw new Error("passThrough and drag cannot both be enabled");
72
137
  for (const key of ["model", "fallback"]) if (typeof config[key] !== "string" || !config[key].trim()) throw new Error(`${key} is required`);
@@ -102,11 +167,11 @@ function validateVrmWidgetConfig(config) {
102
167
  names.add(name);
103
168
  }
104
169
  for (const animation of animations) {
105
- if (!animation || typeof animation.name !== "string" || !animation.name.trim() || animation.name.length > 128 || names.has(animation.name) || typeof animation.url !== "string" || !animation.url.trim()) throw new Error("invalid or duplicate animation");
170
+ if (!animation || typeof animation.name !== "string" || !animation.name.trim() || animation.name.length > 128 || names.has(animation.name) || animation.name !== "StarLiteIdle" && isBuiltinAnimation(animation.name) || typeof animation.url !== "string" || !animation.url.trim()) throw new Error("invalid or duplicate animation");
106
171
  if (animation.durationMs !== void 0 && !finite(animation.durationMs, 1, 6e5)) throw new Error("invalid animation duration");
107
172
  names.add(animation.name);
108
173
  }
109
- names.add("StarLiteIdle");
174
+ for (const { name } of BUILTIN_ANIMATIONS) names.add(name);
110
175
  for (const name of config.ambient?.animations ?? []) if (!names.has(name)) throw new Error(`unknown ambient animation: ${name}`);
111
176
  if ((config.ambient?.animations?.length ?? 0) > 1) throw new Error("VRM supports one explicitly configured ambient clip");
112
177
  if (config.neutralAnimation !== void 0 && !names.has(config.neutralAnimation)) throw new Error("unknown neutral animation");
@@ -147,6 +212,10 @@ function validateCharacterProfile(input) {
147
212
  }
148
213
  // Annotate the CommonJS export names for ESM import in node:
149
214
  0 && (module.exports = {
215
+ BUILTIN_ANIMATIONS,
216
+ isBuiltinAnimation,
150
217
  validateCharacterProfile,
218
+ validateEnvironmentLighting,
219
+ validateModelAdjustments,
151
220
  validateVrmWidgetConfig
152
221
  });
package/dist/config.d.ts CHANGED
@@ -1,3 +1,6 @@
1
- import type { CharacterProfile, VrmWidgetConfig } from "./types.js";
1
+ export { validateEnvironmentLighting } from "@starcried/starlite-core";
2
+ export { BUILTIN_ANIMATIONS, isBuiltinAnimation } from "./builtin-motion-catalog.js";
3
+ import type { CharacterProfile, ModelAdjustments, VrmWidgetConfig } from "./types.js";
4
+ export declare function validateModelAdjustments(input: unknown): asserts input is ModelAdjustments;
2
5
  export declare function validateVrmWidgetConfig(config: VrmWidgetConfig): void;
3
6
  export declare function validateCharacterProfile(input: unknown): asserts input is CharacterProfile;
package/dist/config.js CHANGED
@@ -1,10 +1,40 @@
1
- import { resolveDialogueSentences } from "@starcried/starlite-core";
1
+ export { validateEnvironmentLighting } from "@starcried/starlite-core";
2
+ import { validateEnvironmentLighting, resolveDialogueSentences, validatePerformanceConfig, validateLocale } from "@starcried/starlite-core";
3
+ import { BUILTIN_ANIMATIONS, isBuiltinAnimation } from "./builtin-motion-catalog.js";
4
+ export { BUILTIN_ANIMATIONS, isBuiltinAnimation } from "./builtin-motion-catalog.js";
2
5
  function finite(value, min, max) {
3
6
  return typeof value === "number" && Number.isFinite(value) && value >= min && value <= max;
4
7
  }
8
+ export function validateModelAdjustments(input) {
9
+ const object = (value) => Boolean(value && typeof value === "object" && !Array.isArray(value));
10
+ if (!object(input) || Object.keys(input).some(key => key !== "idle" && key !== "gaze"))
11
+ throw new Error("invalid model adjustments");
12
+ const groups = {
13
+ idle: { armLiftDegrees: [-30, 45], palmTwistDegrees: [-45, 45], wristLiftDegrees: [-45, 45], handCurl: [0, 1], elbowBendDegrees: [-20, 45] },
14
+ gaze: { headStrength: [0, 2], eyeStrength: [0, 2], smoothingMs: [20, 1000] },
15
+ };
16
+ for (const [group, limits] of Object.entries(groups)) {
17
+ const values = input[group];
18
+ if (values === undefined)
19
+ continue;
20
+ if (!object(values))
21
+ throw new Error(`invalid model adjustments.${group}`);
22
+ for (const [key, value] of Object.entries(values)) {
23
+ const range = Object.hasOwn(limits, key) ? limits[key] : undefined;
24
+ if (!range || (value !== undefined && !finite(value, range[0], range[1])))
25
+ throw new Error(`invalid model adjustments.${group}.${key}`);
26
+ }
27
+ }
28
+ }
5
29
  export function validateVrmWidgetConfig(config) {
6
30
  if (!config || (config.backend !== undefined && config.backend !== "vrm"))
7
31
  throw new Error("invalid VRM backend");
32
+ validateLocale(config.locale);
33
+ if (config.adjustments !== undefined)
34
+ validateModelAdjustments(config.adjustments);
35
+ validatePerformanceConfig(config.performance);
36
+ if (config.environmentLighting !== undefined)
37
+ validateEnvironmentLighting(config.environmentLighting);
8
38
  if (config.interactions?.passThrough !== undefined && typeof config.interactions.passThrough !== "boolean")
9
39
  throw new Error("interactions.passThrough must be boolean");
10
40
  if (config.interactions?.passThrough && config.interactions.drag)
@@ -46,13 +76,14 @@ export function validateVrmWidgetConfig(config) {
46
76
  names.add(name);
47
77
  }
48
78
  for (const animation of animations) {
49
- if (!animation || typeof animation.name !== "string" || !animation.name.trim() || animation.name.length > 128 || names.has(animation.name) || typeof animation.url !== "string" || !animation.url.trim())
79
+ if (!animation || typeof animation.name !== "string" || !animation.name.trim() || animation.name.length > 128 || names.has(animation.name) || (animation.name !== "StarLiteIdle" && isBuiltinAnimation(animation.name)) || typeof animation.url !== "string" || !animation.url.trim())
50
80
  throw new Error("invalid or duplicate animation");
51
81
  if (animation.durationMs !== undefined && !finite(animation.durationMs, 1, 600000))
52
82
  throw new Error("invalid animation duration");
53
83
  names.add(animation.name);
54
84
  }
55
- names.add("StarLiteIdle");
85
+ for (const { name } of BUILTIN_ANIMATIONS)
86
+ names.add(name);
56
87
  for (const name of config.ambient?.animations ?? [])
57
88
  if (!names.has(name))
58
89
  throw new Error(`unknown ambient animation: ${name}`);
@@ -0,0 +1,12 @@
1
+ import { DirectionalLight, HemisphereLight } from "three";
2
+ import { type EnvironmentLighting } from "@starcried/starlite-core";
3
+ /** Reuses both existing lights; no capture, material changes or extra render work. */
4
+ export declare class EnvironmentLight {
5
+ #private;
6
+ readonly light: HemisphereLight;
7
+ readonly key: DirectionalLight;
8
+ constructor();
9
+ set(value: EnvironmentLighting | null, immediate?: boolean): void;
10
+ snap(): void;
11
+ update(delta: number): void;
12
+ }
@@ -0,0 +1,82 @@
1
+ import { Color, DirectionalLight, HemisphereLight } from "three";
2
+ import { validateEnvironmentLighting } from "@starcried/starlite-core";
3
+ const luminance = (color) => .2126 * color.r + .7152 * color.g + .0722 * color.b;
4
+ const close = (a, b) => Math.abs(a.r - b.r) < 1e-6 && Math.abs(a.g - b.g) < 1e-6 && Math.abs(a.b - b.b) < 1e-6;
5
+ const encodeSrgb = (linear) => linear <= .0031308 ? 12.92 * linear : 1.055 * linear ** (1 / 2.4) - .055;
6
+ /** Reuses both existing lights; no capture, material changes or extra render work. */
7
+ export class EnvironmentLight {
8
+ light = new HemisphereLight(0xffffff, 0xb6c6d5, .65);
9
+ key = new DirectionalLight(0xffffff, 2);
10
+ #baseSky = this.light.color.clone();
11
+ #baseGround = this.light.groundColor.clone();
12
+ #sky = this.#baseSky.clone();
13
+ #ground = this.#baseGround.clone();
14
+ #settled = true;
15
+ #ambientIntensity = .65;
16
+ #keyIntensity = 2;
17
+ constructor() { this.key.position.set(-3, 4, 2); }
18
+ set(value, immediate = false) {
19
+ if (value !== null)
20
+ validateEnvironmentLighting(value);
21
+ this.#sky.copy(this.#baseSky);
22
+ this.#ground.copy(this.#baseGround);
23
+ this.#ambientIntensity = .65;
24
+ this.#keyIntensity = 2;
25
+ const strength = value?.strength ?? 1;
26
+ if (value && strength > 0) {
27
+ // Hex is sRGB; Three converts it to linear working space before arithmetic.
28
+ const color = new Color(value.color), maximum = Math.max(color.r, color.g, color.b);
29
+ // Preserve the accepted 0–100% curve exactly; higher values amplify
30
+ // linearly rather than extrapolating the easing polynomial.
31
+ const response = strength <= 1 ? 1 - (1 - strength) ** 3 : strength;
32
+ const perceived = Math.min(1, Math.max(0, encodeSrgb(luminance(color))));
33
+ const automatic = perceived <= .5 ? .8 + .4 * perceived : 1 + .2 * (perceived - .5);
34
+ const brightness = 1 + ((value.brightness ?? automatic) - 1) * response;
35
+ let transfer = 0;
36
+ if (maximum > 0) {
37
+ // Near-black compression/noise must not become a fully saturated colour.
38
+ const confidence = Math.min(1, maximum / .02);
39
+ const saturation = (maximum - Math.min(color.r, color.g, color.b)) / maximum;
40
+ if (saturation > 0) {
41
+ const hue = color.clone().multiplyScalar(1 / maximum);
42
+ // Unit-luminance chroma avoids double darkening. The accepted
43
+ // peak-2 cap stays exact through 100%, rising to 3 at 200%.
44
+ const peak = strength <= 1 ? 2 : 1 + strength;
45
+ const mix = Math.min(.65 * response * confidence, (1 - 1 / peak) / (1 - luminance(hue)), .9);
46
+ const filter = new Color(0xffffff).lerp(hue, mix);
47
+ filter.multiplyScalar(1 / luminance(filter));
48
+ this.#sky.multiply(filter);
49
+ this.#ground.multiply(filter).multiplyScalar(luminance(this.#baseGround) / luminance(this.#baseGround.clone().multiply(filter)));
50
+ transfer = .8 * response * confidence * saturation;
51
+ }
52
+ }
53
+ // Rebalance a bounded amount of key energy into the existing fill. Its
54
+ // chromatic contribution is visible while the key colour stays white.
55
+ this.#ambientIntensity = (.65 + transfer) * brightness;
56
+ this.#keyIntensity = (2 - transfer) * brightness;
57
+ }
58
+ this.#settled = false;
59
+ if (immediate)
60
+ this.snap();
61
+ }
62
+ snap() {
63
+ this.light.color.copy(this.#sky);
64
+ this.light.groundColor.copy(this.#ground);
65
+ this.light.intensity = this.#ambientIntensity;
66
+ this.key.intensity = this.#keyIntensity;
67
+ this.#settled = true;
68
+ }
69
+ update(delta) {
70
+ if (this.#settled || !Number.isFinite(delta) || delta <= 0)
71
+ return;
72
+ // ~0.5 seconds to settle. No hidden elapsed time or extra animation frame.
73
+ const alpha = 1 - Math.exp(-Math.min(delta, .1) / .18);
74
+ this.light.color.lerp(this.#sky, alpha);
75
+ this.light.groundColor.lerp(this.#ground, alpha);
76
+ this.light.intensity += (this.#ambientIntensity - this.light.intensity) * alpha;
77
+ this.key.intensity += (this.#keyIntensity - this.key.intensity) * alpha;
78
+ if (close(this.light.color, this.#sky) && close(this.light.groundColor, this.#ground)
79
+ && Math.abs(this.light.intensity - this.#ambientIntensity) < 1e-6 && Math.abs(this.key.intensity - this.#keyIntensity) < 1e-6)
80
+ this.snap();
81
+ }
82
+ }
package/dist/gaze.d.ts CHANGED
@@ -1,11 +1,12 @@
1
1
  import { type Object3D, type PerspectiveCamera } from "three";
2
2
  import type { VRMLookAt } from "@pixiv/three-vrm";
3
- import type { VrmWidgetConfig } from "./types.js";
3
+ import type { ModelAdjustments, VrmWidgetConfig } from "./types.js";
4
4
  /** Pointer projection and angle limits are independent of avatar scale and VRM version. */
5
5
  export declare class PointerGaze {
6
6
  #private;
7
7
  readonly limits: NonNullable<VrmWidgetConfig["gaze"]>;
8
8
  constructor(limits?: NonNullable<VrmWidgetConfig["gaze"]>);
9
+ setAdjustments(adjustments?: ModelAdjustments["gaze"]): void;
9
10
  move(x: number, y: number, rect: {
10
11
  left: number;
11
12
  top: number;
package/dist/gaze.js CHANGED
@@ -18,9 +18,11 @@ export class PointerGaze {
18
18
  #tracking = false;
19
19
  #yaw = 0;
20
20
  #pitch = 0;
21
+ #adjustments = {};
21
22
  constructor(limits = {}) {
22
23
  this.limits = limits;
23
24
  }
25
+ setAdjustments(adjustments = {}) { this.#adjustments = { ...adjustments }; }
24
26
  move(x, y, rect, viewport = { width: rect.left + rect.width, height: rect.top + rect.height }) {
25
27
  if (![x, y, rect.left, rect.top, rect.width, rect.height, viewport.width, viewport.height].every(Number.isFinite) || rect.width <= 0 || rect.height <= 0 || viewport.width <= 0 || viewport.height <= 0)
26
28
  return;
@@ -51,16 +53,17 @@ export class PointerGaze {
51
53
  /** Normalized humanoid correction precedes the single humanoid update. */
52
54
  updateHead(head, camera, delta, authored) {
53
55
  this.restoreHead();
54
- if (!head || authored) {
56
+ if (!head || authored || this.#adjustments.headStrength === 0) {
55
57
  this.#headYaw = 0;
56
58
  this.#headPitch = 0;
57
59
  return;
58
60
  }
59
61
  head.getWorldPosition(this.#head);
60
62
  const offset = this.#offset(camera, this.#head);
61
- const blend = 1 - Math.exp(-Math.max(0, delta) / ((this.limits.smoothingMs ?? 100) / 1000));
62
- this.#headYaw += (offset.x * (this.limits.maxYawDegrees ?? 25) * .6 - this.#headYaw) * blend;
63
- this.#headPitch += (offset.y * (this.limits.maxPitchDegrees ?? 15) * .6 - this.#headPitch) * blend;
63
+ const blend = 1 - Math.exp(-Math.max(0, delta) / ((this.#adjustments.smoothingMs ?? this.limits.smoothingMs ?? 100) / 1000));
64
+ const yawLimit = this.limits.maxYawDegrees ?? 25, pitchLimit = this.limits.maxPitchDegrees ?? 15, strength = this.#adjustments.headStrength ?? 1;
65
+ this.#headYaw += (MathUtils.clamp(offset.x * yawLimit * .6 * strength, -yawLimit, yawLimit) - this.#headYaw) * blend;
66
+ this.#headPitch += (MathUtils.clamp(offset.y * pitchLimit * .6 * strength, -pitchLimit, pitchLimit) - this.#headPitch) * blend;
64
67
  this.#headBase.copy(head.quaternion);
65
68
  this.#posedHead = head;
66
69
  this.#rotation.setFromAxisAngle(this.#scratch.setFromMatrixColumn(camera.matrixWorld, 1), MathUtils.degToRad(this.#headYaw));
@@ -80,12 +83,20 @@ export class PointerGaze {
80
83
  this.#pitch = lookAt.pitch;
81
84
  return;
82
85
  }
86
+ if (this.#adjustments.eyeStrength === 0) {
87
+ this.#yaw = 0;
88
+ this.#pitch = 0;
89
+ lookAt.yaw = 0;
90
+ lookAt.pitch = 0;
91
+ return;
92
+ }
83
93
  lookAt.humanoid.getRawBoneNode("head")?.updateWorldMatrix(true, false);
84
94
  lookAt.getLookAtWorldPosition(this.#head);
85
95
  const yawLimit = this.limits.maxYawDegrees ?? 25, pitchLimit = this.limits.maxPitchDegrees ?? 15;
86
96
  this.#target.copy(camera.position);
87
97
  if (this.#tracking) {
88
- const { x, y } = this.#offset(camera, this.#head);
98
+ const offset = this.#offset(camera, this.#head), strength = this.#adjustments.eyeStrength ?? 1;
99
+ const x = MathUtils.clamp(offset.x * strength, -1, 1), y = MathUtils.clamp(offset.y * strength, -1, 1);
89
100
  const distance = camera.position.distanceTo(this.#head);
90
101
  this.#target.addScaledVector(this.#scratch.setFromMatrixColumn(camera.matrixWorld, 0), distance * Math.tan(MathUtils.degToRad(x * yawLimit)));
91
102
  this.#target.addScaledVector(this.#scratch.setFromMatrixColumn(camera.matrixWorld, 1), distance / Math.cos(MathUtils.degToRad(x * yawLimit)) * Math.tan(MathUtils.degToRad(y * pitchLimit)));
@@ -93,7 +104,7 @@ export class PointerGaze {
93
104
  lookAt.lookAt(this.#target);
94
105
  const yaw = Math.max(-yawLimit, Math.min(yawLimit, lookAt.yaw));
95
106
  const pitch = Math.max(-pitchLimit, Math.min(pitchLimit, lookAt.pitch));
96
- const blend = 1 - Math.exp(-Math.max(0, delta) / ((this.limits.smoothingMs ?? 100) / 1000));
107
+ const blend = 1 - Math.exp(-Math.max(0, delta) / ((this.#adjustments.smoothingMs ?? this.limits.smoothingMs ?? 100) / 1000));
97
108
  this.#yaw += (yaw - this.#yaw) * blend;
98
109
  this.#pitch += (pitch - this.#pitch) * blend;
99
110
  lookAt.yaw = Math.max(-yawLimit, Math.min(yawLimit, this.#yaw));
package/dist/index.d.ts CHANGED
@@ -1,6 +1,9 @@
1
+ export { resolveLocale, type StarLiteLocale } from "@starcried/starlite-core";
1
2
  import { type MountOptions } from "@starcried/starlite-core";
3
+ export { BUILTIN_ANIMATIONS } from "./builtin-motion-catalog.js";
2
4
  import type { VrmController, VrmWidgetConfig } from "./types.js";
3
5
  export type * from "./types.js";
4
- export { validateVrmWidgetConfig } from "./config.js";
6
+ export { QuickSettingsMenu, type QuickSettingsOptions, type QuickSettingItem } from "@starcried/starlite-core";
7
+ export { validateModelAdjustments, validateVrmWidgetConfig } from "./config.js";
5
8
  export declare function mountModelWidget(host: HTMLElement, config: VrmWidgetConfig, options?: MountOptions): Promise<VrmController>;
6
9
  export declare const mountVrmWidget: typeof mountModelWidget;