Four pre-launch additions to make the game feel alive and shareable:
- Live win feed: server broadcasts 'win_feed' on every escape; client shows a
transient toast ('X escaped with the bag') so the room feels active.
- Public stats: GET /api/stats (10s-cached) returns games + escapes in the last
24h and total paid out; a status-area strip polls it for social proof.
- Share-a-win: game-over screen gets 'Share to X' + 'Copy link' buttons pointing
at the /verify page, which now renders OpenGraph/Twitter-card meta (brand OG
image per instance) so shared links unfurl with a preview.
- Land-on-action: a dismissible 'N games live now — watch' CTA appears on connect
when games are in progress, opening the existing spectator panel. Non-intrusive:
hidden while playing/spectating and never hijacks the canvas.
- Currency display is now network-aware: XMR on a test network shows sXMR
(stagenet) / tXMR (testnet) so it's clear the coins aren't mainnet; mainnet
XMR shows XMR, Wownero stays WOW. A fork running mainnet automatically shows
XMR. cryptoType (used for decimals/QR asset) is unchanged.
- Product name (Monerogue/Wownerogue) is derived from the currency and no
longer hardcoded: the chat "Connected to <name> server" message, payment
descriptions, and the verify page title all follow it.
- Smirk is forced off on any test network (stagenet/testnet) regardless of
SMIRK_ENABLED, since Smirk only works on mainnet — so forks don't have to
remember the env flag.
New shared helpers (gameNameFor, currencyLabelFor, isTestNetworkFor); exposed
via game_mode_info (currencyLabel, gameName). Full suite: 22 suites / 159 tests.
- Footer title and favicon now track the currency: "Monero Roguelike" + the
Monero logo favicon on the XMR instance, "Wownero Roguelike" + the existing
favicon on WOW (driven by UI.updateGameTitle).
- "Choose How to Play" modal: a header explains that paying (credits/entry)
puts high scores in the Hall of Champions while free play uses the Pleb
leaderboard, and each option now says which board it feeds — on both the
no-payout (mainnet) and payout (stagenet) variants.
- Leaderboard panel: two boards via tabs — 🏅 Hall of Champions (paid) and
🪙 Pleb (free) — default Champions; passes board= to /api/leaderboard and
retitles per board.
- PaymentUI: the Free button now shows whenever free play is offered (instance
free-only OR free-as-a-choice via freePlayEnabled), labelled "Play Free / no
payout / Pleb leaderboard" on paid instances, and emits the play_free event
(records game_mode=FREE). Replaces an emit of the dead 'enter_game' event.
- Server game_mode_info payload now includes freePlayEnabled so the client can
show the choice.
Full suite: 22 suites / 158 tests.
The client trusted the server to pre-escape chat content and rendered
user-supplied fields via .html()/innerHTML — one server regression away from
stored XSS, and the admin panel injected user-controlled payout addresses
into its authenticated DOM.
- Add a single shared escapeHtml helper (escapes all five HTML-significant
chars including both quotes); leaderboard now delegates to it.
- Escape chat username + message in onChatBroadcast/onChatHistory.
- Escape user-controlled fields in the admin panel (payout address, socket
id, player name, status) and make its local escapeHtml quote-safe so values
in title="..." can't break out.
- Add CSP plus X-Content-Type-Options, X-Frame-Options and Referrer-Policy
headers via middleware. connect-src 'self' keeps Socket.IO working;
removing 'unsafe-inline' (nonces) is tracked as a later cleanup.
Full suite: 13 suites / 119 tests.