Para desenvolvedores / APIs das aplicações
Servidor Arcade
Um servidor WebSocket para cinco jogos, com um punhado de rotas HTTP ao lado. Para quatro dos cinco, um cliente não envia entrada de jogo nenhuma - a entrada é uma transação, e o socket é como você a vê aterrissar.
Um socket, não uma API
Um único processo roda cinco salas de jogo mais um saguão num mesmo laço de eventos, lê a cadeia pelo seu próprio socket até o nó, e distribui o resultado a todos os clientes conectados. A interface é o WebSocket; as rotas HTTP existem para as verificações de saúde e para os dois números que o enquadramento do site exibe em cada página.
A entrada de jogo é uma transação, não um quadro
Pilotar na arena, uma aposta na curva, um giro, uma aposta no tabuleiro - todas são transações que o jogador envia a um contrato. Este socket carrega quem você está assistindo, um ping de vivacidade, e a visão do servidor sobre o que a cadeia fez. Nada do que um cliente envia por ele autoriza coisa alguma nem muda um resultado.
A sala da garra é a exceção deliberada: pilotar uma garra seria uma transação por quadro, então as intenções passam pelo socket. Mesmo ali a costura é estreita - três verbos, nenhum dos quais carrega uma posição, um tempo ou um veredito, e o piloto é autorizado por uma pré-imagem que o navegador já publicou na cadeia na sua própria puxada.
Conectar-se
| Ponto de acesso | Value |
|---|---|
| Socket | wss://minigame.picklechain.xyz/ws/<room>o nginx do próprio site encaminha a mudança de protocolo |
| HTTP | https://minigame.picklechain.xyz/api/o mesmo processo, com o prefixo retirado |
| Uma pilha sua | http://127.0.0.1:3008o site da arcade; o servidor em si está na rede de contêineres |
| Origin | verificada na mudança de protocolouma Origin fora da lista configurada é recusada com 403 antes do aperto de mão, então o socket nunca abre |
Um caminho desconhecido sob /ws/ é recusado com 404 na mudança de protocolo, e uma conexão além dos limites abaixo é recusada com 429 - as duas como uma simples resposta HTTP no socket bruto, antes de qualquer aperto de mão WebSocket. Um cliente que só trata o evento error do seu socket verá uma falha genérica; leia o status se quiser saber qual delas foi.
As seis salas
/ws/arena
sala- Ao entrar
- `hello`, then a full snapshot of the arena.
The snake arena. Steering is a transaction, so the socket carries no input for it.
/ws/crash
sala- Ao entrar
- `hello`, then the whole round: phase, time left, the commitment, every bet placed and every cash-out judged so far.
The curve. Bets are transactions.
/ws/claw
sala- Ao entrar
- `hello`, then the cabinet: what is left, where each capsule stands, the machine's constants and every attempt being driven.
The claw cabinet, and the ONLY room whose gameplay input rides this socket rather than the chain - a transaction per frame is not a game.
/ws/patch
sala- Ao entrar
- `hello`, then the machine: the bet ladder, the paytable, the meter and the spins still open.
The reels. A spin is a transaction; the socket watches it.
/ws/trellis
sala- Ao entrar
- `hello`, then the table: every cell's multiplier, the current snapshot, the line so far and the bets still standing.
The board. Bets are transactions; the socket has one verb, and it buys nothing.
/ws/stack
sala- Ao entrar
- `hello`, then `stack`: every run being climbed with its rules state, the day's board and the rules' constants.
The tower. A start and every tap are transactions; the socket only listens, and says what the rules made of each tap.
/ws/derby
sala- Ao entrar
- `hello`, then `derby`: the current and next race, recent history, the rules and the paddock.
The race. A bet is a transaction; the socket only listens, and every runner is one of the chain's own oracle feeds.
/ws/glide
sala- Ao entrar
- `hello`, then `glide`: the day's board, every run in the air, the canyon cards and the rules.
The canyon. A start and every flap are transactions; the socket only listens, and the canyon it draws comes from a live price feed.
/ws/hall
sala- Ao entrar
- `hello` alone.
The lobby. Somebody who has not chosen a game sits here, and it is counted in the socket total.
Toda sala responde com a mesma sequência de abertura: um quadro hello carregando o nome da sala, o número de quem joga, o número de quem assiste e o endereço do operador de liquidação, depois um instantâneo completo do estado daquela sala. As duas contagens são RELATIVAS À SALA - elas antes vinham da arena qualquer que fosse a sala pedida, o que anunciava jogadores que não estavam lá.
O que um cliente pode enviar
watch
every room- Forma
- {"t":"watch","account":"0x…"}
Says which account this socket is following, so the server can address one player directly - a refused join has to reach the person it was refused for. In the reels room it also arms that account.
Vale saber. It AUTHORISES NOTHING. The account is not verified, the contract decides who may play, and the address must match a 40-hex pattern or the message is ignored in silence.
ping
every room- Forma
- {"t":"ping","id":<any>}
Answered with `{t:"pong", id}`. The server also sends protocol-level pings and terminates a socket that stops answering them.
Vale saber. This is not the liveness mechanism the server relies on; it is the one a client can observe.
claim / drive / drop / release
the claw room only- Forma
- {"t":"claim","tx":"0x…","secret":"0x…"} and three verbs after it
Takes the wheel of an attempt and drives it. The proof is a preimage: the hash of the secret must equal the seed the pull published on chain, so the only client that can drive an attempt is the one that made it - no signature, no challenge, no round trip.
Vale saber. None of the verbs carries a position, a time or a verdict. A wrong secret is refused and counted and never logged. A reconnect takes the wheel from the previous socket, which is told so.
quote
the trellis room only- Forma
- {"t":"quote", …}
Asks the operator to sign the price already printed in a square.
Vale saber. It carries no stake and no account, and the contract checks the clock, the lock and the float itself. A flood of them costs this process signatures and nothing else.
const socket = new WebSocket("wss://minigame.picklechain.xyz/ws/crash");
socket.addEventListener("open", () => {
// Tell the server which account to address directly. It verifies nothing:
// this only decides who a per-player frame is sent to.
socket.send(JSON.stringify({ t: "watch", account: myAddress }));
});
socket.addEventListener("message", (event) => {
const frame = JSON.parse(event.data);
switch (frame.t) {
case "hello": /* room, playing, watching, operator */ break;
case "full": /* the whole room, sent once after hello */ break;
case "chain": /* resyncing, and the range of mini-blocks lost, if any */ break;
default: /* IGNORE. The frame list is not versioned and rooms gain frames. */
}
});O que o servidor envia
Every frame is a JSON object with a `t` discriminator. `hello` arrives first and carries the room, the number playing, the number watching and the operator's address; a full snapshot follows. After that the room pushes what changed.
Across the eight rooms: chain, tick, state, full, bet, settled, settle-failed, refused, refunded, refund-due, fulfil-failed, pull, claimed, released, blocked, armed, quote, feed, unsealed, void, race, started, finished, paddock, board, run, points, flight, over, cancelled, canyons, eat, death, pong.
Um buraco na cadeia é anunciado, e ele quer dizer outra coisa em cada sala
O quadro chain carrega resyncing e o intervalo de miniblocos que o leitor não conseguiu buscar. Ele é difundido a todas as salas, e cada uma o trata de um jeito: uma rodada que se sobrepôs a um intervalo perdido diz que sua lista de vencedores pode estar incompleta em vez de anunciá-la como sendo todo mundo, e a sala da garra manda o intervalo perdido ao seu log para que as apostas de dentro dele possam ser encontradas e reembolsadas. Um cliente que ignora este quadro vai desenhar um retrato confiante de um retrato incompleto.
As rotas HTTP
/health
GET- Params
- none
- Retorno
- {ok, loop:{p50Ms, p99Ms, maxMs, windowMs}}
Liveness, plus the event loop's recent delay. Eight rooms, a physics step and every socket's fan-out share one loop, so a loop that stalls stalls every quote, every echo and every frame at once.
Vale saber. This is the figure that answers `the arcade is slow sometimes`. It is measured over ten-second windows and reset each time.
/ready
GET- Params
- none
- Retorno
- {ok, reasons, miniBlock, appliedMiniBlock, appliedLagMs, resyncing, lost}
503 when the socket to the node is down, or when the last APPLIED mini-block is more than five seconds old.
Vale saber. It measures what was applied, not what arrived. Measuring arrival meant a wedged dispatch chain answered 200 while no input had been applied for minutes, which is the exact failure this probe exists to catch.
/state
GET- Params
- none
- Retorno
- {total:{playing, sockets, spectators}, arena, crash, claw, patch, trellis, stack, derby, glide, …}
The whole arcade in one object: a summary per game and three numbers for the chrome every page draws. `playing` means somebody with something at stake right now, and each game measures that its own way.
Vale saber. PUBLIC AND UNAUTHENTICATED, which decides what may appear on it. The crash summary withholds its point until the curve has stopped, and the claw summary carries the published commitment and never a link beneath it - one link is the outcome of the next pull nobody has made.
/inventory
GET- Params
- none
- Retorno
- {supply, remaining, claimed, cells, price, pullsPerAccount, commit, chainLeft, blocked, machine}
The cabinet on its own, so a page can state what is left without opening a socket.
Vale saber. 503 when the claw room is not running.
- Params
- the pull id in the path
- Retorno
- one finished pull, whole
The seed the player published, every intent the server recorded, the tick the drop landed on and the verdict that came out of them - the arithmetic a reader can rerun.
Vale saber. A pull still being driven is a 404, deliberately: its trace is not finished and its link is not revealed. A revealed link is served only because the fulfilment that finished the pull already put it on chain.
/stack
GET- Params
- none
- Retorno
- {playing, watching, day, pot, top, leaders}
Stack's day without a socket: the UTC day, its pot in wei, the chain's own top three and the tallest runs this server has finished today.
Vale saber. 503 when the stack room is not running. The pot and the top three are read off the contract once a minute and after each finish, so they can trail a finish by a moment.
- Params
- the run id in the path
- Retorno
- one run, whole: {id, a, t0Us, startMini, taps:[{us, mini, hash}], results, height, why, finished, rules}
Every tap of a run with the mini-block that carried it and what the rules made of it - the arithmetic a reader reruns with arcade-server/src/stack.js to check the height the operator reported.
Vale saber. A 404 for a run this process never saw or has already let go of; it keeps the last five hundred. The chain still holds every tap, and the rules still apply to them.
/derby
GET- Params
- none
- Retorno
- {playing, watching, k, phase, card, unsettled, current, next, history, rules}
Derby's board for a page that has no socket yet: the current race and the next one, recent history, and the rules (the feed allowlist, the wall and night cards, the stake bounds and the seed).
Vale saber. 503 when the derby room is not running. `unsettled` counts bets still waiting on a settle, across every race this process still holds.
- Params
- the race number in the path
- Retorno
- one race, whole: {k, state, card, gate, line, runners, handicaps, pools, startPrices, startRounds, lineRounds, roundsFrom, scores, winners, why, hashes, bets}
The lineup, the handicaps, the rounds both seals named, the scores, the winners and every bet - the arithmetic a reader reruns with arcade-server/src/derby.js against the same public rounds.
Vale saber. A race this process never opened is a 404. `roundsFrom` says whether each end's rounds came from this room sealing them or were recomputed from the tape after a restart - the proof is the same either way.
/glide
GET- Params
- none
- Retorno
- {playing, watching, day, pot, top, leaders, canyons}
Glide's board for a page that has no socket yet, with every canyon's card alongside the day's pot and its leaders.
Vale saber. 503 when the glide room is not running.
- Params
- the run id in the path
- Retorno
- one run, whole: {id, a, feed, symbol, r0, sigma, startUs, startMini, startHash, answers, flaps, result, rules}
r0, the rounds of the canyon this run flew, and every flap with the mini-block that carried it - the arithmetic a reader reruns with arcade-server/src/glide.js `replay` to get the same score.
Vale saber. A run this process never saw or has already let go of is a 404.
As quatro são sem autenticação, e é isso que decide o que pode aparecer nelas. Nada que entregasse uma rodada inacabada é publicado: um ponto de crash é retido até a curva ter parado, e a cadeia de hashes da garra só é publicada como o seu compromisso, com um elo revelado em /fair unicamente porque o cumprimento que terminou aquela puxada já o colocou na cadeia.
Os limites, e o que os quebra
| Limite | Value |
|---|---|
| Sockets | 256 total, 8 per addressthe upgrade is refused 429 before the handshake; the slot is released when the raw socket closes, not when the handshake succeeds |
| Origin | an allowlistthe upgrade is refused 403, so a page on another origin cannot drive this socket with a visitor's network |
| Frame size | 4 KiBtwo orders of magnitude above the largest real message; the library's default is 100 MiB |
| Inbound rate | 20 a second, burst 4045 and 90 in the claw room, because its driving is on this socket and a hand working a pad passes ten changes a second without trying |
| Overflow | the socket is closedclosing is kinder than ignoring - and in the claw room it ends a paid attempt, which is why that allowance is the wider one |
| Backpressure | 512 KiB buffereda socket further behind than that is cut and terminated rather than written to; a client that never reads would otherwise grow the process's heap without limit |
| Liveness | a ping every half minuteanything that has not answered by the next one is terminated |
Um leitor lento é desconectado, não colocado em buffer
A biblioteca guarda em buffer, no heap deste processo e sem limite, tudo o que não consegue escrever. Por isso um socket atrasado em mais de meio megabyte é cortado e terminado em vez de escrito de novo - um punhado de clientes que abrem um socket e nunca o leem esgotaria o contêiner, o que, num processo que detém uma chave, é uma derrubada remota. Meio megabyte é mais ou menos cinquenta dos maiores quadros que este servidor envia; nenhum cliente que lê fica jamais tão atrasado.
Em outras salas um cliente manda um ping a cada poucos segundos e nada mais, então vinte mensagens por segundo só podem ser um cliente quebrado ou hostil. O pilotar da garra manda uma mensagem por mudança de intenção - um apertar e um soltar são duas - e um jogador que trabalha um controle com as duas mãos passa de dez por segundo sem esforço. Terminar esse socket tira o volante das mãos de alguém no meio da tentativa, e a tentativa que ele pagou se esgota contra a grade. Por isso o teto ali fica acima do que uma mão consegue produzir, e não na altura dela.