// Orrery Lab · H5 · Replicação: Saturno e Úrano nas linhas dos ingressos trimestrais (Caranguejo, Balança, Capricórnio) // × tipo de violência (UCDP GED 26.1). Análise confirmatória, no browser, com astronomy-engine 2.1.19. // Uso: const linhas = H5.parseGED(textoCSV); const res = await H5.runH5(Astronomy, linhas); // Tudo o que este ficheiro faz está descrito no H5.md e não pode mudar depois do selo. const H5 = (() => { const ORBE_KM = 500, KM_POR_GRAU = 6371 * Math.PI / 180; const ROT_MIN = 15, N_NULO = 10000, SEMENTE = 20261009, ALFA = 0.025; // 2 primários const LIMITE_DADOS = Date.UTC(2026, 0, 1); // GED 26.1 termina a 31-12-2025 const rad = Math.PI / 180, deg = 180 / Math.PI; // Primários: [nome, corpo, tipos de violência] const PRIMARIOS = [ ['P1_Saturno_estatal_e_contra_civis', 'Saturn', [1, 3]], ['P2_Urano_nao_estatal', 'Uranus', [2]], ]; 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] })); } 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 }; } 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 merid = Math.asin(Math.min(1, Math.abs(Math.sin(H)) * Math.cos(f))) * deg; return Math.min(Math.abs(90 - ang), merid) * KM_POR_GRAU; } // Janelas: cada ingresso em Caranguejo (90°), Balança (180°) e Capricórnio (270°) rege até ao ingresso cardinal seguinte. // Só entram janelas que terminam até 31-12-2025 (fim do GED 26.1). function janelas(A) { const ing = []; for (let y = 1989; y <= 2026; y++) for (const [lonSol, mes] of [[0, 2], [90, 5], [180, 8], [270, 11]]) ing.push({ lonSol, t: A.SearchSunLongitude(lonSol, A.MakeTime(new Date(Date.UTC(y, mes, 15))), 12) }); ing.sort((a, b) => a.t.ut - b.t.ut); const out = []; for (let i = 0; i < ing.length - 1; i++) { if (ing[i].lonSol === 0) continue; // a janela de Carneiro já foi usada na H2 const a = ing[i].t.date.getTime(), b = ing[i + 1].t.date.getTime(); if (a < Date.UTC(1989, 0, 1) || b > LIMITE_DADOS) continue; out.push({ lonSol: ing[i].lonSol, t: ing[i].t, a, b }); } return out; } const pausa = () => new Promise(r => setTimeout(r, 0)); async function tabela(A, linhas, corpo, tipos, progresso) { const J = janelas(A), tab = []; let N = 0; for (let k = 0; k < J.length; k++) { const sp = subponto(A, corpo, J[k].t), cont = new Float64Array(360); for (const e of linhas) { if (e.t < J[k].a || e.t >= J[k].b || e.wp > 4 || !isFinite(e.lat) || !tipos.includes(e.tipo)) continue; N++; for (let th = 0; th < 360; th++) if (distLinhas(e.lat, e.lon, sp, th) <= ORBE_KM) cont[th] += 1; } tab.push(cont); if (progresso) progresso(k + 1, J.length); await pausa(); } return { tab, N, janelas: J.length }; } function teste(T, semente) { const rnd = mulberry32(semente), obs = T.tab.reduce((s, c) => s + c[0], 0), nul = new Float64Array(N_NULO); for (let i = 0; i < N_NULO; i++) { let s = 0; for (const c of T.tab) s += c[ROT_MIN + Math.floor(rnd() * (360 - 2 * ROT_MIN + 1))]; nul[i] = s; } let ge = 0, soma = 0; for (const x of nul) { if (x >= obs) ge++; soma += x; } const mu = soma / N_NULO; return { janelas: T.janelas, acontecimentos: T.N, perto: obs, prop: obs / T.N, prop_acaso: mu / T.N, razao: obs / mu, p: (1 + ge) / (1 + N_NULO) }; } async function runH5(A, linhas, progresso) { const res = {}; for (let i = 0; i < PRIMARIOS.length; i++) { const [nome, corpo, tipos] = PRIMARIOS[i]; const r = teste(await tabela(A, linhas, corpo, tipos, progresso), SEMENTE + i); r.sucesso = r.p < ALFA; res[nome] = r; } res.decisao = Object.values(res).some(r => r.sucesso) ? 'H5 CONFIRMADA' : 'H5 NÃO CONFIRMADA'; return res; } // Exploratório (não decide): os mesmos testes com as linhas de Marte e de Plutão, e Saturno/Úrano com todos os tipos. async function exploratorio(A, linhas, progresso) { const casos = [['Marte_todos', 'Mars', [1, 2, 3]], ['Plutao_todos', 'Pluto', [1, 2, 3]], ['Saturno_todos', 'Saturn', [1, 2, 3]], ['Urano_todos', 'Uranus', [1, 2, 3]]]; const res = {}; for (let i = 0; i < casos.length; i++) res[casos[i][0]] = teste(await tabela(A, linhas, casos[i][1], casos[i][2], progresso), SEMENTE + 10 + i); return res; } return { parseGED, runH5, exploratorio, janelas, subponto, distLinhas, tabela, teste, mulberry32, PRIMARIOS }; })(); if (typeof module !== 'undefined') module.exports = H5;