@hology/core 0.0.213 → 0.0.214

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.
@@ -1,4 +1,4 @@
1
- import{describe as e,expect as t,it as n}from"vitest";import{Bone as o,AnimationClip as r,BufferGeometry as a,Group as s,MeshBasicMaterial as i,Quaternion as g,QuaternionKeyframeTrack as c,Skeleton as d,SkinnedMesh as p,Vector3 as l,VectorKeyframeTrack as u}from"three";import{areAnimationRigsCompatible as m,bakeRetargetedAnimationClip as f,createAutoAlignedRetargetPose as b,createDefaultAnimationRetargeter as h,extractAnimationRigs as w,findGeneratedAnimationOutput as B,getAnimationRigChainBoneNames as y,isAnimationClipCompatibleWithRig as L,resolveAnimationRigBasisRotation as k,resolveAnimationClipAssetForTarget as v,validateAnimationRetargeter as R,validateAnimationRig as A}from"../gameplay/animation/retarget.js";function T(e,t=!1){const n=["Root","Hips","Spine","Chest","Neck","Head","LeftUpperArm",...t?["LeftArmTwist"]:[],"LeftHand","RightUpperArm","RightHand","LeftUpLeg","LeftLeg","LeftFoot","RightUpLeg","RightLeg","RightFoot"],o=new Map(n.map(e=>[e,x(e)]));o.get("Root").add(o.get("Hips")),o.get("Hips").add(o.get("Spine"),o.get("LeftUpLeg"),o.get("RightUpLeg")),o.get("Spine").add(o.get("Chest")),o.get("Chest").add(o.get("Neck"),o.get("LeftUpperArm"),o.get("RightUpperArm")),o.get("Neck").add(o.get("Head")),t?(o.get("LeftUpperArm").add(o.get("LeftArmTwist")),o.get("LeftArmTwist").add(o.get("LeftHand"))):o.get("LeftUpperArm").add(o.get("LeftHand")),o.get("RightUpperArm").add(o.get("RightHand")),o.get("LeftUpLeg").add(o.get("LeftLeg")),o.get("LeftLeg").add(o.get("LeftFoot")),o.get("RightUpLeg").add(o.get("RightLeg")),o.get("RightLeg").add(o.get("RightFoot"));const r=new p(new a,new i);return r.name=e,r.add(o.get("Root")),r.bind(new d([...o.values()])),r}function x(e){const t=new o;return t.name=e,t.position.y="Root"===e?0:1,t}function U(e){const t=[...T("Canonical").skeleton.bones],n=new Map(t.map((e,t)=>[e,t])),r=t.map(e=>e.parent instanceof o?n.get(e.parent)??-1:-1);for(const e of t)e.removeFromParent();const s=t.map(e=>{const t=new o;return t.name=e.name,t}),g=new p(new a,new i);g.name=e;for(let e=0;e<t.length;e++){const n=r[e];n<0?g.add(t[e]):s[n].add(t[e]),t[e].add(s[e])}return g.updateMatrixWorld(!0),g.bind(new d(s)),g}function P(e,t){let n=H(e,"LeftHand");for(const o of t){const t=x(o);n.add(t),e.skeleton.bones.push(t),n=t}e.updateMatrixWorld(!0),e.skeleton.calculateInverses()}function H(e,t){const n=e.skeleton.getBoneByName(t);if(null==n)throw new Error(`Missing test bone ${t}`);return n}function M(e,t){return{...e,previewMeshId:t}}e("animation retargeting assets",()=>{n("extracts stable hierarchy and bind-pose fingerprints",()=>{const e=w(T("Body")).rigs[0],n=w(T("Body")).rigs[0];t(e.topologyFingerprint).toBe(n.topologyFingerprint),t(e.bindPoseFingerprint).toBe(n.bindPoseFingerprint),t(e.skinnedMeshName).toBe("Body"),t(e.chains.map(e=>e.role)).toEqual(t.arrayContaining(["spine","leftArm","rightArm","leftLeg","rightLeg"]));const o=T("Body");H(o,"LeftHand").position.x+=.25;const r=w(o).rigs[0];t(r.topologyFingerprint).toBe(e.topologyFingerprint),t(r.bindPoseFingerprint).not.toBe(e.bindPoseFingerprint)}),n("extracts each distinct skeleton and reuses compatible rigs",()=>{const e=new s;e.add(T("BodyA"),T("BodyB")),t(w(e).rigs).toHaveLength(1);const n=T("BodyC");H(n,"LeftHand").position.x+=.1,e.add(n);const o=w(e).rigs;t(o).toHaveLength(2),t(m(M(o[0],"mesh-a"),M(o[0],"mesh-b"))).toBe(!0),t(m(M(o[0],"mesh-a"),M(o[1],"mesh-c"))).toBe(!1)}),n("extracts a rig from a standalone bone hierarchy",()=>{const e=new s,n=new o;n.name="Hips";const r=new o;r.name="Spine",r.position.y=1,n.add(r),e.add(n);const a=w(e);t(a.rigs).toHaveLength(1),t(a.skinnedMeshNames).toEqual(["Hips"]),t(a.rigs[0].skinnedMeshName).toBeUndefined(),t(a.rigs[0].bones.map(e=>e.name)).toEqual(["Hips","Spine"]),t(a.rigs[0].bones[1].parentIndex).toBe(0),t(a.rigs[0].bones[1].inverseBindMatrix).toHaveLength(16)}),n("canonicalizes same-named FBX bone wrappers and prefers a direct skinned hierarchy",()=>{const e=new s,n=U("WrappedLOD"),o=T("DirectLOD");e.add(n,o);const a=w(e),i=w(T("AnimationSource")).rigs[0];t(a.rigs).toHaveLength(1),t(a.skinnedMeshNames).toEqual(["DirectLOD"]),t(a.rigs[0].bones).toHaveLength(i.bones.length),t(a.rigs[0].topologyFingerprint).toBe(i.topologyFingerprint),t(a.rigs[0].bindPoseFingerprint).toBe(i.bindPoseFingerprint),t(A(M(a.rigs[0],"direct"))).toEqual([]);const g=M(a.rigs[0],"source"),d=M(i,"target");g.skinnedMeshName=void 0,g.basisPreset="yUp",d.basisPreset="yUp";const p=f({sourceRig:g,targetRig:d,retargeter:h("source-rig","target-rig",g,d),sourceObject:e,targetObject:T("AnimationTarget"),clip:new r("Wrapped source",1,[new c("Spine.quaternion",[0,1],[0,0,0,1,0,.3826834,0,.9238795])])}).tracks.find(e=>e.name.includes("[Spine]"));t(p).toBeDefined(),t(Array.from(p?.createInterpolant().evaluate(1)??[])).toEqual(t.arrayContaining([t.closeTo(0,5),t.closeTo(.3826834,5),t.closeTo(0,5),t.closeTo(.9238795,5)]));const l=U("StandaloneWrapped"),u=new s;for(;l.children.length>0;)u.add(l.children[0]);const m=w(u).rigs[0];t(m.bones).toHaveLength(i.bones.length),t(m.topologyFingerprint).toBe(i.topologyFingerprint),t(m.bindPoseFingerprint).toBe(i.bindPoseFingerprint)}),n("reports duplicate bone names and stale rig fingerprints",()=>{const e=w(T("Body")).rigs[0],n=M(e,"source-mesh"),o=M(e,"target-mesh"),r=h("source-rig","target-rig",n,o);o.bones[1].name=o.bones[0].name,o.bindPoseFingerprint="changed",t(A(o).map(e=>e.code)).toContain("duplicate-bone-names"),t(R(r,n,o).map(e=>e.code)).toContain("target-rig-changed")}),n("resolves semantic chain bones and generated output indexes",()=>{const e=M(w(T("Body")).rigs[0],"mesh");t(y(e,"leftLeg")).toEqual(["LeftUpLeg","LeftLeg","LeftFoot"]);const n=[{id:"retargeter",name:"A to B",type:"animationRetargeter",animationRetargeter:{...h("a","b",e,e),generatedOutputs:{walk:"walk-b"}}},{id:"walk-b",name:"Walk B",type:"animationclip",anim:{clip:"Walk",rigId:"b"}}];t(B(n,"walk","b")?.id).toBe("walk-b"),t(B(n,"walk","other")).toBeUndefined();const o={id:"walk",name:"Walk",type:"animationclip",anim:{clip:"Walk",rigId:"a"}};t(v(o,n,"b").id).toBe("walk-b"),t(()=>v(o,n,"missing")).toThrow(/Generate this source\/target output/);const a={...o,id:"legacy-walk",anim:{clip:"Walk"}};t(v(a,n,"b")).toBe(a),t(L(new r("Known",1,[new c(".bones[LeftHand].quaternion",[0],[0,0,0,1])]),e)).toBe(!0),t(L(new r("Other",1,[new c(".bones[Tail].quaternion",[0],[0,0,0,1])]),e)).toBe(!1)}),n("bakes target-named tracks at a minimum 60 Hz and maps root axes",()=>{const e=T("SourceBody"),n=T("TargetBody"),o=M(w(e).rigs[0],"source"),a=M(w(n).rigs[0],"target");o.basisPreset="yUp",a.basisPreset="yUp";const s=h("source-rig","target-rig",o,a);s.bakeSettings.framesPerSecond=10,s.rootSettings.translationAxisMap=[3,2,-1];const i=new r("Move",1,[new u(".bones[Root].position",[0,1],[0,0,0,2,0,4]),new c(".bones[Spine].quaternion",[0,1],[0,0,0,1,0,0,0,1])]),g=f({sourceRig:o,targetRig:a,retargeter:s,sourceObject:e,targetObject:n,clip:i});t(g.name).toBe("Move"),t(g.tracks.some(e=>e.name.includes("Spine"))).toBe(!0),t(Math.max(...g.tracks.map(e=>e.times.length))).toBeGreaterThanOrEqual(2);const d=g.tracks.find(e=>e instanceof u);t(d).toBeInstanceOf(u),t(d?.name).toContain("Root"),t(d?.values.at(-3)).toBeCloseTo(4),t(d?.values.at(-1)).toBeCloseTo(-2)}),n("detects a Z-up rig basis and converts root motion into target Y-up space",()=>{const e=T("ZUpSource");for(const t of e.skeleton.bones)t.position.set(t.position.x,0,t.position.y);e.updateMatrixWorld(!0),e.skeleton.calculateInverses();const n=T("YUpTarget"),o=M(w(e).rigs[0],"source"),a=M(w(n).rigs[0],"target"),s=new l(0,0,1).applyQuaternion(k(o));t(s.angleTo(new l(0,1,0))).toBeLessThan(1e-6);const i=h("source-rig","target-rig",o,a),c=b(o,a,i).rotationOffsets[a.retargetRootBoneId];t((new g).fromArray(c).angleTo(new g)).toBeLessThan(1e-6);const d=f({sourceRig:o,targetRig:a,retargeter:i,sourceObject:e,targetObject:n,clip:new r("Vertical",1,[new u(".bones[Root].position",[0,1],[0,0,0,0,0,1])])}).tracks.find(e=>e instanceof u);t(d?.values.at(-2)).toBeCloseTo(1),t(d?.values.at(-1)).toBeCloseTo(0)}),n("combines source pelvis height with root motion when the target pelvis is its root",()=>{const e=T("SourceBody"),n=T("TargetBody"),o=M(w(e).rigs[0],"source"),a=M(w(n).rigs[0],"target");o.basisPreset="yUp",a.basisPreset="yUp",a.retargetRootBoneId=a.pelvisBoneId;const s=h("source-rig","target-rig",o,a),i=f({sourceRig:o,targetRig:a,retargeter:s,sourceObject:e,targetObject:n,clip:new r("Crouch forward",1,[new u(".bones[Root].position",[0,1],[0,0,0,2,0,0]),new u(".bones[Hips].position",[0,1],[0,1,0,0,.6,0])])}).tracks.find(e=>e instanceof u&&e.name.includes("[Hips]"));t(i?.values.at(-3)).toBeCloseTo(2),t(i?.values.at(-2)).toBeCloseTo(.6),t(i?.values.at(-1)).toBeCloseTo(0)}),n("maps semantic chain position when target limb bone counts differ",()=>{const e=T("SourceBody"),n=T("TargetBody",!0),o=M(w(e).rigs[0],"source"),a=M(w(n).rigs[0],"target");o.basisPreset="yUp",a.basisPreset="yUp";const s=h("source-rig","target-rig",o,a),i=new r("Wave",1,[new c(".bones[LeftUpperArm].quaternion",[0,1],[0,0,0,1,0,.3826834,0,.9238795])]),g=f({sourceRig:o,targetRig:a,retargeter:s,sourceObject:e,targetObject:n,clip:i});t(g.tracks.some(e=>e.name.includes("LeftArmTwist"))).toBe(!0),t(g.tracks.every(e=>!1===e.name.includes("Target"))).toBe(!0)}),n("transfers bind-relative rotation across different root coordinate bases",()=>{const e=T("SourceBody"),n=T("TargetBody");H(n,"Root").quaternion.setFromAxisAngle(new l(1,0,0),Math.PI/2),e.updateMatrixWorld(!0),n.updateMatrixWorld(!0),n.skeleton.calculateInverses();const o=M(w(e).rigs[0],"source"),a=M(w(n).rigs[0],"target");o.basisPreset="yUp",a.basisPreset="yUp";const s=h("source-rig","target-rig",o,a),i=(new g).setFromAxisAngle(new l(0,1,0),Math.PI/3),d=new r("Turn",1,[new c(".bones[Spine].quaternion",[0,1],[0,0,0,1,...i.toArray()])]),p=H(e,"Spine"),u=H(n,"Spine"),m=p.getWorldQuaternion(new g),b=u.getWorldQuaternion(new g);p.quaternion.copy(i),e.updateMatrixWorld(!0);const B=p.getWorldQuaternion(new g).multiply(m.clone().invert()).multiply(b),y=f({sourceRig:o,targetRig:a,retargeter:s,sourceObject:e,targetObject:n,clip:d}).tracks.find(e=>e.name.includes("[Spine]"));t(y).toBeInstanceOf(c),u.quaternion.fromArray(y.values,y.values.length-4),n.updateMatrixWorld(!0),t(u.getWorldQuaternion(new g).angleTo(B)).toBeLessThan(1e-5)}),n("transfers root displacement through the target root parent basis",()=>{const e=T("SourceBody"),n=T("TargetBody");n.quaternion.setFromAxisAngle(new l(1,0,0),-Math.PI/2),H(n,"Root").quaternion.setFromAxisAngle(new l(1,0,0),Math.PI/2),e.updateMatrixWorld(!0),n.updateMatrixWorld(!0),n.skeleton.calculateInverses();const o=M(w(e).rigs[0],"source"),a=M(w(n).rigs[0],"target");o.basisPreset="yUp",a.basisPreset="yUp";const s=h("source-rig","target-rig",o,a),i=new r("Move forward",1,[new u(".bones[Root].position",[0,1],[0,0,0,0,0,2])]),g=f({sourceRig:o,targetRig:a,retargeter:s,sourceObject:e,targetObject:n,clip:i}).tracks.find(e=>e instanceof u);t(g?.values.at(-3)).toBeCloseTo(0),t(g?.values.at(-2)).toBeCloseTo(-2),t(g?.values.at(-1)).toBeCloseTo(0)}),n("maps finger segments with different humanoid naming conventions",()=>{const e=T("SourceBody"),n=T("TargetBody");P(e,["mixamorigLeftHandIndex1","mixamorigLeftHandIndex2"]),P(n,["index1l","index2l"]);const o=M(w(e).rigs[0],"source"),a=M(w(n).rigs[0],"target"),s=h("source-rig","target-rig",o,a),i=(new g).setFromAxisAngle(new l(1,0,0),Math.PI/3),d=new r("Grip",1,[new c(".bones[mixamorigLeftHandIndex1].quaternion",[0,1],[0,0,0,1,...i.toArray()])]),p=f({sourceRig:o,targetRig:a,retargeter:s,sourceObject:e,targetObject:n,clip:d});t(p.tracks.some(e=>e.name.includes("[index1l]"))).toBe(!0),t(p.tracks.some(e=>e.name.includes("[index2l]"))).toBe(!0)}),n("creates a target pose that aligns mapped limb directions",()=>{const e=T("SourceBody"),n=T("TargetBody");H(n,"LeftUpperArm").quaternion.setFromAxisAngle(new l(0,0,1),Math.PI/4),n.updateMatrixWorld(!0),n.skeleton.calculateInverses();const o=M(w(e).rigs[0],"source"),r=M(w(n).rigs[0],"target"),a=h("source-rig","target-rig",o,r),s=b(o,r,a),i=H(n,"LeftUpperArm");i.quaternion.fromArray(r.bones.find(e=>"LeftUpperArm"===e.name).quaternion).multiply((new g).fromArray(s.rotationOffsets[r.bones.find(e=>"LeftUpperArm"===e.name).id])),n.updateMatrixWorld(!0);const c=H(n,"LeftHand").getWorldPosition(new l).sub(i.getWorldPosition(new l)).normalize(),d=H(e,"LeftHand").getWorldPosition(new l).sub(H(e,"LeftUpperArm").getWorldPosition(new l)).normalize();t(c.angleTo(d)).toBeLessThan(1e-5)})});/*
1
+ import{describe as e,expect as t,it as o}from"vitest";import{Bone as n,AnimationClip as r,BufferGeometry as s,Group as a,MeshBasicMaterial as i,Quaternion as g,QuaternionKeyframeTrack as c,Skeleton as d,SkinnedMesh as p,Vector3 as l,VectorKeyframeTrack as u}from"three";import{areAnimationRigsCompatible as m,bakeRetargetedAnimationClip as b,createAutoAlignedRetargetPose as f,createDefaultAnimationRetargeter as w,extractAnimationRigs as h,findGeneratedAnimationOutput as B,getAnimationRigChainBoneNames as y,isAnimationClipCompatibleWithRig as L,resolveAnimationRigBasisRotation as k,resolveAnimationClipAssetForTarget as A,validateAnimationRetargeter as R,validateAnimationRig as v}from"../gameplay/animation/retarget.js";function T(e,t=!1){const o=["Root","Hips","Spine","Chest","Neck","Head","LeftUpperArm",...t?["LeftArmTwist"]:[],"LeftHand","RightUpperArm","RightHand","LeftUpLeg","LeftLeg","LeftFoot","RightUpLeg","RightLeg","RightFoot"],n=new Map(o.map(e=>[e,x(e)]));n.get("Root").add(n.get("Hips")),n.get("Hips").add(n.get("Spine"),n.get("LeftUpLeg"),n.get("RightUpLeg")),n.get("Spine").add(n.get("Chest")),n.get("Chest").add(n.get("Neck"),n.get("LeftUpperArm"),n.get("RightUpperArm")),n.get("Neck").add(n.get("Head")),t?(n.get("LeftUpperArm").add(n.get("LeftArmTwist")),n.get("LeftArmTwist").add(n.get("LeftHand"))):n.get("LeftUpperArm").add(n.get("LeftHand")),n.get("RightUpperArm").add(n.get("RightHand")),n.get("LeftUpLeg").add(n.get("LeftLeg")),n.get("LeftLeg").add(n.get("LeftFoot")),n.get("RightUpLeg").add(n.get("RightLeg")),n.get("RightLeg").add(n.get("RightFoot"));const r=new p(new s,new i);return r.name=e,r.add(n.get("Root")),r.bind(new d([...n.values()])),r}function x(e){const t=new n;return t.name=e,t.position.y="Root"===e?0:1,t}function P(e,t=new g){const o=F(T(`${e}Skeleton`),"Root");o.removeFromParent();const n=new a;n.name=e,n.quaternion.copy(t),n.add(o);const r=new a;return r.add(n),r.updateMatrixWorld(!0),r}function U(e){const t=[...T("Canonical").skeleton.bones],o=new Map(t.map((e,t)=>[e,t])),r=t.map(e=>e.parent instanceof n?o.get(e.parent)??-1:-1);for(const e of t)e.removeFromParent();const a=t.map(e=>{const t=new n;return t.name=e.name,t}),g=new p(new s,new i);g.name=e;for(let e=0;e<t.length;e++){const o=r[e];o<0?g.add(t[e]):a[o].add(t[e]),t[e].add(a[e])}return g.updateMatrixWorld(!0),g.bind(new d(a)),g}function M(e,t){let o=F(e,"LeftHand");for(const n of t){const t=x(n);o.add(t),e.skeleton.bones.push(t),o=t}e.updateMatrixWorld(!0),e.skeleton.calculateInverses()}function F(e,t){const o=e.skeleton.getBoneByName(t);if(null==o)throw new Error(`Missing test bone ${t}`);return o}function S(e,t){return{...e,previewMeshId:t}}e("animation retargeting assets",()=>{o("extracts stable hierarchy and bind-pose fingerprints",()=>{const e=h(T("Body")).rigs[0],o=h(T("Body")).rigs[0];t(e.topologyFingerprint).toBe(o.topologyFingerprint),t(e.bindPoseFingerprint).toBe(o.bindPoseFingerprint),t(e.skinnedMeshName).toBe("Body"),t(e.chains.map(e=>e.role)).toEqual(t.arrayContaining(["spine","leftArm","rightArm","leftLeg","rightLeg"]));const n=T("Body");F(n,"LeftHand").position.x+=.25;const r=h(n).rigs[0];t(r.topologyFingerprint).toBe(e.topologyFingerprint),t(r.bindPoseFingerprint).not.toBe(e.bindPoseFingerprint)}),o("extracts each distinct skeleton and reuses compatible rigs",()=>{const e=new a;e.add(T("BodyA"),T("BodyB")),t(h(e).rigs).toHaveLength(1);const o=T("BodyC");F(o,"LeftHand").position.x+=.1,e.add(o);const n=h(e).rigs;t(n).toHaveLength(2),t(m(S(n[0],"mesh-a"),S(n[0],"mesh-b"))).toBe(!0),t(m(S(n[0],"mesh-a"),S(n[1],"mesh-c"))).toBe(!1)}),o("extracts a rig from a standalone bone hierarchy",()=>{const e=new a,o=new n;o.name="Hips";const r=new n;r.name="Spine",r.position.y=1,o.add(r),e.add(o);const s=h(e);t(s.rigs).toHaveLength(1),t(s.skinnedMeshNames).toEqual(["Hips"]),t(s.rigs[0].skinnedMeshName).toBeUndefined(),t(s.rigs[0].bones.map(e=>e.name)).toEqual(["Hips","Spine"]),t(s.rigs[0].bones[1].parentIndex).toBe(0),t(s.rigs[0].bones[1].inverseBindMatrix).toHaveLength(16)}),o("canonicalizes same-named FBX bone wrappers and prefers a direct skinned hierarchy",()=>{const e=new a,o=U("WrappedLOD"),n=T("DirectLOD");e.add(o,n);const s=h(e),i=h(T("AnimationSource")).rigs[0];t(s.rigs).toHaveLength(1),t(s.skinnedMeshNames).toEqual(["DirectLOD"]),t(s.rigs[0].bones).toHaveLength(i.bones.length),t(s.rigs[0].topologyFingerprint).toBe(i.topologyFingerprint),t(s.rigs[0].bindPoseFingerprint).toBe(i.bindPoseFingerprint),t(v(S(s.rigs[0],"direct"))).toEqual([]);const g=S(s.rigs[0],"source"),d=S(i,"target");g.skinnedMeshName=void 0,g.basisPreset="yUp",d.basisPreset="yUp";const p=b({sourceRig:g,targetRig:d,retargeter:w("source-rig","target-rig",g,d),sourceObject:e,targetObject:T("AnimationTarget"),clip:new r("Wrapped source",1,[new c("Spine.quaternion",[0,1],[0,0,0,1,0,.3826834,0,.9238795])])}).tracks.find(e=>e.name.includes("[Spine]"));t(p).toBeDefined(),t(Array.from(p?.createInterpolant().evaluate(1)??[])).toEqual(t.arrayContaining([t.closeTo(0,5),t.closeTo(.3826834,5),t.closeTo(0,5),t.closeTo(.9238795,5)]));const l=U("StandaloneWrapped"),u=new a;for(;l.children.length>0;)u.add(l.children[0]);const m=h(u).rigs[0];t(m.bones).toHaveLength(i.bones.length),t(m.topologyFingerprint).toBe(i.topologyFingerprint),t(m.bindPoseFingerprint).toBe(i.bindPoseFingerprint)}),o("reports duplicate bone names and stale rig fingerprints",()=>{const e=h(T("Body")).rigs[0],o=S(e,"source-mesh"),n=S(e,"target-mesh"),r=w("source-rig","target-rig",o,n);n.bones[1].name=n.bones[0].name,n.bindPoseFingerprint="changed",t(v(n).map(e=>e.code)).toContain("duplicate-bone-names"),t(R(r,o,n).map(e=>e.code)).toContain("target-rig-changed")}),o("resolves semantic chain bones and generated output indexes",()=>{const e=S(h(T("Body")).rigs[0],"mesh");t(y(e,"leftLeg")).toEqual(["LeftUpLeg","LeftLeg","LeftFoot"]);const o=[{id:"retargeter",name:"A to B",type:"animationRetargeter",animationRetargeter:{...w("a","b",e,e),generatedOutputs:{walk:"walk-b"}}},{id:"walk-b",name:"Walk B",type:"animationclip",anim:{clip:"Walk",rigId:"b"}}];t(B(o,"walk","b")?.id).toBe("walk-b"),t(B(o,"walk","other")).toBeUndefined();const n={id:"walk",name:"Walk",type:"animationclip",anim:{clip:"Walk",rigId:"a"}};t(A(n,o,"b").id).toBe("walk-b"),t(()=>A(n,o,"missing")).toThrow(/Generate this source\/target output/);const s={...n,id:"legacy-walk",anim:{clip:"Walk"}};t(A(s,o,"b")).toBe(s),t(L(new r("Known",1,[new c(".bones[LeftHand].quaternion",[0],[0,0,0,1])]),e)).toBe(!0),t(L(new r("Other",1,[new c(".bones[Tail].quaternion",[0],[0,0,0,1])]),e)).toBe(!1)}),o("bakes target-named tracks at a minimum 60 Hz and maps root axes",()=>{const e=T("SourceBody"),o=T("TargetBody"),n=S(h(e).rigs[0],"source"),s=S(h(o).rigs[0],"target");n.basisPreset="yUp",s.basisPreset="yUp";const a=w("source-rig","target-rig",n,s);a.bakeSettings.framesPerSecond=10,a.rootSettings.translationAxisMap=[3,2,-1];const i=new r("Move",1,[new u(".bones[Root].position",[0,1],[0,0,0,2,0,4]),new c(".bones[Spine].quaternion",[0,1],[0,0,0,1,0,0,0,1])]),g=b({sourceRig:n,targetRig:s,retargeter:a,sourceObject:e,targetObject:o,clip:i});t(g.name).toBe("Move"),t(g.tracks.some(e=>e.name.includes("Spine"))).toBe(!0),t(Math.max(...g.tracks.map(e=>e.times.length))).toBeGreaterThanOrEqual(2);const d=g.tracks.find(e=>e instanceof u);t(d).toBeInstanceOf(u),t(d?.name).toContain("Root"),t(d?.values.at(-3)).toBeCloseTo(4),t(d?.values.at(-1)).toBeCloseTo(-2)}),o("bakes from a standalone source skeleton without a skinned mesh",()=>{const e=P("AnimationSource"),o=T("TargetBody"),n=S(h(e).rigs[0],"source"),s=S(h(o).rigs[0],"target");n.basisPreset="yUp",s.basisPreset="yUp";const a=w("source-rig","target-rig",n,s),i=(new g).setFromAxisAngle(new l(0,1,0),Math.PI/3),d=b({sourceRig:n,targetRig:s,retargeter:a,sourceObject:e,targetObject:o,clip:new r("Animation only",1,[new c(".bones[Spine].quaternion",[0,1],[0,0,0,1,...i.toArray()])])});t(d.tracks.some(e=>e.name.includes("[Spine]"))).toBe(!0)}),o("preserves the imported parent basis of a standalone source skeleton",()=>{const e=P("RotatedAnimationSource",(new g).setFromAxisAngle(new l(0,1,0),Math.PI/2)),o=T("TargetBody"),n=S(h(e).rigs[0],"source"),s=S(h(o).rigs[0],"target");n.basisPreset="yUp",s.basisPreset="yUp";const a=w("source-rig","target-rig",n,s),i=b({sourceRig:n,targetRig:s,retargeter:a,sourceObject:e,targetObject:o,clip:new r("Move forward",1,[new u(".bones[Root].position",[0,1],[0,0,0,0,0,2])])}).tracks.find(e=>e instanceof u);t(i?.values.at(-3)).toBeCloseTo(2),t(i?.values.at(-2)).toBeCloseTo(0),t(i?.values.at(-1)).toBeCloseTo(0)}),o("detects a Z-up rig basis and converts root motion into target Y-up space",()=>{const e=T("ZUpSource");for(const t of e.skeleton.bones)t.position.set(t.position.x,0,t.position.y);e.updateMatrixWorld(!0),e.skeleton.calculateInverses();const o=T("YUpTarget"),n=S(h(e).rigs[0],"source"),s=S(h(o).rigs[0],"target"),a=new l(0,0,1).applyQuaternion(k(n));t(a.angleTo(new l(0,1,0))).toBeLessThan(1e-6);const i=w("source-rig","target-rig",n,s),c=f(n,s,i).rotationOffsets[s.retargetRootBoneId];t((new g).fromArray(c).angleTo(new g)).toBeLessThan(1e-6);const d=b({sourceRig:n,targetRig:s,retargeter:i,sourceObject:e,targetObject:o,clip:new r("Vertical",1,[new u(".bones[Root].position",[0,1],[0,0,0,0,0,1])])}).tracks.find(e=>e instanceof u);t(d?.values.at(-2)).toBeCloseTo(1),t(d?.values.at(-1)).toBeCloseTo(0)}),o("combines source pelvis height with root motion when the target pelvis is its root",()=>{const e=T("SourceBody"),o=T("TargetBody"),n=S(h(e).rigs[0],"source"),s=S(h(o).rigs[0],"target");n.basisPreset="yUp",s.basisPreset="yUp",s.retargetRootBoneId=s.pelvisBoneId;const a=w("source-rig","target-rig",n,s),i=b({sourceRig:n,targetRig:s,retargeter:a,sourceObject:e,targetObject:o,clip:new r("Crouch forward",1,[new u(".bones[Root].position",[0,1],[0,0,0,2,0,0]),new u(".bones[Hips].position",[0,1],[0,1,0,0,.6,0])])}).tracks.find(e=>e instanceof u&&e.name.includes("[Hips]"));t(i?.values.at(-3)).toBeCloseTo(2),t(i?.values.at(-2)).toBeCloseTo(.6),t(i?.values.at(-1)).toBeCloseTo(0)}),o("maps semantic chain position when target limb bone counts differ",()=>{const e=T("SourceBody"),o=T("TargetBody",!0),n=S(h(e).rigs[0],"source"),s=S(h(o).rigs[0],"target");n.basisPreset="yUp",s.basisPreset="yUp";const a=w("source-rig","target-rig",n,s),i=new r("Wave",1,[new c(".bones[LeftUpperArm].quaternion",[0,1],[0,0,0,1,0,.3826834,0,.9238795])]),g=b({sourceRig:n,targetRig:s,retargeter:a,sourceObject:e,targetObject:o,clip:i});t(g.tracks.some(e=>e.name.includes("LeftArmTwist"))).toBe(!0),t(g.tracks.every(e=>!1===e.name.includes("Target"))).toBe(!0)}),o("transfers bind-relative rotation across different root coordinate bases",()=>{const e=T("SourceBody"),o=T("TargetBody");F(o,"Root").quaternion.setFromAxisAngle(new l(1,0,0),Math.PI/2),e.updateMatrixWorld(!0),o.updateMatrixWorld(!0),o.skeleton.calculateInverses();const n=S(h(e).rigs[0],"source"),s=S(h(o).rigs[0],"target");n.basisPreset="yUp",s.basisPreset="yUp";const a=w("source-rig","target-rig",n,s),i=(new g).setFromAxisAngle(new l(0,1,0),Math.PI/3),d=new r("Turn",1,[new c(".bones[Spine].quaternion",[0,1],[0,0,0,1,...i.toArray()])]),p=F(e,"Spine"),u=F(o,"Spine"),m=p.getWorldQuaternion(new g),f=u.getWorldQuaternion(new g);p.quaternion.copy(i),e.updateMatrixWorld(!0);const B=p.getWorldQuaternion(new g).multiply(m.clone().invert()).multiply(f),y=b({sourceRig:n,targetRig:s,retargeter:a,sourceObject:e,targetObject:o,clip:d}).tracks.find(e=>e.name.includes("[Spine]"));t(y).toBeInstanceOf(c),u.quaternion.fromArray(y.values,y.values.length-4),o.updateMatrixWorld(!0),t(u.getWorldQuaternion(new g).angleTo(B)).toBeLessThan(1e-5)}),o("transfers root displacement through the target root parent basis",()=>{const e=T("SourceBody"),o=T("TargetBody");o.quaternion.setFromAxisAngle(new l(1,0,0),-Math.PI/2),F(o,"Root").quaternion.setFromAxisAngle(new l(1,0,0),Math.PI/2),e.updateMatrixWorld(!0),o.updateMatrixWorld(!0),o.skeleton.calculateInverses();const n=S(h(e).rigs[0],"source"),s=S(h(o).rigs[0],"target");n.basisPreset="yUp",s.basisPreset="yUp";const a=w("source-rig","target-rig",n,s),i=new r("Move forward",1,[new u(".bones[Root].position",[0,1],[0,0,0,0,0,2])]),g=b({sourceRig:n,targetRig:s,retargeter:a,sourceObject:e,targetObject:o,clip:i}).tracks.find(e=>e instanceof u);t(g?.values.at(-3)).toBeCloseTo(0),t(g?.values.at(-2)).toBeCloseTo(-2),t(g?.values.at(-1)).toBeCloseTo(0)}),o("maps finger segments with different humanoid naming conventions",()=>{const e=T("SourceBody"),o=T("TargetBody");M(e,["mixamorigLeftHandIndex1","mixamorigLeftHandIndex2"]),M(o,["index1l","index2l"]);const n=S(h(e).rigs[0],"source"),s=S(h(o).rigs[0],"target"),a=w("source-rig","target-rig",n,s),i=(new g).setFromAxisAngle(new l(1,0,0),Math.PI/3),d=new r("Grip",1,[new c(".bones[mixamorigLeftHandIndex1].quaternion",[0,1],[0,0,0,1,...i.toArray()])]),p=b({sourceRig:n,targetRig:s,retargeter:a,sourceObject:e,targetObject:o,clip:d});t(p.tracks.some(e=>e.name.includes("[index1l]"))).toBe(!0),t(p.tracks.some(e=>e.name.includes("[index2l]"))).toBe(!0)}),o("creates a target pose that aligns mapped limb directions",()=>{const e=T("SourceBody"),o=T("TargetBody");F(o,"LeftUpperArm").quaternion.setFromAxisAngle(new l(0,0,1),Math.PI/4),o.updateMatrixWorld(!0),o.skeleton.calculateInverses();const n=S(h(e).rigs[0],"source"),r=S(h(o).rigs[0],"target"),s=w("source-rig","target-rig",n,r),a=f(n,r,s),i=F(o,"LeftUpperArm");i.quaternion.fromArray(r.bones.find(e=>"LeftUpperArm"===e.name).quaternion).multiply((new g).fromArray(a.rotationOffsets[r.bones.find(e=>"LeftUpperArm"===e.name).id])),o.updateMatrixWorld(!0);const c=F(o,"LeftHand").getWorldPosition(new l).sub(i.getWorldPosition(new l)).normalize(),d=F(e,"LeftHand").getWorldPosition(new l).sub(F(e,"LeftUpperArm").getWorldPosition(new l)).normalize();t(c.angleTo(d)).toBeLessThan(1e-5)}),o("auto-aligns using a root bone external parent from the bind pose",()=>{const e=T("SourceBody"),o=T("TargetBody");o.quaternion.setFromAxisAngle(new l(1,0,0),-Math.PI/2),F(o,"Root").quaternion.setFromAxisAngle(new l(1,0,0),Math.PI/2),o.updateMatrixWorld(!0),o.skeleton.calculateInverses();const n=S(h(e).rigs[0],"source"),r=S(h(o).rigs[0],"target");n.basisPreset="yUp",r.basisPreset="yUp";const s=w("source-rig","target-rig",n,r),a=f(n,r,s),i=r.bones.find(e=>"Spine"===e.name).id,c=(new g).fromArray(a.rotationOffsets[i]??[0,0,0,1]);t(c.angleTo(new g)).toBeLessThan(1e-5)})});/*
2
2
  * Copyright (©) 2026 Hology Interactive AB. All rights reserved.
3
3
  * See the LICENSE.md file for details.
4
4
  */
