# UniverseClient _(no submodules)_ ## Purpose `UniverseClient` is the single composition point that aggregates solar, star, tidal, and weather provider implementations into one cohesive `UniverseScene` snapshot for a given observer at a given instant. It owns no domain knowledge of its own — every datum on the returned scene is delegated to a provider the consumer registered. This indirection lets the same client power a real-time astronomy app, a deterministic game world, a server-rendered ephemeris page, or a unit test, simply by swapping providers. The client is intentionally minimal: no caching, no retry, no fan-out; it propagates the caller's `AbortSignal` to every registered provider and assembles their results into one `UniverseScene` value. Tides are skipped when the observer is in space (no surface to compute against). Unregistered domains contribute empty arrays (`bodies`, `stars`) or are omitted entirely (`tides`, `weather`). ## Features - Fluent registration: `registerSolar` / `registerStars` / `registerTides` / `registerWeather` chain on `this`. - One-shot scene assembly via `getScene(observer, time, signal?)`. - AbortSignal forwarded to every provider call. - Tides automatically skipped for `kind: "space"` observers. - Optional fields (`tides`, `weather`) only present when their provider is registered and produces a value. - Provider-agnostic — interfaces only, zero base classes. ## Usage: Basic Example ```ts import { UniverseClient } from "@suaveplan/universe"; const client = new UniverseClient(); const scene = await client.getScene( { kind: "space", heliocentricVec: { x: 0, y: 0, z: 0 }, frame: "icrs" }, new Date() ); // scene.bodies === [], scene.stars === [], scene.tides/weather undefined ``` ## Usage: Advanced Example ```ts import { UniverseClient, type UniverseScene } from "@suaveplan/universe"; import type { SolarProvider, StarProvider, WeatherProvider, } from "@suaveplan/universe"; const solar: SolarProvider = { async getBodies() { return []; }, async getBody(name) { return { name, type: "star", altitude: 45, azimuth: 180, apparentMagnitude: -26.7, angularDiameterArcmin: 32, distanceAu: 1, heliocentricPosition: { x: 0, y: 0, z: 0 }, }; }, async getSunrise() { return new Date("2026-01-01T06:00:00Z"); }, async getSunset() { return new Date("2026-01-01T18:00:00Z"); }, }; const stars: StarProvider = { async getVisibleStars() { return []; }, async queryCone() { return []; }, async getStar() { return null; }, }; const weather: WeatherProvider = { async getCurrent() { return { temperature: 18, pressure: 1015, humidity: 60 }; }, }; const controller = new AbortController(); const client = new UniverseClient() .registerSolar(solar) .registerStars(stars) .registerWeather(weather); const scene: UniverseScene = await client.getScene( { kind: "surface", planet: { name: "Earth", mass: 1, radius: 1, polarRadius: 1, surfaceGravity: 1, rotationPeriod: 1, axialTilt: 23.4, atmosphere: null, satellites: [], }, coord: { latitude: 51.5, longitude: -0.12 }, }, new Date(), controller.signal ); ``` ## API Reference ### `class UniverseClient` | Method | Returns | Description | |---|---|---| | `registerSolar(provider)` | `this` | Register a `SolarProvider`. | | `registerStars(provider)` | `this` | Register a `StarProvider`. | | `registerTides(provider)` | `this` | Register a `TidalProvider`. | | `registerWeather(provider)` | `this` | Register a `WeatherProvider`. | | `getScene(observer, time, signal?)` | `Promise` | Assemble the scene. Forwards `signal` to every provider. | ### `interface UniverseScene` | Property | Type | Notes | |---|---|---| | `bodies` | `CelestialBody[]` | Empty when no solar provider. | | `stars` | `ObservedStar[]` | Empty when no star provider. | | `tides?` | `TideExtreme[]` | Omitted for space observers and when no tidal provider. | | `weather?` | `WeatherState` | Omitted when no weather provider. | The client throws (propagates) any error a provider throws, including `AbortError` from a fired signal. ## Implementation Notes `UniverseClient` is a coordinator, not a cache. Each `getScene` call invokes every registered provider unconditionally (subject to the space-tide skip). Consumers that need memoisation, retry, or rate limiting should compose those policies into the providers themselves rather than wrapping the client. Re-exporting the four provider interfaces and `WeatherState` from this module (in addition to `@suaveplan/universe/providers/providers`) is intentional so consumers can `import { type SolarProvider } from "@suaveplan/universe"` without reaching into a sub-path.