Skip to content

04 — Lobbies & matchmaking

With an access token (chapter 03) you ask the lattice-director service to place you in a session and hand back a server endpoint + a session token, which you then feed to lattice_runner_connect.

The getting-started guide states the flow explicitly at the C level:

sequenceDiagram
  participant App as Your C/C++ client
  participant AU as auth
  participant DI as director
  participant GS as Game server (your sim)
  App->>AU: login → access_token
  App->>DI: matchmake (Bearer access_token) → session_handle
  App->>DI: resolve → { endpoint, session_token }
  App->>GS: lattice_runner_connect(endpoint, session_token)

Verified against: docs-site/docs/getting-started/common-setup.md:43-52.

Read this first — the core has no matchmaking client

Just like auth, the director endpoints are plain HTTP you call yourself. lattice.h exposes no /matchmake or /resolve function — the netcode ABI stops at lattice_runner_connect. The director's endpoints are fully implemented server-side and return real data, but the glue (making the HTTP calls, splitting the endpoint, enforcing the token server-side) is yours to write. The exact status is at the end of this chapter.
Verified against: absence of any matchmake/resolve/director symbol in reference/include/lattice/lattice.h; docs-site/docs/getting-started/common-setup.md:39-41.

The director runs on port 3010 in local dev. The /matchmake and /resolve request/response shapes, their status codes, and the session-token claims are documented and verified per-line in the Unity tutorial chapter 04 — they are the same HTTP service regardless of client language, so those shapes apply to you unchanged. The rest of this chapter covers the part that is specific to a C/C++ integrator: turning the resolve result into a lattice_runner_connect call, and the honest wiring status.

Verified against: director port 3010 at run-all.sh:30 (PORT_DIRECTOR=3010); the endpoint contracts at Unity chapter 04.

The two director calls (summary)

Both require the auth Bearer token from chapter 03.

  • POST /matchmake places you in a session and returns a session_handle, a short room_code and an endpoint (host:port).
  • POST /resolve exchanges either identifier for concrete connect details and mints a session token. Resolving by room_code also needs your game's X-Lattice-Api-Key, because short codes are unique within a game rather than globally:
POST /resolve → 200 (documented shape)
{
  "endpoint": "1.2.3.4:9000",
  "session_token": "eyJ…",       // director-signed Ed25519 JWT — pass to connect
  "session_id": "b1f2…"
}

Verified against: the /matchmake + /resolve contracts documented in Unity chapter 04 (grounded there against control-plane/lattice-director/src/LatticeDirector/Program.cs and …/Api/Contracts.cs; control-plane/lattice-director/src/LatticeDirector/Program.cs confirmed present in the repo).

Feeding /resolve into lattice_runner_connect

Here is the C-specific work. lattice_runner_connect takes a host (const char*), a uint16_t port, and a token as bytes + length — so you split the endpoint string on : and pass the session token as raw bytes:

The connect signature (reference/include/lattice/lattice.h:374-375)
lattice_result lattice_runner_connect(lattice_runner* r, const char* addr, uint16_t port,
                                      const uint8_t* token, uint32_t token_len);

Verified against: reference/include/lattice/lattice.h:374-375.

From /resolve to connect (you write this glue)
// resolve returned:  std::string endpoint (e.g. "1.2.3.4:9000"),  std::string sessionToken (a JWT)
auto colon   = endpoint.rfind(':');
std::string host = endpoint.substr(0, colon);
uint16_t    port = (uint16_t)std::stoi(endpoint.substr(colon + 1));

lattice_runner_start(r, LATTICE_MODE_CLIENT);
lattice_runner_connect(r, host.c_str(), port,
                       (const uint8_t*)sessionToken.data(),
                       (uint32_t)sessionToken.size());

Verified against: reference/include/lattice/lattice.h:374-375; the "split the host:port endpoint and pass the token bytes" pattern is the same one the Unity binding uses (Unity chapter 04).

The endpoint is host:port as a string; there is no parser in the core

The core takes host and port separately — it does not parse a combined host:port. Split it yourself (the sample above splits on the last : so IPv6-less hosts and explicit ports work).
Verified against: reference/include/lattice/lattice.h:374 (separate addr + port parameters).

Where game servers come from

The director does not spawn servers. A session's endpoint is the address a running game-server instance advertised by registering itself with the fleet. In the self-hosted path, you run the server — a LATTICE_MODE_SERVER runner that lattice_runner_listens on a port (exactly chapter 02's persistent_server.cpp) — and register its endpoint with the director. With no live registered instance matching the requested region+mode, /matchmake returns 409.

Verified against: reference/tests/persistent_server.cpp:43-55 (a real SERVER + listen you run yourself); the fleet-registration + 409 behaviour documented in Unity chapter 04.

Verified wiring status (honest)

What is real:

  • The connect target is real. lattice_runner_connect(addr, port, token, token_len) accepts the host, port, and session_token bytes you obtained. This is the verified boundary between the control plane and the netcode.
    Verified against: reference/include/lattice/lattice.h:374-375.
  • The director endpoints are real and return real data (a session_handle, an endpoint, and a genuine Ed25519-signed session_token), documented and per-line-verified in Unity chapter 04; control-plane/lattice-director/ is present in the repo.

The caveats you must design around:

  1. The core includes no matchmaking client. lattice.h has no /matchmake or /resolve function — you make those HTTP calls yourself (your TLS HTTP stack, or the plaintext-only mediated fetch from chapter 03), parse the JSON, split the endpoint, and pass the token bytes to lattice_runner_connect.
    Verified against: absence of any matchmaking symbol in reference/include/lattice/lattice.h.
  2. You must run and register a game server. The director never launches servers; you start your own (LATTICE_MODE_SERVER + lattice_runner_listen) and register its endpoint. With no live instance for the requested region+mode, /matchmake returns 409.
    Verified against: reference/tests/persistent_server.cpp:43-55; the 409 behaviour in Unity chapter 04.
  3. Session-token enforcement at the game server is yours to wire. The /resolve token is designed to be verified offline by the game server at the netcode handshake, but the reference skeleton omits crypto — the loopback/UDP handshake accepts an arbitrary token (the tests use "auth"). Verifying the director-signed token at the server is a responsibility of a production core / your integration, not something the reference proves.
    Verified against: reference/include/lattice/lattice.h:12-14 (the skeleton "deliberately omits crypto"); reference/tests/two_process.cpp:251-252 (arbitrary "auth" token accepted).

Bottom line: the matchmake → resolve → connect contract is real and returns real values; the glue (calling the director, splitting the endpoint, and enforcing the token server-side) is yours. For learning and for local/P2P play, skip the director entirely and use the listen-server flow from chapter 02 — the path the tests verify end to end.


Where the session token actually gets checked

Passing the token to lattice_runner_connect only delivers it. The game server verifies it inside the handshake, through the token-validation seam of the secure datagram channel — which is also what turns that channel from unauthenticated into authenticated.


Next: 05 — Networked objects, the heart of replication.