Skip to content

01 — Setup

Before any netcode you need two things:

  1. the liblattice.so shared library (the core), and
  2. the lattice/lattice.h header (the only file you include).

Your program includes the header, links the library, and calls the extern "C" functions. That's the whole integration surface — no internal headers, no C++ types across the boundary.

Verified against: reference/README.md (include/lattice/lattice.h — "the only file a binding includes"); reference/include/lattice/lattice.h:5-8.

Requirements

  • A C++ toolchain: g++ with -std=c++20 (tested on g++ 11). The examples use no external dependencies.
  • pthread (the core links -lpthread for its worker job pool).

Verified against: examples/README.md ("Toolchain: g++ with -std=c++20 … No external dependencies"); reference/build.sh:61-62.

You include a C header, but you can compile as C++

lattice.h is pure C wrapped in extern "C", so it is callable from C or C++. Every example and test in the repo is C++ (.cpp), which is the path this tutorial follows. The header guards the extern "C" with #ifdef __cplusplus, so a C compiler works too.
Verified against: reference/include/lattice/lattice.h:42-44.

1. Build liblattice.so

The reference ships a g++ fallback build (build.sh) that needs no cmake. From reference/:

Build the shared library + conformance harness
cd reference
./build.sh            # build only  → build/liblattice.so
./build.sh run        # build, then run the conformance harness

This produces reference/build/liblattice.so (plus lattice_conformance, lattice_relay, and lattice_nat_unit). The library is built -fPIC -fvisibility=hidden with -DLATTICE_BUILD=1, so only the LATTICE_API-marked ABI functions are exported — no C++ symbols leak.

Verified against: reference/build.sh:57-62, :96; the LATTICE_API visibility macro at reference/include/lattice/lattice.h:38-40.

There is also a CMake path

If you prefer cmake, reference/CMakeLists.txt builds the same targets:

mkdir -p build && cd build && cmake .. && make
Both paths produce the same liblattice.so.
Verified against: reference/README.md ("Build & run"); reference/CMakeLists.txt.

Confirm the exported C symbols

The whole ABI is C. You can see exactly what the library exposes:

nm -D --defined-only build/liblattice.so | grep lattice_

Every symbol is a flat lattice_* C function — that flat surface is the contract your binding compiles against.

Verified against: reference/README.md ("Inspect the exported C symbols").

Point the compiler at the header with -I, link the library with -L/-llattice, and add -lpthread. Use an $ORIGIN rpath so the executable finds liblattice.so sitting beside it, regardless of the working directory — this is exactly how the reference links its conformance harness:

Compile a program against the public header only
g++ -std=c++20 -O2 \
    -I/path/to/reference/include \
    -o my_server my_server.cpp \
    -L/path/to/reference/build -llattice -lpthread \
    -Wl,-rpath,'$ORIGIN'

Verified against: reference/build.sh:64-69 (the conformance harness is compiled with exactly this shape: -I…/include, -L…/build -llattice -lpthread -Wl,-rpath,'$ORIGIN').

Your .cpp includes just the one header:

my_server.cpp — the only include you need from Lattice
#include <lattice/lattice.h>

Verified against: reference/tests/two_process.cpp:28 (#include <lattice/lattice.h> — the file includes nothing else from Lattice); examples/starter-server-module/server_main.cpp:20.

A ready-made template build script

The examples/starter-server-module/build.sh is a copy-paste starting point. It compiles server_main.cpp against the header and links liblattice.so, using -rpath so the binary finds the library:

From examples/starter-server-module/build.sh (abridged)
"$CXX" -std=c++20 -Wall -Wextra -O2 \
  -I"$SNAP/include" \
  -o "$OUT/starter_server" \
  "$HERE/server_main.cpp" \
  -L"$LIBDIR" -llattice -Wl,-rpath,"$LIBDIR" -lpthread

Verified against: examples/starter-server-module/build.sh:46-50.

The examples build their own copy of liblattice.so

To stay decoupled from the live core tree, the examples build the library from a snapshot (examples/anti-cheat-demo/native-snapshot/) into examples/anti-cheat-demo/build/, and the starter module reuses it (building it on demand if missing). To build against your own Lattice install instead, point the -I (header) and -L/-rpath (library) flags at it. Running bash build.sh run in either example builds everything and runs it.
Verified against: examples/README.md ("Decoupling", "Notes"); examples/starter-server-module/build.sh:36-40.

3. Verify it works

Run the reference conformance harness — it links liblattice.so through the public header only (exactly like your binding) and round-trips the bit codec, then stands up a SERVER and a CLIENT runner over the loopback transport and asserts spawn/replicate/RPC/despawn end to end:

cd reference
./build.sh run

It prints [PASS]/[FAIL] per check, a summary, and returns non-zero on any failure.

Verified against: reference/README.md ("What the conformance harness proves"); reference/build.sh:98-101.

You can also build and run the two runnable examples:

cd examples/starter-server-module && bash build.sh run   # a minimal server module
cd examples/anti-cheat-demo       && bash build.sh run   # the opt-in anti-cheat layer

Verified against: examples/README.md ("Build & run" for each example).

The build baked-in version

Each build stamps a build-version id into the library as the exported lattice_module_version() symbol (format <version>_<YYYYMMDD>_<epoch_millis>). It never enters the wire format, so it can't affect determinism; it exists so an operator can confirm which build is running. You can print it from any program:

std::printf("module-version=%s\n", lattice_module_version());

Verified against: reference/include/lattice/lattice.h:407-419; examples/starter-server-module/server_main.cpp:110; the id is generated + baked in at reference/build.sh:13-26.


Next: 02 — Your first runner, where you create a lattice_runner, wire up callbacks, and pump the tick loop.