## 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: ```text 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.