Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
142 commits
Select commit Hold shift + click to select a range
13bf0f1
docs: record the 64 hz tick and four-player room decision
thnbi Sep 23, 2026
e31eebe
docs: record livekit as the voice chat server
thnbi Sep 23, 2026
6236779
docs: describe the multiplayer network architecture
thnbi Sep 23, 2026
ecae674
docs: add the domain glossary
thnbi Sep 28, 2026
e7d3324
refactor: rename snapshotHz to patchHz
thnbi Sep 28, 2026
586df00
docs: call the simulation unit a tick in the network architecture
thnbi Sep 28, 2026
6b99802
docs: record the four-player room decision
thnbi Sep 28, 2026
ed8a816
docs: record ipv4-style room addresses with a queue
thnbi Sep 28, 2026
f663750
docs: narrow adr 0008 to the transport decision
thnbi Sep 28, 2026
9cb4235
docs: narrow adr 0009 to the voice decision
thnbi Sep 28, 2026
d94d0c4
docs: use the glossary terms for tick and state patch
thnbi Sep 28, 2026
0741e0b
test: derive the tick from the config instead of assuming 60 hz
thnbi Sep 28, 2026
1eb2a78
feat: raise tick, patch and input rates to 64 hz
thnbi Sep 28, 2026
7425c77
feat: cap rooms at four players
thnbi Sep 28, 2026
0461492
docs: accept adrs 0008 to 0011
thnbi Sep 28, 2026
738659b
docs: carry the accepted network decisions into the design docs
thnbi Sep 28, 2026
404d7d6
docs: record the network condition for the acceptance test
thnbi Sep 28, 2026
2697e92
docs: size the network parameters, messages and stand network
thnbi Sep 28, 2026
9c855f6
docs: bring proximity voice into the fair scope
thnbi Sep 28, 2026
1b7cfba
docs: define matchmaking as pairing by room address
thnbi Sep 28, 2026
e7fb6a0
docs: call the entry system matchmaking in the gdd
thnbi Sep 28, 2026
6ef3c7a
docs: point the nfr at the resolved network conditions
thnbi Sep 28, 2026
c271276
docs: note adr 0006 in the status of adr 0002
thnbi Sep 28, 2026
c315090
docs: mark the message table as temporary until the schema exists
thnbi Sep 28, 2026
93381ca
docs: pin the colyseus packages individually in adr 0002
thnbi Sep 29, 2026
23a90b4
docs: show the pinned colyseus versions in the topology figure
thnbi Sep 29, 2026
d22ceee
refactor: move the greybox arena into the core
thnbi Sep 29, 2026
8f77f04
feat: simulate a multiplayer match in the core
thnbi Sep 29, 2026
0db4b3e
feat: add the quantized wire format to the core
thnbi Sep 29, 2026
154c3b4
docs: send the aim as a quantized direction instead of yaw and pitch
thnbi Sep 29, 2026
12d8e77
feat: parse and generate ipv4-style room addresses
thnbi Sep 29, 2026
e04575d
refactor: move the spawn feet conversion into the core
thnbi Sep 29, 2026
8a2c0ec
feat: run the authoritative game room on colyseus
thnbi Sep 29, 2026
0871cd4
perf: encode the aim and buttons without allocating
thnbi Sep 29, 2026
5f4614c
feat: hand each simulated tick's input to the network
thnbi Sep 29, 2026
7e32dd4
feat: resolve the room address and game server url from the page
thnbi Sep 29, 2026
e6a18be
fix: write a joining player's state before the first patch
thnbi Sep 29, 2026
170e445
feat: link the client to the game server with interpolated remotes
thnbi Sep 29, 2026
dd4ba36
feat: add a late-bound network port and a remote player roster
thnbi Sep 29, 2026
424b831
feat: adopt the spawn the game server chose
thnbi Sep 29, 2026
caea480
feat: join the room from the url and show the other players
thnbi Sep 29, 2026
5df3d28
feat: drive each player by its own inputs with a credit bucket
thnbi Sep 29, 2026
0fe3e04
docs: describe input-driven stepping and the credit bucket
thnbi Sep 29, 2026
e05d491
feat: reconcile the local prediction against the server
thnbi Sep 29, 2026
01c166a
fix: predict with the movement direction the server receives
thnbi Sep 29, 2026
91bf4dc
fix: read the room players only after the state arrives
thnbi Sep 29, 2026
1b94be2
feat: reconcile the local player every frame
thnbi Sep 29, 2026
412893a
docs: describe the client reconciliation and its measurement
thnbi Sep 29, 2026
ed91f91
feat: derive a remote player's locomotion pose from its motion
thnbi Sep 29, 2026
9a4717f
feat: animate the other players by their motion
thnbi Sep 29, 2026
7f3df9a
fix: make the dev server revalidate pre-bundled dependencies
thnbi Sep 29, 2026
68f7d33
feat: share the shot eye, aim and seed between client and server
thnbi Sep 30, 2026
2222f14
fix: fire the predicted shot from the tick eye with the wire aim
thnbi Sep 30, 2026
9fdbf9e
feat: decode the weapon input from the wire buttons
thnbi Sep 30, 2026
14d3e77
feat: validate the shot on the server against rewound positions
thnbi Sep 30, 2026
fabf34c
fix: stamp each input with the interpolation delay for the rewind
thnbi Sep 30, 2026
c7c3f95
feat: hide the other players while they are dead
thnbi Sep 30, 2026
b1b2b1e
docs: describe the rewound shot and its measurement
thnbi Sep 30, 2026
8695996
feat: define the kill confirmation message in the wire format
thnbi Sep 30, 2026
0e35257
feat: confirm each kill to the shooter with what the medals read
thnbi Sep 30, 2026
dce248d
feat: score confirmed kills on other players like dummy kills
thnbi Sep 30, 2026
6fc6fc0
feat: log the close code when a player leaves the room
thnbi Sep 30, 2026
d0dde4d
docs: describe the kill confirmation sent to the shooter
thnbi Sep 30, 2026
0796a05
feat: name the room entry route and the queue call in the wire protocol
thnbi Sep 30, 2026
299830e
feat: add the room directory with a queue per address
thnbi Sep 30, 2026
bee82af
feat: enter rooms by address through a queueing matchmaking route
thnbi Sep 30, 2026
f2e8f4c
feat: enter through the matchmaking route and follow the queue call
thnbi Sep 30, 2026
1c58a90
docs: describe the room entry route and the queue
thnbi Sep 30, 2026
d76bd7e
feat: serve the client build from the game server, room addresses inc…
thnbi Oct 13, 2026
a7c221c
feat: speak https and wss when a certificate is configured
thnbi Oct 13, 2026
8f241a3
refactor: drop an unused import from the game room
thnbi Oct 13, 2026
438cab6
docs: describe tls on the game server and the page route
thnbi Oct 13, 2026
e7294f0
feat: name the voice ticket message in the wire protocol
thnbi Oct 13, 2026
1d56328
feat: issue voice tickets and remove leavers from the voice room
thnbi Oct 13, 2026
a010ef8
feat: talk in the room's voice channel while V is held
thnbi Oct 13, 2026
2bb9f74
docs: describe the voice ticket, push-to-talk and the media server keys
thnbi Oct 13, 2026
a33bc61
feat: terminate tls for the voice signaling in the game server
thnbi Oct 13, 2026
ab63c8e
docs: describe the signaling relay to the media server
thnbi Oct 13, 2026
08980c6
feat: add the voice proximity radii to the gameplay config
thnbi Oct 13, 2026
8028af2
feat: place each voice where its speaker stands, heard from the camera
thnbi Oct 13, 2026
a34ebda
docs: add the proximity voice to the network parameters
thnbi Oct 13, 2026
6c9afda
feat: admit only the rooms and seats the matchmaking issued
thnbi Oct 13, 2026
d438456
feat: recheck the room queue when a reserved seat expires
thnbi Oct 13, 2026
8548abb
docs: describe the seat expiry recheck and the closed default routes
thnbi Oct 13, 2026
5d99e71
docs: renumber the network ADRs past the numbers dev already uses
thnbi Oct 13, 2026
9e99f9d
chore: merge dev, with the server on the core fighters and the maps i…
thnbi Oct 14, 2026
67f7f5a
fix: move push-to-talk to T, since V is the melee key
thnbi Oct 14, 2026
247d5cd
feat: carry the room deaths and the class blade through the network port
thnbi Oct 14, 2026
593198a
feat: play online in the arena session, with the room players in the …
thnbi Oct 14, 2026
a583f85
feat: fill the empty seats of a room with core bots on the server
thnbi Oct 14, 2026
f488587
fix: quantize positions at 2 mm so the 70 m maps fit the int16 range
thnbi Oct 14, 2026
01eeeec
docs: recompute the network figures for 60 Hz and describe the bots a…
thnbi Oct 14, 2026
0725513
docs: bring proximity voice and the room address back into the gdd
thnbi Oct 14, 2026
907f769
fix: load the room's map, since the server simulates the room on it
thnbi Oct 14, 2026
39ae06c
feat: show the room round trip as the hud ping
thnbi Oct 14, 2026
218b24b
feat: log network stats each 2 s in perf mode
thnbi Oct 14, 2026
9589579
perf: send the patch at the end of each tick and draw others two patc…
thnbi Oct 14, 2026
0a73d76
docs: describe the patch sent at the end of each tick and the 33 ms b…
thnbi Oct 14, 2026
f93de05
feat: light the hitmarker on the frame the predicted shot hits
thnbi Oct 14, 2026
38330c6
docs: describe the predicted hitmarker and when the kill message arrives
thnbi Oct 14, 2026
440ddd1
fix: keep the room's kills in the frame log so the killfeed and medal…
thnbi Oct 14, 2026
026ba5a
feat: announce every room shot to the clients
thnbi Oct 14, 2026
3b05e13
feat: draw the tracers of the other players' shots
thnbi Oct 14, 2026
bad4f5b
docs: describe the room shot message and its bandwidth
thnbi Oct 14, 2026
4788205
refactor: move the profile name rules to core so the server can apply…
thnbi Oct 14, 2026
685586c
feat: carry each room player's callsign in the room state
thnbi Oct 14, 2026
a0be1a7
refactor: let the schema skip the unchanged callsign
thnbi Oct 14, 2026
fd8f029
feat: show the room players' callsigns instead of the bot names
thnbi Oct 14, 2026
e9df16d
docs: describe how the callsign reaches the other players
thnbi Oct 14, 2026
39af5d4
fix: record the tick after the play area limit so the respawn needs n…
thnbi Oct 14, 2026
683ede5
docs: note that the prediction records the tick after the play area l…
thnbi Oct 14, 2026
bdd83a0
build: restart the dev game server whenever a file it imports changes
thnbi Oct 14, 2026
d7e9f3f
docs: show how to run the game server in development
thnbi Oct 14, 2026
168a509
feat: open each room on the arena of whoever creates it
thnbi Oct 14, 2026
e89875f
feat: ask for the room on the arena the scene loaded
thnbi Oct 14, 2026
35a0ce0
docs: describe the arena of each room
thnbi Oct 14, 2026
7be2ebe
fix: write the room state when a player leaves, so the bot back from …
thnbi Oct 14, 2026
3f4576a
fix: change only the blade when a client resends its class, keeping t…
thnbi Oct 14, 2026
b7e475a
fix: clear the frame logs every frame the room is followed, paused or…
thnbi Oct 14, 2026
e8f20ae
fix: release the seats of players who left only after the room's kill…
thnbi Oct 14, 2026
8433bc8
fix: seat a player who arrived with every seat taken as soon as one f…
thnbi Oct 14, 2026
eebfad9
fix: keep the server in step when an online match restarts from the menu
thnbi Oct 14, 2026
2febbc2
fix: restart the prediction history in a new room and after a respawn
thnbi Oct 14, 2026
ed5fbc0
fix: start the per-seat counters over when the session moves to a new…
thnbi Oct 14, 2026
11af681
fix: let go of a room link once its connection closes
thnbi Oct 14, 2026
78e051c
fix: bring the local bots back when the room goes away
thnbi Oct 14, 2026
e842400
fix: fire the predicted shot and strike from the tick eye, as the ser…
thnbi Oct 14, 2026
661473e
fix: send the camera aim to the room so the wire rounds it only once
thnbi Oct 14, 2026
38bce18
fix: ignore the talk key while typing into a text field
thnbi Oct 14, 2026
f260bcc
refactor: place the voice listener with the shared Web Audio helpers
thnbi Oct 14, 2026
40118b8
refactor: move the room-following decisions out of the join adapter
thnbi Oct 14, 2026
83346f3
fix: show the queued room in the url before reloading into its arena
thnbi Oct 14, 2026
a051fa9
refactor: move the matchmaking decisions out of the colyseus adapter
thnbi Oct 14, 2026
aaafab4
feat: add a voice chat volume to the settings menu
thnbi Oct 14, 2026
4a856bf
feat: play the voice chat through the mixer's voice channel
thnbi Oct 15, 2026
fbc294f
fix: accept one wire step of difference so a correction does not cascade
thnbi Oct 15, 2026
05c0429
docs: explain the one step of slack in the reconciliation
thnbi Oct 15, 2026
ca9f3b1
Merge pull request #33 from Evolutionary-Coders/feat/multiplayer
thnbi Oct 15, 2026
3d5d882
refactor: move the seat reservation time to core so the client can re…
thnbi Oct 15, 2026
1c6e498
fix: make the server bots forget their targets when a player leaves
thnbi Oct 15, 2026
8dc8726
fix: carry the reserved seat across the arena reload so the queue kee…
thnbi Oct 15, 2026
9bfe68b
Merge pull request #34 from Evolutionary-Coders/fix/seat-across-reload
thnbi Oct 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 87 additions & 0 deletions CONTEXT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# shōbu

