← Back to Docs

Custom C++ Engine

Integrate Compuon into any C++17 codebase. Link the static library from the SDK package, include the headers, and connect the game client to the integrity server over WebSocket. The package’s INTEGRATION_TIPS.md is the full reference; this page is the short version.

CMake Integration

# COMPUON_SDK = the unzipped package; the library is at its root
set(COMPUON_SDK ${CMAKE_SOURCE_DIR}/third_party/compuon)
add_library(compuon STATIC IMPORTED)
set_target_properties(compuon PROPERTIES
    IMPORTED_LOCATION ${COMPUON_SDK}/libcompuon.a   # compuon.lib on Windows
    INTERFACE_INCLUDE_DIRECTORIES ${COMPUON_SDK}/include
)

target_link_libraries(YourGame PRIVATE compuon)
# Windows: match the package's /MD runtime (see Build Requirements)
set_property(TARGET YourGame PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreadedDLL")

include/ covers both compuon/ and protocol/, so <compuon/compuon.h> and <protocol/sync_handler.h> both resolve.

Initialization

#include <compuon/compuon.h>
#include <protocol/sync_handler.h>

bool running = true;
void update() { running = false; }
void render() {}

int main() {
    // The two seeds are placeholders. The integrity server issues the real
    // per-session seeds at AUTH_OK and SyncHandler applies them with
    // compuon::api::rekey_session. The third argument is what matters:
    // false = server-verified profile (no locally minted operands at boot).
    compuon::api::init(0x1ULL, 0x2ULL, /*bootstrap_operands=*/false);

    // Open your WebSocket, authenticate, and create Compuon<T> values only
    // after AUTH_OK -- see "Connecting to the integrity server" below.

    while (running) {
        // No per-frame SDK call is required. Your socket code pumps the
        // handler (on_message / tick / pop_outgoing).
        update();
        render();
    }

    // Terminal: every Compuon handle dangles after this.
    compuon::api::shutdown();
}

Call api::init() once, before any Compuon<T> is constructed. To re-key a session that is already initialized call api::rekey_session(); SyncHandler does this for you on AUTH_OK. Never call api::init() a second time: it re-reads its third argument (defaulting back to true) and silently re-arms locally minted operands in a session that asked for the server-verified profile.

The default package build is ungated: the local operand mint is still compiled in, so a build that passes bootstrap_operands = true (or omits the argument) keeps working offline, on operands it minted itself. If you want that code path absent from the shipped binary, ask us for a server-gated build (hello@compuon.dev); a gated build has no offline play, and TOOLCHAIN.txt records which kind you received.

Connecting to the integrity server

You own the socket; the SDK owns the protocol. SyncHandler (include/protocol/sync_handler.h) never touches the network: it parses the text frames you hand it and queues the text frames you send.

  • +Endpoint: wss://<host>/sync. The hosted demo server is cloud.compuon.dev; a self-hosted server (the cloud container) listens on ws://<host>:3000/sync.
  • +API key: a ck_… key issued by whoever operates the server (npx tsx src/cli.ts create --project <name> on the server host; it is printed once). For the hosted server that is us: email hello@compuon.dev with your project name. This is notthe key you sign in to the build dashboard with — the dashboard requests builds, the ck_ key authenticates game clients.

Order of calls (the order is load-bearing):

#include <compuon/compuon.h>
#include <protocol/sync_handler.h>
#include <memory>

void on_verdict(int action, void* userdata);   // action: NONE=0 WARN=1 KICK=2 ESCALATE=3

// 1. compuon::api::init(placeholder, placeholder, /*bootstrap_operands=*/false);
// 2. Open your WebSocket to wss://<host>/sync (your code).

// 3. One SyncHandler per connection. Opt in to the server-issued
//    per-session seed BEFORE send_auth.
compuon::SyncHandler handler;
handler.set_request_session_seed(true);
handler.set_verdict_callback(on_verdict, nullptr);
handler.send_auth(player_id, project_id, api_key);   // queues AUTH; api_key is the ck_ key

// 4. Pump from your socket code. Every inbound text frame -> on_message;
//    every frame -> tick; then drain the outgoing queue onto the socket.
handler.on_message(frame_bytes, frame_len);
handler.tick();
auto out = std::make_unique<compuon::SyncHandler::OutMessage>();   // 32 KB: keep it off the stack
while (handler.pop_outgoing(*out))                                  // or pop_outgoing_into(buf, n)
    ws_send_text(out->data, out->len);

// 5. Create protected values only after AUTH_OK. By then the handler has
//    already called compuon::api::rekey_session with the server's seeds; a
//    value created earlier is keyed to the placeholder seed and mismatches
//    on its first spot-check.
if (handler.is_authenticated()) {
    compuon::Compuon<int> hp(100);
    handler.send_var_register(hp.ct_id(), compuon::Health(100, "hp"));
    // ... on destroy:  handler.send_var_unregister(hp.ct_id());
}

What arrives on the wire, so the callbacks make sense:

  • +AUTH_OK carries the per-session seeds (only if you opted in); the handler re-keys via rekey_session.
  • +BUNDLE_DELIVER carries server-issued material the SDK needs before it can verify; the handler stores it and answers BUNDLE_ACK, rejecting a bundle whose epoch does not advance.
  • +VAR_REGISTER with a VarProfile (Health(max, name), Currency(name), Counter(name) from messages.h) tells the server how the value may legitimately move. With a name, the server binds the variable to its own manifest and ignores client-declared bounds — prefer that.
  • +SYNC_REQ periodically picks a few of the registered ids and gives the handler a short deadline to answer SYNC_RESP. A missing, late, or empty answer scores as a timeout.
  • +KEY_ROTATE: the handler calls api::rotate and answers ROTATE_ACK; your values are re-keyed in place. You do not call rotate() yourself.
  • +VERDICT fires your callback with an action code. The codes are tags, not ranks: compare with verdict_severity(action), never action >= X.

Two mechanics to know: SyncHandler has no disconnect hook and stays authenticated once it has been, so construct a fresh one per connection rather than reusing it across reconnects; and tick()is currently a no-op kept for future timeouts — call it anyway.

When the server is unreachable:without a live session there is nothing verifying the values, and the SDK does not fail the game on its own — nor does it reconnect or re-verify once a connection drops. Verification resumes only when you open a new connection with a fresh handler. That policy is yours to set: put any reconnect, grace-period, or “no session → treat as untrusted” logic in the transport and game layers you own. The SDK hands you the verdict; the enforcement is yours.

Using Compuon<T>

#include <compuon/compuon.h>
using namespace compuon;

// Construct after AUTH_OK when you talk to the integrity server.
struct Player {
    Compuon<int>   hp{100};
    Compuon<int>   gold{0};
    Compuon<float> speed{5.0f};
    Compuon<float> x{0.0f}, y{0.0f};

    void take_damage(int dmg) {
        hp -= Compuon<int>(dmg);
        if (hp <= 0) die();   // reads the cache; builds no temporary
    }

    void move(float dx, float dy) {
        // dx and dy change per call, so each += builds a temporary
        // value (see Operation Tiers)
        x += Compuon<float>(dx * speed.val());
        y += Compuon<float>(dy * speed.val());
    }

    void add_gold(int amount) {
        gold += Compuon<int>(amount);
    }

    void haste() {
        speed *= 2;            // integer scalar: Tier 0
        // speed *= 1.5f;      // does not compile: the float-scalar overload is deleted.
        // Fractional factor: speed *= Compuon<float>(1.5f) (Tier 1), or keep the
        // quantity in tenths as Compuon<int> and scale by an integer.
    }

    void die() {}
};

Supported Types

TypeUse Case
Compuon<int>Health, ammo, score, gold
Compuon<float>Speed, position. Scalar *= takes an integer only: m *= 1.5f is a compile error (deleted overload). A fractional multiplier is Compuon<float> * Compuon<float> (Tier 1), or integer math on a Compuon<int> kept in tenths.
Compuon<int64_t>Large counters, timestamps

Build Requirements

  • +C++17 or later
  • +Windows: MSVC, Release, /MD (MultiThreadedDLL) runtime, and the same MSVC toolset major version recorded in TOOLCHAIN.txt. The package compuon.lib is built with /GL, so it only links under the compiler that produced it; a mismatch fails at link (C1047 / LNK1257), and a /MT or Debug-CRT configuration fails with LNK2038.
  • +Linux: GCC or Clang with libstdc++; libcompuon.a is plain GCC object code (no LTO). Match the GCC major in TOOLCHAIN.txt so the libstdc++ ABI agrees.
  • +No external dependencies. The public headers pull in only <cstdint>, <cmath>, <type_traits>, <utility>, <cstddef> and <memory>.
  • +Link the static library at the package root. For another toolset or a Debug-CRT variant, email hello@compuon.dev.

Package layout:

compuon.lib               // static library, at the root (libcompuon.a in the linux-x64 package)
include/
  compuon/
    compuon.h
    platform.h
    types.h
  protocol/
    sync_handler.h        // SyncHandler: your link to the integrity server
    messages.h            // VarProfile, VerdictAction, verdict_severity()
INTEGRATION_TIPS.md       // read this first
README.md
TOOLCHAIN.txt             // compiler, CMake, commit, seed, gated or not