152 lines
4.2 KiB
Markdown
152 lines
4.2 KiB
Markdown
# 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<Omit<TimeModifier, 'label' | 'fromHour' | 'toHour'>>
|
||
}
|
||
```
|
||
|
||
### 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`.
|