Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-05-05

View File

@@ -0,0 +1,115 @@
## 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.

View File

@@ -0,0 +1,37 @@
## Why
The existing `@suaveplan/geo` package only covers Earth-surface geometry and mixes abstract math with Earth-specific constants. Neither the existing `@suaveplan/day-night` nor `@suaveplan/geo` is enterprise-grade, extensible to other planets, or observatory-accurate. This change introduces a seven-package astronomical core that provides real astronomical calculations, generalised planet-surface geometry, and a pluggable provider architecture.
## What Changes
- **Restructure `@suaveplan/geo`** — abstract math extracted into new `@suaveplan/geo`; Earth-specific logic migrates to new `@suaveplan/geo-earth`.
- **Create `@suaveplan/geo-planet`** — configurable `PlanetConfig` and `PlanetSurface` for any planet's ellipsoid, atmosphere, and satellites.
- **Create `@suaveplan/universe`** — observer-centric astronomical core: coordinate frames, provider interfaces (`SolarProvider`, `StarProvider`, `TidalProvider`, `WeatherProvider`), `UniverseClient` plugin registry, and BitSchema wire protocols for star/body data.
- **Create `@suaveplan/solar-system`** — `SolarProvider` implementation using astronomy.js for real bodies plus Keplerian propagator for game-defined planets.
- **Create `@suaveplan/stars`** — `StarProvider` with bundled BSC5 catalog (~9k stars, bitpacked), swappable catalog source architecture, and deferred interfaces for Hipparcos/Tycho-2/Gaia.
- **Rewrite `@suaveplan/tides`** — enterprise-grade tidal prediction preserving TICON/ADMIRALTY Earth providers, adding physics-based multi-satellite model for any planet, `TidalProvider` adapter for `universe`, and full OTel/AbortSignal/Zod coverage.
**BREAKING**: `@suaveplan/geo` public API is preserved but internal implementation changes; consumers should verify no dependency on internal file paths.
## Capabilities
### New Capabilities
- `geo`: Abstract spherical and ellipsoidal geometry — pure math with no planet-specific constants.
- `geo-planet`: Configurable planet surface geometry from `PlanetConfig` — ellipsoid, atmosphere, satellites, and orbital elements.
- `geo-earth`: Earth pre-configuration of `geo-planet` — WGS84, Moon, ISA atmosphere, plus geocoding, geohash, and coordinate formatting.
- `universe`: Astronomical observer model — `ObserverPosition`, `CoordinateFrame`, provider interfaces, `UniverseClient` plugin registry, and BitSchema wire protocols.
- `solar-system`: Solar system computation — real body positions via astronomy.js, Keplerian propagator for custom planets, seasons, equinoxes, solstices, lunar phases, eclipses.
- `stars`: Star catalog provider — BSC5 bundled source, swappable catalog architecture, atmospheric extinction, and deferred SurrealDB/Hipparcos/Tycho-2/Gaia interfaces.
- `tides`: Tidal prediction — Earth harmonic providers (TICON, ADMIRALTY) and physics-based multi-satellite model for any planet.
### Modified Capabilities
- None.
## Impact
- **New packages**: 7 packages across `universal/geo`, `universal/astronomy`, and `universal/primitives` plugin directories.
- **Dependency graph**: `geo` → `geo-planet` → `geo-earth` → `universe` → (`solar-system`, `stars`, `tides`).
- **Downstream consumers**: Any package depending on `@suaveplan/geo` internals must update to `@suaveplan/geo-planet` abstractions.
- **Follow-up change**: `enterprise-gaming-universe` builds the gaming layer (`gaming-universe`, `gaming-environment-lighting`) on top of this astronomical core.
- **Deferred integrations**: `@suaveplan/weather` (locked interface in `WeatherProvider` but no implementation), SurrealDB star storage, Hipparcos/Tycho-2/Gaia catalog sources, and R3F/Three.js rendering integrations are explicitly out of scope.

View File

@@ -0,0 +1,59 @@
## ADDED Requirements
### Requirement: Earth constant
The system SHALL export a pre-built `EARTH` `PlanetConfig` using the WGS84 ellipsoid, Moon as a `SatelliteConfig`, and ISA atmosphere as `AtmosphereConfig`.
#### Scenario: Earth constant validation
- **WHEN** `EARTH` is imported
- **THEN** `EARTH.name` equals `"Earth"`
- **AND** `EARTH.radius` equals `6378137`
- **AND** `EARTH.satellites` contains exactly one entry with `name` matching `"Moon"`
### Requirement: Earth surface instance
The system SHALL export a pre-built `earthSurface` `PlanetSurface` constructed from `EARTH`.
#### Scenario: Earth surface delegation
- **WHEN** `earthSurface.distance(a, b)` is called for two coordinates
- **THEN** the result equals the WGS84 geodesic distance
### Requirement: Geocoding provider interface
The system SHALL export `GeocodingProvider`, `GeocodingResult`, `createInMemoryProvider`, and `batchGeocode`.
#### Scenario: In-memory geocode
- **WHEN** `createInMemoryProvider([{ name: "London", latitude: 51.5074, longitude: -0.1278 }])` is created
- **AND** `batchGeocode` is called with `["London"]`
- **THEN** the result contains one entry with matching coordinates
### Requirement: Geohash encoding and decoding
The system SHALL export `encodeGeohash` and `decodeGeohash` with round-trip fidelity.
#### Scenario: Geohash round-trip
- **WHEN** `encodeGeohash({ latitude: 51.5074, longitude: -0.1278 }, 9)` is called
- **AND** the result is passed to `decodeGeohash`
- **THEN** the decoded coordinate matches the original within `0.0001` degrees
### Requirement: Coordinate parsing and formatting
The system SHALL export `parseCoordinate` and `formatCoordinate` supporting DMS (degrees-minutes-seconds) and decimal formats.
#### Scenario: Parse DMS string
- **WHEN** `parseCoordinate("51°30'26.5\"N")` is called
- **THEN** the result is approximately `51.5074`
### Requirement: Preserve existing geo public API
The system SHALL preserve all public exports that existed in the legacy `@suaveplan/geo` package.
#### Scenario: Legacy export compatibility
- **WHEN** all public exports of the legacy `@suaveplan/geo` are compared to `@suaveplan/geo-earth`
- **THEN** every legacy export name exists in `geo-earth` with the same or broader type signature

