merge stage/6-almanac → main: almanac + textures + art, gate green (opus)
107
README.md
@ -1,29 +1,98 @@
|
||||
# SOLARGOD ▸ Solar System View
|
||||
# SOLARGOD ▸ Orrery
|
||||
|
||||
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.
|
||||
A standalone, browser-based 3D solar-system explorer — the interplanetary sibling
|
||||
of [GODSIGH](../GODSIGH), which watches Earth. Float above a compressed-scale
|
||||
("MEGA") solar system with every world visible at once, moving on **real JPL
|
||||
ephemerides**; scrub a time bar across centuries; fly the camera into orbit around
|
||||
any planet, its moons circling; toggle **TRUE scale** and watch the system animate
|
||||
out into its real, humbling emptiness. Live layers plot where Voyager 1, JWST and
|
||||
Parker Solar Probe are *right now*, plus real asteroids, comets and Earth
|
||||
close-approaches. Everything is client-side, free, and keyless.
|
||||
|
||||
**Status: pre-build.** The full spec lives in [SOLARGOD_BRIEF.md](SOLARGOD_BRIEF.md).
|
||||

|
||||
|
||||
## Quickstart (once built)
|
||||
## Quickstart
|
||||
|
||||
```sh
|
||||
python3 serve.py
|
||||
python3 serve.py # stdlib only — no npm, no build step
|
||||
# open http://127.0.0.1:8147
|
||||
```
|
||||
|
||||
## Planned layers
|
||||
`serve.py` statically serves the app and proxies the JPL feeds a browser can't call
|
||||
directly (Horizons / SBDB / CAD — no CORS), with a disk cache in `cache/`. Three.js
|
||||
loads from a pinned CDN importmap. The deterministic core (Sun, planets, moons,
|
||||
orbits, time travel) runs with **zero network calls** — positions are computed
|
||||
locally from Keplerian elements; network layers are additive and fail soft.
|
||||
|
||||
| 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 |
|
||||
## Verify
|
||||
|
||||
MEGA mode compresses distances so the whole system is one readable view;
|
||||
TRUE mode animates it back out to its real, humbling emptiness.
|
||||
Open **http://127.0.0.1:8147/verify.html** — an in-browser gate runner that checks
|
||||
the ephemeris against **JPL Horizons ground truth** (hard-coded checkpoints + live
|
||||
queries through the proxy). All 13 gates should read PASS, e.g.:
|
||||
|
||||
| Gate | Result |
|
||||
|------|--------|
|
||||
| Mars / Jupiter / Earth @ 2026-07-15 (heliocentric ecliptic AU) | Δ ≤ 3.3e-4 / 2.9e-3 / 3.9e-5 AU |
|
||||
| Kepler solver converges at e=0.97 · orbitPath closes · frame round-trips | exact |
|
||||
| MEGA→TRUE preserves direction (unit dot ≈ 1) | 1.000000000000 |
|
||||
| Mars @ J2000 · Jupiter @ 1900 vs **live** Horizons | Δ 4.8e-5 / 2.0e-3 AU |
|
||||
| Main belt between Mars & Jupiter · Halley aphelion beyond Neptune | pass |
|
||||
| Voyager 1 @ 2026-07-15 = (−32.07, −136.21, +98.55) AU · JWST hugs Earth | Δ 0.002 / 0.011 AU |
|
||||
|
||||
## Layers
|
||||
|
||||
| Layer | Source | Default | Notes |
|
||||
|-------|--------|:------:|-------|
|
||||
| **Planets & orbits** | JPL SSD approximate elements (Standish) | on | 8 planets + Pluto, textured, MEGA-compressed orbit paths |
|
||||
| **Body labels** | — | on | names; the focused body's children also get distance labels |
|
||||
| **Moons** | JPL satellite data | on | 21 moons, per-parent local compression, retrograde Triton |
|
||||
| **Rings** | true km, scaled to parent | on | Saturn (textured annulus) · Uranus (schematic) |
|
||||
| **Spacecraft** | JPL **Horizons** (proxy) | on | Voyager 1/2, New Horizons, JWST, Parker, Juno, Lucy, Psyche — live distance + one-way light-time |
|
||||
| **Main belt** | JPL **SBDB** (proxy) | off | ~1500 main-belt asteroids, shared Kepler core |
|
||||
| **Comets** | JPL **SBDB** (proxy) | on | 1P/Halley, 2P/Encke, 67P, 96P — full orbits + anti-sunward 1/r² tail |
|
||||
| **Close approaches** | JPL **CAD** (proxy) | off | ±30-day NEO radar around Earth, miss distance in lunar distances |
|
||||
|
||||
## Controls
|
||||
|
||||
- **Focus** — click any body (or use the Focus menu) → the camera flies into orbit
|
||||
around it; breadcrumb `SOL ▸ JUPITER ▸ EUROPA`; `Esc` steps out one level.
|
||||
- **MEGA / TRUE** — toggle the scale mode; the transition animates over ~2.5 s.
|
||||
- **Body scale** — 1× / 100× / 1200× radius exaggeration.
|
||||
- **Time bar** — NOW, play/pause, reverse, rate presets (`1× · 1 min/s … 1 yr/s`),
|
||||
and a scrubbable 1800–2050 timeline. A **LIVE / SCRUBBED** chip mirrors GODSIGH.
|
||||
- **Info panel** — appears on focus: a grimoire card with a physical fact sheet,
|
||||
hand-written prose, a stylized portrait, and **live computed** facts (distance
|
||||
from Sun & Earth, sunlight age, orbital speed, day & year length). Focusing Earth
|
||||
reveals a link to the GODSIGH world view.
|
||||
- **Shareable URLs** — the view serializes to the hash
|
||||
(`#f=<body>&t=<time>&s=<mega|true>&L=<layers>&cam=…`); paste one to reproduce a view.
|
||||
- **`?debug=1`** — frame-rate / scale readout.
|
||||
|
||||
## Architecture
|
||||
|
||||
No build step, no framework, plain ES modules. One module owns the truth→view
|
||||
transform (`scale.js`); every position is computed in real heliocentric J2000
|
||||
ecliptic AU first (`ephem.js`) then compressed. A **floating origin** subtracts the
|
||||
focused body's position in float64 before writing to the scene graph, so a focused
|
||||
planet never jitters at close zoom.
|
||||
|
||||
```
|
||||
index.html · serve.py · verify.html
|
||||
css/style.css
|
||||
js/ config lib ephem bodies scale ui main
|
||||
js/layers/ planets moons rings spacecraft asteroids comets neos almanac
|
||||
assets/textures/ (CC BY) · assets/art/grimoire/ (generated)
|
||||
```
|
||||
|
||||
The full execution spec is [SOLARGOD_BRIEF.md](SOLARGOD_BRIEF.md).
|
||||
|
||||
## Credits
|
||||
|
||||
- **Positions** — [JPL SSD](https://ssd.jpl.nasa.gov/planets/approx_pos.html)
|
||||
approximate elements; live feeds via **JPL Horizons / SBDB / CAD**.
|
||||
- **Textures** — [Solar System Scope](https://www.solarsystemscope.com/textures/),
|
||||
CC BY 4.0 (see `assets/textures/CREDITS.md`).
|
||||
- **Grimoire art** — generated locally with MODELBEAST / FLUX (see
|
||||
`assets/art/PROMPTS.md`); the *aesthetic* layer only — real worlds get real data
|
||||
textures.
|
||||
- Built on [Three.js](https://threejs.org/).
|
||||
|
||||
50
assets/art/PROMPTS.md
Normal file
@ -0,0 +1,50 @@
|
||||
# Generated art — reproducibility log
|
||||
|
||||
House rule (brief §7b): every shipped generated asset records its prompt, model,
|
||||
seed and operator chain so any card can be regenerated in-style. All art here is
|
||||
the **aesthetic layer only** — never a substitute for real-world data textures.
|
||||
|
||||
## Pipeline
|
||||
|
||||
- Operator: `flux_local` (MODELBEAST, mflux/MLX on-device, **free & unlimited**)
|
||||
- Model: `flux2-klein-4b`
|
||||
- Host: MODELBEAST (`~/Documents/MODELBEAST`), guest `agents` credential
|
||||
- Post: JPG re-encode at 640px, q82 (`sips`) to stay lean (brief §7 budget)
|
||||
|
||||
## Splash / boot hero (`splash/`, candidates only)
|
||||
|
||||
- Seeds: 1–5, 1280×768
|
||||
- Prompt: *"a grand antique brass orrery mechanism floating in the black void of
|
||||
deep space, armillary sphere rings and intricate clockwork gears in aged brass
|
||||
and gold, a glowing sun at the center, faint distant stars and nebula dust,
|
||||
cinematic volumetric god-rays, ornate engraved astronomical grimoire aesthetic,
|
||||
moody chiaroscuro, ultra detailed, dark observatory"*
|
||||
|
||||
## Grimoire portraits (`grimoire/<body>.jpg`) — info-panel cards
|
||||
|
||||
- Seeds run: 1–2 · **chosen seed: 1** for every body · 800×800 → 640px JPG
|
||||
- Shared style suffix: *", antique astronomical engraving, ornate brass astrolabe
|
||||
frame, aged parchment, gold leaf accents, occult grimoire illustration,
|
||||
intricate etched linework, chiaroscuro, dark background, centered circular
|
||||
celestial portrait, NOT photorealistic, no text, no words"*
|
||||
- Per-body subject prompt:
|
||||
|
||||
| Body | Subject prompt (prefix) |
|
||||
|------|-------------------------|
|
||||
| sun | the radiant Sun as a crowned solar deity with a face of fire, flames and rays |
|
||||
| mercury | the swift small planet Mercury, cratered grey orb, a winged caduceus |
|
||||
| venus | the planet Venus veiled in golden sulphur clouds, a rose and hand-mirror |
|
||||
| earth | the planet Earth, blue and green world with white clouds, encircled by a brass armillary sphere |
|
||||
| mars | the red planet Mars, ochre desert sphere, a spear and shield, god of war |
|
||||
| jupiter | the giant planet Jupiter, swirling cloud bands and a great red storm, a thunderbolt, king of gods |
|
||||
| saturn | the ringed planet Saturn, a scythe and hourglass, aged father of time |
|
||||
| uranus | the pale cyan ice giant Uranus tilted on its side, a lyre and aurora |
|
||||
| neptune | the deep blue planet Neptune, a trident and ocean waves, god of the sea |
|
||||
| pluto | the distant dwarf world Pluto with its moon Charon, a key and a shadowed lantern, lord of the underworld |
|
||||
|
||||
Regenerate any card:
|
||||
```sh
|
||||
source ~/Documents/MODELBEAST/data/agent.env
|
||||
~/Documents/MODELBEAST/mb run flux_local -p prompt="<subject><style>" \
|
||||
-p model=flux2-klein-4b -p seed=1 -p width=800 -p height=800 --wait --download art/candidates/grimoire/<body>/
|
||||
```
|
||||
BIN
assets/art/grimoire/earth.jpg
Normal file
|
After Width: | Height: | Size: 182 KiB |
BIN
assets/art/grimoire/jupiter.jpg
Normal file
|
After Width: | Height: | Size: 212 KiB |
BIN
assets/art/grimoire/mars.jpg
Normal file
|
After Width: | Height: | Size: 175 KiB |
BIN
assets/art/grimoire/mercury.jpg
Normal file
|
After Width: | Height: | Size: 166 KiB |
BIN
assets/art/grimoire/neptune.jpg
Normal file
|
After Width: | Height: | Size: 202 KiB |
BIN
assets/art/grimoire/pluto.jpg
Normal file
|
After Width: | Height: | Size: 219 KiB |
BIN
assets/art/grimoire/saturn.jpg
Normal file
|
After Width: | Height: | Size: 219 KiB |
BIN
assets/art/grimoire/sun.jpg
Normal file
|
After Width: | Height: | Size: 233 KiB |
BIN
assets/art/grimoire/uranus.jpg
Normal file
|
After Width: | Height: | Size: 220 KiB |
BIN
assets/art/grimoire/venus.jpg
Normal file
|
After Width: | Height: | Size: 234 KiB |
BIN
assets/textures/2k_earth_daymap.jpg
Normal file
|
After Width: | Height: | Size: 452 KiB |
BIN
assets/textures/2k_jupiter.jpg
Normal file
|
After Width: | Height: | Size: 487 KiB |
BIN
assets/textures/2k_mars.jpg
Normal file
|
After Width: | Height: | Size: 733 KiB |
BIN
assets/textures/2k_mercury.jpg
Normal file
|
After Width: | Height: | Size: 852 KiB |
BIN
assets/textures/2k_moon.jpg
Normal file
|
After Width: | Height: | Size: 1.0 MiB |
BIN
assets/textures/2k_neptune.jpg
Normal file
|
After Width: | Height: | Size: 236 KiB |
BIN
assets/textures/2k_saturn.jpg
Normal file
|
After Width: | Height: | Size: 195 KiB |
BIN
assets/textures/2k_saturn_ring_alpha.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
assets/textures/2k_stars_milky_way.jpg
Normal file
|
After Width: | Height: | Size: 246 KiB |
BIN
assets/textures/2k_sun.jpg
Normal file
|
After Width: | Height: | Size: 803 KiB |
BIN
assets/textures/2k_uranus.jpg
Normal file
|
After Width: | Height: | Size: 76 KiB |
BIN
assets/textures/2k_venus_surface.jpg
Normal file
|
After Width: | Height: | Size: 864 KiB |
33
assets/textures/CREDITS.md
Normal file
@ -0,0 +1,33 @@
|
||||
# Texture credits
|
||||
|
||||
All planet, moon, Sun, ring and starfield maps in this directory are from the
|
||||
**Solar System Scope** texture pack and are used under **CC BY 4.0**.
|
||||
|
||||
- Source: https://www.solarsystemscope.com/textures/
|
||||
- License: Creative Commons Attribution 4.0 International (CC BY 4.0)
|
||||
https://creativecommons.org/licenses/by/4.0/
|
||||
- Attribution: *Solar System Scope* (INOVE / solarsystemscope.com). Textures are
|
||||
based on NASA elevation and imagery data.
|
||||
|
||||
Downloaded 2026-07-16 at 2k resolution:
|
||||
|
||||
| File | Body |
|
||||
|------|------|
|
||||
| 2k_sun.jpg | Sun |
|
||||
| 2k_mercury.jpg | Mercury |
|
||||
| 2k_venus_surface.jpg | Venus (surface) |
|
||||
| 2k_earth_daymap.jpg | Earth (day) |
|
||||
| 2k_mars.jpg | Mars |
|
||||
| 2k_jupiter.jpg | Jupiter |
|
||||
| 2k_saturn.jpg | Saturn |
|
||||
| 2k_uranus.jpg | Uranus |
|
||||
| 2k_neptune.jpg | Neptune |
|
||||
| 2k_moon.jpg | Moon |
|
||||
| 2k_saturn_ring_alpha.png | Saturn rings (alpha) |
|
||||
| 2k_stars_milky_way.jpg | Milky Way skybox panorama |
|
||||
|
||||
Attribution also appears in the app's HUD footer, as CC BY requires.
|
||||
|
||||
**Truth boundary (brief §7b):** these are the *real data* textures — real worlds
|
||||
get real maps. The stylized grimoire portraits in `../art/grimoire/` are the
|
||||
*aesthetic* layer (generated art), kept visually and directorially separate.
|
||||
@ -145,7 +145,7 @@ html, body {
|
||||
|
||||
/* ---- info panel (left, Stage 6) ---- */
|
||||
#infopanel {
|
||||
position: absolute; top: 14px; left: 14px; width: 300px; max-height: calc(100vh - 130px);
|
||||
position: absolute; top: 46px; left: 14px; width: 300px; max-height: calc(100vh - 160px);
|
||||
padding: 16px; background: var(--panel-solid); border: 1px solid var(--border);
|
||||
border-left: 2px solid var(--brass); border-radius: 12px; overflow-y: auto;
|
||||
z-index: 9; box-shadow: 0 8px 44px rgba(0,0,0,0.6);
|
||||
|
||||
BIN
docs/hero.jpg
Normal file
|
After Width: | Height: | Size: 146 KiB |
@ -140,6 +140,12 @@ export function helioEcl(bodyId, jd, out = {}) {
|
||||
// Mean motion [deg/day] from semi-major axis via Kepler's third law (Gaussian).
|
||||
export function meanMotionDegPerDay(a) { return 0.9856076686 / Math.pow(a, 1.5); }
|
||||
|
||||
// Resolved semi-major axis [AU] of a planet at jd (for vis-viva speed, year length).
|
||||
export function semiMajor(bodyId, jd) {
|
||||
const p = useExtended ? resolvePlanetExt(bodyId, jd) : resolvePlanet(bodyId, jd);
|
||||
return p.a;
|
||||
}
|
||||
|
||||
// Small-body position (asteroids/comets/Pluto): epoch elements propagated by
|
||||
// M = M0 + n·(jd − epoch). el = {a,e,i,om(Ω),w(ω),ma(M0),epoch}.
|
||||
export function smallBodyEcl(el, jd, out = {}) {
|
||||
|
||||
121
js/layers/almanac.js
Normal file
@ -0,0 +1,121 @@
|
||||
// SOLARGOD almanac (Stage 6) — the info panel that knows. On focus it shows a
|
||||
// grimoire card: a physical fact sheet + hand-written prose + (if generated) a
|
||||
// MODELBEAST portrait, plus LIVE computed facts that come free from the ephemeris
|
||||
// — distance from Sun and Earth, sunlight age (light-time), orbital speed, and the
|
||||
// length of the body's day and year (brief §5). Focusing Earth reveals the GODSIGH
|
||||
// easter egg. Not a toggleable feed — no HUD row.
|
||||
|
||||
const GM_SUN = 1.32712440018e11; // km^3/s^2, heliocentric gravitational parameter
|
||||
const YEAR_DAYS = 365.256;
|
||||
|
||||
const BLURBS = {
|
||||
sun: "The hearth of the system and 99.86% of its mass — a self-gravitating furnace fusing 600 million tonnes of hydrogen every second. Every world here is a fleck of its leftover disk, still falling around it.",
|
||||
mercury: "A cratered iron cinder with almost no air, swinging so close to the Sun that it laps every other world. Its days are longer than its years; three rotations fit in two of its tight orbits.",
|
||||
venus: "Earth's twin gone wrong: a runaway greenhouse under sulphuric cloud, hot enough to melt lead and crushing as the deep sea. It turns backwards, so slowly that a Venusian day outlasts its year.",
|
||||
earth: "The one world we know that woke up. A thin film of water and air over spinning iron, tilted just so for seasons — the pale blue vantage from which all the rest of this was measured.",
|
||||
mars: "A rusted desert half Earth's size, streaked with the dry beds of ancient rivers and crowned by Olympus Mons, the tallest volcano known. Cold, thin-aired, and the most-visited graveyard of robots off-world.",
|
||||
jupiter: "A failed star and the system's great vacuum cleaner, its storms wider than Earth and its gravity shepherding the asteroid belt. Four bright moons circle it in a clockwork Galileo used to unseat the Earth from the centre.",
|
||||
saturn: "The jewel — a globe you could float on water, wrapped in rings of ice no thicker than a mountain is tall. Titan below hides a haze-orange world of methane rain and hydrocarbon seas.",
|
||||
uranus: "An ice giant knocked onto its side, likely by an ancient impact, so it rolls around the Sun pole-first. Its faint rings and pale cyan haze mark the coldest atmosphere of any planet.",
|
||||
neptune: "The last great world, a deep-blue storm-lantern found by mathematics before any eye saw it. Its winds are the fastest in the system, and Triton, its captured moon, orbits backwards toward a slow doom.",
|
||||
pluto: "The heart-marked dwarf at the edge of the known, half rock and half ice, locked in a slow gravitational dance with its outsized moon Charon. Demoted from planet, it remains the king of the Kuiper Belt.",
|
||||
moon: "Earth's single great moon, born of a Mars-sized collision and slowly drifting away. It keeps one face turned to us forever, and its pull raises the tides that may have coaxed life ashore.",
|
||||
io: "The most volcanic body in the system, flexed and heated by Jupiter's tides until it turns itself inside out in sulphur.",
|
||||
europa: "A cracked shell of ice over a global ocean — perhaps twice Earth's water — and one of the best places to look for life beyond home.",
|
||||
ganymede: "The largest moon in the system, bigger than Mercury, and the only one with its own magnetic field.",
|
||||
callisto: "The most heavily cratered world known, a dark ancient face that has barely changed in four billion years.",
|
||||
titan: "The only moon with a thick atmosphere, raining methane onto seas of liquid hydrocarbon under an orange sky.",
|
||||
enceladus: "A tiny snowball that jets plumes of ocean water into space from cracks at its south pole.",
|
||||
triton: "A captured Kuiper Belt world orbiting Neptune backwards, spouting nitrogen geysers across a frozen pink surface.",
|
||||
charon: "Pluto's giant partner — the two are tidally locked face to face, forever hanging still in each other's sky.",
|
||||
};
|
||||
|
||||
export default function create(ctx) {
|
||||
const { bodies, ephem, lib, focus } = ctx;
|
||||
const panel = document.getElementById('infopanel');
|
||||
let lastFocus = null;
|
||||
let lastWall = 0;
|
||||
const _b = {}, _e = {};
|
||||
|
||||
function helioOf(id, jd, out) {
|
||||
const b = bodies[id];
|
||||
if (id === 'sun') { out.x = out.y = out.z = 0; return out; }
|
||||
if (b.ephemId) return ephem.helioEcl(b.ephemId, jd, out);
|
||||
if (b.elements) return ephem.smallBodyEcl(b.elements, jd, out);
|
||||
if (b.parent) return helioOf(b.parent, jd, out); // moon ≈ parent at AU scale
|
||||
out.x = out.y = out.z = 0; return out;
|
||||
}
|
||||
|
||||
function render(id) {
|
||||
const b = bodies[id];
|
||||
const artPath = `assets/art/grimoire/${id}.jpg`;
|
||||
const earthEgg = id === 'earth'
|
||||
? `<a class="ip-link" href="http://127.0.0.1:8137" target="_blank" rel="noopener">▸ open GODSIGH world view</a>` : '';
|
||||
panel.innerHTML = `
|
||||
<div class="ip-close" role="button" aria-label="close">×</div>
|
||||
<div class="ip-title"></div>
|
||||
<div class="ip-type"></div>
|
||||
<img class="ip-art" alt="" />
|
||||
<div class="ip-blurb"></div>
|
||||
<div class="ip-facts"></div>
|
||||
${earthEgg}`;
|
||||
panel.querySelector('.ip-title').textContent = b.name;
|
||||
panel.querySelector('.ip-type').textContent = b.type;
|
||||
panel.querySelector('.ip-blurb').textContent = BLURBS[id] || `A ${b.type}${b.parent ? ' of ' + bodies[b.parent].name : ''} in the solar system.`;
|
||||
const img = panel.querySelector('.ip-art');
|
||||
img.src = artPath;
|
||||
img.onerror = () => { img.style.display = 'none'; };
|
||||
panel.querySelector('.ip-close').addEventListener('click', () => { panel.hidden = true; });
|
||||
panel.hidden = false;
|
||||
}
|
||||
|
||||
function fact(label, value) {
|
||||
return `<div class="ip-fact"><span>${label}</span><span>${value}</span></div>`;
|
||||
}
|
||||
|
||||
function updateLive(id, jd) {
|
||||
const b = bodies[id];
|
||||
helioOf(id, jd, _b);
|
||||
const rSun = Math.hypot(_b.x, _b.y, _b.z); // AU
|
||||
ephem.helioEcl('earth', jd, _e);
|
||||
const rEarth = id === 'earth' ? 0 : Math.hypot(_b.x - _e.x, _b.y - _e.y, _b.z - _e.z);
|
||||
const rows = [];
|
||||
// physical
|
||||
rows.push(fact('Radius', lib.fmtKm(b.radiusKm)));
|
||||
if (b.axialTiltDeg != null) rows.push(fact('Axial tilt', `${b.axialTiltDeg.toFixed(1)}°`));
|
||||
// live distances
|
||||
if (id !== 'sun') rows.push(fact('From Sun', `${lib.fmtAU(rSun)} · ${lib.fmtLightTime(rSun)} light`));
|
||||
rows.push(fact('From Earth', id === 'earth' ? '—' : `${lib.fmtAU(rEarth)} · ${lib.fmtLightTime(rEarth)} light`));
|
||||
// orbital speed + periods
|
||||
if (b.type === 'moon') {
|
||||
const per = Math.abs(b.moonOrbit.periodDays);
|
||||
const v = (2 * Math.PI * b.moonOrbit.aKm) / (per * 86400);
|
||||
rows.push(fact('Orbits', bodies[b.parent].name));
|
||||
rows.push(fact('Orbital speed', lib.fmtSpeed(v)));
|
||||
rows.push(fact('Orbital period', `${lib.fmtDuration(per)}${b.moonOrbit.periodDays < 0 ? ' (retrograde)' : ''}`));
|
||||
} else if (id !== 'sun') {
|
||||
const a = b.elements ? b.elements.a : ephem.semiMajor(b.ephemId, jd);
|
||||
const rKm = rSun * lib.AU_KM, aKm = a * lib.AU_KM;
|
||||
const v = Math.sqrt(GM_SUN * (2 / rKm - 1 / aKm)); // vis-viva
|
||||
rows.push(fact('Orbital speed', lib.fmtSpeed(v)));
|
||||
rows.push(fact('Year', lib.fmtDuration(YEAR_DAYS * Math.pow(a, 1.5))));
|
||||
}
|
||||
if (b.rotationHours) {
|
||||
rows.push(fact('Day (rotation)', `${lib.fmtDuration(Math.abs(b.rotationHours) / 24)}${b.rotationHours < 0 ? ' (retrograde)' : ''}`));
|
||||
}
|
||||
const facts = panel.querySelector('.ip-facts');
|
||||
if (facts) facts.innerHTML = rows.join('');
|
||||
}
|
||||
|
||||
return {
|
||||
id: 'almanac',
|
||||
onClockTick(simMs, jd) {
|
||||
if (focus.id !== lastFocus) { lastFocus = focus.id; render(focus.id); lastWall = 0; }
|
||||
if (panel.hidden) return;
|
||||
const now = performance.now();
|
||||
if (now - lastWall < 200) return; // ≤5 Hz
|
||||
lastWall = now;
|
||||
updateLive(focus.id, jd);
|
||||
},
|
||||
};
|
||||
}
|
||||
@ -61,7 +61,7 @@ export default function create(ctx) {
|
||||
|
||||
let showOrbits = true, showLabels = true;
|
||||
ui.addLayer('orbits', 'Orbit paths', true, (on) => { showOrbits = on; for (const e of entries) e.line.visible = on; });
|
||||
ui.addLayer('labels', 'Body labels', true, (on) => { showLabels = on; for (const e of entries) e.lbl.div.style.display = on ? '' : 'none'; });
|
||||
ui.addLayer('labels', 'Body labels', true, (on) => { showLabels = on; for (const e of entries) e.lbl.obj.visible = on; });
|
||||
ui.setStatus('orbits', `${entries.length} orbits`, 'ok');
|
||||
ui.setStatus('labels', 'names shown', 'ok');
|
||||
|
||||
|
||||
@ -28,7 +28,7 @@ ephem.setExtendedRange(CONFIG.useExtendedRange);
|
||||
scale.init(CONFIG);
|
||||
|
||||
// ---- renderer / scene / camera ----
|
||||
const renderer = new THREE.WebGLRenderer({ antialias: true, logarithmicDepthBuffer: true });
|
||||
const renderer = new THREE.WebGLRenderer({ antialias: true, logarithmicDepthBuffer: true, preserveDrawingBuffer: true });
|
||||
renderer.setSize(innerWidth, innerHeight);
|
||||
renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
|
||||
document.getElementById('scene').appendChild(renderer.domElement);
|
||||
@ -340,7 +340,8 @@ const ctx = {
|
||||
};
|
||||
|
||||
const LAYER_MODULES = ['./layers/planets.js', './layers/moons.js', './layers/rings.js',
|
||||
'./layers/spacecraft.js', './layers/asteroids.js', './layers/comets.js', './layers/neos.js'];
|
||||
'./layers/spacecraft.js', './layers/asteroids.js', './layers/comets.js', './layers/neos.js',
|
||||
'./layers/almanac.js'];
|
||||
const activeLayers = [];
|
||||
async function loadLayers() {
|
||||
const results = await Promise.allSettled(LAYER_MODULES.map((p) => import(p)));
|
||||
|
||||