Axiom/Docs
Adding a game / Packs

Packs

A pack is how Axiom knows how to talk to one game: where the entities are, which matrix is the camera, what that field means. It is the only thing you publish to add a game.

The split worth holding onto: the backend knows how to read the process, the pack knows what the bytes mean in that game. Reading memory, resolving symbols, hooking functions, drawing the overlay and taking input are the same on every Unity game and every JVM game, so a backend ships with Axiom once per runtime family. Which region is the entity pool is not a property of x64 or the JVM, so no backend can ever supply it.

That is why a pack is both the whole third-party story and a small thing to write. You are not implementing the SDK. You are telling it where to look in one game.

Two verbs

VerbMeaning
ANSWERAnswer a question Axiom asks about a game
ASKAsk your own question, when the game has something no other game has

That is the whole vocabulary. There is no second artifact type, no interface to implement, no tier to choose.

The questions Axiom asks

Answer the ones your game lets you. Stay silent on the rest. A pack does not have to be complete to be useful.

QuestionWithout an answer
entitiesNo mod can list or find anything in the world
transformNothing knows where anything is
cameraNo world-to-screen, so no overlays drawn in the scene
typesNo access to the game's own structs by name
componentsNo component filters, on games that have them
assetsNo texture or model replacement
worldNo line of sight checks
Everything else is free

Drawing the overlay, reading input, hooking functions, reading memory and resolving symbols come from the backend. Backends ship with Axiom, one per runtime family, and you will almost certainly never write one.

Writing one

$ axiom pack new mygame
detected native x64, 14 modules
created mygame/pack.toml, mygame/answers.cpp
PACKmygame/answers.cpp
AXIOM_PACK("alice.mygame")

// Q: which entities are alive right now?
ANSWER(entities) {
auto pool = sym("entity_pool");
auto hdr = read<PoolHeader>(pool);
for (u32 i = 0; i < hdr.count; ++i) {
auto e = read<Entity>(pool + 0x10 + i \* 0x2C0);
if (e.flags & LIVE) found(e.index, e.generation);
}
}

// Q: where is this entity?
ANSWER(transform) {
auto e = read<Entity>(handle);
gives(e.pos, e.rot, e.scale);
}

That is the whole surface area. If a game happens to store things in a plain flat array, the same answer is four lines of TOML instead, and you never find out they compile to the same thing.

Knowing where you stand

TERMINALaxiom pack status
$ axiom pack status --game mygame
 
entities alice.mygame 428 found 9/9
transform alice.mygame 7/8 rotation looks like degrees
camera bob.mygame-cam 6/6
spellbook alice.mygame (asked by this pack)
types alice.mygame
components -- not answered

Ship when you are happy with that. A pack answering two questions is worth publishing.

Packs never ship inside mods

A mod artifact may not contain a pack

Publish rejects one that does. This is not a style rule; bundling would undo everything on this page.

If a mod bundled a packWhat breaks
The game patchesThe bundled copy is a private fork only that mod's author can update
Two mods on one gameTwo copies of the same answers, disagreeing silently
The user wants a better packNothing to switch, stack or roll back; it was never installed

So they resolve separately. A mod declares questions; the manager finds packs that answer them.

TERMINALresolving a mod's questions
$ axiom install radar.wasm --game mygame
 
radar 1.3.0 needs:
entities, transform answered by pack.alice.mygame (installed)
overlay backend
alice.mygame/spellbook answered by pack.alice.mygame (installed)
camera not answered
 
1 pack available for camera on this build:
pack.bob.mygame-cam 0.3.0 conformance 6/6
 
install it too? [Y/n]

A mod whose required question nothing answers does not install for that game, and the manager names the missing question rather than blaming the mod. A slashed name is a hard dependency on one specific pack, which is the cost the slash exists to advertise.

Packs stack

No pack has to be complete, so several can be active for one game at once. Above, Alice answers most of it and Bob answers camera, which she never did. Bob did not fork her pack; he published twelve lines.

This is the part that makes third-party support work

Forking becomes the last resort instead of the only move. When a maintainer disappears, somebody patches one answer rather than adopting everything.

Stacking is priority, never merge

Exactly one pack answers each question

Packs stack in the sense that different packs answer different questions. They never combine answers to the same one.

Merging sounds better and cannot be made correct. Two packs reading different substrates both hand back an index 5 that means different entities, and fixing that inside a 64-bit id means shaving the generation down until it wraps in minutes, which reintroduces the exact aliasing bug generations exist to prevent.

So the order decides, the user controls it, and a second pack filling a gap the first left works exactly as above.

Substrate is declared and checked

