Para desarrolladores / API de las aplicaciones

Servidor Arcade

Un servidor WebSocket para cinco juegos, con un puñado de rutas HTTP al lado. Para cuatro de los cinco, un cliente no envía ninguna entrada de juego - la entrada es una transacción, y el socket es la forma de verla aterrizar.

Un socket, no una API

Un solo proceso ejecuta cinco salas de juego más un vestíbulo sobre un mismo bucle de eventos, lee la cadena por su propio socket hacia el nodo, y reparte el resultado a todos los clientes conectados. La interfaz es el WebSocket; las rutas HTTP existen para las comprobaciones de salud y para las dos cifras que el marco del sitio dibuja en cada página.

La entrada de juego es una transacción, no una trama

Pilotar en la arena, apostar sobre la curva, lanzar un giro, apostar sobre el tablero - todas ellas son transacciones que el jugador envía a un contrato. Este socket lleva a quién está mirando, un ping de vivacidad, y la visión del servidor sobre lo que la cadena hizo. Nada de lo que un cliente envíe por él autoriza nada ni cambia un resultado.

La sala de la pinza es la excepción deliberada: pilotar una pinza sería una transacción por trama, así que las intenciones viajan por el socket. Incluso allí la costura es estrecha - tres verbos, ninguno de los cuales lleva una posición, un tiempo ni un veredicto, y el piloto queda autorizado por una preimagen que el navegador ya publicó en la cadena en su propia tirada.

Conectarse

Punto de accesoValue
Socketwss://minigame.picklechain.xyz/ws/<room>el nginx propio del sitio transmite la actualización de conexión
HTTPhttps://minigame.picklechain.xyz/api/el mismo proceso, con el prefijo retirado
Una pila propiahttp://127.0.0.1:3008el sitio del arcade; el servidor en sí está en la red de contenedores
Origincomprobado al actualizar la conexiónun Origin que no esté en la lista configurada se rechaza con 403 antes del apretón de manos, así que el socket nunca se abre

Una ruta desconocida bajo /ws/ se rechaza con 404 en la actualización, y una conexión por encima de los límites de abajo se rechaza con 429 - las dos como una simple respuesta HTTP sobre el socket en bruto, antes de cualquier apretón de manos WebSocket. Un cliente que solo maneje el evento error de su socket verá un fallo genérico; lea el estado si quiere saber cuál.

Las seis salas

Al entrar
`hello`, then a full snapshot of the arena.

The snake arena. Steering is a transaction, so the socket carries no input for it.

Al 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.

Al 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.

Al 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.

Al 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.

Al 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.

Al 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.

Al 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.

Al entrar
`hello` alone.

The lobby. Somebody who has not chosen a game sits here, and it is counted in the socket total.

Cada sala responde con la misma secuencia de apertura: una trama hello que lleva el nombre de la sala, el número de jugadores, el número de espectadores y la dirección del operador de liquidación, y después una instantánea completa del estado de esa sala. Los dos números son RELATIVOS A LA SALA - antes venían de la arena fuera cual fuera la sala pedida, lo que anunciaba jugadores que no estaban allí.

Qué puede enviar un cliente

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.

Conviene saberlo. 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.

Conviene saberlo. This is not the liveness mechanism the server relies on; it is the one a client can observe.

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.

Conviene saberlo. 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.

Conviene saberlo. 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.

javascript
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. */
  }
});

Qué envía el servidor

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.

Un hueco en la cadena se anuncia, y significa algo distinto en cada sala

La trama chain lleva resyncing y el rango de minibloques que el lector no pudo recuperar. Se difunde a todas las salas, y cada una la trata de forma distinta: una ronda que se solapaba con un rango perdido dice que su lista de ganadores puede estar incompleta en lugar de anunciarla como todo el mundo, y la sala de la pinza envía el rango perdido a su registro para que las apuestas que hay dentro puedan encontrarse y reembolsarse. Un cliente que ignore esta trama dibujará una imagen segura de una imagen incompleta.

Las rutas HTTP

Params
none
Devuelve
{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.

Conviene saberlo. This is the figure that answers `the arcade is slow sometimes`. It is measured over ten-second windows and reset each time.

Params
none
Devuelve
{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.

Conviene saberlo. 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.

Params
none
Devuelve
{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.

Conviene saberlo. 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.

Params
none
Devuelve
{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.

Conviene saberlo. 503 when the claw room is not running.

Params
the pull id in the path
Devuelve
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.

Conviene saberlo. 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.

Params
none
Devuelve
{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.

Conviene saberlo. 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
Devuelve
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.

Conviene saberlo. 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.

Params
none
Devuelve
{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).

Conviene saberlo. 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
Devuelve
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.

Conviene saberlo. 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.

Params
none
Devuelve
{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.

Conviene saberlo. 503 when the glide room is not running.

Params
the run id in the path
Devuelve
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.

Conviene saberlo. A run this process never saw or has already let go of is a 404.

Las cuatro son sin autenticación, y eso es lo que decide qué puede aparecer en ellas. No se publica nada que delate una ronda inacabada: un punto de crash se retiene hasta que la curva se ha detenido, y la cadena de hash de la pinza solo se publica en forma de compromiso, con un eslabón revelado en /fair únicamente porque el cumplimiento que terminó esa tirada ya lo puso en la cadena.

Los límites, y qué los rompe

CotaValue
Sockets256 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
Originan allowlistthe upgrade is refused 403, so a page on another origin cannot drive this socket with a visitor's network
Frame size4 KiBtwo orders of magnitude above the largest real message; the library's default is 100 MiB
Inbound rate20 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
Overflowthe 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
Backpressure512 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
Livenessa ping every half minuteanything that has not answered by the next one is terminated

Un lector lento se desconecta, no se almacena en búfer

La biblioteca almacena en búfer sin límite, en el montículo de este proceso, todo lo que no puede escribir. Por eso un socket con más de medio megabyte de retraso se corta y se termina en lugar de volver a escribirse - un puñado de clientes que abren un socket y no lo leen nunca agotarían si no el contenedor, lo que, en un proceso que guarda una clave, es una muerte a distancia. Medio megabyte son unas cincuenta de las tramas más grandes que este servidor envía; ningún cliente que lea va jamás tan retrasado.

El presupuesto de mensajes es más amplio en la sala de la pinza, y es deliberado

En el resto un cliente envía un ping cada pocos segundos y nada más, así que veinte mensajes por segundo solo pueden ser un cliente roto u hostil. El pilotaje de la pinza envía un mensaje por cambio de intención - una pulsación y una suelta son dos - y un jugador que trabaja un mando con las dos manos pasa de diez por segundo sin esfuerzo. Terminar ese socket le quita el volante en plena tentativa, y la tentativa que ha pagado se agota contra la barandilla. Por eso el techo está puesto allí por encima de lo que una mano puede producir, y no a su altura.