# Axiom Style Guide: rules digest

> Every rule from the Axiom Style Guide (rev. 2026.10), without the prose, tables and examples. The full guide: https://style.chopper.dev/axiom/llms.txt

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/rules.md. Changelog: https://style.chopper.dev/axiom/changelog.md.

MUST / MUST NOT are non-negotiable. SHOULD / SHOULD NOT are the default; deviate only with a short comment saying why. MAY is allowed when it helps. AVOID is allowed but a smell. 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`. 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.

## 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.

## 02. How Axiom fits together

- `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

- `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

- `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-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

- `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

- `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.

## 08. Calling the C ABI correctly

- `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).

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

- `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.
- `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.
- `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-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.

## 11. Memory and ownership across the boundary

- `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

- `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.
- `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.
- `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

- `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`.
- `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

- `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.
- `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.
- `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.

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

- `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.
- `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.
- `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

- `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.
- `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.
- `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

- `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.
- `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

- `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

- `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.
- `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.
- `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.
- `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

- `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.
- `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.
- `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

- `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>`.
