Contexto
Fatia 2 do epic #24 (Practice/Solo mode §V.5). A Fatia 1 (#25, PR #40) já entregou o Treino Livre solo em src/components/PracticeView.tsx — hoje é só o jogador contra o cronômetro, sem ninguém pra comparar em tempo real. A spec (§V.5) pede um modo "contra bots": um WPM-alvo configurável (40/60/80/100) dá ao Iniciante alguém pra "competir" sem precisar de sala/amigos — reduz a sensação de vazio do treino solo.
Escopo
- Seletor de WPM-alvo no
PracticeView.tsx (ex.: chips 40/60/80/100, ou "sem bot"), ao lado dos seletores de linguagem/dificuldade já existentes.
- Um "racer fantasma" que avança a progresso linear no ritmo do WPM escolhido (progresso =
f(tempo decorrido, wpm_alvo, tamanho do snippet)), iniciando no mesmo startedAt do jogador (1º keystroke).
- Barra de progresso do bot visível ao lado/abaixo da corrida do jogador (reusar visual de progresso já usado no multiplayer, se der pra extrair sem acoplar a
useRoom/Realtime — o Treino Livre não pode ganhar dependência de sala).
- Ao terminar, mostrar quem venceu (jogador vs bot) nas stats de conclusão (
stats em PracticeView.tsx).
Acceptance criteria (verificáveis)
Dica de abordagem
Progresso do bot em função do tempo é puro e testável: botProgress(elapsedMs, targetWpm, charCount) — mesma filosofia dos builders extraídos em #50/#32 (função pura ao lado da lógica de UI, fácil de cobrir com Vitest). Evite reinventar o cálculo de WPM→progresso; se Race.tsx/TypingCore já expõe algo equivalente, reuse.
Refs: spec docs/UI-AAA-OVERHAUL.md §V.5 (modo 3, "contra bots"); epic #24; Treino Livre atual em src/components/PracticeView.tsx.
Contexto
Fatia 2 do epic #24 (Practice/Solo mode §V.5). A Fatia 1 (#25, PR #40) já entregou o Treino Livre solo em
src/components/PracticeView.tsx— hoje é só o jogador contra o cronômetro, sem ninguém pra comparar em tempo real. A spec (§V.5) pede um modo "contra bots": um WPM-alvo configurável (40/60/80/100) dá ao Iniciante alguém pra "competir" sem precisar de sala/amigos — reduz a sensação de vazio do treino solo.Escopo
PracticeView.tsx(ex.: chips 40/60/80/100, ou "sem bot"), ao lado dos seletores de linguagem/dificuldade já existentes.f(tempo decorrido, wpm_alvo, tamanho do snippet)), iniciando no mesmostartedAtdo jogador (1º keystroke).useRoom/Realtime — o Treino Livre não pode ganhar dependência de sala).statsemPracticeView.tsx).Acceptance criteria (verificáveis)
startedAtdo jogador./api/rooms, Supabase ou Realtime — o bot é 100% client-side/determinístico (mantém a garantia da Fatia 1 de treino sem sala).textarea/o loop de digitação (TypingCore); a barra do bot é só leitura visual.pnpm typecheck+pnpm buildverdes;node scripts/validate-metrics.mjsverde se tocar cálculo de progresso/WPM.Dica de abordagem
Progresso do bot em função do tempo é puro e testável:
botProgress(elapsedMs, targetWpm, charCount)— mesma filosofia dos builders extraídos em #50/#32 (função pura ao lado da lógica de UI, fácil de cobrir com Vitest). Evite reinventar o cálculo de WPM→progresso; seRace.tsx/TypingCorejá expõe algo equivalente, reuse.Refs: spec
docs/UI-AAA-OVERHAUL.md§V.5 (modo 3, "contra bots"); epic #24; Treino Livre atual emsrc/components/PracticeView.tsx.