View File

@@ -0,0 +1,67 @@
## ADDED Requirements
### Requirement: PlanetConfig shape
The system SHALL export a `PlanetConfig` type containing name, mass, radius, polar radius, surface gravity, rotation period, axial tilt, optional atmosphere, satellites, optional orbital elements, and optional texture manifest.
#### Scenario: Validate PlanetConfig schema
- **WHEN** a `PlanetConfig` with all required fields is constructed via Zod parse
- **THEN** validation succeeds
- **AND** when mass is omitted, validation throws `ValidationError`
### Requirement: AtmosphereConfig shape
The system SHALL export an `AtmosphereConfig` type with surface pressure, scale height, Rayleigh coefficient, Mie coefficient, and extinction per air mass.
#### Scenario: Validate atmosphere
- **WHEN** an `AtmosphereConfig` with valid positive values is parsed
- **THEN** validation succeeds
- **AND** `atmosphericDensity(0)` on a `PlanetSurface` using this config returns surface pressure-derived density
### Requirement: SatelliteConfig shape
The system SHALL export a `SatelliteConfig` type with name, mass, radius, orbital radius, orbital period, inclination, eccentricity, and albedo.
#### Scenario: Satellite definition
- **WHEN** a `SatelliteConfig` for a moon is parsed with `mass > 0` and `orbitalRadius > 0`
- **THEN** validation succeeds
### Requirement: PlanetSurface geometry delegation
The system SHALL implement `PlanetSurface` whose `distance`, `bearing`, `destination`, and `pointInPolygon` methods delegate to `@suaveplan/geo` functions using the planet's ellipsoid.
#### Scenario: PlanetSurface uses correct ellipsoid
- **WHEN** a `PlanetSurface` is constructed with a planet having `radius = 3396200` and `polarRadius = 3376200` (Mars-like)
- **THEN** `distance` between two surface points matches the great-circle distance on that ellipsoid, not WGS84
### Requirement: Atmospheric density
The system SHALL compute atmospheric density at a given altitude from `AtmosphereConfig` using the scale-height exponential model.
#### Scenario: Density halves at scale height
- **WHEN** `atmosphericDensity(scaleHeight)` is called on a planet with `scaleHeight = 8500`
- **THEN** the result equals half the surface density
### Requirement: Air mass
The system SHALL compute air mass for a given altitude angle using the Kasten–Young formula.
#### Scenario: Zenith air mass
- **WHEN** `airMass(90)` is called
- **THEN** the result is approximately `1.0`
- **AND** `airMass(0)` returns a value significantly greater than `1.0`
### Requirement: No bundled planet data
The system SHALL NOT bundle any pre-defined planet configuration.
#### Scenario: Inspect geo-planet exports
- **WHEN** all exports from `@suaveplan/geo-planet` are enumerated
- **THEN** no `EARTH`, `MARS`, or pre-built `PlanetConfig` constant exists

View File

