RFC-041 — AR night sky: one sky, everything on it (constellations + stars + deep-sky + horizon)
Status: Implemented · 2026-08-27 · builds on: the sky-pointing AR scene (src/lib/ar/sky-scene.ts, #393/#51), the astronomy layer (src/lib/astronomy/, equatorialToHorizontal), the existing celestial data (static/data/universe/constellation-lines.json, named-stars.json, deep-sky-objects.json) · target: v0.8 · Tracking: #487, #488
Why this is an RFC. It freezes an architecture decision that would otherwise be re-litigated per feature: the AR sky is one sky with additive content layers, not a planets-sky vs a constellations-sky vs a per-
/explore- scale sky. That decision binds several subsystems in lockstep — the AR scene (sky-scene.ts), the shared coordinate contract (equatorial data →equatorial ToHorizontal→skyDirectionENU, the same transform the planets use, so a new layer can never drift into its own frame), the celestial data contracts (constellation-lines.json/named-stars.jsonshapes), and the AR HUD's layer model (what's toggleable, defaults). Get the frame wrong and constellations silently misalign from the planets; split the sky per scale and the "see everything on one screen" intent is lost. Freeze the one-sky model + the vertex→ENU contract once, then slice.
1 · The idea (operator, 2026-08-26)
When you hold the phone up in AR "SKY" mode you are looking at the one real sky. Today it marks only the Solar-System bodies (Sun, Moon, planets) + the ISS/Tiangong. The operator wants to see everything we have data for on that one screen — the planets and the constellations and the bright named stars — all at once, entered from one place, not a planets-sky vs a constellations-sky.
"I don't wanna have one sky with planets, another one with constellations… we wanna have only the sky on the first one, and we see everything on the sky there coming from all the other tops as well."
2 · Decision
One AR Sky feature with additive content layers, all on by default. The /explore scale (Solar System → Neighborhood → Milky Way) is a navigation concept in the flat/3D view; it is not a property of the real sky. So the sky is one thing, and the scale-specific topics (planets, constellations, stars) are just layers on that one sky:
- Planets / Sun / Moon / stations — already built (#393/#51).
- Constellation figures + names — Slice 1 (shipped).
- Bright named stars + labels — Slice 2 (shipped).
- Deep-sky objects (nebulae / galaxies / clusters) — soft category-coloured glows + labels, #488 (shipped). Big diffuse "clouds in the distance" to orient by.
- Horizon line + hide-below-horizon (default hidden, with a toggle) + sunrise/sunset horizon markers — #488 (shipped).
Entry stays the single existing SKY affordance (solar-system scale). We do not add per-scale sky variants or per-scale entry points — the operator explicitly wants one screen showing everything. Layer toggles exist only as optional decluttering; the default is everything visible.
3 · How (reuses the pipeline we just hardened — no new astronomy)
The AR scene already anchors a marker at a body's real direction via skyPosition(body,date,lat,lon) → skyDirectionENU. Constellations and stars plug into the same pipe, because the celestial data is already in the equatorial frame:
constellation-lines.json— 89 constellations; each is a flat list of equatorial XYZ vertices (built from RA/Dec bybuild-constellation-lines), every 2 points = one figure segment.named-stars.json— 62 bright named stars (Sirius, Vega, …) with proper name, magnitude, and equatorial XYZ (standard HYG frame: x=vernal equinox, z=NCP).
For any vertex (x,y,z) (equatorial): raRad = atan2(y,x), decRad = asin(z/‖v‖) → equatorialToHorizontal({raRad,decRad,distanceAu:∞}, jd, lat, lon) → alt/az → skyDirectionENU → the same world direction the planets use (distance set effectively infinite ⇒ zero topocentric parallax). Render: constellation segments as THREE.LineSegments anchored at camPos + dir·R; stars as point sprites sized by magnitude with labels on the brightest. Recompute at the existing ~2 Hz ephemeris cadence so the field tracks sidereal rotation.
Because constellations ride the same ENU transform as the planets, a familiar constellation landing where you actually see it is also a free correctness check on the whole sky alignment.
4 · Non-goals
- No per-scale skies / per-scale entry. One sky, one entry (§2).
- No new imagery or new catalogs. Uses the existing
constellation-lines.json,named-stars.json+deep-sky-objects.jsononly. A deeper star catalog (fainter mags) is out of scope — 62 named stars + the figure lines + 141 deep-sky objects are the "everything we have". - No zodiac artwork layer (the drawn zodiac characters over the figures). The operator scoped it out of this batch ("everything except for the zodiac"); it remains a possible later layer.
- Not touching the non-AR
/exploreconstellation science layer.
5 · Risks / design constraints
- Clutter. 88 figures + stars can overwhelm a phone FOV. Mitigate with subtle line weight/opacity, labels on major/brightest only, and an optional per-layer declutter toggle. Tune on-device.
- Correctness rides on the planet alignment, still unconfirmed on-device (the #51 AR retest). If planets are off, constellations are off the same way.
- Data availability on native.
data/universe/*is small (constellation-lines ≈ 39 KB) and already loaded by/explore; reuse theuniverse.tsget()loader (streams from the asset origin) so no bundle change.
6 · Slices
- S1 — constellation lines + names in the AR sky + a Constellations toggle. ✓
- S2 — bright named stars + labels + a Stars toggle. ✓
- S3 — everything on by default (one screen); wire the optional toggles into the AR HUD. (Originally "ungate entry across scales" — dropped per §2.) ✓
- S4 (#488) — deep-sky / nebula glows + labels + a Nebulas toggle. ✓
- S5 (#488) — horizon line, hide-below-horizon (default on, toggle), and sunrise/sunset horizon markers + a Rise/set toggle. ✓
Correctness is unit-tested in isolation (celestial-sky.test.ts): RA/Dec→ENU via the north celestial pole, the deep-sky filter, and sunrise/sunset azimuths (equinox equator ⇒ rises due-E / sets due-W). Visual framing is the operator's on-device check alongside the #51 AR retest; desktop framing verified via the /dev/sky-preview harness (a mock SkyView at a fixed location + heading).
7 · Editorial redesign (2026-08-27) — advisor-led
The first-cut layers were functional but cluttered and off-brand. A design pass (Fable-5 advisor review) pulled the whole sky to a "printed star atlas / WIRED" direction, aligned to tokens.css + the /fly + ConstellationFinder idioms:
- Labels — no chip pills.
makeTextSpriterenders box-free UPPERCASE tracked labels with a dark shadow-halo (the /fly milestone idiom), in tiers: constellations whisper (faint/wide), stars small, planets a bold display tier, cardinals/sun quiet. Typography carries the hierarchy. - Label budget + declutter — only "primary" labels show by default (brightest stars mag<1.5 + real-named deep-sky); bare catalog IDs (
NGC…) and faint stars wait for an All names toggle. A screen-space pass projects every label, priority-sorts (planets > sun > stars > stations > figures > deep-sky), rejects overlaps and requires the whole box on-screen (kills the Orion's-belt smear + edge clip). - Starfield / deep-sky — hard-core star texture + diffraction spikes on the brightest; deep-sky as small shaped ice-blue glows (galaxy ellipse / cluster speckle / planetary ring / nebula cloud), not grey smudges.
- Planets (
planet-bake.ts, advisor Path B) — baked from the shippedstatic/textures/2k_*.jpgmaps: each body rendered once as a real 3D sphere lit along its true Sun direction into aWebGLRenderTarget→ sprite map. Real albedo, Jupiter's belts + Great Red Spot, Saturn's rings (real 3D occlusion), correct Mercury/Venus/Moon phases from the light geometry, a granulated Sun. Marker size tracks apparent angular diameter (log-compressed from livedistanceAu: Sun≈Moon; Mars grows toward opposition) and brightness drives a per-body aura — one honest quantity per channel (size = geometry, glow = brightness, phase = Sun geometry). Procedural canvas markers remain the pre-bake fallback. - Stations — ISS/Tiangong render as a small coloured diamond glyph (hardware, not worlds), and respect the below-horizon toggle like the planets.
- HUD — one dark-glass strip of text buttons (no emoji, teal status dots), editorial serif hint, chip + scene-fade motion (reduced-motion-safe).
Deferred: per-layer canvas cross-fade on toggle (multi-material + setDrawRange packing make it fight the framework); the true bright-limb orientation of inner- planet crescents (the lit fraction is correct, the terminator tilt is simplified); a daytime "clean disc" Sun variant. All redesign work is desktop-verified via /dev/sky-preview; on-device (WKWebView render-target bakes over the live camera feed) remains the operator's smoke test.
0.8.1 amendment — precession to the equinox of date (M1)
An independent physics review found the sky pipeline mixed reference frames: astronomy/planets.ts outputs J2000 ecliptic positions and the HYG star catalogue is J2000 equatorial, yet equatorialToHorizontal already used the of-date obliquity and of-date local sidereal time — a ~0.36°-in-2026 framing error (growing ~0.014°/yr) between the two ends of the pipeline. Both source frames are now precessed to the equinox of date before the of-date steps consume them, so planets and stars share one consistent sky:
- Planets + Sun —
precessEclipticJ2000ToDaterotates the ecliptic vector about the ecliptic pole by the general precession in longitude (index.ts, beforeeclipticToEquatorial). The Schlyter Moon is already ecliptic-of- date and is left untouched. - Stars / constellations / deep-sky —
precessEquatorialJ2000ToDate(rigorous IAU 1976 ζ/z/θ rotation matrix) precesses the HYG equatorial directions incelestial-sky.tsbefore RA/Dec.
Verified against Meeus worked examples: planet alt/az ex. 13.b (Venus 248.031°/15.130° vs 248.03°/15.12°) and equatorial precession ex. 21.b (θ Persei, arcsecond agreement modulo the star's proper motion).