Skip to content

Other languages and the raw ABI ​

You'll need this when: you're porting a game, or you'd rather write Rust, C, C++, C# or Zig than TypeScript. Status: Built (headless): the raw ABI v1, fuel metering, and generated bindings for C/C++, Rust and C#, tested on a C guest. There's no mandated SDK: any language that compiles to wasm works, with your own bindings if you like.

Two ways in ​

JS / TypeScriptRaw wasm
Writelogic.ts against @inventery/hostanything that compiles to wasm32
Buildbuild.mjs bundles it; it runs in QuickJS compiled to wasmyour toolchain makes logic.wasm
plot.json"code": "dist/logic.js""code": "logic.wasm"
Speedan interpreter: fine for game logicnative wasm speed

Both get the same calls, hooks, budgets and refusals.

C and C++ ​

plot/sdk/inventery.h is header-only and freestanding.

c
#include "inventery.h"

static void on_player_enter(const char *player) {
  inv_ui_chat("\"lobby\"", "Someone arrived", 0);          // objects and unions are JSON text
}
static void tick_lobby(double dt) {
  inv_vec3 p = { 8, 65, 8 };
  inv_world_set_block(p, inv_world_block_id("stone"));
}
INV_PLOT(.tick_lobby = tick_lobby, .on_player_enter = on_player_enter)   // once, in one .c file
sh
zig cc -target wasm32-freestanding -O2 -nostdlib -Wl,--no-entry -o logic.wasm plot.c

A refused call returns 0, false or NULL, and inv_error() gives {"rule","call","message"}.

Rust ​

Copy plot/sdk/inventery.rs into src/ as mod inventery; (crate-type = ["cdylib"], target wasm32-unknown-unknown, Rust 1.82+; no_std works).

rust
use crate::inventery::{self as inv, Plot};

#[derive(Default)]
struct Game { ticks: u32 }

impl Plot for Game {
    fn tick_lobby(&mut self, _dt: f64) { self.ticks += 1; }
    fn on_player_enter(&mut self, player: &str) {
        let _ = inv::ui::chat(&inv::Targets::PlayerId(player.into()), "hi", None);
    }
}
inventery_plot!(Game; tick_lobby, on_player_enter);   // only the listed hooks are exported

Calls return Result<T, HostError>.

C# ​

plot/sdk/Inventery.cs, for NativeAOT-LLVM targeting wasm32. One static class per module (Scene.Create(...)); a refusal throws HostException with .Rule.

csharp
using Inventery;
static class Game {
    [System.Runtime.CompilerServices.ModuleInitializer]
    internal static void Setup() => Plot.Hooks = new Hooks {
        OnPlayerEnter = p => Ui.Chat(Targets.PlayerId(p), "hi"),
    };
}

The raw ABI v1 (for your own bindings) ​

Imports from module "inv":

ImportDoes
call(op, argsPtr, argsLen) -> i32call host op op with a UTF-8 JSON array of args. Returns the result's JSON length, or -1 - len for an error JSON {rule, call, message}
ret(dstPtr)copy the last result (or error) JSON into your memory
result(ptr, len)set a hook's return value (JSON) before the hook returns
log(ptr, len)a debug line

Exports: memory, inv_abi() -> 1, inv_alloc(len) -> ptr, inv_has(namePtr, nameLen) -> i32, inv_hook(namePtr, nameLen, argsPtr, argsLen) -> i32 (1 if a result was set).

  • Op numbers are the schema's call order (INV_OP_* in the C header, constants in the Rust file). They're frozen once API version 1 ships.
  • Bytes cross as {"$b": "<base64>"}; a callback argument (for lobbies.runWithin) as {"$fn": id}, which the host calls back by re-entering inv_hook("$fn", [id]).
  • Don't export inv_fuel or inv_oom: the CPU metering adds them.
  • CPU is metered with fuel injected at function entries and loop heads; memory growth past your cap fails.

Porting checklist ​

  1. Export your level and props as glTF (glTF guide).
  2. Separate game logic from the source engine's runtime; compile it to wasm.
  3. Decide what the room referees (hits, scores, inventory) and what devices only predict.
  4. Decide which of the base game's rules your game keeps: a game that is its own server sets their categories off ("baseRules": { "blocks": false, "pvp": false, ... } in a runtime's plot.json).
  5. Write visible state to the scene; send game messages on channels.
  6. Map input, camera and UI onto input.read, players.setCamera and ui.*.