O Navistron possui um sistema de telemetria pública que captura dados de todas as sessões de jogo — registradas e anônimas — e os apresenta em dashboards acessíveis a qualquer visitante. O diferencial: nenhuma informação pessoal é coletada. Sem IP, sem cookies, sem fingerprinting, sem contas. Neste artigo, vamos explorar cada camada do sistema: coleta, armazenamento, processamento e visualização.

Os Três Caminhos dos Dados: Registrado, Unregistered e Unknown

Quando uma partida termina no Navistron, a função endGame() cria um objeto pendingSession com 7 campos de gameplay: score, tier, tierName, boosts, spread, time e difficulty. Esse objeto fica em espera até que o jogador tome uma ação. Dependendo do que acontece a seguir, os dados seguem um de três caminhos:

  • Registrado — O jogador digita um nome e clica "Save & Ranking". Os dados vão para a collection scores com o nome do piloto. Uma flag sessionResolved = true marca a sessão como resolvida. Nenhum registro anônimo é criado.
  • Unregistered — O jogador clica "Skip". Os dados são salvos na collection anonymous_sessions com type: "Unregistered" via fetch padrão. O jogador deliberadamente optou por não se registrar.
  • Unknown — O jogador fecha a aba ou inicia um novo jogo sem resolver a sessão pendente. Os dados são salvos com type: "Unknown". Se o evento é beforeunload, usa navigator.sendBeacon() para garantir envio mesmo durante o fechamento da página.

Dessa forma, nenhuma partida é perdida: toda sessão gera dados de telemetria, seja no ranking público ou nas estatísticas anônimas.

sendBeacon: Capturando Dados ao Fechar a Aba

O navigator.sendBeacon() é uma API do navegador projetada especificamente para enviar dados de telemetria durante o fechamento de uma página. Diferente do fetch tradicional, o sendBeacon é "fire-and-forget" — o navegador garante o envio mesmo que a aba esteja sendo destruída.

O Navistron usa o sendBeacon no handler de beforeunload: quando o jogador fecha a aba com uma sessão pendente, os dados são serializados em um Blob com tipo application/json e enviados para /api/anonymous-sessions. Isso captura jogadores que simplesmente abandonam o jogo sem clicar em nenhum botão — uma parcela significativa que seria invisível sem essa técnica.

Para as ações de "Skip" e "novo jogo sem resolver", o salvamento usa fetch assíncrono normal, já que a página continua aberta e pode aguardar a resposta.

As 4 Collections do MongoDB

O banco de dados navistron no MongoDB utiliza 4 collections para telemetria:

  • scores — Partidas registradas com nome do piloto. 9 campos: name, score, tier, tierName, boosts, spread, time, difficulty, playedAt. Alimenta o ranking e perfis de jogador.
  • anonymous_sessions — Partidas não registradas. 9 campos: type (Unregistered/Unknown), score, tier, tierName, boosts, spread, time, difficulty, playedAt. O campo type é validado no servidor — valores fora de "Unregistered" e "Unknown" são rejeitados e convertidos para "Unknown".
  • sponsors — Dados de patrocinadores: name, link, linkText, value, totalClicks, createdAt. O campo totalClicks é incrementado atomicamente a cada clique.
  • sponsor_clicks — Log de eventos de clique em links de patrocinadores: sponsorId, clickedAt, userAgent e referer. Esta é a única collection que armazena User-Agent, e apenas para análise de cliques em patrocinadores.

Aggregation Pipelines: 9 Métricas em Paralelo

O coração do sistema de telemetria é a função getAggregatedStats(period), que executa 9 aggregations em paralelo combinando dados de scores e anonymous_sessions. Os períodos suportados são: all (todo o histórico), daily (últimas 24 horas), monthly (últimos 30 dias) e yearly (últimos 365 dias).

As métricas computadas incluem:

  • Totais — Total de partidas registradas (countDocuments em scores) + anônimas (em anonymous_sessions)
  • Tempo de jogo — Soma total ($sum: '$time') e média de tempo de sobrevivência, combinando ambas as collections
  • Scores — Score médio ($avg: '$score') e maior score de todos os tempos ($max), comparando registrados e anônimos
  • Pilotos únicos — Contagem de nomes distintos na collection scores ($group: { _id: '$name' })
  • Jogos por dia — Agrupamento por data com zero-fill para os últimos 30 dias
  • Distribuição de tiers — Agrupamento por tierName para análise de progressão
  • Top jogadores — Top 10 por melhor score, com contagem de partidas e tempo total
  • Jogos recentes — Últimas 10 partidas com todos os campos

Ao usar Promise.all() para executar as 9 queries em paralelo, o tempo de resposta é determinado pela query mais lenta — não pela soma de todas.

Dashboard: 8 Gráficos com Chart.js

O Dashboard (/dashboard) é uma página client-side que busca dados de duas APIs em paralelo: GET /api/stats (scores registrados) e GET /api/anonymous-sessions (sessões anônimas). Os dados são visualizados em 8 gráficos interativos:

  • Jogos por Dia (30 dias) — Gráfico de linha mostrando tendência de atividade diária
  • Horários de Pico (UTC) — Gráfico de barras com distribuição horária das partidas (24 horas)
  • Distribuição de Tiers — Gráfico doughnut mostrando em qual tier os jogadores mais morrem
  • Distribuição de Scores — Gráfico de barras com faixas de pontuação (0–50, 50–100, 100–200, ..., 50K+)
  • Sessões Anônimas/Dia — Gráfico de linha empilhado separando Unregistered vs Unknown
  • Tipo de Sessão — Doughnut comparando proporção Unregistered vs Unknown
  • Tiers Anônimos — Doughnut de distribuição de tiers apenas em sessões anônimas
  • Scores Anônimos — Barras com faixas de pontuação das sessões anônimas

