@hology/core 0.0.212 → 0.0.213

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.
Files changed (135) hide show
  1. package/dist/effects/sequence/index.d.ts +1 -0
  2. package/dist/effects/sequence/index.js +1 -1
  3. package/dist/effects/sequence/sequence-action.d.ts +5 -0
  4. package/dist/effects/sequence/sequence-actor.d.ts +7 -0
  5. package/dist/effects/sequence/sequence-actor.js +1 -1
  6. package/dist/effects/sequence/sequence-animation-retiming.js +1 -1
  7. package/dist/effects/sequence/sequence-data.d.ts +9 -0
  8. package/dist/effects/sequence/sequence-data.js +1 -1
  9. package/dist/effects/sequence/sequence-player.d.ts +12 -1
  10. package/dist/effects/sequence/sequence-player.js +1 -1
  11. package/dist/effects/sequence/sequence-transform.d.ts +10 -0
  12. package/dist/effects/sequence/sequence-transform.js +4 -0
  13. package/dist/effects/sequence/sequence-value-lane.d.ts +2 -0
  14. package/dist/effects/sequence/sequence-value-lane.js +1 -1
  15. package/dist/effects/vfx/vfx-actor.d.ts +5 -0
  16. package/dist/effects/vfx/vfx-actor.js +1 -1
  17. package/dist/effects/vfx/vfx-asset.d.ts +2 -1
  18. package/dist/effects/vfx/vfx-asset.js +1 -1
  19. package/dist/effects/vfx/vfx-materializer.js +1 -1
  20. package/dist/effects/vfx/vfx-renderers.d.ts +1 -0
  21. package/dist/effects/vfx/vfx-renderers.js +1 -1
  22. package/dist/gameplay/actors/actor.d.ts +3 -5
  23. package/dist/gameplay/actors/actor.js +1 -1
  24. package/dist/gameplay/actors/builtin/components/character/character-animation.d.ts +3 -0
  25. package/dist/gameplay/actors/builtin/components/character/character-animation.js +1 -1
  26. package/dist/gameplay/actors/builtin/components/character/character-movement-like.d.ts +2 -0
  27. package/dist/gameplay/actors/builtin/components/character/character-movement.d.ts +13 -0
  28. package/dist/gameplay/actors/builtin/components/character/character-movement.js +1 -1
  29. package/dist/gameplay/actors/builtin/components/character/net-character-movement-protocol.d.ts +4 -1
  30. package/dist/gameplay/actors/builtin/components/character/net-character-movement-protocol.js +1 -1
  31. package/dist/gameplay/actors/builtin/components/mesh-component.d.ts +1 -0
  32. package/dist/gameplay/actors/builtin/components/mesh-component.js +1 -1
  33. package/dist/gameplay/actors/builtin/components/tween-component.js +1 -1
  34. package/dist/gameplay/actors/builtin/navmesh-actor.d.ts +2 -0
  35. package/dist/gameplay/actors/builtin/navmesh-actor.js +1 -1
  36. package/dist/gameplay/actors/builtin/trigger-volume.d.ts +3 -3
  37. package/dist/gameplay/actors/builtin/trigger-volume.js +1 -1
  38. package/dist/gameplay/actors/camera/camera-component.d.ts +1 -3
  39. package/dist/gameplay/actors/camera/camera-component.js +1 -1
  40. package/dist/gameplay/actors/component.d.ts +3 -0
  41. package/dist/gameplay/actors/component.js +1 -1
  42. package/dist/gameplay/actors/type-registry.d.ts +10 -0
  43. package/dist/gameplay/actors/type-registry.js +4 -0
  44. package/dist/gameplay/animation/retarget.d.ts +50 -0
  45. package/dist/gameplay/animation/retarget.js +4 -0
  46. package/dist/gameplay/animation/root-motion.js +1 -1
  47. package/dist/gameplay/event-graph/actor-transform-properties.d.ts +6 -0
  48. package/dist/gameplay/event-graph/actor-transform-properties.js +4 -0
  49. package/dist/gameplay/event-graph/asset-actor-types.d.ts +3 -0
  50. package/dist/gameplay/event-graph/asset-actor-types.js +4 -0
  51. package/dist/gameplay/event-graph/asset-references.d.ts +12 -0
  52. package/dist/gameplay/event-graph/asset-references.js +4 -0
  53. package/dist/gameplay/event-graph/compiler.d.ts +107 -0
  54. package/dist/gameplay/event-graph/compiler.js +4 -0
  55. package/dist/gameplay/event-graph/decorators.d.ts +74 -0
  56. package/dist/gameplay/event-graph/decorators.js +4 -0
  57. package/dist/gameplay/event-graph/document-compiler.d.ts +107 -0
  58. package/dist/gameplay/event-graph/document-compiler.js +4 -0
  59. package/dist/gameplay/event-graph/event-graph-demo.d.ts +2 -0
  60. package/dist/gameplay/event-graph/event-graph-demo.js +4 -0
  61. package/dist/gameplay/event-graph/graph.d.ts +106 -0
  62. package/dist/gameplay/event-graph/graph.js +4 -0
  63. package/dist/gameplay/event-graph/index.d.ts +13 -0
  64. package/dist/gameplay/event-graph/index.js +4 -0
  65. package/dist/gameplay/event-graph/model.d.ts +71 -0
  66. package/dist/gameplay/event-graph/model.js +4 -0
  67. package/dist/gameplay/event-graph/nodes/execution-nodes.d.ts +115 -0
  68. package/dist/gameplay/event-graph/nodes/execution-nodes.js +4 -0
  69. package/dist/gameplay/event-graph/nodes/gameplay-nodes.d.ts +208 -0
  70. package/dist/gameplay/event-graph/nodes/gameplay-nodes.js +4 -0
  71. package/dist/gameplay/event-graph/nodes/index.d.ts +4 -0
  72. package/dist/gameplay/event-graph/nodes/index.js +4 -0
  73. package/dist/gameplay/event-graph/nodes/value-nodes.d.ts +53 -0
  74. package/dist/gameplay/event-graph/nodes/value-nodes.js +4 -0
  75. package/dist/gameplay/event-graph/registry.d.ts +33 -0
  76. package/dist/gameplay/event-graph/registry.js +4 -0
  77. package/dist/gameplay/event-graph/runtime-assets.d.ts +21 -0
  78. package/dist/gameplay/event-graph/runtime-assets.js +4 -0
  79. package/dist/gameplay/event-graph/runtime.d.ts +160 -0
  80. package/dist/gameplay/event-graph/runtime.js +4 -0
  81. package/dist/gameplay/event-graph/type-inference.d.ts +41 -0
  82. package/dist/gameplay/event-graph/type-inference.js +4 -0
  83. package/dist/gameplay/event-graph/types.d.ts +77 -0
  84. package/dist/gameplay/event-graph/types.js +4 -0
  85. package/dist/gameplay/index.d.ts +15 -2
  86. package/dist/gameplay/index.js +1 -1
  87. package/dist/gameplay/initiate.js +1 -1
  88. package/dist/gameplay/input/input.d.ts +22 -17
  89. package/dist/gameplay/input/input.js +1 -1
  90. package/dist/gameplay/net/service/net-decorator.d.ts +1 -1
  91. package/dist/gameplay/net/service/net-decorator.js +1 -1
  92. package/dist/gameplay/net/service/net-serializer.js +1 -1
  93. package/dist/gameplay/net/service/net-service.d.ts +6 -6
  94. package/dist/gameplay/net/service/net-service.js +1 -1
  95. package/dist/gameplay/net/service/rpc-decorator.js +1 -1
  96. package/dist/gameplay/services/asset-loader.d.ts +7 -0
  97. package/dist/gameplay/services/asset-loader.js +1 -1
  98. package/dist/gameplay/services/physics/physics-system.d.ts +14 -0
  99. package/dist/gameplay/services/physics/physics-system.js +1 -1
  100. package/dist/gameplay/services/world.d.ts +12 -0
  101. package/dist/gameplay/services/world.js +1 -1
  102. package/dist/rendering.js +1 -1
  103. package/dist/scene/asset-resource-loader.d.ts +7 -1
  104. package/dist/scene/asset-resource-loader.js +1 -1
  105. package/dist/scene/custom-param-deserialize.js +1 -1
  106. package/dist/scene/custom-param-runtime-types.js +1 -1
  107. package/dist/scene/materializer.d.ts +25 -0
  108. package/dist/scene/materializer.js +1 -1
  109. package/dist/scene/model.d.ts +114 -2
  110. package/dist/scene/model.js +1 -1
  111. package/dist/scene/objects/prefab.js +1 -1
  112. package/dist/shader/graph/compiler.d.ts +3 -0
  113. package/dist/shader/graph/compiler.js +1 -1
  114. package/dist/shader/graph/model.d.ts +1 -1
  115. package/dist/shader/graph/registry.js +1 -1
  116. package/dist/test/animation-retarget.test.d.ts +2 -0
  117. package/dist/test/animation-retarget.test.js +4 -0
  118. package/dist/test/event-graph-generated-code.test.d.ts +2 -0
  119. package/dist/test/event-graph-generated-code.test.js +4 -0
  120. package/dist/test/event-graph-types.test.d.ts +2 -0
  121. package/dist/test/event-graph-types.test.js +4 -0
  122. package/dist/test/event-graph.test.d.ts +2 -0
  123. package/dist/test/event-graph.test.js +4 -0
  124. package/dist/test/input.test.d.ts +2 -0
  125. package/dist/test/input.test.js +4 -0
  126. package/dist/test/net-character-movement.test.js +1 -1
  127. package/dist/test/prefab-instance-params.test.js +1 -1
  128. package/dist/test/sequence-camera-control.test.js +1 -1
  129. package/dist/test/sequence-scene-binding.test.d.ts +2 -0
  130. package/dist/test/sequence-scene-binding.test.js +4 -0
  131. package/dist/test/sequence-transform.test.d.ts +2 -0
  132. package/dist/test/sequence-transform.test.js +4 -0
  133. package/dist/test/shader-graph.test.js +1 -1
  134. package/package.json +10 -2
  135. package/tsconfig.tsbuildinfo +1 -1
