Initial Commit
This commit is contained in:
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user