pg_orrery/CLAUDE.md
Ryan Malloy b309980003 Add v0.18.0: Saturn ring tilt, penumbral eclipse, rise/set windows, angular rate
Four features, 10 new SQL functions (174 → 184 objects), 29 test suites:

Saturn ring tilt: saturn_ring_tilt() exposes sub-observer latitude B'.
planet_magnitude() for Saturn now includes Mallama & Hilton Eq. 10
ring correction (-2.60|sin B'| + 1.25 sin²B'), removing the ~1.5 mag
globe-only caveat. IAU 2000 pole direction, ecliptic J2000 projection.

Conical shadow model: Replaces cylindrical shadow with umbra/penumbra
cones using Sun's finite angular size. Four new functions:
satellite_in_penumbra(), satellite_shadow_state(),
satellite_next_penumbra_entry/exit(). Existing eclipse functions are
backward compatible via narrower (more accurate) umbra boundary.

Rise/set event windows: Three SRFs returning TABLE(event_time, event_type)
for all rise/set events within a time window — planet_rise_set_events(),
sun_rise_set_events(), moon_rise_set_events(). Follows predict_passes()
SRF pattern. Optional refracted parameter, 366-day window limit.

Angular separation rate: Vincenty formula extracted to reusable helper.
eq_angular_rate() for generic finite-difference rate, planet_angular_rate()
for solar system body convenience (1-minute dt, handles Sun/planets/Moon).
2026-02-27 23:52:06 -07:00

27 KiB

pg_orrery — A Database Orrery for PostgreSQL

What This Is

A database orrery — celestial mechanics types and functions for PostgreSQL. Native C extension using PGXS, 184 SQL objects (168 user-visible functions + 16 GiST support), 9 custom types, covering satellites (SGP4/SDP4), planets (VSOP87 + optional JPL DE441), Moon (ELP2000-82B), 19 planetary moons (L1.2/TASS17/GUST86/MarsSat), stars (with proper motion and annual parallax), comets, asteroids (MPC catalog), Jupiter radio bursts, interplanetary Lambert transfers, equatorial RA/Dec coordinates with GiST-indexed angular separation, atmospheric refraction, annual stellar aberration, light-time correction, rise/set prediction (geometric + refracted + event windows) with status diagnostics, IAU constellation identification with full name lookup (Roman 1987), twilight dawn/dusk (civil/nautical/astronomical), lunar phase (angle, illumination, name, age), planet apparent magnitude with Saturn ring correction (Mallama & Hilton 2018), solar elongation, planet phase fraction, satellite eclipse prediction (conical shadow with penumbra), observing night quality assessment, lunar optical libration (Meeus Ch. 53), and angular separation rate.

Current version: 0.18.0 Repository: https://git.supported.systems/warehack.ing/pg_orrery Documentation: https://pg-orrery.warehack.ing

Build System

make PG_CONFIG=/usr/bin/pg_config           # Compile with PGXS
sudo make install PG_CONFIG=/usr/bin/pg_config  # Install extension
make installcheck PG_CONFIG=/usr/bin/pg_config  # Run 29 regression test suites

Requires: PostgreSQL 17 development headers, GCC, Make.

Docker

make docker-build   # Build standalone image (pg17 + pg_orrery)
make docker-test    # Smoke test the image
make docker-push    # Push to git.supported.systems registry

Image: git.supported.systems/warehack.ing/pg_orrery:pg17

Project Layout

