Skip to content

Architecture

This page describes the internal architecture of QS-Bridge: how its components are arranged, how data flows between them, and how the system scales to support multiple game servers from a single deployment.

Prerequisite reading: Platform Overview for a high-level introduction to QS-Bridge and its design philosophy.


Multi-Server Topology

QS-Bridge follows a hub-and-spoke topology where a single SpacetimeDB instance acts as the central data store for N game servers, the API gateway, and the admin panel.

graph TD
    AdminPanel["Admin Panel<br/>React 19 + Vite 6<br/>TypeScript 5.6, Tailwind CSS v4"] -->|HTTPS| Gateway["API Gateway<br/>Hono TypeScript<br/>Steam OpenID 2.0"]
    Gateway -->|ws://| STDB

    subgraph GameServers["Game Servers"]
        GS1["Game Server #1<br/>libqsbridge.so"]
        GS2["Game Server #2<br/>libqsbridge.so"]
        GS3["Game Server #3<br/>libqsbridge.so"]
        GSN["Game Server #N<br/>libqsbridge.so"]
    end

    subgraph STDB["SpacetimeDB v2"]
        Platform["Platform Module<br/>12 tables, 24 reducers"]
        ModRegistry["Mod Registry Module<br/>10 tables, 30+ reducers"]
        GameModule["Game Module e.g. HumanitZ<br/>49 tables, 117 reducers"]
    end

    GS1 -->|ws://| STDB
    GS2 -->|ws://| STDB
    GS3 -->|ws://| STDB
    GSN -->|ws://| STDB

Key Properties

  • Single STDB instance — all game servers, the API gateway, and the admin panel connect to the same SpacetimeDB deployment. There is no per-server database.
  • Row-level partitioning — every table row that is server-specific includes a server_id column. Subscriptions filter by server_id, so each game server only receives its own data.
  • Shared platform state — tables like Account, ServerConfig, and AuditLog span all servers, enabling cross-server administration from a single panel.
  • Stateless frontends — both the API gateway and the admin panel are stateless processes. They can be restarted or replaced without affecting game server operation.

C++ Framework Layers

The libqsbridge.so shared library is structured as a layered framework. Each layer depends only on the layer below it, enforcing clean separation of concerns.

graph TD
    A["Game Module<br/><i>e.g. HumanitZ: 49 tables, 117 reducers</i>"] --> B
    B["Engine Adapter<br/><i>UE4 4.27 hooks, actor replication intercept</i>"] --> C
    C["Bridge Core<br/><i>Lifecycle, table registry, sync scheduler</i>"] --> D
    D["STDB Client<br/><i>WebSocket transport, subscriptions, reducer dispatch</i>"] --> E
    E["BSATN Codec<br/><i>18 header-only files, binary serialisation</i>"]

    style A fill:#d4915c,color:#fff,stroke:#c07840
    style B fill:#b87a4a,color:#fff,stroke:#a06838
    style C fill:#9c6438,color:#fff,stroke:#845628
    style D fill:#7a4e2a,color:#fff,stroke:#6a421e
    style E fill:#5c3a1e,color:#e8d8c8,stroke:#4a2e14

Layer 1: BSATN Codec

The Binary SpacetimeDB Algebraic Type Notation codec is the lowest layer. It provides:

  • Zero-copy serialisation and deserialisation of all STDB primitive types.
  • Algebraic sum and product type encoding, matching the Rust BSATN specification.
  • 18 header-only C++ files — no link-time dependencies, no runtime overhead.
  • Compile-time type reflection via C++17 constexpr and template metaprogramming.

The codec is header-only by design: game modules include it directly, avoiding ABI compatibility issues across compiler versions.

Layer 2: STDB Client

The client layer manages the WebSocket connection to SpacetimeDB:

Responsibility Detail
Connection lifecycle Connect, reconnect with exponential backoff, graceful disconnect
Authentication Identity token exchange, credential persistence
Subscription management Register SQL-style subscriptions, receive incremental updates
Reducer dispatch Invoke server-side reducers with BSATN-encoded arguments
Event loop integration Non-blocking I/O compatible with game server tick loops

Layer 3: Bridge Core

Bridge Core is the orchestration layer. It manages:

  • Table Registry — a compile-time registry of all tables declared by the game module, with schema validation against the STDB module.
  • Sync Scheduler — configurable background flush interval; batches pending writes into single STDB transactions.
  • Identity Resolution — maps the server's server_id (human-readable display name) to its stdb_identity (cryptographic backend identity). See Server Identity Strategy.
  • Hook Dispatch — routes engine events (player join, actor spawn, world save) to registered game module callbacks.
  • Health Reporting — exposes connection state, queue depth, and sync latency to systemd watchdog and the admin panel.

Layer 4: Engine Adapter

The engine adapter provides engine-specific glue code. For Unreal Engine 4.27:

  • Hooks into UWorld::Tick to drive the sync scheduler.
  • Intercepts AActor replication relevancy checks to offload state to STDB.
  • Provides FStringstd::string ↔ BSATN string conversion utilities.
  • Maps UE4 FVector, FRotator, FTransform to STDB-compatible column types.

Layer 5: Game Module

Game modules are the user-authored layer. Each module targets a specific game and declares:

  • Tables — the STDB schema for that game's managed state.
  • Reducers — server-side functions that mutate table rows.
  • Hooks — callbacks invoked by Bridge Core when engine events occur.

The first shipped game module is HumanitZ (Unreal Engine 4.27), comprising 49 tables and 117 reducers.


Component Reference

