Files
Module-Rust/server/swagger/doc.js
wtclaude 0cb9bdd1f0
All checks were successful
PR Checks / server-tests (pull_request) Successful in 28s
PR Checks / client-build (pull_request) Successful in 27s
PR Checks / frozen-manifest (pull_request) Successful in -1m9s
feat(rust): the live map (phase 14, protocol 11)
PLAN.md §30 as approved, plus D119/D120 from the build.

Server:
- rust_map_images (one row per server: picture as MEDIUMBLOB, geometry,
  monuments, DERIVATION_VERSION) and rust_map_overrides; purge.sql pair.
- mapImages.js: D110. The board poll notices a new boot/wipe/seed/size and
  asks map.info; a new key or hash from the free Rust+ cache (or a render
  kept on disk) is fetched in slices, checked against its SHA-256 and stored
  in one statement. One fetch per server, a backoff on failure, `stale`
  abandons a fetch that straddles a map change. Render now (D109) is
  admin-only and watched to completion.
- mapLive.js: D111. One map.live per server per 5 s whoever asks; positions
  are held in memory only.
- model/map: four layers (world, events public; players, bases staff), a
  fleet default plus per-server override (D114), the players layer capped by
  presence (D113), own dot and online first-party clan mates for a linked
  viewer (D115, D117, D118). A layer the viewer may not see is absent from
  the answer, never sent and hidden.
- Routes: public /servers/:id/map, /map/image (immutable under its hash),
  /map/live; admin /servers/:id/map/fetch and /render; the Map card on the
  visibility PUT. Swagger fragment and frozen manifest regenerated.

Client:
- A Map tab: Leaflet over the picture in CRS.Simple, the game's own grid
  (labels only when a cell is wide enough to hold one), a legend that lists
  hidden layers with who can see them, polled every 10 s while visible.
