Skip to content

feat(multiplayer): votação de linguagem na sala de espera (#72) - #89

Merged
caioross merged 2 commits into
mainfrom
auto/issue-72-lang-voting
Jul 26, 2026
Merged

feat(multiplayer): votação de linguagem na sala de espera (#72)#89
caioross merged 2 commits into
mainfrom
auto/issue-72-lang-voting

Conversation

@caioross

Copy link
Copy Markdown
Owner

Contexto

A linguagem da corrida era escolha exclusiva do líder. A #72 torna a decisão coletiva:
na sala de espera, cada jogador tem um voto (mutável até o start); a mais votada vence e, em
empate, há sorteio. Mais interação, mais variedade, incentivo a experimentar linguagens.

O que mudou

Arquivo O quê
src/lib/room.ts tallyVotes + pickVoteWinner(votes, fallback, rng) — puros/testáveis
src/lib/useRoom.ts broadcast vote, tally por presença, castVote, resolução do vencedor no start
src/components/Lobby.tsx seletor de linguagem → grade de votação (todos votam, contadores, resumo)
src/components/RoomView.tsx repassa voteTally/myVote/onVote
src/lib/room.test.ts 8 casos de votação

Como as votações se propagam (mesma receita do ready): o voto é um broadcast Realtime
confiável, re-anunciado no join para quem entra depois (broadcast não tem replay). O tally
conta só quem está presente — quem sai ou é kickado deixa de pesar no resultado.

Quem decide o vencedor, e onde (fonte única de verdade): ao iniciar, o líder resolve
pickVoteWinner(votos, room.language) e grava a linguagem vencedora em rooms.language pela
action settings já existente, ANTES do start (que lê essa linha para sortear o snippet).
Assim, a decisão passa por um único ponto e todos os clientes convergem pela linha da sala —
sem inventar um caminho novo de configuração.

Empate e sem-votos: empate → sorteio entre as empatadas (rng injetável, testado com
()=>0 e ()=>0.99); ninguém votou → mantém a linguagem padrão da sala. Votos são zerados ao
(re)entrar no lobby e ao começar a corrida (rodada nova = votação nova).

Critérios de aceite

  • Todas as linguagens podem receber votos (grade com as 24).
  • Cada jogador tem um voto (mapa playerId→lang; re-clique troca).
  • Mutável até o início (broadcast a cada clique; resolvido só no start).
  • Todos veem em tempo real (broadcast + contadores + resumo).
  • Empate → sorteio automático (pickVoteWinner).
  • A vencedora é usada na próxima corrida (gravada em rooms.language antes do start).

Como foi validado (resultado real)

pnpm install --frozen-lockfile   OK
pnpm typecheck                   OK
pnpm build                       OK  (/room/[id] 224 kB)
pnpm test                        OK — 62 testes (8 novos de votação)
validate-metrics.mjs             OK — 37 passaram
validate-persistence.mjs         OK — 22 passaram
pnpm lint                        N/A — sem config ESLint no repo

Render-check do Lobby (harness temporário, removido antes do commit): a grade renderiza
sem erro, os contadores aparecem (python 3, go 3, rust 1) e o resumo detecta empate corretamente
"empate: Python, Go — sorteio no início"; dificuldade/limite seguem só do líder.

Limitação de verificação: a propagação realtime dos votos entre clientes e a aplicação da
linguagem vencedora no start exigem dois clientes num canal Realtime real — inviável
headless nesta rodada. A lógica pura de contagem/vencedor/empate está coberta por unit tests
determinísticos; a fiação de UI/props por typecheck + build + render-check. Recomendo ao PR
Doctor um teste com duas abas para confirmação visual do fluxo completo.

Riscos

  • Voto de jogador ausente: neutralizado — o tally e o vencedor contam só presence.
  • Linguagem inválida num voto forjado: isValidLang descarta antes de contar; e o vencedor é
    sempre um LangId válido (voto válido ou fallback = linguagem da sala).
  • Governança: não toca src/app/api/rooms/** (usa a action settings existente) nem
    supabase.ts/anti-cheat/migrations/CI; nenhuma dependência nova. Área sagrada intacta.

Solicito quórum (HANDBOOK §7)

Closes #72

@gemini-code-assist

Copy link
Copy Markdown

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@vercel

vercel Bot commented Jul 26, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
code-racer Ready Ready Preview, Comment Jul 26, 2026 6:13pm

A linguagem da próxima corrida passa a ser decidida coletivamente: cada jogador tem
um voto, mutável até o início; a mais votada vence e, em empate, há sorteio. Antes a
linguagem era escolha exclusiva do líder — a votação aumenta a interação e a variedade.

- `room.ts`: `tallyVotes` e `pickVoteWinner(votes, fallback, rng=Math.random)` puros e
  testáveis (empate → sorteio com rng injetável; sem votos → linguagem padrão da sala).
  Votos em linguagem inexistente são ignorados (`isValidLang`).
- `useRoom.ts`: novo broadcast `vote` (mesmo transporte confiável do `ready`, com
  re-anúncio no join para quem chega depois). `voteTally`/`myVote` derivados só de quem
  está PRESENTE (quem sai/é kickado deixa de pesar). `castVote` otimista. No `start`, o
  líder resolve o vencedor e grava em `rooms.language` via a action `settings` ANTES do
  `start` — a linha da sala segue sendo a fonte de verdade que todos os clientes leem.
  Votos são zerados ao (re)entrar no lobby e ao começar a corrida.
- `Lobby.tsx`: seletor de linguagem vira grade de VOTAÇÃO — todos clicam, cada botão
  mostra o contador, meu voto destacado, e a(s) linguagem(ns) na frente marcada(s). Um
  resumo mostra quem está vencendo ou o empate. Dificuldade e limite seguem só do líder.
- `RoomView.tsx`: repassa `voteTally`/`myVote`/`onVote`.
- `room.test.ts`: 8 casos de `tallyVotes`/`pickVoteWinner`.

Nenhuma mudança na API de salas: o vencedor é aplicado pela action `settings` já
existente. Nada toca a área sagrada (só o lobby).
@caioross
caioross force-pushed the auto/issue-72-lang-voting branch from 492ddb4 to 9451217 Compare July 26, 2026 13:42
@caioross

Copy link
Copy Markdown
Owner Author

Rebase nota: o PR #79 (issue #66, moderação/kick) mergeou na main enquanto eu trabalhava, tocando useRoom.ts. Rebasei em cima de origin/main; o único conflito foi no registro dos handlers de broadcast: minha branch (anterior ao #39/#66) ainda trazia o handler antigo de kick por broadcast, que a main removeu em favor da expulsão autoritativa via kicked_ids (postgres_changes). Resolvi mantendo o handler novo de vote e descartando o handler obsoleto de kick — a versão autoritativa da main (kickpostAction, vítima sai no kicked_ids) foi preservada intacta. Gate re-rodado: typecheck · build · test 62 · validators verdes. mergeable: MERGEABLE.

…avada

Uniao com origin/main (resolve room.test.ts: mantém os blocos de votação #72 e
de roomUpdateOutcome #56). Reparo do veto do quórum: startRace ignorava o
retorno do postAction(settings) e iniciava incondicionalmente — se a gravação
da linguagem vencedora falhasse (pós-#56 devolve ok:false), a corrida começava
com a linguagem antiga enquanto o líder via toast de erro. Agora aborta o start
quando !res.ok.
@caioross

Copy link
Copy Markdown
Owner Author

🏛️ Quórum §7.2 — reparada e APROVADA

Lente Ofensiva/Domínio vetou (com razão): startRace ignorava o retorno do postAction("settings") e iniciava incondicionalmente → se a gravação da linguagem vencedora falhasse (pós-#56 devolve ok:false), a corrida começava com a linguagem antiga enquanto o líder via toast de erro.

Reparo (commit a7c9182): startRace agora aborta o start quando !res?.ok. Re-verificação: RESOLVIDO — caminho feliz intacto, sem defeito novo, e o toast enganoso some. Spoof de voto confirmado como não-escalável (só muda a linguagem; gravação final é leader-only; isValidLang barra inválidos).

União com origin/main resolvida (room.test.ts: mantidos os blocos de votação e de roomUpdateOutcome). Gate no worktree: typecheck ✓, test 88 ✓, build ✓. CI + Vercel verdes. Closes #72. Squash-merge.

@caioross
caioross merged commit 881d13b into main Jul 26, 2026
3 checks passed
@caioross
caioross deleted the auto/issue-72-lang-voting branch July 26, 2026 18:14
caioross added a commit that referenced this pull request Jul 26, 2026
Uniao com origin/main (que trouxe #60/#79/#96/#89). Resolucao:
- room.ts: mantidos os builders (buildMatchRow/buildScoreRows/MatchInsert/
  ScoreInsert) E as funcoes de kick/vote/outcome que entraram na main.
- route.ts: imports unificados; persistMatch segue como wrapper fino dos builders.
- room.test.ts: imports unificados; adicionado kicked_ids:[] ao FULL_ROOM
  (campo virou obrigatorio em RoomRow via #79).
Gate: typecheck OK, build OK, test 103 OK, validate-persistence 72/0.
caioross added a commit that referenced this pull request Jul 26, 2026
Uniao com origin/main (#55/#60/#79/#89/#96). Resolucao (tudo aditivo):
- useRoom.ts: mantidos isSpectatorJoin + isSpectator E as importacoes/logica de
  votacao (#89, tallyVotes/pickVoteWinner/activeVotes/voteTally/myVote).
- RoomView.tsx: mantido o branch isSpectator?SpectatorView:Race E os props
  isLeader/onKick do Race (#79), alem de voteTally/myVote no destructure.
- room.test.ts: mantidos os blocos de isSpectatorJoin (#64) e votacao/outcome.
Gate: typecheck OK, test 109 OK, build OK, validate-persistence 72/0.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature] Sistema de votação de linguagens na sala de espera

1 participant