Component Technology Artefact Description
libqsbridge.so C++17, CMake Shared library (~8.6 MB) Injected into game server via LD_PRELOAD. Contains all 5 framework layers.
Platform STDB Module Rust WASM module 12 tables, 24 reducers. Manages accounts, servers, permissions, audit logs.
Mod Registry Module Rust WASM module 10 tables, 30+ reducers, 8 ModKind variants. Catalogues and configures server-side mods.
Admin Panel React 19, Vite 6, TypeScript 5.6, Tailwind CSS v4 Static SPA Server management dashboard. Stateless; all data from API gateway.
API Gateway Hono (TypeScript), Node.js 20+ Node.js service Steam OpenID 2.0 authentication, RBAC, REST-to-reducer translation.
Codegen TypeScript CLI tool Generates C++ table/reducer stubs from STDB module schemas.

Server Identity Strategy

Every game server in QS-Bridge has two identities:

graph LR
    A["server_id<br/><code>String</code><br/><i>Human-readable display name</i>"] --- B["stdb_identity<br/><code>Identity</code><br/><i>Cryptographic backend identity</i>"]

    style A fill:#d4915c,color:#fff,stroke:#c07840
    style B fill:#b87a4a,color:#fff,stroke:#a06838

server_id — Display Name

  • Type: String
  • Example: "us-east-1-pvp", "au-sydney-pve", "eu-west-creative"
  • Set via the QSB_SERVER_ID environment variable.
  • Used in the admin panel UI, API routes, and log messages.
  • Must be unique within a deployment.
  • Mutable — can be renamed without affecting data integrity.

stdb_identity — Backend Identity

  • Type: Identity (SpacetimeDB's built-in cryptographic identity)
  • Generated automatically by SpacetimeDB on first connection.
  • Used as the primary key for row-level partitioning in all server-scoped tables.
  • Immutable — changing it would orphan all associated table rows.
  • Stored in a local credential file and reused across restarts.

Why Two Identities?

Separating the display name from the backend identity provides:

  1. Rename safety — server owners can rename a server in the panel without migrating data.
  2. Cryptographic authenticity — STDB reducers can verify the caller's identity without trusting a user-supplied string.
  3. Collision avoidanceIdentity values are globally unique by construction; server_id uniqueness is enforced at the application layer.

Data Flow

Write Path (Game Server → SpacetimeDB)

sequenceDiagram
    participant GS as Game Server (UE4)
    participant EA as Engine Adapter
    participant BC as Bridge Core
    participant SC as STDB Client
    participant DB as SpacetimeDB

    GS->>EA: Actor state change
    EA->>BC: Enqueue row update
    BC->>BC: Batch in write queue
    Note over BC: Flush on tick interval
    BC->>SC: Submit transaction (BSATN)
    SC->>DB: WebSocket send
    DB->>DB: Execute reducer
    DB-->>SC: Transaction result
    SC-->>BC: Confirm / retry

Read Path (SpacetimeDB → Game Server)

sequenceDiagram
    participant DB as SpacetimeDB
    participant SC as STDB Client
    participant BC as Bridge Core
    participant EA as Engine Adapter
    participant GS as Game Server (UE4)

    DB->>SC: Subscription update (BSATN)
    SC->>BC: Decoded row delta
    BC->>BC: Update local cache
    BC->>EA: Notify changed rows
    EA->>GS: Apply to engine state

Authentication Flow (Admin Panel → API Gateway)

sequenceDiagram
    participant U as User (Browser)
    participant P as Admin Panel
    participant G as API Gateway
    participant S as Steam OpenID
    participant DB as SpacetimeDB

    U->>P: Click "Sign in with Steam"
    P->>G: GET /auth/steam
    G->>S: OpenID 2.0 redirect
    S-->>U: Steam login page
    U->>S: Authenticate
    S-->>G: OpenID assertion
    G->>G: Validate assertion
    G->>DB: Lookup/create Account
    DB-->>G: Account + roles
    G-->>P: JWT session token
    P-->>U: Dashboard

Build System

The C++ shared library is built with CMake 3.20+ using a 5-layer build structure that mirrors the framework layers:

graph TD
    Root["CMakeLists.txt root"] --> L1["src/bsatn/<br/>Layer 1: BSATN Codec<br/>header-only, interface library"]
    Root --> L2["src/client/<br/>Layer 2: STDB Client"]
    Root --> L3["src/core/<br/>Layer 3: Bridge Core"]
    Root --> L4["src/engine/ue4/<br/>Layer 4: Engine Adapter UE4"]
    Root --> L5["src/games/humanitz/<br/>Layer 5: Game Module HumanitZ"]

Each layer is a separate CMake target with explicit dependency declarations. This ensures:

  • Incremental builds only recompile affected layers.
  • Layer boundary violations produce compile-time errors.
  • New engine adapters or game modules can be added without modifying existing layers.

Testing

QS-Bridge maintains 860+ test assertions across 7 test suites:

Suite Scope Key Assertions
bsatn_tests BSATN codec correctness Round-trip encoding for all primitive and algebraic types
client_tests STDB client transport Connection lifecycle, reconnection, subscription management
core_tests Bridge Core logic Table registry, sync scheduler, identity resolution
adapter_tests Engine adapter UE4 type conversions, hook dispatch
platform_tests Platform module Account CRUD, RBAC, server lifecycle
registry_tests Mod registry Mod CRUD, per-server enablement, ModKind validation
e2e_tests End-to-end integration Full write/read path, auth flow, multi-server scenarios

All suites run in CI on every commit and pull request.