Skip to content

Engine/Testes: extrair métricas puras de Race.tsx para src/lib/metrics.ts + testes de honestidade (WPM/precisão) #32

Closed

Description

@caioross

Contexto

A honestidade das métricas (WPM, precisão, erros) é a verdade sagrada do domínio — foi a origem do feedback do Iniciante que virou #20 ("Precisão 100%" coexistindo com "Erros 1"). Mas hoje toda a lógica de cálculo mora inline dentro do componente src/components/Race.tsx, o que a torna impossível de testar por unidade:

  • correctChars — loop char-a-char (Race.tsx:49-53)
  • wpm — (correctChars / 5) / elapsedMin arredondado (Race.tsx:55)
  • accuracy — (1 - errors/totalKeystrokes) * 100 com clamp e arredondamento (Race.tsx:56-57)
  • progress — typed.length / code.length (Race.tsx:58)
  • contagem de erros/keystrokes em handleInput, incluindo a regra "backspace não conta keystroke" (Race.tsx:81-92)

Existe o script .claude/skills/cr-typing-engine/scripts/validate-metrics.mjs, mas ele valida uma cópia da lógica, não o código que roda em produção — divergência silenciosa é possível. Esta issue extrai as funções puras para src/lib/ e as cobre com testes reais, tornando a engine testável sem alterar comportamento.

Depende do runner instalado pela issue de infra de testes (Vitest) — se ela ainda não tiver mergeado, esta pode incluir a config mínima ou aguardar.

Escopo

  1. Criar src/lib/metrics.ts com funções puras e determinísticas, extraídas 1:1 de Race.tsx (sem mudar fórmula nem arredondamento):
    • countCorrectChars(typed: string, code: string): number
    • computeWpm(correctChars: number, elapsedMin: number): number
    • computeAccuracy(errors: number, totalKeystrokes: number): number
    • computeProgress(typedLen: number, codeLen: number): number
  2. Race.tsx passa a importar e usar essas funções (nenhuma mudança de comportamento observável — mesmos números na tela).
  3. Suite src/lib/metrics.test.ts cobrindo as invariantes de honestidade.

