// 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' ? `▸ open GODSIGH world view` : ''; panel.innerHTML = `
×
${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 `
${label}${value}
`; } 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); }, }; }