solargod/PERIHELION_BRIEF.md
monsterrobotparty dec2b89de6 📋 perihelion: Part ω — closer lane, orchestrator mode canonized, deferred items (fable)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 10:33:24 +10:00

310 lines
17 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# PERIHELION — SOLARGOD wave 2 · NEARGOD · SELENE · SYZYGY · VESSEL
> **You are an execution agent (Opus).** You have been assigned ONE lane. Read
> Part 0 fully, execute only your lane, end with the §0.6 report. The planning
> model (Fable) reviews every lane before merge. Wave 1 (the full build) shipped
> at `main@6391273` — 13/13 verify gates; you are polishing a working instrument,
> not rescuing a broken one. **Do not break a passing gate.**
---
## Part 0 — shared context (all lanes read this)
### 0.1 The four lanes
| lane | name | what | why |
|---|---|---|---|
| **N** | 🌑 NEARGOD | real orbits for the NEO close-approach layer | the one layer whose *positions* are schematic — make them true |
| **M** | 🌖 SELENE | real textures + tidal lock for the major moons | worlds become places, twice over |
| **E** | 🌓 SYZYGY | oppositions & conjunctions — computed sky events on the timeline | the almanac learns to predict |
| **V** | 🛳 VESSEL | vendor Three.js (offline) + deep-time mode (3000 BC3000 AD) | no CDN dependency; the full Table-2a range the engine already ships |
### 0.2 Ground truth & hard gate
- **GATE:** branch from `main``6391273`. Confirm: `git log --oneline -3`
includes the 📡 NEO-status commit, and `verify.html` passes 13/13 in a browser
**before you write a line** (`python3 serve.py <your port>` → `/verify.html`).
If either fails, STOP and report.
- Read `SOLARGOD_BRIEF.md` (wave 1) for architecture: truth→view in `scale.js`
only; floating origin; layer factory contract (`js/main.js` header comment);
fail-soft network layers; §12 traps (all still apply, especially: don't hammer
JPL — the serve.py disk cache is the rate limiter; Horizons/SBDB burst-throttle).
- Multi-file repo (unlike godstrument's monolith) — territory is per-FILE here.
Match house style: 2-space indent, `const`, terse precise comments, no deps.
### 0.3 Territory & merge order — what makes parallel work safe
| lane | OWNS (may edit) | MUST NOT touch |
|---|---|---|
| N | `js/layers/neos.js` · `serve.py` (ONE `UPSTREAMS` line) · append-only `verify.html` gate | everything else |
| M | `js/bodies.js` (texture/rotation fields only) · `js/layers/moons.js` · `assets/textures/` · `assets/textures/CREDITS.md` | ephem/scale/main; planet entries in bodies.js |
| E | `js/layers/events.js` (new) · `js/layers/almanac.js` · append-only `css/style.css` block · append-only `verify.html` gate · ONE line in `js/main.js` `LAYER_MODULES` | everything else in main.js |
| V | `index.html` · `verify.html` (importmap lines) · `vendor/` (new) · `js/config.js` (time block) · `js/main.js` | layers/, ephem.js math, scale.js |
- **Merge order: N → M → E → V.** E and V both touch `main.js`; V merges last and
rebases first (`git rebase main`) — V's owner expects this. `verify.html` and
`style.css` additions are **append-only** (new rows/blocks at the end, marked
`/* PERIHELION-<lane> */`) so they merge clean.
- If you believe you must cross your boundary: STOP, log it as an open question
in your report, work around it.
### 0.4 Git & run
```sh
cd /Users/m3ultra/Documents/SOLARGOD
git worktree add ../SOLARGOD-N -b perihelion/n-neargod # lane N
git worktree add ../SOLARGOD-M -b perihelion/m-selene # lane M
git worktree add ../SOLARGOD-E -b perihelion/e-syzygy # lane E
git worktree add ../SOLARGOD-V -b perihelion/v-vessel # lane V
```
Work only in your worktree. **Dev ports** (serve.py takes the port as argv):
N=8148 · M=8149 · E=8150 · V=8151. Port 8147 is the user's. Commit style:
emoji + lowercase + `(opus)`, e.g. `🌑 neargod: the ring becomes real (opus)`.
Never push to `main` — push your branch; Fable merges after review.
### 0.5 Hard rules (inherited from wave 1)
- No npm, no build step, no frameworks. Stdlib-only serve.py.
- Never commit `cache/` or `assets/art/candidates/`.
- Verification gates are mandatory; measurement beats reasoning. Every lane adds
at least one `verify.html` gate for its own work and re-runs the full suite
(must stay green) before reporting.
- UI input paths get a REAL-input test (the wave-1 lesson: OrbitControls was dead
because everything was tested via JS — if you add anything clickable, click it
via the browser tools, don't just call its handler).
### 0.6 The report (end your run with this)
1. What shipped (per feature, one line each). 2. Gate results with numbers
(verify.html count + your lane's new gates + the full-suite re-run). 3. Any
deviation from this brief and why. 4. Open questions for Fable. 5. Honest list
of what you could not verify and why.
---
# Lane N — 🌑 NEARGOD ("the ring becomes real")
The close-approach layer currently draws REAL data (designation, miss distance,
date, speed from JPL CAD) at SCHEMATIC positions — a ring around Earth, honestly
labeled. Replace the schematic with truth.
**N.1 The lookup.** CAD gives designations; orbital elements come from the SBDB
*lookup* API (a different endpoint from the `sbdb_query` the belt uses). Add ONE
line to serve.py's `UPSTREAMS`:
`"sbdb_lookup": "https://ssd-api.jpl.nasa.gov/sbdb.api"`. Client-side:
`proxy/sbdb_lookup?sstr=<des>` → JSON with `orbit.epoch` (JD, TDB) and
`orbit.elements` — an array of `{name, value}` STRINGS. Parse by NAME
(`a, e, i, om, w, ma`), `+Number` each, epoch from `orbit.epoch`. Reject if any
is non-finite or `e >= 0.98` (the wave-1 elliptical-only rule; note skips in the
status line). Existing 24 h cache TTL covers this endpoint automatically —
verify with a repeated fetch (`X-Solargod-Cache: hit`).
**N.2 The layer.** Rewrite `neos.js`: for each CAD row (keep `dist-max=0.05`,
`limit=40`), fetch elements **sequentially** through the proxy (never parallel —
burst throttling), then propagate with the existing shared core
(`ephem.smallBodyEcl`) exactly like the belt. Each NEO gets: marker sprite at its
TRUE position each tick (reuse the reticle glyph; keep the miss-distance color
ramp: <1 LD red, <5 amber, else green), its real orbit drawn on `worldGroup` via
`ephem.orbitPathFromElements` (thin, 25% opacity, the marker's color), and the
existing label/`handlePick` facts (des · LD · date · speed all unchanged).
Fail-soft PER OBJECT: a failed lookup falls back to the wave-1 schematic ring
placement for that object alone, and the status line reports the split honestly:
`38 real orbits · 2 schematic · CAD+SBDB`. First load is 40 sequential lookups
(~2040 s cold): status must count up `fetching 12/40…` like the spacecraft
layer does. Cached reloads are instant.
**N.3 Gate (append to verify.html).** Pick one NEO from the current CAD window
programmatically; at its close-approach epoch (`cd`/`jd` from CAD), compute
`|helio(NEO) helio(EMB)|` from your propagated elements it must be within
**25%** of CAD's `dist` (generous: EarthEMB, two-body propagation, CAD dist is
geocentric; the point is catching wrong-by-an-AU bugs, not chasing arcseconds).
Full suite stays green. Real-input test: toggle the layer on in the browser and
click one reticle.
---
# Lane M — 🌖 SELENE ("the moons get faces")
21 moons render as flat-color spheres. Give the eight that matter real surfaces,
and lock every moon's face to its parent.
**M.1 Textures.** Targets: **Moon (has one already leave it), Io, Europa,
Ganymede, Callisto, Titan, Triton, Charon, Phobos** (skip any you can't source
cleanly). Sources, in order of preference: USGS Astrogeology equirectangular
mosaics, NASA/JPL photojournal maps, NASA 3D-resources public-domain NASA/USGS
imagery only (no fan maps, no unknown-license repacks). Downscale to **1024×512
JPG** (`sips -Z 1024 --setProperty format jpeg …`), target 150 KB each,
**2 MB added total** (wave-1 committed size is 8.2 MB against a 25 MB budget).
Files: `assets/textures/1k_<moon>.jpg`. Every file gets a line in
`assets/textures/CREDITS.md`: source URL, mission/instrument, license. If a
download 404s: skip, flat color stays (the wave-1 fallback is already in
`moons.js` verify it still works by feeding one bogus filename in dev, then
remove the test).
**M.2 Wiring.** `bodies.js`: add `texture:` to each sourced moon touch NOTHING
else in the file. `moons.js`: call the ctx-provided `loadTextureInto` (same as
planets.js does) the material/fallback machinery already exists.
**M.3 Tidal lock.** Every major moon keeps one face to its parent. In
`moons.js`'s tick: `mesh.rotation.y = θ + π` where θ is the SAME orbital angle
main.js uses for the moon's position (`·(jd J2000)/periodDays` negative
period gives retrograde rotation for free; copy the formula, don't import
main.js internals). Eyeball gate: focus Jupiter at 1 hr/s and confirm Io's
textured face toward Jupiter stays fixed across a full orbit.
**M.4 Gate.** No verify.html math gate (this is a content lane) instead:
screenshot Jupiter-focus showing 2 textured Galileans, zero console errors,
full suite still 13/13, texture bytes added reported exactly, CREDITS.md
complete. Real-input test: click a textured moon focus flight works.
---
# Lane E — 🌓 SYZYGY ("the almanac learns to predict")
Compute real sky events from the ephemeris no network, pure math on
`ephem.helioEcl`.
**E.1 The finder** (`js/layers/events.js`, new also export plain functions).
Event types, all from heliocentric longitudes λ(body) = atan2(y, x) of the
J2000-ecliptic position:
- **Opposition** (outer planets, MarsNeptune + Pluto): `normDegPM180(λp λe)`
crosses **0** SunEarthplanet in a line, planet at its brightest.
- **Solar conjunction** (outer): the same function crosses **180**.
- **Inferior / superior conjunction** (Mercury, Venus): crossing **0 / 180**.
- **Greatest elongation** (Mercury, Venus): local maxima of the GEOCENTRIC
elongation angle `acos( (E·(PE)) / (|E||PE|) )` where E, P are heliocentric
vectors scan for the derivative sign flip.
Scanner: coarse 5-day steps across a window, detect sign changes / extrema,
refine by bisection to <1 hour. API:
`findEvents(jdStart, jdEnd) → [{jd, type, bodies, label}]` and
`nextEvent(bodyId, jd)`. Compute lazily ±2 years around the current sim date,
recompute when the clock leaves the computed window. Keep it off the hot path
run the scan in chunks (a few planets per frame or via `setTimeout(0)`), never
a 4-year scan in one tick.
**E.2 The timeline.** Events render as small diamond marks on `#timeline-outer`
(DOM divs positioned by date fraction, your own append-only CSS block, brass for
oppositions, dim for conjunctions). Hover title = label + exact date; click =
jump the sim clock there (set `clock.simMs` via the ctx the layer contract
already hands you `clock`). HUD row: `Sky events` (default ON), status like
`14 events ±2 yr · computed`.
**E.3 The almanac hook.** In `almanac.js` (yours this wave): planets' cards gain
one live fact `Next opposition` (outer) / `Next elongation` (inner) with date
and countdown, via `nextEvent()` imported from events.js. Keep the existing
5 Hz update discipline.
**E.4 The one main.js line.** Add `'./layers/events.js'` to `LAYER_MODULES`.
Nothing else in main.js.
**E.5 Gates (append to verify.html).** (a) Mars opposition: known truth
**2027-02-19/20** (next Mars opposition). Your finder, scanned over 20262028,
must produce a Mars opposition within **±2 days** of 2027-02-19. (b) Venus
greatest elongations come ~72 days apart in alternating east/west pairs; assert
two consecutive found elongations are 6080 days apart. Full suite green.
Real-input test: click a timeline diamond in the browser clock jumps.
---
# Lane V — 🛳 VESSEL ("cut the tether, open deep time")
**V.1 Vendor Three.js.** Fetch the exact pinned files the importmap uses
(`three@0.170.0`: `build/three.module.js`, `examples/jsm/controls/OrbitControls.js`,
`examples/jsm/renderers/CSS2DRenderer.js` + anything they import chase the
import graph, it's shallow) into `vendor/three/`. Rewrite BOTH importmaps
(`index.html`, `verify.html`) to relative vendor paths. Add a top-of-file
comment in vendor/: version, source URL, MIT license note. Gate: **disconnect
network** (or block jsdelivr via a hosts trick simpler: rename the CDN domain
in devtools offline mode) full app boots, planets move, 13/13 gates minus the
live-Horizons ones (which legitimately need JPL note them as expected-fail
offline, confirm they pass online).
**V.2 Deep time.** The engine already ships Table 2a + 2b
(`ephem.setExtendedRange`) wave 1 built the plumbing, you build the door.
HUD toggle `EPOCH: 18002050 / 3000 BC3000 AD` next to MEGA/TRUE. Deep-time
mode: `ephem.setExtendedRange(true)`, `CONFIG.time` swaps to
`Date.UTC(-2999, 0, 1) … Date.UTC(2999, 11, 31)` (JS Date handles astronomical
years fine), timeline end labels read from CONFIG (they're hardcoded `1800`/
`2050` in index.html today fix that), and the hash gains `&e=1` so deep-time
views share. **Toggle = reload**: orbit paths and element resolution are baked
per-session by design, and you don't own the layers so the toggle serializes
state and reloads the page with `&e=1` in the hash (hash restore is already
proven; add the `e` token to `applyHash`/`serializeHash`, which you own). This
is deterministic, one line of behavior, and honest about what changes
underneath. Spacecraft/NEO/
belt layers already fail-soft outside their data windows verify they degrade
gracefully at 1000 BC (spacecraft hidden, belt propagates fine, no errors).
- **Time-rate presets**: deep time needs faster travel append `1 decade/s` and
`1 century/s` to `CONFIG.rates` (they're config-driven; the menu builds
itself).
**V.3 Gates (append to verify.html).** Deep-time accuracy: with
`setExtendedRange(true)`, Jupiter heliocentric position at **JD 1355818.5
(1000 BC-01-01)** vs live Horizons through the proxy (`TLIST`, same pattern as
gates 1011), tol **0.15 AU** (Table 2a is an approximation over millennia
the gate catches wrong-table/wrong-term bugs, not arcseconds). Mercury at
2500-01-01, tol 0.02 AU. Confirm Table 2b's M-correction actually engages: with
the correction terms zeroed in a scratch copy of the math (in-page, not in
ephem.js), Jupiter@1000 BC must get WORSE that proves the terms are live.
Full suite green, online and offline-minus-live-gates.
---
*Merge order N M E V; Fable reviews each. HERALD (deploy to the VPS) is
deliberately NOT in this wave it needs the user's DNS/tunnel decision first.*
---
# Part ω — the closer (added post-merge, Fable 2026-07-16)
**Status: all four lanes MERGED to `main` (`b42473a`), 21/21 gates in-browser,
pushed.** NMEV merged in order; integration fixes landed in review
(`sbdb_lookup` `CONFIG.proxy.sbdbLookup`; era-aware BC dates in `lib.js`;
V's verify wait-guard raised 1318 for the merged gate count).
### ω.0 Process note — orchestrator mode is now canon
Wave 2 ran as ONE Opus session dispatching four worktree-locked subagents that
built headlessly (node + serve.py/curl), with the orchestrator keeping the
single browser for all verification and real-input tests. **This is the
endorsed topology for all future multi-lane waves** it preserves the
one-tree-one-writer rule and centralizes the thing there's only one of (the
browser). Lane briefs stay written for individual agents; the orchestrator
passes each its lane section verbatim.
### ω.1 The closer lane (one agent, branch `perihelion/o-closer` from ≥ b42473a)
1. **README refresh.** README.md still describes wave 1: CDN importmap (it's
vendored now), "schematic ring" NEOs (real orbits now), 13 gates (21 now),
no Epoch toggle / Sky events / moon textures / decade-century rates. Rewrite
the affected sections; keep the voice; update the layer table + controls +
verify table + credits (add USGS/NASA moon maps). New `docs/hero.jpg` via
the `/snap` endpoint if the old one no longer represents the app (deep-time
or Jupiter+textured-Galileans would sell it).
2. **Timeline event density** (E's flagged UX): the ±2 yr event window maps to
~1.6% of the 250-year strip (worse in deep time). Build the **event band**:
a second thin strip directly under `timeline-outer` that maps ONLY the
computed window to full width diamonds live there (same DOM/click-jump
machinery, same CSS classes); the main strip gets a subtle bracket marking
the window's extent. Events layer owns it; no main.js changes beyond what
the layer contract already allows. Real-input gate: click a diamond in the
band clock jumps; near-mode AND deep-mode.
3. **Housekeeping.** After user confirmation: `git worktree remove` the four
lane worktrees and delete the merged `perihelion/*` branches (their commits
live in main's merge history). Do NOT touch `../godstrument-*` worktrees.
4. **Full regression before report:** 21/21 (+ your band gate), zero console
errors in near and deep modes, README quickstart from a fresh clone.
### ω.2 Deferred (needs the user, not an agent)
- **HERALD** deploy to the VPS (domain/tunnel decision then mirror the
godstrument.pro rsync+systemd pattern; serve.py binds loopback, so prod
needs the same reverse-proxy shape godstrument uses).
- **Titan/Europa color** M shipped grayscale where USGS has no color mosaic
(truth boundary held; ENDORSED in review). If the user wants iconic-color
moons, that's an explicit directorial choice to source non-data colorized
maps flag it as such in CREDITS if taken.