🛳 vessel: vendor three@0.170.0 (offline) + deep-time epoch mode (opus)
V.1 — cut the CDN tether. Vendored the exact pinned files the importmap used (three.module.js r170 + OrbitControls + CSS2DRenderer, ~1.29 MB) under vendor/three/, mirroring the package layout so the addons' bare `import from 'three'` resolves. Import graph verified shallow (addons import only 'three'; the bundle is self-contained). Both importmaps (index.html, verify.html) now point at ./vendor/three/… — zero jsdelivr/CDN URLs remain. MIT LICENSE + PROVENANCE.txt retained. V.2 — open deep time. EPOCH toggle (1800–2050 / 3000 BC–3000 AD) next to MEGA/TRUE; deep mode is a hash-carried page mode (&e=1) read before ephem/clock init → ephem.setExtendedRange(true) + CONFIG.time swaps to the deep bounds. Timeline end labels + scrub tooltip now read from CONFIG (were hardcoded 1800/2050). Toggle serializes current view and reloads with the flipped e token (orbit paths + element resolution are baked per-session). Appended 1 decade/s and 1 century/s rates. Hash e token added to applyHash/serializeHash. V.3 — appended verify gates (PERIHELION-V): Jupiter @1000 BC (extended 2a+2b) vs live Horizons barycenter tol 0.15 AU (Δ 0.0041); Mercury @2500 tol 0.02 AU (Δ 3e-5); Table 2b M-correction proven live (zeroing it worsens Jupiter@1000BC by 0.0214 AU). Core 13 gates untouched. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
c794ca1182
commit
c1c1afb0db
17
index.html
17
index.html
@ -5,11 +5,12 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>SOLARGOD ▸ Orrery</title>
|
||||
<link rel="stylesheet" href="css/style.css" />
|
||||
<!-- Three.js is vendored (offline-first, zero CDN). See vendor/three/PROVENANCE.txt. -->
|
||||
<script type="importmap">
|
||||
{
|
||||
"imports": {
|
||||
"three": "https://cdn.jsdelivr.net/npm/three@0.170.0/build/three.module.js",
|
||||
"three/addons/": "https://cdn.jsdelivr.net/npm/three@0.170.0/examples/jsm/"
|
||||
"three": "./vendor/three/build/three.module.js",
|
||||
"three/addons/": "./vendor/three/examples/jsm/"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -35,6 +36,14 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ctl-row slider-row">
|
||||
<label class="ctl-label">Epoch</label>
|
||||
<div class="seg" id="epoch-toggle" title="Deep-time range — switching reloads the session onto Table 2a/2b">
|
||||
<button data-epoch="near" class="active" type="button">1800–2050</button>
|
||||
<button data-epoch="deep" type="button">3000 BC – 3000 AD</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ctl-row slider-row">
|
||||
<label class="ctl-label">Body scale</label>
|
||||
<div class="seg" id="bodyscale-toggle">
|
||||
@ -66,11 +75,11 @@
|
||||
<select id="tb-rate" title="Time rate"></select>
|
||||
<div id="date-readout">—</div>
|
||||
</div>
|
||||
<div id="timeline-outer" title="Scrub 1800 → 2050">
|
||||
<div id="timeline-outer" title="Scrub">
|
||||
<div id="timeline-fill"></div>
|
||||
<div id="timeline-cursor"></div>
|
||||
</div>
|
||||
<div class="tb-ends"><span>1800</span><span>2050</span></div>
|
||||
<div class="tb-ends"><span id="tb-end-min">1800</span><span id="tb-end-max">2050</span></div>
|
||||
</div>
|
||||
|
||||
<!-- info panel (left, Stage 6) -->
|
||||
|
||||
10
js/config.js
10
js/config.js
@ -9,10 +9,14 @@ export const CONFIG = {
|
||||
cad: 'proxy/cad',
|
||||
},
|
||||
|
||||
// Clock validity = Table 1 range. Scrubbing clamps here (brief §2).
|
||||
// Clock validity = Table 1 range. Scrubbing clamps here (brief §2). Deep-time
|
||||
// (EPOCH toggle, &e=1) swaps min/maxMs to the deep bounds below — main.js does
|
||||
// the swap at boot, before the clock inits. JS Date handles astronomical years.
|
||||
time: {
|
||||
minMs: Date.UTC(1800, 0, 1),
|
||||
maxMs: Date.UTC(2050, 0, 1),
|
||||
deepMinMs: Date.UTC(-2999, 0, 1), // 3000 BC (Table 2a extended range)
|
||||
deepMaxMs: Date.UTC(2999, 11, 31), // 3000 AD
|
||||
liveThresholdSec: 60, // |sim − wall| ≤ 60 s at 1× → LIVE
|
||||
},
|
||||
|
||||
@ -53,6 +57,8 @@ export const CONFIG = {
|
||||
{ label: '1 wk/s', value: 604800 },
|
||||
{ label: '1 mo/s', value: 2629800 }, // 30.4375 d
|
||||
{ label: '1 yr/s', value: 31557600 }, // 365.25 d
|
||||
{ label: '1 decade/s', value: 315576000 }, // 10 · 365.25 d — deep-time travel
|
||||
{ label: '1 century/s', value: 3155760000 }, // 100 · 365.25 d
|
||||
],
|
||||
defaultRateIndex: 3, // 1 day/s
|
||||
|
||||
@ -65,7 +71,7 @@ export const CONFIG = {
|
||||
orbitRebuildCy: 0.1,
|
||||
orbitSamples: 512,
|
||||
|
||||
useExtendedRange: false, // Table 2a swap — plumbing only, no v1 UI
|
||||
useExtendedRange: false, // default (near mode); main.js flips it on for &e=1 deep time
|
||||
|
||||
colors: {
|
||||
accent: '#d9a441', // brass
|
||||
|
||||
50
js/main.js
50
js/main.js
@ -24,6 +24,17 @@ import * as ephem from './ephem.js';
|
||||
import { BODIES, PLANET_ORDER, moonsOf } from './bodies.js';
|
||||
|
||||
const DEBUG = new URLSearchParams(location.search).has('debug');
|
||||
|
||||
// Deep-time (EPOCH) mode is a page-level switch carried in the hash as &e=1. Read
|
||||
// it HERE — before ephem/clock init — so the whole session runs on the extended
|
||||
// Table 2a/2b elements and the wider clock window. Toggling it reloads the page
|
||||
// (see wireEpochToggle); orbit paths + element resolution are baked per-session.
|
||||
const DEEP_TIME = new URLSearchParams(location.hash.replace(/^#/, '')).get('e') === '1';
|
||||
if (DEEP_TIME) {
|
||||
CONFIG.useExtendedRange = true;
|
||||
CONFIG.time.minMs = CONFIG.time.deepMinMs;
|
||||
CONFIG.time.maxMs = CONFIG.time.deepMaxMs;
|
||||
}
|
||||
ephem.setExtendedRange(CONFIG.useExtendedRange);
|
||||
scale.init(CONFIG);
|
||||
|
||||
@ -285,6 +296,34 @@ function buildRateMenu() {
|
||||
sel.addEventListener('change', () => { clock.rateIndex = Number(sel.value); });
|
||||
}
|
||||
|
||||
// Deep-time is a whole-session mode (extended tables + a wider clock + per-session
|
||||
// baked orbit paths), so switching serializes the current view and RELOADS with the
|
||||
// flipped &e token — deterministic, and honest about what re-resolves underneath.
|
||||
function wireEpochToggle() {
|
||||
const seg = document.getElementById('epoch-toggle');
|
||||
if (!seg) return;
|
||||
for (const b of seg.querySelectorAll('button')) b.classList.toggle('active', (b.dataset.epoch === 'deep') === DEEP_TIME);
|
||||
seg.addEventListener('click', (e) => {
|
||||
const btn = e.target.closest('button'); if (!btn) return;
|
||||
const wantDeep = btn.dataset.epoch === 'deep';
|
||||
if (wantDeep === DEEP_TIME) return; // already in this mode
|
||||
location.hash = serializeHash(wantDeep); // keep focus/time/scale/layers/cam, flip e
|
||||
location.reload();
|
||||
});
|
||||
}
|
||||
|
||||
// Timeline end labels + scrub tooltip follow CONFIG.time (were hardcoded 1800/2050).
|
||||
function epochYearLabel(ms) {
|
||||
const y = new Date(ms).getUTCFullYear(); // astronomical year: 0 = 1 BC, -1 = 2 BC …
|
||||
return y <= 0 ? `${1 - y} BC` : String(y);
|
||||
}
|
||||
function applyTimelineBounds() {
|
||||
const lo = epochYearLabel(CONFIG.time.minMs), hi = epochYearLabel(CONFIG.time.maxMs);
|
||||
const min = document.getElementById('tb-end-min'); if (min) min.textContent = lo;
|
||||
const max = document.getElementById('tb-end-max'); if (max) max.textContent = hi;
|
||||
const strip = document.getElementById('timeline-outer'); if (strip) strip.title = `Scrub ${lo} → ${hi}`;
|
||||
}
|
||||
|
||||
function wireControls() {
|
||||
// scale toggle
|
||||
document.getElementById('scale-toggle').addEventListener('click', (e) => {
|
||||
@ -381,12 +420,16 @@ function applyHash() {
|
||||
const cam = p.get('cam');
|
||||
if (cam) { const a = cam.split(',').map(Number); if (a.length === 3 && a.every(Number.isFinite)) pendingCam = a; }
|
||||
if (p.has('L')) pendingLayers = p.get('L');
|
||||
// `e` (deep-time) was already consumed at module load — it must precede clock/
|
||||
// ephem init, so it can't be re-applied here. It stays in the round-trip; the
|
||||
// EPOCH toggle reloads to change it. Warn if it somehow drifted post-load.
|
||||
if ((p.get('e') === '1') !== DEEP_TIME) console.warn('[solargod] hash e-token changed after load — reload to apply');
|
||||
}
|
||||
let pendingCam = null;
|
||||
function syncScaleButtons(mode) {
|
||||
for (const b of document.querySelectorAll('#scale-toggle button')) b.classList.toggle('active', b.dataset.mode === mode);
|
||||
}
|
||||
function serializeHash() {
|
||||
function serializeHash(deep = DEEP_TIME) {
|
||||
const off = Math.round((clock.simMs - Date.now()) / 1000);
|
||||
const t = Math.abs(off) <= CONFIG.time.liveThresholdSec ? '0' : `@${Math.round(clock.simMs)}`;
|
||||
const dist = camera.position.length();
|
||||
@ -394,7 +437,8 @@ function serializeHash() {
|
||||
const pitch = Math.asin(lib.clamp(camera.position.y / (dist || 1), -1, 1));
|
||||
const cam = `${dist.toFixed(2)},${heading.toFixed(3)},${pitch.toFixed(3)}`;
|
||||
const on = ui.getLayerIds().filter((id) => ui.getLayerChecked(id)).join(',');
|
||||
return `#f=${focus.id}&t=${t}&s=${scale.getMode()}&L=${on}&cam=${cam}`;
|
||||
const e = deep ? '&e=1' : ''; // deep-time token — omitted in near mode (clean links)
|
||||
return `#f=${focus.id}&t=${t}&s=${scale.getMode()}&L=${on}&cam=${cam}${e}`;
|
||||
}
|
||||
function startHashSync() {
|
||||
setInterval(() => { const h = serializeHash(); if (h !== location.hash) history.replaceState(null, '', h); }, 1000);
|
||||
@ -475,6 +519,8 @@ addEventListener('resize', () => {
|
||||
buildFocusMenu();
|
||||
buildRateMenu();
|
||||
wireControls();
|
||||
wireEpochToggle();
|
||||
applyTimelineBounds();
|
||||
if (DEBUG) document.getElementById('debug').hidden = false;
|
||||
try { applyHash(); } catch (err) { console.warn('bad hash', err); }
|
||||
setFocus(focus.id, false);
|
||||
|
||||
21
vendor/three/LICENSE
vendored
Normal file
21
vendor/three/LICENSE
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
The MIT License
|
||||
|
||||
Copyright © 2010-2024 three.js authors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
31
vendor/three/PROVENANCE.txt
vendored
Normal file
31
vendor/three/PROVENANCE.txt
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
SOLARGOD vendored dependency — Three.js
|
||||
=======================================
|
||||
|
||||
Version : three@0.170.0 (REVISION '170')
|
||||
License : MIT (see ./LICENSE — retained verbatim from the package)
|
||||
Fetched : 2026-07-16 via jsdelivr CDN
|
||||
|
||||
Source URLs (exact files, pinned version):
|
||||
build/three.module.js
|
||||
https://cdn.jsdelivr.net/npm/three@0.170.0/build/three.module.js
|
||||
examples/jsm/controls/OrbitControls.js
|
||||
https://cdn.jsdelivr.net/npm/three@0.170.0/examples/jsm/controls/OrbitControls.js
|
||||
examples/jsm/renderers/CSS2DRenderer.js
|
||||
https://cdn.jsdelivr.net/npm/three@0.170.0/examples/jsm/renderers/CSS2DRenderer.js
|
||||
LICENSE
|
||||
https://cdn.jsdelivr.net/npm/three@0.170.0/LICENSE
|
||||
|
||||
Why vendored (not an npm dep): SOLARGOD has no build step and no node_modules.
|
||||
These are the *exact* pinned files the importmap used to pull from the CDN, copied
|
||||
in so the app boots with zero network. Directory layout mirrors the package so the
|
||||
addons' bare `import ... from 'three'` resolves through the importmap:
|
||||
index.html / verify.html importmap →
|
||||
"three": "./vendor/three/build/three.module.js"
|
||||
"three/addons/": "./vendor/three/examples/jsm/"
|
||||
|
||||
Import graph (verified shallow): OrbitControls.js and CSS2DRenderer.js import ONLY
|
||||
from bare 'three'; three.module.js is a self-contained bundle with no external or
|
||||
relative imports. No further files are needed.
|
||||
|
||||
Do not edit these files — they are upstream verbatim. Re-vendor by re-fetching the
|
||||
same pinned URLs.
|
||||
54571
vendor/three/build/three.module.js
vendored
Normal file
54571
vendor/three/build/three.module.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1523
vendor/three/examples/jsm/controls/OrbitControls.js
vendored
Normal file
1523
vendor/three/examples/jsm/controls/OrbitControls.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
238
vendor/three/examples/jsm/renderers/CSS2DRenderer.js
vendored
Normal file
238
vendor/three/examples/jsm/renderers/CSS2DRenderer.js
vendored
Normal file
@ -0,0 +1,238 @@
|
||||
import {
|
||||
Matrix4,
|
||||
Object3D,
|
||||
Vector2,
|
||||
Vector3
|
||||
} from 'three';
|
||||
|
||||
class CSS2DObject extends Object3D {
|
||||
|
||||
constructor( element = document.createElement( 'div' ) ) {
|
||||
|
||||
super();
|
||||
|
||||
this.isCSS2DObject = true;
|
||||
|
||||
this.element = element;
|
||||
|
||||
this.element.style.position = 'absolute';
|
||||
this.element.style.userSelect = 'none';
|
||||
|
||||
this.element.setAttribute( 'draggable', false );
|
||||
|
||||
this.center = new Vector2( 0.5, 0.5 ); // ( 0, 0 ) is the lower left; ( 1, 1 ) is the top right
|
||||
|
||||
this.addEventListener( 'removed', function () {
|
||||
|
||||
this.traverse( function ( object ) {
|
||||
|
||||
if (
|
||||
object.element instanceof object.element.ownerDocument.defaultView.Element &&
|
||||
object.element.parentNode !== null
|
||||
) {
|
||||
|
||||
object.element.remove();
|
||||
|
||||
}
|
||||
|
||||
} );
|
||||
|
||||
} );
|
||||
|
||||
}
|
||||
|
||||
copy( source, recursive ) {
|
||||
|
||||
super.copy( source, recursive );
|
||||
|
||||
this.element = source.element.cloneNode( true );
|
||||
|
||||
this.center = source.center;
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
const _vector = new Vector3();
|
||||
const _viewMatrix = new Matrix4();
|
||||
const _viewProjectionMatrix = new Matrix4();
|
||||
const _a = new Vector3();
|
||||
const _b = new Vector3();
|
||||
|
||||
class CSS2DRenderer {
|
||||
|
||||
constructor( parameters = {} ) {
|
||||
|
||||
const _this = this;
|
||||
|
||||
let _width, _height;
|
||||
let _widthHalf, _heightHalf;
|
||||
|
||||
const cache = {
|
||||
objects: new WeakMap()
|
||||
};
|
||||
|
||||
const domElement = parameters.element !== undefined ? parameters.element : document.createElement( 'div' );
|
||||
|
||||
domElement.style.overflow = 'hidden';
|
||||
|
||||
this.domElement = domElement;
|
||||
|
||||
this.getSize = function () {
|
||||
|
||||
return {
|
||||
width: _width,
|
||||
height: _height
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
this.render = function ( scene, camera ) {
|
||||
|
||||
if ( scene.matrixWorldAutoUpdate === true ) scene.updateMatrixWorld();
|
||||
if ( camera.parent === null && camera.matrixWorldAutoUpdate === true ) camera.updateMatrixWorld();
|
||||
|
||||
_viewMatrix.copy( camera.matrixWorldInverse );
|
||||
_viewProjectionMatrix.multiplyMatrices( camera.projectionMatrix, _viewMatrix );
|
||||
|
||||
renderObject( scene, scene, camera );
|
||||
zOrder( scene );
|
||||
|
||||
};
|
||||
|
||||
this.setSize = function ( width, height ) {
|
||||
|
||||
_width = width;
|
||||
_height = height;
|
||||
|
||||
_widthHalf = _width / 2;
|
||||
_heightHalf = _height / 2;
|
||||
|
||||
domElement.style.width = width + 'px';
|
||||
domElement.style.height = height + 'px';
|
||||
|
||||
};
|
||||
|
||||
function hideObject( object ) {
|
||||
|
||||
if ( object.isCSS2DObject ) object.element.style.display = 'none';
|
||||
|
||||
for ( let i = 0, l = object.children.length; i < l; i ++ ) {
|
||||
|
||||
hideObject( object.children[ i ] );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function renderObject( object, scene, camera ) {
|
||||
|
||||
if ( object.visible === false ) {
|
||||
|
||||
hideObject( object );
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
if ( object.isCSS2DObject ) {
|
||||
|
||||
_vector.setFromMatrixPosition( object.matrixWorld );
|
||||
_vector.applyMatrix4( _viewProjectionMatrix );
|
||||
|
||||
const visible = ( _vector.z >= - 1 && _vector.z <= 1 ) && ( object.layers.test( camera.layers ) === true );
|
||||
|
||||
const element = object.element;
|
||||
element.style.display = visible === true ? '' : 'none';
|
||||
|
||||
if ( visible === true ) {
|
||||
|
||||
object.onBeforeRender( _this, scene, camera );
|
||||
|
||||
element.style.transform = 'translate(' + ( - 100 * object.center.x ) + '%,' + ( - 100 * object.center.y ) + '%)' + 'translate(' + ( _vector.x * _widthHalf + _widthHalf ) + 'px,' + ( - _vector.y * _heightHalf + _heightHalf ) + 'px)';
|
||||
|
||||
if ( element.parentNode !== domElement ) {
|
||||
|
||||
domElement.appendChild( element );
|
||||
|
||||
}
|
||||
|
||||
object.onAfterRender( _this, scene, camera );
|
||||
|
||||
}
|
||||
|
||||
const objectData = {
|
||||
distanceToCameraSquared: getDistanceToSquared( camera, object )
|
||||
};
|
||||
|
||||
cache.objects.set( object, objectData );
|
||||
|
||||
}
|
||||
|
||||
for ( let i = 0, l = object.children.length; i < l; i ++ ) {
|
||||
|
||||
renderObject( object.children[ i ], scene, camera );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function getDistanceToSquared( object1, object2 ) {
|
||||
|
||||
_a.setFromMatrixPosition( object1.matrixWorld );
|
||||
_b.setFromMatrixPosition( object2.matrixWorld );
|
||||
|
||||
return _a.distanceToSquared( _b );
|
||||
|
||||
}
|
||||
|
||||
function filterAndFlatten( scene ) {
|
||||
|
||||
const result = [];
|
||||
|
||||
scene.traverseVisible( function ( object ) {
|
||||
|
||||
if ( object.isCSS2DObject ) result.push( object );
|
||||
|
||||
} );
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
function zOrder( scene ) {
|
||||
|
||||
const sorted = filterAndFlatten( scene ).sort( function ( a, b ) {
|
||||
|
||||
if ( a.renderOrder !== b.renderOrder ) {
|
||||
|
||||
return b.renderOrder - a.renderOrder;
|
||||
|
||||
}
|
||||
|
||||
const distanceA = cache.objects.get( a ).distanceToCameraSquared;
|
||||
const distanceB = cache.objects.get( b ).distanceToCameraSquared;
|
||||
|
||||
return distanceA - distanceB;
|
||||
|
||||
} );
|
||||
|
||||
const zMax = sorted.length;
|
||||
|
||||
for ( let i = 0, l = sorted.length; i < l; i ++ ) {
|
||||
|
||||
sorted[ i ].element.style.zIndex = zMax - i;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export { CSS2DObject, CSS2DRenderer };
|
||||
77
verify.html
77
verify.html
@ -16,8 +16,9 @@
|
||||
#summary { margin-top: 16px; font-size: 15px; }
|
||||
.importmap-note { color: #8a8674; }
|
||||
</style>
|
||||
<!-- Three.js vendored (offline-first, zero CDN). See vendor/three/PROVENANCE.txt. -->
|
||||
<script type="importmap">
|
||||
{ "imports": { "three": "https://cdn.jsdelivr.net/npm/three@0.170.0/build/three.module.js" } }
|
||||
{ "imports": { "three": "./vendor/three/build/three.module.js", "three/addons/": "./vendor/three/examples/jsm/" } }
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
@ -284,6 +285,80 @@
|
||||
}
|
||||
finish();
|
||||
}
|
||||
|
||||
/* PERIHELION-V — deep-time (extended Table 2a/2b) accuracy gates.
|
||||
Capture the "mine" positions with the extended range ON, then restore it OFF
|
||||
synchronously and atomically: ES modules are singletons, so setExtendedRange
|
||||
mutates the same ephem the live gates above (Mars@J2000 / Jupiter@1900, which
|
||||
expect Table 1) share — the flag must never be ON across an await. */
|
||||
const JD_JUP_1000BC = 1355818.5; // 1000 BC-01-01
|
||||
const JD_MERC_2500 = lib.jdFromUnixMs(Date.UTC(2500, 0, 1));
|
||||
// Scratch extended-Jupiter resolve — a faithful copy of ephem's Table 2a+2b
|
||||
// math so the 2b M-correction can be ZEROED without editing ephem.js.
|
||||
const T2A_JUP = { el: [5.20248019, 0.04853590, 1.29861416, 34.33479152, 14.27495244, 100.29282654],
|
||||
rate: [-0.00002864, 0.00018026, -0.00322699, 3034.90371757, 0.18199196, 0.13024619] };
|
||||
const B2_JUP = [-0.00012452, 0.06064060, -0.35635438, 38.35125000]; // [b, c, s, f]
|
||||
function jupiterExtScratch(jd, with2b) {
|
||||
const T = lib.centuriesSinceJ2000(jd);
|
||||
const a = T2A_JUP.el[0] + T2A_JUP.rate[0] * T, e = T2A_JUP.el[1] + T2A_JUP.rate[1] * T;
|
||||
const I = T2A_JUP.el[2] + T2A_JUP.rate[2] * T, L = T2A_JUP.el[3] + T2A_JUP.rate[3] * T;
|
||||
const wb = T2A_JUP.el[4] + T2A_JUP.rate[4] * T, Om = T2A_JUP.el[5] + T2A_JUP.rate[5] * T;
|
||||
let M = L - wb;
|
||||
if (with2b) { const [b, c, s, f] = B2_JUP; M += b * T * T + c * Math.cos(f * T * lib.DEG) + s * Math.sin(f * T * lib.DEG); }
|
||||
return ephem.eclFromElements(a, e, I, Om, wb - Om, M, {});
|
||||
}
|
||||
let jupMine, mercMine, jupZeroed;
|
||||
{
|
||||
ephem.setExtendedRange(true);
|
||||
jupMine = ephem.helioEcl('jupiter', JD_JUP_1000BC, {});
|
||||
mercMine = ephem.helioEcl('mercury', JD_MERC_2500, {});
|
||||
ephem.setExtendedRange(false); // restore before any await
|
||||
jupZeroed = jupiterExtScratch(JD_JUP_1000BC, false); // 2b correction removed
|
||||
}
|
||||
const dmax3 = (a, b) => Math.max(Math.abs(a.x - b.x), Math.abs(a.y - b.y), Math.abs(a.z - b.z));
|
||||
(async () => {
|
||||
// Wait for the base live gates + lane-N/E gates (18 total) to finish — ThreadingHTTPServer
|
||||
// does NOT serialize upstream calls, so we must never fetch Horizons in parallel.
|
||||
for (let i = 0; i < 480 && n < 18; i++) await new Promise((r) => setTimeout(r, 250));
|
||||
// Jupiter ground truth: barycenter '5' (DE441, long span). Planet-center '599'
|
||||
// has NO Horizons ephemeris before A.D. 1600; the SSD approximate elements model
|
||||
// the giant-planet SYSTEM barycenter anyway (Jupiter↔bary ≈ 5e-4 AU ≪ tol).
|
||||
let jupH = null;
|
||||
{
|
||||
const label = 'Jupiter @1000 BC (extended 2a+2b) vs live Horizons — tol 0.15 AU';
|
||||
const tr = pendingRow(label);
|
||||
try {
|
||||
jupH = await horizonsVec('5', JD_JUP_1000BC);
|
||||
const w = dmax3(jupMine, jupH);
|
||||
tr.remove();
|
||||
row(label, w <= 0.15, `Δmax=${w.toFixed(4)} AU (tol 0.15) · bary '5' (${jupH.x.toFixed(4)}, ${jupH.y.toFixed(4)}, ${jupH.z.toFixed(4)}) · mine (${jupMine.x.toFixed(4)}, ${jupMine.y.toFixed(4)}, ${jupMine.z.toFixed(4)})`);
|
||||
} catch (err) { tr.remove(); row(label, false, `Horizons error: ${err.message}`); }
|
||||
finish();
|
||||
}
|
||||
{
|
||||
const label = 'Mercury @2500-01-01 (extended 2a) vs live Horizons — tol 0.02 AU';
|
||||
const tr = pendingRow(label);
|
||||
try {
|
||||
const mercH = await horizonsVec('199', JD_MERC_2500);
|
||||
const w = dmax3(mercMine, mercH);
|
||||
tr.remove();
|
||||
row(label, w <= 0.02, `Δmax=${w.toExponential(2)} AU (tol 0.02) · Horizons (${mercH.x.toFixed(5)}, ${mercH.y.toFixed(5)}, ${mercH.z.toFixed(5)})`);
|
||||
} catch (err) { tr.remove(); row(label, false, `Horizons error: ${err.message}`); }
|
||||
finish();
|
||||
}
|
||||
{
|
||||
// Prove Table 2b's M-correction is LIVE: zeroing it must worsen Jupiter@1000 BC
|
||||
// (reuses the Jupiter Horizons vector fetched above — no extra request).
|
||||
const label = 'Table 2b M-correction engages — zeroing it worsens Jupiter@1000 BC';
|
||||
if (jupH) {
|
||||
const dWith = dmax3(jupMine, jupH), dZero = dmax3(jupZeroed, jupH);
|
||||
row(label, dZero > dWith, `with 2b Δ=${dWith.toFixed(4)} AU → zeroed Δ=${dZero.toFixed(4)} AU (worse by ${(dZero - dWith).toFixed(4)} AU)`);
|
||||
} else {
|
||||
row(label, false, 'skipped — Jupiter Horizons fetch failed above');
|
||||
}
|
||||
finish();
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user