Skip to content

About

The duel and share relay behind AKAddons RuneLite plugins: a Cloudflare Worker + Durable Object that replays every run, binds names to hashed accounts, and keeps the boards honest

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

18 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

akaddons-relay

The relay behind duels and shares in AKAddons RuneLite plugins (Trappy Chinchompa first). A Cloudflare Worker with one Durable Object. It knows no game: it holds who is online by display name, forwards challenges, broadcasts inside rooms, and passes player-to-player payloads.

Protocol (JSON over WebSocket at /ws)

Client to relay:

Message Fields Relay does
hello name, v, game, account (the client's SHA-256 of its RuneLite account hash under the akaddons: prefix), hidden, dev registers the socket under the name (newest wins), binds the name to the account (4001 if another account holds it, 4002 if banned), replies welcome then sync{duels, history} for the game
challenge to, game, data forwards {challenge, from, game, data}; replies sent or offline
accept / decline to, game, data forwards to the challenger
join / leave room membership; replies joined{members}; others get peer{name, online}
env room, body broadcasts {env, room, from, body} to every member, sender included
send to, game, kind, data forwards any plugin payload to a named player (share primitive)
who names[] replies who{online[]}

Errors come back as {t:"error", msg}. Messages over 64KB or more than 30 per second per socket are refused. Names compare case-insensitively with non-breaking spaces and underscores as spaces.

Plugins put a game id on everything and derive room ids themselves (tc1:<hash>); the relay never reads data or body.

Duels (v2): the relay keeps the book

An accept no longer just forwards: the relay creates a duel record with ten seeds and a five-minute idle deadline and pushes duel{record} to both players. From there:

Message Fields Relay does
report duel, game, score, flaps[], ticks replays the run against the seed (src/sim.js); a mismatch or an impossible time forfeits the reporter (CHEAT); otherwise stores it, refreshes the deadline, pushes the record to both; settles when both sides are in
start duel, game reveals that game's seed to you (one at a time); a report for an unstarted game is refused
touch duel "still playing": refreshes the deadline (send once a minute mid-game)
concede duel forfeit
compete / uncompete game, rules queue at these rules; the next arrival with another name is paired into a compete duel
board / ranks game, key verified compete scores / Elo ratings for a difficulty (e, n, h)
history game wins, losses, draws, per-opponent record, compete record, ratings, last 20 duels

Settlement: best round compares each side's highest score, average the sums; a tie adds a sudden-death game from the next seed (ten seeds, then a draw). At the deadline a side with its stretch complete wins by timeout; if neither finished, the side that never started loses; if nobody did anything, a challenge's acceptor loses (accept and vanish is a concede) and a compete pairing is no contest. A hello that names a game gets sync{duels, history} back. Only compete duels move Elo ratings and feed the score board, and only runs the relay itself replayed.

What the relay stores

Data Why For how long
display name who is online, who to challenge until the operator releases or forgets it
keyed HMAC of the account id (HASH_SALT) the name stays with one account same
duel records: rules, seeds, each run's score, flap trace and tick count replay and settlement finished duels drop after a day; no duel outlives 45 minutes
results, ratings, last 20 duels, compete record per difficulty the player's Record and the boards until forgotten; hidden keeps a player off every board
up to 5 recent addresses per player compete never pairs one machine with itself; abuse handling rolling, operator-only
telemetry counters, per-day buckets, an audit trail of security events and operator actions usage, defects, abuse 400 days of buckets, latest 200 audit entries

Nothing in a duel payload is read for anything but the duel. Bans are shadow bans and only the operator applies them. A player is erased with admin.mjs forget <name> on request.

Fairness (v2.1)

  • The relay replays every run (src/sim.js, a line-for-line port of the Java game with java.util.Random reproduced exactly; test/golden.json holds runs the Java game recorded and the port must score identically). A report that does not replay to its score forfeits the reporter as CHEAT. No client's verdict is ever taken.
  • Seeds are revealed one at a time with start{duel, game}, and a report is refused if it arrives sooner than its tick count could have been played, or claims more traps than the physics allow in that time.
  • Names are bound to accounts. hello must carry account (a SHA-256 the client makes of its RuneLite account hash, never the number itself); the relay stores a keyed HMAC of it (HASH_SALT) and refuses a different account on a claimed name (close 4001). Bans are shadow bans: a banned account or address connects and looks normal, but the compete queue never pairs it.
  • Farming limits. The compete queue never pairs the same account, the same IP, or two players paired within the hour; Elo gains against a repeat opponent in a day go full, half, quarter, then nothing; no account gains more than 100 rating a day; a sender may challenge one player three times in five minutes.
  • No duel outlives 45 minutes, touches or not; finished records are dropped after a day (the player records stay).
  • Developer mode. hello{dev: true} (RuneLite's --developer-mode) lets two dev clients pair from one machine. Everything they play is tagged dev and feeds a shadow board and shadow ratings (dev:<game>) that only dev clients are shown; the real board never sees them.
  • Privacy. hello{hidden: true} keeps a player off boards and rankings. The operator (admin{key, op, name} with ADMIN_KEY) can forget a player, release a name, ban/unban an account, and removeScore{game, key}.

Telemetry (node scripts/admin.mjs stats [--days N]), all-time totals and a bucket per day for 400 days (the readout prints the last N days, 14 by default, and a per-month roll-up):

  • Usage. Hellos, players seen and active today, session length, challenges sent / accepted / declined / offline, shares, duels by kind and rules, games by difficulty with a score histogram and average length, settlements by reason, sudden deaths, compete queues / pairings / leaves / wait time, board and record views.
  • Health. Oversized, unparseable and malformed messages, unknown types, rate-limit hits, refused starts and reports by reason, replaced sockets, sweeps, expired duels and records, storage failures, and gauges for connections, queue length and open duels.
  • Abuse. Hellos without an account, name-taken and banned-account attempts, bad admin keys, challenge floods, cheat forfeits split by replay mismatch vs physics cap, too-fast reports, pairings blocked by same account / machine / recent pair, Elo damping and daily caps hit, names claimed, accounts holding several names, a suspects list, and an audit trail (latest 200) of security events and operator actions. Security events are also written to the Worker's logs.
  • Recommendations, never automation. Players who abandon duels (accept and never play, let a timeout happen, start games and never report), fail replay, or flood challenges are listed with the evidence and their recent addresses. The operator decides: admin.mjs ban <name> (account) or admin.mjs banip <ip> (address); unban and unbanip reverse them. Bans are shadow bans: the player connects as usual and simply never finds an opponent. No ban is ever applied by the relay itself.

Addresses are used for pairing fairness and abuse control and are seen by the operator only.

Secrets (set once with npx wrangler secret put NAME): HASH_SALT (never rotate: bindings would all break) and ADMIN_KEY.

Run and deploy

npm test                    # node --test, no account needed
npx wrangler dev            # local, ws://localhost:8787/ws
npx wrangler login          # once, opens the browser
npx wrangler deploy         # -> https://akaddons-relay.<account>.workers.dev

Disclosure text for plugins

The plugin README carries the full text (Trappy Chinchompa's "Duels" section is the reference). The short form:

Duels connect to the AKAddons relay (akaddons-relay.workers.dev) while you are logged in and only after you turn duels on. It receives your display name, a hash of your RuneLite account id (made on your client and keyed again by the relay, so the name stays yours), the duels you play (scores and runs, kept a day) and sees your address like any server. It keeps your results, ratings and last 20 duels; hide-from-boards is a setting and the operator erases a player on request. The relay's source is this repository.

For reviewers

  • When it connects. Only with the plugin's Enable duels setting on (default off) and a player logged in. Off, the plugin opens no socket. The endpoint is a config value (relayUrl), so a reviewer can point the plugin at npx wrangler dev (ws://localhost:8787/ws) and watch every frame.
  • What the plugin sends. Exactly the message types in the two tables above: hello, challenge, accept, decline, start, report, touch, concede, compete, uncompete, board, ranks, history, send, who. Payloads carry duel rules, scores and flap traces of the minigame; nothing about the game client, the world, the bank or other players.
  • Outages. The client reconnects with backoff and the duel dock says "relay offline"; the minigame itself never needs the relay.
  • Abuse. 64KB frames, 30 messages a second per socket, one name per account, challenge floods refused, every run replayed server-side.
  • Operator. One person (the plugin author) holds ADMIN_KEY; the telemetry and ban tooling is in scripts/admin.mjs in this repository.

About

The duel and share relay behind AKAddons RuneLite plugins: a Cloudflare Worker + Durable Object that replays every run, binds names to hashed accounts, and keeps the boards honest

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages