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
This commit is contained in:
193
modules/rust/README.md
Normal file
193
modules/rust/README.md
Normal file
@@ -0,0 +1,193 @@
|
||||
# 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`](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`](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`](../../website/MODULE_API.md) and
|
||||
> [`docs/link/`](../../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`](../../website/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`](../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`](../../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`](../../website/ARCHITECTURE.md),
|
||||
[`docs/link/PLAN.md`](../../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`](../../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`](../rust-dryrun.md) for the
|
||||
design, [`MODULE_API.md`](../../website/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:
|
||||
|
||||
```bash
|
||||
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](https://umod.org/documentation/plugins/getting-started) | The plugin skeleton, lifecycle, and the universal hooks (`Init`, `Loaded`, `Unload`) |
|
||||
| [Plugin configuration](https://umod.org/documentation/plugins/configuration) | `Config`, defaults, and the config file on disk |
|
||||
| [Data files](https://umod.org/documentation/plugins/data-files) | Persisting plugin state |
|
||||
| [Permissions](https://umod.org/documentation/plugins/permissions) | The Covalence permission and group API |
|
||||
| [Plugin installation](https://umod.org/documentation/plugins/installation) | Operator-side install |
|
||||
| [Getting Started](https://umod.org/documentation/getting-started) | Installing Oxide/uMod on a server |
|
||||
|
||||
Mirror these too if the module work starts leaning on them.
|
||||
Reference in New Issue
Block a user