@lovelace_lol/embody 0.0.4 → 0.0.6

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.cjs CHANGED
@@ -3696,410 +3696,6 @@ function getBlendingName(value) {
3696
3696
  return "Normal";
3697
3697
  }
3698
3698
 
3699
- // src/core/TsRuntimeCore.ts
3700
- var identityQuat = { x: 0, y: 0, z: 0, w: 1 };
3701
- function isRotationBoneBinding2(binding) {
3702
- return binding?.channel === "rx" || binding?.channel === "ry" || binding?.channel === "rz";
3703
- }
3704
- var TsRuntimeCore = class {
3705
- constructor(options) {
3706
- __publicField(this, "profile");
3707
- __publicField(this, "model");
3708
- __publicField(this, "auValues", {});
3709
- __publicField(this, "auBalances", {});
3710
- __publicField(this, "visemeValues");
3711
- __publicField(this, "visemeJawScales");
3712
- __publicField(this, "mixWeights");
3713
- this.profile = options.profile;
3714
- this.model = options.model;
3715
- this.visemeValues = new Array(getProfileVisemeSlots(this.profile).length).fill(0);
3716
- this.visemeJawScales = new Array(this.visemeValues.length).fill(1);
3717
- this.mixWeights = { ...this.profile.auMixDefaults };
3718
- }
3719
- setProfile(profile) {
3720
- this.profile = profile;
3721
- this.visemeValues = new Array(getProfileVisemeSlots(this.profile).length).fill(0);
3722
- this.visemeJawScales = new Array(this.visemeValues.length).fill(1);
3723
- this.mixWeights = { ...this.profile.auMixDefaults };
3724
- }
3725
- setModelDescriptor(model) {
3726
- this.model = model;
3727
- }
3728
- setAU(id, value, balance) {
3729
- if (value < 0 && this.profile.continuumPairs) {
3730
- const pairInfo = this.profile.continuumPairs[id];
3731
- if (pairInfo) {
3732
- const negAU = pairInfo.isNegative ? id : pairInfo.pairId;
3733
- const posAU = pairInfo.isNegative ? pairInfo.pairId : id;
3734
- this.setContinuum(negAU, posAU, pairInfo.isNegative ? -value : value, balance);
3735
- return;
3736
- }
3737
- }
3738
- this.auValues[id] = clamp01(value);
3739
- if (balance !== void 0) {
3740
- this.auBalances[id] = clampBalance2(balance);
3741
- }
3742
- }
3743
- getAU(id) {
3744
- return this.auValues[id] ?? 0;
3745
- }
3746
- setContinuum(negAU, posAU, continuumValue, balance) {
3747
- const value = Math.max(-1, Math.min(1, continuumValue));
3748
- if (value < 0) {
3749
- this.setAU(posAU, 0, balance);
3750
- this.setAU(negAU, Math.abs(value), balance);
3751
- return;
3752
- }
3753
- if (value > 0) {
3754
- this.setAU(negAU, 0, balance);
3755
- this.setAU(posAU, value, balance);
3756
- return;
3757
- }
3758
- this.setAU(negAU, 0, balance);
3759
- this.setAU(posAU, 0, balance);
3760
- }
3761
- setViseme(visemeIndex, value, jawScale = 1) {
3762
- if (visemeIndex < 0 || visemeIndex >= this.visemeValues.length) return;
3763
- this.visemeValues[visemeIndex] = clamp01(value);
3764
- this.visemeJawScales[visemeIndex] = jawScale;
3765
- }
3766
- setVisemeById(slotId, value, jawScale = 1) {
3767
- const index = getVisemeSlotIndex(this.profile, slotId);
3768
- if (index < 0) return;
3769
- this.setViseme(index, value, jawScale);
3770
- }
3771
- setAUMixWeight(id, weight) {
3772
- this.mixWeights[id] = clamp01(weight);
3773
- }
3774
- evaluateFrameDelta(deltaSeconds) {
3775
- const morphWrites = /* @__PURE__ */ new Map();
3776
- this.collectAUWrites(morphWrites);
3777
- this.collectVisemeWrites(morphWrites);
3778
- return {
3779
- deltaSeconds,
3780
- morphTargets: Array.from(morphWrites.values()),
3781
- bones: this.collectBoneWrites()
3782
- };
3783
- }
3784
- collectAUWrites(morphWrites) {
3785
- for (const [auIdText, entry] of Object.entries(this.profile.auToMorphs || {})) {
3786
- const auId = Number(auIdText);
3787
- if (Number.isNaN(auId) || !entry) continue;
3788
- const mixWeight = this.isMixedAU(auId) ? this.getAUMixWeight(auId) : 1;
3789
- const base = clamp01(this.auValues[auId] ?? 0) * mixWeight;
3790
- const { left, right } = computeSideValues(base, this.auBalances[auId]);
3791
- const meshNames = getMeshNamesForAUProfile(this.profile, auId);
3792
- for (const morph of entry.left || []) {
3793
- this.addMorphWrites(morphWrites, morph, meshNames, left);
3794
- }
3795
- for (const morph of entry.right || []) {
3796
- this.addMorphWrites(morphWrites, morph, meshNames, right);
3797
- }
3798
- for (const morph of entry.center || []) {
3799
- this.addMorphWrites(morphWrites, morph, meshNames, base);
3800
- }
3801
- }
3802
- }
3803
- collectVisemeWrites(morphWrites) {
3804
- const meshNames = getMeshNamesForVisemeProfile(this.profile);
3805
- const localMax = /* @__PURE__ */ new Map();
3806
- for (let index = 0; index < this.visemeValues.length; index += 1) {
3807
- const value = clamp01(this.visemeValues[index] ?? 0);
3808
- for (const bindingTarget of getVisemeBindingTargets(this.profile, index)) {
3809
- const weighted = clamp01(value * bindingTarget.weight);
3810
- for (const resolved of this.resolveMorphTargets(bindingTarget.morph, meshNames)) {
3811
- const key = morphWriteKey(resolved.mesh.id, resolved.morphTarget.id);
3812
- const existing = localMax.get(key);
3813
- if (!existing || weighted > existing.value) {
3814
- localMax.set(key, {
3815
- meshId: resolved.mesh.id,
3816
- morphTargetId: resolved.morphTarget.id,
3817
- value: weighted,
3818
- mode: "absolute"
3819
- });
3820
- }
3821
- }
3822
- }
3823
- }
3824
- for (const [key, write] of localMax) {
3825
- const existing = morphWrites.get(key);
3826
- if (!existing || write.value > existing.value) {
3827
- morphWrites.set(key, write);
3828
- }
3829
- }
3830
- }
3831
- collectBoneWrites() {
3832
- const writes = [];
3833
- const rotations = this.computeRotationState();
3834
- for (const composite of this.profile.compositeRotations || []) {
3835
- const bone = this.findBoneDescriptor(composite.node);
3836
- if (!bone) continue;
3837
- const transform = this.evaluateCompositeTransform(bone, composite, rotations[composite.node]);
3838
- if (transform) {
3839
- writes.push({
3840
- boneId: bone.id,
3841
- transform,
3842
- mode: "absolute",
3843
- space: "local"
3844
- });
3845
- }
3846
- }
3847
- for (const [nodeKey, translation] of Object.entries(this.computeTranslations())) {
3848
- const bone = this.findBoneDescriptor(nodeKey);
3849
- if (!bone) continue;
3850
- const position = addVec3(bone.restTransform?.position, translation);
3851
- upsertBoneWrite(writes, bone.id, { position });
3852
- }
3853
- const jawAmount = this.getActiveVisemeJawAmount();
3854
- const jawBinding = this.findAutoVisemeJawBinding();
3855
- const jawBone = jawBinding ? this.findBoneDescriptor(jawBinding.node) : void 0;
3856
- if (jawBone && jawBinding?.maxDegrees && jawBinding.channel) {
3857
- const current = writes.find((write) => write.boneId === jawBone.id);
3858
- if (jawAmount > 1e-6 || !current?.transform.rotation) {
3859
- const rest = jawBone.restTransform?.rotation ?? identityQuat;
3860
- const rotation = jawAmount > 1e-6 ? multiplyQuat(
3861
- rest,
3862
- quatFromChannel(jawBinding.channel, deg2rad(jawBinding.maxDegrees) * jawAmount * jawBinding.scale)
3863
- ) : rest;
3864
- upsertBoneWrite(writes, jawBone.id, { rotation });
3865
- }
3866
- }
3867
- return writes;
3868
- }
3869
- computeRotationState() {
3870
- const rotations = {};
3871
- for (const composite of this.profile.compositeRotations || []) {
3872
- rotations[composite.node] = {
3873
- pitch: this.getCompositeAxisValueForNode(composite.node, composite.pitch),
3874
- yaw: this.getCompositeAxisValueForNode(composite.node, composite.yaw),
3875
- roll: this.getCompositeAxisValueForNode(composite.node, composite.roll)
3876
- };
3877
- }
3878
- return rotations;
3879
- }
3880
- evaluateCompositeTransform(bone, composite, state) {
3881
- if (!state) return null;
3882
- let rotation = bone.restTransform?.rotation ?? identityQuat;
3883
- const applyAxis = (axisConfig, direction) => {
3884
- if (!axisConfig || Math.abs(direction) <= 1e-6) return;
3885
- const binding = getCompositeAxisBinding(
3886
- composite.node,
3887
- axisConfig,
3888
- direction,
3889
- (auId) => this.getEffectiveBoneAUValue(auId, composite.node),
3890
- this.profile.auToBones
3891
- );
3892
- if (!binding?.maxDegrees || !binding.channel) return;
3893
- rotation = multiplyQuat(
3894
- rotation,
3895
- quatFromChannel(binding.channel, deg2rad(binding.maxDegrees) * Math.abs(direction) * binding.scale)
3896
- );
3897
- };
3898
- applyAxis(composite.yaw, state.yaw);
3899
- applyAxis(composite.pitch, state.pitch);
3900
- applyAxis(composite.roll, state.roll);
3901
- return { rotation };
3902
- }
3903
- computeTranslations() {
3904
- const translations = {};
3905
- for (const [auIdText, bindings] of Object.entries(this.profile.auToBones || {})) {
3906
- const auId = Number(auIdText);
3907
- if (Number.isNaN(auId)) continue;
3908
- const value = clamp01(this.auValues[auId] ?? 0);
3909
- for (const binding of bindings || []) {
3910
- if (binding.channel !== "tx" && binding.channel !== "ty" && binding.channel !== "tz") continue;
3911
- if (binding.maxUnits === void 0) continue;
3912
- const current = translations[binding.node] ?? { x: 0, y: 0, z: 0 };
3913
- translations[binding.node] = current;
3914
- if (value <= 1e-6) continue;
3915
- const offset = clampSigned(value * binding.scale) * binding.maxUnits;
3916
- translations[binding.node] = {
3917
- x: binding.channel === "tx" ? offset : current.x,
3918
- y: binding.channel === "ty" ? offset : current.y,
3919
- z: binding.channel === "tz" ? offset : current.z
3920
- };
3921
- }
3922
- }
3923
- return translations;
3924
- }
3925
- addMorphWrites(morphWrites, morph, meshNames, value) {
3926
- for (const resolved of this.resolveMorphTargets(morph, meshNames)) {
3927
- const key = morphWriteKey(resolved.mesh.id, resolved.morphTarget.id);
3928
- const existing = morphWrites.get(key);
3929
- if (existing && existing.value >= value) continue;
3930
- morphWrites.set(key, {
3931
- meshId: resolved.mesh.id,
3932
- morphTargetId: resolved.morphTarget.id,
3933
- value,
3934
- mode: "absolute"
3935
- });
3936
- }
3937
- }
3938
- resolveMorphTargets(morph, meshNames) {
3939
- const result = [];
3940
- for (const meshName of meshNames) {
3941
- const mesh = this.model.meshes.find((candidate) => candidate.name === meshName);
3942
- if (!mesh) continue;
3943
- const morphTargets = mesh.morphTargetIds.map((id) => this.model.morphTargets.find((candidate) => candidate.id === id)).filter((target2) => !!target2);
3944
- const target = typeof morph === "number" ? morphTargets.find((candidate) => candidate.hostIndex === morph) : resolveMorphTargetByName(morphTargets, morph, this.profile);
3945
- if (target) {
3946
- result.push({ mesh, morphTarget: target });
3947
- }
3948
- }
3949
- return result;
3950
- }
3951
- findBoneDescriptor(nodeKey) {
3952
- const configuredName = this.profile.boneNodes[nodeKey] || nodeKey;
3953
- const prefix = this.profile.bonePrefix ?? "";
3954
- const suffix = this.profile.boneSuffix ?? "";
3955
- const prefixedName = prefix && !configuredName.startsWith(prefix) ? `${prefix}${configuredName}` : configuredName;
3956
- const fullName = suffix && !prefixedName.endsWith(suffix) ? `${prefixedName}${suffix}` : prefixedName;
3957
- const candidates = /* @__PURE__ */ new Set([nodeKey, configuredName, fullName]);
3958
- return this.model.bones.find((bone) => candidates.has(bone.name));
3959
- }
3960
- findAutoVisemeJawBinding() {
3961
- const candidates = [
3962
- this.profile.auToBones[103]?.find(isRotationBoneBinding2),
3963
- this.profile.auToBones[26]?.find(isRotationBoneBinding2)
3964
- ].filter((binding) => !!binding);
3965
- return candidates.find((binding) => !!this.findBoneDescriptor(binding.node)) ?? candidates[0] ?? null;
3966
- }
3967
- getActiveVisemeJawAmount() {
3968
- const jawAmounts = getVisemeJawAmounts(this.profile) ?? [];
3969
- let jawAmount = 0;
3970
- for (let index = 0; index < this.visemeValues.length; index += 1) {
3971
- const value = clamp01(this.visemeValues[index] ?? 0);
3972
- if (value <= 1e-6) continue;
3973
- const jawScale = this.visemeJawScales[index] ?? 1;
3974
- if (Math.abs(jawScale) <= 1e-6) continue;
3975
- jawAmount = Math.max(jawAmount, (jawAmounts[index] ?? 0) * value * jawScale);
3976
- }
3977
- return jawAmount;
3978
- }
3979
- getCompositeAxisValueForNode(nodeKey, axisConfig) {
3980
- return getCompositeAxisValue(axisConfig, (auId) => this.getEffectiveBoneAUValue(auId, nodeKey));
3981
- }
3982
- getEffectiveBoneAUValue(auId, nodeKey) {
3983
- const rawValue = clamp01(this.auValues[auId] ?? 0);
3984
- if (rawValue <= 1e-6) return 0;
3985
- const binding = this.profile.auToBones[auId]?.find((candidate) => candidate.node === nodeKey) ?? null;
3986
- if (!binding?.side) return rawValue;
3987
- return rawValue * getSideScale2(this.auBalances[auId] ?? 0, binding.side);
3988
- }
3989
- getAUMixWeight(auId) {
3990
- return this.mixWeights[auId] ?? this.profile.auMixDefaults?.[auId] ?? 1;
3991
- }
3992
- isMixedAU(auId) {
3993
- const morphs = this.profile.auToMorphs[auId];
3994
- const hasMorphs = !!(morphs?.left?.length || morphs?.right?.length || morphs?.center?.length);
3995
- return !!(hasMorphs && this.profile.auToBones[auId]?.length);
3996
- }
3997
- };
3998
- function resolveMorphTargetByName(targets, key, profile) {
3999
- const prefix = profile.morphPrefix || "";
4000
- const suffix = profile.morphSuffix || "";
4001
- const fullName = prefix + key + suffix;
4002
- const exact = targets.find((target) => target.name === fullName);
4003
- if (exact) return exact;
4004
- const suffixRegex = profile.suffixPattern ? new RegExp(profile.suffixPattern) : null;
4005
- if (!suffixRegex) return void 0;
4006
- return targets.find((target) => {
4007
- if (!target.name.startsWith(fullName)) return false;
4008
- const candidateSuffix = target.name.slice(fullName.length);
4009
- return candidateSuffix === "" || suffixRegex.test(candidateSuffix);
4010
- });
4011
- }
4012
- function computeSideValues(base, balance) {
4013
- const b = clampBalance2(balance ?? 0);
4014
- if (b === 0) return { left: base, right: base };
4015
- if (b < 0) return { left: base, right: base * (1 + b) };
4016
- return { left: base * (1 - b), right: base };
4017
- }
4018
- function getSideScale2(balance, side) {
4019
- if (side !== "left" && side !== "right") return 1;
4020
- const b = clampBalance2(balance);
4021
- if (side === "left") return b > 0 ? 1 - b : 1;
4022
- return b < 0 ? 1 + b : 1;
4023
- }
4024
- function addVec3(base, offset) {
4025
- return {
4026
- x: (base?.x ?? 0) + offset.x,
4027
- y: (base?.y ?? 0) + offset.y,
4028
- z: (base?.z ?? 0) + offset.z
4029
- };
4030
- }
4031
- function upsertBoneWrite(writes, boneId2, transform) {
4032
- const existingIndex = writes.findIndex((write) => write.boneId === boneId2);
4033
- if (existingIndex >= 0) {
4034
- const existing = writes[existingIndex];
4035
- writes[existingIndex] = {
4036
- ...existing,
4037
- transform: {
4038
- ...existing.transform,
4039
- ...transform
4040
- }
4041
- };
4042
- return;
4043
- }
4044
- writes.push({
4045
- boneId: boneId2,
4046
- transform,
4047
- mode: "absolute",
4048
- space: "local"
4049
- });
4050
- }
4051
- function quatFromChannel(channel, radians) {
4052
- if (channel === "rx") return quatFromAxis({ x: 1, y: 0, z: 0 }, radians);
4053
- if (channel === "ry") return quatFromAxis({ x: 0, y: 1, z: 0 }, radians);
4054
- return quatFromAxis({ x: 0, y: 0, z: 1 }, radians);
4055
- }
4056
- function quatFromAxis(axis, radians) {
4057
- const half = radians / 2;
4058
- const s = Math.sin(half);
4059
- return normalizeQuat({
4060
- x: axis.x * s,
4061
- y: axis.y * s,
4062
- z: axis.z * s,
4063
- w: Math.cos(half)
4064
- });
4065
- }
4066
- function multiplyQuat(a, b) {
4067
- return normalizeQuat({
4068
- x: a.w * b.x + a.x * b.w + a.y * b.z - a.z * b.y,
4069
- y: a.w * b.y - a.x * b.z + a.y * b.w + a.z * b.x,
4070
- z: a.w * b.z + a.x * b.y - a.y * b.x + a.z * b.w,
4071
- w: a.w * b.w - a.x * b.x - a.y * b.y - a.z * b.z
4072
- });
4073
- }
4074
- function normalizeQuat(q) {
4075
- const len = Math.hypot(q.x, q.y, q.z, q.w);
4076
- if (len <= 1e-12) return identityQuat;
4077
- return {
4078
- x: q.x / len,
4079
- y: q.y / len,
4080
- z: q.z / len,
4081
- w: q.w / len
4082
- };
4083
- }
4084
- function deg2rad(degrees) {
4085
- return degrees * Math.PI / 180;
4086
- }
4087
- function clamp01(value) {
4088
- if (!Number.isFinite(value)) return 0;
4089
- return value < 0 ? 0 : value > 1 ? 1 : value;
4090
- }
4091
- function clampSigned(value) {
4092
- if (!Number.isFinite(value)) return 0;
4093
- return value < -1 ? -1 : value > 1 ? 1 : value;
4094
- }
4095
- function clampBalance2(value) {
4096
- if (!Number.isFinite(value)) return 0;
4097
- return Math.max(-1, Math.min(1, value));
4098
- }
4099
- function morphWriteKey(meshId2, morphTargetId2) {
4100
- return `${meshId2}:${morphTargetId2}`;
4101
- }
4102
-
4103
3699
  // src/core/contracts/abi.ts