pg_orrery.control                # Extension metadata (version 0.18.0)
Makefile                        # PGXS build + Docker targets
sql/
  pg_orrery--0.1.0.sql           # v0.1.0: satellite types/functions/operators
  pg_orrery--0.2.0.sql           # v0.2.0: solar system (57 functions)
  pg_orrery--0.3.0.sql           # v0.3.0: DE ephemeris (68 functions)
  pg_orrery--0.4.0.sql           # v0.4.0: orbit determination
  pg_orrery--0.5.0.sql           # v0.5.0: SP-GiST orbital trie
  pg_orrery--0.6.0.sql           # v0.6.0: conjunction screening
  pg_orrery--0.7.0.sql           # v0.7.0: GiST improvements
  pg_orrery--0.8.0.sql           # v0.8.0: orbital_elements type + MPC parser (82 functions)
  pg_orrery--0.9.0.sql           # v0.9.0: equatorial type, refraction, proper motion, light-time (106 functions)
  pg_orrery--0.10.0.sql          # v0.10.0: angular separation, cone search, apparent functions (114 functions)
  pg_orrery--0.11.0.sql          # v0.11.0: orbital_elements constructors, moon equatorial (120 functions)
  pg_orrery--0.12.0.sql          # v0.12.0: equatorial GiST, DE moon equatorial (132 objects)
  pg_orrery--0.13.0.sql          # v0.13.0: nutation, make_equatorial, rise/set (141 objects)
  pg_orrery--0.14.0.sql          # v0.14.0: refracted rise/set, constellation ID (147 objects)
  pg_orrery--0.15.0.sql          # v0.15.0: constellation full name, rise/set status (151 objects)
  pg_orrery--0.16.0.sql          # v0.16.0: twilight, lunar phase, planet magnitude (162 objects)
  pg_orrery--0.17.0.sql          # v0.17.0: elongation, phase, eclipse, night quality, libration (174 objects)
  pg_orrery--0.18.0.sql          # v0.18.0: ring tilt, penumbral eclipse, rise/set windows, angular rate (184 objects)
  pg_orrery--0.1.0--0.2.0.sql    # Migration: v0.1.0 → v0.2.0 (adds solar system)
  pg_orrery--0.2.0--0.3.0.sql    # Migration: v0.2.0 → v0.3.0 (adds DE ephemeris)
  pg_orrery--0.3.0--0.4.0.sql    # Migration: v0.3.0 → v0.4.0
  pg_orrery--0.4.0--0.5.0.sql    # Migration: v0.4.0 → v0.5.0
  pg_orrery--0.5.0--0.6.0.sql    # Migration: v0.5.0 → v0.6.0
  pg_orrery--0.6.0--0.7.0.sql    # Migration: v0.6.0 → v0.7.0
  pg_orrery--0.7.0--0.8.0.sql    # Migration: v0.7.0 → v0.8.0 (orbital_elements type)
  pg_orrery--0.8.0--0.9.0.sql    # Migration: v0.8.0 → v0.9.0 (equatorial, refraction, proper motion, light-time)
  pg_orrery--0.9.0--0.10.0.sql   # Migration: v0.9.0 → v0.10.0 (angular separation, cone search)
  pg_orrery--0.10.0--0.11.0.sql  # Migration: v0.10.0 → v0.11.0 (constructors, moon equatorial)
  pg_orrery--0.11.0--0.12.0.sql  # Migration: v0.11.0 → v0.12.0 (equatorial GiST, DE moon equatorial)
  pg_orrery--0.12.0--0.13.0.sql  # Migration: v0.12.0 → v0.13.0 (nutation, make_equatorial, rise/set)
  pg_orrery--0.13.0--0.14.0.sql  # Migration: v0.13.0 → v0.14.0 (refracted rise/set, constellation ID)
  pg_orrery--0.14.0--0.15.0.sql  # Migration: v0.14.0 → v0.15.0 (constellation full name, rise/set status)
  pg_orrery--0.15.0--0.16.0.sql  # Migration: v0.15.0 → v0.16.0 (twilight, lunar phase, planet magnitude)
  pg_orrery--0.16.0--0.17.0.sql  # Migration: v0.16.0 → v0.17.0 (elongation, phase, eclipse, night quality, libration)
  pg_orrery--0.17.0--0.18.0.sql  # Migration: v0.17.0 → v0.18.0 (ring tilt, penumbral eclipse, rise/set windows, angular rate)
