constellation_full_name(text) returns full IAU name from 3-letter
abbreviation (88-entry static table, IMMUTABLE). Returns NULL for
invalid input — composable with constellation() in queries.
Three rise_set_status functions classify body visibility as
'rises_and_sets', 'circumpolar', or 'never_rises' by sampling
elevation at 48 points across 24h. Separate diagnostic path —
called only when rise/set returns NULL, zero cost in normal case.
147 → 151 SQL objects. 25 → 26 regression suites. All pass.
Add 4 refracted rise/set functions completing the rise/set feature set:
- planet_next_rise/set_refracted: -0.569 deg threshold (refraction only,
point source — even Jupiter at opposition is only 24 arcsec)
- moon_next_rise/set_refracted: -0.833 deg threshold (refraction +
mean semidiameter, same as Sun)
Add IAU constellation identification from Roman (1987) CDS VI/42:
- 357 boundary segments covering all 88 constellations
- Precesses J2000 coordinates to B1875.0 epoch for lookup
- Two overloads: constellation(equatorial) and constellation(float8, float8)
- IMMUTABLE (compiled-in static data)
141 -> 147 SQL objects. 24 -> 25 regression suites. All 25 pass.
Integrate IAU 2000B nutation (~9 arcsec) into the solar system observation
pipeline via precess_and_nutate_j2000_to_date(). Affects all planet, star,
moon, and small body RA/Dec and az/el values. Satellite SGP4/TEME pipeline
unchanged.
Add make_equatorial(ra_hours, dec_deg, distance_km) constructor to replace
error-prone text literal casts.
Add 8 rise/set prediction functions (planet_next_rise/set, sun_next_rise/set,
moon_next_rise/set, sun_next_rise/set_refracted) using bisection algorithm
adapted from satellite pass prediction. Returns NULL for circumpolar and
polar night edge cases.
Fix DE fallback test fragility: replace exact float equality with tolerance
comparisons to handle GCC LTO inlining divergence across translation units.
132 -> 141 SQL objects. 22 -> 24 regression suites. All 24 passing.
CLAUDE.md: bump version to 0.12.0, function count to 132, test count
to 22, add v0.10-0.12 SQL files to layout, add gist_equatorial.c,
update function domains table, add DE moon equatorial to DE variants.
Docs: add equatorial GiST operator class section to operators-gist.mdx
(KNN queries, cone search, RA wrapping, polar behavior). Add 4 DE moon
equatorial functions to functions-de.mdx (galilean, saturn, uranus, mars).
- Add validate_orbital_elements_args() with isnan/isinf checks for all
7 propagation parameters (epoch, q, e, inc, omega, node, tp); h_mag
and g_slope exempt (NaN is valid sentinel for "unknown magnitude")
- Deduplicate validation between make_orbital_elements() and _deg()
- Update SQL COMMENTs to clarify geometric vs apparent coordinates
- Add NaN/Inf rejection tests (q, e, epoch, Inf inclination)
- Add NaN H/G acceptance test (sentinel value)
- Expand error path coverage to all 4 moon families + negative body_id
- All 20 regression suites pass
6 new SQL functions (114 -> 120):
- make_orbital_elements(): construct from 9 floats, angles in radians
- make_orbital_elements_deg(): same with angles in degrees, matches
text I/O convention and typical catalog column layouts
- galilean_equatorial(): geocentric RA/Dec for Io/Europa/Ganymede/Callisto
- saturn_moon_equatorial(): geocentric RA/Dec for Mimas through Hyperion
- uranus_moon_equatorial(): geocentric RA/Dec for Miranda through Oberon
- mars_moon_equatorial(): geocentric RA/Dec for Phobos/Deimos
Constructors requested by astrolock-api to replace fragile
format(9 args)::orbital_elements cast pattern. Moon equatorial
functions fill the last NULL RA/Dec gaps in their unified sky query.
All 20 regression suites pass.
- Add timing numbers for equatorial, aberration, angular distance,
refraction, and star proper motion+parallax to benchmarks page
- Update From Skyfield page: v0.10.0 now has light-time + aberration
parity; remaining gap narrowed to nutation (~9 arcsec) and polar motion
- Update llms.txt and llms-full.txt for 114 functions, new DE apparent
variants, equatorial spatial operators, and aberration/parallax notes
Annual stellar aberration (~20 arcsec) added to all 6 existing _apparent()
functions via classical first-order v/c projection (Ron & Vondrak). Earth
velocity sourced from VSOP87 xyz[3..5] (analytic) or DE numerical
differentiation.
New functions (106 -> 114):
- eq_angular_distance(): Vincenty formula, stable at 0 and 180 deg
- eq_within_cone(): cosine shortcut for fast cone-search predicate
- <-> operator on equatorial type
- 6 DE apparent variants with VSOP87 fallback:
planet/sun/moon_observe_apparent_de(),
planet/moon_equatorial_apparent_de(),
small_body_observe_apparent_de()
Stellar parallax now functional in star_observe_pm() and
star_equatorial_pm() — Green (1985) Eq. 11.3 displacement using
Earth heliocentric position from VSOP87.
All 19 regression suites pass (18 existing + new aberration suite).
- Add Cache-Control: no-cache as default so browsers revalidate via ETag
instead of heuristic caching stale llms.txt and HTML pages
- Fix hashed asset path from /docs/_astro/* to /_astro/* (root is /srv)
- _astro/* immutable rule still applies for hashed Vite bundles
Per the llms.txt spec — standard index at /llms.txt linking all 44 doc
pages, plus /llms-full.txt with all 82 function signatures, 8 types,
body ID tables, operators, and runnable query patterns inline (~18KB).
New guide combining multiple pg_orrery function families with
PostgreSQL analytical functions: Kirkwood gap histograms, Kepler
regression, asteroid family taxonomy, universal sky report, planetary
alignment detection, ISS eclipse timing, PostGIS ground tracks,
solar system dashboard view, and satellite shell census.
Add orbital_elements (72-byte Keplerian element type) to types reference,
three new function sections (oe_from_mpc, small_body_heliocentric,
small_body_observe) to the functions reference, MPC catalog loading
workflow to the comets/asteroids guide, and update CLAUDE.md with
v0.8.0 version numbers, 82 functions, 8 types, 16 test suites.
- benchmarks.mdx: Add GiST conjunction screening and KNN sections,
update all numbers to 66,440-object catalog, PG 17→18, show SP-GiST
slower than seqscan at this scale with explanation of why
- operators-gist.mdx: Real 66k performance tables for GiST and SP-GiST,
rewrite KNN example with scalar subquery pattern, add CTE warning
- conjunction-screening.mdx: Update catalog size, candidate counts,
add KNN scalar subquery note, verified performance numbers
The pg_tle struct has been 104 bytes since v0.1.0, but INTERNALLENGTH
is 112. The size comment claimed "11 doubles (88 bytes)" — there are
10 (80 bytes). Every palloc(sizeof(pg_tle)) across the codebase
allocated 104 bytes while PostgreSQL's datumCopy/heap_form_tuple
copied 112, causing an 8-byte overread.
Fix: add _reserved[8] to pg_tle, making sizeof(pg_tle) == 112.
This is backward compatible — existing on-disk tuples already have
112 bytes allocated (from typlen), with zeros in the trailing 8.
Also in gist_tle.c:
- Remove TLE_TYPLEN band-aid, use sizeof(pg_tle) everywhere
- Set recheck = false for leaf entries in consistent: the orbital
key is computed identically to the SQL operator, so the GiST
leaf check is exact (eliminates unnecessary heap fetches)
Two bugs in gist_tle.c caused the && (overlap) operator to return
zero results through the GiST index while sequential scan worked:
1. gist_tle_union read from vector[FirstOffsetNumber] (index 1),
skipping vector[0] which holds the accumulated union key.
Every internal node collapsed to a single-entry bounding box.
Fixed: seed from vector[0], loop from 1.
2. All GiST key allocations used sizeof(tle_orbital_key) (32 bytes)
or sizeof(pg_tle) (104 bytes), but INTERNALLENGTH is 112.
index_form_tuple() copies typlen bytes, causing buffer overread.
Fixed: TLE_TYPLEN constant (112) for all index datum allocations.
The <-> (KNN distance) operator was unaffected because it uses
gist_tle_distance, not gist_tle_consistent.
Verified against 66,440-object catalog:
- && consistency: 9 seqscan == 9 GiST (ISS conjunction)
- <-> KNN: 10 nearest in 2.1ms via index-ordered scan
- All 15 regression tests pass
Three-tier discovery: pg-orrery-catalog in PATH, sibling dev
checkout, or original build_catalog.py + curl. Indexes use
IF NOT EXISTS for idempotent re-runs.
New guide: guides/catalog-management.mdx covering the full
download/merge/load pipeline with pg-orrery-catalog CLI.
Updated tracking-satellites.mdx to reference the companion tool
instead of "No TLE fetching". Added cross-reference in benchmarks
setup instructions.
Bug: inner_consistent used sma_low for footprint calculation, but
ground footprint grows with altitude. High-SMA bins (GTO, HEO)
need sma_high to compute the maximum footprint — using sma_low
caused 453 false negatives at high-latitude observers (Tromsoe).
Fix: use sma_high (not sma_low) in L1 inclination pruning.
Added regression test: GTO-debris (inc 5 deg, e=0.73) at Tromsoe
must return identical results from seqscan and index scan.
Benchmark on 65,886-object catalog (full Space-Track including
decayed): 80-92% pruning, zero false negatives across 7 query
patterns. SP-GiST beats seqscan for high-latitude observers.
Space-Track USSPACECOM catalog: 29,784 objects from full GP query.
Benchmark shows SP-GiST index reaches parity with seqscan at 30k:
- Delta: +1.6ms (14k) -> +0.9ms (20k) -> +0.0ms (30k)
- Planner voluntarily chooses Index Only Scan at this scale
- Zero heap fetches (all data served from index pages)
- 75.9% candidate pruning on 2h/10deg query
Archive includes TLEs from Space-Track, TLE API, and SatNOGS.
- types.mdx: "seven" → "seven base types + one SQL composite",
add observer_window section with field table and usage example
- operators-gist.mdx: "three operators" → "four operators", reframe
SP-GiST performance as scalability feature (honest about seqscan
being faster at 14k catalog size, index helps at 100k+)
- installation.mdx: "14 test suites" → "15 test suites", list all
suites including od_fit, spgist_tle, vallado_518
- design-principles.mdx: clarify observer_window is SQL composite
(variable-length, query-time only), base types still STORAGE=plain
- pass-prediction.mdx: lead with operator value (80-90% elimination),
SP-GiST index framed as optional for large catalogs
New docs:
- guides/pass-prediction.mdx: two-stage workflow (SP-GiST filter
then SGP4 propagation), query window comparison tabs, GiST/SP-GiST
coexistence example
- reference/operators-gist.mdx: &? operator signature and description,
observer_window type reference, SP-GiST operator class docs with
eccentricity/HEO limitation aside
Benchmarks on 14,376 CelesTrak active satellites:
- SP-GiST index: 2,344 kB, builds in 19 ms
- GiST index: 2,904 kB, builds in 45 ms
- Consistency: 0 false negatives, 0 false positives
- At 14k catalog size, seqscan (~6 ms) still beats index scan (~8 ms)
due to low page count; cross-over expected at ~100k objects
GiST: entryvec->vector[] uses 1-based indexing (FirstOffsetNumber),
not 0-based. Reading vector[0] hit uninitialized memory, causing
SIGSEGV on large catalogs (14k+ satellites). Fixed in gist_tle_union
and gist_tle_picksplit.
SP-GiST: PostgreSQL requires the indexed column as the LEFT argument
of the operator to form a ScanKey (skey.h:23-26). Flipped &? from
(observer_window, tle) to (tle, observer_window) so inner_consistent
receives scankeys for tree-level pruning.
Removed L0 altitude pruning from inner_consistent — SMA bins don't
carry eccentricity, so HEO satellites (e.g. CLUSTER II, e=0.88,
SMA ~70000 km, perigee ~2000 km) were falsely pruned. L0 now only
narrows SMA range for L1 footprint computation.
All 15 regression tests pass. Consistency check on 14,376 satellites
confirms 0 false negatives, 0 false positives.
Fix M2: clamp picksplit nBins to nTuples to prevent out-of-bounds
read on the entries array when called with a single tuple.
Fix H2: use WGS72_AE as effective bin_low for the first bin in L0
inner_consistent, preventing false negatives when objects with lower
SMA than the first label are inserted after index creation.
Fix H3: reject degenerate TLEs (mean_motion <= 0) early in the
visibility filter rather than propagating nonsensical values.
Fix L1: extract shared tle_passes_visibility_filter() to eliminate
duplicated 3-stage filter logic between leaf_consistent and the
standalone tle_visibility_possible operator.
Add boundary tests: degenerate TLE, polar observer, zero-duration
window, and post-insert index-vs-seqscan consistency check.
2-level SP-GiST index on TLE data: SMA at L0, inclination at L1, with
query-time RAAN filter via J2 secular precession. New &? operator
(observer_window &? tle) returns true when a satellite might be visible
from a ground observer during a time window.
Index prunes by altitude band, inclination+footprint vs observer
latitude, and RAAN alignment against local sidereal time. Operator
class tle_spgist_ops is opt-in (not default), coexists with existing
GiST tle_ops. Equal-population picksplit with sqrt(n) bins.
Evolved from the original KTrie custom AM proposal (preserved as
KTRIE-SPEC-ORIGINAL.md). Key design decisions: 2-level trie (SMA +
inclination) instead of 5, SP-GiST framework instead of custom AM,
query-time RAAN filter instead of trie level, propagation-aware cost
estimation via traversalValue.
New pages:
- OD function reference (tle_from_eci, tle_from_topocentric,
tle_from_angles, tle_fit_residuals)
- OD guide (ECI, topocentric, angles-only, range rate, weights,
multi-observer, covariance interpretation)
- From find_orb to SQL (OD workflow comparison)
- From Poliastro to SQL (Lambert/Kepler comparison)
Updated pages:
- Corrected stale "No orbit determination" claim
- Updated function counts and test suite counts
- Added v0.4.0-v0.6.0 upgrade paths
- Added OD to capabilities table, theory-to-code mapping,
constants/accuracy reference
- Added OD examples to Skyfield comparison and SQL Advantage
- Fixed stale version references across workflow pages
Range rate: topocentric residuals now include an optional 4th component
(dot(Δr, v_ecef) / |Δr|) with OD_RR_SCALE=10.0 for unit balancing.
Controlled via fit_range_rate parameter on tle_from_topocentric().
Weighted observations: per-observation weights applied as √w scaling
to both residuals and Jacobian rows, producing the weighted normal
equations H'WH without explicit W construction. Weights parameter
added to tle_from_eci, tle_from_topocentric, and tle_from_angles.
Gauss angles-only IOD: Vallado Algorithm 52 implementation for
seed-free orbit recovery from 3+ RA/Dec observations. New RA/Dec
residual function with cos(dec) scaling and wrap-around handling.
New tle_from_angles() and tle_from_angles_multi() SQL functions
accepting RA in hours [0,24), Dec in degrees [-90,90].
New standalone test suite: test_od_gauss (17 assertions).
New regression tests: Tests 18-25 covering range rate, weights,
angles-only with/without seed, and error cases.