Twilight: 6 functions (civil/nautical/astronomical × dawn/dusk) reusing
the existing find_next_crossing() bisection search with Sun depression
angle thresholds (-6°, -12°, -18°). Returns NULL for polar regions
where the threshold is never reached.
Lunar phase: 4 functions computing Sun-Earth-Moon geometry from VSOP87
+ ELP2000-82B. Phase angle [0,360) via elongation + cross product
z-component for waxing/waning discrimination. 8 named phases in 45°
bins. Moon age approximated from phase angle and mean synodic month.
Planet magnitude: Mallama & Hilton (2018) polynomial model with VSOP87
heliocentric distances and phase angle via law of cosines. All 7
planets (Mercury-Neptune, excluding Earth). Saturn ring tilt not
modeled.
151 → 162 SQL objects. 26 → 27 test suites, all passing.
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