@@ -0,0 +1,86 @@
## ADDED Requirements
### Requirement: Ellipsoid definition
The system SHALL provide a way to define an ellipsoid from semi-major and semi-minor axes, deriving flattening and eccentricity automatically.
#### Scenario: Define WGS84 ellipsoid
- **WHEN** `defineEllipsoid(6378137, 6356752.314245)` is called
- **THEN** the returned `Ellipsoid` has `semiMajorAxis = 6378137`, `semiMinorAxis = 6356752.314245`, and derived `flattening` and `eccentricity` within floating-point tolerance
### Requirement: Spherical coordinate conversion
The system SHALL convert between spherical coordinates (latitude, longitude, altitude) and Cartesian vectors.
#### Scenario: Convert spherical to Cartesian and back
- **WHEN** a `SphericalCoord` with `latitude = 51.5074`, `longitude = -0.1278`, `altitude = 0` is converted to Cartesian via `sphericalToCartesian` using a WGS84 ellipsoid
- **THEN** converting the result back via `cartesianToSpherical` yields the original coordinates within `0.0001` degrees
### Requirement: Great-circle distance
The system SHALL compute great-circle distance between two spherical coordinates on a given ellipsoid.
#### Scenario: Distance between London and Paris
- **WHEN** `greatCircleDistance` is called with London (`51.5074, -0.1278`) and Paris (`48.8566, 2.3522`) on WGS84
- **THEN** the result is approximately `344000` metres
### Requirement: Vincenty distance
The system SHALL compute geodesic distance using Vincenty's formulae for high precision.
#### Scenario: Vincenty vs great-circle consistency
- **WHEN** both `greatCircleDistance` and `vincentyDistance` are called for the same nearby points (< 100 km)
- **THEN** the results differ by less than `0.1%`
### Requirement: Bearing and destination
The system SHALL compute initial bearing between two points and compute a destination point given start, bearing, and distance.
#### Scenario: Round-trip destination
- **WHEN** `destination(start, bearing, distance, ellipsoid)` is called
- **AND** `bearing` is computed from `start` to a known `end`
- **THEN** the computed destination equals `end` within `0.0001` degrees
### Requirement: Bounding boxes
The system SHALL provide bounding box creation, point containment, expansion, merging, and intersection.
#### Scenario: Box contains point
- **WHEN** a `BoundingBox` spanning `(-10, -10)` to `(10, 10)` is created
- **THEN** `containsPoint(box, { latitude: 0, longitude: 0 })` returns `true`
- **AND** `containsPoint(box, { latitude: 20, longitude: 0 })` returns `false`
### Requirement: Polygon operations
The system SHALL provide point-in-polygon, polygon area, perimeter, and simplification.
#### Scenario: Point inside a square polygon
- **WHEN** a square polygon with vertices at `(0,0)`, `(0,10)`, `(10,10)`, `(10,0)` is defined
- **THEN** `pointInPolygon({ latitude: 5, longitude: 5 }, polygon)` returns `true`
- **AND** `pointInPolygon({ latitude: 15, longitude: 5 }, polygon)` returns `false`
### Requirement: Distance unit conversion
The system SHALL convert distances between metres, kilometres, miles, and nautical miles.
#### Scenario: Convert metres to kilometres
- **WHEN** `convertDistance(1000, 'metre', 'kilometre')` is called
- **THEN** the result equals `1`
### Requirement: No planet-specific constants
The system SHALL NOT export any numeric constant for a specific celestial body.
#### Scenario: Inspect geo exports
- **WHEN** all exports from `@suaveplan/geo` are enumerated
- **THEN** no export named `EARTH`, `MARS`, `MOON`, or similar exists
- **AND** no hardcoded radius, mass, or orbital value for any body is present

View File

@@ -0,0 +1,110 @@
## ADDED Requirements
### Requirement: SolarProvider implementation
The system SHALL export `createSolarSystemProvider` returning a `SolarProvider` that computes real solar system body positions.
#### Scenario: Sun altitude at noon
- **WHEN** `getBody("Sun", observer, time)` is called for an observer at latitude `0` at local noon
- **THEN** the returned `altitude` is approximately `90` minus the absolute declination offset
### Requirement: Astronomy.js engine injection
The system SHALL accept an injectable `AstronomyEngine` in `createSolarSystemProvider`, defaulting to astronomy.js.
#### Scenario: Deterministic engine injection
- **WHEN** a deterministic mock `AstronomyEngine` is injected
- **THEN** `getBodies` returns exactly the mocked positions
### Requirement: Keplerian propagator for custom planets
The system SHALL propagate positions for planets with `OrbitalConfig` using a Keplerian solver from epoch.
#### Scenario: Custom planet position
- **WHEN** a `PlanetConfig` with valid `OrbitalConfig` is used as observer's planet or target body
- **THEN** `getBodies` includes the custom planet with a propagated heliocentric position
### Requirement: Angular diameter computation
The system SHALL compute `angularDiameterArcmin` as `2 * arctan(radius / distance) * (180/π * 60)`.
#### Scenario: Sun angular diameter
- **WHEN** `getBody("Sun", ...)` is called
- **THEN** `angularDiameterArcmin` is approximately `32` arcminutes
### Requirement: Apparent magnitude for real bodies
The system SHALL compute `apparentMagnitude` for real bodies using phase angle and distance via astronomy.js.
#### Scenario: Venus magnitude range
- **WHEN** `getBody("Venus", ...)` is called at multiple times
- **THEN** `apparentMagnitude` varies between approximately `-4.9` and `-3.8`
### Requirement: Sunrise and sunset computation
The system SHALL compute sunrise and sunset dates in UTC for a given observer and calendar date.
#### Scenario: Equator sunrise consistency
- **WHEN** `getSunrise` and `getSunset` are called for an equatorial observer on an equinox date
- **THEN** sunrise is approximately 12 hours before sunset
### Requirement: Season computation
The system SHALL export `getSeason` defaulting to Earth but accepting any `PlanetConfig` with `axialTilt` and `OrbitalConfig`.
#### Scenario: Northern hemisphere season
- **WHEN** `getSeason(new Date("2026-06-21"), EARTH)` is called
- **THEN** the result is `summer`
### Requirement: Equinox and solstice prediction
The system SHALL export `getNextEquinox` and `getNextSolstice` for any configured planet.
#### Scenario: Next equinox after a date
- **WHEN** `getNextEquinox(new Date("2026-01-01"), EARTH)` is called
- **THEN** the result is a `Date` in March 2026
### Requirement: Lunar phase
The system SHALL export `getLunarPhase` returning phase (0–1), illumination percentage, and name for Earth/Moon.
#### Scenario: Full moon phase
- **WHEN** `getLunarPhase(fullMoonDate)` is called
- **THEN** `phase` is approximately `0.5`
- **AND** `illumination` is approximately `100%`
### Requirement: Eclipse prediction
The system SHALL export `predictEclipse` for solar and lunar eclipses on Earth.
#### Scenario: Known eclipse
- **WHEN** `predictEclipse(new Date("2026-01-01"), "solar")` is called
- **THEN** the returned `EclipseEvent` has a date after the input and a `totality` value
### Requirement: Constellation lookup
The system SHALL export `getConstellation` mapping RA/Dec to IAU constellation name.
#### Scenario: Orion lookup
- **WHEN** `getConstellation(raOrion, decOrion)` is called with Orion's centre coordinates
- **THEN** the result equals `"Orion"`
### Requirement: Atmosphere extinction for surface observers
The system SHALL apply atmospheric extinction to apparent magnitudes when the observer has an atmosphere.
#### Scenario: Horizon extinction
- **WHEN** `getBody("Sun", surfaceObserver, time)` is called with the Sun near the horizon
- **THEN** `apparentMagnitude` is dimmer than the vacuum value

