Skip to content

Runner lifecycle & the tick pump

A runner (lattice_runner) is one networked simulation instance. The lifecycle is the same for every role; the role is chosen by lattice_runner_start.

The standard sequence

flowchart TD
  C["lattice_runner_create(cfg)"] --> SC["lattice_runner_set_callbacks(cb)"]
  SC --> S["lattice_runner_start(MODE)"]
  S -->|SERVER / HOST / SHARED_HOST| L["lattice_runner_listen(port)"]
  S -->|CLIENT| CO["lattice_runner_connect(addr, port, token)"]
  L --> RT["register_type ×N — identically on every peer"]
  CO --> RT
  RT --> T["loop: lattice_runner_tick(dt) — recv → tick → send; callbacks fire here"]
  T --> D["lattice_runner_destroy(r)"]

lattice_runner_create

LATTICE_API lattice_runner* lattice_runner_create(const lattice_runner_config* cfg);

Create a runner. Role: both.

Param Meaning
cfg Optional lattice_runner_config. May be NULL for all defaults. Deep-copied; need not outlive the call.

Returns: an owning lattice_runner*, or NULL on allocation failure. Destroy with lattice_runner_destroy.

lattice_runner_config

typedef struct {
    uint32_t tick_rate_hz; /* fixed simulation rate; default 60 if 0 */
    uint32_t max_objects;  /* soft hint; 0 => unbounded in this skeleton */
    uint32_t reserved;
    uint32_t worker_threads; /* async job pool size; 0 => a small default */
} lattice_runner_config;
  • tick_rate_hz — fixed simulation rate (default 60 Hz). max_objects is a soft capacity hint (0 ⇒ unbounded in the reference). Leave reserved zeroed.
  • worker_threads new — the number of worker threads the runner spins up for the off-the-main-thread job system and the mediated web fetch. 0 ⇒ a small default pool. Additive: appended after reserved, so a caller that zero-inits the struct (the documented pattern) gets the default pool with no behaviour change.
lattice_runner_config cfg = {0};
cfg.tick_rate_hz = 60;
cfg.max_objects  = 4096;
cfg.worker_threads = 4;        /* optional; 0 is fine */
lattice_runner* r = lattice_runner_create(&cfg);
if (!r) { /* OOM */ }

lattice_runner_set_callbacks

LATTICE_API lattice_result lattice_runner_set_callbacks(lattice_runner* r,
                                                        const lattice_callbacks* cb);

Install the event callback table (lattice_callbacks). Role: both. Set this before start/listen/connect so no event is missed. The struct (including user_data) is copied; the function pointers must remain valid for the runner's lifetime.

Returns: LATTICE_OK, or LATTICE_ERR_INVALID_ARG if r is NULL.


lattice_runner_start

LATTICE_API lattice_result lattice_runner_start(lattice_runner* r, lattice_game_mode mode);

Start the runner in a role. Role: both. The mode is the one knob that distinguishes a dedicated server, a P2P host, a client, and a shared-authority host — the rest of your module code is byte-for-byte identical across them.

LATTICE_MODE_SERVER      = 0  /* dedicated server: authority, typically headless    */
LATTICE_MODE_HOST        = 1  /* P2P host: authority + a local participant          */
LATTICE_MODE_CLIENT      = 2  /* predicts/converges; holds no server-auth authority */
LATTICE_MODE_SHARED_HOST = 3  /* shared/distributed authority arbiter                */

Returns: LATTICE_OK, or LATTICE_ERR_INVALID_ARG.


lattice_runner_listen

LATTICE_API lattice_result lattice_runner_listen(lattice_runner* r, uint16_t port);

Open the authority's session on port. Role: authority (SERVER / HOST / SHARED_HOST). Returns: LATTICE_OK or an error.


lattice_runner_connect

LATTICE_API lattice_result lattice_runner_connect(lattice_runner* r, const char* addr,
                                                  uint16_t port,
                                                  const uint8_t* token, uint32_t token_len);

Connect a client to an authority. Role: CLIENT. token/token_len is the connect token (a director-minted session token in production); pass the bytes your auth flow produced. The buffer is caller-owned and copied.

Returns: LATTICE_OK or an error (e.g. LATTICE_ERR_INVALID_ARG).

uint8_t token[4] = {'a','u','t','h'};
lattice_runner_connect(r, "127.0.0.1", 9000, token, 4);

lattice_runner_tick

LATTICE_API lattice_result lattice_runner_tick(lattice_runner* r, double dt);

Pump one or more fixed steps. Role: both. The per-step order is recv → tick → send, and all callbacks fire synchronously on the calling thread inside this call — including on_spawned, on_state_updated, on_rpc, on_event, on_authority_changed, on_violation, on_http_result, and on_store_result. Do no blocking work in a callback.

A headless server typically runs it in a fixed-timestep accumulator:

const double dt = 1.0 / 60.0;
while (running) {
    /* sample inputs / run game logic that mutates state + marks dirty ... */
    lattice_runner_tick(r, dt);
    /* sleep to pace to the tick rate */
}

Returns: LATTICE_OK or an error.


lattice_runner_state

LATTICE_API lattice_connection_state lattice_runner_state(lattice_runner* r);

The runner's current connection state — one of LATTICE_CONN_DISCONNECTED, LATTICE_CONN_CONNECTING, LATTICE_CONN_CONNECTED, LATTICE_CONN_DISCONNECTING. Role: both.


lattice_runner_destroy

LATTICE_API void lattice_runner_destroy(lattice_runner* r);

Destroy the runner and free everything it owns. Role: both. Safe on NULL. After this the handle is dangling; do not reuse it.


lattice_module_version new

LATTICE_API const char* lattice_module_version(void);

The build-time version id baked into this binary, of the form <versionnumber>_<YYYYMMDD>_<epoch_millis> — e.g. "1.2.0_20260623_1782227746626". It is generated once per build invocation and embedded by the build script (the generated lattice_version.c). Role: both; takes no runner. The string never enters the wire/snapshot format, so it cannot affect determinism (conformance stays byte-identical).

Returns: a static string owned by the library — do not free it. Returns the literal "0.0.0_00000000_0" if a build somehow did not stamp an id (the symbol is always defined, so callers need no fallback of their own). The host/director reports this at runtime so the live module version is dashboard-verifiable.

printf("lattice build: %s\n", lattice_module_version());
/* => lattice build: 1.2.0_20260623_1782227746626 */