docs(modules): mirror the rest of the Oxide ecosystem, and add a machine-readable set

Completes the uMod mirror beyond the Rust hook table, and adds agent/ — the
same facts in TSV/JSONL at ~46% of the tokens.

New prose:
- OXIDE_API.md: the 19 developer pages under umod.org/documentation/api/ —
  plugin structure, hooks, commands, IPlayer, permissions, config, data files,
  database, localization, timers, web requests, dependencies, integration,
  preprocessor directives, security, style guide, CI, review. This is the
  framework our plugin is a guest in, where HOOKS.md is what the game says.
- OPERATING.md: the 6 operator pages — installing Oxide on a server, then
  installing, configuring and permissioning plugins.

New machine-readable set (agent/):
- hooks.tsv    477 rows, ~31% of HOOKS.md
- items.tsv    678 rows, ~80% of DEFINITIONS.md's item table
- skins.tsv    104 rows covering 2,590 skins, ~77%
- api.jsonl    150 code examples, ~47% of the two prose docs
Generated in the same pass as the markdown, so the two cannot drift.

HOOKS.md gains the universal-hook split: 34 of the 477 are uMod's own
Covalence hooks, raised identically on every game uMod supports. Verified
against /documentation/games/universal in the same capture - all 34 are in
the Rust set and the Rust page adds none of its own, so the overlap is exact.
The distinction is architectural: a universal hook is the portable part of
the surface.

Two things worth recording from building it:
- The first skins.tsv was one row per skin and came out 11% LARGER than the
  markdown it replaces. Grouping it one row per item is what made it a
  saving. The token win is real for prose (3.2x on hooks) and small for
  tables that were already dense - agent/README.md says so plainly rather
  than claiming a flat number.
