5.0 KiB
Context
The existing @suaveplan/day-night package conflates gaming concerns (ECS components, gameplay modifiers, in-game clock) with pseudo-astronomy (hardcoded sinusoidal sun arc, 30-day moon cycle, no lat/lon). The astronomical computation layer is now provided by @suaveplan/universe and its provider plugins (solar-system, stars, tides). This design defines the two gaming-layer packages that consume that core.
Goals / Non-Goals
Goals:
- Wrap
UniverseClientwith a configurable N× game clock that drives scene queries at game time. - Emit semantically meaningful game events (sunrise, season change, eclipse) via
@suaveplan/events. - Replace hardcoded lighting presets with physically-derived sky colour and gameplay modifiers.
- Export plain TypeScript interfaces for ECS component shapes so consumers can wire into any ECS.
- Lock in
RenderPayloadas the canonical per-tick output for downstream render integrations. - Achieve 100% test coverage, Zod schemas for all public options, OTel instrumentation, and
AbortSignalon every async method.
Non-Goals:
- Astronomical calculations — delegated to
@suaveplan/universeand its providers. - R3F / Three.js rendering integration.
- Multiplayer synchronisation of game time across clients.
@suaveplan/weatherintegration (locked interface but no implementation).- Bundling texture assets — only identifiers pass through.
Decisions
1. Two-package split: clock vs lighting
gaming-universe owns time and events. gaming-environment-lighting owns sky colour and modifiers. This separation allows consumers to use the clock without the lighting system, or replace lighting with a custom renderer while keeping the clock.
Rationale: Single-responsibility. The clock is a scheduler concern; lighting is a render-data concern.
2. GamingUniverse wraps @suaveplan/timing's AsyncScheduler
Rather than inventing a scheduler, GamingUniverse applies a time-scale multiplier to timing's ticks.
Rationale: Uniform cancellation, clock injection, and AbortSignal drain with the rest of the monorepo. @suaveplan/game-loop becomes the natural tick integration point.
3. Plain TypeScript interfaces for ECS component shapes
gaming-environment-lighting exports plain interfaces (DayNightClock, LightState, DayNightAffected) rather than registering ECS archetypes.
Rationale: Framework-agnostic. Consumers using bitecs, miniplex, or custom ECS map fields to their own storage. No @suaveplan/ecs dependency.
4. RenderPayload as canonical render output
A single RenderPayload interface is published each tick containing sky colour, lighting, atmosphere, textures, and weather.
Rationale: Prevents churn when R3F/Three.js integrations land. Downstream renderers depend on a stable data shape, not on the lighting package's internals.
5. Feature-module directory layout — Pattern B
Every feature module uses a dedicated folder containing a main implementation file, a barrel index.ts with explicit named re-exports, sibling .md docs, and co-located tests.
Example:
src/lighting/
├── index.ts
├── lighting.ts
├── lighting.test.ts
└── lighting.md
The package.json exports map uses the repeated-name form (./lighting/lighting) pointing to the main implementation file, not to index.ts.
Rationale: The barrel is reviewable and explicit. The main file name makes stack traces and import paths self-describing. Tests and docs are co-located so they stay in sync.
6. Physically-derived everything
Sky colour uses Rayleigh/Mie scattering from AtmosphereConfig. Star visibility uses sun-altitude twilight thresholds. Moonlight uses phase, angular diameter, and albedo. Gameplay modifiers are continuous functions of totalAmbientLight.
Rationale: No hardcoded tables means the same code works for Earth, Mars, and game-defined planets without preset authoring.
Risks / Trade-offs
| Risk | Mitigation |
|---|---|
| Rayleigh/Mie model is simplified (no spectral sampling) | Documented as approximate. Consumers needing spectral accuracy can override skyColour in RenderPayload. |
| NPC sleep probability sigmoid may not match game design | Exposed as a configurable function in advanced options, with a sensible default. |
gaming-universe depends on @suaveplan/timing scheduler internals |
timing is a foundation package with stable API. If scheduler changes, gaming-universe is a known dependent. |
Two packages to migrate from one day-night |
Migration guide documents which day-night exports map to which new package. |
Migration Plan
- Land
gaming-universeafteruniverseis complete. - Land
gaming-environment-lightingaftergaming-universeis complete. - Update
STATUS.mdandSUMMARY.mdas each package completes. - Archive
@suaveplan/day-nightaftergaming-environment-lightingships. - Publish migration guide mapping
day-nightexports to new package exports.
Open Questions
- None — all deferred items are explicitly scoped out with locked interfaces.