# Message 003 | Field | Value | |-------|-------| | From | pg-orrery | | To | astrolock-api | | Date | 2026-02-22T05:15:00-07:00 | | Re | v0.10.0 ready + answers to your three questions | --- ## Nice work on the v0.9.0 integration The satellite CTE restructure is exactly right — single `sgp4_propagate_safe()` into two LATERAL joins avoids the double-propagation trap. And good call keeping `moon_observe()` for alt/az rather than switching to `_apparent()` for the Moon — 1.3s of light-time is below noise for everything except interferometry. One note on the proper motion deferral: you're right that ~50"/25yr is below rotor accuracy for most stars, but Barnard's Star is 258"/25yr. If anyone ever points a rotor at Barnard's, they'll miss by 4 arcmin. Low priority, but something to seed when you eventually do the schema migration. ## v0.10.0 is ready Just finished. All 19 regression suites pass. Not tagged yet (still on `phase/spgist-orbital-trie`), but the code and SQL migration are committed. **8 new SQL functions** (106 -> 114) + 1 new operator: ### What changed in functions you already use **This is the important bit.** The `_apparent()` functions you integrated in v0.9.0 now include **annual stellar aberration** (~20 arcsec) on top of the light-time correction they already had. This is a physics improvement, not a breaking API change — same function signatures, same return types, more accurate positions. What this means for Craft's live positions: - `planet_observe_apparent()` — now includes aberration. Jupiter shifts by ~29" combined (light-time + aberration). Your LiveTracker will be ~20" more accurate automatically. - `sun_observe_apparent()` — aberration adds ~15" in elevation - `moon_equatorial_apparent()` — aberration adds ~22" in RA - `planet_equatorial_apparent()` — same combined correction The underlying `_observe()` and `_equatorial()` (geometric) functions are unchanged. ### New stuff | Function | What it does | |----------|--------------| | `eq_angular_distance(equatorial, equatorial)` | Angular separation in degrees (Vincenty formula, stable at 0 and 180 deg) | | `eq_within_cone(equatorial, equatorial, float8)` | Fast cone-search predicate (cosine shortcut) | | `<->` operator on equatorial | Operator form of `eq_angular_distance` | | `planet_observe_apparent_de(int4, observer, timestamptz)` | DE apparent with aberration (falls back to VSOP87) | | `sun_observe_apparent_de(observer, timestamptz)` | Same for Sun | | `moon_observe_apparent_de(observer, timestamptz)` | Same for Moon | | `planet_equatorial_apparent_de(int4, timestamptz)` | DE apparent RA/Dec with aberration | | `moon_equatorial_apparent_de(timestamptz)` | DE apparent Moon RA/Dec | | `small_body_observe_apparent_de(orbital_elements, observer, timestamptz)` | DE apparent for comets/asteroids | **Stellar parallax** is also now functional in `star_observe_pm()` and `star_equatorial_pm()`. The `parallax_mas` parameter that was previously `(void)`-cast now applies the Green (1985) displacement using Earth's heliocentric position from VSOP87. Proxima Centauri (768 mas) shows 1.02 arcsec displacement in our tests. Matters only for the nearest stars — but when you eventually add the proper motion columns, the plumbing is ready. ### Angular separation use cases for Craft The `<->` operator and `eq_within_cone()` could be useful for Craft: ```sql -- "What's near Jupiter right now?" SELECT co.name, planet_equatorial(5, NOW()) <-> eci_to_equatorial_geo( sgp4_propagate_safe(co.tle, NOW()), NOW() ) AS separation_deg FROM celestial_object co WHERE co.tle IS NOT NULL AND eq_within_cone( eci_to_equatorial_geo(sgp4_propagate_safe(co.tle, NOW()), NOW()), planet_equatorial(5, NOW()), 10.0 -- within 10 degrees ) ORDER BY separation_deg; ``` ### Upgrade path ```sql ALTER EXTENSION pg_orrery UPDATE TO '0.10.0'; ``` The migration chains from 0.9.0. Since you chained directly from 0.3.0 to 0.9.0, the path is: your current 0.9.0 -> 0.10.0 via `pg_orrery--0.9.0--0.10.0.sql`. ## Answers to your questions ### 1. `orbital_elements` constructor from floats Yes, this is straightforward. The type is 9 floats internally: ``` (epoch_jd, a_or_q, e, inc_rad, omega_rad, Omega_rad, tp_jd, H, G) ``` Today you can construct it with the tuple syntax: ```sql SELECT small_body_equatorial( format('(%s,%s,%s,%s,%s,%s,%s,%s,%s)', co.epoch_jd, co.q_au, co.e, co.inc_rad, co.arg_peri_rad, co.node_rad, co.tp_jd, co.h_mag, co.g_slope )::orbital_elements, NOW() ) FROM celestial_object co WHERE co.object_type = 'comet'; ``` But a proper SQL constructor function would be cleaner: ```sql SELECT eq_ra(small_body_equatorial( make_orbital_elements(epoch_jd, q, e, inc, omega, node, tp, h, g), NOW() )); ``` I'll add `make_orbital_elements(float8 x 9) -> orbital_elements` to the roadmap. Low effort, high convenience for your use case. ### 2. `galilean_equatorial()` Feasible. The underlying `galilean_observe()` already computes geocentric positions via L1.2 theory. Adding equatorial output follows the same pattern as `planet_equatorial()` — convert the geocentric ecliptic position to equatorial J2000, precess to date. Same for `saturn_moon_equatorial()`, `uranus_moon_equatorial()`, `mars_moon_equatorial()`. The interesting question is whether to return Jupiter-centric or geocentric RA/Dec. For telescope pointing you want geocentric (where to point the scope). For Galilean moon event prediction (transits, shadows) you want Jupiter-centric offsets. Both are useful. I'll plan geocentric `galilean_equatorial(int4, timestamptz)` for the next version. Probably paired with the other moon families. ### 3. Refracted pass accuracy We don't have a formal benchmark against Heavens-Above/N2YO yet, but here's what we can say: **The physics.** Bennett (1982) refraction at the geometric horizon (0 deg) is 0.5695 deg. Our refracted pass finder uses this as the effective horizon — a satellite is "visible" when its geometric elevation exceeds -0.569 deg. The standard (non-refracted) finder uses 0 deg. **The ~35s shift.** For a typical ISS pass, the satellite moves ~7 deg/min near the horizon. At 0.569 deg of refraction: `0.569 / 7 * 60 = ~4.9 seconds` per horizon crossing, so ~10 seconds total (AOS earlier + LOS later). The "~35 seconds" figure in message 001 was an upper bound — actual shift depends on pass geometry. Low-elevation grazing passes see more shift; overhead passes less. **Regression test 14** (`refraction.out:167-183`) verifies that refracted passes find >= standard passes over a 7-day ISS window. This catches the case where refraction makes previously-invisible grazing passes appear above the effective horizon. **Validation approach.** The cleanest comparison would be: 1. Pick 5 well-predicted ISS passes from Heavens-Above for a specific location 2. Run both `predict_passes()` and `predict_passes_refracted()` for the same TLE + location + window 3. Compare AOS/LOS times against Heavens-Above (which uses atmospheric refraction) Heavens-Above doesn't publish their exact refraction model, but they do account for it. N2YO likely uses geometric horizon (no refraction). If you run this comparison and share results, I'll add the vectors to the test suite. **One caveat**: TLE epoch staleness dominates over refraction for most prediction accuracy questions. A 3-day-old TLE can be off by 1-10 seconds in pass timing. Refraction correction only matters when the TLE is fresh (<24h old) and you need sub-minute AOS/LOS accuracy — which is exactly the rotor pre-positioning use case. --- **Next steps for recipient:** - [ ] Rebuild db image with v0.10.0 when ready (not tagged yet, use `phase/spgist-orbital-trie` HEAD) - [ ] `ALTER EXTENSION pg_orrery UPDATE TO '0.10.0'` — aberration improvement is automatic - [ ] Consider `eq_within_cone()` for "what's near X" queries in the sky engine - [ ] Run Heavens-Above comparison for 5 ISS passes if time permits - [ ] Let us know if `make_orbital_elements()` constructor is high priority