View File

@@ -0,0 +1,97 @@
## ADDED Requirements
### Requirement: RawStar shape
The system SHALL export a `RawStar` interface with catalog ID, RA, Dec, apparent magnitude, spectral type, optional distance, proper motion, and parallax.
#### Scenario: Raw star validation
- **WHEN** a `RawStar` with all required fields is parsed
- **THEN** validation succeeds
- **AND** omitting `catalogId` causes validation to fail
### Requirement: StarCatalogSource interface
The system SHALL export a `StarCatalogSource` interface with `id`, `magnitudeLimit`, `starCount`, `queryCone`, and `getById`.
#### Scenario: Source query
- **WHEN** `queryCone` is called on a source with RA, Dec, radius, and options
- **THEN** the returned `RawStar[]` contains only stars within the cone
### Requirement: BSC5 bundled catalog
The system SHALL export `createBsc5Source` returning a `StarCatalogSource` with ~9,096 stars, requiring no network or configuration.
#### Scenario: BSC5 offline query
- **WHEN** `createBsc5Source()` is created and `queryCone(0, 0, 1)` is called
- **THEN** the promise resolves with an array of `RawStar` entries
- **AND** the source `id` equals `"bsc5"`
### Requirement: StarProvider implementation
The system SHALL export `StarProviderImpl` implementing `StarProvider` with priority-ordered catalog sources.
#### Scenario: Visible stars query
- **WHEN** `getVisibleStars` is called with a surface observer and default options
- **THEN** the result is an `ObservedStar[]` sorted by brightness (brightest first)
- **AND** every entry has `altitude` and `azimuth` populated
### Requirement: Horizon frame computation
The system SHALL compute the local horizon frame from `ObserverPosition` for star altitude/azimuth calculation.
#### Scenario: Zenith star altitude
- **WHEN** a star with RA/Dec matching the observer's zenith is queried
- **THEN** the returned `altitude` is approximately `90`
- **AND** `azimuth` is `0` or `180` depending on convention
### Requirement: Atmospheric extinction
The system SHALL apply Pogson's extinction formula using the Kasten–Young air mass when the observer has an `AtmosphereConfig`.
#### Scenario: Extinction at low altitude
- **WHEN** `getVisibleStars` is called for a surface observer on a planet with atmosphere
- **THEN** stars near the horizon have `extinctedMagnitude > apparentMagnitude`
- **AND** stars at the zenith have `extinctedMagnitude ≈ apparentMagnitude`
### Requirement: No extinction in vacuum
The system SHALL NOT apply extinction for `SpacePosition` observers or planets without `AtmosphereConfig`.
#### Scenario: Space observer magnitudes
- **WHEN** `getVisibleStars` is called for a `SpacePosition` observer
- **THEN** `extinctedMagnitude` equals `apparentMagnitude` for all stars
### Requirement: Cone query returns catalog stars
The system SHALL return `CatalogStar[]` (without altitude/azimuth) from `queryCone`.
#### Scenario: Catalog cone query
- **WHEN** `queryCone(ra, dec, radiusDeg)` is called
- **THEN** the result contains `CatalogStar` entries without `altitude` or `azimuth`
### Requirement: SurrealDB adapter interface
The system SHALL export `SurrealAdapterLike`, `SurrealStarProviderOptions`, and `createSurrealStarSource` as a structural interface with no hard dependency on `@suaveplan/surrealdb`.
#### Scenario: Structural satisfaction
- **WHEN** an object with a `query` method matching `SurrealAdapterLike` is passed to `createSurrealStarSource`
- **THEN** the returned object satisfies `StarCatalogSource`
### Requirement: Deferred catalog interfaces
The system SHALL export type signatures for `createHipparcosSource`, `createTycho2Source`, and `createGaiaSource` with injectable `fetchFn` and optional `cacheFn`.
#### Scenario: Interface existence
- **WHEN** the public API surface of `@suaveplan/stars` is inspected
- **THEN** the three deferred factory function types are present
- **AND** their option types include `fetchFn` and `cacheFn` fields

View File

