# OpenSpec Change: enterprise-gaming-day-night - **Change ID**: enterprise-gaming-day-night - **Package**: `@suaveplan/day-night` - **Category**: gaming/world - **Target path**: `gaming/world/day-night` - **Status**: PROPOSED - **Date**: 2026-04-24 - **Reference spec**: `/mnt/s/web/suaveplan/enterprise/day-night.md` --- ## Summary Deliver `@suaveplan/day-night` on the genesis branch from scratch at enterprise quality. The package provides a day/night cycle with configurable time scale, sun/moon position math, light schedule interpolation, and gameplay effect modifiers. `DayNightClock` advances in-game time at a configurable multiple of real time; `SkyPositionCalculator` computes sun and moon azimuth/elevation from in-game time using a simplified celestial model; `LightSchedule` keyframes ambient light color and intensity across the day; `GameplayModifiers` maps time-of-day segments to gameplay effect multipliers (spawn rate, visibility, economy). Genesis adds Zod schemas, structured errors, OTel time counter, injectable clock, and 100% coverage. --- ## Current-state audit (genesis) Package does not exist on genesis yet — fresh build. --- ## Available workspace deps on genesis | Package | Role | |---|---| | `@suaveplan/error` (`workspace:^`) | `DayNightError`; `DAY_NIGHT.*` codes | | `@suaveplan/types` (`workspace:^`) | `Clock` | | `@suaveplan/events` (`workspace:^`) | `time:dawn`, `time:noon`, `time:dusk`, `time:midnight` events | | `zod` (`catalog:forms`) | `LightSchedule`, `GameplayModifiers` schemas | | `@opentelemetry/api` (`catalog:observability`) | In-game time gauge | --- ## Public API surface ```ts function createDayNightClock(options: DayNightClockOptions): DayNightClock interface DayNightClockOptions { timeScale?: number // real seconds per in-game minute, default 1 min = 1 sec initialHour?: number // 0–24 clock?: Clock } interface DayNightClock { tick(dt: number): void readonly hour: number // 0–24 fractional readonly dayCount: number setHour(hour: number): void pause(): void resume(): void readonly isPaused: boolean } interface SkyPosition { azimuth: number // degrees elevation: number // degrees (-90 to 90) intensity: number // 0–1 } function computeSunPosition(hour: number): SkyPosition function computeMoonPosition(hour: number): SkyPosition // Light schedule interface LightKeyframe { hour: number ambientColor: [r: number, g: number, b: number] ambientIntensity: number fogDensity?: number } function createLightSchedule(keyframes: LightKeyframe[]): LightSchedule interface LightSchedule { evaluate(hour: number): LightKeyframe } // Gameplay modifiers interface TimeModifier { fromHour: number toHour: number spawnRateMultiplier?: number visibilityMultiplier?: number economyMultiplier?: number label?: string } function createGameplayModifiers(modifiers: TimeModifier[]): GameplayModifiers interface GameplayModifiers { evaluate(hour: number): TimeModifier & Required> } ``` ### Error codes ```ts type DayNightErrorCode = | 'DAY_NIGHT_INVALID_HOUR' | 'DAY_NIGHT_SCHEDULE_EMPTY' | 'DAY_NIGHT_KEYFRAME_GAP' ``` --- ## File layout ```text gaming/world/day-night/ package.json tsconfig.json vitest.config.ts src/ index.ts error-codes.ts constants/index.ts types/common.ts types/common.md core/ day-night-clock/ day-night-clock.ts day-night-clock.md day-night-clock.types.ts day-night-clock.test.ts index.ts light-schedule/ light-schedule.ts light-schedule.md light-schedule.types.ts light-schedule.test.ts index.ts gameplay-modifiers/ gameplay-modifiers.ts gameplay-modifiers.md gameplay-modifiers.types.ts gameplay-modifiers.test.ts index.ts utils/ sky-position.ts sky-position.md sky-position.test.ts tests/ integration.test.ts ``` --- ## Impact **Downstream**: `@suaveplan/r3f-environment` uses sun/moon positions for directional light; `@suaveplan/weather` reads time for weather transitions. Prerequisites: `@suaveplan/error`, `@suaveplan/types`, `@suaveplan/events`.