- Signature extraction by brace depth silently captured body lines (a nested
  '}' in OnUserConnected's example ended the block early). It now matches on
  the hook's own name; all 477 rows verified to carry a real signature.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4
This commit is contained in:
2026-09-15 10:01:11 -05:00
parent 8d31e497d2
commit 4e2c2dc439
10 changed files with 4616 additions and 577 deletions

View File

@@ -1,15 +1,20 @@
# Rust — the Oxide/uMod API reference
# Rust — the Oxide/uMod ecosystem 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.
Reference material for the **upcoming `module-rust`**: a mirror of the uMod/Oxide documentation —
the Rust game API *and* the game-independent plugin framework around it — captured here so the
module can be designed and built against it without a round trip to umod.org on every question.
Two documents, both **scraped verbatim from uMod on 2026-09-15**:
Everything below was **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 |
## The mirror
| Doc | What it holds |
|---|---|
| [`HOOKS.md`](HOOKS.md) | **What Rust will tell you.** All **477 hooks** in 20 categories — description, return contract, tags, every C# overload. The 34 *universal* hooks are marked. |
| [`OXIDE_API.md`](OXIDE_API.md) | **How a plugin is built.** The 19 developer pages — plugin structure, hooks, commands, `IPlayer`, permissions, config, data files, database, localization, timers, web requests, dependencies, CI, review. |
| [`DEFINITIONS.md`](DEFINITIONS.md) | **What things are called.** 678 items (short name, id, display name) and 2,590 workshop skin ids across 104 items. |
| [`OPERATING.md`](OPERATING.md) | **How it gets run.** The 6 operator pages — installing Oxide on a server, then installing, configuring and permissioning plugins. |
| [`agent/`](agent/README.md) | The same facts in **machine shape** — TSV and JSONL, ~46% of the tokens. Generated in the same pass, so it cannot drift. |
> **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
@@ -33,24 +38,25 @@ The dry run's central structural fact is the thing this reference serves:
> 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.
Two consequences, and they are the two halves of this directory:
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
1. **We can only emit what Oxide already hands us.** [`HOOKS.md`](HOOKS.md) is the hard ceiling on
what a Rust module can ever know about a live server. If a fact is not reachable from one of
those 477 hooks (or from a game type one of them hands you), the bridge cannot report it. That
makes it the input to the Rust sidecar's event catalogue — 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. **We are a guest in someone else's plugin framework.** Our plugin is compiled, loaded, permissioned
and configured by Oxide, on Oxide's terms. [`OXIDE_API.md`](OXIDE_API.md) is that rulebook, and
[`OPERATING.md`](OPERATING.md) is what the server owner has to do — which is the surface our
deployment story has to sit on, the way
[`installer/INSTALL.md`](../../installer/INSTALL.md) sits on top of ServUO.
---
## 2. How an Oxide hook actually binds
The one thing to understand before reading `HOOKS.md`, because it is not in the table itself:
The one thing to understand before reading [`HOOKS.md`](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
@@ -64,14 +70,14 @@ visible rather than mysterious.
### The return contract
`HOOKS.md` reproduces uMod's return line for every hook and it is the load-bearing part:
[`HOOKS.md`](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. |
| uMod's wording | Code in [`agent/hooks.tsv`](agent/hooks.tsv) | What it means |
|---|---|---|
| *No return behavior* | `none` | A **notification**. Declare `void`. Nothing you return is read. |
| *Returning a non-null value overrides default behavior* | `nonnull` | 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* | `bool` | A **veto**. Declare `object`; `null` abstains — returning `false` is *not* the same as abstaining. |
| *Returning a string will kick…* | `data` | The value is **consumed as data**, not merely as a veto. |
Two cautions that apply across the whole table:
@@ -79,10 +85,11 @@ Two cautions that apply across the whole table:
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.
and must return `null` from every veto hook it listens on — see §5.
Three hooks (`OnEntityTakeDamage`, `OnExcavatorSuppliesRequested`, `OnPhoneCallStarted`) state no
return behaviour upstream at all; `HOOKS.md` marks those *(not stated upstream)* rather than guessing.
return behaviour upstream at all; both the markdown and the TSV mark those *(not stated upstream)* /
`?` rather than guessing.
---
@@ -108,20 +115,43 @@ return behaviour upstream at all; `HOOKS.md` marks those *(not stated upstream)*
| 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.
**Player and Entity are 58% of the surface between them.** [`HOOKS.md`](HOOKS.md) opens with a full
alphabetical index (name → category → universal → return contract), which is the fastest way in when
you already know the hook's name.
### Universal vs. Rust-specific
**34 of the 477 are not Rust's.** They are uMod's own *universal* (Covalence) hooks — `Init`,
`Loaded`, `Unload`, `OnUserConnected`, the whole `OnGroup*`/`OnUserPermission*` family — which uMod
raises identically on every game it supports. They appear on the Rust page because they fire on Rust
too. Verified against <https://umod.org/documentation/games/universal> in the same capture: all 34
are in the Rust set, and the Rust page adds none of its own, so the overlap is exact.
The distinction is architectural, not trivia: **a universal hook is the portable part of the
surface.** Code written against one would carry to any other uMod-supported game; code written
against a Rust-specific hook would not. They are marked in `HOOKS.md` and flagged `1` in
[`agent/hooks.tsv`](agent/hooks.tsv).
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
- Twelve 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
## 4. Reading the machine copy
[`agent/`](agent/README.md) holds the same facts as TSV and JSONL, at ~46% of the tokens — a real
3.2× saving on the hooks, 2.1× on the API prose, and only ~1.25× on the item and skin tables, which
were already dense. Reach for it when you want to *find* something; read the markdown when you want
the reasoning, which is deliberately not in there. [`agent/README.md`](agent/README.md) has the
column definitions and an honest account of where the saving is and is not.
---
## 5. 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
@@ -132,6 +162,8 @@ them. Restated against Oxide:
- **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`.
Oxide's own `timer` and `NextFrame` helpers ([`OXIDE_API.md` § Timers](OXIDE_API.md#timers)) are
the tools for deferring work off a hot hook.
- **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
@@ -141,53 +173,66 @@ them. Restated against Oxide:
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.
design, [`MODULE_API.md`](../../website/MODULE_API.md) for the contract it must satisfy,
[`OXIDE_API.md`](OXIDE_API.md) for the framework we are a guest in, then [`HOOKS.md`](HOOKS.md) for
what the game can actually tell us.
---
## 5. Provenance and refreshing
## 6. 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 |
| Captured | **2026-09-15** |
| Hooks | <https://umod.org/documentation/games/rust> · <https://umod.org/documentation/games/universal> |
| Definitions | <https://umod.org/documentation/games/rust/definitions> |
| Developer API | <https://umod.org/documentation/api/> — 19 pages |
| Operator docs | <https://umod.org/documentation/> — 6 pages |
| Content | 477 hooks (34 universal) · 678 items · 2,590 skins · 25 prose pages · 150 code examples |
**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 module, and update the date in every file — they all carry it.
The hooks page renders client-side from a JSON endpoint, and *that endpoint is not behind
Cloudflare* even though the HTML page is:
### How to capture it
Three different routes, because uMod serves these three ways. Cloudflare gates the HTML pages but
**not** the JSON endpoints:
**1. Hooks — a plain `curl`, no browser needed.** The page renders client-side from an endpoint that
is not gated. One request returns everything (`per_page` is 9999, so there is no paging):
```bash
curl -A "Mozilla/5.0" -H "Accept: application/json" \
"https://umod.org/documentation/hooks/rust.json" -o all_rust_hooks.json
"https://umod.org/documentation/hooks/rust.json" -o all_rust_hooks.json
curl -A "Mozilla/5.0" -H "Accept: application/json" \
"https://umod.org/documentation/hooks/universal.json" -o all_universal_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.
Each record carries `name`, `subcategory`, `tags`, `description` (HTML `<ul>`) and `example` (a
markdown-fenced C# block). 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
**2. Definitions — a real browser.** No JSON endpoint, and the HTML *is* gated. Load
`/documentation/games/rust/definitions`, then read the `Rust Items` table and the per-item
`Rust Skins` tables out of the rendered DOM.
---
**3. The prose pages — a real browser, throttled.** Also gated. Fetch each page same-origin from an
already-loaded uMod tab and take `document.querySelector('.documentation')`, dropping the
`.table-of-contents` child. **Pause ~1s between pages** — uMod returns `429 Too Many Requests` on a
tight loop, and a 429 is easy to mistake for a real page because it still returns 200-shaped HTML
with a title.
## 6. Beyond this page
The pages mirrored here, in the order they appear:
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:
- `api/` — overview, getting-started, hooks, commands, player, permissions, configuration,
data-files, database, localization, timers, web-requests, dependencies, integration,
preprocessor-directives, security, style-guide, continuous-integration, approval-guide
- operator — getting-started, plugins/getting-started, plugins/installation, plugins/configuration,
plugins/data-files, plugins/permissions
| 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 |
### Deliberately not mirrored
Mirror these too if the module work starts leaning on them.
- **The other games** uMod supports (Hurtworld, 7 Days to Die, Reign of Kings, The Forest). Not our
ecosystem. `api/preprocessor-directives` lists their compile symbols if that ever changes.
- **Community pages** — contributing, community guidelines, reporting issues, getting help. Process,
not API.
- **The `/documentation/umod/*` paths**, which are aliases of the same pages.