@@ -0,0 +1,114 @@
## ADDED Requirements
### Requirement: TidalProvider implementation
The system SHALL provide `TidalProvider` implementations for Earth (TICON, ADMIRALTY) and any planet (physics-based).
#### Scenario: Provider creation
- **WHEN** `createTiconProvider()`, `createAdmiraltyProvider(options)`, and `createPhysicsBasedProvider()` are called
- **THEN** each returns an object satisfying `TidalProvider`
### Requirement: Earth harmonic providers preserved
The system SHALL preserve TICON and ADMIRALTY provider logic, harmonic constituent engine, and station data.
#### Scenario: TICON prediction
- **WHEN** `predictHeight` is called via a TICON provider for a known UK station at a known time
- **THEN** the result matches the legacy `@suaveplan/tides` value within `0.01` metres
### Requirement: Multi-satellite physics model
The system SHALL compute tidal height from all satellites in `PlanetConfig` using superposition.
#### Scenario: Two-satellite planet
- **WHEN** a `PlanetConfig` with two satellites is used with `createPhysicsBasedProvider()`
- **AND** `predictHeight` is called
- **THEN** the result reflects the combined gravitational influence
### Requirement: Individual satellite contribution
The system SHALL export `satelliteTidalContribution` for visualising per-satellite effects.
#### Scenario: Single contribution
- **WHEN** `satelliteTidalContribution(planet, satellite, coord, time)` is called
- **THEN** the result is a number representing that satellite's height contribution
### Requirement: Zod schemas for all public options
The system SHALL validate all public option types with Zod and throw `ValidationError` on failure.
#### Scenario: Invalid options
- **WHEN** `createAdmiraltyProvider({ apiKey: "" })` is called with an invalid key
- **THEN** validation throws before any network request
### Requirement: AbortSignal on async methods
The system SHALL accept `AbortSignal` on every async provider method.
#### Scenario: Abort tide prediction
- **WHEN** `predictHeight` is called with an aborted signal
- **THEN** the promise rejects with an abort error
### Requirement: TidesError with stable codes
The system SHALL export `TidesError` extending `@suaveplan/error` with `TIDES.*` error codes.
#### Scenario: Error code stability
- **WHEN** a provider throws for a missing station
- **THEN** the error code begins with `TIDES.`
- **AND** the same condition yields the same code across calls
### Requirement: OTel instrumentation
The system SHALL create OTel spans, counters, and histograms on every provider call.
#### Scenario: Span creation
- **WHEN** `predictHeight` is called
- **THEN** an OTel span is started with the operation name
- **AND** a histogram records the prediction latency
### Requirement: Injectable fetch and clock
The system SHALL accept `fetchFn`, `clockFn`, and optional `cacheFn` in ADMIRALTY provider options.
#### Scenario: Custom fetch
- **WHEN** a custom `fetchFn` is injected into `createAdmiraltyProvider`
- **THEN** the provider uses it for all HTTP requests
### Requirement: Property-based tests on harmonic engine
The system SHALL include fast-check property tests on the harmonic prediction engine.
#### Scenario: Property test existence
- **WHEN** the test suite runs
- **THEN** fast-check properties verify monotonicity and bounds of the harmonic sum
### Requirement: TidalProvider routing
The system SHALL export `TidalProviderImpl` routing Earth to `earthProviders` and non-Earth to `physicsProvider`.
#### Scenario: Earth vs Mars routing
- **WHEN** `predictHeight` is called with `EARTH`
- **THEN** the first Earth provider is tried
- **AND** when called with a Mars-like config, the physics provider is used
### Requirement: Planet identification by name
The system SHALL identify Earth by `planet.name.toLowerCase() === "earth"`.
#### Scenario: Name-based routing
- **WHEN** a planet with `name: "Earth"` is passed
- **THEN** it routes to Earth providers
- **AND** a planet with `name: "Mars"` routes to physics

View File

