1) Double invoice: the "Buy Selected Package" button carries BOTH id=buy-credits-btn AND
class=mode-option, so a click fired the generic .mode-option handler (no data attrs -> defaulted
to single_game -> a spurious 1-credit invoice) AND its own buy-credits handler (the real pack).
Guard the generic handler to ignore #buy-credits-btn and intentless clicks.
2) Smirk one-click payment never opened: /api/auth/smirk/status 403d, leaving _isLinked=false so
onPaymentCreated skipped the Smirk path. The BOLA-hardened query matched socket_id AND anon_token,
but socket_id is VOLATILE (changes every refresh) so it returned 0 rows after a reload. Match on
the anon_token alone — the actual ownership secret — same protection, no refresh fragility.
The single-game option now reads 'Single game · 1 credit' and notes it counts toward cosmetic unlocks (matching the backend: a direct entry advances total_credits_purchased). Fixes the direct payout copy to only show multipliers when payouts are actually enabled (was defaulting on).
- 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.