Skip to content

Common setup

These three steps are identical for both self-hosted and managed WASM hosting. Do them first, then branch.

1. Register your game in the portal

In the developer portal (port 3007), create a developer account and register a game. You get a gameId and an API key + secret pair (the secret is shown once).

flowchart LR
  R[Register developer] --> L[Log in] --> G["Create game"] --> K["gameId + ApiKey + ApiSecret<br/>(secret revealed once)"]

Equivalently via the console API:

TOKEN=$(curl -s -X POST http://localhost:3004/developers/login \
        -H 'content-type: application/json' \
        -d '{"Email":"you@studio.gg","Password":"..."}' | jq -r .Token)

curl -s -X POST http://localhost:3004/games -H "authorization: Bearer $TOKEN" \
     -H 'content-type: application/json' \
     -d '{"DisplayName":"My Game","LaunchCommand":"./mygame"}' | jq
# => { "Game": { "GameId": "g_...", "ApiKey": "lat_pub_..." }, "ApiSecret": "lat_sec_..." }

The ApiKey/ApiSecret authenticate your game's server-to-server calls into analytics (X-Lattice-Api-Key/-Secret), tenancy in social, and module uploads.

2. Integrate the SDK / engine binding

Your client talks to two surfaces:

  • the netcode C ABI (liblattice) — via your engine binding (Unity / Unreal / Godot / web), for the realtime session;
  • the control-plane HTTP/WS APIsauth (login → access token), director (matchmake → session token), and social (friends/presence/parties).
sequenceDiagram
  participant App as Your 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)

3. Author your game-sim against the C ABI

Write your deterministic, game-specific logic once: register networked types, spawn authoritative objects, mutate + mark dirty, send/receive RPCs and events. It contains no transport, reliability, replication, crypto, prediction, or NAT code — all of that lives below the C ABI.

The shape of every Lattice module
#include <lattice/lattice.h>

lattice_runner_config cfg = {0}; cfg.tick_rate_hz = 60;
lattice_runner* r = lattice_runner_create(&cfg);

lattice_callbacks cb = {0};
cb.on_spawned = on_spawned; cb.on_state_updated = on_state_updated; cb.on_rpc = on_rpc;
lattice_runner_set_callbacks(r, &cb);

register_types(r);                       /* same order on every peer */

lattice_runner_start(r, LATTICE_MODE_SERVER);  /* ← the one knob: SERVER / HOST / CLIENT */
lattice_runner_listen(r, 9000);

for (;;) {                               /* fixed-tick loop */
    /* mutate state + lattice_object_mark_dirty(...) */
    lattice_runner_tick(r, 1.0/60.0);    /* recv → tick → send; callbacks fire here */
}

See the full Custom Module Guide for the complete schema + registration + RPC example.


Now branch: Path A — self-hosted / P2P (native) or Path B — managed WASM hosting.