@@ -0,0 +1,115 @@
## ADDED Requirements
### Requirement: ObserverPosition types
The system SHALL export a discriminated union `ObserverPosition` with three variants: `surface` (planet + coord), `atmospheric` (planet + coord + altitude), and `space` (Vec3 + frame).
#### Scenario: Surface observer construction
- **WHEN** an `ObserverPosition` of type `surface` is constructed with a valid `PlanetConfig` and `SphericalCoord`
- **THEN** the object validates successfully
- **AND** the `PlanetConfig` is accessible inline without lookup
### Requirement: CoordinateFrame types and conversion
The system SHALL export `CoordinateFrame` as a union of `heliocentric`, `galactic`, and `custom`, plus pure conversion functions `convertFrame`, `toHeliocentric`, and `toGalactic`.
#### Scenario: Identity conversion
- **WHEN** `convertFrame(vec, heliocentric, heliocentric)` is called
- **THEN** the result equals the input vector
### Requirement: CelestialBody shape
The system SHALL export a `CelestialBody` interface with name, type, altitude, azimuth, apparent magnitude, angular diameter, distance in AU, and heliocentric position.
#### Scenario: Body validation
- **WHEN** a `CelestialBody` object is constructed with all required fields
- **THEN** Zod validation succeeds
### Requirement: CatalogStar and ObservedStar shapes
The system SHALL export `CatalogStar` (catalog-space, no observer fields) and `ObservedStar` (extends `CatalogStar` with altitude, azimuth, and extincted magnitude).
#### Scenario: Observed star construction
- **WHEN** an `ObservedStar` is created from a `CatalogStar` with added `altitude`, `azimuth`, and `extinctedMagnitude`
- **THEN** the object satisfies both the `CatalogStar` and `ObservedStar` contracts
### Requirement: SolarProvider interface
The system SHALL export a `SolarProvider` interface with `getBodies`, `getBody`, `getSunrise`, and `getSunset`, all accepting `ObserverPosition`, `Date`, and optional `AbortSignal`.
#### Scenario: SolarProvider cancellation
- **WHEN** `getBodies` is called with an `AbortSignal` that is aborted before resolution
- **THEN** the promise rejects with an abort error
### Requirement: StarProvider interface
The system SHALL export a `StarProvider` interface with `getVisibleStars` (returns `ObservedStar[]`), `queryCone` (returns `CatalogStar[]`), and `getStar`.
#### Scenario: StarProvider query
- **WHEN** `getVisibleStars` is called with a surface observer and `StarQueryOptions`
- **THEN** the returned array contains only `ObservedStar` entries with `altitude` and `azimuth` populated
### Requirement: TidalProvider interface
The system SHALL export a `TidalProvider` interface with `predictHeight` and `getExtremes`, accepting `PlanetConfig`, `SphericalCoord`, `Date`, and optional `AbortSignal`.
#### Scenario: Tidal prediction cancellation
- **WHEN** `predictHeight` is called with an aborted signal
- **THEN** the promise rejects with an abort error
### Requirement: WeatherState and WeatherProvider interface
The system SHALL export `WeatherState` and `WeatherProvider` with a locked interface for future `@suaveplan/weather` integration.
#### Scenario: WeatherProvider registration
- **WHEN** a `WeatherProvider` is registered with `UniverseClient`
- **THEN** `UniverseScene.weather` is populated after `getScene`
### Requirement: UniverseClient plugin registry
The system SHALL provide `UniverseClient` with typed `register` methods for `solar`, `stars`, `tides`, and `weather` domains, returning `this` for chaining.
#### Scenario: Register all providers
- **WHEN** providers are registered for all four domains
- **AND** `getScene` is called with valid observer and time
- **THEN** the returned `UniverseScene` contains `bodies`, `stars`, `tides`, and `weather`
### Requirement: UniverseScene shape
The system SHALL export `UniverseScene` with `bodies`, `stars`, optional `tides`, and optional `weather`.
#### Scenario: Scene without optional providers
- **WHEN** only `solar` and `stars` providers are registered
- **AND** `getScene` is called
- **THEN** the scene has `bodies` and `stars`, with `tides` and `weather` undefined
### Requirement: StarPacket BitSchema
The system SHALL export a `StarPacket` BitSchema quantising RA, Dec, magnitude, altitude, azimuth, and spectral class into ~11 bytes per star.
#### Scenario: Pack and unpack stars
- **WHEN** an array of `ObservedStar` objects is packed via `packStars`
- **AND** the buffer is unpacked via `unpackStars` with the correct count
- **THEN** the unpacked array has the same length
- **AND** each field is within the quantisation error tolerance of the original
### Requirement: CelestialBodyPacket BitSchema
The system SHALL export a `CelestialBodyPacket` BitSchema quantising altitude, azimuth, angular diameter, apparent magnitude, and body type.
#### Scenario: Pack and unpack bodies
- **WHEN** an array of `CelestialBody` objects is packed via `packBodies`
- **AND** the buffer is unpacked via `unpackBodies` with the correct count
- **THEN** the unpacked array has the same length and matching fields within tolerance

View File

