feat(multiplayer): entrada após o início vira espectador — congela a lista da rodada (#64) - #80
Conversation
|
Caution The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased. |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
…lista da rodada (#64) A lista de participantes ficava aberta durante toda a partida: quem entrasse com a corrida já em `racing` passava a compor a mesma rodada, e todos que já tinham terminado ficavam presos esperando o retardatário terminar ou desistir. Uma partida deve ser composta por quem estava presente quando ela COMEÇOU. - `isSpectatorJoin(joinedAt, startMs, status)` em `src/lib/room.ts` — puro e determinístico (joinedAt e start_at são compartilhados por presence + linha da sala, então todos os clientes classificam igual). Espectador = entrou depois do start (`joinedAt > startMs`) com a rodada em andamento. - `useRoom`: cada `LivePlayer` ganha `spectator`; o gate de fim de corrida passa a ignorar espectadores (`racers = players.filter(!spectator)`), então um retardatário sem `finishedAt` não prende mais a sala. Expõe `isSpectator` para a UI. - `RoomView`: espectadores saem da pista/ranking (filtrados do roster de competidores) e veem a nova `SpectatorView` — banner "Partida em andamento", pista ao vivo e chat. Entram automaticamente na próxima rodada (start_at muda → deixam de ser espectadores). - 6 casos novos em `room.test.ts` cobrindo a classificação (limite exato, lobby, finished, sem corrida).
d4736f1 to
0c9bb04
Compare
|
Rebase nota: o PR #60 (issue #58, liveness) mergeou na
|
🔧 PR Doctor — CONFLITANTE, reparo por união antes de avaliarA #80 (espectador ao entrar depois do início) está |
🔧 PR Doctor — ainda CONFLITANTE; reparo por união + re-quórumA #80 (espectador ao entrar depois do início) segue Ação (§5.D): resetar a branch ao head da PR, |
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.
Achado do quorum: congelar a lista da rodada reintroduzia sala presa. Se todos os competidores saem durante racing e resta so um retardatario-espectador, racers fica [] e shouldFinishRace (guarda de lista vazia) nunca encerra. Agora o lider posta finish com results vazios (tela de Results vazia da #63) apos o countdown, liberando a sala.
🏛️ Quórum §7.2 — reparada e APROVADA (2 lentes)
|
Contexto
A lista de participantes ficava aberta durante toda a partida: quem entrasse com a
corrida já em
racingpassava a compor a mesma rodada, e todos que já tinham terminadoficavam presos esperando o retardatário terminar/desistir — quebrando o ritmo da sala
(issue #64). A regra passa a ser: uma partida é composta por quem estava presente
quando ela começou (o instante
start_at); quem chega depois assiste e entra na próxima.O que mudou
src/lib/room.tsisSpectatorJoin(joinedAt, startMs, status)— puro/determinístico;LivePlayer.spectatorsrc/lib/useRoom.tsspectatorno roster; gate de fim ignora espectadores; expõeisSpectatorsrc/components/RoomView.tsxSpectatorViewsrc/components/SpectatorView.tsxsrc/lib/room.test.tsDeterminismo (por que é seguro sem tocar o servidor):
joinedAt(presence) estart_at(linha da sala) são compartilhados por todos os clientes, então todosclassificam o mesmo jogador igual. Espectador = rodada em andamento (
racing/finished)e
joinedAt > startMs. Emlobby/countdown ninguém é espectador — quem está antes dostart compõe a corrida.
O ponto de maior risco (e o que este PR conserta): o gate de fim de corrida era
players.every(p => p.finishedAt). Um retardatário semfinishedAtprendia a sala emracingpara sempre. Agora o líder finaliza quando todos os RACERS (!spectator)terminaram; espectadores nunca entram no gate nem no
results(não têmfinishedAt, entãojá eram naturalmente excluídos do ranking — nenhum caminho novo para o leaderboard).
Próxima rodada: quando o líder reinicia,
start_atmuda; o antigo espectador passa a terjoinedAt < novo startMs→ vira competidor automaticamente (AC atendido).Como foi validado (resultado real)
Smoke test (
next devna worktree):/,/practicee/room/<code>renderizam sem errode console; o name-gate da sala aparece normal.
Limitação de verificação: a
SpectatorViewsó aparece para um jogador que entra numasala com corrida em andamento — exige dois clientes simultâneos num canal Realtime real,
inviável de forma confiável headless nesta rodada. A classificação (o núcleo da mudança) está
coberta por unit tests determinísticos; a troca de view e o filtro do roster estão cobertos
por typecheck + build. Recomendo ao PR Doctor um teste manual com duas abas se quiser
confirmação visual.
Riscos
joinedAtéDate.now()do cliente que entra;startMséDate.parse(start_at)do servidor. Skew de segundos só afeta quem entra na fronteira exata do start (durante o
countdown de 4s) — no pior caso vira espectador e compete na rodada seguinte (degradação
suave, nunca trava). Quem entra claramente no meio da corrida é sempre classificado certo.
textareafoi tocado.Player(types.ts) não mudou — espectadores são filtrados antes da conversãoLivePlayer→Player.Solicito quórum (HANDBOOK §7)
Closes #64