/** * Turnkey day-night cycle: one serializable brain that advances a normalized day * fraction on an injected clock and blends per-keyframe phase labels and tint/light * colors. A game wires this one model and drives an existing sky/daylight seam from * `sample()` instead of hand-rolling a clock plus a color lerp. * * The model is genre-agnostic: `phase` labels and colors are free-form values the * game styles. The model never branches on what a phase string "means" — it only * interpolates between the keyframes you give it. */ /** One day-night keyframe: at a normalized day position, the phase label and target colors the game styles. */ export interface DayNightKeyframe { /** Normalized day position in [0,1) where this keyframe sits; the cycle wraps past 1 back to 0. */ at: number; /** Free-form phase label (e.g. `"dawn"`, `"day"`, `"dusk"`, `"night"`, or anything). The model never interprets it. */ phase: string; /** Primary tint/grade color at this keyframe as `"#rrggbb"`. */ color: string; /** Optional secondary color (e.g. sky zenith or key-light color). Falls back to `color` when sampled. */ lightColor?: string; /** Optional light level in [0,1] a presenter maps to intensity. Falls back to `1` when sampled. */ intensity?: number; } /** The interpolated day-night look at a moment: day fraction, active phase, and blended colors. */ export interface DayNightSample { /** Current normalized day fraction in [0,1). */ dayFraction: number; /** Phase label of the keyframe segment currently active (the free string the game styles). */ phase: string; /** Blended primary tint/grade color as `"#rrggbb"`. */ color: string; /** Blended secondary color as `"#rrggbb"` (equals `color` when no keyframe set `lightColor`). */ lightColor: string; /** Blended light level in [0,1]. */ intensity: number; } /** Serializable day-night position — accumulated clock offset, pause state, and speed — for save/load. */ export interface DayNightSnapshot { /** Accumulated game-time in ms since the cycle's zero, before wrapping into a day. */ elapsedMs: number; /** Whether the clock is paused. */ paused: boolean; /** Clock rate multiplier in effect. */ speed: number; } /** Options for {@link createDayNightCycle}. */ export interface DayNightCycleOptions { /** At least one keyframe; sorted and wrapped into [0,1) internally. Colors and phases are free-form. */ keyframes: readonly DayNightKeyframe[]; /** Real milliseconds for one full day at speed 1. Default `120000` (two minutes). */ dayLengthMs?: number; /** Normalized day fraction to start at, in [0,1). Default `0`. */ start?: number; /** Clock rate multiplier (`2` runs twice as fast). Default `1`. */ speed?: number; /** Start with the clock paused. Default `false`. */ startPaused?: boolean; /** Injected clock in ms so advancement stays deterministic under test. Default `Date.now`. */ now?: () => number; } /** A running, observable, serializable day-night cycle. A presenter renders `sample()`. */ export interface DayNightCycle { /** The configured keyframes, sorted by `at` and wrapped into [0,1). */ readonly keyframes: readonly DayNightKeyframe[]; /** Real milliseconds for one full day at speed 1. */ readonly dayLengthMs: number; /** Current normalized day fraction in [0,1), advanced from the injected clock. */ dayFraction(): number; /** The free-form phase label active at the current day fraction. */ phase(): string; /** The interpolated look at the current day fraction. */ sample(): DayNightSample; /** The interpolated look at an explicit day fraction, without touching the clock. */ sampleAt(fraction: number): DayNightSample; /** * Calendar adapter (`{ dayFraction }`) so the cycle drops straight into any * `{ calendar(): { dayFraction: number } }` sky/daylight seam that already * consumes a day fraction — no glue in the game. */ calendar(): { dayFraction: number; }; /** Jump the clock to a normalized day fraction (wrapped into [0,1)). */ setDayFraction(fraction: number): void; /** Whether the clock is paused. */ isPaused(): boolean; /** Pause advancement. */ pause(): void; /** Resume advancement. */ play(): void; /** Toggle pause/resume. */ toggle(): void; /** Current clock rate multiplier. */ speed(): number; /** Set the clock rate multiplier; `0` or negative pauses. */ setSpeed(multiplier: number): void; /** Observe control changes (pause/play/speed/setDayFraction/restore). Returns an unsubscribe fn. Continuous advance is read via `sample()`, not events. */ subscribe(listener: () => void): () => void; /** Serializable position for a save. */ snapshot(): DayNightSnapshot; /** Restore position from a {@link DayNightSnapshot}. */ restore(snapshot: DayNightSnapshot): void; } /** * Creates a turnkey day-night cycle: a serializable model that advances a normalized * day fraction on an injected clock and blends per-keyframe phase labels and tint/light * colors. Wire this one model, then drive an existing sky/daylight seam from `sample()` * (or drop it straight into a `{ calendar(): { dayFraction } }` seam via `calendar()`) * to get a moving day-night cycle with color grading — no hand-rolled clock or lerp. * `phase` labels and colors are free-form; the model never interprets their meaning. * * @capability day-night-cycle one wired model gives a moving day-night cycle — running clock plus keyframed phase labels and tint/light colors a game drives an existing sky/daylight seam from */ export declare function createDayNightCycle(options: DayNightCycleOptions): DayNightCycle;