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

7.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). It is not extensible to other planets, not observatory-accurate, and not enterprise-grade. The existing @suaveplan/geo covers Earth-surface geometry only and mixes abstract math with Earth-specific constants.

This design defines a seven-package family split across three layers: abstract geometry (Layer 0), concrete planet surfaces (Layer 1), and astronomical observer core (Layer 2).

Goals / Non-Goals

Goals:

  • Provide real, observatory-accurate astronomical calculations via a pluggable provider architecture.
  • Generalise Earth-surface geometry to any planet via configurable ellipsoid and physical parameters.
  • Support an observer-centric model: stars, planets, tides, and lighting computed from a single observer position on any planet surface, in atmosphere, or in space.
  • Lock in a binary wire protocol (BitSchemas via @suaveplan/bitpacker) for efficient server→client star delivery.
  • Preserve the public API of existing @suaveplan/geo consumers while restructuring internals.
  • Achieve 100% test coverage, Zod schemas for all public options, OTel instrumentation, and AbortSignal on every async method.

Non-Goals:

  • Gaming-layer integration — deferred to enterprise-gaming-universe.
  • SurrealDB star storage schema and SurrealQL JS function bodies (deferred).
  • Hipparcos, Tycho-2, Gaia catalog source implementations (interfaces locked, implementations deferred).
  • R3F / Three.js rendering integration for sky, terrain, planet textures.
  • Multiplayer synchronisation of game time across clients.
  • @suaveplan/weather integration (locked interface but no implementation).
  • Bundling texture assets — only identifiers pass through.

Decisions

1. Layered architecture with explicit dependency graph

Layers prevent circular dependencies and make testing tractable:

  • geo → geo-planet → geo-earth
  • geo + geo-planet → universe
  • universe → (solar-system, stars, tides)

Rationale: Each layer adds semantic meaning. geo has no astronomical meaning. universe is gaming-agnostic and can be consumed directly by game engines or simulation tools without pulling in timing or event dependencies.

2. PlanetConfig as the single source of truth for any planet

All planet-specific data (mass, radius, rotation, axial tilt, atmosphere, satellites, orbit, textures) lives in one serialisable struct. PlanetSurface wraps it with geometry operations.

Rationale: Prevents scattered constants and makes multi-planet games trivial — swap the config, reuse the same code paths.

3. ObserverPosition carries PlanetConfig inline

For surface and atmospheric observers, the PlanetConfig is embedded in the position value rather than referenced by ID.

Rationale: Eliminates lookup at query time. Providers receive everything they need in one argument. No global registry, no async lookup, no cache invalidation complexity.

4. Provider registry pattern in UniverseClient

UniverseClient.register(domain, provider) allows consumers to mix and match: astronomy.js for solar, BSC5 for stars, TICON for Earth tides, physics model for Mars tides.

Rationale: Composition over inheritance. Each provider is independently testable. Consumers can inject mocks or custom game-defined providers.

5. Two-engine model in solar-system

Real bodies use astronomy.js (observatory-accurate). Game-defined planets with OrbitalConfig use a Keplerian propagator (physically plausible, not observatory-grade).

Rationale: astronomy.js is battle-tested and correct to arcsecond precision. Rewriting it would be error-prone. Custom planets need only plausible orbits, not ephemeris data.

6. BitSchema wire protocol for stars and bodies

StarPacket (~11 bytes/star) and CelestialBodyPacket quantise data for compact transmission. Rendering does not need float64.

Rationale: ~18× compression vs JSON. Essential for server→client star streaming. The schema is locked now so downstream render integrations can depend on it.

7. 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/distance/
├── index.ts
├── distance.ts
├── distance.test.ts
└── distance.md

The package.json exports map uses the repeated-name form (./distance/distance) 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.

8. TICON and ADMIRALTY providers preserved verbatim

The Earth harmonic engine and station data are kept intact; enterprise additions wrap them with Zod, OTel, and AbortSignal.

Rationale: The harmonic engine is correct and tested. Rewriting it risks regressions. The enterprise layer adds cross-cutting concerns without changing physics.

Risks / Trade-offs

Risk Mitigation
astronomy.js bundle size Evaluate at build time; tree-shaking removes unused bodies. Document size impact in README.
BSC5 catalog (~100 KB bitpacked) may bloat browser bundles Subpath export stars/catalogs/bsc5 is optional; consumers importing only StarProviderImpl do not pull the catalog.
Keplerian propagator for custom planets is not observatory-accurate Documented as "physically plausible" in API docs. Games needing precision must supply real ephemeris.
Multi-satellite tidal physics model is simplified (no ocean basin resonance) Documented limitation. Earth consumers use TICON/ADMIRALTY for accuracy.
PlanetConfig embeds full config on every ObserverPosition Config objects are small (~1 KB). Copying avoids lookup complexity and shared mutable state.
7 packages increase monorepo maintenance surface Each package has a single responsibility and narrow public API. Dependency graph is a DAG with no cycles.
Deferred SurrealDB / weather / Gaia integrations may drift from locked interfaces Interfaces are versioned in stars and universe specs. Future implementations must satisfy the structural contract.

Migration Plan

  1. Land packages in dependency order: geo → geo-planet → geo-earth → universe → solar-system → stars → tides.
  2. Update STATUS.md and SUMMARY.md as each package completes.
  3. Run dependent sweep on @suaveplan/geo-earth (successor to @suaveplan/geo) to verify downstream consumers compile and test.
  4. After this change archives, enterprise-gaming-universe can begin implementing gaming-universe and gaming-environment-lighting.

Open Questions

  • None — all deferred items are explicitly scoped out in §13 of the design spec with locked interfaces.