feat(bridge): publish points/loyalty leaderboards as points.board
Protocol 3.0 §7 (docs/link/v3.md). ServUO carries ~25 separate point currencies
— Queen's Loyalty, Void Pool, Casino, Clean Up Britannia, the nine city
loyalties, the Doom/Khaldun/Kotl treasure systems — every one a standing players
build over months, and none of them visible outside an in-game gump until now.
BridgePoints.cs
- A diff sweep shaped like BridgeHousing: ServerStarted arms the timer, a
sidecar connect clears the diff state so a fresh sidecar gets every board,
and each pass emits only the systems whose top N or participant count moved.
One ~600 B frame per system rather than one 12 KB frame, matching
champ.update / guild.update. No points.remove — the system set is fixed at
startup by PointsSystem.Configure, the same argument city.update makes.
- Selection is a single bounded pass into a fixed N-element array kept sorted
by insertion, NOT OrderByDescending().Take(N). PlayerTable is a plain List
and ten of the ~25 systems have AutoAdd = true, so they hold a row for every
character ever created: the naive version is ~25 full sorts on the Core
thread, which BRIDGE_PLUGIN_PLAN.md §1 measured as the second thing in the
bridge capable of blowing a frame budget.
- Which systems publish defaults to the shard's OWN answer — ShowOnLoyaltyGump
— rather than a list here that would drift; Bridge.cfg PointsSystems=
overrides it, and an unrecognised name is logged rather than dropped.
- Entries are written inline as {serial, name}, never via BridgeJson.Actor. A
board is the widest-audience surface the bridge has, so acct/webId
deliberately do not cross the wire; the site resolves serial → user from its
own link mirror.
char.profile gains a points block, the titles precedent from PROTOCOL_2.md §10.3
- Never uses PointsSystem.GetEntry/GetPoints: both MUTATE THE WORLD, since
GetEntry(create: false) still calls AddEntry when the system has AutoAdd
(PointsSystem.cs:207). Using them would have appended up to ten rows to the
points save file every time anyone opened a character sheet. Hand-rolled
read-only scan instead.
- rank is off by default (PointsProfileRank). A points lookup stops at the
character's own row; a rank must count every row that beats them, in every
system, on every profile build.
Verified by running it, not by reading it: the whole Scripts tree (6,207 files)
compiles clean against real ServUO 57.4 assemblies, and a boot against the local
shard with a 43,011-mobile world emitted five live boards. That run caught a bug
no fake shard could — ServUO's uncapped idiom is MaxPoints = double.MaxValue,
and (long) on it is an UNCHECKED conversion yielding long.MinValue, so the first
sweep published "maxPoints": -9223372036854775808 for three of the five boards.
Cap()/Score() now normalise anything unrepresentable, and maxPoints: 0 is the
documented "uncapped" value — which on a real shard is the common case, not an
edge case. Re-verified after the fix: 0 for the uncapped systems, 15000 and
10000 for the two that genuinely cap.
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -48,6 +48,41 @@ PresenceSweepSeconds=30
|
||||
# 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
|
||||
|
||||
# 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
|
||||
|
||||
Reference in New Issue
Block a user