Files
runicgateway.com/src/content/docs/docs/reference/event-catalog.mdx
wtclaude d89ce06bb8
All checks were successful
PR checks / checks (pull_request) Successful in 1m13s
docs(builder): phase 8 — modules, architecture and reference
Twenty pages completing the tree section 10 planned: Modules (8), Architecture
(5) and Reference (7). Four decisions, D38-D41, recorded in PLAN.md section 10.

D39 is the one that shaped the phase. Section 1 forbids re-specifying a
contract, and a Reference section is exactly where that rule is most tempting to
break, so the line is drawn at names: every environment variable, config key,
installer command, visibility rung and canonical document is listed with one
terse line saying what it is FOR, while shapes, semantics and every "why" stay
in the canonical document.

That is only safe because the names are checked. checkReference.mjs compares six
enumerations against the repositories that own them, over the Gitea API, as set
comparisons in BOTH directions -- and the second direction is the one that earns
its keep, because a reference page does not usually rot by describing something
that vanished, it rots by quietly not mentioning what was added since.

The check went green on its first run, which is the least trustworthy possible
outcome, so it was verified by breaking it: seven mutations, all caught. The one
worth keeping is the visibility ladder REORDERED with its membership unchanged
-- it is a security boundary, and a set comparison alone would have passed it.

D41 turns plannedSidebar from a checklist into a checked invariant, and finding
out why was the phase's first defect: it had already drifted, because phase 7
added the Content page under D37 and never updated the list. Nothing failed,
because nothing read it. checkSidebar.mjs now asserts the two trees agree on
groups, labels and order -- order because the order of Getting started IS the
installation path.

Two more things the writing found. PLAN.md's page count was wrong and had been
since section 10 was written ("roughly 38, 37 planned" for a tree of forty).
And module.json's `mounts` and the SPA's paths are different mechanisms that no
single document stated plainly -- module-uo declares admin: ["/shard",
"/uo-link"] while its screen lives at /admin/uo/link, because API routes are
deliberately NOT namespaced while SPA routes are. That is precisely the
distinction the installer got wrong in v0.1.0, and it now has a named home.

D40: the docs link to /architecture/'s drawn diagrams rather than importing
them. Those components carry marketing chrome and depend on diagram.css, which
Starlight does not load; the docs use text diagrams, which paste into an issue.

npm run verify green: 40 pages across 5 groups agree with plannedSidebar, 2390
internal links resolve, 123 repository links point at a branch, 19 facts, 59
quickstart checks, 22 reference enumerations, astro check 0 errors, 36 tests.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-24 12:17:31 -05:00

105 lines
4.2 KiB
Plaintext

---
title: Event catalog
description: What a game server can tell the website, how those events are grouped, and the five-rung ladder that decides who may see each one.
---
import { Aside } from '@astrojs/starlight/components';
import { visibilityLadder } from '../../../../data/reference.mjs';
The events a shard emits, and the mechanism that decides who may see them.
The exact wire shapes are in the protocol specification and are **not** restated here — a
copy of a wire format is a copy that will be wrong after the next bump. This page is the map
and the security model.
## What the shard can say
Nine groups, from the data catalog:
| Group | Covers |
|---|---|
| Session & identity | Logins, logouts, account linking |
| Character state | Vitals, stats, skills, position |
| Economy & commerce | Gold movement, vendor sales, supply totals |
| Housing / IDOC | Decay stages, ownership, coordinates |
| Combat, death, PvP | Kills, deaths, notable fights |
| Progression & activity | Skill gains, points, leaderboards |
| Cheat detection & staff audit | Fastwalk and friends; staff property edits |
| Lifecycle | `server.hello`, shutdown, crash |
| Known gaps | Things ServUO offers no clean hook for |
A representative line:
```json
{"t":1752,"kind":"cheat.fastwalk","serial":"0x1A2B","acct":"PerryAdimn"}
```
Note that one. **Cheat and audit events exist, and they are exactly what must never reach a
public page.**
## How events are handled
Not all alike, and the difference is deliberate:
- **State-changing kinds** update tables. The current state is what a page renders.
- **Notable kinds** additionally append to an events log, because a history is worth
keeping.
- **High-frequency kinds** only update state. Accumulating history for something that fires
constantly buys nothing and costs a table that grows forever.
## The visibility ladder
Five rungs, in order, least privileged first:
<ol>
{visibilityLadder.map((rung) => (<li key={rung}><code>{rung}</code></li>))}
</ol>
Every feature declares the rung it is visible from, and individual **fields** can require a
higher rung than the feature that carries them — a character's presence may be public while
its *location* is staff-only.
This list and its **order** are checked against the module that enforces it on every build.
Order matters as much as membership: reasoning about "staff and above" depends on the rungs
being in the right sequence.
<Aside type="caution" title="This is a security boundary, not a filter">
It is applied in **three** places — at routes, at SSE subscribe time, and at the navigation.
All three, because a surface filtered in only two of them leaks through the third.
Defaults **fail closed**: an unresolvable viewer is anonymous, not privileged, and a feature
with no configuration is not public by accident.
</Aside>
## Two SSE channels
Ingested events fan out to browsers over two streams:
- a **public** stream, carrying only allowlisted kinds;
- an **admin** stream, which additionally carries staff audit, cheat detection and login
attempts with IP addresses.
**The catalog is the module's; the boundary is core's.** A module declares which of its kinds
are public-safe, and core enforces the split — a sensitive kind cannot reach the public
channel.
A viewer's rung is resolved **once, when the stream opens, and frozen for its life**. A
long-lived connection must not silently gain privilege because the session changed
underneath it. Configuration changes, by contrast, *do* take effect live.
## Administering it
[The shard connection](/docs/administration/the-shard-connection/) covers the admin screens,
and the visibility ladder is administrator-configurable per feature and per field.
## Canonical documents
[`link/PLAN.md`](https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/link/PLAN.md)
§5 is the data catalog and §7 the wire protocol;
[`link/v4.md`](https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/link/v4.md)
is the current protocol;
[`SHARD_VISIBILITY.md`](https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/website/SHARD_VISIBILITY.md)
is the administrator's guide to the ladder, and
[`modules/uo/API.md`](https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/modules/uo/API.md)
§4 specifies it.