From cfdb0d62522cf030737847c8e54f5c4d248c1751 Mon Sep 17 00:00:00 2001 From: m3ultra Date: Wed, 15 Jul 2026 23:15:17 +1000 Subject: [PATCH] =?UTF-8?q?=F0=9F=8C=9E=20genesis:=20SOLARGOD=20brief=20?= =?UTF-8?q?=E2=80=94=20the=20solar=20system=20gets=20its=20instrument=20(f?= =?UTF-8?q?able)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Full execution spec for Opus 4.8: Three.js compressed-scale explorer on GODSIGH's architecture DNA. Authoritative JPL Standish element tables, Horizons ground-truth verification gates, 7 stages GENESIS→RESONANCE. Co-Authored-By: Claude Fable 5 --- .gitignore | 4 + CLAUDE.md | 19 +++ README.md | 29 ++++ SOLARGOD_BRIEF.md | 415 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 467 insertions(+) create mode 100644 .gitignore create mode 100644 CLAUDE.md create mode 100644 README.md create mode 100644 SOLARGOD_BRIEF.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..51eb57d --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +cache/ +.DS_Store +__pycache__/ +*.pyc diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..91ad2e9 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,19 @@ +# SOLARGOD — Claude context + +Standalone browser-based 3D solar system explorer (Three.js, no build step). The +complete execution spec is **SOLARGOD_BRIEF.md** — read it in full before coding; +it contains the authoritative JPL element tables, verification checkpoints with +Horizons ground truth, the stage plan, and the known traps. + +- Sibling project & architecture reference: `/Users/m3ultra/Documents/GODSIGH` + (CesiumJS OSINT globe, port 8137) — **read-only**, never modify it. Port its + idioms (layer contract, HUD, hash state, serve.py proxy), not its code verbatim. +- Run: `python3 serve.py` → http://127.0.0.1:8147 (stdlib only, no deps). +- Origin: Gitea `ssh://git@100.71.119.27:222/monster/solargod.git`, default branch + `main`. Branch per stage (`stage/N-name`), merge on green gate, push. Commits: + emoji prefix + short message + `(opus)` suffix. +- Verification gates in `verify.html` are mandatory per stage — measurement beats + reasoning; never trust remembered constants over a fetched JPL value. +- `cache/` is serve.py's disk cache — gitignored, never commit it. +- Keep committed assets lean (~25 MB texture budget) and attribute CC BY sources + in `assets/textures/CREDITS.md` + the HUD footer. diff --git a/README.md b/README.md new file mode 100644 index 0000000..3eb22da --- /dev/null +++ b/README.md @@ -0,0 +1,29 @@ +# SOLARGOD ▸ Solar System View + +A browser-based **compressed-scale 3D solar system explorer** — real ephemerides, +scrubbable time across centuries, jump-to-orbit navigation, and live layers for +deep-space probes, asteroids, and comets. Sibling of +[GODSIGH](../GODSIGH/README.md), which watches Earth; SOLARGOD watches everything +else. Everything runs client-side from free, public, keyless data. + +**Status: pre-build.** The full spec lives in [SOLARGOD_BRIEF.md](SOLARGOD_BRIEF.md). + +## Quickstart (once built) + +```sh +python3 serve.py +# open http://127.0.0.1:8147 +``` + +## Planned layers + +| Layer | Source | Notes | +|---|---|---| +| **Planets** | JPL Keplerian elements (Standish) | computed locally, no network — scrub 1800–2050 | +| **Moons & rings** | JPL SSD satellite elements | 21 major moons, Saturn/Uranus rings | +| **Spacecraft** | JPL Horizons | Voyagers, New Horizons, JWST, Parker — where they are *now* | +| **Asteroids** | JPL SBDB | real main-belt sample + NEO close approaches (CNEOS CAD) | +| **Comets** | JPL SBDB | Halley & friends, full orbits, 1/r² tails | + +MEGA mode compresses distances so the whole system is one readable view; +TRUE mode animates it back out to its real, humbling emptiness. diff --git a/SOLARGOD_BRIEF.md b/SOLARGOD_BRIEF.md new file mode 100644 index 0000000..ec3a59d --- /dev/null +++ b/SOLARGOD_BRIEF.md @@ -0,0 +1,415 @@ +# SOLARGOD — execution brief + +**For: Opus 4.8. Read this whole file before writing any code.** + +SOLARGOD is a standalone, browser-based 3D solar system explorer — the sibling of +GODSIGH (the CesiumJS OSINT globe at `/Users/m3ultra/Documents/GODSIGH`, read-only +reference; do not modify it). GODSIGH proved the house architecture: **no build step, +ES modules, a layer-factory contract, a HUD with per-layer status lines, a scrubbable +time slider with live/scrubbed gating, shareable hash-state URLs, and a zero-dependency +`serve.py` that statically serves the app and proxies the feeds a browser can't call +directly.** SOLARGOD transplants all of that onto **Three.js** — Cesium is an +Earth-geodesy engine and is wrong for interplanetary space. + +The experience: you load the page and you're floating above a **compressed-scale** +("MEGA mode") solar system — every planet visible at once, moving on real ephemerides, +with a time bar you can scrub across centuries. Click Jupiter and the camera flies out +and settles into orbit around it, moons circling. Toggle TRUE scale and the system +animates out into its real, humbling emptiness. Live layers show where Voyager 1, +JWST and Parker Solar Probe are *right now*, real asteroids, comets, and close +approaches. Everything client-side, free, keyless. + +--- + +## 0. Ground rules + +- **No build step. No npm. No framework. No TypeScript.** Plain ES modules loaded by + the browser, Three.js via CDN importmap (pin an exact version, e.g. `three@0.170.0` + from jsdelivr; you need the core + `three/addons/` for OrbitControls, CSS2DRenderer, + Lensflare). Vendoring the two Three.js files into `vendor/` instead of the CDN is + acceptable and preferred if trivial, so the app works offline. +- **serve.py**: stdlib-only Python static server + API proxy (see §6). Default port + **8147** (GODSIGH owns 8137). Quickstart must be exactly `python3 serve.py` → + open `http://127.0.0.1:8147`. +- **Deterministic first**: the core scene (Sun, planets, moons, orbits, time travel) + must work with **zero network calls** — planet positions are computed locally from + Keplerian elements (§3), the way GODSIGH computes satellites from TLEs with SGP4. + Network-dependent layers (spacecraft, asteroids, comets) are additive and must fail + soft with a status line in the HUD, never break the scene. +- **Truth before view**: every position is computed in real heliocentric J2000-ecliptic + AU first, then passed through the view transform (§4). Never compute directly in + view units; never mix the two spaces. One module owns the transform. +- **No n-body integration.** The Standish Keplerian approximation IS the spec. + Resist the urge to be more accurate than the mission needs. +- **Verification gates are mandatory** (§9). Each stage ends with its gate passing; + don't move on with a failing gate. Measurement beats reasoning. +- Git: work on `stage/N-name` branches, merge to `main` when the stage gate passes, + push to `origin` (Gitea, already configured). Commit style matches the house: + emoji prefix + short message + `(opus)` suffix, e.g. + `🪐 kepler: planets on rails — ephemeris engine + orbit paths (opus)`. + +## 1. Repo layout + +``` +solargod/ + index.html # importmap, canvas, HUD skeleton, time bar + serve.py # static + Horizons/SBDB/CAD proxy + disk cache + css/style.css # dark UI, HUD, labels, time bar + js/ + config.js # tunables: scale constants, camera, layer defaults, body registry refs + lib.js # time (JD/centuries), math helpers, eclToWorld(), formatters + ephem.js # Keplerian propagator + element tables (§3) + bodies.js # physical data registry: radii, tilts, rotation, colors, texture paths + scale.js # THE view transform: MEGA/TRUE compression, radius exaggeration (§4) + ui.js # HUD rows, status lines, LIVE/SCRUBBED chip (port GODSIGH's ui.js idioms) + main.js # bootstrap: renderer, clock, focus/camera system, layer loader, picking, hash state + layers/ + planets.js # Stage 1 + moons.js # Stage 3 + rings.js # Stage 3 + spacecraft.js # Stage 4 + asteroids.js # Stage 5 + comets.js # Stage 5 + neos.js # Stage 5 + assets/textures/ # 2k textures + CREDITS.md (§7) + cache/ # serve.py disk cache — gitignored + verify.html # in-browser test runner (§9) + SOLARGOD_BRIEF.md # this file +``` + +**Layer contract — identical to GODSIGH** (`js/layers/*.js` default-exports a factory): + +```js +export default function create(ctx) { + const { scene, camera, CONFIG, lib, ui, scale, ephem, bodies, focus, clock } = ctx; + // ui.addLayer(id, name, defaultOn, onToggle); ui.setStatus(id, text, 'ok'|'warn'|'err') + return { + id: 'x', + onClockTick(simDate, jd, isLive) {}, // every frame; keep allocation-free + handlePick(intersect) { return false; }, + clearPick() {}, + }; +} +``` + +`main.js` loads layers with dynamic `import()` + `Promise.allSettled` so one broken +layer never kills the app (copy GODSIGH `main.js:179-208` idiom). + +## 2. Time system + +- Simulation clock lives in `main.js`: a float64 unix-ms `simTimeMs`, advanced each + frame by `rate * dtWallMs`. `lib.js` provides `jdFromUnixMs(ms)` (JD = ms/86400000 + + 2440587.5) and `centuriesSinceJ2000(jd)` (T = (jd − 2451545.0)/36525). + Ignore TT/TDB−UTC (~69 s) — irrelevant at this scale; note it in a comment. +- **Range: 1800–2050** (Table 1 validity). Clamp scrubbing to it. An extended-range + mode (Table 2a/2b, 3000 BC–3000 AD) is a stretch goal — build the table plumbing so + it's a config swap, but don't build UI for it in v1. +- Controls (bottom bar): **NOW** button, play/pause, a rate control with presets + `1×real · 1 min/s · 1 hr/s · 1 day/s · 1 wk/s · 1 mo/s · 1 yr/s` (also negatives for + reverse), a scrubbable timeline strip spanning the full range with a zoomed inner + strip around the cursor, and a date readout (UTC). +- **LIVE / SCRUBBED chip** exactly like GODSIGH: live = |sim − wallclock| ≤ 60 s at + 1× rate. Live-only network layers pause off-live where that matters (spacecraft + layer interpolates its cached span instead). + +## 3. Ephemeris engine (`ephem.js`) — Standish approximate elements + +Source: JPL SSD "Approximate Positions of the Planets" +(https://ssd.jpl.nasa.gov/planets/approx_pos.html). The tables below were extracted +from that page on 2026-07-15 — **they are authoritative, use them verbatim.** +(The page's `e` column header says "rad" — that's the page's own typo; `e` is +dimensionless. Angles are degrees, rates per Julian century.) + +**Table 1 — J2000 ecliptic elements + rates/century, valid 1800–2050 AD** +(rows: `a[au] e I[deg] L[deg] long.peri ϖ[deg] long.node Ω[deg]`, second line = rates/Cy): + +``` +Mercury 0.38709927 0.20563593 7.00497902 252.25032350 77.45779628 48.33076593 + 0.00000037 0.00001906 -0.00594749 149472.67411175 0.16047689 -0.12534081 +Venus 0.72333566 0.00677672 3.39467605 181.97909950 131.60246718 76.67984255 + 0.00000390 -0.00004107 -0.00078890 58517.81538729 0.00268329 -0.27769418 +EM Bary 1.00000261 0.01671123 -0.00001531 100.46457166 102.93768193 0.0 + 0.00000562 -0.00004392 -0.01294668 35999.37244981 0.32327364 0.0 +Mars 1.52371034 0.09339410 1.84969142 -4.55343205 -23.94362959 49.55953891 + 0.00001847 0.00007882 -0.00813131 19140.30268499 0.44441088 -0.29257343 +Jupiter 5.20288700 0.04838624 1.30439695 34.39644051 14.72847983 100.47390909 + -0.00011607 -0.00013253 -0.00183714 3034.74612775 0.21252668 0.20469106 +Saturn 9.53667594 0.05386179 2.48599187 49.95424423 92.59887831 113.66242448 + -0.00125060 -0.00050991 0.00193609 1222.49362201 -0.41897216 -0.28867794 +Uranus 19.18916464 0.04725744 0.77263783 313.23810451 170.95427630 74.01692503 + -0.00196176 -0.00004397 -0.00242939 428.48202785 0.40805281 0.04240589 +Neptune 30.06992276 0.00859048 1.77004347 -55.12002969 44.96476227 131.78422574 + 0.00026291 0.00005105 0.00035372 218.45945325 -0.32241464 -0.00508664 +``` + +**Table 2a — same format, valid 3000 BC–3000 AD** (ship it in the file, unused by v1 UI): + +``` +Mercury 0.38709843 0.20563661 7.00559432 252.25166724 77.45771895 48.33961819 + 0.00000000 0.00002123 -0.00590158 149472.67486623 0.15940013 -0.12214182 +Venus 0.72332102 0.00676399 3.39777545 181.97970850 131.76755713 76.67261496 + -0.00000026 -0.00005107 0.00043494 58517.81560260 0.05679648 -0.27274174 +EM Bary 1.00000018 0.01673163 -0.00054346 100.46691572 102.93005885 -5.11260389 + -0.00000003 -0.00003661 -0.01337178 35999.37306329 0.31795260 -0.24123856 +Mars 1.52371243 0.09336511 1.85181869 -4.56813164 -23.91744784 49.71320984 + 0.00000097 0.00009149 -0.00724757 19140.29934243 0.45223625 -0.26852431 +Jupiter 5.20248019 0.04853590 1.29861416 34.33479152 14.27495244 100.29282654 + -0.00002864 0.00018026 -0.00322699 3034.90371757 0.18199196 0.13024619 +Saturn 9.54149883 0.05550825 2.49424102 50.07571329 92.86136063 113.63998702 + -0.00003065 -0.00032044 0.00451969 1222.11494724 0.54179478 -0.25015002 +Uranus 19.18797948 0.04685740 0.77298127 314.20276625 172.43404441 73.96250215 + -0.00020455 -0.00001550 -0.00180155 428.49512595 0.09266985 0.05739699 +Neptune 30.06952752 0.00895439 1.77005520 304.22289287 46.68158724 131.78635853 + 0.00006447 0.00000818 0.00022400 218.46515314 0.01009938 -0.00606302 +``` + +**Table 2b — extra terms added to M for Jupiter–Neptune, Table 2a only** +(`M += b·T² + c·cos(f·T) + s·sin(f·T)`, everything in degrees, T in centuries): + +``` +Jupiter -0.00012452 0.06064060 -0.35635438 38.35125000 +Saturn 0.00025899 -0.13434469 0.87320147 38.35125000 +Uranus 0.00058331 -0.97731848 0.17689245 7.67025000 +Neptune -0.00041348 0.68346318 -0.10162547 7.67025000 +``` + +**Algorithm** (per body, per tick — this is the whole engine, ~80 lines): + +1. `T = centuriesSinceJ2000(jd)`; each element `x = x0 + ẋ·T`. +2. Argument of perihelion `ω = ϖ − Ω`; mean anomaly `M = L − ϖ` (+ Table 2b terms if + using Table 2a for the outer planets). Normalize M to (−180°, +180°]. +3. Solve Kepler's equation `E − e·sin E = M` (radians) by Newton: + `E₀ = M + e·sin M`, iterate `E -= (E − e·sinE − M)/(1 − e·cosE)`, tol 1e-9 rad, + max 20 iterations. +4. Orbital-plane coordinates: `x′ = a(cos E − e)`, `y′ = a·√(1−e²)·sin E`. +5. Rotate to heliocentric J2000 ecliptic: + ``` + x = (cosω·cosΩ − sinω·sinΩ·cosI)·x′ + (−sinω·cosΩ − cosω·sinΩ·cosI)·y′ + y = (cosω·sinΩ + sinω·cosΩ·cosI)·x′ + (−sinω·sinΩ + cosω·cosΩ·cosI)·y′ + z = (sinω·sinI)·x′ + (cosω·sinI)·y′ + ``` +6. **Earth = EM Bary** for rendering purposes (the barycenter offset is ~4 700 km, + invisible at any scale we draw). The Moon is drawn by the moons layer around Earth. + +Expose `ephem.helioEcl(bodyId, jd) -> {x,y,z} [AU]` and +`ephem.orbitPath(bodyId, jd, nSamples) -> Float64Array` (one full period sampled in +mean anomaly, re-used for orbit lines; regenerate lazily when |T| drifts, elements +change slowly). The same `keplerToEcl(elements, jd)` core must be reusable for +asteroids/comets in Stage 5 (they come as epoch elements, not element rates: +propagate with `M = M0 + n·(jd − epoch)`, n from `a` via Kepler's third law, +`n[deg/day] = 0.9856076686 / a^1.5`). Skip hyperbolic/parabolic bodies (e ≥ 0.98) +in v1 — filter them out with a status note. + +**Frame convention** (fix once in `lib.eclToWorld(v)`): ecliptic X → Three.js x, +ecliptic Z (north) → Three.js y, ecliptic Y → Three.js −z (keeps the frame +right-handed with Y-up). All of Three-space is "world"; nothing outside `lib.js` +does this mapping by hand. + +## 4. The view transform (`scale.js`) — "compressed mega" + +The whole product lives or dies here. Two scale modes, one smooth parameter. + +- **MEGA mode (default)**: compress heliocentric *radius only*, keep direction true: + `r_view = K · r_AU^P` with **P = 0.4, K = 9.0** as starting tunables in `config.js` + (puts Mercury ≈ 6.2, Earth 9, Jupiter ≈ 17.4, Neptune ≈ 35 scene units — the whole + system fits in one ~80-unit view). Orbit *paths* must be built by compressing the + sampled true positions with the same function so body and path always agree. +- **TRUE mode**: `r_view = K · r_AU` (linear, 1 AU = K units). Mostly empty space. + That's the point. +- **The transition is a feature**: animate exponent `P` from 0.4 → 1.0 over ~2.5 s + (smoothstep) when toggling. Everything — bodies, paths, trails — flows outward. + Implement as `scale.radial(r_AU)` reading a live `P`; nothing else changes. +- **Body draw radius**: true radius in AU (`r_km / 1.496e8`) × view K × an + exaggeration factor `E`. Default E = 1200 for planets, Sun gets its own + (E_sun ≈ 60, else it swallows Mercury). As the camera approaches a focused body, + lerp its effective E → 1 based on `cameraDistance / (E · trueDrawRadius)` so you + can descend to something like true scale in close orbit without being inside a + balloon. Expose a body-scale slider (1× / 100× / 1200×) in the HUD. +- **Moon-system compression** (Stage 3): moons get *local* compression around the + parent: `d_view = R_parent_draw · (1.8 + 1.2 · log10(1 + d_true_km / R_parent_km))` + — guarantees every moon clears the exaggerated parent surface and preserves + ordering. In TRUE mode use plain linear true distance. Same animated blend. +- **Floating origin (critical)**: float32 GPU precision jitters when the camera sits + 1e-4 units from a position of magnitude 30+. Therefore: all true positions are kept + in float64 JS; each frame, `focusPos` (the focused body's view-space position) is + subtracted from every rendered object's position *in JS* before writing to the + scene graph, and the camera orbits near the scene origin. Focus = Sun at boot. + This also makes "in orbit around a moving planet" free — the planet stays at the + origin and the sky moves. Use `logarithmicDepthBuffer: true` on the renderer for + the huge near/far span. + +## 5. Navigation & UX (`main.js` + `ui.js`) + +- **Focus system**: click a body (raycast, generous sphere hit targets) or pick from + a body menu → animated camera flight (~2 s eased): tween the *focus point* from old + to new body while tweening camera distance to `~6× the target's draw radius`, then + hand over to OrbitControls targeting the origin (floating origin does the rest). + Breadcrumb top-left: `SOL ▸ JUPITER ▸ EUROPA`. `Esc` steps out one level. +- **Labels**: CSS2DRenderer DOM labels per body — name + (for the focused body's + children) distance. Fade by screen density; never let labels overlap the HUD. +- **HUD (right, GODSIGH style)**: layer rows with toggle + live status line + (`spacecraft — 8 tracked, Horizons ok`), LIVE/SCRUBBED chip, MEGA/TRUE toggle, + body-scale slider, mode credits footer. +- **Hash state** (port GODSIGH `main.js:85-163` wholesale): + `#f=&t=&s=&L=&cam=` + — poll-serialize 1 Hz with `history.replaceState`, apply once at boot, reject + malformed tokens (GODSIGH learned: `Number('')` is 0 — validate every token). +- **Info panel** (Stage 6, left slide-in on focus): physical fact card (from + `bodies.js`) + **live computed** facts — current distance from Sun and from Earth + (AU + km), light-time ("sunlight is 43 min old at Jupiter right now"), current + orbital speed, length of day/year. These live numbers are the soul of the tool — + they come free from the ephemeris. + +## 6. serve.py — static + proxy + cache + +stdlib only (`http.server`, `urllib.request`, `hashlib`, `json`, `pathlib`). Serves +the repo dir; plus proxy endpoints (the browser cannot call JPL directly — verified: +**no CORS headers on ssd.jpl.nasa.gov or ssd-api.jpl.nasa.gov**): + +- `/proxy/horizons?` → `https://ssd.jpl.nasa.gov/api/horizons.api?` +- `/proxy/sbdb?` → `https://ssd-api.jpl.nasa.gov/sbdb_query.api?` +- `/proxy/cad?` → `https://ssd-api.jpl.nasa.gov/cad.api?` + +Allowlist the upstream hosts + paths (never a general proxy). **Disk cache** in +`cache/` keyed by sha256 of the full upstream URL: responses whose requested time +span lies entirely in the past never expire; anything else TTL 24 h. Be polite to +JPL — the cache is the rate limiter. Print one boot line like GODSIGH's serve.py. + +## 7. Textures & assets + +- Planet/moon textures: **Solar System Scope texture pack** (CC BY 4.0, + https://www.solarsystemscope.com/textures/) — download the **2k** maps for Sun, the + 8 planets, Moon, plus the 8k→resized-to-4k Milky Way panorama for the skybox and + the Saturn ring texture (PNG with alpha). Commit them under `assets/textures/` with + an `assets/textures/CREDITS.md` (CC BY requires attribution; also credit in the HUD + footer). Keep the committed set under ~25 MB total; downscale with `sips` if needed. +- Every textured material needs a **procedural fallback** (flat color from + `bodies.js`) so a missing file degrades gracefully. +- Moons without textures: colored lambert spheres. Sun: emissive texture + additive + sprite glow (+ subtle Lensflare addon if cheap). +- Rings: Saturn — annulus geometry, inner 74 500 km / outer 140 220 km (true km, + scaled with the parent's draw scale), ring texture, `DoubleSide`, transparent. + Uranus gets a thin schematic gray ring. Tilt with the parent's axial tilt. + +## 8. Physical data (`bodies.js`) + +Registry keyed by body id with: `name, type, radiusKm, axialTiltDeg, rotationHours +(negative = retrograde), color, texture, parent, elements|moonOrbit`. Radii to use: +Sun 695 700 · Mercury 2 439.7 · Venus 6 051.8 · Earth 6 371.0 · Mars 3 389.5 · +Jupiter 69 911 · Saturn 58 232 · Uranus 25 362 · Neptune 24 622 · Moon 1 737.4 · +Pluto 1 188.3. Source the rest (tilts, rotation periods, moon data) from JPL SSD +(https://ssd.jpl.nasa.gov/planets/phys_par.html and /sats/elem/) — **fetch and check, +don't trust training memory for numbers.** + +Moons v1 roster (circular orbits in the parent's equatorial plane; correct `a` and +period, phase = deterministic function of jd so scrubbing is stable). Starter values +(verify against JPL): Moon 384 400 km / 27.322 d · Phobos 9 378 / 0.319 · Deimos +23 459 / 1.262 · Io 421 700 / 1.769 · Europa 671 034 / 3.551 · Ganymede 1 070 412 / +7.155 · Callisto 1 882 709 / 16.689 · Mimas 185 539 / 0.942 · Enceladus 238 042 / +1.370 · Tethys 294 672 / 1.888 · Dione 377 415 / 2.737 · Rhea 527 068 / 4.518 · +Titan 1 221 870 / 15.945 · Iapetus 3 560 851 / 79.33 · Miranda 129 900 / 1.413 · +Ariel 190 900 / 2.520 · Umbriel 266 000 / 4.144 · Titania 436 300 / 8.706 · Oberon +583 500 / 13.46 · **Triton 354 759 / −5.877 (retrograde)** · Charon 19 591 / 6.387. +Pluto itself: treat as a Stage 5 small body (SBDB elements), but give it a real +texture-less sphere and Charon. + +## 9. Verification gates (`verify.html`) + +A standalone page that imports `lib.js` + `ephem.js`, runs assertions, renders a +PASS/FAIL table. No test framework. **Ground truth from JPL Horizons** (heliocentric, +J2000 ecliptic, AU, TDB — fetched 2026-07-15 from the live API; JD 2461236.5 = +2026-Jul-15 00:00 TDB): + +| Body | X | Y | Z | tolerance | +|---------|---|---|---|-----------| +| Mars (499) | +1.05351078 | +1.00862522 | −0.00469549 | ≤ 0.005 AU each axis | +| Jupiter (599) | −3.01967256 | +4.33218144 | +0.04956505 | ≤ 0.01 AU | +| EM Bary (3) | +0.38381080 | −0.94118486 | +0.00005421 | ≤ 0.003 AU | + +Add at least two more epochs (e.g. J2000.0 itself and 1900-01-01) by querying +Horizons through the proxy from `verify.html` and comparing live — that also gates +the proxy. Additional gates: Kepler solver converges for e = 0.97; `orbitPath` +endpoints join; `eclToWorld` round-trips; MEGA→TRUE transition keeps direction +(unit-vector dot ≈ 1). Spacecraft gate (Stage 4): Voyager 1 (`COMMAND='-31'`) on +2026-07-15 must plot at (−32.07, −136.21, +98.55) AU ecliptic — ~172 AU out, +high above the plane; JWST (`-170`) must hug Earth (Δ < 0.02 AU). + +## 10. Stages + +Branch per stage; gate before merge; one commit per coherent step. + +- **Stage 0 — GENESIS** (scaffold): repo layout, `.gitignore` (`cache/`, `.DS_Store`), + serve.py static-only, index.html + importmap, renderer with logarithmic depth, + Milky Way skybox, Sun with glow, OrbitControls, HUD + time-bar skeletons (static), + 60 fps. Gate: boots clean, no console errors, `verify.html` scaffolding runs. +- **Stage 1 — KEPLER** (the engine): `ephem.js` + tables, planets layer (textured, + exaggerated), orbit paths, labels, full time system (§2) driving it, MEGA layout. + Gate: §9 Horizons checkpoints pass; scrub 1800→2050 smoothly; retrograde loops of + Mars visible when watched from Earth-focus (eyeball check). +- **Stage 2 — HELM** (navigation): focus system, camera flights, floating origin, + breadcrumb, MEGA/TRUE animated toggle, body-scale slider, hash-state URLs, proxy + endpoints in serve.py (+ cache). Gate: jump Sun→Jupiter→back at 60 fps with zero + visible jitter while clock runs at 1 mo/s; a pasted hash URL reproduces the view. +- **Stage 3 — RETINUE** (worlds become places): moons layer w/ local compression, + axial tilts, rotation (textures spin at true rates), sun-lit materials (day/night + terminator falls out of a single point light at the Sun — verify Earth's night side + faces away), Saturn/Uranus rings. Gate: Galilean moons orbit with correct relative + periods (Io:Europa:Ganymede ≈ 1:2:4 — count laps at 1 day/s); Triton runs backwards. +- **Stage 4 — PROBES** (the live layer): spacecraft via Horizons proxy. Roster: + Voyager 1 `-31`, Voyager 2 `-32`, New Horizons `-98`, JWST `-170`, Parker Solar + Probe `-96`, plus 2–3 more that resolve cleanly (check Juno, Lucy, Psyche ids via + Horizons lookup API). Fetch VECTORS at 1-day steps spanning the clock window, + through the cache; cubic-interpolate between samples; glowing marker + fading + trail + label with live distance and one-way light time. Fail soft per craft. + Gate: §9 spacecraft checkpoints. +- **Stage 5 — SWARM** (small bodies): main-belt sample via SBDB query (~1–2 k + asteroids with `a,e,i,om,w,ma,epoch`, propagated by the shared Kepler core, + InstancedMesh points); NEO close approaches this ±30 d via CAD API (highlighted, + clickable, miss distance in lunar distances); famous comets via SBDB lookup + (1P/Halley, 2P/Encke, 67P, 96P — elliptical only) with full drawn orbits and + anti-sunward tail sprite scaled by 1/r². All three are separate HUD layers, + off-by-default except comets. Gate: belt sits between Mars and Jupiter (not a + sphere, not a line); Halley's aphelion beyond Neptune's orbit… in the compressed + view, near it. +- **Stage 6 — ALMANAC** (knowing): info panel with live facts (§5), per-body prose + blurbs (2–3 sentences, hand-written, grimoire voice), Earth Easter egg — focusing + Earth shows "▸ open GODSIGH world view" linking `http://127.0.0.1:8137`, README.md + with the full GODSIGH-style layer table and screenshots (`docs/`), HUD credits. + Gate: every clickable body has a card; README quickstart works from a fresh clone. +- **Stage 7 — RESONANCE** *(optional, only if explicitly requested later)*: the + godstrument bridge — serve.py WebSocket broadcasting `{body: {helioLonDeg, rAU, + speedKms}}` at 10 Hz for sonification (music of the spheres: orbital periods as + polyrhythms). Do not build in this pass. + +## 11. Performance rules + +- Zero allocations in `onClockTick` paths — module-scope scratch `Vector3`s. +- Orbit paths: build once, rebuild only when elements drift (|ΔT| > 0.1 Cy) or scale + mode changes; the MEGA/TRUE transition may rebuild per-frame *during* the 2.5 s + tween only (or better: write paths in true AU and compress in a tiny custom shader + with `P` as a uniform — do this only if the CPU rebuild visibly hitches). +- Asteroids: one InstancedMesh (or Points), positions updated at ≤ 10 Hz, not every + frame, unless rate > 1 day/s. +- Labels: update DOM at ≤ 5 Hz; cull labels behind the camera. +- Target: 60 fps with all layers on, MacBook-class GPU; test with `stats`-style + frame readout available behind `?debug=1`. + +## 12. Known traps (read twice) + +- Float32 jitter — floating origin is not optional (§4). Symptom: focused planet + "swims" at close zoom while time runs. +- Mixing spaces — if a body and its orbit path disagree, someone compressed twice + or not at all. One transform module, everything flows through it. +- `M = L − ϖ` can exceed ±360° enormously for Mercury (L rate 149 473°/Cy) — + normalize *after* the subtraction, in float64. +- Horizons is text-first: with `format=json` the payload is still one big text blob + in `result` — parse the `$$SOE…$$EOE` block by line. Request + `VEC_TABLE='1'&REF_PLANE='ECLIPTIC'&CENTER='500@10'&OUT_UNITS='AU-D'`. +- Don't hammer JPL while iterating — the disk cache exists so a dev reload costs + zero upstream calls. Check the cache works before Stage 4 fetch loops. +- Texture download links from solarsystemscope.com occasionally move — if a fetch + 404s, fall back to procedural colors and leave a `warn` status, don't block. +- Retrograde ≠ negative inclination — Triton is retrograde via i > 90° (or the + negative-period convention in §8; pick one and comment it). +- First Horizons call for a spacecraft can take 2–5 s — status line must say + `fetching…` so it doesn't read as broken.