FPS de sniper PvP no navegador, com servidor autoritativo. Este glossário fixa os nomes que o
código, os documentos e as conversas usam para as mesmas coisas.

## Partida

**Sala**:
O grupo de jogadores conectados juntos ao servidor de jogo. Dura várias partidas seguidas, e
quem chega entra na sala mesmo com uma partida em andamento.
_Evitar_: room, lobby

**Matchmaking**:
O pareamento de jogadores pela URL: quem abre o mesmo endereço da sala joga junto. Põe o jogador
na sala do endereço, cria a sala se ela não existe, ou o põe na fila da sala se ela está cheia.
Não pareia por habilidade.
_Evitar_: lobby, alocação de sala

**Endereço da sala**:
O identificador de uma sala, escrito como um endereço IPv4 fictício no caminho da URL
(`/203.0.113.7`). Não tem relação com o endereço real de nenhuma máquina. Os gerados evitam as
faixas privadas e reservadas; um endereço digitado vale em qualquer faixa, e abrir um endereço
sem sala cria a sala. Cada octeto vai de 0 a 255, sem zeros à esquerda: `10.0.0.07` é a sala
`10.0.0.7`.
_Evitar_: código da sala, IP da sala, room id

**Fila da sala**:
Os jogadores que abriram o endereço de uma sala cheia e esperam vaga nela, por ordem de chegada.
Quem está na fila joga numa sala provisória até a vaga abrir.
_Evitar_: lobby, espera

