@hology/core 0.0.211 → 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.
- package/dist/effects/sequence/index.d.ts +1 -0
- package/dist/effects/sequence/index.js +1 -1
- package/dist/effects/sequence/sequence-action.d.ts +5 -0
- package/dist/effects/sequence/sequence-actor.d.ts +7 -0
- package/dist/effects/sequence/sequence-actor.js +1 -1
- package/dist/effects/sequence/sequence-animation-retiming.js +1 -1
- package/dist/effects/sequence/sequence-data.d.ts +9 -0
- package/dist/effects/sequence/sequence-data.js +1 -1
- package/dist/effects/sequence/sequence-player.d.ts +12 -1
- package/dist/effects/sequence/sequence-player.js +1 -1
- package/dist/effects/sequence/sequence-transform.d.ts +10 -0
- package/dist/effects/sequence/sequence-transform.js +4 -0
- package/dist/effects/sequence/sequence-value-lane.d.ts +2 -0
- package/dist/effects/sequence/sequence-value-lane.js +1 -1
- package/dist/effects/vfx/initializsers.d.ts +8 -1
- package/dist/effects/vfx/initializsers.js +1 -1
- package/dist/effects/vfx/vfx-actor.d.ts +5 -0
- package/dist/effects/vfx/vfx-actor.js +1 -1
- package/dist/effects/vfx/vfx-asset.d.ts +2 -1
- package/dist/effects/vfx/vfx-asset.js +1 -1
- package/dist/effects/vfx/vfx-collision-behaviour.js +1 -1
- package/dist/effects/vfx/vfx-defs.d.ts +10 -1
- package/dist/effects/vfx/vfx-defs.js +1 -1
- package/dist/effects/vfx/vfx-materializer.js +1 -1
- package/dist/effects/vfx/vfx-renderers.d.ts +2 -0
- package/dist/effects/vfx/vfx-renderers.js +1 -1
- package/dist/gameplay/actors/actor.d.ts +21 -1
- package/dist/gameplay/actors/actor.js +1 -1
- package/dist/gameplay/actors/builtin/components/character/character-animation.d.ts +3 -0
- package/dist/gameplay/actors/builtin/components/character/character-animation.js +1 -1
- package/dist/gameplay/actors/builtin/components/character/character-movement-like.d.ts +25 -0
- package/dist/gameplay/actors/builtin/components/character/character-movement-like.js +4 -0
- package/dist/gameplay/actors/builtin/components/character/character-movement-policy.d.ts +26 -0
- package/dist/gameplay/actors/builtin/components/character/character-movement-policy.js +4 -0
- package/dist/gameplay/actors/builtin/components/character/character-movement.d.ts +158 -55
- package/dist/gameplay/actors/builtin/components/character/character-movement.js +1 -1
- package/dist/gameplay/actors/builtin/components/character/net-character-movement-protocol.d.ts +128 -0
- package/dist/gameplay/actors/builtin/components/character/net-character-movement-protocol.js +4 -0
- package/dist/gameplay/actors/builtin/components/character/old-character-movement.d.ts +100 -0
- package/dist/gameplay/actors/builtin/components/character/old-character-movement.js +4 -0
- package/dist/gameplay/actors/builtin/components/index.d.ts +2 -0
- package/dist/gameplay/actors/builtin/components/index.js +1 -1
- package/dist/gameplay/actors/builtin/components/mesh-component.d.ts +1 -0
- package/dist/gameplay/actors/builtin/components/mesh-component.js +1 -1
- package/dist/gameplay/actors/builtin/components/tween-component.js +1 -1
- package/dist/gameplay/actors/builtin/navmesh-actor.d.ts +2 -0
- package/dist/gameplay/actors/builtin/navmesh-actor.js +1 -1
- package/dist/gameplay/actors/builtin/trigger-volume.d.ts +3 -3
- package/dist/gameplay/actors/builtin/trigger-volume.js +1 -1
- package/dist/gameplay/actors/camera/camera-component.d.ts +1 -3
- package/dist/gameplay/actors/camera/camera-component.js +1 -1
- package/dist/gameplay/actors/camera/third-person-camera-component.d.ts +3 -0
- package/dist/gameplay/actors/camera/third-person-camera-component.js +1 -1
- package/dist/gameplay/actors/component.d.ts +3 -0
- package/dist/gameplay/actors/component.js +1 -1
- package/dist/gameplay/actors/controller/actor-controller.d.ts +16 -0
- package/dist/gameplay/actors/controller/actor-controller.js +4 -0
- package/dist/gameplay/actors/factory.d.ts +3 -0
- package/dist/gameplay/actors/factory.js +1 -1
- package/dist/gameplay/actors/index.d.ts +4 -0
- package/dist/gameplay/actors/index.js +1 -1
- package/dist/gameplay/actors/internal/component-init.js +1 -1
- package/dist/gameplay/actors/type-registry.d.ts +10 -0
- package/dist/gameplay/actors/type-registry.js +4 -0
- package/dist/gameplay/ai/behavior-tree/move.d.ts +2 -2
- package/dist/gameplay/animation/retarget.d.ts +50 -0
- package/dist/gameplay/animation/retarget.js +4 -0
- package/dist/gameplay/animation/root-motion.js +1 -1
- package/dist/gameplay/event-graph/actor-transform-properties.d.ts +6 -0
- package/dist/gameplay/event-graph/actor-transform-properties.js +4 -0
- package/dist/gameplay/event-graph/asset-actor-types.d.ts +3 -0
- package/dist/gameplay/event-graph/asset-actor-types.js +4 -0
- package/dist/gameplay/event-graph/asset-references.d.ts +12 -0
- package/dist/gameplay/event-graph/asset-references.js +4 -0
- package/dist/gameplay/event-graph/compiler.d.ts +107 -0
- package/dist/gameplay/event-graph/compiler.js +4 -0
- package/dist/gameplay/event-graph/decorators.d.ts +74 -0
- package/dist/gameplay/event-graph/decorators.js +4 -0
- package/dist/gameplay/event-graph/document-compiler.d.ts +107 -0
- package/dist/gameplay/event-graph/document-compiler.js +4 -0
- package/dist/gameplay/event-graph/event-graph-demo.d.ts +2 -0
- package/dist/gameplay/event-graph/event-graph-demo.js +4 -0
- package/dist/gameplay/event-graph/graph.d.ts +106 -0
- package/dist/gameplay/event-graph/graph.js +4 -0
- package/dist/gameplay/event-graph/index.d.ts +13 -0
- package/dist/gameplay/event-graph/index.js +4 -0
- package/dist/gameplay/event-graph/model.d.ts +71 -0
- package/dist/gameplay/event-graph/model.js +4 -0
- package/dist/gameplay/event-graph/nodes/execution-nodes.d.ts +115 -0
- package/dist/gameplay/event-graph/nodes/execution-nodes.js +4 -0
- package/dist/gameplay/event-graph/nodes/gameplay-nodes.d.ts +208 -0
- package/dist/gameplay/event-graph/nodes/gameplay-nodes.js +4 -0
- package/dist/gameplay/event-graph/nodes/index.d.ts +4 -0
- package/dist/gameplay/event-graph/nodes/index.js +4 -0
- package/dist/gameplay/event-graph/nodes/value-nodes.d.ts +53 -0
- package/dist/gameplay/event-graph/nodes/value-nodes.js +4 -0
- package/dist/gameplay/event-graph/registry.d.ts +33 -0
- package/dist/gameplay/event-graph/registry.js +4 -0
- package/dist/gameplay/event-graph/runtime-assets.d.ts +21 -0
- package/dist/gameplay/event-graph/runtime-assets.js +4 -0
- package/dist/gameplay/event-graph/runtime.d.ts +160 -0
- package/dist/gameplay/event-graph/runtime.js +4 -0
- package/dist/gameplay/event-graph/type-inference.d.ts +41 -0
- package/dist/gameplay/event-graph/type-inference.js +4 -0
- package/dist/gameplay/event-graph/types.d.ts +77 -0
- package/dist/gameplay/event-graph/types.js +4 -0
- package/dist/gameplay/index.d.ts +17 -2
- package/dist/gameplay/index.js +1 -1
- package/dist/gameplay/initiate.d.ts +4 -0
- package/dist/gameplay/initiate.js +1 -1
- package/dist/gameplay/input/input.d.ts +22 -17
- package/dist/gameplay/input/input.js +1 -1
- package/dist/gameplay/net/browser/index.d.ts +147 -0
- package/dist/gameplay/net/browser/index.js +4 -0
- package/dist/gameplay/net/index.d.ts +7 -0
- package/dist/gameplay/net/index.js +4 -0
- package/dist/gameplay/net/net-connection.d.ts +25 -0
- package/dist/gameplay/net/net-connection.js +4 -0
- package/dist/gameplay/net/net-session.d.ts +70 -0
- package/dist/gameplay/net/net-session.js +4 -0
- package/dist/gameplay/net/service/net-actor-role.d.ts +12 -0
- package/dist/gameplay/net/service/net-actor-role.js +4 -0
- package/dist/gameplay/net/service/net-decorator.d.ts +29 -0
- package/dist/gameplay/net/service/net-decorator.js +4 -0
- package/dist/gameplay/net/service/net-serializer.d.ts +15 -0
- package/dist/gameplay/net/service/net-serializer.js +4 -0
- package/dist/gameplay/net/service/net-service.d.ts +171 -0
- package/dist/gameplay/net/service/net-service.js +4 -0
- package/dist/gameplay/net/service/net-utils.d.ts +8 -0
- package/dist/gameplay/net/service/net-utils.js +4 -0
- package/dist/gameplay/net/service/replication.d.ts +31 -0
- package/dist/gameplay/net/service/replication.js +4 -0
- package/dist/gameplay/net/service/rpc-decorator.d.ts +21 -0
- package/dist/gameplay/net/service/rpc-decorator.js +4 -0
- package/dist/gameplay/net/service/rpc.d.ts +35 -0
- package/dist/gameplay/net/service/rpc.js +4 -0
- package/dist/gameplay/services/asset-loader.d.ts +10 -2
- package/dist/gameplay/services/asset-loader.js +1 -1
- package/dist/gameplay/services/physics/physics-system.d.ts +16 -1
- package/dist/gameplay/services/physics/physics-system.js +1 -1
- package/dist/gameplay/services/world.d.ts +25 -2
- package/dist/gameplay/services/world.js +1 -1
- package/dist/rendering/color-pass.js +1 -1
- package/dist/rendering.d.ts +2 -0
- package/dist/rendering.js +1 -1
- package/dist/scene/asset-resource-loader.d.ts +7 -1
- package/dist/scene/asset-resource-loader.js +1 -1
- package/dist/scene/batched-mesh-2.d.ts +9 -0
- package/dist/scene/batched-mesh-2.js +1 -1
- package/dist/scene/bootstrap.d.ts +2 -0
- package/dist/scene/bootstrap.js +1 -1
- package/dist/scene/custom-param-deserialize.js +1 -1
- package/dist/scene/custom-param-runtime-types.js +1 -1
- package/dist/scene/materializer.d.ts +29 -0
- package/dist/scene/materializer.js +1 -1
- package/dist/scene/model.d.ts +114 -2
- package/dist/scene/model.js +1 -1
- package/dist/scene/objects/prefab.js +1 -1
- package/dist/scene/storage/storage.d.ts +1 -1
- package/dist/scene/storage/storage.js +1 -1
- package/dist/shader/builtin/standard-shader.js +1 -1
- package/dist/shader/builtin/toon-shader.js +1 -1
- package/dist/shader/builtin/unlit-shader.js +1 -1
- package/dist/shader/color-layer.js +1 -1
- package/dist/shader/graph/compiler.d.ts +3 -0
- package/dist/shader/graph/compiler.js +1 -1
- package/dist/shader/graph/model.d.ts +1 -1
- package/dist/shader/graph/registry.js +1 -1
- package/dist/shader/parameter.d.ts +1 -1
- package/dist/shader/parameter.js +1 -1
- package/dist/shader-nodes/depth.js +1 -1
- package/dist/shader-nodes/scene-sample.js +1 -1
- package/dist/test/animation-retarget.test.d.ts +2 -0
- package/dist/test/animation-retarget.test.js +4 -0
- package/dist/test/batched-mesh-2.test.d.ts +2 -0
- package/dist/test/batched-mesh-2.test.js +4 -0
- package/dist/test/browser-net-session.test.d.ts +2 -0
- package/dist/test/browser-net-session.test.js +4 -0
- package/dist/test/event-graph-generated-code.test.d.ts +2 -0
- package/dist/test/event-graph-generated-code.test.js +4 -0
- package/dist/test/event-graph-types.test.d.ts +2 -0
- package/dist/test/event-graph-types.test.js +4 -0
- package/dist/test/event-graph.test.d.ts +2 -0
- package/dist/test/event-graph.test.js +4 -0
- package/dist/test/first-person-camera-component.test.js +1 -1
- package/dist/test/input.test.d.ts +2 -0
- package/dist/test/input.test.js +4 -0
- package/dist/test/net-character-movement.test.d.ts +2 -0
- package/dist/test/net-character-movement.test.js +4 -0
- package/dist/test/net-property-snapshot.test.d.ts +2 -0
- package/dist/test/net-property-snapshot.test.js +4 -0
- package/dist/test/prefab-instance-params.test.js +1 -1
- package/dist/test/sequence-animation-retiming.test.js +1 -1
- package/dist/test/sequence-camera-control.test.js +1 -1
- package/dist/test/sequence-scene-binding.test.d.ts +2 -0
- package/dist/test/sequence-scene-binding.test.js +4 -0
- package/dist/test/sequence-transform.test.d.ts +2 -0
- package/dist/test/sequence-transform.test.js +4 -0
- package/dist/test/shader-graph.test.js +1 -1
- package/dist/test/vfx-random-color-initializer.test.d.ts +2 -0
- package/dist/test/vfx-random-color-initializer.test.js +4 -0
- package/dist/test/world-prefab-spawn.test.d.ts +2 -0
- package/dist/test/world-prefab-spawn.test.js +4 -0
- package/dist/utils/three/placeholder-texture.d.ts +3 -0
- package/dist/utils/three/placeholder-texture.js +4 -0
- package/package.json +18 -2
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -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,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,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
|
+
*/
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import"reflect-metadata";import{afterEach as e,expect as t,test as o,vi as n}from"vitest";n.hoisted(()=>{if("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,o)=>(e[t]=o,!0)});Object.defineProperty(HTMLCanvasElement.prototype,"getContext",{configurable:!0,value:()=>e})}),n.mock("../gameplay/services/render.js",()=>({ViewController:class{}})),n.mock("../gameplay/services/physics/physics-system.js",()=>({PhysicsSystem:class{},RayTestResult:class{}})),n.mock("../gameplay/services/world.js",()=>({World:class{}})),n.mock("@hology/nebula",()=>({ease:{easeInOutCubic:e=>e}}));import{Subject as a}from"rxjs";import{Container as i}from"typedi";import{Object3D as
|
|
1
|
+
import"reflect-metadata";import{afterEach as e,expect as t,test as o,vi as n}from"vitest";n.hoisted(()=>{if("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,o)=>(e[t]=o,!0)});Object.defineProperty(HTMLCanvasElement.prototype,"getContext",{configurable:!0,value:()=>e})}),n.mock("../gameplay/services/render.js",()=>({ViewController:class{}})),n.mock("../gameplay/services/physics/physics-system.js",()=>({PhysicsSystem:class{},RayTestResult:class{}})),n.mock("../gameplay/services/world.js",()=>({World:class{}})),n.mock("@hology/nebula",()=>({ease:{easeInOutCubic:e=>e}}));import{Subject as a}from"rxjs";import{Container as i}from"typedi";import{Object3D as s,Ray as r,Scene as c,Vector3 as p}from"three";import{BaseActor as m}from"../gameplay/actors/actor.js";import{FirstPersonCameraComponent as l}from"../gameplay/actors/camera/first-person-camera-component.js";import{ThirdPersonCameraComponent as d}from"../gameplay/actors/camera/third-person-camera-component.js";import{PhysicsSystem as y}from"../gameplay/services/physics/physics-system.js";import{ViewController as u}from"../gameplay/services/render.js";import{World as v}from"../gameplay/services/world.js";function f(e=new g){const t=h(),o=new l;return o.actor=e,{camera:o,actor:e,element:t}}function h(){const e=document.createElement("div"),t=document.createElement("canvas");return e.appendChild(t),Object.defineProperty(e,"clientWidth",{configurable:!0,value:1280}),Object.defineProperty(e,"clientHeight",{configurable:!0,value:720}),i.set(u,{htmlElement:e,setCamera:n.fn(),onUpdate:()=>new a,onLateUpdate:()=>new a}),i.set(v,{scene:new c}),i.set(y,{sphereCast:n.fn()}),e}function w(){Object.defineProperty(document.body,"requestPointerLock",{configurable:!0,value:n.fn()}),Object.defineProperty(document,"pointerLockElement",{configurable:!0,value:null})}function B(e,t){return e.mock.calls.filter(e=>e[0]===t).length}e(()=>{n.restoreAllMocks(),i.reset()}),o("first-person camera clamps pitch input",()=>{const{camera:e}=f();e.rotationInput.rotateX(Math.PI),t(e.rotationInput.rotation.x).toBeCloseTo(Math.PI/2-.01),e.rotationInput.rotateX(2*-Math.PI),t(e.rotationInput.rotation.x).toBeCloseTo(-Math.PI/2+.01)}),o("first-person camera follows actor eye offset",async()=>{const e=new g;e.position.set(1,2,3),e.rotation.y=Math.PI/2,e.object.updateMatrixWorld(!0);const{camera:o}=f(e);o.eyeHeight=1.5,o.offsetZ=.25,o.autoActivate=!1,await o.onInit(),o.updateCameraTransform(),t(o.camera.position.x).toBeCloseTo(1.25),t(o.camera.position.y).toBeCloseTo(3.5),t(o.camera.position.z).toBeCloseTo(3)}),o("first-person aim helpers write to caller-provided objects",async()=>{const e=new g;e.position.set(0,1,0);const{camera:o}=f(e);o.autoActivate=!1,await o.onInit();const n=new p,a=new p,i=new r;t(o.getAimOrigin(n)).toBe(n),t(o.getAimDirection(a)).toBe(a),t(o.getAimRay(i)).toBe(i),t(i.origin).toEqual(n),t(i.direction).toEqual(a),t(a.x).toBeCloseTo(0),t(a.y).toBeCloseTo(0),t(a.z).toBeCloseTo(1),t(a.length()).toBeCloseTo(1)}),o("first-person camera pitch input tilts aim downward when positive",async()=>{const{camera:e}=f();e.autoActivate=!1,await e.onInit(),e.rotationInput.rotateX(.25),e.updateCameraTransform();const o=e.getAimDirection(new p);t(o.y).toBeLessThan(0),t(o.z).toBeGreaterThan(0),t(o.length()).toBeCloseTo(1)}),o("first-person hidden objects restore their previous visibility",()=>{const{camera:e}=f(),o=new s,n=new s;n.visible=!1,e.hideObjects(o,n),t(o.visible).toBe(!1),t(n.visible).toBe(!1),e.restoreHiddenObjects(),t(o.visible).toBe(!0),t(n.visible).toBe(!1)}),o("first-person activation is idempotent",async()=>{const{camera:e,element:o}=f();e.autoActivate=!1,await e.onInit(),w();const a=n.spyOn(o,"addEventListener"),i=n.spyOn(o,"removeEventListener"),s=n.spyOn(document,"addEventListener"),r=n.spyOn(document,"removeEventListener");e.activate(),e.activate(),t(B(a,"pointerdown")).toBe(1),t(B(a,"keydown")).toBe(1),t(B(s,"pointerlockchange")).toBe(1),e.deactivate(),e.deactivate(),t(B(i,"pointerdown")).toBe(1),t(B(i,"keydown")).toBe(1),t(B(r,"pointerlockchange")).toBe(1)}),o("third-person activation remains idempotent",async()=>{const e=new g,o=h(),a=new d;a.actor=e,a.autoActivate=!1,await a.onInit(),w();const i=n.spyOn(o,"addEventListener"),s=n.spyOn(o,"removeEventListener"),r=n.spyOn(document,"addEventListener"),c=n.spyOn(document,"removeEventListener");a.activate(),a.activate(),t(B(i,"pointerdown")).toBe(1),t(B(i,"keydown")).toBe(1),t(B(r,"pointerlockchange")).toBe(1),a.deactivate(),a.deactivate(),t(B(s,"pointerdown")).toBe(1),t(B(s,"keydown")).toBe(1),t(B(c,"pointerlockchange")).toBe(1)}),o("third-person camera does not treat sustained fast movement as a teleport",async()=>{const e=new g;h();const o=new d;o.actor=e,o.autoActivate=!0,o.collision=!1,o.fixedBehind=!1,o.smoothCamera=!0,o.smoothSpeed=10,await o.onInit(),o.onLateUpdate(1/60);for(let t=0;t<12;t++)e.position.x+=.2,o.onLateUpdate(1/60);t(e.position.x-o.camera.position.x).toBeGreaterThan(.7)}),o("third-person camera still snaps after a target teleport",async()=>{const e=new g;h();const o=new d;o.actor=e,o.autoActivate=!0,o.collision=!1,o.fixedBehind=!1,o.smoothCamera=!0,o.teleportSnapDistance=5,await o.onInit(),o.onLateUpdate(1/60),e.position.x=10,o.onLateUpdate(1/60),t(o.camera.position.x).toBeCloseTo(10)});class g extends m{constructor(){super(),this.__isInitialised=!0}}/*
|
|
2
2
|
* Copyright (©) 2026 Hology Interactive AB. All rights reserved.
|
|
3
3
|
* See the LICENSE.md file for details.
|
|
4
4
|
*/
|
|
@@ -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
|
+
*/
|
|
@@ -0,0 +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 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
|
+
* Copyright (©) 2026 Hology Interactive AB. All rights reserved.
|
|
3
|
+
* See the LICENSE.md file for details.
|
|
4
|
+
*/
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import{__decorate as e,__metadata as t}from"tslib";import"reflect-metadata";import{Subject as n}from"rxjs";import{Vector3 as r}from"three";import{expect as s,test as o,vi as c}from"vitest";c.mock("../gameplay/services/render.js",()=>({ViewController:class{}})),c.mock("../gameplay/services/world.js",()=>({World:class{}}));import{Actor as a,BaseActor as i}from"../gameplay/actors/actor.js";import{ActorComponent as l}from"../gameplay/actors/component.js";import{$actorComponents as p}from"../gameplay/actors/internal/component-init.js";import{NetMode as d}from"../gameplay/net/net-session.js";import{NetRole as u}from"../gameplay/net/service/net-actor-role.js";import{NetSerializer as w}from"../gameplay/net/service/net-serializer.js";import{NetService as _}from"../gameplay/net/service/net-service.js";import{ReplOn as R}from"../gameplay/net/service/replication.js";import{getRpcMethodId as y,registerRpcMethod as g,RunsOn as v}from"../gameplay/net/service/rpc.js";import{RunOnServer as m}from"../gameplay/net/service/rpc-decorator.js";const f=2,h=3,B=4,b=1,S=21,V=25,A=7,C=8;let P=class extends i{constructor(){super(...arguments),this.allValue=0,this.ownerValue=0,this.notOwnerValue=0,this.changingValue=0,this.allOnRepSawOwnerValue=-1,this.clientRpcCalls=[],this.serverRpcCalls=[],this.clientBinaryRpcCalls=[],this.serverBinaryRpcCalls=[],this.largeBinaryRpcByteLength=0,this.largeBinaryRpcLastByte=0}onRep_allValue(){this.allOnRepSawOwnerValue=this.ownerValue}clientRpc(e){this.clientRpcCalls.push(e)}serverRpc(e){this.serverRpcCalls.push(e)}clientBinaryRpc(e){this.clientBinaryRpcCalls.push(Array.from(e))}serverBinaryRpc(e){this.serverBinaryRpcCalls.push(Array.from(e))}largeBinaryRpc(e){this.largeBinaryRpcByteLength=e.byteLength,this.largeBinaryRpcLastByte=e[e.byteLength-1]}};P=e([a({replicate:!0})],P);class O extends l{constructor(){super(...arguments),this.componentValue="",this.binaryRpcCalls=[]}componentBinaryRpc(e){this.binaryRpcCalls.push(Array.from(e))}}g(P.prototype,"clientRpc"),g(P.prototype,"serverRpc"),g(P.prototype,"clientBinaryRpc"),g(P.prototype,"serverBinaryRpc"),g(P.prototype,"largeBinaryRpc"),g(O.prototype,"componentBinaryRpc");class I extends i{constructor(){super(...arguments),this.baseRpcCalls=[]}baseRpc(e){this.baseRpcCalls.push(e)}}class M extends I{constructor(){super(...arguments),this.derivedRpcCalls=[]}derivedRpc(e){this.derivedRpcCalls.push(e)}}g(I.prototype,"baseRpc"),g(M.prototype,"derivedRpc");class k extends i{inheritedRpc(){}}e([m(),t("design:type",Function),t("design:paramtypes",[]),t("design:returntype",void 0)],k.prototype,"inheritedRpc",null);class q extends k{derivedRpc(){}}function U(e,t,n={}){const r={actors:t,removeActor(e){const n=t.indexOf(e);n>=0&&t.splice(n,1)}},s=Object.create(w.prototype);s.world=r;const o=j(e,n.clients??[],n.server,n.sent??[]),c=Object.create(_.prototype);return c.session=o,c.world=r,c.serializer=s,c.actorConnection=new Map,c.actorPreSpawnMessageBuffer=new Map,c.actorRefMissingMessageQueue=new Map,c.netStartupActorSpawnQueue=new Map,c.spawningActorNetIds=new Set,c.shouldReplicateMap=new Map,c.replicatedActors=new Map,c.replicatedProperties=new Map,c.replicatedPropertyRegistry=new Map,c.nextNetId=1,c}function j(e,t,r,s){return{id:"test-session",mode:e,clients:t,server:r,reconnect(){},disconnect(){},sendMessage(e,t,n){s.push({receiver:e,reliable:t,buffer:n})},hasMessage:()=>0,readMessage:()=>null,playerJoined:new n,playerLeft:new n}}function L(e){return{id:e}}function x(e){const t=new ArrayBuffer(5),n=new DataView(t);return n.setUint8(0,A),n.setUint32(1,e,!0),t}function E(e){const t=new ArrayBuffer(5),n=new DataView(t);return n.setUint8(0,B),n.setUint32(1,e,!0),t}function z(e){return new DataView(e).getUint8(0)}function D(e){return new DataView(e).getUint8(5)}function Q(e){const t=new DataView(e);let n=0;const r=t.getUint8(n);n+=1;const s=t.getUint32(n,!0);n+=4;n+=1+t.getUint8(n),n+=12,n+=12;const o=t.getUint8(n);n+=1;return{messageType:r,actorId:s,netRole:o,ownerId:t.getUint32(n)}}e([m(),t("design:type",Function),t("design:paramtypes",[]),t("design:returntype",void 0)],q.prototype,"derivedRpc",null),o("property snapshots are sent after actorReady and respect replication targets",()=>{const e=L(1),t=L(2),n=L("server"),r=new P;r.__netid=10,r.allValue=11,r.ownerValue=22,r.notOwnerValue=33;const o=[],c=U(d.dedicatedServer,[r],{clients:[e,t],sent:o});c.setOwningConnection(r,e),c.registerReplicatedProperty(R.all,!0,r,"allValue",r),c.registerReplicatedProperty(R.owner,!0,r,"ownerValue",r),c.registerReplicatedProperty(R.notOwner,!0,r,"notOwnerValue",r),c.getPreSpawnMessageQueue(r,e),c.processActorReady(e,x(r.__netid));const a=new P;a.__netid=r.__netid;U(d.client,[a],{server:n}).processPropSnapshot(n,o[0].buffer),s(a.allValue).toBe(11),s(a.ownerValue).toBe(22),s(a.notOwnerValue).toBe(0),s(a.allOnRepSawOwnerValue).toBe(22),o.length=0,c.getPreSpawnMessageQueue(r,t),c.processActorReady(t,x(r.__netid));const i=new P;i.__netid=r.__netid;U(d.client,[i],{server:n}).processPropSnapshot(n,o[0].buffer),s(i.allValue).toBe(11),s(i.ownerValue).toBe(0),s(i.notOwnerValue).toBe(33)}),o("property updates queued during spawn are replaced by the latest snapshot value",()=>{const e=L(1),t=L("server"),n=new P;n.changingValue=1;const r=[],o=U(d.dedicatedServer,[n],{clients:[e],sent:r});o.setReplicate(n,!0),o.registerReplicatedProperty(R.all,!0,n,"changingValue",n),o.sendActorSpawn(n,[e]),r.length=0,n.changingValue=2,o.handlePropertySet(R.all,!0,n,"changingValue",n.changingValue),n.changingValue=3,o.handlePropertySet(R.all,!0,n,"changingValue",n.changingValue),s(r).toHaveLength(0),o.processActorReady(e,x(n.__netid)),s(r.map(e=>z(e.buffer))).toEqual([C]);const c=new P;c.__netid=n.__netid;U(d.client,[c],{server:t}).processPropSnapshot(t,r[0].buffer),s(c.changingValue).toBe(3)}),o("property snapshots include component properties",()=>{const e=L(1),t=L("server"),n=new P;n.__netid=20;const r=new O;r.actor=n,r.__netid=1,r.componentValue="server component",n[p]=[r];const o=[],c=U(d.dedicatedServer,[n],{clients:[e],sent:o});c.registerReplicatedProperty(R.all,!0,n,"componentValue",r,r),c.getPreSpawnMessageQueue(n,e),c.processActorReady(e,x(n.__netid));const a=new P;a.__netid=n.__netid;const i=new O;i.actor=a,i.__netid=r.__netid,a[p]=[i];U(d.client,[a],{server:t}).processPropSnapshot(t,o[0].buffer),s(i.componentValue).toBe("server component")}),o("property snapshots queue atomically when they reference a missing actor",()=>{const e=c.spyOn(console,"warn").mockImplementation(()=>{}),t=c.spyOn(console,"debug").mockImplementation(()=>{}),n=c.spyOn(console,"log").mockImplementation(()=>{}),r=L(1),o=L("server"),a=new P;a.__netid=30,a.allValue=44;const i=new P;i.__netid=31,a.referencedActor=i;const l=[],p=U(d.dedicatedServer,[a,i],{clients:[r],sent:l});p.registerReplicatedProperty(R.all,!0,a,"allValue",a),p.registerReplicatedProperty(R.all,!0,a,"referencedActor",a),p.getPreSpawnMessageQueue(a,r),p.processActorReady(r,x(a.__netid));const u=new P;u.__netid=a.__netid;const w=U(d.client,[u],{server:o});w.processPropSnapshot(o,l[0].buffer),s(u.allValue).toBe(0),s(u.referencedActor).toBeUndefined();const _=new P;_.__netid=i.__netid,w.world.actors.push(_),w.processQueuedActorMessages(_.__netid),s(u.allValue).toBe(44),s(u.referencedActor).toBe(_),e.mockRestore(),t.mockRestore(),n.mockRestore()}),o("live property replication applies server updates and ignores non-server messages",()=>{const e=L("server"),t=L(1),n=new P;n.__netid=40;const r=[],o=U(d.dedicatedServer,[n],{clients:[t],sent:r});o.setReplicate(n,!0),o.sendActorSpawn(n,[t]),o.processActorReady(t,x(n.__netid)),r.length=0,o.handlePropertySet(R.all,!0,n,"allValue",55),s(r).toHaveLength(1),s(z(r[0].buffer)).toBe(f);const a=new P;a.__netid=n.__netid,a.ownerValue=66;const i=U(d.client,[a],{server:e});i.processProp(e,r[0].buffer),s(a.allValue).toBe(55),s(a.allOnRepSawOwnerValue).toBe(66);const l=c.spyOn(console,"warn").mockImplementation(()=>{}),p=r[0].buffer;a.allValue=12,i.processProp(L("rogue"),p),s(a.allValue).toBe(12),l.mockRestore()}),o("server RPCs from clients require ownership and owner RPCs run on the owning client",()=>{const e=L(1),t=L(2),n=L("server"),r=new P;r.__netid=50;const o=U(d.dedicatedServer,[r],{clients:[e,t]});o.setReplicate(r,!0),o.replicatedActors.set(r,{spawnedAt:Date.now(),connections:[e,t]}),o.setOwningConnection(r,e);const a=new P;a.__netid=r.__netid;const i=[],l=U(d.client,[a],{server:n,sent:i});l.setReplicate(a,!0),l.sendRpc(v.server,!0,a,"serverRpc",[77]),s(D(i[0].buffer)).toBe(y(a,"serverRpc")),o.processRpc(e,i[0].buffer),s(r.serverRpcCalls).toEqual([77]);const p=new P;p.__netid=r.__netid;const u=[],w=U(d.client,[p],{server:n,sent:u});w.setReplicate(p,!0);const _=c.spyOn(console,"warn").mockImplementation(()=>{});w.sendRpc(v.server,!0,p,"serverRpc",[88]),o.processRpc(t,u[0].buffer),s(r.serverRpcCalls).toEqual([77]),_.mockRestore();const R=[];o.session=j(d.dedicatedServer,[e,t],void 0,R),o.sendRpc(v.client,!0,r,"clientRpc",[99]),s(R.map(e=>e.receiver.id)).toEqual([e.id]),s(z(R[0].buffer)).toBe(b),s(D(R[0].buffer)).toBe(y(r,"clientRpc")),l.processRpc(n,R[0].buffer),s(a.clientRpcCalls).toEqual([99])}),o("RPC method ids include inherited methods before derived registrations",()=>{const e=L(1),t=L("server"),n=new M;n.__netid=60;const r=U(d.dedicatedServer,[n],{clients:[e]});r.setReplicate(n,!0),r.setOwningConnection(n,e);const o=new M;o.__netid=n.__netid;const c=[],a=U(d.client,[o],{server:t,sent:c});a.setReplicate(o,!0),s(y(o,"baseRpc")).toBe(0),s(y(o,"derivedRpc")).toBe(1),a.sendRpc(v.server,!0,o,"baseRpc",[10]),a.sendRpc(v.server,!0,o,"derivedRpc",[20]),s(D(c[0].buffer)).toBe(0),s(D(c[1].buffer)).toBe(1),r.processRpc(e,c[0].buffer),r.processRpc(e,c[1].buffer),s(n.baseRpcCalls).toEqual([10]),s(n.derivedRpcCalls).toEqual([20])}),o("decorated inherited RPCs do not need to be redecorated on subclasses",()=>{const e=new q;s(y(e,"inheritedRpc")).toBe(0),s(y(e,"derivedRpc")).toBe(1)}),o("binary actor RPCs bypass NetSerializer from client to server",()=>{const e=L(1),t=L("server"),n=new P;n.__netid=80;const r=U(d.dedicatedServer,[n],{clients:[e]});r.setReplicate(n,!0),r.setOwningConnection(n,e);const o=new P;o.__netid=n.__netid;const a=[],i=U(d.client,[o],{server:t,sent:a});i.setReplicate(o,!0);const l=c.spyOn(i.serializer,"encode"),p=c.spyOn(r.serializer,"decode");i.sendRpc(v.server,!0,o,"serverBinaryRpc",[new Uint8Array([1,2,3])]),s(z(a[0].buffer)).toBe(S),s(l).not.toHaveBeenCalled(),r.processRpc(e,a[0].buffer),s(p).not.toHaveBeenCalled(),s(n.serverBinaryRpcCalls).toEqual([[1,2,3]]),l.mockRestore(),p.mockRestore()}),o("server to owner binary RPC preserves owner routing",()=>{const e=L(1),t=L(2),n=L("server"),r=new P;r.__netid=81;const o=[],c=U(d.dedicatedServer,[r],{clients:[e,t],sent:o});c.setReplicate(r,!0),c.replicatedActors.set(r,{spawnedAt:Date.now(),connections:[e,t]}),c.setOwningConnection(r,e);const a=new Uint8Array([9,8,7]);c.sendRpc(v.client,!0,r,"clientBinaryRpc",[new DataView(a.buffer)]),s(o.map(e=>e.receiver.id)).toEqual([e.id]),s(z(o[0].buffer)).toBe(S);const i=new P;i.__netid=r.__netid;U(d.client,[i],{server:n}).processRpc(n,o[0].buffer),s(i.clientBinaryRpcCalls).toEqual([[9,8,7]])}),o("component binary RPC resolves component id",()=>{const e=L(1),t=L("server"),n=new P;n.__netid=82;const r=new O;r.actor=n,r.__netid=4,n[p]=[r];const o=[],c=U(d.dedicatedServer,[n],{clients:[e],sent:o});c.setReplicate(n,!0),c.replicatedActors.set(n,{spawnedAt:Date.now(),connections:[e]}),c.sendRpc(v.all,!0,n,"componentBinaryRpc",[new Uint8Array([4,5,6])],r),s(z(o[0].buffer)).toBe(V);const a=new P;a.__netid=n.__netid;const i=new O;i.actor=a,i.__netid=r.__netid,a[p]=[i];U(d.client,[a],{server:t}).processRpc(t,o[0].buffer),s(i.binaryRpcCalls).toEqual([[4,5,6]])}),o("binary RPC payload length supports more than uint16",()=>{const e=L(1),t=L("server"),n=new P;n.__netid=83;const r=U(d.dedicatedServer,[n],{clients:[e]});r.setReplicate(n,!0),r.setOwningConnection(n,e);const o=new P;o.__netid=n.__netid;const c=[],a=U(d.client,[o],{server:t,sent:c});a.setReplicate(o,!0);const i=new Uint8Array(7e4);i[i.length-1]=231,a.sendRpc(v.server,!0,o,"largeBinaryRpc",[i]),s(z(c[0].buffer)).toBe(S),s(function(e){const t=new DataView(e);let n=6;return t.getUint32(n,!0)}(c[0].buffer)).toBe(i.length),r.processRpc(e,c[0].buffer),s(n.largeBinaryRpcByteLength).toBe(i.length),s(n.largeBinaryRpcLastByte).toBe(231)}),o("binary RPCs queue when the target actor is missing",()=>{const e=c.spyOn(console,"debug").mockImplementation(()=>{}),t=L(1),n=L("server"),r=new P;r.__netid=84;const o=[],a=U(d.dedicatedServer,[r],{clients:[t],sent:o});a.setReplicate(r,!0),a.replicatedActors.set(r,{spawnedAt:Date.now(),connections:[t]}),a.sendRpc(v.all,!0,r,"clientBinaryRpc",[new Uint8Array([8,4])]);const i=U(d.client,[],{server:n});i.processRpc(n,o[0].buffer);const l=new P;l.__netid=r.__netid,i.world.actors.push(l),i.processQueuedActorMessages(l.__netid),s(l.clientBinaryRpcCalls).toEqual([[8,4]]),e.mockRestore()}),o("actor spawn messages are personalized with owner role and owner actor id",()=>{const e=L(1),t=L(2),n=new P;n.__netid=90;const r=new P;r.owner=n;const o=[],c=U(d.dedicatedServer,[n,r],{clients:[e,t],sent:o});c.setOwningConnection(n,e),c.sendActorSpawn(r,[e,t]),s(o).toHaveLength(2);const a=Q(o[0].buffer),i=Q(o[1].buffer);s(a.messageType).toBe(h),s(a.actorId).toBe(r.__netid),s(a.netRole).toBe(u.autonomousProxy),s(a.ownerId).toBe(n.__netid),s(i.actorId).toBe(r.__netid),s(i.netRole).toBe(u.simulatedProxy),s(i.ownerId).toBe(n.__netid)}),o("startup actor spawn binds an existing materialized scene actor",async()=>{const e=c.spyOn(console,"log").mockImplementation(()=>{}),t=L(1),n=L("server"),r="level/chest",o=new P;o.__netSceneId=r;const a=[];U(d.dedicatedServer,[o],{clients:[t],sent:a}).sendActorSpawn(o,[t]);const i=new P;i.__netSceneId=r;const l=[],p=U(d.client,[i],{server:n,sent:l});await p.processSpawn(n,a[0].buffer),s(i.__netid).toBe(o.__netid),s(i.netRole).toBe(u.simulatedProxy),s(p.world.actors).toEqual([i]),s(l).toHaveLength(1),s(z(l[0].buffer)).toBe(A),s(new DataView(l[0].buffer).getUint32(1,!0)).toBe(o.__netid),e.mockRestore()}),o("startup actor spawn waits for the materialized scene actor",async()=>{const e=c.spyOn(console,"log").mockImplementation(()=>{}),t=L(1),n=L("server"),r="level/delayed-chest",o=new P;o.__netSceneId=r;const a=[];U(d.dedicatedServer,[o],{clients:[t],sent:a}).sendActorSpawn(o,[t]);const i=[],l=[],p=U(d.client,i,{server:n,sent:l});await p.processSpawn(n,a[0].buffer),s(l).toHaveLength(0),s(p.netStartupActorSpawnQueue.get(r)).toHaveLength(1);const w=new P;w.__netSceneId=r,i.push(w),p.processQueuedNetStartupActorSpawns(w),s(w.__netid).toBe(o.__netid),s(w.netRole).toBe(u.simulatedProxy),s(p.netStartupActorSpawnQueue.has(r)).toBe(!1),s(l).toHaveLength(1),s(z(l[0].buffer)).toBe(A),e.mockRestore()}),o("materialized scene actors get client-side initial roles",()=>{const e=new P;e.__netSceneId="level/replicated";const t=new I;t.__netSceneId="level/local-only";const n=U(d.client,[e,t]);n.configureMaterializedActorRole(e),n.configureMaterializedActorRole(t),s(e.netRole).toBe(u.simulatedProxy),s(t.netRole).toBe(u.none)}),o("remove actor messages only remove client actors when sent by the server",()=>{const e=L("server"),t=new P;t.__netid=60;const n=[t],r=U(d.client,n,{server:e}),o=c.spyOn(console,"warn").mockImplementation(()=>{});r.processRemoveActor(L("rogue"),E(t.__netid)),s(n).toContain(t),r.processRemoveActor(e,E(t.__netid)),s(n).not.toContain(t),o.mockRestore()}),o("net serializer preserves actor references and three vector values",()=>{const e=new P;e.__netid=70;const t={actors:[e]},n=Object.create(w.prototype);n.world=t;const o=n.encode({actor:e,position:new r(1,2,3),nested:{values:new Set([new r(4,5,6)])}}),c={error:!1,missingActorId:void 0,reset(){this.error=!1,this.missingActorId=void 0}},a=n.decode(o,c);s(a.actor).toBe(e),s(a.position).toBeInstanceOf(r),s(a.position.toArray()).toEqual([1,2,3]),s([...a.nested.values][0].toArray()).toEqual([4,5,6]),s(c.error).toBe(!1)});/*
|
|
2
|
+
* Copyright (©) 2026 Hology Interactive AB. All rights reserved.
|
|
3
|
+
* See the LICENSE.md file for details.
|
|
4
|
+
*/
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{__decorate as e,__metadata as a}from"tslib";import{describe as t,expect as
|
|
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{expect as t,test as o}from"vitest";import*as e from"three";import{BaseActor as i}from"../gameplay/actors/actor.js";import{CharacterAnimationComponent as n,
|
|
1
|
+
import{expect as t,test as o}from"vitest";import*as e from"three";import{BaseActor as i}from"../gameplay/actors/actor.js";import{CharacterAnimationComponent as n,OldCharacterMovementComponent as s}from"../gameplay/actors/index.js";import{RootMotionClip as r}from"../gameplay/animation/root-motion.js";import{createAnimationClipInstance as a,createAnimationSubTrack as c,evaluateAnimationClipOutputProgress as l,evaluateAnimationClipSourceProgress as m,normalizeAnimationClipRetiming as p,SequencePlayer as u,buildRetimedAnimationClip as d}from"../effects/sequence/index.js";function g(t){return Array.from(t).map(t=>Math.round(1e3*t)/1e3)}o("uniform retiming mapping stays linear",()=>{t(m(void 0,.25)).toBeCloseTo(.25),t(l(void 0,.75)).toBeCloseTo(.75),t(m({mode:"uniform",points:[]},.6)).toBeCloseTo(.6)}),o("impact retiming redistributes time around the impact marker",()=>{const o={mode:"impact",points:[{id:"impact",clipProgress:.35,sourceProgress:.55}]};t(m(o,.35)).toBeCloseTo(.55),t(l(o,.55)).toBeCloseTo(.35),t(m(o,.7)).toBeGreaterThan(.7)}),o("advanced retiming sanitization stays monotonic and bounded",()=>{const o=p({mode:"advanced",points:[{id:"a",clipProgress:.9,sourceProgress:.2},{id:"b",clipProgress:-4,sourceProgress:.8},{id:"c",clipProgress:.9,sourceProgress:2},{id:"d",clipProgress:.4,sourceProgress:.1}]});t(o.points).toHaveLength(3);for(let e=1;e<o.points.length;e++)t(o.points[e].clipProgress).toBeGreaterThan(o.points[e-1].clipProgress),t(o.points[e].sourceProgress).toBeGreaterThan(o.points[e-1].sourceProgress);t(o.points[0].clipProgress).toBeGreaterThan(0),t(o.points[o.points.length-1].sourceProgress).toBeLessThan(1)}),o("retimed clip baking preserves trim boundary poses and remaps track times to clip duration",()=>{const o=new e.AnimationClip("trim-test",2,[new e.NumberKeyframeTrack("weight",[0,1,2],[0,10,20])]),i=a("anim",0,1);i.clipStartOffset=.5,i.clipEndOffset=.25,i.retiming={mode:"advanced",points:[]};const n=d(o,i),s=n.tracks[0];t(n.duration).toBeCloseTo(1),t(g(s.times)).toEqual([0,.4,1]),t(g(s.values)).toEqual([5,10,17.5])}),o("root motion track baking stays aligned with the retimed visual clip",()=>{const o=new e.AnimationClip("root-motion-test",2,[new e.VectorKeyframeTrack("hips.position",[0,1,2],[0,0,0,1,0,0,2,0,0])]),i=a("anim",0,1);i.retiming={mode:"impact",points:[{id:"impact",clipProgress:.25,sourceProgress:.5}]};const n=d(o,i),s=n.tracks[0],c=r.fromClip(n,!1);t(g(s.times)).toEqual([0,.25,1]),t(g(c.motionTrack.times)).toEqual([0,.25,1]),t(g(c.motionTrack.values)).toEqual([0,0,0,1,0,0,2,0,0])}),o("sequence player keeps uniform clips on the existing playback path",()=>{const o=new u,i=c(),n=a("anim",0,1);n.playbackRate=1.75,n.clipStartOffset=.25,i.clips.push(n);const s=new e.AnimationClip("uniform-test",2,[new e.NumberKeyframeTrack("weight",[0,1,2],[0,1,2])]);o.animationClips.set("anim",s);const r=new e.Object3D,l={target:r,mixer:new e.AnimationMixer(r),activeActions:new Map};o.evaluateAnimationSubTrack(i,l,.1);const m=l.activeActions.get(`${i.id}-${n.id}`);t(m).toBeDefined(),t(m.getClip()).toBe(s),t(m.timeScale).toBeCloseTo(1.75),t(m.time).toBeCloseTo(.25)}),o("sequence player uses baked retimed clips and still wires root motion through the action",()=>{const o=new u,i=c(),n=a("anim",0,1.5);n.rootMotion=!0,n.playbackRate=2,n.retiming={mode:"impact",points:[{id:"impact",clipProgress:.3,sourceProgress:.55}]},i.clips.push(n);const s=new e.AnimationClip("retimed-root",2,[new e.VectorKeyframeTrack("hips.position",[0,1,2],[0,0,0,1,0,0,2,0,0]),new e.NumberKeyframeTrack("weight",[0,1,2],[0,1,2])]);o.animationClips.set("anim",s);const l=new h,m=new f(l.object),p=new A;l.charAnim=m,l.movement=p;const d={target:l,activeActions:new Map};o.evaluateAnimationSubTrack(i,d,.1);const g=d.activeActions.get(`${i.id}-${n.id}`);t(g).toBeDefined(),t(g.timeScale).toBeCloseTo(1),t(g.getClip()).toBeInstanceOf(r),t(g.getClip().duration).toBeCloseTo(1.5),t(p.lastRootMotionAction).toBe(g),t(o.retimedAnimationClips.size).toBe(1)});class h extends i{constructor(){super(...arguments),this.charAnim=null,this.movement=null}getComponent(t){return t===n?this.charAnim:t===s?this.movement:void 0}}class f{constructor(t){this.fullBodyAction=null,this.mixer=new e.AnimationMixer(t)}play(t,o={}){this.fullBodyAction=this.mixer.clipAction(t),this.fullBodyAction.setLoop(e.LoopOnce,1),this.fullBodyAction.clampWhenFinished=!0,this.fullBodyAction.timeScale=o.timeScale??1,null!=o.offset&&(this.fullBodyAction.time=o.offset),this.fullBodyAction.play()}beginExternalAnimationControl(t,o={}){return this.play(t,o),this.getFullBodyAction()}getFullBodyAction(){if(null==this.fullBodyAction)throw new Error("No full body action is active");return this.fullBodyAction}getMixer(){return this.mixer}beginExternalControl(){}endExternalControl(){}stopSequenceAnimation(){}}class A{constructor(){this.lastRootMotionAction=null}setRootMotionAction(t){this.lastRootMotionAction=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{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
|
|
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,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,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
|
+
*/
|