@@ -0,0 +1,147 @@
## 1. geo — Abstract Geometry
- [ ] 1.1 Scaffold `@suaveplan/geo` package in `packages/geo/geo` with `package.json`, `tsconfig.json`, `vitest.config.ts` (100/100/100/100 thresholds)
- [ ] 1.2 Implement `Ellipsoid` type and `defineEllipsoid(a, b)` factory
- [ ] 1.3 Implement `SphericalCoord`, `Vec3`, `Mat3x3` types
- [ ] 1.4 Implement `geo/transforms` — `sphericalToCartesian`, `cartesianToSpherical`, `toRadians`, `toDegrees`
- [ ] 1.5 Implement `geo/distance` — `greatCircleDistance` and `vincentyDistance`
- [ ] 1.6 Implement `geo/bearing` — `bearing`, `destination`, `midpoint`
- [ ] 1.7 Implement `geo/angular` — `angularSeparation`
- [ ] 1.8 Implement `geo/bounds` — `BoundingBox`, `createBoundingBox`, `containsPoint`, `expandBox`, `mergeBoxes`, `intersects`
- [ ] 1.9 Implement `geo/polygon` — `pointInPolygon`, `polygonArea`, `polygonPerimeter`, `simplifyPolygon`
- [ ] 1.10 Implement `geo/units` — `DistanceUnit`, `convertDistance`
- [ ] 1.11 Add Zod schemas for all public option types
- [ ] 1.12 Add OTel spans on all public entrypoints
- [ ] 1.13 Write tests achieving 100% coverage for all source files
- [ ] 1.14 Write sibling `.md` docs for every implementation file
- [ ] 1.15 Write `README.md` with install, quick-start, and module links
- [ ] 1.16 Configure explicit named barrel exports for all subpaths (no `export *`)
- [ ] 1.17 Run `bunx turbo lint typecheck test build --filter=@suaveplan/geo` and fix all errors
- [ ] 1.18 Run `bunx turbo test:coverage --filter=@suaveplan/geo` and verify 100%
## 2. geo-planet — Configurable Planet Surface
- [ ] 2.1 Scaffold `@suaveplan/geo-planet` package in `packages/geo/geo-planet`
- [ ] 2.2 Implement `PlanetConfig`, `AtmosphereConfig`, `SatelliteConfig`, `OrbitalConfig`, `PlanetTextureManifest` types with Zod schemas
- [ ] 2.3 Implement `PlanetSurface` class delegating geometry to `@suaveplan/geo`
- [ ] 2.4 Implement `atmosphericDensity(altitudeMetres)` using scale-height model
- [ ] 2.5 Implement `airMass(altitudeDeg)` using Kasten–Young formula
- [ ] 2.6 Add OTel spans and `AbortSignal` support on async methods
- [ ] 2.7 Write tests achieving 100% coverage
- [ ] 2.8 Write sibling `.md` docs and `README.md`
- [ ] 2.9 Configure explicit named barrel exports
- [ ] 2.10 Run lint, typecheck, test, build, and coverage gates
## 3. geo-earth — Earth Configuration
- [ ] 3.1 Scaffold `@suaveplan/geo-earth` package in `packages/geo/geo-earth`
- [ ] 3.2 Define `EARTH` `PlanetConfig` constant (WGS84, Moon, ISA atmosphere)
- [ ] 3.3 Create `earthSurface` `PlanetSurface` instance from `EARTH`
- [ ] 3.4 Migrate and preserve legacy `@suaveplan/geo` public API: geocoding, geohash, coordinate parsing/formatting
- [ ] 3.5 Implement `GeocodingProvider`, `GeocodingResult`, `createInMemoryProvider`, `batchGeocode`
- [ ] 3.6 Implement `encodeGeohash`, `decodeGeohash` with round-trip fidelity
- [ ] 3.7 Implement `parseCoordinate`, `formatCoordinate` (DMS and decimal)
- [ ] 3.8 Write parity checklist comparing legacy `@suaveplan/geo` to `geo-earth`
- [ ] 3.9 Add Zod schemas, OTel spans, and `AbortSignal` where async
- [ ] 3.10 Write tests achieving 100% coverage
- [ ] 3.11 Write sibling `.md` docs and `README.md`
- [ ] 3.12 Configure explicit named barrel exports
- [ ] 3.13 Run lint, typecheck, test, build, and coverage gates
- [ ] 3.14 Run dependent sweep: `bunx turbo test --filter=...@suaveplan/geo-earth`
## 4. universe — Observer Model, Frames, Plugin Registry, Wire Protocol
- [ ] 4.1 Scaffold `@suaveplan/universe` package in `packages/geo/universe`
- [ ] 4.2 Implement `ObserverPosition` discriminated union type
- [ ] 4.3 Implement `CoordinateFrame` union and `convertFrame`, `toHeliocentric`, `toGalactic`
- [ ] 4.4 Implement `CelestialBody`, `CatalogStar`, `ObservedStar`, `StarQueryOptions`, `TideExtreme` types
- [ ] 4.5 Implement `SolarProvider`, `StarProvider`, `TidalProvider`, `WeatherState`, `WeatherProvider` interfaces
- [ ] 4.6 Implement `UniverseClient` with typed `register` methods and `getScene`
- [ ] 4.7 Implement `UniverseScene` shape
- [ ] 4.8 Define `StarPacket` BitSchema (~11 bytes/star) with `packStars` / `unpackStars`
- [ ] 4.9 Define `CelestialBodyPacket` BitSchema with `packBodies` / `unpackBodies`
- [ ] 4.10 Add Zod schemas for all public option types
- [ ] 4.11 Add OTel spans on all public entrypoints
- [ ] 4.12 Add `AbortSignal` on all async provider interface methods
- [ ] 4.13 Write tests achieving 100% coverage
- [ ] 4.14 Write sibling `.md` docs and `README.md`
- [ ] 4.15 Configure explicit named barrel exports
- [ ] 4.16 Run lint, typecheck, test, build, and coverage gates
## 5. solar-system — Solar System Provider
- [ ] 5.1 Scaffold `@suaveplan/solar-system` package in `packages/geo/solar-system`
- [ ] 5.2 Add `astronomy-engine` dependency via catalog ref
- [ ] 5.3 Implement `AstronomyEngine` interface and default astronomy.js wrapper
- [ ] 5.4 Implement `createSolarSystemProvider` with injectable engine
- [ ] 5.5 Implement real body position computation (Sun, Moon, Mercury–Neptune, Pluto, Ceres)
- [ ] 5.6 Implement Keplerian propagator for custom planets with `OrbitalConfig`
- [ ] 5.7 Implement `angularDiameterArcmin`, `apparentMagnitude`, `heliocentricPosition` fields
- [ ] 5.8 Implement `getSunrise`, `getSunset` in UTC
- [ ] 5.9 Implement `getSeason`, `getNextEquinox`, `getNextSolstice` (default Earth, configurable planet)
- [ ] 5.10 Implement `getLunarPhase`, `predictEclipse`, `getConstellation`
- [ ] 5.11 Apply atmospheric extinction for surface observers with `AtmosphereConfig`
- [ ] 5.12 Add Zod schemas, OTel spans, `AbortSignal`
- [ ] 5.13 Write tests achieving 100% coverage (inject deterministic engine for unit tests)
- [ ] 5.14 Write sibling `.md` docs and `README.md`
- [ ] 5.15 Configure explicit named barrel exports
- [ ] 5.16 Run lint, typecheck, test, build, and coverage gates
## 6. stars — Star Catalog Provider
- [ ] 6.1 Scaffold `@suaveplan/stars` package in `packages/geo/stars`
- [ ] 6.2 Implement `RawStar` type with optional proper motion and parallax
- [ ] 6.3 Implement `StarCatalogSource` interface
- [ ] 6.4 Bitpack BSC5 catalog at build time into compact binary (~100 KB)
- [ ] 6.5 Implement `createBsc5Source` returning offline `StarCatalogSource`
- [ ] 6.6 Implement `StarProviderImpl` with priority-ordered sources
- [ ] 6.7 Implement horizon frame computation from `ObserverPosition`
- [ ] 6.8 Implement `getVisibleStars` with hemisphere query, altitude filter, magnitude filter, brightness sort
- [ ] 6.9 Implement `queryCone` returning `CatalogStar[]` without observer fields
- [ ] 6.10 Implement atmospheric extinction via Pogson's formula and Kasten–Young air mass
- [ ] 6.11 Implement `getStar` by catalog ID
- [ ] 6.12 Define `SurrealAdapterLike`, `SurrealStarProviderOptions`, `createSurrealStarSource` (structural interface, no hard dep)
- [ ] 6.13 Define deferred factory types: `createHipparcosSource`, `createTycho2Source`, `createGaiaSource` with `fetchFn` and `cacheFn`
- [ ] 6.14 Add Zod schemas, OTel spans, `AbortSignal`
- [ ] 6.15 Write tests achieving 100% coverage
- [ ] 6.16 Write sibling `.md` docs and `README.md`
- [ ] 6.17 Configure explicit named barrel exports (ensure `stars/catalogs/bsc5` is optional)
- [ ] 6.18 Run lint, typecheck, test, build, and coverage gates
## 7. tides — Enterprise Rewrite
- [ ] 7.1 Scaffold `@suaveplan/tides` package in `packages/geo/tides`
- [ ] 7.2 Preserve TICON harmonic engine, station data, and provider logic
- [ ] 7.3 Preserve ADMIRALTY provider logic and station data
- [ ] 7.4 Implement `createTiconProvider` returning `TidalProvider`
- [ ] 7.5 Implement `createAdmiraltyProvider(options)` with injectable `fetchFn`, `clockFn`, optional `cacheFn`
- [ ] 7.6 Implement `createPhysicsBasedProvider` for any `PlanetConfig` with satellites
- [ ] 7.7 Implement `tidalHeight` with multi-satellite superposition
- [ ] 7.8 Implement `satelliteTidalContribution` for per-satellite visualisation
- [ ] 7.9 Implement `TidalProviderImpl` routing Earth by name to `earthProviders`, non-Earth to `physicsProvider`
- [ ] 7.10 Add Zod schemas for all public option types
- [ ] 7.11 Add `TidesError` extending `@suaveplan/error` with stable `TIDES.*` codes
- [ ] 7.12 Add `AbortSignal` on all async methods
- [ ] 7.13 Add OTel spans, counters, histograms on every provider call
- [ ] 7.14 Write property-based tests (fast-check) on harmonic prediction engine
- [ ] 7.15 Write tests achieving 100% coverage
- [ ] 7.16 Write sibling `.md` docs and `README.md`
- [ ] 7.17 Configure explicit named barrel exports
- [ ] 7.18 Run lint, typecheck, test, build, and coverage gates
- [ ] 7.19 Run dependent sweep: `bunx turbo test --filter=...@suaveplan/tides`
## 8. Cross-cutting and Archive
- [ ] 8.1 Update `STATUS.md`: mark `geo`, `geo-planet`, `geo-earth`, `universe`, `solar-system`, `stars`, `tides` as completed
- [ ] 8.2 Update `SUMMARY.md` with per-package paragraphs
- [ ] 8.3 Update `MANIFEST.md` via `bun scripts/folder-reorg/generate-manifest.ts`
- [ ] 8.4 Update `MISSING.yml` via `bun scripts/generate-missing.ts`
- [ ] 8.5 Run `bunx markdownlint-cli2 --fix` on all touched root docs
- [ ] 8.6 Run `bunx turbo lint typecheck test` across the full universe-family filter set
- [ ] 8.7 Verify zero banned patterns in all new package `src/` directories
- [ ] 8.8 Verify zero `export *` in feature barrels
- [ ] 8.9 Verify zero direct `vitest`/`bun:test` imports in non-exempt packages
- [ ] 8.10 Verify every `.ts` has a sibling `.md`
- [ ] 8.11 Verify every package has a `README.md`
- [ ] 8.12 Verify no bare version strings in any new `package.json`
- [ ] 8.13 Archive change: move `openspec/changes/enterprise-universe-family` to `openspec/archive/2026-04-25-enterprise-universe-family`