Skip to content

C / C++ tutorial — build multiplayer against the Lattice C ABI

A complete, hands-on guide to the Lattice C ABI — the single extern "C" header (lattice/lattice.h) that the whole networking suite exposes. Everything an engine binding (Unity / Unreal / Godot) does, it does by calling this surface. So this tutorial teaches the foundation layer directly: you create a lattice_runner, register replicated types, spawn objects, send RPCs and events, and pump the fixed-tick loop — all in plain C/C++.

This tutorial teaches the real API that actually runs, grounded in code you can read and compile in the repository. Every function name, signature, and snippet is taken from the shipped public header (reference/include/lattice/lattice.h) and the working, runnable test drivers and examples (reference/tests/, examples/). Where something is a stub, an extension point, or only partially wired, we say so plainly.

This is the layer the engine bindings wrap

The Unity binding (com.lattice.netcode), the Unreal and Godot bindings, and the WASM host are all thin adapters over this exact C ABI. If you are writing a dedicated server module, a headless simulation, or your own engine integration, this is the surface you target. If you are in Unity, read the Unity tutorial instead — it wraps everything here in idiomatic C#.
Verified against: reference/README.md (the header "is the only file a binding includes"); reference/include/lattice/lattice.h:1-13.

This is the reference skeleton, not the production core

The liblattice.so you build here is a faithful, compilable model of the documented ABI + wire encodings, with a working loopback transport, real UDP transport, delta replication, and reliable RPC. It deliberately omits crypto, NAT/relay, client-side prediction/rollback, and interest management — those are called out at their extension points. The ABI shape, the wire codec, and the runner semantics are real and exercised by the conformance harness.
Verified against: reference/include/lattice/lattice.h:9-14; reference/README.md ("What it deliberately omits").

What you'll build

By the end you will have built, from the repository's own working drivers, a real UDP game server and a real UDP client that:

  • start as SERVER / CLIENT runners over an actual 127.0.0.1 UDP socket,
  • complete a handshake (the client observes LATTICE_CONN_CONNECTED),
  • register a shared replicated type and spawn + replicate an authoritative object,
  • exchange RPCs and custom events in both directions, and
  • shut down cleanly.

That is exactly what reference/tests/two_process.cpp (client) and reference/tests/persistent_server.cpp (server) do — both include only <lattice/lattice.h>, exactly like your code will. We build chapter 09 directly from them.

Verified against: reference/tests/two_process.cpp:1-27, reference/tests/persistent_server.cpp:1-11.

Chapters

# Chapter What it covers
00 This page Overview and orientation
01 Setup Build liblattice.so (build.sh), include lattice.h, compile + link
02 Your first runner lattice_runner_createset_callbacksstart / listen / connect → the tick loop
03 Logging in The auth HTTP service → an access token; how a C app makes the call
04 Lobbies & matchmaking The director flow → lattice_runner_connect; verified wiring status
05 Networked objects lattice_register_type + field specs, lattice_spawn, on_state_updated
06 RPCs lattice_rpc + targets + the reliable flag
07 Events lattice_send_event / on_event, the object-less message channel
08 Authority Host-authoritative validation; lattice_request_authority / on_authority_changed
09 Full walkthrough A minimal UDP server + client, end to end from the real drivers

How to read this

Chapters build on each other. If you are new to Lattice, read them in order. If you already know a netcode C API, skim 02 and jump to 05.

The one rule that governs the whole ABI

Read this now; it explains the shape of every later chapter.

Callbacks fire synchronously inside lattice_runner_tick() on the calling thread. No exception may propagate out of a callback into the core. All buffers are caller-owned; the core copies anything it retains. Pointers handed to a callback are valid only for the duration of the call.

Verified against: reference/include/lattice/lattice.h:22-26 and :290-292.

Because callbacks land inside your tick call, on your thread, there is no cross-thread marshalling to worry about for gameplay: your on_spawned / on_state_updated / on_rpc / on_event handlers run where you called tick. (The one exception is the optional async job pool / web fetch, whose results are drained back onto the tick thread before your callback runs — see chapter 03.)

Conventions

  • c / cpp blocks are real or faithfully-adapted code from the header, the tests, or the examples.
  • Verified against: notes cite the source file (and line) each claim is grounded in.
  • Where a feature is an extension point, a stub, or not wired in the reference, a warning or danger admonition calls it out.

See it applied end to end

Once you have worked through a few chapters, the Battleship case study shows the whole surface used in anger by a complete, shipped two-player game — including the parts this tutorial cannot tell you until you meet them: that the live relay is one shared world so every payload needs a room stamp, that the reliable flag on lattice_send_event is currently discarded so you need your own ack/resend, and how to handle hidden per-player state on a server that runs no game logic.


Next: 01 — Setup, where you build liblattice.so and compile your first program against the header.