diff --git a/CONTEXT.md b/CONTEXT.md
new file mode 100644
index 00000000..b207b199
--- /dev/null
+++ b/CONTEXT.md
@@ -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
diff --git a/README.md b/README.md
index 28b36df7..1bd8e140 100644
--- a/README.md
+++ b/README.md
@@ -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
diff --git a/config/gameplay.json b/config/gameplay.json
index fc5fc84b..dd0d1147 100644
--- a/config/gameplay.json
+++ b/config/gameplay.json
@@ -1,7 +1,7 @@
{
"simulation": {
"tickHz": 60,
- "snapshotHz": 30,
+ "patchHz": 60,
"inputSendHz": 60
},
"movement": {
@@ -143,5 +143,9 @@
"airborneErrorDeg": 5,
"flinchErrorDeg": 4,
"meleeReactionS": 0.3
+ },
+ "voice": {
+ "fullVolumeRadiusM": 10,
+ "silentRadiusM": 45
}
}
diff --git a/docs/adr/0002-transporte-de-rede.md b/docs/adr/0002-transporte-de-rede.md
index 44d51852..302332fa 100644
--- a/docs/adr/0002-transporte-de-rede.md
+++ b/docs/adr/0002-transporte-de-rede.md
@@ -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
diff --git a/docs/adr/0003-fisica-e-controlador.md b/docs/adr/0003-fisica-e-controlador.md
index b1f14149..911ee978 100644
--- a/docs/adr/0003-fisica-e-controlador.md
+++ b/docs/adr/0003-fisica-e-controlador.md
@@ -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
diff --git a/docs/adr/0006-condicao-de-rede-do-aceite.md b/docs/adr/0006-condicao-de-rede-do-aceite.md
new file mode 100644
index 00000000..6787a88d
--- /dev/null
+++ b/docs/adr/0006-condicao-de-rede-do-aceite.md
@@ -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.
diff --git a/docs/adr/0025-websocket-sem-udp.md b/docs/adr/0025-websocket-sem-udp.md
new file mode 100644
index 00000000..b54316f0
--- /dev/null
+++ b/docs/adr/0025-websocket-sem-udp.md
@@ -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.
diff --git a/docs/adr/0026-chat-de-voz.md b/docs/adr/0026-chat-de-voz.md
new file mode 100644
index 00000000..d8cbd80d
--- /dev/null
+++ b/docs/adr/0026-chat-de-voz.md
@@ -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.
diff --git a/docs/adr/0027-sala-de-quatro.md b/docs/adr/0027-sala-de-quatro.md
new file mode 100644
index 00000000..307d8bce
--- /dev/null
+++ b/docs/adr/0027-sala-de-quatro.md
@@ -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.
diff --git a/docs/adr/0028-endereco-da-sala.md b/docs/adr/0028-endereco-da-sala.md
new file mode 100644
index 00000000..ce61e0bb
--- /dev/null
+++ b/docs/adr/0028-endereco-da-sala.md
@@ -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.
diff --git a/docs/gdd.md b/docs/gdd.md
index 8d372b64..c07e523f 100644
--- a/docs/gdd.md
+++ b/docs/gdd.md
@@ -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
@@ -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
@@ -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.
@@ -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
diff --git a/docs/nfr.md b/docs/nfr.md
index 962f0230..cb75b256 100644
--- a/docs/nfr.md
+++ b/docs/nfr.md
@@ -12,7 +12,7 @@ o dono deste documento é quem estiver com o protótipo da semana 1 na mão.
| requisito | valor | de onde sai |
|---|---|---|
| tempo do clique no link ao controle do personagem | **≤ 5 s** | [pilar 2](pillars.md), literal |
-| jogadores por sala | **8** | [gdd](gdd.md) |
+| jogadores por sala | **4** | [adr 0027](adr/0027-sala-de-quatro.md) |
| p99 do erro posicional de validação | **< 0.10 m** | cerca de ¼ do raio de uma cápsula de escala humana |
estes três são critério de aceite. os outros números do projeto ou são conta derivada deles, ou
@@ -33,24 +33,15 @@ personagem que este jogo vá usar.
[adr 0002](adr/0002-transporte-de-rede.md). se ele estourar, o problema é o rewind, não o tick
rate.
-## em que rede os três são medidos: ninguém sabe
+## em que rede os três são medidos
-**esta é a lacuna mais grave do projeto, e não é uma medição de código.** os três requisitos acima
-só significam algo contra uma rede, e a rede que importa é a do local da feira, que ninguém mediu.
-rtt, perda de pacote e banda disponível ali são desconhecidos.
-
-qualquer número que a gente escrevesse aqui (150 ms de rtt, 2% de perda, 20 Mbps de descida) seria
-plausível e inventado, e pior que inventado: a janela de rewind e o orçamento de banda **derivam**
-dele, então o chute se propagaria para decisões de arquitetura disfarçado de conta.
-
-o que dá para afirmar hoje:
+o jogo roda num servidor local, na rede própria do estande, e não depende da rede do local da
+feira ([adr 0025](adr/0025-websocket-sem-udp.md)). as condições são conhecidas porque são as
+nossas, e a [adr 0006](adr/0006-condicao-de-rede-do-aceite.md) fixa os três perfis emulados em
+que os requisitos são medidos.
- **a máquina de teste é uma das duas da equipe**, porque não há orçamento para outra. isso é
fato, não alvo.
-- **o cenário que mais provavelmente quebra a demo não é o jogo, é a rede do local.**
-- **medir a rede da feira, ou decidir não depender dela**, é o que destrava esta seção. as duas
- saídas vão para a adr 0006: ou alguém mede o local antes de novembro, ou o plano passa a ser
- servidor local na mesma lan, e aí as condições são conhecidas porque são as nossas.
## números de arquitetura já fixados
@@ -59,15 +50,15 @@ dependem delas.
| grandeza | valor | onde foi decidido |
|---|---|---|
-| tick de simulação | 60 Hz | [adr 0002](adr/0002-transporte-de-rede.md) |
-| taxa de snapshot do servidor | 30 Hz | [adr 0002](adr/0002-transporte-de-rede.md) |
-| taxa de envio de input do cliente | 60 Hz | [adr 0002](adr/0002-transporte-de-rede.md) |
+| tick | 60 Hz | [docs/rede](rede/arquitetura-de-rede.md) |
+| taxa de patches de estado do servidor | 60 Hz | [docs/rede](rede/arquitetura-de-rede.md) |
+| taxa de envio de input do cliente | 60 Hz | [docs/rede](rede/arquitetura-de-rede.md) |
-**a janela máxima de rewind não tem valor ainda, tem fórmula.** ela cobre o quanto o cliente
-enxerga o mundo atrás do servidor, que é *meio rtt* mais o *buffer de interpolação de snapshot*
-(um a dois intervalos de snapshot), mais folga para jitter e um snapshot perdido. o rtt é a
-incógnita da seção anterior, então o número sai no dia em que a rede for conhecida. janela maior
-custa histórico por sala; menor começa a descartar disparo legítimo de quem joga com rtt alto.
+**a janela máxima de rewind é de 150 ms.** ela cobre o quanto o cliente enxerga o mundo atrás do
+servidor: o buffer de interpolação, meio rtt, um intervalo de patch e uma retransmissão, no pior
+perfil da [adr 0006](adr/0006-condicao-de-rede-do-aceite.md). a conta está em
+[docs/rede](rede/arquitetura-de-rede.md). janela maior custa histórico por sala e deixa um cliente adulterado voltar
+mais no tempo; menor começa a descartar disparo legítimo.
**orçamento de quadro** é aritmética da taxa do monitor, não requisito: é 1/taxa, uns 6,9 ms a
144 Hz e 4,2 ms a 240 Hz. serve para saber quanto de um quadro o tick e o replay de reconciliação
@@ -80,10 +71,10 @@ estas não são alvos, são decisões de implementação, e valem antes de qualq
- **zero alocação no caminho quente.** a causa mais provável de engasgo em javascript é o coletor
de lixo, e engasgo é pior de jogar que fps médio baixo.
- **o critério de fluidez é o percentil 1, não a média.** e ele é medido nos quadros que carregam
- passo de simulação, porque um quadro que só desenha é barato e esconde o problema.
+ tick, porque um quadro que só desenha é barato e esconde o problema.
- **estado de rede quantizado**, posição e ângulo em inteiro, não float de 32 bits cru do schema
- do colyseus. 30 snapshots por segundo para 8 jogadores é da ordem de 60 bytes por jogador por
- snapshot, e float cru não cabe nessa ordem de grandeza.
+ do colyseus. 64 patches de estado por segundo para 4 jogadores é da ordem de 60 bytes por jogador por
+ patch, e float cru não cabe nessa ordem de grandeza.
- **para chegar a qualquer número com babylon**: congelar matriz de mundo da geometria estática,
congelar material, instanciar prop repetido, manter contagem de draw call baixa, e nada de pbr
ou sombra dinâmica ([adr 0004](adr/0004-pipeline-de-assets.md)).
@@ -99,7 +90,7 @@ e a primeira rodada é entregável da semana 1.
| download inicial total (js, mapa, textura, áudio) | o resto do peso da página, que compete com o bundle pelos mesmos 5 s | **medido em 12/10** — ver [abaixo](#download-do-boot-padrão-medido-em-1210); o alvo continua sem número |
| tempo até a tela de boot | carregamento não bloqueante é o que faz os 5 s parecerem 5 s | semana 1 |
| percentil 1 de fps nas máquinas da equipe | é o critério de fluidez | com o greybox navegável |
-| custo do passo de simulação no cliente | quanto do orçamento de quadro o tick come | com o controlador |
+| custo do tick no cliente | quanto do orçamento de quadro o tick come | com o controlador |
| custo do replay de reconciliação | idem, e é o que cresce com rtt | com o protótipo de netcode |
| memória da aba | o navegador mata a aba antes de avisar | com o greybox |
| p99 do tick de servidor | é o que limita jogadores por sala | com o servidor |
@@ -154,7 +145,6 @@ baixam no boot mesmo para quem não as empunha; carregar só a lâmina da classe
- **quantos jogadores simultâneos a feira precisa atender.** não é medição, é decisão: depende de
quanta fila vocês aceitam no estande. ela é que define quantas salas o servidor tem que
sustentar, e sem ela "cpu por sala" não tem alvo contra o que ser comparado.
-- **hospedagem e o fallback de lan**, que vai para a adr 0006.
## o que não é requisito
diff --git a/docs/rede/arquitetura-de-rede-1-topologia.drawio.svg b/docs/rede/arquitetura-de-rede-1-topologia.drawio.svg
new file mode 100644
index 00000000..e7301f15
--- /dev/null
+++ b/docs/rede/arquitetura-de-rede-1-topologia.drawio.svg
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/docs/rede/arquitetura-de-rede-2-protocolos.drawio.svg b/docs/rede/arquitetura-de-rede-2-protocolos.drawio.svg
new file mode 100644
index 00000000..402b697f
--- /dev/null
+++ b/docs/rede/arquitetura-de-rede-2-protocolos.drawio.svg
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/docs/rede/arquitetura-de-rede-3-tiro.drawio.svg b/docs/rede/arquitetura-de-rede-3-tiro.drawio.svg
new file mode 100644
index 00000000..05336849
--- /dev/null
+++ b/docs/rede/arquitetura-de-rede-3-tiro.drawio.svg
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/docs/rede/arquitetura-de-rede-4-entrada.drawio.svg b/docs/rede/arquitetura-de-rede-4-entrada.drawio.svg
new file mode 100644
index 00000000..c1eb4609
--- /dev/null
+++ b/docs/rede/arquitetura-de-rede-4-entrada.drawio.svg
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/docs/rede/arquitetura-de-rede.drawio b/docs/rede/arquitetura-de-rede.drawio
new file mode 100644
index 00000000..8cc125e9
--- /dev/null
+++ b/docs/rede/arquitetura-de-rede.drawio
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/rede/arquitetura-de-rede.md b/docs/rede/arquitetura-de-rede.md
new file mode 100644
index 00000000..1e476891
--- /dev/null
+++ b/docs/rede/arquitetura-de-rede.md
@@ -0,0 +1,347 @@
+# Arquitetura de rede do multiplayer
+
+Arquitetura de rede do shōbu na feira. As decisões estão nas ADRs
+[0002](../adr/0002-transporte-de-rede.md), [0006](../adr/0006-condicao-de-rede-do-aceite.md), [0025](../adr/0025-websocket-sem-udp.md),
+[0026](../adr/0026-chat-de-voz.md), [0027](../adr/0027-sala-de-quatro.md) e
+[0028](../adr/0028-endereco-da-sala.md). N é o número de jogadores na sala. Nenhum valor abaixo
+depende de N ser fixo.
+
+Cada número traz a origem: **padrão** da biblioteca, **configuração** nossa ou **estimativa**
+com a premissa declarada.
+
+| Parâmetro | Valor | Origem |
+|---|---|---|
+| Tick | 60 Hz (16,7 ms) | configuração, `setFixedTimestep(simular, 60)`, o `tickHz` do jogo |
+| Envio de patches de estado | 60 Hz, no fim de cada tick | configuração (`patchHz`), enviado pelo passo fixo com `patchRate = null`; o padrão é um timer de 20 Hz |
+| Envio de input | 60 Hz | configuração: um input por tick do cliente |
+| Consumo de input no servidor | um tick de personagem por input, com balde de 8 créditos (133 ms) | configuração (`INPUT_CREDIT_CAP`) |
+| Buffer de interpolação | 2 intervalos de patch, 33,3 ms | configuração (`delay` do `Predict`); o padrão é 100 ms |
+| Janela máxima de rewind | 150 ms | configuração (`maxRewindMs`); o padrão é 500 ms |
+| Heartbeat do WebSocket | ping a cada 1 s, desconecta após 2 sem resposta | configuração (`pingInterval: 1000`); o padrão é 3 s |
+| Reconexão | nenhuma | configuração: o servidor não guarda a vaga, e o cliente desliga `reconnection.enabled`, ligado por padrão no SDK |
+| Tamanho máximo de mensagem do cliente | 4 KiB | padrão do `WebSocketTransport` (`maxPayload`) |
+| Áudio | Opus, preset `speech` (24 kbps), quadro de 20 ms, RED e DTX | configuração; o padrão do `livekit-client` é o preset `music`, 48 kbps |
+| Voz por proximidade | volume cheio até 10 m, silêncio a partir de 45 m, direção por HRTF | configuração (`voice` em `config/gameplay.json`), aplicada por um `PannerNode` por jogador no cliente |
+
+O tick é o do jogo, 60 Hz (`simulation.tickHz` em `config/gameplay.json`), com o movimento, as
+armas e os bots afinados nele, e patches e input saem no mesmo ritmo. 1/60 s não é exato em
+ponto flutuante binário: uma hora de ticks soma 3600,0000000182 s, e o relógio da partida
+encerra com meio tick de folga (`endMatchOnTime`).
+
+O servidor avança cada jogador pelos inputs dele, e não pelo relógio: cada input aplicado é um
+tick daquele personagem, e sem input o personagem fica parado, inclusive no ar. É o que faz "o
+servidor aplicou N inputs" e "o cliente simulou N inputs" darem o mesmo estado, e a reconciliação
+do cliente depende disso. Cada tick do servidor dá um crédito ao jogador e cada input gasta um,
+com teto de 8. Um soluço de rede de até 133 ms é recuperado aplicando os inputs acumulados, e um
+cliente adulterado que mande inputs mais rápido não passa de um por tick na média.
+
+A reconciliação do cliente guarda, por número de sequência do input, o input e o estado que a
+predição produziu, até 64 inputs (pouco mais de 1 s). Quando o servidor confirma o input N
+(`input.lastProcessed` do sdk), o cliente compara a posição prevista em N com a do servidor, na
+resolução de 2 mm do fio, com um degrau de folga por eixo. Se batem, nada muda; se diferem,
+adota a posição do servidor e reaplica os inputs ainda não confirmados. A folga existe porque a
+posição adotada já vem arredondada, e o cliente segue até 1 mm fora da exata do servidor: com a
+comparação exata, a primeira correção no meio de um movimento puxava outras, e de 112 a 241 das
+cerca de 800 confirmações de 15 s viravam correção; com a folga, nenhuma, em três corridas. A predição anda com a direção arredondada a 1/127, a do fio, e
+não com a do teclado: com a direção crua, medido contra o servidor real, 258 de 506 confirmações
+viravam correção; com a do fio, nenhuma de 460, andando, virando, correndo, pulando e agachando
+por 8 s (medido a 64 Hz, com o personagem de antes dos lutadores do núcleo). Pelo mesmo motivo a
+mira que o gancho e a investida leem passa pelo arredondamento do fio antes da predição. A
+predição guarda o estado de cada input no fim do tick, depois do limite da área de jogo: no tick
+em que o prazo vence, o servidor confirma o corpo já no spawn. Guardado antes do limite, cada
+morte fora da área custava de 4 a 6 correções seguidas (54 em 15 s, a 60 Hz); depois, nenhuma, em
+três corridas de 15 s com uma morte fora da área cada.
+
+O patch sai no fim do tick da simulação, e não num timer próprio. A simulação usa acumulador
+(`setFixedTimestep`) e faz exatamente 60 ticks por segundo em média, mas o timer do Node que a
+acorda mediu 16,35 ms de período médio para 16,67 ms pedidos: em 12 de 600 intervalos (2%) ele
+acorda antes de completar um tick e não roda nenhum. Medido no cliente durante 10 s, todo patch
+traz tick novo; o intervalo entre patches tem mediana de 16,6 ms, p99 de 32,3 ms e máximo de
+33,6 ms. Com patches num timer separado, como antes, 18 de 612 patches chegavam sem tick novo. O
+buffer de interpolação de 2 intervalos (33,3 ms) cobre o intervalo normal mais o jitter do perfil
+congestionado da ADR 0006, de até 10 ms. Nos 2% de intervalos de 33 ms, e numa retransmissão TCP,
+o cliente extrapola por alguns milissegundos. O rewind grava o histórico em cada patch enviado,
+exatamente o que o cliente recebeu.
+
+## Topologia
+
+
+
+| Origem | Destino | Protocolo | Porta | Conteúdo |
+|---|---|---|---|---|
+| Cliente de jogo | Servidor de jogo | HTTPS | TCP 2567 | página, matchmaking |
+| Cliente de jogo | Servidor de jogo | WSS | TCP 2567 | input, patches de estado, eventos, token de voz |
+| Cliente de voz | Servidor de mídia | WSS | TCP 7880 | sinalização |
+| Cliente de voz | Servidor de mídia | RTP/SRTP | UDP 7882, fallback ICE-TCP 7881 | áudio Opus |
+| Servidor de jogo | Servidor de mídia | TCP, loopback | TCP 7890 | sinalização repassada da 7880 |
+| Servidor de jogo | Servidor de mídia | Twirp sobre HTTP, loopback | TCP 7890 | `RemoveParticipant` |
+
+*Tabela 1. Conexões de rede. As portas são padrões de Colyseus e LiveKit, exceto UDP 7882,
+que é o mux UDP configurado em `rtc.udp_port`, e TCP 7890, a porta do LiveKit (`port`) atrás do
+repasse. Sem ele, o LiveKit usa a faixa UDP 50000 a
+60000.*
+
+Toda conexão que sai do navegador é cifrada: TLS no jogo e na sinalização, DTLS-SRTP na mídia.
+O navegador só libera o microfone em contexto seguro, e uma página HTTPS não abre `ws://`. O
+`WebSocketTransport` do Colyseus cria um servidor HTTP sem TLS, então o servidor de jogo
+recebe um `https.Server` com o certificado e a chave em PEM, indicados por `SHOBU_TLS_CERT` e
+`SHOBU_TLS_KEY`. HTTPS e WSS dividem a porta TCP 2567: o WebSocket começa como requisição HTTP
+com `Upgrade` sobre a mesma conexão TLS. Sem as duas variáveis, o servidor fala HTTP puro, para
+o desenvolvimento em `localhost`.
+
+O mesmo servidor entrega a página. O caminho da sala tem pontos, como `/203.0.113.7`, e um
+servidor estático o procuraria como arquivo. Todo caminho que é endereço de sala devolve o
+`index.html`, e o cliente lê o endereço do caminho.
+
+O servidor de mídia roda fora do modo `development` e com chaves próprias: as chaves desse
+modo são públicas.
+O servidor de jogo lê a chave em `LIVEKIT_API_KEY` e `LIVEKIT_API_SECRET`. Sem as duas, ele não
+emite token, e o jogo roda sem voz. O LiveKit só tem TLS no TURN, e não na porta de
+sinalização. O servidor de jogo escuta a TCP 7880, termina o TLS com o mesmo certificado e
+repassa os bytes ao LiveKit na TCP 7890 do loopback, sem interpretar o HTTP: o `Upgrade` e o
+WebSocket passam inteiros. Por isso os dois processos dividem o loopback, e num cluster
+Kubernetes o LiveKit roda no mesmo pod do servidor de jogo. A TCP 7890 não é aberta no
+firewall.
+
+O servidor roda em Linux nativo ou Windows. No WSL2 em modo NAT, o encaminhamento de portas do
+Windows (`netsh interface portproxy`) só cobre TCP, e a voz ficaria restrita ao ICE-TCP.
+
+## Pilha de protocolos
+
+
+
+O canal de jogo usa TCP porque a rede da feira bloqueia UDP. Sem UDP, todo transporte do
+navegador termina em TCP, e o WebSocket direto ao servidor tem menos camadas e menos saltos. A
+lib `ws`, usada pelo Colyseus, desativa o algoritmo de Nagle em cada socket
+(`socket.setNoDelay()`), e o `WebSocketTransport` desativa `permessage-deflate`. Cada patch sai
+no instante em que é gerado, sem agregação nem compressão.
+
+A fragilidade do TCP é a perda de pacote: um segmento perdido retém os seguintes até a
+retransmissão (head-of-line blocking). Num fluxo de 60 pacotes por segundo, o segmento seguinte
+chega 16,7 ms depois, o receptor responde com SACK, e o RACK-TLP do Linux (RFC 8985) marca a
+perda e retransmite. A recuperação típica leva um intervalo entre pacotes mais um RTT, cerca de
+20 ms na LAN. No pior caso, com perdas seguidas, ela espera o RTO mínimo do Linux, 200 ms. É
+esse caso que o critério de aceite mede com perda simulada. A defesa contra a perda é a rede
+própria do estande: Wi-Fi de 5 GHz só para o jogo e o servidor no cabo.
+
+A mídia de voz usa WebRTC porque o áudio tolera atraso e perda. O RED (RFC 2198) repete o quadro
+anterior em cada pacote, e uma perda isolada é reconstruída sem retransmissão.
+
+## Orçamento de banda
+
+Por pacote, o canal de jogo soma IPv4 (20 B), TCP com timestamps (32 B), registro TLS 1.3
+(22 B: cabeçalho de 5, tipo de 1, tag AEAD de 16) e cabeçalho WebSocket (6 B do cliente, com
+máscara; 4 B do servidor para cargas de 126 B a 64 KiB). Premissas: input de 32 B e patch de
+60 B por jogador, a ordem de grandeza do estado quantizado em [nfr.md](../nfr.md).
+
+A mídia de voz soma IPv4 (20 B), UDP (8 B), RTP com extensões (cerca de 24 B) e tag SRTP (16 B
+com AES-GCM, 10 B com HMAC-SHA1-80). Um quadro Opus de 20 ms a 24 kbps tem 60 B, e o RED leva a
+carga a 125 B.
+
+| Fluxo | Bytes por pacote | Pacotes/s | Banda |
+|---|---|---|---|
+| Input, cliente → servidor | 32 + 80 = 112 | 60 | 53,8 kbps |
+| Patches, servidor → cliente | 60N + 78 | 60 | 480·(60N + 78) bps; 152,6 kbps com N = 4 |
+| Tiros, servidor → cliente | 129 + 78 = 207 | até N/1,3 | até 1274·N bps; 5,1 kbps com N = 4 |
+| Áudio de um jogador falando (um fluxo RTP) | 125 + 68 = 193 | 50 | 77,2 kbps |
+
+*Tabela 2. Banda por cliente. Camada de enlace, ACKs puros do TCP e RTCP ficam fora da conta.*
+
+A mensagem de tiro tem 129 B medidos na saída do Colyseus: o tipo, o id de 9 caracteres e seis
+coordenadas em float64 do MessagePack. Cada jogador atira no máximo uma vez a cada 1,3 s, o ciclo
+do ferrolho (`boltCycleS`), e a mensagem sai no próprio segmento, sem esperar o patch.
+
+Cada jogador que fala gera um fluxo de áudio. A fala é push-to-talk, na tecla T: o V é o corpo a corpo. Soltar a tecla
+silencia a faixa (`MediaStreamTrack.enabled = false`) sem desfazer a publicação, e o servidor de
+mídia deixa de repassá-la. O enviador continua codificando silêncio, que o DTX reduz a um quadro
+de ruído de conforto a cada 400 ms. Um cliente recebe no máximo N − 1 fluxos, um por jogador falando ao mesmo
+tempo, e o servidor de mídia emite no máximo N(N − 1). Com o preset padrão `music`, cada fluxo
+passaria de 77 para 125 kbps.
+
+No Wi-Fi, o limite prático é a taxa de pacotes, não a banda: cada quadro paga contenção e
+preâmbulo independentemente do tamanho. Um cliente envia até 110 pacotes/s e recebe até
+60 + 50(N − 1).
+
+## Sequência de um disparo
+
+
+
+O cliente aplica o próprio disparo sem esperar a rede (mensagem 1), e o servidor decide o
+acerto (mensagens 3 a 5). Cada input leva um `renderTime`: o instante que o cliente exibia, no
+relógio do servidor, sincronizado por `room.clock`. O SDK o calcula como o relógio do servidor
+menos o buffer de interpolação e meio RTT, e o buffer só entra se o cliente o declarar em
+`room.input({ renderDelay })`. Na mensagem 4, `rewind.lastSeenBy` limita esse instante à janela
+de histórico e rebobina a posição dos outros jogadores até ele, interpolando entre patches. O
+servidor não estima o atraso do atirador: o cliente informa o que viu, e o servidor só aceita
+instantes dentro da janela.
+
+O tiro do servidor é o mesmo raio que o cliente previu: sai do olho do personagem no tick do
+input, com a mira quantizada do fio, e a dispersão sem luneta é sorteada com a semente do contador
+de tiros. O jogador atingido morre, e o campo `vivo` do patch leva a morte aos outros clientes
+(mensagens 6a e 6b). Morto, o input dele é consumido sem mover nem atirar, e o respawn é do
+servidor: o cliente renasce onde o patch diz, quando o `vivo` volta. Toda kill da sala, de tiro ou
+de lâmina, vai para todos os clientes na mensagem `match.kill`, com o `KillEvent` do núcleo: quem
+atirou, quem morreu, a arma, a distância, a altura do acerto na cápsula rebobinada, a luneta e se
+estava no ar. Cada cliente passa a mesma lista, na ordem que o TCP garante, pelo mesmo
+`recordKill`, e chega ao mesmo placar e às mesmas medalhas sem o servidor mandar placar. A
+mensagem chega um RTT e até um tick depois do disparo. Só o hitmarker não a espera: acende no
+quadro em que o raio previsto acerta alguém, e se o servidor discordar não há kill, placar nem
+medalha. Placar, medalhas e killfeed esperam a mensagem.
+
+Todo tiro da sala, de humano ou de bot, também vai para todos, na mensagem `match.shot`: quem
+atirou, de onde, onde o raio parou e se parou num combatente. Cada cliente desenha o rastro, o
+clarão e o som na vaga de quem atirou; o atirador ignora o próprio, que a predição já desenhou.
+
+O golpe de lâmina é validado contra as posições de agora, sem rewind: o alcance é de cerca de
+2 m, e a vítima anda poucos centímetros em meio RTT. A morte por sair da área de jogo vai na
+mensagem `match.boundary-death`, e o servidor põe o jogador no mesmo spawn que a predição escolhe,
+o do rodízio por mortes.
+
+Medido contra o servidor real, com o alvo correndo a 7,5 m/s perpendicular à linha de tiro, a
+52 m, e o atirador mirando no centro interpolado com buffer de 100 ms (0,75 m atrás da posição
+real, mais que o raio de 0,4 m da cápsula): 20 acertos em 20 disparos com o `renderDelay`
+declarado, e 2 em 20 sem ele.
+
+A janela de 150 ms cobre o buffer de interpolação (33,3 ms), meio RTT do pior perfil da ADR 0006
+(até 20 ms), um intervalo de patch (16,7 ms) e uma retransmissão TCP (cerca de 35 ms nesse
+perfil), somando 105 ms. Como o `renderTime` vem do cliente, a janela também limita quanto um
+cliente adulterado pode voltar no tempo para acertar quem já saiu da linha de tiro.
+
+| Componente | Latência | Origem |
+|---|---|---|
+| Movimento e disparo do próprio jogador | 0 ms | predição local |
+| Ida e volta no Wi-Fi da LAN | 2 a 10 ms | estimativa, a medir |
+| Espera até o próximo tick do servidor | até 16,7 ms | 60 Hz |
+| Buffer de interpolação dos outros jogadores | 33,3 ms | 2 intervalos de patch |
+| Recuperação de um segmento TCP perdido | cerca de 20 ms; até 200 ms | RACK-TLP; RTO mínimo do Linux |
+
+*Tabela 3. Orçamento de latência na LAN.*
+
+O critério de aceite é o p99 do erro posicional abaixo de 0,10 m ([nfr.md](../nfr.md)), medido
+no perfil Wi-Fi congestionado da [ADR 0006](../adr/0006-condicao-de-rede-do-aceite.md).
+
+## Bots e jogadores na sala
+
+Os bots completam a sala ([ADR 0016](../adr/0016-bots-no-nucleo.md)) e rodam no servidor, com as
+mesmas funções do núcleo que o jogador usa: arma, tiro, golpe e corpo, num tick fixo, depois dos
+inputs dos humanos. Um humano que entra toma a vaga de um bot, e quem sai a devolve
+(`botSeatsFor`); a fila da sala conta só humanos. Para o cliente, um bot é mais um jogador: o
+patch de estado o traz como traz um humano, e as kills dele chegam pela mesma `match.kill`. O
+tiro do humano acerta o bot pelo mesmo rewind que acerta outro humano.
+
+O servidor simula o mesmo mapa que o cliente desenha: colisão, spawns, área de jogo e grafo dos
+bots moram no núcleo (`ArenaLayout`), e o cliente só acrescenta arte, céu, névoa e luz. Cada
+sala tem o mapa de quem a criou: o pedido de entrada leva o mapa que a cena subiu, e o servidor
+tem um tipo de sala por mapa do catálogo (`arena:neon-kaido`, `arena:ichiban-gai`); mapa fora do
+catálogo vira o padrão. Quem entra numa sala que já existe recebe o mapa dela no assento e, com
+outro mapa subido, recarrega a página nele. A lâmina
+da classe vai do cliente ao servidor na mensagem `loadout.melee`, porque katana e faca andam e
+investem diferente, e lâmina diferente nos dois lados seria predição corrigida. O nome de perfil
+vai na mensagem `profile.callsign`, na entrada e a cada partida que o troca. O servidor o limpa
+com a mesma regra do menu (`profileNameLabel`, no núcleo), porque o que ele repassa chega à tela
+dos outros clientes, e o põe no estado da sala; o bot leva o próprio apelido no mesmo campo.
+
+No cliente, cada jogador da sala ocupa uma vaga de bot da sessão, como fantoche: o corpo é o do
+patch, interpolado, sem cérebro local. É assim que o avatar, o killfeed, o placar e a tela de
+morte valem online sem código de desenho próprio. Sem servidor, ou até a sala conectar, a arena
+joga sozinha com os bots locais.
+
+## Entrada na partida e na voz
+
+
+
+O servidor de jogo decide quem está em cada sala. O cliente pede a entrada pelo endereço da URL
+em `POST /rooms/enter` (mensagem 2), e a resposta é um assento já reservado numa sala, que o
+cliente ocupa pelo WebSocket (mensagem 3). O servidor mantém um diretório de endereço para sala e
+serializa as entradas: o `joinOrCreate` do Colyseus foi descartado porque, com a sala cheia, ele
+cria uma segunda sala com o mesmo endereço. Com a sala pedida cheia, o jogador entra na fila dela
+e recebe o assento numa sala provisória, com vaga ou nova. Quando abre vaga na sala pedida, o
+servidor reserva o assento para o primeiro da fila e o envia pela mensagem `room.move` da sala
+provisória. O cliente sai dela e ocupa o assento, e a URL passa a mostrar o endereço da sala em
+que ele joga. A reserva conta como ocupação da vaga, e é isso que impede quem chega depois de
+tomar a vaga de quem foi chamado. A reserva vale 15 s. O Colyseus não avisa quando ela vence, então o servidor
+confere a fila de novo nesse instante, e quem foi chamado e não veio não segura a vaga.
+
+As rotas padrão `/matchmake` do Colyseus continuam no ar. Uma sala que o matchmaking não pediu é
+recusada ao nascer, e o `onAuth` da sala só admite assentos reservados pela rota de entrada:
+pular a fila por elas não funciona.
+
+O servidor de mídia só aceita quem traz um
+token assinado com a chave dele (mensagem 4), com sala igual ao `roomId`, identidade igual ao
+`sessionId` e permissão de publicar apenas o microfone. O servidor de jogo emite o token logo
+depois da entrada, na mensagem `voice.ticket`, com validade de 60 s: ele só serve para entrar, e
+o servidor de mídia o renova na conexão. Antes do áudio, o cliente faz as verificações de conectividade do
+ICE e o handshake DTLS que deriva as chaves do SRTP (mensagem 7). Quando o jogador sai, o
+servidor de jogo o remove da sala de voz (mensagem 10).
+
+Não há reconexão: quando a conexão cai, o jogador sai da sala. Numa queda limpa, o TCP fecha na
+hora. Quando o Wi-Fi some sem aviso, nada chega ao servidor, e o heartbeat detecta a queda em 2
+a 3 s. Até lá, o personagem continua no mundo, parado.
+
+## Formato das mensagens
+
+Estado e input são quantizados em inteiros ([nfr.md](../nfr.md)). O erro máximo de posição, 1 mm,
+fica 100 vezes abaixo do critério de aceite. A mira viaja como direção, e não como yaw e pitch: voltar
+de ângulo para vetor exige seno e cosseno, que o núcleo proíbe porque o IEEE não fixa o último bit
+deles, e a raiz quadrada da normalização é exata. O formato está em `wireFormat.ts`, no núcleo.
+
+| Mensagem | Campo | Tipo | Resolução |
+|---|---|---|---|
+| Input | direção desejada, x e z | `int8` cada | 1/127 |
+| Input | corrida, pulo, agachar, gancho, disparo, luneta, mira de segurar, recarga, corpo a corpo, arma principal | bits de um `uint16` | |
+| Input e snapshot | direção de mira, x, y e z | `int16` cada | 1/32767 por componente, cerca de 0,0017° |
+| Snapshot | posição, x, y e z | `int16` cada | 2 mm; cobre ±65,534 m |
+| Snapshot | postura: em pé, agachado, slide, no ar | `uint8` | |
+| Snapshot | vivo | `boolean` | |
+| Snapshot | nome de perfil, ou o apelido do bot | `string`, até 12 caracteres | viaja só quando muda |
+
+*Tabela 4. Campos quantizados. Os mapas do catálogo têm 70 m de área de jogo e colisão até 63 m
+do centro, onde o jogador chega fora da área durante o prazo de `outOfBoundsGraceS`: a margem de
+posição é de 2,5 m. A 1 mm o `int16` cobria só ±32,767 m. A tabela vale até o schema existir no
+código; a partir daí o schema é a fonte, e ela sai ([planejamento.md](../planejamento.md)).*
+
+A reconciliação corrige só a posição: velocidade, pulos, slide, gancho e armas são os da predição,
+que roda o mesmo código do servidor sobre os mesmos inputs. Os outros clientes só interpolam
+posição e mira.
+
+O SDK envia cada input confiável com 1 B de código de protocolo e o carimbo de tempo codificado
+como diferença do anterior (1 B a 16,7 ms). O corpo, serializado pelo `@colyseus/schema`, soma
+um índice de 1 B por campo, e o input fica em cerca de 18 B. Um jogador em movimento ocupa cerca
+de 22 B por patch. As premissas da Tabela 2, 32 B e 60 B, são conservadoras.
+
+## Rede do estande
+
+| Item | Configuração | Motivo |
+|---|---|---|
+| Roteador | dedicado, rede só do jogo, sem isolamento de clientes | isolamento de clientes impede o navegador de alcançar o servidor |
+| Wi-Fi | 5 GHz, canal fixo entre 36 e 48 | fora da faixa DFS: um canal DFS que detecta radar é abandonado, e o novo canal DFS exige 60 s de escuta antes de transmitir |
+| Servidor | Ethernet, IP fixo por reserva de DHCP | o domínio aponta para um endereço que não muda |
+| DNS | o roteador resolve o domínio do projeto para o IP do servidor | a feira não depende de internet, e muitos roteadores descartam respostas públicas com IP privado, como proteção contra DNS rebinding |
+| Certificado | Let's Encrypt, desafio DNS-01, emitido antes da feira | o desafio DNS não exige o servidor exposto à internet; a validade do perfil padrão é de 90 dias |
+| Firewall do servidor | entrada em TCP 2567, 7880 e 7881, e UDP 7882 | as portas da Tabela 1 |
+
+## Medição
+
+A emulação da [ADR 0006](../adr/0006-condicao-de-rede-do-aceite.md) roda no servidor. A saída
+passa por um `netem`, e a entrada é redirecionada para uma interface `ifb` com outro `netem`.
+Cada direção recebe metade do atraso, e a perda vale por direção. Para o perfil Wi-Fi
+congestionado, com `eth0` como interface do servidor:
+
+```sh
+tc qdisc add dev eth0 root netem delay 5ms 2.5ms loss 1%
+modprobe ifb && ip link set ifb0 up
+tc qdisc add dev eth0 ingress
+tc filter add dev eth0 parent ffff: matchall action mirred egress redirect dev ifb0
+tc qdisc add dev ifb0 root netem delay 5ms 2.5ms loss 1%
+```
+
+O pior caso troca os dois `netem` por `delay 15ms 5ms loss gemodel 1% 33%`: 1% de chance de
+entrar em rajada e 33% de sair dela, cerca de 3% de perda média em rajadas de 3 pacotes.
+
+| Grandeza | Onde se mede |
+|---|---|
+| Erro posicional p99 | servidor: posição rebobinada contra a exibida pelo cliente |
+| RTT p50 e p99 | cliente: `room.clock` |
+| Intervalo de chegada dos patches p99 | cliente |
+| Retransmissões TCP | servidor: `ss -ti` |
+| Quadros com a interpolação sem patch | cliente |
diff --git a/docs/simulation-model.md b/docs/simulation-model.md
index 76586935..d0ff9bc2 100644
--- a/docs/simulation-model.md
+++ b/docs/simulation-model.md
@@ -16,26 +16,26 @@ confundir as três é o erro mais fácil de cometer aqui, então elas ficam nome
| taxa | valor | o que é |
|---|---|---|
-| **tick de simulação** | 60 Hz ([adr 0002](adr/0002-transporte-de-rede.md)) | passo **fixo** em que o estado do jogo avança. idêntico no cliente e no servidor, e é o que torna a predição reproduzível. |
+| **tick** | 64 Hz ([docs/rede](docs/rede/arquitetura-de-rede.md)) | unidade **fixa** de tempo em que o estado do jogo avança. idêntico no cliente e no servidor, e é o que torna a predição reproduzível. |
| **taxa de render** | livre | quantos quadros são desenhados. limitada pela taxa do monitor, não pelo tick. o render **interpola** entre os dois últimos estados de simulação. |
-| **taxa de snapshot** | 30 Hz ([adr 0002](adr/0002-transporte-de-rede.md)) | quantas vezes por segundo o servidor transmite estado. |
+| **taxa de patches de estado** | 64 Hz ([docs/rede](docs/rede/arquitetura-de-rede.md)) | quantas vezes por segundo o servidor envia a diferença do estado aos clientes. |
-consequência que precisa estar clara: **simulação a 60 Hz não limita o jogo a 60 fps.** num
-monitor de 240 Hz o jogo desenha 240 quadros interpolando entre os estados de 60 Hz, e o ângulo
+consequência que precisa estar clara: **simulação a 64 Hz não limita o jogo a 64 fps.** num
+monitor de 240 Hz o jogo desenha 240 quadros interpolando entre os estados de 64 Hz, e o ângulo
de visão é atualizado na taxa de render, então a mira responde a 240 Hz mesmo com a movimentação
-simulando a 60.
+simulando a 64.
-### por que 60 Hz e não 120
+### por que 64 Hz e não 128
-a 60 Hz o deslocamento por tick de uma mecânica rápida (gancho, queda) passa do raio da cápsula,
+a 64 Hz o deslocamento por tick de uma mecânica rápida (gancho, queda) passa do raio da cápsula,
o que de fato causaria atravessamento de parede fina. mas a correção certa é **sub-passo de
colisão por deslocamento**, não dobrar o tick: dobrar reduz o erro pela metade e ainda deixa
erro, enquanto o sub-passo o elimina.
e o que decide "acertei na minha tela e não morreu" é rebobinar para o **instante** do disparo
interpolando entre ticks. encaixar no tick mais próximo erra meio tick, que a velocidade de
-gancho é da ordem de meio raio de cápsula. **rewind interpolado a 60 Hz é melhor que rewind
-grosseiro a 120 Hz, e é de graça.**
+gancho é da ordem de meio raio de cápsula. **rewind interpolado a 64 Hz é melhor que rewind
+grosseiro a 128 Hz, e é de graça.**
## convenções do contrato
diff --git a/package-lock.json b/package-lock.json
index e3fa7a04..ca38b1f9 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -95,6 +95,18 @@
"node": ">=18"
}
},
+ "node_modules/@better-auth/utils": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/@better-auth/utils/-/utils-0.3.1.tgz",
+ "integrity": "sha512-+CGp4UmZSUrHHnpHhLPYu6cV+wSUSvVbZbNykxhUDocpVNTo9uFFxw/NqJlh1iC4wQ9HKKWGCKuZ5wUgS0v6Kg==",
+ "license": "MIT"
+ },
+ "node_modules/@better-fetch/fetch": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/@better-fetch/fetch/-/fetch-1.3.2.tgz",
+ "integrity": "sha512-Gs7n99b5tqUC6cQAPbV0uED3IraHB6xQbHLQ/C3l7ZFafHScOx9pQ+DYmP5blbLFShVWLqxNUlI9wi4xU/X+ow==",
+ "license": "MIT"
+ },
"node_modules/@biomejs/biome": {
"version": "2.5.11",
"resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.5.11.tgz",
@@ -258,6 +270,185 @@
"node": ">=14.21.3"
}
},
+ "node_modules/@bufbuild/protobuf": {
+ "version": "1.10.1",
+ "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-1.10.1.tgz",
+ "integrity": "sha512-wJ8ReQbHxsAfXhrf9ixl0aYbZorRuOWpBNzm8pL8ftmSxQx/wnJD5Eg861NwJU/czy2VXFIebCeZnZrI9rktIQ==",
+ "license": "(Apache-2.0 AND BSD-3-Clause)"
+ },
+ "node_modules/@colyseus/better-call": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/@colyseus/better-call/-/better-call-1.3.3.tgz",
+ "integrity": "sha512-ISJtvMx6fV0ABPQt/ljzPc4O2+mFvyVKeHKX02qBSWKR0VXOXzniAC64POea4pp8DxZTbgHoSPpXjdnNpUkeLw==",
+ "license": "MIT",
+ "dependencies": {
+ "@better-auth/utils": "^0.3.1",
+ "@better-fetch/fetch": "^1.1.21",
+ "rou3": "^0.7.12",
+ "set-cookie-parser": "^3.0.1"
+ },
+ "peerDependencies": {
+ "zod": "^4.0.0"
+ },
+ "peerDependenciesMeta": {
+ "zod": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@colyseus/clock": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/@colyseus/clock/-/clock-2.0.3.tgz",
+ "integrity": "sha512-kziqG0dcGsvSfcerHLZTY1V2PmGnZAdG5DM0T5BKQDAiYBXYoMqfzA1Wxfe3KSnyeZuP99isffkUQLEwpFYF7A==",
+ "license": "MIT"
+ },
+ "node_modules/@colyseus/core": {
+ "version": "0.18.17",
+ "resolved": "https://registry.npmjs.org/@colyseus/core/-/core-0.18.17.tgz",
+ "integrity": "sha512-SlWL+4Nf9Wqs7Ee5ApY6e6jhmvtBNArGrENr4WELPlPRZb0bIIud82O0RAfQY3Ns/iwmgev5heX0jhlqlgdXvQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@colyseus/better-call": "^1.3.1",
+ "@colyseus/greeting-banner": "^4.0.3",
+ "@colyseus/shared-types": "^0.18.3",
+ "@colyseus/timer": "^2.0.0",
+ "@standard-schema/spec": "^1.0.0",
+ "debug": "^4.3.4",
+ "msgpackr": "^2.0.1",
+ "nanoid": "^3.3.11"
+ },
+ "engines": {
+ "node": ">= 22.x"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/endel"
+ },
+ "peerDependencies": {
+ "@colyseus/auth": "^0.18.3",
+ "@colyseus/better-call": "^1.3.1",
+ "@colyseus/schema": "^5.0.8",
+ "@colyseus/ws-transport": "^0.18.4",
+ "@pm2/io": "^6.1.0",
+ "express": "^4.16.0 || ^5.0.0",
+ "zod": "^4.1.12"
+ },
+ "peerDependenciesMeta": {
+ "@colyseus/auth": {
+ "optional": true
+ },
+ "@colyseus/ws-transport": {
+ "optional": true
+ },
+ "@pm2/io": {
+ "optional": true
+ },
+ "express": {
+ "optional": true
+ },
+ "zod": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@colyseus/greeting-banner": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/@colyseus/greeting-banner/-/greeting-banner-4.0.3.tgz",
+ "integrity": "sha512-2Ned3SnWS2XoxG9I7zy2qbKsaev0aE9+VZrBgg3NrNBVcxJjRba7jscqWs89k7D7tsGCDAa5d17ac0DYviUACA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 22.x"
+ }
+ },
+ "node_modules/@colyseus/schema": {
+ "version": "5.0.34",
+ "resolved": "https://registry.npmjs.org/@colyseus/schema/-/schema-5.0.34.tgz",
+ "integrity": "sha512-66YPLYAl7l+ezlymFtvYtfEpQasRUd+TjAprEph1ieDWC/FHolAnFsYhL4yESZf4r5qMIkaGTz+4Zz1Ex0LB4A==",
+ "license": "MIT",
+ "bin": {
+ "schema-codegen": "bin/schema-codegen",
+ "schema-debug": "bin/schema-debug"
+ },
+ "peerDependencies": {
+ "typescript": ">=5.0.0"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@colyseus/sdk": {
+ "version": "0.18.4",
+ "resolved": "https://registry.npmjs.org/@colyseus/sdk/-/sdk-0.18.4.tgz",
+ "integrity": "sha512-abCF8The7tACU5IM3yNKDnLfhqZQRBgw00OXxSHS0LuXfHJWFSc8mpDUJ40e0TEQPFPpKsZKzT+cnd5jQCQXvA==",
+ "license": "MIT",
+ "dependencies": {
+ "@colyseus/better-call": "^1.3.1",
+ "@colyseus/schema": "^5.0.8",
+ "@colyseus/shared-types": "^0.18.3",
+ "msgpackr": "^2.0.1",
+ "tslib": "^2.1.0",
+ "ws": "^8.13.0"
+ },
+ "engines": {
+ "node": ">= 12.x"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/endel"
+ },
+ "peerDependencies": {
+ "@colyseus/core": "0.18.x"
+ },
+ "peerDependenciesMeta": {
+ "@colyseus/better-call": {
+ "optional": true
+ },
+ "@colyseus/core": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@colyseus/shared-types": {
+ "version": "0.18.3",
+ "resolved": "https://registry.npmjs.org/@colyseus/shared-types/-/shared-types-0.18.3.tgz",
+ "integrity": "sha512-Kye6Lmpd4RXY52Ro8NawurUKMNagkNfAdV79Dx/cdWib1LrNW4e94JOtl9HIxy4QYVdHlkxc2m5ZjcwQXekWng==",
+ "license": "MIT",
+ "dependencies": {
+ "@standard-schema/spec": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 22.x"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/endel"
+ }
+ },
+ "node_modules/@colyseus/timer": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/@colyseus/timer/-/timer-2.0.3.tgz",
+ "integrity": "sha512-75MI7LfXQncibesa7W6FKWUc8YBcVI6jwBrDXOq1TlgYQXSMRv7a5WFPBdDKkP5Pht9IWpE2hyUb8B1DPrLYqg==",
+ "license": "MIT",
+ "dependencies": {
+ "@colyseus/clock": "^2.0.0"
+ }
+ },
+ "node_modules/@colyseus/ws-transport": {
+ "version": "0.18.4",
+ "resolved": "https://registry.npmjs.org/@colyseus/ws-transport/-/ws-transport-0.18.4.tgz",
+ "integrity": "sha512-gSPEkhV9cX8QjJWJiXfkRF38ZIiWq7DbDvzkgGqa1zmdOSZBP1aaM9tnZyvcjc1OpG+AFggeehrs/QMDBEzz2g==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/ws": "^8.5.14",
+ "ws": "^8.19.0"
+ },
+ "engines": {
+ "node": ">= 22.x"
+ },
+ "peerDependencies": {
+ "@colyseus/core": "^0.18.5",
+ "express": "^4.16.0 || ^5.0.0"
+ }
+ },
"node_modules/@emnapi/runtime": {
"version": "1.11.3",
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.3.tgz",
@@ -1311,6 +1502,99 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
+ "node_modules/@livekit/mutex": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@livekit/mutex/-/mutex-1.1.1.tgz",
+ "integrity": "sha512-EsshAucklmpuUAfkABPxJNhzj9v2sG7JuzFDL4ML1oJQSV14sqrpTYnsaOudMAw9yOaW53NU3QQTlUQoRs4czw==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/@livekit/protocol": {
+ "version": "1.51.0",
+ "resolved": "https://registry.npmjs.org/@livekit/protocol/-/protocol-1.51.0.tgz",
+ "integrity": "sha512-kEDEDaFvIcp5VlYBromdSKMJMEJKfPvXPfEaJPdEz1Qgo84gZAoLF2g58CcMlVYGsjrwE5XYgQxRS5B7KuEj3w==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@bufbuild/protobuf": "^1.10.0"
+ }
+ },
+ "node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.4.tgz",
+ "integrity": "sha512-LCkGo6JDfaBhgST7UpPWgNgLINpcpabaHfyz5OBx75nUYxBsaEPxjnyNjWpeb/xBup/682QnBfRBy2/LvPutZQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@msgpackr-extract/msgpackr-extract-darwin-x64": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-3.0.4.tgz",
+ "integrity": "sha512-zExlW9zUJKZH/tOtVMttwjKa4Xm/3KcNjnE3dPN92uCktwavMxpgCA3MoJK/DOnTWsQgo224OaST27/mPNAf+w==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-3.0.4.tgz",
+ "integrity": "sha512-Tg3yX65f5GbtXLkrYEHE5oibZG9epyYWas7FogTTEJeDEF9JlXJzKgXaNhT3UXlTOeA+AfZpYZYZ0uPj7Cfquw==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm64": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-3.0.4.tgz",
+ "integrity": "sha512-dgX0P/9wGPJeHFBG+ZmhgE6bmtMt7NP5CRBGyyktpopdk/mW4POnrpQsSLtKI1dwpc+pPLuXHDh6vvskyQE/sw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@msgpackr-extract/msgpackr-extract-linux-x64": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.4.tgz",
+ "integrity": "sha512-8TNXMEjJc3QEy7R/x1INhgiU+XakDAFUzBhaz7+Rbrs8NH5UQeHQxxmzsSBJGyV6I1jW79undiQm8tOI+D+8FQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@msgpackr-extract/msgpackr-extract-win32-x64": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.4.tgz",
+ "integrity": "sha512-CmCXPQrkbwExx3j946/PtHWHbYJiCRBRDl4BlkRQcJB/YOwQxJRTpoo7aTsortjgoJ1x7opzTSxn7C+ASSLVjQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
"node_modules/@napi-rs/lzma-linux-x64-gnu": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz",
@@ -1686,6 +1970,27 @@
"resolved": "packages/core",
"link": true
},
+ "node_modules/@shobu/server": {
+ "resolved": "packages/server",
+ "link": true
+ },
+ "node_modules/@standard-schema/spec": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
+ "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==",
+ "license": "MIT"
+ },
+ "node_modules/@types/body-parser": {
+ "version": "1.19.6",
+ "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz",
+ "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/connect": "*",
+ "@types/node": "*"
+ }
+ },
"node_modules/@types/chai": {
"version": "5.2.3",
"resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz",
@@ -1697,6 +2002,16 @@
"assertion-error": "^2.0.1"
}
},
+ "node_modules/@types/connect": {
+ "version": "3.4.38",
+ "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz",
+ "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
"node_modules/@types/deep-eql": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz",
@@ -1704,6 +2019,13 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@types/dom-mediacapture-record": {
+ "version": "1.0.22",
+ "resolved": "https://registry.npmjs.org/@types/dom-mediacapture-record/-/dom-mediacapture-record-1.0.22.tgz",
+ "integrity": "sha512-mUMZLK3NvwRLcAAT9qmcK+9p7tpU2FHdDsntR3YI4+GY88XrgG4XiE7u1Q2LAN2/FZOz/tdMDC3GQCR4T8nFuw==",
+ "license": "MIT",
+ "peer": true
+ },
"node_modules/@types/estree": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
@@ -1711,6 +2033,38 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@types/express": {
+ "version": "5.0.6",
+ "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz",
+ "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/body-parser": "*",
+ "@types/express-serve-static-core": "^5.0.0",
+ "@types/serve-static": "^2"
+ }
+ },
+ "node_modules/@types/express-serve-static-core": {
+ "version": "5.1.3",
+ "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.3.tgz",
+ "integrity": "sha512-dPfW8NFiOF4wOHc7+N/QSxlY9cfSsenewGbAz8C8U/MULPd/YZ27LvJUIlzaXie7e6Ove9YunJGgC9tbHD2cKw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*",
+ "@types/qs": "*",
+ "@types/range-parser": "*",
+ "@types/send": "*"
+ }
+ },
+ "node_modules/@types/http-errors": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz",
+ "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@types/ndarray": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@types/ndarray/-/ndarray-1.1.0.tgz",
@@ -1722,12 +2076,55 @@
"version": "26.4.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-26.4.1.tgz",
"integrity": "sha512-k97ENvZWtvA6yqz5/FS6a7duDgOPEeOQOc2iKS/nY6mX6qJUKtLnWzQS+Xj6tXweyj6ZcTAK2Qecetnvi9nCLA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~8.3.0"
}
},
+ "node_modules/@types/qs": {
+ "version": "6.15.1",
+ "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.1.tgz",
+ "integrity": "sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/range-parser": {
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz",
+ "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/send": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz",
+ "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/serve-static": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz",
+ "integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/http-errors": "*",
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/ws": {
+ "version": "8.18.1",
+ "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
+ "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
"node_modules/@vitest/coverage-v8": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-5.0.0.tgz",
@@ -1818,6 +2215,19 @@
"url": "https://opencollective.com/vitest"
}
},
+ "node_modules/accepts": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
+ "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-types": "^3.0.0",
+ "negotiator": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/assertion-error": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
@@ -1847,44 +2257,244 @@
"license": "Apache-2.0",
"peer": true
},
- "node_modules/chai": {
- "version": "6.2.2",
- "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz",
- "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==",
- "dev": true,
+ "node_modules/body-parser": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz",
+ "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==",
"license": "MIT",
+ "dependencies": {
+ "bytes": "^3.1.2",
+ "content-type": "^2.0.0",
+ "debug": "^4.4.3",
+ "http-errors": "^2.0.1",
+ "iconv-lite": "^0.7.2",
+ "on-finished": "^2.4.1",
+ "qs": "^6.15.2",
+ "raw-body": "^3.0.2",
+ "type-is": "^2.1.0"
+ },
"engines": {
"node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
}
},
- "node_modules/cwise-compiler": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/cwise-compiler/-/cwise-compiler-1.1.3.tgz",
- "integrity": "sha512-WXlK/m+Di8DMMcCjcWr4i+XzcQra9eCdXIJrgh4TUgh0pIS/yJduLxS9JgefsHJ/YVLdgPtXm9r62W92MvanEQ==",
- "dev": true,
+ "node_modules/body-parser/node_modules/content-type": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz",
+ "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==",
"license": "MIT",
- "dependencies": {
- "uniq": "^1.0.0"
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
}
},
- "node_modules/detect-libc": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
- "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
- "dev": true,
- "license": "Apache-2.0",
+ "node_modules/bytes": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
+ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
+ "license": "MIT",
"engines": {
- "node": ">=8"
+ "node": ">= 0.8"
}
},
- "node_modules/es-module-lexer": {
- "version": "2.3.2",
- "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.2.tgz",
- "integrity": "sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==",
- "dev": true,
- "license": "MIT"
+ "node_modules/call-bind-apply-helpers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
},
- "node_modules/esbuild": {
+ "node_modules/call-bound": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
+ "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "get-intrinsic": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/chai": {
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz",
+ "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/content-disposition": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz",
+ "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/content-type": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
+ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
+ "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie-signature": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
+ "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.6.0"
+ }
+ },
+ "node_modules/cwise-compiler": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/cwise-compiler/-/cwise-compiler-1.1.3.tgz",
+ "integrity": "sha512-WXlK/m+Di8DMMcCjcWr4i+XzcQra9eCdXIJrgh4TUgh0pIS/yJduLxS9JgefsHJ/YVLdgPtXm9r62W92MvanEQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "uniq": "^1.0.0"
+ }
+ },
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/depd": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/detect-libc": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
+ "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/dunder-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/ee-first": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
+ "license": "MIT"
+ },
+ "node_modules/encodeurl": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
+ "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/es-define-property": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-module-lexer": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.2.tgz",
+ "integrity": "sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/es-object-atoms": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz",
+ "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/esbuild": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz",
"integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==",
@@ -1926,6 +2536,12 @@
"@esbuild/win32-x64": "0.28.2"
}
},
+ "node_modules/escape-html": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
+ "license": "MIT"
+ },
"node_modules/estree-walker": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
@@ -1936,6 +2552,24 @@
"@types/estree": "^1.0.0"
}
},
+ "node_modules/etag": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/events": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
+ "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8.x"
+ }
+ },
"node_modules/expect-type": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz",
@@ -1946,6 +2580,49 @@
"node": ">=12.0.0"
}
},
+ "node_modules/express": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz",
+ "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
+ "license": "MIT",
+ "dependencies": {
+ "accepts": "^2.0.0",
+ "body-parser": "^2.2.1",
+ "content-disposition": "^1.0.0",
+ "content-type": "^1.0.5",
+ "cookie": "^0.7.1",
+ "cookie-signature": "^1.2.1",
+ "debug": "^4.4.0",
+ "depd": "^2.0.0",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "etag": "^1.8.1",
+ "finalhandler": "^2.1.0",
+ "fresh": "^2.0.0",
+ "http-errors": "^2.0.0",
+ "merge-descriptors": "^2.0.0",
+ "mime-types": "^3.0.0",
+ "on-finished": "^2.4.1",
+ "once": "^1.4.0",
+ "parseurl": "^1.3.3",
+ "proxy-addr": "^2.0.7",
+ "qs": "^6.14.0",
+ "range-parser": "^1.2.1",
+ "router": "^2.2.0",
+ "send": "^1.1.0",
+ "serve-static": "^2.2.0",
+ "statuses": "^2.0.1",
+ "type-is": "^2.0.1",
+ "vary": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
"node_modules/fdir": {
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
@@ -1964,6 +2641,45 @@
}
}
},
+ "node_modules/finalhandler": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz",
+ "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.4.0",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "on-finished": "^2.4.1",
+ "parseurl": "^1.3.3",
+ "statuses": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/forwarded": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
+ "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/fresh": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
+ "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
@@ -1979,6 +2695,130 @@
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-intrinsic": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "es-define-property": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "function-bind": "^1.1.2",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "math-intrinsics": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+ "license": "MIT",
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/gopd": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-symbols": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz",
+ "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==",
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/http-errors": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
+ "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
+ "license": "MIT",
+ "dependencies": {
+ "depd": "~2.0.0",
+ "inherits": "~2.0.4",
+ "setprototypeof": "~1.2.0",
+ "statuses": "~2.0.2",
+ "toidentifier": "~1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/iconv-lite": {
+ "version": "0.7.3",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz",
+ "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==",
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "license": "ISC"
+ },
"node_modules/iota-array": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/iota-array/-/iota-array-1.0.0.tgz",
@@ -1986,6 +2826,15 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/ipaddr.js": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
+ "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
"node_modules/is-buffer": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
@@ -1993,6 +2842,21 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/is-promise": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
+ "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
+ "license": "MIT"
+ },
+ "node_modules/jose": {
+ "version": "5.10.0",
+ "resolved": "https://registry.npmjs.org/jose/-/jose-5.10.0.tgz",
+ "integrity": "sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/panva"
+ }
+ },
"node_modules/js-tokens": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz",
@@ -2007,33 +2871,203 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/livekit-client": {
+ "version": "2.22.3",
+ "resolved": "https://registry.npmjs.org/livekit-client/-/livekit-client-2.22.3.tgz",
+ "integrity": "sha512-jw9zBKXY5Gtr5MZ7vEON3QhMNccuDvYHck1PFSyG1aaateQPqgKZFBMgZkFZaXHIf9RV4MDW5xpTK2b/+qbwOg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@livekit/mutex": "1.1.1",
+ "@livekit/protocol": "1.50.4",
+ "events": "^3.3.0",
+ "jose": "^6.1.0",
+ "loglevel": "^1.9.2",
+ "machina": "^7.0.0",
+ "sdp-transform": "^2.15.0",
+ "tslib": "2.8.1",
+ "typed-emitter": "^2.1.0",
+ "webrtc-adapter": "9.0.6"
+ },
+ "peerDependencies": {
+ "@types/dom-mediacapture-record": "^1"
+ }
+ },
+ "node_modules/livekit-client/node_modules/@livekit/protocol": {
+ "version": "1.50.4",
+ "resolved": "https://registry.npmjs.org/@livekit/protocol/-/protocol-1.50.4.tgz",
+ "integrity": "sha512-L1uggNQAqyY21smQY8AllyOYbcv9Me9TaxwuLytL1R8ck9nbYPmQLNwEDi3pOFGAMa5F8I2nUi2Jc59W5awxlA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@bufbuild/protobuf": "^1.10.0"
+ }
+ },
+ "node_modules/livekit-client/node_modules/jose": {
+ "version": "6.2.12",
+ "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.12.tgz",
+ "integrity": "sha512-9NiFmJEex0sy2Dk58j2UGBSHgUs2ypF9eZSu4L6vjOX3Dp96Sw1F3uL+H+D1sx02jZZdzUT0HgvCy59CuvXcWw==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/panva"
+ }
+ },
+ "node_modules/livekit-server-sdk": {
+ "version": "2.19.1",
+ "resolved": "https://registry.npmjs.org/livekit-server-sdk/-/livekit-server-sdk-2.19.1.tgz",
+ "integrity": "sha512-tqjM5oPDDNBrkb/64YReta9UWP32l1a2m0HHXq8WOuEzwxh4zsaXjeZZtPG4Er4eVAk4kjR+EMxH7lPKzg1g6g==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@bufbuild/protobuf": "^1.10.1",
+ "@livekit/protocol": "1.51.0",
+ "jose": "^5.1.2"
+ },
+ "engines": {
+ "node": ">=19"
+ }
+ },
+ "node_modules/loglevel": {
+ "version": "1.9.2",
+ "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.9.2.tgz",
+ "integrity": "sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6.0"
+ },
+ "funding": {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/loglevel"
+ }
+ },
+ "node_modules/machina": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/machina/-/machina-7.0.1.tgz",
+ "integrity": "sha512-0Xkgs8NeBvO2hW6bjJT8ewLWrMfh1haMPstaghpASWjXkLjRhtDiaJG1Kb7mslaOHxZYxpH7PIjpmirRVYbMMA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=22.22"
+ }
+ },
"node_modules/magic-string": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-1.2.3.tgz",
"integrity": "sha512-Bpb0W2TbLKOZ7vJnOUnVRGq3WL2p+ISV29M6hYPL1AFCpyKZpdr5ytiXoTSSxRVhg8YW7f65+6gbG8WG6PCa/g==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "@jridgewell/sourcemap-codec": "^1.5.5"
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.5"
+ }
+ },
+ "node_modules/magicast": {
+ "version": "0.5.4",
+ "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.4.tgz",
+ "integrity": "sha512-llBEhWm1SacoRwgHUoQJYtwp4PBLF4faQi5TCpIGyGs9n4y5+juI0tDgyKIfpqxckRHaHzouUEph3THklWh03w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.29.7",
+ "@babel/types": "^7.29.7",
+ "source-map-js": "^1.2.1"
+ }
+ },
+ "node_modules/math-intrinsics": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/media-typer": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.1.tgz",
+ "integrity": "sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/merge-descriptors": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
+ "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.54.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
+ "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz",
+ "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "^1.54.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "license": "MIT"
+ },
+ "node_modules/msgpackr": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-2.1.0.tgz",
+ "integrity": "sha512-p/pBCVO63CsvvpkomUnNNag6+n38rULuDA6HHe70o2gtC8ODI52foF/4ko2qQcp6OiErJXTmrZeXmsGGHsIQNQ==",
+ "license": "MIT",
+ "optionalDependencies": {
+ "msgpackr-extract": "^3.0.4"
}
},
- "node_modules/magicast": {
- "version": "0.5.4",
- "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.4.tgz",
- "integrity": "sha512-llBEhWm1SacoRwgHUoQJYtwp4PBLF4faQi5TCpIGyGs9n4y5+juI0tDgyKIfpqxckRHaHzouUEph3THklWh03w==",
- "dev": true,
+ "node_modules/msgpackr-extract": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/msgpackr-extract/-/msgpackr-extract-3.0.4.tgz",
+ "integrity": "sha512-4kmO/MdyUIkLIvTPr8VHLil4AtoKIoniWPIEk5+CDy0xnWC84azhSFmuJ7PxZdsYtiP5kEeQsORAVIeMgxT+Hw==",
+ "hasInstallScript": true,
"license": "MIT",
+ "optional": true,
"dependencies": {
- "@babel/parser": "^7.29.7",
- "@babel/types": "^7.29.7",
- "source-map-js": "^1.2.1"
+ "node-gyp-build-optional-packages": "5.2.2"
+ },
+ "bin": {
+ "download-msgpackr-prebuilds": "bin/download-prebuilds.js"
+ },
+ "optionalDependencies": {
+ "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.4",
+ "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.4",
+ "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.4",
+ "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.4",
+ "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.4",
+ "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.4"
}
},
"node_modules/nanoid": {
"version": "3.3.18",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz",
"integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==",
- "dev": true,
"funding": [
{
"type": "github",
@@ -2093,6 +3127,62 @@
"sharp": "^0.35.0"
}
},
+ "node_modules/negotiator": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.1.0.tgz",
+ "integrity": "sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg==",
+ "license": "MIT",
+ "dependencies": {
+ "content-type": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/negotiator/node_modules/content-type": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz",
+ "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/node-gyp-build-optional-packages": {
+ "version": "5.2.2",
+ "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.2.2.tgz",
+ "integrity": "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "detect-libc": "^2.0.1"
+ },
+ "bin": {
+ "node-gyp-build-optional-packages": "bin.js",
+ "node-gyp-build-optional-packages-optional": "optional.js",
+ "node-gyp-build-optional-packages-test": "build-test.js"
+ }
+ },
+ "node_modules/object-inspect": {
+ "version": "1.13.4",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
+ "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/obug": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/obug/-/obug-2.1.4.tgz",
@@ -2107,6 +3197,46 @@
"node": ">=12.20.0"
}
},
+ "node_modules/on-finished": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
+ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+ "license": "MIT",
+ "dependencies": {
+ "ee-first": "1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+ "license": "ISC",
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/parseurl": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/path-to-regexp": {
+ "version": "8.4.2",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz",
+ "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==",
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
@@ -2163,6 +3293,67 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/proxy-addr": {
+ "version": "2.0.8",
+ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.8.tgz",
+ "integrity": "sha512-5nnx0yGyVUcY6t9RnWcARWtwT9F1D8O9rt08htPvnd49W1IgZtmLkhu9WfMzQj1cFxjHIO6connUNVW5k7AVyQ==",
+ "license": "MIT",
+ "dependencies": {
+ "forwarded": "0.2.0",
+ "ipaddr.js": "1.9.1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/qs": {
+ "version": "6.16.0",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.16.0.tgz",
+ "integrity": "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "es-define-property": "^1.0.1",
+ "side-channel": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=0.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/range-parser": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.3.0.tgz",
+ "integrity": "sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/raw-body": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz",
+ "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==",
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "~3.1.2",
+ "http-errors": "~2.0.1",
+ "iconv-lite": "~0.7.0",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
"node_modules/rollup": {
"version": "4.63.1",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.63.1.tgz",
@@ -2209,6 +3400,59 @@
"fsevents": "~2.3.2"
}
},
+ "node_modules/rou3": {
+ "version": "0.7.12",
+ "resolved": "https://registry.npmjs.org/rou3/-/rou3-0.7.12.tgz",
+ "integrity": "sha512-iFE4hLDuloSWcD7mjdCDhx2bKcIsYbtOTpfH5MHHLSKMOUyjqQXTeZVa289uuwEGEKFoE/BAPbhaU4B774nceg==",
+ "license": "MIT"
+ },
+ "node_modules/router": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
+ "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.4.0",
+ "depd": "^2.0.0",
+ "is-promise": "^4.0.0",
+ "parseurl": "^1.3.3",
+ "path-to-regexp": "^8.0.0"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ },
+ "node_modules/rxjs": {
+ "version": "7.8.2",
+ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz",
+ "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==",
+ "license": "Apache-2.0",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.1.0"
+ }
+ },
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+ "license": "MIT"
+ },
+ "node_modules/sdp": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/sdp/-/sdp-3.2.2.tgz",
+ "integrity": "sha512-xZocWwfyp4hkbN4hLWxMjmv2Q8aNa9MhmOZ7L9aCZPT+dZsgRr6wZRrSYE3HTdyk/2pZKPSgqI7ns7Een1xMSA==",
+ "license": "MIT"
+ },
+ "node_modules/sdp-transform": {
+ "version": "2.15.0",
+ "resolved": "https://registry.npmjs.org/sdp-transform/-/sdp-transform-2.15.0.tgz",
+ "integrity": "sha512-KrOH82c/W+GYQ0LHqtr3caRpM3ITglq3ljGUIb8LTki7ByacJZ9z+piSGiwZDsRyhQbYBOBJgr2k6X4BZXi3Kw==",
+ "license": "MIT",
+ "bin": {
+ "sdp-verify": "checker.js"
+ }
+ },
"node_modules/semver": {
"version": "7.8.5",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz",
@@ -2222,6 +3466,63 @@
"node": ">=10"
}
},
+ "node_modules/send": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz",
+ "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.4.3",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "etag": "^1.8.1",
+ "fresh": "^2.0.0",
+ "http-errors": "^2.0.1",
+ "mime-types": "^3.0.2",
+ "ms": "^2.1.3",
+ "on-finished": "^2.4.1",
+ "range-parser": "^1.2.1",
+ "statuses": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/serve-static": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz",
+ "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==",
+ "license": "MIT",
+ "dependencies": {
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "parseurl": "^1.3.3",
+ "send": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/set-cookie-parser": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-3.1.2.tgz",
+ "integrity": "sha512-5/r/lTwbJ3zQ+qwdUFZYeRNqda7P5HD8zQKqlSjdGt1/S0cjLAphHusj4Y58ahDtWn/g32xrIS58/ikOvwl0Lw==",
+ "license": "MIT"
+ },
+ "node_modules/setprototypeof": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
+ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
+ "license": "ISC"
+ },
"node_modules/sharp": {
"version": "0.35.4",
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.4.tgz",
@@ -2272,6 +3573,78 @@
}
}
},
+ "node_modules/side-channel": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz",
+ "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.4",
+ "side-channel-list": "^1.0.1",
+ "side-channel-map": "^1.0.1",
+ "side-channel-weakmap": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-list": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz",
+ "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.4"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-map": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
+ "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-weakmap": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
+ "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3",
+ "side-channel-map": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/siginfo": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz",
@@ -2296,6 +3669,15 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/statuses": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
+ "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/std-env": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/std-env/-/std-env-4.2.0.tgz",
@@ -2350,19 +3732,66 @@
"node": ">=14.0.0"
}
},
+ "node_modules/toidentifier": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
+ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.6"
+ }
+ },
"node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
- "dev": true,
- "license": "0BSD",
- "optional": true
+ "license": "0BSD"
+ },
+ "node_modules/type-is": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz",
+ "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==",
+ "license": "MIT",
+ "dependencies": {
+ "content-type": "^2.0.0",
+ "media-typer": "^1.1.0",
+ "mime-types": "^3.0.0"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/type-is/node_modules/content-type": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz",
+ "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/typed-emitter": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/typed-emitter/-/typed-emitter-2.1.0.tgz",
+ "integrity": "sha512-g/KzbYKbH5C2vPkaXGu8DJlHrGKHLsM25Zg9WuC9pMGfuvT+X25tZQWo5fK1BjBm8+UrVE9LDCvaY0CQk+fXDA==",
+ "license": "MIT",
+ "optionalDependencies": {
+ "rxjs": "*"
+ }
},
"node_modules/typescript": {
"version": "5.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
- "dev": true,
+ "devOptional": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
@@ -2376,7 +3805,6 @@
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz",
"integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==",
- "dev": true,
"license": "MIT"
},
"node_modules/uniq": {
@@ -2386,6 +3814,24 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/unpipe": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+ "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/vary": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/vite": {
"version": "7.3.6",
"resolved": "https://registry.npmjs.org/vite/-/vite-7.3.6.tgz",
@@ -2544,6 +3990,19 @@
}
}
},
+ "node_modules/webrtc-adapter": {
+ "version": "9.0.6",
+ "resolved": "https://registry.npmjs.org/webrtc-adapter/-/webrtc-adapter-9.0.6.tgz",
+ "integrity": "sha512-CHbl2ZQbxx164IgWRgzJno4hWtM4tFbRam1QfI3Yxhs3w/DvqluVxVWeXs3oL5/fbGkSNLKo0Ty5MgUWceNhog==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "sdp": "^3.2.0"
+ },
+ "engines": {
+ "node": ">=6.0.0",
+ "npm": ">=3.10.0"
+ }
+ },
"node_modules/why-is-node-running": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz",
@@ -2561,13 +4020,42 @@
"node": ">=8"
}
},
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+ "license": "ISC"
+ },
+ "node_modules/ws": {
+ "version": "8.22.0",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.22.0.tgz",
+ "integrity": "sha512-Ydggc987+RO0AnWtZ/7Wq9FtNvcrL1b/RO0ud9mWjUPgDrsAAwQSF51sm2hm1XofbU/4jkpGEsLFsZZxU+1DOg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": ">=5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
+ }
+ },
"packages/client": {
"name": "@shobu/client",
"version": "0.0.0",
"dependencies": {
"@babylonjs/core": "9.23.0",
"@babylonjs/loaders": "9.23.0",
- "@shobu/core": "*"
+ "@colyseus/sdk": "0.18.4",
+ "@shobu/core": "*",
+ "livekit-client": "2.22.3"
},
"devDependencies": {
"vite": "^7.1.14"
@@ -2576,6 +4064,21 @@
"packages/core": {
"name": "@shobu/core",
"version": "0.0.0"
+ },
+ "packages/server": {
+ "name": "@shobu/server",
+ "version": "0.0.0",
+ "dependencies": {
+ "@colyseus/core": "0.18.17",
+ "@colyseus/schema": "5.0.34",
+ "@colyseus/ws-transport": "0.18.4",
+ "@shobu/core": "*",
+ "express": "5.2.1",
+ "livekit-server-sdk": "2.19.1"
+ },
+ "devDependencies": {
+ "@types/express": "5.0.6"
+ }
}
}
}
diff --git a/package.json b/package.json
index 92f5f6a0..db46185c 100644
--- a/package.json
+++ b/package.json
@@ -12,7 +12,7 @@
"verify": "npm run lint && npm run typecheck && npm test",
"lint": "biome check .",
"format": "biome format --write .",
- "typecheck": "tsc -p packages/core && tsc -p packages/client",
+ "typecheck": "tsc -p packages/core && tsc -p packages/client && tsc -p packages/server",
"test": "vitest run --coverage",
"test:watch": "vitest",
"convert:viewmodel": "node scripts/convert-viewmodel.mjs",
diff --git a/packages/client/index.html b/packages/client/index.html
index bafcd45d..a92bfec3 100644
--- a/packages/client/index.html
+++ b/packages/client/index.html
@@ -461,17 +461,18 @@