Per the menu redesign: PLAY on top, then Loadout (Character + Style/render-picker), Explore
(Watch + Tavern), Progress & account (Shop + Anim + the hud-chip toolbar). Pure reorder +
group headers — every id/handler preserved, no grid change. Bottom-console (#3) is next.
The migration that makes the pack ladder pay off IN the dungeon, not just the tavern.
The SP game now draws via RK (Tiled/ASCII/Iso/3D + unlocked packs) instead of the
legacy ROT display.
- Adapter: sceneFromGameState now consumes the CLIENT state shape (sparse {y:{x:v}}
tile maps, not just server arrays) via maxDims(). Verified + unit-tested.
- Bug fix: RK.sceneFromGameState was never actually exposed (only RK.scene.*), so
matchClient + tavern spectator were silently no-op-ing. Aliased onto RK.
- Bridge (spGameRenderer.js, RK.SPGame): mounts an RK renderer in a dedicated host
inside #game-display, toggles the legacy ROT canvases, zoom-enabled. Game._draw
GameScreen prefers it and FALLS BACK to RenderEngine on any failure, so the live
game can never blank. ROT still draws the splash/win/lose text screens.
- Picker (spRenderPicker.js): homepage mode + pack selector, gated by entitlements
(free = Tiled/Original + ASCII; ladder unlocks Iso/3D/packs), rebuilds on identity.
- index.html loads the render-kit renderers + the bridge/picker.
Bundled with the catalog ladder (migration 028): free Original bare pack, ladder
1/5/10/20/40/50. Deploying makes the free tavern bare (by design) AND the dungeon
render-kit-ed. v1 renders the whole explored level + zoom (player-centered camera is
the next polish). Suite 62/435 green; adapter smoke-verified.
- Splash legend (screenManager.js): every row now uses a uniform 2-cell icon slot at
a shared left edge and a single fixed text column, so the four icons + descriptions
line up regardless of glyph width (was: per-row span math + leading-space hacks →
ragged columns, player sprite offset).
- Character creator was already on the homepage but read like an admin control: the
button showed the DEFAULT character label "Operator". Renamed the default to
"Adventurer" (charCustomize.js), switched the icon 🧍→🎭, and gave it a clear
"Create / customize your character" tooltip so it announces itself.
The engine behind 'add more 2D tilesets / iso packs / 3D skins as products'. A
pack is a self-contained render environment for a projection (topdown/iso/3d);
because scenes are kind-based, the SAME pack renders both town and dungeon
(interchangeable), and each maps to a catalog id (monetizable, entitlement-gated).
- packRegistry.js: registerPack / packsForProjection / unlockedPacks (canUsePack)
/ activePackId (saved-if-unlocked else first unlocked) / setActivePack /
activePackAssets. Per-projection active-pack selection, persisted. In-memory
store fallback so it's testable under Node.
- assetPacks: registers the 3 built-ins (roguelike-interior topdown, iso-dungeon,
kenney-3d-characters) + activeIsoAssets()/activeThreeAssets() resolvers; RK.theme()
now defaults to the active topdown pack.
- isoRenderer + avatarVisuals (iso/3d character lookup) read the ACTIVE pack.
- packRegistry loaded before assetPacks in tavern + game.
Backward-compatible: one pack per projection resolves to today's defaults — nothing
changes visually. Adding a pack now = registerPack(...) + assets + a catalog row.
Tests: +5 (register/list, unlock filter, active/saved/fallback, refuse locked).
Suite 58 suites / 419 tests green.
Reframe the two 'Direct Payment / Credits Mode' cards as one 'Credits' model — 1 credit = 1 run, bought singly at base price or in discounted bundles. Adds the drop-in-timing note (jump in now or next block; block timing is memoryless so the expected wait is the same). Reward line is now config-driven: shows crypto multipliers only when payouts are actually enabled, otherwise the Hall-of-Champions framing — fixes the help advertising 2x/3x payouts on a PAYOUTS_ENABLED=false (prestige) deployment. Header renamed to 'Credits & Entry'.
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.
On phones the primary buttons now read START / WATCH / SHOP (short labels via
spans toggled by media query) with tighter padding so they fit a single row.
The on-screen D-pad is smaller (44px) and more transparent so it covers less
of the chat/game underneath.
The layout was already partly responsive (max-width:100vw, min(480px,90vw)
modals, canvas max-width:100%, and the renderer centres a fixed viewport on the
player) but had no viewport meta, so none of it engaged on phones.
- Add the viewport (+charset) meta — unlocks the existing responsive rules.
- Mobile media query (<=820px): stack the 3-column grid into one column
(header/status/game/chat/input/footer), shrink fonts, and cap the fixed-width
play/history/leaderboard modals to 94vw so they fit instead of overflowing.
- On-screen D-pad + swipe-to-move (new touchControls.js), both emitting the
same player_move {direction} event the keyboard uses — no game-logic change.
Shown on small screens or any touch device (body.touch).
- Remove the leftover "GAME AREA" debug overlay.
- 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.
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.
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