# module-uo — the Ultima Online module Everything specific to Ultima Online that the website serves. Code: [RunicGateway/Module-uo](https://gitea.whitlocktech.com/RunicGateway/Module-uo). Module id `uo`, installed at `modules/uo/` on the website's modules volume. This page is **orientation**: what the module is, what it serves, what it owns, and what an operator has to know. It is not a second copy of the contract — [`../../website/MODULE_API.md`](../../website/MODULE_API.md) is normative for anything about how a module and core fit together, and the module's own repo is authoritative for its file layout. Where this page and either of those disagree, they win. Module documentation aggregates here rather than in module repos ([`MODULE_SYSTEM.md`](../../website/MODULE_SYSTEM.md) §2.10), so the feature docs that describe what these routes *mean* are the ones that already existed and did not move: | Doc | What it covers | |---|---| | [`SHARD_VISIBILITY.md`](../../website/SHARD_VISIBILITY.md) | Who sees which shard data — the admin-configurable audience framework | | [`SPAWN_ATLAS.md`](../../website/SPAWN_ATLAS.md) | The bestiary / spawn atlas, parsed from the shard's own ServUO tree | | [`MARKETPLACE.md`](../../website/MARKETPLACE.md) | The player-vendor index | | [`CLILOCS.md`](../../website/CLILOCS.md) | UO's id → name table | | [`UOFIDDLER.md`](../../website/UOFIDDLER.md) | Operator runbook for extracting the cliloc table and creature art | | [`../../link/PLAN.md`](../../link/PLAN.md), [`../../link/INTEGRATION.md`](../../link/INTEGRATION.md) | The wire protocol this module speaks to the sidecar | --- ## What it serves **72 URLs**, frozen in the module's own [`routes.manifest.json`](https://gitea.whitlocktech.com/RunicGateway/Module-uo/src/branch/main/routes.manifest.json) and documented in its [`swagger-fragment.json`](https://gitea.whitlocktech.com/RunicGateway/Module-uo/src/branch/main/swagger-fragment.json), which core merges into `/api/docs.json` while the module is running. | Mount | Tier | What | |---|---|---| | `/api/v1/public/shard` | public | Status, activity feed, economy, presence, houses/IDOC, champs, guilds, governors, points boards, the player-vendor market, the ruleset, and the SSE event stream | | `/api/v1/public/atlas` | public | The bestiary: creatures, regions, landmarks, champion altars, and what is loaded | | `/api/v1/admin/shard` | admin | Shard ops (kick/ban/broadcast/pages), the visibility config, atlas and cliloc imports, market admin, account links | | `/api/v1/admin/uo-link` | admin | The sidecar connection config, live status, and the town crier | | `/api/v1/player/shard` | player | A player's own linked accounts: rosters, character sheets, vendors, sales | | `/api/v1/admin/users/:id/shard/*` | admin | Six routes filling core's `admin.users.detail` **extension slot** — a module's routes hanging off a *core* resource, since core owns the user | Every URL is byte-identical to the one core served before the extraction. That is the whole point of moving the code and not the paths: the shipped Android app calls `POST /api/v1/admin/shard/kick`, the Discord bot reads `/api/v1/public/shard/*`, and neither knows a module answers now. **In the SPA**: twelve public pages under `/uo/*`, the admin views under `/admin/uo/*`, the player views under `/player/uo/*`, three extension-slot fills, and the nav rows for all of them, interleaved into core's nav so an operator can reorder, relabel or hide them like any other row. ## What it owns - **27 database tables** — 26 `shard_*` plus `uo_link_config`. Created by an idempotent `schema.sql` fragment core replays on every boot, after its own schema. The `shard_`/`uo_link_` prefixes are **grandfathered** ([`MODULE_API.md`](../../website/MODULE_API.md) §6.5): the rule for a new module is `_`, and these predate it. - **Seven push notification streams** and the announce leg `towncrier`, likewise grandfathered — they are stored in `notification_subs` and `announce_job_legs.leg` and read by the shipped Android app, so renaming them would be a data migration plus a client break. - **Two settings rows**: `game_account_signup` and the shard's protocol pin. - **The public-safety filter.** Which shard event kinds may reach the *public* SSE stream is decided here, not in core — the kinds, the streams and the filter are one file that moves together. ## For an operator **Installing.** A release is `module-uo-.tar.gz` plus a manifest carrying its `sha256`. Unpack it as `modules/uo/` on the website's modules volume (or use the admin Modules screen when phase 4 lands) and restart. **You never build anything** — the client chunk is prebuilt and the one runtime dependency ships inside the tarball. **Connecting it to a shard.** The module needs the [uo-link sidecar](https://gitea.whitlocktech.com/RunicGateway/link) running next to the ServUO shard. Deploy that with the [installer](https://gitea.whitlocktech.com/RunicGateway/installer) — [`../../installer/INSTALL.md`](../../installer/INSTALL.md) is the operator guide — and paste the four values it prints into **Admin → Shard**. The token is encrypted at rest and write-only in the API. **Nothing requires the shard to exist.** With no sidecar configured the site renders normally and shows the shard offline. That is the same bargain the module system makes one level up: a module that fails to load never takes the site down. **Environment variables** — four, all optional, all read by the module and documented in its README: `UOLINK_BASE_URL`, `UOLINK_WS_URL`, `UOLINK_PROTOCOL`, `TOWNCRIER_DURATION_SEC`. They live in the Compose `.env`, because that is what reaches the container. ## Compatibility `module.json` declares a `coreApi` semver range, checked at boot against core's `MODULE_API_VERSION`. A mismatch fails **loudly** — the module is marked `startup_failed` and the site comes up without it. This is a separate number from `PROTOCOL_VERSION`, which versions the shard wire and says nothing about a website module. The module's CI clones core at a **pinned** ref ([`MODULE_API.md`](../../website/MODULE_API.md) §5.3) to generate its frozen manifest. Bumping that pin is a deliberate commit that says which core the module was last proved against — not a tracking reference that turns core's unrelated changes into red Xes here.