@@ -1,4 +1,4 @@
1
- import{__decorate as e,__metadata as a}from"tslib";import{describe as t,expect as r,it as n,vi as o}from"vitest";import{Scene as i}from"three";import{Subject as s}from"rxjs";o.hoisted(()=>{if("undefined"==typeof HTMLCanvasElement)return;const e=new Proxy({},{get:(e,a)=>(a in e||(e[a]=("string"!=typeof a||!a.startsWith("is"))&&o.fn()),e[a]),set:(e,a,t)=>(e[a]=t,!0)});Object.defineProperty(HTMLCanvasElement.prototype,"getContext",{configurable:!0,value:()=>e}),"undefined"==typeof AudioBuffer&&Object.defineProperty(globalThis,"AudioBuffer",{configurable:!0,value:class{}})}),o.mock("../gameplay/actors/builtin/index.js",()=>({default:{}})),o.mock("../effects/vfx/vfx-actor.js",()=>({VfxActor:class{}})),o.mock("../effects/vfx/vfx-param.js",()=>({VisualEffect:class{}})),o.mock("../rendering.js",()=>({RenderingView:class{},setRenderingPaused:o.fn()}));import{BaseActor as c}from"../gameplay/actors/actor.js";import{ActorComponent as p}from"../gameplay/actors/component.js";import{Parameter as m}from"../shader/parameter.js";import{SceneMaterializer as l}from"../scene/materializer.js";import{SerializedParamType as f}from"../scene/model.js";class u extends p{constructor(){super(...arguments),this.animationPreset="mixamo"}}e([m(),a("design:type",Object)],u.prototype,"animationPreset",void 0);class d extends c{constructor(){super(...arguments),this.animation=new u,this.animationPreset="mixamo"}}e([m(),a("design:type",Object)],d.prototype,"animationPreset",void 0);class y extends c{constructor(){super(...arguments),this.value="before"}applySceneParameters(e,a){return!(1!==a.size||!a.has("value"))&&(this.value=e.value,!0)}}e([m(),a("design:type",Object)],y.prototype,"value",void 0);class v extends p{constructor(){super(...arguments),this.value="before"}applySceneParameters(e,a){return!(1!==a.size||!a.has("value"))&&(this.value=e.value,!0)}}e([m(),a("design:type",Object)],v.prototype,"value",void 0);class b extends c{constructor(){super(...arguments),this.component=new v}}class P extends c{}async function g(e){return(await h(e)).actor}async function h(e){const a=(t="prefab-asset",r=[A({id:"prefab-actor",type:"actor",actor:{type:"PrefabParamActor",params:{animationPreset:{type:f.String,value:e.prefabActorValue}},innerParams:null==e.prefabComponentValue?[]:[{path:["animation"],params:{animationPreset:{type:f.String,value:e.prefabComponentValue}}}],components:[]}})],{id:t,name:t,type:"prefab",prefab:{objects:r,mainActorId:"prefab-actor"}});var t,r;const n=A({id:"prefab-instance",type:"prefab",assetId:a.id,prefab:{mainActorId:"prefab-actor",params:null==e.instanceParam?{}:{animationPreset:e.instanceParam},innerParams:null==e.instanceInnerParam?[]:[{path:["animation"],params:{animationPreset:e.instanceInnerParam}}]}}),{materializer:o}=w([n],new Map([[a.id,a]]));return await o.materialize(n),await o.initActorsPostInit(),{actor:o.materializedActors.get("prefab-instance/prefab-actor"),materializer:o}}function w(e,a){const t={getObjects:()=>e,onCreate:()=>{},onUpdate:()=>{},onRemove:()=>{}},r={onCreate:new s,onDelete:new s,onUpdate:new s,getAsset:o.fn(async e=>a.get(e)??null),getAssets:o.fn(async()=>Array.from(a.values()))},n={create:o.fn(async(e,a)=>{const t=new e;return null!=a&&t.position.copy(a),t}),initActor:o.fn(async e=>{e.__isInitialised=!0})};return{materializer:new l(new i,t,r,{},{renderer:{shadowMap:{needsUpdate:!1}}},[],[{name:"PrefabParamActor",type:d},{name:"SceneAttachedComponentActor",type:P}],n,[{name:"LiveParamComponent",type:v}])}}function A(e){return{name:e.id,position:[0,0,0],rotation:[0,0,0,"XYZ"],scale:[1,1,1],...e}}t("prefab instance actor params",()=>{n("does not let generated prefab instance defaults override the prefab actor value",async()=>{const e=await g({prefabActorValue:"basic",instanceParam:{type:f.String,value:"mixamo"}});r(e.animationPreset).toBe("basic")}),n("applies prefab instance params when the override is explicit",async()=>{const e=await g({prefabActorValue:"basic",instanceParam:{type:f.String,value:"mixamo",override:!0}});r(e.animationPreset).toBe("mixamo")}),n("does not let generated prefab instance component defaults override prefab component values",async()=>{const e=await g({prefabActorValue:"basic",prefabComponentValue:"basic",instanceInnerParam:{type:f.String,value:"mixamo"}});r(e.animation.animationPreset).toBe("basic")}),n("applies prefab instance component params when the override is explicit",async()=>{const e=await g({prefabActorValue:"basic",prefabComponentValue:"basic",instanceInnerParam:{type:f.String,value:"mixamo",override:!0}});r(e.animation.animationPreset).toBe("mixamo")}),n("resolves direct scene actors and exposed prefab actors by scene object id",async()=>{const{actor:e,materializer:a}=await h({prefabActorValue:"basic"}),t=new d;a.materializedActors.set("direct-actor",t),r(a.resolveSceneActor("direct-actor")).toBe(t),r(a.resolveSceneActor("prefab-instance")).toBe(e)}),n("applies supported scene parameter changes without recreating the actor",async()=>{const e=A({id:"live-actor",type:"actor",actor:{type:"LiveParamActor",params:{value:{type:f.String,value:"after"}},innerParams:[],components:[]}}),a={...e.actor,params:{value:{type:f.String,value:"before"}}},{materializer:t}=w([e],new Map),n=new y,o=await t.tryApplyLiveActorParameterUpdate(n,e,JSON.stringify(a));r(o).toBe(!0),r(n.value).toBe("after")}),n("applies supported component parameter changes without recreating the actor",async()=>{const e=A({id:"live-component",type:"actor",actor:{type:"LiveComponentParamActor",params:{},innerParams:[{path:["component"],params:{value:{type:f.String,value:"after"}}}],components:[]}}),a={...e.actor,innerParams:[{path:["component"],params:{value:{type:f.String,value:"before"}}}]},{materializer:t}=w([e],new Map),n=new b,o=await t.tryApplyLiveActorParameterUpdate(n,e,JSON.stringify(a));r(o).toBe(!0),r(n.component.value).toBe("after")}),n("applies supported scene-attached component parameter changes without recreating the actor",async()=>{const e=A({id:"scene-attached-component",type:"actor",actor:{type:"SceneAttachedComponentActor",params:{},innerParams:[],components:[{type:"LiveParamComponent",params:{value:{type:f.String,value:"before"}},innerParams:[]}]}}),{materializer:a}=w([e],new Map);await a.materialize(e),await a.initActorsPostInit();const t=a.materializedActors.get(e.id),n=t.attachedComponents[0],o=JSON.stringify(e.actor);e.actor.components[0].params.value={type:f.String,value:"after"};const i=await a.tryApplyLiveActorParameterUpdate(t,e,o);r(i).toBe(!0),r(n.value).toBe("after"),r(t.attachedComponents[0]).toBe(n)})});/*
1
+ import{__decorate as e,__metadata as a}from"tslib";import{describe as t,expect as r,it as n,vi as o}from"vitest";import{Scene as i}from"three";import{Subject as s}from"rxjs";o.hoisted(()=>{if("undefined"==typeof HTMLCanvasElement)return;const e=new Proxy({},{get:(e,a)=>(a in e||(e[a]=("string"!=typeof a||!a.startsWith("is"))&&o.fn()),e[a]),set:(e,a,t)=>(e[a]=t,!0)});Object.defineProperty(HTMLCanvasElement.prototype,"getContext",{configurable:!0,value:()=>e}),"undefined"==typeof AudioBuffer&&Object.defineProperty(globalThis,"AudioBuffer",{configurable:!0,value:class{}})}),o.mock("../gameplay/actors/builtin/index.js",()=>({default:{}})),o.mock("../effects/vfx/vfx-actor.js",()=>({VfxActor:class{}})),o.mock("../effects/vfx/vfx-param.js",()=>({VisualEffect:class{}})),o.mock("../rendering.js",()=>({RenderingView:class{},setRenderingPaused:o.fn()}));import{BaseActor as c}from"../gameplay/actors/actor.js";import{ActorComponent as p}from"../gameplay/actors/component.js";import{Parameter as m}from"../shader/parameter.js";import{SceneMaterializer as l}from"../scene/materializer.js";import{SerializedParamType as u}from"../scene/model.js";class f extends p{constructor(){super(...arguments),this.animationPreset="mixamo"}}e([m(),a("design:type",Object)],f.prototype,"animationPreset",void 0);class d extends c{constructor(){super(...arguments),this.animation=new f,this.animationPreset="mixamo"}}e([m(),a("design:type",Object)],d.prototype,"animationPreset",void 0);class v extends c{constructor(){super(...arguments),this.value="before"}applySceneParameters(e,a){return!(1!==a.size||!a.has("value"))&&(this.value=e.value,!0)}}e([m(),a("design:type",Object)],v.prototype,"value",void 0);class y extends p{constructor(){super(...arguments),this.value="before"}applySceneParameters(e,a){return!(1!==a.size||!a.has("value"))&&(this.value=e.value,!0)}}e([m(),a("design:type",Object)],y.prototype,"value",void 0);class b extends c{constructor(){super(...arguments),this.component=new y}}class P extends c{}async function g(e){return(await w(e)).actor}async function w(e){const a=x("prefab-asset",[A({id:"prefab-actor",type:"actor",actor:{type:"PrefabParamActor",params:{animationPreset:{type:u.String,value:e.prefabActorValue}},innerParams:null==e.prefabComponentValue?[]:[{path:["animation"],params:{animationPreset:{type:u.String,value:e.prefabComponentValue}}}],components:[]}})],"prefab-actor"),t=A({id:"prefab-instance",type:"prefab",assetId:a.id,prefab:{mainActorId:"prefab-actor",params:null==e.instanceParam?{}:{animationPreset:e.instanceParam},innerParams:null==e.instanceInnerParam?[]:[{path:["animation"],params:{animationPreset:e.instanceInnerParam}}]}}),{materializer:r}=h([t],new Map([[a.id,a]]));return await r.materialize(t),await r.initActorsPostInit(),{actor:r.materializedActors.get("prefab-instance/prefab-actor"),materializer:r}}function h(e,a,t=!1){const r={getObjects:()=>e,onCreate:()=>{},onUpdate:()=>{},onRemove:()=>{}},n={supportsLivePrefabParameters:t,onCreate:new s,onDelete:new s,onUpdate:new s,getAsset:o.fn(async e=>a.get(e)??null),getAssets:o.fn(async()=>Array.from(a.values()))},c={create:o.fn(async(e,a)=>{const t=new e;return null!=a&&t.position.copy(a),t}),initActor:o.fn(async e=>{e.__isInitialised=!0})};return{materializer:new l(new i,r,n,{},{renderer:{shadowMap:{needsUpdate:!1}}},[],[{name:"PrefabParamActor",type:d},{name:"LiveParamActor",type:v},{name:"SceneAttachedComponentActor",type:P}],c,[{name:"LiveParamComponent",type:y}]),assetsProvider:n}}function A(e){return{name:e.id,position:[0,0,0],rotation:[0,0,0,"XYZ"],scale:[1,1,1],...e}}function x(e,a,t){return{id:e,name:e,type:"prefab",prefab:{objects:a,mainActorId:t}}}t("prefab instance actor params",()=>{n("does not let generated prefab instance defaults override the prefab actor value",async()=>{const e=await g({prefabActorValue:"basic",instanceParam:{type:u.String,value:"mixamo"}});r(e.animationPreset).toBe("basic")}),n("applies prefab instance params when the override is explicit",async()=>{const e=await g({prefabActorValue:"basic",instanceParam:{type:u.String,value:"mixamo",override:!0}});r(e.animationPreset).toBe("mixamo")}),n("does not let generated prefab instance component defaults override prefab component values",async()=>{const e=await g({prefabActorValue:"basic",prefabComponentValue:"basic",instanceInnerParam:{type:u.String,value:"mixamo"}});r(e.animation.animationPreset).toBe("basic")}),n("applies prefab instance component params when the override is explicit",async()=>{const e=await g({prefabActorValue:"basic",prefabComponentValue:"basic",instanceInnerParam:{type:u.String,value:"mixamo",override:!0}});r(e.animation.animationPreset).toBe("mixamo")}),n("resolves direct scene actors and exposed prefab actors by scene object id",async()=>{const{actor:e,materializer:a}=await w({prefabActorValue:"basic"}),t=new d;a.materializedActors.set("direct-actor",t),r(a.resolveSceneActor("direct-actor")).toBe(t),r(a.resolveSceneActor("prefab-instance")).toBe(e)}),n("applies supported scene parameter changes without recreating the actor",async()=>{const e=A({id:"live-actor",type:"actor",actor:{type:"LiveParamActor",params:{value:{type:u.String,value:"after"}},innerParams:[],components:[]}}),a={...e.actor,params:{value:{type:u.String,value:"before"}}},{materializer:t}=h([e],new Map),n=new v,o=await t.tryApplyLiveActorParameterUpdate(n,e,JSON.stringify(a));r(o).toBe(!0),r(n.value).toBe("after")}),n("applies supported component parameter changes without recreating the actor",async()=>{const e=A({id:"live-component",type:"actor",actor:{type:"LiveComponentParamActor",params:{},innerParams:[{path:["component"],params:{value:{type:u.String,value:"after"}}}],components:[]}}),a={...e.actor,innerParams:[{path:["component"],params:{value:{type:u.String,value:"before"}}}]},{materializer:t}=h([e],new Map),n=new b,o=await t.tryApplyLiveActorParameterUpdate(n,e,JSON.stringify(a));r(o).toBe(!0),r(n.component.value).toBe("after")}),n("applies supported scene-attached component parameter changes without recreating the actor",async()=>{const e=A({id:"scene-attached-component",type:"actor",actor:{type:"SceneAttachedComponentActor",params:{},innerParams:[],components:[{type:"LiveParamComponent",params:{value:{type:u.String,value:"before"}},innerParams:[]}]}}),{materializer:a}=h([e],new Map);await a.materialize(e),await a.initActorsPostInit();const t=a.materializedActors.get(e.id),n=t.attachedComponents[0],o=JSON.stringify(e.actor);e.actor.components[0].params.value={type:u.String,value:"after"};const i=await a.tryApplyLiveActorParameterUpdate(t,e,o);r(i).toBe(!0),r(n.value).toBe("after"),r(t.attachedComponents[0]).toBe(n)}),n("applies saved prefab parameters to a runtime-spawned prefab instance",async()=>{const e=x("live-prefab",[A({id:"live-actor",type:"actor",actor:{type:"LiveParamActor",params:{value:{type:u.String,value:"before"}},components:[]}})],"live-actor"),a=structuredClone(e);a.prefab.objects[0].actor.params.value={type:u.String,value:"after"};const{materializer:t,assetsProvider:n}=h([],new Map([[e.id,e]]),!0),i=await t.createFromPrefabAsset(e,{sceneObjectChain:["runtime-instance"],runtimeSpawn:!0},void 0,void 0,!1),s=i.mainActor;n.onUpdate.next(a),await o.waitFor(()=>r(s.value).toBe("after")),r(i.mainActor).toBe(s)}),n("applies supported component changes when the actor rejects an independent change",async()=>{const e=x("partially-live-prefab",[A({id:"component-actor",type:"actor",actor:{type:"SceneAttachedComponentActor",params:{unsupported:{type:u.String,value:"before"}},components:[{type:"LiveParamComponent",params:{value:{type:u.String,value:"before"}}}]}})],"component-actor"),a=structuredClone(e);a.prefab.objects[0].actor.params.unsupported.value="after",a.prefab.objects[0].actor.components[0].params.value.value="after";const{materializer:t,assetsProvider:n}=h([],new Map([[e.id,e]]),!0),i=await t.createFromPrefabAsset(e,{sceneObjectChain:["runtime-component-instance"],runtimeSpawn:!0},void 0,void 0,!1),s=i.mainActor,c=s.attachedComponents[0],p=o.spyOn(console,"warn").mockImplementation(()=>{});n.onUpdate.next(a),await o.waitFor(()=>r(c.value).toBe("after")),r(i.mainActor).toBe(s),r(p).toHaveBeenCalledWith(r.stringContaining("were not applied")),p.mockRestore()})});/*
2
2
  * Copyright (©) 2026 Hology Interactive AB. All rights reserved.
3
3
  * See the LICENSE.md file for details.
4
4
  */