- D120: Leaflet is a lazy split chunk beside entry.js, not in it. release.yml
  copies every dist/*.js; checkExternals and build.test.js hold both ends.
- The Map card on Admin -> Rust visibility, with Fetch again and Render now.

Capability `map` declared for the Android app (phase 15).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
2026-09-25 01:06:10 -05:00

920 lines
41 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// ── The OpenAPI fragment: the shared half ─────────────────────────────────
//
// The tags and component schemas the `#swagger.*` annotations refer to.
// `scripts/swaggerFragment.js` feeds this to swagger-autogen; the per-endpoint
// detail lives beside each route, exactly as it does in core.
//
// **Two rules about names, and both belong to the MERGED document rather than to
// this file** (MODULE_API.md §6.1a). Core merges every started module's fragment
// over its own committed spec and serves the result at `/api/docs.json`, and core
// wins any key collision:
//
// • **Namespace what you DEFINE.** `RustServerList`, not `ServerList`. A second
// game's module describing the same idea under the same bare name would
// silently clobber this one or be clobbered by it.
// • **Reference what CORE defines by core's name.** `#/components/schemas/Error`
// and `ValidationError` are core's; point at them and do not redefine them.
//
// **swagger-autogen renders `components.schemas` from an EXAMPLE object, not from
// raw OpenAPI.** `{ type: 'object' }` comes back as a meta-description of itself.
// That is uniform across core's committed spec and is the house shape.
module.exports = {
tags: [
{
name: 'Public · Rust',
description: 'The Rust servers this site follows, as each one last reported itself',
},
{
name: 'Player · Rust',
description: 'The Rust surface for a signed-in player',
},
{
name: 'Admin · Rust',
description: 'Configuring the Rust servers and their sidecars',
},
],
components: {
schemas: {
RustServerList: {
type: 'object',
description: 'Every Rust server this site follows (GET /public/rust/servers).',
properties: {
servers: {
type: 'array',
items: { $ref: '#/components/schemas/RustServer' },
},
},
},
RustServer: {
type: 'object',
description: 'One Rust server, as it last reported itself.',
properties: {
id: { type: 'string', example: 'main' },
name: { type: 'string', example: 'Main · Vanilla' },
online: { type: 'boolean', example: true },
players: { type: 'integer', example: 42 },
maxPlayers: { type: 'integer', example: 100 },
hostname: { type: 'string', nullable: true, example: 'Runic Gateway · Main' },
level: { type: 'string', nullable: true, example: 'Procedural Map' },
worldSize: { type: 'integer', nullable: true, example: 4000 },
seed: { type: 'integer', nullable: true, example: 1234 },
updatedAt: { type: 'string', format: 'date-time', nullable: true },
stale: {
type: 'boolean',
description: 'Has nothing reported in longer than the freshness window? A stale row is reported offline.',
example: false,
},
},
},
RustAdminServerList: {
type: 'object',
description: 'The configured servers, with their sidecar settings (GET /admin/rust/servers).',
properties: {
servers: {
type: 'array',
items: { $ref: '#/components/schemas/RustAdminServer' },
},
},
},
RustAdminServer: {
type: 'object',
description: 'One configured server. The sidecar token is never included — `hasToken` reports only whether one is stored.',
properties: {
id: { type: 'string', example: 'main' },
name: { type: 'string', example: 'Main · Vanilla' },
sidecarBaseUrl: { type: 'string', example: 'http://10.0.0.5:8090' },
hasToken: { type: 'boolean', example: true },
protocol: { type: 'integer', example: 1 },
enabled: { type: 'boolean', example: true },
sortOrder: { type: 'integer', example: 0 },
reachable: {
type: 'boolean',
description: 'Did the sidecar answer on the last poll? Separate from `online`, which is about the game rather than the bridge.',
example: true,
},
bootId: { type: 'string', nullable: true, example: 'boot-20260915T194502Z' },
sidecarProtocol: { type: 'integer', nullable: true, example: 1 },
online: { type: 'boolean', example: true },
players: { type: 'integer', example: 42 },
stale: { type: 'boolean', example: false },
},
},
RustLink: {
type: 'object',
description: 'One Steam account linked to a website user. Never carries a code.',
properties: {
steamId: { type: 'string', example: '76561198000000000' },
name: {
type: 'string',
nullable: true,
description: 'What the player was called in game when they linked. A display name only — a Rust name changes on a whim and nothing identifies anybody by it.',
example: 'Wanderer',
},
serverId: {
type: 'string',
nullable: true,
description: 'Which server minted the code. Not part of the identity — a link is fleet-wide — but it is where a support conversation starts.',
example: 'main',
},
linkedAt: { type: 'string', format: 'date-time' },
},
},
RustLinkList: {
type: 'object',
description: 'The Steam accounts one website user holds (GET /player/rust/links).',
properties: {
links: { type: 'array', items: { $ref: '#/components/schemas/RustLink' } },
},
},
RustPlayerReach: {
type: 'object',
description: 'One server an entitlement’s scope reaches, and whether it is there yet.',
properties: {
id: { type: 'string', example: 'main' },
name: { type: 'string', example: 'Main · Vanilla+' },
live: {
type: 'boolean',
description: 'True only when a sync confirmed this into that server’s own store. False covers every way it has not arrived — the server is offline, no loaded plugin registered the name, or its store has never seen the account — and the difference between those is an operator’s diagnosis, not a player’s.',
example: true,
},
},
},
RustPlayerPermissions: {
type: 'object',
description: 'What the site has given the signed-in player in game (GET /player/rust/permissions).',
properties: {
accounts: {
type: 'integer',
description: 'How many Steam accounts the caller has linked. Zero is why an entitlement can be authored and reach nobody.',
example: 1,
},
groups: {
type: 'array',
items: {
type: 'object',
properties: {
name: { type: 'string', example: 'vip' },
title: { type: 'string', example: 'VIP' },
scope: { type: 'string', description: 'A server id, or `*` for the whole fleet.', example: '*' },
since: { type: 'string', format: 'date-time' },
permissions: { type: 'array', items: { type: 'string' }, example: ['kits.vip'] },
reach: { type: 'array', items: { $ref: '#/components/schemas/RustPlayerReach' } },
},
},
},
grants: {
type: 'array',
items: {
type: 'object',
properties: {
permission: { type: 'string', example: 'kits.vip' },
scope: { type: 'string', example: '*' },
source: { type: 'string', description: 'Who authored it — `admin` now, an event action later.', example: 'admin' },
note: { type: 'string', nullable: true },
since: { type: 'string', format: 'date-time' },
reach: { type: 'array', items: { $ref: '#/components/schemas/RustPlayerReach' } },
},
},
},
},
},
RustLinkRequest: {
type: 'object',
required: ['code'],
properties: {
code: {
type: 'string',
description: 'The six-character code /link handed the player in game. Good for five minutes, and it works once.',
example: 'K7M2PQ',
},
},
},
RustLinkResult: {
type: 'object',
description: 'The result of redeeming a code.',
properties: {
linked: { type: 'boolean', example: true },
link: { $ref: '#/components/schemas/RustLink' },
already: {
type: 'boolean',
description: 'True when this Steam id was already linked to the caller — a second press of the button, not an error.',
example: false,
},
},
},
RustAdminLinkList: {
type: 'object',
description: 'One user’s Rust identity, for the admin.users.detail panel (GET /admin/users/{id}/rust/links).',
properties: {
links: {
type: 'array',
items: {
type: 'object',
properties: {
steamId: { type: 'string', example: '76561198000000000' },
name: {
type: 'string',
nullable: true,
description: 'What the game last saw this player called, falling back to the name recorded at link time.',
example: 'Wanderer',
},
linkedName: { type: 'string', nullable: true, example: 'Wanderer' },
serverId: { type: 'string', nullable: true, example: 'main' },
linkedAt: { type: 'string', format: 'date-time' },
firstSeen: { type: 'string', format: 'date-time', nullable: true },
lastSeen: { type: 'string', format: 'date-time', nullable: true },
servers: {
type: 'array',
description: 'All-time totals per server, summed across every wipe.',
items: {
type: 'object',
properties: {
serverId: { type: 'string', example: 'main' },
serverName: { type: 'string', example: 'Main · Vanilla' },
kills: { type: 'integer', example: 41 },
deaths: { type: 'integer', example: 37 },
npcKills: { type: 'integer', example: 120 },
structures: { type: 'integer', example: 64 },
playtimeSec: { type: 'integer', example: 43200 },
wipes: { type: 'integer', example: 2 },
lastSeen: { type: 'string', format: 'date-time', nullable: true },
},
},
},
},
},
},
},
},
RustPermissionModel: {
type: 'object',
description:
'The whole permission model (GET /admin/rust/permissions): what the site authors, what each game reported back, and the names a grant may use.',
properties: {
groups: {
type: 'array',
description: 'Groups the site authors, mirrored into each in-scope game as a real group.',
items: {
type: 'object',
properties: {
name: { type: 'string', example: 'vip' },
title: { type: 'string', example: 'VIP' },
rank: { type: 'integer', example: 10 },
scope: {
type: 'string',
description: 'A server id, or `*` for every server.',
example: '*',
},
permissions: { type: 'array', items: { type: 'string', example: 'kits.vip' } },
members: {
type: 'array',
items: {
type: 'object',
properties: {
userId: { type: 'integer', example: 42 },
username: { type: 'string', example: 'wanderer' },
steamId: {
type: 'string',
nullable: true,
description: 'Null when this account has linked no Steam id, in which case the membership reaches nobody yet.',
example: '76561198000000000',
},
playerName: { type: 'string', nullable: true, example: 'Wanderer' },
},
},
},
},
},
},
grants: {
type: 'array',
description: 'Permissions held by one person without a group. Unlike membership, a direct grant reaches a player who has never connected.',
items: {
type: 'object',
properties: {
id: { type: 'integer', example: 7 },
userId: { type: 'integer', example: 42 },
username: { type: 'string', example: 'wanderer' },
permission: { type: 'string', example: 'kits.gold' },
scope: { type: 'string', example: 'main' },
source: {
type: 'string',
description: 'What authored it — `admin`, `adopted`, or a later phase’s own writer.',
example: 'admin',
},
note: { type: 'string', nullable: true, example: null },
grantedAt: { type: 'string', format: 'date-time' },
accounts: {
type: 'array',
description: 'The Steam accounts this grant reaches. Empty means it reaches nobody yet.',
items: {
type: 'object',
properties: {
steamId: { type: 'string', example: '76561198000000000' },
name: { type: 'string', nullable: true, example: 'Wanderer' },
},
},
},
},
},
},
servers: {
type: 'array',
description: 'The state of the mirror, per configured server.',
items: { $ref: '#/components/schemas/RustPermissionSyncState' },
},
drift: {
type: 'array',
description: 'What a game holds that the site did not author. Reported, never undone.',
items: {
type: 'object',
properties: {
id: { type: 'integer', example: 3 },
serverId: { type: 'string', example: 'main' },
kind: {
type: 'string',
description: 'One of `grant`, `member`, `group-permission`.',
example: 'grant',
},
subject: {
type: 'string',
description: 'A Steam id, or a group name.',
example: '76561198000000000',
},
object: {
type: 'string',
description: 'A permission name, or a group name.',
example: 'kits.admin',
},
username: {
type: 'string',
nullable: true,
description: 'The website account holding that Steam id, when there is one. Without it the drift cannot be adopted, only revoked.',
example: 'wanderer',
},
firstSeen: { type: 'string', format: 'date-time' },
},
},
},
catalogue: {
type: 'array',
items: { $ref: '#/components/schemas/RustPermissionCatalogueEntry' },
},
},
},
RustPermissionSyncState: {
type: 'object',
description: 'Whether one server’s store matches what the site authors, and what its last report said.',
properties: {
serverId: { type: 'string', example: 'main' },
state: {
type: 'string',
description: 'One of `pending`, `ok`, `failed`.',
example: 'ok',
},
inSync: {
type: 'boolean',
description: 'True when the last successful push carried the set the site currently authors.',
example: true,
},
dirty: { type: 'boolean', example: false },
lastAttemptAt: { type: 'string', format: 'date-time', nullable: true },
lastOkAt: { type: 'string', format: 'date-time', nullable: true },
error: {
type: 'string',
nullable: true,
description: 'Why the last attempt failed — a transport word (`timeout`, `no-token`, `protocol-mismatch`) or the game’s own refusal.',
example: null,
},
report: {
type: 'object',
nullable: true,
description: 'The plugin’s report from the last successful sync.',
properties: {
applied: {
type: 'object',
properties: {
grants: { type: 'integer', example: 2 },
revokes: { type: 'integer', example: 0 },
groupsCreated: { type: 'integer', example: 1 },
members: { type: 'integer', example: 3 },
},
},
alreadyCorrect: { type: 'integer', example: 14 },
unresolved: {
type: 'array',
description: 'Permission names no loaded plugin on that server has registered. A grant naming one lands nowhere and is not recorded as pushed.',
items: { type: 'string', example: 'kits.gold' },
},
pending: {
type: 'array',
description: 'Memberships waiting on a first connection: the store has no user record to put in a group yet.',
items: { type: 'string', example: '76561198000000000:vip' },
},
},
},
},
},
RustPermissionCatalogue: {
type: 'object',
description: 'Every permission name the configured servers have registered (GET /admin/rust/permissions/catalogue).',
properties: {
permissions: {
type: 'array',
items: { $ref: '#/components/schemas/RustPermissionCatalogueEntry' },
},
},
},
RustPermissionCatalogueEntry: {
type: 'object',
description: 'One registered permission name, and which servers know it.',
properties: {
permission: { type: 'string', example: 'kits.vip' },
servers: { type: 'array', items: { type: 'string', example: 'main' } },
},
},
RustPermissionSyncResult: {
type: 'object',
description: 'What a forced sync produced (POST /admin/rust/permissions/sync).',
properties: {
servers: { type: 'array', items: { $ref: '#/components/schemas/RustPermissionSyncState' } },
drift: { type: 'array', items: { type: 'object' } },
},
},
RustUserPermissions: {
type: 'object',
description: 'One person’s Rust privileges, for the admin.users.detail panel (GET /admin/users/{id}/rust/permissions).',
properties: {
groups: {
type: 'array',
items: {
type: 'object',
properties: {
name: { type: 'string', example: 'vip' },
title: { type: 'string', example: 'VIP' },
scope: { type: 'string', example: '*' },
permissions: { type: 'array', items: { type: 'string', example: 'kits.vip' } },
},
},
},
grants: {
type: 'array',
items: {
type: 'object',
properties: {
id: { type: 'integer', example: 7 },
permission: { type: 'string', example: 'kits.gold' },
scope: { type: 'string', example: 'main' },
source: { type: 'string', example: 'admin' },
grantedAt: { type: 'string', format: 'date-time' },
},
},
},
reaches: {
type: 'array',
description: 'The Steam accounts these privileges reach. Empty means this person has linked nothing and holds them on paper only.',
items: { type: 'string', example: '76561198000000000' },
},
},
},
RustOnline: {
type: 'object',
description: 'Who is on one server (GET /public/rust/servers/{id}/online). Below the operator’s presence audience the names are withheld and only the count is answered — nothing names who is online by default.',
properties: {
players: {
type: 'array',
description: 'Empty whenever `hidden` is true.',
items: {
type: 'object',
properties: {
steamId: { type: 'string', example: '76561198000000000' },
name: { type: 'string', nullable: true, example: 'Wanderer' },
sleeping: { type: 'boolean', example: false },
connectedAt: { type: 'string', nullable: true },
},
},
},
hidden: { type: 'boolean', description: 'Were the names withheld from this viewer?', example: true },
count: { type: 'integer', description: 'How many are online. Public at every audience.', example: 12 },
audience: { $ref: '#/components/schemas/RustAudience' },
},
},
RustAudience: {
type: 'string',
enum: ['staff', 'signed_in', 'public'],
description: 'Who may see something: admins and moderators, any signed-in account, or anybody. Ordered — each includes the ones before it.',
example: 'staff',
},
RustVisibility: {
type: 'object',
description: 'Who may see who is online: the fleet default and each server’s optional override (GET /admin/rust/visibility).',
properties: {
audiences: { type: 'array', items: { $ref: '#/components/schemas/RustAudience' } },
presence: {
type: 'object',
properties: {
fleet: { $ref: '#/components/schemas/RustAudience' },
servers: {
type: 'array',
items: {
type: 'object',
properties: {
id: { type: 'string', example: 'main' },
name: { type: 'string', example: 'Main · Vanilla' },
enabled: { type: 'boolean', example: true },
override: {
type: 'string',
nullable: true,
enum: ['staff', 'signed_in', 'public', null],
description: 'This server’s own choice, or null to follow the fleet default.',
},
effective: { $ref: '#/components/schemas/RustAudience' },
},
},
},
},
},
clans: {
type: 'object',
description: 'Who may see a clan roster, and each server’s clan board.',
properties: {
audiences: { type: 'array', items: { $ref: '#/components/schemas/RustClanAudience' } },
roster: { $ref: '#/components/schemas/RustClanAudience' },
servers: {
type: 'array',
items: {
type: 'object',
properties: {
id: { type: 'string', example: 'main' },
name: { type: 'string', example: 'Main · Vanilla' },
supported: { type: 'boolean', example: true },
enabled: { type: 'boolean', example: true },
fresh: { type: 'boolean', example: true },
truncated: { type: 'boolean', example: false },
reason: { type: 'string', nullable: true },
clans: { type: 'integer', example: 14 },
umodClans: { type: 'boolean', description: 'Is the uMod Clans plugin loaded? Its clans are a separate system and are not Teams.', example: false },
},
},
},
},
},
map: {
type: 'object',
description: 'The live map’s switches and each server’s picture (phase 14).',
properties: {
layers: { type: 'array', items: { type: 'string' }, example: ['world', 'events', 'players', 'bases'] },
fleet: {
type: 'object',
properties: {
world: { $ref: '#/components/schemas/RustAudience' },
events: { $ref: '#/components/schemas/RustAudience' },
players: { $ref: '#/components/schemas/RustAudience' },
bases: { $ref: '#/components/schemas/RustAudience' },
mates: { type: 'boolean', example: true },
},
},
servers: {
type: 'array',
items: {
type: 'object',
properties: {
id: { type: 'string', example: 'main' },
name: { type: 'string', example: 'Main · Vanilla' },
overrides: { type: 'object', description: 'Each layer and `mates`, or null to follow the fleet.' },
effective: { type: 'object' },
picture: {
type: 'object',
nullable: true,
properties: {
source: { type: 'string', enum: ['companion', 'rendered', 'none'] },
mapKey: { type: 'string' },
hasPicture: { type: 'boolean' },
bytes: { type: 'integer' },
worldSize: { type: 'integer' },
fetchedAt: { type: 'string', format: 'date-time', nullable: true },
},
},
renderStallSeconds: { type: 'integer', description: 'About how long Render now would stall this server.', example: 9 },
fetching: { type: 'boolean' },
rendering: { type: 'boolean' },
lastError: { type: 'string', nullable: true },
},
},
},
},
},
news: {
type: 'object',
description: 'Whether a published news post is also said in each server’s in-game chat. Off by default (D104).',
properties: {
servers: {
type: 'array',
items: {
type: 'object',
properties: {
id: { type: 'string', example: 'main' },
name: { type: 'string', example: 'Main · Vanilla' },
enabled: { type: 'boolean', example: true },
on: { type: 'boolean', example: false },
},
},
},
},
},
},
},
RustMapLayer: {
type: 'object',
description: 'Whether this viewer gets one map layer, and which audience does. A hidden layer never says what it holds.',
properties: {
visible: { type: 'boolean', example: false },
audience: { $ref: '#/components/schemas/RustAudience' },
cappedByPresence: {
type: 'boolean',
description: 'Players layer only: narrower than its own switch because who may see who is online is narrower (D113).',
example: true,
},
},
},
RustMap: {
type: 'object',
description: 'One server’s map, as this viewer may see it (GET /public/rust/servers/{id}/map).',
properties: {
serverId: { type: 'string', example: 'main' },
mapKey: { type: 'string', nullable: true, example: '3000.1234.1' },
picture: {
type: 'object',
nullable: true,
description: 'Null when the game has no picture of its map.',
properties: {
path: { type: 'string', description: 'Relative to /api/v1, with the picture’s hash in it.', example: '/public/rust/servers/main/map/image?v=28da6e8a' },
source: { type: 'string', enum: ['companion', 'rendered'], example: 'companion' },
fetchedAt: { type: 'string', format: 'date-time', nullable: true },
},
},
geometry: {
type: 'object',
nullable: true,
description: 'How world coordinates reach a pixel: s = (width − 2 × oceanMargin) / worldSize, px = (x + worldSize/2) × s + oceanMargin, and z the same way up from the bottom edge.',
properties: {
worldSize: { type: 'integer', example: 3000 },
oceanMargin: { type: 'integer', description: 'In pixels, unscaled.', example: 500 },
width: { type: 'integer', example: 2500 },
height: { type: 'integer', example: 2500 },
gridCells: { type: 'integer', description: 'Cells per side, from the game’s own grid.', example: 20 },
gridCellSize: { type: 'number', description: 'Metres.', example: 150 },
background: { type: 'string', nullable: true, example: '#0B3B4A' },
},
},
monuments: {
type: 'array',
description: 'Present only when the viewer may see the world layer.',
items: {
type: 'object',
properties: {
value: { type: 'string', example: 'harbor_1#2' },
kind: { type: 'string', example: 'harbor_1' },
label: { type: 'string', example: 'Harbor' },
grid: { type: 'string', nullable: true, example: 'O3' },
x: { type: 'number', example: 678.1 },
z: { type: 'number', example: 1005.7 },
},
},
},
layers: {
type: 'object',
properties: {
world: { $ref: '#/components/schemas/RustMapLayer' },
events: { $ref: '#/components/schemas/RustMapLayer' },
players: { $ref: '#/components/schemas/RustMapLayer' },
bases: { $ref: '#/components/schemas/RustMapLayer' },
},
},
mates: {
type: 'object',
description: 'Whether this viewer gets their own position and their online clan mates’ (D115).',
properties: {
visible: { type: 'boolean', example: true },
on: { type: 'boolean', description: 'Is the switch on for this server?', example: true },
linked: { type: 'boolean', description: 'Has this viewer linked a Steam account?', example: true },
signedIn: { type: 'boolean', description: 'Is this viewer signed in at all?', example: true },
},
},
pollMs: { type: 'integer', example: 10000 },
},
},
RustMapLive: {
type: 'object',
description: 'What moves on one server’s map, cut down to this viewer (GET /public/rust/servers/{id}/map/live). A layer the viewer may not see is ABSENT, not empty.',
properties: {
live: { type: 'boolean', description: 'False when the game did not answer; `reason` says why.', example: true },
reason: { type: 'string', nullable: true },
mapKey: { type: 'string', nullable: true, example: '3000.1234.1' },
world: {
type: 'array',
items: {
type: 'object',
properties: {
kind: { type: 'string', enum: ['cargo', 'heli', 'chinook', 'bradley', 'supply', 'crate'], example: 'cargo' },
x: { type: 'number', example: 812.4 },
z: { type: 'number', example: -1320.6 },
hackLeftSec: { type: 'integer', description: 'A locked crate being hacked: seconds left.', example: 540 },
hacked: { type: 'boolean', example: false },
},
},
},
events: {
type: 'array',
items: {
type: 'object',
properties: {
kind: { type: 'string', enum: ['zone', 'crate', 'npc'], example: 'zone' },
runId: { type: 'string', example: '41' },
x: { type: 'number' },
z: { type: 'number' },
radius: { type: 'number', description: 'A zone’s.', example: 60 },
name: { type: 'string', description: 'A zone’s.', example: 'Harbor brawl' },
prefab: { type: 'string', description: 'A crate’s or NPC’s allowlist key.', example: 'crate.elite' },
},
},
},
players: {
type: 'array',
items: {
type: 'object',
properties: {
steamId: { type: 'string', example: '76561198000000000' },
name: { type: 'string', example: 'Wanderer' },
x: { type: 'number' },
z: { type: 'number' },
sleeping: { type: 'boolean', example: false },
online: { type: 'boolean', example: true },
},
},
},
playersTruncated: { type: 'boolean', description: 'More sleepers than the plugin’s MapMaxSleepers.' },
bases: {
type: 'array',
description: 'Positions only: no owner, no authorised list, no shop name.',
items: {
type: 'object',
properties: {
kind: { type: 'string', enum: ['tc', 'vending'], example: 'tc' },
x: { type: 'number' },
z: { type: 'number' },
},
},
},
basesTruncated: { type: 'boolean', description: 'More than the plugin’s MapMaxBases.' },
mates: {
type: 'array',
description: 'The viewer’s own positions (their sleeper too) and their online first-party clan mates on this server.',
items: {
type: 'object',
properties: {
steamId: { type: 'string' },
name: { type: 'string' },
x: { type: 'number' },
z: { type: 'number' },
sleeping: { type: 'boolean' },
online: { type: 'boolean' },
self: { type: 'boolean', description: 'One of the viewer’s own accounts.' },
},
},
},
},
},
RustClanAudience: {
type: 'string',
enum: ['members', 'signed_in', 'public'],
description: 'Who may see a clan’s roster: the clan’s own members (a website account linked to one of them) and staff, any signed-in account, or anybody. Widening it also shows which members are online to that audience.',
example: 'members',
},
RustClanBoard: {
type: 'object',
description: 'Whether a server’s clan list can be trusted right now.',
properties: {
supported: { type: 'boolean', description: 'Could the bridge read this server’s clans at all?', example: true },
enabled: { type: 'boolean', description: 'Is the game’s clan system switched on?', example: true },
fresh: { type: 'boolean', description: 'Has the board been re-sent within the last three minutes?', example: true },
truncated: { type: 'boolean', description: 'At the game’s 100-clan ceiling, or too large for one line: there may be clans the list does not show.', example: false },
reason: { type: 'string', nullable: true, description: 'Why the clans cannot be read, when they cannot.' },
},
},
RustClanList: {
type: 'object',
description: 'One server’s clans (GET /public/rust/servers/{id}/clans). Public: nothing here names a player.',
properties: {
clans: {
type: 'array',
items: {
type: 'object',
properties: {
externalId: { type: 'string', example: 'main:12:1790142840535' },
name: { type: 'string', example: 'Northwatch' },
color: { type: 'string', nullable: true, example: '#3fa9f5' },
score: { type: 'integer', example: 140 },
memberCount: { type: 'integer', example: 6 },
maxMembers: { type: 'integer', nullable: true, example: 100 },
},
},
},
board: { $ref: '#/components/schemas/RustClanBoard' },
},
},
RustClan: {
type: 'object',
description: 'One clan (GET /public/rust/clans/{externalId}) and, inside the roster audience, its roster.',
properties: {
clan: {
type: 'object',
properties: {
externalId: { type: 'string', example: 'main:12:1790142840535' },
name: { type: 'string', example: 'Northwatch' },
color: { type: 'string', nullable: true, example: '#3fa9f5' },
score: { type: 'integer', example: 140 },
memberCount: { type: 'integer', example: 6 },
maxMembers: { type: 'integer', nullable: true, example: 100 },
serverId: { type: 'string', example: 'main' },
serverName: { type: 'string', example: 'Main · Vanilla' },
founded: { type: 'integer', nullable: true, description: 'When the clan was founded, epoch milliseconds.' },
gone: { type: 'boolean', description: 'The clan has been disbanded, or has left its server’s board.', example: false },
},
},
roster: {
type: 'object',
properties: {
visible: { type: 'boolean', description: 'Is this viewer inside the roster audience? When false, `members` is empty.', example: false },
audience: { $ref: '#/components/schemas/RustClanAudience' },
members: {
type: 'array',
items: {
type: 'object',
properties: {
name: { type: 'string', nullable: true, example: 'Wanderer' },
role: { type: 'string', nullable: true, example: 'Leader' },
leader: { type: 'boolean', example: true },
online: { type: 'boolean', example: false },
joined: { type: 'integer', nullable: true, description: 'Epoch milliseconds.' },
},
},
},
},
},
},
},
RustVisibilityUpdate: {
type: 'object',
description: 'A change to who may see who is online. Either part may be omitted; a server set to null follows the fleet default again.',
properties: {
fleet: { $ref: '#/components/schemas/RustAudience' },
servers: {
type: 'object',
additionalProperties: { type: 'string', nullable: true, enum: ['staff', 'signed_in', 'public', null] },
example: { main: 'public', pvp: null },
},
clanRoster: { $ref: '#/components/schemas/RustClanAudience' },
news: {
type: 'object',
description: 'A server id to whether a published news post is said in its in-game chat.',
additionalProperties: { type: 'boolean' },
example: { main: true },
},
map: {
type: 'object',
description: 'The live map’s switches. `fleet` maps a layer to an audience and `mates` to true or false; `servers` maps a server id to the same shape, where null follows the fleet.',
example: { fleet: { players: 'signed_in', mates: true }, servers: { pve: { players: 'public' }, pvp: { players: null } } },
},
},
},
RustSidecarProbe: {
type: 'object',
description: 'What a sidecar said when probed (POST /admin/rust/servers/{id}/test).',
properties: {
ok: { type: 'boolean', example: true },
status: {
type: 'string',
description: 'What happened, in one word — this is what tells a wrong URL from a wrong token from a mismatched protocol. One of `ok`, `no-token`, `unauthorized`, `protocol-mismatch`, `timeout`, `transport-error`, or `http-<code>`.',
example: 'ok',
},
sidecar: {
type: 'object',
nullable: true,
description: 'The sidecar’s own health document, or the mismatch detail on a protocol disagreement.',
properties: {
status: { type: 'string', example: 'ok' },
protocol: { type: 'integer', example: 1 },
plugin_connected: { type: 'boolean', example: true },
database: { type: 'string', example: 'ok' },
uptime: { type: 'string', example: '3h 2m' },
last_event: { type: 'string', format: 'date-time', nullable: true },
},
},
},
},
},
},
}