Ouvir os sismos: traçar raios por um planeta inteiro
O núcleo externo líquido mata as ondas de corte. O renderizador do Tremor não tem uma única linha de código que saiba disso — a sombra aparece porque deixam de chegar raios, e os buracos que eles deixam no buffer são a física.
Nesta página
- O modelo de tempos de percurso ak135, em 22 nós
- Curvar 360 raios através de um planeta
- As fases que nunca escrevemos
- Um isócrono por fotograma, zero alocações
- Uma Terra cortada feita de geometria, não de recorte
- Quatro mil sismos numa só malha
- Sonificação de sismos, a partir de um descodificador miniSEED escrito à mão
Uma onda S que chega à fronteira núcleo-manto está acabada. O núcleo externo é líquido, os líquidos não têm resistência ao corte e uma onda de corte precisa de alguma coisa que corte — por isso, 2891,5 km mais abaixo, a fase termina. O que isso deixa do outro lado de um grande sismo é uma zona de sombra sísmica do tamanho de um continente, e é uma das observações que nos disse que o meio do planeta está fundido, décadas antes de alguém o conseguir modelar.
Essa sombra está no ecrã do Tremor sempre que se seleciona um evento grande, e a forma como lá vai parar é o desenho todo em miniatura. Nenhum código desenha uma sombra. Não existe em lado nenhum do renderizador um limiar sobre a distância angular. Traçamos raios através de um modelo de velocidades, perguntamos a cada um onde está no instante de reprodução atual e escrevemos as respostas num buffer pré-alocado.
Os raios que morreram no núcleo escrevem NaN. O renderizador de linhas interrompe a sequência num NaN e começa outra. Os intervalos são as zonas de sombra.
- 360
- raios traçados por fase, por seleção
- 22
- nós no modelo de velocidades
- 2,700 s
- tempo de percurso máximo integrado
- ×150
- velocidade de reprodução do sismograma
O orçamento de propagação do Tremor. Uma seleção volta a traçar as duas fases de raiz em cerca de 10–20 ms cada.
O modelo de tempos de percurso ak135, em 22 nós
Tudo o que vem a jusante assenta numa tabela. O ak135 é um modelo unidimensional de referência da Terra: para cada profundidade, uma velocidade de compressão e uma de corte, médias para todo o planeta — aproximado o suficiente para se descrever num parágrafo, bom o suficiente para os sismólogos ainda localizarem sismos reais com ele. A nossa versão são 22 linhas de [depthKm, vp, vs], interpoladas linearmente por troços em raio e não em profundidade, a cerca de 0,1 km/s do modelo publicado. O código-fonte chama-lhe grau visual e é literal: uma ferramenta de desenho, não uma tabela de tempos de percurso ak135 com que se cronometre a leitura de uma fase.
O truque da tabela está na forma como as descontinuidades são codificadas: uma profundidade que aparece duas vezes é uma descontinuidade. O construtor percorre a lista de nós, parte-a em segmentos contínuos sempre que uma profundidade se repete e regista o salto. Mais nada no motor conhece as fronteiras pelo nome — o Moho, a de 410, a de 660, a fronteira núcleo-manto e a fronteira do núcleo interno são uma só estrutura de dados, e acrescentar outra seria acrescentar duas linhas.
| Fronteira | Profundidade (km) | Velocidade P na travessia (km/s) | O que muda |
|---|---|---|---|
| Moho | 35 | 6,5 → 8,04 | a crosta dá lugar ao manto |
| 410 | 410 | 9,03 → 9,36 | transição de fase no manto superior |
| 660 | 660 | 10,2 → 10,79 | base do manto superior |
| Núcleo-manto | 2891,5 | 13,66 → 8,0 | ferro sólido para líquido; velocidade S 7,28 → 0 |
| Núcleo interno | 5153,5 | 10,29 → 11,04 | líquido para sólido; velocidade S 0 → 3,5 |
As cinco descontinuidades de primeira ordem no modelo do Tremor, cada uma escrita como uma profundidade repetida na tabela de nós.
O núcleo externo líquido não é um caso especial no motor. É um zero numa tabela.
Esse zero faz um trabalho enorme. Quando um raio S chega à fronteira núcleo-manto, o passo de refração procura a velocidade de corte do outro lado, não encontra nada para onde refratar e termina o raio — o traçador nunca chega a saber que bateu num núcleo. Abaixo da fronteira do núcleo interno a velocidade de corte regressa, porque o núcleo interno é sólido; o traçador propagaria por lá alegremente se alguma onda de corte lá pudesse chegar. Nenhuma chega, e uma asserção confirma que nunca chegou.
Curvar 360 raios através de um planeta
Uma Terra esfericamente simétrica oferece-lhe uma dimensão de graça: um raio que sai da fonte nunca abandona o plano que contém a fonte e o centro do planeta. Por isso o traçador é bidimensional, faz marchar cada raio dentro desse plano de círculo máximo e guarda-o como um raio, uma distância angular e um tempo acumulado — três floats por amostra guardada, num Float32Array plano, com uma amostra guardada a cada seis passos.
A integração é teoria clássica de raios: com índice de refração n = 1/v, a equação do raio d/ds(n·d̂) = ∇n diz que um raio se curva na direção do índice maior — na direção do material mais lento. Num meio radialmente simétrico o gradiente aponta ao longo do raio-vetor, por isso a curvatura toda se resume a uma dúzia de linhas de aritmética escalar, corridas uma vez por cada passo de oito quilómetros.
if (r > 0.5) {
const dndr = -velocityGradientAt(r, phase) / (v * v);
const k = (dndr * DS_KM) / r;
const n = 1 / v;
const wx = n * dx + k * x;
const wz = n * dz + k * z;
const wl = Math.hypot(wx, wz);
if (wl > 0) {
dx = wx / wl;
dz = wz / wl;
}
}A curvatura, tal e qual como está em tremor-engine.ts: a nova direção é n·(direção antiga) + k·(vetor radial), renormalizada, com a guarda a manter a divisão honesta perto do centro do planeta. DS_KM é 8, MAX_STEPS é 6000, e a integração para aos 2700 s de tempo de percurso — o suficiente para a PKIKP atravessar o planeta e voltar a sair.
Avançar em linha reta chega, até um raio encontrar uma descontinuidade, onde a aproximação se vê logo: atravesse a fronteira núcleo-manto uns quilómetros tarde de mais e o ângulo de refração fica errado e o raio vai parar onde não tem nada que fazer. Por isso as travessias são resolvidas exatamente. A função stepToRadius() resolve a quadrática que coloca o raio precisamente sobre a fronteira, o passo é truncado aí e a lei de Snell é aplicada na base tangencial/normal local: sin θ₂ = sin θ₁ · v₂/v₁. Se isso exceder um, não há raio transmitido e a direção espelha-se — reflexão interna total, que é como as fases refletidas no núcleo aparecem sem terem sido pedidas. Caso contrário, a direção refratada é reconstruída e afastada 0,02 km, para que o passo seguinte não volte a detetar a mesma travessia.
Trezentos e sessenta raios saem em leque do hipocentro entre 0,25° e 179,75° de ângulo de saída, com a profundidade da fonte limitada a 1–700 km. Um leque completo custa 10–20 ms por fase — na thread principal, sem Web Worker, o que só é defensável porque acontece uma vez por seleção e não uma vez por fotograma.
As fases que nunca escrevemos
A sismologia tem um alfabeto de fases rico — P, S, PKP, PKIKP, PcP, ScS — e a forma tentadora de o desenhar é tratar cada uma como caso especial: desenhar este arco para uma fase de núcleo, esconder a P direta a partir daquele ângulo. O Tremor não tem nada disso. O traçador conhece velocidades, gradientes, fronteiras e a lei de Snell; todas as fases da imagem decorrem dessas quatro coisas.
O leque S para no núcleo porque a velocidade de corte aí é zero. O leque P faz algo mais interessante: a velocidade de compressão cai de 13,66 para 8,0 km/s ao entrar no núcleo externo, por isso os raios refratam abruptamente para baixo, as chegadas diretas à superfície esgotam-se para lá dos 104° e a energia que atravessa o núcleo reemerge como PKP muito mais à frente. Ambas as bordas da sombra da P saem da refração e não de um if — que é exatamente por isso que precisam de ser verificadas.
Por isso o motor sai com uma autoverificação executável, corrida em desenvolvimento, no mesmo espírito da asserção da ISS no Orrery: um motor de física que ninguém consegue avaliar à vista precisa de um teste que consiga.
- O tempo de percurso cresce de forma monótona ao longo de cada raio. (Um raio que anda para trás no tempo significa que o integrador se partiu.)
- A P direta a 60° chega perto dos 600 s, dentro de ±60 s; a 90°, perto dos 780 s, dentro de ±80 s.
- A S direta a 60° chega perto dos 1090 s, dentro de ±110 s.
- A última chegada de P direta à superfície cai dentro de 90°–118° — largo o suficiente para tolerar um modelo de grau visual, estreito o suficiente para apanhar uma refração partida.
- Existem chegadas PKP para lá dos 130°.
- Nunca se encontra um raio S mais de 5 km abaixo da fronteira núcleo-manto.
Um isócrono por fotograma, zero alocações
Um raio traçado é um percurso no espaço e no tempo; uma frente de onda é uma fatia através dele. A função sampleIsochron() faz uma pesquisa binária nas amostras de cada raio à procura do intervalo que contém o instante de reprodução atual e interpola dele uma posição — um par raio/distância por raio, para um vetor pré-alocado. Os raios que nunca chegaram a este instante, ou que morreram no núcleo, escrevem (NaN, NaN).
As frentes são desenhadas como LineSegments e não como uma polilinha, e essa escolha é estrutural. Uma polilinha ligaria a última amostra viva antes de um intervalo à primeira a seguir a ele, soldando uma corda a atravessar a sombra. Os segmentos não: um NaN termina uma sequência, o par válido seguinte começa outra, e a sombra esculpe-se sozinha na geometria. A fita luminosa é construída a partir de quadriláteros independentes pela mesma razão — uma faixa partilhada voltaria a soldar o intervalo que as linhas acabaram de deixar.
Nada aqui é reconstruído por fotograma: 720 posições de amostra, 2880 vértices de linha e 1440 quadriláteros de fita são alocados uma vez, e é o setDrawRange que faz o trabalho. Cada frente é desenhada duas vezes, espelhada em ±delta, porque uma frente de onda numa Terra esfericamente simétrica é uma superfície de revolução em torno do eixo da fonte e o corte expõe dois semiplanos dela. As frentes duram 120 s de tempo simulado e esbatem-se à medida que envelhecem — a P do quase branco à brasa, a S do verde-menta claro ao azul-petróleo — para que uma reprodução movimentada se leia como uma sequência e não como uma mancha.
Uma Terra cortada feita de geometria, não de recorte
Para ver uma frente de onda atravessar o interior é preciso abrir o planeta, e a forma óbvia — planos de recorte — é a ferramenta errada para as camadas: trabalho de fragmento em cada píxel de cada camada e, no fim disso tudo, faces ocas. Em vez disso, as camadas são construídas incompletas. Uma SphereGeometry com phiStart = π e phiLength = 1.5π é uma camada de 270° com um quadrante em falta, apontada à câmara por omissão. O buraco está na malha, por isso não custa nada.
Há uma camada que não pode usar esse truque. As linhas de costa e a quadrícula geográfica têm de rodar — é para isso que existem —, por isso são a única coisa da cena genuinamente recortada na GPU, por dois planos com clipIntersection ativado. Os dados de litoral são descodificados a partir do módulo que empacotámos para o Orrery e reinterpolados esfericamente ao longo de círculos máximos a cerca de 2° por subdivisão, para que as cordas longas nunca afundem abaixo da superfície e desapareçam dentro da esfera.
A cena corre então sobre dois referenciais, e esta é a peça de que estamos discretamente mais orgulhosos. O referencial do corte nunca se mexe: cunha, estratos, fios de fronteira e eixo polar são estáticos, por isso os planos de recorte em espaço-mundo são constantes e a arte das faces do corte é cozida numa malha indexada e numa chamada de desenho. O referencial geográfico — linhas de costa, quadrícula, o catálogo inteiro — é interpolado esfericamente para que o hipocentro selecionado assente no eixo polar +Y, a aresta partilhada pelas duas faces expostas. Qualquer plano que passe pelo eixo da fonte é uma secção de círculo máximo válida de uma Terra esfericamente simétrica, e aqui dois deles calham ser paredes que se veem. O corte transversal é honesto qualquer que seja o evento escolhido — e instantâneo, sem animação nenhuma, para quem pediu movimento reduzido.
Quatro mil sismos numa só malha
O catálogo chega de dois feeds de resumo do USGS — o último dia com qualquer magnitude, mais M4,5 para cima no último mês — através de um proxy que reduz cada evento a um tuplo posicional e gere a sua própria cache. A revalidação ao nível da rota guardaria em cache um 503 com o mesmo à-vontade com que guarda dados, o que uma vez, no Orrery, congelou um estado offline durante seis horas. Aqui o TTL de 90 segundos está ancorado ao carimbo temporal mais antigo dos dois feeds, por isso um feed falhado continua a ser tentado enquanto o saudável continua a servir. Desatualizado é melhor do que vazio.
No ecrã, o catálogo inteiro — até 4000 eventos — é uma única InstancedMesh de octaedros. Cada instância fica ao raio (6371 − depth) / 1000 unidades de cena: à sua profundidade hipocentral real em vez de espetada à superfície, para que uma zona de subducção se leia como uma placa a mergulhar no manto. A escala é logarítmica na magnitude, 0.012 + 0.02 · 1.9^(mag − 4), limitada nos dois extremos — com teto, para que um M8 continue a ser uma conta e não uma lua, e com chão, para que os microssismos continuem a existir. A cor é a idade: laranja quente a brasa nas primeiras 24 horas, depois brasa a ardósia ao longo de uma cauda de 30 dias. A história da última semana está visível antes de se clicar em seja o que for.
Sonificação de sismos, a partir de um descodificador miniSEED escrito à mão
Os sismogramas são públicos. Qualquer pessoa pode obter movimento do solo em bruto dos serviços dataselect da FDSN, e as estações IU da Global Seismographic Network gravam em contínuo há décadas. Por isso a última coisa que o Tremor faz com um evento é propor tocá-lo — sonificação de sismos a sério, não um sintetizador a imitar um.
O navegador não consegue fazer isto sozinho, por duas razões independentes. Os dados chegam em miniSEED comprimido em Steim, um formato binário de codificação diferencial de que nenhum navegador ouviu falar, e o serviço não envia cabeçalhos CORS, por isso uma página não conseguiria sequer obter os bytes, mesmo que os soubesse ler. As duas metades vivem no nosso servidor: um proxy obtém, um descodificador escrito à mão desempacota, e um buffer Int16 normalizado chega ao cliente.
Escolher uma estação é um pequeno problema de ordenação com uma restrição física lá dentro. O Tremor conhece 31 estações IU de banda larga e quer quarenta minutos de registo na componente vertical, mas a mais próxima não é a melhor: a menos de 2° o instrumento satura num evento grande, e para lá dos 90° o núcleo começa a ensombrar a P. Por isso a banda telessísmica lidera a ordenação, a distância desempata, três candidatas são tentadas por ordem, e um registo com menos de 45 minutos é recusado à partida — os arquivos precisam de tempo, e o cliente antecipa essa recusa em vez de o deixar descobri-la.
O descodificador miniSEED é mais ou menos o que há de mais baixo nível em código para a web: um DataView, leituras big-endian e um cabeçalho blockette-1000 a dizer que codificação está ali à frente. O Steim empacota amostras em tramas de 64 bytes com dezasseis palavras de 32 bits, sendo a primeira um mapa de nibbles — dezasseis códigos de dois bits que descrevem como as outras quinze estão empacotadas. O Steim-1 oferece uma, duas ou quatro diferenças por palavra; o Steim-2 acrescenta subcódigos que metem cinco diferenças de 6 bits, seis de 5 bits ou sete de 4 bits numa palavra, mais um espaço de 30 bits para valores atípicos. As palavras um e dois da trama zero não são diferenças, são as constantes de integração X0 e Xn, o que torna o fluxo autoverificável. As duas codificações estão implementadas; quando o alinhamento temporal dos registos falha por mais de 1,5 amostras, contamos a falha e concatenamos na mesma, porque em sonificação a continuidade vale mais do que a exatidão.
O que nos traz à parte que não tem código nenhum. A energia sísmica vive à volta de 0,05–10 Hz; a audição começa perto dos 20 Hz. A solução de manual é um deslocador de tom, e não escrevemos nada disso, porque a Web Audio API permite declarar a taxa de amostragem de um buffer. Quarenta amostras por segundo, declaradas como 40 × 150 = 6000 Hz, tocam 150 vezes mais depressa e 150 vezes mais agudo — dentro da janela legal de taxas de buffer, de 3000 a 768 000 Hz — colocando a banda sísmica em 7,5–1500 Hz. Quarenta minutos passam a dezasseis segundos. Não é uma transformação dos dados; são os dados, lidos depressa.
A gama dinâmica é o problema mais difícil. A chegada de uma P telessísmica pode ser cem vezes a coda que vem atrás, e reproduzida linearmente o visitante ouve um estalido e depois silêncio. Por isso o cliente aplica compansão com um expoente perto de 0,6 — no navegador, não no servidor, porque o trabalho do servidor são contagens honestas e a modelação pertence ao sítio onde o visitante a pode ouvir a ser aplicada. Antes do transporte, ao registo é-lhe removida a média (a SNZO, por exemplo, fica cerca de 1500 contagens acima do zero) e é normalizado pelo pico. O minigráfico desenha o pico por intervalo em vez do RMS, pela mesma razão: a chegada é um pico, e fazer médias é a única operação que a esconderia.
O reprodutor em si é deliberadamente pequeno: um GainNode, rampas de 30 ms agendadas com um passo de antecedência, um contexto de áudio criado dentro do manipulador do clique para que a política de reprodução automática não tenha nada a opor, e reprodução que para quando o separador desaparece. O silenciar não é do Tremor — lê o silenciar global do sintetizador, partilhado com todos os jogos do Lab. Silencie um jogo e o planeta também se cala.
Um selo ao canto do ecrã diz tempos de percurso de grau visual, pela mesma razão que o do Orrery diz propagação de grau visual. Um modelo 1-D sem correções de estação e sem estrutura tridimensional põe a frente de onda onde o olho a espera, não onde um instrumento a registaria. Fiel ao olho, não ao instrumento; nunca o use para outra coisa que não seja maravilhar-se. Dizê-lo com esta clareza não custa nada e compra ao resto da imagem a sua credibilidade.
As lacunas assumidas: o traçado de raios corre na thread principal, e a página ainda não tem cobertura automatizada de ponta a ponta — só as asserções de desenvolvimento, que verificam a física e não os botões. Ambas estão na lista.