**Sala provisória**:
Uma sala comum com vaga, onde um jogador da fila joga enquanto espera. A URL mostra o endereço
dela, não o da sala pedida. Quem espera pela mesma sala pode estar em salas provisórias
diferentes. Quando abre vaga na sala pedida, o jogador é levado para lá, mesmo no meio de uma
partida, e a URL muda.
_Evitar_: sala temporária, overflow

**Partida**:
Uma rodada de 5 minutos dentro de uma sala. Quem entra na sala durante a partida joga o tempo
que resta dela.
_Evitar_: match, round, sala

**Sala de voz**:
O canal de voz de uma sala. Existe enquanto a sala existe e atravessa as partidas.
_Evitar_: room de voz

## Simulação

**Tick**:
A unidade fixa de tempo em que o estado da partida avança, igual no cliente e no servidor.
_Evitar_: passo, frame, quadro

**Snapshot**:
O estado completo da partida num tick.
_Evitar_: estado, frame

**Patch de estado**:
A diferença entre dois snapshots, que é o que o servidor de jogo envia aos clientes.
_Evitar_: delta snapshot, update

**Input**:
O que um jogador comanda num tick: movimento, mira e botões.
_Evitar_: comando

## Rede

**Servidor de jogo**:
Única autoridade da partida: simula, valida cada disparo e decide quem está em cada sala.
_Evitar_: servidor Colyseus, host

