Stage 5. asteroids.js: 1500-strong main-belt sample from SBDB (sb-class=MBA), propagated by the shared Kepler core, one Points cloud refreshed ≤10 Hz, off by default. comets.js: 1P/2P/67P/96P from SBDB (elliptical only) with full drawn orbits, marker + anti-sunward tail scaled 1/r², on by default. neos.js: CAD ±30d close-approach radar around Earth, colored by miss distance, clickable, lunar- distance labels, off by default. All fail-soft. verify.html gate: belt view-radii 12.1–13.9 ∈ (Mars 10.7, Jupiter 17.4); Halley aphelion 35.3 AU > Neptune. Also serialized verify's live Horizons checks (burst-throttle fix) → 13/13 green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
68 lines
2.6 KiB
JavaScript
68 lines
2.6 KiB
JavaScript
// SOLARGOD asteroids layer (Stage 5) — a main-belt sample from JPL SBDB, each rock
|
|
// propagated by the SAME Kepler core as the planets (ephem.smallBodyEcl over epoch
|
|
// elements). One Points cloud, positions refreshed ≤10 Hz. Off by default; additive
|
|
// and fail-soft (brief §5, §11). Gate: the belt sits as a torus between Mars and
|
|
// Jupiter — not a sphere, not a line.
|
|
|
|
export default function create(ctx) {
|
|
const { THREE, CONFIG, ui, scale, ephem, worldGroup } = ctx;
|
|
const CAP = 1500;
|
|
|
|
let elements = [];
|
|
let geo = null, points = null, positions = null;
|
|
let on = false, lastWall = 0;
|
|
const _e = {}, _v = {};
|
|
|
|
ui.addLayer('asteroids', 'Main belt', false, (v) => { on = v; if (points) points.visible = v; if (v) refresh(ctx.clock.jd, true); });
|
|
ui.setStatus('asteroids', 'off', 'off');
|
|
load();
|
|
|
|
async function load() {
|
|
ui.setStatus('asteroids', 'fetching SBDB…', 'warn');
|
|
try {
|
|
const q = `fields=full_name,a,e,i,om,w,ma,epoch&sb-kind=a&sb-class=MBA&limit=${CAP}`;
|
|
const j = await (await fetch(`${CONFIG.proxy.sbdb}?${q}`)).json();
|
|
const F = j.fields, ix = (k) => F.indexOf(k);
|
|
elements = j.data.map((r) => ({
|
|
a: +r[ix('a')], e: +r[ix('e')], i: +r[ix('i')], om: +r[ix('om')], w: +r[ix('w')], ma: +r[ix('ma')], epoch: +r[ix('epoch')],
|
|
})).filter((el) => el.a > 0 && el.e < 0.98 && Number.isFinite(el.a) && Number.isFinite(el.ma));
|
|
build();
|
|
ui.setStatus('asteroids', `${elements.length} main-belt · SBDB`, on ? 'ok' : 'off');
|
|
} catch (err) {
|
|
console.warn('[solargod] asteroids', err.message);
|
|
ui.setStatus('asteroids', 'SBDB unavailable', 'err');
|
|
}
|
|
}
|
|
|
|
function build() {
|
|
geo = new THREE.BufferGeometry();
|
|
positions = new Float32Array(elements.length * 3);
|
|
geo.setAttribute('position', new THREE.BufferAttribute(positions, 3));
|
|
points = new THREE.Points(geo, new THREE.PointsMaterial({ color: 0x9a8464, size: 0.16, sizeAttenuation: true, transparent: true, opacity: 0.85, depthWrite: false }));
|
|
points.frustumCulled = false;
|
|
points.visible = on;
|
|
worldGroup.add(points);
|
|
if (on) refresh(ctx.clock.jd, true);
|
|
}
|
|
|
|
function refresh(jd) {
|
|
for (let k = 0; k < elements.length; k++) {
|
|
ephem.smallBodyEcl(elements[k], jd, _e);
|
|
scale.viewFromEcl(_e, _v);
|
|
positions[k * 3] = _v.x; positions[k * 3 + 1] = _v.y; positions[k * 3 + 2] = _v.z;
|
|
}
|
|
geo.attributes.position.needsUpdate = true;
|
|
}
|
|
|
|
return {
|
|
id: 'asteroids',
|
|
onClockTick(simMs, jd) {
|
|
if (!on || !points) return;
|
|
const now = performance.now();
|
|
if (now - lastWall < 100) return; // ≤10 Hz
|
|
lastWall = now;
|
|
refresh(jd);
|
|
},
|
|
};
|
|
}
|