// ── module-uo's OpenAPI fragment: the shared half ────────────────────────── // // The tags and component schemas every `#swagger.*` annotation under // `server/router/**` refers to. `scripts/swaggerFragment.js` feeds this to // swagger-autogen; the per-endpoint detail lives beside each route, exactly as // it does in core. // // These 31 schemas were core's until phase 3 — they sat in // `website/server/swagger/swagger.js` describing routes core no longer serves, // which is what an extraction leaves behind if nobody looks (the inert-leaf // class slice 4 found in `api/client.js`). They moved with the routes. // // **Two rules about names, and both are the merged document's, not this file's** // (docs/website/MODULE_API.md §6.1a): // // • **What this module DEFINES is namespaced `Uo…`.** Core merges started // modules' fragments into one `/api/docs.json`, and core wins every key // collision — so an un-namespaced `ShardStatus` from a second game's module // would silently lose to, or clobber, this one. The prefix is what makes two // modules able to describe the same idea. // • **What core defines is referenced by CORE's name.** The annotations point // at `#/components/schemas/Error` and `ValidationError` and this file does // not redefine them: they resolve in the merged spec, where core's // definitions are. Shipping our own copy would be a collision core drops, // which is the correct outcome arrived at the expensive way. // // Tag NAMES are core's originals (`Public · Shard`, not `Uo · Shard`). A tag is // how the docs UI groups operations, and core stopped declaring these four in // the same slice this file started — nothing collides, and renaming them would // churn every reader's bookmark for no gain. module.exports = { tags: [ { name: 'Public · Shard', description: 'Live shard data ingested from the uo-link sidecar (status, feed, economy, IDOC, characters)' }, { name: 'Public · Atlas', description: 'Spawn atlas / bestiary — static shard content parsed from the shard\'s own ServUO tree, independent of the sidecar' }, { name: 'Player · Shard', description: 'Link an in-game account and read its roster / vendors (uo-link)' }, { name: 'Admin · Shard', description: 'uo-link sidecar connection config, live status and town crier (admin only)' }, ], components: { schemas: { // ── uo-link shard data ────────────────────────────────────────────── UoShardStatus: { type: 'object', description: 'Public shard status (GET /public/shard/status).', properties: { enabled: { type: 'boolean', example: true }, status: { type: 'string', example: 'connected', description: 'connected | reconnecting | disconnected | error' }, pluginConnected: { type: 'boolean', description: 'Is the shard link up right now?', example: true }, lastEventAt: { type: 'string', format: 'date-time', nullable: true }, onlineCount: { type: 'integer', example: 12 }, economy: { $ref: '#/components/schemas/UoShardEconomyPoint' }, }, }, UoShardEvent: { type: 'object', description: 'A logged shard event.', properties: { id: { type: 'integer', example: 4821 }, kind: { type: 'string', example: 'vendor.sale' }, t: { type: 'integer', description: 'Event time, epoch ms.', example: 1783720195626 }, bootId: { type: 'string', nullable: true, example: 'boot-abc123' }, payload: { type: 'object', additionalProperties: true, description: 'The full event object.' }, createdAt: { type: 'string', format: 'date-time' }, }, }, UoShardEconomyPoint: { type: 'object', nullable: true, description: 'One gold-supply sample.', properties: { accounts: { type: 'integer', nullable: true, example: 240 }, gold: { type: 'integer', nullable: true, example: 1028983421 }, t: { type: 'integer', description: 'Sample time, epoch ms.', example: 1783720000000 }, }, }, UoShardOnlinePlayer: { type: 'object', description: 'A LINKED player online now (only accounts linked to a website user are listed).', properties: { serial: { type: 'string', example: '0x24C' }, name: { type: 'string', example: 'Darrow' }, map: { type: 'string', nullable: true, example: 'Trammel' }, x: { type: 'integer', nullable: true, example: 1402 }, y: { type: 'integer', nullable: true, example: 1604 }, z: { type: 'integer', nullable: true, example: 0 }, }, }, UoShardVendorSale: { type: 'object', description: 'A player-vendor sale (visible only to the linked owner).', properties: { t: { type: 'integer', description: 'Sale time, epoch ms.', example: 1783720195626 }, itemType: { type: 'string', example: 'Longsword' }, amount: { type: 'integer', example: 1 }, price: { type: 'integer', example: 100 }, commission: { type: 'integer', nullable: true, example: 5 }, ownerAcct: { type: 'string', example: 'whitlocktech' }, }, }, UoShardHouse: { type: 'object', description: 'A house at its current decay stage.', properties: { serial: { type: 'string', example: '0x4004705F' }, stage: { type: 'string', example: 'IDOC' }, map: { type: 'string', nullable: true, example: 'Trammel' }, x: { type: 'integer', nullable: true }, y: { type: 'integer', nullable: true }, z: { type: 'integer', nullable: true }, region: { type: 'string', nullable: true }, name: { type: 'string', nullable: true, example: 'An Unnamed House' }, ownerSerial: { type: 'string', nullable: true }, ownerAcct: { type: 'string', nullable: true }, builtOn: { type: 'string', format: 'date-time', nullable: true }, lastRefreshed: { type: 'string', format: 'date-time', nullable: true }, isIdoc: { type: 'boolean', example: true }, updatedAt: { type: 'string', format: 'date-time' }, }, }, UoShardPointsBoard: { type: 'object', description: "One point system's leaderboard (Protocol 3.0 points.board). The shard carries ~25 separate point currencies; each publishes its own board. The display name may arrive as a literal string, a cliloc id, or both — resolve clilocs client-side.", properties: { system: { type: 'string', example: 'QueensLoyalty', description: "The shard's PointsType name; the board's stable key." }, nameString: { type: 'string', nullable: true, example: "Queen's Loyalty" }, nameNumber: { type: 'integer', nullable: true, example: 1114938, description: 'Cliloc id, 0 when the name is a literal.' }, maxPoints: { type: 'integer', nullable: true, example: 30000 }, players: { type: 'integer', nullable: true, example: 842, description: 'Players actually holding points in this system.' }, showOnGump: { type: 'boolean', example: true, description: "The shard's own 'is this player-facing?' flag." }, top: { type: 'array', description: 'The ranked players, best first. Capped by the shard (10 by default). Empty when nobody has scored yet.', items: { type: 'object', properties: { rank: { type: 'integer', example: 1 }, serial: { type: 'string', example: '0x1A2B' }, name: { type: 'string', example: 'Darrow', description: 'Omitted when the leaderboards `name` field is gated above the caller.' }, points: { type: 'integer', example: 29500 }, }, }, }, t: { type: 'integer', nullable: true, description: 'Frame time, epoch ms.' }, updatedAt: { type: 'string', format: 'date-time' }, }, }, UoShardMarketLocation: { type: 'object', nullable: true, description: "Where a vendor is standing. ONE nested object rather than flat map/x/y/region because it is one admin-configurable field (`market.location`) — the whole object is omitted when that field is gated above the caller.", properties: { map: { type: 'string', nullable: true, example: 'Trammel' }, x: { type: 'integer', nullable: true, example: 1421 }, y: { type: 'integer', nullable: true, example: 1699 }, z: { type: 'integer', nullable: true, example: 0 }, region: { type: 'string', nullable: true, example: 'Britain' }, house: { type: 'string', nullable: true, example: "Darrow's Villa", description: "The house SIGN's name, not the house type. Null for a vendor standing outside one." }, }, }, UoShardMarketListing: { type: 'object', description: 'One priced listing on a player vendor, carrying enough of its shop to be actionable without a second request.', properties: { serial: { type: 'string', example: '0x40012ABC' }, itemId: { type: 'integer', example: 3922, description: 'ItemID (the art/graphic id).' }, hue: { type: 'integer', example: 0 }, amount: { type: 'integer', example: 1 }, price: { type: 'integer', example: 25000 }, name: { type: 'string', nullable: true, description: "The item's own literal name, set by a player. Null for most items." }, cliloc: { type: 'integer', nullable: true, example: 1023721, description: "The item's LabelNumber." }, displayName: { type: 'string', nullable: true, example: 'quarter staff', description: 'Resolved server-side from `name` (preferred, being player-set and more specific) else `cliloc`. Null on a shard with no cliloc table configured — render the item id.', }, child: { type: 'boolean', example: false, description: 'Priced by an enclosing container rather than itself, exactly as the in-game Vendor Search reports it.' }, vendor: { type: 'object', properties: { serial: { type: 'string', example: '0x40001234' }, shopName: { type: 'string', nullable: true, example: "Darrow's Bargains" }, ownerSerial: { type: 'string', nullable: true, example: '0x1A2B', description: 'Omitted when the market `ownerSerial` field is gated above the caller.' }, ownerName: { type: 'string', nullable: true, example: 'Darrow', description: 'Omitted when the market `ownerName` field is gated above the caller.' }, location: { $ref: '#/components/schemas/UoShardMarketLocation' }, updatedAt: { type: 'string', format: 'date-time', description: 'When the shard last published this shop.' }, }, }, }, }, UoShardMarketPage: { type: 'object', description: 'A page of marketplace listings plus the unpaginated total and the staleness stamp.', properties: { listings: { type: 'array', items: { $ref: '#/components/schemas/UoShardMarketListing' } }, total: { type: 'integer', example: 1284, description: 'Matching listings, ignoring paging.' }, limit: { type: 'integer', example: 50 }, offset: { type: 'integer', example: 0 }, vendors: { type: 'integer', example: 137, description: 'Vendors in the whole index.' }, staleAt: { type: 'string', format: 'date-time', nullable: true, description: 'The OLDEST vendor row. The shard sweeps vendors round-robin, so the index can be a full cycle behind and a client must say so rather than implying live prices.', }, }, }, UoShardMarketVendor: { type: 'object', description: 'One player vendor and its listings.', properties: { serial: { type: 'string', example: '0x40001234' }, shopName: { type: 'string', nullable: true, example: "Darrow's Bargains" }, ownerSerial: { type: 'string', nullable: true }, ownerName: { type: 'string', nullable: true, example: 'Darrow' }, location: { $ref: '#/components/schemas/UoShardMarketLocation' }, count: { type: 'integer', example: 250, description: 'Listings the shard published for this shop.' }, total: { type: 'integer', example: 3104, description: 'Listings the shop actually holds.' }, truncated: { type: 'boolean', example: true, description: '`total` exceeds `count` — the shop holds more than the shard publishes per frame.' }, updatedAt: { type: 'string', format: 'date-time' }, items: { type: 'array', items: { $ref: '#/components/schemas/UoShardMarketListing' } }, }, }, UoShardMarketMeta: { type: 'object', description: 'Marketplace size, staleness and the filter options a client needs to build its UI.', properties: { vendors: { type: 'integer', example: 137 }, items: { type: 'integer', example: 18422 }, staleAt: { type: 'string', format: 'date-time', nullable: true }, freshAt: { type: 'string', format: 'date-time', nullable: true }, maps: { type: 'array', items: { type: 'string' }, example: ['Felucca', 'Trammel'], description: "Facets that actually hold vendors. From the shard's own data — never a hardcoded list." }, regions: { type: 'array', items: { type: 'string' }, example: ['Britain', 'Luna'] }, }, }, UoShardFeatures: { type: 'object', description: "The shard features the caller may reach, plus the audience rung they resolved to. Drives client nav so it never renders a link that would 403.", properties: { level: { type: 'string', enum: ['anonymous', 'logged_in', 'player', 'staff', 'admin'], example: 'anonymous', }, features: { type: 'array', items: { type: 'string' }, example: ['status', 'activity', 'champs', 'guilds', 'governors', 'houses', 'presence'], }, }, }, UoShardFeatureVisibility: { type: 'object', description: 'Visibility settings for one shard feature.', properties: { enabled: { type: 'boolean', example: true }, audience: { type: 'string', enum: ['anonymous', 'logged_in', 'player', 'staff', 'admin'], description: 'Minimum rung that may reach this feature. Each rung implies the ones below it.', example: 'anonymous', }, stream: { type: 'boolean', description: "Whether this feature's event kinds fan out over SSE at all.", example: true, }, fieldRules: { type: 'object', additionalProperties: { type: 'string' }, description: 'Per-field rung overrides for the sensitive fields this feature exposes. acct / webId are admin-only always and are rejected here.', example: { location: 'staff' }, }, }, }, UoShardVisibilityConfig: { type: 'object', properties: { ladder: { type: 'array', items: { type: 'string' }, example: ['anonymous', 'logged_in', 'player', 'staff', 'admin'], }, lockedFields: { type: 'array', items: { type: 'string' }, example: ['acct', 'webId'] }, defaults: { type: 'object', additionalProperties: { $ref: '#/components/schemas/UoShardFeatureVisibility' }, }, features: { type: 'object', additionalProperties: { $ref: '#/components/schemas/UoShardFeatureVisibility' }, }, }, }, UoShardVisibilityUpdate: { type: 'object', required: ['features'], properties: { features: { type: 'object', additionalProperties: { $ref: '#/components/schemas/UoShardFeatureVisibility' }, example: { market: { enabled: true, audience: 'player', stream: false, fieldRules: { ownerName: 'player' } } }, }, }, }, // ── Spawn atlas (Protocol 3.0 Part C) ──────────────────────────────── // Static shard content, parsed from the shard's own ServUO tree. Nothing // here comes from the sidecar, so it stays populated while the shard is // down. Facet names are whatever the shard's files declare — the examples // below are stock ServUO, not a fixed list. UoAtlasCreature: { type: 'object', description: 'A creature in the bestiary. `places`/`points`/`alsoHere` are present only on the single-creature route.', properties: { slug: { type: 'string', example: 'lizardman' }, name: { type: 'string', example: 'Lizardman' }, total: { type: 'integer', description: 'How many can be alive at once, summed across every spawner.', example: 214 }, points: { type: 'integer', description: 'How many spawners mention this creature.', example: 62 }, facets: { type: 'object', additionalProperties: { type: 'integer' }, description: "This creature's share per facet.", example: { Felucca: 96, Trammel: 88, Tokuno: 30 }, }, art: { type: 'string', nullable: true, description: 'Operator-supplied art under uploads/atlas/. NULL on a fresh import — the repo ships no creature art.' }, places: { type: 'array', description: 'Where it spawns, aggregated by resolved place. The answer the atlas exists to give.', items: { type: 'object', properties: { facet: { type: 'string', example: 'Trammel' }, label: { type: 'string', description: 'Resolved region, else nearest landmark group, else "Wilderness".', example: 'Shrines' }, spawners: { type: 'integer', example: 7 }, maxAlive: { type: 'integer', example: 21 }, }, }, }, spawners: { type: 'array', description: 'The individual spawners. Named separately from `points` (the count) so one key never means two things.', items: { $ref: '#/components/schemas/UoAtlasSpawner' }, }, spawnersTruncated: { type: 'boolean', description: 'True when the spawner list was cut at the requested bound.', example: false }, alsoHere: { type: 'array', description: 'Creatures sharing a spawner with this one.', items: { type: 'object', properties: { slug: { type: 'string', example: 'lizardman-warrior' }, name: { type: 'string', example: 'Lizardman Warrior' }, shared: { type: 'integer', example: 12 }, }, }, }, }, }, UoAtlasSpawner: { type: 'object', description: 'One ServUO spawner, with the place its coordinates resolved to.', properties: { id: { type: 'integer' }, facet: { type: 'string', example: 'Felucca' }, name: { type: 'string', nullable: true, description: "The spawner's own name in the ServUO file." }, x: { type: 'integer', example: 5411 }, y: { type: 'integer', example: 1234 }, width: { type: 'integer' }, height: { type: 'integer' }, range: { type: 'integer', description: 'Spawn radius.' }, maxCount: { type: 'integer', description: 'How many of THIS creature this spawner keeps alive.', example: 3 }, minDelay: { type: 'integer', description: 'Respawn window, in SECONDS. Normalised at parse time — the source stores minutes or seconds per record, decided by its own DelayInSec flag.', example: 300 }, maxDelay: { type: 'integer', example: 600 }, todStart: { type: 'integer', description: 'Meaningless unless todMode is non-zero.' }, todEnd: { type: 'integer' }, todMode: { type: 'integer' }, region: { type: 'string', nullable: true, example: 'Despise' }, landmark: { type: 'string', nullable: true, example: 'Covetous' }, label: { type: 'string', description: 'Region, else landmark group, else "Wilderness".', example: 'Despise' }, }, }, UoAtlasCreaturePage: { type: 'object', properties: { total: { type: 'integer', description: 'Matching creatures before pagination.', example: 800 }, limit: { type: 'integer', example: 50 }, offset: { type: 'integer', example: 0 }, creatures: { type: 'array', items: { $ref: '#/components/schemas/UoAtlasCreature' } }, }, }, UoAtlasRegion: { type: 'object', description: 'A named region, flattened out of the shard\'s nested Regions.xml.', properties: { facet: { type: 'string', example: 'Felucca' }, name: { type: 'string', example: 'Despise' }, type: { type: 'string', nullable: true, description: 'ServUO region class.', example: 'DungeonRegion' }, priority: { type: 'integer', example: 50 }, parent: { type: 'string', nullable: true, example: 'Britain' }, rects: { type: 'array', description: 'The rectangles that placed each spawn point.', items: { type: 'object', additionalProperties: true }, }, }, }, UoAtlasLandmark: { type: 'object', properties: { facet: { type: 'string', example: 'Trammel' }, name: { type: 'string', example: 'Level 1' }, group: { type: 'string', nullable: true, description: 'Innermost enclosing parent — the label worth showing.', example: 'Covetous' }, x: { type: 'integer', example: 5411 }, y: { type: 'integer', example: 1234 }, z: { type: 'integer', example: 0 }, }, }, UoAtlasChampion: { type: 'object', description: 'A CONFIGURED champion altar. Not the live board — see GET /public/shard/champs for that.', properties: { slug: { type: 'string', example: 'felucca-deceit' }, name: { type: 'string', example: 'Deceit' }, group: { type: 'string', nullable: true, description: 'Spawn group; one altar active per group.', example: 'Dungeons' }, type: { type: 'string', nullable: true, description: 'NULL when the champion is drawn at activation.', example: 'UnholyTerror' }, randomType: { type: 'boolean', example: false }, facet: { type: 'string', example: 'Felucca' }, x: { type: 'integer' }, y: { type: 'integer' }, z: { type: 'integer' }, radius: { type: 'integer', example: 60 }, label: { type: 'string', nullable: true, example: 'Deceit' }, }, }, UoAtlasMeta: { type: 'object', description: 'What atlas is loaded. Game-world facts only: the ServUO path, source hashes and any pending refresh are operator detail and live on the admin status route.', properties: { importedAt: { type: 'string', format: 'date-time', nullable: true }, generatedAt: { type: 'string', format: 'date-time', nullable: true }, counts: { type: 'object', nullable: true, additionalProperties: true, example: { facets: 6, points: 6455, creatures: 800, regions: 387, landmarks: 558, champions: 25, unresolvedPoints: 1086 }, }, facets: { type: 'array', items: { type: 'string' }, example: ['Felucca', 'Ilshenar', 'Malas', 'TerMur', 'Tokuno', 'Trammel'] }, }, }, UoAtlasStatus: { type: 'object', description: 'Admin view of atlas state: which source the tree comes from, whether it is readable, whether it has drifted from what is loaded, and any refresh staged for review.', properties: { configured: { type: 'boolean', example: true }, source: { type: 'string', enum: ['bridge', 'fs'], description: '`bridge`: the shard serves its own configuration files over uo-link (protocol 8 phase 7, the normal case once a shard is linked). `fs`: a ServUO tree the website can read directly — development and same-host installs, and the only source where boot re-imports by itself.', example: 'bridge', }, path: { type: 'string', description: 'The local tree path, or `the shard bridge` when that is the source.', example: 'the shard bridge' }, treeReadable: { type: 'boolean', example: true }, drift: { type: 'boolean', nullable: true, description: 'True when the source file hashes differ from the loaded atlas. NULL when the source could not be read. On the bridge this is answered from the shard\'s file MANIFEST — hashes only, no file bytes.', example: false }, facets: { type: 'array', items: { type: 'string' } }, importedAt: { type: 'string', format: 'date-time', nullable: true }, counts: { type: 'object', nullable: true, additionalProperties: true }, pending: { type: 'object', nullable: true, description: 'A refresh that was parsed but NOT applied because it would remove a facet. `status` is pending or rejected.', additionalProperties: true, }, }, }, UoAtlasRefreshResult: { type: 'object', description: 'Outcome of a refresh. Reported rather than thrown, so an unreadable tree is an answer and not a 500.', properties: { status: { type: 'string', enum: ['skipped', 'unavailable', 'unchanged', 'imported', 'needsReview', 'failed', 'rejected', 'none'], example: 'imported', }, reason: { type: 'string', nullable: true }, source: { type: 'string', enum: ['bridge', 'fs'], nullable: true, description: 'Which end this attempt read from. Absent only on `skipped`, where there was no source at all.', example: 'bridge', }, path: { type: 'string', nullable: true, description: 'The local tree path, or `the shard bridge`.' }, code: { type: 'string', nullable: true, description: 'On `unavailable`: NO_PATH, NOT_FOUND, NO_REGIONS or NO_SPAWNS from a local tree; DISABLED, SOURCE_CHANGED, INCOMPLETE, MALFORMED, BUSY, SHARD_DOWN or TOO_LARGE from the bridge.' }, counts: { type: 'object', nullable: true, additionalProperties: true }, addedFacets: { type: 'array', items: { type: 'string' } }, removedFacets: { type: 'array', items: { type: 'string' } }, }, }, UoClilocStatus: { type: 'object', description: 'Admin view of cliloc state: which source the base table comes from, whether it can be read, how many entries are loaded, and whether anything has drifted from them. Nothing configured at all is a supported state — item names then render as ids.', properties: { source: { type: 'string', enum: ['bridge', 'file'], description: '`bridge`: the shard reads its own UO client (protocol 8, the normal case). `file`: a converted file on disk — the pre-protocol-8 pipeline, deprecated, kept for installs with no shard link.', example: 'bridge', }, configured: { type: 'boolean', example: true }, path: { type: 'string', description: 'On the bridge: where `custom/` overlays are read from. On a file source: the base path too.', example: '/srv/uo-client' }, file: { type: 'string', nullable: true, description: 'The base file in use — the shard’s own `cliloc.enu` on the bridge, the resolved local file otherwise.', example: 'cliloc.enu' }, fileReadable: { type: 'boolean', example: true }, problem: { type: 'string', nullable: true, description: 'Why the base cannot be used, when it cannot: a shard that is down or has assets switched off, or (on a file source) a missing or still-compressed file.', example: null }, code: { type: 'string', nullable: true, description: 'Machine-readable cause of `problem`.', enum: ['NO_PATH', 'NOT_FOUND', 'NO_FILE', 'UNREADABLE', 'COMPRESSED', 'DISABLED', 'NO_SOURCE', 'SHARD_DOWN', 'PROTOCOL', 'BUSY', 'UNAVAILABLE'] }, shard: { type: 'object', nullable: true, description: 'Present on the bridge: the shard’s own cliloc file as it is right now. `hashing: true` with a null `sha256` means the hash has not been computed yet — “ask again”, not “changed”.', properties: { size: { type: 'integer', example: 4989921 }, mtime: { type: 'integer', description: 'Unix milliseconds.', example: 1757462400000 }, sha256: { type: 'string', nullable: true }, extractorVersion: { type: 'integer', description: 'The version of the shard’s extraction code. A bump makes everything derived from it drift.', example: 1 }, hashing: { type: 'boolean', example: false }, complete: { type: 'boolean', description: 'Every client file has a hash.', example: true }, }, }, drift: { type: 'boolean', nullable: true, description: 'True when any source hash differs from the loaded table. NULL when the sources could not be read or are not usable.', example: false }, count: { type: 'integer', description: 'Entries currently loaded.', example: 67496 }, sources: { type: 'array', items: { type: 'string' }, description: 'Every source found now, root-relative, base first then overlays in merge order.', example: ['custom/uomysticmoon.tsv'], }, loadedSources: { type: 'array', nullable: true, description: 'What each source contributed at the last import.', items: { type: 'object', properties: { label: { type: 'string', example: 'custom/uomysticmoon.tsv' }, kind: { type: 'string', enum: ['shard', 'base', 'custom'], description: '`shard` is the table read over the bridge; `base` a converted file on disk.', example: 'custom' }, entries: { type: 'integer', example: 37 }, added: { type: 'integer', description: 'Ids this source introduced.', example: 25 }, overrode: { type: 'integer', description: 'Ids it replaced from an earlier source.', example: 12 }, }, }, }, missingSources: { type: 'array', items: { type: 'string' }, description: 'Sources loaded previously and now absent. An import refuses these without `approve`.', example: [], }, importedAt: { type: 'string', format: 'date-time', nullable: true }, sourceBytes: { type: 'integer', nullable: true, example: 4973525 }, }, }, UoClilocRefreshResult: { type: 'object', description: 'Outcome of a cliloc refresh. Reported rather than thrown, so a missing or compressed file is an answer and not a 500.', properties: { status: { type: 'string', enum: ['skipped', 'unavailable', 'unchanged', 'imported', 'needsReview', 'failed'], description: '`needsReview` means a previously-loaded source has vanished and nothing was applied; re-run with `approve` to accept it.', example: 'imported', }, reason: { type: 'string', nullable: true }, source: { type: 'string', nullable: true, enum: ['bridge', 'file'], description: 'Which source this refresh read.', example: 'bridge' }, code: { type: 'string', nullable: true, description: 'Machine-readable cause. Bridge codes describe the shard (`DISABLED`: the operator switched the asset plane off; `NO_SOURCE`: its client has no cliloc file; `SHARD_DOWN`; `SOURCE_CHANGED`: the client was patched mid-import, so nothing was applied). File codes describe the path — `COMPRESSED` means the client\'s own Cliloc.enu was supplied instead of a converted one.', enum: ['NO_PATH', 'NOT_FOUND', 'NO_FILE', 'UNREADABLE', 'COMPRESSED', 'TRUNCATED', 'EMPTY', 'NOT_BUFFER', 'DISABLED', 'NO_SOURCE', 'SHARD_DOWN', 'PROTOCOL', 'BUSY', 'UNAVAILABLE', 'SOURCE_CHANGED', 'INCOMPLETE', 'STUCK', 'MALFORMED', 'TOO_LARGE'], }, path: { type: 'string', nullable: true }, file: { type: 'string', nullable: true }, count: { type: 'integer', nullable: true, description: 'Entries stored (blank strings are dropped).', example: 67496 }, parsed: { type: 'integer', nullable: true, description: 'Entries read across every source before blanks were dropped.', example: 123527 }, blank: { type: 'integer', nullable: true, example: 0 }, pages: { type: 'integer', nullable: true, description: 'Bridge only: how many pages the table arrived in (a stock English table is about eleven).', example: 11 }, reported: { type: 'integer', nullable: true, description: 'Bridge only: how many rows the shard said it holds.', example: 67496 }, received: { type: 'integer', nullable: true, description: 'Bridge only: how many arrived. Disagreeing with `reported` means the walk is wrong.', example: 67496 }, overlayProblem: { type: 'string', nullable: true, description: 'The base imported, but the overlay directory could not be read. Reported rather than fatal.' }, sources: { type: 'array', nullable: true, description: 'Per-source breakdown: what each file contributed and how much of it overrode an earlier source.', items: { type: 'object', properties: { label: { type: 'string' }, kind: { type: 'string', enum: ['shard', 'base', 'custom'] }, entries: { type: 'integer' }, added: { type: 'integer' }, overrode: { type: 'integer' }, }, }, }, missingSources: { type: 'array', nullable: true, items: { type: 'string' }, description: 'On `needsReview`: the sources that vanished. Nothing was applied.', }, acceptedMissing: { type: 'array', nullable: true, items: { type: 'string' }, description: 'On `imported` with `approve`: the vanished sources the admin accepted.', }, }, }, UoAssetStatus: { type: 'object', description: 'Admin view of the client-asset import (docs/link/v8.md §6, §8). What the site holds beside what the shard’s UO client currently is. Holding nothing at all is a supported state — creature pages simply render without pictures, which is what every install did before this pipeline existed.', properties: { loaded: { type: 'object', description: 'What this site currently holds.', properties: { assets: { type: 'integer', description: 'Rows in the imported catalogue.', example: 787 }, stored: { type: 'integer', description: 'How many of those have a picture on disk. Lower than `assets` when the shard listed a key it could not render.', example: 787 }, creatures: { type: 'integer', description: 'Atlas creatures the shard has answered a body question about, resolved or not.', example: 812 }, resolved: { type: 'integer', description: 'How many of those resolved to a body id. The rest are types this shard’s scripts do not define, or spawn entries naming an item rather than a creature.', example: 780 }, catalog: { type: 'string', nullable: true, description: 'The shard’s catalogue id at the last import — derived from its client files, so it changes exactly when they do.', example: 'a3f9c21d4b8e0771' }, extractorVersion: { type: 'integer', nullable: true, description: 'The version of the shard’s extraction code. A bump makes every derived byte drift even though the client files did not move.', example: 1 }, importedAt: { type: 'string', format: 'date-time', nullable: true }, items: { type: 'integer', description: 'Item pictures held. Unlike the catalogue this has no total to compare against: item art is fetched because something on the site names it, so this is the working set rather than a fraction of one.', example: 1840 }, land: { type: 'integer', description: 'Land tile pictures held. Zero on every install until something asks for one.', example: 0 }, }, }, shard: { type: 'object', nullable: true, description: 'The shard’s own client files right now. NULL when there is no shard link or it could not be reached — see `reason`.', properties: { files: { type: 'integer', description: 'How many of the animation/definition files this catalogue reads the shard actually has. Few clients carry all five anim files.', example: 9 }, extractorVersion: { type: 'integer', example: 1 }, hashing: { type: 'boolean', description: 'A hash is being computed in the background. A null `sha256` while this is true means “not yet”, never “changed”.', example: false }, complete: { type: 'boolean', description: 'Every client file has a content hash.', example: true }, imaging: { type: 'object', nullable: true, description: 'Whether the shard host can render an image at all. `ok: false` is the named NO_IMAGING state: ServUO under Mono needs libgdiplus, and without it a Linux shard cannot decode a sprite. Cliloc and atlas import are unaffected.', properties: { ok: { type: 'boolean', example: true }, code: { type: 'string', nullable: true, example: null }, reason: { type: 'string', nullable: true }, }, }, families: { type: 'array', items: { type: 'string' }, description: 'Which asset key families this shard’s plugin overlay serves. An overlay older than phase 5 answers `["body"]` only — it has the creature catalogue and no item art.', example: ['body', 'land', 'static'], }, }, }, drift: { type: 'boolean', nullable: true, description: 'True when the shard’s client files no longer match what was imported — press Import. NULL when they could not be read.', example: false, }, reason: { type: 'string', nullable: true, description: 'Why the shard could not be asked, when it could not.' }, code: { type: 'string', nullable: true, description: 'Machine-readable cause of `reason`.', enum: ['DISABLED', 'NO_SOURCE', 'SHARD_DOWN', 'PROTOCOL', 'BUSY', 'NO_IMAGING', 'SOURCE_CHANGED', 'UNAVAILABLE'], }, }, }, UoAssetImportResult: { type: 'object', description: 'Outcome of an asset import. Reported rather than thrown, so a shard that is down or a host that cannot render images is an answer and not a 500.', properties: { status: { type: 'string', enum: ['skipped', 'unavailable', 'unchanged', 'imported', 'needsReview', 'failed'], description: '`skipped`: no shard is configured. `unchanged`: the client files match what was imported and nothing was fetched. `needsReview`: assets this site holds are no longer offered by the shard, and nothing was changed — re-run with `approve` to accept it.', example: 'imported', }, reason: { type: 'string', nullable: true }, code: { type: 'string', nullable: true, description: 'Machine-readable cause. `NO_IMAGING` is a shard host with no libgdiplus; `SOURCE_CHANGED` is a client patched partway through the walk, in which case nothing was applied.', enum: ['DISABLED', 'NO_SOURCE', 'SHARD_DOWN', 'PROTOCOL', 'BUSY', 'NO_IMAGING', 'SOURCE_CHANGED', 'INCOMPLETE', 'STUCK', 'MALFORMED', 'TOO_LARGE', 'UNAVAILABLE'], }, catalog: { type: 'string', nullable: true, example: 'a3f9c21d4b8e0771' }, extractorVersion: { type: 'integer', nullable: true, example: 1 }, assets: { type: 'integer', nullable: true, description: 'Catalogue rows after the import.', example: 787 }, fetched: { type: 'integer', nullable: true, description: 'How many sprites actually crossed the wire. On an Update after a client patch this is far smaller than `assets`, which is the point of the manifest.', example: 12 }, written: { type: 'integer', nullable: true, description: 'How many were written to disk.', example: 12 }, absent: { type: 'integer', nullable: true, description: 'Keys the shard listed but could not render. NOT a failure: this client has no art at that key, which is the expected answer for two thirds of the playable ghost and gargoyle bodies.', example: 0, }, unsupported: { type: 'integer', nullable: true, description: 'Keys the shard does not serve at all. Unlike `absent` this indicates a bug on the site’s side, not a gap in the client.', example: 0 }, removed: { type: 'integer', nullable: true, description: 'Assets deleted because the shard no longer offers them (only with `approve`).', example: 0 }, scanned: { type: 'integer', nullable: true, description: 'Body ids the shard walked. Far larger than `assets` — most of the addressable range has no art.', example: 2047 }, pages: { type: 'integer', nullable: true, description: 'Manifest pages. This family pages on the shard’s scan budget rather than on bytes, so several is normal.', example: 4 }, playerBodies: { type: 'array', nullable: true, items: { type: 'integer' }, description: 'The body ids the shard reports as player-character bodies — every registered race’s male, female and ghost bodies, asked of the shard rather than hardcoded. These render head-on; everything else renders three-quarter.', example: [400, 401, 402, 403, 605, 606, 607, 608, 666, 667, 694, 695], }, vanished: { type: 'array', nullable: true, items: { type: 'string' }, description: 'On `needsReview`: up to fifty of the keys that disappeared.' }, vanishedCount: { type: 'integer', nullable: true }, bodies: { type: 'object', nullable: true, description: 'The slug → body id pass (§8). The shard constructs each creature and reads its body id, which is the only thing correct for a shard’s own custom creatures.', properties: { asked: { type: 'integer', example: 812 }, answered: { type: 'integer', example: 812 }, resolved: { type: 'integer', example: 780 }, tally: { type: 'object', description: 'Per-outcome counts. `unknown` is real drift worth acting on — a spawn file naming a type this shard’s scripts do not define. `notCreature` is a spawn entry for an item or decoration and is permanent.', properties: { ok: { type: 'integer', example: 780 }, unknown: { type: 'integer', example: 20 }, notCreature: { type: 'integer', example: 12 }, failed: { type: 'integer', example: 0 }, }, }, reason: { type: 'string', nullable: true }, }, }, art: { type: 'object', nullable: true, description: 'The derivation onto `shard_spawn_creatures.art`. An operator-supplied `spawnAtlas.art.json` always wins over an imported sprite.', properties: { applied: { type: 'integer', description: 'Creatures now pointing at a picture.', example: 763 }, derived: { type: 'integer', description: 'From the import.', example: 763 }, operator: { type: 'integer', description: 'From the operator’s own map.', example: 0 }, error: { type: 'string', nullable: true }, }, }, }, }, UoItemArtWarmResult: { type: 'object', description: 'Outcome of one item-art warm pass (docs/link/v8.md §11, phase 5). Unlike the body catalogue there is no manifest and no set: the client addresses 49,152 item graphics times three thousand hues, so what gets fetched is defined by what this site’s own rows name — every distinct (ItemID, hue) on a player vendor, plus anything a character sheet has shown since the last pass. Reported rather than thrown, so a shard that is down is an answer and not a 500.', properties: { status: { type: 'string', enum: ['skipped', 'unavailable', 'unchanged', 'imported', 'failed'], description: '`skipped`: no shard is configured. `unchanged`: every wanted picture is already held and current. `unavailable`: the shard could not be asked, or its plugin overlay is too old to serve item art.', example: 'imported', }, reason: { type: 'string', nullable: true }, code: { type: 'string', nullable: true, description: 'Machine-readable cause. `NO_IMAGING` is a shard host with no libgdiplus. `UNSUPPORTED` is a plugin overlay that serves the creature catalogue but not item art — update the overlay.', enum: ['DISABLED', 'NO_SOURCE', 'SHARD_DOWN', 'PROTOCOL', 'BUSY', 'NO_IMAGING', 'UNSUPPORTED', 'INCOMPLETE', 'STUCK', 'MALFORMED', 'TOO_LARGE', 'UNAVAILABLE'], }, catalog: { type: 'string', nullable: true, description: 'The shard’s art catalogue id these pictures were fetched under — a hash of the files that decide their bytes. Stored per row, which is how staleness is answered without a manifest.', example: '7c1e04b9aa2f3d58', }, wanted: { type: 'integer', nullable: true, description: 'Distinct keys this site’s rows name right now.', example: 1840 }, held: { type: 'integer', nullable: true, description: 'How many of those are already stored and current.', example: 1440 }, asked: { type: 'integer', nullable: true, description: 'How many this pass actually requested. Bounded by `limit`.', example: 400 }, fetched: { type: 'integer', nullable: true, description: 'How many the shard returned a picture for.', example: 396 }, written: { type: 'integer', nullable: true, description: 'How many were written to disk.', example: 396 }, absent: { type: 'integer', nullable: true, description: 'Keys the shard has no art for. NOT a failure — 9,963 of this client’s static ids have an empty index entry, and an item using one simply has no picture.', example: 4, }, unsupported: { type: 'integer', nullable: true, description: 'Keys the shard does not serve at all. A bug on the site’s side rather than a gap in the client.', example: 0 }, remaining: { type: 'integer', nullable: true, description: 'Wanted keys left for the next pass. Passes repeat on a timer, so a backlog drains without an operator.', example: 0 }, }, }, UoShardLinkRequest: { type: 'object', required: ['code'], properties: { code: { type: 'string', description: 'The one-time code shown by [link in game.', example: 'AB12CD' }, }, }, UoShardLinkResult: { type: 'object', properties: { linked: { type: 'boolean', example: true }, account: { type: 'string', example: 'whitlocktech' }, }, }, UoShardLink: { type: 'object', description: 'A linked in-game account (GET /player/shard/accounts).', properties: { account: { type: 'string', example: 'whitlocktech' }, userId: { type: 'integer', example: 42 }, charName: { type: 'string', nullable: true, example: 'Darrow' }, linkedAt: { type: 'string', format: 'date-time' }, }, }, UoTownCrierRequest: { type: 'object', required: ['id', 'lines'], properties: { id: { type: 'string', maxLength: 64, description: 'Re-posting the same id replaces the prior entry.', example: 'news-42' }, lines: { type: 'array', items: { type: 'string', maxLength: 200 }, example: ['Hear ye!', 'Market tax is now 5%.'] }, durationSec: { type: 'integer', minimum: 1, maximum: 86400, example: 3600 }, }, }, }, }, }