# Axiom Style Guide

> How to write, test, package and ship plugins, game providers and language providers for the Axiom universal modding framework.

Revision 2026.10, plus edits not yet released. This URL always serves the latest text; pin a revision with https://style.chopper.dev/axiom@2026.10/llms.txt. Changelog: https://style.chopper.dev/axiom/changelog.md.

Rule levels: MUST / MUST NOT (non-negotiable), SHOULD / SHOULD NOT (the default; deviate only with a short comment saying why), MAY (allowed when it helps), AVOID (allowed but a smell). In this document they appear as a bracketed prefix on each rule, after its stable ID, e.g. "`AXIOM-02.1` [MUST] ...". Cite a rule by its ID (AXIOM-02.1): IDs never change or get reused, and https://style.chopper.dev/axiom#AXIOM-02.1 links to the rule. Code examples use a fictional game, `mygame`, and a fictional author, `acme`.

## Starter project

Starter repositories for Axiom plugins (Rust, C#, Lua or C++) and for game and language providers, each laid out per the guide with its manifest, lifecycle, tests beside the code and CI. It is generated from this guide and follows it, so prefer it over scaffolding a new codebase by hand.

Download it as a zip, renamed to the user's names, with a GET request (query parameters are optional; missing ones take their defaults, invalid ones return HTTP 400 with a message):

```bash
curl -fsSL "https://style.chopper.dev/axiom/template/download?kind=plugin&lang=rust&name=BetterMinimap&author=acme&game=mygame" -o better-minimap.zip && unzip better-minimap.zip
```

Parameters:

- `kind`: What you are building. A plugin (mod), or a provider that adds a game or a language to Axiom. One of: `plugin` (Plugin (mod)), `game-provider` (Game provider), `language-provider` (Language provider). Default `plugin`.
- `lang`: Plugin language. Rust, C# and C++ compile to WASM (tier 1); Lua runs as a script (tier 2). One of: `rust` (Rust (WASM)), `csharp` (C# (WASM)), `lua` (Lua (script)), `cpp` (C++ (WASM)). Only used when `kind` is `plugin`. Default `rust`.
- `name`: Plugin name. PascalCase. Names the plugin type; as kebab-case (better-minimap) it is the plugin id after the author. Must match `^(?:(?!(?:Plugin|Ctx|Axiom)$)[A-Z][A-Za-z0-9]{1,31})$`. Only used when `kind` is `plugin`. Default `BetterMinimap`.
- `language`: Language id. The language the provider adds, lowercase (e.g. python). Its PascalCase form names the types. Must match `^(?:[a-z][a-z0-9-]{1,31})$`. Only used when `kind` is `language-provider`. Default `mylang`.
- `author`: Author. Your registry handle, lowercase. Must match `^(?:[a-z][a-z0-9-]{1,31})$`. Default `acme`.
- `game`: Game id. The game's id, lowercase: the game a plugin targets, or the one a game provider adds. Its PascalCase form names a provider's types. Must match `^(?:[a-z][a-z0-9-]{1,31})$`. Only used when `kind` is `plugin` or `game-provider`. Default `mygame`.

Each starting point, and what to do after unzipping it:

### Rust plugin (`kind=plugin&lang=rust`)

A ready-to-build Rust/WASM Axiom plugin with the guide's manifest, layout and lifecycle, a sample low-health warning, unit and mock-host lifecycle tests beside the code, and a cross-platform CI workflow that lints, tests and packs the .axmod.

1. **Set up.** Install rustup and the Axiom CLI; the pinned toolchain installs itself. Run cargo generate-lockfile and commit Cargo.lock.
2. **Build and test.** Run cargo test and axiom test, then axiom build and axiom pack to produce the .axmod.

### Lua plugin (`kind=plugin&lang=lua`)

A ready-to-run Lua (tier 2) Axiom plugin with the guide's manifest, layout and lifecycle, a sample low-health warning, unit and mock-host lifecycle tests beside the code, a plain-Lua test runner, the StyLua config, and a CI workflow that checks, tests and packs the .axmod.

1. **Set up.** Install Lua 5.4, StyLua and the Axiom CLI at the versions pinned in CI. There is no build step or lock file.
2. **Test and pack.** Run stylua --check src tests and lua tests/run.lua src/low_health.test.lua, then axiom test and axiom pack to produce the .axmod.

### C# plugin (`kind=plugin&lang=csharp`)

A ready-to-build C#/WASI Axiom plugin with the guide's manifest, layout and lifecycle, a sample low-health warning, xUnit unit and mock-host lifecycle tests beside the code, nullable and warnings-as-errors builds, and a CI workflow that formats, tests, builds the WASM module and packs the .axmod.

1. **Set up.** Install the .NET SDK from global.json, the wasi-experimental workload, wasi-sdk 25 and the Axiom CLI. Run dotnet restore and commit the lock files.
2. **Build and test.** Run dotnet test and axiom test, then dotnet publish -r wasi-wasm (or axiom build) and axiom pack to produce the .axmod.

### C++ plugin (`kind=plugin&lang=cpp`)

A ready-to-build C++/WASM Axiom plugin with the guide's manifest, layout and lifecycle over the C ABI, a sample low-health warning, doctest unit and mock-host lifecycle tests beside the code, the C++ guide's clang-format and clang-tidy configs, CMake presets for wasi-sdk and native builds, and CI.

1. **Set up.** Install CMake 3.28+, a C++23 compiler, wasi-sdk 34 (set WASI_SDK_PATH), clang-format 19 and the Axiom CLI.
2. **Build and test.** Run cmake --preset native, build and ctest, then axiom test, axiom build and axiom pack to produce the .axmod.

### Game provider (`kind=game-provider`)

A ready-to-build Premake game provider with the guide's layout and manifest, a game API module spec and its generated C11 header, a combat hook, per-build signature data that fails closed, a capability-checked API module, doctest tests against a fake core, a conformance config, an example plugin, and CI with sanitizers.

1. **Set up.** Install the Axiom CLI and SDK 1.4.0, set AXIOM_SDK, then run scripts/Setup.sh or Setup.bat to add submodules and generate projects.
2. **Build and test.** Build with make or Visual Studio, then run Provider-Tests from the repository root; CI adds sanitizers and packs the .axprov.

### Language provider (`kind=language-provider`)

A Premake C++23 language provider with a C11 entry point, the provider lifecycle, per-plugin quotas, capability-checked calls and bounds-checked guest memory, lifecycle, quota and fuzz tests against a fake host, a conformance config, an SDK skeleton for plugin authors, and CI with sanitizers.

1. **Set up.** Install Premake 5.0.0-beta8 and the Axiom SDK, point AXIOM_SDK at it, then run scripts/Setup.sh or Setup.bat.
2. **Build and test.** Build with make config=debug or the Visual Studio solution, then run the -Tests executable from the repository root.

## Contents

- 01. Purpose and how to read this guide
- 02. How Axiom fits together
- 03. Choosing what to build
- 04. Toolchain and project layout
- 05. Naming
- 06. The plugin manifest
- 07. Plugin entry point and lifecycle
- 08. Calling the C ABI correctly
- 09. Events, hooks and talking to other plugins
- 10. Threading and the game loop
- 11. Memory and ownership across the boundary
- 12. Errors and crash safety
- 13. Writing a game provider
- 14. Game providers: hooking, signatures and game updates
- 15. Game API modules: exposing a game to plugins
- 16. Writing a language provider
- 17. Capabilities and security
- 18. Performance budgets
- 19. Logging and diagnostics
- 20. Testing plugins and providers
- 21. Packaging, versioning and publishing
- 22. Git, releases and review
- A. Appendix

## 01. Purpose and how to read this guide

Axiom is a universal modding framework: one core that loads plugins into many games, with plugins written in many languages. This guide is for the people who **extend** Axiom:

- **Plugin authors** write the gameplay changes, tools and overlays that players install.
- **Game provider authors** teach Axiom how to load into one game and expose that game to plugins.
- **Language provider authors** let plugins be written in another language (a WASM engine, a script VM, a native loader).

It covers how to structure, write, test, package and ship each of them so that every plugin and provider looks and behaves the same, and so that a broken one never takes the game down. Code examples use a fictional game, `mygame`, and a fictional author, `acme`.

Plugins and providers written in C or C++ also follow the [**C++ Style Guide**](https://style.chopper.dev/cpp). This guide only adds what is specific to Axiom.

### Rule levels

| Level | Meaning |
| --- | --- |
| [MUST] / [MUST NOT] | Required. The registry, CI or review rejects the plugin or provider. |
| [SHOULD] / [SHOULD NOT] | The default. Deviate only with a short comment explaining why. |
| [MAY] | Allowed when it helps; no justification needed. |
| [AVOID] | Legal but discouraged; expect a review question. |

### Principles

- **Plugins are guests.** They borrow the game's process and frame time. Leave everything as you found it.
- **Go through the API.** A plugin talks to the game only through the Axiom API and game API modules; a provider is the only code that touches game internals.
- **Fail small.** An error disables one plugin, never the game, the core or another plugin.
- **The lowest tier that works.** Write a WASM plugin before a script plugin, and a script plugin before a native one.
- **Version everything.** Plugins, providers, game API modules and the ABI each carry a version and declare what they need.

## 02. How Axiom fits together

```text
Every plugin reaches the game only through the C ABI and the core

+-----------------------+  +-----------------------+  +-----------------------+  +-----------------------+
|      WASM plugin      |  |     Script plugin     |  |    Hosted C# plugin   |  |     Native plugin     |
| Rust, C, C++, Zig, C# |  |          Lua          |  |   Full .NET, trusted  |  |  C or C++ via the ABI |
+-----------------------+  +-----------------------+  +-----------------------+  +-----------------------+
            |                          |                          |                          |
            v                          v                          v                          v
+-----------------------+  +-----------------------+  +-----------------------+  +-----------------------+
|     WASM provider     |  |      Lua provider     |  |     .NET provider     |  |    Native provider    |
|   Sandboxed, metered  |  |   One VM per plugin   |  | One load context each |  |  Signed plugins only  |
+-----------------------+  +-----------------------+  +-----------------------+  +-----------------------+
            |                          |                          |                          |
            v                          v                          v                          v
+--------------------------------------------------------------------------------------------------------+
|                                         Axiom C ABI (axiom.h)                                          |
|         Versioned, C only, frozen once released: the single source every SDK is generated from         |
+--------------------------------------------------------------------------------------------------------+
                                                     |
                                                     v
+--------------------------------------------------------------------------------------------------------+
| Axiom Core (C++23)                                                                                     |
|   +----------------+  +----------------+  +----------------+  +----------------+  +----------------+   |
|   | Plugin manager |  |  Hook manager  |  |   Event bus    |  |    Sandbox     |  |  Diagnostics   |   |
|   +----------------+  +----------------+  +----------------+  +----------------+  +----------------+   |
+--------------------------------------------------------------------------------------------------------+
                 |                                   |                                   |
                 v                                   v                                   v
+--------------------------------+  +--------------------------------+  +--------------------------------+
|      Native game provider      |  |     Managed game provider      |  |     Emulated game provider     |
|   Injected, hooks game code    |  |  Bridges .NET or JVM runtimes  |  |    Hooks inside an emulator    |
+--------------------------------+  +--------------------------------+  +--------------------------------+
                 |                                   |                                   |
                 v                                   v                                   v
+--------------------------------+  +--------------------------------+  +--------------------------------+
|        Native PC games         |  |     Managed-runtime games      |  |     Emulated console games     |
+--------------------------------+  +--------------------------------+  +--------------------------------+
```

*Axiom layers: plugins, language providers, the C ABI, the core and game providers.*

A plugin never touches a game directly. It calls the C ABI through its **language provider**; the core checks and applies the call; a **game provider** turns it into hooks and reads inside one specific game. The launcher (not drawn) starts the game, loads the core and the right game provider, and manages plugin profiles.

| Piece | Who writes it | Owns | May touch game memory? |
| --- | --- | --- | --- |
| Plugin | Plugin authors | Gameplay changes, tools, UI | No — only through game API modules |
| Language provider | Provider authors | Running plugin code in one language, safely | No |
| C ABI (`axiom.h`) | Axiom | The contract every plugin and provider builds against | — |
| Core | Axiom | Loading, ordering, hooks, events, capabilities, diagnostics | No |
| Game provider | Provider authors | Loading into one game; its hooks, symbols and game API module | Yes, for exactly one game |

### Rules

- `AXIOM-02.1` [MUST] keep dependencies pointing down the diagram: plugins → language providers → ABI → core → game providers.
- `AXIOM-02.2` [MUST] reach the game from a plugin only through the Axiom API and a game API module. There is no back door for "trusted" native plugins.
- `AXIOM-02.3` [MUST] add support for a game by writing a game provider, and for a language by writing a language provider. Neither requires changes to the core.

## 03. Choosing what to build

| You want to… | Build a… | Tier |
| --- | --- | --- |
| Change gameplay, add UI, add commands, record stats | **Plugin** | WASM by default |
| Share code between your plugins | **Library plugin** (a plugin that exports services, no gameplay) | Same as its users |
| Make Axiom work with a new game | **Game provider** + its **game API module** | Native (C++) |
| Expose more of a supported game to plugins | A new version of that game's **game API module** | Native (C++) |
| Let plugins be written in a new language | **Language provider** + its **SDK** | Native (C++) |

### Plugin tiers

Plugins are tiered by the language provider they run on. The tier decides how much Axiom trusts the code.

| Tier | Language provider | Trust | Isolation | Pick it when |
| --- | --- | --- | --- | --- |
| 1 | WASM (Rust, C, C++, Zig, AssemblyScript, C#) | Untrusted | Own linear memory, fuel limits, capability-gated imports | Almost always; the default template |
| 2 | Script (Lua) | Untrusted | Own VM state, stripped standard library, instruction and memory quotas | Small scripts, config-heavy plugins, fast iteration with live reload |
| 3 | Native (C or C++ via the C ABI) or hosted .NET (C#) | Trusted | None; runs in the game process | Measured performance needs or OS access WASM cannot offer; must be signed |

**C# has two paths.** Untrusted C# plugins compile to WASM with .NET's WASI target and run on the WASM language provider like any other tier 1 plugin. C# plugins that need full .NET (reflection-heavy libraries, large existing codebases, fast startup) run on the hosted .NET language provider. .NET cannot sandbox code inside a process, so hosted .NET plugins are tier 3: trusted and signed, under the same rules as native plugins.

- `AXIOM-03.1` [MUST] start every new plugin on tier 1 and move to a higher tier only for a reason written in the plugin's README.
- `AXIOM-03.2` [MUST NOT] write a game provider to work around a missing API; ask for (or contribute) the API in the game's API module instead.
- `AXIOM-03.3` [SHOULD] split a large plugin into a library plugin and thin feature plugins when other authors could reuse the library.

## 04. Toolchain and project layout

Start every plugin and provider from the official template (`axiom new plugin --lang rust`, `axiom new game-provider`, `axiom new language-provider`). The templates already follow this guide.

### Toolchain

| Area | Plugins | Providers |
| --- | --- | --- |
| Language | Any with an Axiom SDK (Rust, C, C++, Zig, AssemblyScript, C#, Lua) | C++23, with a C11 public surface |
| Build | The language's own build tool (`cargo`, `zig build`, `npm`, `dotnet`) wrapped by `axiom build` | Premake5; [MUST] build with MSVC on Windows; Clang/GCC for Linux and macOS |
| Formatting | The language's standard formatter (`rustfmt`, `clang-format`, `dotnet format`, `stylua`) | `clang-format`, 2-space indent |
| Packaging | `axiom pack` → `.axmod` | `axiom pack --provider` → `.axprov` |
| Testing | `axiom test` with the mock host | Unit tests + the provider conformance suite |

### A plugin project

```text
better-minimap/
  axiom.toml              # Manifest (§6)
  src/
    lib.rs                # Entry point: on_load / on_enable / on_disable / on_unload
    minimap.rs
    minimap.test.rs       # Tests beside the code
  assets/                 # Textures, sounds, localisation (lang/en.json)
  config/default.toml     # Default user settings
  README.md
  CHANGELOG.md
```

### A game provider project

```text
provider-mygame/
  axiom.toml              # Provider manifest: game IDs, supported builds
  premake5.lua
  include/axiom/mygame.h  # Game API module (generated from spec/)
  spec/                   # Game API definitions (IDL)
  src/
    Provider.cpp          # ax_game_provider_t implementation
    Hooks/                # One file per hooked subsystem
    Signatures/           # Patterns and offsets per game build
    Api/                  # Game API module implementation
    Provider.test.cpp
  tests/conformance/      # Provider conformance suite config
```

### Rules

- `AXIOM-04.1` [MUST] keep one plugin or provider per repository (or per top-level folder in a monorepo), with its manifest at the root.
- `AXIOM-04.2` [MUST] keep tests beside the code they test (`<file>.test.<ext>`), and shared fixtures in `tests/`.
- `AXIOM-04.3` [MUST] commit the lock file of the language's package manager, and pin the Axiom SDK version.
- `AXIOM-04.4` [MUST NOT] vendor or modify Axiom headers; depend on the SDK package.
- `AXIOM-04.5` [SHOULD] keep game-build-specific data (signatures, offsets) in data files under `Signatures/`, not inline in code, so a game update is a data change.

## 05. Naming

Axiom's short code is `AX`: it prefixes every Axiom macro, and in lower case every C ABI symbol. Your own code uses your own prefix.

| Thing | Convention | Example |
| --- | --- | --- |
| Plugin ID | Lower-case `author.plugin-name` | `acme.better-minimap` |
| Game provider ID | `provider.<game>` (official) or `author.provider-<game>` | `provider.mygame` |
| Language provider ID | `lang.<language>` or `author.lang-<language>` | `lang.wasm`, `lang.lua`, `lang.dotnet` |
| Game ID | Lower-case, stable, chosen by the game provider | `mygame` |
| Package file | `<id>-<version>.axmod` / `.axprov` | `acme.better-minimap-1.2.0.axmod` |
| Event name | `<area>.<noun>.<verb-past>` | `player.health.changed` |
| Plugin-defined event | Your plugin ID + `/` + event | `acme.better-minimap/zoom.changed` |
| Capability | `<area>:<access>` | `fs:read`, `net:connect`, `game:write` |
| Config key | snake_case | `zoom_level` |
| C ABI symbols (Axiom) | `ax_<area>_<verb>`, types `ax_..._t`, constants `AX_...` | `ax_event_subscribe`, `AX_RESULT_OK` |
| Game API symbols | `ax_<game>_<area>_<verb>` | `ax_mygame_player_get_health` |
| Your C/C++ macros | Your own 2–3 letter prefix, never `AX_` | `BMM_ASSERT` |

### Rules

- `AXIOM-05.1` [MUST] choose a plugin ID once; it is how saves, configs, dependencies and the registry find the plugin. [MUST NOT] rename it after the first release.
- `AXIOM-05.2` [MUST NOT] use the `ax_`, `AX_`, `axiom.` or `provider.` prefixes for your own symbols or IDs; they are reserved for Axiom and official providers.
- `AXIOM-05.3` [MUST] namespace every event, config key and storage key a plugin defines under its plugin ID.
- `AXIOM-05.4` [MUST] keep IDs, event names and capability names lower-case ASCII so they are stable across file systems and languages.
- `AXIOM-05.5` [SHOULD] use Axiom's nouns consistently in code, docs and UI: *plugin*, *game provider*, *language provider*, *game API module*, *event*, *hook*, *capability*.

## 06. The plugin manifest

Every plugin has an `axiom.toml` at its root. The core reads it before loading any code, so it decides load order, dependencies and permissions.

```toml
[plugin]
id = "acme.better-minimap"
name = "Better Minimap"
version = "1.2.0"                  # Semantic version
description = "A zoomable minimap with waypoints."
authors = ["Acme Mods <mods@acme.example>"]
license = "MIT"
language = "wasm"                  # Language provider: wasm | lua | dotnet | native
entry = "better_minimap.wasm"

[requires]
axiom = ">=1.4, <2"                # Core ABI version range
"game:mygame" = { api = ">=2.1, <3", builds = ["*"] }

[dependencies]
"acme.ui-kit" = ">=0.9, <1"

[optional-dependencies]
"other.waypoints-sync" = ">=1, <2"

[load]
after = ["acme.ui-kit"]
phase = "game"                     # early | game | late

[capabilities]
required = ["game:read", "ui:draw", "storage:plugin"]
optional = ["net:connect"]          # Asked for at runtime with a reason

[capabilities.reasons]
"net:connect" = "Sync waypoints with your party."

[network]
hosts = ["sync.acme.example"]     # The only hosts net:connect may reach (§17)
```

### Rules

- `AXIOM-06.1` [MUST] declare every capability the plugin uses, with a one-sentence reason for each optional one. Undeclared calls fail with `AX_RESULT_DENIED`.
- `AXIOM-06.2` [MUST] declare version ranges, never exact versions, for `axiom`, game API modules and dependencies; the upper bound is the next major.
- `AXIOM-06.3` [MUST] declare load order with `after` / `before` and `phase`, never by relying on file names or install order.
- `AXIOM-06.4` [MUST] bump `version` for every published change, following semantic versioning (§21).
- `AXIOM-06.5` [MUST NOT] put secrets, tokens or machine-specific paths in the manifest.
- `AXIOM-06.6` [SHOULD] keep `description` under 120 characters; it is shown in the launcher and registry.

## 07. Plugin entry point and lifecycle

### States

| State | Callback | Do here | Never do here |
| --- | --- | --- | --- |
| Loaded | `on_load` | Read config, register events, hooks and services; allocate long-lived state | Touch game state; the game may not be ready |
| Enabled | `on_enable` | Start gameplay effects, show UI, start timers | Long blocking work (move it to a task) |
| Disabled | `on_disable` | Undo **everything** `on_enable` did: remove effects, hide UI, stop timers | Fail; disabling must always succeed |
| Unloaded | `on_unload` | Free everything allocated in `on_load`; save state if hot-reloading | Call any Axiom API other than logging |

- `AXIOM-07.1` [MUST] keep every callback idempotent and safe to call in order load → enable → disable → enable → disable → unload. Players toggle plugins at runtime.
- `AXIOM-07.2` [MUST] leave the game exactly as it was found after `on_disable`: every hook, event subscription, spawned entity and UI element the plugin added is gone.
- `AXIOM-07.3` [SHOULD] register subscriptions and hooks through scoped handles the SDK releases automatically on disable, rather than tracking them by hand.
- `AXIOM-07.4` [MUST] return from each callback within the lifecycle budget (§18); slow initialisation goes into an async task.
- `AXIOM-07.5` [MAY] implement `on_save_state` / `on_restore_state` to keep state across hot reloads during development.

### Entry point (C)

```c
#include <axiom/axiom.h>
#include <axiom/mygame.h>

typedef struct bmm_state { ax_subscription_t on_health; int32_t zoom; } bmm_state;

static ax_result_t AX_CALL bmm_on_load(ax_plugin_ctx_t* ctx) {
  bmm_state* s = ax_alloc(ctx, sizeof(bmm_state));   /* owned by the plugin */
  if (s == NULL) return AX_RESULT_OUT_OF_MEMORY;
  s->zoom = ax_config_get_i32(ctx, AX_STR("zoom_level"), 3);
  ax_plugin_set_user_data(ctx, s);
  return AX_RESULT_OK;
}

static ax_result_t AX_CALL bmm_on_disable(ax_plugin_ctx_t* ctx) {
  bmm_state* s = ax_plugin_get_user_data(ctx);
  ax_event_unsubscribe(ctx, s->on_health);
  return AX_RESULT_OK;
}

AX_PLUGIN_EXPORT ax_result_t AX_CALL ax_plugin_main(const ax_api_t* api, ax_plugin_desc_t* out) {
  if (api->version_major != AX_ABI_VERSION_MAJOR) return AX_RESULT_UNSUPPORTED_VERSION;
  out->struct_size = sizeof(*out);
  out->on_load = bmm_on_load;
  out->on_enable = bmm_on_enable;
  out->on_disable = bmm_on_disable;
  out->on_unload = bmm_on_unload;
  return AX_RESULT_OK;
}
```

### Entry point (Rust SDK)

```rust
use axiom::prelude::*;
use axiom_mygame::player;

#[axiom::plugin]
struct BetterMinimap {
    zoom: i32,
}

impl Plugin for BetterMinimap {
    fn on_load(ctx: &mut Ctx) -> Result<Self> {
        Ok(Self { zoom: ctx.config().get("zoom_level").unwrap_or(3) })
    }

    fn on_enable(&mut self, ctx: &mut Ctx) -> Result<()> {
        // Scoped: removed automatically on disable.
        ctx.events().on(player::HealthChanged, |ev, _ctx| {
            log::debug!("health {} -> {}", ev.old, ev.new);
        })?;
        Ok(())
    }
}
```

## 08. Calling the C ABI correctly

Most authors use an SDK, which handles these rules. Native plugins, providers and SDK authors work with `axiom.h` directly and [MUST] follow them.

- `AXIOM-08.1` [MUST] check the `ax_result_t` of every call that returns one. `AX_RESULT_DENIED` means a capability is missing; `AX_RESULT_NOT_READY` means the game is not in a state where the call is valid.
- `AXIOM-08.2` [MUST] set `struct_size = sizeof(...)` on every struct passed to Axiom. This is how newer cores stay compatible with your plugin; a zero `struct_size` is rejected.
- `AXIOM-08.3` [MUST] zero-initialise structs before filling them (`ax_event_desc_t d = {0};`) so fields added in later minor versions start at their defaults.
- `AXIOM-08.4` [MUST] pass strings as `ax_str_t` (UTF-8 pointer + length; use `AX_STR("literal")`), never assume Axiom returns null-terminated strings.
- `AXIOM-08.5` [MUST] use only fixed-width types at the boundary (`int32_t`, `uint64_t`, `float`, `ax_bool_t`); never `bool`, `long`, `wchar_t` or a C++ type.
- `AXIOM-08.6` [MUST] treat every handle (`ax_entity_t`, `ax_subscription_t`) as opaque and possibly stale: check results when using a handle you stored across frames.
- `AXIOM-08.7` [MUST] mark callbacks `AX_CALL` and keep them non-throwing (C++: `noexcept`), and pass a `user_data` pointer instead of using globals.
- `AXIOM-08.8` [MUST] check the API version you were given at the entry point and call newer functions only when `api->version_minor` says they exist (the SDK's `ax_has(api, fn)` macro does this).
- `AXIOM-08.9` [MUST NOT] call Axiom from threads Axiom did not give you unless the function is documented as thread-safe (§10).

```c
ax_event_desc_t desc = {0};
desc.struct_size = sizeof(desc);
desc.name = AX_STR("player.health.changed");
desc.priority = 0;
desc.callback = bmm_on_health;
desc.user_data = state;

ax_result_t r = api->event_subscribe(ctx, &desc, &state->on_health);
if (r != AX_RESULT_OK) {
  ax_log(ctx, AX_LOG_ERROR, "subscribe player.health.changed failed: %s", ax_result_name(r));
  return r;
}
```

## 09. Events, hooks and talking to other plugins

### Events

- `AXIOM-09.1` [SHOULD] react to events instead of polling game state every frame.
- `AXIOM-09.2` [MUST] keep event handlers short and non-blocking; heavy work is scheduled as a task (§10).
- `AXIOM-09.3` [MUST] treat event payloads as borrowed: valid only during the callback. Copy what you need.
- `AXIOM-09.4` [MUST] use priorities only to express a real ordering need, and document it. Lower numbers run first; the default is `0`.
- `AXIOM-09.5` [MUST] cancel a cancellable event (`ax_event_cancel`) only when the plugin's purpose is to block that action, and log why at `debug` level.

### Hooks

Hooks are the plugin-facing way to change game behaviour at a point the game API module exposes (`mygame.damage.apply`). Plugins never patch game code themselves.

| Kind | Runs | Can |
| --- | --- | --- |
| `pre` | Before the game's code | Read and change arguments, or skip the original |
| `post` | After the game's code | Read and change the result |
| `observe` | After, read-only | Read arguments and result; cheapest |

- `AXIOM-09.6` [MUST] use the weakest hook kind that works: `observe` over `post`, `post` over `pre`.
- `AXIOM-09.7` [MUST] skip the original in a `pre` hook only when the plugin replaces that behaviour completely; two plugins skipping the same original is a conflict the core reports.
- `AXIOM-09.8` [MUST NOT] keep a hook's argument pointers after the hook returns.

### Services between plugins

- `AXIOM-09.9` [MUST] talk to other plugins through **services** (a versioned interface a plugin registers under its ID) or plugin-defined events, never by reaching into another plugin's memory or files.
- `AXIOM-09.10` [MUST] version every service interface with semantic versioning, and declare the dependency in the manifest.
- `AXIOM-09.11` [MUST] handle an optional dependency being absent or disabled at any time; check `ax_service_get` every time you need it, or listen for `plugin.disabled`.
- `AXIOM-09.12` [SHOULD] pass plain data (structs, strings, byte buffers) through services, not handles that only make sense inside one language provider.

## 10. Threading and the game loop

Axiom runs plugin callbacks on the **game thread** during defined phases of each frame, because most game state is only safe to touch there.

| Phase | When | Typical work |
| --- | --- | --- |
| `frame.begin` | Start of the game's update | Read input, start per-frame work |
| `game` events and hooks | During the game's own update | React to gameplay |
| `frame.end` | After the game's update, before rendering | Apply queued changes |
| `render.ui` | During UI rendering | Draw overlays and windows |
| Task pool | Worker threads, any time | File I/O, networking, heavy computation |

- `AXIOM-10.1` [MUST] touch game state and call game API functions only from the game thread, inside a callback or hook, unless the function is documented as thread-safe.
- `AXIOM-10.2` [MUST NOT] block the game thread: no sleeping, no synchronous network or disk I/O, no waiting on locks held by worker threads.
- `AXIOM-10.3` [MUST] move slow work to the task pool with `ax_task_spawn` and bring results back with `ax_task_post_to_game` (or the SDK's `ctx.spawn(...).then_on_game(...)`).
- `AXIOM-10.4` [MUST NOT] create raw OS threads in a plugin. Native plugins that need a dedicated thread request one through `ax_thread_create` so the core can name, track and stop it on disable.
- `AXIOM-10.5` [MUST] cancel and wait for every task a plugin started before `on_disable` returns; the SDK's scoped tasks do this.
- `AXIOM-10.6` [MUST] keep state shared between a task and the game thread behind the SDK's channels or a mutex, never as unsynchronised globals.

WASM and Lua plugins are single-threaded per plugin: their callbacks never run concurrently, and tasks are run as async calls on the plugin's own queue.

## 11. Memory and ownership across the boundary

Every pointer and handle that crosses the C ABI has one of four ownership rules, and the header says which.

| Marked | Meaning | Your job |
| --- | --- | --- |
| `/* borrowed */` | Axiom owns it; valid only during this call or callback | Copy what you keep |
| `/* valid until <event> */` | Axiom owns it; valid until a named point (end of frame, unload) | Do not use it after that point |
| `/* caller owns */` | You allocated it and pass it in; Axiom does not keep it | Free it when you are done |
| `/* transfers ownership */` | Axiom allocated it for you | Release it with the matching `ax_*_release` function |

- `AXIOM-11.1` [MUST] free memory with the allocator that allocated it. Memory from `ax_alloc` goes back through `ax_free`; never mix with `free`, `delete` or another language's allocator.
- `AXIOM-11.2` [MUST NOT] pass ownership of a C++ object, a `std::` type, a Rust `Box` or a Lua table across the ABI; pass plain data or a handle.
- `AXIOM-11.3` [MUST] release every handle and buffer a plugin owns by `on_unload`. The core reports leaks per plugin in development builds.
- `AXIOM-11.4` [MUST] treat handles to game objects (`ax_entity_t`) as weak: the object can be destroyed between frames. Re-validate with `ax_<game>_entity_is_valid` or listen for the destroy event.
- `AXIOM-11.5` [SHOULD] use the SDK's RAII or scoped wrappers (`ax::Handle<T>` in C++, `Drop` types in Rust) instead of manual release calls.
- `AXIOM-11.6` [MUST] in providers: validate every pointer and length that comes from a plugin before reading it, and copy guest data out of WASM linear memory before using it.

## 12. Errors and crash safety

### In plugins

- `AXIOM-12.1` [MUST] handle every error result. Expected failures (missing config, denied capability, entity gone) are logged at `warn` or `debug` and the plugin carries on.
- `AXIOM-12.2` [MUST] return a failure from `on_load` or `on_enable` when the plugin cannot work, with a message that tells the player what to do ("requires acme.ui-kit 0.9 or newer").
- `AXIOM-12.3` [MUST NOT] let an exception, Rust panic or Lua error escape a callback. SDKs catch them at the boundary, log them with a stack trace and disable the plugin; native plugins [MUST] do the same (`noexcept` callbacks with a `try` / `catch` at the edge).
- `AXIOM-12.4` [MUST NOT] call `abort`, `exit`, `std::terminate` or anything that ends the process.

### In providers

- `AXIOM-12.5` [MUST] isolate every call into a plugin: catch C++ exceptions, and on Windows use structured exception handling around native plugin calls, so a crash in a plugin disables that plugin instead of the game.
- `AXIOM-12.6` [MUST] disable a plugin that exceeds its limits (instruction budget, memory quota, frame-time budget repeated across frames) and tell the player which plugin and why.
- `AXIOM-12.7` [MUST] write a crash report on a real crash (minidump on Windows) that names the plugins and providers loaded and which one was running, and [MUST NOT] upload it without the player's consent.
- `AXIOM-12.8` [MUST] fail closed when a game build is unknown: refuse to install hooks and report "unsupported game version" rather than patching memory that may have moved.

### Messages

- `AXIOM-12.9` [MUST] write player-facing errors as: what happened, which plugin, what to do. "Better Minimap was disabled: it needs the `ui:draw` permission. Allow it in the launcher."
- `AXIOM-12.10` [SHOULD] give every error a stable code (`BMM-0012`) so players can search for it and you can find it in logs.

## 13. Writing a game provider

A game provider is the only code in Axiom that knows about one specific game. It implements the provider contract, installs the low-level hooks, and backs the game API module that plugins call.

### The provider contract

| Function | Called | Must |
| --- | --- | --- |
| `detect` | By the launcher, before starting the game | Identify the game and its exact build from files only (hashes, version resources); no side effects |
| `attach` | Once, early in the game process | Resolve signatures, verify the build, prepare hooks; fail closed on unknown builds |
| `start` | When the game is ready for plugins | Install hooks, register the game API module, emit `game.ready` |
| `frame` | Each frame, from the game thread | Drive the frame phases (§10) |
| `stop` | Before unload or on shutdown | Remove every hook, release every resource |
| `detach` | Last | Leave the process as it was before `attach` |

```cpp
namespace MyGame {

class Provider final : public Axiom::GameProvider {
public:
  ax_result_t Detect(const ax_game_files_t& files, ax_game_match_t& out) noexcept override;
  ax_result_t Attach(const ax_api_t& api) noexcept override;
  ax_result_t Start() noexcept override;
  void Frame(ax_frame_phase_t phase) noexcept override;
  void Stop() noexcept override;
  void Detach() noexcept override;

private:
  Signatures m_Signatures;
  HookSet m_Hooks;
  Api::Module m_Api;
};

}  // namespace MyGame

AX_GAME_PROVIDER(MyGame::Provider)
```

### Living inside the game's process

- `AXIOM-13.1` [MUST NOT] do work in `DllMain` or a shared-library constructor beyond recording the module handle: no allocation, threads, locks, logging or library loads under the loader lock.
- `AXIOM-13.2` [MUST NOT] rely on static initialisation order or non-trivial global constructors; create and destroy state in `attach` / `detach`.
- `AXIOM-13.3` [MUST] link the C runtime statically on Windows (`/MT`) so the provider never conflicts with the game's CRT.
- `AXIOM-13.4` [MUST NOT] change process-wide state the game depends on (FPU control word, locale, working directory, signal or exception handlers, COM apartment) without restoring it on the same thread.
- `AXIOM-13.5` [MUST] be able to stop and detach cleanly at runtime: every hook removed, every thread joined, every allocation returned.
- `AXIOM-13.6` [MUST] use `std::expected<T, Axiom::Error>` for recoverable failures and keep every function the core or a hook can call `noexcept`.

### Managed and emulated games

- `AXIOM-13.7` [SHOULD] hook managed games (.NET, JVM) at the runtime level through the runtime's supported profiling or agent interfaces before resorting to native patches.
- `AXIOM-13.8` [MUST] in emulated targets, hook the emulator's guest-facing layer (CPU, memory, HLE calls), not the emulator's UI or host code, and translate guest addresses through the emulator's memory map every time.

## 14. Game providers: hooking, signatures and game updates

### Hooking

- `AXIOM-14.1` [MUST] install every hook through Axiom's hook manager (`Axiom::Hooks`), never with a private detour library, so hooks from different providers are ordered, tracked and removable.
- `AXIOM-14.2` [MUST] suspend or synchronise game threads while patching code, and verify the bytes being replaced match what the signature expected.
- `AXIOM-14.3` [MUST] keep each native hook as thin as possible: capture arguments, call into the core's dispatch, return. Game logic lives in the game API module, not in the trampoline.
- `AXIOM-14.4` [MUST] preserve the hooked function's calling convention, registers and stack alignment exactly; write a test for each hook that calls the original through it.
- `AXIOM-14.5` [SHOULD] hook the narrowest stable function that gives plugins what they need, and prefer one hook that feeds many events over many hooks.

### Signatures and offsets

- `AXIOM-14.6` [MUST] locate game code and data by byte-pattern signatures, exported symbols, RTTI or metadata — never by hard-coded absolute addresses.
- `AXIOM-14.7` [MUST] keep signatures and offsets in data files per game build (`Signatures/<build>.toml`), each with a name, pattern, expected match count and the build it was verified on.
- `AXIOM-14.8` [MUST] fail closed: if any required signature has zero or more than one match, refuse to start and report which one.
- `AXIOM-14.9` [SHOULD] resolve all signatures once in `attach`, in parallel, and cache the result per build hash.

### Game updates

- `AXIOM-14.10` [MUST] detect the game build exactly (file hash or version resource) and list supported builds in the provider manifest.
- `AXIOM-14.11` [MUST] treat a new game build as unsupported until the signatures are verified on it; the launcher tells players to wait for a provider update.
- `AXIOM-14.12` [SHOULD] run the provider conformance suite against every supported build in CI, and keep at least the last two builds of a live-service game supported while players update.

```toml
# Signatures/1.18.4-0b7c2e.toml
[build]
version = "1.18.4"
sha256 = "0b7c2e…"

[[signature]]
name = "Player::ApplyDamage"
pattern = "48 89 5C 24 ?? 57 48 83 EC 30 8B FA"
matches = 1
```

## 15. Game API modules: exposing a game to plugins

The game API module is the public face of a game provider: the functions, types, events and hook points plugins use for that game. It is a public API, held to the same standard as `axiom.h`.

### One definition

- `AXIOM-15.1` [MUST] define the module in `spec/` (IDL). `AxiomGen` generates the C header (`axiom/mygame.h`), the size asserts, every SDK binding and the reference pages from it. Generated files [MUST NOT] be edited by hand.
- `AXIOM-15.2` [MUST] land a spec change, its generated output and an example plugin that uses it in the same pull request.

### Design

- `AXIOM-15.3` [MUST] expose game concepts, not game internals: `ax_mygame_player_get_health(player)` rather than a pointer to the game's `Player` struct or an offset.
- `AXIOM-15.4` [MUST] use stable identifiers that survive game updates (names, hashed names, provider-assigned IDs), never raw addresses.
- `AXIOM-15.5` [MUST] separate queries (no side effects) from commands (change state), and state which phases each may be called in.
- `AXIOM-15.6` [SHOULD] publish events for state changes plugins would otherwise poll (`player.health.changed`), and hook points for behaviour they may change (`mygame.damage.apply`).
- `AXIOM-15.7` [SHOULD] offer batched calls for things plugins do in loops (`ax_mygame_entities_query` filling a caller buffer), because each boundary crossing from WASM or Lua has a fixed cost.
- `AXIOM-15.8` [MUST] gate writes and anything that affects multiplayer or other players behind capabilities (`game:write`, `game:network`).
- `AXIOM-15.9` [MUST] keep the module the same across supported game builds; differences between builds are the provider's problem, hidden behind the module.

### Versioning

| Change | Bump | Existing plugins |
| --- | --- | --- |
| New function, event, hook point or optional trailing struct field | Minor | Keep working |
| Bug fix that keeps the documented behaviour | Patch | Keep working |
| Remove, rename, reorder or change the meaning of anything released | Major | Must be updated; keep the previous major available for at least one year |

- `AXIOM-15.10` [MUST] version the game API module independently of the provider binary and of Axiom; a game update that only needs new signatures does not change the module version.
- `AXIOM-15.11` [MUST] deprecate before removing: mark it in the spec, warn once per plugin at load, and keep it working until the next major.

## 16. Writing a language provider

A language provider lets plugins be written in one language. It loads plugin code, runs it safely, and bridges every Axiom and game API call.

### The contract

| Function | Must |
| --- | --- |
| `load` | Validate and load the plugin's code; no plugin code runs yet |
| `call` | Invoke a plugin callback with arguments marshalled from the C ABI |
| `limits` | Apply the plugin's memory, instruction and time quotas |
| `usage` | Report memory, CPU time and call counts per plugin |
| `save_state` / `restore_state` | Support hot reload where the language allows it |
| `unload` | Free every resource the plugin had, even if it misbehaved |

### Rules

- `AXIOM-16.1` [MUST] expose exactly the API the C ABI defines, generated by AxiomGen from the same spec. A function available to native plugins but missing in your language is a bug.
- `AXIOM-16.2` [MUST] give every plugin its own isolated instance (a WASM instance, a Lua state); plugins never share globals.
- `AXIOM-16.3` [MUST] enforce quotas: memory caps, and fuel, epoch or instruction-count interruption so an infinite loop is stopped and the plugin disabled, not the game frozen.
- `AXIOM-16.4` [MUST] remove ambient authority from the language: no WASI file, clock, random or network imports and no Lua `os`, `io`, `debug`, `package.loadlib` or FFI, except as granted by capabilities.
- `AXIOM-16.5` [MUST] bounds-check every (pointer, length) pair a guest passes, and copy data out of guest memory before using it.
- `AXIOM-16.6` [MUST] convert language-level failures (WASM traps, Lua errors, panics) into an error result plus a log entry with the plugin's own stack trace.
- `AXIOM-16.7` [MUST] ship an SDK for the language with idiomatic wrappers: scoped handles, typed events, the language's normal error type and async model.
- `AXIOM-16.8` [SHOULD] support hot reload in development builds: save state, unload, load the new code, restore.
- `AXIOM-16.9` [MUST] embed the engine (WASM runtime, Lua VM) behind your provider's own interface and pin its version, so it can be upgraded without changing plugins.

### The hosted .NET language provider

- `AXIOM-16.10` [MUST] host the .NET runtime once per game process (through `hostfxr`), pinned to one supported .NET LTS version that the provider ships with; plugins never bring their own runtime.
- `AXIOM-16.11` [MUST] load each plugin into its own collectible `AssemblyLoadContext` so it can be unloaded and hot-reloaded, and [MUST] release every GC handle and delegate pointing into a plugin before unloading it, or the context will never collect.
- `AXIOM-16.12` [MUST] marshal across the C ABI with `[UnmanagedCallersOnly]` entry points and blittable structs generated by AxiomGen; no `Marshal.GetDelegateForFunctionPointer` or COM interop at the boundary.
- `AXIOM-16.13` [MUST] catch every exception at the boundary and never let one unwind into native frames.
- `AXIOM-16.14` [MUST] treat hosted .NET plugins as trusted (tier 3): signed packages only, `process:native` capability required.
- `AXIOM-16.15` [SHOULD] run plugin callbacks on the game thread without allocating, keeping per-frame paths free of LINQ, closures and boxing so the .NET GC does not cause frame spikes.

### C# SDK

- `AXIOM-16.16` [MUST] target the same API surface for both C# paths: one `Axiom` NuGet package whose code builds for the WASI target and for hosted .NET.
- `AXIOM-16.17` [MUST] follow the standard .NET conventions in C# plugins: PascalCase public members, `async` / `await` for tasks (mapped onto Axiom's task pool), nullable reference types enabled, warnings as errors.
- `AXIOM-16.18` [SHOULD] expose events as C# events or `IObservable<T>`, and scoped resources as `IDisposable`, so `using` releases them automatically.

## 17. Capabilities and security

Players install plugins from strangers. Capabilities let them see and limit what each plugin can do.

| Capability | Grants |
| --- | --- |
| `game:read` | Read game state through game API modules |
| `game:write` | Change game state and use `pre` / `post` hooks |
| `game:network` | Send or alter multiplayer traffic (denied on servers that forbid it) |
| `ui:draw` | Draw overlays and windows |
| `input:read` | Read keyboard, mouse and controller input |
| `storage:plugin` | Read and write the plugin's own data folder |
| `fs:read` / `fs:write` | Read or write files outside the plugin's folder, through a picker the player approves |
| `net:connect` | Open outbound connections to the hosts the manifest lists |
| `process:native` | Run as a native (tier 3) plugin |

### For plugin authors

- `AXIOM-17.1` [MUST] request the fewest capabilities that work, and explain every optional one in the manifest.
- `AXIOM-17.2` [MUST] handle `AX_RESULT_DENIED` gracefully: turn the feature off and tell the player, never retry in a loop.
- `AXIOM-17.3` [MUST] list the exact hosts a plugin connects to (`[network] hosts = ["api.acme.example"]`) and use TLS.
- `AXIOM-17.4` [MUST NOT] collect personal data, telemetry or crash reports without the player's opt-in, and [MUST] say what is sent in the README.
- `AXIOM-17.5` [MUST NOT] include obfuscated code, downloaders that fetch new code at runtime, or anything that bypasses a game's anti-cheat or multiplayer rules.

### For provider authors

- `AXIOM-17.6` [MUST] enforce capabilities in the provider for every call that needs one; never trust the plugin's SDK to have checked.
- `AXIOM-17.7` [MUST] load native (tier 3) plugins only when signed by a trusted key or explicitly allowed by the player in developer mode.
- `AXIOM-17.8` [MUST] verify package signatures and hashes before loading any plugin or provider.
- `AXIOM-17.9` [MUST] refuse to run plugins with `game:write` or `game:network` in online modes the game provider marks as protected.

## 18. Performance budgets

A plugin shares the frame with the game and every other plugin. Budgets keep one plugin from costing everyone their frame rate. The numbers below are starting defaults; a game provider may tighten them for its game.

| Where | Budget per plugin | What happens when exceeded |
| --- | --- | --- |
| Work on the game thread per frame | 0.5 ms average, 2 ms peak | Warning in the overlay; disabled after sustained overruns |
| A single event handler or hook | 0.1 ms | Logged with the handler name in development builds |
| `on_load` / `on_enable` | 50 ms | Warning; move work to a task |
| Memory (WASM and Lua) | 64 MiB default, raised in the manifest up to 512 MiB | Allocation fails; plugin disabled if it cannot recover |
| Hooks installed by a game provider | No measurable cost when no plugin uses the hook point | Provider bug |

- `AXIOM-18.1` [MUST] stay within budget on the reference machine the game's minimum specs describe.
- `AXIOM-18.2` [MUST] measure before optimising, with `axiom profile` or the in-game overlay, and include before and after numbers in performance pull requests.
- `AXIOM-18.3` [SHOULD] cache results that do not change every frame, and spread periodic work across frames instead of doing it all on one.
- `AXIOM-18.4` [SHOULD] use batched game API calls in WASM and Lua plugins; each boundary crossing has a fixed cost.
- `AXIOM-18.5` [MUST NOT] allocate on hot paths in native plugins and providers (per-frame handlers, hook trampolines); reuse buffers.
- `AXIOM-18.6` [MUST] in providers: make a hook point cost nothing when no plugin subscribes to it, by checking a subscriber count before dispatching.

## 19. Logging and diagnostics

- `AXIOM-19.1` [MUST] log through Axiom's logger (`ax_log`, or the SDK's `log` macros); never `printf`, `std::cout`, `print` or the game's own console. Axiom tags each line with the plugin or provider ID.
- `AXIOM-19.2` [MUST] use levels consistently: `trace` and `debug` for development detail, `info` for lifecycle and rare player-relevant events, `warn` for handled problems, `error` for failures that disabled a feature.
- `AXIOM-19.3` [MUST NOT] log every frame at `info` or above. Rate-limit repeated messages (`ax_log_once`, `log::warn_every!(5s, ...)`).
- `AXIOM-19.4` [MUST NOT] log secrets, tokens, or personal data such as player names or chat in non-debug levels.
- `AXIOM-19.5` [SHOULD] add structured fields instead of formatting them into text: `log::info!(zoom = self.zoom; "minimap enabled")`.
- `AXIOM-19.6` [SHOULD] expose a debug panel in the in-game overlay (`ax_debug_panel_add`) for plugin state, instead of verbose logging.
- `AXIOM-19.7` [MUST] in providers: report per-plugin frame time, memory and hook counts to Axiom's diagnostics so the overlay and `axiom profile` can show them, and include the provider version and game build in every crash report.

## 20. Testing plugins and providers

### Plugins

- `AXIOM-20.1` [MUST] unit-test plugin logic with the language's normal test runner, tests beside the code (`minimap.test.rs`, `minimap.test.lua`).
- `AXIOM-20.2` [MUST] test behaviour against the **mock host** (`axiom test`): a fake core and fake game API module that let tests fire events, call hooks, advance frames and assert on what the plugin did.
- `AXIOM-20.3` [MUST] test the full lifecycle at least once: load → enable → disable → enable → disable → unload, asserting nothing leaks and nothing stays hooked.
- `AXIOM-20.4` [MUST] test the denied-capability and missing-optional-dependency paths.
- `AXIOM-20.5` [SHOULD] run an in-game smoke test (`axiom run --game mygame --headless --script smoke.toml`) before each release when the game supports it.

```rust
#[axiom::test]
fn zoom_resets_on_disable(host: &mut MockHost) -> Result<()> {
    let plugin = host.load::<BetterMinimap>()?;
    host.enable(&plugin)?;
    host.emit(player::HealthChanged { old: 100, new: 40 });
    host.advance_frames(3);
    host.disable(&plugin)?;
    assert!(host.ui().windows_of(&plugin).is_empty());
    assert_eq!(host.hooks_of(&plugin).len(), 0);
    Ok(())
}
```

### Game providers

- `AXIOM-20.6` [MUST] pass the **provider conformance suite**: lifecycle, attach and detach, hook install and removal, event delivery, capability checks and crash isolation.
- `AXIOM-20.7` [MUST] verify every signature file against its game build in CI (match counts and expected bytes) and fail on any drift.
- `AXIOM-20.8` [MUST] test each hook by calling the original through it with known arguments and comparing the result to an unhooked call.
- `AXIOM-20.9` [SHOULD] keep a set of real community plugins as a compatibility corpus and run it against every provider release.

### Language providers

- `AXIOM-20.10` [MUST] run the shared API conformance tests through your language's SDK, proving every ABI function is reachable and behaves the same as from C.
- `AXIOM-20.11` [MUST] test quota enforcement: an infinite loop, a memory bomb and a deep recursion are each stopped and the plugin disabled, with the game still running.
- `AXIOM-20.12` [MUST] fuzz the marshalling layer with malformed guest pointers and lengths.

### CI

- `AXIOM-20.13` [MUST] build and test on every supported platform, and run providers' tests under ASan and UBSan builds as well as the release build.

## 21. Packaging, versioning and publishing

### Versioning

| Change to a plugin | Bump |
| --- | --- |
| Bug fix, no change to config, saves or services | Patch |
| New feature, new config key with a default, new service method | Minor |
| Removed feature, renamed config key, changed save format or service contract | Major |

- `AXIOM-21.1` [MUST] follow semantic versioning for plugins, providers, game API modules and services.
- `AXIOM-21.2` [MUST] migrate a plugin's saved data and config forward automatically on upgrade, and keep a schema version in every saved file.
- `AXIOM-21.3` [MUST] keep a `CHANGELOG.md` generated from Conventional Commits, with player-facing notes at the top of each release.

### Packages

- `AXIOM-21.4` [MUST] build packages with `axiom pack` only. A `.axmod` contains the manifest, the compiled plugin (`.wasm`, Lua sources, or one native library per platform), assets, licences and a file hash list.
- `AXIOM-21.5` [MUST] sign every published package with the author's registry key; native plugins and all providers [MUST] be signed.
- `AXIOM-21.6` [MUST] make builds reproducible: pinned toolchains, lock files, no timestamps or absolute paths in the output.
- `AXIOM-21.7` [MUST NOT] include debug symbols, source maps or test data in the published package; publish symbols separately for crash reports.

### Publishing

- `AXIOM-21.8` [MUST] fill in the registry listing: description, screenshots, supported games and builds, capabilities with reasons, source link and licence.
- `AXIOM-21.9` [MUST] publish open-source plugins with an OSI licence file at the root; closed-source plugins are allowed but cannot request `process:native`.
- `AXIOM-21.10` [SHOULD] release to a `beta` channel first and promote to `stable` after it has run without new crash reports.

## 22. Git, releases and review

### Commits and branches

- `AXIOM-22.1` [MUST] follow **Conventional Commits**: `type(scope): summary`, imperative, lower case, no trailing period, under 72 characters. Scope is the subsystem: `feat(waypoints): add party sync`, `fix(signatures): update for 1.18.4`.
- `AXIOM-22.2` [MUST] mark breaking changes with `!` and a `BREAKING CHANGE:` footer; these require a major version bump.
- `AXIOM-22.3` [MUST] keep commits small and focused, each building and passing tests.
- `AXIOM-22.4` [MUST] branch from `main` with a typed name (`feat/party-sync`, `fix/1.18.4-signatures`), and tag releases `v<semver>`.

### Review checklist: plugins

- Does `on_disable` undo everything `on_enable` did, with no leaks or hooks left behind?
- Are capabilities minimal, explained, and is denial handled?
- Does every handler stay within its budget and keep heavy work off the game thread?
- Are events, config keys and services namespaced under the plugin ID?
- Are errors handled, and are player-facing messages clear?

### Review checklist: providers

- Does it fail closed on unknown game builds and ambiguous signatures?
- Are all hooks installed through the hook manager and removed on stop?
- Is every plugin call isolated, and are capabilities enforced in the provider?
- Are game API changes made in the spec, generated, versioned and covered by an example plugin?
- Does the conformance suite pass on every supported build and platform?

## Appendix

### A. Minimal WASM plugin (Rust)

```rust
// src/lib.rs
use axiom::prelude::*;

#[axiom::plugin]
struct HelloAxiom;

impl Plugin for HelloAxiom {
    fn on_load(_ctx: &mut Ctx) -> Result<Self> {
        Ok(Self)
    }

    fn on_enable(&mut self, ctx: &mut Ctx) -> Result<()> {
        log::info!("hello from {}", ctx.plugin_id());
        Ok(())
    }
}
```

```toml
# axiom.toml
[plugin]
id = "acme.hello-axiom"
name = "Hello Axiom"
version = "0.1.0"
description = "Logs a greeting when enabled."
authors = ["acme"]
license = "MIT"
language = "wasm"
entry = "hello_axiom.wasm"

[requires]
axiom = ">=1.4, <2"

[load]
phase = "game"

[capabilities]
required = []
```

### B. Minimal Lua plugin

```lua
-- main.lua
local plugin = axiom.plugin()

function plugin:on_enable(ctx)
  ctx.events:on("player.health.changed", function(ev)
    if ev.new < 20 then
      ctx.ui:toast("Low health!")
    end
  end)
end

return plugin
```

### B2. Minimal C# plugin

```csharp
// HelloAxiom.cs
using Axiom;
using Axiom.MyGame;

[Plugin("acme.hello-axiom")]
public sealed class HelloAxiom : Plugin
{
    private IDisposable? _onHealth;

    public override void OnEnable(PluginContext ctx)
    {
        // Removed automatically on disable; disposing early is fine too.
        _onHealth = ctx.Events.On<Player.HealthChanged>(ev =>
        {
            if (ev.New < 20)
            {
                ctx.Ui.Toast("Low health!");
            }
        });
    }

    public override void OnDisable(PluginContext ctx) => _onHealth?.Dispose();
}
```

### C. New plugin checklist

- [ ] Started from `axiom new plugin`; WASM unless the README says why not.
- [ ] Plugin ID chosen and final; events, config and services namespaced under it.
- [ ] Manifest declares version ranges, load order and minimal capabilities with reasons.
- [ ] Lifecycle test passes: enable/disable twice, no leaks, no hooks left.
- [ ] Within the frame budget on minimum-spec hardware.
- [ ] README, CHANGELOG and licence present; package signed.

### D. New game provider checklist

- [ ] Detects the game and exact build from files only.
- [ ] Signatures in data files per build, verified in CI, failing closed.
- [ ] All hooks through the hook manager, removed on stop; process state restored on detach.
- [ ] Game API module defined in `spec/`, generated, versioned, with an example plugin.
- [ ] Capabilities enforced for writes and network; protected online modes respected.
- [ ] Conformance suite passing on every supported build and platform.

### E. Sources

- [Semantic Versioning 2.0.0](https://semver.org/)
- [Conventional Commits 1.0.0](https://www.conventionalcommits.org/)
- [WebAssembly specification](https://webassembly.github.io/spec/)
- [Lua 5.4 reference manual](https://www.lua.org/manual/5.4/)
