diff --git a/src/app/ProjectSequencesPanel.tsx b/src/app/ProjectSequencesPanel.tsx deleted file mode 100644 index 8b96e427..00000000 --- a/src/app/ProjectSequencesPanel.tsx +++ /dev/null @@ -1,1007 +0,0 @@ -import { - type KeyboardEvent as ReactKeyboardEvent, - type PointerEvent as ReactPointerEvent, - useEffect, - useMemo, - useRef, - useState -} from "react"; - -import { formatControlEffectValue, getControlTargetRefKey } from "../controls/control-surface"; -import { type ProjectDialogueLibrary, getProjectDialogues } from "../dialogues/project-dialogues"; -import { - getProjectScheduleEffectOptionId, - getProjectScheduleTargetOptionByKey, - listProjectScheduleEffectOptions, - type ProjectScheduleEffectOptionId, - type ProjectScheduleTargetOption -} from "../scheduler/project-schedule-control-options"; -import { - getProjectSequenceHeldSteps, - getProjectSequenceImpulseSteps, - getSequenceClipEndMinute, - getSequenceClipLabel, - type SequenceClip -} from "../sequencer/project-sequence-steps"; -import { - getProjectSequences, - type ProjectSequenceLibrary -} from "../sequencer/project-sequences"; - -interface ProjectSequencesPanelProps { - sequences: ProjectSequenceLibrary; - dialogues: ProjectDialogueLibrary; - targetOptions: ProjectScheduleTargetOption[]; - selectedSequenceId: string | null; - onSelectSequence(sequenceId: string | null): void; - onAddSequence(): void; - onDeleteSequence(sequenceId: string): void; - onSetSequenceTitle(sequenceId: string, title: string): void; - onSetSequenceDurationMinutes(sequenceId: string, durationMinutes: number): void; - onAddHeldControlStep(sequenceId: string, targetKey: string): void; - onAddImpulseControlStep(sequenceId: string, targetKey: string): void; - onAddDialogueStep(sequenceId: string, dialogueId: string): void; - onDeleteStep(sequenceId: string, stepIndex: number): void; - onSetClipTiming( - sequenceId: string, - stepIndex: number, - timing: { - startMinute: number; - durationMinutes: number; - lane: number; - } - ): void; - onSetControlStepTarget( - sequenceId: string, - stepIndex: number, - targetKey: string - ): void; - onSetControlStepEffectOption( - sequenceId: string, - stepIndex: number, - effectOptionId: ProjectScheduleEffectOptionId - ): void; - onSetControlStepNumericValue( - sequenceId: string, - stepIndex: number, - value: number - ): void; - onSetControlStepColorValue( - sequenceId: string, - stepIndex: number, - colorHex: string - ): void; - onSetControlStepAnimationClip( - sequenceId: string, - stepIndex: number, - clipName: string - ): void; - onSetControlStepAnimationLoop( - sequenceId: string, - stepIndex: number, - loop: boolean - ): void; - onSetDialogueStepDialogueId( - sequenceId: string, - stepIndex: number, - dialogueId: string - ): void; -} - -const SEQUENCE_TIMELINE_LANE_HEIGHT = 52; -const MIN_SEQUENCE_TIMELINE_LANES = 3; - -type SequenceClipTimingDraft = Pick< - SequenceClip, - "startMinute" | "durationMinutes" | "lane" ->; - -interface SequenceClipDragState { - sequenceId: string; - clipIndex: number; - mode: "move" | "resize-start" | "resize-end"; - pointerId: number; - originX: number; - originY: number; - containerWidth: number; - sequenceDurationMinutes: number; - initial: SequenceClipTimingDraft; - preview: SequenceClipTimingDraft; -} - -function commitOnEnter( - event: ReactKeyboardEvent, - commit: () => void -) { - if (event.key !== "Enter") { - return; - } - - event.currentTarget.blur(); - commit(); -} - -function clampInteger(value: number, min: number, max: number): number { - if (!Number.isFinite(value)) { - return min; - } - - return Math.min(max, Math.max(min, Math.trunc(value))); -} - -function formatSequenceMinuteValue(minutes: number): string { - const normalized = Math.max(0, Math.trunc(minutes)); - const hours = Math.floor(normalized / 60); - const remainderMinutes = normalized % 60; - - return `${String(hours).padStart(2, "0")}:${String(remainderMinutes).padStart(2, "0")}`; -} - -function getControlClipNumericValue( - clip: Extract -): number | null { - switch (clip.effect.type) { - case "setSoundVolume": - return clip.effect.volume; - case "setLightIntensity": - case "setAmbientLightIntensity": - case "setSunLightIntensity": - return clip.effect.intensity; - default: - return null; - } -} - -function getControlClipColorValue( - clip: Extract -): string | null { - switch (clip.effect.type) { - case "setLightColor": - case "setAmbientLightColor": - case "setSunLightColor": - return clip.effect.colorHex; - default: - return null; - } -} - -function getSequenceTimelineClipClassName( - clip: SequenceClip, - selected: boolean -): string { - return [ - "sequence-timeline__clip", - clip.stepClass === "held" - ? "sequence-timeline__clip--held" - : "sequence-timeline__clip--impulse", - selected ? "sequence-timeline__clip--selected" : "" - ] - .filter((value) => value.length > 0) - .join(" "); -} - -export function ProjectSequencesPanel({ - sequences, - dialogues, - targetOptions, - selectedSequenceId, - onSelectSequence, - onAddSequence, - onDeleteSequence, - onSetSequenceTitle, - onSetSequenceDurationMinutes, - onAddHeldControlStep, - onAddImpulseControlStep, - onAddDialogueStep, - onDeleteStep, - onSetClipTiming, - onSetControlStepTarget, - onSetControlStepEffectOption, - onSetControlStepNumericValue, - onSetControlStepColorValue, - onSetControlStepAnimationClip, - onSetControlStepAnimationLoop, - onSetDialogueStepDialogueId -}: ProjectSequencesPanelProps) { - const sequenceList = getProjectSequences(sequences); - const dialogueList = getProjectDialogues(dialogues); - const selectedSequence = - selectedSequenceId === null - ? null - : sequences.sequences[selectedSequenceId] ?? null; - const [selectedClipIndex, setSelectedClipIndex] = useState(null); - const [dragState, setDragState] = useState(null); - const timelineRef = useRef(null); - - useEffect(() => { - if (selectedSequence === null || selectedSequence.clips.length === 0) { - setSelectedClipIndex(null); - return; - } - - setSelectedClipIndex((current) => - current !== null && selectedSequence.clips[current] !== undefined ? current : 0 - ); - }, [selectedSequence]); - - useEffect(() => { - if (dragState === null) { - return; - } - - const handlePointerMove = (event: PointerEvent) => { - if (event.pointerId !== dragState.pointerId) { - return; - } - - const deltaMinutes = Math.round( - ((event.clientX - dragState.originX) / Math.max(1, dragState.containerWidth)) * - dragState.sequenceDurationMinutes - ); - const deltaLane = Math.round( - (event.clientY - dragState.originY) / SEQUENCE_TIMELINE_LANE_HEIGHT - ); - - setDragState((current) => { - if (current === null) { - return current; - } - - if (current.mode === "move") { - const startMinute = clampInteger( - current.initial.startMinute + deltaMinutes, - 0, - Math.max(0, current.sequenceDurationMinutes - current.initial.durationMinutes) - ); - - return { - ...current, - preview: { - startMinute, - durationMinutes: current.initial.durationMinutes, - lane: Math.max(0, current.initial.lane + deltaLane) - } - }; - } - - if (current.mode === "resize-start") { - const startMinute = clampInteger( - current.initial.startMinute + deltaMinutes, - 0, - current.initial.startMinute + current.initial.durationMinutes - 1 - ); - - return { - ...current, - preview: { - startMinute, - durationMinutes: - current.initial.durationMinutes + - current.initial.startMinute - - startMinute, - lane: current.initial.lane - } - }; - } - - return { - ...current, - preview: { - startMinute: current.initial.startMinute, - durationMinutes: clampInteger( - current.initial.durationMinutes + deltaMinutes, - 1, - current.sequenceDurationMinutes - current.initial.startMinute - ), - lane: current.initial.lane - } - }; - }); - }; - - const handlePointerFinish = (event: PointerEvent) => { - if (event.pointerId !== dragState.pointerId) { - return; - } - - const changed = - dragState.preview.startMinute !== dragState.initial.startMinute || - dragState.preview.durationMinutes !== dragState.initial.durationMinutes || - dragState.preview.lane !== dragState.initial.lane; - - if (changed) { - onSetClipTiming(dragState.sequenceId, dragState.clipIndex, dragState.preview); - } - - setDragState(null); - }; - - window.addEventListener("pointermove", handlePointerMove); - window.addEventListener("pointerup", handlePointerFinish); - window.addEventListener("pointercancel", handlePointerFinish); - - return () => { - window.removeEventListener("pointermove", handlePointerMove); - window.removeEventListener("pointerup", handlePointerFinish); - window.removeEventListener("pointercancel", handlePointerFinish); - }; - }, [dragState, onSetClipTiming]); - - const displayedClips = useMemo(() => { - if (selectedSequence === null) { - return []; - } - - return selectedSequence.clips.map((clip, clipIndex) => - dragState !== null && - dragState.sequenceId === selectedSequence.id && - dragState.clipIndex === clipIndex - ? { - ...clip, - ...dragState.preview - } - : clip - ); - }, [dragState, selectedSequence]); - - const selectedClip = - selectedClipIndex === null ? null : displayedClips[selectedClipIndex] ?? null; - const selectedControlClip = - selectedClip?.type === "controlEffect" ? selectedClip : null; - const selectedDialogueClip = - selectedClip?.type === "startDialogue" ? selectedClip : null; - const targetKey = - selectedControlClip === null - ? null - : getControlTargetRefKey(selectedControlClip.effect.target); - const targetOption = - targetKey === null - ? null - : getProjectScheduleTargetOptionByKey(targetOptions, targetKey); - const effectOptions = - targetOption === null ? [] : listProjectScheduleEffectOptions(targetOption); - const effectOptionId = - selectedControlClip === null || targetOption === null - ? null - : (() => { - try { - return getProjectScheduleEffectOptionId(selectedControlClip.effect); - } catch { - return null; - } - })(); - const laneCount = - displayedClips.length === 0 - ? MIN_SEQUENCE_TIMELINE_LANES - : Math.max( - MIN_SEQUENCE_TIMELINE_LANES, - ...displayedClips.map((clip) => clip.lane + 1) - ); - const hourMarkers = - selectedSequence === null - ? [] - : Array.from( - { length: Math.floor(selectedSequence.durationMinutes / 60) + 1 }, - (_, index) => index * 60 - ).filter((minute) => minute <= selectedSequence.durationMinutes); - const quarterHourMarkers = - selectedSequence === null - ? [] - : Array.from( - { length: Math.floor(selectedSequence.durationMinutes / 15) + 1 }, - (_, index) => index * 15 - ).filter( - (minute) => - minute > 0 && - minute < selectedSequence.durationMinutes && - minute % 60 !== 0 - ); - const defaultTargetKey = - targetKey ?? targetOptions[0]?.key ?? ""; - - const beginClipDrag = ( - clipIndex: number, - mode: SequenceClipDragState["mode"], - event: ReactPointerEvent - ) => { - if (selectedSequence === null) { - return; - } - - const clip = selectedSequence.clips[clipIndex]; - - if (clip === undefined || timelineRef.current === null) { - return; - } - - const rect = timelineRef.current.getBoundingClientRect(); - - if (rect.width <= 0) { - return; - } - - event.preventDefault(); - event.stopPropagation(); - setSelectedClipIndex(clipIndex); - setDragState({ - sequenceId: selectedSequence.id, - clipIndex, - mode, - pointerId: event.pointerId, - originX: event.clientX, - originY: event.clientY, - containerWidth: rect.width, - sequenceDurationMinutes: selectedSequence.durationMinutes, - initial: { - startMinute: clip.startMinute, - durationMinutes: clip.durationMinutes, - lane: clip.lane - }, - preview: { - startMinute: clip.startMinute, - durationMinutes: clip.durationMinutes, - lane: clip.lane - } - }); - }; - - return ( -
-
Sequences
- {sequenceList.length === 0 ? ( -
No project sequences authored yet.
- ) : ( -
- {sequenceList.map((sequence) => ( -
-
- - -
-
- ))} -
- )} - -
- -
- - {selectedSequence === null ? ( -
- Select a sequence to edit its timeline and clips. -
- ) : ( -
-
- Build a sequence from timed clips. Held clips stay active over their - clip span; impulse clips mark one-shot events on the same local - timeline. Multiple clips can overlap on separate lanes. -
- -
- - -
- -
Clip Timeline
-
-
- Drag clips horizontally to move them in time, drag vertically to - move them between lanes, and drag the handles to resize their - length. Snapping is minute-precise. -
-
- {hourMarkers.map((minute) => ( -
- {formatSequenceMinuteValue(minute)} -
- ))} -
-
- {Array.from({ length: laneCount }, (_, lane) => ( -
- - Lane {lane + 1} - -
- ))} - {quarterHourMarkers.map((minute) => ( -
- ))} - {hourMarkers.map((minute) => ( -
- ))} - - {displayedClips.map((clip, clipIndex) => ( -
setSelectedClipIndex(clipIndex)} - onPointerDown={(event) => beginClipDrag(clipIndex, "move", event)} - > - - beginClipDrag(clipIndex, "resize-start", event) - } - /> - - {getSequenceClipLabel(clip)} - - - {formatSequenceMinuteValue(clip.startMinute)} -{" "} - {formatSequenceMinuteValue(getSequenceClipEndMinute(clip))} - - - beginClipDrag(clipIndex, "resize-end", event) - } - /> -
- ))} -
-
- -
- - - -
- - {selectedClip === null || selectedClipIndex === null ? ( -
- Select a clip in the timeline to edit its target, timing, and - payload. -
- ) : ( -
-
-
Selected Clip
- -
-
{getSequenceClipLabel(selectedClip)}
- -
- - - -
- - {selectedControlClip !== null ? ( - <> - {targetOption === null || effectOptionId === null ? ( -
- {formatControlEffectValue(selectedControlClip.effect)}. This - control clip is preserved, but the current editor can only - edit targets and effects that are exposed through the current - control catalog. -
- ) : ( - <> -
- - -
- - - {effectOptions.find((option) => option.id === effectOptionId) - ?.valueKind === "number" ? ( - - ) : null} - - {effectOptions.find((option) => option.id === effectOptionId) - ?.valueKind === "color" ? ( - - ) : null} - - {selectedControlClip.effect.type === "playModelAnimation" ? ( - <> - - - - ) : null} - - )} - - ) : null} - - {selectedDialogueClip !== null ? ( - - ) : null} - - {selectedClip.type === "teleportPlayer" || - selectedClip.type === "toggleVisibility" ? ( -
- This impulse clip is preserved, but the current editor only - exposes direct payload editing for dialogue and control clips. -
- ) : null} -
- )} -
- )} -
- ); -} diff --git a/src/document/migrate-scene-document.ts b/src/document/migrate-scene-document.ts index 25dc364b..6177874f 100644 --- a/src/document/migrate-scene-document.ts +++ b/src/document/migrate-scene-document.ts @@ -157,6 +157,7 @@ import { NPC_DIALOGUE_REFERENCE_SCENE_DOCUMENT_VERSION, PROJECT_DIALOGUE_LIBRARY_SCENE_DOCUMENT_VERSION, PROJECT_SEQUENCE_LIBRARY_SCENE_DOCUMENT_VERSION, + PROJECT_SEQUENCE_TIMING_SCENE_DOCUMENT_VERSION, RUNNER_V1_SCENE_DOCUMENT_VERSION, SCENE_TRANSITION_ENTITIES_SCENE_DOCUMENT_VERSION, SPATIAL_AUDIO_SCENE_DOCUMENT_VERSION, @@ -198,8 +199,6 @@ import { type ProjectSequenceLibrary } from "../sequencer/project-sequences"; import { - DEFAULT_IMPULSE_SEQUENCE_CLIP_DURATION_MINUTES, - LEGACY_PROJECT_SEQUENCE_DURATION_MINUTES, type SequenceClip } from "../sequencer/project-sequence-steps"; import { @@ -3521,7 +3520,7 @@ function readProjectDialogueLibrary( }; } -function readProjectSequenceClip(value: unknown, label: string): SequenceClip { +function readProjectSequenceEffect(value: unknown, label: string): SequenceClip { if (!isRecord(value)) { throw new Error(`${label} must be an object.`); } @@ -3533,35 +3532,9 @@ function readProjectSequenceClip(value: unknown, label: string): SequenceClip { throw new Error(`${label}.stepClass must be held or impulse.`); } - const startMinute = - value.startMinute === undefined - ? 0 - : Math.trunc(expectNonNegativeFiniteNumber(value.startMinute, `${label}.startMinute`)); - const durationMinutes = - value.durationMinutes === undefined - ? stepClass === "held" - ? LEGACY_PROJECT_SEQUENCE_DURATION_MINUTES - : DEFAULT_IMPULSE_SEQUENCE_CLIP_DURATION_MINUTES - : Math.max( - 1, - Math.trunc( - expectPositiveFiniteNumber( - value.durationMinutes, - `${label}.durationMinutes` - ) - ) - ); - const lane = - value.lane === undefined - ? 0 - : Math.trunc(expectNonNegativeFiniteNumber(value.lane, `${label}.lane`)); - switch (type) { case "controlEffect": return { - startMinute, - durationMinutes, - lane, stepClass, type: "controlEffect", effect: readControlEffect(value.effect, `${label}.effect`) @@ -3572,9 +3545,6 @@ function readProjectSequenceClip(value: unknown, label: string): SequenceClip { } return { - startMinute, - durationMinutes, - lane, stepClass: "impulse", type: "startDialogue", dialogueId: expectString(value.dialogueId, `${label}.dialogueId`) @@ -3585,9 +3555,6 @@ function readProjectSequenceClip(value: unknown, label: string): SequenceClip { } return { - startMinute, - durationMinutes, - lane, stepClass: "impulse", type: "teleportPlayer", targetEntityId: expectString( @@ -3601,9 +3568,6 @@ function readProjectSequenceClip(value: unknown, label: string): SequenceClip { } return { - startMinute, - durationMinutes, - lane, stepClass: "impulse", type: "toggleVisibility", targetBrushId: expectString(value.targetBrushId, `${label}.targetBrushId`), @@ -3613,7 +3577,7 @@ function readProjectSequenceClip(value: unknown, label: string): SequenceClip { : expectBoolean(value.visible, `${label}.visible`) }; default: - throw new Error(`${label}.type must be a supported sequence clip.`); + throw new Error(`${label}.type must be a supported sequence effect.`); } } @@ -3645,13 +3609,15 @@ function readProjectSequenceLibrary( throw new Error(`${label}.sequences.${sequenceKey} must be an object.`); } - const clipsValue = Array.isArray(sequenceValue.clips) - ? sequenceValue.clips - : sequenceValue.steps; + const effectsValue = Array.isArray(sequenceValue.effects) + ? sequenceValue.effects + : Array.isArray(sequenceValue.clips) + ? sequenceValue.clips + : sequenceValue.steps; - if (!Array.isArray(clipsValue)) { + if (!Array.isArray(effectsValue)) { throw new Error( - `${label}.sequences.${sequenceKey}.clips must be an array.` + `${label}.sequences.${sequenceKey}.effects must be an array.` ); } @@ -3661,22 +3627,10 @@ function readProjectSequenceLibrary( sequenceValue.title, `${label}.sequences.${sequenceKey}.title` ), - durationMinutes: - sequenceValue.durationMinutes === undefined - ? undefined - : Math.max( - 1, - Math.trunc( - expectPositiveFiniteNumber( - sequenceValue.durationMinutes, - `${label}.sequences.${sequenceKey}.durationMinutes` - ) - ) - ), - clips: clipsValue.map((clipValue, clipIndex) => - readProjectSequenceClip( - clipValue, - `${label}.sequences.${sequenceKey}.clips.${clipIndex}` + effects: effectsValue.map((effectValue, effectIndex) => + readProjectSequenceEffect( + effectValue, + `${label}.sequences.${sequenceKey}.effects.${effectIndex}` ) ) }); @@ -4497,7 +4451,8 @@ export function migrateSceneDocument(source: unknown): SceneDocument { source.version !== NPC_DIALOGUE_REFERENCE_SCENE_DOCUMENT_VERSION && source.version !== PROJECT_DIALOGUE_LIBRARY_SCENE_DOCUMENT_VERSION && source.version !== PLAYER_START_PAUSE_BINDINGS_SCENE_DOCUMENT_VERSION && - source.version !== PROJECT_SEQUENCE_LIBRARY_SCENE_DOCUMENT_VERSION + source.version !== PROJECT_SEQUENCE_LIBRARY_SCENE_DOCUMENT_VERSION && + source.version !== PROJECT_SEQUENCE_TIMING_SCENE_DOCUMENT_VERSION ) { throw new Error( `Unsupported scene document version: ${String(source.version)}.` diff --git a/src/document/scene-document-validation.ts b/src/document/scene-document-validation.ts index 9a2aceec..515a9c1f 100644 --- a/src/document/scene-document-validation.ts +++ b/src/document/scene-document-validation.ts @@ -88,8 +88,7 @@ import type { import { getHeldSequenceControlEffects, getProjectScheduleRoutineHeldSteps, - getProjectSequenceImpulseSteps, - getSequenceClipEndMinute + getProjectSequenceImpulseSteps } from "../sequencer/project-sequence-steps"; export type SceneDiagnosticSeverity = "error" | "warning"; @@ -4806,146 +4805,40 @@ function validateProjectSequence( ); } - if (sequence.clips.length === 0) { + if (sequence.effects.length === 0) { diagnostics.push( createDiagnostic( "error", - "invalid-project-sequence-clips-empty", - "Project sequences must contain at least one clip.", - `${path}.clips` + "invalid-project-sequence-effects-empty", + "Project sequences must contain at least one effect.", + `${path}.effects` ) ); return; } - if (!isFiniteNumber(sequence.durationMinutes)) { - diagnostics.push( - createDiagnostic( - "error", - "invalid-project-sequence-duration", - "Project sequence duration must be a finite positive number of minutes.", - `${path}.durationMinutes` - ) - ); - } else if ( - sequence.durationMinutes < 1 || - sequence.durationMinutes > HOURS_PER_DAY * 60 || - Math.trunc(sequence.durationMinutes) !== sequence.durationMinutes - ) { - diagnostics.push( - createDiagnostic( - "error", - "invalid-project-sequence-duration-range", - "Project sequence duration must be an integer minute value between 1 and 1440.", - `${path}.durationMinutes` - ) - ); - } + for (const [effectIndex, effect] of sequence.effects.entries()) { + const effectPath = `${path}.effects.${effectIndex}`; - for (const [clipIndex, clip] of sequence.clips.entries()) { - const clipPath = `${path}.clips.${clipIndex}`; - - if (!isFiniteNumber(clip.startMinute)) { - diagnostics.push( - createDiagnostic( - "error", - "invalid-project-sequence-clip-start", - "Sequence clip start must be a finite non-negative integer minute value.", - `${clipPath}.startMinute` - ) - ); - } else if ( - clip.startMinute < 0 || - Math.trunc(clip.startMinute) !== clip.startMinute - ) { - diagnostics.push( - createDiagnostic( - "error", - "invalid-project-sequence-clip-start-range", - "Sequence clip start must be a non-negative integer minute value.", - `${clipPath}.startMinute` - ) - ); - } - - if (!isFiniteNumber(clip.durationMinutes)) { - diagnostics.push( - createDiagnostic( - "error", - "invalid-project-sequence-clip-duration", - "Sequence clip duration must be a finite positive integer minute value.", - `${clipPath}.durationMinutes` - ) - ); - } else if ( - clip.durationMinutes < 1 || - Math.trunc(clip.durationMinutes) !== clip.durationMinutes - ) { - diagnostics.push( - createDiagnostic( - "error", - "invalid-project-sequence-clip-duration-range", - "Sequence clip duration must be a positive integer minute value.", - `${clipPath}.durationMinutes` - ) - ); - } - - if (!isFiniteNumber(clip.lane)) { - diagnostics.push( - createDiagnostic( - "error", - "invalid-project-sequence-clip-lane", - "Sequence clip lane must be a finite non-negative integer value.", - `${clipPath}.lane` - ) - ); - } else if (clip.lane < 0 || Math.trunc(clip.lane) !== clip.lane) { - diagnostics.push( - createDiagnostic( - "error", - "invalid-project-sequence-clip-lane-range", - "Sequence clip lane must be a non-negative integer value.", - `${clipPath}.lane` - ) - ); - } - - if ( - isFiniteNumber(sequence.durationMinutes) && - isFiniteNumber(clip.startMinute) && - isFiniteNumber(clip.durationMinutes) && - getSequenceClipEndMinute(clip) > sequence.durationMinutes - ) { - diagnostics.push( - createDiagnostic( - "error", - "invalid-project-sequence-clip-overflow", - "Sequence clips must fit within the authored sequence duration.", - `${clipPath}.durationMinutes` - ) - ); - } - - switch (clip.type) { + switch (effect.type) { case "controlEffect": validateProjectSchedulerEffect( - clip.effect, - `${clipPath}.effect`, + effect.effect, + `${effectPath}.effect`, context, diagnostics ); break; case "startDialogue": if ( - projectResources.dialogues.dialogues[clip.dialogueId] === undefined + projectResources.dialogues.dialogues[effect.dialogueId] === undefined ) { diagnostics.push( createDiagnostic( "error", "missing-sequence-dialogue-resource", - `Dialogue ${clip.dialogueId} does not exist in the project dialogue library.`, - `${clipPath}.dialogueId` + `Dialogue ${effect.dialogueId} does not exist in the project dialogue library.`, + `${effectPath}.dialogueId` ) ); }