Cenosis Docs Integration Guide
v4.0 · ABI major 4

Integration Guide

Cenosis exposes exactly two integration surfaces: a C ABI for native engines (Unity / Unreal / any C-compatible host) and a WebAssembly surface for browsers. Both share the same underlying engine — the WASM build is an SS-safe browser branch of the same codebase.

The Two-Pane Mental Model

There are exactly two things a host does:

  1. Ingress — push events from the game into the engine: npc_engine_record_event(event_json). Calling from the game thread is safe — returns in microseconds.
  2. Egress — pull structured actions from the engine: peek_action_sizepop_action. The host drains on its own schedule.

Everything else — ticking, LLM calls, social updates, WAL projection, budget governance, storylet evaluation, scene management, bundle save/load — is internal to the engine.

Native (C ABI) — Unity / Unreal

Building

.\build-ffi.ps1

Produces a platform-native cdylib (DLL on Windows, .so on Linux, .dylib on macOS) via cargo-zigbuild. Six targets are built in CI: x86_64-windows, aarch64-windows, x86_64-linux, aarch64-linux, x86_64-macos, aarch64-macos.

Lifecycle

npc_engine.h
EngineHandle* npc_engine_init(const char* config_json);
void          npc_engine_shutdown(EngineHandle* handle);

EngineHandle is opaque — a boxed Engine with no global state. Multiple engines can coexist in the same process (the test suite relies on this).

Config JSON (key fields)

FieldTypeDescription
storage_pathstringDirectory for the WAL and SQLite projection store.
rng_seedu64Determinism seed for SS social encounters and storylets.
primary_channel_capacityu32Ingress channel buffer size (default: 4096).
budgetobject?Per-agent, session, and hourly LLM spend caps.
cloudobject?Cloud provider config. Omit for SS-only (no narrative).
max_full_sim_agentsu32Hard cap on concurrent FS agents (default: 8).

Ingress — record_event

int npc_engine_record_event(EngineHandle*, const char* event_json);
// Returns:  0 = enqueued on primary channel
//           1 = primary saturated, enqueued on overflow
//          -1 = JSON parse / null pointer error
//          -2 = engine not initialized

Call this from your game thread. It must return in microseconds — internally it does JSON parse → crossbeam-channel::try_send → return. Never block.

Tick

int npc_engine_tick(EngineHandle*, double now_seconds);

now_seconds is real wall time in seconds. The engine converts to game-time delta internally. Call once per game tick (or at your desired simulation frequency).

Egress — peek-then-pop

uint32_t npc_engine_peek_action_size(EngineHandle*);
int      npc_engine_pop_action(EngineHandle*, char* buf, uint32_t buf_len);
// Returns: >0 = bytes written
//          -2 = buf_len too small (message NOT dropped)
//          -3 = queue empty

Always peek first. An undersized buffer returns -2 without dropping the message — this is the only way to bound your allocation cost. Pattern:

uint32_t sz = npc_engine_peek_action_size(handle);
while (sz > 0) {
    char* buf = malloc(sz);
    npc_engine_pop_action(handle, buf, sz);
    handle_action(buf);
    free(buf);
    sz = npc_engine_peek_action_size(handle);
}

Proximity — tier transitions

int npc_engine_set_proximity(EngineHandle*, const char* agent_id_json, float distance, double now_seconds);

Call whenever agent-to-player distance changes. The engine handles hysteresis (enter FS at 20m, exit at 30m by default). FS promotion is capped at max_full_sim_agents.

Save / Load

// Export world state to a NPCB v17 bundle
int npc_engine_export_bundle(EngineHandle*, const char* path);
// Import a bundle into a fresh engine handle
int npc_engine_import_bundle(EngineHandle*, const char* path);
// Error codes: -5 = I/O error, -6 = corrupt bundle, -7 = unsupported version

Generated Bindings

npc-codegen generates host bindings from the frozen ABI surface. Committed artifacts:

  • npc-ffi/bindings/NpcEngineV40.cs — Unity C# (P/Invoke wrappers)
  • npc-ffi/bindings/NpcEngineV40.hpp — Unreal C++ (RAII handle)
  • npc-ffi/bindings/NpcEngineV40.ts — TypeScript (for non-WASM native Node)

Binding drift is gated by npc-tests::v40_codegen_hosts in CI — generated output must be byte-identical to committed artifacts.

WebAssembly (Browser / Three.js)

Building

.\build-wasm.ps1

Produces cenosis_wasm_bg.wasm + cenosis_wasm.js glue via wasm-pack.

Required HTTP Headers

COOP / COEP Required

The WASM build uses SharedArrayBuffer for main↔worker communication over OPFS. Your server must send these headers:

Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp

Web Worker Setup

The engine must run in a dedicated Web Worker — OPFS sync handles require workers. Main thread → worker communication is via SharedArrayBuffer (requires the headers above) or standard postMessage.

worker.js
import { EngineWasm } from './cenosis_wasm.js';

const engine = new EngineWasm(configJson);
await engine.init_store('my_world.db');  // OPFS-backed SQLite

self.onmessage = ({ data }) => {
  switch (data.type) {
    case 'tick':
      engine.tick(data.nowSeconds);
      const actions = engine.pop_actions_json();
      if (actions) self.postMessage({ type: 'actions', actions });
      break;
    case 'event':
      engine.record_event(data.eventJson);
      break;
    case 'proximity':
      engine.set_proximity(data.agentId, data.distance, data.nowSeconds);
      break;
  }
};

WASM API Surface

MethodDescription
new EngineWasm(configJson)Construct and configure the engine.
await init_store(dbName)Initialize OPFS-backed SQLite storage.
record_event(eventJson)Ingest a game event (non-blocking).
tick(nowSeconds)Advance simulation by one world tick.
pop_actions_json()Drain all pending NPC actions as a JSON array string.
peek_metrics()Return engine metrics JSON (budget, circuit breaker state, etc.).
set_proximity(agentId, distance, now)Update agent-to-player distance for tier promotion.
export_bundle(dbName)Export world state bundle to OPFS.
import_bundle(dbName)Import a bundle from OPFS.
register_decision_hook(seam, fn)Register a JS function to override a policy seam decision.
unregister_decision_hook(seam)Remove a decision hook (restores engine default).

WASM streaming

Provider streaming (LLM token-by-token output) is deferred on wasm32. Structured conversation, subscriptions, relationship/snapshot queries, and bundle import/export have full parity with native.