// Orrery Lab · H6 · Linhas de Marte, Saturno e Úrano nas Luas Novas (todas, e só quando afligidos por aspeto tenso) // × violência organizada (UCDP GED 26.1) // Análise confirmatória, no browser, com astronomy-engine 2.1.19. // Uso: const linhas = H6.parseGED(textoCSV); const res = await H6.runH6(Astronomy, linhas); // Tudo o que este ficheiro faz está descrito no H6.md e não pode mudar depois do selo. const H6 = (() => { const GRUPO = ['Mars', 'Saturn', 'Uranus']; // fixado após H2 e H5, antes de ver estas linhas const ORBE_KM = 500, KM_POR_GRAU = 6371 * Math.PI / 180; const ROT_MIN = 15, N_NULO = 10000, SEMENTE = 20261011, ALFA = 0.025; // 2 primários → 0,05 / 2 const ORBE_ASPETO = 8; // graus, conjunção / quadratura / oposição const ALVOS_ASPETO = ['Mars', 'Saturn', 'Uranus', 'Pluto']; // além do ponto da Lua Nova (Sol = Lua) const INICIO = Date.UTC(1989, 0, 1), FIM = Date.UTC(2026, 0, 1); // cobertura do GED 26.1 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] })); } 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; } // longitude eclíptica geocêntrica aparente function elon(A, corpo, t) { return A.Ecliptic(A.GeoVector(corpo, t, true)).elon; } // um corpo está afligido se faz conjunção, quadratura ou oposição (orbe ≤ 8°) ao ponto da lunação (Sol) // ou a outro corpo de ALVOS_ASPETO function afligidos(A, corpos, t) { const L = {}; for (const c of new Set([...corpos, ...ALVOS_ASPETO, 'Sun'])) L[c] = elon(A, c, t); const tenso = (x, y) => { let d = Math.abs(x - y) % 360; if (d > 180) d = 360 - d; return [0, 90, 180].some(a => Math.abs(d - a) <= ORBE_ASPETO); }; return corpos.filter(c => tenso(L[c], L.Sun) || ALVOS_ASPETO.some(o => o !== c && tenso(L[c], L[o]))); } // Janelas: de cada Lua Nova até à seguinte; só as que cabem inteiras em 1989-01-01 … 2025-12-31. function janelas(A, fase) { const ln = []; let t = A.MakeTime(new Date(Date.UTC(1988, 11, 1))); while (true) { const m = A.SearchMoonPhase(fase, t, 40); if (m.date.getTime() > FIM + 40 * 864e5) break; ln.push(m); t = m.AddDays(1); } const out = []; for (let i = 0; i < ln.length - 1; i++) { const a = ln[i].date.getTime(), b = ln[i + 1].date.getTime(); if (a >= INICIO && b <= FIM) out.push({ t: ln[i], a, b }); } return out; } const pausa = () => new Promise(r => setTimeout(r, 0)); // contagem por rotação: acontecimentos a ≤ orbe de alguma linha de algum corpo do conjunto async function tabela(A, linhas, corpos, filtro, fase, progresso, soAfligidos) { const J = janelas(A, fase), tab = []; let N = 0, nAflig = 0; const ev = linhas.filter(e => e.t >= INICIO && e.t < FIM && e.wp <= 4 && isFinite(e.lat) && filtro(e)).sort((x, y) => x.t - y.t); let i0 = 0; for (let k = 0; k < J.length; k++) { const usar = soAfligidos ? afligidos(A, corpos, J[k].t) : corpos; if (usar.length) nAflig++; const sp = usar.map(c => subponto(A, c, J[k].t)), cont = new Float64Array(360); while (i0 < ev.length && ev[i0].t < J[k].a) i0++; for (let i = i0; i < ev.length && ev[i].t < J[k].b; i++) { const e = ev[i]; N++; for (let th = 0; th < 360; th++) { for (const s of sp) if (distLinhas(e.lat, e.lon, s, th) <= ORBE_KM) { cont[th] += 1; break; } } } tab.push(cont); if (progresso && k % 20 === 0) progresso(k + 1, J.length); if (k % 10 === 0) await pausa(); } return { tab, N, janelas: J.length, janelas_com_linhas: nAflig }; } 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, janelas_com_linhas: T.janelas_com_linhas, acontecimentos: T.N, perto: obs, prop: obs / T.N, prop_acaso: mu / T.N, razao: obs / mu, p: (1 + ge) / (1 + N_NULO) }; } const TODOS = () => true; async function runH6(A, linhas, progresso) { const P1 = teste(await tabela(A, linhas, GRUPO, TODOS, 0, progresso, false), SEMENTE); const P2 = teste(await tabela(A, linhas, GRUPO, TODOS, 0, progresso, true), SEMENTE + 100); P1.sucesso = P1.p < ALFA; P2.sucesso = P2.p < ALFA; return { grupo: GRUPO, P1_todas_as_lunacoes: P1, P2_so_planetas_afligidos: P2, decisao: P1.sucesso || P2.sucesso ? 'H6 CONFIRMADA' : 'H6 NÃO CONFIRMADA' }; } // Exploratório (não decide): cada planeta sozinho, controlos (Plutão, Júpiter, Vénus), tipos de violência, Luas Cheias. async function exploratorio(A, linhas, progresso) { const casos = [ ['Marte', ['Mars'], TODOS, 0], ['Saturno', ['Saturn'], TODOS, 0], ['Urano', ['Uranus'], TODOS, 0], ['Plutao', ['Pluto'], TODOS, 0], ['Jupiter', ['Jupiter'], TODOS, 0], ['Venus', ['Venus'], TODOS, 0], ['Grupo_estatal', GRUPO, e => e.tipo === 1, 0], ['Grupo_nao_estatal', GRUPO, e => e.tipo === 2, 0], ['Grupo_contra_civis', GRUPO, e => e.tipo === 3, 0], ['Grupo_Luas_Cheias', GRUPO, TODOS, 180], ]; 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], casos[i][3], progresso, false), SEMENTE + 1 + i); res.Grupo_NAO_afligidos = 'ver P1 − P2 (contagens publicadas)'; return res; } return { GRUPO, afligidos, parseGED, runH6, exploratorio, janelas, subponto, distLinhas, tabela, teste, mulberry32 }; })(); if (typeof module !== 'undefined') module.exports = H6;