Além dos gráficos, cards de métricas exibem: Total de Jogos, Tempo Total de Jogo, Pilotos Únicos, Score Médio, Tempo Médio, Maior Score, Jogadores Ativos (7 dias), e métricas equivalentes para sessões anônimas.

Stats Pages: Telemetria Pública com SSR e ISR

As páginas de estatísticas públicas são renderizadas no servidor com Incremental Static Regeneration, cada uma com intervalo de revalidação diferente:

  • /stats (todo o histórico) — revalida a cada 5 minutos (300s)
  • /stats/daily (últimas 24h) — revalida a cada 2 minutos (120s) — dados mais voláteis
  • /stats/monthly (últimos 30 dias) — revalida a cada 5 minutos (300s)
  • /stats/yearly (último ano) — revalida a cada 10 minutos (600s) — dados mais estáveis

Todas as páginas de stats exibem: Total de Jogos (registrados + anônimos), Pilotos Únicos, Tempo Total de Jogo, Score Médio, Tempo Médio, Maior Score, tabela de distribuição de tiers e tabela de jogos por dia (quando aplicável). Cada página inclui JSON-LD com schema schema.org/Dataset para indexação como dados estruturados.

Tracking de Patrocinadores: Cliques e Relatórios

O sistema de telemetria também acompanha a interação com patrocinadores. Quando um jogador clica em um link de patrocinador (no leaderboard in-game), duas operações são executadas no servidor:

  1. O campo totalClicks do documento do patrocinador é incrementado atomicamente ($inc: { totalClicks: 1 })
  2. Um evento de clique é registrado em sponsor_clicks com: sponsorId, clickedAt, userAgent e referer

O painel admin (protegido por senha) apresenta relatórios com: receita total, total de cliques, cliques dos últimos 7 e 30 dias, tabela de cliques por patrocinador, e gráfico de cliques por dia.

Stats API: Métricas dos Registrados

A API GET /api/stats fornece 10 aggregations em paralelo exclusivamente sobre a collection scores: total de jogos, totais/médias de tempo e score, pilotos únicos, jogadores ativos na semana, jogos por dia (30 dias com zero-fill), horários de pico (24h), distribuição de tiers, top 10 jogadores (com best score, partidas e tempo total), últimos 10 jogos, e distribuição de scores por faixas.

A API GET /api/anonymous-sessions fornece 7 aggregations em paralelo sobre anonymous_sessions: total de sessões, contagem por tipo (Unregistered/Unknown), totais/médias de tempo e score, sessões por dia (30 dias empilhadas por tipo), distribuição de tiers, últimas 15 sessões, e distribuição de scores.

Privacidade: Zero Dados Pessoais

A telemetria do Navistron foi projetada com privacidade por padrão:

  • Sem IP — Nenhuma API armazena endereço IP do jogador
  • Sem cookies — Nenhum cookie de rastreamento é emitido pelo jogo
  • Sem fingerprinting — Nenhum identificador de dispositivo, canvas fingerprint ou localStorage de tracking
  • Sem contas/login — O nome do piloto é voluntário e digitado do zero a cada partida
  • User-Agent apenas em cliques de patrocinador — A única informação semi-identificável é o User-Agent, armazenado exclusivamente na collection sponsor_clicks para análise de dispositivos em cliques de links externos
  • Timestamps server-side — Todos os playedAt e clickedAt são gerados no servidor com new Date(), nunca confiando em dados do cliente
  • Dados públicos — Todas as estatísticas são acessíveis publicamente nas páginas /stats

FAQ — Perguntas Frequentes sobre a Telemetria do Navistron

Meus dados pessoais são coletados?

Não. A telemetria coleta exclusivamente dados de gameplay: score, tier, boosts, spread, tempo e dificuldade. Nenhuma informação pessoal — IP, email, localização, identificador de dispositivo ou cookie — é armazenada. O único dado "pessoal" é o nome de piloto, que é voluntário, pode ser qualquer texto, e só é registrado se o jogador clicar em "Save & Ranking".

Qual é a diferença entre sessões Unregistered e Unknown?

Unregistered significa que o jogador clicou o botão "Skip" — uma ação deliberada de não registrar o nome. Unknown significa que o jogador fechou a aba, navegou para outra página, ou iniciou um novo jogo sem decidir — a sessão foi capturada automaticamente via sendBeacon ou no início da próxima partida.

Os dados de telemetria são públicos?

Sim. Todas as estatísticas são acessíveis em /stats e suas subpáginas (diário, mensal, anual). O dashboard completo também é público, com gráficos interativos. As páginas de stats incluem metadata schema.org/Dataset para indexação como dados abertos.

Como os horários de pico são calculados?

A API agrupa todas as partidas registradas pela hora UTC do campo playedAt usando $hour do MongoDB, produzindo uma distribuição de 0 a 23. Horários sem partidas são preenchidos com zero. O resultado é exibido como gráfico de barras no dashboard, revelando quando a comunidade mais joga.

O sendBeacon garante 100% de captura?

Não com 100% de certeza. O sendBeacon é "best-effort" — funciona na grande maioria dos navegadores modernos, mas pode falhar em navegadores muito antigos ou em situações extremas (crash do navegador, kill forçado do processo). Ainda assim, a taxa de captura é significativamente superior a fetch em beforeunload, que frequentemente é cancelado pelo navegador.