Files
docs/modules/rust
wtclaude 8d31e497d2 docs(modules): add the Rust Oxide/uMod API reference for module-rust
Mirrors uMod's Rust game API into docs/modules/rust/ so the upcoming
module-rust can be designed against it without a round trip upstream.

- HOOKS.md: all 477 hooks in 20 categories, scraped verbatim from
  umod.org/documentation/games/rust. Each entry keeps its description,
  its return contract, its tags and every C# overload uMod publishes.
  Opens with a category table and a full alphabetical index
  (name -> category -> return contract).
- DEFINITIONS.md: 678 items (short name, item id, display name) and
  2,590 workshop skin ids across 104 items.
- README.md: why the reference exists, how an Oxide hook actually binds
  (by name and arity, by reflection - a typo is silently never called),
  the four return contracts, and how the bridge invariants restate
  against Oxide. Records provenance and the exact refresh procedure:
  the hooks JSON endpoint is not Cloudflare-gated even though the HTML
  page is, and the definitions page has to come out of a browser.

The mirror is reference only - uMod stays upstream and normative, and
nothing here is a Runic Gateway contract. Rust wipes monthly, so both
files carry their capture date (2026-09-15) and a re-capture note.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4
2026-09-15 09:38:43 -05:00
..

Rust — the Oxide/uMod API reference

Reference material for the upcoming module-rust: a mirror of uMod's Rust game API, captured here so the module can be designed and built against it without a round trip to the website on every question.

Two documents, both scraped verbatim from uMod on 2026-09-15:

Doc What it holds Size
HOOKS.md Every hook the uMod Rust extension raises — 477 hooks in 20 categories, each with its description, its return contract, its tags and the C# signature to declare 9.3k lines
DEFINITIONS.md The item table (678 items: short name, item id, display name) and the workshop skin ids (2,590 skins across 104 items) 3.8k lines

This is a mirror, not a specification we own. uMod is upstream and wins any disagreement; the point of copying it is availability and grep-ability, not authority. Nothing here may be cited as a Runic Gateway contract — our contracts are MODULE_API.md and docs/link/.


1. Why this exists

The website core is game-agnostic; a module is what makes it a site for one particular game (MODULE_SYSTEM.md). module-uo was the first. A Rust module is the second, and it was already designed once on paper — ../rust-dryrun.md is that dry run, written deliberately without implementing it, to find out whether the module contract generalises past Ultima Online.

The dry run's central structural fact is the thing this reference serves:

A ServUO shard is C# source the operator compiles into their own server, so our bridge plugin can be anything we want. A Rust server is a binary nobody outside Facepunch patches. The only way in is a mod — specifically an Oxide plugin, since Oxide/uMod is what modded Rust servers run — hooking the game's own events.

So where the UO side gets to choose what the shard emits, the Rust side can only emit what Oxide already hands it. HOOKS.md is therefore the hard boundary on what a Rust module can ever know about a live server: if a fact is not reachable from one of these 477 hooks (or from a game type one of them hands you), the bridge cannot report it.

That makes this reference the input to two decisions the module has to make:

  1. What the Rust sidecar's event catalogue can contain — the analogue of docs/link/PLAN.md §5 on the UO side.
  2. Which hooks are cheap and which are hot. OnTick, OnFrame and OnEntityTakeDamage fire at game-loop rates; a bridge that does real work inside them stalls the server. The same invariant as UO's Emit() applies — see §4.

2. How an Oxide hook actually binds

The one thing to understand before reading HOOKS.md, because it is not in the table itself:

Oxide binds hooks by name and arity, by reflection, at runtime. There is no interface to implement and no compile-time check. A method whose name is misspelled, or whose parameter types do not match, is simply never called — silently, with no warning at load. This is the single most common way a Rust plugin "does nothing".

The consequence for us: every hook name a module relies on is an untyped string constant against a moving upstream. Facepunch renames and removes hooks on wipes. A Rust sidecar should log which of its expected hooks have actually fired at least once, so a hook that quietly stopped existing is visible rather than mysterious.

The return contract

HOOKS.md reproduces uMod's return line for every hook and it is the load-bearing part:

uMod's wording What it means
No return behavior A notification. Declare void. Nothing you return is read.
Returning a non-null value overrides default behavior A veto with a payload. Declare object; return null to let the game proceed, anything else to cancel it.
Returning true or false overrides default behavior A veto. Declare object; null abstains — returning false is not the same as abstaining.
Returning a string will kick… The value is consumed as data, not merely as a veto.

