Files
kickkingsapi/docs/unity_match_lifecycle.md
T
2026-08-14 20:55:11 +00:00

4.1 KiB

Unity dedicated server — match lifecycle (escrow + always finish)

Contract for the Kick Kings dedicated server build against the matchmaker API. Header on all /internal/* calls: X-Dedicated-Server-Secret: <secret from settings>.

Goals

  1. Collect entry fees into a per-match escrow only after both players are connected.
  2. Pay the winner (and house fee) only from that escrow.
  3. Never leave a match unfinished — always report a winner, including disconnect forfeits.

Sequence

1. Match row exists (matchmaking or rematch) with match_id, user_red, user_blue, entry_fee
2. Red connects  → PATCH /internal/match/:matchId  { "red_joined_at": "<ISO-8601 UTC>" }
3. Blue connects → PATCH /internal/match/:matchId  { "blue_joined_at": "<ISO-8601 UTC>" }
   → When both join timestamps are set, the API collects entry fees into escrow
4. Start the match only after collect succeeds (see responses below)
5. Match ends (score / disconnect / crash path) → PATCH /internal/match/:matchId/winner
   { "winner": "red" | "blue" }

Join PATCH

PATCH /internal/match/:matchId

Example bodies:

{ "red_joined_at": "2026-08-12T18:00:00.000Z" }
{ "blue_joined_at": "2026-08-12T18:00:01.000Z" }

Success when both have joined (collect ran)

{
  "ok": true,
  "id": 123,
  "entries_collected": true,
  "already_collected": false,
  "escrow_user_id": 456
}

Success when only one side joined yet

{
  "ok": true,
  "id": 123,
  "entries_collected": false
}

Collect failure (fatal — do not start the match)

Typical cases:

HTTP Meaning
402 One or both players lack enough RC for entry_fee
409 Players/joins incomplete (should not happen if you set both joins)
503 DB / economy RPCs not installed on the server

On any non-2xx after the second join, abort kickoff, disconnect both clients, and do not call winner (fees were not escrowed).

Idempotent: repeating the join PATCH after a successful collect returns entries_collected: true (or already_collected: true).

Winner PATCH

PATCH /internal/match/:matchId/winner

{ "winner": "red" }

or

{ "winner": "blue" }

Requires entry fees already collected. Response includes economy summary (entry_fee_rc, rc_prize, participant_cc).

HTTP Meaning
200 Settled (or already settled for the same winner)
409 Entries not collected yet, or winner already set to the other side
500 Escrow inconsistency (should be rare; escalate)

Call winner exactly once when the match outcome is known. Retries with the same side are safe if the first call succeeded.

Always finish — disconnect / forfeit rules

Matches must never end without a winner call after fees were collected.

Track disconnect order on the dedicated server (timestamps or a queue).

  1. One player disconnects mid-match
    Remaining player wins.
    Example: blue disconnects → { "winner": "red" }.

  2. Both players disconnect
    The last player who disconnected is the winner.
    Example: red disconnects first, then blue → blue was last to leave → { "winner": "blue" }.

  3. Process crash / forced shutdown
    If fees were collected, choose a winner with the same rules (remaining player, or last disconnect) and call winner before exit whenever possible.

  4. Normal score / time win
    Report the actual winning side as today.

Do not leave escrow locked with no winner. Do not invent a draw path that skips the winner endpoint.

Rematch

POST /internal/rematch still only creates the room/match row. Entry fees are not taken there. Collect still happens when both *_joined_at are set on the new match_id.

Checklist for the Unity build

  • Set red_joined_at / blue_joined_at in UTC ISO when each client is fully connected
  • Treat 402/4xx/5xx on the second join as fatal — no kickoff
  • Gate gameplay start on entries_collected: true
  • Always call winner after collect (score, forfeit, or last-disconnect-wins)
  • Persist disconnect order until winner is acknowledged