// Orrery Lab · H2 · Linhas do ingresso em Carneiro (10 corpos; grupo de tensão Marte/Saturno/Úrano/Plutão) // × violência organizada (UCDP GED 26.1) // Análise confirmatória. Corre no browser com astronomy-engine 2.1.19 (window.Astronomy). // Uso: const linhas = H2.parseGED(textoCSV); const res = await H2.runH2(Astronomy, linhas); // Tudo o que este ficheiro faz está descrito no H2.md e não pode mudar depois do selo. const H2 = (() => { const CORPOS = ['Sun', 'Moon', 'Mercury', 'Venus', 'Mars', 'Jupiter', 'Saturn', 'Uranus', 'Neptune', 'Pluto']; const ORBE_KM = 500; const KM_POR_GRAU = 6371 * Math.PI / 180; // 111,195 km const ANO0 = 1989, ANO1 = 2024; // 36 ingressos; janelas até 20-03-2025 const ROT_MIN = 15; // rotações do nulo: 15° … 345° const TENSAO = ['Mars', 'Saturn', 'Uranus', 'Pluto']; // grupo afim à tensão (escolhido pelo autor antes do selo) const N_NULO = 10000, SEMENTE = 20261008, ALFA = 0.025; // 2 primários → 0,05 / 2 const rad = Math.PI / 180, deg = 180 / Math.PI; function mulberry32(a) { return function () { a |= 0; a = a + 0x6D2B79F5 | 0; let t = Math.imul(a ^ a >>> 15, 1 | a); t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t; return ((t ^ t >>> 14) >>> 0) / 4294967296; }; } function parseCSV(txt) { const rows = []; let row = [], f = '', q = false; for (let i = 0; i < txt.length; i++) { const c = txt[i]; if (q) { if (c === '"') { if (txt[i + 1] === '"') { f += '"'; i++; } else q = false; } else f += c; } else if (c === '"') q = true; else if (c === ',') { row.push(f); f = ''; } else if (c === '\n' || c === '\r') { if (c === '\r' && txt[i + 1] === '\n') i++; row.push(f); f = ''; if (row.length > 1) rows.push(row); row = []; } else f += c; } if (f || row.length) { row.push(f); if (row.length > 1) rows.push(row); } return rows; } function parseGED(txt) { const R = parseCSV(txt), H = R[0], ix = n => { const k = H.indexOf(n); if (k < 0) throw new Error('coluna ' + n); return k; }; const d = ix('date_start'), la = ix('latitude'), lo = ix('longitude'), wp = ix('where_prec'), b = ix('best'), tv = ix('type_of_violence'); return R.slice(1).map(r => ({ t: Date.parse(r[d].slice(0, 10) + 'T00:00:00Z'), lat: +r[la], lon: +r[lo], wp: +r[wp], best: +r[b], tipo: +r[tv] })); } // ponto sub-planetário: lat = declinação aparente, lon = AR − tempo sideral aparente de Greenwich function subponto(A, corpo, t) { const v = A.RotateVector(A.Rotation_EQJ_EQD(t), A.GeoVector(corpo, t, true)); const eq = A.EquatorFromVector(v); let lon = (eq.ra - A.SiderealTime(t)) * 15; lon = ((lon + 540) % 360) - 180; return { lat: eq.dec, lon }; } // distância (km) às 4 linhas de um corpo: MC/IC (meridiano) e ASC/DSC (círculo a 90° do ponto sub-planetário) function distLinhas(lat, lon, sp, rot) { const f = lat * rad, H = (lon - (sp.lon + rot)) * rad, d0 = sp.lat * rad; const cosd = Math.sin(f) * Math.sin(d0) + Math.cos(f) * Math.cos(d0) * Math.cos(H); const ang = Math.acos(Math.max(-1, Math.min(1, cosd))) * deg; const horiz = Math.abs(90 - ang); const merid = Math.asin(Math.min(1, Math.abs(Math.sin(H)) * Math.cos(f))) * deg; return Math.min(horiz, merid) * KM_POR_GRAU; } function ingressos(A) { const out = []; for (let y = ANO0; y <= ANO1 + 1; y++) out.push(A.SearchSunLongitude(0, A.MakeTime(new Date(Date.UTC(y, 2, 15))), 10)); return out; } const FILTRO = e => e.wp <= 4 && isFinite(e.lat) && isFinite(e.lon); const UM = () => 1; const pausa = () => new Promise(r => setTimeout(r, 0)); // tab[c][k][θ] = soma dos pesos dos acontecimentos da janela k a ≤ orbe das linhas do corpo c rodadas θ° async function tabelas(A, linhas, corpos, orbe, filtro, peso, progresso) { const ing = ingressos(A), tab = corpos.map(() => []), tot = []; for (let k = 0; k < ing.length - 1; k++) { const a = ing[k].date.getTime(), b = ing[k + 1].date.getTime(); const sp = corpos.map(c => subponto(A, c, ing[k])); const ev = linhas.filter(e => e.t >= a && e.t < b && filtro(e)); const cont = corpos.map(() => new Float64Array(360)); let n = 0; for (const e of ev) { const w = peso(e); n += w; for (let c = 0; c < corpos.length; c++) for (let th = 0; th < 360; th++) if (distLinhas(e.lat, e.lon, sp[c], th) <= orbe) cont[c][th] += w; } for (let c = 0; c < corpos.length; c++) tab[c].push(cont[c]); tot.push(n); if (progresso) progresso(k + 1, ing.length - 1); await pausa(); } return { tab, tot }; } // Teste global (máximo |z| sobre os corpos, rotação comum a todos os corpos em cada ano) function testeGlobal(T, corpos) { const C = corpos.length, K = T.tot.length, N = T.tot.reduce((a, b) => a + b, 0); const rnd = mulberry32(SEMENTE), rots = []; for (let i = 0; i < N_NULO; i++) { const r = new Int16Array(K); for (let k = 0; k < K; k++) r[k] = ROT_MIN + Math.floor(rnd() * (360 - 2 * ROT_MIN + 1)); rots.push(r); } const obs = [], nul = []; for (let c = 0; c < C; c++) { obs.push(T.tab[c].reduce((s, v) => s + v[0], 0)); const v = new Float64Array(N_NULO); for (let i = 0; i < N_NULO; i++) { let s = 0; for (let k = 0; k < K; k++) s += T.tab[c][k][rots[i][k]]; v[i] = s; } nul.push(v); } const mu = nul.map(v => v.reduce((a, b) => a + b, 0) / N_NULO); const sd = nul.map((v, c) => Math.sqrt(v.reduce((a, b) => a + (b - mu[c]) ** 2, 0) / (N_NULO - 1))); const zObs = obs.map((o, c) => (o - mu[c]) / sd[c]); const maxNul = new Float64Array(N_NULO); for (let i = 0; i < N_NULO; i++) { let m = 0; for (let c = 0; c < C; c++) m = Math.max(m, Math.abs((nul[c][i] - mu[c]) / sd[c])); maxNul[i] = m; } const maxObs = Math.max(...zObs.map(Math.abs)); const pGlobal = (1 + maxNul.filter(m => m >= maxObs).length) / (1 + N_NULO); // P1: média dos z do grupo de tensão (unilateral: positivo = mais violência perto das linhas) const gi = TENSAO.map(n => corpos.indexOf(n)).filter(i => i >= 0); const zGrupo = gi.reduce((s, c) => s + zObs[c], 0) / gi.length; let geG = 0; for (let i = 0; i < N_NULO; i++) { let s = 0; for (const c of gi) s += (nul[c][i] - mu[c]) / sd[c]; if (s / gi.length >= zGrupo) geG++; } const pGrupo = (1 + geG) / (1 + N_NULO); const porCorpo = corpos.map((nome, c) => ({ corpo: nome, perto: obs[c], prop: obs[c] / N, prop_acaso: mu[c] / N, razao: obs[c] / mu[c], z: zObs[c], sentido: zObs[c] >= 0 ? 'mais perto (tensão)' : 'menos perto (alívio)', p_ajustado: (1 + maxNul.filter(m => m >= Math.abs(zObs[c])).length) / (1 + N_NULO), p_isolado: (1 + nul[c].filter(x => Math.abs(x - mu[c]) >= Math.abs(obs[c] - mu[c])).length) / (1 + N_NULO), })); const P1 = pGrupo < ALFA, P2 = pGlobal < ALFA; return { acontecimentos: N, P1_grupo_tensao: { corpos: TENSAO, z_medio: zGrupo, p: pGrupo, sucesso: P1 }, P2_global_10_corpos: { max_abs_z: maxObs, p: pGlobal, sucesso: P2 }, decisao: P1 || P2 ? 'H2 CONFIRMADA' : 'H2 NÃO CONFIRMADA', porCorpo }; } async function runH2(A, linhas, progresso) { return testeGlobal(await tabelas(A, linhas, CORPOS, ORBE_KM, FILTRO, UM, progresso), CORPOS); } // Exploratório (não decide): por tipo de violência, peso por mortes, orbes de 300 e 800 km, só localização exata async function exploratorio(A, linhas, progresso) { const tipo = k => e => FILTRO(e) && e.tipo === k; return { tipo1_estatal: testeGlobal(await tabelas(A, linhas, CORPOS, ORBE_KM, tipo(1), UM, progresso), CORPOS), tipo2_nao_estatal: testeGlobal(await tabelas(A, linhas, CORPOS, ORBE_KM, tipo(2), UM, progresso), CORPOS), tipo3_unilateral: testeGlobal(await tabelas(A, linhas, CORPOS, ORBE_KM, tipo(3), UM, progresso), CORPOS), peso_log_mortes: testeGlobal(await tabelas(A, linhas, CORPOS, ORBE_KM, FILTRO, e => Math.log10(1 + Math.max(0, e.best)), progresso), CORPOS), orbe_300km: testeGlobal(await tabelas(A, linhas, CORPOS, 300, FILTRO, UM, progresso), CORPOS), orbe_800km: testeGlobal(await tabelas(A, linhas, CORPOS, 800, FILTRO, UM, progresso), CORPOS), so_local_exato: testeGlobal(await tabelas(A, linhas, CORPOS, ORBE_KM, e => e.wp <= 2 && isFinite(e.lat) && isFinite(e.lon), UM, progresso), CORPOS), }; } return { CORPOS, TENSAO, parseGED, runH2, exploratorio, subponto, distLinhas, ingressos, tabelas, testeGlobal, mulberry32 }; })(); if (typeof module !== 'undefined') module.exports = H2;