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 APIs — auth (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.
#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.