Files
suaveplan/openspec/changes/enterprise-gaming-universe/design.md
2026-05-27 23:29:53 +01:00

5.0 KiB
Raw Blame History

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 UniverseClient with 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 RenderPayload as the canonical per-tick output for downstream render integrations.
  • Achieve 100% test coverage, Zod schemas for all public options, OTel instrumentation, and AbortSignal on every async method.

Non-Goals:

  • Astronomical calculations — delegated to @suaveplan/universe and its providers.
  • R3F / Three.js rendering integration.
  • Multiplayer synchronisation of game time across clients.
  • @suaveplan/weather integration (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

  1. Land gaming-universe after universe is complete.
  2. Land gaming-environment-lighting after gaming-universe is complete.
  3. Update STATUS.md and SUMMARY.md as each package completes.
  4. Archive @suaveplan/day-night after gaming-environment-lighting ships.
  5. Publish migration guide mapping day-night exports to new package exports.

Open Questions

  • None — all deferred items are explicitly scoped out with locked interfaces.