// SOLARGOD shared helpers — time (JD / centuries), math, the ecliptic→world // frame map, and human formatters. Pure and dependency-free so verify.html can // import it (and ephem.js) with zero Three.js in scope. // ---- constants ---- export const AU_KM = 149597870.7; // 1 astronomical unit in km (IAU 2012) export const C_KMS = 299792.458; // speed of light, km/s export const MS_PER_DAY = 86400000; export const J2000_JD = 2451545.0; // 2000-Jan-01 12:00 TT export const UNIX_EPOCH_JD = 2440587.5; // JD of 1970-Jan-01 00:00 UTC export const DEG = Math.PI / 180; export const RAD = 180 / Math.PI; // ---- time ---- // We treat the sim clock as UTC and ignore the TT/TDB−UTC offset (~69 s in 2026). // At interplanetary scale that is < 1e-5 AU even for Mars — noted, deliberately // dropped (brief §2). JD from unix-ms: divide by ms/day, add the unix-epoch JD. export function jdFromUnixMs(ms) { return ms / MS_PER_DAY + UNIX_EPOCH_JD; } export function unixMsFromJd(jd) { return (jd - UNIX_EPOCH_JD) * MS_PER_DAY; } // Julian centuries since J2000.0. export function centuriesSinceJ2000(jd) { return (jd - J2000_JD) / 36525; } // ---- scalar math ---- export const clamp = (x, lo, hi) => (x < lo ? lo : x > hi ? hi : x); export const lerp = (a, b, t) => a + (b - a) * t; export function smoothstep(t) { t = clamp(t, 0, 1); return t * t * (3 - 2 * t); } // Normalize degrees to (−180, +180]. Must run in float64 AFTER any large // subtraction (Mercury's L rate is 149473°/Cy → M can be tens of thousands of // degrees before reduction; brief §12). export function normDegPM180(d) { d = d % 360; if (d <= -180) d += 360; else if (d > 180) d -= 360; return d; } export const normDeg360 = (d) => ((d % 360) + 360) % 360; // ---- frame convention (fixed here once; nothing else maps by hand) ---- // Heliocentric J2000 ecliptic {x,y,z} [AU or view units] → Three.js world: // ecliptic X → world x, ecliptic Z (north) → world y, ecliptic Y → world −z. // Keeps the frame right-handed with Y up. `out` is any {x,y,z} sink (Vector3 ok). export function eclToWorld(ecl, out = {}) { out.x = ecl.x; out.y = ecl.z; out.z = -ecl.y; return out; } // Inverse, for round-trip checks and picking rays. export function worldToEcl(w, out = {}) { out.x = w.x; out.y = -w.z; out.z = w.y; return out; } // ---- formatters ---- const pad = (n, w = 2) => String(n).padStart(w, '0'); // "2026-07-15 00:00:00 UTC" from unix-ms. export function formatUTC(ms) { const d = new Date(ms); return `${d.getUTCFullYear()}-${pad(d.getUTCMonth() + 1)}-${pad(d.getUTCDate())} ` + `${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())}:${pad(d.getUTCSeconds())} UTC`; } export function formatUTCDate(ms) { const d = new Date(ms); return `${d.getUTCFullYear()}-${pad(d.getUTCMonth() + 1)}-${pad(d.getUTCDate())}`; } export function fmtAU(au) { const a = Math.abs(au); if (a < 0.001) return `${(au * AU_KM).toFixed(0)} km`; if (a < 100) return `${au.toFixed(3)} AU`; return `${au.toFixed(1)} AU`; } export function fmtKm(km) { const a = Math.abs(km); if (a >= 1e7) return `${(km / 1e6).toLocaleString('en-US', { maximumFractionDigits: 2 })} M km`; return `${Math.round(km).toLocaleString('en-US')} km`; } // Light-time for a distance in AU → "43 min 12 s" / "1 h 08 m" / "8.3 s". export function fmtLightTime(au) { let s = (au * AU_KM) / C_KMS; if (s < 90) return `${s.toFixed(1)} s`; const h = Math.floor(s / 3600); s -= h * 3600; const m = Math.floor(s / 60); s -= m * 60; if (h > 0) return `${h} h ${pad(m)} m`; return `${m} min ${pad(Math.floor(s))} s`; } // Duration in days → "27.3 d" / "1.88 yr". export function fmtDuration(days) { const a = Math.abs(days); if (a < 2) return `${(a * 24).toFixed(1)} h`; if (a < 900) return `${a.toFixed(1)} d`; return `${(a / 365.25).toFixed(2)} yr`; } export function fmtSpeed(kms) { return `${kms.toFixed(2)} km/s`; }