🌞 genesis: SOLARGOD brief — the solar system gets its instrument (fable)
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 <noreply@anthropic.com>
This commit is contained in:
commit
cfdb0d6252
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
cache/
|
||||||
|
.DS_Store
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
19
CLAUDE.md
Normal file
19
CLAUDE.md
Normal file
@ -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.
|
||||||
29
README.md
Normal file
29
README.md
Normal file
@ -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.
|
||||||
415
SOLARGOD_BRIEF.md
Normal file
415
SOLARGOD_BRIEF.md
Normal file
@ -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=<bodyId>&t=<offsetSec|@unixMs>&s=<mega|true>&L=<on,ids>&cam=<dist,heading,pitch>`
|
||||||
|
— 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?<query>` → `https://ssd.jpl.nasa.gov/api/horizons.api?<query>`
|
||||||
|
- `/proxy/sbdb?<query>` → `https://ssd-api.jpl.nasa.gov/sbdb_query.api?<query>`
|
||||||
|
- `/proxy/cad?<query>` → `https://ssd-api.jpl.nasa.gov/cad.api?<query>`
|
||||||
|
|
||||||
|
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.
|
||||||
Loading…
Reference in New Issue
Block a user