Two cautions that apply across the whole table:

  • Never return non-null from a hook documented as no return behavior; on some hooks Oxide will still read it as a veto.
  • When several plugins hook the same veto, the first non-null return wins and the rest are not consulted. Abstain with null unless you mean to decide. A bridge plugin should be a reader, and must return null from every veto hook it listens on — see §4.

Three hooks (OnEntityTakeDamage, OnExcavatorSuppliesRequested, OnPhoneCallStarted) state no return behaviour upstream at all; HOOKS.md marks those (not stated upstream) rather than guessing.


3. What is in the 477

Category Hooks What it covers
Server 17 Process lifecycle, tick/frame, save and restart, RCON and console commands
Player 138 Connect, chat, command, craft, loot, build, die, respawn
Entity 140 The generic BaseEntity graph — spawn, kill, damage, mount, loot, flags
Item 43 Item stacks and containers
Vehicle 22 Boats, cars, horses, helicopters, trains, submarines
Vending 16 Vending machines, the shop/trade flow, marketplace and drone deliveries
Weapon 16 Firing, reloading, throwing, melee, projectiles and traps
Structure 15 Placement, upgrade, demolish, stability, doors and locks
Resource 12 Gathering and node dispensers
Team 12 Rust's built-in team/party system
Phone 12 In-game telephones
Permission 8 uMod's own permission/group system (Covalence)
Clan 7 The first-party clan system, distinct from Team
Fishing · Plugin · TechTree 4 each
Sign 3
Electronic 2
Terrain · World 1 each

Player and Entity are 58% of the surface between them. HOOKS.md opens with a full alphabetical index (name → category → return contract), which is the fastest way in when you already know the hook's name.

Two notes on the data, both faithful to upstream:

  • OnShopCompleteTrade appears twice — two separate records. Both are kept; the second carries the anchor #onshopcompletetrade-1.
  • Eleven hooks document more than one overload (e.g. CanUpdateSign takes either a Signage or a PhotoFrame). Every code block is reproduced.

4. Where this meets our architecture

The bridge invariants the platform already holds (ARCHITECTURE.md, docs/link/PLAN.md) are game-independent, and a Rust module inherits all of them. Restated against Oxide:

  • The game server is never network-reachable. The Rust sidecar listens; the Oxide plugin dials out to it, exactly as BridgeLink.cs does on the UO side.
  • A wedged or absent sidecar must never stall the server. Oxide hooks run on Rust's main thread. An emit must be an enqueue onto a bounded drop-oldest queue that returns immediately — never a socket write, never a blocking call, and never anything expensive inside OnTick/OnFrame.
  • The bridge plugin decides nothing. It returns null from every veto hook it subscribes to. Access control and audience scoping live on the website (SHARD_VISIBILITY.md), not in the game plugin — the same rule that makes the uo-link sidecar a dumb forwarder.
  • Sensitive events never reach the public stream. Rust raises plenty that must not: CanUserLogin and OnUserApproved carry IP addresses, OnPlayerReported carries player reports. These belong on the admin channel only.

Reading order for someone picking the module up: ../rust-dryrun.md for the design, MODULE_API.md for the contract it must satisfy, then HOOKS.md for what the game can actually tell it.


5. Provenance and refreshing

Source (hooks) https://umod.org/documentation/games/rust
Source (definitions) https://umod.org/documentation/games/rust/definitions
Captured 2026-09-15
Content 477 hooks · 678 items · 2,590 skins

Rust wipes monthly and the hook list moves with it. Re-capture before any significant work on the module, and note the new date at the top of both files.

The hooks page renders client-side from a JSON endpoint, and that endpoint is not behind Cloudflare even though the HTML page is:

curl -A "Mozilla/5.0" -H "Accept: application/json" \
  "https://umod.org/documentation/hooks/rust.json" -o all_rust_hooks.json

One request returns every hook (per_page is 9999, so there is no paging). Each record carries name, subcategory, tags, description (HTML <ul>) and example (a markdown-fenced C# block) — which is exactly what HOOKS.md renders. A ?subcategory=Server query narrows it.

The definitions page has no such endpoint and the HTML is Cloudflare-gated, so it has to come out of a real browser session: load the page, then read the Rust Items table and the per-item Rust Skins tables out of the rendered DOM.


6. Beyond this page

This reference covers the Rust-specific API only — the hooks and the item data. uMod's general plugin-authoring documentation is a separate set and is not mirrored here:

Page Covers
Plugins — Getting Started The plugin skeleton, lifecycle, and the universal hooks (Init, Loaded, Unload)
Plugin configuration Config, defaults, and the config file on disk
Data files Persisting plugin state
Permissions The Covalence permission and group API
Plugin installation Operator-side install
Getting Started Installing Oxide/uMod on a server

Mirror these too if the module work starts leaning on them.