src/
  pg_orrery.c                    # PG_MODULE_MAGIC + _PG_init() (GUC registration)
  types.h                       # All struct definitions + constants + DE body ID mapping
  astro_math.h                  # Shared astronomical helpers + observe_from_geocentric()
  # --- Satellite (v0.1.0) ---
  tle_type.c                    # TLE custom type (I/O, binary, 15 accessors)
  eci_type.c                    # ECI position type + geodetic/topocentric types
  observer_type.c               # Observer type with flexible string parsing
  sgp4_funcs.c                  # sgp4_propagate(), _safe(), _series(), tle_distance()
  coord_funcs.c                 # eci_to_geodetic(), eci_to_topocentric(), ground_track()
  pass_funcs.c                  # next_pass(), predict_passes(), predict_passes_refracted(), pass_visible()
  gist_tle.c                    # GiST operator class for TLE (&&, <->)
  gist_equatorial.c              # GiST operator class for equatorial (KNN <->)
  # --- Solar System (v0.2.0) ---
  vsop87.c / vsop87.h           # VSOP87 planetary ephemeris (Bretagnon 1988)
  elp82b.c / elp82b.h           # ELP2000-82B lunar ephemeris (Chapront 1988)
  precession.c / precession.h   # IAU 1976 precession (Lieske 1979)
  sidereal_time.c / .h          # GMST calculation (Vallado Eq. 3-47)
  elliptic_to_rectangular.c/.h  # Orbital element conversions
  planet_funcs.c                # planet_observe(), planet_heliocentric(), sun/moon_observe(), _equatorial(), _apparent()
  star_funcs.c                  # star_observe(), star_observe_safe(), star_equatorial(), star_observe_pm(), star_equatorial_pm()
  kepler_funcs.c                # kepler_propagate(), comet_observe()
  kepler.h                      # Shared Kepler solver interface (kepler_position())
  orbital_elements_type.c       # orbital_elements type, MPC parser, small_body_observe/equatorial/apparent()
  equatorial_funcs.c            # equatorial type I/O, accessors, satellite/planet/sun/moon RA/Dec, angular rate
  refraction_funcs.c            # atmospheric_refraction(), _ext(), topo_elevation_apparent()
  rise_set_funcs.c              # planet/sun/moon rise/set (geometric + refracted) + twilight dawn/dusk + event window SRFs
  constellation_data.h / .c     # Roman (1987) IAU boundary table (CDS VI/42, 357 segments)
  constellation_funcs.c         # constellation() from equatorial or RA/Dec
  lunar_phase_funcs.c           # moon_phase_angle(), moon_illumination(), moon_phase_name(), moon_age()
  magnitude_funcs.c             # planet_magnitude() (with Saturn ring correction), solar_elongation(), planet_phase(), saturn_ring_tilt()
  eclipse_funcs.c               # satellite eclipse prediction (conical shadow with penumbra, Vallado §5.3)
  libration.h / libration_funcs.c # lunar optical libration (Meeus Ch. 53)
  l12.c / l12.h                 # L1.2 Galilean moon theory (Lieske 1998)
  tass17.c / tass17.h           # TASS 1.7 Saturn moon theory (Vienne & Duriez 1995)
  gust86.c / gust86.h           # GUST86 Uranus moon theory (Laskar & Jacobson 1987)
  marssat.c / marssat.h         # MarsSat Mars moon theory (Jacobson 2014)
  moon_funcs.c                  # galilean/saturn/uranus/mars_moon_observe()
  radio_funcs.c                 # io_phase_angle(), jupiter_cml(), burst_probability()
  lambert.c / lambert.h         # Lambert transfer solver (Izzo 2015)
  transfer_funcs.c              # lambert_transfer(), lambert_c3()
  # --- JPL DE Ephemeris (v0.3.0) ---
  de_reader.h / de_reader.c     # Clean-room JPL DE binary reader (Chebyshev/Clenshaw)
  eph_provider.h / eph_provider.c # Provider dispatch, GUC, lazy init, frame rotation
  de_funcs.c                    # All _de() SQL function implementations (incl. moon equatorial DE)
  sgp4/                         # Vendored SGP4/SDP4 (Bill Gray's sat_code, MIT license)
    sgp4.c                      # Near-earth propagator (period < 225 min)
    sdp4.c                      # Deep-space propagator (period >= 225 min)
    deep.c                      # Lunar/solar perturbations, resonance integration
    common.c                    # Shared initialization (Brouwer mean elements, Kepler solver)
    basics.c                    # select_ephemeris(), FMod2p()
    get_el.c                    # TLE parsing (parse_elements(), checksum)
    tle_out.c                   # TLE output (write_elements_in_tle_format())
    norad.h / norad_in.h        # Public + internal headers
    PROVENANCE.md               # Vendoring decision, modifications, verification
    LICENSE                     # MIT license (Bill Gray / Project Pluto)
test/
  sql/                          # 29 regression test suites
  expected/                     # Expected output
  data/vallado_518.json         # 518 Vallado test vectors (AIAA 2006-6753-Rev1)
docs/
  DESIGN.md                     # Architecture decisions, theory-to-code mappings
  Dockerfile                    # Starlight docs site (Astro + Caddy)
  package.json                  # Docs site dependencies
  astro.config.mjs              # Starlight configuration
  src/content/docs/             # MDX documentation pages

Type System

