From f41117cab11679699ac60a6cf712301bf9388566 Mon Sep 17 00:00:00 2001 From: Victor Giers Date: Tue, 14 Apr 2026 13:52:44 +0200 Subject: [PATCH] Add tests for actor animation and follow-path state resolution --- .../domain/runtime-project-scheduler.test.ts | 129 ++++++++++++++++++ 1 file changed, 129 insertions(+) diff --git a/tests/domain/runtime-project-scheduler.test.ts b/tests/domain/runtime-project-scheduler.test.ts index 0f67a672..4e526934 100644 --- a/tests/domain/runtime-project-scheduler.test.ts +++ b/tests/domain/runtime-project-scheduler.test.ts @@ -6,6 +6,8 @@ import { createDefaultResolvedControlSource, createEmptyRuntimeResolvedControlState, createLightControlTargetRef, + createFollowActorPathControlEffect, + createPlayActorAnimationControlEffect, createSetActorPresenceControlEffect } from "../../src/controls/control-surface"; import { createSetLightIntensityControlEffect } from "../../src/controls/control-surface"; @@ -238,4 +240,131 @@ describe("runtime project scheduler", () => { ]) ); }); + + it("resolves actor animation and deterministic follow-path state from the active routine window", () => { + const actorTarget = createActorControlTargetRef("actor-patrol"); + const scheduler = createEmptyProjectScheduler(); + scheduler.routines["routine-patrol"] = createProjectScheduleRoutine({ + id: "routine-patrol", + title: "Patrolling", + target: actorTarget, + startHour: 22, + endHour: 2, + effects: [ + createSetActorPresenceControlEffect({ + target: actorTarget, + active: true + }), + createPlayActorAnimationControlEffect({ + target: actorTarget, + clipName: "Walk", + loop: true + }), + createFollowActorPathControlEffect({ + target: actorTarget, + pathId: "path-patrol", + speed: 2, + loop: false, + progressMode: "deriveFromTime" + }) + ] + }); + + const resolved = resolveRuntimeProjectScheduleState({ + scheduler, + actorIds: ["actor-patrol"], + dayNumber: 2, + timeOfDayHours: 1, + pathsById: new Map([ + [ + "path-patrol", + { + id: "path-patrol", + loop: false, + points: [ + { + position: { x: 0, y: 0, z: 0 } + }, + { + position: { x: 8, y: 0, z: 0 } + } + ], + segments: [ + { + start: { x: 0, y: 0, z: 0 }, + end: { x: 8, y: 0, z: 0 }, + length: 8, + distanceStart: 0, + distanceEnd: 8, + tangent: { x: 1, y: 0, z: 0 } + } + ], + totalLength: 8 + } + ] + ]) + }); + const actorState = resolved.actors[0]; + const resolvedControl = applyRuntimeProjectScheduleToControlState( + createEmptyRuntimeResolvedControlState(), + resolved + ); + + expect(actorState).toEqual( + expect.objectContaining({ + actorId: "actor-patrol", + active: true, + activeRoutineId: "routine-patrol", + activeRoutineTitle: "Patrolling", + animationEffect: expect.objectContaining({ + type: "playActorAnimation", + clipName: "Walk" + }), + pathEffect: expect.objectContaining({ + type: "followActorPath", + pathId: "path-patrol", + speed: 2 + }), + resolvedPath: expect.objectContaining({ + progress: 0.75, + elapsedHours: 3, + position: { + x: 6, + y: 0, + z: 0 + }, + yawDegrees: 90 + }) + }) + ); + expect(resolvedControl.discrete).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + type: "actorPresence", + value: true, + source: { + kind: "scheduler", + scheduleId: "routine-patrol" + } + }), + expect.objectContaining({ + type: "actorAnimationPlayback", + clipName: "Walk", + source: { + kind: "scheduler", + scheduleId: "routine-patrol" + } + }), + expect.objectContaining({ + type: "actorPathAssignment", + pathId: "path-patrol", + speed: 2, + source: { + kind: "scheduler", + scheduleId: "routine-patrol" + } + }) + ]) + ); + }); });