**Servidor de mídia**:
Repassa o áudio de voz entre os jogadores de uma mesma sala, sem entender o jogo.
_Evitar_: SFU de voz, servidor de voz

**Cliente de jogo**:
A parte do navegador que troca input e patches de estado com o servidor de jogo.

**Cliente de voz**:
A parte do navegador que troca sinalização e áudio com o servidor de mídia.

**Sinalização**:
Troca de controle que abre, mantém e fecha a conexão de voz, separada do áudio.

**Fluxo de áudio**:
O áudio de um jogador enquanto ele fala. Existe um por jogador falando.
_Evitar_: locutor
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
```bash
npm install
npm run dev # vite em http://localhost:5173
npm run dev --workspace @shobu/server # servidor de jogo em :2567; reinicia a cada edição, núcleo incluso
npm test # vitest headless, sem navegador
npm run typecheck # tsc sem emitir, nos dois pacotes
npm run lint # biome
Expand Down
6 changes: 5 additions & 1 deletion config/gameplay.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"simulation": {
"tickHz": 60,
"snapshotHz": 30,
"patchHz": 60,
"inputSendHz": 60
},
"movement": {
Expand Down Expand Up @@ -143,5 +143,9 @@
"airborneErrorDeg": 5,
"flinchErrorDeg": 4,
"meleeReactionS": 0.3
},
"voice": {
"fullVolumeRadiusM": 10,
"silentRadiusM": 45
}
}
9 changes: 8 additions & 1 deletion docs/adr/0002-transporte-de-rede.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
# ADR 0002 — transporte de rede e framework de sincronização

- **status**: aceita
- **status**: aceita. o transporte foi reavaliado e mantido na [adr 0025](0025-websocket-sem-udp.md),
o tamanho de sala foi revisto na [adr 0027](0027-sala-de-quatro.md), a condição de medição
foi fixada na [adr 0006](0006-condicao-de-rede-do-aceite.md), e as taxas de rede atuais estão
em [docs/rede](../rede/arquitetura-de-rede.md).
a versão pinada também foi revista em 2026-09-28: a linha 0.18 virou a estável, e os pacotes do
colyseus têm versões independentes, então `0.18.2` em todos não forma um conjunto válido. o
pino exato passou a ser `@colyseus/core` 0.18.17, `@colyseus/ws-transport` 0.18.4,
`@colyseus/sdk` 0.18.4 e `@colyseus/schema` 5.0.34, sem o meta-pacote `colyseus`.
- **data**: 2026-08-18
- **decidem**: renato, nicolas

Expand Down
2 changes: 1 addition & 1 deletion docs/adr/0003-fisica-e-controlador.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ controlador de personagem baseado em havok, já pronto na engine.
com **sub-passo por deslocamento**: quando o movimento no tick passa de uma fração do raio da
cápsula, o passo é dividido, e o número de sub-passos é o deslocamento dividido por esse
limite. as mecânicas rápidas (puxão do gancho, queda perto da terminal) caem nesse caso a
60 Hz, e é isso, e não tick rate maior, que impede atravessar parede fina.
64 Hz, e é isso, e não tick rate maior, que impede atravessar parede fina.
- **o havok fica reservado** a prop, ragdoll, destroço e qualquer coisa que não afete a
autoridade nem a predição.
- **hitscan é raycast no servidor**, contra as cápsulas dos jogadores rebobinadas para o
Expand Down
24 changes: 24 additions & 0 deletions docs/adr/0006-condicao-de-rede-do-aceite.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# ADR 0006 — condição de rede em que o aceite é medido

- **status**: aceita
- **data**: 2026-09-28
- **decide**: renato

o critério de aceite do [nfr.md](../nfr.md), p99 do erro posicional abaixo de 0,10 m, não dizia
em que rede é medido, e a rede da mesa da equipe é otimista. medimos em três perfis
emulados no servidor, e o aceite vale no perfil **wi-fi congestionado**. os outros dois são
relatados junto, para mostrar como o jogo degrada.

| perfil | rtt | perda, por direção |
|---|---|---|
| lan ideal | o da rede do estande, sem emulação | a da rede |
| **wi-fi congestionado** | 10 ± 5 ms | 1%, aleatória |
| pior caso | 30 ± 10 ms | 3%, em rajadas de 3 pacotes em média |

## consequências

- a emulação roda no servidor, nas duas direções, com `tc netem` na saída e uma interface `ifb`
na entrada. os comandos, o modelo de perda em rajada e as grandezas registradas em cada rodada
estão em [docs/rede](../rede/arquitetura-de-rede.md).
- o jitter do netem reordena pacotes. o tcp absorve a reordenação, mas ela pesa na medição, e o
relatório declara isso.
28 changes: 28 additions & 0 deletions docs/adr/0025-websocket-sem-udp.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# ADR 0025 — websocket mantido, sem udp

- **status**: aceita
- **data**: 2026-09-22
- **decide**: renato

a rede da feira bloqueia udp, e no navegador todo transporte com datagrama depende dele:
webtransport não conecta, e webrtc cai para tcp. reavaliamos a [adr 0002](0002-transporte-de-rede.md)
e mantemos websocket direto entre cliente de jogo e servidor de jogo, porque sobre tcp é o caminho
com menos camadas e menos saltos. o ponto fraco do tcp é a perda de pacote, e por isso o estande
tem rede própria. o detalhe de rede está em [docs/rede](../rede/arquitetura-de-rede.md).

## opções descartadas

- **quic, pelo `@colyseus/h3-transport`**: não conecta sem udp e não cai sozinho para tcp. o
próprio colyseus declara esse transporte instável.
- **webrtc com sfu (livekit)**: sem udp, a mídia vai por ice-tcp e o head-of-line blocking volta,
com mais camadas. o sfu não entende o jogo, então o servidor de jogo entra como participante e
cada mensagem ganha um salto. e sai do colyseus, com o netcode escrito à mão.
- **p2p**: ninguém tem autoridade sobre o acerto. um jogador vira host, com vantagem e chance de
trapaça, ou o cliente afirma o acerto, que a adr 0002 proíbe.
- **mqtt**: pub/sub de iot sobre websocket, com um broker no meio e sem sala, estado nem rewind.

## revisão

o transporte fica atrás de uma interface do projeto. se a rede da feira liberar udp, o quic volta
a ser candidato sem reescrever o jogo. reabrir também se o critério do [nfr.md](../nfr.md) falhar
com perda de pacote simulada.
34 changes: 34 additions & 0 deletions docs/adr/0026-chat-de-voz.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# ADR 0026 — chat de voz por livekit, ao lado do colyseus

- **status**: aceita
- **data**: 2026-09-23
- **decide**: renato

voz precisa de cancelamento de eco, supressão de ruído, codec de voz, jitter buffer e ocultação
de perda, e aceita atraso e perda que o tiro não aceita. usamos um servidor de mídia livekit na
rede do estande, ao lado do servidor de jogo. o webrtc do navegador já faz o tratamento de áudio,
e o livekit só repassa os fluxos de áudio, sem entender o jogo. nenhum dado de jogo passa por
ele. o detalhe de rede está em [docs/rede](../rede/arquitetura-de-rede.md).

## opções descartadas

- **voz pelo websocket do jogo**: tratamento de áudio escrito à mão, e sobre tcp um pacote
perdido congela o áudio em vez de ser disfarçado. o áudio ainda disputaria banda com o jogo.
- **webrtc p2p em malha**: sinalização e ice escritos à mão, e cada jogador envia uma cópia da
voz para cada outro.

## consequências

- o servidor de jogo decide quem está na sala de voz: ele emite o token de acesso quando o
jogador entra na sala e o remove quando ele sai.
- o navegador só libera o microfone em https, então a página e o canal de jogo também passam a
exigir tls, com certificado válido na rede do estande.
- a voz por proximidade é calculada no cliente, com as posições que o servidor de jogo já envia.
um cliente adulterado pode ouvir quem está longe. se isso importar, o servidor de jogo passa a
decidir quem ouve quem.
- a voz é o primeiro corte se a data apertar, e o jogo roda inteiro com ela desligada.

## revisão

reabrir se o servidor de mídia não passar pela rede do estande, se o áudio pesar no quadro, ou
se jogar lado a lado no estande tornar a voz desnecessária.
18 changes: 18 additions & 0 deletions docs/adr/0027-sala-de-quatro.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# ADR 0027 — sala de quatro jogadores

- **status**: aceita
- **data**: 2026-09-28
- **decide**: renato

o [gdd](../gdd.md) previa oito jogadores por sala. reduzimos para quatro. com patches de estado a
64 hz, cada jogador a mais aumenta o histórico de rewind, a cpu de cada tick e a banda de descida
de todos os clientes, porque o patch cresce com o número de jogadores. a arquitetura de rede não
depende desse número: o orçamento em [docs/rede](../rede/arquitetura-de-rede.md) está em função
de N.

## consequências

- o gdd e o [nfr.md](../nfr.md) mudam de oito para quatro.
- o item 4 da fila de corte da [adr 0002](0002-transporte-de-rede.md), reduzir para quatro
jogadores, deixa de existir porque já foi aplicado.
- o level design, pensado para oito, precisa ser revisto para o mapa não ficar vazio.
28 changes: 28 additions & 0 deletions docs/adr/0028-endereco-da-sala.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# ADR 0028 — endereço da sala em formato ipv4, com fila e sala provisória

- **status**: aceita
- **data**: 2026-09-28
- **decide**: renato

cada sala é identificada por um endereço ipv4 fictício no caminho da url, como
`shobu.com/203.0.113.7`. abrir um endereço sem sala cria a sala, e os endereços gerados evitam as
faixas privadas e reservadas para não se confundir com os ips reais da rede do estande. quem abre
o endereço de uma sala cheia entra na fila dela e joga numa sala provisória até a vaga abrir. os
termos estão definidos no [CONTEXT.md](../../CONTEXT.md).

## opções descartadas

- **código curto alfanumérico**: funciona igual, mas perde a identidade de rede do jogo.
- **esperar vaga parado**: contradiz o pilar 2 ([pillars.md](../pillars.md)), que exige controle
do personagem em menos de cinco segundos depois do clique no link.
- **mandar para outra sala sem fila**: cumpre o pilar, mas separa quem abriu o link de quem o
mandou.

## consequências

- o id de sala do colyseus só aceita letras, números, `_` e `-`, então o servidor de jogo mantém
um mapa de endereço para sala.
- um caminho com ponto, como `/203.0.113.7`, costuma ser tratado como arquivo pelo servidor de
página. a rota precisa devolver a página para esses caminhos.
- ir da sala provisória para a pedida é sair e entrar de novo no servidor de jogo e no servidor
de mídia, no meio de uma partida.
8 changes: 4 additions & 4 deletions docs/gdd.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ o loop é: nascer, buscar linha de tiro, matar ou morrer, voltar. kill vale 100

cada mapa do catálogo segue a mesma planta: sem paredes em volta — a cidade continua além dele — e cada um tem três camadas: becos embaixo (distância curta), passarelas no meio (rota principal de trânsito, uso mais frequente do gancho), telhados em cima (linhas de tiro longas, jogador exposto). cada camada tem pelo menos duas rotas de subida que não exigem gancho, e toda linha de tiro longa tem cobertura intermediária. oito pontos de spawn distribuídos pelas três camadas. o que delimita a partida é a área de jogo, um retângulo no chão do tamanho do piso da arena: quem sai dela vê na tela o aviso "VOLTE PARA A ARENA" com uma contagem de cinco segundos, e se não voltar a tempo morre e reaparece num spawn. voltar zera a contagem.

o escopo entregável é um catálogo de dois mapas, um personagem, um sniper, uma faca, um gancho e um modo, para até quatro jogadores. os mapas são o Neon-kaidō, rua-cânion cyberpunk de noite e o padrão, e o Ichiban-gai, na hora azul ([adr 0023](adr/0023-catalogo-de-mapas-e-neon-kaido.md)). fora do escopo entregável: voip, times, mapas além desses dois, progressão, loja funcional, matchmaking por habilidade e anti-cheat. voip entra como expansão, depois da base jogável.
o escopo entregável é um catálogo de dois mapas, um personagem, um sniper, uma faca, um gancho e um modo, para até quatro jogadores. os mapas são o Neon-kaidō, rua-cânion cyberpunk de noite e o padrão, e o Ichiban-gai, na hora azul ([adr 0023](adr/0023-catalogo-de-mapas-e-neon-kaido.md)). a voz por proximidade entre os jogadores da sala também entra, construída depois da base jogável e primeira a ser cortada se a data apertar ([adr 0026](adr/0026-chat-de-voz.md)). fora do escopo entregável: times, mapas além desses dois, progressão, loja funcional, matchmaking por habilidade e anti-cheat.

# What sets this project apart?
- roda no navegador, sem instalação e sem conta obrigatória
Expand Down Expand Up @@ -162,7 +162,7 @@ até quatro jogadores em free-for-all livre de cinco minutos numa arena vertical
- feedback de tiro: hitmarker no acerto, zunido de bala no quase-acerto.
- fim de partida ([adr 0022](adr/0022-fim-de-partida-mvp-e-voto.md)): o relógio zera e a arena congela — ninguém anda, atira nem pontua, a luneta fecha —, e o narrador diz o fim por três segundos. depois, dez segundos de tela de mvp: o nome e a skin do melhor jogador da partida (o corpo dele num pódio de neon sobre o mapa, e o retrato no cartão), os números dele — pontos, abates, mortes, melhor sequência e medalhas — e o placar final da sala. o mvp é quem tem mais pontos; o desempate é mais abates, depois menos mortes, depois quem chegou antes aos pontos. no óculos, a mesma tela no visor, com o retrato.
- voto do próximo mapa: durante os dez segundos da tela de mvp, os mapas do catálogo e aleatório. vota-se pelo número (1, 2, 3), pelas setas e enter, pelo mouse (mexer anda o foco, o clique vota), pelo d-pad e A no controle, ou tocando na opção. os bots votam também, cada um uma vez, da semente da partida, e o voto deles aparece chegando, com o nome. vence a maioria; empate é sorteio da semente entre os empatados; aleatório vencendo sorteia um mapa. o mapa vencedor fica na tela por um segundo e meio, e a partida seguinte começa sozinha: no mesmo mapa, na hora, com placar, relógio, medalhas e spawns zerados; em outro, a página recarrega no mapa novo e um clique (ou enter, ou A) entra direto, com as mesmas regras. esc ou B na tela de mvp voltam ao menu. saída fechando a aba.
- fora do escopo entregável: voip, times, modos alternativos, mapas além dos dois do catálogo ([adr 0023](adr/0023-catalogo-de-mapas-e-neon-kaido.md)), progressão, loja funcional, matchmaking por habilidade, anti-cheat.
- fora do escopo entregável: times, modos alternativos, mapas além dos dois do catálogo ([adr 0023](adr/0023-catalogo-de-mapas-e-neon-kaido.md)), progressão, loja funcional, matchmaking por habilidade, anti-cheat.

# Assets Needed

Expand Down Expand Up @@ -224,7 +224,7 @@ a equipe não tem especialista em arte. a maior parte dos assets vem de pacotes
- Ambient Scripts (Runs in the background)
- servidor autoritativo: estado da sala, validação de disparo, respawn, cronômetro, placar
- sincronização de estado entre servidor e clientes
- lobby: entrada por link, apelido, alocação em sala de até quatro jogadores
- matchmaking: pareamento pelo endereço da sala na url ([adr 0028](adr/0028-endereco-da-sala.md)), nome de perfil, salas de até quatro jogadores
- killfeed, placar e transição de fim de partida
- NPC Scripts
- bots completam a sala quando faltam jogadores ([adr 0016](adr/0016-bots-no-nucleo.md)): andam pelo mapa com a mesma física, atiram com a mesma arma e pontuam pelo mesmo placar. dificuldade em `config/gameplay.json`, seção `bots`. no treino, andam sem atirar.
Expand Down Expand Up @@ -259,7 +259,7 @@ a equipe não tem especialista em arte. a maior parte dos assets vem de pacotes
- implementação do cliente, 13/10
- expansões e feira
- Time Scale: 13/10 a 19/11
- expansão 1, voip aberto, 20/10 e 27/10
- expansão 1, voz por proximidade, 20/10 e 27/10
- expansão 2, a definir, 03/11 e 10/11
- integração com a feira de jogos, 17/11
- feira de jogos, 19/11
Loading
Loading