Skip to content

Vaynerov Technologies

Não nos limitamos a desenvolver — conjuramos cada linha de código e cada píxel.

Todos os artigosMergulhos técnicos

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.

Vaynerov TechnologiesO estúdio
Publicado 15 min de leitura
Nesta página
  1. O modelo de tempos de percurso ak135, em 22 nós
  2. Curvar 360 raios através de um planeta
  3. As fases que nunca escrevemos
  4. Um isócrono por fotograma, zero alocações
  5. Uma Terra cortada feita de geometria, não de recorte
  6. Quatro mil sismos numa só malha
  7. 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.

FronteiraProfundidade (km)Velocidade P na travessia (km/s)O que muda
Moho356,5 → 8,04a crosta dá lugar ao manto
4104109,03 → 9,36transição de fase no manto superior
66066010,2 → 10,79base do manto superior
Núcleo-manto2891,513,66 → 8,0ferro sólido para líquido; velocidade S 7,28 → 0
Núcleo interno5153,510,29 → 11,04lí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 leque, duas fases. Os raios S terminam na fronteira núcleo-manto; os raios P curvam-se com força para o núcleo externo, mais lento, e reemergem como PKP para lá dos 130°. Nada no renderizador sabe o que é uma zona de sombra.

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.

Todo o caminho da sonificação. O único passo engenhoso é o último, e é engenhoso sobretudo porque não faz absolutamente nada.

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.