All types are fixed-size, STORAGE = plain, ALIGNMENT = double. No TOAST overhead.

Type Bytes Description
tle 112 Parsed mean orbital elements for SGP4/SDP4
eci_position 48 x,y,z + vx,vy,vz (km, km/s) in TEME frame
geodetic 24 lat, lon (radians), alt (km) above WGS-84
topocentric 32 azimuth, elevation, range, range_rate
observer 24 lat, lon (radians), alt_m (meters)
pass_event 48 AOS/MAX/LOS times + max_el + AOS/LOS azimuth
heliocentric 24 x, y, z in AU (ecliptic J2000 frame)
orbital_elements 72 Classical Keplerian elements for comets/asteroids (epoch, q, e, inc, omega, Omega, tp, H, G)
equatorial 24 Apparent RA (hours), Dec (degrees), distance (km) — of date

Function Domains (184 SQL objects)

Domain Theory Key Functions Count
Satellite SGP4/SDP4 (Brouwer 1959) observe(), predict_passes(), eci_to_equatorial() 25
Planets VSOP87 (Bretagnon 1988) planet_observe(), planet_equatorial(), planet_observe_apparent() 7
Sun/Moon VSOP87 + ELP2000-82B sun_observe(), moon_observe(), sun/moon_equatorial() 6
Planetary moons L1.2, TASS17, GUST86, MarsSat galilean_observe(), saturn_moon_observe(), *_equatorial() 12
Stars J2000 + IAU 1976 precession star_observe(), star_equatorial(), star_observe_pm() 5
Comets/asteroids Two-body Keplerian + MPC small_body_observe(), small_body_equatorial(), oe_from_mpc() 19
Refraction Bennett (1982) atmospheric_refraction(), predict_passes_refracted() 4
Equatorial spatial Vincenty formula eq_angular_distance(), eq_within_cone(), eq_angular_rate(), <-> 4
Jupiter radio Carr et al. (1983) jupiter_burst_probability() 3
Transfers Lambert (Izzo 2015) lambert_transfer(), lambert_c3() 2
DE ephemeris JPL DE440/441 (optional) planet_observe_de(), *_equatorial_de(), *_apparent_de() 23
GiST index (TLE) Altitude-band approximation && (overlap), <-> (distance) 8
GiST index (equatorial) Spherical bounding box <-> (KNN ordering) 8
Rise/set Bisection (60s scan) planet_next_rise(), sun_next_rise_refracted(), *_rise_set_status(), *_rise_set_events() 18
Twilight Sun depression angles sun_civil_dawn(), sun_nautical_dusk(), sun_astronomical_dawn() 6
Lunar phase VSOP87 + ELP2000-82B geometry moon_phase_angle(), moon_illumination(), moon_phase_name(), moon_age() 4
Planet magnitude Mallama & Hilton (2018) planet_magnitude(), saturn_ring_tilt() 2
Solar elongation VSOP87 geometry solar_elongation() 1
Planet phase VSOP87 geometry planet_phase() 1
Satellite eclipse Conical shadow (Vallado §5.3) satellite_is_eclipsed(), satellite_next_eclipse_entry(), satellite_shadow_state(), satellite_in_penumbra() 8
Observing quality Composite (twilight+Moon) observing_night_quality() 1
Lunar libration Meeus (1998) Ch. 53 moon_libration_longitude(), moon_libration(), moon_subsolar_longitude() 5
Constellation Roman (1987) CDS VI/42 constellation(), constellation_full_name() 3
Diagnostics -- pg_orrery_ephemeris_info() 1

All functions are PARALLEL SAFE. VSOP87/ELP82B functions are IMMUTABLE (compiled-in coefficients). DE functions are STABLE (external file dependency).

Body IDs

Planets (VSOP87 convention)

ID Body ID Body
0 Sun 5 Jupiter
1 Mercury 6 Saturn
2 Venus 7 Uranus
3 Earth 8 Neptune
4 Mars 10 Moon

Planetary Moons (per-family indexing)

  • Galilean (0-3): Io, Europa, Ganymede, Callisto
  • Saturn (0-7): Mimas, Enceladus, Tethys, Dione, Rhea, Titan, Iapetus, Hyperion
  • Uranus (0-4): Miranda, Ariel, Umbriel, Titania, Oberon
  • Mars (0-1): Phobos, Deimos

Constant Chain of Custody

