From 5fe8c3cee19479982563045d4902e624cb0e39f3 Mon Sep 17 00:00:00 2001 From: Victor Giers Date: Wed, 15 Apr 2026 08:01:03 +0200 Subject: [PATCH] Add function to resolve most recent completed actor path state --- .../runtime-project-scheduler.ts | 111 +++++++++++++++++- 1 file changed, 109 insertions(+), 2 deletions(-) diff --git a/src/runtime-three/runtime-project-scheduler.ts b/src/runtime-three/runtime-project-scheduler.ts index aa1236ad..cc0f9e80 100644 --- a/src/runtime-three/runtime-project-scheduler.ts +++ b/src/runtime-three/runtime-project-scheduler.ts @@ -21,8 +21,11 @@ import { normalizeYawDegrees } from "../entities/entity-instances"; import { cloneProjectScheduler, compareProjectScheduleRoutinePriority, + getProjectScheduleRoutineDurationHours, getProjectScheduleRoutineElapsedHoursAt, + isProjectScheduleDaySelectionActive, isProjectScheduleRoutineActiveAt, + resolveProjectScheduleWeekday, type ProjectScheduler, type ProjectScheduleRoutine } from "../sequencer/project-sequencer"; @@ -413,6 +416,101 @@ function resolveActorScheduleRules( .sort(compareProjectScheduleRoutinePriority); } +function getAbsoluteScheduleHours(dayNumber: number, timeOfDayHours: number): number { + return (Math.floor(dayNumber) - 1) * 24 + timeOfDayHours; +} + +function resolveMostRecentCompletedActorPathState(options: { + actorRules: ActorScheduleRoutine[]; + sequences: ProjectSequenceLibrary; + actorId: string; + dayNumber: number; + timeOfDayHours: number; + pathsById?: ReadonlyMap; +}): { + pathEffect: FollowActorPathControlEffect; + resolvedPath: RuntimeResolvedActorPathState; +} | null { + const currentAbsoluteHours = getAbsoluteScheduleHours( + options.dayNumber, + options.timeOfDayHours + ); + let bestMatch: + | { + endAbsoluteHours: number; + routine: ActorScheduleRoutine; + pathEffect: FollowActorPathControlEffect; + } + | null = null; + + for (const routine of options.actorRules) { + const heldSteps = getProjectScheduleRoutineHeldSteps(routine, options.sequences); + const pathEffect = findHeldSequenceControlEffect(heldSteps, "followActorPath"); + + if (pathEffect === null) { + continue; + } + + const durationHours = getProjectScheduleRoutineDurationHours(routine); + + for (let dayOffset = 0; dayOffset <= 7; dayOffset += 1) { + const occurrenceStartDayNumber = options.dayNumber - dayOffset; + + if ( + !isProjectScheduleDaySelectionActive( + routine.days, + resolveProjectScheduleWeekday(occurrenceStartDayNumber) + ) + ) { + continue; + } + + const occurrenceEndAbsoluteHours = + getAbsoluteScheduleHours(occurrenceStartDayNumber, routine.startHour) + + durationHours; + + if (occurrenceEndAbsoluteHours > currentAbsoluteHours) { + continue; + } + + if ( + bestMatch !== null && + (bestMatch.endAbsoluteHours > occurrenceEndAbsoluteHours || + (bestMatch.endAbsoluteHours === occurrenceEndAbsoluteHours && + compareProjectScheduleRoutinePriority(bestMatch.routine, routine) <= 0)) + ) { + continue; + } + + bestMatch = { + endAbsoluteHours: occurrenceEndAbsoluteHours, + routine, + pathEffect + }; + break; + } + } + + if (bestMatch === null) { + return null; + } + + const resolvedPath = resolveActorSchedulePathState({ + effect: bestMatch.pathEffect, + elapsedHours: getProjectScheduleRoutineDurationHours(bestMatch.routine), + path: options.pathsById?.get(bestMatch.pathEffect.pathId) ?? null + }); + + if (resolvedPath === null) { + return null; + } + + return { + pathEffect: cloneControlEffect(bestMatch.pathEffect), + resolvedPath + }; +} + export function resolveRuntimeActorScheduleState(options: { scheduler: ProjectScheduler; sequences: ProjectSequenceLibrary; @@ -432,6 +530,15 @@ export function resolveRuntimeActorScheduleState(options: { ) ?? null; if (activeRoutine === null) { + const completedPathState = resolveMostRecentCompletedActorPathState({ + actorRules, + sequences: options.sequences, + actorId: options.actorId, + dayNumber: options.dayNumber, + timeOfDayHours: options.timeOfDayHours, + pathsById: options.pathsById + }); + return { actorId: options.actorId, hasRules: actorRules.length > 0, @@ -440,8 +547,8 @@ export function resolveRuntimeActorScheduleState(options: { activeRoutineTitle: null, presenceEffect: null, animationEffect: null, - pathEffect: null, - resolvedPath: null + pathEffect: completedPathState?.pathEffect ?? null, + resolvedPath: completedPathState?.resolvedPath ?? null }; }