# uo-link bridge settings. # # Key scope is the filename: Bridge.cfg + StatSweepSeconds => "Bridge.StatSweepSeconds". # Read in Configure(), which runs before World.Load. # Loopback only. The socket being local is the trust boundary for inbound commands; # if the sidecar ever moves off-host, add a shared secret first. Host=127.0.0.1 Port=7788 # Outbound queue cap. On overflow the plugin drops oldest and counts the drops, # because a stalled sidecar must never OOM the shard. QueueCap=10000 # Sweep intervals, seconds. Measured on a 150-character shard: a vitals sweep costs # 0.0015 ms/char, so 1000 online players is ~1.5 ms per sweep. See https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/link/PLAN.md §1. StatSweepSeconds=30 DecaySweepSeconds=60 EconomySweepSeconds=300 # Champion-spawn board poll. ChampionSpawn has no EventSink, so every spawn is diffed on # this interval to emit champ.update on any status/level/kills/boss change. The world holds # only a handful of spawns, so the pass is trivial; 5-10s is well within site tolerance. ChampSweepSeconds=10 # Help-page queue poll. The in-game page queue has no EventSink, so it is diffed on this # interval to emit page.new / page.closed / page.updated. A few seconds is fine for a # support queue; the full open queue is also available on demand via pages.snapshot. PageSweepSeconds=5 # Guild roster poll (https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/link/PROTOCOL_2.md Part B). Guilds expose only EventSink.JoinGuild, so # create/disband/leave/leader/alliance changes are found by diffing BaseGuild.List on this # interval (emit guild.update / guild.remove). Guild membership moves slowly; 60s is ample. GuildSweepSeconds=60 # Town-governor poll. Each city's Governor / election is diffed on this interval to emit # city.update on change. Governors turn over on the order of weeks, so a slow sweep is fine. # Idle (emits nothing) unless the City Loyalty system is enabled (CityLoyalty.Enabled). CitySweepSeconds=300 # Presence poll. Online population (total, per-facet, per-region) is snapshotted on this # interval and emitted as presence.online only when it changes. Region transitions come # through separately in real time as region.enter (EventSink.OnEnterRegion). PresenceSweepSeconds=30 # Housing registry poll. Every house is diffed on this interval to emit house.update / # house.remove (owner, region, location, decay). Houses change slowly; a few minutes is fine. HousingSweepSeconds=300 # Points / loyalty leaderboards (https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/link/v3.md §7). ServUO carries ~25 point # currencies (Queen's Loyalty, Void Pool, Casino, Clean Up Britannia, the nine city loyalties, # the Doom/Khaldun/Kotl treasure systems, …). Each is diffed on this interval and emitted as # one points.board frame per system when its top N moves. # # Slow on purpose: these are month-scale standings, and ten of the systems keep a row for # every character ever created, so the pass is the widest read in the bridge. It is still # cheap — a single bounded pass, never a sort — but there is nothing to gain by hurrying it. PointsSweepSeconds=300 # Master switch for the boards. Off leaves char.profile points alone (see below). PointsLeaderboardEnabled=true # How many players per board. Clamped to 1..100 — the frame is emitted PER SYSTEM, so a big # N is multiplied by ~25. PointsTopN=10 # Which systems to publish, as a comma-separated list of PointsType names, e.g. # PointsSystems=QueensLoyalty,CleanUpBritannia,VoidPool # Blank (the default) publishes whatever the shard itself shows on the in-game loyalty gump # (ShowOnLoyaltyGump), so a subsystem you add later gets a board without an edit here. # An unrecognized name is logged and ignored, never silently dropped. PointsSystems= # Include a per-character "points" block in char.profile (the website character sheet). This # is a lookup across every published system's table, so it is the dominant cost of building a # profile; turn it off on a very large shard that does not want the sheet paying for it. PointsProfileEnabled=true # Also compute each system's rank in that block. OFF by default and worth leaving off: a # points lookup stops at the character's own row, but a rank must count every row that beats # them, in every system, on every profile build. The website already derives rank from the # board for anyone in the top N. PointsProfileRank=false # Player-vendor market index (https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/link/v3.md §8). Every player vendor's shop name, # owner, location and priced inventory, published as one vendor.listing frame per vendor so the # website can offer the search the in-game Vendor Search gump offers. Honours each player's own # in-game opt-out (the vendor's VendorSearch flag) — hide your vendor in game and it is hidden # on the site too. MarketEnabled=true # Sweep interval. UNLIKE every other sweep here, a tick does NOT walk the whole world: it # inventories at most MarketSweepBatch vendors and a persistent cursor round-robins through the # rest, so the per-tick cost is bounded by the batch rather than by how many vendors exist. Full # coverage takes ceil(vendors / batch) x MarketSweepSeconds — 500 vendors at the defaults is one # complete pass every 20 minutes, and the site labels the data with how stale it may be. # # Lower this (or raise the batch) for faster coverage; both trade directly against per-tick cost, # and the expensive part is the item walk, which recurses into every container a vendor is selling. MarketSweepSeconds=60 MarketSweepBatch=25 # Per-vendor listing cap, after which the frame carries "truncated": true. A commodity reseller # with thousands of stacked resources is a real thing, and an uncapped frame for one is measured # in megabytes. Clamped to 1..5000. MarketMaxListings=250 # Shard ruleset (https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/link/v3.md §5). One world.ruleset frame — expansion, which # systems are on, skill/stat caps, account and house limits, champion scroll rules — # emitted on every sidecar connect (and on [bridge reload), so the website's rules page # cannot drift from the server. Not a sweep: it changes only when you edit a .cfg. # # The frame is built from an explicit allowlist of keys in BridgeRuleset.cs. Server.cfg, # Staff.cfg, Email.cfg, DataPath.cfg, Bridge.cfg, Compiler.cfg, Reports.cfg and Client.cfg # are never read. RulesetEnabled=true # The one connection detail the bridge will publish, e.g. play.myshard.com,2593. Blank # (the default) omits it entirely. Server.cfg's Address/Listen/Port are NEVER published — # if you want a connect string on the site, put it here deliberately. PublicConnectAddress= # Include the save/restart schedule (AutoSave frequency, AutoRestart hour) in the frame. # Turn off if you would rather not advertise a predictable restart window. RulesetIncludeSchedule=true # Shown to a player when they run [link. The website page where they enter the code. LinkUrl=https://yoursite/link # Town-crier news pushed from the website. Caps are defense in depth on top of the # loopback trust boundary: a buggy or compromised sidecar still cannot flood the criers. TownCrierMaxLines=6 TownCrierMaxLineLength=200 TownCrierMaxActive=20 TownCrierMaxDurationSec=86400 # Town Cryer news gump. Website articles (news.add) become entries in the modern Town # Cryer News gump (TownCryerSystem.NewsEntries), separate from the scrolling-crier lines # above. The article title is also proclaimed by the criers (announce defaults on). Caps # are defense in depth on top of the loopback trust boundary. NewsMaxTitleLength=100 NewsMaxBodyLength=2000 NewsMaxExternal=20 NewsAnnounceDurationSec=300 # Admin write plane (staff moderation from the website). OFF by default: the whole # feature is opt-in per shard. When enabled, inbound admin.* commands (kick/ban/unban/ # broadcast) are honored. Authorization is enforced on the website; the shard trusts the # loopback socket and applies a hard floor below. AdminWriteEnabled=false # The one shard-side safety floor. An admin.* command refuses any target whose AccessLevel # is at or above this, so even a compromised sidecar can never touch the Owner. Values are # AccessLevel names (Player, VIP, Counselor, Decorator, Spawner, GameMaster, Seer, # Administrator, Developer, CoOwner, Owner). Default CoOwner => only Owner/CoOwners shielded. AdminAccessFloor=CoOwner # Defense-in-depth caps on admin.* payloads (mirroring the town-crier caps). AdminBroadcastMaxLength=300 AdminReasonMaxLength=400 # Clamp on a timed ban's duration, seconds. A ban with no/zero duration is indefinite. AdminBanMaxDurationSec=31536000 # Account provisioning (https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/link/PROTOCOL_2.md Part A). Which side may mint game accounts: # website — the website is the authority; pair with Accounts.AutoCreateAccounts=false # (else an in-game login of any new name still mints an account). # game — the game server is the authority; website account.create is refused. # hybrid — either side may create (the default). # The bridge governs only the account.create verb; the in-game first-login auto-create is # the core Accounts.AutoCreateAccounts setting, which you pair with the mode above. On boot # the bridge warns if the two contradict. An unrecognized value here falls back to 'game' # (the safest — no website creation). SignupMode=hybrid # Master switch for the account.create verb. Absent, it follows the mode (on unless # SignupMode=game). Set explicitly to force it on or off regardless of mode. AccountCreateEnabled=true # Fail closed if account.create omits a usable browser IP. The per-IP cap # (Accounts.AccountsPerIp) only means something if a missing/loopback IP is refused rather # than waved through. Turn off only for a deployment that deliberately does not cap website # signups by IP (MaxAccountsPerIP still applies in-game either way). RequireIpForCreate=true # Length caps on a website-supplied username / password, checked before the account is made. AccountNameMaxLength=16 AccountPasswordMaxLength=30 # The test scaffolding in tools/scaffolding/ reads its own flags from this file # (SeedOnStart, CensusOnStart, ProbeOnStart). They are absent here on purpose: # Config.Get returns the default of false when a key is missing, so a deployed # server never runs the scaffolding even if its .cs files are present.