The most critical design constraint. TLEs absorb geodetic model biases — using wrong constants silently corrupts positions by kilometers.

Rules

  1. SGP4 propagation: WGS-72 constants ONLY (mu, ae, J2, J3, J4, ke)
  2. Coordinate output (geodetic, topocentric): WGS-84 (a=6378.137km, f=1/298.257223563)
  3. TEME frame: Only 4 of 106 IAU-80 nutation terms (matching SGP4's internal model)
  4. Solar system pipeline: IAU 1976 precession, J2000 obliquity, GMST from Vallado Eq. 3-47
  5. Never mix: WGS-72 propagation + WGS-84 output. No other combination.
  6. DE frame rotation: DE positions (ICRS equatorial) pass through equatorial_to_ecliptic() at the provider boundary before entering the observation pipeline
  7. Same-provider rule: Both target and Earth must come from the same provider in any geocentric computation (never mix DE target with VSOP87 Earth)
  8. DE AU consistency: Verify DE header AU matches compiled-in AU_KM (149597870.7) at init time

WGS-72 Constants (from Hoots & Roehrich STR#3, propagation only)

#define WGS72_MU      398600.8            /* km^3/s^2 */
#define WGS72_AE      6378.135            /* km */
#define WGS72_J2      0.001082616
#define WGS72_KE      0.0743669161331734132  /* (min)^(-1) */

WGS-84 Constants (coordinate output only)

#define WGS84_A       6378.137            /* km */
#define WGS84_F       (1.0 / 298.257223563)

Astronomical Constants

#define AU_KM           149597870.7         /* IAU 2012 */
#define GAUSS_K         0.01720209895       /* AU^(3/2)/day */
#define OBLIQUITY_J2000 0.40909280422232897 /* 23.4392911 deg in radians */
#define J2000_JD        2451545.0           /* 2000 Jan 1.5 TT */
#define C_LIGHT_AU_DAY  173.1446327         /* speed of light in AU/day */

JPL DE Ephemeris (Optional)

v0.3.0 adds optional JPL DE440/441 ephemeris support (~0.1 milliarcsecond accuracy) alongside the existing VSOP87 pipeline (~1 arcsecond). DE functions are separate _de() variants — existing VSOP87 functions are completely unchanged.

Architecture

  • Clean-room DE reader (de_reader.c): ~250 lines of C. Parses the JPL binary format, evaluates Chebyshev polynomials via Clenshaw recurrence. No GPL dependency (avoids Bill Gray's jpl_eph).
  • Per-backend lazy init: Each PostgreSQL backend opens its own file descriptor on first _de() call. Never opens in _PG_init() (postmaster context). Safe for PARALLEL SAFE.
  • VSOP87 fallback: Every _de() function falls back to its VSOP87/ELP82B equivalent when DE is unavailable.
  • STABLE volatility: DE functions are STABLE (not IMMUTABLE) because output depends on an external file. Existing VSOP87 functions remain IMMUTABLE.

GUC Configuration

-- Set the path to a JPL DE binary file (requires superuser)
ALTER SYSTEM SET pg_orrery.ephemeris_path = '/var/lib/postgres/de441.bin';
SELECT pg_reload_conf();

-- Check which provider is active
SELECT * FROM pg_orrery_ephemeris_info();
GUC Type Default Context
pg_orrery.ephemeris_path string '' (empty = VSOP87 only) SIGHUP (superuser only)

DE Function Variants

Every _de() function mirrors an existing VSOP87 function:

DE Function VSOP87 Equivalent Volatility
planet_heliocentric_de() planet_heliocentric() STABLE
planet_observe_de() planet_observe() STABLE
sun_observe_de() sun_observe() STABLE
moon_observe_de() moon_observe() STABLE
lambert_transfer_de() lambert_transfer() STABLE
lambert_c3_de() lambert_c3() STABLE
galilean_observe_de() galilean_observe() STABLE
saturn_moon_observe_de() saturn_moon_observe() STABLE
uranus_moon_observe_de() uranus_moon_observe() STABLE
mars_moon_observe_de() mars_moon_observe() STABLE
planet_equatorial_de() planet_equatorial() STABLE
moon_equatorial_de() moon_equatorial() STABLE
galilean_equatorial_de() galilean_equatorial() STABLE
saturn_moon_equatorial_de() saturn_moon_equatorial() STABLE
uranus_moon_equatorial_de() uranus_moon_equatorial() STABLE
mars_moon_equatorial_de() mars_moon_equatorial() STABLE
planet_observe_apparent_de() planet_observe_apparent() STABLE
sun_observe_apparent_de() sun_observe_apparent() STABLE
moon_observe_apparent_de() moon_observe_apparent() STABLE
planet_equatorial_apparent_de() planet_equatorial_apparent() STABLE
moon_equatorial_apparent_de() moon_equatorial_apparent() STABLE
small_body_observe_apparent_de() small_body_observe_apparent() STABLE
pg_orrery_ephemeris_info() STABLE

Vendored SGP4/SDP4

Bill Gray's sat_code (MIT license): https://github.com/Bill-Gray/sat_code

Vendored into src/sgp4/ from upstream commit ff7b98957dfa2979700a482bde9de9542807293e. The .cpp files were renamed to .c — the code is valid C99 with zero C++ features (no classes, templates, namespaces, exceptions, or STL). This eliminates the g++ and -lstdc++ dependencies.

Modifications from upstream are minimal and documented in src/sgp4/PROVENANCE.md:

  • Renamed .cpp.c (no code changes — already valid C99)
  • Stripped Win32 DLL_FUNC/__stdcall decorators
  • Removed extern "C" wrapper (now compiled as C)
  • Removed unused SGP/SGP8/SDP8 model prototypes
  • Added forward declarations (-Wmissing-prototypes)
  • Changed bare inline to static inline for C99 compliance
  • Added equation citations referencing STR#3 and Vallado Rev-1

All numerical logic is byte-identical to upstream. Verified against 518 Vallado test vectors (AIAA 2006-6753-Rev1).

Testing

29 regression test suites via make installcheck:

Suite What it tests
tle_parse TLE I/O round-trip, malformed input rejection, all 15 accessors
sgp4_propagate SGP4/SDP4, propagation series, tle_distance
coord_transforms TEME-to-geodetic, TEME-to-topocentric, ground_track
pass_prediction predict_passes, next_pass, pass_visible, min elevation filter
gist_index && overlap, <-> distance, GiST index scan, KNN ordering (TLE)
convenience observe(), observe_safe(), tle_from_lines(), observer_from_geodetic()
star_observe Star observation, IAU 1976 precession, heliocentric type I/O
kepler_comet Keplerian propagation (elliptic/parabolic/hyperbolic), comet_observe
planet_observe VSOP87 planets, sun_observe, moon_observe (ELP2000-82B)
moon_observe Galilean/Saturn/Uranus/Mars moons, Io phase, Jupiter CML, burst probability
lambert_transfer Lambert solver, lambert_c3, pork chop grid, error handling
de_ephemeris DE function fallback to VSOP87, cross-provider consistency, error handling
od_fit Orbit determination from ECI/topocentric/angles-only observations
spgist_tle SP-GiST orbital trie index operations
orbital_elements orbital_elements type I/O, MPC parser, small_body_observe/heliocentric
equatorial equatorial type I/O, RA/Dec for planets/stars/satellites, proper motion, light-time
refraction Bennett refraction, P/T correction, apparent elevation, refracted pass prediction
aberration Annual aberration magnitude, DE apparent fallback, angular distance, cone search, stellar parallax
vallado_518 518 Vallado test vectors (AIAA 2006-6753-Rev1), per-satellite breakdown
v011_features make_orbital_elements constructors, moon equatorial functions
gist_equatorial Equatorial GiST KNN ordering, RA wrapping, cone search, EXPLAIN index scan
v012_features DE moon equatorial fallback to VSOP87, invalid body_id rejection
v013_features Nutation correction, make_equatorial constructor
rise_set Planet/Sun/Moon rise/set (geometric + refracted), circumpolar, polar night
constellation Roman (1987) boundary lookup, known stars, solar system objects, edge cases
v015_features constellation_full_name lookup, rise_set_status diagnostics (circumpolar/never_rises)
v016_features Twilight ordering/offset/polar, lunar phase at known events, planet magnitude ranges/errors
v017_features Solar elongation ranges/errors, planet phase ranges, satellite eclipse, observing night quality, lunar libration ranges, subsolar longitude
v018_features Saturn ring tilt range/variation, penumbral eclipse (shadow state, penumbra precedes umbra), rise/set event windows (Sun/Moon/planet, refracted vs geometric), angular separation rate (generic + planet convenience)

PG Version Matrix

Test all 29 regression suites + DE reader unit test across PostgreSQL 14-18 using Docker:

make test-matrix                              # Full matrix (PG 14-18)
make test-pg18                                # Single version
PG_TEST_VERSIONS="16 17" make test-matrix     # Subset
make test-matrix-clean                        # Remove logs + test images

Logs saved to test/matrix-logs/pg${ver}.log. The script reuses the Dockerfile builder stage as the test engine — no additional test infrastructure.

Adding a new PG version: Update PG_TEST_VERSIONS default in Makefile and PG_VERSIONS default in test/pg-version-matrix.sh.

Error Handling Patterns

  • _safe() variants (sgp4_propagate_safe, observe_safe, star_observe_safe) return NULL on error instead of raising exceptions. Use these for batch queries over potentially invalid data.
  • SGP4 error codes: -1 (nearly parabolic), -2 (negative semi-major axis/decayed), -3/-4 (orbit within Earth, returns with NOTICE), -5 (negative mean motion), -6 (convergence failure)
  • Pass prediction: propagation failures return -pi elevation (below horizon), shedding the failed timestep without aborting the scan.
  • Input validation: same-body Lambert check, arrival-before-departure, invalid body_id, RA out of [0,24), negative perihelion distance.

Documentation Site

Live: https://pg-orrery.warehack.ing

Starlight docs at docs/ — 44+ MDX pages covering all domains.

Sections: Getting Started, Guides (9 domain walkthroughs incl. DE ephemeris), Workflow Translation (Skyfield/Horizons/GMAT/Radio Jupiter Pro comparisons), Reference (all 184 SQL objects incl. DE variants, equatorial GiST, refraction, rise/set, constellation, twilight, lunar phase, planet magnitude, Saturn ring tilt, solar elongation, planet phase, satellite eclipse with penumbra, observing quality, lunar libration, angular separation rate), Architecture (Hamilton's principles, constant custody, observation pipeline), Performance (benchmarks).

Local Development

cd docs && npm run dev      # Dev server on :4321
cd docs && npm run build    # Static build to dist/

Production Deployment

The docs site deploys to the warehack.ing VPS (149.28.126.25) which runs caddy-docker-proxy with wildcard DNS for *.warehack.ing.

Deploy (or redeploy after changes):

ssh -A warehack-ing@pg-orrery.warehack.ing
cd ~/pg_orrery
git pull origin phase/spgist-orbital-trie   # or the current branch
cd docs
make prod                                       # builds image + starts container

First-time setup on VPS:

ssh -A warehack-ing@pg-orrery.warehack.ing
git clone git@git.supported.systems:warehack.ing/pg_orrery.git
cd pg_orrery && git checkout phase/spgist-orbital-trie
cat > docs/.env << 'EOF'
COMPOSE_PROJECT_NAME=pg-orrery-docs
NODE_ENV=production
VITE_HMR_HOST=pg-orrery.warehack.ing
EOF
cd docs && make prod

Makefile targets:

  • make prod — build + start production (Caddy serves static files)
  • make dev — build + start dev mode (hot-reload, volume mounts)
  • make down — stop containers
  • make restart — stop + start production
  • make clean — stop + remove volumes
  • make logs — tail container logs

Infrastructure: Container pg-orrery-docs joins external caddy network. caddy-docker-proxy reads labels to auto-configure reverse proxy + TLS (Let's Encrypt via Vultr DNS challenge). TLS cert provisioning takes ~2 minutes on first deploy.

Do NOT run the docs container locally if also deployed on the VPS — competing ACME DNS challenges will corrupt each other's TXT records.

Coding Style

  • Standard PostgreSQL extension C style
  • ereport(ERROR, ...) for user-facing errors, never elog(ERROR, ...)
  • All memory via palloc/pfree (PostgreSQL memory contexts)
  • Comments explain "why", not "what"
  • No global mutable state — all computation from function arguments (exceptions: per-backend DE handle via on_proc_exit; 3 statics in vendored deep.c + 1 cache in sdp4.c, safe in PostgreSQL's fork model, never modified by pg_orrery)
  • Every function handling SGP4 must check the error return code
  • All functions marked PARALLEL SAFE
  • DE functions: always fall back to VSOP87/ELP82B on any error

Git Conventions

  • One commit per logical change
  • Branch per phase: phase/spgist-orbital-trie
  • Tag releases: v0.1.0, v0.2.0, v0.3.0
  • Commit messages: imperative mood, no AI attribution