diff --git a/docs/astro.config.mjs b/docs/astro.config.mjs
index d97b624..50104af 100644
--- a/docs/astro.config.mjs
+++ b/docs/astro.config.mjs
@@ -97,6 +97,7 @@ export default defineConfig({
{ label: "Functions: Radio", slug: "reference/functions-radio" },
{ label: "Functions: Transfers", slug: "reference/functions-transfers" },
{ label: "Functions: Refraction", slug: "reference/functions-refraction" },
+ { label: "Functions: Rise/Set & Constellation", slug: "reference/functions-rise-set" },
{ label: "Functions: DE Ephemeris", slug: "reference/functions-de" },
{ label: "Functions: Orbit Determination", slug: "reference/functions-od" },
{ label: "Operators & Indexes", slug: "reference/operators-gist" },
diff --git a/docs/public/llms-full.txt b/docs/public/llms-full.txt
index 078486a..7a13f60 100644
--- a/docs/public/llms-full.txt
+++ b/docs/public/llms-full.txt
@@ -1,6 +1,6 @@
# pg_orrery — Complete LLM Reference
-> Celestial mechanics types and functions for PostgreSQL. Native C extension (v0.10.0) with 114 SQL functions, 9 custom types + 1 composite, GiST/SP-GiST indexing. All functions PARALLEL SAFE.
+> Celestial mechanics types and functions for PostgreSQL. Native C extension (v0.15.0) with 151 SQL objects (135 user-visible functions + 16 GiST support), 9 custom types + 1 composite, GiST/SP-GiST indexing. All functions PARALLEL SAFE.
- Source: https://git.supported.systems/warehack.ing/pg_orrery
- Docs: https://pg-orrery.warehack.ing
@@ -216,15 +216,31 @@ planet_equatorial_apparent(body_id int4, timestamptz) → equatorial IMMUTAB
moon_equatorial_apparent(timestamptz) → equatorial IMMUTABLE
-- All _apparent() functions include annual aberration correction (~20 arcsec) + light-time
+
+-- Constructor
+make_equatorial(ra_hours float8, dec_deg float8, dist_km float8) → equatorial IMMUTABLE -- construct equatorial from components
```
-### Planetary Moons (4 functions)
+### Nutation — IAU 2000B
+
+```
+nutation_dpsi(timestamptz) → float8 IMMUTABLE -- nutation in longitude (radians)
+nutation_deps(timestamptz) → float8 IMMUTABLE -- nutation in obliquity (radians)
+```
+
+### Planetary Moons (8 functions)
```
galilean_observe(moon_id int4, observer, timestamptz) → topocentric IMMUTABLE -- L1.2 theory, IDs 0-3
saturn_moon_observe(moon_id int4, observer, timestamptz) → topocentric IMMUTABLE -- TASS 1.7, IDs 0-7
uranus_moon_observe(moon_id int4, observer, timestamptz) → topocentric IMMUTABLE -- GUST86, IDs 0-4
mars_moon_observe(moon_id int4, observer, timestamptz) → topocentric IMMUTABLE -- MarsSat, IDs 0-1
+
+-- Equatorial RA/Dec for planetary moons (geocentric, of date)
+galilean_equatorial(moon_id int4, timestamptz) → equatorial IMMUTABLE -- L1.2 theory, IDs 0-3
+saturn_moon_equatorial(moon_id int4, timestamptz) → equatorial IMMUTABLE -- TASS 1.7, IDs 0-7
+uranus_moon_equatorial(moon_id int4, timestamptz) → equatorial IMMUTABLE -- GUST86, IDs 0-4
+mars_moon_equatorial(moon_id int4, timestamptz) → equatorial IMMUTABLE -- MarsSat, IDs 0-1
```
### Stars (5 functions)
@@ -296,7 +312,7 @@ eq_within_cone(equatorial, equatorial, float8) → bool IMMUTAB
Operator: `equatorial <-> equatorial → float8` (angular separation in degrees, commutative).
-### DE Ephemeris — Optional High-Precision (19 functions)
+### DE Ephemeris — Optional High-Precision (23 functions)
All _de() functions fall back to VSOP87/ELP2000-82B when DE is unavailable. All STABLE (external file dependency).
@@ -313,6 +329,10 @@ uranus_moon_observe_de(moon_id, observer, timestamptz) → topocentric STABLE
mars_moon_observe_de(moon_id, observer, timestamptz) → topocentric STABLE
planet_equatorial_de(body_id int4, timestamptz) → equatorial STABLE -- geocentric RA/Dec via DE
moon_equatorial_de(timestamptz) → equatorial STABLE -- geocentric RA/Dec via DE
+galilean_equatorial_de(moon_id int4, timestamptz) → equatorial STABLE -- geocentric RA/Dec via DE
+saturn_moon_equatorial_de(moon_id int4, timestamptz) → equatorial STABLE -- geocentric RA/Dec via DE
+uranus_moon_equatorial_de(moon_id int4, timestamptz) → equatorial STABLE -- geocentric RA/Dec via DE
+mars_moon_equatorial_de(moon_id int4, timestamptz) → equatorial STABLE -- geocentric RA/Dec via DE
pg_orrery_ephemeris_info() → (provider, file_path, start_jd, end_jd, version, au_km) STABLE
-- Apparent DE variants (light-time + aberration, falls back to VSOP87)
@@ -354,6 +374,50 @@ tle_fit_residuals(fitted tle, positions eci_position[], times timestamptz[])
→ SETOF (t, dx_km, dy_km, dz_km, pos_err_km) IMMUTABLE
```
+## Rise/Set Prediction
+
+Predicts next rise or set time for Sun, Moon, and planets using coarse 60-second scan + bisection to 0.1-second precision. Returns NULL for circumpolar bodies or bodies that never rise within the 7-day search window.
+
+### Geometric (horizon = 0 deg)
+
+```
+sun_next_rise(obs observer, t timestamptz) → timestamptz STABLE STRICT PARALLEL SAFE
+sun_next_set(obs observer, t timestamptz) → timestamptz STABLE STRICT PARALLEL SAFE
+moon_next_rise(obs observer, t timestamptz) → timestamptz STABLE STRICT PARALLEL SAFE
+moon_next_set(obs observer, t timestamptz) → timestamptz STABLE STRICT PARALLEL SAFE
+planet_next_rise(body_id int4, obs observer, t timestamptz) → timestamptz STABLE STRICT PARALLEL SAFE -- body_id 1-8 (Mercury-Neptune)
+planet_next_set(body_id int4, obs observer, t timestamptz) → timestamptz STABLE STRICT PARALLEL SAFE
+```
+
+### Refracted
+
+```
+sun_next_rise_refracted(obs observer, t timestamptz) → timestamptz STABLE STRICT PARALLEL SAFE -- threshold -0.833 deg (refraction + semidiameter)
+sun_next_set_refracted(obs observer, t timestamptz) → timestamptz STABLE STRICT PARALLEL SAFE
+moon_next_rise_refracted(obs observer, t timestamptz) → timestamptz STABLE STRICT PARALLEL SAFE -- threshold -0.833 deg
+moon_next_set_refracted(obs observer, t timestamptz) → timestamptz STABLE STRICT PARALLEL SAFE
+planet_next_rise_refracted(body_id int4, obs observer, t timestamptz) → timestamptz STABLE STRICT PARALLEL SAFE -- threshold -0.569 deg (point source)
+planet_next_set_refracted(body_id int4, obs observer, t timestamptz) → timestamptz STABLE STRICT PARALLEL SAFE
+```
+
+### Status Diagnostics
+
+```
+sun_rise_set_status(obs observer, t timestamptz) → text STABLE STRICT PARALLEL SAFE -- returns 'rises_and_sets', 'circumpolar', or 'never_rises'
+moon_rise_set_status(obs observer, t timestamptz) → text STABLE STRICT PARALLEL SAFE
+planet_rise_set_status(body_id int4, obs observer, t timestamptz) → text STABLE STRICT PARALLEL SAFE
+```
+
+## Constellation Identification
+
+IAU constellation identification using Roman (1987) boundary table (CDS VI/42). Precesses J2000 coordinates to B1875.0 internally.
+
+```
+constellation(eq equatorial) → text IMMUTABLE STRICT PARALLEL SAFE -- 3-letter IAU abbreviation
+constellation(ra_hours float8, dec_deg float8) → text IMMUTABLE STRICT PARALLEL SAFE -- J2000 RA hours [0,24) + Dec degrees [-90,90]
+constellation_full_name(abbr text) → text IMMUTABLE STRICT PARALLEL SAFE -- full IAU name from abbreviation, NULL for invalid input
+```
+
## Operators & Indexes
### GiST — tle_ops (DEFAULT for type tle)
@@ -379,11 +443,17 @@ CREATE INDEX ON satellites USING spgist (elements tle_spgist_ops);
SP-GiST is a 2-level orbital trie (SMA → inclination) with query-time RAAN filter. Returns a conservative superset — survivors need `predict_passes()` for ground truth.
-### Equatorial distance
+### GiST — equatorial_ops (DEFAULT for type equatorial)
+
+```sql
+CREATE INDEX ON sky_objects USING gist (position);
+```
| Operator | Meaning | Usage |
|----------|---------|-------|
-| `<->` (equatorial) | Angular separation in degrees (Vincenty formula) | `ORDER BY pos1 <-> pos2` or `WHERE pos1 <-> pos2 < 5.0` |
+| `<->` (equatorial) | Angular separation in degrees (Vincenty formula), GiST-indexed KNN | `ORDER BY position <-> target LIMIT 10` or `WHERE pos1 <-> pos2 < 5.0` |
+
+Supports KNN ordering (`ORDER BY ... <-> ... LIMIT N`) via GiST index scan. Handles RA wraparound at 0h/24h boundary.
## Common Query Patterns
@@ -555,6 +625,34 @@ SELECT eq_within_cone(
FROM star_catalog;
```
+### Rise and set times
+
+```sql
+-- When does the Sun next rise and set?
+SELECT sun_next_rise('40.0N 105.3W 1655m'::observer, NOW()) AS sunrise,
+ sun_next_set('40.0N 105.3W 1655m'::observer, NOW()) AS sunset;
+
+-- Refracted sunrise (accounts for atmospheric refraction + solar semidiameter)
+SELECT sun_next_rise_refracted('40.0N 105.3W 1655m'::observer, NOW()) AS sunrise_refracted;
+
+-- Check if body is circumpolar at high latitude
+SELECT sun_rise_set_status('70.0N 25.0E'::observer, '2024-06-21'::timestamptz);
+-- Returns: 'circumpolar' (midnight sun)
+```
+
+### Constellation identification
+
+```sql
+-- What constellation is Jupiter in right now?
+SELECT constellation(planet_equatorial(5, NOW())) AS jupiter_constellation;
+
+-- Full constellation name
+SELECT constellation_full_name(constellation(planet_equatorial(5, NOW())));
+
+-- From raw RA/Dec coordinates
+SELECT constellation(6.75, -16.72) AS sirius_constellation; -- 'CMa'
+```
+
## Error Handling
### _safe() variants
diff --git a/docs/public/llms.txt b/docs/public/llms.txt
index 3559b07..55fdc89 100644
--- a/docs/public/llms.txt
+++ b/docs/public/llms.txt
@@ -1,6 +1,6 @@
# pg_orrery
-> Celestial mechanics types and functions for PostgreSQL. Native C extension with 114 SQL functions, 9 custom types, GiST/SP-GiST indexing. Covers satellites (SGP4/SDP4), planets (VSOP87), Moon (ELP2000-82B), 19 planetary moons, stars (with proper motion), comets, asteroids (MPC catalog), Jupiter radio bursts, orbit determination, interplanetary Lambert transfers, equatorial RA/Dec coordinates, atmospheric refraction, light-time correction, annual stellar aberration, and equatorial angular separation. Optional JPL DE440/441 ephemeris for sub-arcsecond accuracy.
+> Celestial mechanics types and functions for PostgreSQL. Native C extension with 151 SQL objects (135 user-visible functions + 16 GiST support), 9 custom types, GiST/SP-GiST indexing. Covers satellites (SGP4/SDP4), planets (VSOP87), Moon (ELP2000-82B), 19 planetary moons, stars (with proper motion), comets, asteroids (MPC catalog), Jupiter radio bursts, orbit determination, interplanetary Lambert transfers, equatorial RA/Dec coordinates, atmospheric refraction, light-time correction, annual stellar aberration, equatorial angular separation, rise/set prediction (geometric + refracted), constellation identification, and nutation. Optional JPL DE440/441 ephemeris for sub-arcsecond accuracy.
- [Source code](https://git.supported.systems/warehack.ing/pg_orrery)
- [Full LLM reference](https://pg-orrery.warehack.ing/llms-full.txt): All function signatures, types, body IDs, operators, and query patterns inline
@@ -48,6 +48,7 @@
- [Functions: Transfers](https://pg-orrery.warehack.ing/reference/functions-transfers/): Lambert transfer solver for interplanetary trajectory design
- [Functions: Refraction](https://pg-orrery.warehack.ing/reference/functions-refraction/): Bennett (1982) atmospheric refraction, P/T correction, apparent elevation, refracted pass prediction
- [Functions: Equatorial Spatial](https://pg-orrery.warehack.ing/reference/functions-equatorial/): Angular separation (Vincenty formula), cone search, `<->` operator on equatorial type
+- [Functions: Rise/Set & Constellation](https://pg-orrery.warehack.ing/reference/functions-rise-set/): Rise/set prediction (geometric + refracted), status diagnostics, IAU constellation identification
- [Functions: DE Ephemeris](https://pg-orrery.warehack.ing/reference/functions-de/): Optional JPL DE440/441 variants of observation, equatorial, and apparent functions
- [Functions: Orbit Determination](https://pg-orrery.warehack.ing/reference/functions-od/): TLE fitting from ECI, topocentric, and angles-only observations
- [Operators & Indexes](https://pg-orrery.warehack.ing/reference/operators-gist/): GiST (&&, <->) and SP-GiST (&?) operator classes for orbital indexing
diff --git a/docs/src/content/docs/reference/functions-rise-set.mdx b/docs/src/content/docs/reference/functions-rise-set.mdx
new file mode 100644
index 0000000..2a1edb4
--- /dev/null
+++ b/docs/src/content/docs/reference/functions-rise-set.mdx
@@ -0,0 +1,583 @@
+---
+title: "Functions: Rise/Set & Constellations"
+sidebar:
+ order: 8
+---
+
+import { Aside, Tabs, TabItem } from "@astrojs/starlight/components";
+
+Functions for predicting when celestial bodies rise and set, and for identifying which constellation a sky coordinate falls in. Rise/set prediction uses bisection search on elevation with a 7-day search window. Constellation identification uses the Roman (1987) boundary table (CDS VI/42, 357 segments), precessing input coordinates to B1875.0 internally.
+
+
+
+---
+
+## sun_next_rise
+
+Returns the next geometric sunrise after the given time. The geometric horizon is 0 degrees --- no refraction or semidiameter correction.
+
+### Signature
+
+```sql
+sun_next_rise(obs observer, t timestamptz) → timestamptz
+```
+
+### Parameters
+
+| Parameter | Type | Description |
+|-----------|------|-------------|
+| `obs` | `observer` | Observer location |
+| `t` | `timestamptz` | Search start time |
+
+### Returns
+
+The `timestamptz` of the next sunrise, or NULL if the Sun does not rise within 7 days (circumpolar or never-rises).
+
+### Example
+
+```sql
+-- Next sunrise from Boise
+SELECT sun_next_rise('43.7N 116.4W 800m'::observer, now());
+```
+
+---
+
+## sun_next_set
+
+Returns the next geometric sunset after the given time.
+
+### Signature
+
+```sql
+sun_next_set(obs observer, t timestamptz) → timestamptz
+```
+
+### Parameters
+
+| Parameter | Type | Description |
+|-----------|------|-------------|
+| `obs` | `observer` | Observer location |
+| `t` | `timestamptz` | Search start time |
+
+### Returns
+
+The `timestamptz` of the next sunset, or NULL if the Sun does not set within 7 days.
+
+### Example
+
+```sql
+-- How long until sunset?
+SELECT sun_next_set('43.7N 116.4W 800m'::observer, now()) - now() AS time_until_sunset;
+```
+
+---
+
+## moon_next_rise
+
+Returns the next geometric moonrise after the given time.
+
+### Signature
+
+```sql
+moon_next_rise(obs observer, t timestamptz) → timestamptz
+```
+
+### Parameters
+
+| Parameter | Type | Description |
+|-----------|------|-------------|
+| `obs` | `observer` | Observer location |
+| `t` | `timestamptz` | Search start time |
+
+### Returns
+
+The `timestamptz` of the next moonrise, or NULL if the Moon does not rise within 7 days.
+
+### Example
+
+```sql
+-- Next moonrise
+SELECT moon_next_rise('40.0N 105.3W 1655m'::observer, now());
+```
+
+---
+
+## moon_next_set
+
+Returns the next geometric moonset after the given time.
+
+### Signature
+
+```sql
+moon_next_set(obs observer, t timestamptz) → timestamptz
+```
+
+### Parameters
+
+| Parameter | Type | Description |
+|-----------|------|-------------|
+| `obs` | `observer` | Observer location |
+| `t` | `timestamptz` | Search start time |
+
+### Returns
+
+The `timestamptz` of the next moonset, or NULL if the Moon does not set within 7 days.
+
+### Example
+
+```sql
+-- Moon visibility window
+SELECT moon_next_rise('40.0N 105.3W 1655m'::observer, now()) AS moonrise,
+ moon_next_set('40.0N 105.3W 1655m'::observer, now()) AS moonset;
+```
+
+---
+
+## planet_next_rise
+
+Returns the next geometric rise time for a planet after the given time.
+
+### Signature
+
+```sql
+planet_next_rise(body_id int4, obs observer, t timestamptz) → timestamptz
+```
+
+### Parameters
+
+| Parameter | Type | Description |
+|-----------|------|-------------|
+| `body_id` | `int4` | Planet identifier: 1=Mercury, 2=Venus, 4=Mars, 5=Jupiter, 6=Saturn, 7=Uranus, 8=Neptune |
+| `obs` | `observer` | Observer location |
+| `t` | `timestamptz` | Search start time |
+
+### Returns
+
+The `timestamptz` of the next rise, or NULL if the planet does not rise within 7 days. Raises an error for invalid `body_id` (0=Sun, 3=Earth, 10=Moon have dedicated functions).
+
+### Example
+
+```sql
+-- When does Jupiter rise tonight?
+SELECT planet_next_rise(5, '43.7N 116.4W 800m'::observer, now());
+```
+
+---
+
+## planet_next_set
+
+Returns the next geometric set time for a planet after the given time.
+
+### Signature
+
+```sql
+planet_next_set(body_id int4, obs observer, t timestamptz) → timestamptz
+```
+
+### Parameters
+
+| Parameter | Type | Description |
+|-----------|------|-------------|
+| `body_id` | `int4` | Planet identifier: 1=Mercury, 2=Venus, 4=Mars, 5=Jupiter, 6=Saturn, 7=Uranus, 8=Neptune |
+| `obs` | `observer` | Observer location |
+| `t` | `timestamptz` | Search start time |
+
+### Returns
+
+The `timestamptz` of the next set, or NULL if the planet does not set within 7 days.
+
+### Example
+
+```sql
+-- Venus visibility window
+SELECT planet_next_rise(2, '43.7N 116.4W 800m'::observer, now()) AS venus_rise,
+ planet_next_set(2, '43.7N 116.4W 800m'::observer, now()) AS venus_set;
+```
+
+---
+
+## sun_next_rise_refracted
+
+Returns the next refracted sunrise after the given time. The threshold is -0.833 degrees geometric elevation, accounting for atmospheric refraction (0.569 deg) and the Sun's semidiameter (0.266 deg). Refracted sunrise is earlier than geometric sunrise.
+
+### Signature
+
+```sql
+sun_next_rise_refracted(obs observer, t timestamptz) → timestamptz
+```
+
+### Parameters
+
+| Parameter | Type | Description |
+|-----------|------|-------------|
+| `obs` | `observer` | Observer location |
+| `t` | `timestamptz` | Search start time |
+
+### Returns
+
+The `timestamptz` of the next refracted sunrise, or NULL if the Sun does not rise within 7 days.
+
+### Example
+
+```sql
+-- Refracted vs geometric sunrise difference
+SELECT sun_next_rise_refracted(obs, t) AS refracted,
+ sun_next_rise(obs, t) AS geometric
+FROM (VALUES ('43.7N 116.4W 800m'::observer, '2024-01-15 00:00:00+00'::timestamptz)) AS v(obs, t);
+```
+
+---
+
+## sun_next_set_refracted
+
+Returns the next refracted sunset after the given time. Uses the same -0.833 degree threshold. Refracted sunset is later than geometric sunset.
+
+### Signature
+
+```sql
+sun_next_set_refracted(obs observer, t timestamptz) → timestamptz
+```
+
+### Parameters
+
+| Parameter | Type | Description |
+|-----------|------|-------------|
+| `obs` | `observer` | Observer location |
+| `t` | `timestamptz` | Search start time |
+
+### Returns
+
+The `timestamptz` of the next refracted sunset, or NULL if the Sun does not set within 7 days.
+
+### Example
+
+```sql
+-- How much longer is the refracted day?
+SELECT sun_next_set_refracted(obs, t) - sun_next_rise_refracted(obs, t) AS refracted_day,
+ sun_next_set(obs, t) - sun_next_rise(obs, t) AS geometric_day
+FROM (VALUES ('43.7N 116.4W 800m'::observer, '2024-03-20 00:00:00+00'::timestamptz)) AS v(obs, t);
+```
+
+---
+
+## moon_next_rise_refracted
+
+Returns the next refracted moonrise after the given time. Uses the -0.833 degree threshold (refraction + semidiameter).
+
+### Signature
+
+```sql
+moon_next_rise_refracted(obs observer, t timestamptz) → timestamptz
+```
+
+### Parameters
+
+| Parameter | Type | Description |
+|-----------|------|-------------|
+| `obs` | `observer` | Observer location |
+| `t` | `timestamptz` | Search start time |
+
+### Returns
+
+The `timestamptz` of the next refracted moonrise, or NULL if the Moon does not rise within 7 days.
+
+### Example
+
+```sql
+SELECT moon_next_rise_refracted('40.0N 105.3W 1655m'::observer, now());
+```
+
+---
+
+## moon_next_set_refracted
+
+Returns the next refracted moonset after the given time. Uses the -0.833 degree threshold.
+
+### Signature
+
+```sql
+moon_next_set_refracted(obs observer, t timestamptz) → timestamptz
+```
+
+### Parameters
+
+| Parameter | Type | Description |
+|-----------|------|-------------|
+| `obs` | `observer` | Observer location |
+| `t` | `timestamptz` | Search start time |
+
+### Returns
+
+The `timestamptz` of the next refracted moonset, or NULL if the Moon does not set within 7 days.
+
+### Example
+
+```sql
+SELECT moon_next_set_refracted('40.0N 105.3W 1655m'::observer, now());
+```
+
+---
+
+## planet_next_rise_refracted
+
+Returns the next refracted rise time for a planet after the given time. The threshold is -0.569 degrees geometric elevation, accounting for atmospheric refraction only (planets are point sources).
+
+### Signature
+
+```sql
+planet_next_rise_refracted(body_id int4, obs observer, t timestamptz) → timestamptz
+```
+
+### Parameters
+
+| Parameter | Type | Description |
+|-----------|------|-------------|
+| `body_id` | `int4` | Planet identifier: 1=Mercury, 2=Venus, 4=Mars, 5=Jupiter, 6=Saturn, 7=Uranus, 8=Neptune |
+| `obs` | `observer` | Observer location |
+| `t` | `timestamptz` | Search start time |
+
+### Returns
+
+The `timestamptz` of the next refracted rise, or NULL if the planet does not rise within 7 days.
+
+
+
+### Example
+
+```sql
+-- Refracted vs geometric rise for Saturn
+SELECT planet_next_rise_refracted(6, obs, t) AS refracted,
+ planet_next_rise(6, obs, t) AS geometric
+FROM (VALUES ('43.7N 116.4W 800m'::observer, now())) AS v(obs, t);
+```
+
+---
+
+## planet_next_set_refracted
+
+Returns the next refracted set time for a planet after the given time. Uses the -0.569 degree threshold.
+
+### Signature
+
+```sql
+planet_next_set_refracted(body_id int4, obs observer, t timestamptz) → timestamptz
+```
+
+### Parameters
+
+| Parameter | Type | Description |
+|-----------|------|-------------|
+| `body_id` | `int4` | Planet identifier: 1=Mercury, 2=Venus, 4=Mars, 5=Jupiter, 6=Saturn, 7=Uranus, 8=Neptune |
+| `obs` | `observer` | Observer location |
+| `t` | `timestamptz` | Search start time |
+
+### Returns
+
+The `timestamptz` of the next refracted set, or NULL if the planet does not set within 7 days.
+
+### Example
+
+```sql
+-- Mars visibility tonight (refracted)
+SELECT planet_next_rise_refracted(4, obs, t) AS mars_rise,
+ planet_next_set_refracted(4, obs, t) AS mars_set
+FROM (VALUES ('43.7N 116.4W 800m'::observer, now())) AS v(obs, t);
+```
+
+---
+
+## sun_rise_set_status
+
+Reports whether the Sun rises and sets, is circumpolar, or never rises at the given location and time. Samples elevation at 48 points across 24 hours.
+
+### Signature
+
+```sql
+sun_rise_set_status(obs observer, t timestamptz) → text
+```
+
+### Parameters
+
+| Parameter | Type | Description |
+|-----------|------|-------------|
+| `obs` | `observer` | Observer location |
+| `t` | `timestamptz` | Reference time (defines the 24h sampling window) |
+
+### Returns
+
+One of three text values: `'rises_and_sets'`, `'circumpolar'`, or `'never_rises'`.
+
+### Example
+
+```sql
+-- Why doesn't the Sun set? (Arctic summer)
+SELECT sun_rise_set_status('70N 25E 0m'::observer, '2024-06-21 00:00:00+00'::timestamptz);
+-- → 'circumpolar'
+
+-- Normal mid-latitude behavior
+SELECT sun_rise_set_status('43.7N 116.4W 800m'::observer, now());
+-- → 'rises_and_sets'
+```
+
+---
+
+## moon_rise_set_status
+
+Reports whether the Moon rises and sets, is circumpolar, or never rises at the given location and time.
+
+### Signature
+
+```sql
+moon_rise_set_status(obs observer, t timestamptz) → text
+```
+
+### Parameters
+
+| Parameter | Type | Description |
+|-----------|------|-------------|
+| `obs` | `observer` | Observer location |
+| `t` | `timestamptz` | Reference time |
+
+### Returns
+
+One of three text values: `'rises_and_sets'`, `'circumpolar'`, or `'never_rises'`.
+
+### Example
+
+```sql
+-- Check Moon status before querying rise/set
+SELECT moon_rise_set_status('80N 0E 0m'::observer, now()) AS status,
+ moon_next_rise('80N 0E 0m'::observer, now()) AS next_rise;
+```
+
+---
+
+## planet_rise_set_status
+
+Reports whether a planet rises and sets, is circumpolar, or never rises at the given location and time.
+
+### Signature
+
+```sql
+planet_rise_set_status(body_id int4, obs observer, t timestamptz) → text
+```
+
+### Parameters
+
+| Parameter | Type | Description |
+|-----------|------|-------------|
+| `body_id` | `int4` | Planet identifier: 1=Mercury, 2=Venus, 4=Mars, 5=Jupiter, 6=Saturn, 7=Uranus, 8=Neptune |
+| `obs` | `observer` | Observer location |
+| `t` | `timestamptz` | Reference time |
+
+### Returns
+
+One of three text values: `'rises_and_sets'`, `'circumpolar'`, or `'never_rises'`.
+
+### Example
+
+```sql
+-- Check all planets' status from a high-latitude site
+SELECT body_id, name,
+ planet_rise_set_status(body_id, '65N 18W 0m'::observer, now()) AS status
+FROM (VALUES (1,'Mercury'),(2,'Venus'),(4,'Mars'),(5,'Jupiter'),
+ (6,'Saturn'),(7,'Uranus'),(8,'Neptune')) AS p(body_id, name);
+```
+
+---
+
+## constellation
+
+Returns the 3-letter IAU constellation abbreviation for a sky position. Uses the Roman (1987) boundary table (CDS VI/42) with 357 boundary segments. Input coordinates are precessed from J2000 to B1875.0 internally to match the boundary epoch.
+
+### Signature
+
+```sql
+constellation(eq equatorial) → text
+constellation(ra_hours float8, dec_deg float8) → text
+```
+
+### Parameters
+
+**Overload 1 --- from equatorial type:**
+
+| Parameter | Type | Description |
+|-----------|------|-------------|
+| `eq` | `equatorial` | Equatorial sky position (RA in hours, Dec in degrees) |
+
+**Overload 2 --- from explicit coordinates:**
+
+| Parameter | Type | Unit | Description |
+|-----------|------|------|-------------|
+| `ra_hours` | `float8` | hours | Right ascension in J2000, range [0, 24) |
+| `dec_deg` | `float8` | degrees | Declination in J2000, range [-90, 90] |
+
+### Returns
+
+A 3-letter IAU constellation abbreviation (e.g., `'Ori'`, `'UMa'`, `'Sgr'`). There are 88 possible values, one for every IAU constellation.
+
+### Example
+
+```sql
+-- What constellation is Jupiter in?
+SELECT constellation(planet_equatorial(5, now()));
+-- → 'Ari'
+
+-- Polaris
+SELECT constellation(2.5303, 89.2641);
+-- → 'UMi'
+
+-- Orion's belt star Alnitak
+SELECT constellation(5.679, -1.943);
+-- → 'Ori'
+```
+
+---
+
+## constellation_full_name
+
+Converts a 3-letter IAU constellation abbreviation to its full IAU name.
+
+### Signature
+
+```sql
+constellation_full_name(abbr text) → text
+```
+
+### Parameters
+
+| Parameter | Type | Description |
+|-----------|------|-------------|
+| `abbr` | `text` | 3-letter IAU abbreviation (e.g., `'Ori'`, `'UMa'`) |
+
+### Returns
+
+The full IAU constellation name (e.g., `'Orion'`, `'Ursa Major'`), or NULL if the abbreviation is not recognized.
+
+### Example
+
+```sql
+-- Full name for display
+SELECT constellation_full_name(constellation(planet_equatorial(5, now())));
+-- → 'Aries'
+
+-- All 88 constellations (abbreviated sample)
+SELECT constellation_full_name('Ori') AS orion,
+ constellation_full_name('UMa') AS ursa_major,
+ constellation_full_name('Sgr') AS sagittarius,
+ constellation_full_name('Crx') AS invalid;
+-- → 'Orion', 'Ursa Major', 'Sagittarius', NULL
+```
+
+```sql
+-- What constellation is each planet in right now?
+SELECT name,
+ constellation(planet_equatorial(body_id, now())) AS abbr,
+ constellation_full_name(constellation(planet_equatorial(body_id, now()))) AS constellation
+FROM (VALUES (1,'Mercury'),(2,'Venus'),(4,'Mars'),(5,'Jupiter'),
+ (6,'Saturn'),(7,'Uranus'),(8,'Neptune')) AS p(body_id, name);
+```