Unity

Two things a client must never decide

Choosing a client credential settles what you hold. Getting players into a session is matchmaking, randomness and wagering is why the server holds the seed, and versus platform leaderboards is the trade against Game Center and Play Games.

A score. Boards are server-authoritative by default — every vendor that offers this ships it off; this ships it on. Submitting requires a scope you mint for your trusted server’s token, never for a player’s.

A result. Recording a rating outcome needs the record scope, for the same reason: a rating a client can write is a rating a client can invent, and every ladder, queue and rank downstream inherits the lie.

A submission body carries a score and nothing else — the player comes from the session, the timestamp from the server’s clock, the rank from the data. A client that could name any of those could score as somebody else or backdate past the tiebreak.

Sign in

Passwordless — a magic link or an emailed code. Hold an access token and a refresh credential, rotate on 401, and do not fire two rotations at once from two coroutines.

Register the device with x-pithy-device-id and a stable id.

Getting into a match

Four ways in, all authenticated, all binding to the caller rather than an id the client sends:

A room code — WXYZ-1234, alphabet without I, O, 0 or 1, tolerant of lowercase and a missing dash. The zero-discovery path: play with whoever is beside you.

A direct invite by email, or best-effort by screen name.

Friends, by mutual accept.

The open queue, bucketed by region and skill, with the band widening every second waited.

Learn you matched through presence, not by polling:

GET /matchmaking/presence      (WebSocket)
→ { "type": "match_found", "sessionId": "…", "gameKey": "duel" }

Playing

GET  /multiplayer/sessions/<id>          your redacted view
POST /multiplayer/sessions/<id>/action   your move
GET  /multiplayer/sessions/<id>/socket   live play

The action body is whatever the game’s model defines, forwarded untouched.

GET /sessions/:id returns your view. Opponents’ hidden state stays hidden — there is no route that returns the raw state, so a client cannot read what it should not know.

This is turn-based, and deliberately

Fairness you can show the player

Every session commits its random seed’s SHA-256 hash up front — before a die is rolled — and reveals the seed when the session ends.

Show the commitment in your UI. An auditor, or a suspicious player, can hash the revealed seed against it and replay the stream to verify every roll.

Money

Hold the stake at the wager, capture or release at the result. The balance is unchanged while held, so the stake is unspendable without having been spent.

A hold a player cannot cover rejects the action, and the transition is never persisted.

ESC