One answer per question is not enough on its own. Alice can answer entities from the actor pool while Bob answers transform from the physics-body array on different indexing. Both pass conformance. The stack hands mods wrong positions.

PACKpack.toml
[substrate]
entities   = "actor_pool"
transform  = "actor_pool"
camera     = "render_view"
TERMINALa refused stack
$ axiom pack status --game mygame
entities alice.mygame actor_pool 428 found 9/9
transform bob.mygame-phys physics_body REFUSED
answers transform from a substrate alice.mygame does not index.
Install pack.carol.mygame (actor_pool), or disable alice.mygame.

A substrate name is scoped to the game rather than global. Two packs claiming actor_pool are asserting they index the same thing, and a wrong assertion becomes a pack bug with a name instead of a mystery.

Handles are minted by the core

A pack's internal index and generation never reach a mod. The core mints an opaque 64-bit handle and keeps a per-session indirection table, rebuilt whenever the active pack set changes.

CostWhat it buys
Opaque handleOne lookup per resolve()Pack-internal ids can be any width or shape
Per-session tableRebuilt on stack changeChanging packs cannot alias old handles onto new entities
Core-owned generationA u32 the core controlsWorks on games with no generation counter of their own
That last row matters more than it reads

On a game with no generation concept a pack used to have no honest option: synthesising one is impossible and shipping a constant made the safety guarantee silently untrue. Now the core owns it, and a pack declares whether slot reuse is reuse = "observable" or "opaque". An opaque game gets a conservative policy and a visible marker rather than a promise nobody is keeping.

Three events the surface was missing

EVENTS
handoff(question, from_pack, to_pack)   // a pack update changed who answers
invalidate_all(reason)                  // pool compaction; every handle is dead
stack_changed(questions)                // packs installed, removed or reordered

A compacting pool preserves neither index nor generation relationships, so mass invalidation is the only correct outcome and a mod has to be told. And a pack update that quietly starts answering a question nobody answered before used to change mod behavior with no error raised anywhere.

Builds are declared, and skew is refused

A pack refuses to attach outside the builds it declares, and the manager refuses a stack whose members disagree about the build. Without that, a game auto-updates, one pack's offsets move, and its snapshot reads moved memory as pool data: garbage entities, a lying alive(), or a stride walk into unmapped memory.

Name the trade

Loud refusal converts silent corruption into visible support load, and on a weekly-patching game it will strand packs constantly. That is the correct direction to fail, and it is why the registry shows build coverage per pack rather than a version number alone.

Asking your own questions

Some games hold things no portable question covers: a spellbook, a karma meter, a build grid, a faction table. Ask for it in the same file.

PACKmygame/answers.cpp
ASK(spellbook) {
  Spell { id: u32, name: str, cooldown_ms: u32 }

bulk known() -> [Spell]
cooldown(spell: u32) -> Result<u32>
}

ANSWER(spellbook) { ... }

It gets the same treatment as a built-in question: host dispatch, bindings in every language, bulk calls, typed errors, versioning, docs.

For a mod, the only signal is a slash

MANIFESTaxiom.toml
require = ["entities", "transform", "overlay", "alice.mygame/spellbook"]
NameMeans
entitiesPortable. Runs on any game whose packs answer it.
alice.mygame/spellbookCame from one pack. The mod is game-specific.

One list. The registry derives the game-specific label itself rather than trusting the author to declare it.

First-party packs are just packs

No game support is compiled into Axiom

The games we ship support for are packs: same ANSWER and ASK, same format, same conformance runner, same registry, same install path, published under our key.

This is a rule, not a habit, because the alternative has a predictable failure mode.

If first-party took a private pathWhat happens
The internal API is nicerThe public one rots, and nobody inside notices because nobody inside uses it
Our packs skip conformanceOurs become the ones with silent unit and handle bugs
Our packs cannot be replacedA better community pack has nowhere to go
A first-party pack does not getMeaning
Resolution priorityA community pack can answer over ours; the user controls order
Extra questionsThe same set as everyone else
A conformance exemptionScores published, failures and all, next to everyone else's
Un-removabilityUninstall or replace it like any pack

The single difference is that the official signing key is pre-trusted, so a native-tier first-party pack installs without a prompt. That is a trust decision about a key, revocable in axiom trust, rather than a privilege in the architecture. Packs bundled with the installer are the same artifacts pinned to a version, updated and removed independently of Axiom itself.

What a pack cannot do

LimitWhy
Add a new runtimeA game on an engine no backend covers needs a backend, which is different work against the nine primitives. A pack answers questions on a runtime that already works.
Unblock a protected titleA pack for a game with kernel anticheat is refused at publish and at attach.
Redefine a built-in questionYou cannot change what entities means. Your own questions live under your pack's name, where the slash makes the trade visible.