@@ -1,4 +1,4 @@
1
- import{describe as e,expect as s,it as t,vi as a}from"vitest";import{RuntimeAssetsService as n}from"../scene/runtime-asset-service.js";e("RuntimeAssetsService",()=>{t("shares concurrent asset list loads and reuses the loaded cache",async()=>{const e=[{id:"mesh-a",name:"Mesh A",type:"mesh"},{id:"material-a",name:"Material A",type:"material"}],t={getAssets:a.fn(async()=>(await new Promise(e=>setTimeout(e,5)),e)),getAsset:a.fn(),getScenes:a.fn(),getScene:a.fn(),getSceneData:a.fn()},i=new n(t);await Promise.all([i.getAssets(),i.getAssets()]),await i.getAssets(),s(t.getAssets).toHaveBeenCalledTimes(1)})});/*
1
+ import{describe as e,expect as t,it as a,vi as n}from"vitest";import{RuntimeAssetsService as s}from"../scene/runtime-asset-service.js";function o(e){return{getAssets:n.fn(async()=>e),getAsset:n.fn(async t=>e.find(e=>e.id===t)),getScenes:n.fn(),getScene:n.fn(),getSceneData:n.fn()}}function r(e){return{id:"prefab-a",name:"Prefab A",type:"prefab",prefab:{mainActorId:"actor-a",objects:[{id:"actor-a",name:"Actor A",type:"actor",position:[1,2,3],rotation:[0,0,0],scale:[1,1,1],actor:{type:"TestActor",params:{label:{type:0,value:e}},components:[{type:"TestComponent",params:{label:{type:0,value:`${e}-component`}}}]}}]}}}e("RuntimeAssetsService",()=>{a("shares concurrent asset list loads and reuses the loaded cache",async()=>{const e=[{id:"mesh-a",name:"Mesh A",type:"mesh"},{id:"material-a",name:"Material A",type:"material"}],a={getAssets:n.fn(async()=>(await new Promise(e=>setTimeout(e,5)),e)),getAsset:n.fn(),getScenes:n.fn(),getScene:n.fn(),getSceneData:n.fn()},o=new s(a);await Promise.all([o.getAssets(),o.getAssets()]),await o.getAssets(),t(a.getAssets).toHaveBeenCalledTimes(1)}),a("merges only compatible prefab parameter data into the runtime asset",async()=>{const e=r("old-value"),a=o([e]),c=new s(a,!1),i=[];c.onUpdate.subscribe(e=>i.push(e));const p=r("new-value");p.prefab.objects[0].position=[99,99,99],p.prefab.eventGraph={nodes:[{id:"ignored"}]};const l=n.spyOn(console,"warn").mockImplementation(()=>{});t(await c.applyLivePrefabParameters(p)).toBe(!0);const m=await c.getAsset(e.id);t(m.prefab.objects[0].actor.params.label.value).toBe("new-value"),t(m.prefab.objects[0].actor.components[0].params.label.value).toBe("new-value-component"),t(m.prefab.objects[0].position).toEqual([1,2,3]),t(m.prefab.eventGraph).toBeUndefined(),t(i).toEqual([m]),t(l).toHaveBeenCalledWith(t.stringContaining("event graph updates are not supported")),l.mockRestore()}),a("rejects structural prefab changes without replacing the cached asset",async()=>{const e=r("old-value"),a=o([e]),c=new s(a,!1),i=r("new-value");i.prefab.objects[0].actor.components[0].type="OtherComponent";const p=n.spyOn(console,"warn").mockImplementation(()=>{});t(await c.applyLivePrefabParameters(i)).toBe(!1),t(await c.getAsset(e.id)).toBe(e),t(p).toHaveBeenCalledWith(t.stringContaining("structure changed")),p.mockRestore()})});/*
2
2
  * Copyright (©) 2026 Hology Interactive AB. All rights reserved.
3
3
  * See the LICENSE.md file for details.
4
4
  */
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=vfx-world-space-emitter-rotation.test.d.ts.map
@@ -0,0 +1,4 @@
1
+ import{Emitter as t,Particle as o}from"@hology/nebula";import{Euler as e,Quaternion as r}from"three";import{describe as n,expect as a,test as i}from"vitest";import{WorldSpaceEmitterRotationInitializer as s}from"../effects/vfx/initializsers.js";n("world-space VFX emitter rotation",()=>{i("composes emitter rotation with the particle local rotation once at spawn",()=>{const n=new t;n.rotation.set(0,Math.PI/2,0);const i=new o;i.rotation.set(Math.PI/6,0,0),(new s).init(n,i);const m=(new r).setFromEuler(new e(i.rotation.x,i.rotation.y,i.rotation.z)),l=(new r).setFromEuler(new e(0,Math.PI/2,0)).multiply((new r).setFromEuler(new e(Math.PI/6,0,0)));a(Math.abs(m.dot(l))).toBeCloseTo(1),n.rotation.set(0,Math.PI,0);const w=(new r).setFromEuler(new e(i.rotation.x,i.rotation.y,i.rotation.z));a(Math.abs(w.dot(l))).toBeCloseTo(1)})});/*
2
+ * Copyright (©) 2026 Hology Interactive AB. All rights reserved.
3
+ * See the LICENSE.md file for details.
4
+ */
@@ -1,4 +1,4 @@
1
- import{describe as e,expect as t,test as o,vi as n}from"vitest";import{Euler as s,Group as a,Scene as i,Vector3 as r}from"three";n.hoisted(()=>{if("undefined"==typeof HTMLCanvasElement)return;const e=new Proxy({},{get:(e,t)=>(t in e||(e[t]=n.fn()),e[t]),set:(e,t,o)=>(e[t]=o,!0)});Object.defineProperty(HTMLCanvasElement.prototype,"getContext",{configurable:!0,value:()=>e})}),n.mock("../rendering.js",()=>({RenderingView:class{}})),n.mock("../gameplay/services/physics/physics-system.js",()=>({PhysicsSystem:class{}}));import{World as c}from"../gameplay/services/world.js";e("World.spawnPrefab",()=>{o("makes the requested transform available while prefab actors initialize",async()=>{const e=new i,o=new i,p=new a;p.position.set(1,0,0);const l=new a;let d,m=o;const y={inEditor:!0,setScene:n.fn(e=>{m=e}),createFromPrefabAsset:n.fn(async(e,t,o)=>(o.add(l),l.add(p),y.inEditor||m.attach(p),d=p.position.clone(),{object:l,actors:[],mainActor:null}))},f=Object.create(c.prototype);f.scene=e,f.materializer=y,f.actorFactory={inEditor:!1},f.physics={addFromScene:n.fn()};const w=new r(10,2,5),b=new s(0,Math.PI/2,0),h=new a,j=new a;h.position.copy(w),h.rotation.copy(b),j.position.set(1,0,0),h.add(j),h.updateWorldMatrix(!0,!0);const u=j.getWorldPosition(new r),g=await f.spawnPrefab({asset:{}},w,b);t(d).toEqual(u),t(y.setScene).toHaveBeenCalledWith(e),t(y.inEditor).toBe(!1),t(p.parent).toBe(e),t(g?.object.parent).toBe(e),t(g?.object.position).toEqual(w),t(g?.object.rotation.y).toBeCloseTo(b.y)})});/*
1
+ import{describe as e,expect as t,test as n,vi as o}from"vitest";import{Euler as a,Group as s,Scene as r,Vector3 as i}from"three";o.hoisted(()=>{if("undefined"==typeof HTMLCanvasElement)return;const e=new Proxy({},{get:(e,t)=>(t in e||(e[t]=o.fn()),e[t]),set:(e,t,n)=>(e[t]=n,!0)});Object.defineProperty(HTMLCanvasElement.prototype,"getContext",{configurable:!0,value:()=>e})}),o.mock("../rendering.js",()=>({RenderingView:class{}})),o.mock("../gameplay/services/physics/physics-system.js",()=>({PhysicsSystem:class{}}));import{World as c}from"../gameplay/services/world.js";e("World.spawnPrefab",()=>{n("makes the requested transform available while prefab actors initialize",async()=>{const e=new r,n=new r,p=new s;p.position.set(1,0,0);const d=new s;let f,l=n;const m={id:"prefab-a",type:"prefab",prefab:{objects:[]},revision:2},y={inEditor:!0,setScene:o.fn(e=>{l=e}),createFromPrefabAsset:o.fn(async(e,t,n)=>(n.add(d),d.add(p),y.inEditor||l.attach(p),f=p.position.clone(),{object:d,actors:[],mainActor:null,instanceId:"runtime-instance"})),unregisterLivePrefabInstance:o.fn()},b=Object.create(c.prototype);b.scene=e,b.materializer=y,b.actorFactory={inEditor:!1},b.physics={addFromScene:o.fn(),removeRemoved:o.fn()},b.assetsProvider={getAsset:o.fn(async()=>m)};const v=new i(10,2,5),w=new a(0,Math.PI/2,0),h=new s,u=new s;h.position.copy(v),h.rotation.copy(w),u.position.set(1,0,0),h.add(u),h.updateWorldMatrix(!0,!0);const P=u.getWorldPosition(new i),g=await b.spawnPrefab({asset:{id:"prefab-a",type:"prefab",prefab:{objects:[]}}},v,w);t(f).toEqual(P),t(y.setScene).toHaveBeenCalledWith(e),t(y.inEditor).toBe(!1),t(p.parent).toBe(e),t(g?.object.parent).toBe(e),t(g?.object.position).toEqual(v),t(g?.object.rotation.y).toBeCloseTo(w.y),t(y.createFromPrefabAsset).toHaveBeenCalledWith(m,t.anything(),t.any(s),void 0,!1),b.removePrefab(g),t(y.unregisterLivePrefabInstance).toHaveBeenCalledWith("runtime-instance")})});/*
2
2
  * Copyright (©) 2026 Hology Interactive AB. All rights reserved.
3
3
  * See the LICENSE.md file for details.
4
4
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hology/core",
3
- "version": "0.0.213",
3
+ "version": "0.0.214",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",