// ─── Primitive geometry ────────────────────────────────────────────────────── /** 3-element position or velocity vector in km or km/s */ export type Vec3 = [number, number, number]; /** Position + velocity state vector from the ephemeris */ export interface StateVector { position: Vec3; // km, in the frame determined by context velocity: Vec3; // km/s } /** Azimuth + altitude in degrees */ export interface AzAlt { /** Degrees from North, measured clockwise (0 = N, 90 = E, 180 = S, 270 = W) */ azimuth: number; /** Degrees above the horizon (negative = below) */ altitude: number; } // ─── Kernel-free moon results ───────────────────────────────────────────────── /** * Topocentric moon position from getMoonPosition(). * Computed via Meeus Ch. 47 (no kernel required). * Accuracy: azimuth/altitude ~0.3°, distance ~300 km. */ export interface MoonPosition { /** Azimuth in degrees from North, measured clockwise (0 = N, 90 = E, 180 = S, 270 = W) */ azimuth: number; /** Apparent altitude in degrees above the horizon (atmospheric refraction applied) */ altitude: number; /** Distance from Earth center to Moon center, km */ distance: number; /** * Parallactic angle in radians. * The angle between the great circle through the Moon and zenith, and the great circle * through the Moon and the north celestial pole. Positive east of the meridian. */ parallacticAngle: number; } /** * Moon illumination from getMoonIllumination(). * Computed via Meeus Ch. 47/48 (no kernel required). * Accuracy: fraction ~0.5%, phase fraction ~0.003. */ export interface MoonIlluminationResult { /** Illuminated fraction of the Moon disk, 0 (new moon) to 1 (full moon) */ fraction: number; /** * Phase cycle fraction in [0, 1): * 0 = new moon, 0.25 = first quarter, 0.5 = full moon, 0.75 = last quarter */ phase: number; /** * Position angle of the midpoint of the bright limb, measured eastward from * the north celestial pole, in radians. Matches the suncalc convention. */ angle: number; /** True while elongation is increasing (new moon toward full moon) */ isWaxing: boolean; } // ─── Time ──────────────────────────────────────────────────────────────────── /** All relevant time scale values for a single moment */ export interface TimeScales { utc: Date; /** Julian Date in UTC */ jdUTC: number; /** Julian Date in Terrestrial Time (TT = TAI + 32.184s) */ jdTT: number; /** Julian Date in Barycentric Dynamical Time (used by JPL ephemerides) */ jdTDB: number; /** Julian Date in UT1 (Earth rotation time) */ jdUT1: number; /** TT - UT1 in seconds (delta-T) */ deltaT: number; /** TAI - UTC in seconds (leap seconds count) */ deltaAT: number; } // ─── Observer ──────────────────────────────────────────────────────────────── /** Observer location and environmental parameters */ export interface Observer { /** Geodetic latitude in degrees (north positive) */ lat: number; /** Longitude in degrees (east positive) */ lon: number; /** Height above WGS84 ellipsoid in meters */ elevation: number; /** Optional label for the location */ name?: string; /** * Override TT - UT1 in seconds. * When provided, used directly. Otherwise the built-in polynomial is used. * For maximum accuracy, supply the current IERS value (typically within ±0.9s). */ deltaT?: number; /** * Override UT1 - UTC in seconds (from IERS Bulletin A). * Takes precedence over deltaT when both are provided. */ ut1utc?: number; /** Atmospheric pressure in millibars (default 1013.25) */ pressure?: number; /** Ambient temperature in Celsius (default 15) */ temperature?: number; } // ─── Crescent geometry ─────────────────────────────────────────────────────── /** * The five geometric quantities used by all major crescent visibility criteria. * All values computed at best time (T_b) unless noted. */ export interface CrescentGeometry { /** Arc of light: topocentric Sun-Moon angular separation (elongation), degrees */ ARCL: number; /** * Arc of vision: Moon airless altitude minus Sun airless altitude, degrees. * Used as the primary visibility discriminant in both Yallop and Odeh. */ ARCV: number; /** * Relative azimuth: Sun azimuth minus Moon azimuth, normalized to [-180, 180], degrees. * Positive = Moon north of Sun. */ DAZ: number; /** * Topocentric crescent width in arc minutes. * Used directly in Odeh's polynomial V expression. */ W: number; /** Moonset minus sunset in minutes. Negative = Moon sets before Sun (no sighting possible). */ lag: number; } // ─── Yallop q-test ─────────────────────────────────────────────────────────── /** Yallop q-test visibility category (NAO Technical Note 69) */ /** Yallop visibility category (A = easily visible, F = below Danjon limit). */ export type YallopCategory = "A" | "B" | "C" | "D" | "E" | "F"; /** * Published q thresholds (Yallop 1997, NAO TN 69): * A: q > +0.216 — Easily visible to the naked eye * B: q > -0.014 — Visible under perfect conditions * C: q > -0.160 — May need optical aid to find; visible to naked eye * D: q > -0.232 — Optical aid needed; will not be visible to naked eye * E: q > -0.293 — Not visible even with telescope * F: q <= -0.293 — Below Danjon limit (Moon too close to Sun) */ export const YALLOP_THRESHOLDS = { A: 0.216, B: -0.014, C: -0.16, D: -0.232, E: -0.293, } as const; /** * Human-readable descriptions for each Yallop visibility category (A–F). * Sourced from Yallop (NAO TN 69, 1997). */ export const YALLOP_DESCRIPTIONS: Record = { A: "Easily visible to the naked eye", B: "Visible under perfect conditions", C: "May need optical aid to find; naked eye possible", D: "Optical aid needed; naked eye not possible", E: "Not visible even with telescope under good conditions", F: "Below Danjon limit — crescent cannot form", }; export interface YallopResult { /** The continuous q parameter (higher = more visible) */ q: number; /** Visibility category A through F */ category: YallopCategory; /** Human-readable interpretation */ description: string; /** True for categories A and B */ isVisibleNakedEye: boolean; /** True for categories C and D */ requiresOpticalAid: boolean; /** True for category F */ isBelowDanjonLimit: boolean; /** Topocentric crescent width W' used in the q formula, arc minutes */ Wprime: number; } // ─── Odeh criterion ────────────────────────────────────────────────────────── /** Odeh visibility zone (Experimental Astronomy 2006) */ /** Odeh visibility zone (A = naked eye visible, D = not visible with any aid). */ export type OdehZone = "A" | "B" | "C" | "D"; /** * Published V thresholds (Odeh 2006): * A: V >= 5.65 — Visible with naked eye * B: V >= 2.00 — Visible with optical aid; may be seen with naked eye * C: V >= -0.96 — Visible with optical aid only * D: V < -0.96 — Not visible even with optical aid */ export const ODEH_THRESHOLDS = { A: 5.65, B: 2.0, C: -0.96, } as const; /** * Human-readable descriptions for each Odeh visibility zone (A–D). * Sourced from Odeh (Experimental Astronomy, 2006). */ export const ODEH_DESCRIPTIONS: Record = { A: "Visible with naked eye", B: "Visible with optical aid; may be seen with naked eye under excellent conditions", C: "Visible with optical aid only", D: "Not visible even with optical aid", }; export interface OdehResult { /** * Continuous visibility parameter V = ARCV - (arcv_minimum(W)). * Positive = crescent exceeds minimum visibility threshold. */ V: number; /** Visibility zone A through D */ zone: OdehZone; /** Human-readable interpretation */ description: string; /** True for zone A */ isVisibleNakedEye: boolean; /** True for zones A and B */ isVisibleWithOpticalAid: boolean; } // ─── Kernel-free visibility estimate ───────────────────────────────────────── /** * Kernel-free Odeh-based crescent visibility estimate from getMoonVisibilityEstimate(). * Computed via Meeus Ch. 47 approximation at the given observation time. * For DE442S-quality results, use getMoonSightingReport(). */ export interface MoonVisibilityEstimate { /** * Odeh V parameter: V = ARCV − f(W). * Positive = crescent exceeds minimum visibility threshold. */ V: number; /** Visibility zone A through D */ zone: OdehZone; /** Human-readable zone description */ description: string; /** True for zone A */ isVisibleNakedEye: boolean; /** True for zones A and B */ isVisibleWithOpticalAid: boolean; /** Arc of light (Sun-Moon elongation) in degrees */ ARCL: number; /** Arc of vision (Moon airless altitude minus Sun airless altitude) in degrees */ ARCV: number; /** Topocentric crescent width in arc minutes */ W: number; /** True when Moon is above the horizon at the given time */ moonAboveHorizon: boolean; /** Always true: computed via Meeus approximation, not DE442S */ isApproximate: true; } /** * Combined kernel-free moon snapshot from getMoon(). * Bundles phase, position, illumination, and a quick visibility estimate * into a single call. */ export interface MoonSnapshot { /** Phase name, illumination, age, and next events */ phase: MoonPhaseResult; /** Topocentric az/alt, distance, parallactic angle */ position: MoonPosition; /** Illumination fraction, phase cycle, bright limb angle, waxing/waning */ illumination: MoonIlluminationResult; /** Quick Odeh-based crescent visibility estimate */ visibility: MoonVisibilityEstimate; } // ─── Moon phase ────────────────────────────────────────────────────────────── export type MoonPhaseName = | "new-moon" | "waxing-crescent" | "first-quarter" | "waxing-gibbous" | "full-moon" | "waning-gibbous" | "last-quarter" | "waning-crescent"; export interface MoonPhaseResult { /** Named phase based on illumination and waxing/waning state */ phase: MoonPhaseName; /** Human-readable phase name, e.g. "Waxing Crescent" */ phaseName: string; /** Moon phase emoji symbol, e.g. "🌒" */ phaseSymbol: string; /** Illuminated fraction 0-100 (percent) */ illumination: number; /** Hours since last new moon */ age: number; /** Ecliptic longitude of the Moon minus the Sun, degrees [0, 360) */ elongationDeg: number; /** True when Moon is moving away from the Sun (illumination increasing) */ isWaxing: boolean; /** UTC date of the next new moon */ nextNewMoon: Date; /** UTC date of the next full moon */ nextFullMoon: Date; /** UTC date of the previous new moon */ prevNewMoon: Date; } // ─── Event times ───────────────────────────────────────────────────────────── export interface SunMoonEvents { /** UTC time of sunset for the given date at the observer's location */ sunsetUTC: Date | null; /** UTC time of moonset for the given date at the observer's location */ moonsetUTC: Date | null; /** UTC time of sunrise */ sunriseUTC: Date | null; /** UTC time of moonrise */ moonriseUTC: Date | null; /** UTC time when civil twilight ends (Sun at -6°) */ civilTwilightEndUTC: Date | null; /** UTC time when nautical twilight ends (Sun at -12°) */ nauticalTwilightEndUTC: Date | null; /** UTC time when astronomical twilight ends (Sun at -18°) */ astronomicalTwilightEndUTC: Date | null; } // ─── Full moon sighting report ──────────────────────────────────────────────── export interface MoonSightingReport { /** Date for which the sighting report was computed */ date: Date; /** Observer location used */ observer: Observer; // Event times sunsetUTC: Date | null; moonsetUTC: Date | null; /** Moonset minus sunset, in minutes. Null if either event is null. */ lagMinutes: number | null; /** Best observation time (Odeh/Yallop: T_s + 4/9 * Lag) */ bestTimeUTC: Date | null; /** Conservative observation window [bestTime - 20min, bestTime + 20min] */ bestTimeWindowUTC: [Date, Date] | null; // At best time /** Topocentric Moon position at best time */ moonPosition: AzAlt | null; /** Topocentric Sun position at best time */ sunPosition: AzAlt | null; /** Moon illumination percent at best time */ illumination: number | null; /** Hours since conjunction (new moon) */ moonAge: number | null; // Crescent geometry at best time geometry: CrescentGeometry | null; // Visibility criteria results yallop: YallopResult | null; odeh: OdehResult | null; // Sighting guidance /** * Plain-language direction for observers. * Includes where to look (azimuth, altitude), when (best time), and what to expect. */ guidance: string; // Metadata /** Source ephemeris used for this calculation */ ephemerisSource: "DE442S" | "approximate"; /** Whether the Moon is even above the horizon at best time */ moonAboveHorizon: boolean | null; /** Whether sighting is geometrically possible (lag > 0, Moon above horizon at best time) */ sightingPossible: boolean; } // ─── Kernel configuration ───────────────────────────────────────────────────── /** * How to source a binary kernel file. * Used for both the planetary SPK (de442s.bsp) and leap-second kernel (naif0012.tls). */ export type KernelSource = | { type: "file"; path: string } | { type: "buffer"; data: ArrayBuffer; name: string } | { type: "url"; url: string } | { type: "auto" }; // auto-download from NAIF, cache in ~/.cache/moon-sighting export interface KernelConfig { /** Planetary SPK kernel — defaults to de442s.bsp via auto-download */ planetary?: KernelSource; /** Leap-second kernel — defaults to naif0012.tls via auto-download */ leapSeconds?: KernelSource; /** * Directory for the download cache. * Defaults to ~/.cache/moon-sighting on POSIX, %LOCALAPPDATA%\moon-sighting on Windows. */ cacheDir?: string; /** * SHA-256 checksum of de442s.bsp for download verification. * Bundled default matches the NAIF distribution as of 2024. */ checksumOverride?: string; } // ─── Top-level options ──────────────────────────────────────────────────────── export interface SightingOptions { /** Kernel acquisition configuration. Defaults to auto-download. */ kernels?: KernelConfig; /** * Best-time computation method. * 'heuristic' — T_b = T_sunset + 4/9 * Lag (Odeh/Yallop approximation, fast) * 'optimized' — scan sunset-to-moonset interval, maximize Odeh V parameter * Default: 'heuristic' */ bestTimeMethod?: "heuristic" | "optimized"; } // ─── WGS84 constants ───────────────────────────────────────────────────────── /** WGS84 reference ellipsoid parameters */ export const WGS84 = { /** Semi-major axis in meters */ a: 6378137.0, /** Inverse flattening */ invF: 298.257223563, /** Flattening */ f: 1 / 298.257223563, /** Semi-minor axis in meters */ get b() { return this.a * (1 - this.f); }, /** First eccentricity squared */ get e2() { return 2 * this.f - this.f * this.f; }, } as const; // ─── Internal ephemeris types ───────────────────────────────────────────────── /** A segment in a JPL SPK (DAF) kernel file */ export interface SpkSegment { /** NAIF body ID of the target body */ target: number; /** NAIF body ID of the center body */ center: number; /** Reference frame code */ frame: number; /** SPK data type (2 = Chebyshev position only, 3 = Chebyshev position + velocity) */ dataType: 2 | 3; /** Segment start time in ET seconds past J2000 */ startET: number; /** Segment end time in ET seconds past J2000 */ endET: number; /** Byte offset of the data array in the file */ dataOffset: number; /** Number of double-precision values in the data array */ dataSize: number; } /** A decoded Chebyshev record from a Type 2 or Type 3 SPK segment */ export interface ChebRecord { /** Midpoint of the record interval in ET seconds past J2000 */ mid: number; /** Half-width of the record interval in seconds */ radius: number; /** Chebyshev coefficients for X, Y, Z [3][degree+1] */ coeffs: Float64Array[]; /** Degree of the polynomial */ degree: number; }