Axiom/SDK
SDK / Types and assets

Types and assets

Reading the game’s own structs by name, replacing what it loads, and taking input without learning a scancode.

types@1

Named access to the game's own structures. This is how a mod reads a field nobody wrote a question for.

Answered by: a pack
types.of(name: str) -> Result<TypeRef>

Look up a struct the pack declared.

Errors
Failed{TypeUnknown}
The pack does not map that name on this build
types.fields(t: TypeRef) -> [(str, Kind, u32)]

Name, kind and offset of every mapped field.

types.size_of(t: TypeRef) -> Result<u32>

Declared size in bytes.

types.enum_name(e: str, value: i64) -> Result<str>

Map a raw value to its name, so a panel shows “Red” rather than 2.

types.enum_value(e: str, name: str) -> Result<i64>

And back, so a filter compares against “Red” without the mod learning the number.

Where the names come from

The pack’s layout file. A mod names fields; the pack maps them per build.

LAYOUTSthe pack's types/layouts.toml
[struct.Player]
size   = 0x4A0
health = { at = 0x120, kind = "f32" }
mana   = { at = 0x124, kind = "f32" }
team   = { at = 0x1A4, kind = "enum", enum = "Team" }
types.raw@1

Reading and writing those fields on a live instance. A sharper tool than the rest of the surface, so it is gated separately.

Answered by: a pack
field<T>(ref: EntityRef, name: str) -> Result<T>

Read one field by name.

auto mana = f.types().of("Player").field<float>("mana");
if (mana.failed())
  log.warn("mana moved on this build: {}", mana.detail());
set_field<T>(ref: EntityRef, name: str, v: T) -> Result<>WRITE

Write it back.

By name, never by offset

A mod holding 0x124 reads garbage after the next patch and never tells anyone. A mod holding "mana" gets a Failed the moment the pack stops mapping it, which is loud, correct, and fixed by updating one pack. Using this does make a mod game-specific.

assets@1

Replacing what the game loads, by logical path, without touching a single file on disk.

Answered by: a pack and the backend
assets.replace(logical: str, with: Blob) -> Result<>WRITE

Replace an asset. The pack maps the logical path onto whatever the game actually does: a loose file, a pak entry, a resource blob, a JAR entry.

Parameters
logical
Such as textures/player/base.png
assets.overlay_dir(path: str) -> Result<>

Layer a whole directory by matching paths.

assets.restore(logical: str) -> Result<>

Drop your layer for one asset.

assets.list(pattern: str) -> [AssetRef]

What exists, for a mod that wants to show the user a picker.

assets.reload_hint() -> Result<>

Ask the engine to re-read. Frequently denied, and the capability says so rather than pretending.

Errors
Denied
This backend cannot force a reload
Property
Behavior
Layered
Replacements stack in mod load order; last one wins
Reversible
restore drops a layer, and unloading drops a mod’s layers automatically
Non-destructive
Nothing is written to the game's files, so verification and cloud saves keep working
Timing
Registered in pre-init it is seen; at runtime it applies next load, which for a loaded texture may be a level change
input@1

Keybinds the user can rebind, and exclusive capture when you need the keys.

Answered by: the backend
input.bind(name: str, default: str) -> BindId

Declare a bind. You never read a scancode, which is why rebinding works without the mod knowing.

toggle_ = f.input().bind("toggle", "F7");
input.pressed(id: BindId) -> bool

Edge, this frame.

input.held(id: BindId) -> bool

Level.

input.axis(name: str) -> f32

Analogue, where the backend exposes it.

input.capture(scope: CaptureScope) -> Result<CaptureToken>

Exclusive input. The game stops seeing it until the token drops.

Errors
Failed{CaptureHeld}
Names the mod already holding it
{
  auto tok = f.input().capture(CaptureScope::Keyboard);
  // game sees nothing while tok is alive
}
// released here, deterministically
capture is the one to get right

A mod that takes capture and never releases makes the game stop responding to keys, and nothing about that is self-explanatory. The overlay’s capture command names the holder, and the overlay’s own capture always releases on Escape.