- 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.
- Remove the duplicate _gameMode declaration in the client SocketHandlers
object literal — it was declared twice, so the later `null` silently
shadowed the earlier `'free'`. Now declared once with the effective value.
- Remove the dead SocketHandlers._appendMessage calls in smirkAuth.js; that
method never existed, so the guard always fell through to the working
.text() append. Keep the safe append.
- Gate broadcastManager's high-volume per-event logs (user count, chat message
incl. message/socketId PII, game update, game start) behind debug mode;
previously they logged on every event regardless of NODE_ENV.
Full suite: 21 suites / 155 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.
The anonymous session token is a bearer credential that was sent in the
Socket.IO handshake query string, where it leaks into proxy/access/referer
logs and is easily observed. Anyone holding it becomes that user (credits,
payout address, pending payments).
- Client now sends the token in the handshake `auth` payload instead of the
query string. Server reads `auth` first and falls back to the query string
for backward compatibility during rollout.
- The token is rotated on every successful resume, so a leaked/observed
token is invalidated the next time the legitimate owner reconnects. The
client already persists the token returned in `session_resumed`.
Adds a security test asserting resume returns a new token and the old token
no longer resumes. Full suite: 12 suites / 116 tests.
- Fix wallet balance always showing 'Disconnected' in admin dashboard
- Always attempt balance fetch even if isHealthy=false
- Show error state with message instead of generic 'Disconnected'
- Add POST /api/admin/alerts/test-email endpoint to verify Resend config
- Move alertService to module scope for endpoint access
- Replace asterisk (*) sparkles with floor tiles + bright golden color
- Remove skull emoji (☠) and death particles (% *) - use red flash effect
- Fix monster shadows causing black boxes - use red floor glow instead
- Replace 'ESCAPED!' emoji text with character-by-character transparent rendering
- Use GameTiles.getPlayerTile() instead of hardcoded '@'
- Use GameTiles.getMonsterTile() instead of hardcoded '~'
All tiles now render with proper transparency on floor tile base.
New dedicated animation while waiting for payment:
- Player runs toward BIG pulsating golden treasure
- Player always grabs the treasure (with dramatic pickup moment)
- Monster pack (4-6 monsters) spawns from multiple directions
- 60% chance: player escapes with treasure to the exit
- 40% chance: player gets devoured by monster pack (skull/death effect)
Animation features:
- Massive golden aura around treasure with sparkle ring
- Player glows gold when carrying treasure
- Death particles expand outward on devour
- Monsters converge from edges (left, right, top, bottom)
Existing 'Awaiting next block' animation preserved unchanged.
- Fix game start failure in direct payment mode when payment confirms
before mempool detection (add player to queue if not already there)
- Fix debug logging in production: correct indentation in debugRegisteredUsers,
swap console_logging config (false in prod, true in debug)
- Add close button to QR code container
- Auto-hide QR on payment detected, confirmed, and game start
Payout retry system:
- Add migration 007 with retry_count, last_error, last_retry_at columns
- Payouts now retry up to PAYOUT_MAX_RETRIES times before permanent failure
- Each failure logs the attempt number and error message
- Console shows ⚠️ for retryable failures, 💀 for permanent failures
- Added SQL examples in .env.example for querying/resetting failed payouts
HOSTED_BY feature:
- Add HOSTED_BY and HOSTED_BY_NAME env vars
- /api/game-modes endpoint returns hostedBy info
- Footer displays 'Hosted by: <link>' when configured
Also improved .env.example:
- Better documentation for credit packages with reasonable defaults
- Clearer explanation of retry behavior