Skip to content

Path A — Self-hosted / P2P (native)

You run the authoritative server on infrastructure you trust — your own dedicated fleet, or a player acting as a P2P host. The module is your code on your machines, so no sandbox is needed: you build it native and link liblattice directly. This path is the full 07 — Custom Module Guide, distilled.

Prerequisites

Finish the common setup first (game registered, SDK integrated, sim authored). Tooling: a C++20 compiler (g++/clang), and for Windows the MinGW-w64 -posix cross-toolchain.

1. Build liblattice

cd reference
./build.sh run
# => PASS: 130   FAIL: 0   TOTAL: 130   /   RESULT: ALL CHECKS PASSED   (exit 0)

This produces build/liblattice.so. Copy reference/include/lattice/lattice.h and the library next to your module.

2. Build your module against the public header

Compile your game-sim/ once and link it into each target. The only difference between a dedicated server and a P2P host is the start mode.

Linux dedicated server
g++ -std=c++20 -O2 -Iinclude \
    game-sim/schema.cpp game-sim/simulate.cpp server/main.cpp \
    -o build/lattice-gameserver \
    -Lbuild -llattice -Wl,-rpath,'$ORIGIN'
Linux P2P host — same module objects, one different main
g++ -std=c++20 -O2 -Iinclude \
    game-sim/schema.cpp game-sim/simulate.cpp host/main.cpp \
    -o build/lattice-host \
    -Lbuild -llattice -Wl,-rpath,'$ORIGIN'
server/main.cpp vs host/main.cpp differ by ONE line
lattice_runner_start(r, LATTICE_MODE_SERVER);  /* dedicated server */
lattice_runner_start(r, LATTICE_MODE_HOST);    /* P2P host         */

Windows (MinGW-w64 cross-build)

cd reference && ./build-windows.sh run   # cross-builds, runs the .exe under wine: 130/130

Three Windows requirements are baked into the recipe: the -posix thread-model compiler (x86_64-w64-mingw32-g++-posix), the -static -static-libgcc -static-libstdc++ flags (so the DLL/EXE depend only on core Windows DLLs), and _WIN32_WINNT=0x0600. The resulting lattice.dll is self-contained. Full detail in 07 §6.

3. Run it

# Ship liblattice.so next to the binary (found via $ORIGIN rpath)
./build/lattice-gameserver         # opens the authority session on its listen port

Register the running instance with the director so clients can find it:

curl -s -X POST http://director/fleet/register -H "X-Fleet-Token: $FLEET" \
  -H 'content-type: application/json' \
  -d '{"instance_id":"i-1","endpoint":"1.2.3.4:9000","region":"eu","modes":["ffa"],"capacity":64}'

Then clients matchmakeresolveconnect (the common-setup sequence).

4. Wire up the server-side features

Because you control the process, you can use the authority-only ABI directly:

flowchart LR
  SRC["game_sim.cpp (authored once)"] -->|"g++/clang + link liblattice"| BIN["native binary"]
  BIN --> SRV["dedicated server (SERVER)"]
  BIN --> HOST["P2P host (HOST)"]
  SRV -->|fleet/register| DIR[director]
  HOST -.player-hosted.-> DIR

Untrusted native code is a different story

Self-hosting assumes you trust the binary (it's yours). If you ever need to run untrusted native modules from third parties, that needs a microVM boundary — not an import scan. For untrusted code, prefer Path B — managed WASM. See 09 §8.


The exact same game_sim.cpp you built here also compiles to WASM with no code change — that's Path B, explained in write-once, dual-target.