## 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