diff --git a/tests/unit/runtime-host.test.ts b/tests/unit/runtime-host.test.ts index d770671e..c97c0ddc 100644 --- a/tests/unit/runtime-host.test.ts +++ b/tests/unit/runtime-host.test.ts @@ -2,17 +2,23 @@ import { waitFor } from "@testing-library/react"; import { afterEach, describe, expect, it, vi } from "vitest"; import { + createActorControlTargetRef, createLightControlTargetRef, + createSetActorPresenceControlEffect, type ControlEffect, createSetLightEnabledControlEffect, createSetLightIntensityControlEffect } from "../../src/controls/control-surface"; import { createEmptySceneDocument } from "../../src/document/scene-document"; -import { createPointLightEntity } from "../../src/entities/entity-instances"; +import { + createNpcEntity, + createPointLightEntity +} from "../../src/entities/entity-instances"; import { createControlInteractionLink, type InteractionLink } from "../../src/interactions/interaction-links"; +import { createProjectScheduleRoutine } from "../../src/scheduler/project-scheduler"; import { RapierCollisionWorld } from "../../src/runtime-three/rapier-collision-world"; import { RuntimeHost, @@ -195,4 +201,86 @@ describe("RuntimeHost", () => { host.dispose(); }); + + it("re-resolves NPC activity from the project scheduler when the runtime clock advances", () => { + vi.spyOn(console, "warn").mockImplementation(() => undefined); + vi.spyOn(RapierCollisionWorld, "create").mockResolvedValue({ + dispose: vi.fn(), + resolveThirdPersonCameraCollision: vi.fn( + (_pivot, desiredCameraPosition) => desiredCameraPosition + ) + } as unknown as RapierCollisionWorld); + + const npc = createNpcEntity({ + id: "entity-npc-night-guard", + actorId: "actor-night-guard" + }); + const document = createEmptySceneDocument(); + document.entities[npc.id] = npc; + document.scheduler.routines["routine-night-guard"] = + createProjectScheduleRoutine({ + id: "routine-night-guard", + title: "Night Shift", + target: createActorControlTargetRef(npc.actorId), + startHour: 20, + endHour: 4, + effect: createSetActorPresenceControlEffect({ + target: createActorControlTargetRef(npc.actorId), + active: true + }) + }); + + const runtimeScene = buildRuntimeSceneFromDocument(document); + const host = new RuntimeHost({ + enableRendering: false + }); + host.loadScene(runtimeScene); + + const hostInternals = host as unknown as { + currentClockState: { + timeOfDayHours: number; + dayCount: number; + dayLengthMinutes: number; + } | null; + sceneReady: boolean; + runtimeScene: typeof runtimeScene | null; + syncRuntimeNpcScheduleToCurrentClock(): void; + }; + + expect(runtimeScene.entities.npcs).toEqual([]); + + hostInternals.sceneReady = true; + hostInternals.currentClockState = { + timeOfDayHours: 21, + dayCount: 0, + dayLengthMinutes: 24 + }; + hostInternals.syncRuntimeNpcScheduleToCurrentClock(); + + expect(hostInternals.runtimeScene?.entities.npcs).toEqual([ + expect.objectContaining({ + entityId: npc.id, + actorId: npc.actorId, + activeRoutineTitle: "Night Shift" + }) + ]); + + hostInternals.currentClockState = { + timeOfDayHours: 6, + dayCount: 1, + dayLengthMinutes: 24 + }; + hostInternals.syncRuntimeNpcScheduleToCurrentClock(); + + expect(hostInternals.runtimeScene?.entities.npcs).toEqual([]); + expect(hostInternals.runtimeScene?.npcDefinitions[0]).toEqual( + expect.objectContaining({ + entityId: npc.id, + active: false, + activeRoutineTitle: null + }) + ); + + host.dispose(); + }); });