Pour les développeurs / API des applications

Serveur Arcade

Un serveur WebSocket pour cinq jeux, avec une poignée de routes HTTP à côté. Pour quatre des cinq, un client n'envoie aucune entrée de jeu - l'entrée est une transaction, et le socket est la façon de la regarder atterrir.

Un socket, pas une API

Un seul processus fait tourner cinq salles de jeu plus un hall sur une même boucle d'événements, lit la chaîne par son propre socket vers le nœud, et diffuse le résultat à tous les clients connectés. L'interface est le WebSocket ; les routes HTTP existent pour les contrôles de santé et pour les deux chiffres que l'habillage du site affiche sur chaque page.

L'entrée de jeu est une transaction, pas une trame

Piloter dans l'arène, parier sur la courbe, lancer un tour, parier sur le tableau - ce sont toutes des transactions que le joueur envoie à un contrat. Ce socket porte qui vous regardez, un ping de vivacité, et la vue du serveur sur ce que la chaîne a fait. Rien de ce qu'un client y envoie n'autorise quoi que ce soit ni ne change une issue.

La salle de la pince est l'exception délibérée : piloter une pince ferait une transaction par trame, donc les intentions passent par le socket. Même là, la couture est étroite - trois verbes, dont aucun ne porte une position, un temps ou un verdict, et le pilote est autorisé par une préimage que le navigateur a déjà publiée sur la chaîne dans son propre tirage.

Se connecter

Point d'accèsValue
Socketwss://minigame.picklechain.xyz/ws/<room>le nginx propre du site transmet la montée en WebSocket
HTTPhttps://minigame.picklechain.xyz/api/le même processus, le préfixe retiré
Une pile à voushttp://127.0.0.1:3008le site de l'arcade ; le serveur lui-même est sur le réseau de conteneurs
Originvérifiée à la montée en WebSocketune Origin absente de la liste configurée est refusée 403 avant la poignée de main, donc le socket ne s'ouvre jamais

Un chemin inconnu sous /ws/ est refusé 404 à la montée, et une connexion au-delà des limites ci-dessous est refusée 429 - les deux comme une simple réponse HTTP sur le socket brut, avant toute poignée de main WebSocket. Un client qui ne gère que l'événement error de son socket verra un échec générique ; lisez le statut si vous voulez savoir lequel.

Les six salles

À la connexion
`hello`, then a full snapshot of the arena.

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

À la connexion
`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.

À la connexion
`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.

À la connexion
`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.

À la connexion
`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.

À la connexion
`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.

À la connexion
`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.

À la connexion
`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.

À la connexion
`hello` alone.

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

Chaque salle répond par la même séquence d'ouverture : une trame hello portant le nom de la salle, le nombre de joueurs, le nombre de spectateurs et l'adresse de l'opérateur de règlement, puis un instantané complet de l'état de cette salle. Les deux nombres sont RELATIFS À LA SALLE - ils venaient auparavant de l'arène quelle que soit la salle demandée, ce qui annonçait des joueurs qui n'étaient pas là.

Ce qu'un client peut envoyer

watch

every room
Forme
{"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.

Bon à savoir. 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
Forme
{"t":"ping","id":<any>}

Answered with `{t:"pong", id}`. The server also sends protocol-level pings and terminates a socket that stops answering them.

Bon à savoir. This is not the liveness mechanism the server relies on; it is the one a client can observe.

Forme
{"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.

Bon à savoir. 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
Forme
{"t":"quote", …}

Asks the operator to sign the price already printed in a square.

Bon à savoir. 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. */
  }
});

Ce que le serveur envoie

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 trou dans la chaîne est annoncé, et il veut dire autre chose selon la salle

La trame chain porte resyncing et la plage de mini-blocs que le lecteur n'a pas pu récupérer. Elle est diffusée à toutes les salles, et chacune la traite différemment : une manche qui chevauchait une plage perdue dit que sa liste de gagnants peut être incomplète plutôt que de l'annoncer comme étant tout le monde, et la salle de la pince envoie la plage perdue à son journal pour que les mises qui s'y trouvent puissent être retrouvées et remboursées. Un client qui ignore cette trame dessinera une image assurée d'une image incomplète.

Les routes HTTP

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

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

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

Bon à savoir. 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
Retour
{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.

Bon à savoir. 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
Retour
{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.

Bon à savoir. 503 when the claw room is not running.

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

Bon à savoir. 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
Retour
{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.

Bon à savoir. 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
Retour
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.

Bon à savoir. 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
Retour
{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).

Bon à savoir. 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
Retour
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.

Bon à savoir. 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
Retour
{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.

Bon à savoir. 503 when the glide room is not running.

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

Bon à savoir. A run this process never saw or has already let go of is a 404.

Les quatre sont sans authentification, et c'est ce qui décide de ce qui peut y apparaître. Rien de ce qui trahirait une manche inachevée n'est publié : un point de crash est retenu jusqu'à l'arrêt de la courbe, et la chaîne de hash de la pince n'est publiée que sous forme d'engagement, avec un maillon révélé sur /fair uniquement parce que l'accomplissement qui a fini ce tirage l'a déjà mis sur la chaîne.

Les limites, et ce qui les casse

BorneValue
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 lecteur lent est déconnecté, pas tamponné

La bibliothèque tamponne sans limite, dans le tas de ce processus, tout ce qu'elle ne peut pas écrire. Un socket en retard de plus d'un demi-mégaoctet est donc coupé et terminé plutôt que réécrit - une poignée de clients qui ouvrent un socket et ne le lisent jamais épuiseraient sinon le conteneur, ce qui, sur un processus qui détient une clé, est une mise à mort à distance. Un demi-mégaoctet, c'est environ cinquante des plus grosses trames que ce serveur envoie ; aucun client qui lit n'est jamais aussi en retard.

Le budget de messages est plus large dans la salle de la pince, et c'est délibéré

Ailleurs un client envoie un ping toutes les quelques secondes et rien d'autre, donc vingt messages par seconde ne peuvent être qu'un client cassé ou hostile. Le pilotage de la pince envoie un message par changement d'intention - un appui et un relâchement en font deux - et un joueur qui travaille une manette à deux mains dépasse les dix par seconde sans effort. Terminer ce socket lui retire le volant en pleine tentative, et la tentative qu'il a payée s'épuise contre la rambarde. Le plafond y est donc placé au-dessus de ce qu'une main peut produire plutôt qu'à sa hauteur.