4.2 KiB
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
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<Omit<TimeModifier, 'label' | 'fromHour' | 'toHour'>>
}
Error codes
type DayNightErrorCode =
| 'DAY_NIGHT_INVALID_HOUR'
| 'DAY_NIGHT_SCHEDULE_EMPTY'
| 'DAY_NIGHT_KEYFRAME_GAP'
File layout
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.