@@ -0,0 +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)})});/*
2
+ * Copyright (©) 2026 Hology Interactive AB. All rights reserved.
3
+ * See the LICENSE.md file for details.
4
+ */
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=event-graph-generated-code.test.d.ts.map
@@ -0,0 +1,4 @@
1
+ import{describe as e,expect as n,test as t}from"vitest";import{registerActorClass as o}from"../gameplay/actors/type-registry.js";import{EventGraphCompiler as a}from"../gameplay/event-graph/document-compiler.js";function i(e){return(new a).compile(e).beginPlay[0]}function r(e,n="value"){return{nodeId:e,port:n}}function s(e){return{value:e}}function d(e,n,t){return{from:{nodeId:e,port:n},to:{nodeId:t,port:"exec"}}}o("event-graph-generated.find-target",class{}),e("event graph generated code",()=>{t("resolves inline actor classes for pure world lookups",()=>{const e={version:1,nodes:[{id:"begin",kind:"event.beginPlay"},{id:"update",kind:"event.update"},{id:"find",kind:"actor.findByType",inputs:{actorClass:s("event-graph-generated.find-target")}},{id:"findAll",kind:"actor.findAllByType",inputs:{actorClass:s("event-graph-generated.find-target")}},{id:"log",kind:"debug.log",inputs:{value:r("find")}},{id:"logAll",kind:"debug.log",inputs:{value:r("findAll")}}],edges:[d("begin","then","log"),d("update","then","logAll")]},t=(new a).compile(e);n(t.beginPlay[0].async).toBe(!1),n(t.beginPlay[0].source).toContain('runtime.findActorByType(runtime.actorClass("event-graph-generated.find-target"))'),n(t.update[0].source).toContain('runtime.findActorsByType(runtime.actorClass("event-graph-generated.find-target"))')}),t("emits synchronous pure data flow inside a branch",()=>{const e=i({version:1,nodes:[{id:"begin",kind:"event.beginPlay"},{id:"branch",kind:"flow.branch",inputs:{condition:s(!0)}},{id:"one",kind:"constant.number",params:{value:1}},{id:"two",kind:"constant.number",params:{value:2}},{id:"sum",kind:"math.add",inputs:{a:r("one"),b:r("two")}},{id:"log",kind:"debug.log",inputs:{value:r("sum")}}],edges:[d("begin","then","branch"),d("branch","true","log")]});n(e.async).toBe(!1),n(e.source).toMatchInlineSnapshot('\n ""use strict"\n if (true) {\n const binary_1 = (1 + 2)\n console.log(binary_1)\n }"\n ')}),t("emits event values and persistent variable access without async code",()=>{const e={version:1,variables:[{name:"elapsed",defaultValue:0}],nodes:[{id:"update",kind:"event.update"},{id:"elapsed",kind:"variable.get",params:{variable:"elapsed"}},{id:"sum",kind:"math.add",inputs:{a:r("elapsed"),b:{nodeId:"update",port:"deltaTime"}}},{id:"set",kind:"variable.set",params:{variable:"elapsed"},inputs:{value:r("sum")}}],edges:[d("update","then","set")]},t=(new a).compile(e).update[0];n(t.async).toBe(!1),n(t.source).toMatchInlineSnapshot('\n ""use strict"\n const variable_1 = runtime.readValue(runtime.getVariable("elapsed"))\n const binary_2 = (variable_1 + eventValue)\n runtime.setVariable("elapsed", binary_2)"\n ')}),t("emits structured parallel branches and cancellable delay",()=>{const e=i({version:1,nodes:[{id:"begin",kind:"event.beginPlay"},{id:"parallel",kind:"flow.parallel",params:{ports:["slow","fast"]}},{id:"delay",kind:"flow.delay",inputs:{durationMs:s(100)}},{id:"slowLog",kind:"debug.log",inputs:{value:s("slow")}},{id:"fastLog",kind:"debug.log",inputs:{value:s("fast")}},{id:"doneLog",kind:"debug.log",inputs:{value:s("done")}}],edges:[d("begin","then","parallel"),d("parallel","slow","delay"),d("delay","then","slowLog"),d("parallel","fast","fastLog"),d("parallel","then","doneLog")]});n(e.async).toBe(!0),n(e.source).toMatchInlineSnapshot('\n ""use strict"\n await Promise.all([\n (async () => {\n await runtime.delay(100)\n console.log("slow")\n })(),\n (async () => {\n console.log("fast")\n })()\n ])\n console.log("done")"\n ')}),t("registers a game-time interval and continues immediately",()=>{const e=i({version:1,nodes:[{id:"begin",kind:"event.beginPlay"},{id:"interval",kind:"flow.interval",inputs:{intervalMs:s(250)}},{id:"tick",kind:"debug.log",inputs:{value:s("tick")}},{id:"started",kind:"debug.log",inputs:{value:s("started")}},{id:"clear",kind:"flow.clearInterval",inputs:{timerId:r("interval","timerId")}}],edges:[d("begin","then","interval"),d("interval","interval","tick"),d("interval","then","started"),d("started","then","clear")]});n(e.async).toBe(!1),n(e.source).toContain("runtime.setInterval("),n(e.source).toContain('"interval"'),n(e.source).toContain("250"),n(e.source).toContain('console.log("tick")'),n(e.source).toContain("runtime.clearInterval(out_timerId_"),n(e.source).toMatch(/runtime\.setInterval[\s\S]+console\.log\("started"\)[\s\S]+runtime\.clearInterval/)}),t("emits ordered sequence branches and an inclusive for loop",()=>{const e=i({version:1,nodes:[{id:"begin",kind:"event.beginPlay"},{id:"sequence",kind:"flow.sequence",params:{ports:["first","second"]}},{id:"first",kind:"debug.log",inputs:{value:s("first")}},{id:"second",kind:"debug.log",inputs:{value:s("second")}},{id:"loop",kind:"flow.forLoop",inputs:{firstIndex:s(2),lastIndex:s(4)}},{id:"index",kind:"debug.log",inputs:{value:r("loop","index")}},{id:"completed",kind:"debug.log",inputs:{value:s("completed")}}],edges:[d("begin","then","sequence"),d("sequence","first","first"),d("sequence","second","second"),d("sequence","then","loop"),d("loop","loopBody","index"),d("loop","completed","completed")]}).source;n(e.indexOf('console.log("first")')).toBeLessThan(e.indexOf('console.log("second")')),n(e).toContain("Number.isSafeInteger"),n(e).toContain("100000 iteration safety limit"),n(e).toMatch(/for \(let index_\d+ = firstIndex_\d+; index_\d+ <= lastIndex_\d+; index_\d+\+\+\)/),n(e).toMatch(/}\nconsole\.log\("completed"\)$/)}),t("emits a structured for-each loop with typed iteration values and latent bodies",()=>{const e=i({version:1,nodes:[{id:"begin",kind:"event.beginPlay"},{id:"forEach",kind:"flow.forEach",inputs:{array:{value:[10,20]}}},{id:"delay",kind:"flow.delay",inputs:{durationMs:s(1)}},{id:"element",kind:"debug.log",inputs:{value:r("forEach","arrayElement")}},{id:"index",kind:"debug.log",inputs:{value:r("forEach","arrayIndex")}},{id:"done",kind:"debug.log",inputs:{value:s("done")}}],edges:[d("begin","then","forEach"),d("forEach","loopBody","delay"),d("delay","then","element"),d("element","then","index"),d("forEach","completed","done")]});n(e.async).toBe(!0),n(e.source).toContain("const array_"),n(e.source).toContain("for (let index_"),n(e.source).toContain("await runtime.delay(1)"),n(e.source).toContain("console.log(out_arrayElement_"),n(e.source).toContain("console.log(out_arrayIndex_"),n(e.source).toMatch(/}\nconsole\.log\("done"\)$/)}),t("emits switch and try error routing",()=>{const e=i({version:1,nodes:[{id:"begin",kind:"event.beginPlay"},{id:"switch",kind:"flow.switch",params:{cases:[{value:"call",port:"call"}]},inputs:{value:s("call")}},{id:"actor",kind:"actor.reference",params:{actorId:"hero"}},{id:"tryCall",kind:"actor.tryCallMethod",params:{method:"act",argumentCount:0},inputs:{target:r("actor")}},{id:"success",kind:"debug.log",inputs:{value:r("tryCall")}},{id:"error",kind:"debug.log",inputs:{value:{nodeId:"tryCall",port:"errorValue"}}},{id:"done",kind:"debug.log",inputs:{value:s("done")}}],edges:[d("begin","then","switch"),d("switch","call","tryCall"),d("tryCall","success","success"),d("tryCall","error","error"),d("tryCall","then","done")]});n(e.async).toBe(!1),n(e.source).toMatchInlineSnapshot('\n ""use strict"\n let out_value_1, out_errorValue_2\n switch ("call") {\n case "call": {\n const actor_4 = runtime.actor("hero")\n let succeeded_5 = false\n try {\n out_value_1 = runtime.callMethod(actor_4, "act", [])\n out_errorValue_2 = undefined\n succeeded_5 = true\n } catch (caught_3) {\n out_value_1 = undefined\n out_errorValue_2 = caught_3\n }\n if (succeeded_5) {\n console.log(out_value_1)\n } else {\n console.log(out_errorValue_2)\n }\n console.log("done")\n break\n }\n default: {\n \n }\n }"\n ')}),t("constructs fresh runtime vectors and compares their components",()=>{const e=i({version:1,nodes:[{id:"begin",kind:"event.beginPlay"},{id:"left",kind:"constant.vector3",params:{value:[1,2,3]}},{id:"right",kind:"constant.vector3",params:{value:[1,2,3]}},{id:"equal",kind:"compare.equal",inputs:{a:r("left"),b:r("right")}},{id:"branch",kind:"flow.branch",inputs:{condition:r("equal")}},{id:"log",kind:"debug.log",inputs:{value:r("left")}}],edges:[d("begin","then","branch"),d("branch","true","log")]});n(e.source).toContain("runtime.createVector3(1, 2, 3).equals(runtime.createVector3(1, 2, 3))"),n(e.source).toContain("console.log(runtime.createVector3(1, 2, 3))")}),t("emits runtime vector math while keeping scalar math native",()=>{const e=i({version:1,nodes:[{id:"begin",kind:"event.beginPlay"},{id:"vector",kind:"constant.vector3",params:{value:[1,2,3]}},{id:"offset",kind:"constant.vector3",params:{value:[4,5,6]}},{id:"two",kind:"constant.number",params:{value:2}},{id:"sum",kind:"math.add",inputs:{a:r("vector"),b:r("offset")}},{id:"scaled",kind:"math.multiply",inputs:{a:r("sum"),b:r("two")}},{id:"scalar",kind:"math.multiply",inputs:{a:r("two"),b:r("two")}},{id:"logVector",kind:"debug.log",inputs:{value:r("scaled")}},{id:"logScalar",kind:"debug.log",inputs:{value:r("scalar")}}],edges:[d("begin","then","logVector"),d("logVector","then","logScalar")]});n(e.source).toContain("runtime.mathAdd(runtime.createVector3(1, 2, 3), runtime.createVector3(4, 5, 6))"),n(e.source).toContain("runtime.mathMultiply(vectorMath_1, 2)"),n(e.source).toContain("const binary_3 = (2 * 2)")}),t("emits runtime vector utility operations",()=>{const e=i({version:1,nodes:[{id:"begin",kind:"event.beginPlay"},{id:"a",kind:"constant.vector3",params:{value:[1,0,0]}},{id:"b",kind:"constant.vector3",params:{value:[0,1,0]}},{id:"cross",kind:"vector.cross",inputs:{a:r("a"),b:r("b")}},{id:"normalize",kind:"vector.normalize",inputs:{value:r("cross")}},{id:"rotated",kind:"vector.applyRotation",inputs:{value:r("normalize"),rotation:{value:[0,.5,0]}}},{id:"distance",kind:"vector.distance",inputs:{a:r("a"),b:r("b")}},{id:"logVector",kind:"debug.log",inputs:{value:r("rotated")}},{id:"logDistance",kind:"debug.log",inputs:{value:r("distance")}}],edges:[d("begin","then","logVector"),d("logVector","then","logDistance")]});n(e.source).toContain("runtime.vectorCross(runtime.createVector3(1, 0, 0), runtime.createVector3(0, 1, 0))"),n(e.source).toContain("runtime.vectorNormalize(vector_1)"),n(e.source).toContain("runtime.vectorApplyRotation(vector_2, runtime.createEuler(0, 0.5, 0))"),n(e.source).toContain("runtime.vectorDistance(runtime.createVector3(1, 0, 0), runtime.createVector3(0, 1, 0))")}),t("keeps soft references scalar and emits async prefab placement with optional-handle routing",()=>{const e=i({version:1,nodes:[{id:"begin",kind:"event.beginPlay"},{id:"prefab",kind:"asset.reference",params:{reference:{assetId:"prefab-1",assetType:"prefab"}}},{id:"spawn",kind:"actor.spawnPrefab",inputs:{prefab:r("prefab"),position:{value:[1,2,3]},rotation:{value:[0,.5,0]}}},{id:"valid",kind:"flow.isValid",inputs:{value:r("spawn","mainActor")}},{id:"destroy",kind:"actor.destroy",inputs:{actor:r("valid")}}],edges:[d("begin","then","spawn"),d("spawn","then","valid"),d("valid","valid","destroy")]});n(e.async).toBe(!0),n(e.source).toContain('await runtime.spawnPrefab("prefab-1", runtime.createVector3(1, 2, 3), runtime.createEuler(0, 0.5, 0))'),n(e.source).toContain("!== null &&"),n(e.source).toContain("runtime.destroyActor("),n(e.source).not.toContain("assetType")}),t("continues after VFX start and registers a separate completion branch",()=>{const e={version:1,nodes:[{id:"begin",kind:"event.beginPlay"},{id:"vfx",kind:"vfx.play",inputs:{vfx:{value:{assetId:"vfx-1",assetType:"vfx"}}}},{id:"completed",kind:"debug.log",inputs:{value:{value:"completed"}}}],edges:[d("begin","then","vfx"),d("vfx","completed","completed")]},t=i(e);n(t.source).toContain("await runtime.playVfx("),n(t.source).toContain("runtime.onActorCompleted("),n(t.source).toContain('console.log("completed")');const o=structuredClone(e);o.nodes=o.nodes.filter(e=>"completed"!==e.id),o.edges=[d("begin","then","vfx")],n(i(o).source).not.toContain("runtime.onActorCompleted(")}),t("emits generic runtime callbacks for property and transform tweens",()=>{const e=i({version:1,nodes:[{id:"begin",kind:"event.beginPlay"},{id:"actor",kind:"actor.reference",params:{actorId:"hero"}},{id:"rotate",kind:"transform.rotateBy",params:{curve:"InOutQuad",space:"world",axis:"z"},inputs:{target:r("actor"),angle:s(360),duration:s(2)}},{id:"completed",kind:"debug.log",inputs:{value:s("completed")}}],edges:[d("begin","then","rotate"),d("rotate","completed","completed")]});n(e.async).toBe(!1),n(e.source).toContain("runtime.tweenRotateBy("),n(e.source).toContain("360,"),n(e.source).toContain('"InOutQuad"'),n(e.source).toContain('"world"'),n(e.source).toContain('"z"'),n(e.source).toContain("async () => {"),n(e.source).toContain('console.log("completed")')})});/*
2
+ * Copyright (©) 2026 Hology Interactive AB. All rights reserved.
3
+ * See the LICENSE.md file for details.
4
+ */
@@ -0,0 +1,2 @@
1
+ import 'reflect-metadata';
2
+ //# sourceMappingURL=event-graph-types.test.d.ts.map
@@ -0,0 +1,4 @@
1
+ import{__decorate as e,__metadata as t}from"tslib";import"reflect-metadata";import{Subject as a}from"rxjs";import{Euler as o,Vector2 as n,Vector3 as r}from"three";import{describe as i,expect as s,test as p,vi as d}from"vitest";d.hoisted(()=>{if("undefined"==typeof HTMLCanvasElement)return;const e=new Proxy({},{get:(e,t)=>(t in e||(e[t]=d.fn()),e[t])});Object.defineProperty(HTMLCanvasElement.prototype,"getContext",{configurable:!0,value:()=>e})}),d.mock("../gameplay/services/render.js",()=>({ViewController:class{}})),d.mock("../gameplay/services/physics/physics-system.js",()=>({PhysicsSystem:class{}}));import{Actor as c,BaseActor as u}from"../gameplay/actors/actor.js";import{ActorComponent as l,Component as v}from"../gameplay/actors/component.js";import{TriggerVolume as g,TriggerVolumeComponent as y}from"../gameplay/actors/builtin/trigger-volume.js";import{EVENT_GRAPH_ACTOR_CLASS_TYPE as m,EVENT_GRAPH_ACTOR_TYPE as b,EVENT_GRAPH_VOID_TYPE as k,EventGraphCallable as h,EventGraphCompiler as f,EventGraphEventSource as E,EventGraphProperty as q,analyzeEventGraphTypes as T,collectEventGraphAssetReferences as C,createEventGraphTypeContext as O,eventGraphTypeFromConstructor as I,eventGraphTypeName as j,eventGraphAssetType as w,eventGraphComponentType as x,eventGraphOptionalType as S,getEventGraphCallableInfo as P,getEventGraphEventSourceInfo as A,getEventGraphMembersForType as V,getEventGraphNodePorts as B,getEventGraphPropertyInfo as N,isEventGraphTypeAssignable as M,normalizeEventGraphDocument as D,validateEventGraphDocument as L}from"../gameplay/event-graph/index.js";let R=class extends l{constructor(){super(...arguments),this.score=10,this.damaged=new a,this.scored=new a,this.direction=new r(0,0,1)}applyDamage(e){return e>0}reset(){}project(e){return new n(e.x,e.y)}onScore(){return this.scored}onActorType(e){return new a}};e([q({type:Number}),t("design:type",Object)],R.prototype,"score",void 0),e([q({observableValueType:Number,writable:!1}),t("design:type",Object)],R.prototype,"damaged",void 0),e([q({type:r}),t("design:type",Object)],R.prototype,"direction",void 0),e([h({label:"Apply Damage",parameters:[{name:"amount",type:Number}],returns:Boolean}),t("design:type",Function),t("design:paramtypes",[Number]),t("design:returntype",Boolean)],R.prototype,"applyDamage",null),e([h({returns:k}),t("design:type",Function),t("design:paramtypes",[]),t("design:returntype",void 0)],R.prototype,"reset",null),e([h({parameters:[{name:"position",type:r}],returns:n}),t("design:type",Function),t("design:paramtypes",[r]),t("design:returntype",n)],R.prototype,"project",null),e([E({label:"On Score",emits:Number}),t("design:type",Function),t("design:paramtypes",[]),t("design:returntype",void 0)],R.prototype,"onScore",null),e([E({label:"On Actor Type",parameters:[{name:"actorType",type:m}],emits:b}),t("design:type",Function),t("design:paramtypes",[Object]),t("design:returntype",void 0)],R.prototype,"onActorType",null),R=e([v({typeId:"event-graph-test.stats"})],R);class z extends l{reset(){}}Reflect.defineMetadata("design:paramtypes",[],z.prototype,"reset"),Reflect.defineMetadata("design:returntype",void 0,z.prototype,"reset"),h()(z.prototype,"reset",Object.getOwnPropertyDescriptor(z.prototype,"reset"));let F=class extends u{constructor(){super(...arguments),this.stats=new R,this.intensity=0,this.enabled=!0,this.progress=0}};function H(e,t="value"){return{nodeId:e,port:t}}function W(e,t,a){return{from:{nodeId:e,port:t},to:{nodeId:a,port:"exec"}}}e([q({type:R,writable:!1}),t("design:type",Object)],F.prototype,"stats",void 0),e([q({type:Number}),t("design:type",Object)],F.prototype,"intensity",void 0),e([q({type:Boolean}),t("design:type",Object)],F.prototype,"enabled",void 0),e([q({type:Number,writable:!1}),t("design:type",Object)],F.prototype,"progress",void 0),F=e([c({typeId:"event-graph-test.hero"})],F),i("event graph types",()=>{p("narrows actor lookup results from static class inputs and keeps dynamic classes generic",()=>{const e={version:1,variables:[{name:"dynamicClass",type:m,defaultValue:"event-graph-test.hero"}],nodes:[{id:"find",kind:"actor.findByType",inputs:{actorClass:{value:"event-graph-test.hero"}}},{id:"findAll",kind:"actor.findAllByType",inputs:{actorClass:{value:"event-graph-test.hero"}}},{id:"forEach",kind:"flow.forEach",inputs:{array:H("findAll")}},{id:"classVariable",kind:"variable.get",params:{variable:"dynamicClass"}},{id:"findDynamic",kind:"actor.findByType",inputs:{actorClass:H("classVariable")}}],edges:[]},t=T(e);s(t.getInputType("find","actorClass")).toEqual({kind:"actorClass",classId:"event-graph-test.hero"}),s(t.getOutputType("find","value")).toEqual({kind:"optional",value:{kind:"actor",classId:"event-graph-test.hero"}}),s(t.getOutputType("findAll","value")).toEqual({kind:"array",element:{kind:"actor",classId:"event-graph-test.hero"}}),s(t.getInputType("forEach","array")).toEqual({kind:"array",element:{kind:"actor",classId:"event-graph-test.hero"}}),s(t.getOutputType("forEach","arrayElement")).toEqual({kind:"actor",classId:"event-graph-test.hero"}),s(t.getOutputType("forEach","arrayIndex")).toEqual({kind:"number"}),s(t.getOutputType("findDynamic","value")).toEqual({kind:"optional",value:b}),s(L(e).diagnostics).toEqual([]);const a=structuredClone(e);a.nodes[0].inputs.actorClass={value:"missing.actor"},s(L(a).diagnostics).toContainEqual(s.objectContaining({nodeId:"find",portId:"actorClass",message:'Actor class "missing.actor" does not exist'}))}),p("types tween endpoints from writable continuous actor properties",()=>{const e={version:1,nodes:[{id:"hero",kind:"actor.reference",params:{actorId:"hero"}},{id:"intensity",kind:"actor.tweenProperty",params:{property:"intensity",curve:"Linear"},inputs:{target:H("hero"),duration:{value:1},to:{value:1}}},{id:"enabled",kind:"actor.tweenProperty",params:{property:"enabled",curve:"Linear"},inputs:{target:H("hero"),duration:{value:1},to:{value:!0}}},{id:"progress",kind:"actor.tweenProperty",params:{property:"progress",curve:"Linear"},inputs:{target:H("hero"),duration:{value:1},to:{value:1}}}],edges:[]},t=O(new Map([["hero",F]]),"hero"),a=T(e,t);s(a.getInputType("intensity","to")).toEqual({kind:"number"}),s(a.diagnostics).toContainEqual(s.objectContaining({nodeId:"enabled",message:"Enabled cannot be tweened"})),s(a.diagnostics).toContainEqual(s.objectContaining({nodeId:"progress",message:"Progress is read-only"})),s(B(e.nodes[1],"inputs").map(e=>e.name)).toEqual(["exec","target","to","duration"]),s(B({id:"rotate",kind:"transform.rotateBy"},"inputs").map(e=>e.name)).toEqual(["exec","target","angle","duration"]);const o=structuredClone(e);o.nodes[1].inputs.from={value:0},s(L(o,t).diagnostics).toContainEqual(s.objectContaining({code:"invalid-structure",message:s.stringContaining("from")}))}),p("exposes reflected members and infers concrete actor/component pins",()=>{const e={version:1,nodes:[{id:"begin",kind:"event.beginPlay"},{id:"damaged",kind:"event.observable",params:{actorId:"hero",component:"stats",componentTypeId:"event-graph-test.stale-stats",property:"damaged"}},{id:"scoreSource",kind:"event.source",params:{actorId:"hero",component:"stats",componentTypeId:"event-graph-test.stale-stats",source:"onScore"}},{id:"hero",kind:"actor.reference",params:{actorId:"hero"}},{id:"actorPosition",kind:"actor.getProperty",params:{property:"position"},inputs:{target:H("hero")}},{id:"actorRotation",kind:"actor.getProperty",params:{property:"rotation"},inputs:{target:H("hero")}},{id:"actorScale",kind:"actor.getProperty",params:{property:"scale"},inputs:{target:H("hero")}},{id:"setActorScale",kind:"actor.setProperty",params:{property:"scale"},inputs:{target:H("hero"),value:{value:[1,1,1]}}},{id:"stats",kind:"actor.component",params:{property:"stats",componentTypeId:"event-graph-test.stats"},inputs:{target:H("hero")}},{id:"score",kind:"actor.getProperty",params:{property:"score"},inputs:{target:H("stats")}},{id:"apply",kind:"actor.callMethod",params:{method:"applyDamage",argumentCount:1},inputs:{target:H("stats"),arg0:H("score")}},{id:"reset",kind:"actor.callMethod",params:{method:"reset",argumentCount:0},inputs:{target:H("stats")}},{id:"bindScore",kind:"event.bindSource",params:{source:"onScore",argumentCount:0},inputs:{target:H("stats")}},{id:"bindActorType",kind:"event.bindSource",params:{source:"onActorType",argumentCount:0,arg0ActorClassId:"event-graph-test.hero"},inputs:{target:H("stats")}},{id:"bindScoreViaProperty",kind:"event.bindSource",params:{component:"stats",componentTypeId:"event-graph-test.stale-stats",source:"onScore",argumentCount:0},inputs:{target:H("hero")}}],edges:[W("begin","then","bindScoreViaProperty"),W("bindScoreViaProperty","then","apply")]},t=O(new Map([["hero",F]]),"hero"),a=T(e,t);s(a.diagnostics).toEqual([]),s(a.getOutputType("hero","value")).toEqual({kind:"actor",classId:"event-graph-test.hero"}),s(a.getOutputType("stats","value")).toEqual({kind:"component",classId:"event-graph-test.stats"}),s(a.getOutputType("score","value")).toEqual({kind:"number"}),s(a.getInputType("apply","arg0")).toEqual({kind:"number"}),s(a.getOutputType("apply","value")).toEqual({kind:"boolean"}),s(a.getOutputType("reset","value")).toEqual({kind:"void"}),s(a.getOutputType("damaged","value")).toEqual({kind:"number"}),s(a.getOutputType("scoreSource","value")).toEqual({kind:"number"}),s(a.getOutputType("bindScore","value")).toEqual({kind:"number"}),s(a.getOutputType("bindActorType","value")).toEqual({kind:"actor",classId:"event-graph-test.hero"}),s(a.getOutputType("bindScoreViaProperty","value")).toEqual({kind:"number"}),s(a.getOutputType("actorPosition","value")).toEqual({kind:"vector3"}),s(a.getOutputType("actorRotation","value")).toEqual({kind:"euler"}),s(a.getOutputType("actorScale","value")).toEqual({kind:"vector3"}),s(a.getInputType("setActorScale","value")).toEqual({kind:"vector3"}),s(t.resolveActorClass?.("$self")).toBe(F),s(P(R,"applyDamage")).toMatchObject({label:"Apply Damage",parameters:[{name:"amount",type:{kind:"number"}}],parametersKnown:!0,returnType:{kind:"boolean"}}),s(P(R,"reset")?.returnType).toEqual({kind:"void"}),s(P(z,"reset")?.returnType).toEqual({kind:"void"}),s(B(e.nodes.find(e=>"reset"===e.id),"outputs",a)).not.toEqual(s.arrayContaining([s.objectContaining({name:"value"})])),s(N(R,"score")?.type).toEqual({kind:"number"}),s(N(R,"damaged")).toMatchObject({type:{kind:"observable",value:{kind:"number"}},writable:!1}),s(N(R,"direction")?.type).toEqual({kind:"vector3"}),s(P(R,"project")).toMatchObject({parameters:[{name:"position",type:{kind:"vector3"}}],returnType:{kind:"vector2"}}),s(A(R,"onScore")).toMatchObject({label:"On Score",valueType:{kind:"number"},valueLabel:"Value"}),s(V(a.getOutputType("stats","value")).callables).toEqual(s.arrayContaining([s.objectContaining({methodName:"applyDamage"})])),s(V(a.getOutputType("stats","value")).eventSources).toEqual(s.arrayContaining([s.objectContaining({methodName:"onScore"})])),s(V(a.getOutputType("hero","value")).properties).toEqual(s.arrayContaining([s.objectContaining({propertyName:"position",type:{kind:"vector3"},writable:!0}),s.objectContaining({propertyName:"rotation",type:{kind:"euler"},writable:!0}),s.objectContaining({propertyName:"scale",type:{kind:"vector3"},writable:!0})])),s(V({kind:"actor"}).properties).toHaveLength(3),s(()=>(new f).compile(e,t)).not.toThrow();const o={...e,nodes:[...e.nodes,{id:"log-reset",kind:"debug.log"}],edges:[...e.edges,{from:{nodeId:"reset",port:"value"},to:{nodeId:"log-reset",port:"value"}}]};s(L(o,t).diagnostics).toEqual(s.arrayContaining([s.objectContaining({code:"unavailable-output-port",nodeId:"reset",portId:"value"})])),s(()=>(new f).compile(o,t)).toThrow("unavailable for its inferred type")}),p("exposes TriggerVolume overlap event sources through its graph-visible component",()=>{s(N(g,"trigger")).toMatchObject({propertyName:"trigger",writable:!1,type:{kind:"component",classId:"TriggerVolumeComponent"}});const e=V(x(y)).eventSources;s(e).toEqual(s.arrayContaining([s.objectContaining({methodName:"onBeginOverlap",valueLabel:"Other Actor",valueType:b}),s.objectContaining({methodName:"onEndOverlap",valueLabel:"Other Actor",valueType:b}),s.objectContaining({methodName:"onBeginOverlapWithActorType",parameters:[{name:"actorType",type:m}]}),s.objectContaining({methodName:"onEndOverlapWithActor",parameters:[{name:"actor",type:b}]})]))}),p("reports incompatible connections and prevents invalid code generation",()=>{const e={version:1,nodes:[{id:"begin",kind:"event.beginPlay"},{id:"text",kind:"constant.string",params:{value:"later"}},{id:"delay",kind:"flow.delay",inputs:{durationMs:H("text")}}],edges:[W("begin","then","delay")]};s(T(e).diagnostics).toEqual([s.objectContaining({severity:"error",nodeId:"delay",port:"durationMs",message:"Expected number, received string"})]),s(()=>(new f).compile(e)).toThrow("Expected number, received string")}),p("recognizes vector constructors and validates contextual tuple literals",()=>{s(I(n)).toEqual({kind:"vector2"}),s(I(r)).toEqual({kind:"vector3"}),s(j({kind:"vector3"})).toBe("vector3"),s(M({kind:"vector2"},{kind:"vector2"})).toBe(!0),s(M({kind:"vector2"},{kind:"vector3"})).toBe(!1);const e={version:1,variables:[{name:"position",type:{kind:"vector3"},defaultValue:[0,1,2]}],nodes:[{id:"vector2",kind:"constant.vector2",params:{value:[1,2]}},{id:"set",kind:"variable.set",params:{variable:"position"},inputs:{value:H("vector2")}}],edges:[]};s(T(e).diagnostics).toContainEqual(s.objectContaining({nodeId:"set",port:"value",message:"Expected vector3, received vector2"}));s(L({version:1,variables:[{name:"position",type:{kind:"vector3"},defaultValue:[0,1]}],nodes:[{id:"vector3",kind:"constant.vector3",params:{value:[1,2]}}],edges:[]}).diagnostics).toContainEqual(s.objectContaining({code:"invalid-structure",message:s.stringContaining("incompatible default value")}));s(L({version:1,nodes:[{id:"vector3",kind:"constant.vector3",params:{value:[1,2]}}],edges:[]}).diagnostics).toContainEqual(s.objectContaining({code:"invalid-structure",message:s.stringContaining("requires a finite vector3 tuple")}))}),p("contextualizes authored and untouched vector equality defaults",()=>{const e={version:1,nodes:[{id:"begin",kind:"event.beginPlay"},{id:"vector",kind:"constant.vector2",params:{value:[1,2]}},{id:"authored",kind:"compare.equal",inputs:{a:H("vector"),b:{value:[0,0]}}},{id:"untouched",kind:"compare.equal",inputs:{a:H("vector")}},{id:"branch",kind:"flow.branch",inputs:{condition:H("untouched")}}],edges:[W("begin","then","branch")]},t=T(e);s(t.diagnostics).toEqual([]),s(t.getInputType("authored","b")).toEqual({kind:"vector2"}),s(t.getInputType("untouched","b")).toEqual({kind:"vector2"}),s(L(e).diagnostics).toEqual([]),s((new f).compile(e).beginPlay[0].source).toContain("runtime.createVector2(1, 2).equals(runtime.createVector2(0, 0))")}),p("infers arithmetic nodes for numbers, vectors, and vector-scalar permutations",()=>{const e={version:1,nodes:[{id:"vector3",kind:"constant.vector3",params:{value:[1,2,3]}},{id:"vector2",kind:"constant.vector2",params:{value:[4,5]}},{id:"scale",kind:"constant.number",params:{value:2}},{id:"add",kind:"math.add",inputs:{a:H("vector3"),b:{value:[10,20,30]}}},{id:"leftScale",kind:"math.multiply",inputs:{a:H("scale"),b:H("vector2")}},{id:"rightScale",kind:"math.divide",inputs:{a:H("vector3"),b:H("scale")}},{id:"invalid",kind:"math.subtract",inputs:{a:H("vector2"),b:H("vector3")}}],edges:[]},t=T(e);s(t.getOutputType("add","value")).toEqual({kind:"vector3"}),s(t.getInputType("add","b")).toEqual({kind:"vector3"}),s(t.getOutputType("leftScale","value")).toEqual({kind:"vector2"}),s(t.getInputType("leftScale","a")).toEqual({kind:"number"}),s(t.getOutputType("rightScale","value")).toEqual({kind:"vector3"}),s(t.diagnostics).toContainEqual(s.objectContaining({nodeId:"invalid",message:"Subtract requires vector operands to have the same dimension, received vector2 and vector3"}))}),p("infers vector utility nodes across vector dimensions",()=>{const e={version:1,nodes:[{id:"vector3",kind:"constant.vector3",params:{value:[1,2,3]}},{id:"vector2",kind:"constant.vector2",params:{value:[4,5]}},{id:"length",kind:"vector.length",inputs:{value:H("vector3")}},{id:"normalize",kind:"vector.normalize",inputs:{value:H("vector2")}},{id:"distance",kind:"vector.distance",inputs:{a:H("vector2"),b:{value:[5,6]}}},{id:"lerp",kind:"vector.lerp",inputs:{a:H("vector3"),b:{value:[3,4,5]}}},{id:"cross",kind:"vector.cross",inputs:{a:H("vector3")}},{id:"applyRotation",kind:"vector.applyRotation",inputs:{value:H("vector3")}},{id:"invalidDot",kind:"vector.dot",inputs:{a:H("vector2"),b:H("vector3")}}],edges:[]},t=T(e);s(t.getOutputType("length","value")).toEqual({kind:"number"}),s(t.getOutputType("normalize","value")).toEqual({kind:"vector2"}),s(t.getInputType("distance","b")).toEqual({kind:"vector2"}),s(t.getOutputType("distance","value")).toEqual({kind:"number"}),s(t.getOutputType("lerp","value")).toEqual({kind:"vector3"}),s(t.getOutputType("cross","value")).toEqual({kind:"vector3"}),s(t.getOutputType("applyRotation","value")).toEqual({kind:"vector3"}),s(t.diagnostics).toContainEqual(s.objectContaining({nodeId:"invalidDot",message:"Dot Product requires matching vector dimensions, received vector2 and vector3"}))}),p("types soft asset references, optional handles, Euler literals, and catalog diagnostics",()=>{s(I(o)).toEqual({kind:"euler"}),s(j({kind:"euler"})).toBe("Rotation"),s(M(w("prefab"),w("prefab"))).toBe(!0),s(M(w("prefab"),w("vfx"))).toBe(!1),s(M({kind:"actor"},S({kind:"actor"}))).toBe(!0),s(M(S({kind:"actor"}),{kind:"actor"})).toBe(!1);const e={version:1,variables:[{name:"sequence",type:w("sequence"),defaultValue:{assetId:"sequence-1",assetType:"sequence"}}],nodes:[{id:"prefab",kind:"asset.reference",params:{reference:{assetId:"prefab-1",assetType:"prefab"}}},{id:"vfx",kind:"vfx.play",inputs:{vfx:{value:{assetId:"vfx-1",assetType:"vfx"}}}}],edges:[]};s(C(e).map(e=>e.reference.assetId)).toEqual(["sequence-1","prefab-1","vfx-1"]),s(T(e).getOutputType("prefab","value")).toEqual({kind:"asset",assetType:"prefab"}),s(T(e).getOutputType("vfx","value")).toEqual({kind:"actor",classId:"VfxActor"}),s(L(e,{resolveAssetType:e=>new Map([["sequence-1","sequence"],["prefab-1","prefab"],["vfx-1","mesh"]]).get(e)}).diagnostics).toContainEqual(s.objectContaining({code:"wrong-asset-type",nodeId:"vfx",portId:"vfx"})),s(L(e,{resolveAssetType:()=>{}}).diagnostics).toEqual(s.arrayContaining([s.objectContaining({code:"missing-asset"})]));s(L({version:1,variables:[{name:"rotation",type:{kind:"euler"},defaultValue:[0,1]}],nodes:[],edges:[]}).diagnostics).toContainEqual(s.objectContaining({code:"invalid-structure"}));const t={version:1,nodes:[{id:"spawn",kind:"actor.spawnPrefab",inputs:{location:{value:[1,2,3]}}}],edges:[]},a=D(t);s(a.nodes[0].inputs).toEqual({position:{value:[1,2,3]}}),s(t.nodes[0].inputs).toHaveProperty("location")})});/*
2
+ * Copyright (©) 2026 Hology Interactive AB. All rights reserved.
3
+ * See the LICENSE.md file for details.
4
+ */
@@ -0,0 +1,2 @@
1
+ import 'reflect-metadata';
2
+ //# sourceMappingURL=event-graph.test.d.ts.map
@@ -0,0 +1,4 @@
1
+ import{__decorate as e,__metadata as t}from"tslib";import"reflect-metadata";import{describe as a,expect as n,test as o,vi as r}from"vitest";import{Observable as i,Subject as d}from"rxjs";import{Euler as s,Group as c,MathUtils as l,Quaternion as p,Scene as u,Vector2 as m,Vector3 as g}from"three";r.hoisted(()=>{if("undefined"==typeof HTMLCanvasElement)return;const e=new Proxy({},{get:(e,t)=>(t in e||(e[t]=r.fn()),e[t]),set:(e,t,a)=>(e[t]=a,!0)});Object.defineProperty(HTMLCanvasElement.prototype,"getContext",{configurable:!0,value:()=>e}),"undefined"==typeof AudioBuffer&&Object.defineProperty(globalThis,"AudioBuffer",{configurable:!0,value:class{}})}),r.mock("../gameplay/actors/builtin/index.js",()=>({default:{}})),r.mock("../effects/vfx/vfx-actor.js",()=>({VfxActor:class{}})),r.mock("../effects/vfx/vfx-param.js",()=>({VisualEffect:class{}})),r.mock("../gameplay/services/render.js",()=>({ViewController:class{}})),r.mock("../rendering.js",()=>({RenderingView:class{}})),r.mock("../gameplay/services/physics/physics-system.js",()=>({PhysicsSystem:class{}}));import{Actor as h,BaseActor as v}from"../gameplay/actors/actor.js";import{actorConstructContext as y}from"../gameplay/actors/factory.js";import{ActorComponent as b}from"../gameplay/actors/component.js";import{EventGraphCallable as w,EventGraphCompiler as f,EventGraphEventSource as k,EventGraphInstance as P,EventGraphProperty as C,createDefaultEventGraphDocument as E,hasExecutableEventGraphDocument as B,validateEventGraphDocument as M}from"../gameplay/event-graph/index.js";import{World as I}from"../gameplay/services/world.js";import{Prefab as x}from"../scene/objects/prefab.js";import{SceneMaterializer as A}from"../scene/materializer.js";a("event graph compiler",()=>{o("uses connected values while preserving authored input fallbacks",()=>{const e={version:1,nodes:[{id:"begin",kind:"event.beginPlay"},{id:"connected",kind:"constant.number",params:{value:2}},{id:"log",kind:"debug.log",inputs:{value:O(1)}}],edges:[W("begin","then","log"),{from:L("connected"),to:{nodeId:"log",port:"value"}}]};n((new f).compile(e).beginPlay[0].source).toContain("console.log(2)"),n(e.nodes[2].inputs?.value).toEqual(O(1))}),o("uses sensible core defaults for omitted fixed-type inputs",()=>{const e={version:1,nodes:[{id:"begin",kind:"event.beginPlay"},{id:"multiply",kind:"math.multiply",inputs:{a:O(3)}},{id:"log",kind:"debug.log",inputs:{value:L("multiply")}}],edges:[W("begin","then","log")]};n(M(e).diagnostics).toEqual([]),n((new f).compile(e).beginPlay[0].source).toContain("(3 * 1)"),n(e.nodes[1].inputs?.b).toBeUndefined()}),o("returns validation diagnostics without throwing",()=>{const e={version:1,variables:[{name:"value",type:{kind:"number"},defaultValue:0}],nodes:[{id:"begin",kind:"event.beginPlay"},{id:"set",kind:"variable.set",params:{variable:"value"}}],edges:[W("begin","then","set")]};n(M(e).diagnostics).toContainEqual(n.objectContaining({code:"missing-input",nodeId:"set",portId:"value"}))}),o("allows execution fan-in while keeping data inputs single-source",()=>{const e={version:1,nodes:[{id:"begin",kind:"event.beginPlay"},{id:"branch",kind:"flow.branch",inputs:{condition:O(!0)}},{id:"log",kind:"debug.log",inputs:{value:O("joined")}}],edges:[W("begin","then","branch"),W("branch","true","log"),W("branch","false","log")]};n(M(e).diagnostics).toEqual([]),n((new f).compile(e).beginPlay[0].source.match(/console\.log/g)).toHaveLength(2);const t={version:1,nodes:[{id:"one",kind:"constant.number",params:{value:1}},{id:"two",kind:"constant.number",params:{value:2}},{id:"log",kind:"debug.log"}],edges:[{from:L("one"),to:{nodeId:"log",port:"value"}},{from:L("two"),to:{nodeId:"log",port:"value"}}]};n(M(t).diagnostics).toContainEqual(n.objectContaining({code:"invalid-structure",message:n.stringContaining("has more than one connection")}))}),o("generates and executes math, component property, branch, and callable nodes",async()=>{const e=new V,t={version:1,nodes:[{id:"begin",kind:"event.beginPlay"},{id:"actor",kind:"actor.reference",params:{actorId:"hero"}},{id:"component",kind:"actor.component",params:{property:"stats"},inputs:{target:L("actor")}},{id:"current",kind:"actor.getProperty",params:{property:"score"},inputs:{target:L("component")}},{id:"two",kind:"constant.number",params:{value:2}},{id:"sum",kind:"math.add",inputs:{a:L("current"),b:L("two")}},{id:"set",kind:"actor.setProperty",params:{property:"score"},inputs:{target:L("component"),value:L("sum")}},{id:"branch",kind:"flow.branch",inputs:{condition:O(!0)}},{id:"call",kind:"actor.callMethod",params:{method:"record",argumentCount:1},inputs:{target:L("actor"),arg0:L("sum")}}],edges:[W("begin","then","set"),W("set","then","branch"),W("branch","true","call")]},a=(new f).compile(t);n(a.beginPlay[0].async).toBe(!1),n(a.beginPlay[0].source).toContain("runtime.setProperty"),n(a.beginPlay[0].source).toContain("if (true)"),n(a.beginPlay[0].source).toContain("runtime.callMethod");const o=S(t,new Map([["hero",e]]),e);await o.beginPlay(),n(e.stats.score).toBe(3),n(e.recorded).toEqual([5]),n(()=>o.getProperty(e,"privateValue")).toThrow("@EventGraphProperty"),n(()=>o.callMethod(e,"privateMethod",[])).toThrow("@EventGraphCallable")}),o("gets and sets actor Object3D transforms without replacing their Three.js values",async()=>{const e=new V,t={updateActorTransform:r.fn()},a=e.object.position,o=e.object.rotation,i=e.object.scale,d=S({version:1,nodes:[{id:"begin",kind:"event.beginPlay"},{id:"actor",kind:"actor.reference",params:{actorId:"hero"}},{id:"position",kind:"constant.vector3",params:{value:[10,20,30]}},{id:"scale",kind:"constant.vector3",params:{value:[2,3,4]}},{id:"setPosition",kind:"actor.setProperty",params:{property:"position"},inputs:{target:L("actor"),value:L("position")}},{id:"setRotation",kind:"actor.setProperty",params:{property:"rotation"},inputs:{target:L("actor"),value:O([.1,.2,.3])}},{id:"setScale",kind:"actor.setProperty",params:{property:"scale"},inputs:{target:L("actor"),value:L("scale")}}],edges:[W("begin","then","setPosition"),W("setPosition","then","setRotation"),W("setRotation","then","setScale")]},new Map([["hero",e]]),e,void 0,t);n(d.getProperty(e,"position")).toBe(a),n(d.getProperty(e,"rotation")).toBe(o),n(d.getProperty(e,"scale")).toBe(i),await d.beginPlay(),n(e.object.position).toBe(a),n(e.object.rotation).toBe(o),n(e.object.scale).toBe(i),n(a.toArray()).toEqual([10,20,30]),n(o.toArray()).toEqual([.1,.2,.3,"XYZ"]),n(i.toArray()).toEqual([2,3,4]),n(t.updateActorTransform).toHaveBeenCalledTimes(2),n(t.updateActorTransform).toHaveBeenNthCalledWith(1,e),n(t.updateActorTransform).toHaveBeenNthCalledWith(2,e)}),o("tweens writable actor properties from frame delta time and routes completion separately",async()=>{const e=new V;e.tweenValue=2;const t=new d,a={version:1,nodes:[{id:"begin",kind:"event.beginPlay"},{id:"actor",kind:"actor.reference",params:{actorId:"hero"}},{id:"tween",kind:"actor.tweenProperty",params:{property:"tweenValue",curve:"Linear"},inputs:{target:L("actor"),duration:O(1),to:O(10)}},{id:"immediate",kind:"actor.callMethod",params:{method:"record",argumentCount:1},inputs:{target:L("actor"),arg0:O(1)}},{id:"completed",kind:"actor.callMethod",params:{method:"markCompletion",argumentCount:0},inputs:{target:L("actor")}}],edges:[W("begin","then","tween"),W("tween","then","immediate"),W("tween","completed","completed")]},o=(new f).compile(a);n(o.beginPlay[0].async).toBe(!1),n(o.beginPlay[0].source).toContain("runtime.tweenProperty(");const r=S(a,new Map([["hero",e]]),e,t);await r.beginPlay(),n(e.tweenValue).toBe(2),n(e.recorded).toEqual([1]),n(e.completionCount).toBe(0),t.next(.25),n(e.tweenValue).toBe(4),n(e.completionCount).toBe(0),t.next(.75),n(e.tweenValue).toBe(10),n(e.completionCount).toBe(1),n(t.observed).toBe(!1)}),o("updates physics on every tweened actor position change",async()=>{const e=new V,t=new d,a={updateActorTransform:r.fn()},o=S({version:1,nodes:[{id:"begin",kind:"event.beginPlay"},{id:"actor",kind:"actor.reference",params:{actorId:"hero"}},{id:"tween",kind:"actor.tweenProperty",params:{property:"position",curve:"Linear"},inputs:{target:L("actor"),duration:O(1),to:{value:[10,0,0]}}}],edges:[W("begin","then","tween")]},new Map([["hero",e]]),e,t,a);await o.beginPlay(),t.next(.5),n(e.object.position.toArray()).toEqual([5,0,0]),n(a.updateActorTransform).toHaveBeenCalledTimes(2)}),o("runs local Move, Rotate, and Scale To/By actions from current transforms",async()=>{const e=new d,t={updateActorTransform:r.fn()},a=new V,o=new V,i=new V,c=new V,u=new V,m=new V;a.position.set(1,0,0),o.position.set(2,0,0),i.rotation.set(0,l.degToRad(170),0),u.object.scale.setScalar(2),m.object.scale.setScalar(2);const h=S({version:1,nodes:[],edges:[]},new Map,a,e,t);await h.beginPlay(),h.tweenMoveTo(a,new g(5,0,0),1,"Linear","local",void 0,"moveTo"),h.tweenMoveBy(o,new g(4,0,0),1,"Linear","local",void 0,"moveBy"),h.tweenRotateTo(i,new s(0,l.degToRad(-170),0),1,"Linear","local",void 0,"rotateTo"),h.tweenRotateBy(c,360,1,"Linear","local","y",void 0,"rotateBy"),h.tweenScaleTo(u,new g(6,6,6),1,"Linear",void 0,"scaleTo"),h.tweenScaleBy(m,new g(3,3,3),1,"Linear",void 0,"scaleBy"),e.next(.5),n(a.position.toArray()).toEqual([3,0,0]),n(o.position.toArray()).toEqual([4,0,0]),n(u.object.scale.toArray()).toEqual([4,4,4]),n(m.object.scale.toArray()).toEqual([4,4,4]),j(i.quaternion,(new p).setFromEuler(new s(0,Math.PI,0))),j(c.quaternion,(new p).setFromAxisAngle(new g(0,1,0),Math.PI)),e.next(.5),n(a.position.toArray()).toEqual([5,0,0]),n(o.position.toArray()).toEqual([6,0,0]),j(c.quaternion,new p),n(t.updateActorTransform).toHaveBeenCalledTimes(12)}),o("keeps world-space Move and Rotate actions in world space while parents move",async()=>{const e=new d,t=new V,a=new V,o=new V,r=new V,i=new c,u=new c,m=new c,h=new c;i.position.x=10,u.position.x=10,m.rotation.y=Math.PI/2,h.rotation.y=Math.PI/2,i.add(t.object),u.add(a.object),m.add(o.object),h.add(r.object);const v=S({version:1,nodes:[],edges:[]},new Map,t,e);await v.beginPlay(),v.tweenMoveTo(t,new g(20,0,0),1,"Linear","world",void 0,"moveTo"),v.tweenMoveBy(a,new g(4,0,0),1,"Linear","world",void 0,"moveBy"),v.tweenRotateTo(o,new s(0,Math.PI,0),1,"Linear","world",void 0,"rotateTo"),v.tweenRotateBy(r,180,1,"Linear","world","y",void 0,"rotateBy"),e.next(.5),n(t.object.getWorldPosition(new g).x).toBeCloseTo(15),n(a.object.getWorldPosition(new g).x).toBeCloseTo(12),j(o.object.getWorldQuaternion(new p),(new p).setFromAxisAngle(new g(0,1,0),l.degToRad(135))),j(r.object.getWorldQuaternion(new p),(new p).setFromAxisAngle(new g(0,1,0),Math.PI)),i.position.x=12,u.position.x=11,m.rotation.y=l.degToRad(120),h.rotation.y=l.degToRad(120),e.next(.5),n(t.object.getWorldPosition(new g).x).toBeCloseTo(20),n(a.object.getWorldPosition(new g).x).toBeCloseTo(14),j(o.object.getWorldQuaternion(new p),(new p).setFromAxisAngle(new g(0,1,0),Math.PI)),j(r.object.getWorldQuaternion(new p),(new p).setFromAxisAngle(new g(0,1,0),l.degToRad(270)))}),o("handles replacement, zero-duration completion ordering, and teardown cancellation",async()=>{const e=new d,t=new V,a=S({version:1,nodes:[],edges:[]},new Map,t,e),o=[];await a.beginPlay(),a.tweenProperty(t,"tweenValue",10,1,"Linear",()=>o.push("replaced"),"first"),a.tweenProperty(t,"tweenValue",20,1,"Linear",()=>o.push("replacement"),"second"),e.next(1),n(t.tweenValue).toBe(20),n(o).toEqual(["replacement"]),a.tweenScaleTo(t,new g(2,2,2),0,"Linear",()=>o.push("zero"),"zero"),o.push("immediate"),n(t.object.scale.toArray()).toEqual([2,2,2]),n(o).toEqual(["replacement","immediate"]),await Promise.resolve(),n(o).toEqual(["replacement","immediate","zero"]),a.tweenMoveBy(t,new g(10,0,0),1,"Linear","local",()=>o.push("ended"),"ended"),a.endPlay(),e.next(1),n(o).not.toContain("ended")}),o("materializes vector constants, inline inputs, properties, and variable defaults as fresh Three.js values",async()=>{const e=new V,t={version:1,variables:[{name:"position",type:{kind:"vector3"},defaultValue:[7,8,9]}],nodes:[{id:"begin",kind:"event.beginPlay"},{id:"observable",kind:"event.observable",params:{actorId:"hero",property:"vectorEvent"}},{id:"actor",kind:"actor.reference",params:{actorId:"hero"}},{id:"vector3",kind:"constant.vector3",params:{value:[1,2,3]}},{id:"vector2",kind:"constant.vector2",params:{value:[4,5]}},{id:"position",kind:"variable.get",params:{variable:"position"}},{id:"setProperty",kind:"actor.setProperty",params:{property:"vectorProperty"},inputs:{target:L("actor"),value:L("vector3")}},{id:"first",kind:"actor.callMethod",params:{method:"captureVector3",argumentCount:1},inputs:{target:L("actor"),arg0:L("vector3")}},{id:"second",kind:"actor.callMethod",params:{method:"captureVector3",argumentCount:1},inputs:{target:L("actor"),arg0:L("vector3")}},{id:"inline",kind:"actor.callMethod",params:{method:"captureVector3",argumentCount:1},inputs:{target:L("actor"),arg0:O([10,11,12])}},{id:"variable",kind:"actor.callMethod",params:{method:"captureVector3",argumentCount:1},inputs:{target:L("actor"),arg0:L("position")}},{id:"capture2",kind:"actor.callMethod",params:{method:"captureVector2",argumentCount:1},inputs:{target:L("actor"),arg0:L("vector2")}},{id:"make",kind:"actor.callMethod",params:{method:"makeVector3",argumentCount:0},inputs:{target:L("actor")}},{id:"captureReturn",kind:"actor.callMethod",params:{method:"captureVector3",argumentCount:1},inputs:{target:L("actor"),arg0:L("make")}},{id:"captureObservable",kind:"actor.callMethod",params:{method:"captureVector3",argumentCount:1},inputs:{target:L("actor"),arg0:{nodeId:"observable",port:"value"}}}],edges:[W("begin","then","setProperty"),W("setProperty","then","first"),W("first","then","second"),W("second","then","inline"),W("inline","then","variable"),W("variable","then","capture2"),W("capture2","then","make"),W("make","then","captureReturn"),W("observable","then","captureObservable")]},a=S(t,new Map([["hero",e]]),e);await a.beginPlay(),n(e.vectorProperty).toBeInstanceOf(g),n(e.vectorProperty.toArray()).toEqual([1,2,3]),n(e.vectorSnapshots).toEqual([[1,2,3],[1,2,3],[10,11,12],[7,8,9],[13,14,15]]),n(e.vectorValues[0]).not.toBe(e.vectorValues[1]),n(e.vector2Values[0]).toBeInstanceOf(m),n(e.vector2Values[0].toArray()).toEqual([4,5]);const o=new g(16,17,18);e.vectorEvent.next(o),n(e.vectorSnapshots.at(-1)).toEqual([16,17,18]),n(e.vectorValues.at(-1)).toBe(o);const r=new V;await S(t,new Map([["hero",r]]),r).beginPlay(),n(r.vectorSnapshots[3]).toEqual([7,8,9])}),o("runs vector arithmetic with value semantics for graph-owned storage",async()=>{const e=new V,t={version:1,variables:[{name:"position",type:{kind:"vector3"},defaultValue:[1,2,3]}],nodes:[{id:"begin",kind:"event.beginPlay"},{id:"actor",kind:"actor.reference",params:{actorId:"hero"}},{id:"position",kind:"variable.get",params:{variable:"position"}},{id:"offset",kind:"constant.vector3",params:{value:[4,5,6]}},{id:"one",kind:"constant.vector3",params:{value:[1,1,1]}},{id:"sum",kind:"math.add",inputs:{a:L("position"),b:L("offset")}},{id:"setProperty",kind:"actor.setProperty",params:{property:"vectorProperty"},inputs:{target:L("actor"),value:L("sum")}},{id:"captureSum",kind:"actor.callMethod",params:{method:"captureVector3",argumentCount:1},inputs:{target:L("actor"),arg0:L("sum")}},{id:"capturePosition",kind:"actor.callMethod",params:{method:"captureVector3",argumentCount:1},inputs:{target:L("actor"),arg0:L("position")}},{id:"incremented",kind:"math.add",inputs:{a:L("position"),b:L("one")}},{id:"setVariable",kind:"variable.set",params:{variable:"position"},inputs:{value:L("incremented")}},{id:"firstVariable",kind:"actor.callMethod",params:{method:"captureVector3",argumentCount:1},inputs:{target:L("actor"),arg0:L("position")}},{id:"secondVariable",kind:"actor.callMethod",params:{method:"captureVector3",argumentCount:1},inputs:{target:L("actor"),arg0:L("position")}}],edges:[W("begin","then","setProperty"),W("setProperty","then","captureSum"),W("captureSum","then","capturePosition"),W("capturePosition","then","setVariable"),W("setVariable","then","firstVariable"),W("firstVariable","then","secondVariable")]};await S(t,new Map([["hero",e]]),e).beginPlay(),n(e.vectorProperty.toArray()).toEqual([5,7,9]),n(e.vectorSnapshots).toEqual([[5,7,9],[1,2,3],[2,3,4],[2,3,4]])}),o("runs vector utility nodes for common gameplay math",async()=>{const e=new V,t={version:1,nodes:[{id:"begin",kind:"event.beginPlay"},{id:"actor",kind:"actor.reference",params:{actorId:"hero"}},{id:"forward",kind:"constant.vector3",params:{value:[1,0,0]}},{id:"up",kind:"constant.vector3",params:{value:[0,1,0]}},{id:"zero",kind:"constant.vector3",params:{value:[0,0,0]}},{id:"ten",kind:"constant.vector3",params:{value:[10,0,0]}},{id:"threeFour",kind:"constant.vector3",params:{value:[0,3,4]}},{id:"cross",kind:"vector.cross",inputs:{a:L("forward"),b:L("up")}},{id:"normalize",kind:"vector.normalize",inputs:{value:L("threeFour")}},{id:"lerp",kind:"vector.lerp",inputs:{a:L("zero"),b:L("ten"),alpha:O(.25)}},{id:"rotated",kind:"vector.applyRotation",inputs:{value:L("forward"),rotation:O([0,Math.PI/2,0])}},{id:"distance",kind:"vector.distance",inputs:{a:L("forward"),b:L("up")}},{id:"dot",kind:"vector.dot",inputs:{a:L("forward"),b:L("up")}},{id:"length",kind:"vector.length",inputs:{value:L("threeFour")}},{id:"captureCross",kind:"actor.callMethod",params:{method:"captureVector3",argumentCount:1},inputs:{target:L("actor"),arg0:L("cross")}},{id:"captureNormalize",kind:"actor.callMethod",params:{method:"captureVector3",argumentCount:1},inputs:{target:L("actor"),arg0:L("normalize")}},{id:"captureLerp",kind:"actor.callMethod",params:{method:"captureVector3",argumentCount:1},inputs:{target:L("actor"),arg0:L("lerp")}},{id:"captureRotated",kind:"actor.callMethod",params:{method:"captureVector3",argumentCount:1},inputs:{target:L("actor"),arg0:L("rotated")}},{id:"recordDistance",kind:"actor.callMethod",params:{method:"record",argumentCount:1},inputs:{target:L("actor"),arg0:L("distance")}},{id:"recordDot",kind:"actor.callMethod",params:{method:"record",argumentCount:1},inputs:{target:L("actor"),arg0:L("dot")}},{id:"recordLength",kind:"actor.callMethod",params:{method:"record",argumentCount:1},inputs:{target:L("actor"),arg0:L("length")}}],edges:[W("begin","then","captureCross"),W("captureCross","then","captureNormalize"),W("captureNormalize","then","captureLerp"),W("captureLerp","then","captureRotated"),W("captureRotated","then","recordDistance"),W("recordDistance","then","recordDot"),W("recordDot","then","recordLength")]};await S(t,new Map([["hero",e]]),e).beginPlay(),n(e.vectorSnapshots[0]).toEqual([0,0,1]),n(e.vectorSnapshots[1][0]).toBeCloseTo(0),n(e.vectorSnapshots[1][1]).toBeCloseTo(.6),n(e.vectorSnapshots[1][2]).toBeCloseTo(.8),n(e.vectorSnapshots[2]).toEqual([2.5,0,0]),n(e.vectorSnapshots[3][0]).toBeCloseTo(0),n(e.vectorSnapshots[3][1]).toBeCloseTo(0),n(e.vectorSnapshots[3][2]).toBeCloseTo(-1),n(e.recorded[0]).toBeCloseTo(Math.SQRT2),n(e.recorded[1]).toBe(0),n(e.recorded[2]).toBe(5)}),o("keeps synchronous update handlers allocation-free and owns observable subscriptions",async()=>{const e=new V,t=new d,a={version:1,nodes:[{id:"update",kind:"event.update"},{id:"observable",kind:"event.observable",params:{actorId:"hero",property:"damage"}},{id:"end",kind:"event.endPlay"},{id:"actor",kind:"actor.reference",params:{actorId:"hero"}},{id:"tick",kind:"actor.callMethod",params:{method:"tick",argumentCount:1},inputs:{target:L("actor"),arg0:{nodeId:"update",port:"deltaTime"}}},{id:"damage",kind:"actor.callMethod",params:{method:"takeDamage",argumentCount:1},inputs:{target:L("actor"),arg0:{nodeId:"observable",port:"value"}}},{id:"ended",kind:"actor.callMethod",params:{method:"graphEnded",argumentCount:0},inputs:{target:L("actor")}}],edges:[W("update","then","tick"),W("observable","then","damage"),W("end","then","ended")]},o=(new f).compile(a);n(o.update[0].async).toBe(!1),n(o.update[0].source).toContain("eventValue");const r=S(a,new Map([["hero",e]]),e,t);await r.beginPlay(),t.next(.25),e.damage.next(7),n(e.deltaTimes).toEqual([.25]),n(e.damageTaken).toEqual([7]),r.endPlay(),t.next(.5),e.damage.next(9),n(e.deltaTimes).toEqual([.25]),n(e.damageTaken).toEqual([7]),n(e.graphEndCount).toBe(1)}),o("subscribes static event sources and routes emitted values through event pins",async()=>{const e=new V,t={version:1,nodes:[{id:"source",kind:"event.source",params:{actorId:"hero",source:"onNumberEvent"}},{id:"actor",kind:"actor.reference",params:{actorId:"hero"}},{id:"record",kind:"actor.callMethod",params:{method:"record",argumentCount:1},inputs:{target:L("actor"),arg0:{nodeId:"source",port:"value"}}}],edges:[W("source","then","record")]},a=(new f).compile(t,{resolveActorClass:e=>"hero"===e?V:void 0});n(a.eventSources).toHaveLength(1),n(a.eventSources[0]).toMatchObject({actorId:"hero",method:"onNumberEvent"});const o=S(t,new Map([["hero",e]]),e);await o.beginPlay(),e.numberEvent.next(12),n(e.recorded).toEqual([12]),o.endPlay(),e.numberEvent.next(13),n(e.recorded).toEqual([12])}),o("binds runtime event sources once per node and replaces subscriptions on rebind",async()=>{const e=new V,t=new d,a={version:1,nodes:[{id:"update",kind:"event.update"},{id:"actor",kind:"actor.reference",params:{actorId:"hero"}},{id:"bind",kind:"event.bindSource",params:{source:"onNumberEvent",argumentCount:0},inputs:{target:L("actor")}},{id:"immediate",kind:"actor.callMethod",params:{method:"markCompletion",argumentCount:0},inputs:{target:L("actor")}},{id:"record",kind:"actor.callMethod",params:{method:"record",argumentCount:1},inputs:{target:L("actor"),arg0:{nodeId:"bind",port:"value"}}}],edges:[W("update","then","bind"),W("bind","then","immediate"),W("bind","triggered","record")]},o=(new f).compile(a,{resolveActorClass:e=>"hero"===e?V:void 0});n(o.update[0].source).toContain("runtime.bindEventSource");const r=S(a,new Map([["hero",e]]),e,t);await r.beginPlay(),t.next(.016),t.next(.016),n(e.completionCount).toBe(2),e.numberEvent.next(7),n(e.recorded).toEqual([7]),r.endPlay(),e.numberEvent.next(8),n(e.recorded).toEqual([7])}),o("awaits actor class spawn nodes before continuing execution",async()=>{const e=new V,t=new q,a=r.fn(async()=>t),o={version:1,nodes:[{id:"begin",kind:"event.beginPlay"},{id:"spawn",kind:"actor.spawnClass",params:{classId:"SpawnedGraphActor"}},{id:"mark",kind:"actor.callMethod",params:{method:"mark",argumentCount:0},inputs:{target:L("spawn")}}],edges:[W("begin","then","spawn"),W("spawn","then","mark")]},i=(new f).compile(o);n(i.beginPlay[0].async).toBe(!0),n(i.beginPlay[0].source).toContain("await runtime.spawnActor");const d=new P(o,{world:{spawnActor:a},actorsById:new Map([["owner",e]]),mainActor:e});await d.beginPlay(),n(a).toHaveBeenCalledOnce(),n(t.marked).toBe(!0)}),o("finds a registered actor type through a statically selected class input",async()=>{const e=new V,t=new q,a=r.fn(()=>t),o={version:1,nodes:[{id:"begin",kind:"event.beginPlay"},{id:"find",kind:"actor.findByType",inputs:{actorClass:{value:"SpawnedGraphActor"}}},{id:"valid",kind:"flow.isValid",inputs:{value:L("find")}},{id:"mark",kind:"actor.callMethod",params:{method:"mark",argumentCount:0},inputs:{target:L("valid")}}],edges:[W("begin","then","valid"),W("valid","valid","mark")]},i=new P(o,{world:{spawnActor:r.fn(),findActorByType:a},actorsById:new Map([["owner",e]]),mainActor:e});await i.beginPlay(),n(a).toHaveBeenCalledWith(q),n(t.marked).toBe(!0)}),o("plays typed transient assets, auto-destroys on completion, and preserves spawned prefabs",async()=>{const e=new V,t=new V,a=new V,o=new d,i=new d,c={id:"spawned-prefab"},l={spawnPrefab:r.fn(async()=>({instance:c,mainActor:null})),playVfx:r.fn(async()=>({actor:t,completed:o})),playSequence:r.fn(async()=>({actor:a,completed:i})),destroyPrefab:r.fn(),destroyActor:r.fn()},p={version:1,nodes:[{id:"begin",kind:"event.beginPlay"},{id:"vfx",kind:"vfx.play",inputs:{vfx:{value:{assetId:"vfx-1",assetType:"vfx"}}}},{id:"sequence",kind:"sequence.play",inputs:{sequence:{value:{assetId:"sequence-1",assetType:"sequence"}}}},{id:"prefab",kind:"actor.spawnPrefab",inputs:{prefab:{value:{assetId:"prefab-1",assetType:"prefab"}}}},{id:"owner",kind:"actor.reference",params:{actorId:"$self"}},{id:"completed",kind:"actor.callMethod",params:{method:"markCompletion",argumentCount:0},inputs:{target:L("owner")}}],edges:[W("begin","then","vfx"),W("vfx","then","sequence"),W("vfx","completed","completed"),W("sequence","then","prefab")]},u=new P(p,{world:{spawnActor:r.fn()},assets:l,actorsById:new Map,mainActor:e});await u.beginPlay(),n(l.playVfx).toHaveBeenCalledWith("vfx-1",n.any(g),n.any(s)),n(l.playSequence).toHaveBeenCalledWith("sequence-1",e,null,!1),n(l.spawnPrefab).toHaveBeenCalledWith("prefab-1",n.any(g),n.any(s)),n(e.completionCount).toBe(0),o.next(),await new Promise(e=>queueMicrotask(e)),n(e.completionCount).toBe(1),n(l.destroyActor).toHaveBeenCalledWith(t),u.endPlay(),i.next(),n(e.completionCount).toBe(1),n(l.destroyActor).toHaveBeenCalledWith(a),n(l.destroyPrefab).not.toHaveBeenCalled()}),o("destroys transient asset handles that finish loading after graph teardown",async()=>{const e=new V;let t;const a=new Promise(e=>{t=e}),o={spawnPrefab:r.fn(),playVfx:r.fn(()=>a),playSequence:r.fn(),destroyPrefab:r.fn(),destroyActor:r.fn()},i={version:1,nodes:[{id:"begin",kind:"event.beginPlay"},{id:"vfx",kind:"vfx.play",inputs:{vfx:{value:{assetId:"vfx-1",assetType:"vfx"}}}}],edges:[W("begin","then","vfx")]},d=new P(i,{world:{spawnActor:r.fn()},assets:o,actorsById:new Map}),s=d.beginPlay();d.endPlay(),t({actor:e}),await n(s).rejects.toThrow("Event graph execution was cancelled"),n(o.destroyActor).toHaveBeenCalledWith(e)}),o("persists graph variables across synchronous update executions",async()=>{const e=new V,t=new d,a={version:1,variables:[{name:"count",defaultValue:0}],nodes:[{id:"update",kind:"event.update"},{id:"actor",kind:"actor.reference",params:{actorId:"hero"}},{id:"count",kind:"variable.get",params:{variable:"count"}},{id:"one",kind:"constant.number",params:{value:1}},{id:"incremented",kind:"math.add",inputs:{a:L("count"),b:L("one")}},{id:"set",kind:"variable.set",params:{variable:"count"},inputs:{value:L("incremented")}},{id:"record",kind:"actor.callMethod",params:{method:"record",argumentCount:1},inputs:{target:L("actor"),arg0:L("count")}}],edges:[W("update","then","set"),W("set","then","record")]},o=(new f).compile(a);n(o.update[0].async).toBe(!1),n(o.update[0].source).toContain("runtime.getVariable"),n(o.update[0].source).toContain("runtime.setVariable");const r=S(a,new Map([["hero",e]]),e,t);await r.beginPlay(),t.next(.1),t.next(.1),n(e.recorded).toEqual([1,2])}),o("lowers parallel branches and delays through the structured flow compiler",async()=>{const e=new V,t={version:1,nodes:[{id:"begin",kind:"event.beginPlay"},{id:"parallel",kind:"flow.parallel",params:{ports:["delayed","immediate"]}},{id:"delay",kind:"flow.delay",inputs:{durationMs:O(1)}},{id:"actor",kind:"actor.reference",params:{actorId:"hero"}},{id:"delayedValue",kind:"constant.number",params:{value:1}},{id:"immediateValue",kind:"constant.number",params:{value:2}},{id:"doneValue",kind:"constant.number",params:{value:3}},{id:"recordDelayed",kind:"actor.callMethod",params:{method:"record",argumentCount:1},inputs:{target:L("actor"),arg0:L("delayedValue")}},{id:"recordImmediate",kind:"actor.callMethod",params:{method:"record",argumentCount:1},inputs:{target:L("actor"),arg0:L("immediateValue")}},{id:"recordDone",kind:"actor.callMethod",params:{method:"record",argumentCount:1},inputs:{target:L("actor"),arg0:L("doneValue")}}],edges:[W("begin","then","parallel"),W("parallel","delayed","delay"),W("delay","then","recordDelayed"),W("parallel","immediate","recordImmediate"),W("parallel","then","recordDone")]},a=(new f).compile(t);n(a.beginPlay[0].async).toBe(!0),n(a.beginPlay[0].source).toContain("await Promise.all"),n(a.beginPlay[0].source).toContain("runtime.delay(1)");const o=S(t,new Map([["hero",e]]),e);await o.beginPlay(),n(e.recorded).toEqual([2,1,3])}),o("runs Delay through the game-time timer",async()=>{const e=new V,t=new d,a={setInterval:r.fn(()=>1),clearInterval:r.fn(),createTimer:r.fn(()=>t)},o={version:1,nodes:[{id:"begin",kind:"event.beginPlay"},{id:"delay",kind:"flow.delay",inputs:{durationMs:O(250)}},{id:"actor",kind:"actor.reference",params:{actorId:"hero"}},{id:"record",kind:"actor.callMethod",params:{method:"record",argumentCount:1},inputs:{target:L("actor"),arg0:O(1)}}],edges:[W("begin","then","delay"),W("delay","then","record")]},i=new P(o,{world:{spawnActor:r.fn()},actorsById:new Map([["hero",e]]),mainActor:e,timer:a}).beginPlay();n(a.createTimer).toHaveBeenCalledWith(250),n(e.recorded).toEqual([]),t.next(),t.complete(),await i,n(e.recorded).toEqual([1])}),o("owns game-time intervals and clears them when the graph ends",async()=>{const e=new V;let t;const a={setInterval:r.fn(e=>(t=e,42)),clearInterval:r.fn()},o={version:1,nodes:[{id:"begin",kind:"event.beginPlay"},{id:"interval",kind:"flow.interval",inputs:{intervalMs:O(250)}},{id:"actor",kind:"actor.reference",params:{actorId:"hero"}},{id:"tick",kind:"actor.callMethod",params:{method:"record",argumentCount:1},inputs:{target:L("actor"),arg0:O(1)}},{id:"started",kind:"actor.callMethod",params:{method:"record",argumentCount:1},inputs:{target:L("actor"),arg0:O(2)}}],edges:[W("begin","then","interval"),W("interval","interval","tick"),W("interval","then","started")]},i=new P(o,{world:{spawnActor:r.fn()},actorsById:new Map([["hero",e]]),mainActor:e,timer:a});await i.beginPlay(),n(a.setInterval).toHaveBeenCalledWith(n.any(Function),250,e),n(e.recorded).toEqual([2]),t?.(),n(e.recorded).toEqual([2,1]),i.endPlay(),n(a.clearInterval).toHaveBeenCalledWith(42)}),o("shares Do Once and Gate state across execution input entry points",async()=>{const e=new V,t=S({version:1,nodes:[{id:"onceFirst",kind:"event.beginPlay"},{id:"onceReset",kind:"event.beginPlay"},{id:"onceSecond",kind:"event.beginPlay"},{id:"gateClose",kind:"event.beginPlay"},{id:"gateBlocked",kind:"event.beginPlay"},{id:"gateOpen",kind:"event.beginPlay"},{id:"gateAllowed",kind:"event.beginPlay"},{id:"once",kind:"flow.doOnce"},{id:"gate",kind:"flow.gate"},{id:"actor",kind:"actor.reference",params:{actorId:"hero"}},{id:"onceRecord",kind:"actor.callMethod",params:{method:"record",argumentCount:1},inputs:{target:L("actor"),arg0:O(1)}},{id:"gateRecord",kind:"actor.callMethod",params:{method:"record",argumentCount:1},inputs:{target:L("actor"),arg0:O(2)}}],edges:[W("onceFirst","then","once"),{from:{nodeId:"onceReset",port:"then"},to:{nodeId:"once",port:"reset"}},W("onceSecond","then","once"),W("once","then","onceRecord"),{from:{nodeId:"gateClose",port:"then"},to:{nodeId:"gate",port:"close"}},{from:{nodeId:"gateBlocked",port:"then"},to:{nodeId:"gate",port:"enter"}},{from:{nodeId:"gateOpen",port:"then"},to:{nodeId:"gate",port:"open"}},{from:{nodeId:"gateAllowed",port:"then"},to:{nodeId:"gate",port:"enter"}},W("gate","exit","gateRecord")]},new Map([["hero",e]]),e);await t.beginPlay(),n(e.recorded).toEqual([1,1,2])}),o("iterates arrays in order and continues after the final element",async()=>{const e=new V,t=S({version:1,nodes:[{id:"begin",kind:"event.beginPlay"},{id:"actor",kind:"actor.reference",params:{actorId:"hero"}},{id:"forEach",kind:"flow.forEach",inputs:{array:O([4,7])}},{id:"recordElement",kind:"actor.callMethod",params:{method:"record",argumentCount:1},inputs:{target:L("actor"),arg0:L("forEach","arrayElement")}},{id:"recordIndex",kind:"actor.callMethod",params:{method:"recordIndex",argumentCount:1},inputs:{target:L("actor"),arg0:L("forEach","arrayIndex")}},{id:"completed",kind:"actor.callMethod",params:{method:"markCompletion",argumentCount:0},inputs:{target:L("actor")}}],edges:[W("begin","then","forEach"),W("forEach","loopBody","recordElement"),W("recordElement","then","recordIndex"),W("forEach","completed","completed")]},new Map([["hero",e]]),e);await t.beginPlay(),n(e.recorded).toEqual([4,7]),n(e.recordedIndexes).toEqual([0,1]),n(e.completionCount).toBe(1)}),o("cancels delayed update continuations when the graph ends",async()=>{r.useFakeTimers();try{const e=new V,t=new d,a=r.fn(),o={version:1,nodes:[{id:"update",kind:"event.update"},{id:"delay",kind:"flow.delay",inputs:{durationMs:O(100)}},{id:"actor",kind:"actor.reference",params:{actorId:"hero"}},{id:"value",kind:"constant.number",params:{value:1}},{id:"record",kind:"actor.callMethod",params:{method:"record",argumentCount:1},inputs:{target:L("actor"),arg0:L("value")}}],edges:[W("update","then","delay"),W("delay","then","record")]},i=new P(o,{world:{spawnActor:r.fn()},actorsById:new Map([["hero",e]]),mainActor:e,updates:t,timer:F(),onError:a});await i.beginPlay(),t.next(.1),i.endPlay(),await r.runAllTimersAsync(),n(e.recorded).toEqual([]),n(a).not.toHaveBeenCalled()}finally{r.useRealTimers()}}),o("lowers switch and callable error routes through structured compiler routes",async()=>{const e=new V,t={version:1,nodes:[{id:"begin",kind:"event.beginPlay"},{id:"switch",kind:"flow.switch",params:{cases:[{value:"a",port:"a"},{value:"b",port:"b"}]},inputs:{value:O("b")}},{id:"actor",kind:"actor.reference",params:{actorId:"hero"}},{id:"one",kind:"constant.number",params:{value:1}},{id:"two",kind:"constant.number",params:{value:2}},{id:"three",kind:"constant.number",params:{value:3}},{id:"recordA",kind:"actor.callMethod",params:{method:"record",argumentCount:1},inputs:{target:L("actor"),arg0:L("one")}},{id:"recordB",kind:"actor.callMethod",params:{method:"record",argumentCount:1},inputs:{target:L("actor"),arg0:L("two")}},{id:"tryCall",kind:"actor.tryCallMethod",params:{method:"fail",argumentCount:0},inputs:{target:L("actor")}},{id:"markFailure",kind:"actor.callMethod",params:{method:"markFailure",argumentCount:0},inputs:{target:L("actor")}},{id:"recordDone",kind:"actor.callMethod",params:{method:"record",argumentCount:1},inputs:{target:L("actor"),arg0:L("three")}}],edges:[W("begin","then","switch"),W("switch","a","recordA"),W("switch","b","recordB"),W("switch","then","tryCall"),W("tryCall","error","markFailure"),W("tryCall","then","recordDone")]},a=(new f).compile(t);n(a.beginPlay[0].source).toContain('switch ("b")'),n(a.beginPlay[0].source).toContain("try {"),n(a.beginPlay[0].source).toContain("catch (caught_");const o=S(t,new Map([["hero",e]]),e);await o.beginPlay(),n(e.recorded).toEqual([2,3]),n(e.failureCount).toBe(1)})}),o("World owns a prefab event graph from actor begin play through actor teardown",async()=>{const e=new V,t=new d,a=[];e.lifecycle=a;const o={id:"prefab",name:"Prefab",type:"prefab",prefab:{objects:[],mainActorId:"hero",eventGraph:{version:1,nodes:[{id:"begin",kind:"event.beginPlay"},{id:"end",kind:"event.endPlay"},{id:"actor",kind:"actor.reference",params:{actorId:"hero"}},{id:"graphBegin",kind:"actor.callMethod",params:{method:"graphBegin",argumentCount:0},inputs:{target:L("actor")}},{id:"graphEnd",kind:"actor.callMethod",params:{method:"graphEnd",argumentCount:0},inputs:{target:L("actor")}}],edges:[W("begin","then","graphBegin"),W("end","then","graphEnd")]}}},i=new c,s=R({inEditor:!0,setScene:r.fn(),createFromPrefabAsset:r.fn(async()=>({object:i,actors:[e],actorsById:new Map([["hero",e]]),mainActor:e}))},t),l=await s.spawnPrefab(new x(o));n(a).toEqual(["actor-begin","graph-begin"]),s.removePrefab(l),n(a).toEqual(["actor-begin","graph-begin","graph-end","actor-end"])}),o("World skips prefab event graphs with no connected event execution outputs",async()=>{const e=new V,t=new d,a=[];e.lifecycle=a;const o=E(),i={id:"prefab",name:"Prefab",type:"prefab",prefab:{objects:[],mainActorId:"hero",eventGraph:o}},s=R({inEditor:!0,setScene:r.fn(),createFromPrefabAsset:r.fn(async()=>({object:new c,actors:[e],actorsById:new Map([["hero",e]]),mainActor:e}))},t),l=await s.spawnPrefab(new x(i));n(B(o)).toBe(!1),n(l.eventGraph).toBeUndefined(),n(a).toEqual(["actor-begin"]),s.removePrefab(l),n(a).toEqual(["actor-begin","actor-end"])}),o("SceneMaterializer collects executable scene prefab graphs but excludes no-op and World runtime spawns",async()=>{const e=function(){const e={getObjects:()=>[],onCreate:r.fn(),onUpdate:r.fn(),onRemove:r.fn()},t={onCreate:new d,onUpdate:new d};return new A(new u,e,t,{},{},[],[],{create:r.fn(),initActor:r.fn()})}();e.inEditor=!1;const t={version:1,nodes:[{id:"begin",kind:"event.beginPlay"},{id:"log",kind:"debug.log"}],edges:[W("begin","then","log")]},a=E(),o={id:"decoration",name:"Decoration",type:"prefab",prefab:{objects:[],eventGraph:a}},i={id:"interactable",name:"Interactable",type:"prefab",prefab:{objects:[],eventGraph:t}};await e.createFromPrefabAsset(i,{sceneObjectChain:["placed-interactable"]}),await e.createFromPrefabAsset(i,{sceneObjectChain:["runtime-interactable"],runtimeSpawn:!0}),await e.createFromPrefabAsset(o,{sceneObjectChain:["placed-decoration"]}),n(B(t)).toBe(!0),n(B(a)).toBe(!1),n(e.prefabEventGraphInstances).toHaveLength(1),n(e.prefabEventGraphInstances[0].id).toBe("placed-interactable"),n(e.prefabEventGraphInstances[0].graph).toBe(t)}),o("World owns scene-materialized prefab graphs around actor lifetime",async()=>{const e=new V,t=new d,a=[];e.lifecycle=a;const o={id:"scene/interactable",graph:{version:1,nodes:[{id:"begin",kind:"event.beginPlay"},{id:"end",kind:"event.endPlay"},{id:"actor",kind:"actor.reference",params:{actorId:"hero"}},{id:"graphBegin",kind:"actor.callMethod",params:{method:"graphBegin",argumentCount:0},inputs:{target:L("actor")}},{id:"graphEnd",kind:"actor.callMethod",params:{method:"graphEnd",argumentCount:0},inputs:{target:L("actor")}}],edges:[W("begin","then","graphBegin"),W("end","then","graphEnd")]},object:new c,actors:[e],actorsById:new Map([["hero",e]]),mainActor:e},r=new d,i=new d,s={prefabEventGraphAdded$:r,prefabEventGraphRemoved$:i,prefabEventGraphInstances:[o]},l=R({},t);await l.startSceneEventGraphs(s),n(a).toEqual(["actor-begin","graph-begin"]),i.next(o),n(a).toEqual(["actor-begin","graph-begin","graph-end","actor-end"]),l.stopSceneEventGraphs()});class T extends b{constructor(){super(...arguments),this.score=1}}e([C(),t("design:type",Object)],T.prototype,"score",void 0);let V=class extends v{constructor(){super(),this.stats=new T,this.damage=new d,this.privateValue=5,this.recorded=[],this.recordedIndexes=[],this.deltaTimes=[],this.damageTaken=[],this.graphEndCount=0,this.failureCount=0,this.completionCount=0,this.vectorValues=[],this.vectorSnapshots=[],this.vector2Values=[],this.vectorProperty=new g,this.tweenValue=0,this.vectorEvent=new d,this.numberEvent=new d,this.stats.actor=this,y.actor=null}onBeginPlay(){this.lifecycle?.push("actor-begin")}onEndPlay(){this.lifecycle?.push("actor-end")}record(e){this.recorded.push(e)}recordIndex(e){this.recordedIndexes.push(e)}tick(e){this.deltaTimes.push(e)}takeDamage(e){this.damageTaken.push(e)}graphEnded(){this.graphEndCount++}graphBegin(){this.lifecycle?.push("graph-begin")}graphEnd(){this.lifecycle?.push("graph-end")}fail(){throw new Error("expected graph test failure")}markFailure(){this.failureCount++}markCompletion(){this.completionCount++}privateMethod(){}captureVector3(e){this.vectorValues.push(e),this.vectorSnapshots.push(e.toArray()),e.set(99,99,99)}captureVector2(e){this.vector2Values.push(e)}makeVector3(){return new g(13,14,15)}onNumberEvent(){return this.numberEvent}};e([C(),t("design:type",Object)],V.prototype,"damage",void 0),e([C({type:g}),t("design:type",Object)],V.prototype,"vectorProperty",void 0),e([C({type:Number}),t("design:type",Object)],V.prototype,"tweenValue",void 0),e([C({observableValueType:g,writable:!1}),t("design:type",Object)],V.prototype,"vectorEvent",void 0),e([w(),t("design:type",Function),t("design:paramtypes",[Number]),t("design:returntype",void 0)],V.prototype,"record",null),e([w(),t("design:type",Function),t("design:paramtypes",[Number]),t("design:returntype",void 0)],V.prototype,"recordIndex",null),e([w(),t("design:type",Function),t("design:paramtypes",[Number]),t("design:returntype",void 0)],V.prototype,"tick",null),e([w(),t("design:type",Function),t("design:paramtypes",[Number]),t("design:returntype",void 0)],V.prototype,"takeDamage",null),e([w(),t("design:type",Function),t("design:paramtypes",[]),t("design:returntype",void 0)],V.prototype,"graphEnded",null),e([w(),t("design:type",Function),t("design:paramtypes",[]),t("design:returntype",void 0)],V.prototype,"graphBegin",null),e([w(),t("design:type",Function),t("design:paramtypes",[]),t("design:returntype",void 0)],V.prototype,"graphEnd",null),e([w(),t("design:type",Function),t("design:paramtypes",[]),t("design:returntype",void 0)],V.prototype,"fail",null),e([w(),t("design:type",Function),t("design:paramtypes",[]),t("design:returntype",void 0)],V.prototype,"markFailure",null),e([w(),t("design:type",Function),t("design:paramtypes",[]),t("design:returntype",void 0)],V.prototype,"markCompletion",null),e([w({parameters:[{name:"value",type:g}]}),t("design:type",Function),t("design:paramtypes",[g]),t("design:returntype",void 0)],V.prototype,"captureVector3",null),e([w({parameters:[{name:"value",type:m}]}),t("design:type",Function),t("design:paramtypes",[m]),t("design:returntype",void 0)],V.prototype,"captureVector2",null),e([w({returns:g}),t("design:type",Function),t("design:paramtypes",[]),t("design:returntype",g)],V.prototype,"makeVector3",null),e([k({label:"On Number Event",emits:Number}),t("design:type",Function),t("design:paramtypes",[]),t("design:returntype",void 0)],V.prototype,"onNumberEvent",null),V=e([h({typeId:"event-graph-test.runtime-actor"}),t("design:paramtypes",[])],V);let q=class extends v{constructor(){super(),this.marked=!1,y.actor=null}mark(){this.marked=!0}};function j(e,t){n(Math.abs(e.dot(t))).toBeCloseTo(1,5)}function S(e,t,a,n,o){return new P(e,{world:{spawnActor:r.fn()},actorsById:t,mainActor:a,updates:n,timer:F(),physics:o})}function F(){return{setInterval:(e,t)=>window.setInterval(e,t),clearInterval:e=>window.clearInterval(e),createTimer:e=>new i(t=>{const a=setTimeout(()=>{t.next(),t.complete()},e);return()=>clearTimeout(a)})}}function R(e,t){const a=Object.create(I.prototype);return a.scene=new u,a.actors=[],a.actorAdded=new d,a.actorRemoved=new d,a.actorFactory={inEditor:!1},a.materializer=e,a.sceneEventGraphs=new Map,a.sceneEventGraphSubscriptions=[],a.physics={addFromScene:r.fn(),removeActor:r.fn(),removeRemoved:r.fn()},a.viewController={onUpdate:()=>t},a}function L(e,t="value"){return{nodeId:e,port:t}}function O(e){return{value:e}}function W(e,t,a){return{from:{nodeId:e,port:t},to:{nodeId:a,port:"exec"}}}e([w(),t("design:type",Function),t("design:paramtypes",[]),t("design:returntype",void 0)],q.prototype,"mark",null),q=e([h(),t("design:paramtypes",[])],q);/*
2
+ * Copyright (©) 2026 Hology Interactive AB. All rights reserved.
3
+ * See the LICENSE.md file for details.
4
+ */
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=input.test.d.ts.map
@@ -0,0 +1,4 @@
1
+ import{expect as t,test as e}from"vitest";import{ActionInput as o,ButtonInput as a,ToggleInput as i}from"../gameplay/input/input.js";e("button input uses hold mode by default",()=>{const e=new a,o=[];e.onStart(()=>o.push(e.activated)),e.onEnd(()=>o.push(e.activated)),e.toggle(!0),t(e.activated).toBe(!0),e.toggle(!0),t(o).toEqual([!0]),e.toggle(!1),t(e.activated).toBe(!1),t(o).toEqual([!0,!1])}),e("button input can switch to toggle mode at runtime",()=>{const e=new a,o=[];e.onStart(()=>o.push(e.activated)),e.onEnd(()=>o.push(e.activated)),e.mode="toggle",e.toggle(!0),t(e.activated).toBe(!1),e.toggle(!1),t(e.activated).toBe(!0),e.toggle(!1),t(e.activated).toBe(!1),t(o).toEqual([!0,!1])}),e("action and toggle inputs remain compatibility presets",()=>{const e=new o;e.toggle(!0),t(e.activated).toBe(!0),e.toggle(!1),t(e.activated).toBe(!1);const a=new i;a.toggle(!0),t(a.activated).toBe(!1),a.toggle(!1),t(a.activated).toBe(!0)});/*
2
+ * Copyright (©) 2026 Hology Interactive AB. All rights reserved.
3
+ * See the LICENSE.md file for details.
4
+ */
@@ -1,4 +1,4 @@
1
- import{beforeAll as e,describe as t,expect as o,test as a}from"vitest";import{CharacterMovementMode as n}from"../gameplay/actors/builtin/components/character/modes.js";let r,c;function s(e={}){return{sequence:1,clientTime:.016,dt:.016,inputX:0,inputY:0,yaw:0,flags:0,clientX:0,clientY:0,clientZ:0,rootMotionX:0,rootMotionY:0,rootMotionZ:0,...e}}function i(e={}){return{sequence:0,x:0,y:0,z:0,velocityX:0,velocityY:0,velocityZ:0,yaw:0,mode:n.walking,horizontalSpeed:0,...e}}function l(e){return{directionInput:{},jumpInput:{},sprintInput:{},rotationInput:{},setRootMotionAction(){},applyImpulse(){},...e}}e(async()=>{"undefined"!=typeof HTMLCanvasElement&&Object.defineProperty(HTMLCanvasElement.prototype,"getContext",{configurable:!0,value:()=>({fillStyle:"",fillRect(){},clearRect(){},getImageData:()=>({data:new Uint8ClampedArray(4)}),putImageData(){},createImageData:()=>({data:new Uint8ClampedArray(4)}),setTransform(){},drawImage(){},save(){},restore(){},beginPath(){},moveTo(){},lineTo(){},closePath(){},stroke(){},translate(){},scale(){},rotate(){},arc(){},fill(){},measureText:()=>({width:0}),transform(){},rect(){},clip(){}})}),r=await import("../gameplay/actors/builtin/components/character/net-character-movement-protocol.js"),c=(await import("../gameplay/actors/builtin/components/character/character-movement-like.js")).getCharacterMovementLike}),t("net character movement packets",()=>{a("round trips batched saved moves",()=>{const e=[s({sequence:1,dt:.016,inputX:.25,inputY:1,yaw:.5,flags:3,clientX:1,clientY:2,clientZ:3}),s({sequence:2,dt:.033,inputX:-.5,inputY:.5,yaw:-1,flags:1,clientX:4,clientY:5,clientZ:6,rootMotionZ:.2})],t=r.decodeNetCharacterMoveBatch(r.encodeNetCharacterMoveBatch(e));o(t.ok).toBe(!0),t.ok&&(o(t.value.moves).toHaveLength(2),o(t.value.moves[0].sequence).toBe(1),o(t.value.moves[0].clientTime).toBeCloseTo(.016,3),o(t.value.moves[0].dt).toBeCloseTo(.016,3),o(t.value.moves[0].inputX).toBeCloseTo(.25,4),o(t.value.moves[1].rootMotionZ).toBeCloseTo(.2))}),a("round trips owner ack, correction, and simulated state packets",()=>{const e=i({sequence:42,x:10,y:2,z:-3,velocityX:1,yaw:1.25,mode:n.falling,horizontalSpeed:7}),t=r.decodeNetCharacterOwnerAck(r.encodeNetCharacterOwnerAck(42)),a=r.decodeNetCharacterOwnerCorrection(r.encodeNetCharacterOwnerCorrection(e)),c=r.decodeNetCharacterSimulatedState(r.encodeNetCharacterSimulatedState(e));o(t.ok&&t.value.sequence).toBe(42),o(a.ok&&a.value.snapshot.sequence).toBe(42),o(c.ok&&c.value.mode).toBe(n.falling),o(c.ok&&c.value.horizontalSpeed).toBeCloseTo(7)}),a("rejects malformed, unsupported version, and wrong-kind packets",()=>{const e=r.encodeNetCharacterMoveBatch([s({sequence:1})]),t=e.slice();t[0]=99;const a=e.slice();a[1]=99,o(r.decodeNetCharacterMoveBatch(new Uint8Array([1,1,0]))).toMatchObject({ok:!1,reason:"malformed"}),o(r.decodeNetCharacterMoveBatch(t)).toMatchObject({ok:!1,reason:"unsupported-version"}),o(r.decodeNetCharacterMoveBatch(a)).toMatchObject({ok:!1,reason:"wrong-kind"})})}),t("net character prediction helpers",()=>{a("combines adjacent equivalent saved moves by default",()=>{const e=new r.NetCharacterSavedMoveBuffer(4);e.push(s({sequence:1,clientTime:.016,dt:.016,inputY:1,clientZ:.1})),e.push(s({sequence:2,clientTime:.032,dt:.016,inputY:1,clientZ:.3}));const t=e.getMovesForSend(4);o(t).toHaveLength(1),o(t[0].sequence).toBe(2),o(t[0].dt).toBeCloseTo(.032),o(t[0].clientZ).toBeCloseTo(.3)}),a("keeps adjacent saved moves separate when combining is disabled",()=>{const e=new r.NetCharacterSavedMoveBuffer(4,-1);e.push(s({sequence:1,clientTime:.016,dt:.016,inputY:1,clientZ:.1})),e.push(s({sequence:2,clientTime:.032,dt:.016,inputY:1,clientZ:.3}));const t=e.getMovesForSend(4);o(t.map(e=>e.sequence)).toEqual([1,2]),o(t.map(e=>e.dt)).toEqual([.016,.016])}),a("preserves a sprint start move for server simulation and replay",()=>{const e=new r.NetCharacterSavedMoveBuffer(4),t=r.MOVE_FLAG_SPRINT|r.MOVE_FLAG_SPRINT_START;e.push(s({sequence:1,clientTime:.016,dt:.016,inputY:1,flags:t})),e.push(s({sequence:2,clientTime:.032,dt:.016,inputY:1,flags:r.MOVE_FLAG_SPRINT}));const a=r.encodeNetCharacterMoveBatch(e.getMovesForSend(4)),n=r.decodeNetCharacterMoveBatch(a);o(n.ok).toBe(!0),n.ok&&(o(n.value.moves.map(e=>e.sequence)).toEqual([1,2]),o(n.value.moves[0].flags).toBe(t))}),a("respects saved move max combined delta time",()=>{const e=new r.NetCharacterSavedMoveBuffer(4,.02);e.push(s({sequence:1,clientTime:.016,dt:.016,inputY:1})),e.push(s({sequence:2,clientTime:.032,dt:.016,inputY:1})),o(e.getMovesForSend(4).map(e=>e.sequence)).toEqual([1,2])}),a("does not combine a move after it has been selected for sending",()=>{const e=new r.NetCharacterSavedMoveBuffer(4,.1);e.push(s({sequence:1,clientTime:.016,dt:.016,inputY:1})),o(e.getMovesForSend(4).map(e=>e.sequence)).toEqual([1]),e.push(s({sequence:2,clientTime:.032,dt:.016,inputY:1}));const t=e.getMovesForSend(4);o(t.map(e=>e.sequence)).toEqual([1,2]),o(t.map(e=>e.dt)).toEqual([.016,.016])}),a("selects the oldest unacked move and newest moves for send packets",()=>{const e=new r.NetCharacterSavedMoveBuffer(8);for(let t=1;t<=5;t++)e.push(s({sequence:t,clientTime:.02*t,dt:.02,inputX:t%2==0?.25:-.25}));o(e.getMovesForSend(3).map(e=>e.sequence)).toEqual([1,4,5])}),a("replays saved moves with a deterministic simulator",()=>{const e=r.replayNetCharacterSavedMoves({x:0},[s({sequence:1,dt:.1,inputY:1}),s({sequence:2,dt:.2,inputY:1})],(e,t)=>({x:e.x+t.inputY*t.dt*10}));o(e.x).toBeCloseTo(3)}),a("validates move sequence, dt, and normalized input",()=>{o(r.validateNetCharacterMove(s({sequence:2,dt:.016}),1)).toBe(!0),o(r.validateNetCharacterMove(s({sequence:1,dt:.016}),1)).toBe(!1),o(r.validateNetCharacterMove(s({sequence:2,clientTime:0}),1)).toBe(!1),o(r.validateNetCharacterMove(s({sequence:2,dt:.5}),1,.1)).toBe(!1),o(r.validateNetCharacterMove(s({sequence:2,inputX:1,inputY:1}),1)).toBe(!1)}),a("derives server move dt from client timestamps",()=>{const e=r.createNetCharacterServerMoveTimingState();r.accrueNetCharacterServerMoveTime(e,.05);const t=r.consumeNetCharacterServerMoveTime(e,s({sequence:1,clientTime:.016}),{maxMoveDeltaTime:.1}),a=r.consumeNetCharacterServerMoveTime(e,s({sequence:2,clientTime:.05}),{maxMoveDeltaTime:.1});o(t).toBeCloseTo(.016),o(a).toBeCloseTo(.034),o(e.lastClientTime).toBeCloseTo(.05)}),a("clamps server move dt and rejects clients too far ahead of server time",()=>{const e=r.createNetCharacterServerMoveTimingState();r.accrueNetCharacterServerMoveTime(e,.02);const t=r.consumeNetCharacterServerMoveTime(e,s({sequence:1,clientTime:.5}),{maxMoveDeltaTime:.05,maxServerMoveDeltaTimeScalar:2,maxClientTimeAhead:.1}),a=r.consumeNetCharacterServerMoveTime(e,s({sequence:2,clientTime:.7}),{maxMoveDeltaTime:.05,maxServerMoveDeltaTimeScalar:2,maxClientTimeAhead:.01});o(t).toBeCloseTo(.1),o(a).toBe(-1)})}),t("net character simulated proxy smoothing",()=>{a("interpolates delayed snapshots",()=>{const e=new r.NetCharacterProxySmoother(.1,20),t=i();e.push(i({sequence:1,x:0}),0),e.push(i({sequence:2,x:10}),1),o(e.sample(.6,t)).toBe(!0),o(t.x).toBeCloseTo(5)}),a("snaps instead of interpolating large gaps",()=>{const e=new r.NetCharacterProxySmoother(0,1),t=i();e.push(i({sequence:1,x:0}),0),e.push(i({sequence:2,x:10}),1),o(e.sample(.5,t)).toBe(!0),o(t.x).toBe(10)})}),t("character movement compatibility",()=>{a("prefers the default movement component when present",()=>{const e=l({horizontalSpeed:2}),t=l({horizontalSpeed:5,isDefaultCharacterMovementComponent:!0});o(c({oldMovement:e,defaultMovement:t,attachedComponents:[]})).toBe(t)})});/*
1
+ import{beforeAll as e,describe as t,expect as o,test as a}from"vitest";import{CharacterMovementMode as n}from"../gameplay/actors/builtin/components/character/modes.js";let c,r;function s(e={}){return{sequence:1,clientTime:.016,dt:.016,inputX:0,inputY:0,yaw:0,flags:0,clientX:0,clientY:0,clientZ:0,rootMotionX:0,rootMotionY:0,rootMotionZ:0,...e}}function i(e={}){return{sequence:0,x:0,y:0,z:0,velocityX:0,velocityY:0,velocityZ:0,yaw:0,mode:n.walking,flags:0,horizontalSpeed:0,...e}}function l(e){return{directionInput:{},jumpInput:{},sprintInput:{},rotationInput:{},setRootMotionAction(){},applyImpulse(){},...e}}e(async()=>{"undefined"!=typeof HTMLCanvasElement&&Object.defineProperty(HTMLCanvasElement.prototype,"getContext",{configurable:!0,value:()=>({fillStyle:"",fillRect(){},clearRect(){},getImageData:()=>({data:new Uint8ClampedArray(4)}),putImageData(){},createImageData:()=>({data:new Uint8ClampedArray(4)}),setTransform(){},drawImage(){},save(){},restore(){},beginPath(){},moveTo(){},lineTo(){},closePath(){},stroke(){},translate(){},scale(){},rotate(){},arc(){},fill(){},measureText:()=>({width:0}),transform(){},rect(){},clip(){}})}),c=await import("../gameplay/actors/builtin/components/character/net-character-movement-protocol.js"),r=(await import("../gameplay/actors/builtin/components/character/character-movement-like.js")).getCharacterMovementLike}),t("net character movement packets",()=>{a("round trips batched saved moves",()=>{const e=[s({sequence:1,dt:.016,inputX:.25,inputY:1,yaw:.5,flags:c.MOVE_FLAG_SPRINT|c.MOVE_FLAG_CROUCH,clientX:1,clientY:2,clientZ:3}),s({sequence:2,dt:.033,inputX:-.5,inputY:.5,yaw:-1,flags:1,clientX:4,clientY:5,clientZ:6,rootMotionZ:.2})],t=c.decodeNetCharacterMoveBatch(c.encodeNetCharacterMoveBatch(e));o(t.ok).toBe(!0),t.ok&&(o(t.value.moves).toHaveLength(2),o(t.value.moves[0].sequence).toBe(1),o(t.value.moves[0].clientTime).toBeCloseTo(.016,3),o(t.value.moves[0].dt).toBeCloseTo(.016,3),o(t.value.moves[0].inputX).toBeCloseTo(.25,4),o(t.value.moves[0].flags).toBe(c.MOVE_FLAG_SPRINT|c.MOVE_FLAG_CROUCH),o(t.value.moves[1].rootMotionZ).toBeCloseTo(.2))}),a("round trips owner ack, correction, and simulated state packets",()=>{const e=i({sequence:42,x:10,y:2,z:-3,velocityX:1,yaw:1.25,mode:n.falling,flags:c.SNAPSHOT_FLAG_CROUCHING,horizontalSpeed:7}),t=c.decodeNetCharacterOwnerAck(c.encodeNetCharacterOwnerAck(42)),a=c.decodeNetCharacterOwnerCorrection(c.encodeNetCharacterOwnerCorrection(e)),r=c.decodeNetCharacterSimulatedState(c.encodeNetCharacterSimulatedState(e));o(t.ok&&t.value.sequence).toBe(42),o(a.ok&&a.value.snapshot.sequence).toBe(42),o(a.ok&&a.value.snapshot.flags).toBe(c.SNAPSHOT_FLAG_CROUCHING),o(r.ok&&r.value.mode).toBe(n.falling),o(r.ok&&r.value.flags).toBe(c.SNAPSHOT_FLAG_CROUCHING),o(r.ok&&r.value.horizontalSpeed).toBeCloseTo(7)}),a("rejects malformed, unsupported version, and wrong-kind packets",()=>{const e=c.encodeNetCharacterMoveBatch([s({sequence:1})]),t=e.slice();t[0]=99;const a=e.slice();a[1]=99,o(c.decodeNetCharacterMoveBatch(new Uint8Array([1,1,0]))).toMatchObject({ok:!1,reason:"malformed"}),o(c.decodeNetCharacterMoveBatch(t)).toMatchObject({ok:!1,reason:"unsupported-version"}),o(c.decodeNetCharacterMoveBatch(a)).toMatchObject({ok:!1,reason:"wrong-kind"})})}),t("net character prediction helpers",()=>{a("combines adjacent equivalent saved moves by default",()=>{const e=new c.NetCharacterSavedMoveBuffer(4);e.push(s({sequence:1,clientTime:.016,dt:.016,inputY:1,clientZ:.1})),e.push(s({sequence:2,clientTime:.032,dt:.016,inputY:1,clientZ:.3}));const t=e.getMovesForSend(4);o(t).toHaveLength(1),o(t[0].sequence).toBe(2),o(t[0].dt).toBeCloseTo(.032),o(t[0].clientZ).toBeCloseTo(.3)}),a("keeps adjacent saved moves separate when combining is disabled",()=>{const e=new c.NetCharacterSavedMoveBuffer(4,-1);e.push(s({sequence:1,clientTime:.016,dt:.016,inputY:1,clientZ:.1})),e.push(s({sequence:2,clientTime:.032,dt:.016,inputY:1,clientZ:.3}));const t=e.getMovesForSend(4);o(t.map(e=>e.sequence)).toEqual([1,2]),o(t.map(e=>e.dt)).toEqual([.016,.016])}),a("preserves a sprint start move for server simulation and replay",()=>{const e=new c.NetCharacterSavedMoveBuffer(4),t=c.MOVE_FLAG_SPRINT|c.MOVE_FLAG_SPRINT_START;e.push(s({sequence:1,clientTime:.016,dt:.016,inputY:1,flags:t})),e.push(s({sequence:2,clientTime:.032,dt:.016,inputY:1,flags:c.MOVE_FLAG_SPRINT}));const a=c.encodeNetCharacterMoveBatch(e.getMovesForSend(4)),n=c.decodeNetCharacterMoveBatch(a);o(n.ok).toBe(!0),n.ok&&(o(n.value.moves.map(e=>e.sequence)).toEqual([1,2]),o(n.value.moves[0].flags).toBe(t))}),a("does not combine saved moves across crouch changes",()=>{const e=new c.NetCharacterSavedMoveBuffer(4);e.push(s({sequence:1,clientTime:.016,dt:.016,inputY:1})),e.push(s({sequence:2,clientTime:.032,dt:.016,inputY:1,flags:c.MOVE_FLAG_CROUCH}));const t=e.getMovesForSend(4);o(t.map(e=>e.sequence)).toEqual([1,2]),o(t.map(e=>e.flags)).toEqual([0,c.MOVE_FLAG_CROUCH])}),a("combines adjacent crouched saved moves",()=>{const e=new c.NetCharacterSavedMoveBuffer(4);e.push(s({sequence:1,clientTime:.016,dt:.016,inputY:1,flags:c.MOVE_FLAG_CROUCH})),e.push(s({sequence:2,clientTime:.032,dt:.016,inputY:1,flags:c.MOVE_FLAG_CROUCH}));const t=e.getMovesForSend(4);o(t).toHaveLength(1),o(t[0].sequence).toBe(2),o(t[0].flags).toBe(c.MOVE_FLAG_CROUCH)}),a("respects saved move max combined delta time",()=>{const e=new c.NetCharacterSavedMoveBuffer(4,.02);e.push(s({sequence:1,clientTime:.016,dt:.016,inputY:1})),e.push(s({sequence:2,clientTime:.032,dt:.016,inputY:1})),o(e.getMovesForSend(4).map(e=>e.sequence)).toEqual([1,2])}),a("does not combine a move after it has been selected for sending",()=>{const e=new c.NetCharacterSavedMoveBuffer(4,.1);e.push(s({sequence:1,clientTime:.016,dt:.016,inputY:1})),o(e.getMovesForSend(4).map(e=>e.sequence)).toEqual([1]),e.push(s({sequence:2,clientTime:.032,dt:.016,inputY:1}));const t=e.getMovesForSend(4);o(t.map(e=>e.sequence)).toEqual([1,2]),o(t.map(e=>e.dt)).toEqual([.016,.016])}),a("selects the oldest unacked move and newest moves for send packets",()=>{const e=new c.NetCharacterSavedMoveBuffer(8);for(let t=1;t<=5;t++)e.push(s({sequence:t,clientTime:.02*t,dt:.02,inputX:t%2==0?.25:-.25}));o(e.getMovesForSend(3).map(e=>e.sequence)).toEqual([1,4,5])}),a("replays saved moves with a deterministic simulator",()=>{const e=c.replayNetCharacterSavedMoves({x:0},[s({sequence:1,dt:.1,inputY:1}),s({sequence:2,dt:.2,inputY:1})],(e,t)=>({x:e.x+t.inputY*t.dt*10}));o(e.x).toBeCloseTo(3)}),a("validates move sequence, dt, and normalized input",()=>{o(c.validateNetCharacterMove(s({sequence:2,dt:.016}),1)).toBe(!0),o(c.validateNetCharacterMove(s({sequence:1,dt:.016}),1)).toBe(!1),o(c.validateNetCharacterMove(s({sequence:2,clientTime:0}),1)).toBe(!1),o(c.validateNetCharacterMove(s({sequence:2,dt:.5}),1,.1)).toBe(!1),o(c.validateNetCharacterMove(s({sequence:2,inputX:1,inputY:1}),1)).toBe(!1),o(c.validateNetCharacterMove(s({sequence:2,flags:128}),1)).toBe(!1)}),a("derives server move dt from client timestamps",()=>{const e=c.createNetCharacterServerMoveTimingState();c.accrueNetCharacterServerMoveTime(e,.05);const t=c.consumeNetCharacterServerMoveTime(e,s({sequence:1,clientTime:.016}),{maxMoveDeltaTime:.1}),a=c.consumeNetCharacterServerMoveTime(e,s({sequence:2,clientTime:.05}),{maxMoveDeltaTime:.1});o(t).toBeCloseTo(.016),o(a).toBeCloseTo(.034),o(e.lastClientTime).toBeCloseTo(.05)}),a("clamps server move dt and rejects clients too far ahead of server time",()=>{const e=c.createNetCharacterServerMoveTimingState();c.accrueNetCharacterServerMoveTime(e,.02);const t=c.consumeNetCharacterServerMoveTime(e,s({sequence:1,clientTime:.5}),{maxMoveDeltaTime:.05,maxServerMoveDeltaTimeScalar:2,maxClientTimeAhead:.1}),a=c.consumeNetCharacterServerMoveTime(e,s({sequence:2,clientTime:.7}),{maxMoveDeltaTime:.05,maxServerMoveDeltaTimeScalar:2,maxClientTimeAhead:.01});o(t).toBeCloseTo(.1),o(a).toBe(-1)})}),t("net character simulated proxy smoothing",()=>{a("interpolates delayed snapshots",()=>{const e=new c.NetCharacterProxySmoother(.1,20),t=i();e.push(i({sequence:1,x:0}),0),e.push(i({sequence:2,x:10}),1),o(e.sample(.6,t)).toBe(!0),o(t.x).toBeCloseTo(5)}),a("snaps instead of interpolating large gaps",()=>{const e=new c.NetCharacterProxySmoother(0,1),t=i();e.push(i({sequence:1,x:0}),0),e.push(i({sequence:2,x:10}),1),o(e.sample(.5,t)).toBe(!0),o(t.x).toBe(10)})}),t("character movement compatibility",()=>{a("prefers the default movement component when present",()=>{const e=l({horizontalSpeed:2}),t=l({horizontalSpeed:5,isDefaultCharacterMovementComponent:!0});o(r({oldMovement:e,defaultMovement:t,attachedComponents:[]})).toBe(t)})});/*
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 n,it as r,vi as i}from"vitest";import{Scene as o}from"three";import{Subject as s}from"rxjs";i.hoisted(()=>{if("undefined"==typeof HTMLCanvasElement)return;const e=new Proxy({},{get:(e,a)=>(a in e||(e[a]=("string"!=typeof a||!a.startsWith("is"))&&i.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{}})}),i.mock("../gameplay/actors/builtin/index.js",()=>({default:{}})),i.mock("../effects/vfx/vfx-actor.js",()=>({VfxActor:class{}})),i.mock("../effects/vfx/vfx-param.js",()=>({VisualEffect:class{}})),i.mock("../rendering.js",()=>({RenderingView:class{},setRenderingPaused:i.fn()}));import{BaseActor as c}from"../gameplay/actors/actor.js";import{ActorComponent as m}from"../gameplay/actors/component.js";import{Parameter as p}from"../shader/parameter.js";import{SceneMaterializer as f}from"../scene/materializer.js";import{SerializedParamType as l}from"../scene/model.js";class d extends m{constructor(){super(...arguments),this.animationPreset="mixamo"}}e([p(),a("design:type",Object)],d.prototype,"animationPreset",void 0);class u extends c{constructor(){super(...arguments),this.animation=new d,this.animationPreset="mixamo"}}async function b(e){const a=(t="prefab-asset",n=[y({id:"prefab-actor",type:"actor",actor:{type:"PrefabParamActor",params:{animationPreset:{type:l.String,value:e.prefabActorValue}},innerParams:null==e.prefabComponentValue?[]:[{path:["animation"],params:{animationPreset:{type:l.String,value:e.prefabComponentValue}}}],components:[]}})],{id:t,name:t,type:"prefab",prefab:{objects:n,mainActorId:"prefab-actor"}});var t,n;const r=y({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:c}=function(e,a){const t={getObjects:()=>e,onCreate:()=>{},onUpdate:()=>{},onRemove:()=>{}},n={onCreate:new s,onDelete:new s,onUpdate:new s,getAsset:i.fn(async e=>a.get(e)??null),getAssets:i.fn(async()=>Array.from(a.values()))},r={create:i.fn(async(e,a)=>{const t=new e;return null!=a&&t.position.copy(a),t}),initActor:i.fn(async e=>{e.__isInitialised=!0})},c={renderer:{shadowMap:{needsUpdate:!1}}},m=new f(new o,t,n,{},c,[],[{name:"PrefabParamActor",type:u}],r);return{materializer:m}}([r],new Map([[a.id,a]]));return await c.materialize(r),await c.initActorsPostInit(),c.materializedActors.get("prefab-instance/prefab-actor")}function y(e){return{name:e.id,position:[0,0,0],rotation:[0,0,0,"XYZ"],scale:[1,1,1],...e}}e([p(),a("design:type",Object)],u.prototype,"animationPreset",void 0),t("prefab instance actor params",()=>{r("does not let generated prefab instance defaults override the prefab actor value",async()=>{const e=await b({prefabActorValue:"basic",instanceParam:{type:l.String,value:"mixamo"}});n(e.animationPreset).toBe("basic")}),r("applies prefab instance params when the override is explicit",async()=>{const e=await b({prefabActorValue:"basic",instanceParam:{type:l.String,value:"mixamo",override:!0}});n(e.animationPreset).toBe("mixamo")}),r("does not let generated prefab instance component defaults override prefab component values",async()=>{const e=await b({prefabActorValue:"basic",prefabComponentValue:"basic",instanceInnerParam:{type:l.String,value:"mixamo"}});n(e.animation.animationPreset).toBe("basic")}),r("applies prefab instance component params when the override is explicit",async()=>{const e=await b({prefabActorValue:"basic",prefabComponentValue:"basic",instanceInnerParam:{type:l.String,value:"mixamo",override:!0}});n(e.animation.animationPreset).toBe("mixamo")})});/*
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)})});/*
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 a,it as t,vi as o}from"vitest";import*as n from"three";import{createCameraClipInstance as s,createCameraShakeClipInstance as r,createCameraShakeTrack as i,createCameraTrack as c,createSequenceData as l,createTransformKeyframe as m,createTransformSubTrack as p,SequenceRole as u}from"../effects/sequence/sequence-data.js";import{BaseActor as f}from"../gameplay/actors/actor.js";import{sampleCameraShake as d}from"../gameplay/services/camera-shake.js";async function C(e){const{SequencePlayer:a}=await import("../effects/sequence/sequence-player.js"),t=new a;return t.setWorld({scene:new n.Scene}),t.setViewController(e),t}async function w(e){const{ViewController:a}=await import("../gameplay/services/render.js");return new a({camera:e,running:!1,paused:!1,onLoop:o.fn(),setCamera(e){this.camera=e},stop:o.fn()})}function y(e,a,t){const o=c(),n=s(a,t);n.blendInDuration=0,n.blendOutDuration=0,o.clips.push(n);const r=p(),i=m(a);return i.position=e,r.keyframes.push(i),o.subTracks.push(r),o}function T(e,a,t){const o=i(),n=r(e,a);return n.attack=0,n.decay=0,n.frequency=8,n.positionAmplitude=t,n.rotationAmplitude=[0,0,0],n.seed=17,o.clips.push(n),o}o.mock("../gameplay/actors/builtin/index.js",()=>({builtInActors:{},default:{}})),o.mock("../gameplay/actors/index.js",()=>({CharacterAnimationComponent:class{},CharacterMovementComponent:class{}})),o.mock("../effects/vfx/vfx-actor.js",()=>({VfxActor:class{}})),o.mock("../effects/vfx/vfx-param.js",()=>({VisualEffect:class{}})),o.mock("../effects/vfx/vfx-service.js",()=>({VfxService:class{}})),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});class a{constructor(){this.currentTime=0,this.destination={},this.listener={positionX:{linearRampToValueAtTime:o.fn(),setValueAtTime:o.fn()},positionY:{linearRampToValueAtTime:o.fn(),setValueAtTime:o.fn()},positionZ:{linearRampToValueAtTime:o.fn(),setValueAtTime:o.fn()},forwardX:{linearRampToValueAtTime:o.fn(),setValueAtTime:o.fn()},forwardY:{linearRampToValueAtTime:o.fn(),setValueAtTime:o.fn()},forwardZ:{linearRampToValueAtTime:o.fn(),setValueAtTime:o.fn()},upX:{linearRampToValueAtTime:o.fn(),setValueAtTime:o.fn()},upY:{linearRampToValueAtTime:o.fn(),setValueAtTime:o.fn()},upZ:{linearRampToValueAtTime:o.fn(),setValueAtTime:o.fn()}}}createGain(){return{connect:o.fn(),disconnect:o.fn(),gain:{value:1,setValueAtTime:o.fn()}}}}Object.defineProperty(window,"AudioContext",{configurable:!0,value:a}),Object.defineProperty(window,"webkitAudioContext",{configurable:!0,value:a})}),e("sequencer camera control",()=>{t("keeps setCamera as the base camera while an override lease is active",async()=>{const e=new n.PerspectiveCamera,t=new n.PerspectiveCamera,o=new n.PerspectiveCamera,s=await w(e),r=s.pushCameraOverride(t,{});a(s.getCamera()).toBe(t),s.setCamera(o),a(s.getBaseCamera()).toBe(o),a(s.getCamera()).toBe(t),r.release(),a(s.getCamera()).toBe(o)}),t("does not take over the local camera unless camera control is enabled",async()=>{const e=new n.PerspectiveCamera,t=await w(e),o=await C(t),s=y([5,0,0],0,1);o.load({...l(),duration:1,tracks:[s]}),o.play(),a(t.getCamera()).toBe(e)}),t("activates an opted-in camera shot and releases it after the clip",async()=>{const e=new n.PerspectiveCamera,t=await w(e),o=await C(t),s=y([5,2,1],0,1);o.cameraControlEnabled=!0,o.load({...l(),duration:2,tracks:[s]}),o.play(),a(t.getCamera()).not.toBe(e),a(t.getCamera().position.toArray()).toEqual([5,2,1]),o.seek(1.1),a(t.getCamera()).toBe(e)}),t("uses the later camera track when shots overlap",async()=>{const e=new n.PerspectiveCamera,t=await w(e),o=await C(t),s=y([1,0,0],0,1),r=y([7,0,0],0,1);o.cameraControlEnabled=!0,o.load({...l(),duration:1,tracks:[s,r]}),o.play(),a(t.getCamera().position.x).toBeCloseTo(7)}),t("blends into the camera shot using the clip blend settings",async()=>{const e=new n.PerspectiveCamera;e.position.set(0,0,0);const t=await w(e),o=await C(t),s=y([10,0,0],0,2);s.clips[0].blendInDuration=1,s.clips[0].blendInterpolation="linear",o.cameraControlEnabled=!0,o.load({...l(),duration:2,tracks:[s]}),o.play(),o.seek(.5),a(t.getCamera().position.x).toBeCloseTo(5)}),t("anchors camera tracks to bound roles",async()=>{const e=new n.PerspectiveCamera,t=await w(e),o=await C(t),s=new h;s.object.position.set(10,0,0);const r=y([1,0,0],0,1);r.parentRole=u.Self,o.cameraControlEnabled=!0,o.bindRole(u.Self,s),o.load({...l(),duration:1,tracks:[r]}),o.play(),a(t.getCamera().position.x).toBeCloseTo(11)}),t("applies camera shake tracks without taking over the local camera",async()=>{const e=new n.PerspectiveCamera,t=await w(e),o=await C(t),s=T(0,1,[.8,0,0]);o.load({...l(),duration:1,tracks:[s]}),o.play(),o.seek(.2);const r=d(s.clips[0],.2);a(t.getSourceCamera()).toBe(e),a(t.getCamera()).not.toBe(e),a(t.getCamera().position.x).toBeCloseTo(e.position.x+(r?.position[0]??0))}),t("layers camera shake on top of a controlled camera shot",async()=>{const e=new n.PerspectiveCamera,t=await w(e),o=await C(t),s=y([10,0,0],0,1),r=T(0,1,[.6,0,0]);o.cameraControlEnabled=!0,o.load({...l(),duration:1,tracks:[s,r]}),o.play(),o.seek(.2);const i=d(r.clips[0],.2);a(t.getSourceCamera().position.x).toBeCloseTo(10),a(t.getCamera().position.x).toBeCloseTo(10+(i?.position[0]??0))}),t("blends camera shots from the unshaken source camera when shake is already active",async()=>{const e=new n.PerspectiveCamera,t=await w(e),o=await C(t),s=y([10,0,0],.5,1);s.clips[0].blendInDuration=1,s.clips[0].blendInterpolation="linear";const r=T(0,2,[.6,0,0]);o.cameraControlEnabled=!0,o.load({...l(),duration:2,tracks:[s,r]}),o.play(),o.seek(.25),o.seek(.75);const i=d(r.clips[0],.75);a(t.getSourceCamera().position.x).toBeCloseTo(2.5),a(t.getCamera().position.x).toBeCloseTo(2.5+(i?.position[0]??0))}),t("clears sequence-owned camera shake when stopped",async()=>{const e=new n.PerspectiveCamera,t=await w(e),o=await C(t),s=T(0,1,[.8,0,0]);o.load({...l(),duration:1,tracks:[s]}),o.play(),o.seek(.2),a(t.getCamera()).not.toBe(e),o.stop(),a(t.getCamera()).toBe(e)})});class h extends f{}/*
1
+ import{describe as e,expect as a,it as t,vi as o}from"vitest";import*as n from"three";import{createCameraClipInstance as s,createCameraShakeClipInstance as r,createCameraShakeTrack as i,createCameraTrack as c,createSequenceData as l,createTransformKeyframe as m,createTransformSubTrack as p,SequenceRole as u}from"../effects/sequence/sequence-data.js";import{BaseActor as f}from"../gameplay/actors/actor.js";import{sampleCameraShake as d}from"../gameplay/services/camera-shake.js";async function C(e){const{SequencePlayer:a}=await import("../effects/sequence/sequence-player.js"),t=new a;return t.setWorld({scene:new n.Scene}),t.setViewController(e),t}async function w(e){const{ViewController:a}=await import("../gameplay/services/render.js");return new a({camera:e,running:!1,paused:!1,onLoop:o.fn(),setCamera(e){this.camera=e},stop:o.fn()})}function y(e,a,t){const o=c(),n=s(a,t);n.blendInDuration=0,n.blendOutDuration=0,o.clips.push(n);const r=p(),i=m(a);return i.position=e,r.keyframes.push(i),o.subTracks.push(r),o}function k(e,a,t){const o=i(),n=r(e,a);return n.attack=0,n.decay=0,n.frequency=8,n.positionAmplitude=t,n.rotationAmplitude=[0,0,0],n.seed=17,o.clips.push(n),o}o.mock("../gameplay/actors/builtin/index.js",()=>({builtInActors:{},default:{}})),o.mock("../gameplay/actors/index.js",()=>({CharacterAnimationComponent:class{},CharacterMovementComponent:class{}})),o.mock("../effects/vfx/vfx-actor.js",()=>({VfxActor:class{}})),o.mock("../effects/vfx/vfx-param.js",()=>({VisualEffect:class{}})),o.mock("../effects/vfx/vfx-service.js",()=>({VfxService:class{}})),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});class a{constructor(){this.currentTime=0,this.destination={},this.listener={positionX:{linearRampToValueAtTime:o.fn(),setValueAtTime:o.fn()},positionY:{linearRampToValueAtTime:o.fn(),setValueAtTime:o.fn()},positionZ:{linearRampToValueAtTime:o.fn(),setValueAtTime:o.fn()},forwardX:{linearRampToValueAtTime:o.fn(),setValueAtTime:o.fn()},forwardY:{linearRampToValueAtTime:o.fn(),setValueAtTime:o.fn()},forwardZ:{linearRampToValueAtTime:o.fn(),setValueAtTime:o.fn()},upX:{linearRampToValueAtTime:o.fn(),setValueAtTime:o.fn()},upY:{linearRampToValueAtTime:o.fn(),setValueAtTime:o.fn()},upZ:{linearRampToValueAtTime:o.fn(),setValueAtTime:o.fn()}}}createGain(){return{connect:o.fn(),disconnect:o.fn(),gain:{value:1,setValueAtTime:o.fn()}}}}Object.defineProperty(window,"AudioContext",{configurable:!0,value:a}),Object.defineProperty(window,"webkitAudioContext",{configurable:!0,value:a})}),e("sequencer camera control",()=>{t("keeps setCamera as the base camera while an override lease is active",async()=>{const e=new n.PerspectiveCamera,t=new n.PerspectiveCamera,o=new n.PerspectiveCamera,s=await w(e),r=s.pushCameraOverride(t,{});a(s.getCamera()).toBe(t),s.setCamera(o),a(s.getBaseCamera()).toBe(o),a(s.getCamera()).toBe(t),r.release(),a(s.getCamera()).toBe(o)}),t("does not take over the local camera unless camera control is enabled",async()=>{const e=new n.PerspectiveCamera,t=await w(e),o=await C(t),s=y([5,0,0],0,1);o.load({...l(),duration:1,tracks:[s]}),o.play(),a(t.getCamera()).toBe(e)}),t("activates an opted-in camera shot and releases it after the clip",async()=>{const e=new n.PerspectiveCamera,t=await w(e),o=await C(t),s=y([5,2,1],0,1);o.cameraControlEnabled=!0,o.load({...l(),duration:2,tracks:[s]}),o.play(),a(t.getCamera()).not.toBe(e),a(t.getCamera().position.toArray()).toEqual([5,2,1]),o.seek(1.1),a(t.getCamera()).toBe(e)}),t("uses the later camera track when shots overlap",async()=>{const e=new n.PerspectiveCamera,t=await w(e),o=await C(t),s=y([1,0,0],0,1),r=y([7,0,0],0,1);o.cameraControlEnabled=!0,o.load({...l(),duration:1,tracks:[s,r]}),o.play(),a(t.getCamera().position.x).toBeCloseTo(7)}),t("blends into the camera shot using the clip blend settings",async()=>{const e=new n.PerspectiveCamera;e.position.set(0,0,0);const t=await w(e),o=await C(t),s=y([10,0,0],0,2);s.clips[0].blendInDuration=1,s.clips[0].blendInterpolation="linear",o.cameraControlEnabled=!0,o.load({...l(),duration:2,tracks:[s]}),o.play(),o.seek(.5),a(t.getCamera().position.x).toBeCloseTo(5)}),t("evaluates authored cubic position tangents on camera tracks",async()=>{const e=new n.PerspectiveCamera,t=await w(e),o=await C(t),s=y([0,0,0],0,1),r=s.subTracks[0];if("transform"!==r.type)throw new Error("Expected a transform sub-track");r.keyframes[0].positionTangentMode="broken",r.keyframes[0].positionOutTangent=[0,4,0];const i=m(1);i.position=[2,0,0],i.positionTangentMode="broken",i.positionInTangent=[0,-4,0],r.keyframes.push(i),o.cameraControlEnabled=!0,o.load({...l(),duration:1,tracks:[s]}),o.play(),o.seek(.5),a(t.getCamera().position.toArray()).toEqual([1,1,0])}),t("anchors camera tracks to bound roles",async()=>{const e=new n.PerspectiveCamera,t=await w(e),o=await C(t),s=new T;s.object.position.set(10,0,0);const r=y([1,0,0],0,1);r.parentRole=u.Self,o.cameraControlEnabled=!0,o.bindRole(u.Self,s),o.load({...l(),duration:1,tracks:[r]}),o.play(),a(t.getCamera().position.x).toBeCloseTo(11)}),t("applies camera shake tracks without taking over the local camera",async()=>{const e=new n.PerspectiveCamera,t=await w(e),o=await C(t),s=k(0,1,[.8,0,0]);o.load({...l(),duration:1,tracks:[s]}),o.play(),o.seek(.2);const r=d(s.clips[0],.2);a(t.getSourceCamera()).toBe(e),a(t.getCamera()).not.toBe(e),a(t.getCamera().position.x).toBeCloseTo(e.position.x+(r?.position[0]??0))}),t("layers camera shake on top of a controlled camera shot",async()=>{const e=new n.PerspectiveCamera,t=await w(e),o=await C(t),s=y([10,0,0],0,1),r=k(0,1,[.6,0,0]);o.cameraControlEnabled=!0,o.load({...l(),duration:1,tracks:[s,r]}),o.play(),o.seek(.2);const i=d(r.clips[0],.2);a(t.getSourceCamera().position.x).toBeCloseTo(10),a(t.getCamera().position.x).toBeCloseTo(10+(i?.position[0]??0))}),t("blends camera shots from the unshaken source camera when shake is already active",async()=>{const e=new n.PerspectiveCamera,t=await w(e),o=await C(t),s=y([10,0,0],.5,1);s.clips[0].blendInDuration=1,s.clips[0].blendInterpolation="linear";const r=k(0,2,[.6,0,0]);o.cameraControlEnabled=!0,o.load({...l(),duration:2,tracks:[s,r]}),o.play(),o.seek(.25),o.seek(.75);const i=d(r.clips[0],.75);a(t.getSourceCamera().position.x).toBeCloseTo(2.5),a(t.getCamera().position.x).toBeCloseTo(2.5+(i?.position[0]??0))}),t("clears sequence-owned camera shake when stopped",async()=>{const e=new n.PerspectiveCamera,t=await w(e),o=await C(t),s=k(0,1,[.8,0,0]);o.load({...l(),duration:1,tracks:[s]}),o.play(),o.seek(.2),a(t.getCamera()).not.toBe(e),o.stop(),a(t.getCamera()).toBe(e)})});class T extends f{}/*
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=sequence-scene-binding.test.d.ts.map
@@ -0,0 +1,4 @@
1
+ import{__decorate as e,__metadata as t}from"tslib";import{describe as a,expect as s,it as c,vi as n}from"vitest";import{Object3D as r}from"three";import{createGroupTrack as o,createObjectTrack as i,createSequenceData as u,createTransformKeyframe as l,createTransformSubTrack as d,remapSequenceObjectTrackTargets as p,SequenceRole as g}from"../effects/sequence/sequence-data.js";import{Parameter as f}from"../shader/parameter.js";import{prepareClassParameters as m,prepareCustomParamsFromType as b}from"../scene/materializer.js";import{SerializedParamType as q}from"../scene/model.js";n.mock("../gameplay/actors/builtin/index.js",()=>({builtInActors:{},default:{}})),n.mock("../gameplay/actors/index.js",()=>({CharacterAnimationComponent:class{},CharacterMovementComponent:class{}})),n.mock("../effects/vfx/vfx-actor.js",()=>({VfxActor:class{}})),n.mock("../effects/vfx/vfx-param.js",()=>({VisualEffect:class{}})),n.mock("../rendering.js",()=>({RenderingView:class{},setRenderingPaused:n.fn()})),n.hoisted(()=>{if("undefined"==typeof AudioBuffer&&Object.defineProperty(globalThis,"AudioBuffer",{configurable:!0,value:class{}}),"undefined"==typeof HTMLCanvasElement)return;const e=new Proxy({},{get:(e,t)=>(t in e||(e[t]=("string"!=typeof t||!t.startsWith("is"))&&n.fn()),e[t]),set:(e,t,a)=>(e[t]=a,!0)});Object.defineProperty(HTMLCanvasElement.prototype,"getContext",{configurable:!0,value:()=>e})});class y{constructor(){this.sequenceData=u()}}e([f({label:"Sequence Data"}),t("design:type",Object)],y.prototype,"sequenceData",void 0),a("sequence scene bindings",()=>{c("binds scene object tracks nested inside groups",async()=>{const{SequencePlayer:e}=await import("../effects/sequence/sequence-player.js"),t=new e,a=u(),c=o(),n=i(),l=new r;n.targetId="scene-actor",c.childTracks.push(n),a.tracks.push(c),t.load(a),t.bindObject("scene-actor",l),s(t.getActiveTrackObjects(n.id)).toEqual([l])}),c("gives role bindings precedence over a malformed scene target id",async()=>{const{SequencePlayer:e}=await import("../effects/sequence/sequence-player.js"),t=new e,a=u(),c=i(),n=new r,o=new r;c.targetId="scene-actor",c.role=g.Self,a.tracks.push(c),t.load(a),t.bindObject("scene-actor",n),t.bindRole(g.Self,o),s(t.getActiveTrackObjects(c.id)).toEqual([o])}),c("removes a scene binding when its target can no longer be resolved",async()=>{const{SequencePlayer:e}=await import("../effects/sequence/sequence-player.js"),t=new e,a=u(),c=i();c.targetId="scene-actor",a.tracks.push(c),t.load(a),t.bindObject("scene-actor",new r),t.unbindObject("scene-actor"),s(t.getActiveTrackObjects(c.id)).toEqual([])}),c("preserves the original transform when refreshing the same binding while scrubbing",async()=>{const{SequencePlayer:e}=await import("../effects/sequence/sequence-player.js"),t=new e,a=u(),c=i(),n=d(),o=l(1);o.position=[5,0,0],n.keyframes.push(o),c.targetId="scene-actor",c.subTracks.push(n),a.tracks.push(c);const p=new r;p.position.set(2,0,0),t.load(a),t.bindObject("scene-actor",p),t.seek(1),t.bindObject("scene-actor",p),t.stop(),s(p.position.toArray()).toEqual([2,0,0])}),c("remaps grouped internal targets while preserving external references",()=>{const e=u(),t=o(),a=i(),c=i();a.targetId="internal-actor",c.targetId="external-actor",t.childTracks.push(a,c),e.tracks.push(t),p(e,new Map([["internal-actor","internal-actor-copy"]])),s(a.targetId).toBe("internal-actor-copy"),s(c.targetId).toBe("external-actor")}),c("preserves legacy raw inline sequence data when preparing editor params",()=>{const e=u(),t=i();t.targetId="scene-actor",e.tracks.push(t);const a=b(y,{sequenceData:e});s(a.sequenceData.type).toBe(q.SequenceData),s(a.sequenceData.value.tracks[0]).toMatchObject({type:"object",targetId:"scene-actor"})}),c("materializes legacy Sequence-tagged inline scene sequence data without loading a sequence asset",async()=>{const e=u(),t=i();t.targetId="scene-actor",e.tracks.push(t);const a=await m({sequenceData:{type:q.Sequence,value:e}},y,{},{});s(a.sequenceData).toBe(e),s(a.sequenceData.tracks[0]).toMatchObject({type:"object",targetId:"scene-actor"})}),c("materializes current inline scene sequence data without loading a sequence asset",async()=>{const e=u(),t=i();t.targetId="scene-actor",e.tracks.push(t);const a=await m({sequenceData:{type:q.SequenceData,value:e}},y,{},{});s(a.sequenceData).toBe(e),s(a.sequenceData.tracks[0]).toMatchObject({type:"object",targetId:"scene-actor"})}),c("materializes legacy raw inline scene sequence data after reload",async()=>{const e=u(),t=i();t.targetId="scene-actor",e.tracks.push(t);const a=await m({sequenceData:e},y,{},{});s(a.sequenceData).toBe(e),s(a.sequenceData.tracks[0]).toMatchObject({type:"object",targetId:"scene-actor"})})});/*
2
+ * Copyright (©) 2026 Hology Interactive AB. All rights reserved.
3
+ * See the LICENSE.md file for details.
4
+ */
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=sequence-transform.test.d.ts.map
@@ -0,0 +1,4 @@
1
+ import{describe as o,expect as n,it as t}from"vitest";import{Vector3 as e}from"three";import{createTransformKeyframe as s}from"../effects/sequence/sequence-data.js";import{evaluateTransformPosition as i,resolveTransformPositionTangent as r}from"../effects/sequence/sequence-transform.js";o("sequence transform position curves",()=>{t("derives smooth automatic tangents from neighboring position keys",()=>{const o=s(0);o.position=[0,0,0];const t=s(1);t.position=[1,1,0];const r=s(2);r.position=[2,0,0];const a=new e;i([o,t,r],.5,a),n(a.x).toBeCloseTo(.5),n(a.y).toBeCloseTo(.625),n(a.z).toBeCloseTo(0)}),t("uses independent authored tangents for broken cubic keys",()=>{const o=s(0);o.position=[0,0,0],o.positionTangentMode="broken",o.positionOutTangent=[0,4,0];const t=s(1);t.position=[2,0,0],t.positionTangentMode="broken",t.positionInTangent=[0,-4,0];const r=new e;i([o,t],.5,r),n(r.toArray()).toEqual([1,1,0])}),t("keeps linear and step segments independent of tangent data",()=>{const o=s(0);o.position=[0,0,0],o.positionTangentMode="broken",o.positionOutTangent=[0,100,0];const t=s(1);t.position=[2,2,0];const r=new e;o.interpolation="linear",i([o,t],.5,r),n(r.toArray()).toEqual([1,1,0]),o.interpolation="step",i([o,t],.5,r),n(r.toArray()).toEqual([0,0,0])}),t("resolves aligned tangents from either authored side",()=>{const o=s(0);o.positionTangentMode="aligned",o.positionOutTangent=[1,2,3];const t=new e;r([o],0,"in",t),n(t.toArray()).toEqual([1,2,3])})});/*
2
+ * Copyright (©) 2026 Hology Interactive AB. All rights reserved.
3
+ * See the LICENSE.md file for details.
4
+ */