Axiom/SDK
SDK / Mod and Framework

Mod and Framework

The lifecycle you implement, the handle you are given, and the result type that everything in the surface returns.

Mod

The class you implement. Every method is optional; implement the ones you need.

LIFECYCLEC++23
struct MyMod : axiom::Mod {
  void init(Framework& f) override;       // acquire, build panels
  void post_init(Framework& f) override;  // other mods exist now
  void frame(Framework& f) override;      // once per presented frame
  void event(Event& e) override;          // binds, buttons, config
  void shutdown() override;               // optional; cleanup is automatic
};

AXIOM_MOD(MyMod);
Cleanup is automatic

Hooks detach, asset layers drop, input capture releases and the widget tree is removed when a mod unloads. shutdown is for your own state, not for undoing anything the surface gave you.

Framework

The handle you are given. Each accessor returns a question or primitive the mod declared.

f.entities() -> EntitiesPACK

Everything about what is in the world.

f.camera() -> CameraPACK

View, projection, position, field of view.

f.project() -> ProjectPACK

World to screen and back.

f.types() -> TypesPACK

Named access to the game's own structs.

f.ui() -> UiBACKEND

Panels, rows, controls.

f.canvas() -> CanvasBACKEND

Your own draw list.

f.input() -> InputBACKEND

Binds, state, capture.

f.config() -> ConfigBACKEND

Your declared settings, typed and clamped.

f.log() -> LogBACKEND

Goes to the overlay console with your mod id attached.

f.ext<T>(name: str) -> Option<T>PACK

A question asked by a pack rather than by Axiom. Comes back empty, not denied, when no installed pack asks it, so a mod can light up extra panels where they exist and stay useful elsewhere.

Parameters
name
A slashed name, such as alice.mygame/spellbook
Returns
Option<T>
Empty when no pack provides it
if (auto sb = f.ext<Spellbook>("alice.mygame/spellbook"))
  for (auto& s : sb->known()) panel.row(s.name, s.cooldown_ms);

Result, Denied and Failed

Everything fallible returns Result. The difference between the two failures is the most useful thing in the API.

Outcome
Means
What a mod does
Denied
You never had it
Degrade permanently, stop asking
Failed
You had it, this attempt did not work
Retry or report, keep the feature
Trap
Your mod did something illegal
Nothing; it is already unloading
BRANCHINGC++23
auto cam = f.camera().view();

if (cam.denied())      shown_ = false;   // never on this game
else if (cam.failed()) return;          // transient, try next frame
else                   draw_with(*cam);

Denied is decided at attach and never changes during a session, which is what makes the “stop asking” branch safe.

Failed { code, detail }

Every failure carries a reason that is safe to paste into a bug report.

Returns
code
SymbolUnresolved, ReadFault, StaleHandle, Unsupported, CaptureHeld
detail
A sentence naming the symbol, address, handle or build
Failed{ SymbolUnresolved, "entity_pool not found on build 1.4.3" }
Failed{ StaleHandle,      "entity 412:7 generation is now 9" }

Config

Declared in the manifest, which generates the overlay tab and the manager panel. You never write settings UI.

config.get<T>(key: str) -> T

Typed and clamped to the declared range.

Returns
T
Never out of range; a hand-edited config file cannot hand you a negative radius
config.on_change(key: str, cb)

Fires on the mod’s thread, next frame after the user changes it.

float range = f.config().get<float>("range");
f.config().on_change("range", [&](float v) { range = v; });