solargod/verify.html
monsterrobotparty 6084acad0f 🛰 probes: the live layer — 8 spacecraft via Horizons, trails + light-time (opus)
Stage 4. spacecraft.js: Voyager 1/2, New Horizons, JWST, Parker, Juno, Lucy,
Psyche. Fetches VECTORS (heliocentric ecliptic AU) over a multi-year span through
serve.py's cache, Catmull-Rom interpolated per frame; glowing diamond marker +
fading trail + label with live distance from Sun and one-way light-time from
Earth. Fail-soft per craft. Robustness: sequential fetch (Horizons throttles
bursts), serve.py skips caching non-$$SOE (error) responses, and span
auto-recovery clamps START/STOP to a craft's SPK coverage from Horizons' own
"prior to / after" hints (Psyche pre-launch, Juno past coverage) → 8/8 tracked.
Gate: Voyager 1 @2026-07-15 = (-32.07,-136.21,98.55) Δ0.002 AU; JWST Δ0.011 AU
from Earth. verify.html gates 10-11 green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 00:32:44 +10:00

172 lines
8.3 KiB
HTML
Raw 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.

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>SOLARGOD ▸ verify</title>
<style>
body { background: #05060a; color: #e6ddca; font-family: ui-monospace, Menlo, monospace; font-size: 13px; padding: 24px; }
h1 { color: #d9a441; font-size: 18px; letter-spacing: 2px; }
table { border-collapse: collapse; width: 100%; max-width: 960px; margin-top: 12px; }
th, td { text-align: left; padding: 5px 10px; border-bottom: 1px solid rgba(217,164,65,0.14); }
th { color: #8a8674; font-weight: 600; text-transform: uppercase; font-size: 10px; }
.pass { color: #6fcf6f; font-weight: 700; }
.fail { color: #e06a5a; font-weight: 700; }
.pending { color: #8a8674; }
.detail { color: #8a8674; font-size: 11px; }
#summary { margin-top: 16px; font-size: 15px; }
.importmap-note { color: #8a8674; }
</style>
<script type="importmap">
{ "imports": { "three": "https://cdn.jsdelivr.net/npm/three@0.170.0/build/three.module.js" } }
</script>
</head>
<body>
<h1>SOLARGOD ▸ VERIFY</h1>
<div class="importmap-note">lib.js + ephem.js + scale.js assertions · live Horizons via serve.py proxy. Serve with <code>python3 serve.py</code> then open <code>/verify.html</code>.</div>
<table><thead><tr><th>#</th><th>gate</th><th>result</th><th>detail</th></tr></thead><tbody id="rows"></tbody></table>
<div id="summary" class="pending">running…</div>
<script type="module">
import * as lib from './js/lib.js';
import * as ephem from './js/ephem.js';
import * as scale from './js/scale.js';
import { CONFIG } from './js/config.js';
scale.init(CONFIG);
const rows = document.getElementById('rows');
let n = 0, passed = 0, failed = 0;
function row(name, ok, detail) {
n++; if (ok) passed++; else failed++;
const tr = document.createElement('tr');
tr.innerHTML = `<td>${n}</td><td>${name}</td><td class="${ok ? 'pass' : 'fail'}">${ok ? 'PASS' : 'FAIL'}</td><td class="detail">${detail}</td>`;
rows.appendChild(tr);
}
function pendingRow(name) {
const tr = document.createElement('tr');
tr.innerHTML = `<td>—</td><td>${name}</td><td class="pending">…</td><td class="detail">querying Horizons…</td>`;
rows.appendChild(tr);
return tr;
}
// ---- 1. Horizons ground truth (brief §9), JD 2461236.5 = 2026-07-15 TDB ----
const JD = 2461236.5;
const truth = {
mars: { x: +1.05351078, y: +1.00862522, z: -0.00469549, tol: 0.005 },
jupiter: { x: -3.01967256, y: +4.33218144, z: +0.04956505, tol: 0.01 },
earth: { x: +0.38381080, y: -0.94118486, z: +0.00005421, tol: 0.003 },
};
for (const [id, t] of Object.entries(truth)) {
const p = ephem.helioEcl(id, JD);
const worst = Math.max(Math.abs(p.x - t.x), Math.abs(p.y - t.y), Math.abs(p.z - t.z));
row(`${id} @ 2026-07-15 (heliocentric ecliptic AU)`, worst <= t.tol,
`Δmax=${worst.toExponential(2)} AU (tol ${t.tol}) · got (${p.x.toFixed(5)}, ${p.y.toFixed(5)}, ${p.z.toFixed(5)})`);
}
// ---- 2. Kepler solver converges for e = 0.97 ----
{
const M = 0.5, e = 0.97, E = ephem.solveKepler(M, e);
const resid = Math.abs(E - e * Math.sin(E) - M);
row('Kepler solver e=0.97', resid < 1e-9, `residual ${resid.toExponential(2)} rad`);
}
// ---- 3. orbitPath endpoints join ----
{
const path = ephem.orbitPath('mercury', JD, 128);
const m = path.length / 3;
const d = Math.max(Math.abs(path[0] - path[(m-1)*3]), Math.abs(path[1] - path[(m-1)*3+1]), Math.abs(path[2] - path[(m-1)*3+2]));
row('orbitPath endpoints join', d < 1e-12, `Δ ${d.toExponential(2)} AU`);
}
// ---- 4. eclToWorld round-trips ----
{
const v = { x: 1.234, y: -5.678, z: 0.910 };
const w = lib.eclToWorld(v, {}); const back = lib.worldToEcl(w, {});
const d = Math.max(Math.abs(back.x - v.x), Math.abs(back.y - v.y), Math.abs(back.z - v.z));
row('eclToWorld round-trip', d === 0, `Δ ${d}`);
}
// ---- 5. MEGA→TRUE preserves direction (unit dot ≈ 1) ----
{
const ecl = ephem.helioEcl('jupiter', JD);
const a = scale.viewFromEcl(ecl, {});
scale.setMode('true');
for (let i = 0; i < 60 && scale.isTransitioning(); i++) scale.update(100);
const b = scale.viewFromEcl(ecl, {});
const na = Math.hypot(a.x, a.y, a.z), nb = Math.hypot(b.x, b.y, b.z);
const dot = (a.x * b.x + a.y * b.y + a.z * b.z) / (na * nb);
row('MEGA→TRUE keeps direction', Math.abs(dot - 1) < 1e-9, `unit dot = ${dot.toFixed(12)}`);
scale.setMode('mega'); for (let i = 0; i < 60 && scale.isTransitioning(); i++) scale.update(100);
}
// ---- 6+7. Live Horizons via proxy — two extra epochs (also gates the proxy) ----
// Mars @ J2000.0 (JD 2451545.0) and Jupiter @ 1900-01-01 (JD 2415020.5).
async function horizonsVec(command, jd) {
const q = {
format: 'text', COMMAND: `'${command}'`, OBJ_DATA: 'NO', MAKE_EPHEM: 'YES',
EPHEM_TYPE: 'VECTORS', CENTER: "'500@10'", TLIST: String(jd),
REF_PLANE: 'ECLIPTIC', REF_SYSTEM: 'J2000', VEC_TABLE: '1', OUT_UNITS: 'AU-D', CSV_FORMAT: 'NO',
};
const qs = Object.entries(q).map(([k, v]) => `${k}=${encodeURIComponent(v)}`).join('&');
const res = await fetch(`${CONFIG.proxy.horizons}?${qs}`);
const text = await res.text();
const soe = text.indexOf('$$SOE'), eoe = text.indexOf('$$EOE');
if (soe < 0 || eoe < 0) throw new Error('no $$SOE block — ' + text.slice(0, 160));
const block = text.slice(soe, eoe);
const gx = block.match(/X\s*=\s*(-?[\d.]+E?[-+]?\d*)/i);
const gy = block.match(/Y\s*=\s*(-?[\d.]+E?[-+]?\d*)/i);
const gz = block.match(/Z\s*=\s*(-?[\d.]+E?[-+]?\d*)/i);
if (!gx || !gy || !gz) throw new Error('could not parse X/Y/Z');
return { x: parseFloat(gx[1]), y: parseFloat(gy[1]), z: parseFloat(gz[1]) };
}
async function liveCheck(label, id, command, jd, tol) {
const tr = pendingRow(label);
try {
const got = await horizonsVec(command, jd);
const mine = ephem.helioEcl(id, jd);
const worst = Math.max(Math.abs(got.x - mine.x), Math.abs(got.y - mine.y), Math.abs(got.z - mine.z));
tr.remove();
row(label, worst <= tol, `Δmax=${worst.toExponential(2)} AU (tol ${tol}) · Horizons (${got.x.toFixed(5)}, ${got.y.toFixed(5)}, ${got.z.toFixed(5)})`);
} catch (err) {
tr.remove();
row(label, false, `proxy/Horizons error: ${err.message}`);
}
finish();
}
function finish() {
const s = document.getElementById('summary');
s.textContent = `${passed}/${n} gates pass` + (failed ? `${failed} FAILING` : ' — ALL PASS ✦');
s.className = failed ? 'fail' : 'pass';
}
// ---- 10. Voyager 1 (-31) @ 2026-07-15 ≈ (32.07, 136.21, +98.55) AU (brief §9) ----
async function craftCheck(label, command, jd, want, tol) {
const tr = pendingRow(label);
try {
const got = await horizonsVec(command, jd);
const worst = Math.max(Math.abs(got.x - want.x), Math.abs(got.y - want.y), Math.abs(got.z - want.z));
tr.remove();
row(label, worst <= tol, `Δmax=${worst.toFixed(3)} AU (tol ${tol}) · Horizons (${got.x.toFixed(2)}, ${got.y.toFixed(2)}, ${got.z.toFixed(2)}) · ${Math.hypot(got.x,got.y,got.z).toFixed(1)} AU out`);
} catch (err) { tr.remove(); row(label, false, `Horizons error: ${err.message}`); }
finish();
}
// ---- 11. JWST (-170) hugs Earth (Δ < 0.02 AU) ----
async function jwstCheck() {
const label = 'JWST (-170) hugs Earth Δ<0.02 AU';
const tr = pendingRow(label);
try {
const j = await horizonsVec('-170', 2461236.5);
const e = await horizonsVec('399', 2461236.5);
const d = Math.hypot(j.x - e.x, j.y - e.y, j.z - e.z);
tr.remove();
row(label, d < 0.02, `Δ=${(d).toFixed(4)} AU from Earth (${(d*lib.AU_KM/1e6).toFixed(2)}M km)`);
} catch (err) { tr.remove(); row(label, false, `Horizons error: ${err.message}`); }
finish();
}
finish();
liveCheck('Mars @ J2000.0 vs live Horizons', 'mars', '499', 2451545.0, 0.01);
liveCheck('Jupiter @ 1900-01-01 vs live Horizons', 'jupiter', '599', 2415020.5, 0.02);
craftCheck('Voyager 1 (-31) @ 2026-07-15', '-31', 2461236.5, { x: -32.07, y: -136.21, z: 98.55 }, 0.05);
jwstCheck();
</script>
</body>
</html>