Changelog
This changelog summarizes the developer-facing capabilities that are implemented today. It is grouped by area rather than by release number, since Citadel is pre-1.0 and its product surface is still moving quickly.
Realtime transports
Section titled “Realtime transports”- Transport abstraction — one wire-agnostic boundary shared by every transport, with the typed envelope codec, per-connection bounded outbound queues, and reliable/unreliable delivery policies.
- QUIC (
quinn, TLS 1.3, ALPNcitadel/0) — unreliable datagrams + reliable streams. Primary native transport. - WebSocket (
tokio-tungstenite) — reliable-only fallback carrying framed envelopes as binary messages. - WebTransport (
web-transport-quinn) — browser path: QUIC-grade datagrams + streams over HTTP/3 on its own UDP endpoint, with a dev cert-hash flow.
Realtime gateway
Section titled “Realtime gateway”-
Single-room relay gateway — relays application messages to other sessions (no echo to sender), tagging each with the sender’s session id. All transports share one gateway room and interoperate.
-
Server-side RPC dispatch — the gateway routes a
KIND_RPC_REQUESTto a server-sideon_rpchandler and unicasts the correlatedKIND_RPC_RESPONSEback to the caller only (never a broadcast); unknown method, handler error, or a blown deadline all yield a well-formed error response without crashing the node. -
Authenticated handshake — every connection presents its session token in a uniform
KIND_AUTHfirst frame; the server validates it and binds the connection to the account (ctx.user_id), or accepts a guest, or rejects with a coarse reason. Guests-allowed by default; an auth-required stance is available. See Envelope reference and Gateway. TheKIND_AUTH/KIND_AUTH_RESULTkinds are a client-contract change; SDKs pick up the new helpers via the auto-sync fan-out.
Realtime gameplay
Section titled “Realtime gameplay”-
Embedded Lua runtime —
main.luagame logic with lifecycle hooks (on_join/on_leave/on_message/on_rpc/on_tick/on_room_create/on_room_join), a fixed-rate server tick (runtime.tick_hz), failure-safe hot-reload, and scopedrequiremodules. (, , , ) -
Transform sync — snapshot-based movement replication with area-of-interest, client-side interpolation with an adaptive buffer, and an
OwnerPredictedprediction + server-rewind path for authoritative actors. See transform sync. (, , ) -
Networked actors (presence + spawn) — drop-in player replication: a client announces its avatar on connect, the server spawns it for every peer, hands a newcomer everyone already present, and despawns it on disconnect. Movement rides the snapshot path. See networked actors.
-
Server-owned actors / NPCs — Lua game logic spawns and moves actors nobody is connected as (
citadel.spawn_actor/move_actor/despawn_actor,owner = 0), replicated to every client through the same spawn / snapshot / despawn path; late joiners receive live NPCs in their presence batch. Requiresruntime.tick_hz > 0. See server-owned actors. -
Rooms — server-owned, admission-gated groupings; clients join-or-create by name as a matchmaking primitive (same name → same room), with the server owning the map/mode choice via
on_room_createand the admission gate viaon_room_join. Networked-actor visibility is room-scoped. See rooms. (–) -
Maps (level geometry pipeline) — a level’s static collision geometry is exported to a versioned
.map(CMAP) file and loaded server-side: the Unreal editor cook tool (Tools → Citadel → Cook Map Data) writes world-space collision triangles; the server scansruntime.maps_dirat startup, indexes each map by file stem, and resolves a room’smapname against it on create. This is the geometry the server will bake a navmesh from (next phase). See maps.
Shared wire format
Section titled “Shared wire format”citadel-wire— the single source of truth for the envelope and its framed- datagram encodings, plus the relay protocol constants (
KIND_POSITION,KIND_PEER_POSITION) and sender-tagging helpers.
- datagram encodings, plus the relay protocol constants (
- RPC request/response wire format — additive
KIND_RPC_REQUEST/KIND_RPC_RESPONSEkinds with typedencode/decode_rpc_requestandencode/decode_rpc_responsehelpers. A client sends a method + payload + correlation id; the server replies to that caller only, correlated byrequest_id, with anok/errorstatus. See the envelope reference.
Client SDKs
Section titled “Client SDKs”- Rust SDK (
citadel-client) —WsClientandQuicClientwith a smallconnect / send / recvsurface over WebSocket and QUIC. - Client RPC call helpers —
WsClient::call_rpcandQuicClient::call_rpcgenerate a monotonicrequest_id, send aKIND_RPC_REQUESTreliably, and await the correlatedKIND_RPC_RESPONSE, returning the reply bytes or a newClientError::Rpc { request_id, message }on an error status. They discard non-RPC envelopes while awaiting the reply and impose no timeout (wrap intokio::time::timeout). The Unity C# sample gains anRpcClientMonoBehaviour (CallRpc+ single-poll-owner dispatch by kind,Rfires sampleadd/pingcalls) over the unchanged C ABI. See the Rust SDK reference and the Unity sample. - C ABI (
citadel-client-ffi) — a stable, poll-based, panic-safe C ABI over the Rust SDK, with a committed cbindgen header.
Tooling and demos
Section titled “Tooling and demos”-
CLI —
citadel serve/citadel checkwith layered TOML/env/flag configuration and a non-secret config summary.serveis the default command, so a barecitadel(orcargo run) starts the server;checkvalidates config without listening. -
Standalone startup UX — once
serveis ready it prints a boxed ASCII banner (version, node id, selected database backend, and aligned links for the dashboard/status/health endpoints and each enabled transport with its bind); detailed init logging drops todebug. On an interactive terminal with no--config, a first-run wizard offers to scaffold a starter Lua script and pick a database (SQLite default or PostgreSQL), persisting the choice tocitadel.toml. The new--yes/--non-interactiveflag (and any headless or--configrun) skips the wizard and uses silent defaults. See the CLI reference. -
Web demo (
examples/web-demo/) — no-build-step Three.js demo over WebTransport (with WebSocket fallback). -
Native demo (
demo-client) — macroquad 2D demo over QUIC via the Rust SDK. -
Unity C# SDK (
clients/unity/) — Unity move-and-broadcast demo over QUIC through the C ABI, with amake unity-plugintarget that builds and installs the native plugin. Windows x86_64, manual in-editor run. -
Makefile demo targets —
make demo-web,make demo-native,make demo-native2,make unity-plugin.
Persistence
Section titled “Persistence”- PostgreSQL backend — durable
jsonb-backed storage plus identity/session repositories behind the same async contracts as the in-memory references, with embeddedsqlx::migrate!migrations applied on connect. (, ) - Backend selection, live in the node — on
citadel servethe node selects its backend from[database]by URL scheme: with aurlit connects, migrates, and runs on the chosen backend (failing fast if unreachable); with nourlit runs in-memory. The selected backend (in-memory/postgres/sqlite, never the URL) appears in the/statusbackendfield and on the/dashboardconsole. Account creation runs in one unit-of-work transaction. - SQLite backend (storage + identity/sessions) — an embedded, single-file
data.sqlitesibling backend behind the sameBackend/UnitOfWorkseam, selected withurl = "sqlite:data.sqlite"(or a bare path). It serves all four repositories (storage plus users, auth identities, and sessions), so accounts and sessions persist durably with the same guarantees as Postgres, including atomic account creation through one SQLite transaction. Semantics are identical to Postgres/in-memory (optimistic concurrency, permission filtering, keyset cursors), proven by the same contract tests run un-gated on every build with no external database. (, ) - Standalone self-bootstrap — with a
sqlite:URL the node bootstraps its own on-disk state on first run: it createsdata.sqlite, applies migrations, and creates an emptygame/scripts folder — no migration command ormkdir. With no--config, it discovers and loads acitadel.tomlnext to the binary, so the shipped standalone config is a true unzip-and-run start (SQLite + runtime + all transports).
Authentication
Section titled “Authentication”- HTTP device/custom auth —
POST /v1/auth/deviceandPOST /v1/auth/customlet a client register or log in with a device id or a custom id and receive a session token, running through the persistent, transactional authentication/session services (account creation is one transaction on the selected backend). Uniform401on any credential failure (no account enumeration), typed400on invalid input, and generic500s that never leak internals. See the HTTP authentication API.
Admin console
Section titled “Admin console”- Fully live admin console — the
/dashboardsingle-page console (navy Nakama-style shell, no build step, fully self-contained) now has every sidebar section live against the operator API under/console/v1: operator login withadmin/viewerroles ([console]config section, in-process bearer tokens), Accounts (list/search, create, detail with linked credentials, ban/unban with session revocation, edit, logical delete, export, wallet + friends panels), Storage browser (collections, objects, conditional writes/deletes with runtime authority), Groups (roles member/admin/superadmin, promote/demote/kick), Chat history moderation, Notifications (targeted + broadcast composer), Leaderboards (best/set/incr operators, ranked records), live Matches introspection from the room registry, Runtime introspection + RPC caller over the embedded Lua runtime, a redacted Configuration browser, Purchases & Subscriptions (pluggable receipt validation, dev validator today), and a bounded Audit Logs trail recording every console mutation. See the console reference. (..)
Not implemented yet (deferred)
Section titled “Not implemented yet (deferred)”- Signed / refresh-rotating session tokens — tokens are opaque unsigned reference tokens today; signing, rotation, and rate limiting are follow-ups.
- Fully packaged per-engine bindings — Unreal (drop-in plugin), Unity (C#
over the C ABI), and Godot SDKs exist and are kept in contract parity; a
fully packaged Unity plugin (macOS/Linux, IL2CPP,
.unitypackage) is still a follow-up. - Coherent web ↔ native interop — the web and native demos use different position payloads; run two web tabs or two native clients.
- Production TLS for QUIC/WebTransport, and
wss://for WebSocket. - RPC ergonomics — client-side RPC timeouts/retries, a C ABI-level RPC convenience, and streaming RPC are follow-ups to the client RPC call helpers.
- Client-facing APIs for groups, chat, notifications, leaderboards, friends, wallet, and purchases — these product areas now exist server-side with operator/console management (see Admin console above), but their player-facing client/Lua APIs, persistence, and realtime delivery are follow-ups; the in-process stores clear on restart (see the technical-debt register). Real App Store / Google Play receipt validators are .
- Matches, parties, tournaments, and other remaining Nakama product areas. (Presence and multi-room grouping now exist — see the networked-actors and rooms entries under Realtime gameplay above.)