_(no submodules)_ ## Purpose The `atmospheric` module provides the two physics primitives every planetary renderer, sky-colour solver, and observation-quality estimator needs: the local density of an atmospheric column at altitude, and the relative path length light travels through that column for a given elevation angle. Both functions accept a `PlanetConfig` so the same code path serves Earth, Mars, Titan, or any other modelled body. These primitives are intentionally pure and free of vendor coupling so they can be reused by `@suaveplan/universe`, `@suaveplan/gaming-environment-lighting`, server-side astronomical computations, and offline analysis without dragging a graphics runtime into the call site. ## Features - `atmosphericDensity(planet, altitudeMetres)` — exponential isothermal scale-height model that returns `0` for airless bodies - `airMass(altitudeDeg)` — Kasten–Young 1989 air-mass approximation valid from zenith down to the horizon - Numerical-stable horizon handling: negative altitudes are clamped to `0` so the formula remains finite at the bound - Pure functions: no I/O, no side effects, no global state — safe for `Worker` threads, SSR, and deterministic tests ## Usage ### Basic Example ```ts import { atmosphericDensity } from "@suaveplan/geo-planet"; import type { PlanetConfig } from "@suaveplan/geo-planet"; const earth: PlanetConfig = { name: "Earth", mass: 5.972e24, radius: 6_378_137, polarRadius: 6_356_752, surfaceGravity: 9.81, rotationPeriod: 86_164, axialTilt: 23.44, atmosphere: { surfacePressure: 101_325, scaleHeight: 8_500, rayleighCoefficient: 5.8e-6, mieCoefficient: 21e-6, extinctionPerAirMass: 0.13, }, satellites: [], }; const surface = atmosphericDensity(earth, 0); // 101325 Pa const cruise = atmosphericDensity(earth, 11_000); // ~27 kPa ``` ### Advanced Example ```ts import { airMass, atmosphericDensity } from "@suaveplan/geo-planet"; import type { PlanetConfig } from "@suaveplan/geo-planet"; function transmittance( planet: PlanetConfig, altitudeMetres: number, sunElevationDeg: number, ): number { if (!planet.atmosphere) return 1; const density = atmosphericDensity(planet, altitudeMetres); const am = airMass(sunElevationDeg); const tau = planet.atmosphere.extinctionPerAirMass * am; return Math.exp(-tau) * (density / planet.atmosphere.surfacePressure); } // Use at every camera step in a sky shader, or to score // observability for ground-station pass scheduling. ``` ## API Reference ### `atmosphericDensity(planet: PlanetConfig, altitudeMetres: number): number` Returns the local pressure (`Pa`) using `p₀ · exp(-h / H)` where `p₀` is `planet.atmosphere.surfacePressure` and `H` is `planet.atmosphere.scaleHeight`. Returns `0` when `planet.atmosphere === null`. No clamping is applied to altitude; negative altitudes (subterranean, sub-sea-level) yield pressures above `p₀`, which is the physically correct extrapolation for a barometric law. ### `airMass(altitudeDeg: number): number` Returns the Kasten–Young air mass: `1 / (sin(alt) + 0.50572 · (alt + 6.07995)^-1.6364)` with `alt` in degrees. Negative altitudes are clamped to `0` so the result remains finite at the horizon (returning ≈ `38`). At zenith the return value approaches `1`. ## Implementation Notes The density function uses the isothermal barometric equation rather than a layered ISA model because every consumer in this project treats density as a smooth analytic function for shader sampling. The Kasten–Young formulation was chosen over the simpler `1 / sin(alt)` secant model because it remains stable down to the horizon and only diverges by ≈ `0.4%` from full atmospheric ray-tracing above 5° elevation. Both functions are allocation-free hot-path code; consumers can call them once per pixel without GC pressure.