// 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); }, }; }