4104
3700
  var PACKED_MORPH_FRAME_DELTA_STRIDE = 4;
4105
3701
  var PACKED_BONE_FRAME_DELTA_STRIDE = 9;
@@ -4229,25 +3825,17 @@ function positiveFiniteOr(value, fallback) {
4229
3825
  }
4230
3826
 
4231
3827
  // src/core/WasmRuntimeCore.ts
4232
- var AU_MORPH_BINDING_STRIDE = 5;
4233
- var VISEME_MORPH_BINDING_STRIDE = 4;
4234
- var AXIS_GROUP_NEGATIVE = 0;
4235
- var AXIS_GROUP_POSITIVE = 1;
4236
- var AXIS_GROUP_PLAIN = 2;
4237
- var BONE_SIDE_NONE = 0;
4238
- var BONE_SIDE_LEFT = 1;
4239
- var BONE_SIDE_RIGHT = 2;
4240
- var ROTATION_CHANNELS = { rx: 0, ry: 1, rz: 2 };
4241
- var TRANSLATION_CHANNELS = { tx: 0, ty: 1, tz: 2 };
4242
3828
  var WasmRuntimeCore = class {
4243
3829
  constructor(options) {
4244
3830
  __publicField(this, "wasm");
4245
3831
  __publicField(this, "core");
3832
+ __publicField(this, "allowEmptyBindings");
4246
3833
  __publicField(this, "profile");
4247
3834
  __publicField(this, "model");
4248
3835
  this.wasm = options.wasm;
4249
3836
  this.profile = options.profile;
4250
3837
  this.model = options.model;
3838
+ this.allowEmptyBindings = options.allowEmptyBindings ?? false;
4251
3839
  const RuntimeCtor = options.wasm.RuntimeCore;
4252
3840
  if (!RuntimeCtor) {
4253
3841
  throw new Error("Embody Wasm module does not export RuntimeCore");
@@ -4266,6 +3854,24 @@ var WasmRuntimeCore = class {
4266
3854
  setAU(id, value, balance = 0) {
4267
3855
  this.core.set_au(id >>> 0, value, balance);
4268
3856
  }
3857
+ setContinuum(negativeAU, positiveAU, value, balance = 0) {
3858
+ this.core.set_continuum(negativeAU >>> 0, positiveAU >>> 0, value, balance);
3859
+ }
3860
+ transitionAU(id, to, durationMs, balance = Number.NaN) {
3861
+ this.core.transition_au(id >>> 0, to, durationMs, balance);
3862
+ }
3863
+ transitionViseme(visemeIndex, to, durationMs, jawScale = 1) {
3864
+ this.core.transition_viseme(visemeIndex >>> 0, to, durationMs, jawScale);
3865
+ }
3866
+ update(deltaSeconds) {
3867
+ return this.core.update(deltaSeconds);
3868
+ }
3869
+ activeTransitionCount() {
3870
+ return this.core.active_transition_count();
3871
+ }
3872
+ clearTransitions() {
3873
+ this.core.clear_transitions();
3874
+ }
4269
3875
  getAU(id) {
4270
3876
  return this.core.get_au(id >>> 0);
4271
3877
  }
@@ -4301,182 +3907,15 @@ var WasmRuntimeCore = class {
4301
3907
  };
4302
3908
  }
4303
3909
  reloadBindings() {
4304
- this.core.configure_with_profile(JSON.stringify(this.profile), JSON.stringify(this.model));
4305
- }
4306
- };
4307
- function compileMorphBindings(profile, model) {
4308
- const auRows = [];
4309
- const mixed = [];
4310
- for (const [auIdText, entry] of Object.entries(profile.auToMorphs || {})) {
4311
- const auId = Number(auIdText);
4312
- if (Number.isNaN(auId) || !entry) continue;
4313
- const meshNames = getMeshNamesForAUProfile(profile, auId);
4314
- const hasMorphs = !!(entry.left?.length || entry.right?.length || entry.center?.length);
4315
- if (hasMorphs && profile.auToBones?.[auId]?.length) {
4316
- mixed.push(auId);
4317
- }
4318
- pushAuSide(auRows, auId, 0, entry.left || [], meshNames, model, profile);
4319
- pushAuSide(auRows, auId, 1, entry.right || [], meshNames, model, profile);
4320
- pushAuSide(auRows, auId, 2, entry.center || [], meshNames, model, profile);
4321
- }
4322
- const visemeRows = [];
4323
- const visemeMeshNames = getMeshNamesForVisemeProfile(profile);
4324
- const slotCount = getProfileVisemeSlots(profile).length;
4325
- for (let index = 0; index < slotCount; index += 1) {
4326
- for (const bindingTarget of getVisemeBindingTargets(profile, index)) {
4327
- for (const resolved of resolveMorphTargets(model, profile, bindingTarget.morph, visemeMeshNames)) {
4328
- visemeRows.push(
4329
- index,
4330
- Number(resolved.mesh.id),
4331
- Number(resolved.morphTarget.id),
4332
- bindingTarget.weight
4333
- );
4334
- }
3910
+ const profileJson = JSON.stringify(this.profile);
3911
+ const modelJson = JSON.stringify(this.model);
3912
+ if (this.allowEmptyBindings) {
3913
+ this.core.configure(profileJson, modelJson);
3914
+ } else {
3915
+ this.core.configure_with_profile(profileJson, modelJson);
4335
3916
  }
4336
3917
  }
4337
- return {
4338
- auBindings: Float32Array.from(auRows),
4339
- visemeBindings: Float32Array.from(visemeRows),
4340
- mixedAus: Uint32Array.from(mixed)
4341
- };
4342
- }
4343
- function compileBoneBindings(profile, model) {
4344
- const referencedBones = /* @__PURE__ */ new Map();
4345
- const findBone = (nodeKey) => {
4346
- const bone = findBoneDescriptor(profile, model, nodeKey);
4347
- if (bone) referencedBones.set(bone.id, bone);
4348
- return bone;
4349
- };
4350
- const boneSideForAU = (auId, nodeKey) => {
4351
- const side = profile.auToBones?.[auId]?.find((candidate) => candidate.node === nodeKey)?.side;
4352
- if (side === "left") return BONE_SIDE_LEFT;
4353
- if (side === "right") return BONE_SIDE_RIGHT;
4354
- return BONE_SIDE_NONE;
4355
- };
4356
- const compositeRows = [];
4357
- for (const composite of profile.compositeRotations || []) {
4358
- const bone = findBone(composite.node);
4359
- if (!bone) continue;
4360
- const axes = [
4361
- { axis: 1, config: composite.yaw },
4362
- { axis: 0, config: composite.pitch },
4363
- { axis: 2, config: composite.roll }
4364
- ];
4365
- for (const { axis, config } of axes) {
4366
- if (!config) continue;
4367
- const negativeAUs = toAUList(config.negative);
4368
- const positiveAUs = toAUList(config.positive);
4369
- const hasDirectional = negativeAUs.length > 0 && positiveAUs.length > 0;
4370
- const valueRows = [];
4371
- if (hasDirectional) {
4372
- for (const auId of negativeAUs) {
4373
- valueRows.push([auId, AXIS_GROUP_NEGATIVE, boneSideForAU(auId, composite.node)]);
4374
- }
4375
- for (const auId of positiveAUs) {
4376
- valueRows.push([auId, AXIS_GROUP_POSITIVE, boneSideForAU(auId, composite.node)]);
4377
- }
4378
- } else {
4379
- for (const auId of config.aus) {
4380
- valueRows.push([auId, AXIS_GROUP_PLAIN, boneSideForAU(auId, composite.node)]);
4381
- }
4382
- }
4383
- const bindingRows = [];
4384
- const pushBindingRows = (auIds, group) => {
4385
- for (const auId of auIds) {
4386
- const binding = profile.auToBones?.[auId]?.find(
4387
- (candidate) => candidate.node === composite.node
4388
- );
4389
- if (!binding?.maxDegrees) continue;
4390
- const channel = ROTATION_CHANNELS[binding.channel];
4391
- if (channel === void 0) continue;
4392
- bindingRows.push([
4393
- auId,
4394
- group,
4395
- boneSideForAU(auId, composite.node),
4396
- channel,
4397
- binding.scale,
4398
- binding.maxDegrees
4399
- ]);
4400
- }
4401
- };
4402
- pushBindingRows(negativeAUs, AXIS_GROUP_NEGATIVE);
4403
- pushBindingRows(positiveAUs, AXIS_GROUP_POSITIVE);
4404
- pushBindingRows(config.aus, AXIS_GROUP_PLAIN);
4405
- compositeRows.push(
4406
- Number(bone.id),
4407
- axis,
4408
- hasDirectional ? 1 : 0,
4409
- valueRows.length,
4410
- bindingRows.length,
4411
- 0,
4412
- 0,
4413
- 0
4414
- );
4415
- for (const row of valueRows) compositeRows.push(...row);
4416
- for (const row of bindingRows) compositeRows.push(...row);
4417
- }
4418
- }
4419
- const translationRows = [];
4420
- for (const [auIdText, bindings] of Object.entries(profile.auToBones || {})) {
4421
- const auId = Number(auIdText);
4422
- if (Number.isNaN(auId)) continue;
4423
- for (const binding of bindings || []) {
4424
- const channel = TRANSLATION_CHANNELS[binding.channel];
4425
- if (channel === void 0 || binding.maxUnits === void 0) continue;
4426
- const bone = findBone(binding.node);
4427
- if (!bone) continue;
4428
- translationRows.push(auId, Number(bone.id), channel, binding.scale, binding.maxUnits);
4429
- }
4430
- }
4431
- const jawRow = [];
4432
- const jawBinding = findAutoVisemeJawBinding(profile, model);
4433
- if (jawBinding?.maxDegrees) {
4434
- const bone = findBone(jawBinding.node);
4435
- const channel = ROTATION_CHANNELS[jawBinding.channel];
4436
- if (bone && channel !== void 0) {
4437
- jawRow.push(Number(bone.id), channel, jawBinding.scale, jawBinding.maxDegrees);
4438
- }
4439
- }
4440
- const restRows = [];
4441
- for (const bone of referencedBones.values()) {
4442
- const position = bone.restTransform?.position;
4443
- const rotation = bone.restTransform?.rotation;
4444
- restRows.push(
4445
- Number(bone.id),
4446
- position?.x ?? 0,
4447
- position?.y ?? 0,
4448
- position?.z ?? 0,
4449
- rotation?.x ?? 0,
4450
- rotation?.y ?? 0,
4451
- rotation?.z ?? 0,
4452
- rotation?.w ?? 1
4453
- );
4454
- }
4455
- return {
4456
- restTransforms: Float32Array.from(restRows),
4457
- compositeAxes: Float32Array.from(compositeRows),
4458
- translations: Float32Array.from(translationRows),
4459
- jawBinding: Float32Array.from(jawRow),
4460
- visemeJawAmounts: Float32Array.from(getVisemeJawAmounts(profile) ?? [])
4461
- };
4462
- }
4463
- function findBoneDescriptor(profile, model, nodeKey) {
4464
- const configuredName = profile.boneNodes?.[nodeKey] || nodeKey;
4465
- const prefix = profile.bonePrefix ?? "";
4466
- const suffix = profile.boneSuffix ?? "";
4467
- const prefixedName = prefix && !configuredName.startsWith(prefix) ? `${prefix}${configuredName}` : configuredName;
4468
- const fullName = suffix && !prefixedName.endsWith(suffix) ? `${prefixedName}${suffix}` : prefixedName;
4469
- const candidates = /* @__PURE__ */ new Set([nodeKey, configuredName, fullName]);
4470
- return model.bones.find((bone) => candidates.has(bone.name));
4471
- }
4472
- function findAutoVisemeJawBinding(profile, model) {
4473
- const isRotation = (binding) => binding?.channel === "rx" || binding?.channel === "ry" || binding?.channel === "rz";
4474
- const candidates = [
4475
- profile.auToBones?.[103]?.find(isRotation),
4476
- profile.auToBones?.[26]?.find(isRotation)
4477
- ].filter((binding) => !!binding);
4478
- return candidates.find((binding) => !!findBoneDescriptor(profile, model, binding.node)) ?? candidates[0] ?? null;
4479
- }
3918
+ };
4480
3919
  function unpackBoneFrameDelta(values) {
4481
3920
  const stride = PACKED_BONE_FRAME_DELTA_STRIDE;
4482
3921
  const writes = [];
@@ -4517,46 +3956,6 @@ function unpackMorphFrameDelta(values) {
4517
3956
  }
4518
3957
  return writes;
4519
3958
  }
4520
- function pushAuSide(rows, auId, side, morphs, meshNames, model, profile) {
4521
- for (const morph of morphs) {
4522
- for (const resolved of resolveMorphTargets(model, profile, morph, meshNames)) {
4523
- rows.push(
4524
- auId,
4525
- side,
4526
- Number(resolved.mesh.id),
4527
- Number(resolved.morphTarget.id),
4528
- 1
4529
- );
4530
- }
4531
- }
4532
- }
4533
- function resolveMorphTargets(model, profile, morph, meshNames) {
4534
- const result = [];
4535
- for (const meshName of meshNames) {
4536
- const mesh = model.meshes.find((candidate) => candidate.name === meshName);
4537
- if (!mesh) continue;
4538
- const morphTargets = mesh.morphTargetIds.map((id) => model.morphTargets.find((candidate) => candidate.id === id)).filter((target2) => !!target2);
4539
- const target = typeof morph === "number" ? morphTargets.find((candidate) => candidate.hostIndex === morph) : resolveMorphTargetByName2(morphTargets, morph, profile);
4540
- if (target) {
4541
- result.push({ mesh, morphTarget: target });
4542
- }
4543
- }
4544
- return result;
4545
- }
4546
- function resolveMorphTargetByName2(targets, key, profile) {
4547
- const prefix = profile.morphPrefix || "";
4548
- const suffix = profile.morphSuffix || "";
4549
- const fullName = prefix + key + suffix;
4550
- const exact = targets.find((target) => target.name === fullName);
4551
- if (exact) return exact;
4552
- const suffixRegex = profile.suffixPattern ? new RegExp(profile.suffixPattern) : null;
4553
- if (!suffixRegex) return void 0;
4554
- return targets.find((target) => {
4555
- if (!target.name.startsWith(fullName)) return false;
4556
- const candidateSuffix = target.name.slice(fullName.length);
4557
- return candidateSuffix === "" || suffixRegex.test(candidateSuffix);
4558
- });
4559
- }
4560
3959
 
4561
3960
  // src/hair/appearance.ts
4562
3961
  var HAIR_COLOR_PRESETS = {
@@ -5872,7 +5271,7 @@ var CC4_PRESET = {
5872
5271
  };
5873
5272
 
5874
5273
  // src/engines/three/hair/HairPhysicsController.ts
5875
- var clamp012 = (value) => value < 0 ? 0 : value > 1 ? 1 : value;
5274
+ var clamp01 = (value) => value < 0 ? 0 : value > 1 ? 1 : value;
5876
5275
  var HairPhysicsController = class {
5877
5276
  constructor(host) {
5878
5277
  __publicField(this, "host");
@@ -6370,7 +5769,7 @@ var HairPhysicsController = class {
6370
5769
  }
6371
5770
  }
6372
5771
  setMorphOnMeshes(meshNames, morphKey, value) {
6373
- const val = clamp012(value);
5772
+ const val = clamp01(value);
6374
5773
  for (const name of meshNames) {
6375
5774
  const mesh = this.registeredHairObjects.get(name) || this.host.getMeshByName(name);
6376
5775
  if (!mesh) continue;
@@ -6614,9 +6013,9 @@ var HairPhysicsController = class {
6614
6013
  const deltaVertical = vertical - this.lastHeadVertical;
6615
6014
  this.lastHeadHorizontal = horizontal;
6616
6015
  this.lastHeadVertical = vertical;
6617
- const leftWeight = clamp012(deltaHorizontal > 0 ? deltaHorizontal : 0);
6618
- const rightWeight = clamp012(deltaHorizontal < 0 ? -deltaHorizontal : 0);
6619
- const frontWeight = clamp012(deltaVertical > 0 ? deltaVertical : 0);
6016
+ const leftWeight = clamp01(deltaHorizontal > 0 ? deltaHorizontal : 0);
6017
+ const rightWeight = clamp01(deltaHorizontal < 0 ? -deltaHorizontal : 0);
6018
+ const frontWeight = clamp01(deltaVertical > 0 ? deltaVertical : 0);
6620
6019
  const minTrigger = 0.01;
6621
6020
  if (leftWeight <= minTrigger && rightWeight <= minTrigger && frontWeight <= minTrigger) return;
6622
6021
  if (this.impulseClipDirty) {
@@ -7586,7 +6985,7 @@ async function getPresetWithProfile(presetType, profile) {
7586
6985
  var resolvePresetWithOverrides = getPresetWithProfile;
7587
6986
 
7588
6987
  // src/engines/three/Embody.ts
7589
- var deg2rad2 = (d) => d * Math.PI / 180;
6988
+ var deg2rad = (d) => d * Math.PI / 180;
7590
6989
  var X_AXIS2 = new THREE.Vector3(1, 0, 0);
7591
6990
  var Y_AXIS2 = new THREE.Vector3(0, 1, 0);
7592
6991
  var Z_AXIS2 = new THREE.Vector3(0, 0, 1);
@@ -7609,9 +7008,18 @@ function buildAUToCompositeMap(composites) {
7609
7008
  });
7610
7009
  return map;
7611
7010
  }
7612
- function clamp013(x) {
7011
+ function clamp012(x) {
7613
7012
  return x < 0 ? 0 : x > 1 ? 1 : x;
7614
7013
  }
7014
+ var RESOLVED_TRANSITION_HANDLE = {
7015
+ promise: Promise.resolve(),
7016
+ pause: () => {
7017
+ },
7018
+ resume: () => {
7019
+ },
7020
+ cancel: () => {
7021
+ }
7022
+ };
7615
7023
  var _Embody = class _Embody {
7616
7024
  constructor(config = {}, animation) {
7617
7025
  // Optional hook for animation schedulers.
@@ -7655,7 +7063,6 @@ var _Embody = class _Embody {
7655
7063
  __publicField(this, "modelInspector", new ThreeModelInspector());
7656
7064
  __publicField(this, "frameApplier", new ThreeFrameApplier());
7657
7065
  __publicField(this, "modelDescriptor", null);
7658
- __publicField(this, "tsRuntimeCore", null);
7659
7066
  __publicField(this, "wasmRuntimeCore", null);
7660
7067
  __publicField(this, "framePathReady", false);
7661
7068
  // Internal animation loop
@@ -7768,48 +7175,21 @@ var _Embody = class _Embody {
7768
7175
  bindHostNeutralRuntime(inspection) {
7769
7176
  this.modelDescriptor = inspection.descriptor;
7770
7177
  this.frameApplier.setBindings(buildFrameApplierBindings(inspection));
7771
- this.tsRuntimeCore = new TsRuntimeCore({
7178
+ const wasm$1 = wasm.requireInitializedEmbodyCore();
7179
+ this.attachWasmCore(wasm$1);
7180
+ this.wasmRuntimeCore = new WasmRuntimeCore({
7772
7181
  profile: this.config,
7773
- model: inspection.descriptor
7182
+ model: inspection.descriptor,
7183
+ wasm: wasm$1,
7184
+ allowEmptyBindings: true
7774
7185
  });
7775
7186
  this.syncRuntimeCoreState();
7776
7187
  this.framePathReady = true;
7777
- void wasm.initEmbodyCore().then((wasm) => {
7778
- if (!this.modelDescriptor) return;
7779
- this.attachWasmCore(wasm);
7780
- this.wasmRuntimeCore = new WasmRuntimeCore({
7781
- profile: this.config,
7782
- model: this.modelDescriptor,
7783
- wasm
7784
- });
7785
- this.syncRuntimeCoreState();
7786
- }).catch(() => {
7787
- this.wasmRuntimeCore = null;
7788
- });
7789
7188
  }
7790
7189
  attachWasmCore(core) {
7791
7190
  this.hairPhysics.setCore(core);
7792
7191
  }
7793
7192
  syncRuntimeCoreState() {
7794
- if (this.tsRuntimeCore) {
7795
- this.tsRuntimeCore.setProfile(this.config);
7796
- if (this.modelDescriptor) {
7797
- this.tsRuntimeCore.setModelDescriptor(this.modelDescriptor);
7798
- }
7799
- for (const [auIdText, value] of Object.entries(this.auValues)) {
7800
- const auId = Number(auIdText);
7801
- if (Number.isNaN(auId)) continue;
7802
- this.tsRuntimeCore.setAU(auId, value, this.auBalances[auId]);
7803
- }
7804
- for (const [auIdText, weight] of Object.entries(this.mixWeights)) {
7805
- const auId = Number(auIdText);
7806
- if (Number.isNaN(auId)) continue;
7807
- this.tsRuntimeCore.setAUMixWeight(auId, weight);
7808
- }
7809
- for (let index = 0; index < this.visemeValues.length; index += 1) {
7810
- this.tsRuntimeCore.setViseme(index, this.visemeValues[index] ?? 0, this.visemeJawScales[index] ?? 1);
7811
- }
7812
- }
7813
7193
  if (this.wasmRuntimeCore) {
7814
7194
  this.wasmRuntimeCore.setProfile(this.config);
7815
7195
  if (this.modelDescriptor) {
@@ -7827,7 +7207,7 @@ var _Embody = class _Embody {
7827
7207
  this.wasmRuntimeCore.setAUMixWeight(auId, weight);
7828
7208
  }
7829
7209
  for (let index = 0; index < this.visemeValues.length; index += 1) {
7830
- this.wasmRuntimeCore.setViseme(index, this.visemeValues[index] ?? 0);
7210
+ this.wasmRuntimeCore.setViseme(index, this.visemeValues[index] ?? 0, this.visemeJawScales[index] ?? 1);
7831
7211
  }
7832
7212
  }
7833
7213
  }
@@ -7845,25 +7225,13 @@ var _Embody = class _Embody {
7845
7225
  });
7846
7226
  this.modelDescriptor = inspection.descriptor;
7847
7227
  this.frameApplier.setBindings(buildFrameApplierBindings(inspection));
7848
- this.tsRuntimeCore?.setModelDescriptor(inspection.descriptor);
7849
7228
  this.wasmRuntimeCore?.setModelDescriptor(inspection.descriptor);
7850
7229
  }
7851
- applyLiveMorphFrameDelta() {
7852
- if (!this.framePathReady || !this.model) return false;
7853
- if (this.wasmRuntimeCore) {
7854
- this.frameApplier.applyPackedMorphFrameDelta(
7855
- this.wasmRuntimeCore.evaluatePackedMorphFrameDelta()
7856
- );
7857
- return true;
7858
- }
7859
- if (this.tsRuntimeCore) {
7860
- const frame = this.tsRuntimeCore.evaluateFrameDelta();
7861
- this.frameApplier.applyFrameDelta(this.model, {
7862
- morphTargets: frame.morphTargets
7863
- });
7864
- return true;
7865
- }
7866
- return false;
7230
+ applyLiveFrameDelta() {
7231
+ if (!this.framePathReady || !this.model || !this.wasmRuntimeCore) return false;
7232
+ this.frameApplier.applyFrameDelta(this.model, this.wasmRuntimeCore.evaluateFrameDelta());
7233
+ this.pendingCompositeNodes.clear();
7234
+ return true;
7867
7235
  }
7868
7236
  rebuildMorphTargetsCache() {
7869
7237
  this.morphKeyCache.clear();
@@ -7965,6 +7333,11 @@ var _Embody = class _Embody {
7965
7333
  const dtSeconds = Math.max(0, deltaSeconds || 0);
7966
7334
  if (dtSeconds <= 0 || this.isPaused) return;
7967
7335
  this.animation.tick(dtSeconds);
7336
+ const wasmTransitionCount = this.wasmRuntimeCore?.activeTransitionCount() ?? 0;
7337
+ const remainingWasmTransitions = this.wasmRuntimeCore?.update(dtSeconds) ?? 0;
7338
+ if (wasmTransitionCount > 0 || remainingWasmTransitions > 0) {
7339
+ this.applyLiveFrameDelta();
7340
+ }
7968
7341
  this.flushPendingComposites();
7969
7342
  this.animationController.update(dtSeconds);
7970
7343
  this.hairPhysics.update(dtSeconds);
@@ -7999,7 +7372,6 @@ var _Embody = class _Embody {
7999
7372
  this.model = null;
8000
7373
  this.bones = {};
8001
7374
  this.modelDescriptor = null;
8002
- this.tsRuntimeCore = null;
8003
7375
  this.wasmRuntimeCore = null;
8004
7376
  this.framePathReady = false;
8005
7377
  }
@@ -8036,15 +7408,14 @@ var _Embody = class _Embody {
8036
7408
  if (balance !== void 0) {
8037
7409
  this.auBalances[id] = balance;
8038
7410
  }
8039
- this.tsRuntimeCore?.setAU(id, v, balance);
8040
7411
  this.wasmRuntimeCore?.setAU(id, v, balance ?? this.auBalances[id] ?? 0);
8041
- const appliedViaFrame = this.applyLiveMorphFrameDelta();
7412
+ const appliedViaFrame = this.applyLiveFrameDelta();
8042
7413
  if (!appliedViaFrame) {
8043
7414
  const resolvedMorphTargets = this.resolvedAUMorphTargets.get(id);
8044
7415
  const { left: leftKeys, right: rightKeys, center: centerKeys } = this.getAUMorphsBySide(id);
8045
7416
  if (resolvedMorphTargets) {
8046
7417
  const mixWeight = this.isMixedAU(id) ? this.getAUMixWeight(id) : 1;
8047
- const base = clamp013(v) * mixWeight;
7418
+ const base = clamp012(v) * mixWeight;
8048
7419
  const { left: leftVal, right: rightVal } = this.computeSideValues(base, balance);
8049
7420
  if (resolvedMorphTargets.left.length || resolvedMorphTargets.right.length) {
8050
7421
  this.applyMorphTargets(resolvedMorphTargets.left, leftVal);
@@ -8053,7 +7424,7 @@ var _Embody = class _Embody {
8053
7424
  this.applyMorphTargets(resolvedMorphTargets.center, base);
8054
7425
  } else if (leftKeys.length || rightKeys.length || centerKeys.length) {
8055
7426
  const mixWeight = this.isMixedAU(id) ? this.getAUMixWeight(id) : 1;
8056
- const base = clamp013(v) * mixWeight;
7427
+ const base = clamp012(v) * mixWeight;
8057
7428
  const meshNames = this.getMeshNamesForAU(id);
8058
7429
  const { left: leftVal, right: rightVal } = this.computeSideValues(base, balance);
8059
7430
  if (leftKeys.length || rightKeys.length) {
@@ -8072,6 +7443,7 @@ var _Embody = class _Embody {
8072
7443
  }
8073
7444
  }
8074
7445
  }
7446
+ if (appliedViaFrame) return;
8075
7447
  const compositeInfo = this.auToCompositeMap.get(id);
8076
7448
  if (compositeInfo) {
8077
7449
  for (const nodeKey of compositeInfo.nodes) {
@@ -8106,7 +7478,7 @@ var _Embody = class _Embody {
8106
7478
  return this.transitionContinuum(negAU, posAU, continuumValue, durationMs, balance);
8107
7479
  }
8108
7480
  }
8109
- const target = clamp013(to);
7481
+ const target = clamp012(to);
8110
7482
  if (balance !== void 0) {
8111
7483
  this.auBalances[numId] = balance;
8112
7484
  }
@@ -8117,6 +7489,19 @@ var _Embody = class _Embody {
8117
7489
  const base = target * mixWeight;
8118
7490
  const { left: leftVal, right: rightVal } = this.computeSideValues(base, storedBalance);
8119
7491
  this.auValues[numId] = target;
7492
+ if (this.wasmRuntimeCore) {
7493
+ this.wasmRuntimeCore.transitionAU(
7494
+ numId,
7495
+ target,
7496
+ durationMs,
7497
+ balance ?? this.auBalances[numId] ?? Number.NaN
7498
+ );
7499
+ this.update(0);
7500
+ if (durationMs <= 0) {
7501
+ this.applyLiveFrameDelta();
7502
+ }
7503
+ return RESOLVED_TRANSITION_HANDLE;
7504
+ }
8120
7505
  const handles = [];
8121
7506
  const meshNames = this.getMeshNamesForAU(numId);
8122
7507
  if (leftKeys.length || rightKeys.length) {
@@ -8179,6 +7564,17 @@ var _Embody = class _Embody {
8179
7564
  */
8180
7565
  setContinuum(negAU, posAU, continuumValue, balance) {
8181
7566
  const value = Math.max(-1, Math.min(1, continuumValue));
7567
+ this.auValues[negAU] = value < 0 ? Math.abs(value) : 0;
7568
+ this.auValues[posAU] = value > 0 ? value : 0;
7569
+ if (balance !== void 0) {
7570
+ this.auBalances[negAU] = balance;
7571
+ this.auBalances[posAU] = balance;
7572
+ }
7573
+ if (this.wasmRuntimeCore) {
7574
+ this.wasmRuntimeCore.setContinuum(negAU, posAU, value, balance ?? 0);
7575
+ this.applyLiveFrameDelta();
7576
+ return;
7577
+ }
8182
7578
  if (value < 0) {
8183
7579
  this.setAU(posAU, 0, balance);
8184
7580
  this.setAU(negAU, Math.abs(value), balance);
@@ -8204,10 +7600,32 @@ var _Embody = class _Embody {
8204
7600
  */
8205
7601
  transitionContinuum(negAU, posAU, continuumValue, durationMs = 200, balance) {
8206
7602
  const target = Math.max(-1, Math.min(1, continuumValue));
8207
- const driverKey = `continuum_${negAU}_${posAU}`;
8208
7603
  const currentNeg = this.auValues[negAU] ?? 0;
8209
7604
  const currentPos = this.auValues[posAU] ?? 0;
8210
7605
  const currentContinuum = currentPos - currentNeg;
7606
+ this.auValues[negAU] = target < 0 ? Math.abs(target) : 0;
7607
+ this.auValues[posAU] = target > 0 ? target : 0;
7608
+ if (balance !== void 0) {
7609
+ this.auBalances[negAU] = balance;
7610
+ this.auBalances[posAU] = balance;
7611
+ }
7612
+ if (this.wasmRuntimeCore) {
7613
+ if (target < 0) {
7614
+ this.wasmRuntimeCore.transitionAU(posAU, 0, durationMs, balance ?? this.auBalances[posAU] ?? Number.NaN);
7615
+ this.wasmRuntimeCore.transitionAU(negAU, Math.abs(target), durationMs, balance ?? this.auBalances[negAU] ?? Number.NaN);
7616
+ } else if (target > 0) {
7617
+ this.wasmRuntimeCore.transitionAU(negAU, 0, durationMs, balance ?? this.auBalances[negAU] ?? Number.NaN);
7618
+ this.wasmRuntimeCore.transitionAU(posAU, target, durationMs, balance ?? this.auBalances[posAU] ?? Number.NaN);
7619
+ } else {
7620
+ this.wasmRuntimeCore.transitionAU(negAU, 0, durationMs, balance ?? this.auBalances[negAU] ?? Number.NaN);
7621
+ this.wasmRuntimeCore.transitionAU(posAU, 0, durationMs, balance ?? this.auBalances[posAU] ?? Number.NaN);
7622
+ }
7623
+ if (durationMs <= 0) {
7624
+ this.applyLiveFrameDelta();
7625
+ }
7626
+ return RESOLVED_TRANSITION_HANDLE;
7627
+ }
7628
+ const driverKey = `continuum_${negAU}_${posAU}`;
8211
7629
  return this.animation.addTransition(driverKey, currentContinuum, target, durationMs, (value) => this.setContinuum(negAU, posAU, value, balance));
8212
7630
  }
8213
7631
  // ============================================================================
@@ -8289,7 +7707,7 @@ var _Embody = class _Embody {
8289
7707
  this.hairPhysics.refreshMeshSelection();
8290
7708
  }
8291
7709
  setMorph(key, v, meshNamesOrTargets) {
8292
- const val = clamp013(v);
7710
+ const val = clamp012(v);
8293
7711
  if (Array.isArray(meshNamesOrTargets) && meshNamesOrTargets.length > 0 && typeof meshNamesOrTargets[0] === "object" && "infl" in meshNamesOrTargets[0]) {
8294
7712
  const targets2 = meshNamesOrTargets;
8295
7713
  this.applyMorphTargets(targets2, val);
@@ -8301,7 +7719,7 @@ var _Embody = class _Embody {
8301
7719
  this.applyMorphTargets(targets, val);
8302
7720
  }
8303
7721
  setMorphInfluence(index, v, meshNames) {
8304
- const val = clamp013(v);
7722
+ const val = clamp012(v);
8305
7723
  const targets = this.resolveMorphTargetsByIndex(index, meshNames);
8306
7724
  if (targets.length === 0) return;
8307
7725
  this.applyMorphTargets(targets, val);
@@ -8376,21 +7794,21 @@ var _Embody = class _Embody {
8376
7794
  }
8377
7795
  transitionMorph(key, to, durationMs = 120, meshNames) {
8378
7796
  const transitionKey = meshNames?.length ? `morph_${this.getMorphKeyCacheKey(key, meshNames)}` : `morph_${this.getMorphKeyCacheKey(key)}`;
8379
- const target = clamp013(to);
7797
+ const target = clamp012(to);
8380
7798
  const targets = this.resolveMorphTargets(key, meshNames);
8381
7799
  const from = targets.length > 0 ? targets[0].infl[targets[0].idx] ?? 0 : this.getMorphValue(key);
8382
7800
  return this.animation.addTransition(transitionKey, from, target, durationMs, (value) => {
8383
- const val = clamp013(value);
7801
+ const val = clamp012(value);
8384
7802
  this.applyMorphTargets(targets, val);
8385
7803
  });
8386
7804
  }
8387
7805
  transitionMorphInfluence(index, to, durationMs = 120, meshNames) {
8388
7806
  const transitionKey = meshNames?.length ? `morph_${this.getMorphIndexCacheKey(index, meshNames)}` : `morph_${this.getMorphIndexCacheKey(index)}`;
8389
- const target = clamp013(to);
7807
+ const target = clamp012(to);
8390
7808
  const targets = this.resolveMorphTargetsByIndex(index, meshNames);
8391
7809
  const from = targets.length > 0 ? targets[0].infl[targets[0].idx] ?? 0 : this.getMorphValueByIndex(index);
8392
7810
  return this.animation.addTransition(transitionKey, from, target, durationMs, (value) => {
8393
- const val = clamp013(value);
7811
+ const val = clamp012(value);
8394
7812
  this.applyMorphTargets(targets, val);
8395
7813
  });
8396
7814
  }
@@ -8399,7 +7817,7 @@ var _Embody = class _Embody {
8399
7817
  // ============================================================================
8400
7818
  setViseme(visemeIndex, value, jawScale = 1) {
8401
7819
  if (visemeIndex < 0 || visemeIndex >= this.visemeValues.length) return;
8402
- const val = clamp013(value);
7820
+ const val = clamp012(value);
8403
7821
  this.visemeValues[visemeIndex] = val;
8404
7822
  this.visemeJawScales[visemeIndex] = jawScale;
8405
7823
  this.applyVisemeRuntimeState();
@@ -8411,16 +7829,24 @@ var _Embody = class _Embody {
8411
7829
  }, cancel: () => {
8412
7830
  } };
8413
7831
  }
8414
- const target = clamp013(to);
7832
+ const target = clamp012(to);
8415
7833
  const from = this.visemeValues[visemeIndex] ?? 0;
8416
7834
  this.visemeJawScales[visemeIndex] = jawScale;
7835
+ if (this.wasmRuntimeCore) {
7836
+ this.visemeValues[visemeIndex] = target;
7837
+ this.wasmRuntimeCore.transitionViseme(visemeIndex, target, durationMs, jawScale);
7838
+ if (durationMs <= 0) {
7839
+ this.applyLiveFrameDelta();
7840
+ }
7841
+ return RESOLVED_TRANSITION_HANDLE;
7842
+ }
8417
7843
  return this.animation.addTransition(
8418
7844
  `viseme_value_${visemeIndex}`,
8419
7845
  from,
8420
7846
  target,
8421
7847
  durationMs,
8422
7848
  (value) => {
8423
- this.visemeValues[visemeIndex] = clamp013(value);
7849
+ this.visemeValues[visemeIndex] = clamp012(value);
8424
7850
  this.visemeJawScales[visemeIndex] = jawScale;
8425
7851
  this.applyVisemeRuntimeState();
8426
7852
  }
@@ -8445,8 +7871,7 @@ var _Embody = class _Embody {
8445
7871
  // MIX WEIGHT CONTROL
8446
7872
  // ============================================================================
8447
7873
  setAUMixWeight(id, weight) {
8448
- this.mixWeights[id] = clamp013(weight);
8449
- this.tsRuntimeCore?.setAUMixWeight(id, weight);
7874
+ this.mixWeights[id] = clamp012(weight);
8450
7875
  this.wasmRuntimeCore?.setAUMixWeight(id, weight);
8451
7876
  const v = this.auValues[id] ?? 0;
8452
7877
  if (v > 0) this.setAU(id, v);
@@ -8484,9 +7909,10 @@ var _Embody = class _Embody {
8484
7909
  }
8485
7910
  clearTransitions() {
8486
7911
  this.animation.clearTransitions();
7912
+ this.wasmRuntimeCore?.clearTransitions();
8487
7913
  }
8488
7914
  getActiveTransitionCount() {
8489
- return this.animation.getActiveTransitionCount();
7915
+ return this.animation.getActiveTransitionCount() + (this.wasmRuntimeCore?.activeTransitionCount() ?? 0);
8490
7916
  }
8491
7917
  resetToNeutral() {
8492
7918
  this.auValues = {};
@@ -8496,6 +7922,7 @@ var _Embody = class _Embody {
8496
7922
  this.translations = {};
8497
7923
  this.initBoneRotations();
8498
7924
  this.clearTransitions();
7925
+ this.wasmRuntimeCore?.clear();
8499
7926
  const morphTargetsToReset = [];
8500
7927
  for (const m of this.meshes) {
8501
7928
  const infl = m.morphTargetInfluences;
@@ -8674,7 +8101,18 @@ var _Embody = class _Embody {
8674
8101
  this.bones = this.resolveBones(this.model);
8675
8102
  this.missingBoneWarnings.clear();
8676
8103
  this.rebuildMorphTargetsCache();
8677
- this.tsRuntimeCore?.setProfile(this.config);
8104
+ Object.values(this.bones).forEach((entry) => {
8105
+ if (!entry) return;
8106
+ this.frameApplier.applyObjectTransform(entry.obj, {
8107
+ position: { x: entry.basePos.x, y: entry.basePos.y, z: entry.basePos.z },
8108
+ rotation: {
8109
+ x: entry.baseQuat.x,
8110
+ y: entry.baseQuat.y,
8111
+ z: entry.baseQuat.z,
8112
+ w: entry.baseQuat.w
8113
+ }
8114
+ });
8115
+ });
8678
8116
  this.wasmRuntimeCore?.setProfile(this.config);
8679
8117
  this.refreshHostNeutralModelDescriptor();
8680
8118
  }
@@ -8791,20 +8229,20 @@ var _Embody = class _Embody {
8791
8229
  }
8792
8230
  applyVisemeRuntimeState() {
8793
8231
  for (let index = 0; index < this.visemeValues.length; index += 1) {
8794
- this.tsRuntimeCore?.setViseme(index, this.visemeValues[index] ?? 0, this.visemeJawScales[index] ?? 1);
8795
- this.wasmRuntimeCore?.setViseme(index, this.visemeValues[index] ?? 0);
8232
+ this.wasmRuntimeCore?.setViseme(index, this.visemeValues[index] ?? 0, this.visemeJawScales[index] ?? 1);
8796
8233
  }
8797
- if (!this.applyLiveMorphFrameDelta()) {
8234
+ const appliedViaFrame = this.applyLiveFrameDelta();
8235
+ if (!appliedViaFrame) {
8798
8236
  for (const targets of this.resolvedVisemeTargets) {
8799
8237
  this.frameApplier.resetMorphTargets(targets || []);
8800
8238
  }
8801
8239
  for (let index = 0; index < this.visemeValues.length; index += 1) {
8802
- const value = clamp013(this.visemeValues[index] ?? 0);
8240
+ const value = clamp012(this.visemeValues[index] ?? 0);
8803
8241
  if (value <= 1e-6) continue;
8804
8242
  const targets = this.resolvedVisemeTargets[index] || [];
8805
8243
  for (const target of targets) {
8806
8244
  if (target.idx >= target.infl.length) continue;
8807
- const weighted = clamp013(value * target.weight);
8245
+ const weighted = clamp012(value * target.weight);
8808
8246
  this.frameApplier.applyMorphTargets(
8809
8247
  [target],
8810
8248
  Math.max(target.infl[target.idx] ?? 0, weighted)
@@ -8812,7 +8250,9 @@ var _Embody = class _Embody {
8812
8250
  }
8813
8251
  }
8814
8252
  }
8815
- this.updateBoneRotation(this.getJawBoneNodeKey(), "pitch", this.getActiveVisemeJawAmount());
8253
+ if (!appliedViaFrame) {
8254
+ this.updateBoneRotation(this.getJawBoneNodeKey(), "pitch", this.getActiveVisemeJawAmount());
8255
+ }
8816
8256
  }
8817
8257
  getJawBoneNodeKey() {
8818
8258
  const candidates = [
@@ -8834,7 +8274,7 @@ var _Embody = class _Embody {
8834
8274
  getActiveVisemeJawAmount() {
8835
8275
  let jawAmount = 0;
8836
8276
  for (let index = 0; index < this.visemeValues.length; index += 1) {
8837
- const value = clamp013(this.visemeValues[index] ?? 0);
8277
+ const value = clamp012(this.visemeValues[index] ?? 0);
8838
8278
  if (value <= 1e-6) continue;
8839
8279
  const jawScale = this.visemeJawScales[index] ?? 1;
8840
8280
  if (Math.abs(jawScale) <= 1e-6) continue;
@@ -9117,7 +8557,7 @@ var _Embody = class _Embody {
9117
8557
  return !!(hasMorphs && this.config.auToBones[id]?.length);
9118
8558
  }
9119
8559
  getEffectiveBoneAUValue(auId, nodeKey) {
9120
- const rawValue = clamp013(this.auValues[auId] ?? 0);
8560
+ const rawValue = clamp012(this.auValues[auId] ?? 0);
9121
8561
  if (rawValue <= 1e-6) return 0;
9122
8562
  const binding = this.config.auToBones[auId]?.find((candidate) => candidate.node === nodeKey) ?? null;
9123
8563
  if (!binding?.side) return rawValue;
@@ -9217,7 +8657,7 @@ var _Embody = class _Embody {
9217
8657
  if (config.yaw && rotState.yaw !== 0) {
9218
8658
  const binding = this.getCompositeAxisBindingForNode(nodeKey, config.yaw, rotState.yaw);
9219
8659
  if (binding?.maxDegrees && binding.channel) {
9220
- const radians = deg2rad2(binding.maxDegrees) * Math.abs(rotState.yaw) * binding.scale;
8660
+ const radians = deg2rad(binding.maxDegrees) * Math.abs(rotState.yaw) * binding.scale;
9221
8661
  const axis = getAxis(binding.channel);
9222
8662
  const deltaQ = new THREE.Quaternion().setFromAxisAngle(axis, radians);
9223
8663
  compositeQ.multiply(deltaQ);
@@ -9226,7 +8666,7 @@ var _Embody = class _Embody {
9226
8666
  if (config.pitch && rotState.pitch !== 0) {
9227
8667
  const binding = this.getCompositeAxisBindingForNode(nodeKey, config.pitch, rotState.pitch);
9228
8668
  if (binding?.maxDegrees && binding.channel) {
9229
- const radians = deg2rad2(binding.maxDegrees) * Math.abs(rotState.pitch) * binding.scale;
8669
+ const radians = deg2rad(binding.maxDegrees) * Math.abs(rotState.pitch) * binding.scale;
9230
8670
  const axis = getAxis(binding.channel);
9231
8671
  const deltaQ = new THREE.Quaternion().setFromAxisAngle(axis, radians);
9232
8672
  compositeQ.multiply(deltaQ);
@@ -9235,7 +8675,7 @@ var _Embody = class _Embody {
9235
8675
  if (config.roll && rotState.roll !== 0) {
9236
8676
  const binding = this.getCompositeAxisBindingForNode(nodeKey, config.roll, rotState.roll);
9237
8677
  if (binding?.maxDegrees && binding.channel) {
9238
- const radians = deg2rad2(binding.maxDegrees) * Math.abs(rotState.roll) * binding.scale;
8678
+ const radians = deg2rad(binding.maxDegrees) * Math.abs(rotState.roll) * binding.scale;
9239
8679
  const axis = getAxis(binding.channel);
9240
8680
  const deltaQ = new THREE.Quaternion().setFromAxisAngle(axis, radians);
9241
8681
  compositeQ.multiply(deltaQ);
@@ -10074,11 +9514,11 @@ function applyCharacterModelTransform(model, transform = {}) {
10074
9514
  model.position.set(modelOffset.x ?? 0, modelOffset.y ?? 0, modelOffset.z ?? 0);
10075
9515
  }
10076
9516
  if (modelRotation) {
10077
- const deg2rad3 = Math.PI / 180;
9517
+ const deg2rad2 = Math.PI / 180;
10078
9518
  model.rotation.set(
10079
- (modelRotation.x ?? 0) * deg2rad3,
10080
- (modelRotation.y ?? 0) * deg2rad3,
10081
- (modelRotation.z ?? 0) * deg2rad3
9519
+ (modelRotation.x ?? 0) * deg2rad2,
9520
+ (modelRotation.y ?? 0) * deg2rad2,
9521
+ (modelRotation.z ?? 0) * deg2rad2
10082
9522
  );
10083
9523
  }
10084
9524
  if (modelScale !== void 0 && Number.isFinite(modelScale) && modelScale > 0) {
@@ -15094,7 +14534,7 @@ function computeHumanoidSkeletonTemplateRestBounds(template) {
15094
14534
  }
15095
14535
  const parentPosition = worldPositions.get(bone.parent);
15096
14536
  if (!parentPosition) continue;
15097
- worldPositions.set(bone.name, addVec32(parentPosition, bone.translation));
14537
+ worldPositions.set(bone.name, addVec3(parentPosition, bone.translation));
15098
14538
  unresolved.delete(bone.name);
15099
14539
  resolvedThisPass += 1;
15100
14540
  }
@@ -15155,7 +14595,7 @@ function assertNonEmptyString(value, fieldName) {
15155
14595
  throw new Error(`${fieldName} must be a non-empty string`);
15156
14596
  }
15157
14597
  }
15158
- function addVec32(a, b) {
14598
+ function addVec3(a, b) {
15159
14599
  return [a[0] + b[0], a[1] + b[1], a[2] + b[2]];
15160
14600
  }
15161
14601
 
@@ -15254,7 +14694,6 @@ Object.defineProperty(exports, "resetEmbodyCoreForTests", {
15254
14694
  exports.AU_INFO = AU_INFO;
15255
14695
  exports.AU_MAPPING_CONFIG = AU_MAPPING_CONFIG;
15256
14696
  exports.AU_MIX_DEFAULTS = AU_MIX_DEFAULTS;
15257
- exports.AU_MORPH_BINDING_STRIDE = AU_MORPH_BINDING_STRIDE;
15258
14697
  exports.AU_TO_MORPHS = AU_TO_MORPHS;
15259
14698
  exports.AnimationController = ThreeAnimationSystem;
15260
14699
  exports.BETTA_FISH_PRESET = BETTA_FISH_PRESET;
@@ -15312,10 +14751,8 @@ exports.ThreeClipAdapter = ThreeClipAdapter;
15312
14751
  exports.ThreeFrameApplier = ThreeFrameApplier;
15313
14752
  exports.ThreeModelInspector = ThreeModelInspector;
15314
14753
  exports.TsClipCompiler = TsClipCompiler;
15315
- exports.TsRuntimeCore = TsRuntimeCore;
15316
14754
  exports.VISEME_JAW_AMOUNTS = VISEME_JAW_AMOUNTS;
15317
14755
  exports.VISEME_KEYS = VISEME_KEYS;
15318
- exports.VISEME_MORPH_BINDING_STRIDE = VISEME_MORPH_BINDING_STRIDE;
15319
14756
  exports.WasmRuntimeCore = WasmRuntimeCore;
15320
14757
  exports.analyzeModel = analyzeModel;
15321
14758
  exports.applyAUBoneBindingUpdate = applyAUBoneBindingUpdate;
@@ -15328,8 +14765,6 @@ exports.buildFrameApplierBindings = buildFrameApplierBindings;
15328
14765
  exports.buildMappingEditorModel = buildMappingEditorModel;
15329
14766
  exports.classifyAuAsJointControl = classifyAuAsJointControl;
15330
14767
  exports.collectMorphMeshes = collectMorphMeshes2;
15331
- exports.compileBoneBindings = compileBoneBindings;
15332
- exports.compileMorphBindings = compileMorphBindings;
15333
14768
  exports.compileVisemeKeys = compileVisemeKeys;
15334
14769
  exports.composeTemplateSkeletonFitTransform = composeTemplateSkeletonFitTransform;
15335
14770
  exports.computeCameraRelativeGazeOffset = computeCameraRelativeGazeOffset;