Skip to content

Anti-cheat/Leaderboard: recorde impossível (3596 WPM) no Ranking Global — sem teto de plausibilidade #28

Open

Description

@caioross

O quê

O Ranking Global (/leaderboard) exibe um recorde fisicamente impossível no topo:

# Jogador WPM Precisão Lang
🥇 hackerman 3596 100% KT
🥈 gtkatakura 44 100% C++
🥉 yago_naodev 38 96% C#

O 2º colocado tem 44 WPM. 3596 WPM não é humanamente possível (recorde mundial de digitação ~200 WPM, e digitar código é bem mais lento). Ou seja: uma partida com WPM absurdo passou pela persistência e agora polui o placar público.

Por que é grave (P1)

  • A justiça das métricas é o alicerce do produto (spec §0.3 — otimizamos pro Cético e pro Competitivo). Um recorde impossível no topo detona a confiança dos dois perfis mais exigentes.
  • Pro Iniciante (§0.3) é desmotivador: "nunca vou chegar perto" + "esse ranking é furado".
  • Sinaliza um gap de anti-cheat/validação: o WPM não é sanitizado contra teto plausível antes de persistir em matches/scores nem ao montar o leaderboard.

Repro

  1. Abrir https://code-racer-three.vercel.app/leaderboard
  2. Observar o Add animated home effects, spotlight UI & snippets #1 hackerman com 3596 WPM.

(Observado ao vivo em 2026-07-12. Não sei reproduzir como o registro foi criado — pode ser dado antigo pré-anti-cheat ou um vetor ainda aberto; de qualquer forma o placar público está poluído hoje.)

Sugestão de acceptance criteria

  • Definir um teto plausível de WPM (ex.: rejeitar/ignorar partidas com WPM acima de um limite tipo 250–300) na validação antes de persistir (área anti-cheat, area:engine).
  • Ao montar o Ranking Global, filtrar/ocultar entradas acima do teto (defesa em profundidade, area:multiplayer).
  • Sanear os dados existentes: remover/reduzir o registro atual de 3596 WPM do hackerman.
  • Considerar registrar (log) tentativas implausíveis pra auditoria de anti-cheat.

Contexto

Descoberto durante a rodada semanal da persona (O Iniciante). Feedback completo: discussion #27.

