/** One scheduled beat of a cutscene: a typed cue that fires when the playhead reaches `atMs`. */ export interface SequenceCue { /** Playhead time (ms from the start of the timeline) at which this cue fires. Must be `>= 0`. */ atMs: number; /** * Free-string tag naming what this cue is — `"camera"`, `"dialogue"`, `"fade"`, anything. * The director never interprets it; the game switches on `kind` to act on the {@link SequenceCue.payload}. */ kind: string; /** Arbitrary serializable data the game reads when the cue fires. Opaque to the director. */ payload?: Payload; /** Optional stable id, surfaced on emission and useful for logging/debugging. */ id?: string; } /** A cue plus the resolved firing context passed to {@link SequenceDirector.onCue} listeners. */ export interface EmittedCue { /** The cue that fired. */ cue: SequenceCue; /** Its index in the ordered cue list. */ index: number; /** Playhead (ms) at the moment it was emitted — `>= cue.atMs`, possibly well past on a large jump. */ atPlayheadMs: number; } /** A read-only view of the director's playback state, returned by {@link SequenceDirector.state}. */ export interface SequenceState { /** Current playhead position in ms from the start of the timeline. */ playheadMs: number; /** Total timeline length — the largest cue `atMs` (0 when empty). */ durationMs: number; /** Whether the clock is currently advancing the playhead. */ playing: boolean; /** True once the playhead has reached the end (every cue has fired). */ done: boolean; /** Fraction of the timeline elapsed, in `[0, 1]`; `1` when `durationMs` is 0. */ progress: number; /** How many cues have fired so far (a prefix of the ordered list). */ firedCount: number; /** Total number of cues. */ total: number; } /** Serializable snapshot — enough to resume a cutscene exactly where a save left it. */ export interface SequenceSnapshot { /** Playhead position in ms. */ playheadMs: number; /** Index of the next cue to fire; cues before it are considered already fired. */ nextIndex: number; /** Whether playback was running when captured. */ playing: boolean; } /** Options for {@link createSequenceDirector}. */ export interface SequenceDirectorOptions { /** The timeline: cues in any order (sorted by `atMs` internally, stably). */ cues: readonly SequenceCue[]; /** Injected clock (ms) that drives playback. Default `Date.now`; inject a fake for deterministic tests. */ now?: () => number; /** Start playing immediately (default `false` — call {@link SequenceDirector.play}). */ autoplay?: boolean; } /** A cue listener, called once per cue as it fires. Returns nothing. */ export type CueListener = (emitted: EmittedCue) => void; /** * A data-driven cutscene: an ordered timeline of typed cues that fire on a single * injected clock. See {@link createSequenceDirector}. */ export interface SequenceDirector { /** The cues, sorted by `atMs`. */ readonly cues: readonly SequenceCue[]; /** Start (or resume) playback from the current playhead. No-op if already playing or done. */ play(): void; /** Freeze the playhead, capturing elapsed clock time up to now. No-op if not playing. */ pause(): void; /** * Jump the playhead to `ms` (clamped to `[0, durationMs]`). Moving forward fires every * un-fired cue up to `ms` in order (once each, even across a large jump); moving backward * re-arms cues now in the future without emitting them. */ seek(ms: number): void; /** Fast-forward to the end: emit every remaining cue in order, then stop and mark done. */ skip(): void; /** Halt and rewind: pause, reset the playhead to 0, and re-arm every cue. */ stop(): void; /** * Advance the playhead by the elapsed injected-clock time since the last tick/resume and * emit any cues now due. A host calls this once per frame while playing; a no-op when paused. */ tick(): void; /** Current playback state (playhead, duration, playing/done, progress). */ state(): SequenceState; /** Register a cue listener; returns an unsubscribe fn. */ onCue(listener: CueListener): () => void; /** Observe state changes (play/pause/seek/skip/stop/tick/restore); returns an unsubscribe fn. */ subscribe(listener: () => void): () => void; /** Serializable state for a save. */ snapshot(): SequenceSnapshot; /** Restore from a {@link SequenceSnapshot}; does not re-emit already-fired cues. */ restore(snapshot: SequenceSnapshot): void; } /** * A serializable cutscene / sequence director: an ordered timeline of typed cues * (`{ atMs, kind, payload }`) advanced by one injected clock, firing each cue once * and in order as its time passes — even across a large seek — with play/pause/ * seek/skip/stop controls. The director only *schedules and emits* cues; it never * interprets what a `kind` means, so the same primitive drives camera moves, * dialogue lines, fades, or any game event. `snapshot`/`restore` round-trip the * playhead and which cues have fired. Deterministic (no wall clock beyond the * injected `now`) and allocation-free on the tick path. * * @capability sequence-director data-driven cutscene timeline — fire typed camera/dialogue/event cues on one injected clock with play/pause/seek/skip and snapshot/restore */ export declare function createSequenceDirector(options: SequenceDirectorOptions): SequenceDirector;