MMatchdock

Dedicated server

Matchdock starts one copy of your server for every match and stops it when the match is over. This page is the contract between the two.

How your server is started

Inside a container, with the command line from your build's argument template and the session in the environment:

Variable Meaning
MATCHDOCK_API_URL Base URL for the calls below.
MATCHDOCK_SERVER_SESSION_ID The session this process serves.
MATCHDOCK_SERVER_SECRET Authenticates this process. Never log it.
MATCHDOCK_MATCH_ID The match.
MATCHDOCK_SERVER_PORT The UDP port to listen on (also available as {port} in arguments).

Argument templates may use {port}, {serverSessionId}, {serverSecret}, {apiUrl} and {matchId}. Prefer the environment for the secret: command lines are visible in process lists.

The sandbox

  • Read-only root filesystem; your build is mounted read-only at /game. Only /tmp is writable, and HOME points there.
  • Unprivileged user, no Linux capabilities, no privilege escalation.
  • CPU and memory limits from the build (defaults: 1 core, 1024 MB, no swap).
  • Only the session's port is published. Outbound HTTPS works; CA certificates are present in the runtime image.
  • stdout and stderr are captured per session.

Do not write next to your executable and do not depend on the working directory.

1. Admit players

When a client connects and presents its join token, ask Matchdock:

POST /internal/sessions/{serverSessionId}/players/{userId}/connected
{ "serverSecret": "…", "joinToken": "…", "slot": 1 }

2xx - the player belongs to this match in that slot: let them in. 4xx - drop the connection. 5xx or no answer means Matchdock is unreachable, not that the token is bad; refuse the player rather than admit someone unverified. When every expected player is admitted the session becomes ACTIVE by itself.

Report what happens afterwards:

POST /internal/sessions/{serverSessionId}/players/{userId}/heartbeat      { "serverSecret": "…" }
POST /internal/sessions/{serverSessionId}/players/{userId}/disconnected   { "serverSecret": "…", "reason": "CLIENT_DISCONNECTED" }

A heartbeat every 10 seconds per connected player is plenty; a player silent for 30 seconds is considered disconnected.

2. Report the result

POST /internal/sessions/result
{
  "serverSessionId": "…", "serverSecret": "…",
  "winnerUserId": "…", "loserUserId": "…",
  "winnerScore": 3, "loserScore": 1,
  "durationSeconds": 184, "serverBuildVersion": "1.4.0"
}

A draw uses "resultType": "DRAW" with player1UserId, player2UserId and equal player1Score / player2Score.

The call is idempotent: sending it again returns the stored result and never counts a match twice. So retry it - on network errors, 429 and 5xx, with backoff (1, 2, 4, 8, 16 seconds) - and do not retry a 4xx. A player who quits is a normal win for the opponent; report it like any other result.

3. Exit

Exit code When
0 Only after the result was accepted.
non-zero Anything else: nobody showed up, a fatal error, a rejected result.

Exit on your own; do not wait to be killed. Handle SIGTERM (sent when a session is cancelled) by shutting down within a few seconds. Give up and exit non-zero if players do not arrive - for example no player after 90 seconds.

What Matchdock does when a server misbehaves

These exist so a bad build cannot corrupt ratings - not so a server can skip reporting.

  • Exit 0 without a result → the match is ABORTED (SERVER_EXITED_WITHOUT_RESULT), no rating change.
  • The server exits without a result and one player had clearly left before the other → the player who stayed wins by forfeit.
  • Both players vanish together (crash, network) → aborted, nobody loses rating.
  • A player stays disconnected past the 30-second reconnect window and no result arrives → forfeit awarded by the platform.
  • A session that makes no progress is stopped: about 3 minutes waiting for players, 15 minutes of play.