/** * The Reference section's enumerations. * * §1 says a Reference page is "a navigable summary plus a link to the canonical document — * never a re-specification". This file is the line between those two things, and it is * worth being explicit about where it falls: * * * The NAMES are here — every environment variable, every config key, every command, * every event kind. A reference section that cannot answer "what variables are there?" * without a click-through is a link farm. * * The SEMANTICS are not. One terse line each, saying what a thing is FOR. Shapes, * defaults that matter, interactions, and every "why" stay in the canonical document. * * Everything below is checked against its source by `scripts/checkReference.mjs`, in both * directions — a name that disappears upstream fails, and a name that appears upstream and * is missing here fails too. That is the whole reason it is safe to write names down at * all: the enumeration cannot rot into fiction without turning the build red. * * Descriptions are NOT checked, and cannot be. They are the part a human has to keep * honest, which is why they are kept short enough to re-read. */ /** `website` root `.env.example` — the file a Compose deployment actually reads. */ export const envVars = { IMAGE_TAG: 'Which published image tag to run', NODE_ENV: 'production or development — several refusals are production-only', PORT: 'The port the app listens on', INTERNAL_PORT: 'The internal-only listener, for the bot channel', UPLOAD_DIR: 'Where uploads are written', LOG_LEVEL: 'Console log level', FILE_LOG_LEVEL: 'File log level, set separately', LOG_TO_FILE: 'Whether to write a log file at all', LOG_DIR: 'Directory for the log file', LOG_FILE: 'Log file name', BRAND_NAME: 'Site name — branding is data, not a build', BRAND_SHORT_NAME: 'Short form, for tight spaces', BRAND_TAGLINE: 'One line under the name', BRAND_DESCRIPTION: 'Meta description', BRAND_CONTACT_EMAIL: 'Published contact address', BRAND_URL: 'Canonical public URL', BRAND_ACCENT_COLOR: 'Accent colour', BRAND_LOGO: 'Logo path', BRAND_HERO: 'Hero image path', BRAND_FAVICON: 'Favicon path', DB_HOST: 'Database host', DB_PORT: 'Database port', DB_NAME: 'Database name', DB_USER: 'Database user', DB_PASSWORD: 'Database password', DB_ROOT_PASSWORD: "The database container's root password", JWT_SECRET: 'Signs session tokens. Rotating it logs everyone out', SECRET_ENC_KEY: 'Encrypts secrets at rest. Required in production, and rotating it ORPHANS every stored secret', JWT_EXPIRES_IN: 'Session lifetime', COOKIE_SECURE: 'auto decides Secure per request, so HTTPS and LAN HTTP both work', COOKIE_NAME: 'Session cookie name. Changing it invalidates existing sessions', TRUST_PROXY: 'Needed behind a reverse proxy for secure cookies, real IPs and rate limiting', DEBUG_TRUST_PROXY: 'Diagnostic for the above', TOTP_CHALLENGE_TTL: 'How long a pending 2FA challenge is valid', ADMIN_USERNAME: 'First admin, created only when no users exist', ADMIN_PASSWORD: 'First admin password. Set it before the first boot, not after', CLIENT_ORIGIN: 'Dev only — the Vite origin allowed through CORS', BOT_INTERNAL_URL: 'Where the Discord bot listens', BOT_INTERNAL_KEY: 'Authenticates the site↔bot channel. Required in production EVEN IF you run no bot', NTFY_BASE_URL: 'Push notification relay base URL', }; /** `link/sidecar/src/config.rs` → the TOML the sidecar writes on first run. */ export const sidecarConfig = { 'shard.bind': 'Loopback address the game plugin dials out to', 'web.bind': 'Address the website reaches the sidecar on', 'web.auth_token': 'Shared secret the website must present. Generated on first run if blank', 'store.path': "The sidecar's own durable store", }; /** `installer` — `src/cli.rs`'s `Command`. */ export const installerCommands = { Install: "Set up a game server's side: deploy the bridge plugin, install the sidecar, register its service", Doctor: 'Diagnose an existing install', Update: 'Move to a newer bundle', Uninstall: 'Remove what install put there', }; /** `servuo-plugins/overlay/Config/Bridge.cfg` — the plugin's config, grouped for reading. */ export const bridgeCfg = { Connection: { Host: 'Sidecar address the shard dials out to', Port: 'Sidecar port', QueueCap: 'Bounded queue depth. Full means drop-oldest — never block the game', PublicConnectAddress: 'Address players connect to, published to the site', LinkUrl: 'Where in-game account linking sends a player', }, Sweeps: { StatSweepSeconds: 'Character stat sweep interval', DecaySweepSeconds: 'House decay sweep', EconomySweepSeconds: 'Economy totals sweep', ChampSweepSeconds: 'Champion spawn sweep', PageSweepSeconds: 'Staff page sweep', GuildSweepSeconds: 'Guild roster sweep', CitySweepSeconds: 'City / governor sweep', PresenceSweepSeconds: 'Who is online', HousingSweepSeconds: 'Housing sweep', }, Guilds: { GuildRosterMembersPerLine: 'Frame cap — a roster is split rather than sent oversized', GuildRosterGuildsPerTick: 'How many guilds are swept per tick', }, Points: { PointsSweepSeconds: 'Points sweep interval', PointsLeaderboardEnabled: 'Publish a leaderboard at all', PointsTopN: 'Leaderboard length', PointsSystems: 'Which point systems to include', PointsProfileEnabled: 'Show points on a character profile', PointsProfileRank: 'Show rank as well as total', }, Market: { MarketEnabled: 'Publish player vendor listings', MarketSweepSeconds: 'Market sweep interval', MarketSweepBatch: 'Vendors per sweep', MarketMaxListings: 'Cap on listings published', }, Ruleset: { RulesetEnabled: 'Publish the shard ruleset', RulesetIncludeSchedule: 'Include the event schedule with it', }, 'Town crier': { TownCrierMaxLines: 'Lines per notice', TownCrierMaxLineLength: 'Characters per line', TownCrierMaxActive: 'Concurrent notices', TownCrierMaxDurationSec: 'Longest a notice may run', }, News: { NewsMaxTitleLength: 'Title cap', NewsMaxBodyLength: 'Body cap', NewsMaxExternal: 'How many site posts are carried in-game', NewsAnnounceDurationSec: 'How long an announcement shows', }, 'Admin commands': { AdminWriteEnabled: 'Whether the site may write to the game at all. Off by default', AdminAccessFloor: 'Minimum in-game access level for admin actions', AdminBroadcastMaxLength: 'Broadcast cap', AdminReasonMaxLength: 'Reason field cap', AdminBanMaxDurationSec: 'Longest ban the site may set', }, 'Scheduled events': { EventsEnabled: 'Whether the website may run scheduled events against this world at all. Off by ' + 'default, and deliberately a separate switch from AdminWriteEnabled', EventsSweepSeconds: 'How often expired gates are collected and lost objects pruned', EventsMinSaveIntervalSec: 'Shortest gap between world saves, counted from the last save by anyone. A save ' + 'asked for too soon is refused rather than queued', }, 'Event caps': { EventsMaxCreatures: 'Creatures one call may spawn', EventsMaxBosses: 'Enhanced "boss" variants one call may spawn', EventsMaxNpcs: 'Oracle NPCs one call may place', EventsMaxDecor: 'Decoration items one call may place', EventsMaxGateMinutes: 'Longest a temporary gate may stand', EventsMaxOwnedPerRun: 'Objects one run may own across every verb — the runaway bound', EventsMaxSpread: 'How far from the chosen spot things may be scattered', EventsMaxBossMultiplier: 'How much harder than normal a boss may be made', EventsMaxGrantPerRun: 'How many characters one item grant may reach', EventsMaxGrantStack: 'How large one granted stack may be', }, 'The oracle NPC': { EventsOracleMaxLines: 'Keyword lines it will answer to', EventsOracleGreetRange: 'How close a player must be to be greeted', EventsOracleSpeechRange: 'How close a player must be to be heard', EventsOracleGreetCooldownSec: 'How often it greets the same player', EventsOracleAnswerCooldownSec: 'How often it answers the same player', }, Leases: { LeaseMaxDurationSec: 'Longest lease this shard will hold, whatever the site asks for. A longer request ' + 'is refused rather than shortened', LeaseGraceSec: 'How long a restored lease stays listed, so a late teardown still gets a verdict', }, Participation: { ParticipationSweepSeconds: "How often everyone standing in a run's area is credited", ParticipationKillWeight: 'What one kill inside the area is worth against one minute in it', ParticipationMaxRuns: 'Runs counted at once', ParticipationMaxMembers: 'Members counted per run', ParticipationMaxRadius: 'Widest area an event may declare', ParticipationGraceSec: "How long a closed run's tally stays readable", ParticipationSnapshotChunk: "Members resolved per yield of the game's core thread", }, Accounts: { SignupMode: 'How game accounts may be created', AccountCreateEnabled: 'Allow creation at all', RequireIpForCreate: 'Require a real client IP', AccountNameMaxLength: 'Account name cap', AccountPasswordMaxLength: 'Account password cap', }, 'Client assets': { AssetsEnabled: 'Whether the website may read the UO client files on this host \u2014 art, animations, ' + 'the string table \u2014 over the link at all. Its own switch, because it is its own consent', AssetBatchBytes: 'Byte budget for one reply page, inside the 1 MiB line cap the sidecar accepts', AssetBodyBatch: 'Bodies one catalogue request may name. Counted in items rather than bytes, because ' + 'what it bounds is building and deleting that many real mobiles on the core thread. ' + 'A larger request is refused, never truncated', AssetFetchKeys: 'How many keys one fetch may name. The byte budget above still decides where a page is cut', AssetScanMs: 'How long a catalogue scan may run before the page it has is returned', AssetPlayerDirection: 'Which of the five directions a player body renders as. 0 is head-on, facing the viewer', AssetCreatureDirection: 'The same for everything else. 1 is the front three-quarter \u2014 a wolf seen head-on ' + 'is a dark blob', }, 'Spawn files': { TreeEnabled: 'Whether the shard configuration itself \u2014 spawn files, regions, locations, champion ' + 'spawns, decoration \u2014 may cross the bridge. A third switch for a third consent: ' + 'this is the work of the operator rather than the game client. Off means the spawn ' + 'atlas needs a shared filesystem again', TreeChunkBytes: 'How large a slice of one file may be before it is compressed and sent', }, }; /** * The five-rung visibility ladder, from `module-uo`'s `server/utils/shardVisibility.js`. * * This one is a SECURITY boundary, not a convenience filter, which is why it is enumerated * rather than described: a reader needs to see the whole ladder at once to reason about it. */ export const visibilityLadder = ['anonymous', 'logged_in', 'player', 'staff', 'admin']; /** * Canonical documents, by the question each answers. Checked to still exist in `docs`. * * Grouped by whose documents they are (PLAN.md §16, D63): `core` describes the platform in * core's words, and each game's group may use that game's. The keys of the outer object are * `core` plus keys of `platform.json`'s `games`, which is what lets the page label a game's * group with the game's name and `checkTerms.mjs` hold each group to its own vocabulary. */ export const canonicalDocs = { core: { 'website/ARCHITECTURE.md': 'How the website fits together — the canonical diagram', 'website/BACKEND_DESIGN.md': 'The API, schema and security contract', 'website/MODULE_SYSTEM.md': 'Why the module system is shaped this way', 'website/MODULE_API.md': 'Everything a module may do — the contract', 'website/TEAMS.md': 'Teams as a platform primitive', 'website/EVENTS.md': 'The Event System — leases, the ledger, and the module seam', 'website/THEMING_AND_NAV.md': 'Admin-configurable theme, assets and navigation', 'website/TRUSTED_DEVICES_MFA.md': 'Trusted devices and the second factor', 'installer/INSTALL.md': 'The operator guide for setting a game server up', 'installer/PLAN.md': "The installer's design of record", 'modules/rust-dryrun.md': 'The second game module as first designed on paper, to test that the contract generalises — since built', 'android/PLAN.md': 'The Android app', }, uo: { 'link/PLAN.md': 'The sidecar design of record, the data catalog and the wire protocol', 'link/INTEGRATION.md': 'Integrating with the sidecar', 'link/v8.md': 'The current protocol, and its cross-repository obligations', 'link/SHARD_PREREQS.md': 'What a shard host needs before any of this works', 'link/ADMIN_CONTROLS.md': 'What the site may command the game to do', 'website/SHARD_VISIBILITY.md': 'The audience ladder, for administrators', 'modules/uo/API.md': "The module's own API, including its audience rules", }, rust: { 'rust-link/INSTALL.md': 'Installing the Rust bridge — the installer, the Pterodactyl egg, or by hand', 'rust-link/INTEGRATION.md': 'Integrating with the sidecar', 'rust-link/PROTOCOL.md': 'The wire protocol between the bridge plugin and the sidecar', 'modules/rust/PLAN.md': 'The Rust module design of record', }, };