Activity

  1. added
    bugSomething isn't working
    P1Alto valor — próximo da fila
    area:engineMotor de digitação, WPM, precisão, anti-cheat
    area:multiplayerSalas, Realtime, presença, leaderboard, persistência
    em-resolucaoClaim: um Resolvedor está trabalhando nisto
    and removed
    em-resolucaoClaim: um Resolvedor está trabalhando nisto
    on Jul 12, 2026
  2. caioross commented on Jul 12, 2026

    @caioross
    OwnerAuthor

    Aberta a PR #30 (fatia parcial → Refs #28).

    Entregue: defesa em profundidade nas leituras públicas (getLeaderboard + getRecentMatches em src/lib/supabase.ts) filtrando por MAX_PLAUSIBLE_WPM — o registro impossível de 3596 WPM some do Ranking Global sem tocar o dado. Complementa o guard de escrita sanitizeResults (#7).

    Fica para o dono (HANDBOOK §7.1): sanear/remover o registro bruto em scores (delete from scores where wpm > 350;) — operação em dados de produção. Item "logar tentativas implausíveis" (AC "considerar") fora do escopo mínimo desta fatia.

    Gate local: typecheck ✅ · build ✅ · persistence 33/0 ✅ · metrics 24/0 ✅. PR pede quórum (§7.2).

  3. caioross commented on Jul 22, 2026

    @caioross
    OwnerAuthor

    🏛️ Parecer do Conselho · 2026-07-22

    Veredito: PRECISA DECISÃO DO DONO — aplico decisao-dono e aponto para a [Decisão] já aberta (#46).

    Revisado contra origin/main (7236d47). Motivo do parecer: a issue é P1 e está sem label de bloqueio, então continua no topo do pool elegível e vai consumir a rodada de um Resolvedor que não tem o que entregar aqui. Este parecer existe para evitar essa rodada perdida e deixar o resto documentado.

    • Engenharia: situação real dos 4 AC hoje:
      · AC1 (teto antes de persistir) — ✅ já na main: sanitizeResults descarta linha com wpm > MAX_PLAUSIBLE_WPM (350) — src/lib/room.ts:100,143. Descarta, não clampa (correto: clampar premiaria o trapaceiro).
      · AC2 (filtrar na leitura) — ⏸ pronto, parado: getLeaderboard/getRecentMatches (src/lib/supabase.ts:48-61,64-79) são filtrados pela PR fix(leaderboard): oculta WPM implausível do ranking público (#28) #30, que está DRAFT + decisao-dono desde 18/07 (quórum 2 APROVA / 1 VETA).
      · AC3 (sanear a linha bruta de 3596 WPM) — 🔒 dono: delete/update em dados de produção é núcleo irredutível, HANDBOOK §7.1.
      · AC4 (log de tentativas implausíveis) — o único resto executável, e sozinho não muda uma linha do /leaderboard: fazer só isso deixaria a issue P1 aberta com o sintoma público intacto e daria falsa sensação de progresso.
      Conclusão de engenharia: não existe fatia que o Resolvedor possa entregar hoje que mova o sintoma. O gargalo é uma resposta de uma linha em [Decisão] Leaderboard cap (#28/#30): defesa-em-profundidade + deletar linha bruta vs. teto na view #46, não investigação.
    • Segurança/Anti-cheat: o veto da lente Ofensiva no quórum da fix(leaderboard): oculta WPM implausível do ranking público (#28) #30 está correto e vale registrar como invariante da frota: matches/scores/leaderboard têm RLS using (true) + grant select to anon (supabase/migrations/0001_coderacer_init.sql:58-69) e a anon key viaja no bundle do cliente — logo supabaseBrowser.from("leaderboard").select().gt("wpm", 350) reexibe o dado bruto, contornando qualquer filtro de aplicação. Isto não é regressão da fix(leaderboard): oculta WPM implausível do ranking público (#28) #30: é a natureza de um leaderboard público sem login. Implicação prática: filtro em src/lib/supabase.ts é camada, não fechadura. Só duas coisas fecham de verdade — (a) o dado não existir (opção (i) de [Decisão] Leaderboard cap (#28/#30): defesa-em-profundidade + deletar linha bruta vs. teto na view #46: merge + delete from scores where wpm > 350;) ou (b) o teto morar na própria view (opção (ii): migração aditiva create or replace view leaderboard ... where wpm <= 350). As duas são compatíveis e o custo marginal de (ii) é um arquivo em supabase/migrations/ — o dono continua sendo quem aplica (§8).
    • UX/Game-feel: nada a construir; a tela do /leaderboard está correta, o dado é que mente. Enquanto hackerman · 3596 WPM estiver no topo, a mesma tela que a persona O Iniciante chamou de encantamento nº1 (ver o próprio nome no ranking, D#14/D#27) é a que queima o Cético e o Competitivo (spec §0.3).
    • Performance: irrelevante em qualquer opção — .lte()/where server-side sobre leitura já limitada por .limit() (supabase.ts:55,73); a variante (ii) roda no plano da view, sem custo perceptível.
    • Produto: é o item de maior retorno de confiança por unidade de esforço do backlog — o trabalho já está feito e revisado; falta um aceno. Prioridade P1 continua correta; o que estava errado era a ausência de label de bloqueio (corrigido agora). Com decisao-dono aplicado, o Resolvedor de amanhã pula limpo para Infra/UI: harness só-de-dev para verificar telas pós-corrida (Results/pódio) sem jogar #37/Crescimento: tela de Resultado não tem "compartilhar" — transformar cada corrida terminada em convite (loop viral) #22 (Top-3 do Curador) em vez de bater nesta porta.

    Plano sugerido:

  4. caioross commented on Jul 26, 2026

    @caioross
    OwnerAuthor

    🧹 Poda de domingo — escopo restante desta issue mudou

    Atualização factual (não é decisão, a decisao-dono continua de pé): 2 dos 4 acceptance criteria já estão na main, entregues por outro caminho.

    Portanto o que resta aqui é: (a) o delete de uma linha em produção — ação do dono, indelegável; e (b) opcionalmente o AC #4. A parte de código endereçável por agente acabou. Se o dono rodar o delete, esta issue pode fechar e o AC #4 vira issue própria (P3, area:security).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P1Alto valor — próximo da filaarea:engineMotor de digitação, WPM, precisão, anti-cheatarea:multiplayerSalas, Realtime, presença, leaderboard, persistênciabugSomething isn't workingdecisao-donoAguarda decisão humana — agentes não tocam

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions