Merged suite: 21/21 gates in-browser incl. NEO real-orbit (err 1.16%), Jupiter@1000BC Δ0.004 AU, 2b-proof. Deep-time readout now '1000 BC-01-15'. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
99 lines
4.1 KiB
JavaScript
99 lines
4.1 KiB
JavaScript
// 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');
|
||
// Astronomical year → human era label: 0 = "1 BC", −999 = "1000 BC" (deep time,
|
||
// wave 2 VESSEL — a raw getUTCFullYear() would render 1000 BC as "-0999").
|
||
const eraYear = (y) => (y <= 0 ? `${1 - y} BC` : String(y).padStart(4, '0'));
|
||
// "2026-07-15 00:00:00 UTC" from unix-ms.
|
||
export function formatUTC(ms) {
|
||
const d = new Date(ms);
|
||
return `${eraYear(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 `${eraYear(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`; }
|