Acceptance criteria (verificáveis)

  • Race.tsx não contém mais o cálculo inline — usa metrics.ts; a UI mostra exatamente os mesmos valores de antes (sem regressão visual/numérica).
  • computeWpm: elapsedMin <= 0.001 → 0 (evita divisão por ~0); conta só correctChars (chars errados não contam WPM).
  • computeAccuracy: totalKeystrokes === 0 → 100; nunca retorna 100 quando errors > 0 (a honestidade de Engine/Honestidade: "Precisão 100%" coexiste com "Erros 1" — arredondamento mascara erro e rótulo confunde #20 — o arredondamento de 0.995 não pode mascarar erro); clampada em [0,100].
  • countCorrectChars: só conta posições onde typed[i] === code[i]; typed mais longo que code não estoura.
  • computeProgress: codeLen === 0 → 0; clampada em [0,1].
  • node scripts/validate-metrics.mjs (via skill) continua verde; pnpm test verde.

Dica de abordagem

  • Extração pura, zero mudança de fórmula — copie exatamente Race.tsx:49-58. O ganho é testabilidade e uma única fonte de verdade (a duplicata em validate-metrics.mjs pode passar a importar de metrics.ts numa fatia futura).
  • Área sagrada (HANDBOOK §2): Race.tsx está no caminho de input da corrida (60fps). A extração deve ser puro refactor — as funções continuam chamadas dentro de useMemo/render como hoje, sem trabalho extra por keystroke. Não introduza alocação/loop novo no hot path.
  • Gate: pnpm typecheck && pnpm build && pnpm test && node scripts/validate-metrics.mjs. PR normal (§7.3 — refactor + testes de engine com validate-metrics verde).

Activity

  1. added
    P2Prioridade normal
    area:engineMotor de digitação, WPM, precisão, anti-cheat
    on Jul 14, 2026
  2. caioross commented on Jul 17, 2026

    @caioross
    OwnerAuthor

    🏛️ Parecer do Conselho · 2026-07-17

    Veredito: APROVADA PARA EXECUÇÃO — mas a issue tem uma contradição interna que o Resolvedor precisa resolver conscientemente (ver Engenharia). Não é decisão do dono (é fix de bug já reconhecido em #20), porém muda um número visível — o PR deve declarar.

    • Engenharia:
      • Extração de countCorrectChars/computeWpm/computeProgress é 1:1 e segura (Race.tsx:49-58): tracei os três e o comportamento atual já satisfaz seus AC (typed>code não estoura pois code[i]===undefined; elapsedMin>0.001?…:0; codeLen===0?0, clamp [0,1]).
      • Contradição em computeAccuracy: o escopo diz "copie exatamente, zero mudança de fórmula", mas o AC diz "nunca retorna 100 quando errors>0". A fórmula atual (Race.tsx:56-57) Math.round((1 - errors/totalKeystrokes)*100) retorna 100 com erro: errors=1, totalKeystrokes≥200 → 99.5…→Math.round→100. É exatamente o bug Engine/Honestidade: "Precisão 100%" coexiste com "Erros 1" — arredondamento mascara erro e rótulo confunde #20 ("Precisão 100%" + "Erros 1"). Cópia 1:1 → o AC de honestidade falha; satisfazer o AC → viola o "zero mudança". Não há como ter os dois.
      • Resolução recomendada: tratar a honestidade como objetivo primário (origem Engine/Honestidade: "Precisão 100%" coexiste com "Erros 1" — arredondamento mascara erro e rótulo confunde #20, verdade sagrada). computeAccuracy com guarda explícita: const raw = totalKeystrokes===0 ? 100 : (1 - errors/totalKeystrokes)*100; const r = Math.round(raw); return errors>0 ? Math.min(r, 99) : r; (clamp [0,100]). Alternativa Math.floor também mata o mascaramento mas mexe em mais valores; o min(…,99) é o mínimo que satisfaz o AC preservando o resto. Só a accuracy muda; WPM/progress/correctChars ficam 1:1.
      • Impacto visível: corridas que mostravam 100% com 1 erro passam a 99%. É o ganho de correção pedido, não regressão — declarar no PR e idealmente Closes #20 (ou linkar).
    • Segurança/Anti-cheat: positivo — fonte única de verdade elimina a divergência silenciosa entre Race.tsx e a cópia em validate-metrics.mjs (o próprio issue aponta). Numa fatia futura, validate-metrics.mjs pode importar de metrics.ts; nesta, manter o script verde.
    • UX/Game-feel: precisão fica honesta; nenhuma outra mudança visual, nada de movimento/reduced-motion.
    • Performance (área sagrada): correctChars fica no useMemo atual (Race.tsx:49-53) — manter. Funções chamadas em render/useMemo como hoje; não introduzir loop/alocação novos por keystroke. handleInput (l.74-96) conta erros inline — pode ficar como está (fora do escopo) ou extrair countNewErrors(prev,next,code) puro para testar a regra "backspace não pontua" (l.90-92); opcional, alta densidade de teste.
    • Produto: P2 correta; paga a dívida de testabilidade e fecha/prepara Engine/Honestidade: "Precisão 100%" coexiste com "Erros 1" — arredondamento mascara erro e rótulo confunde #20. Alto valor, baixo custo.

    Plano sugerido:

    1. src/lib/metrics.ts: countCorrectChars/computeWpm/computeProgress (1:1 de Race.tsx:49-58) + computeAccuracy com o guard de honestidade acima. Puras, sem DOM.
    2. Race.tsx: importar e usar; correctChars segue em useMemo; UI idêntica exceto accuracy honesta.
    3. src/lib/metrics.test.ts (Vitest de Testes: instalar Vitest + primeiro suite na fronteira anti-cheat (sanitizeResults) e rodar no CI #31): pinar invariantes — WPM só conta corretos e elapsedMin≤0.001→0; accuracy nunca 100 com erro (errors=1,ks=201), ks=0→100, clamp [0,100]; correctChars não estoura; progress codeLen=0→0, clamp [0,1].
    4. Gate: typecheck && build && test && node scripts/validate-metrics.mjs verde.
    5. PR normal §7.3 (refactor+testes de engine). Declarar a mudança de número visível e linkar Engine/Honestidade: "Precisão 100%" coexiste com "Erros 1" — arredondamento mascara erro e rótulo confunde #20.

    Dependência: precisa do Vitest de #31 mergeado para o .test.ts rodar no gate; se ainda não, incluir config mínima do Vitest OU pinar as invariantes no validate-metrics.mjs provisoriamente (sem duplicar harness em definitivo).

  3. added
    em-resolucaoClaim: um Resolvedor está trabalhando nisto
    and removed
    em-resolucaoClaim: um Resolvedor está trabalhando nisto
    on Jul 19, 2026
  4. caioross commented on Jul 19, 2026

    @caioross
    OwnerAuthor

    Resolvida na PR #48 (refactor(engine), Closes #32).

    Entregue: src/lib/metrics.ts (fonte única de verdade — countCorrectChars/computeWpm/computeAccuracy/computeProgress, extração 1:1) + src/lib/metrics.test.ts (13 testes de honestidade); Race.tsx importa e usa, com correctChars seguindo no useMemo (hot path intacto).

    Divergência do Parecer (declarada): a contradição "1:1 × nunca 100 com erro" não se materializou — a correção de honestidade da #20 já estava inline na main (Math.floor + teto de 99%). Portanto é refactor puro, zero mudança de número na tela; os testes só pinam o comportamento vigente. Sem Closes #20.

    Gate: typecheck ✅ · build ✅ · test 34/34 ✅ · validate-metrics 37/0 ✅ · validate-persistence 33/0 ✅ · lint N/A.

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

    P2Prioridade normalarea:engineMotor de digitação, WPM, precisão, anti-cheat

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions